const DEFAULT_MAX_ACTIONS = 20; const DEFAULT_MAX_TEXT_LENGTH = 2000; export const availableProviderActions = [ "say", "startTyping", "stopTyping", "moveTo", "teleport", "setStatus", "emitEvent", "interruptAudioStream", ]; export function normalizeProviderActions(value, options = {}) { const maxActions = options.maxActions ?? DEFAULT_MAX_ACTIONS; if (Array.isArray(value)) { const actions = []; for (const entry of value.slice(0, maxActions)) { const action = normalizeProviderAction(entry, options); if (action) { actions.push(action); } } return actions; } if (isRecord(value)) { if (Array.isArray(value.actions)) { return normalizeProviderActions(value.actions, options); } const action = normalizeProviderAction(value, options); return action ? [action] : []; } return []; } export function actionsFromProviderResponse(value, options = {}) { const normalized = normalizeProviderActions(value, options); if (normalized.length > 0) { return normalized; } const responseText = extractResponseText(value); if (!responseText) { return []; } const actionsFromText = extractActionsFromText(responseText, options); if (actionsFromText.length > 0) { return actionsFromText; } return [ { type: "say", channel: options.defaultChannel ?? "bubble", text: truncateText(responseText, options.maxTextLength ?? DEFAULT_MAX_TEXT_LENGTH), }, ]; } export function extractActionsFromText(text, options = {}) { const candidates = jsonCandidatesFromText(text); for (const candidate of candidates) { try { const parsed = JSON.parse(candidate); const actions = normalizeProviderActions(parsed, options); if (actions.length > 0) { return actions; } } catch (_error) { // Try the next candidate. } } return []; } export function shouldRespondToChat(observation, names, respondToAllBubbleMessages) { if (observation.type !== "chatMessage" || observation.scope !== "bubble") { return false; } if (respondToAllBubbleMessages) { return true; } const normalized = observation.message.toLowerCase(); return names .filter((name) => name.trim().length > 0) .some((name) => normalized.includes(name.toLowerCase()) || normalized.includes(`@${name.toLowerCase()}`)); } function normalizeProviderAction(value, options) { if (!isRecord(value) || typeof value.type !== "string") { return undefined; } const maxTextLength = options.maxTextLength ?? DEFAULT_MAX_TEXT_LENGTH; switch (value.type) { case "say": { if (typeof value.text !== "string" || value.text.trim().length === 0) { return undefined; } const channel = value.channel === "local" || value.channel === "bubble" ? value.channel : options.defaultChannel ?? "bubble"; const action = { type: "say", channel, text: truncateText(value.text, maxTextLength), }; if (typeof value.author === "string") { action.author = truncateText(value.author, 100); } return action; } case "startTyping": { const channel = value.channel === "local" || value.channel === "bubble" ? value.channel : options.defaultChannel ?? "bubble"; const action = { type: "startTyping", channel, }; if (typeof value.author === "string") { action.author = truncateText(value.author, 100); } return action; } case "stopTyping": { const channel = value.channel === "local" || value.channel === "bubble" ? value.channel : options.defaultChannel ?? "bubble"; const action = { type: "stopTyping", channel, }; if (typeof value.author === "string") { action.author = truncateText(value.author, 100); } return action; } case "moveTo": { if (!isFiniteNumber(value.x) || !isFiniteNumber(value.y)) { return undefined; } const action = { type: "moveTo", x: value.x, y: value.y, }; if (isFiniteNumber(value.speed) && value.speed > 0) { action.speed = value.speed; } return action; } case "teleport": { if (!isFiniteNumber(value.x) || !isFiniteNumber(value.y)) { return undefined; } return { type: "teleport", x: value.x, y: value.y }; } case "setStatus": { if (value.status !== "ONLINE" && value.status !== "BUSY" && value.status !== "DO_NOT_DISTURB" && value.status !== "BACK_IN_A_MOMENT") { return undefined; } return { type: "setStatus", status: value.status }; } case "emitEvent": { if (typeof value.name !== "string" || value.name.trim().length === 0) { return undefined; } return { type: "emitEvent", name: truncateText(value.name, 200), data: toJsonValue(value.data), }; } case "speakPcm": { if (!isFiniteNumber(value.sampleRate) || typeof value.pcm16Base64 !== "string" || value.pcm16Base64.length === 0) { return undefined; } return { type: "speakPcm", sampleRate: value.sampleRate, pcm16Base64: value.pcm16Base64, }; } case "closeAudioStream": { return { type: "closeAudioStream" }; } case "interruptAudioStream": { return { type: "interruptAudioStream" }; } default: return undefined; } } function extractResponseText(value) { if (typeof value === "string") { return value.trim() || undefined; } if (!isRecord(value)) { return undefined; } for (const key of ["text", "reply", "message", "content", "response", "output", "result"]) { const entry = value[key]; if (typeof entry === "string" && entry.trim().length > 0) { return entry.trim(); } const nested = extractResponseText(entry); if (nested) { return nested; } } return undefined; } function jsonCandidatesFromText(text) { const trimmed = text.trim(); const candidates = [trimmed]; for (const match of trimmed.matchAll(/```(?:json)?\s*([\s\S]*?)```/gi)) { if (match[1]) { candidates.push(match[1].trim()); } } const objectStart = trimmed.indexOf("{"); const objectEnd = trimmed.lastIndexOf("}"); if (objectStart >= 0 && objectEnd > objectStart) { candidates.push(trimmed.slice(objectStart, objectEnd + 1)); } const arrayStart = trimmed.indexOf("["); const arrayEnd = trimmed.lastIndexOf("]"); if (arrayStart >= 0 && arrayEnd > arrayStart) { candidates.push(trimmed.slice(arrayStart, arrayEnd + 1)); } return Array.from(new Set(candidates)); } function truncateText(value, maxLength) { if (value.length <= maxLength) { return value; } return `${value.slice(0, Math.max(0, maxLength - 3))}...`; } function toJsonValue(value) { if (value === undefined) { return null; } return JSON.parse(JSON.stringify(value)); } function isRecord(value) { return typeof value === "object" && value !== null && !Array.isArray(value); } function isFiniteNumber(value) { return typeof value === "number" && Number.isFinite(value); } //# sourceMappingURL=agentProvider.js.map