@stackbone/cli 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +163 -0
- package/main.js +20538 -8590
- package/migrations/0094_rag_document_versions.sql +61 -0
- package/migrations/0095_rag_document_version_counters.sql +22 -0
- package/migrations/0096_trigger_subscriptions_target_kind.sql +29 -0
- package/migrations/0097_voice_settings.sql +34 -0
- package/migrations/0098_model_selections_provider_id.sql +23 -0
- package/migrations/0099_memory.sql +35 -0
- package/migrations/0100_memory_call_run.sql +9 -0
- package/migrations/0101_memory_call_run_no_fk.sql +5 -0
- package/migrations/0102_voice_recordings.sql +31 -0
- package/migrations/0103_rag_collection_embeddings.sql +21 -0
- package/migrations/0104_rag_collection_rebuilds.sql +33 -0
- package/migrations/0105_rag_rebuild_manifest_size.sql +15 -0
- package/migrations/0106_memory_agent_scope.sql +16 -0
- package/migrations/0107_memory_settings.sql +7 -0
- package/migrations/0108_memory_identity_bindings.sql +13 -0
- package/migrations/0109_trigger_subscriptions_conversation_key.sql +17 -0
- package/migrations/0110_concurrency_limits.sql +52 -0
- package/migrations/0111_concurrency_slots_now_idx.sql +21 -0
- package/migrations/0112_concurrency_max_hold_override.sql +15 -0
- package/migrations/0113_trigger_subscriptions_debounce_ms.sql +13 -0
- package/package.json +2 -1
- package/runtime-deps/@stackbone/workflow-world/index.js +223 -85
- package/stackbone-cli-0.7.0.tgz +0 -0
- package/voice-worker/main.js +2150 -0
- package/voice-worker/main.js.map +7 -0
- package/voice-worker/package.json +18 -0
- package/stackbone-cli-0.6.2.tgz +0 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../libs/runtime/voice-worker/src/main.ts", "../../../../libs/runtime/voice-worker/src/thinking-sound.ts", "../../../../libs/runtime/voice-worker/src/vad.ts", "../../../../libs/runtime/voice-worker/src/worker.ts", "../../../../libs/shared/validators/src/lib/common/json-object.ts", "../../../../libs/shared/validators/src/lib/common/page.ts", "../../../../libs/shared/validators/src/lib/agents/runs.ts", "../../../../libs/shared/validators/src/lib/common/system-entries.ts", "../../../../libs/shared/validators/src/lib/studio/studio-model-provider.ts", "../../../../libs/shared/validators/src/lib/studio/studio-voice.ts", "../../../../libs/shared/validators/src/lib/studio/memory-identities.ts", "../../../../libs/shared/validators/src/lib/studio/studio-rag.ts", "../../../../libs/runtime/voice-worker/src/box-llm.ts", "../../../../libs/shared/llm-wire/src/lib/sse/read-sse-data.ts", "../../../../libs/runtime/voice-worker/src/box-wire.ts", "../../../../libs/runtime/voice-worker/src/heard.ts", "../../../../libs/runtime/voice-worker/src/dispatch.ts", "../../../../libs/runtime/voice-worker/src/hang-up.ts", "../../../../libs/runtime/voice-worker/src/session-options.ts", "../../../../libs/runtime/voice-worker/src/number-text.ts", "../../../../libs/runtime/voice-worker/src/phone-voice.ts", "../../../../libs/runtime/voice-worker/src/telemetry/call-traces.ts"],
|
|
4
|
+
"sourcesContent": ["// The voice worker process (FDE-62 S4).\n//\n// nx run voice-worker:dev # node <bundle>/main.js dev\n//\n// Run from the `bundle` target's output, not under `tsx`: the bundle inlines\n// `@stackbone/validators`, a CommonJS package whose named exports an ES module\n// cannot import from its TypeScript source.\n//\n// Registers on LiveKit as `stackbone-voice`, or as `STACKBONE_VOICE_AGENT_NAME` when\n// `stackbone dev` starts it under its process's own name (S14, D36), so it only joins\n// rooms whose token dispatches it by name (the box's `POST /api/voice/rooms`). Each job\n// runs `runVoiceJob` with the real LiveKit pieces. LiveKit reads `LIVEKIT_URL`,\n// `LIVEKIT_API_KEY` and `LIVEKIT_API_SECRET` itself, for registration and for the\n// Inference STT, TTS and turn detector. `stackbone dev` also sets\n// `STACKBONE_VOICE_WORKER_PORT` for the health server (`resolveWorkerHealthServer`). The\n// worker reads no other variable and builds no model: each job's dispatch names the box\n// and carries the call token, and says whether the call's spans go to the box (S11).\nimport { fileURLToPath } from 'node:url';\nimport {\n type JobContext,\n type JobProcess,\n ServerOptions,\n cli,\n defineAgent,\n inference,\n telemetry,\n voice,\n} from '@livekit/agents';\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';\nimport { startThinkingCue } from './thinking-sound';\nimport { loadVad } from './vad';\nimport {\n resolveWorkerAgentName,\n resolveWorkerHealthServer,\n runVoiceJob,\n type VoiceJobDeps,\n} from './worker';\n\ninterface ProcessData {\n vad: Awaited<ReturnType<typeof loadVad>>;\n}\n\n/** `CompressionAlgorithm` of `@opentelemetry/otlp-exporter-base`, read off the exporter. */\ntype ExportCompression = NonNullable<\n NonNullable<ConstructorParameters<typeof OTLPTraceExporter>[0]>['compression']\n>;\n\n/** The real LiveKit pieces, built per job. */\nconst liveDeps = (): VoiceJobDeps<voice.AgentSession> => ({\n createSession: (options) => new voice.AgentSession(options),\n createStt: (options) => new inference.STT(options),\n createTts: (options) => new inference.TTS(options),\n createTurnDetector: (options) => new inference.TurnDetector(options),\n createCue: startThinkingCue,\n // OTLP/HTTP JSON, uncompressed: the encoding the box relay takes and forwards as is.\n // `compression` is set so an `OTEL_EXPORTER_OTLP_COMPRESSION=gzip` in the worker's\n // environment cannot turn every export into a body the relay refuses.\n createExporter: ({ url, headers }) =>\n new OTLPTraceExporter({\n url,\n headers: { ...headers },\n compression: 'none' as ExportCompression,\n }),\n setTracerProvider: telemetry.setTracerProvider,\n});\n\nexport default defineAgent<ProcessData>({\n prewarm: async (proc: JobProcess<ProcessData>) => {\n proc.userData.vad = await loadVad();\n },\n entry: (ctx: JobContext<ProcessData>) => runVoiceJob(ctx, liveDeps()),\n});\n\n// The job processes import this file too; only the entry process starts the CLI.\nif (process.argv[1] === fileURLToPath(import.meta.url)) {\n cli.runApp(\n new ServerOptions({\n agent: fileURLToPath(import.meta.url),\n agentName: resolveWorkerAgentName(process.env),\n ...resolveWorkerHealthServer(process.env),\n }),\n );\n}\n", "// A soft \"working on it\" cue played while a box tool call runs: two short sine\n// notes with a raised-cosine envelope, then a pause, looped. Generated here instead\n// of shipped as a file, so there is nothing to decode and nothing to license.\n//\n// LiveKit's BackgroundAudioPlayer pushes frames into a 48 kHz mono AudioSource\n// with no resampling, so the frames are built at that rate.\nimport { voice } from '@livekit/agents';\nimport { AudioFrame, type Room } from '@livekit/rtc-node';\nimport type { ThinkingCue } from './worker';\n\nconst SAMPLE_RATE = 48_000;\nconst FRAME_MS = 20;\nconst SAMPLES_PER_FRAME = (SAMPLE_RATE * FRAME_MS) / 1000;\nconst VOLUME = 0.12;\n\n/** One loop: [frequency in Hz, or 0 for silence, duration in ms]. */\nconst PATTERN: ReadonlyArray<readonly [number, number]> = [\n [660, 140],\n [0, 60],\n [880, 160],\n [0, 900],\n];\n\nconst renderPattern = (): Int16Array => {\n const total = PATTERN.reduce((n, [, ms]) => n + (SAMPLE_RATE * ms) / 1000, 0);\n const pcm = new Int16Array(total);\n let offset = 0;\n for (const [hz, ms] of PATTERN) {\n const length = (SAMPLE_RATE * ms) / 1000;\n for (let i = 0; hz > 0 && i < length; i++) {\n // Raised cosine: no clicks at either end of the note.\n const envelope = 0.5 - 0.5 * Math.cos((2 * Math.PI * i) / length);\n const sample = Math.sin((2 * Math.PI * hz * i) / SAMPLE_RATE) * envelope * VOLUME;\n pcm[offset + i] = Math.round(sample * 32767);\n }\n offset += length;\n }\n return pcm;\n};\n\nconst LOOP = renderPattern();\n\n/** Endless frames of the cue; the player stops pulling when its handle is stopped. */\nexport async function* thinkingCue(): AsyncGenerator<AudioFrame> {\n for (;;) {\n for (let start = 0; start + SAMPLES_PER_FRAME <= LOOP.length; start += SAMPLES_PER_FRAME) {\n yield new AudioFrame(\n LOOP.slice(start, start + SAMPLES_PER_FRAME),\n SAMPLE_RATE,\n 1,\n SAMPLES_PER_FRAME,\n );\n }\n }\n}\n\n/**\n * The cue of one call: a background audio track published in the room, playing\n * `thinkingCue()` from `start()` to `stop()`. Resolves once the track is up.\n */\nexport const startThinkingCue = async ({\n room,\n session,\n}: {\n room: Room;\n session: voice.AgentSession;\n}): Promise<ThinkingCue> => {\n const player = new voice.BackgroundAudioPlayer();\n await player.start({ room, agentSession: session });\n let playing: voice.PlayHandle | undefined;\n return {\n start: () => {\n playing ??= player.play(thinkingCue());\n },\n stop: () => {\n playing?.stop();\n playing = undefined;\n },\n };\n};\n", "// Load the Silero VAD so it also works from a deep Windows checkout.\n//\n// `silero.VAD.load()` opens `silero_vad.onnx` next to its own dist file. Under\n// `.claude/worktrees/\u2026` plus pnpm's virtual store that path is ~290 characters,\n// and onnxruntime-node only opens a path over 260 on Windows in the extended form\n// `\\\\?\\D:\\\u2026`: without it, every call's job process died on \"File doesn't exist\"\n// and the bot sat in the meeting with no agent (2026-10-04). So on Windows, with a\n// long path, this builds the session itself with the prefix and hands it to the\n// public `VAD` constructor; everywhere else it is plain `VAD.load()`.\nimport { createRequire } from 'node:module';\nimport { dirname, join } from 'node:path';\nimport * as silero from '@livekit/agents-plugin-silero';\n\nconst WINDOWS_MAX_PATH = 260;\n\n/** The plugin's defaults (`defaultVADOptions` in its dist), which it does not export. */\nconst SILERO_DEFAULTS = {\n minSpeechDuration: 50,\n minSilenceDuration: 550,\n prefixPaddingDuration: 500,\n maxBufferedSpeech: 60_000,\n activationThreshold: 0.5,\n sampleRate: 16_000 as const,\n forceCPU: true,\n};\n\ntype SileroSession = ConstructorParameters<typeof silero.VAD>[0];\n/** The one call this file makes into onnxruntime-node, typed here because it is not our dependency. */\ninterface OnnxRuntime {\n InferenceSession: { create(path: string, options: object): Promise<SileroSession> };\n}\n\n/**\n * The Silero VAD, typed as one: `VAD.load()` is declared to return the base `VAD`\n * but builds a `silero.VAD`, whose `updateOptions` each call tunes (`session-options.ts`).\n */\nexport const loadVad = async (): Promise<silero.VAD> => {\n const require = createRequire(import.meta.url);\n const pluginEntry = require.resolve('@livekit/agents-plugin-silero');\n const model = join(dirname(pluginEntry), 'silero_vad.onnx');\n if (process.platform !== 'win32' || model.length < WINDOWS_MAX_PATH) {\n return (await silero.VAD.load()) as silero.VAD;\n }\n // onnxruntime-node is the plugin's dependency, not ours: resolve it from there.\n const { InferenceSession } = createRequire(pluginEntry)('onnxruntime-node') as OnnxRuntime;\n const session = await InferenceSession.create(`\\\\\\\\?\\\\${model}`, {\n interOpNumThreads: 1,\n intraOpNumThreads: 1,\n executionMode: 'sequential',\n executionProviders: [{ name: 'cpu' }],\n });\n return new silero.VAD(session, SILERO_DEFAULTS);\n};\n", "// One job of the voice worker: answer one call by sending every turn to the box.\n//\n// The job reads its dispatch (`dispatch.ts`) and refuses one it cannot read, joins\n// the room, waits for the caller, then runs a LiveKit `AgentSession` built from the\n// dispatch's voice settings whose LLM is `BoxLLM`: the deep agent in the box\n// answers every turn (D1). Around it the worker only greets, plays the thinking\n// cue while a box tool call runs, speaks a fixed phrase when a turn fails, ends\n// the call after a fixed phrase when the box pauses the run for a human decision\n// (D16), three turns in a row fail (D17) or the box refuses the call token (401,\n// which no later turn can fix), and ends it after the agent's own\n// goodbye when the agent calls `hang_up` (`hang-up.ts`). Ending a call turns the\n// caller's audio off first (D28): the caller can no longer pause or cut what the\n// agent still says.\n// The LiveKit pieces come in through `VoiceJobDeps` (`main.ts` builds the real\n// ones), because LiveKit Inference reads its credentials when constructed and the\n// specs run with none. `session-options.ts` maps the dispatch's settings onto them.\n// The box comes from the dispatch alone (S10): every request of the call goes to\n// its `boxUrl` with its `callToken` as the bearer, a token the box signed for this\n// agent and this session key. The worker holds no box URL and no box key. The call's\n// spans go to the same box, when the dispatch asks for them (S11, `telemetry/call-traces.ts`).\nimport { type JobContext, type llm, log, voice } from '@livekit/agents';\nimport { BackgroundVoiceCancellation } from '@livekit/noise-cancellation-node';\nimport { ParticipantKind, type NoiseCancellationOptions } from '@livekit/rtc-node';\nimport {\n voiceCallCompleteSchema,\n VOICE_WORKER_AGENT_NAME,\n VOICE_WORKER_AGENT_NAME_ENV,\n VOICE_WORKER_PORT_ENV,\n} from '@stackbone/validators';\nimport { BoxLLM } from './box-llm';\nimport { BoxTurnError, voiceCallRequest } from './box-wire';\nimport { readDispatch } from './dispatch';\nimport { createHangUp, HANG_UP_TOOL, type IdleSource, untilGoodbyePlayed } from './hang-up';\nimport { type VoicePieceFactories, type VoicePieces, voiceSessionOptions } from './session-options';\nimport { type CallTracesDeps, installCallTraces } from './telemetry/call-traces';\n\n/**\n * The name the worker registers under: `STACKBONE_VOICE_AGENT_NAME`, which `stackbone dev`\n * sets to the per-process name its box dispatches (FDE-62 S14, D36), else the name a\n * deployed box's room token dispatches.\n * A blank value falls back too: LiveKit hands every room to a worker registered with no\n * name (automatic dispatch).\n */\nexport const resolveWorkerAgentName = (env: Readonly<Record<string, string | undefined>>): string =>\n env[VOICE_WORKER_AGENT_NAME_ENV]?.trim() || VOICE_WORKER_AGENT_NAME;\n\n/**\n * Where the worker's health server listens. `stackbone dev` sets\n * `STACKBONE_VOICE_WORKER_PORT` to a free port it picked: in `@livekit/agents` 1.9.0 the\n * `start` mode puts that server on 8081 and prewarms up to four job processes, and\n * `ServerOptions` reads a 0 for either as unset, so explicit values are the only way to\n * keep two sessions on one machine apart. Unset, the image defaults stay.\n */\nexport const resolveWorkerHealthServer = (\n env: Readonly<Record<string, string | undefined>>,\n): { host?: string; port?: number; numIdleProcesses?: number } => {\n const port = Number(env[VOICE_WORKER_PORT_ENV]);\n if (!Number.isInteger(port) || port <= 0 || port > 65_535) return {};\n return { host: '127.0.0.1', port, numIdleProcesses: 1 };\n};\n\n/** A fixed phrase the worker says, in Spanish and in English. */\ntype CallPhrase = Readonly<{ es: string; en: string }>;\n\n/** What the caller hears when a turn fails. */\nexport const TURN_FALLBACK_PHRASE: CallPhrase = Object.freeze({\n es: 'Perdona, no he podido responderte. \u00BFMe lo repites?',\n en: \"Sorry, I couldn't answer that. Could you say it again?\",\n});\n\n/** What the caller hears before the call ends on a run paused for a human decision (D16). */\nexport const PAUSED_RUN_PHRASE: CallPhrase = Object.freeze({\n es: 'Esto lo tiene que revisar una persona. Te contactaremos en cuanto est\u00E9. Hasta luego.',\n en: \"Someone needs to review this. We'll contact you once it's done. Goodbye.\",\n});\n\n/** What the caller hears before the call ends on the third failed turn in a row (D17). */\nexport const CALL_CLOSING_PHRASE: CallPhrase = Object.freeze({\n es: 'Lo siento, ahora mismo no puedo atenderte. Por favor, vuelve a llamar m\u00E1s tarde.',\n en: \"Sorry, I can't help you right now. Please call again later.\",\n});\n\n/** Failed turns in a row that end the call (D17). */\nconst MAX_FAILED_TURNS_IN_A_ROW = 3;\n\n/** The part of LiveKit's `JobContext` a voice job uses. */\nexport interface VoiceJobContext {\n readonly job: { readonly id: string; readonly metadata?: string };\n readonly room: JobContext['room'];\n /** The VAD comes from the process prewarm (`loadVad`). */\n readonly proc: { readonly userData: { readonly vad: VoicePieces['vad'] } };\n connect(): Promise<void>;\n waitForParticipant(): Promise<unknown>;\n /** Disconnects every participant, the caller included; `shutdown` only takes the agent out. */\n deleteRoom(): Promise<void>;\n shutdown(reason?: string): void;\n /** Awaited before the job process exits: a traced call ships its last spans there. */\n addShutdownCallback(callback: () => Promise<void>): void;\n}\n\n/** The part of LiveKit's `SpeechHandle` a voice job uses. */\nexport interface SpeechPlayout {\n waitForPlayout(): Promise<void>;\n}\n\n/** The part of LiveKit's `AgentSession` a voice job uses. */\nexport interface VoiceSession extends IdleSource {\n close(): Promise<void>;\n on(event: voice.AgentSessionEventTypes.Close, listener: () => void): unknown;\n readonly history: llm.ChatContext;\n start(options: {\n agent: voice.Agent;\n room: VoiceJobContext['room'];\n inputOptions: { noiseCancellation?: NoiseCancellationOptions };\n }): Promise<void>;\n say(text: string, options?: { allowInterruptions?: boolean }): SpeechPlayout;\n /** The caller's audio into the session (LiveKit's `AgentInput`, `voice/io.ts`). */\n readonly input: { setAudioEnabled(enable: boolean): void };\n}\n\n/** The sound played while a box tool call runs. */\nexport interface ThinkingCue {\n start(): void;\n stop(): void;\n}\n\nexport interface VoiceJobDeps<S extends VoiceSession = VoiceSession>\n extends VoicePieceFactories,\n CallTracesDeps {\n createSession(options: voice.AgentSessionOptions): S;\n createCue(target: { room: VoiceJobContext['room']; session: S }): Promise<ThinkingCue>;\n}\n\n/** A fixed phrase in the call's language: its primary subtag, English for any other. */\nconst inCallLanguage = (phrase: CallPhrase, language: string): string =>\n language.split('-')[0]?.toLowerCase() === 'es' ? phrase.es : phrase.en;\n\n/**\n * The thinking cue across a call's box tool calls: one cue for however many are\n * open, from the first start to the last end. BoxLLM ends every call it reported\n * started, also on a barge-in or a failed turn, so the cue never outlives a turn.\n */\nconst toolCallCue = () => {\n let cue: ThinkingCue | undefined;\n let open = 0;\n return {\n // A call can open before the cue's track is up.\n attach: (ready: ThinkingCue): void => {\n cue = ready;\n if (open > 0) cue.start();\n },\n onToolCall: (phase: 'start' | 'end'): void => {\n if (phase === 'start') {\n open += 1;\n if (open === 1) cue?.start();\n } else if (open > 0) {\n open -= 1;\n if (open === 0) cue?.stop();\n }\n },\n };\n};\n\nconst messageOf = (err: unknown): string => (err instanceof Error ? err.message : String(err));\n\n/**\n * LiveKit's background voice cancellation on the caller's track, so the VAD, the STT\n * and the turn detector hear the caller and not the room: noise and other voices\n * (a TV, someone beside them) otherwise start false turns and false barge-ins. It runs\n * in the worker, licensed through LiveKit Cloud.\n * A courtesy: on a platform without the native filter the call goes on unfiltered.\n */\nconst callerNoiseCancellation = (): NoiseCancellationOptions | undefined => {\n try {\n return BackgroundVoiceCancellation();\n } catch (err) {\n log().warn({ error: messageOf(err) }, 'voice worker: no noise cancellation on this call');\n return undefined;\n }\n};\n\nexport const runVoiceJob = async <S extends VoiceSession>(\n ctx: VoiceJobContext,\n deps: VoiceJobDeps<S>,\n): Promise<void> => {\n const dispatch = readDispatch(ctx.job.metadata);\n if (!dispatch) {\n // Only the size: the metadata carries the call token, the call's box credential.\n log().warn(\n { metadataChars: ctx.job.metadata?.length ?? 0 },\n 'voice worker: refused a job without a valid dispatch',\n );\n ctx.shutdown('invalid dispatch');\n return;\n }\n\n await ctx.connect();\n // It rejects only once the room is closed, mostly a caller who hung up before the\n // agent saw them: a normal end, not an entry failure.\n const caller = await ctx.waitForParticipant().catch((err: unknown) => {\n log().info(\n { reason: messageOf(err) },\n 'voice worker: the room closed before the caller joined',\n );\n return undefined;\n });\n if (caller === undefined) {\n ctx.shutdown('caller left');\n return;\n }\n // Before the session starts, which is where LiveKit attaches its own cloud tracing\n // to whichever provider is installed.\n installCallTraces(\n dispatch,\n {\n jobId: ctx.job.id,\n room: ctx.room.name,\n addShutdownCallback: (callback) => ctx.addShutdownCallback(callback),\n },\n deps,\n );\n const settings = dispatch.voiceSettings;\n const cue = toolCallCue();\n const fallback = inCallLanguage(TURN_FALLBACK_PHRASE, settings.language);\n let failedInARow = 0;\n let ending = false;\n // `session` is assigned below; a turn, and so its end, only runs once it has started.\n /**\n * `say` throws once the session is closing (the caller hung up): not a job failure,\n * and from a turn callback a throw would leave BoxLLM as the LLM error it exists to avoid.\n */\n const sayIfOpen = (text: string, what: string): void => {\n try {\n session.say(text);\n } catch (err) {\n log().info({ error: messageOf(err) }, `voice worker: the ${what} was not spoken`);\n }\n };\n /**\n * Marks the call as ending and turns the caller's audio off at once (D28), then runs\n * `played` and, once it settles, hangs up: deletes the room, which disconnects the\n * caller too, then ends the job. With the input off LiveKit hears no caller speech:\n * no false-interruption pause of what is still said, no new turn.\n */\n const hangUpAfter = (played: () => Promise<unknown>, reason: string): void => {\n ending = true;\n session.input.setAudioEnabled(false);\n void Promise.resolve()\n .then(played)\n .catch((err: unknown) => {\n log().info({ error: messageOf(err) }, 'voice worker: the closing phrase was not spoken');\n })\n // `shutdown` alone only takes the agent out: the caller would stay in a silent room.\n .then(() => ctx.deleteRoom())\n .catch((err: unknown) => {\n log().warn({ error: messageOf(err) }, 'voice worker: the room was not deleted');\n })\n .then(() =>\n finishCall(\n reason === 'turns kept failing' || reason === 'box refused the call token'\n ? 'failed'\n : 'completed',\n ),\n )\n .catch(() => {\n log().warn(\n { agent: dispatch.agent, sessionKey: dispatch.sessionKey, reason: 'completion_failed' },\n 'voice worker: call finalization failed',\n );\n })\n .finally(() => ctx.shutdown(reason));\n };\n /**\n * Says `phrase` and hangs up once it has played or could not be said. Uninterrupted\n * still: a turn LiveKit committed from words heard before the input went off would cut it.\n */\n const endCall = (phrase: CallPhrase, reason: string): void =>\n hangUpAfter(\n () =>\n session\n .say(inCallLanguage(phrase, settings.language), { allowInterruptions: false })\n .waitForPlayout(),\n reason,\n );\n /** What a log of a failed box request carries; the call token never reaches a log. */\n const boxFailure = (err: unknown) => ({\n agent: dispatch.agent,\n sessionKey: dispatch.sessionKey,\n room: ctx.room.name,\n // A refusal carries only its status.\n ...(err instanceof BoxTurnError ? { status: err.status } : {}),\n error: messageOf(err),\n });\n const onTurnError = (err: Error): void => {\n // The call token cannot be refreshed (the worker has no channel to the box but the\n // token itself), so a 401 is the answer to every turn left: end the call now\n // instead of spending D17's two fallbacks on it. The cause is not named here: the\n // box logs it (`bearer-auth.ts`), and a proxy in front of the box can answer 401 too.\n if (err instanceof BoxTurnError && err.status === 401) {\n log().error(boxFailure(err), 'voice worker: the box refused the call token (401)');\n if (!ending) endCall(CALL_CLOSING_PHRASE, 'box refused the call token');\n return;\n }\n failedInARow += 1;\n log().warn({ ...boxFailure(err), failedInARow }, 'voice worker: a turn failed');\n if (ending) return;\n if (failedInARow >= MAX_FAILED_TURNS_IN_A_ROW) {\n endCall(CALL_CLOSING_PHRASE, 'turns kept failing');\n return;\n }\n sayIfOpen(fallback, 'fallback');\n };\n const onRunFinished = (outcome: 'success' | 'interrupt'): void => {\n failedInARow = 0;\n // The approval stays pending in Studio; the caller is contacted after it.\n if (outcome === 'interrupt' && !ending) endCall(PAUSED_RUN_PHRASE, 'run paused for review');\n };\n const plan = voiceSessionOptions(settings, { ...deps, vad: ctx.proc.userData.vad });\n // Every request of the call, hang-up resume and `heard` turns included, inherits it.\n const wire = {\n boxUrl: dispatch.boxUrl,\n token: dispatch.callToken,\n agent: dispatch.agent,\n sessionKey: dispatch.sessionKey,\n tags: dispatch.tags,\n // With `expressive`, the TTS markup guide: LiveKit gives it only to its own chat context.\n context: plan.boxContext,\n };\n const participant = (caller ?? {}) as {\n kind?: unknown;\n attributes?: Readonly<Record<string, unknown>>;\n };\n const attributes = participant.attributes;\n const phoneNumber =\n participant.kind === ParticipantKind.SIP &&\n attributes &&\n Object.hasOwn(attributes, 'sip.phoneNumber')\n ? attributes['sip.phoneNumber']\n : undefined;\n await voiceCallRequest(wire, 'bootstrap', {\n identities: typeof phoneNumber === 'string' && phoneNumber.length > 0 ? { phoneNumber } : {},\n });\n const hangUp = createHangUp({\n wire,\n untilPlayed: () => untilGoodbyePlayed(session),\n hangUpAfter,\n isEnding: () => ending,\n onResumeFailed: (err) => {\n log().warn(boxFailure(err), 'voice worker: the hang_up resume failed');\n },\n });\n const brain = new BoxLLM({\n ...wire,\n clientTools: [HANG_UP_TOOL],\n onClientToolCall: hangUp.onClientToolCall,\n pendingResume: hangUp.pendingResume,\n isEnding: () => ending,\n onToolCall: cue.onToolCall,\n onTurnError,\n onRunFinished,\n });\n const session = deps.createSession({ ...plan.session, llm: brain });\n let completion: Promise<void> | undefined;\n const finishCall = (\n outcome: 'completed' | 'disconnected' | 'failed' = 'disconnected',\n ): Promise<void> => {\n if (completion) return completion;\n ending = true;\n completion = Promise.resolve()\n .then(async () => {\n await hangUp.pendingResume();\n // close cancels active generation and commits its playback-aligned history.\n await session.close();\n await brain.settled();\n await voiceCallRequest(\n wire,\n 'complete',\n voiceCallCompleteSchema.parse({\n transcript: brain.heardTranscript(session.history),\n outcome,\n }),\n );\n })\n .catch((error: unknown) => {\n log().warn(\n { agent: dispatch.agent, sessionKey: dispatch.sessionKey, reason: 'completion_failed' },\n 'voice worker: call finalization failed',\n );\n throw error;\n });\n return completion;\n };\n ctx.addShutdownCallback(() => finishCall());\n // A normal participant disconnect closes AgentSession without ending the job.\n session.on(voice.AgentSessionEventTypes.Close, () => {\n if (completion || ending) return;\n void finishCall()\n .catch(() => undefined)\n .finally(() => ctx.shutdown('caller left'));\n });\n const noiseCancellation = callerNoiseCancellation();\n await session.start({\n agent: plan.agent,\n room: ctx.room,\n inputOptions: noiseCancellation ? { noiseCancellation } : {},\n });\n // `null`: the agent waits for the caller to speak first.\n if (settings.greeting !== null) sayIfOpen(settings.greeting, 'greeting');\n // The cue is a courtesy: a call without it still answers.\n await deps.createCue({ room: ctx.room, session }).then(cue.attach, (err: unknown) => {\n log().warn({ error: messageOf(err) }, 'voice worker: the thinking cue did not start');\n });\n};\n", "// Centralizes the orval workaround for \"open JSON object\" fields.\n//\n// Why `z.looseObject({})` instead of `z.unknown()` / `z.record(z.unknown())`:\n// nestjs-zod emits OpenAPI 3.1 fragments for those schemas, including a\n// `propertyNames` clause that orval (our frontend client generator) currently\n// rejects. `z.looseObject({})` round-trips through nestjs-zod as a flat\n// open object in the emitted spec, which orval consumes cleanly while\n// preserving the \"any JSON object\" runtime contract.\n//\n// TODO: once orval supports OpenAPI 3.1 (incl. `propertyNames`), migrate\n// every call site of `jsonObject()` to `z.unknown()` / `z.record(z.unknown())`\n// and delete this helper. Until then, this is the single source of truth \u2014\n// do not inline `z.looseObject({})` at call sites.\nimport { z } from 'zod';\n\nexport const jsonObject = () => z.looseObject({});\n", "// The page controls and the page envelope every list on the wire shares \u2014\n// Stackbone Agent Protocol v1, \u00A75.2 (the envelope) and \u00A75.3 (the controls).\n// A surface keeps its own list schema for its FILTERS and `.extend()`s these\n// for everything else; the v16 changelog entry records what this replaced.\n//\n// TWO envelopes, not one. A keyset over `(created_at, id)` walks both ways and\n// \u00A75.2 gives it `prevCursor`. A walk over an S3 continuation token or a bare id\n// has no \"before\", so `forwardPageResponseSchema` leaves `prevCursor` OUT rather\n// than pinning it to `null`: `null` is the spec's word for \"this is the first\n// page\", and page two of a forward-only walk is not. A reader that only walks\n// forward \u2014 the table kit's `cursorPager` \u2014 accepts either envelope.\nimport { z } from 'zod';\n\n/** \u00A75.3 \u2014 the page size a list answers with when the caller names none. */\nexport const PAGE_LIMIT_DEFAULT = 25;\n/** \u00A75.3 \u2014 the hard cap a list refuses above, unless the surface widens it. */\nexport const PAGE_LIMIT_MAX = 100;\n\n/**\n * The opaque token a previous page returned. A client never decodes it, and an\n * empty one is a malformed query, not \"the first page\" \u2014 omitting the param is.\n */\nconst cursorParamSchema = z.string().min(1);\n\n/**\n * Page size, coerced from the query string. `max` is the surface's cap. The\n * DEFAULT is the store's to apply (`clampLimit` on the box) unless the surface\n * says `.default(n)` itself, so the schema never invents a page size on its own.\n */\nexport const limitParamSchema = (max: number = PAGE_LIMIT_MAX) =>\n z.coerce.number().int().min(1).max(max);\n\n/** Direction over the list's `created_at`, or the key the surface names instead. */\nexport const orderParamSchema = z.enum(['asc', 'desc']);\nexport type PageOrder = z.infer<typeof orderParamSchema>;\n\n/** \u00A75.3 \u2014 the three page controls, all optional. A surface extends it with its filters. */\nexport const pageQuerySchema = z.object({\n cursor: cursorParamSchema.optional(),\n limit: limitParamSchema().optional(),\n order: orderParamSchema.optional(),\n});\nexport type PageQuery = z.infer<typeof pageQuerySchema>;\n\n/**\n * The controls of a list that only walks forward in one fixed order (S3 key\n * order, ingest order): no `order`, rather than accepting one it would ignore.\n */\nexport const forwardPageQuerySchema = pageQuerySchema.omit({ order: true });\n\n/** A continuation token, or `null` when there is no page that way. */\nexport const pageCursorSchema = z.string().nullable();\n\n/** \u00A75.2 \u2014 one page of a list that walks both ways. */\nexport const pageResponseSchema = <T extends z.ZodType>(item: T) =>\n z.object({ items: z.array(item), nextCursor: pageCursorSchema, prevCursor: pageCursorSchema });\n\n/** One page of a list that only walks forward. No `prevCursor` \u2014 see the header. */\nexport const forwardPageResponseSchema = <T extends z.ZodType>(item: T) =>\n z.object({ items: z.array(item), nextCursor: pageCursorSchema });\n\n/** The envelope a forward-only reader needs; a two-way {@link Page} satisfies it. */\nexport interface ForwardPage<T> {\n items: T[];\n nextCursor: string | null;\n}\n\n/** The \u00A75.2 envelope. */\nexport interface Page<T> extends ForwardPage<T> {\n prevCursor: string | null;\n}\n", "import { z } from 'zod';\nimport { jsonObject } from '../common/json-object';\nimport { pageQuerySchema, pageResponseSchema } from '../common/page';\nimport { systemEntriesParamSchema, systemEntriesQuerySchema } from '../common/system-entries';\n\n// --- Stackbone Agent Protocol \u2014 Runs (V2) --------------------------------------\n// Spec: docs/arquitectura/specs/stackbone-agent-protocol-v1.md \u00A76.1/\u00A76.2.\n// Schema: libs/platform/platform-schema/src/migrations/0001_runs.sql.\n\n// `waiting` (STA-104): a serialized (FIFO) workflow trigger enqueued behind an\n// active serial run is mirrored as a run row with this status (started_at is\n// NULL \u2014 it has not run yet) so `stackbone dev` and the Studio runs view can\n// surface the queue read-only. It clears when the gate admits the trigger and\n// the run starts. Never set by any queue-management action (visibility only).\n//\n// `cancelled` (PLA-1716): a run an operator stopped. FINAL \u2014 the box store\n// refuses to complete, fail, interrupt or reopen a cancelled row, so a World\n// workflow that keeps running after the cancel (the SDK has no cancel signal\n// yet) cannot bring it back. Distinct from `interrupted`, which now means one\n// thing only: parked on a human decision, and still alive. Mirrored by\n// `runs_status_check` in migration 0074.\n//\n// The tuple is exported beside the enum because `@stackbone/box-schema`'s `runs`\n// table builds its CHECK and its Drizzle `enum` from it: one list, not two.\nexport const RUN_STATUSES = [\n 'running',\n 'done',\n 'failed',\n 'interrupted',\n 'waiting',\n 'cancelled',\n] as const;\nexport const runStatusSchema = z.enum(RUN_STATUSES);\nexport type RunStatus = z.infer<typeof runStatusSchema>;\n\n/**\n * The run statuses an operator may DELETE, as an ALLOWLIST (PLA-1152).\n *\n * DELETABLE and not TERMINAL, deliberately: elsewhere in this repo \"terminal\"\n * answers a different question (`workflow-case-runner.ts` closes an eval case on\n * an `interrupted` park, and `workflow-run-projector.ts` calls a World-cancelled\n * run a raw terminal). A shared export named after that word would be a trap the\n * next caller walks into; this list answers one question only \u2014 may an operator\n * destroy it.\n *\n * Never the negation `NOT IN ('running','waiting','interrupted')`, however much\n * shorter that reads: `runs_status_check` has been widened twice (0023 added\n * `waiting`, 0074 `cancelled`), so another status is a live possibility \u2014 and\n * under the negation it would become deletable the day it lands, with nothing\n * turning red. A run in a status nobody has classified is a run something might\n * still own.\n *\n * `cancelled` IS on the list (PLA-1716): an operator stopped it and nothing\n * reopens it, so it is as finished as a run that ended on its own.\n *\n * `interrupted` is NOT, and that is why the list is written out rather than\n * derived. It means a run parked on a human decision, which is very much alive \u2014\n * `workflow-run-projector.ts` maps a World run that is `running` + `paused` onto\n * it, keeps its serial lock, and reopens it to `running` when the hook resolves\n * (a guardrail-held turn parks the same way). Deleting it would destroy the run a\n * colleague is about to approve: the projector will not recreate it (`if\n * (!projected) return 'unchanged'`), the resume path skips a run it cannot read\n * back, and the steps that follow hit a foreign key with no parent.\n *\n * Lives HERE, beside the enum it is carved out of, because both sides of the\n * gesture read it: the box builds its `DELETE \u2026 WHERE status IN (\u2026)` from it\n * (`invocations-store.ts`), and the Studio's Runs screen decides which rows offer\n * a Delete button from the same list. Two copies is exactly how the button and\n * the SQL would come to disagree.\n */\nexport const DELETABLE_RUN_STATUSES = [\n 'done',\n 'failed',\n 'cancelled',\n] as const satisfies readonly RunStatus[];\n\n/** Whether a run is in a status an operator may delete. */\nexport function isDeletableRunStatus(status: RunStatus): boolean {\n return (DELETABLE_RUN_STATUSES as readonly RunStatus[]).includes(status);\n}\n\n/**\n * The statuses of a run in progress: running, or parked on a person's decision.\n * What the Insights `runs_active` now metric counts (PLA-1725) and what\n * `runs_active_now_idx` is built on, so the metric's filter and the index's\n * predicate are spelled from one list.\n */\nexport const ACTIVE_RUN_STATUSES = [\n 'running',\n 'interrupted',\n] as const satisfies readonly RunStatus[];\n\n// `job` (ADR 2026-05-30): one-shot agent jobs dispatched from BullMQ tag their\n// run with `job`. Recurring (cron) job schedulers keep tagging `cron`, so the\n// two natures of work stay distinguishable in the run history and filters.\n// `workflow` / `chat` (feature 67): the Workflow World \u2192 runs projection tags\n// each durable workflow run `workflow`, and the deep-agent chat recorder persists\n// each chat turn as a `chat` run.\n// `eval` (STA-408): the eval engine executes every case by the REAL path, so each\n// one produces a normal run with its full trace. Tagging them apart is what lets\n// a failed case jump to its trace and lets the Runs screen separate measurement\n// from traffic. Mirrored by `runs_trigger_check` in migration 0037.\n// `platform` / `unknown` (PLA-1717): every workflow start gate now names itself\n// (manual, cron, event, workflow). `platform` is a run the box started as its own\n// errand (rag-ingest, Auto-map, an eval judge); `unknown` is a start no gate\n// stamped. The trigger is never guessed from the workflow's name. Mirrored by\n// `runs_trigger_check` in migration 0075.\nexport const RUN_TRIGGERS = [\n 'manual',\n 'webhook',\n 'cron',\n 'event',\n 'job',\n 'workflow',\n 'chat',\n 'eval',\n 'platform',\n 'unknown',\n] as const;\nexport const runTriggerSchema = z.enum(RUN_TRIGGERS);\nexport type RunTrigger = z.infer<typeof runTriggerSchema>;\n\n// STA-426 \u2014 WHICH KIND of entity produced a run, alongside the name 0020 already\n// denormalises. A deep agent and a workflow may legitimately share a name, so the\n// name alone is not an identity. `trigger` above cannot stand in for it: that records\n// WHY a run started (manual, cron, chat, workflow), not what the entity is \u2014 a\n// workflow run started by cron carries `cron`, and a workflow that opens an agent turn\n// carries `chat`.\n//\n// The values are deliberately RESTATED here rather than imported from the guardrails\n// lib (whose attachment scope already settled the same vocabulary for the same\n// reason): validators is a leaf that imports no other scope, and its wire enum is the\n// contract, so it must be readable on its own. The two lists agree on `agent` /\n// `workflow`; the guardrail scope adds `workspace`, which is a PLACE to attach a rule,\n// never a kind of entity, so it is not admitted here.\nexport const runEntityKindSchema = z.enum(['agent', 'workflow']);\nexport type RunEntityKind = z.infer<typeof runEntityKindSchema>;\n\n// The inbound Connect trigger link that started (or resumed) a run, as the box\n// resolves it at READ time from its delivery journal. Never stored on the run:\n// `triggerRef` is the stamp the start gate wrote, this is the link joined back\n// with what an operator reads it by (PLA-1898).\n//\n// The ids always come; the names and the logo only when the box's connector\n// registry lists the connector and the trigger. A reader falls back to the ids,\n// never to an empty string. `outcome` is optional because a box older than\n// STA-278 does not send it, and absent reads as `started`.\nexport const runTriggerLinkSchema = z.object({\n /** The `trigger_subscriptions.id`: the link the Studio trigger page routes to. */\n subscriptionId: z.string(),\n /** The connector id, e.g. `gmail`. */\n connector: z.string(),\n /** The trigger id within the connector, e.g. `gmail_new_email_received`. */\n trigger: z.string(),\n outcome: z.enum(['started', 'resumed']).optional(),\n /** The connector's brand name, e.g. `Gmail`. */\n connectorDisplayName: z.string().optional(),\n /** The trigger's name, e.g. `New Email`. */\n triggerDisplayName: z.string().optional(),\n connectorLogoUrl: z.string().optional(),\n});\nexport type RunTriggerLink = z.infer<typeof runTriggerLinkSchema>;\n\n// Canonical Run record served by `GET /api/runs/:id`. Wire shape is camelCase;\n// the storage columns in `stackbone_platform.runs` stay snake_case and the\n// service layer maps them once at the boundary (see `apps/api/src/modules/\n// studio/approvals.service.ts`).\n// TODO(studio-v2): runs whose `input` is a primitive (string, number)\n// currently round-trip as objects because of the `jsonObject()` constraint;\n// widen once the underlying helper migrates to `unknown`.\nexport const runSchema = z.object({\n id: z.uuid(),\n status: runStatusSchema,\n trigger: runTriggerSchema,\n createdAt: z.iso.datetime(),\n startedAt: z.iso.datetime().nullable(),\n finishedAt: z.iso.datetime().nullable(),\n durationMs: z.number().int().nullable(),\n traceId: z.string().nullable(),\n // True when the run was triggered from the Studio Playground (header\n // `STACKBONE_PLAYGROUND_HEADER` on `POST /api/invoke`). Billing and the Trust\n // Layer's verified metrics filter on this column to exclude stress-tests.\n isPlayground: z.boolean(),\n // Name of the agent or workflow this run belongs to (migration 0020). Stamped\n // at run creation: the agent name for `chat` runs, the workflow name for\n // `workflow` runs. Combined with `trigger` the UI knows the kind. Null for\n // classic/manual cloud runs (deep-agent chat is dev-only), so the Runs screen renders `\u2014`.\n entityName: z.string().nullable(),\n // Which KIND of entity `entityName` names (STA-426, migration 0037). Stamped at\n // run creation next to the name, so an agent and a workflow that share a name stop\n // sharing one row set. Null for a run written before the column existed (no\n // backfill) \u2014 those stay matched by name alone.\n //\n // OPTIONAL as well as nullable, unlike `entityName`: the box and the control plane\n // version independently and the proxy returns the box's body verbatim, so a new\n // reader (the CLI, or the control plane's response validation) must tolerate an\n // older box that does not send the field at all. Absent reads the same as null.\n entityKind: runEntityKindSchema.nullable().optional(),\n // WHICH Connect link started the run (PLA-1717, migration 0075): the\n // `trigger_subscriptions.id`, stamped at start for `trigger: 'event'` and null\n // for every other gate. Optional as well as nullable for the reason `entityKind`\n // is: an older box does not send the field.\n triggerRef: z.string().nullable().optional(),\n // The Connect link that started or resumed the run, with its names (PLA-1898).\n // Null for a run no link started; optional for the reason `entityKind` is.\n triggerLink: runTriggerLinkSchema.nullable().optional(),\n input: jsonObject().nullable(),\n output: jsonObject().nullable(),\n error: z.object({ message: z.string(), stack: z.string().optional() }).nullable(),\n // Per-turn token usage for deep-agent CHAT runs (feature 68, A5): the chat\n // recorder folds the turn's usage and stamps the four token counters onto the run.\n // Null when the run had no model call; `totalTokens` is derived at read\n // (`input + output`), never stored. No $ cost (D5).\n inputTokens: z.number().int().nullable(),\n outputTokens: z.number().int().nullable(),\n cacheReadTokens: z.number().int().nullable(),\n cacheWriteTokens: z.number().int().nullable(),\n totalTokens: z.number().int().nullable(),\n /**\n * The run's own `metadata` jsonb, verbatim (PLA-1272 slice 4).\n *\n * DECLARED AS A BAG rather than a shape, and the reason is the wire: the box\n * merges stamps onto this column from several places at different times (an\n * eval stamp, an output-schema violation) and versions independently of the\n * control plane, so a reader that named today's keys would strip tomorrow's.\n * It had no field at all until PLA-1272, which is why a violation the box\n * recorded reached the browser and was thrown away here \u2014 zod strips what a\n * schema does not name, silently, with the typecheck green.\n *\n * Read it through a NAMED reader, never by indexing: {@link runOutputViolation}\n * is the one for the violation, and lives beside this so the runtime that\n * writes the stamp and the screen that paints it share one declaration.\n *\n * Optional as well as nullable for the same reason `entityKind` is: an older\n * box does not send the field.\n */\n metadata: jsonObject().nullable().optional(),\n});\nexport type Run = z.infer<typeof runSchema>;\n\n/**\n * One reason a value failed a schema: where it was, and what was wrong.\n *\n * The SAME shape the input side already reports, deliberately, so a screen that\n * renders a `workflow_input_invalid` 400 renders an output violation with no\n * second code path.\n */\nexport const schemaIssueSchema = z.object({\n /** Path segments to the offending field (`[]` for the root value). */\n path: z.array(z.union([z.string(), z.number()])),\n message: z.string(),\n});\nexport type SchemaIssue = z.infer<typeof schemaIssueSchema>;\n\n/** The `runs.metadata` key a recorded output-schema violation lives under. */\nexport const OUTPUT_VIOLATION_KEY = 'outputSchemaViolation';\n\n/** A recorded output-schema violation, as it sits on a run's `metadata`. */\nexport const runOutputViolationSchema = z.object({\n /** The workflow whose declared `outputSchema` the return value broke. */\n workflowName: z.string(),\n issues: z.array(schemaIssueSchema),\n /**\n * When the projector observed it (ISO). Not the run's `finishedAt`: the run\n * closed at that instant whether or not anything validated it.\n */\n recordedAt: z.string(),\n});\nexport type RunOutputViolation = z.infer<typeof runOutputViolationSchema>;\n\n/**\n * Read a recorded violation off a run's metadata, or `null` when the run's\n * output conformed \u2014 or was never validated. A run from a build that predates\n * the check, or one whose validator could not load, records nothing and is\n * therefore indistinguishable from a clean one: **absence is not proof of\n * conformance.**\n *\n * Defensive about the whole shape rather than trusting the key, because this is\n * jsonb read back out of Postgres: a stamp written in an older shape has to read\n * as \"no violation\" instead of crashing whoever renders it.\n */\nexport const runOutputViolation = (metadata: unknown): RunOutputViolation | null => {\n if (typeof metadata !== 'object' || metadata === null || Array.isArray(metadata)) return null;\n const raw = (metadata as Record<string, unknown>)[OUTPUT_VIOLATION_KEY];\n const parsed = runOutputViolationSchema.safeParse(raw);\n return parsed.success ? parsed.data : null;\n};\n\n/**\n * A step's function, the name a reader knows it by: the last `//`-separated\n * segment of the runtime's step name\n * (`step//./workflows/x.workflow//validateInput` \u2192 `validateInput`). A name\n * without `//` is kept as it is; an empty one is null.\n *\n * The runtime names a workflow step by its full addressable path, the right thing\n * to copy into a support thread and the wrong thing to read in a narrow column or\n * a chart legend, where every step of a workflow would show the same prefix. The\n * `workflow_step.finished` event and the Studio trace read it through this one rule.\n */\nexport const shortStepName = (name: string | null | undefined): string | null => {\n const short = name?.split('//').pop()?.trim();\n return short ? short : null;\n};\n\nexport const runIdParamSchema = z.object({ runId: z.uuid() });\nexport type RunIdParam = z.infer<typeof runIdParamSchema>;\n\n/**\n * A run tag key: a lowercase slug starting with a letter. Runs are filtered by\n * `key:value` split on the first colon, so a key never holds one.\n */\nexport const runTagKeySchema = z.string().regex(/^[a-z][a-z0-9_.-]{0,63}$/);\n\n/** The longest run tag value, in UTF-16 code units. */\nexport const RUN_TAG_VALUE_MAX = 256;\n\n/** The most tags one run carries. */\nexport const RUN_TAGS_MAX = 16;\n\n/**\n * A C0 control character or DEL (a line break no `key:value` filter can name, a NUL\n * `jsonb` refuses), or half a UTF-16 surrogate pair without its other half (which\n * `jsonb` refuses too).\n */\nconst UNSTORABLE_TAG_CHAR =\n // eslint-disable-next-line no-control-regex\n /[\\u0000-\\u001f\\u007f]|[\\ud800-\\udbff](?![\\udc00-\\udfff])|(?<![\\ud800-\\udbff])[\\udc00-\\udfff]/;\n\n/**\n * A tag value a run stores and a `key:value` filter can name: 1 to\n * {@link RUN_TAG_VALUE_MAX} UTF-16 units with no control character and no lone\n * surrogate. The one value rule of the voice dispatch tags, the run tag filter and the\n * box's {@link RUN_TAGS_HEADER} reader (which cuts a longer value first).\n */\nexport const runTagValueSchema = z\n .string()\n .min(1)\n .max(RUN_TAG_VALUE_MAX)\n .refine((value) => !UNSTORABLE_TAG_CHAR.test(value), {\n message: 'A tag value holds no control character and no lone surrogate.',\n });\n\n/**\n * A `key:value` tag filter split on its FIRST colon, or `null` when it holds none.\n * A key never holds a colon (`runTagKeySchema`), so every later colon belongs to\n * the value: `room:call-a:b` is the room `call-a:b`. Neither part is trimmed. The\n * schema, the runs store and the sessions store all split through this one rule.\n */\nexport const splitRunTagFilter = (tag: string): { key: string; value: string } | null => {\n const colon = tag.indexOf(':');\n return colon < 0 ? null : { key: tag.slice(0, colon), value: tag.slice(colon + 1) };\n};\n\n/**\n * `?tag=key:value` on the runs and sessions lists (FDE-62 S7). Validated here and\n * kept a string, so `ListRunsQuery` stays a query-string shape for Studio and the CLI;\n * the stores split it with {@link splitRunTagFilter}.\n */\nexport const runTagFilterSchema = z.string().superRefine((tag, ctx) => {\n const filter = splitRunTagFilter(tag);\n if (!filter) {\n ctx.addIssue({ code: 'custom', message: 'A tag filter is `key:value`.' });\n return;\n }\n if (!runTagKeySchema.safeParse(filter.key).success) {\n ctx.addIssue({\n code: 'custom',\n message: 'A tag key is a lowercase slug that starts with a letter, up to 64 characters.',\n });\n }\n // The value rule the box stores a tag by, so a filter never names a value no run holds.\n if (!runTagValueSchema.safeParse(filter.value).success) {\n ctx.addIssue({\n code: 'custom',\n message: `A tag value is 1 to ${RUN_TAG_VALUE_MAX} characters, with no control characters.`,\n });\n }\n});\n\n// Query params for `GET /api/runs` (V4) \u2014 the shared page controls (\u00A75.3) plus\n// this list's filters. The cursor is opaque base64url over (created_at, id).\n// Filter values are exact-match per the protocol spec \u00A76.4 \u2014 `from`/`to` are\n// ISO timestamps and bound the `created_at` window inclusively. The cross-field\n// refinement rejects inverted ranges so a typo (`from=2026-12-01&to=2026-01-01`)\n// does not silently return an empty page.\nexport const listRunsQuerySchema = pageQuerySchema\n .extend({\n status: runStatusSchema.optional(),\n trigger: runTriggerSchema.optional(),\n from: z.iso.datetime().optional(),\n to: z.iso.datetime().optional(),\n // Feature 69 \u2014 the Sessions detail screen reads a conversation's turns with\n // `GET /runs?session_id=<id>&order=asc`. `session_id` is the real\n // `runs.session_id` join column (the deterministic UUIDv5 of the deep-agent\n // session key), so it is exact-match on the indexed column.\n session_id: z.uuid().optional(),\n // Migration 0020 \u2014 the Runs screen filters to one agent/workflow by its\n // exact name (the value picked from the name dropdown, or deep-linked from\n // the agent/workflow detail's \"View runs\"). Exact-match on `runs.entity_name`.\n name: z.string().min(1).optional(),\n // STA-426 \u2014 narrow the name filter to ONE kind of entity, so \"the runs of the\n // workflow named sync\" excludes the agent that shares the name. A row that\n // DECLARES the other kind is always excluded; a row whose kind was never stamped\n // (pre-0037, no backfill) is kept under the name that matches (STA-435), because\n // dropping it would empty the history of every entry in a workspace that ran\n // before the migration. Use the name-only filter to see every kind at once.\n kind: runEntityKindSchema.optional(),\n // PLA-1730 \u2014 the runs whose input has a top-level `input_key` field containing\n // `input_value`, case-insensitive: `input_key=senderEmail&input_value=astex.com`\n // finds every sender at that domain. The field is compared as text, so a number\n // or boolean matches its JSON spelling (`3`, `true`). The ONE filter here that is\n // not exact-match: it serves a search box, and a search box that needs the whole\n // address typed without a typo is not one. Nested paths are out of scope.\n input_key: z.string().min(1).optional(),\n input_value: z.string().min(1).optional(),\n // PLA-1785 \u2014 `exclude` leaves out the runs of the platform's own entries\n // (`studio-copilot`, `mapping-suggest`, `rag-ingest`), decided by the box's\n // `isSystemEntry` and applied before the cursor and the LIMIT, so a page stays\n // full. Absent keeps every run, which is what `stackbone runs` lists.\n system: systemEntriesParamSchema.optional(),\n // FDE-62 S7 \u2014 the runs stamped with one `metadata.tags` entry (`channel:voice`\n // keeps a call's turns). Exact match on the value, case-sensitive.\n tag: runTagFilterSchema.optional(),\n })\n .refine((q) => !q.from || !q.to || q.from <= q.to, {\n message: '`from` must be earlier than or equal to `to`',\n path: ['from'],\n })\n // Half a pair has no reading: a key alone would have to mean \"has the key\", a\n // value alone \"any key holds it\", and neither is the filter asked for. Refused\n // rather than ignored, so a caller that dropped one half learns it.\n .refine((q) => (q.input_key === undefined) === (q.input_value === undefined), {\n message: '`input_key` and `input_value` go together',\n path: ['input_key'],\n })\n // A substring match cannot use an index over the whole input, so it has to run\n // over ONE entry's runs, which the `entity_name` index narrows to. Without `name`\n // the same query reads every run the box ever recorded.\n .refine((q) => q.input_key === undefined || q.name !== undefined, {\n message: \"`input_key` needs `name`: the input filter searches one entry's runs\",\n path: ['input_key'],\n });\nexport type ListRunsQuery = z.infer<typeof listRunsQuerySchema>;\n\nexport const runListResponseSchema = pageResponseSchema(runSchema);\nexport type RunListResponse = z.infer<typeof runListResponseSchema>;\n\n// One entry the runs belong to (STA-426): the name plus which kind of entity it\n// names. `kind` is null for rows written before migration 0037, so a legacy name\n// still appears in the dropdown \u2014 it just cannot be told apart from a same-named\n// sibling, which is the situation today.\n//\n// The name is OPAQUE text and never parsed: no `kind:name` composite, no splitting on\n// any separator. Real names look like `agent:sync` (the Playground target id),\n// `reports/daily`, a bare UUID or a date, and every one of them must come back\n// exactly as stored.\nexport const runEntityEntrySchema = z.object({\n name: z.string(),\n kind: runEntityKindSchema.nullable(),\n});\nexport type RunEntityEntry = z.infer<typeof runEntityEntrySchema>;\n\n// Query params for `GET /api/runs/names` (PLA-1785): the same opt-in filter as the\n// list beside it, so the dropdown stops offering an entry the list leaves out.\nexport const runEntityNamesQuerySchema = systemEntriesQuerySchema;\nexport type RunEntityNamesQuery = z.infer<typeof runEntityNamesQuerySchema>;\n\n// Response for `GET /api/runs/names` (migration 0020) \u2014 the distinct\n// `entity_name`s present in this install's runs, sorted. Feeds the name\n// dropdown on the Runs screen so a creator filters by agent/workflow without\n// typing. Names with no runs yet never appear (the filter would be empty).\n//\n// `entries` (STA-426) is the same list keyed by (name, kind), so the dropdown offers\n// two rows where two same-named entries of different kinds exist instead of one that\n// mixes them. ADDITIVE and optional rather than a replacement of `names`: the box and\n// the control plane version independently and the proxy returns the box's body\n// verbatim, so a new Studio against an older box must degrade to `names`, not break.\nexport const runEntityNamesResponseSchema = z.object({\n names: z.array(z.string()),\n entries: z.array(runEntityEntrySchema).optional(),\n});\nexport type RunEntityNamesResponse = z.infer<typeof runEntityNamesResponseSchema>;\n\n// Action response for `POST /api/runs/:id/cancel` (V4): the updated row\n// (status=`cancelled`).\nexport const cancelRunResponseSchema = z.object({\n run: runSchema,\n});\nexport type CancelRunResponse = z.infer<typeof cancelRunResponseSchema>;\n\n// Header marker the Studio Playground UI sets to opt invocations out of\n// billing and verified metrics. Real triggers (webhook/cron) must NOT set it.\nexport const STACKBONE_PLAYGROUND_HEADER = 'x-stackbone-playground';\n\n// Header that opts a chat turn into a durable session and groups it with the turns\n// that carry the same value. The vendor wires (`/v1/chat/completions`,\n// `/v1/messages`) read it; AG-UI carries the same key as `threadId`, and the voice\n// worker sends both.\nexport const STACKBONE_SESSION_HEADER = 'x-stackbone-session';\n\n// Header a chat caller labels the run its turn opens with (FDE-62 S7): a flat\n// string-to-string JSON object, held to {@link runTagKeySchema} and\n// {@link runTagValueSchema} and stamped on `runs.metadata.tags`, which\n// `?tag=key:value` on the runs and sessions lists filters on.\nexport const RUN_TAGS_HEADER = 'x-stackbone-run-tags';\n\n// Header the box's own EVAL ENGINE sets when it drives a turn as one case of an\n// eval run (STA-415). It carries the `eval_runs.id`, and the conversational\n// surface turns it into `trigger: 'eval'` plus a back-reference on the run's\n// metadata \u2014 so a measured turn is distinguishable from a real conversation and a\n// case result can point at the exact run that produced it.\n//\n// Not something a vendor client sets: it rides beside `x-stackbone-session` on the\n// box's in-process self-call, which is the same road a real message travels. And\n// since PLA-1130 not something a vendor client CAN set on a DEPLOYED box: the chat\n// wire honours this header and the one below only for that in-process caller\n// (`system:eval-engine`) and silently drops both for a workspace API key or an\n// operator identity. An ungated `stackbone dev` box identifies nobody and so\n// refuses nobody \u2014 the emulator runs evals over the same wire.\nexport const STACKBONE_EVAL_RUN_HEADER = 'x-stackbone-eval-run';\n\n// Header the eval engine sets BESIDE the one above when the suite driving the case\n// turned its simulator on (STA-525): this turn's outbound connector calls are\n// answered by stand-ins rather than by the customer's real providers.\n//\n// A second header rather than a second meaning for the first, because every\n// measured turn carries an eval run id and only some of them are simulated \u2014\n// inferring one from the other would divert live traffic the day a suite ran\n// without the simulator. Its presence is the signal; the value is unread.\nexport const STACKBONE_EVAL_SIMULATED_HEADER = 'x-stackbone-eval-simulated';\n\n// Studio Playground fixtures (V5#03). Saved JSON inputs the creator pinned\n// for repeated invocations from the Playground. The shape is identical in the\n// box's Postgres and local (`~/.stackbone/dev/fixtures/<agentId>.jsonl`); the\n// only asymmetry is the storage backend, not the wire format.\nexport const fixtureSchema = z.object({\n id: z.string(),\n name: z.string(),\n input: jsonObject().nullable(),\n description: z.string().nullable(),\n createdAt: z.iso.datetime(),\n});\nexport type Fixture = z.infer<typeof fixtureSchema>;\n\nexport const createFixtureRequestSchema = z.object({\n name: z.string().min(1).max(120),\n input: jsonObject(),\n description: z.string().max(2_000).optional().nullable(),\n});\nexport type CreateFixtureRequest = z.infer<typeof createFixtureRequestSchema>;\n\nexport const fixtureListResponseSchema = pageResponseSchema(fixtureSchema);\nexport type FixtureListResponse = z.infer<typeof fixtureListResponseSchema>;\n\n// Path param for `DELETE /studio/fixtures/:fxtId` (V5#03). The id comes\n// from `stackbone_platform.playground_fixtures.id` (UUID). Validated at\n// the boundary so a non-UUID path collapses to the protocol's standard\n// 400 envelope instead of surfacing as a service-level error.\nexport const fixtureIdParamSchema = z.object({ fxtId: z.uuid() });\nexport type FixtureIdParam = z.infer<typeof fixtureIdParamSchema>;\n\n// Response shape for `GET /studio/schema` (V5#01). The Playground UI\n// reads it once per install to decide between rendering an ngx-formly\n// form (when the agent template's manifest declared a JSON Schema for\n// `input` / `output`) or falling back to a raw JSON textarea. Both\n// fields are nullable so a template that declares neither still gets a\n// 200 response with `{ input: null, output: null }` \u2014 the frontend\n// branches on that nullability instead of a 404.\n//\n// The wire carries the JSON Schema verbatim; the control plane neither\n// parses it nor validates inputs against it (that is the agent's job at\n// invoke time).\n//\n// `handlers` enumerates EVERY handler the agent declares (the default\n// `invoke` plus each named handler) with its own input/output JSON Schema, so\n// the Playground can let the creator pick which handler to invoke and render\n// the matching form. It is OPTIONAL for back-compat: an older runtime or\n// control plane returns only `{ input, output }` (the default invoke pair),\n// and the frontend synthesises a single `invoke` entry from the top level. The\n// top-level `input`/`output` stay equal to the `invoke` handler's schemas so\n// every existing reader keeps working unchanged.\n//\n// Per-handler shape: `{ [name]: { input, output } }`. `HandlerSchema` is the\n// typed view the frontend casts each entry to; the WIRE field uses the open\n// `jsonObject()` form (not `z.record(...)`) because a keyed record emits an\n// OpenAPI 3.1 `propertyNames` clause that orval cannot consume \u2014 the same\n// reason `jsonObject()` exists. The map is opaque on the wire and re-typed at\n// the read boundary.\nexport const handlerSchemaSchema = z.object({\n input: jsonObject().nullable(),\n output: jsonObject().nullable(),\n});\nexport type HandlerSchema = z.infer<typeof handlerSchemaSchema>;\n\nexport const schemaResponseSchema = z.object({\n input: jsonObject().nullable(),\n output: jsonObject().nullable(),\n handlers: jsonObject().nullable().optional(),\n});\nexport type SchemaResponse = z.infer<typeof schemaResponseSchema>;\n", "// The query param a list read takes to leave the platform's own entries out\n// (PLA-1743): `studio-copilot`, `mapping-suggest`, `rag-ingest`. Studio's \"System\n// entries\" toggle sends it; the box decides which entries are system with its own\n// predicate (`isSystemEntry`), so this carries the request, never a list of names.\n//\n// OPT-IN on purpose. Absent means every entry, which is what the box answered\n// before the toggle existed, so the CLI and any other caller keep that answer.\n//\n// A closed enum rather than a boolean. The query string carries text, and\n// `z.coerce.boolean()` reads the string `'false'` as `true`: the one value a caller\n// would send to turn the filter off would turn it on.\nimport { z } from 'zod';\n\n/** `exclude` leaves the system entries out; `include` (or no param) keeps them. */\nexport const systemEntriesParamSchema = z.enum(['include', 'exclude']);\nexport type SystemEntriesParam = z.infer<typeof systemEntriesParamSchema>;\n\n/**\n * The query of a read whose only filter is this one. Each such route keeps its\n * own name for it (`runEntityNamesQuerySchema`, `evalSystemEntriesQuerySchema`).\n */\nexport const systemEntriesQuerySchema = z.object({\n system: systemEntriesParamSchema.optional(),\n});\nexport type SystemEntriesQuery = z.infer<typeof systemEntriesQuerySchema>;\n", "import { z } from 'zod';\n\n// Studio \"Model provider\" screen. Spec:\n// docs/adr/2026-07-20-model-provider-operator-configured.md.\n//\n// The operator configures ONE OpenAI-compatible endpoint (base URL + optional\n// API key) per deployment. The two values are persisted as `is_system` rows in\n// the runtime's own secret store under `MODEL_PROVIDER_BASE_URL` /\n// `MODEL_PROVIDER_API_KEY` (D4), written only through the privileged\n// `/api/model-provider` route (D5). The key value NEVER leaves the server in\n// clear \u2014 GET returns a masked placeholder; the base URL is non-sensitive so it\n// rides back in plaintext. Presets (OpenRouter / Ollama / LM Studio / Custom)\n// are pure UI sugar that prefill the base URL \u2014 the wire shape is one endpoint.\n\n// The two SLOTS of the credential (ADR D8). Each slot is one value under two\n// names: the neutral `MODEL_PROVIDER_*` name and its deprecated `OPENROUTER_*`\n// alias. The neutral name comes first and shadows the alias. This table is the\n// only place the alias names are written down.\nexport const modelProviderSlotSchema = z.enum(['baseUrl', 'apiKey']);\nexport type ModelProviderSlot = z.infer<typeof modelProviderSlotSchema>;\n\nexport const MODEL_PROVIDER_SLOTS: Readonly<\n Record<ModelProviderSlot, readonly [neutral: string, alias: string]>\n> = {\n baseUrl: ['MODEL_PROVIDER_BASE_URL', 'OPENROUTER_BASE_URL'],\n apiKey: ['MODEL_PROVIDER_API_KEY', 'OPENROUTER_API_KEY'],\n};\n\nconst SLOT_NAMES = Object.keys(MODEL_PROVIDER_SLOTS) as readonly ModelProviderSlot[];\n\n// The deprecated aliases the runtime still reads when the neutral\n// `MODEL_PROVIDER_*` names are unset. Shared so the runtime that REPORTS a\n// winning variable and the screen that RENDERS it agree on which names are on\n// their way out \u2014 the transition window is explicitly still open (STA-287 out\n// of scope: \"retiring the OPENROUTER_* aliases\").\nexport const MODEL_PROVIDER_DEPRECATED_ENV_NAMES: readonly string[] = SLOT_NAMES.map(\n (slot) => MODEL_PROVIDER_SLOTS[slot][1],\n);\n\n// Where the credential the deployment is ACTUALLY using came from:\n// - `environment` \u2014 the box exports it, which outranks anything stored (D10).\n// - `stored` \u2014 this screen's own saved configuration is what's live.\n// - `none` \u2014 nothing is configured anywhere.\nexport const modelProviderSourceSchema = z.enum(['environment', 'stored', 'none']);\nexport type ModelProviderSource = z.infer<typeof modelProviderSourceSchema>;\n\n// ---------------------------------------------------------------------------\n// The per-slot rule (PLA-1824, ADR D8). Pure, so the box boot, the live apply\n// after a save, the Studio view and `stackbone dev` all decide the same way.\n//\n// Order per slot: a Studio override, then the exported variable, then the value\n// Studio saved, then nothing. The slots resolve independently, so an exported\n// key never takes the stored base URL down with it.\n//\n// An override is explicit (PLA-1825): the operator pressed Override on a slot\n// the environment exports, and the store carries a flag for that slot beside\n// the value. A stored value with no flag keeps losing to the export, so a box\n// whose environment starts exporting a slot never changes credential on its own.\n//\n// Blank is absent: `undefined`, `''` and whitespace-only all count as \"not\n// set\", in the environment and in the store alike, so a stray\n// `export MODEL_PROVIDER_API_KEY=` never shadows a saved key, and a flag over a\n// blank stored value overrides nothing. A real value is returned verbatim,\n// never trimmed.\n\ntype EnvLike = Readonly<Record<string, string | undefined>>;\n\n/** True for a value that fills a slot: a string with something besides whitespace. */\nexport const isModelProviderValuePresent = (value: string | null | undefined): value is string =>\n typeof value === 'string' && value.trim() !== '';\n\n/** The first name of `slot` that holds a value in `record`: the neutral name shadows the alias. */\nconst firstPresentName = (record: EnvLike, slot: ModelProviderSlot): string | undefined =>\n MODEL_PROVIDER_SLOTS[slot].find((name) => isModelProviderValuePresent(record[name]));\n\n/**\n * Where ONE slot's value came from. `override` exists only here, per slot: the\n * wire `source` stays three-valued (a box and a Studio upgrade apart), and the\n * view reports an override through its own `overridden` list.\n */\nexport type ModelProviderSlotSource = ModelProviderSource | 'override';\n\nexport interface ModelProviderSlotResolution {\n /** The winning value, verbatim; `undefined` when the slot resolves to none. */\n value: string | undefined;\n source: ModelProviderSlotSource;\n /** The exported name that won, when `source` is `environment`. */\n envName?: string;\n}\n\nexport type ModelProviderSlotsResolution = Record<ModelProviderSlot, ModelProviderSlotResolution>;\n\n/** Which slots Studio overrides. An absent slot is not overridden. */\nexport type ModelProviderOverrides = Partial<Record<ModelProviderSlot, boolean>>;\n\nexport interface ModelProviderSlotsInput {\n /** The environment the process was started with (never one boot already wrote to). */\n exported: EnvLike;\n /** The values Studio saved, one per slot. */\n stored?: Partial<Record<ModelProviderSlot, string | null | undefined>> | undefined;\n /** The slots whose stored value outranks the exported one (PLA-1825). */\n overridden?: ModelProviderOverrides | undefined;\n}\n\nexport const resolveModelProviderSlots = ({\n exported,\n stored,\n overridden,\n}: ModelProviderSlotsInput): ModelProviderSlotsResolution => {\n const resolveSlot = (slot: ModelProviderSlot): ModelProviderSlotResolution => {\n const envName = firstPresentName(exported, slot);\n const saved = stored?.[slot];\n if (isModelProviderValuePresent(saved)) {\n // An override only ever beats something: over a slot nobody exports, the\n // flagged value is just the stored one.\n if (envName && overridden?.[slot] === true) return { value: saved, source: 'override' };\n if (!envName) return { value: saved, source: 'stored' };\n }\n if (envName) return { value: exported[envName], source: 'environment', envName };\n return { value: undefined, source: 'none' };\n };\n return { baseUrl: resolveSlot('baseUrl'), apiKey: resolveSlot('apiKey') };\n};\n\n/**\n * The slots whose exported value a resolution ignores, each with the exported\n * name it ignores, in slot order. What the box logs one warning per at boot\n * (ADR D8), and what the view lists by slot name only.\n */\nexport const overriddenExportedSlots = (\n resolution: ModelProviderSlotsResolution,\n exported: EnvLike,\n): { slot: ModelProviderSlot; envName: string }[] =>\n SLOT_NAMES.flatMap((slot) => {\n const envName = firstPresentName(exported, slot);\n return resolution[slot].source === 'override' && envName ? [{ slot, envName }] : [];\n });\n\n// The override FLAGS (PLA-1825), one `is_system` row beside the credential. Boot\n// metadata like the gateway kind: outside the SDK's stored-name \u2192 env map, so it\n// never reaches `process.env`. Here rather than in the box's model-provider\n// service so the SDK boot and `stackbone dev` decode the same row with no import\n// of the runtime.\n//\n// A JSON document, never a bare word: the log scrubber masks every stored value\n// in every line the box prints, and a row holding `true` would redact that word\n// from all of its logs. Strict on read: a row this build cannot read overrides\n// nothing, so the exported value keeps winning, which is the upgrade-safe side.\nexport const MODEL_PROVIDER_OVERRIDE_SECRET = 'MODEL_PROVIDER_OVERRIDE';\n\nconst storedOverridesSchema = z.strictObject({\n baseUrl: z.boolean().optional(),\n apiKey: z.boolean().optional(),\n});\n\nexport const encodeModelProviderOverrides = (flags: ModelProviderOverrides): string =>\n JSON.stringify({ baseUrl: flags.baseUrl === true, apiKey: flags.apiKey === true });\n\nexport const decodeModelProviderOverrides = (\n raw: string | null | undefined,\n): Record<ModelProviderSlot, boolean> => {\n const none = { baseUrl: false, apiKey: false };\n if (typeof raw !== 'string') return none;\n try {\n const parsed = storedOverridesSchema.safeParse(JSON.parse(raw));\n if (!parsed.success) return none;\n return { baseUrl: parsed.data.baseUrl === true, apiKey: parsed.data.apiKey === true };\n } catch {\n return none;\n }\n};\n\n/**\n * Collapses a name-keyed record into one value per slot, the neutral name first\n * and a non-blank alias second. For the stored rows: a box that saved only an\n * `OPENROUTER_*` row, before the neutral names existed, keeps its credential.\n */\nexport const modelProviderSlotsFromNames = (\n record: EnvLike,\n): Record<ModelProviderSlot, string | undefined> => {\n const pick = (slot: ModelProviderSlot): string | undefined => {\n const name = firstPresentName(record, slot);\n return name ? record[name] : undefined;\n };\n return { baseUrl: pick('baseUrl'), apiKey: pick('apiKey') };\n};\n\n/**\n * Writes a resolution onto `env`, in place: the winner of each slot under BOTH\n * its names, and both names deleted when the slot resolves to none.\n *\n * Both names, because most readers never call the rule. `client.ai` and the\n * browser tools read the neutral name, creator code and third-party SDKs may\n * read `OPENROUTER_*`, and a credential that depends on which name a reader\n * picks is two credentials. The losing name is overwritten, not left behind.\n *\n * Returns the names whose value it changed (set, not deleted), for a value-free\n * log line.\n */\nexport const writeModelProviderSlotsToEnv = (\n env: Record<string, string | undefined>,\n resolution: ModelProviderSlotsResolution,\n): string[] => {\n const written: string[] = [];\n for (const slot of SLOT_NAMES) {\n const { value } = resolution[slot];\n for (const name of MODEL_PROVIDER_SLOTS[slot]) {\n if (value === undefined) {\n delete env[name];\n } else if (env[name] !== value) {\n env[name] = value;\n written.push(name);\n }\n }\n }\n return written;\n};\n\n// The GATEWAY KIND behind the one OpenAI-compatible endpoint (PRD PLA-1595,\n// section 3; ADR 2026-09-17 D6). The wire shape stays one endpoint; the kind\n// picks the adapter that knows a gateway's request rules and where it lists\n// model capabilities. The Studio preset sets it on save (`Custom` saves\n// `generic`). A provider with no stored kind (saved before the field existed,\n// or exported through `MODEL_PROVIDER_*`) resolves by its host: `openrouter`\n// for `openrouter.ai`, `generic` for anything else.\nexport const modelProviderKindSchema = z.enum(['openrouter', 'ollama', 'lmstudio', 'generic']);\nexport type ModelProviderKind = z.infer<typeof modelProviderKindSchema>;\n\n// Read: the current provider. `configured` is true once a base URL is set;\n// `api_key_masked` is a masked placeholder when a key is stored, `null` for a\n// keyless local gateway. The key is never returned in clear.\n//\n// `source` + `env_override` say WHICH value is in effect and WHERE it came from\n// (STA-301 / D10). Environment precedence never changed \u2014 what changed is the\n// screen's honesty about it: when the box exports the credential, saving here\n// would write a stored value that silently contradicts the live one, so the\n// screen renders read-only and names the variables to unset.\n//\n// Both are OPTIONAL on purpose. `apps/api` upgrades independently of a\n// customer's BYOC box and validates whatever that box answered, so a stale box\n// that still sends only the three original fields must keep reading cleanly \u2014\n// the screen treats an absent `source` as \"editable, exactly as before\".\n//\n// `env_override` is a LIST because the key and the base URL resolve\n// INDEPENDENTLY (neutral name first, deprecated alias second): a box can\n// legitimately be winning with `MODEL_PROVIDER_BASE_URL` plus\n// `OPENROUTER_API_KEY`, and one string could not say so.\n// `effective_base_url` is the base URL a model call would use RIGHT NOW \u2014 the\n// exported one when the environment wins, the stored one otherwise. Without it\n// a screen whose box exports a different endpoint would show the saved value\n// and call it live, which is exactly the debugging trap this slice removes. The\n// key has no equivalent: it never leaves the box in clear, and `env_override`\n// already names the variable supplying it.\nexport const modelProviderGetResponseSchema = z.object({\n configured: z.boolean(),\n base_url: z.string().nullable(),\n api_key_masked: z.string().nullable(),\n source: modelProviderSourceSchema.optional(),\n env_override: z.array(z.string()).optional(),\n effective_base_url: z.string().nullable().optional(),\n // The gateway kind of the endpoint IN EFFECT (the one `effective_base_url`\n // names), resolved by the box; `null` when nothing is configured. Optional for\n // the same version-skew reason as `source`: a box that predates the field.\n kind: modelProviderKindSchema.nullable().optional(),\n // The slots where a Studio override beats an exported value (PLA-1825, ADR\n // D8), by SLOT name, never the variable's (D4). Such a slot drops out of\n // `env_override`, which keeps naming only the variables that actually win.\n // A box that knows overrides always sends the list, empty or not; an absent\n // list is a box that predates them, and would drop an `override` it cannot\n // read, so Studio offers no Override against it.\n overridden: z.array(modelProviderSlotSchema).optional(),\n});\nexport type ModelProviderGetResponse = z.infer<typeof modelProviderGetResponseSchema>;\n\n// Write / update: a base URL, an OPTIONAL API key and the gateway kind. An\n// omitted (or empty) key means \"local gateway, no key\" \u2014 the runtime persists\n// only the base URL and clears any previously-stored key. This same shape is the\n// \"test connection\" request body (a probe against `{base_url}/models` before\n// saving).\n//\n// `kind` is optional so a client that predates it keeps saving: the runtime then\n// clears any stored kind and the read resolves one from the host, so a kind saved\n// for an earlier endpoint never outlives it.\n//\n// `override` (PLA-1825) sets the per-slot override flag for the slots it names:\n// `true` makes the value saved here beat the exported one, `false` drops that.\n// Omitted, and for a slot it does not name, the stored flag stays as it was, so\n// a Studio that predates the field re-saves without undoing an override.\n// Overriding the key needs a key: a flag over nothing would override nothing.\nexport const modelProviderPutRequestSchema = z\n .object({\n base_url: z.url(),\n api_key: z.string().optional().nullable(),\n kind: modelProviderKindSchema.optional(),\n override: z\n .object({ baseUrl: z.boolean().optional(), apiKey: z.boolean().optional() })\n .optional(),\n })\n .refine((body) => body.override?.apiKey !== true || isModelProviderValuePresent(body.api_key), {\n message: 'Overriding the API key needs the key to use.',\n path: ['api_key'],\n });\nexport type ModelProviderPutRequest = z.infer<typeof modelProviderPutRequestSchema>;\n\n// How the workspace reacted to a save (STA-302). Saving no longer ends at the\n// secret store: the runtime updates its live environment and re-registers every\n// agent that was degraded, so an operator who just fixed the configuration is\n// told what actually came up instead of only that a form was submitted.\nexport const modelProviderAgentsOutcomeSchema = z.object({\n /** Agents that were degraded and are now serving. */\n ready: z.number().int().nonnegative(),\n /** Agents that are still degraded (their reason was refreshed by the retry). */\n degraded: z.number().int().nonnegative(),\n});\nexport type ModelProviderAgentsOutcome = z.infer<typeof modelProviderAgentsOutcomeSchema>;\n\n// Write / update RESPONSE: the fresh masked view (identical to the read) PLUS\n// the outcome of the re-registration the save triggered.\n//\n// `agents` is OPTIONAL, deliberately. The control-plane facade returns the\n// runtime's body verbatim and validates it, and there are legitimate runtimes\n// that cannot report counts: a BYOC box wired with no re-registration callback,\n// and any box built before this existed. A required field would make the facade\n// reject them. Absent means \"nothing was re-registered\", never \"zero came up\".\n//\n// It is a SEPARATE schema from the read on purpose \u2014 a GET can never fill these\n// counts, so folding them into the read would change the Studio catalogue's\n// httpResource type for a field it could never receive.\nexport const modelProviderPutResponseSchema = modelProviderGetResponseSchema.extend({\n agents: modelProviderAgentsOutcomeSchema.optional(),\n});\nexport type ModelProviderPutResponse = z.infer<typeof modelProviderPutResponseSchema>;\n\n// The guided first-run MARKER, as the Studio screen sees it (STA-306). The box\n// records \u2014 per DEPLOYMENT, not per user \u2014 that somebody has already been walked\n// through the setup; the boot gate reads it to know it must not hold the\n// workspace back again, and the first-run screen reads it to know it has stopped\n// being the first run. One boolean is the whole state: the marker carries a\n// decision only by existing, never by its value.\nexport const modelProviderFirstRunResponseSchema = z.object({\n completed: z.boolean(),\n});\nexport type ModelProviderFirstRunResponse = z.infer<typeof modelProviderFirstRunResponseSchema>;\n\n// Test connection: probe `GET {base_url}/models`. On success reports the number\n// of models the endpoint advertises; on failure reports the auth/transport\n// error. Works with an empty key (local gateways don't require one).\n//\n// `key_checked` reports what the probe could establish about the API KEY, which\n// `/models` alone does NOT prove: OpenRouter serves its catalogue publicly, so a\n// wrong key still returns 200 there and the operator would leave believing the\n// key works (it then fails at the first real model call). `verified` means the\n// endpoint accepted the key on an authenticated call; `unverified` means this\n// endpoint offers no way to check it; the rejected case comes back as\n// `ok: false` with the error.\nexport const modelProviderKeyCheckSchema = z.enum(['verified', 'unverified']);\nexport type ModelProviderKeyCheck = z.infer<typeof modelProviderKeyCheckSchema>;\n\nexport const modelProviderTestResponseSchema = z.object({\n ok: z.boolean(),\n model_count: z.number().int().nonnegative().optional(),\n error: z.string().optional(),\n key_checked: modelProviderKeyCheckSchema.optional(),\n});\nexport type ModelProviderTestResponse = z.infer<typeof modelProviderTestResponseSchema>;\n\n// Coarse model bucket, resolved per gateway kind in the box\n// (`libs/shared/model-provider-wire/src/lib/common/model-type.ts`). `other` is a\n// model known to be none of the three (TTS, transcription, moderation, a\n// reranker), or one nothing described: an Ollama or LM Studio model whose own API\n// did not answer, an OpenRouter entry with no `architecture`.\nexport const modelProviderModelTypeSchema = z.enum(['chat', 'image', 'embedding', 'other']);\nexport type ModelProviderModelType = z.infer<typeof modelProviderModelTypeSchema>;\n\n// One model advertised by the provider's OpenAI-compatible `/models`. Only `id`\n// is guaranteed \u2014 metadata richness varies wildly by provider (OpenRouter:\n// name / context_length / pricing; Ollama / LM Studio: little beyond id and\n// owned_by). Absent fields are omitted, never invented. Pricing values stay the\n// raw per-token strings the provider sends (no float parsing on the wire).\nexport const modelProviderModelSchema = z.object({\n id: z.string().min(1),\n name: z.string().optional(),\n context_length: z.number().int().positive().optional(),\n pricing: z\n .object({\n prompt: z.string().optional(),\n completion: z.string().optional(),\n })\n .optional(),\n owned_by: z.string().optional(),\n type: modelProviderModelTypeSchema.optional(),\n // What the gateway says the model can do (PLA-1605): `true`, `false`, or\n // `null` for \"this gateway does not say\". The three values are distinct on\n // purpose \u2014 Studio hides a model that reports no tool support and only WARNS\n // about one that reports nothing, so collapsing `null` into `false` would hide\n // every model on a custom OpenAI-compatible endpoint.\n //\n // Nullable AND optional: a box on an older runtime sends neither field, and\n // that must keep validating, exactly as `type` already does.\n tools: z.boolean().nullable().optional(),\n reasoning: z.boolean().nullable().optional(),\n // STA3-17: on a box with several providers, the one that serves this entry.\n // `responding: false` marks an entry remembered from a provider that is down.\n // Declared so the parse keeps it: an object schema strips what it does not name.\n provider: z.object({ id: z.string(), name: z.string(), responding: z.boolean() }).optional(),\n});\nexport type ModelProviderModel = z.infer<typeof modelProviderModelSchema>;\n\n/**\n * Can this entry serve a text call (a workflow step, an agent turn, a judge)?\n *\n * Only a model KNOWN to be image or embedding is refused. `other` (and an absent\n * `type`) is offered under a warning \u2014 the same three-state rule `tools`\n * follows: an Ollama or LM Studio whose own API did not answer types every model\n * `other`, so refusing it would empty every picker on that gateway.\n */\nexport const canServeText = (model: ModelProviderModel): boolean =>\n model.type !== 'image' && model.type !== 'embedding';\n\n/** The gateway did not say what the model produces. Surfaces warn, never hide. */\nexport const modelTypeUnknown = (model: ModelProviderModel): boolean =>\n model.type === undefined || model.type === 'other';\n\n// List models: with a `base_url` the runtime probes that DRAFT endpoint (same\n// semantics as test-connection: empty key = keyless gateway); with no\n// `base_url` it lists the SAVED provider, resolving the stored base URL + key\n// server-side \u2014 so the plaintext key never needs to reach the browser.\nexport const modelProviderModelsRequestSchema = z.object({\n base_url: z.url().optional().nullable(),\n api_key: z.string().optional().nullable(),\n});\nexport type ModelProviderModelsRequest = z.infer<typeof modelProviderModelsRequestSchema>;\n\n// Probe failures (endpoint down, bad key, nothing configured) ride back as\n// `ok: false` DATA \u2014 mirroring the test-connection response \u2014 so the screen\n// renders a non-blocking error state instead of a thrown transport error.\n// `warning` rides beside an `ok: true` listing that is missing providers: a box\n// that cannot read its extra providers still lists provider 1, which model calls\n// fall back to (STA3-17 D9).\nexport const modelProviderModelsResponseSchema = z.object({\n ok: z.boolean(),\n models: z.array(modelProviderModelSchema).optional(),\n error: z.string().optional(),\n warning: z.string().optional(),\n});\nexport type ModelProviderModelsResponse = z.infer<typeof modelProviderModelsResponseSchema>;\n\n// ---------------------------------------------------------------------------\n// The provider LIST (STA3-17 S2): provider 1 plus the extra providers saved\n// beside it, each routed to by the model ids its catalogue lists.\n\n/** Provider 1, today's `MODEL_PROVIDER_*` credential, under a fixed id. */\nexport const MODEL_PROVIDER_DEFAULT_ID = 'default';\n\n/**\n * An extra provider's id, its slug. It names the provider's rows with `-` as\n * `_`, which is one-to-one only because `_` is refused, and `::` is refused\n * because it separates the provider from the model in a box-issued pin.\n */\nexport const MODEL_PROVIDER_ID_PATTERN = /^[a-z][a-z0-9-]{0,30}[a-z0-9]$/;\n\nexport const modelProviderIdSchema = z\n .string()\n .regex(MODEL_PROVIDER_ID_PATTERN, {\n message: 'Lowercase letters, digits and dashes, 2 to 32, starting with a letter.',\n })\n .refine((id) => id !== MODEL_PROVIDER_DEFAULT_ID, {\n message: `'${MODEL_PROVIDER_DEFAULT_ID}' is provider 1's id.`,\n });\n\n/**\n * The provider a model pick goes to (STA3-17 D10): `default` for provider 1, else\n * an extra provider's slug. The bare pattern, not `modelProviderIdSchema`, which\n * refuses `default` because no EXTRA provider may take it; a pick may pin provider 1.\n * Not checked against the provider list, for the reason a picked model id is not.\n */\nexport const pickedModelProviderIdSchema = z.string().regex(MODEL_PROVIDER_ID_PATTERN);\n\n/** Between the provider and the model in a pinned model id (STA3-17 D3). */\nexport const MODEL_PROVIDER_PIN_SEPARATOR = '::';\n\n/** A model id pinned to the provider that serves it. */\nexport interface PinnedModelId {\n readonly providerId: string;\n readonly modelId: string;\n}\n\n/**\n * The id a pick hands to `ChatOpenAI`: `<providerId>::<modelId>`, or the model\n * id alone when the pick names no provider, so a pick made before STA3-17 keeps\n * sending today's plain id. Whoever reads a pick writes it, and the box proxy\n * takes it apart with {@link unpinModelId} before any provider sees it.\n */\nexport const pinModelId = (providerId: string | null | undefined, modelId: string): string =>\n providerId ? `${providerId}${MODEL_PROVIDER_PIN_SEPARATOR}${modelId}` : modelId;\n\n/**\n * The provider and model of a pinned id, or `null` for a plain id, which no\n * reader rewrites. A pin is everything before the FIRST `::` matching\n * {@link MODEL_PROVIDER_ID_PATTERN}, then a model id that is not empty and does\n * not start with `:`. Real ids carry `:` tags and `/` paths but never a slug\n * then `::`; one that does (`fe80::1`) reads as a pin, on purpose: the one\n * matcher stays a pattern rather than a list of exceptions.\n */\nexport const unpinModelId = (id: string): PinnedModelId | null => {\n const at = id.indexOf(MODEL_PROVIDER_PIN_SEPARATOR);\n if (at < 0) return null;\n const providerId = id.slice(0, at);\n const modelId = id.slice(at + MODEL_PROVIDER_PIN_SEPARATOR.length);\n if (!MODEL_PROVIDER_ID_PATTERN.test(providerId)) return null;\n if (modelId === '' || modelId.startsWith(':')) return null;\n return { providerId, modelId };\n};\n\n// One row of the list. The key is masked like provider 1's view, `null` for a\n// keyless gateway. `source: 'env'` is provider 1 exported by the box, which\n// Studio cannot remove (D6). `responding` is `null` for a provider never read.\n// `error` is why the last read failed (a 401 on a bad key), so the operator\n// sees more than \"not responding\".\nexport const modelProviderListItemSchema = z.object({\n id: z.string(),\n name: z.string(),\n kind: modelProviderKindSchema,\n baseUrl: z.string(),\n apiKey: z.string().nullable(),\n source: z.enum(['env', 'local']),\n removable: z.boolean(),\n responding: z.boolean().nullable(),\n error: z.string().optional(),\n});\nexport type ModelProviderListItem = z.infer<typeof modelProviderListItemSchema>;\n\n// The list, provider 1 first, with every model id two or more providers serve:\n// a call naming one is refused as ambiguous, so Studio warns about the overlap.\nexport const modelProviderListResponseSchema = z.object({\n providers: z.array(modelProviderListItemSchema),\n duplicatedIds: z.array(z.object({ modelId: z.string(), providerIds: z.array(z.string()) })),\n});\nexport type ModelProviderListResponse = z.infer<typeof modelProviderListResponseSchema>;\n\nconst providerFields = {\n name: z.string().trim().min(1),\n kind: modelProviderKindSchema,\n baseUrl: z.url(),\n apiKey: z.string().optional(),\n};\n\n// Add an extra provider. An omitted or blank key is a keyless gateway.\nexport const modelProviderCreateRequestSchema = z.strictObject({\n id: modelProviderIdSchema,\n ...providerFields,\n});\nexport type ModelProviderCreateRequest = z.infer<typeof modelProviderCreateRequestSchema>;\n\n// Edit an extra provider; the id is the path's. The read masks the key, so an\n// omitted `apiKey` keeps the stored one and `''` clears it.\nexport const modelProviderUpdateRequestSchema = z.strictObject(providerFields);\nexport type ModelProviderUpdateRequest = z.infer<typeof modelProviderUpdateRequestSchema>;\n", "import { z } from 'zod';\nimport { integrationIdentitiesSchema } from './memory-identities';\nimport { ragCollectionNameParamSchema } from './studio-rag';\nimport {\n RUN_TAG_VALUE_MAX,\n RUN_TAGS_MAX,\n runTagKeySchema,\n runTagValueSchema,\n} from '../agents/runs';\n\n// Voice settings (FDE-62 D2): how a deep agent declared with `voice: true` hears\n// and speaks on a call. The model, prompt and tools stay the agent's own; these\n// settings only shape the LiveKit worker around it.\n//\n// Two schemas over the same fields:\n// - `voiceSettingsSchema` is PARTIAL: what code (`defineDeepAgent({ voiceSettings })`)\n// and Studio each declare. Every field, and every leaf of a nested group, is optional.\n// - `effectiveVoiceSettingsSchema` is COMPLETE: the merge, Studio > code > box\n// default. Every leaf is required. The dispatch carries it without the box-only\n// `recording`, `fastModelRouting` and `rag` (`callVoiceSettingsSchema`).\n//\n// Both are STRICT at every level: an unknown key is a typo, and accepting it would\n// look like a saved setting that changes nothing. Pure Zod: browser bundles import it.\n\n/**\n * The STT models the worker builds, all on LiveKit Inference (D4: no provider keys).\n * The ids the voice spike ran, plus AssemblyAI Universal-3.6 Pro to compare against\n * Flux; extend this list, nowhere else.\n */\nexport const VOICE_STT_MODELS = [\n 'deepgram/flux-general-multi',\n 'deepgram/nova-3',\n 'assemblyai/universal-3-6-pro',\n] as const;\nexport type VoiceSttModel = (typeof VOICE_STT_MODELS)[number];\n\n/**\n * The TTS models the worker builds, all on LiveKit Inference. Only the ids the voice\n * spike measured. The voice is a separate free id (`ttsVoice`), not a `model:voice`\n * suffix.\n */\nexport const VOICE_TTS_MODELS = [\n 'cartesia/sonic-3.6',\n 'deepgram/aura-2',\n 'gradium/default',\n] as const;\nexport type VoiceTtsModel = (typeof VOICE_TTS_MODELS)[number];\n\n/** Whether `model` is a Cartesia model of the known list: the only TTS with an expressive markup dialect. */\nexport const isCartesiaTtsModel = (model: string): boolean =>\n model.startsWith('cartesia/') && (VOICE_TTS_MODELS as readonly string[]).includes(model);\n\n/** A probability the VAD or the turn detector compares against. */\nconst thresholdSchema = z.number().min(0).max(1);\n\n/** A delay or duration. Ten seconds is past any value a live call can use. */\nconst millisecondsSchema = z.int().min(0).max(10_000);\n\n/**\n * Words the STT must not mishear (brand and product names). Trimmed, so a list\n * pasted with spaces keeps its terms.\n */\nconst keytermsSchema = z.array(z.string().trim().min(1).max(64)).max(50);\n\n/** Silero VAD: when the caller counts as speaking or silent. */\nconst vadSchema = z.strictObject({\n activationThreshold: thresholdSchema,\n minSpeechMs: millisecondsSchema,\n minSilenceMs: millisecondsSchema,\n});\n\n/**\n * End of turn. `stt` lets an STT that ends turns itself decide (Flux); `livekit`\n * runs LiveKit's turn detector at `detectorThreshold`. The endpointing delays are\n * the shortest and longest wait once the caller stops.\n */\nconst turnSchema = z.strictObject({\n mode: z.enum(['stt', 'livekit']),\n minEndpointingDelayMs: millisecondsSchema,\n maxEndpointingDelayMs: millisecondsSchema,\n detectorThreshold: thresholdSchema,\n});\n\n/** Barge-in: whether, and after how much speech, the caller cuts the agent off. */\nconst interruptionsSchema = z.strictObject({\n enabled: z.boolean(),\n minDurationMs: millisecondsSchema,\n minWords: z.int().min(0).max(20),\n});\n\n/**\n * The knowledge search before each reply (FDE-67): the box searches `collection`\n * once per caller turn of at least `minWords` words, keeps the `topK` best passages\n * that score `minScore` or more (cosine similarity), and gives up after `timeoutMs`.\n * A `null` collection turns the search off. One collection per agent (D3): scores\n * from collections with different embedding models are not comparable.\n */\nconst ragSchema = z.strictObject({\n collection: ragCollectionNameParamSchema.nullable(),\n minScore: z.number().min(0).max(1),\n minWords: z.int().min(0).max(20),\n topK: z.int().min(1).max(10),\n timeoutMs: z.int().min(200).max(10_000),\n});\n\n/**\n * The complete settings WITHOUT the cross-field rules. Two layers can each be valid\n * and still merge into a pair the rules refuse (Studio's minimum delay over code's\n * maximum); a response schema embeds this one so a read describes that value\n * instead of failing. Anything that dispatches a call checks the merge with\n * `effectiveVoiceSettingsSchema`, and the dispatch with `callVoiceSettingsSchema`.\n */\nexport const effectiveVoiceSettingsObjectSchema = z.strictObject({\n /** BCP-47-ish tag the STT is hinted with and the TTS speaks (`es`, `en-US`), or `multi`. */\n language: z\n .string()\n .max(16)\n .regex(/^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/),\n sttModel: z.enum(VOICE_STT_MODELS),\n keyterms: keytermsSchema.default(() => []),\n vad: vadSchema,\n turn: turnSchema,\n interruptions: interruptionsSchema,\n ttsModel: z.enum(VOICE_TTS_MODELS),\n /**\n * The provider's own voice id (a Cartesia UUID, an ElevenLabs id, a Deepgram name),\n * so only its outline is checked: no surrounding whitespace, no control characters.\n */\n ttsVoice: z\n .string()\n .max(128)\n .regex(/^[^\\s\\p{Cc}](?:[^\\p{Cc}]*[^\\s\\p{Cc}])?$/u),\n /** Cartesia only (D18): another model keeps its own pace, so it takes only 1. */\n ttsSpeed: z.number().min(0.5).max(2),\n /**\n * What the agent says when the call connects. `null`: it waits for the caller. A\n * blank string is refused rather than read as either: it would be an empty\n * utterance, not a wait.\n */\n greeting: z.string().trim().min(1).max(500).nullable(),\n /** Band-pass the agent's voice to 300-3400 Hz so it sounds like a phone line (D5). */\n phoneLine: z.boolean(),\n /** LiveKit expressive mode: the turn gets Cartesia's markup guide (D5). Cartesia only. */\n expressive: z.boolean(),\n /**\n * Whether the box records the call to its bucket (D30). Off, no egress starts: a\n * box may not be allowed to keep a caller's audio. The box alone reads it, so the\n * dispatch to the worker leaves it out (`callVoiceSettingsSchema`).\n */\n recording: z.boolean(),\n /**\n * Ask OpenRouter to route this agent's model calls to the provider with the\n * highest throughput (`provider: { sort: 'throughput' }`) instead of its default\n * price routing. Gemma 4 31B on price routing landed on a 13 tok/s provider (5 s\n * a turn); throughput routing answered in about 1 s. A request field, not a\n * `:nitro` model suffix, so the model id the box checks and prices stays the\n * one written. The box strips it off any other gateway. The SDK applies it when\n * it builds the graph, so the worker never reads it.\n */\n fastModelRouting: z.boolean(),\n /** The box runs the search before the model's turn, so the worker never reads it. */\n rag: ragSchema,\n});\n\n/** The fields the cross-field rules read, optional so one check serves both schemas. */\ninterface CrossFieldVoiceSettings {\n turn?:\n | { minEndpointingDelayMs?: number | undefined; maxEndpointingDelayMs?: number | undefined }\n | undefined;\n ttsModel?: string | undefined;\n ttsSpeed?: number | undefined;\n expressive?: boolean | undefined;\n}\n\nconst checkCrossFieldRules = (value: CrossFieldVoiceSettings, ctx: z.RefinementCtx): void => {\n const min = value.turn?.minEndpointingDelayMs;\n const max = value.turn?.maxEndpointingDelayMs;\n if (min !== undefined && max !== undefined && min > max) {\n ctx.addIssue({\n code: 'custom',\n path: ['turn', 'minEndpointingDelayMs'],\n message: 'turn.minEndpointingDelayMs must not exceed turn.maxEndpointingDelayMs',\n });\n }\n // A partial without a ttsModel passes: the model may come from another layer,\n // and the merged value is checked again by the effective schema.\n if (\n value.expressive === true &&\n value.ttsModel !== undefined &&\n !isCartesiaTtsModel(value.ttsModel)\n ) {\n ctx.addIssue({\n code: 'custom',\n path: ['expressive'],\n message: 'expressive needs a Cartesia ttsModel',\n });\n }\n // D18: the worker sends a speed to a Cartesia model only, so on another one a speed\n // other than 1 would look saved and change nothing. Same layering as `expressive`.\n if (\n value.ttsSpeed !== undefined &&\n value.ttsSpeed !== 1 &&\n value.ttsModel !== undefined &&\n !isCartesiaTtsModel(value.ttsModel)\n ) {\n ctx.addIssue({\n code: 'custom',\n path: ['ttsSpeed'],\n message: 'ttsSpeed other than 1 needs a Cartesia ttsModel',\n });\n }\n};\n\nexport const effectiveVoiceSettingsSchema =\n effectiveVoiceSettingsObjectSchema.superRefine(checkCrossFieldRules);\nexport type EffectiveVoiceSettings = z.infer<typeof effectiveVoiceSettingsSchema>;\n\n/**\n * The effective settings as a call carries them to the worker: every leaf but\n * `recording` (D30), `fastModelRouting` and `rag` (FDE-67), which only the box and\n * the agent's graph read. The worker parses the dispatch strictly, and workers already\n * deployed on LiveKit Cloud would refuse every call whose settings held a key\n * they do not know.\n */\nconst callVoiceSettingsObjectSchema = effectiveVoiceSettingsObjectSchema.omit({\n recording: true,\n fastModelRouting: true,\n rag: true,\n});\nexport const callVoiceSettingsSchema =\n callVoiceSettingsObjectSchema.superRefine(checkCrossFieldRules);\nexport type CallVoiceSettings = z.infer<typeof callVoiceSettingsSchema>;\n\n/** The frozen `VOICE_SETTINGS_DEFAULTS`' type, so `toCallVoiceSettings` takes it as well as a merge. */\nexport type ReadonlyEffectiveVoiceSettings = {\n readonly [K in keyof EffectiveVoiceSettings]: Readonly<EffectiveVoiceSettings[K]>;\n};\n\n/** `keyterms` is copied because a frozen holder's readonly list is not the call's list type. */\nexport const toCallVoiceSettings = ({\n recording: _recording,\n fastModelRouting: _fastModelRouting,\n rag: _rag,\n ...call\n}: ReadonlyEffectiveVoiceSettings): CallVoiceSettings => ({\n ...call,\n keyterms: [...call.keyterms],\n});\n\n/** The leaf paths of a settings object: a nested group's leaves as `group.leaf`, an array as one leaf. */\ntype LeafPathOf<T> = {\n [K in keyof T & string]: T[K] extends readonly unknown[]\n ? K\n : T[K] extends object\n ? `${K}.${keyof T[K] & string}`\n : K;\n}[keyof T & string];\n\n/**\n * Every leaf of the effective settings, the unit the merge credits to one layer. A\n * nested group merges leaf by leaf (`vad.minSilenceMs` from code keeps the default\n * `vad.activationThreshold`); `keyterms` is one leaf, replaced whole.\n */\nexport const VOICE_SETTINGS_LEAF_PATHS = [\n 'language',\n 'sttModel',\n 'keyterms',\n 'vad.activationThreshold',\n 'vad.minSpeechMs',\n 'vad.minSilenceMs',\n 'turn.mode',\n 'turn.minEndpointingDelayMs',\n 'turn.maxEndpointingDelayMs',\n 'turn.detectorThreshold',\n 'interruptions.enabled',\n 'interruptions.minDurationMs',\n 'interruptions.minWords',\n 'ttsModel',\n 'ttsVoice',\n 'ttsSpeed',\n 'greeting',\n 'phoneLine',\n 'expressive',\n 'recording',\n 'fastModelRouting',\n 'rag.collection',\n 'rag.minScore',\n 'rag.minWords',\n 'rag.topK',\n 'rag.timeoutMs',\n] as const satisfies readonly LeafPathOf<EffectiveVoiceSettings>[];\nexport type VoiceSettingsLeafPath = (typeof VOICE_SETTINGS_LEAF_PATHS)[number];\n\n/**\n * D13: the TTS model and its voice, the two leaves Studio saves together and\n * resets together (a voice id belongs to one provider). Typed as leaf paths, not\n * a literal tuple, so `.includes(path)` takes any leaf path.\n */\nexport const VOICE_TTS_PAIR: readonly VoiceSettingsLeafPath[] = ['ttsModel', 'ttsVoice'];\n\n/** Where a leaf's effective value came from (D2 precedence: Studio > code > box default). */\nexport const voiceSettingOriginSchema = z.enum(['studio', 'code', 'default']);\nexport type VoiceSettingOrigin = z.infer<typeof voiceSettingOriginSchema>;\n\n/**\n * The effective settings as a read shows them: the merged value and, per leaf, the\n * layer it came from. `value` skips the cross-field rules so a merge they refuse\n * is still described rather than failing the read.\n */\nexport const effectiveVoiceSettingsViewSchema = z.strictObject({\n value: effectiveVoiceSettingsObjectSchema,\n origins: z.strictObject(\n Object.fromEntries(\n VOICE_SETTINGS_LEAF_PATHS.map((path) => [path, voiceSettingOriginSchema]),\n ) as Record<VoiceSettingsLeafPath, typeof voiceSettingOriginSchema>,\n ),\n});\nexport type EffectiveVoiceSettingsView = z.infer<typeof effectiveVoiceSettingsViewSchema>;\n\n/**\n * D13: a voice id belongs to one provider, so a layer that picks the TTS model picks\n * its voice too. Otherwise the merge would pair the model with a lower layer's voice\n * (a Cartesia id on a Deepgram model: no audio). A voice alone is fine: it keeps the\n * lower layer's model. The effective schema needs no such rule, both leaves are required.\n */\nconst checkTtsPair = (\n value: { ttsModel?: string | undefined; ttsVoice?: string | undefined },\n ctx: z.RefinementCtx,\n): void => {\n if (value.ttsModel !== undefined && value.ttsVoice === undefined) {\n ctx.addIssue({\n code: 'custom',\n path: ['ttsVoice'],\n message: 'ttsModel needs a ttsVoice of that provider beside it',\n });\n }\n};\n\n/**\n * What one layer declares. `keyterms` drops the effective default: a filled-in `[]`\n * would be credited to that layer by the merge.\n */\nexport const voiceSettingsSchema = effectiveVoiceSettingsObjectSchema\n .extend({\n keyterms: keytermsSchema,\n vad: vadSchema.partial(),\n turn: turnSchema.partial(),\n interruptions: interruptionsSchema.partial(),\n rag: ragSchema.partial(),\n })\n .partial()\n .superRefine(checkCrossFieldRules)\n .superRefine(checkTtsPair);\nexport type VoiceSettings = z.infer<typeof voiceSettingsSchema>;\n\n/**\n * D13 the other way round, for the Studio layer only (FDE-62 S5): a voice saved\n * alone would sit over whatever model code switches to later (a Cartesia id on a\n * Deepgram model). Code may still declare a voice alone and keep the box's model.\n */\nconst checkStudioTtsPair = (\n value: { ttsModel?: string | undefined; ttsVoice?: string | undefined },\n ctx: z.RefinementCtx,\n): void => {\n if (value.ttsVoice !== undefined && value.ttsModel === undefined) {\n ctx.addIssue({\n code: 'custom',\n path: ['ttsModel'],\n message: 'ttsVoice needs the ttsModel it was picked for beside it',\n });\n }\n};\n\n/**\n * What a Studio save declares (`PUT /api/voice/agents/{name}/settings`): one layer,\n * so `voiceSettingsSchema`, with the TTS model and voice always saved together.\n * Strict like it, so `__proto__`, `constructor` and a typo are refused.\n */\nexport const studioVoiceSettingsPatchSchema = voiceSettingsSchema.superRefine(checkStudioTtsPair);\nexport type StudioVoiceSettingsPatch = z.infer<typeof studioVoiceSettingsPatchSchema>;\n\n/**\n * One agent's voice settings as the Studio settings routes answer them: the merged\n * value, the layer of each leaf, and `issue`, why a call cannot carry the value\n * (`path: message`), or `null` when it can. A stored override can stop combining\n * after a later code change, so the read describes it instead of failing.\n */\nexport const voiceSettingsResponseSchema = effectiveVoiceSettingsViewSchema.extend({\n issue: z.string().nullable(),\n});\nexport type VoiceSettingsResponse = z.infer<typeof voiceSettingsResponseSchema>;\n\n/**\n * The first issue of a failed voice settings parse (one layer or the merged value),\n * as `path: message`. One field fixed at a time reads better than every rule the\n * object broke.\n */\nexport const describeVoiceSettingsIssue = (error: z.ZodError): string => {\n const [issue] = error.issues;\n if (!issue) return error.message;\n return issue.path.length > 0 ? `${issue.path.join('.')}: ${issue.message}` : issue.message;\n};\n\n/**\n * The deep agent a call talks to (D11). The box's registry has no name rule of its\n * own: a name is the agent's folder basename. This takes every slug the CLI\n * scaffolder writes (`PIECE_NAME_RE` in `apps/cli`: lowercase letters, digits and\n * hyphens after a leading letter or digit), plus `_`, capped at 64 like the CLI's\n * workspace names and `connectorIdSchema`. Not trimmed: a padded name is not the\n * folder's.\n */\nexport const deepAgentNameSchema = z.string().regex(/^[a-z0-9][a-z0-9_-]{0,63}$/);\n\n/**\n * A call's room name (`call-<agent>-<hex>`) or session key (`voice-<room>`, the\n * `x-stackbone-session` the worker sends each turn). Bounded like a tag value: the\n * room rides in the `room` tag.\n */\nconst voiceCallIdSchema = z.string().min(1).max(RUN_TAG_VALUE_MAX);\n\n/** The tags every run of the call carries (`channel`, `room`, `origin`). */\nconst voiceTagsSchema = z\n .record(runTagKeySchema, runTagValueSchema)\n .refine((tags) => Object.keys(tags).length <= RUN_TAGS_MAX, {\n message: `A call carries at most ${RUN_TAGS_MAX} tags.`,\n });\n\n/**\n * The `description` of the AG-UI `context` entry in which every turn of an `expressive`\n * call carries its TTS's markup guide (FDE-62 S5 t6). The worker writes it; the box\n * reads it, matched exactly, only for a `voice: true` agent.\n */\nexport const VOICE_TTS_MARKUP_CONTEXT = 'voice-tts-markup';\n\n/** One markup guide the box accepts: the SHA-256 (hex) of its UTF-8 text and its length in UTF-16 code units. */\nexport interface VoiceTtsMarkupGuideDigest {\n readonly sha256: string;\n readonly length: number;\n}\n\n/**\n * D19: the only {@link VOICE_TTS_MARKUP_CONTEXT} texts the box puts in a system\n * prompt, as digests (the guides are LiveKit's text; this package imports no LiveKit\n * and no `node:crypto`). Every guide `@livekit/agents` 1.9.0 gives a Cartesia model\n * of `VOICE_TTS_MODELS` with LiveKit's default speech steering; they all share one\n * text. The voice worker's `session-options.spec.ts` regenerates them from the\n * installed LiveKit and fails when this list drifts, so a LiveKit bump updates it here.\n */\nexport const VOICE_TTS_MARKUP_GUIDES: readonly VoiceTtsMarkupGuideDigest[] = [\n { sha256: '59b8c4ef7922d8a7fbe3c92c746e4daa99a7ff7a52665e0875e2fc5efdaf5c1f', length: 3103 },\n];\n\n/**\n * Whether `raw` is an absolute http(s) URL the worker can append `/agui/...` to: the\n * scheme spelled out with `//`, a host, no user part (the call token would go to\n * whoever it names) and no space, query or fragment (each would swallow the suffix).\n * Checked on the raw text as well as the parsed URL, because the WHATWG parser trims\n * spaces and drops an empty `?`.\n */\nconst isVoiceBoxUrl = (raw: string): boolean => {\n if (!/^https?:\\/\\//i.test(raw) || /[\\s?#]/.test(raw)) return false;\n try {\n const url = new URL(raw);\n return url.hostname !== '' && url.username === '' && url.password === '';\n } catch {\n return false;\n }\n};\n\n/** The longest box URL a dispatch carries. */\nconst VOICE_BOX_URL_MAX = 2048;\n\n/**\n * The box's public origin as the dispatch carries it (FDE-62 S10), with an optional\n * path prefix: the worker posts to `<boxUrl>/agui/v1/agents/<agent>`. The room route\n * checks the box's own public URL with it before it mints anything.\n */\nexport const voiceBoxUrlSchema = z.string().max(VOICE_BOX_URL_MAX).refine(isVoiceBoxUrl, {\n message: 'boxUrl must be an absolute http(s) URL with no user part, query or fragment.',\n});\n\n/**\n * Where the voice worker posts a traced call's spans, under the dispatch's `boxUrl`\n * (FDE-62 S11): the box's relay to its own OTLP collector, outside `/api`.\n */\nexport const VOICE_TRACES_PATH = '/voice/v1/traces';\n\n/**\n * The LiveKit `agentName` the voice worker of a deployed box registers under and the\n * box's room token dispatches (explicit dispatch only). A `stackbone dev` process\n * registers a name of its own instead (D36), passed in {@link VOICE_WORKER_AGENT_NAME_ENV}.\n */\nexport const VOICE_WORKER_AGENT_NAME = 'stackbone-voice';\n\n/** The env var `stackbone dev` sets to the per-process name its box dispatches (D36). */\nexport const VOICE_WORKER_AGENT_NAME_ENV = 'STACKBONE_VOICE_AGENT_NAME';\n\n/** The env var `stackbone dev` sets to the loopback port of the worker's health server (D40). */\nexport const VOICE_WORKER_PORT_ENV = 'STACKBONE_VOICE_WORKER_PORT';\n\n/** The longest call token a dispatch carries: a JWT of a few hundred characters fits many times. */\nexport const VOICE_CALL_TOKEN_MAX = 2048;\n\n/**\n * What the box hands the LiveKit worker in the room token's agent dispatch metadata:\n * the agent to talk to, the settings the call runs with, the session every turn lands\n * in and the tags each turn's run carries, plus where the box is and the bearer every\n * turn of this call presents (FDE-62 S10). Strict: the worker reads it as-is.\n */\nexport const voiceDispatchSchema = z.strictObject({\n agent: deepAgentNameSchema,\n /**\n * The merged value (Studio > code > box default), cross-field rules included,\n * without the box-only leaves (`callVoiceSettingsSchema`).\n */\n voiceSettings: callVoiceSettingsSchema,\n sessionKey: voiceCallIdSchema,\n tags: voiceTagsSchema,\n boxUrl: voiceBoxUrlSchema,\n /** The call token: the bearer on every AG-UI request of this call, for this agent and session only. */\n callToken: z\n .string()\n .max(VOICE_CALL_TOKEN_MAX)\n .regex(/^\\S+$/, { message: 'callToken must be a non-empty token with no whitespace.' }),\n /**\n * Set only when the box exports traces to an OTLP endpoint of its own (FDE-62 S11):\n * the worker then sends each call's spans to `<boxUrl>` + {@link VOICE_TRACES_PATH} with the\n * call token. Off is the absent key, never `false`.\n */\n traces: z.literal(true).optional(),\n});\nexport type VoiceDispatch = z.infer<typeof voiceDispatchSchema>;\n\n/**\n * `POST /api/voice/rooms`: open a call to one agent. `origin` lands in the `origin`\n * tag; the caller states it (Studio leaves it out), so it is a label to filter by,\n * not who placed the call.\n */\nexport const voiceRoomRequestSchema = z.strictObject({\n agent: deepAgentNameSchema,\n origin: runTagKeySchema.default('studio'),\n identities: integrationIdentitiesSchema.optional(),\n});\nexport type VoiceRoomRequest = z.infer<typeof voiceRoomRequestSchema>;\n\n/**\n * The call the box opened: the LiveKit URL to dial and the browser's room token,\n * the room, agent and session key the turns land in, and the settings the call runs\n * with. The LiveKit key and secret never appear: strict, so a field carrying one is\n * refused. The settings are the dispatch's (no box-only leaf), and skip the\n * cross-field rules so a read describes the value instead of failing on it; the\n * dispatch already held them.\n */\nexport const voiceRoomResponseSchema = z.strictObject({\n url: z.string().min(1),\n token: z.string().min(1),\n room: voiceCallIdSchema,\n agent: deepAgentNameSchema,\n sessionKey: voiceCallIdSchema,\n voiceSettings: callVoiceSettingsObjectSchema,\n});\nexport type VoiceRoomResponse = z.infer<typeof voiceRoomResponseSchema>;\n\n/**\n * Where a call's recording stands (FDE-62 S12): `recording` from the room's opening\n * until LiveKit reports the egress ended, then `ready` to play or `failed`. The\n * `voice_recordings` CHECK admits exactly these.\n */\nexport const VOICE_RECORDING_STATUSES = ['recording', 'ready', 'failed'] as const;\nexport type VoiceRecordingStatus = (typeof VOICE_RECORDING_STATUSES)[number];\n\n/**\n * `GET /api/voice/recordings?session_id=`: the session whose latest call to read. A\n * UUID, the `sessions.id` the room route derived for the call.\n */\nexport const voiceRecordingQuerySchema = z.object({ session_id: z.uuid() });\nexport type VoiceRecordingQuery = z.infer<typeof voiceRecordingQuerySchema>;\n\n/**\n * How long a ready recording's presigned read lives (D32): the 60 s preview window\n * broke seeks and buffer refills on long calls.\n */\nexport const VOICE_RECORDING_URL_TTL_SECONDS = 3600;\n\n/**\n * The session's latest call recording. `url` is a presigned read of the audio valid\n * {@link VOICE_RECORDING_URL_TTL_SECONDS}, set only while `status` is `ready`; `null`\n * with `expires_at` otherwise.\n */\nexport const voiceRecordingResponseSchema = z.strictObject({\n room: voiceCallIdSchema,\n status: z.enum(VOICE_RECORDING_STATUSES),\n url: z.string().nullable(),\n expires_at: z.iso.datetime().nullable(),\n started_at: z.iso.datetime(),\n ended_at: z.iso.datetime().nullable(),\n});\nexport type VoiceRecordingResponse = z.infer<typeof voiceRecordingResponseSchema>;\n\n/**\n * `GET /api/voice/status` (FDE-62 S13): whether the box reached LiveKit with its saved\n * credentials, and when it last asked. It says nothing about a voice worker (D34): no\n * LiveKit API lists registered workers. `configured` is always `true`: a box without\n * readable credentials answers 503 `voice_not_configured` instead. Strict, so no\n * LiveKit error text can ride along.\n */\nexport const voiceStatusResponseSchema = z.strictObject({\n configured: z.literal(true),\n reachable: z.boolean(),\n checkedAt: z.iso.datetime(),\n});\nexport type VoiceStatusResponse = z.infer<typeof voiceStatusResponseSchema>;\n\n/** The longest heard text, in UTF-16 code units: well past what one voice reply says. */\nexport const HEARD_TEXT_MAX = 4000;\n\n/**\n * `forwardedProps.heard` on a fresh AG-UI turn (FDE-62 S9): the part of the reply to\n * the thread's last user message the caller really heard before a barge-in cut it.\n * The voice worker sends it; the box rewrites that reply to it before the turn runs,\n * and refuses a text that is not an exact prefix of the reply. No message id: the box\n * targets the reply by its position after the last user message. Strict.\n */\nexport const aguiHeardSchema = z.strictObject({\n text: z.string().min(1).max(HEARD_TEXT_MAX),\n});\nexport type AguiHeard = z.infer<typeof aguiHeardSchema>;\n\nexport const VOICE_CALLS_PATH = '/voice/v1/calls';\nexport const voiceCallBootstrapSchema = z.strictObject({\n identities: integrationIdentitiesSchema.optional(),\n});\nexport type VoiceCallBootstrap = z.infer<typeof voiceCallBootstrapSchema>;\nexport const voiceCallTranscriptSchema = z\n .array(\n z.strictObject({\n role: z.enum(['user', 'assistant']),\n text: z.string().trim().min(1).max(16_000),\n }),\n )\n .max(1000)\n .refine((items) => items.reduce((size, item) => size + item.text.length, 0) <= 200_000, {\n message: 'A call transcript may contain at most 200000 characters.',\n });\nexport const voiceCallCompleteSchema = z.strictObject({\n transcript: voiceCallTranscriptSchema,\n outcome: z.enum(['completed', 'disconnected', 'failed']),\n});\nexport type VoiceCallComplete = z.infer<typeof voiceCallCompleteSchema>;\nexport const voiceCallLifecycleResponseSchema = z.strictObject({ ok: z.literal(true) });\n", "import { z } from 'zod';\n\nexport const identityKeySchema = z.string().min(1).max(100);\nconst identityValueSchema = z.string().min(1).max(500);\n\n// z.record discards __proto__; integration field names are literal own keys.\nexport const integrationIdentitiesSchema = z\n .unknown()\n .superRefine((input, ctx) => {\n if (input === null || typeof input !== 'object' || Array.isArray(input)) {\n ctx.addIssue({ code: 'custom', message: 'Identities must be a string map.' });\n return;\n }\n const entries = Object.entries(input);\n if (entries.length > 32)\n ctx.addIssue({ code: 'custom', message: 'At most 32 identity fields are allowed.' });\n for (const [key, value] of entries) {\n if (\n !identityKeySchema.safeParse(key).success ||\n !identityValueSchema.safeParse(value).success\n )\n ctx.addIssue({\n code: 'custom',\n path: [key],\n message: 'Identity keys must contain 1\u2013100 characters and values 1\u2013500 characters.',\n });\n }\n })\n .transform((input) => Object.fromEntries(Object.entries(input as Record<string, string>)));\nexport type IntegrationIdentities = z.infer<typeof integrationIdentitiesSchema>;\n\nexport const mergeIntegrationIdentities = (\n identities: unknown = {},\n customerId?: unknown,\n): IntegrationIdentities => {\n const result = integrationIdentitiesSchema.parse(identities);\n if (customerId !== undefined) {\n const scalar = identityValueSchema.parse(customerId);\n if (Object.hasOwn(result, 'customerId') && result['customerId'] !== scalar)\n throw new TypeError('Contradictory customerId identities.');\n Object.defineProperty(result, 'customerId', {\n value: scalar,\n enumerable: true,\n configurable: true,\n writable: true,\n });\n }\n return integrationIdentitiesSchema.parse(result);\n};\n\nexport const memoryIdentitySnapshotSchema = z.union([\n z.strictObject({ key: z.null(), value: z.null() }),\n z.strictObject({ key: identityKeySchema, value: identityValueSchema.nullable() }),\n]);\nexport type MemoryIdentitySnapshot = z.infer<typeof memoryIdentitySnapshotSchema>;\nexport const memoryIdentityBindingSchema = z.strictObject({\n identityKey: identityKeySchema,\n source: z.enum(['code', 'studio', 'legacy']),\n fixedAt: z.iso.datetime(),\n});\nexport type MemoryIdentityBinding = z.infer<typeof memoryIdentityBindingSchema>;\n", "import { z } from 'zod';\nimport { pickedModelProviderIdSchema } from './studio-model-provider';\nimport { jsonObject } from '../common/json-object';\nimport {\n forwardPageQuerySchema,\n forwardPageResponseSchema,\n limitParamSchema,\n} from '../common/page';\n\n// Studio RAG Collections (V9). Spec: docs/arquitectura/componentes/11-stackbone-studio.md\n// \u00A7\"RAG Collections\". Wire shape is identical for the local emulator and the\n// cloud control plane. Both surfaces query the canonical `stackbone_rag_collections` /\n// `stackbone_rag_documents` / `stackbone_rag_chunks` / `stackbone_rag_jobs`\n// tables against the install's Postgres. The SDK's `client.rag` is the writer side;\n// Studio reads these tables and, via the managed-ingest write surface, also\n// creates collections and uploads/deletes documents.\n\nexport const STUDIO_RAG_DOCS_LIMIT_DEFAULT = 50;\nexport const STUDIO_RAG_DOCS_LIMIT_MAX = 200;\nexport const STUDIO_RAG_TOPK_DEFAULT = 10;\nexport const STUDIO_RAG_TOPK_MAX = 50;\nexport const STUDIO_RAG_PREVIEW_CHARS = 320;\nexport const STUDIO_RAG_JOBS_LIMIT_DEFAULT = 50;\nexport const STUDIO_RAG_JOBS_LIMIT_MAX = 200;\n\n// Collection name identifier \u2014 bounds the `stackbone_rag_collections.name`\n// value. We bound it loosely so creators can use slashes, dots, dashes etc.\n// without surprises.\nconst STUDIO_RAG_COLLECTION_REGEX = /^[A-Za-z0-9][A-Za-z0-9._\\-/:]{0,127}$/;\n\nexport const ragCollectionNameParamSchema = z\n .string()\n .min(1)\n .max(128)\n .regex(STUDIO_RAG_COLLECTION_REGEX, 'collection must be 1..128 ASCII identifier-safe chars');\nexport type RagCollectionNameParam = z.infer<typeof ragCollectionNameParamSchema>;\n\n// Distance metric. The canonical schema pins 'cosine' today, but we keep the\n// schema open so future ingest paths can declare 'l2'/'ip' without forcing a\n// wire migration.\nexport const ragDistanceSchema = z.enum(['cosine', 'l2', 'ip']);\nexport type RagDistance = z.infer<typeof ragDistanceSchema>;\n\n// Reports whether the collection has a usable dimension index or uses exact search.\nexport const ragIndexKindSchema = z.enum(['hnsw', 'exact']);\nexport type RagIndexKind = z.infer<typeof ragIndexKindSchema>;\n\nexport const ragCollectionSchema = z.object({\n name: z.string(),\n dimensions: z.number().int().min(1),\n // Optional for boxes that predate collection-owned embeddings.\n embedding_provider: z.string().min(1).optional(),\n embedding_model: z.string().min(1).optional(),\n distance: ragDistanceSchema,\n index_kind: ragIndexKindSchema,\n chunk_count: z.number().int().nonnegative(),\n doc_count: z.number().int().nonnegative(),\n // Maximum `created_at` across the collection's documents. Null when the\n // collection has zero documents (an empty collection row created before its\n // first ingest).\n last_ingested_at: z.iso.datetime().nullable(),\n});\nexport type RagCollection = z.infer<typeof ragCollectionSchema>;\n\nexport const ragCollectionListResponseSchema = z.object({\n items: z.array(ragCollectionSchema),\n});\nexport type RagCollectionListResponse = z.infer<typeof ragCollectionListResponseSchema>;\n\n// Ingest status vocabulary, shared by ingest jobs and the per-document\n// `ingest_status` below. Defined here (before `ragDocumentSchema`) so a\n// document can echo the status of the ingest run that produced it without a\n// forward reference. The jobs section further down reuses this same enum.\nexport const ragJobStatusSchema = z.enum(['queued', 'running', 'succeeded', 'failed', 'cancelled']);\nexport type RagJobStatus = z.infer<typeof ragJobStatusSchema>;\n\nexport const ragDocumentSchema = z.object({\n id: z.string(),\n namespace: z.string(),\n // Zero while the document is still ingesting (staged row, no chunks yet) or\n // when its ingest run failed before writing any chunk. A ready document has\n // at least one chunk.\n chunks_count: z.number().int().nonnegative(),\n // Document-level metadata copied onto the chunks at ingest time. We surface\n // the document's `metadata` so the UI can render filterable tags without\n // paging through chunks.\n metadata: jsonObject(),\n // Truncated content of the first chunk. Full body is one click away via\n // the docs drawer in the component.\n preview: z.string(),\n first_seen_at: z.iso.datetime(),\n last_seen_at: z.iso.datetime(),\n // Ingest status of the run that produced this document, when the document\n // was uploaded through Studio's managed ingest (which fires a durable\n // `rag-ingest` run). `null` for documents the agent wrote directly via\n // `client.rag.ingest()` (no run to track \u2014 they are ready once they have\n // chunks). Optional so the cloud control plane, which doesn't project runs,\n // can omit it without breaking the wire contract.\n ingest_status: ragJobStatusSchema.nullable().optional(),\n // Id of the ingest run that produced this document, for deep-linking to the\n // run details screen (`studio/runs/:runId`). `null`/absent when there is no\n // tracked run (direct SDK ingest, or the cloud surface).\n run_id: z.string().nullable().optional(),\n // Error message copied off the ingest run when it failed, so the docs table\n // can surface it inline without opening the run. `null`/absent otherwise.\n ingest_error: z.string().nullable().optional(),\n // STA3-8. Every version of a document is its own row; the list shows the\n // published one, so `id` is that version's id. `version_count` counts every\n // version, published or not. `pending_version` is the newest version past the\n // published one whose ingest finished, the \"v2 ready to publish\" on the row;\n // `null` when there is none. Optional, like the ingest fields above, so a box\n // from before versioning still parses.\n version: z.number().int().min(1).optional(),\n version_count: z.number().int().min(1).optional(),\n pending_version: z.number().int().min(1).nullable().optional(),\n});\nexport type RagDocument = z.infer<typeof ragDocumentSchema>;\n\n// JSONB filter payload \u2014 a plain `Record<string, unknown>` mapped to a\n// Postgres `metadata @> $1::jsonb` JSONB containment match.\nexport const ragFilterSchema = jsonObject();\nexport type RagFilter = z.infer<typeof ragFilterSchema>;\n\n// Newest first, always: the docs walk has no `order`, so it does not accept one.\nexport const listRagDocsQuerySchema = forwardPageQuerySchema.extend({\n limit: limitParamSchema(STUDIO_RAG_DOCS_LIMIT_MAX).default(STUDIO_RAG_DOCS_LIMIT_DEFAULT),\n // Filter is sent JSON-encoded inside a single query param so the UI can\n // pass an arbitrarily nested object without colliding with the URL syntax.\n // Empty/absent => no filter.\n filter: z.string().optional(),\n});\nexport type ListRagDocsQuery = z.infer<typeof listRagDocsQuerySchema>;\n\nexport const ragDocumentListResponseSchema = forwardPageResponseSchema(ragDocumentSchema);\nexport type RagDocumentListResponse = z.infer<typeof ragDocumentListResponseSchema>;\n\n// One entry of a document's history (STA3-8). `id` is that version's own id,\n// the one Download and Publish take; `is_active` marks the published one. The\n// ingest fields read as they do on the docs list. `ready` is whether Publish\n// accepts the version: it has chunks and its newest ingest, if any, succeeded.\n// `ingest_status` alone is not that: a run can succeed and write no chunk.\n// `filename` is the name the version was uploaded under, `null` for a document\n// the agent wrote itself.\nexport const ragDocumentVersionSchema = z.object({\n id: z.string(),\n version: z.number().int().min(1),\n is_active: z.boolean(),\n ready: z.boolean(),\n chunks_count: z.number().int().nonnegative(),\n ingest_status: ragJobStatusSchema.nullable(),\n run_id: z.string().nullable(),\n ingest_error: z.string().nullable(),\n filename: z.string().nullable(),\n created_at: z.iso.datetime(),\n});\nexport type RagDocumentVersion = z.infer<typeof ragDocumentVersionSchema>;\n\n// Every version of the document, newest first. A document has a handful, so the\n// history is one page.\nexport const ragDocumentVersionListResponseSchema = z.object({\n items: z.array(ragDocumentVersionSchema),\n});\nexport type RagDocumentVersionListResponse = z.infer<typeof ragDocumentVersionListResponseSchema>;\n\n// A version number as the integer column holds it, so a huge number is a\n// version nothing has rather than a Postgres range error.\nconst ragDocumentVersionNumberSchema = z.number().int().min(1).max(2_147_483_647);\n\n// The `{version}` path segment.\nexport const ragDocumentVersionParamSchema = z\n .string()\n .regex(/^\\d+$/, { message: 'version must be a positive integer' })\n .transform((value) => Number.parseInt(value, 10))\n .pipe(ragDocumentVersionNumberSchema);\nexport type RagDocumentVersionParam = z.infer<typeof ragDocumentVersionParamSchema>;\n\n// Publish a version (POST, STA3-8). `id` is the published version's own id.\n// `previous_version` is the version published before the call: the target's\n// own when it already was (`changed: false`), `null` when none was.\nexport const activateRagDocumentVersionResponseSchema = z.object({\n id: z.string(),\n collection: z.string(),\n version: z.number().int().min(1),\n previous_version: z.number().int().min(1).nullable(),\n changed: z.boolean(),\n});\nexport type ActivateRagDocumentVersionResponse = z.infer<\n typeof activateRagDocumentVersionResponseSchema\n>;\n\n// A text query uses the collection's saved provider/model. An explicit model\n// must match it; precomputed vectors remain caller-owned and must come from the\n// same embedding space. Dimension alone cannot establish model identity.\n// `preview` (STA3-8 D7) searches as if `version` of the document `docId` (any\n// of its versions' id) were published: that version stands in for the\n// document's published one, every other document answers as usual.\nexport const ragQueryRequestSchema = z\n .object({\n text: z.string().min(1).optional(),\n embedding: z.array(z.number().finite()).min(1).optional(),\n model: z.string().min(1).optional(),\n topK: z.coerce.number().int().min(1).max(STUDIO_RAG_TOPK_MAX).default(STUDIO_RAG_TOPK_DEFAULT),\n filter: ragFilterSchema.optional(),\n preview: z\n .object({ docId: z.string().min(1), version: ragDocumentVersionNumberSchema })\n .optional(),\n })\n .refine((value) => value.embedding !== undefined || value.text !== undefined, {\n message: 'either `embedding` or `text` must be provided',\n path: ['embedding'],\n });\nexport type RagQueryRequest = z.infer<typeof ragQueryRequestSchema>;\n\nexport const ragQueryHitSchema = z.object({\n id: z.string(),\n chunk_idx: z.number().int().nonnegative(),\n // Cosine similarity in [-1, 1]. The query handler emits `1 - (embedding <=>\n // query)` over `stackbone_rag_chunks`, so higher is closer. We surface the\n // raw value; the UI formats it.\n score: z.number(),\n content: z.string(),\n metadata: jsonObject(),\n});\nexport type RagQueryHit = z.infer<typeof ragQueryHitSchema>;\n\nexport const ragQueryResponseSchema = z.object({\n items: z.array(ragQueryHitSchema),\n // Echoes the dimensions used at retrieval time so the UI can flag a\n // dim-mismatch before it round-trips to a 4xx.\n dimensions: z.number().int().min(1),\n // Provider-pinned model (`provider::model`) used to embed `text`. Null when\n // the caller passed `embedding` directly.\n embedded_with: z.string().nullable(),\n});\nexport type RagQueryResponse = z.infer<typeof ragQueryResponseSchema>;\n\n// Ingest jobs (V9#03). Managed ingest materialises async jobs as rows in the\n// canonical `stackbone_rag_jobs` table (status / progress / error). The list\n// endpoint reads those rows and the cancel endpoint transitions a non-terminal\n// job to 'cancelled'. The status vocabulary (`ragJobStatusSchema`) is declared\n// near the top so a document can echo its ingest run's status.\nexport const ragJobSchema = z.object({\n id: z.string(),\n collection: z.string(),\n status: ragJobStatusSchema,\n attempts: z.number().int().nonnegative(),\n error: z.string().nullable(),\n created_at: z.iso.datetime(),\n finished_at: z.iso.datetime().nullable(),\n});\nexport type RagJob = z.infer<typeof ragJobSchema>;\n\nexport const listRagJobsQuerySchema = forwardPageQuerySchema.extend({\n collection: ragCollectionNameParamSchema.optional(),\n limit: limitParamSchema(STUDIO_RAG_JOBS_LIMIT_MAX).default(STUDIO_RAG_JOBS_LIMIT_DEFAULT),\n});\nexport type ListRagJobsQuery = z.infer<typeof listRagJobsQuerySchema>;\n\nexport const ragJobListResponseSchema = forwardPageResponseSchema(ragJobSchema);\nexport type RagJobListResponse = z.infer<typeof ragJobListResponseSchema>;\n\nexport const ragJobIdParamSchema = z.object({\n jobId: z.string().min(1).max(128),\n});\nexport type RagJobIdParam = z.infer<typeof ragJobIdParamSchema>;\n\nexport const ragJobActionResponseSchema = z.object({\n id: z.string(),\n status: ragJobStatusSchema,\n});\nexport type RagJobActionResponse = z.infer<typeof ragJobActionResponseSchema>;\n\n// ---------------------------------------------------------------------------\n// Write side \u2014 managed ingest from Studio.\n// ---------------------------------------------------------------------------\n// The schemas above describe the read/cancel surface. The block below is the\n// write-side contract for the managed ingest panel: creating collections,\n// uploading source documents, deleting documents/collections, downloading the\n// original file, and retrying a failed ingest job. The actual embedding +\n// chunking happens inside `@stackbone/rag-core`, which receives the install's\n// Postgres handle and an embeddings port by injection. These schemas only\n// validate the wire shape Studio exchanges with the control plane.\n\n// Upload guard rails. A single source file may not exceed 25 MiB on the wire,\n// and a single ingest job may not produce more than 10k chunks \u2014 both bound\n// the worst-case cost of one managed ingest so a pathological PDF cannot run\n// the org's OpenRouter sub-key dry or pin a Postgres connection for minutes.\nexport const STUDIO_RAG_MAX_FILE_BYTES = 26_214_400;\nexport const STUDIO_RAG_MAX_CHUNKS_PER_JOB = 10_000;\n\n// Source formats the managed ingest path can parse today: plain text,\n// Markdown, and PDF (via `unpdf` in rag-core). Surfaced so the UI can populate\n// the file-picker `accept` attribute and reject unsupported types client-side\n// before the multipart upload starts. The server re-validates by content type.\nexport const STUDIO_RAG_SUPPORTED_FILE_EXTENSIONS = ['.txt', '.md', '.pdf'] as const;\nexport const STUDIO_RAG_SUPPORTED_CONTENT_TYPES = [\n 'text/plain',\n 'text/markdown',\n 'application/pdf',\n] as const;\nexport const ragSupportedContentTypeSchema = z.enum(STUDIO_RAG_SUPPORTED_CONTENT_TYPES);\nexport type RagSupportedContentType = z.infer<typeof ragSupportedContentTypeSchema>;\n\n// An omitted identity preserves historical defaults without contacting a provider.\n// Configured creation probes the saved provider/model and derives its dimension.\nexport const createRagCollectionRequestSchema = z\n .object({\n name: ragCollectionNameParamSchema,\n metadata: jsonObject().optional(),\n embedding_provider: pickedModelProviderIdSchema.optional(),\n embedding_model: z.string().trim().min(1).optional(),\n })\n .refine(\n (value) => (value.embedding_provider === undefined) === (value.embedding_model === undefined),\n {\n message: 'embedding_provider and embedding_model must be provided together',\n path: ['embedding_model'],\n },\n );\nexport type CreateRagCollectionRequest = z.infer<typeof createRagCollectionRequestSchema>;\n\n// Echoes the freshly created collection (same shape the list endpoint returns\n// per item) so the UI can prepend it without re-fetching the list.\nexport const createRagCollectionResponseSchema = ragCollectionSchema;\nexport type CreateRagCollectionResponse = z.infer<typeof createRagCollectionResponseSchema>;\n\n// Delete collection (DELETE). The collection name travels as a route param\n// (validated with `ragCollectionNameParamSchema`); this is just the response.\nexport const deleteRagCollectionResponseSchema = z.object({\n name: z.string(),\n deleted: z.literal(true),\n});\nexport type DeleteRagCollectionResponse = z.infer<typeof deleteRagCollectionResponseSchema>;\n\n// Upload document (POST, multipart/form-data). The binary file itself rides as\n// a multipart part; this schema validates the JSON/text fields that travel\n// alongside it and is also what the OpenAPI request body references for the\n// non-binary properties. `collection` must already exist; `content_type` must\n// be one of the supported source formats; `metadata` is copied onto every\n// chunk the document produces so RAG queries can filter on it later.\n// `replaces` (STA3-8) is the id of any version of an existing document: the\n// upload becomes that document's next version, staged unpublished, whatever its\n// filename. Without it the upload starts a new document, and a filename an\n// active document already has (case-insensitive) is refused.\nexport const uploadRagDocumentRequestSchema = z.object({\n collection: ragCollectionNameParamSchema,\n filename: z.string().min(1).max(255),\n content_type: ragSupportedContentTypeSchema,\n metadata: jsonObject().optional(),\n replaces: z.string().min(1).optional(),\n});\nexport type UploadRagDocumentRequest = z.infer<typeof uploadRagDocumentRequestSchema>;\n\n// Upload kicks off an async ingest job; the response carries the job id the UI\n// polls (via the read-side jobs list) and the initial status, plus the version\n// the upload became: 1 for a new document, the next one for `replaces`.\nexport const uploadRagDocumentResponseSchema = z.object({\n job_id: z.string(),\n status: ragJobStatusSchema,\n version: z.number().int().min(1),\n});\nexport type UploadRagDocumentResponse = z.infer<typeof uploadRagDocumentResponseSchema>;\n\n// Delete document (DELETE). Both the document id and its owning collection\n// travel as route params (`id` is opaque, `collection` reuses the name regex);\n// this is the response confirming the chunks were removed.\nexport const deleteRagDocumentResponseSchema = z.object({\n id: z.string(),\n collection: z.string(),\n deleted: z.literal(true),\n});\nexport type DeleteRagDocumentResponse = z.infer<typeof deleteRagDocumentResponseSchema>;\n\n// Delete one unpublished version (DELETE, STA3-8 D6). `id` is the deleted\n// version's own id; the rest of the document stays.\nexport const deleteRagDocumentVersionResponseSchema = z.object({\n id: z.string(),\n collection: z.string(),\n version: ragDocumentVersionNumberSchema,\n deleted: z.literal(true),\n});\nexport type DeleteRagDocumentVersionResponse = z.infer<\n typeof deleteRagDocumentVersionResponseSchema\n>;\n\n// Download original (GET). The control plane resolves the document's stored\n// source file under the reserved read-only `rag/` object-storage prefix and\n// hands back a short-lived signed URL plus the metadata the browser needs to\n// name/open the download.\nexport const downloadRagDocumentResponseSchema = z.object({\n url: z.string(),\n filename: z.string(),\n content_type: z.string(),\n});\nexport type DownloadRagDocumentResponse = z.infer<typeof downloadRagDocumentResponseSchema>;\n\n// Retry job (POST). The job id travels as a route param (reuse\n// `ragJobIdParamSchema`); the response reuses the generic job-action shape so\n// the cancel and retry endpoints stay wire-compatible.\nexport const retryRagJobResponseSchema = ragJobActionResponseSchema;\nexport type RetryRagJobResponse = z.infer<typeof retryRagJobResponseSchema>;\n\n// A rebuild freezes every version and keeps the current embedding identity visible\n// until the owned worker commits the complete manifest.\nexport const changeRagEmbeddingRequestSchema = z.object({\n provider: pickedModelProviderIdSchema,\n model: z.string().trim().min(1),\n});\nexport type ChangeRagEmbeddingRequest = z.infer<typeof changeRagEmbeddingRequestSchema>;\nexport const retryRagRebuildRequestSchema = z.object({ id: z.uuid() });\nexport const ragEmbeddingIdentitySchema = z.object({\n provider: z.string().min(1),\n model: z.string().min(1),\n dimension: z.number().int().positive(),\n});\nexport const ragRebuildStatusSchema = z.object({\n id: z.uuid(),\n collection: z.string(),\n attempt: z.number().int().positive(),\n status: z.enum(['queued', 'running', 'failed', 'interrupted', 'complete']),\n current: ragEmbeddingIdentitySchema,\n target: ragEmbeddingIdentitySchema,\n completed_versions: z.number().int().nonnegative(),\n total_versions: z.number().int().nonnegative(),\n run_id: z.string().nullable(),\n world_run_id: z.string().nullable(),\n error: z.string().nullable(),\n});\nexport type RagRebuildStatus = z.infer<typeof ragRebuildStatusSchema>;\nexport const ragRebuildStatusResponseSchema = z.object({\n rebuild: ragRebuildStatusSchema.nullable(),\n});\n", "import { randomUUID } from 'node:crypto';\n// A LiveKit `llm.LLM` whose replies come from the agent's deep agent in the box.\n//\n// The worker keeps LiveKit's pipeline (VAD, STT, turn detection, TTS,\n// interruptions) and swaps only the model: each `chat()` sends the LAST user\n// message to the box as one AG-UI turn and streams the answer text back as\n// `ChatChunk`s. The box owns the prompt, model, tools and history (session\n// `voice-<room>`), so LiveKit's chat context is never replayed. The worker's own\n// tools are AG-UI client tools (`hang_up`), declared on every turn and run by the\n// worker once the turn has finished, never LiveKit function tools.\n//\n// After a barge-in, the next turn carries what the caller heard of the reply it cut\n// (`heard.ts`), so the box's history keeps what was said, not what was generated.\n//\n// What a box brain must not inherit from a model plugin:\n// - RETRIES. LiveKit retries a failed LLM stream (`connOptions.maxRetry`); a\n// retried box turn is a second durable turn with the same user message, so\n// `chat()` forces `maxRetry: 0`. The one exception is a turn whose `heard` the\n// box refused (400 `invalid_heard`): the box refuses it before the turn runs,\n// so the turn goes again once, without `heard`, and the caller is answered.\n// - THROWING on failure. LiveKit closes the session after a few unrecoverable\n// LLM errors. A failed turn (refused, `RUN_ERROR`, network, a stream the box\n// closed before `RUN_FINISHED`, a box silent for `turnIdleTimeoutMs`) is\n// reported once to `onTurnError`, where the worker speaks a fallback, and the\n// stream ends cleanly; a barge-in abort ends it quietly.\nimport { DEFAULT_API_CONNECT_OPTIONS, type APIConnectOptions, llm, log } from '@livekit/agents';\nimport type { ClientToolDeclaration } from '@stackbone/llm-wire';\nimport {\n aguiTurn,\n TURN_IDLE_TIMEOUT_MS,\n BoxTurnError,\n runErrorOf,\n type AguiCallWire,\n type AguiTextInput,\n type AguiTurnBase,\n type WireEvent,\n} from './box-wire';\nimport { HeardTracker, VOICE_RESPONSE_MARKER, type HeardSkip } from './heard';\n\n/** A call of a declared client tool. */\nexport interface ClientToolCall {\n readonly id: string;\n readonly name: string;\n}\n\n/** The wire options of every turn of the call; the text and the abort come per turn. */\nexport interface BoxLLMOptions extends AguiCallWire {\n /** Declared on every turn as AG-UI `tools`: the worker runs them, the box does not. */\n clientTools?: readonly ClientToolDeclaration[];\n /**\n * The agent called a declared client tool (the name matches exactly) and the run\n * finished `success`, the outcome the box gives a pause on client tools alone.\n * Called once per call id, before `onRunFinished`; never for a failed turn, a\n * barge-in before `RUN_FINISHED`, an `interrupt` outcome (a pause that also waits\n * on a human decision, D16), or a call the box answered itself with a\n * `TOOL_CALL_RESULT` (a box tool sharing the name: nothing is paused on it). A\n * client tool call is never reported to `onToolCall`.\n */\n onClientToolCall?: (call: ClientToolCall) => void;\n /**\n * A client tool's resume still in flight: a new turn waits for it before it sends\n * anything, since the box answers a new message on a thread paused on a client\n * tool as if the tool never ran. The D15 timer starts after the wait. Never\n * rejects: the resume reports its own failure.\n */\n pendingResume?: () => Promise<void> | undefined;\n /**\n * Whether the call is ending (a `hang_up`, D16, D17): a turn checks it after the\n * `pendingResume` wait and, when true, ends quietly without sending anything, since\n * the room is about to go and nobody would hear the reply of a durable run.\n */\n isEnding?: () => boolean;\n /**\n * A box tool call opened or closed (the worker plays its thinking cue in between).\n * Every start gets exactly one end: a call the box never closes ends with the turn\n * (a barge-in, a failure, before `onTurnError`), a repeated start or a stray end is\n * not reported.\n */\n onToolCall?: (phase: 'start' | 'end', id: string) => void;\n /** The turn failed (refused, `RUN_ERROR`, network, idle); called once, never on a barge-in. */\n onTurnError?: (err: Error) => void;\n /**\n * The run ended with `RUN_FINISHED`: answered, or paused for a human decision (D16).\n * Called also when a barge-in or the idle timer closes the stream after that frame.\n */\n onRunFinished?: (outcome: 'success' | 'interrupt') => void;\n /** Every event restarts it, a tool call's silence included; 30 s unless a spec sets it. */\n turnIdleTimeoutMs?: number;\n}\n\nexport class BoxLLM extends llm.LLM {\n readonly #opts: BoxLLMOptions;\n readonly #replies = new HeardTracker();\n readonly #pending = new Set<Promise<void>>();\n async settled(): Promise<void> {\n await Promise.all(this.#pending);\n }\n constructor(opts: BoxLLMOptions) {\n super();\n this.#opts = opts;\n }\n heardTranscript(history: llm.ChatContext): Array<{ role: 'user' | 'assistant'; text: string }> {\n return this.#replies.transcript(history, (reason) =>\n log().warn(\n { agent: this.#opts.agent, sessionKey: this.#opts.sessionKey, reason },\n 'voice worker: omitted uncertain interrupted output',\n ),\n );\n }\n label(): string {\n return 'stackbone.BoxLLM';\n }\n override get model(): string {\n return this.#opts.agent;\n }\n override get provider(): string {\n return 'stackbone';\n }\n chat({\n chatCtx,\n connOptions,\n }: {\n chatCtx: llm.ChatContext;\n connOptions?: APIConnectOptions;\n }): llm.LLMStream {\n let done: () => void = () => undefined;\n const pending = new Promise<void>((resolve) => {\n done = resolve;\n });\n this.#pending.add(pending);\n return new BoxLLMStream(\n this,\n this.#opts,\n this.#replies,\n () => {\n this.#pending.delete(pending);\n done();\n },\n {\n chatCtx,\n connOptions: { ...(connOptions ?? DEFAULT_API_CONNECT_OPTIONS), maxRetry: 0 },\n },\n );\n }\n}\n\n/** The box refused the turn's `heard` (400 `invalid_heard`), before the turn ran. */\nconst refusedHeard = (err: unknown): err is BoxTurnError => {\n if (!(err instanceof BoxTurnError) || err.status !== 400) return false;\n try {\n return (JSON.parse(err.body) as { code?: unknown } | null)?.code === 'invalid_heard';\n } catch {\n return false;\n }\n};\n\n/** A text turn's events; a turn whose `heard` the box refused goes again once without it. */\nasync function* textTurnEvents({\n heard,\n ...turn\n}: AguiTurnBase & AguiTextInput): AsyncGenerator<WireEvent> {\n if (heard) {\n try {\n // The refusal is a status, so it comes before any event.\n yield* aguiTurn({ ...turn, heard });\n return;\n } catch (err) {\n if (!refusedHeard(err)) throw err;\n log().warn(\n { agent: turn.agent, sessionKey: turn.sessionKey, status: err.status, error: err.message },\n 'voice worker: the box refused heard; the turn goes on without it',\n );\n }\n }\n yield* aguiTurn(turn);\n}\n\n/**\n * How loud a cut reply that sends no `heard` is: `debug` for a skip the design expects,\n * `warn` for a broken alignment or a played part past what the box takes. A `Record`,\n * so a new reason has to pick its level.\n */\nconst HEARD_SKIP_LEVEL: Readonly<Record<HeardSkip, 'debug' | 'warn'>> = {\n not_box_held: 'debug',\n earlier_call: 'debug',\n whole: 'debug',\n unaligned: 'warn',\n over_max: 'warn',\n};\n\n/** One line per turn whose cut reply sends no `heard`: the reason, never the text. */\nconst logHeardSkip = (skipped: HeardSkip, turn: Pick<AguiTurnBase, 'agent' | 'sessionKey'>): void =>\n log()[HEARD_SKIP_LEVEL[skipped]](\n { reason: skipped, agent: turn.agent, sessionKey: turn.sessionKey },\n 'voice worker: no heard for the reply cut',\n );\n\n/** The text of the most recent user message in LiveKit's chat context. */\nconst lastUserText = (chatCtx: llm.ChatContext): string => {\n for (let i = chatCtx.items.length - 1; i >= 0; i--) {\n const item = chatCtx.items[i];\n if (item?.type === 'message' && item.role === 'user') return item.textContent ?? '';\n }\n return '';\n};\n\nclass BoxLLMStream extends llm.LLMStream {\n readonly #opts: BoxLLMOptions;\n readonly #replies: HeardTracker;\n readonly #settle: () => void;\n constructor(\n owner: BoxLLM,\n opts: BoxLLMOptions,\n replies: HeardTracker,\n settle: () => void,\n init: { chatCtx: llm.ChatContext; connOptions: APIConnectOptions },\n ) {\n super(owner, init);\n this.#opts = opts;\n this.#replies = replies;\n this.#settle = settle;\n }\n\n protected async run(): Promise<void> {\n try {\n await this.runTurn();\n } finally {\n this.#settle();\n }\n }\n private async runTurn(): Promise<void> {\n const {\n onToolCall,\n onTurnError,\n onRunFinished,\n onClientToolCall,\n pendingResume,\n isEnding,\n clientTools = [],\n turnIdleTimeoutMs = TURN_IDLE_TIMEOUT_MS,\n ...turn\n } = this.#opts;\n const id = randomUUID();\n // LiveKit's own signal is the barge-in; the idle timer aborts the request too,\n // but as a failed turn.\n const { signal } = this.abortController;\n await pendingResume?.();\n // A barge-in during the wait, or a call that started ending, sends nothing, and\n // leaves what the caller heard of the last reply to the next turn.\n if (signal.aborted || isEnding?.()) return;\n const { heard, skipped, reply } = this.#replies.openTurn(this.chatCtx, id);\n if (skipped) logHeardSkip(skipped, turn);\n const clientToolNames = new Set(clientTools.map((tool) => tool.name));\n const clientToolCalls = new Map<string, ClientToolCall>();\n const idle = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n const restartIdle = (): void => {\n clearTimeout(timer);\n timer = setTimeout(() => idle.abort(), turnIdleTimeoutMs);\n };\n const openToolCalls = new Set<string>();\n let finished: 'success' | 'interrupt' | undefined;\n let failure: Error | undefined;\n try {\n restartIdle();\n const turnSignal = AbortSignal.any([signal, idle.signal]);\n for await (const ev of textTurnEvents({\n ...turn,\n tools: clientTools,\n text: lastUserText(this.chatCtx),\n ...(heard ? { heard } : {}),\n signal: turnSignal,\n })) {\n restartIdle();\n reply.observe(ev);\n switch (ev.kind) {\n case 'text':\n this.queue.put({\n id,\n delta: {\n role: 'assistant',\n content: ev.text,\n extra: { [VOICE_RESPONSE_MARKER]: id },\n },\n });\n break;\n case 'tool_call_start':\n if (clientToolNames.has(ev.name)) {\n clientToolCalls.set(ev.id, { id: ev.id, name: ev.name });\n } else if (!openToolCalls.has(ev.id)) {\n openToolCalls.add(ev.id);\n onToolCall?.('start', ev.id);\n }\n break;\n case 'tool_call_result':\n // A paused client tool never has a result: this one ran on the box.\n clientToolCalls.delete(ev.id);\n if (openToolCalls.delete(ev.id)) onToolCall?.('end', ev.id);\n break;\n case 'finish':\n finished = ev.outcome;\n break;\n case 'error':\n throw runErrorOf(ev);\n }\n }\n // The box closes the stream without a terminal frame when it fails outside the\n // run (its state reads), and a proxy can cut it cleanly: the answer is missing.\n if (!finished) throw new Error('box closed the turn before RUN_FINISHED');\n } catch (err) {\n if (finished) {\n // The run had ended: a barge-in, the idle timer or a cut connection while the\n // box closes the stream changes nothing about how it ended.\n } else if (signal.aborted) {\n // A barge-in: not a failed turn.\n } else if (idle.signal.aborted) {\n failure = new Error(`box sent no event for ${turnIdleTimeoutMs} ms`);\n } else {\n failure = err instanceof Error ? err : new Error(String(err));\n }\n } finally {\n clearTimeout(timer);\n }\n // A call the box never closed (a barge-in, a failed turn) ends with the turn,\n // before the failure is reported.\n for (const callId of openToolCalls) onToolCall?.('end', callId);\n if (failure) {\n this.#replies.dropTurn(reply);\n onTurnError?.(failure);\n return;\n }\n if (finished === 'success')\n for (const call of clientToolCalls.values()) onClientToolCall?.(call);\n if (finished) onRunFinished?.(finished);\n }\n}\n", "/**\n * The `data` of each SSE event in `body`, per the EventSource rules: lines end in\n * CRLF, LF or CR; a blank line ends an event; `:` lines are comments (keep-alives);\n * `data:` takes one optional space and several `data:` lines join with `\\n`; other\n * fields (`event:`, `id:`) are ignored, since every wire this repo reads carries its\n * event type in the JSON. Text is decoded as a stream, so an event or a character\n * split across chunks arrives whole. Stopping early cancels the body, which closes\n * the request.\n */\nexport async function* readSseData(body: ReadableStream<Uint8Array>): AsyncGenerator<string> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffered = '';\n let data: string[] = [];\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (!done) buffered += decoder.decode(value, { stream: true });\n // A trailing CR may be the first half of a CRLF split across chunks: keep it,\n // unless the body ended, where it ends its line.\n const end = !done && buffered.endsWith('\\r') ? buffered.length - 1 : buffered.length;\n const lines = buffered.slice(0, end).split(/\\r\\n|\\r|\\n/);\n buffered = (lines.pop() ?? '') + buffered.slice(end);\n for (const line of lines) {\n if (line === '') {\n if (data.length > 0) yield data.join('\\n');\n data = [];\n } else if (line.startsWith('data:')) {\n const field = line.slice(5);\n data.push(field.startsWith(' ') ? field.slice(1) : field);\n }\n }\n if (done) return;\n }\n } finally {\n await reader.cancel().catch(() => undefined);\n }\n}\n", "// One call turn as one AG-UI request to the deep agent in the box.\n//\n// The worker has no LLM (D1): each turn POSTs the caller's latest words to\n// `/agui/v1/agents/<agent>` with `threadId` = the call's session key, and streams\n// the box's AG-UI events back as a small typed `WireEvent` set. Only the latest\n// user message is sent: the box owns the conversation (its checkpointer), so\n// replaying LiveKit's chat context would double it. A turn may declare client\n// tools (`hang_up`), and a `toolResult` turn answers the one the run paused on,\n// which the box reads as a resume of the thread. A text turn may carry what the\n// caller heard of the previous reply before a barge-in cut it (`heard`, S9), which\n// the box writes into its history before the turn runs. No retries: a refused\n// turn throws once and the caller decides what the caller hears.\n//\n// Where the box is and what bearer it takes come with each request, never from\n// env: in a call, both come from the dispatch (`boxUrl`, `callToken`, S10).\n\nimport {\n readSseData,\n type AguiToolResultValue,\n type ClientToolDeclaration,\n type TurnContextEntry,\n} from '@stackbone/llm-wire';\nimport {\n VOICE_CALLS_PATH,\n voiceCallLifecycleResponseSchema,\n RUN_TAGS_HEADER,\n STACKBONE_SESSION_HEADER,\n type VoiceCallBootstrap,\n type VoiceCallComplete,\n type AguiHeard,\n} from '@stackbone/validators';\n\n/** How much of a refusal's body a `BoxTurnError` keeps. */\nconst BODY_EXCERPT_CHARS = 300;\nexport const TURN_IDLE_TIMEOUT_MS = 30_000;\n/** What a header value cannot carry as is: anything past ASCII. */\nconst NON_ASCII = /[\\u007f-\\uffff]/g;\n\n/** What every request of the call carries, whatever its input. */\nexport interface AguiTurnBase {\n /** The box's base URL; a path prefix is kept, trailing slashes are dropped. */\n boxUrl: string;\n /**\n * The bearer on every request, and its only credential: in a call, the\n * dispatch's call token. Never logged.\n */\n token: string;\n agent: string;\n /** `voice-<room>`: one durable box session per call. */\n sessionKey: string;\n /** Stamped on the turn's run (`channel`, `room`, `origin`). */\n tags: Readonly<Record<string, string>>;\n /** Sent as the request's `context` (the TTS markup guide, S5 t6); none when absent. */\n context?: readonly TurnContextEntry[];\n /**\n * Sent as the request's `tools`; none when absent. The agent may call one, the\n * worker runs it: the box pauses the run on the call and closes it with\n * `RUN_FINISHED` success, and the worker answers it with a `toolResult` turn.\n */\n tools?: readonly ClientToolDeclaration[];\n /** Aborting it (a barge-in) closes the request. */\n signal?: AbortSignal;\n}\n\n/** What every request of one call shares; the tools and the abort come per request. */\nexport type AguiCallWire = Omit<AguiTurnBase, 'tools' | 'signal'>;\n\n/** What the caller said this turn, with what they heard of the previous reply when a barge-in cut it. */\nexport interface AguiTextInput {\n text: string;\n /** Sent as `forwardedProps.heard`; the box answers 400 `invalid_heard` when it does not fit its last reply. */\n heard?: AguiHeard;\n}\n\n/** One request: what the caller said this turn, or the result of a client tool call. */\nexport type AguiTurnOptions = AguiTurnBase & (AguiTextInput | { toolResult: AguiToolResultValue });\n\n/**\n * What one AG-UI event means to the worker. `text` is answer text for the TTS and\n * comes from the agent's own `TEXT_MESSAGE_CONTENT` alone: reasoning, tool\n * arguments and results, state and custom events, and a subagent's text (inside a\n * `STEP_STARTED`\u2026`STEP_FINISHED`, its answer to the agent) carry prose the caller\n * must never hear.\n *\n * A tool call ends at its `TOOL_CALL_RESULT`, not its `TOOL_CALL_END`: the box closes\n * a call's block when the next one opens (a parallel call, a subagent step), so\n * `TOOL_CALL_END` marks the end of its arguments, often before the tool has run.\n */\nexport type WireEvent =\n | { kind: 'text'; text: string }\n | { kind: 'tool_call_start'; id: string; name: string }\n | { kind: 'tool_call_result'; id: string }\n /** `interrupt`: the box paused the run for a human decision (a HITL approval). */\n | { kind: 'finish'; outcome: 'success' | 'interrupt' }\n | { kind: 'error'; message: string; code?: string }\n | { kind: 'other'; type: string };\n\n/** The failure a `RUN_ERROR` event reports, with the box's code when it sent one. */\nexport const runErrorOf = ({ message, code }: { message: string; code?: string }): Error =>\n new Error(`box run failed${code ? ` (${code})` : ''}: ${message}`);\n\n/** The box refused the turn with a non-2xx status. */\nexport class BoxTurnError extends Error {\n readonly status: number;\n /** The first characters of the response body (the box's `RUN_ERROR` JSON). */\n readonly body: string;\n constructor(agent: string, status: number, body: string) {\n super(`box refused the AG-UI turn of ${agent}: HTTP ${status} ${body}`);\n this.name = 'BoxTurnError';\n this.status = status;\n this.body = body;\n }\n}\n\n/**\n * `path` (starting with `/`) under the box's base URL: trailing slashes dropped, a\n * path prefix kept. The one join for every request a call sends the box, its AG-UI\n * turns and its spans (S11).\n */\nexport const boxEndpoint = (boxUrl: string, path: string): string =>\n `${boxUrl.replace(/\\/+$/, '')}${path}`;\n\nexport async function* aguiTurn(opts: AguiTurnOptions): AsyncGenerator<WireEvent> {\n const runId = `run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n const heard = 'toolResult' in opts ? undefined : opts.heard;\n const message =\n 'toolResult' in opts\n ? {\n id: `tool-${opts.toolResult.toolCallId}`,\n role: 'tool',\n toolCallId: opts.toolResult.toolCallId,\n content: opts.toolResult.content,\n }\n : { id: `user-${runId}`, role: 'user', content: opts.text };\n const res = await fetch(\n boxEndpoint(opts.boxUrl, `/agui/v1/agents/${encodeURIComponent(opts.agent)}`),\n {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n accept: 'text/event-stream',\n authorization: `Bearer ${opts.token}`,\n [STACKBONE_SESSION_HEADER]: opts.sessionKey,\n [RUN_TAGS_HEADER]: tagsHeader(opts.tags),\n },\n body: JSON.stringify({\n threadId: opts.sessionKey,\n runId,\n state: {},\n messages: [message],\n tools: opts.tools ?? [],\n context: opts.context ?? [],\n forwardedProps: heard ? { heard } : {},\n }),\n ...(opts.signal ? { signal: opts.signal } : {}),\n },\n );\n if (!res.ok || !res.body) throw new BoxTurnError(opts.agent, res.status, await readExcerpt(res));\n // The box opens a step per subagent delegation and never nests them.\n let inStep = false;\n for await (const data of readSseData(res.body)) {\n const parsed: unknown = JSON.parse(data);\n const ev = typeof parsed === 'object' && parsed !== null ? (parsed as AguiEvent) : {};\n const wire = toWireEvent(ev);\n if (wire.kind === 'other' && wire.type === 'STEP_STARTED') inStep = true;\n if (wire.kind === 'other' && wire.type === 'STEP_FINISHED') inStep = false;\n yield wire.kind === 'text' && inStep ? { kind: 'other', type: 'TEXT_MESSAGE_CONTENT' } : wire;\n }\n}\n\n/**\n * The tags as JSON with every character past ASCII escaped. A header value must be\n * Latin-1 or fetch refuses the whole request, and a tag value is free text; `\\uXXXX`\n * escapes keep it plain JSON for the box's `JSON.parse`.\n */\nconst tagsHeader = (tags: Readonly<Record<string, string>>): string =>\n JSON.stringify(tags).replace(\n NON_ASCII,\n (c) => `\\\\u${c.charCodeAt(0).toString(16).padStart(4, '0')}`,\n );\n\n/** The body's first characters; stops reading there, so a huge or endless body costs nothing. */\nconst readExcerpt = async (res: Response): Promise<string> => {\n if (!res.body) return '';\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let text = '';\n try {\n while (text.length < BODY_EXCERPT_CHARS) {\n const { done, value } = await reader.read();\n if (done) break;\n text += decoder.decode(value, { stream: true });\n }\n } finally {\n await reader.cancel().catch(() => undefined);\n }\n return text.slice(0, BODY_EXCERPT_CHARS);\n};\n\ntype AguiEvent = Readonly<Record<string, unknown>>;\n\nconst stringField = (ev: AguiEvent, key: string): string | undefined => {\n const value = ev[key];\n return typeof value === 'string' ? value : undefined;\n};\n\nconst toWireEvent = (ev: AguiEvent): WireEvent => {\n const type = stringField(ev, 'type') ?? '';\n const id = stringField(ev, 'toolCallId');\n switch (type) {\n case 'TEXT_MESSAGE_CONTENT': {\n const delta = stringField(ev, 'delta');\n return delta ? { kind: 'text', text: delta } : { kind: 'other', type };\n }\n case 'TOOL_CALL_START':\n return id\n ? { kind: 'tool_call_start', id, name: stringField(ev, 'toolCallName') ?? '' }\n : { kind: 'other', type };\n case 'TOOL_CALL_RESULT':\n return id ? { kind: 'tool_call_result', id } : { kind: 'other', type };\n case 'RUN_FINISHED': {\n // `outcome` is `{ type: 'success' }` or `{ type: 'interrupt', interrupts }`; a\n // box that sends none answered.\n const outcome = ev['outcome'];\n const paused =\n typeof outcome === 'object' &&\n outcome !== null &&\n (outcome as AguiEvent)['type'] === 'interrupt';\n return { kind: 'finish', outcome: paused ? 'interrupt' : 'success' };\n }\n case 'RUN_ERROR': {\n const code = stringField(ev, 'code');\n return {\n kind: 'error',\n message: stringField(ev, 'message') ?? 'run error',\n ...(code ? { code } : {}),\n };\n }\n default:\n return { kind: 'other', type };\n }\n};\n\nexport const voiceCallRequest = async (\n wire: Pick<AguiCallWire, 'boxUrl' | 'token' | 'agent' | 'sessionKey'>,\n operation: 'bootstrap' | 'complete',\n payload: VoiceCallBootstrap | VoiceCallComplete,\n): Promise<void> => {\n const response = await fetch(\n boxEndpoint(\n wire.boxUrl,\n `${VOICE_CALLS_PATH}/${encodeURIComponent(wire.sessionKey)}/${operation}`,\n ),\n {\n method: 'POST',\n headers: { 'content-type': 'application/json', authorization: `Bearer ${wire.token}` },\n signal: AbortSignal.timeout(TURN_IDLE_TIMEOUT_MS),\n body: JSON.stringify(payload),\n },\n );\n if (!response.ok)\n throw new BoxTurnError(wire.agent, response.status, await readExcerpt(response));\n voiceCallLifecycleResponseSchema.parse(await response.json());\n};\n", "// What the caller heard of the agent's last reply, for the box (FDE-62 S9, D9).\n//\n// A barge-in cuts the reply LiveKit is playing, and LiveKit commits the part it\n// played as an interrupted assistant message in its own chat context\n// (`forwardedTextFor`, `@livekit/agents` 1.9.0 `voice/generation.ts`). The box never\n// sees that context, so the next AG-UI turn carries the part as\n// `forwardedProps.heard`, and the box rewrites the reply to its last user message to\n// it (or appends it, when the reply was never written) before the turn runs. The box\n// takes `heard` only as an exact prefix of that reply, so the worker:\n// - reads the interrupted message among the chat items added since the turn\n// started, so an older turn's message never counts;\n// - aligns it with the text BoxLLM queued, tolerating what the TTS text transforms\n// deleted (`heardPrefixOf`), and sends a slice of the box text: a greeting or a\n// fixed phrase does not align;\n// - sends only the part inside the box's LAST AI message: the box keeps one per\n// model call, and the first text or tool call after a tool result is the next;\n// - sends nothing it cannot place: a whole reply (no aligned transcript), a reply\n// the box never wrote after a tool call, a failed turn.\nimport type { llm } from '@livekit/agents';\nimport { HEARD_TEXT_MAX, type AguiHeard } from '@stackbone/validators';\nimport type { WireEvent } from './box-wire';\nexport const VOICE_RESPONSE_MARKER = 'stackboneVoiceResponse';\n\n/**\n * Why a reply a barge-in cut sends no `heard`:\n * - `not_box_held`: the box's last AI message is not the call LiveKit was playing;\n * - `unaligned`: the played text is not the box text after the TTS transforms;\n * - `earlier_call`: the caller heard nothing past an earlier model call's text;\n * - `whole`: the played text is the whole reply (no aligned transcript);\n * - `over_max`: the played part is longer than the box takes.\n */\nexport type HeardSkip = 'not_box_held' | 'unaligned' | 'earlier_call' | 'whole' | 'over_max';\n\n/** What the next turn sends of a reply a barge-in cut, or why it sends nothing. */\nexport type HeardRead = { heard: AguiHeard } | { skipped: HeardSkip };\n\n/** A letter or a digit: what the TTS text transforms never delete. */\nconst WORD_CHAR = /[\\p{L}\\p{N}]/u;\nconst SPACE = /\\s/u;\n/** An XML-style tag at the start: expressive markup such as `<expr type=\"laugh\"/>`. */\nconst TAG = /^<[\\p{L}/][^>]*>/u;\n\nconst charAt = (text: string, index: number): string =>\n String.fromCodePoint(text.codePointAt(index) ?? 0);\n\n/**\n * The longest prefix of `boxText` that `heardText` accounts for, trimmed at the end;\n * `undefined` when the heard text cannot be aligned with the box text, or is empty.\n * The result is always a slice of `boxText`, so the box's prefix check holds.\n *\n * The heard text is the box text after LiveKit's TTS text transforms (`filter_markdown`,\n * `filter_emoji`, `dropThousandsComma`) and expressive-tag stripping, which only\n * DELETE characters. So, walking both:\n * - a heard character must equal the box character, case included;\n * - otherwise the box may skip an XML-style tag as a unit, or one character that is\n * neither a letter nor a digit (markdown, an emoji, a comma, a line break);\n * - heard whitespace may match nothing (a line break run heard as one space);\n * - a box letter or digit is never skipped, and heard text left over after the box\n * text ends refuses the whole match.\n */\nexport const heardPrefixOf = (boxText: string, heardText: string): string | undefined => {\n let b = 0;\n let h = 0;\n while (h < heardText.length) {\n const heardChar = charAt(heardText, h);\n if (b >= boxText.length) {\n if (!SPACE.test(heardChar)) return undefined;\n h += heardChar.length;\n continue;\n }\n const boxChar = charAt(boxText, b);\n const tag = boxChar === '<' ? TAG.exec(boxText.slice(b)) : null;\n if (boxChar === heardChar) {\n b += boxChar.length;\n h += heardChar.length;\n } else if (tag) {\n b += tag[0].length;\n } else if (!WORD_CHAR.test(boxChar)) {\n b += boxChar.length;\n } else if (SPACE.test(heardChar)) {\n h += heardChar.length;\n } else {\n return undefined;\n }\n }\n return boxText.slice(0, b).trimEnd() || undefined;\n};\n\n/**\n * The reply BoxLLM queued in one turn, and what the next turn sends of it. It mirrors\n * the box's model calls: the current call is the one whose text arrives now, and the\n * first text or tool call after a tool result opens the next one.\n */\nexport class TurnReply {\n /** LiveKit's chat items when the turn started: the reply a barge-in cut is a later one. */\n readonly #seen: ReadonlySet<string>;\n /** Every text delta queued to LiveKit this turn. */\n #text = '';\n /** Where the current model call's text starts in `#text`. */\n #callStart = 0;\n /** A tool result came after the current call: the next text is a new call's. */\n #callClosed = false;\n /** The current call is the turn's first: no tool result came before it. */\n #firstCall = true;\n /** The current call asked for tools: the box writes it before it runs them. */\n #callToolCalls = false;\n /** `RUN_FINISHED` arrived: the box wrote every call of the run. */\n #finished = false;\n\n constructor(chatCtx: llm.ChatContext) {\n this.#seen = new Set(chatCtx.items.map((item) => item.id));\n }\n\n observe(ev: WireEvent): void {\n switch (ev.kind) {\n case 'text':\n this.#openCallAfterResult();\n this.#text += ev.text;\n break;\n case 'tool_call_start':\n this.#openCallAfterResult();\n this.#callToolCalls = true;\n break;\n case 'tool_call_result':\n this.#callClosed = true;\n break;\n case 'finish':\n this.#finished = true;\n break;\n }\n }\n\n #openCallAfterResult(): void {\n if (!this.#callClosed) return;\n this.#callStart = this.#text.length;\n this.#callClosed = false;\n this.#firstCall = false;\n this.#callToolCalls = false;\n }\n\n /**\n * Whether the box's last AI message after the turn's user message is the current\n * call's, or there is none (the box then appends `heard` as the reply):\n * - the run finished: the current call's, unless a tool result closed it, since the\n * last model call then said nothing (an empty message);\n * - cut while the box still sent: the current call's once the box wrote it, which a\n * tool call or a tool result proves; none for the turn's first call; for a later\n * call torn down mid-text, an EARLIER call's, which the caller's words do not fit.\n */\n #boxHoldsCurrentCall(): boolean {\n if (this.#finished) return !this.#callClosed;\n return this.#callClosed || this.#callToolCalls || this.#firstCall;\n }\n\n finalPrefix(playedText: string): string | undefined {\n const prefix = heardPrefixOf(this.#text, playedText);\n if (prefix === undefined) return undefined;\n // A transformed whole reply is as uncertain as LiveKit's full-generated fallback.\n const remainder = this.#text.slice(prefix.length).replace(/<[\\p{L}/][^>]*>/gu, '');\n return WORD_CHAR.test(remainder) ? prefix : undefined;\n }\n\n /**\n * What the caller heard of this reply's last model call, from the next turn's chat\n * context: only when a barge-in cut it there, and never the whole call's text.\n * `undefined` when no barge-in cut it; otherwise the heard part or why there is none.\n */\n heardIn(chatCtx: llm.ChatContext): HeardRead | undefined {\n const played = lastNewAssistantMessage(chatCtx, this.#seen);\n if (!played?.interrupted) return undefined;\n if (!this.#boxHoldsCurrentCall()) return { skipped: 'not_box_held' };\n const prefix = heardPrefixOf(this.#text, played.textContent ?? '');\n if (prefix === undefined) return { skipped: 'unaligned' };\n if (prefix.length <= this.#callStart) return { skipped: 'earlier_call' };\n const text = prefix.slice(this.#callStart);\n const whole = this.#text.slice(this.#callStart).trimEnd();\n // The whole reply says nothing about how much was heard (no aligned transcript).\n if (text.length >= whole.length) return { skipped: 'whole' };\n return text.length <= HEARD_TEXT_MAX ? { heard: { text } } : { skipped: 'over_max' };\n }\n}\n\n/** The turns of one call, so each one can tell the box what the caller heard of the last. */\nexport class HeardTracker {\n #last: TurnReply | undefined;\n readonly #responses = new Map<string, TurnReply>();\n\n /**\n * Starts a turn that is about to send: what the caller heard of the previous turn's\n * reply (or why a cut reply sends nothing), and this turn's reply. The previous\n * reply is read once.\n */\n openTurn(\n chatCtx: llm.ChatContext,\n responseId?: string,\n ): {\n heard: AguiHeard | undefined;\n skipped: HeardSkip | undefined;\n reply: TurnReply;\n } {\n const read = this.#last?.heardIn(chatCtx);\n const reply = new TurnReply(chatCtx);\n this.#last = reply;\n if (responseId) this.#responses.set(responseId, reply);\n return {\n heard: read && 'heard' in read ? read.heard : undefined,\n skipped: read && 'skipped' in read ? read.skipped : undefined,\n reply,\n };\n }\n\n transcript(\n history: llm.ChatContext,\n onUncertain: (reason: string) => void,\n ): Array<{ role: 'user' | 'assistant'; text: string }> {\n const transcript: Array<{ role: 'user' | 'assistant'; text: string }> = [];\n for (const item of history.items) {\n if (item.type !== 'message' || (item.role !== 'user' && item.role !== 'assistant')) continue;\n let text = item.textContent?.trim();\n if (!text) continue;\n if (item.role === 'assistant' && item.interrupted) {\n const responseId = item.extra[VOICE_RESPONSE_MARKER];\n const response =\n typeof responseId === 'string' ? this.#responses.get(responseId) : undefined;\n text = response?.finalPrefix(text)?.trim();\n if (!text) {\n onUncertain(response ? 'unverified_interrupted_prefix' : 'unowned_interruption');\n continue;\n }\n }\n transcript.push({ role: item.role, text });\n }\n return transcript;\n }\n\n /** A failed turn: the box keeps none of its reply, so the next turn sends nothing for it. */\n dropTurn(reply: TurnReply): void {\n if (this.#last === reply) this.#last = undefined;\n for (const [id, response] of this.#responses)\n if (response === reply) this.#responses.delete(id);\n }\n}\n\nconst lastNewAssistantMessage = (\n chatCtx: llm.ChatContext,\n seen: ReadonlySet<string>,\n): llm.ChatMessage | undefined => {\n for (let i = chatCtx.items.length - 1; i >= 0; i--) {\n const item = chatCtx.items[i];\n if (item === undefined || seen.has(item.id)) continue;\n if (item.type === 'message' && item.role === 'assistant') return item;\n }\n return undefined;\n};\n", "// What one job of the voice worker is told, in the LiveKit dispatch metadata.\n//\n// The box writes it: `POST /api/voice/rooms` parses a `VoiceDispatch` and signs it\n// into the room token's `roomConfig.agents[].metadata`, so the worker reads its\n// agent, settings, session and tags from the job alone and never asks the box's\n// operator API. The worker reads it with the same schema, strict: a job without a\n// valid dispatch is refused, never filled in from env or defaults (no\n// `VOICE_AGENT` fallback), and the spike's per-job knobs (`brain`, `local`,\n// `preemptive`, `textOnly`, `forwardToolCalls`, `hangUpTool`) make the whole\n// dispatch invalid rather than being dropped.\n\nimport { voiceDispatchSchema, type VoiceDispatch } from '@stackbone/validators';\n\n/** The job's dispatch, or `null` when it carries none or one the schema refuses. Never throws. */\nexport const readDispatch = (metadata: string | undefined): VoiceDispatch | null => {\n if (!metadata) return null;\n let value: unknown;\n try {\n value = JSON.parse(metadata);\n } catch {\n return null;\n }\n const parsed = voiceDispatchSchema.safeParse(value);\n return parsed.success ? parsed.data : null;\n};\n", "// The agent ends the call itself: the `hang_up` client tool (FDE-62 S8).\n//\n// Every turn declares `hang_up` (`BoxLLM` `clientTools`). The agent says goodbye and\n// calls it in the same reply; the box pauses the run on the call and closes it with\n// `RUN_FINISHED` success, so it is no D16 pause and no phrase is said. Once the turn\n// has finished, the worker:\n// 1. resumes the box AT ONCE with the tool's result under the same `toolCallId`, so\n// the thread is not paused when the caller's next words arrive (the box answers a\n// new message on a thread paused on a client tool as if the tool never ran, and\n// a resume that came later would no longer match);\n// 2. turns the caller's audio off (D28, the worker's `hangUpAfter`), so nothing the\n// caller says from here pauses the goodbye or starts a turn. A turn LiveKit\n// commits from words it heard before still runs: it waits for that resume\n// (`pendingResume`), then sends nothing: the call is ending (`BoxLLM` `isEnding`);\n// 3. hangs up once the goodbye has played out and the resume has settled: the room\n// is deleted, then the job ends. \"Played out\" is LiveKit's `waitForIdle`, not the\n// agent state: a goodbye LiveKit already paused for caller noise reads `listening`\n// but is still the current speech. Turning the input off ends the caller's speech,\n// which arms LiveKit's false-interruption resume, and the goodbye plays on\n// (`@livekit/agents` 1.9.0: `onAudioEnabledChanged` in `voice/agent_session.ts`,\n// `onEndOfSpeech` in `voice/agent_activity.ts`).\n// A second `hang_up`, or one while D16 or D17 already end the call, is ignored. Only a\n// `TOOL_CALL_START` with exactly this name ends the call: never the reply's text.\nimport { log } from '@livekit/agents';\nimport type { ClientToolDeclaration } from '@stackbone/llm-wire';\nimport type { ClientToolCall } from './box-llm';\nimport { aguiTurn, runErrorOf, TURN_IDLE_TIMEOUT_MS, type AguiCallWire } from './box-wire';\n\nexport const HANG_UP_TOOL: ClientToolDeclaration = Object.freeze({\n name: 'hang_up',\n description: 'End the phone call. Say goodbye first, in the same reply, then call it.',\n parameters: Object.freeze({ type: 'object', properties: {}, additionalProperties: false }),\n});\n\n/** The tool's result the box resumes the run with. */\nexport const HANG_UP_RESULT = 'Done: the call ends after your goodbye.';\n\n/** The longest wait for the goodbye to play out before the call ends anyway. */\nconst HANG_UP_PLAYOUT_CAP_MS = 60_000;\n\n/** The part of LiveKit's `AgentSession` that tells when every speech has played out. */\nexport interface IdleSource {\n /** Rejects once the session is closing (the caller hung up). */\n waitForIdle(): Promise<unknown>;\n}\n\n/**\n * Resolves once the session is idle (no speech playing, paused or queued), or is\n * closing, or after `capMs` whatever its state, so a speech that never ends still ends.\n */\nexport const untilGoodbyePlayed = (\n session: IdleSource,\n capMs = HANG_UP_PLAYOUT_CAP_MS,\n): Promise<void> => {\n let cap: ReturnType<typeof setTimeout> | undefined;\n const capped = new Promise<void>((resolve) => {\n cap = setTimeout(() => {\n log().warn({ capMs }, 'voice worker: the goodbye did not play out; hanging up anyway');\n resolve();\n }, capMs);\n });\n const idle = session.waitForIdle().then(\n () => undefined,\n () => undefined,\n );\n return Promise.race([idle, capped]).finally(() => clearTimeout(cap));\n};\n\nexport interface HangUpDeps {\n /** The call's wire options; the resume carries them as every turn does. */\n readonly wire: AguiCallWire;\n /** Resolves once the agent's goodbye has played out. */\n untilPlayed(): Promise<void>;\n /** Marks the call as ending and turns the caller's audio off at once, then hangs up once `played()` settles. */\n hangUpAfter(played: () => Promise<unknown>, reason: string): void;\n /** Whether the call is already ending (an earlier `hang_up`, D16, D17). */\n isEnding(): boolean;\n /** The resume was refused, failed, cut before `RUN_FINISHED` or timed out; the call still ends. */\n onResumeFailed(err: unknown): void;\n}\n\n/** The `BoxLLM` hooks of the `hang_up` client tool. */\nexport const createHangUp = ({\n wire,\n untilPlayed,\n hangUpAfter,\n isEnding,\n onResumeFailed,\n}: HangUpDeps) => {\n let resumeInFlight: Promise<void> | undefined;\n /** Answers the paused call; the resumed reply is never spoken. Never rejects. */\n const resume = async (toolCallId: string): Promise<void> => {\n let finished = false;\n try {\n for await (const ev of aguiTurn({\n ...wire,\n tools: [HANG_UP_TOOL],\n toolResult: { toolCallId, content: HANG_UP_RESULT },\n // No turn timer watches it: a box that hangs on it must not hold the call open.\n signal: AbortSignal.timeout(TURN_IDLE_TIMEOUT_MS),\n })) {\n if (ev.kind === 'error') throw runErrorOf(ev);\n if (ev.kind === 'finish') finished = true;\n }\n // As on a turn: a stream closed without its terminal frame did not finish.\n if (!finished) throw new Error('box closed the resume before RUN_FINISHED');\n } catch (err) {\n // As on a turn: the run had ended, so the timeout or a cut connection while the\n // box closes the stream changes nothing about how it ended.\n if (!finished) onResumeFailed(err);\n }\n };\n return {\n onClientToolCall: ({ id }: ClientToolCall): void => {\n if (isEnding()) return;\n const resumed = resume(id).finally(() => {\n resumeInFlight = undefined;\n });\n resumeInFlight = resumed;\n hangUpAfter(() => Promise.allSettled([untilPlayed(), resumed]), 'agent hung up');\n },\n pendingResume: (): Promise<void> | undefined => resumeInFlight,\n };\n};\n", "// The call's LiveKit pieces from its effective voice settings (FDE-62 S5).\n//\n// One function maps the dispatch's `CallVoiceSettings` onto what the job\n// hands LiveKit: the session options (VAD, STT, TTS, end of turn, interruptions,\n// expressive mode), the agent the session starts (phone line or not) and the\n// AG-UI context each box turn carries (the TTS markup guide, with expressive). The\n// settings are its only input, never env, so a value saved in Studio is the next\n// call's. The LLM is not here: the worker builds `BoxLLM` per call (D1).\n// The STT, TTS and turn detector come in through factories, because LiveKit\n// Inference reads its credentials when constructed and the specs run with none.\nimport { type inference, type stt, tts, type VAD, voice } from '@livekit/agents';\nimport type * as silero from '@livekit/agents-plugin-silero';\nimport {\n type CallVoiceSettings,\n isCartesiaTtsModel,\n VOICE_TTS_MARKUP_CONTEXT,\n} from '@stackbone/validators';\nimport type { TurnContextEntry } from '@stackbone/llm-wire';\nimport { dropThousandsComma } from './number-text';\nimport { phoneLine } from './phone-voice';\n\ntype TurnDetectionMode = NonNullable<\n NonNullable<voice.AgentSessionOptions['turnHandling']>['turnDetection']\n>;\nexport type TurnDetector = Exclude<TurnDetectionMode, string>;\nexport type SttOptions = NonNullable<ConstructorParameters<typeof inference.STT>[0]>;\nexport type TtsOptions = NonNullable<ConstructorParameters<typeof inference.TTS>[0]>;\n\n/** The factories of the pieces that read LiveKit's credentials when built. */\nexport interface VoicePieceFactories {\n createStt(options: SttOptions): stt.STT;\n createTts(options: TtsOptions): tts.TTS;\n createTurnDetector(options: inference.TurnDetectorOptions): TurnDetector;\n}\n\n/** What `voiceSessionOptions` builds the call from. */\nexport interface VoicePieces extends VoicePieceFactories {\n /**\n * The Silero VAD the process prewarmed (`loadVad`): loading the model per call is\n * slow, so each call tunes the one the process holds.\n */\n vad: VAD & Pick<silero.VAD, 'updateOptions'>;\n}\n\n/** What the job hands LiveKit for one call. */\nexport interface VoiceSessionPlan {\n /** The session's options but its LLM, which the worker builds per call. */\n session: Omit<voice.AgentSessionOptions, 'llm'>;\n /** The agent the session starts. */\n agent: voice.Agent;\n /** The AG-UI context every box turn of the call carries. */\n boxContext: readonly TurnContextEntry[];\n}\n\n/**\n * LiveKit Inference STT options: Flux takes the language as a hint, the others as\n * their language. AssemblyAI names its keyterms `keyterms_prompt`.\n */\nconst sttOptions = ({ sttModel, language, keyterms }: CallVoiceSettings): SttOptions => {\n const terms = keyterms.length > 0 ? [...keyterms] : undefined;\n const keyterm = terms ? { keyterm: terms } : undefined;\n if (sttModel.startsWith('deepgram/flux')) {\n return { model: sttModel, modelOptions: { language_hint: language, ...keyterm } };\n }\n if (sttModel.startsWith('assemblyai/')) {\n // LiveKit's AssemblyAI page: with `stt` end of turn, Universal-3.6 Pro ends a turn\n // on final punctuation after silence and wants `max_turn_silence` at 1000 ms.\n return {\n model: sttModel,\n language,\n modelOptions: { max_turn_silence: 1000, ...(terms ? { keyterms_prompt: terms } : {}) },\n };\n }\n return { model: sttModel, language, ...(keyterm ? { modelOptions: keyterm } : {}) };\n};\n\n/**\n * LiveKit Inference TTS options: the voice as its own option, and `ttsSpeed` as the\n * model's `speed` on a Cartesia model, the only one of the known list LiveKit\n * Inference takes a speed for. At 1 it sends none, so the TTS keeps its own pace.\n * The dispatch refuses a speed other than 1 on another model (D18); the model check\n * stays so a speed never reaches a TTS that takes none.\n */\nconst ttsOptions = ({ ttsModel, ttsVoice, ttsSpeed, language }: CallVoiceSettings): TtsOptions => {\n const options: TtsOptions = { model: ttsModel, voice: ttsVoice, language };\n if (ttsSpeed === 1 || !isCartesiaTtsModel(ttsModel)) return options;\n return { ...options, modelOptions: { speed: ttsSpeed } };\n};\n\n/**\n * With `expressive`, the TTS's markup guide as the box turn's context entry. LiveKit\n * injects that guide into its own chat context only, and `BoxLLM` sends the box the\n * last user text, so without the entry the agent's model never learns the markup the\n * TTS reads. The steering is LiveKit's default, the one an `expressive: true` session\n * resolves, so the box gets the guide LiveKit would have given its own LLM. A TTS that\n * speaks no markup has no guide and sends no entry.\n */\nconst markupContext = (expressive: boolean, speech: tts.TTS): TurnContextEntry[] => {\n if (!expressive) return [];\n const guide = speech.markup.llmInstructions({\n speechSteering: tts.DEFAULT_SPEECH_STEERING_OPTIONS,\n });\n return guide === undefined ? [] : [{ description: VOICE_TTS_MARKUP_CONTEXT, value: guide }];\n};\n\n/** A call's agent whose TTS audio goes out through the phone-line effect (D5). */\nclass PhoneLineAgent extends voice.Agent {\n override async ttsNode(\n text: Parameters<voice.Agent['ttsNode']>[0],\n modelSettings: voice.ModelSettings,\n ): ReturnType<voice.Agent['ttsNode']> {\n const audio = await super.ttsNode(text, modelSettings);\n return audio && phoneLine(audio);\n }\n}\n\n/** The session options and the agent of one call with `settings`. */\nexport const voiceSessionOptions = (\n settings: CallVoiceSettings,\n pieces: VoicePieces,\n): VoiceSessionPlan => {\n const { turn, vad, interruptions } = settings;\n // Every call sets all three, so a call never inherits the previous call's values\n // from the reused process. Both sides count in ms.\n pieces.vad.updateOptions({\n activationThreshold: vad.activationThreshold,\n minSpeechDuration: vad.minSpeechMs,\n minSilenceDuration: vad.minSilenceMs,\n });\n const speech = pieces.createTts(ttsOptions(settings));\n return {\n session: {\n vad: pieces.vad,\n stt: pieces.createStt(sttOptions(settings)),\n tts: speech,\n // The prompt asks for numbers in words; the comma filter is the net for digits.\n ttsTextTransforms: ['filter_markdown', 'filter_emoji', dropThousandsComma],\n // LiveKit's TTS only normalizes the model's markup, converts it to Cartesia's\n // and drops bracket cues when the session is expressive: without it the caller\n // hears the tags. The guide LiveKit then injects into its own chat context never\n // reaches the box (`BoxLLM` sends the last user text); `boxContext` carries it.\n expressive: settings.expressive,\n // LiveKit 1.9.0 feeds the STT silence for the first 3 s the agent speaks (its AEC\n // warmup), so a caller who talks over the greeting lost the start of the sentence\n // (\"Hola Andr\u00E9s\u2026\" reached the box as \"Andr\u00E9s\u2026\"). The browser and the phone network\n // already cancel echo; LiveKit itself disables it for outbound SIP.\n aecWarmupDuration: null,\n turnHandling: {\n // `stt`: an STT that ends turns itself (Flux) decides; `livekit`: LiveKit's detector.\n turnDetection:\n turn.mode === 'stt'\n ? 'stt'\n : pieces.createTurnDetector({ unlikelyThreshold: turn.detectorThreshold }),\n endpointing: { minDelay: turn.minEndpointingDelayMs, maxDelay: turn.maxEndpointingDelayMs },\n // LiveKit 1.9.0 reads `enabled` as the session's `allowInterruptions`, the\n // default of every speech; the old top-level option is deprecated.\n // `adaptive` explicitly: with no mode, LiveKit 1.9.0 (`resolveInterruptionDetector`)\n // turns adaptive interruption off unless the worker runs on LiveKit Cloud or in its\n // deprecated `dev` mode, and `stackbone dev` runs `start` since S15, so a \"s\u00ED\", a\n // \"vale\" or a cough cut the agent off. LiveKit still skips it, with a warning,\n // when interruptions are off or the STT gives no word-aligned transcript.\n interruption: {\n mode: 'adaptive',\n enabled: interruptions.enabled,\n minDuration: interruptions.minDurationMs,\n minWords: interruptions.minWords,\n },\n // A preemptive reply is a box turn: a durable run, with tools, started on a\n // guess at what the caller said. Off explicitly, whatever LiveKit's default.\n preemptiveGeneration: { enabled: false, preemptiveTts: false },\n },\n },\n // The box holds the prompt: LiveKit's own chat context stays prompt-less.\n agent: settings.phoneLine\n ? new PhoneLineAgent({ instructions: '' })\n : new voice.Agent({ instructions: '' }),\n boxContext: markupContext(settings.expressive, speech),\n };\n};\n", "// TTS text transform: drop the English thousands comma before Gradium reads it.\n//\n// Spanish uses the comma as the decimal mark, so Gradium read \"1,000 euros\" as\n// \"uno coma mil euros\" (checked by feeding its audio back through Deepgram,\n// 2026-09-22); `rewrite_rules: 'es'` did not change that. The prompt asks for\n// numbers in words; this is the net for when the model writes digits anyway.\n//\n// The LLM streams tokens, so \"1\", \",\", \"000\" can arrive in separate chunks. A\n// trailing run of digits and separators is held back until the next chunk (or\n// the end) shows where the number stops.\n\nimport { type ReadableStream, TransformStream } from 'node:stream/web';\n\nconst THOUSANDS_COMMA = /(\\d),(?=\\d{3}(?!\\d))/g;\nconst TRAILING_NUMBER = /[\\d.,]+$/;\n\nconst rewrite = (text: string): string => text.replace(THOUSANDS_COMMA, '$1');\n\nexport const dropThousandsComma = (input: ReadableStream<string>): ReadableStream<string> => {\n let pending = '';\n return input.pipeThrough(\n new TransformStream<string, string>({\n transform(chunk, controller) {\n const text = pending + chunk;\n const tail = TRAILING_NUMBER.exec(text)?.[0] ?? '';\n pending = tail;\n const ready = text.slice(0, text.length - tail.length);\n if (ready) controller.enqueue(rewrite(ready));\n },\n flush(controller) {\n if (pending) controller.enqueue(rewrite(pending));\n },\n }),\n );\n};\n", "// Makes the agent's voice sound like it comes through a phone line.\n//\n// The `phoneLine` voice setting (D5) turns it on: `session-options.ts` then gives\n// the call an agent whose `ttsNode` pipes its audio through here. Moved unchanged\n// from the catalog branch's worker.\n//\n// A phone call carries roughly 300\u20133400 Hz, so the TTS audio is band-passed to\n// that range (a 2nd-order high-pass plus two cascaded 2nd-order low-passes,\n// RBJ cookbook biquads) and driven lightly into a soft clip like a small phone\n// mic. No line hiss: at about -50 dBFS it read as constant white noise to the\n// user (2026-09-23). Applied per TTS stream in the agent's\n// `ttsNode`; the ambience and the thinking cue are not touched.\nimport { AudioFrame } from '@livekit/rtc-node';\nimport { type ReadableStream, TransformStream } from 'node:stream/web';\n\nconst LOW_CUT_HZ = 300;\nconst HIGH_CUT_HZ = 3400;\nconst Q = Math.SQRT1_2;\n/** Soft-clip drive; the output is renormalised so loudness stays about the same. */\nconst DRIVE = 1.8;\n\ninterface Biquad {\n b0: number;\n b1: number;\n b2: number;\n a1: number;\n a2: number;\n x1: number;\n x2: number;\n y1: number;\n y2: number;\n}\n\nconst biquad = (kind: 'lowpass' | 'highpass', hz: number, sampleRate: number): Biquad => {\n const w0 = (2 * Math.PI * hz) / sampleRate;\n const alpha = Math.sin(w0) / (2 * Q);\n const cos = Math.cos(w0);\n const a0 = 1 + alpha;\n const [b0, b1, b2] =\n kind === 'lowpass'\n ? [(1 - cos) / 2, 1 - cos, (1 - cos) / 2]\n : [(1 + cos) / 2, -(1 + cos), (1 + cos) / 2];\n return {\n b0: b0 / a0,\n b1: b1 / a0,\n b2: b2 / a0,\n a1: (-2 * cos) / a0,\n a2: (1 - alpha) / a0,\n x1: 0,\n x2: 0,\n y1: 0,\n y2: 0,\n };\n};\n\nconst run = (f: Biquad, x: number): number => {\n const y = f.b0 * x + f.b1 * f.x1 + f.b2 * f.x2 - f.a1 * f.y1 - f.a2 * f.y2;\n f.x2 = f.x1;\n f.x1 = x;\n f.y2 = f.y1;\n f.y1 = y;\n return y;\n};\n\n/** Pipes a stream of mono TTS frames (what the TTS emits) through the phone-line effect. */\nexport const phoneLine = (audio: ReadableStream<AudioFrame>): ReadableStream<AudioFrame> => {\n let chain: Biquad[] | undefined;\n let chainRate = 0;\n const norm = Math.tanh(DRIVE);\n return audio.pipeThrough(\n new TransformStream<AudioFrame, AudioFrame>({\n transform(frame, controller) {\n if (!chain || chainRate !== frame.sampleRate) {\n chainRate = frame.sampleRate;\n chain = [\n biquad('highpass', LOW_CUT_HZ, chainRate),\n biquad('lowpass', HIGH_CUT_HZ, chainRate),\n biquad('lowpass', HIGH_CUT_HZ, chainRate),\n ];\n }\n const out = new Int16Array(frame.data.length);\n for (let i = 0; i < frame.data.length; i++) {\n let y = frame.data[i]! / 32768;\n for (const f of chain) y = run(f, y);\n y = Math.tanh(y * DRIVE) / norm;\n out[i] = Math.max(-32768, Math.min(32767, Math.round(y * 32767)));\n }\n controller.enqueue(\n new AudioFrame(out, frame.sampleRate, frame.channels, frame.samplesPerChannel),\n );\n },\n }),\n );\n};\n", "// Each call's spans, sent to the box that took the call (FDE-62 S11).\n//\n// The worker reads no variable of its own (S10), so whether a call is traced and\n// where its spans go both come from the dispatch: the box sets `traces: true` only\n// when it exports traces to an OTLP endpoint of its own, and the spans then go to\n// `<boxUrl>/voice/v1/traces` with the call token as the bearer. The box relays them\n// to its collector with the operator's headers, which never leave the box. Without\n// the flag nothing is installed and LiveKit keeps its own tracing as it is.\n//\n// The call's session key goes on every SPAN as an attribute, through LiveKit's\n// `setTracerProvider` metadata, not on the resource: the provider is swapped\n// process-wide, and a span attribute is what LiveKit documents per job. The\n// conversation itself is stripped before export (`allowPii: false`): the box exports\n// no message content unless its operator opts in, and the dispatch carries no such\n// opt-in. LiveKit Cloud's own exporter is not affected, it attaches through the\n// fan-out with its own processors.\n//\n// The resource names the service, `stackbone-voice-worker`, over OpenTelemetry's\n// default one (`telemetry.sdk.*`): the relay forwards the bytes as they are, so\n// without it every span lands as `unknown_service:node`. A batch that fails to export\n// is logged here, once per batch: the batch processor reports it only to\n// OpenTelemetry's `diag`, which neither the worker nor LiveKit sets up.\nimport { log, telemetry } from '@livekit/agents';\nimport { ExportResultCode } from '@opentelemetry/core';\nimport { defaultResource, resourceFromAttributes } from '@opentelemetry/resources';\nimport {\n BatchSpanProcessor,\n NodeTracerProvider,\n type SpanExporter,\n} from '@opentelemetry/sdk-trace-node';\nimport { VOICE_TRACES_PATH, type VoiceDispatch } from '@stackbone/validators';\nimport { boxEndpoint } from '../box-wire';\n\n/** Where one call's spans go, and the only header they carry. */\nexport interface CallTraceTarget {\n readonly url: string;\n readonly headers: Readonly<Record<string, string>>;\n}\n\n/** The OTLP pieces, injected so a spec exports nowhere. `main.ts` builds the real ones. */\nexport interface CallTracesDeps {\n createExporter(target: CallTraceTarget): SpanExporter;\n setTracerProvider: typeof telemetry.setTracerProvider;\n}\n\n/** The `service.name` of every span the worker exports. */\nconst SERVICE_NAME = 'stackbone-voice-worker';\n\n/**\n * `exporter`, with a warn for each batch it fails to export: the box refused it, could\n * not be reached, or the export never left the process. The log names the call by its\n * session key and carries the exporter's error message; the call token travels in a\n * header and never appears. `done` runs whatever the log does, so a logging failure\n * cannot hold the batch until the processor's export timeout.\n */\nconst warnOnRefusedBatch = (exporter: SpanExporter, sessionKey: string): SpanExporter => ({\n export: (spans, done) =>\n exporter.export(spans, (result) => {\n try {\n if (result.code !== ExportResultCode.SUCCESS) {\n log().warn(\n { sessionKey, error: result.error?.message ?? 'unknown' },\n 'voice worker: a batch of call spans failed to export to the box',\n );\n }\n } finally {\n done(result);\n }\n }),\n shutdown: () => exporter.shutdown(),\n ...(exporter.forceFlush ? { forceFlush: exporter.forceFlush.bind(exporter) } : {}),\n});\n\n/** The part of LiveKit's job a traced call reads. */\nexport interface TracedJob {\n readonly jobId: string;\n /** The room's name once the job has joined it. */\n readonly room: string | undefined;\n /** LiveKit's `JobContext.addShutdownCallback`: awaited before the job process exits. */\n addShutdownCallback(callback: () => Promise<void>): void;\n}\n\n/**\n * Sends this call's spans to the box when its dispatch asks for it. Call it before the session starts: LiveKit attaches its cloud tracing to\n * the provider in place when the session starts.\n */\nexport const installCallTraces = (\n dispatch: VoiceDispatch,\n job: TracedJob,\n deps: CallTracesDeps,\n): void => {\n if (dispatch.traces !== true) return;\n const exporter = warnOnRefusedBatch(\n deps.createExporter({\n url: boxEndpoint(dispatch.boxUrl, VOICE_TRACES_PATH),\n headers: { authorization: `Bearer ${dispatch.callToken}` },\n }),\n dispatch.sessionKey,\n );\n // OTel 2.x providers take their processors when built; the fan-out is where LiveKit\n // adds its metadata, PII and cloud processors later. First, so a span is labelled\n // and stripped before the batch exports it.\n const fanout = new telemetry.FanoutSpanProcessor();\n const provider = new NodeTracerProvider({\n resource: defaultResource().merge(resourceFromAttributes({ 'service.name': SERVICE_NAME })),\n spanProcessors: [fanout, new BatchSpanProcessor(exporter)],\n });\n // The async context manager, so a span started inside another keeps its parent.\n provider.register();\n deps.setTracerProvider(provider, {\n metadata: {\n 'stackbone.voice.session_key': dispatch.sessionKey,\n ...(job.room === undefined ? {} : { 'livekit.room': job.room }),\n 'livekit.job_id': job.jobId,\n },\n registerSpanProcessor: (processor) => fanout.add(processor),\n allowPii: false,\n });\n // Ships what the batch still holds before the job process exits.\n job.addShutdownCallback(() => provider.shutdown());\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["telemetry", "voice", "require", "log", "voice", "z", "z", "z", "z", "z", "z", "z", "z", "z", "z", "z", "z", "z", "z", "z", "z", "z", "z", "z", "z", "z", "z", "z", "z", "z", "z", "z", "z", "log", "log", "voice", "AudioFrame", "TransformStream", "voice", "log", "log", "log", "voice", "voice", "telemetry"]
|
|
7
|
+
}
|