@moureau/pipeflow 0.0.2 → 0.0.3

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.
Files changed (89) hide show
  1. package/README.md +39 -17
  2. package/dist/cjs/client/client.js +2 -0
  3. package/dist/cjs/client/client.js.map +7 -0
  4. package/dist/cjs/client/index.js +2 -0
  5. package/dist/cjs/client/index.js.map +7 -0
  6. package/dist/cjs/client/json.js +2 -0
  7. package/dist/cjs/client/json.js.map +7 -0
  8. package/dist/cjs/client/protocol/websocket.js +2 -0
  9. package/dist/cjs/client/protocol/websocket.js.map +7 -0
  10. package/dist/cjs/client/protocol.js +2 -0
  11. package/dist/cjs/client/protocol.js.map +7 -0
  12. package/dist/cjs/conversations/conversation/conversation.js +1 -1
  13. package/dist/cjs/conversations/conversation/conversation.js.map +3 -3
  14. package/dist/cjs/conversations/conversations.js +1 -1
  15. package/dist/cjs/conversations/conversations.js.map +2 -2
  16. package/dist/cjs/conversations/orchestration/orchestrator/generation/generation.js +1 -1
  17. package/dist/cjs/conversations/orchestration/orchestrator/generation/generation.js.map +3 -3
  18. package/dist/cjs/conversations/orchestration/orchestrator/orchestrator.js +1 -1
  19. package/dist/cjs/conversations/orchestration/orchestrator/orchestrator.js.map +3 -3
  20. package/dist/cjs/conversations/orchestration/orchestrator/speech/speech.js +1 -1
  21. package/dist/cjs/conversations/orchestration/orchestrator/speech/speech.js.map +3 -3
  22. package/dist/cjs/conversations/orchestration/orchestrator/tools/tools.js +1 -1
  23. package/dist/cjs/conversations/orchestration/orchestrator/tools/tools.js.map +3 -3
  24. package/dist/cjs/conversations/orchestration/test-harness.js +1 -1
  25. package/dist/cjs/conversations/orchestration/test-harness.js.map +2 -2
  26. package/dist/cjs/pipeflow/pipeflow.js +1 -1
  27. package/dist/cjs/pipeflow/pipeflow.js.map +2 -2
  28. package/dist/cjs/providers/llm/adapters/openai-compatible.js +6 -6
  29. package/dist/cjs/providers/llm/adapters/openai-compatible.js.map +3 -3
  30. package/dist/cjs/providers/llm/types.js.map +2 -2
  31. package/dist/cjs/providers/stt/adapters/openrouter/openrouter.js +1 -1
  32. package/dist/cjs/providers/stt/adapters/openrouter/openrouter.js.map +3 -3
  33. package/dist/cjs/providers/tts/adapters/kokoro/kokoro.js +3 -3
  34. package/dist/cjs/providers/tts/adapters/kokoro/kokoro.js.map +3 -3
  35. package/dist/cjs/providers/tts/adapters/openrouter/openrouter.js +1 -1
  36. package/dist/cjs/providers/tts/adapters/openrouter/openrouter.js.map +3 -3
  37. package/dist/esm/client/client.js +2 -0
  38. package/dist/esm/client/client.js.map +7 -0
  39. package/dist/esm/client/index.js +2 -0
  40. package/dist/esm/client/index.js.map +7 -0
  41. package/dist/esm/client/json.js +2 -0
  42. package/dist/esm/client/json.js.map +7 -0
  43. package/dist/esm/client/protocol/websocket.js +2 -0
  44. package/dist/esm/client/protocol/websocket.js.map +7 -0
  45. package/dist/esm/client/protocol.js +1 -0
  46. package/dist/esm/client/protocol.js.map +7 -0
  47. package/dist/esm/conversations/conversation/conversation.js +1 -1
  48. package/dist/esm/conversations/conversation/conversation.js.map +3 -3
  49. package/dist/esm/conversations/conversations.js +1 -1
  50. package/dist/esm/conversations/conversations.js.map +2 -2
  51. package/dist/esm/conversations/orchestration/orchestrator/generation/generation.js +1 -1
  52. package/dist/esm/conversations/orchestration/orchestrator/generation/generation.js.map +3 -3
  53. package/dist/esm/conversations/orchestration/orchestrator/orchestrator.js +1 -1
  54. package/dist/esm/conversations/orchestration/orchestrator/orchestrator.js.map +3 -3
  55. package/dist/esm/conversations/orchestration/orchestrator/speech/speech.js +1 -1
  56. package/dist/esm/conversations/orchestration/orchestrator/speech/speech.js.map +3 -3
  57. package/dist/esm/conversations/orchestration/orchestrator/tools/tools.js +1 -1
  58. package/dist/esm/conversations/orchestration/orchestrator/tools/tools.js.map +3 -3
  59. package/dist/esm/conversations/orchestration/test-harness.js +1 -1
  60. package/dist/esm/conversations/orchestration/test-harness.js.map +2 -2
  61. package/dist/esm/pipeflow/pipeflow.js +1 -1
  62. package/dist/esm/pipeflow/pipeflow.js.map +2 -2
  63. package/dist/esm/providers/llm/adapters/openai-compatible.js +6 -6
  64. package/dist/esm/providers/llm/adapters/openai-compatible.js.map +3 -3
  65. package/dist/esm/providers/llm/types.js.map +2 -2
  66. package/dist/esm/providers/stt/adapters/openrouter/openrouter.js +1 -1
  67. package/dist/esm/providers/stt/adapters/openrouter/openrouter.js.map +3 -3
  68. package/dist/esm/providers/tts/adapters/kokoro/kokoro.js +3 -3
  69. package/dist/esm/providers/tts/adapters/kokoro/kokoro.js.map +3 -3
  70. package/dist/esm/providers/tts/adapters/openrouter/openrouter.js +1 -1
  71. package/dist/esm/providers/tts/adapters/openrouter/openrouter.js.map +3 -3
  72. package/dist/types/client/client.d.ts +186 -0
  73. package/dist/types/client/index.d.ts +31 -0
  74. package/dist/types/client/json.d.ts +13 -0
  75. package/dist/types/client/protocol/websocket.d.ts +56 -0
  76. package/dist/types/client/protocol.d.ts +46 -0
  77. package/dist/types/conversations/conversation/conversation.d.ts +45 -0
  78. package/dist/types/conversations/conversations.d.ts +24 -0
  79. package/dist/types/conversations/orchestration/orchestrator/generation/generation.d.ts +17 -0
  80. package/dist/types/conversations/orchestration/orchestrator/orchestrator.d.ts +25 -6
  81. package/dist/types/conversations/orchestration/orchestrator/speech/speech.d.ts +31 -2
  82. package/dist/types/conversations/orchestration/orchestrator/tools/tools.d.ts +41 -7
  83. package/dist/types/conversations/orchestration/test-harness.d.ts +4 -0
  84. package/dist/types/pipeflow/pipeflow.d.ts +8 -0
  85. package/dist/types/providers/llm/types.d.ts +4 -1
  86. package/dist/types/providers/stt/adapters/openrouter/openrouter.d.ts +103 -1
  87. package/dist/types/providers/tts/adapters/kokoro/kokoro.d.ts +1 -1
  88. package/dist/types/providers/tts/adapters/openrouter/openrouter.d.ts +26 -7
  89. package/package.json +7 -1
package/README.md CHANGED
@@ -309,7 +309,8 @@ The conversation emits a typed event stream the application can subscribe to:
309
309
  * `transcript` — a transcript entry
310
310
  * `audio` — generated audio to play
311
311
  * `generation` — an agent generation
312
- * `tool-call` / `tool-call-result` — tool execution round trips
312
+ * `tool-call` / `tool-call-result` — every tool round trip (auto-executed by
313
+ default; observable even when the framework runs the tool)
313
314
  * `interrupt` — an interruption occurred
314
315
  * `error` — a provider failure
315
316
  * `start` / `stop` / `state` — lifecycle
@@ -515,9 +516,8 @@ const jarvis = pipeflow.agent({
515
516
  });
516
517
  ```
517
518
 
518
- Tools execute in **your backend**.
519
-
520
- Pipeflow does not execute arbitrary application code.
519
+ Tools execute in **your backend**: the `execute` function is your code, and it
520
+ runs in your process — Pipeflow only invokes the callbacks you registered.
521
521
 
522
522
  ```text
523
523
  Pipeflow
@@ -540,28 +540,50 @@ Pipeflow does not execute arbitrary application code.
540
540
 
541
541
  This allows tools to access your database, APIs, Discord bot, business logic, filesystem, or anything else your application controls.
542
542
 
543
- In a conversation, tool calls never execute inside Pipeflow. The orchestrator
544
- emits a `tool-call` event with the requested tool and its arguments; your
545
- backend executes it and reports back:
543
+ ### In a conversation: tools auto-execute
544
+
545
+ Attach tools to an agent and they just work — the orchestrator runs each tool
546
+ and feeds the result back into the generation (same loop as `Agent.run()`):
546
547
 
547
548
  ```ts
548
- conversation.on("tool-call", async ({ call }) => {
549
- const result = await myBackend.execute(call.name, call.arguments);
549
+ const jarvis = pipeflow.agent({
550
+ name: "Jarvis",
551
+ context: "You are a helpful assistant.",
552
+ tools: [getWeather],
553
+ });
550
554
 
551
- conversation.resolveToolCall({
552
- id: call.id,
553
- result,
554
- });
555
+ const conversation = await pipeflow.conversations.create({ agents: [jarvis] });
556
+ // no tool-call handler needed — get_weather runs automatically
557
+ ```
558
+
559
+ The `tool-call` (and `tool-call-result`) events still fire for every call, so
560
+ you can observe or log them. A thrown tool error is caught and returned to the
561
+ model as `{ "error": ... }` so the agent can recover; an unknown tool name
562
+ reports `Unknown tool "..."` the same way. Tool calls the model issues together
563
+ run concurrently, and a hung tool is bounded by `toolTimeoutMs` (default 30s).
564
+
565
+ ### Opt out: resolve tool calls yourself
566
+
567
+ When a tool needs approval, or runs in a *different* backend, pass
568
+ `autoExecuteTools: false` (on `Pipeflow`, `pipeflow.conversations.create()`, or
569
+ `Conversation`) and the orchestrator hands the call to you:
570
+
571
+ ```ts
572
+ const conversation = await pipeflow.conversations.create({
573
+ agents: [jarvis],
574
+ autoExecuteTools: false,
575
+ });
576
+
577
+ conversation.on("tool-call", async ({ call }) => {
578
+ // call.arguments is already parsed JSON
579
+ const result = await myBackend.executeTool(call.name, call.arguments);
580
+ conversation.resolveToolCall({ id: call.id, result });
555
581
  });
556
582
  ```
557
583
 
558
584
  The agent's narration continues while the tool runs, and the generation resumes
559
585
  with the tool result once it is resolved.
560
586
 
561
- Tool calls the model requests together run concurrently: `run()` executes
562
- them in parallel, and in a conversation your backend resolves them in
563
- parallel.
564
-
565
587
  ## Meeting transcription
566
588
 
567
589
  A conversation does not require an agent.
@@ -0,0 +1,2 @@
1
+ "use strict";var s=Object.defineProperty;var u=Object.getOwnPropertyDescriptor;var p=Object.getOwnPropertyNames;var v=Object.prototype.hasOwnProperty;var d=(o,e)=>s(o,"name",{value:e,configurable:!0});var h=(o,e)=>{for(var t in e)s(o,t,{get:e[t],enumerable:!0})},m=(o,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of p(e))!v.call(o,i)&&i!==t&&s(o,i,{get:()=>e[i],enumerable:!(r=u(e,i))||r.enumerable});return o};var f=o=>m(s({},"__esModule",{value:!0}),o);var I={};h(I,{PipeflowClient:()=>C});module.exports=f(I);class C{static{d(this,"PipeflowClient")}protocol;audio;reconnect;defaultUserId;listeners=new Map;mediaStream=null;mediaRecorder=null;startMicPromise=null;reconnectAttempts=0;reconnectTimer=null;closedByUser=!1;nextSequence=0;constructor(e){this.protocol=e.protocol,this.audio=e.audio??{},this.reconnect={maxAttempts:e.reconnect?.maxAttempts??0,initialBackoffMs:e.reconnect?.initialBackoffMs??500,maxBackoffMs:e.reconnect?.maxBackoffMs??1e4},this.defaultUserId=e.userId??"anonymous",this.protocol.onMessage(t=>this.handleMessage(t)),this.protocol.onStatus(t=>this.handleStatus(t))}async connect(){this.closedByUser=!1,await this.protocol.connect(),this.audio.input&&await this.startMic()}async disconnect(){this.closedByUser=!0,this.clearReconnect(),this.stopMic(),await this.protocol.close()}sendText(e,t={}){this.send({type:"text-in",payload:{userId:t.userId??this.defaultUserId,text:e}})}sendAudio(e,t={}){this.send({type:"audio-in",payload:{userId:t.userId??this.defaultUserId,audio:e,sequence:e.sequence??this.nextSequence++}})}interrupt(e={}){this.send({type:"interrupt",payload:{conversationId:e.conversationId??null}})}resolveToolCall(e,t={}){this.send({type:"tool-result",payload:{conversationId:t.conversationId??null,result:e}})}playAudio(e){}on(e,t){let r=this.listeners.get(e);return r||(r=new Set,this.listeners.set(e,r)),r.add(t),()=>{r.delete(t)}}off(e,t){this.listeners.get(e)?.delete(t)}send(e){try{this.protocol.send(e)}catch(t){this.emit("error",{error:t instanceof Error?t:new Error(String(t))})}}handleMessage(e){switch(e.type){case"start":case"stop":case"interrupt":case"turn":case"transcript":case"partial-transcript":case"audio":case"audio-in":case"generation":case"tool-call":case"tool-call-result":case"state":{this.emit(e.type,e.payload);return}case"error":{const t=e.payload;this.emit("error",{conversationId:t?.conversationId,error:new Error(t?.message??"Server error")});return}default:return}}handleStatus(e){if(e==="open"){this.reconnectAttempts=0;return}e==="closed"&&!this.closedByUser&&this.scheduleReconnect()}scheduleReconnect(){if(this.reconnect.maxAttempts<=0)return;if(this.reconnectAttempts>=this.reconnect.maxAttempts){this.emit("error",{error:new Error("PipeflowClient: reconnect attempts exhausted")});return}const e=this.reconnectAttempts++,t=Math.min(this.reconnect.initialBackoffMs*2**e,this.reconnect.maxBackoffMs);this.reconnectTimer=setTimeout(()=>{this.protocol.connect().catch(r=>{this.emit("error",{error:r instanceof Error?r:new Error(String(r))}),this.scheduleReconnect()})},t)}clearReconnect(){this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.reconnectAttempts=0}startMic(){return this.mediaStream?Promise.resolve():(this.startMicPromise??=this.openMic().finally(()=>{this.startMicPromise=null}),this.startMicPromise)}async openMic(){const e=globalThis;if(!e.navigator?.mediaDevices?.getUserMedia||this.mediaStream)return;const t=e.MediaRecorder;if(t)try{const r={echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0,...this.audio.constraints};this.mediaStream=await e.navigator.mediaDevices.getUserMedia({audio:r}),this.mediaRecorder=new t(this.mediaStream),this.mediaRecorder.addEventListener("dataavailable",i=>{i.data.size!==0&&i.data.arrayBuffer().then(async l=>{const a={data:new Uint8Array(l),timestamp:Date.now(),sequence:this.nextSequence++};let n;try{n=this.audio.transform?await this.audio.transform(a):a}catch(c){this.emit("error",{error:c instanceof Error?c:new Error(String(c))});return}n&&this.sendAudio({...n,sequence:n.sequence??a.sequence})})}),this.mediaRecorder.start(250)}catch(r){this.emit("error",{error:r instanceof Error?r:new Error(String(r))})}}stopMic(){if(this.mediaRecorder&&this.mediaRecorder.state!=="inactive")try{this.mediaRecorder.stop()}catch{}if(this.mediaRecorder=null,this.mediaStream){for(const e of this.mediaStream.getTracks())e.stop();this.mediaStream=null}}emit(e,t){const r=this.listeners.get(e);if(r)for(const i of[...r])try{i(t)}catch{}}}
2
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/client/client.ts"],
4
+ "sourcesContent": ["/**\n * Pipeflow client SDK: talk to a Pipeflow server from a browser or Node.\n */\nimport type {\n AudioChunk,\n ConversationId,\n Generation,\n ToolCall,\n ToolCallResult,\n Turn,\n UserId,\n} from \"../conversations/types\";\nimport type { TranscriptEntry } from \"../conversations/transcription/transcription\";\nimport type {\n Protocol,\n ProtocolMessage,\n ProtocolStatus,\n} from \"./protocol\";\n\n/** Track constraints used when opening the mic. */\nexport interface CaptureConstraints {\n echoCancellation?: boolean;\n noiseSuppression?: boolean;\n autoGainControl?: boolean;\n}\n\n/** Optional mic auto-attach on `connect()`. */\nexport interface AudioInputOptions {\n /** Auto-attach `getUserMedia` and stream mic audio into the connection. */\n input?: boolean;\n /**\n * Gate or rewrite captured audio before it hits the wire: return a chunk\n * to send it, or `null` to drop it (a VAD gate). May be async. Chunks are\n * sent as the hook resolves and their `sequence` marks capture order, so\n * the server can reorder them before STT. Applies only to captured\n * audio; manual `sendAudio()` calls go out untouched.\n */\n transform?: (chunk: AudioChunk) => AudioChunk | null | Promise<AudioChunk | null>;\n /**\n * Mic track constraints. Each defaults to `true`. Echo cancellation is the\n * one that matters: without it the mic re-captures the agent's own reply.\n */\n constraints?: CaptureConstraints;\n}\n\nexport interface ReconnectOptions {\n /** Maximum reconnect attempts after an unexpected close. `0` disables it. */\n maxAttempts?: number;\n /** Initial backoff in ms; doubled each attempt up to `maxBackoffMs`. */\n initialBackoffMs?: number;\n /** Backoff cap in ms. */\n maxBackoffMs?: number;\n}\n\nexport interface PipeflowClientOptions {\n /** The wire protocol. */\n protocol: Protocol;\n /** Audio capture options. */\n audio?: AudioInputOptions;\n /** Auto-reconnect on unexpected close. */\n reconnect?: ReconnectOptions;\n /** Default user id for `sendText()` / `sendAudio()` when not provided. */\n userId?: UserId;\n}\n\n/**\n * Payloads emitted by `PipeflowClient` events, mirroring `ConversationEvents`\n * for the events that travel the wire.\n */\nexport interface PipeflowClientEventMap {\n start: { conversationId: ConversationId };\n stop: { conversationId: ConversationId };\n \"audio-in\": { conversationId: ConversationId; userId: UserId; audio: AudioChunk };\n audio: { conversationId: ConversationId; audio: AudioChunk };\n \"partial-transcript\": { conversationId: ConversationId; userId: UserId; text: string };\n turn: { conversationId: ConversationId; turn: Turn };\n transcript: { conversationId: ConversationId; entry: TranscriptEntry };\n generation: { conversationId: ConversationId; generation: Generation };\n \"tool-call\": { conversationId: ConversationId; call: ToolCall };\n \"tool-call-result\": { conversationId: ConversationId; result: ToolCallResult };\n interrupt: { conversationId: ConversationId };\n error: { conversationId?: ConversationId; error: Error };\n state: { conversationId: ConversationId; state: unknown };\n}\n\nexport type PipeflowClientEvent = keyof PipeflowClientEventMap;\n\nexport type Listener<E extends PipeflowClientEvent> = (\n event: PipeflowClientEventMap[E],\n) => void;\n\n/** The minimal browser surface mic capture needs; missing globals make it a no-op in Node. */\ninterface MicCapture {\n getUserMedia?: (constraints: {\n audio: boolean | CaptureConstraints;\n }) => Promise<MediaStreamLike>;\n}\ninterface MediaStreamLike {\n getTracks(): Array<{ stop(): void }>;\n}\ninterface MediaRecorderLike {\n state: string;\n start(timeslice: number): void;\n stop(): void;\n addEventListener(type: \"dataavailable\", listener: (event: { data: { size: number; arrayBuffer(): Promise<ArrayBuffer> } }) => void): void;\n}\ninterface BrowserGlobals {\n navigator?: { mediaDevices?: MicCapture };\n MediaRecorder?: new (stream: MediaStreamLike) => MediaRecorderLike;\n}\n\n/**\n * Typed client for a Pipeflow server: `connect()`, then events flow while\n * you send text and audio, then `disconnect()`.\n *\n * ```ts\n * const client = new PipeflowClient({\n * protocol: new WebSocketProtocol({ url: \"wss://\u2026/conversations/abc\" }),\n * audio: { input: true },\n * });\n * client.on(\"turn\", (e) => console.log(e.turn.text));\n * await client.connect();\n * ```\n *\n * Mic capture is opt-in via `audio.input`. Playback is the caller's job:\n * wire the `audio` event into your own `AudioContext` or `<audio>` queue,\n * or override `playAudio()`.\n */\nexport class PipeflowClient {\n private readonly protocol: Protocol;\n private readonly audio: AudioInputOptions;\n private readonly reconnect: Required<ReconnectOptions>;\n private readonly defaultUserId: UserId;\n\n private readonly listeners = new Map<PipeflowClientEvent, Set<Listener<PipeflowClientEvent>>>();\n private mediaStream: MediaStreamLike | null = null;\n private mediaRecorder: MediaRecorderLike | null = null;\n private startMicPromise: Promise<void> | null = null;\n private reconnectAttempts = 0;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private closedByUser = false;\n private nextSequence = 0;\n\n constructor(options: PipeflowClientOptions) {\n this.protocol = options.protocol;\n this.audio = options.audio ?? {};\n this.reconnect = {\n maxAttempts: options.reconnect?.maxAttempts ?? 0,\n initialBackoffMs: options.reconnect?.initialBackoffMs ?? 500,\n maxBackoffMs: options.reconnect?.maxBackoffMs ?? 10_000,\n };\n this.defaultUserId = options.userId ?? \"anonymous\";\n\n this.protocol.onMessage((m) => this.handleMessage(m));\n this.protocol.onStatus((s) => this.handleStatus(s));\n }\n\n // ---------------------------------------------------------------------\n // Lifecycle\n // ---------------------------------------------------------------------\n\n /**\n * Open the connection and, if `audio.input` is set, attach the mic.\n * Subsequent calls are no-ops while the connection is open.\n */\n async connect(): Promise<void> {\n this.closedByUser = false;\n await this.protocol.connect();\n if (this.audio.input) await this.startMic();\n }\n\n /** Close the connection, release the mic, and disable reconnect. */\n async disconnect(): Promise<void> {\n this.closedByUser = true;\n this.clearReconnect();\n this.stopMic();\n await this.protocol.close();\n }\n\n // ---------------------------------------------------------------------\n // Outbound: text + audio + tool resolution\n // ---------------------------------------------------------------------\n\n /**\n * Send a finalized text turn. Equivalent to `conversation.send({ userId, text })`.\n */\n sendText(text: string, options: { userId?: UserId } = {}): void {\n this.send({\n type: \"text-in\",\n payload: {\n userId: options.userId ?? this.defaultUserId,\n text,\n },\n });\n }\n\n /**\n * Send a raw audio chunk. `listen()` on the server is synchronous; call\n * this for every chunk as it arrives. The chunk's `sequence` marks\n * capture order for the server's reorder buffer.\n */\n sendAudio(audio: AudioChunk, options: { userId?: UserId } = {}): void {\n this.send({\n type: \"audio-in\",\n payload: {\n userId: options.userId ?? this.defaultUserId,\n audio,\n sequence: audio.sequence ?? this.nextSequence++,\n },\n });\n }\n\n /** Request an interrupt on the server. */\n interrupt(options: { conversationId?: ConversationId } = {}): void {\n this.send({\n type: \"interrupt\",\n payload: { conversationId: options.conversationId ?? null },\n });\n }\n\n /** Resolve a tool call when `autoExecuteTools` is off on the server. */\n resolveToolCall(\n result: ToolCallResult,\n options: { conversationId?: ConversationId } = {},\n ): void {\n this.send({\n type: \"tool-result\",\n payload: {\n conversationId: options.conversationId ?? null,\n result,\n },\n });\n }\n\n /**\n * No-op playback hook. Override it, or listen to `audio` directly.\n */\n playAudio(_audio: AudioChunk): void {}\n\n // ---------------------------------------------------------------------\n // Event subscription\n // ---------------------------------------------------------------------\n\n /** Subscribe to an event. Returns an unsubscribe function. */\n on<E extends PipeflowClientEvent>(event: E, listener: Listener<E>): () => void {\n let set = this.listeners.get(event);\n if (!set) {\n set = new Set();\n this.listeners.set(event, set);\n }\n set.add(listener as Listener<PipeflowClientEvent>);\n return () => {\n set!.delete(listener as Listener<PipeflowClientEvent>);\n };\n }\n\n /** Remove a previously added listener. */\n off<E extends PipeflowClientEvent>(event: E, listener: Listener<E>): void {\n this.listeners.get(event)?.delete(listener as Listener<PipeflowClientEvent>);\n }\n\n // ---------------------------------------------------------------------\n // Internals\n // ---------------------------------------------------------------------\n\n private send(message: ProtocolMessage): void {\n try {\n this.protocol.send(message);\n } catch (err) {\n this.emit(\"error\", {\n error: err instanceof Error ? err : new Error(String(err)),\n });\n }\n }\n\n private handleMessage(message: ProtocolMessage): void {\n switch (message.type) {\n case \"start\":\n case \"stop\":\n case \"interrupt\":\n case \"turn\":\n case \"transcript\":\n case \"partial-transcript\":\n case \"audio\":\n case \"audio-in\":\n case \"generation\":\n case \"tool-call\":\n case \"tool-call-result\":\n case \"state\": {\n this.emit(message.type, message.payload as never);\n return;\n }\n case \"error\": {\n const payload = message.payload as { message?: string; conversationId?: ConversationId };\n this.emit(\"error\", {\n conversationId: payload?.conversationId,\n error: new Error(payload?.message ?? \"Server error\"),\n });\n return;\n }\n default:\n // Unknown message types are ignored to stay forward-compatible.\n return;\n }\n }\n\n private handleStatus(status: ProtocolStatus): void {\n if (status === \"open\") {\n this.reconnectAttempts = 0;\n return;\n }\n if (status === \"closed\" && !this.closedByUser) {\n this.scheduleReconnect();\n }\n }\n\n private scheduleReconnect(): void {\n if (this.reconnect.maxAttempts <= 0) return;\n if (this.reconnectAttempts >= this.reconnect.maxAttempts) {\n this.emit(\"error\", {\n error: new Error(\"PipeflowClient: reconnect attempts exhausted\"),\n });\n return;\n }\n const attempt = this.reconnectAttempts++;\n const delay = Math.min(\n this.reconnect.initialBackoffMs * 2 ** attempt,\n this.reconnect.maxBackoffMs,\n );\n this.reconnectTimer = setTimeout(() => {\n this.protocol\n .connect()\n .catch((err: unknown) => {\n this.emit(\"error\", {\n error: err instanceof Error ? err : new Error(String(err)),\n });\n this.scheduleReconnect();\n });\n }, delay);\n }\n\n private clearReconnect(): void {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n this.reconnectAttempts = 0;\n }\n\n /** Attach the mic once, even under concurrent `connect()` calls. */\n private startMic(): Promise<void> {\n if (this.mediaStream) return Promise.resolve();\n this.startMicPromise ??= this.openMic().finally(() => {\n this.startMicPromise = null;\n });\n return this.startMicPromise;\n }\n\n private async openMic(): Promise<void> {\n const globals = globalThis as unknown as BrowserGlobals;\n if (!globals.navigator?.mediaDevices?.getUserMedia) return;\n if (this.mediaStream) return;\n const Recorder = globals.MediaRecorder;\n if (!Recorder) return;\n try {\n const constraints: CaptureConstraints = {\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n ...this.audio.constraints,\n };\n this.mediaStream = await globals.navigator.mediaDevices.getUserMedia({\n audio: constraints,\n });\n this.mediaRecorder = new Recorder(this.mediaStream);\n this.mediaRecorder.addEventListener(\"dataavailable\", (event) => {\n if (event.data.size === 0) return;\n event.data.arrayBuffer().then(async (buffer) => {\n // Assigned before the transform: the sequence marks capture\n // order even when async hooks resolve out of order.\n const chunk: AudioChunk = {\n data: new Uint8Array(buffer),\n timestamp: Date.now(),\n sequence: this.nextSequence++,\n };\n let out: AudioChunk | null;\n try {\n out = this.audio.transform ? await this.audio.transform(chunk) : chunk;\n } catch (err) {\n this.emit(\"error\", {\n error: err instanceof Error ? err : new Error(String(err)),\n });\n return;\n }\n if (!out) return; // gated, e.g. by a VAD\n this.sendAudio({ ...out, sequence: out.sequence ?? chunk.sequence });\n });\n });\n this.mediaRecorder.start(250);\n } catch (err) {\n this.emit(\"error\", {\n error: err instanceof Error ? err : new Error(String(err)),\n });\n }\n }\n\n private stopMic(): void {\n if (this.mediaRecorder && this.mediaRecorder.state !== \"inactive\") {\n try {\n this.mediaRecorder.stop();\n } catch {\n // ignore\n }\n }\n this.mediaRecorder = null;\n if (this.mediaStream) {\n for (const track of this.mediaStream.getTracks()) track.stop();\n this.mediaStream = null;\n }\n }\n\n private emit<E extends PipeflowClientEvent>(\n event: E,\n payload: PipeflowClientEventMap[E],\n ): void {\n const set = this.listeners.get(event);\n if (!set) return;\n for (const listener of [...set]) {\n try {\n (listener as Listener<E>)(payload);\n } catch {\n // A faulty listener must not prevent delivery to others.\n }\n }\n }\n}"],
5
+ "mappings": "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",
6
+ "names": ["client_exports", "__export", "PipeflowClient", "__toCommonJS", "PipeflowClient", "__name", "options", "m", "s", "text", "audio", "result", "_audio", "event", "listener", "set", "message", "err", "payload", "status", "attempt", "delay", "globals", "Recorder", "constraints", "buffer", "chunk", "out", "track"]
7
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";var p=Object.defineProperty;var c=Object.getOwnPropertyDescriptor;var s=Object.getOwnPropertyNames;var P=Object.prototype.hasOwnProperty;var f=(t,o)=>{for(var r in o)p(t,r,{get:o[r],enumerable:!0})},a=(t,o,r,n)=>{if(o&&typeof o=="object"||typeof o=="function")for(let e of s(o))!P.call(t,e)&&e!==r&&p(t,e,{get:()=>o[e],enumerable:!(n=c(o,e))||n.enumerable});return t};var C=t=>a(p({},"__esModule",{value:!0}),t);var m={};f(m,{PipeflowClient:()=>i.PipeflowClient,WebSocketProtocol:()=>l.WebSocketProtocol});module.exports=C(m);var i=require("./client.js"),l=require("./protocol/websocket.js");
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/client/index.ts"],
4
+ "sourcesContent": ["/**\n * Browser/client SDK for talking to a Pipeflow server.\n *\n * The client is intentionally tiny: a typed event surface that mirrors\n * `Conversation`, backed by a swappable wire protocol. It knows nothing about\n * STT/LLM/TTS: it forwards your input and surfaces what comes back.\n *\n * ```ts\n * import { PipeflowClient, WebSocketProtocol } from \"@moureau/pipeflow/client\";\n *\n * const client = new PipeflowClient({\n * protocol: new WebSocketProtocol({\n * url: \"wss://api.example.com/conversations/abc\",\n * token: () => localStorage.getItem(\"jwt\"),\n * }),\n * audio: { input: true },\n * });\n *\n * client.on(\"transcript\", (e) => render(e.entry.text));\n * client.on(\"audio\", (e) => client.playAudio(e.audio));\n * await client.connect();\n * await client.sendText(\"hello\", { userId: \"alice\" });\n * ```\n *\n * @packageDocumentation\n */\n\nexport { PipeflowClient } from \"./client\";\nexport type {\n PipeflowClientOptions,\n PipeflowClientEvent,\n PipeflowClientEventMap,\n Listener,\n ReconnectOptions,\n AudioInputOptions,\n CaptureConstraints,\n} from \"./client\";\n\nexport type {\n Protocol,\n ProtocolStatus,\n ProtocolMessage,\n ProtocolListener,\n StatusListener,\n} from \"./protocol\";\n\nexport { WebSocketProtocol } from \"./protocol/websocket\";\nexport type {\n WebSocketProtocolOptions,\n TokenProvider,\n} from \"./protocol/websocket\";"],
5
+ "mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,gGAAAE,EAAAF,GA2BA,IAAAG,EAA+B,oBAmB/BC,EAAkC",
6
+ "names": ["client_exports", "__export", "__toCommonJS", "import_client", "import_websocket"]
7
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";var o=Object.defineProperty;var b=Object.getOwnPropertyDescriptor;var a=Object.getOwnPropertyNames;var l=Object.prototype.hasOwnProperty;var u=(r,e)=>o(r,"name",{value:e,configurable:!0});var c=(r,e)=>{for(var n in e)o(r,n,{get:e[n],enumerable:!0})},f=(r,e,n,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of a(e))!l.call(r,t)&&t!==n&&o(r,t,{get:()=>e[t],enumerable:!(s=b(e,t))||s.enumerable});return r};var y=r=>f(o({},"__esModule",{value:!0}),r);var p={};c(p,{isJsonSerializable:()=>i});module.exports=y(p);function i(r){if(r===null)return!0;const e=typeof r;return e==="string"||e==="boolean"?!0:e==="number"?Number.isFinite(r):Array.isArray(r)?r.every(i):e==="object"?Object.values(r).every(i):!1}u(i,"isJsonSerializable");
2
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/client/json.ts"],
4
+ "sourcesContent": ["/**\n * Minimal JSON value guard for outbound messages.\n *\n * Kept narrow on purpose: server payloads carry domain types (turns,\n * transcripts, audio chunks) that aren't structurally typed as JSON values,\n * so the wire uses `unknown`. Outbound payloads, however, must serialize;\n * this guard catches values that JSON.stringify would silently drop\n * (functions, `undefined`, `bigint`).\n */\nexport type JsonValue =\n | string\n | number\n | boolean\n | null\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nexport function isJsonSerializable(value: unknown): boolean {\n if (value === null) return true;\n const type = typeof value;\n if (type === \"string\" || type === \"boolean\") return true;\n if (type === \"number\") return Number.isFinite(value as number);\n if (Array.isArray(value)) return value.every(isJsonSerializable);\n if (type === \"object\") {\n const record = value as Record<string, unknown>;\n return Object.values(record).every(isJsonSerializable);\n }\n return false;\n}"],
5
+ "mappings": "4dAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,wBAAAE,IAAA,eAAAC,EAAAH,GAiBO,SAASI,EAAmBC,EAAyB,CAC1D,GAAIA,IAAU,KAAM,MAAO,GAC3B,MAAMC,EAAO,OAAOD,EACpB,OAAIC,IAAS,UAAYA,IAAS,UAAkB,GAChDA,IAAS,SAAiB,OAAO,SAASD,CAAe,EACzD,MAAM,QAAQA,CAAK,EAAUA,EAAM,MAAMD,CAAkB,EAC3DE,IAAS,SAEJ,OAAO,OADCD,CACY,EAAE,MAAMD,CAAkB,EAEhD,EACT,CAXgBG,EAAAH,EAAA",
6
+ "names": ["json_exports", "__export", "isJsonSerializable", "__toCommonJS", "isJsonSerializable", "value", "type", "__name"]
7
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";var i=Object.defineProperty;var u=Object.getOwnPropertyDescriptor;var h=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var n=(o,t)=>i(o,"name",{value:t,configurable:!0});var p=(o,t)=>{for(var e in t)i(o,e,{get:t[e],enumerable:!0})},v=(o,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of h(t))!k.call(o,r)&&r!==e&&i(o,r,{get:()=>t[r],enumerable:!(s=u(t,r))||s.enumerable});return o};var g=o=>v(i({},"__esModule",{value:!0}),o);var f={};p(f,{WebSocketProtocol:()=>S});module.exports=g(f);var l=require("../json.js");class S{static{n(this,"WebSocketProtocol")}url;token;protocols;socketFactory;socket=null;_status="idle";messageListeners=new Set;statusListeners=new Set;constructor(t){this.url=t.url,this.token=t.token,this.protocols=t.protocols,this.socketFactory=t.socketFactory??((e,s)=>new WebSocket(e,s))}get status(){return this._status}async connect(){if(this._status==="open"||this._status==="connecting")return;this.socket&&this.teardownSocket(this.socket),this.setStatus("connecting");const t=this.token?await this.token():null,e=t?P(this.url,t):this.url,s=this.socketFactory(e,this.protocols);return this.socket=s,new Promise((r,d)=>{const a=n(()=>{s.removeEventListener("open",a),s.removeEventListener("error",c),this.setStatus("open"),r()},"onOpen"),c=n(()=>{s.removeEventListener("open",a),s.removeEventListener("error",c),this.setStatus("closed"),d(new Error(`Failed to connect to ${this.url}`))},"onError");s.addEventListener("open",a),s.addEventListener("error",c),s.addEventListener("message",this.handleMessage),s.addEventListener("close",this.handleClose)})}async close(){const t=this.socket;!t||this._status==="closed"||this._status==="idle"||(this.setStatus("closing"),await new Promise(e=>{const s=n(()=>{t.removeEventListener("close",s),e()},"done");t.addEventListener("close",s),t.readyState===WebSocket.CONNECTING?t.close():t.readyState===WebSocket.OPEN?t.close(1e3,"client closing"):e()}),this.teardownSocket(t),this.setStatus("closed"))}send(t){if(this._status!=="open"||!this.socket)throw new Error(`Cannot send on a ${this._status} protocol`);if(!(0,l.isJsonSerializable)(t.payload))throw new Error("ProtocolMessage.payload must be JSON-serializable");this.socket.send(JSON.stringify(t))}onMessage(t){return this.messageListeners.add(t),()=>{this.messageListeners.delete(t)}}onStatus(t){return this.statusListeners.add(t),()=>{this.statusListeners.delete(t)}}handleMessage=n(t=>{const e=typeof t.data=="string"?t.data:"";if(!e)return;let s;try{s=JSON.parse(e)}catch{return}if(y(s))for(const r of[...this.messageListeners])try{r(s)}catch{}},"handleMessage");handleClose=n(()=>{this._status!=="closed"&&this.setStatus("closed"),this.socket&&this.teardownSocket(this.socket)},"handleClose");teardownSocket(t){t.removeEventListener("message",this.handleMessage),t.removeEventListener("close",this.handleClose),this.socket=null}setStatus(t){if(this._status!==t){this._status=t;for(const e of[...this.statusListeners])try{e(t)}catch{}}}}function y(o){return!o||typeof o!="object"?!1:typeof o.type=="string"}n(y,"isProtocolMessage");function P(o,t){try{const e=new URL(o);return e.searchParams.set("token",t),e.toString()}catch{const e=o.includes("?")?"&":"?";return`${o}${e}token=${encodeURIComponent(t)}`}}n(P,"withToken");
2
+ //# sourceMappingURL=websocket.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/client/protocol/websocket.ts"],
4
+ "sourcesContent": ["/**\n * WebSocket-backed implementation of the `Protocol` contract.\n *\n * The URL, auth token, and subprotocols are passed through to the underlying\n * `WebSocket`. The token is resolved lazily on each `connect()` and appended\n * to the URL as a `token` query parameter so the server can authenticate the\n * upgrade (WebSocket has no `Authorization` header mechanism).\n *\n * ```ts\n * const protocol = new WebSocketProtocol({\n * url: \"wss://api.example.com/conversations/abc\",\n * token: () => localStorage.getItem(\"jwt\"),\n * });\n * ```\n */\nimport type {\n Protocol,\n ProtocolListener,\n ProtocolMessage,\n ProtocolStatus,\n StatusListener,\n} from \"../protocol\";\nimport { isJsonSerializable } from \"../json\";\n\n/**\n * Resolves the connection token. Called on every `connect()` so the caller\n * can rotate credentials without rebuilding the client. The resolved value\n * is appended to the URL as `?token=\u2026` (overwriting any existing value).\n */\nexport type TokenProvider = () => string | null | Promise<string | null>;\n\nexport interface WebSocketProtocolOptions {\n /** Fully-qualified WebSocket URL, e.g. `wss://api.example.com/conversations/abc`. */\n url: string;\n /** Optional token resolver; the resolved value is appended as `?token=\u2026`. */\n token?: TokenProvider;\n /** Subprotocols passed to the WebSocket constructor. */\n protocols?: string | string[];\n /**\n * Inject a custom WebSocket constructor (for tests, polyfills, or tracing).\n * Defaults to the global `WebSocket`.\n */\n socketFactory?: (url: string, protocols?: string | string[]) => WebSocket;\n}\n\nexport class WebSocketProtocol implements Protocol {\n readonly url: string;\n private readonly token?: TokenProvider;\n private readonly protocols?: string | string[];\n private readonly socketFactory: NonNullable<\n WebSocketProtocolOptions[\"socketFactory\"]\n >;\n\n private socket: WebSocket | null = null;\n private _status: ProtocolStatus = \"idle\";\n private readonly messageListeners = new Set<ProtocolListener>();\n private readonly statusListeners = new Set<StatusListener>();\n\n constructor(options: WebSocketProtocolOptions) {\n this.url = options.url;\n this.token = options.token;\n this.protocols = options.protocols;\n this.socketFactory =\n options.socketFactory ??\n ((url, protocols) => new WebSocket(url, protocols));\n }\n\n get status(): ProtocolStatus {\n return this._status;\n }\n\n async connect(): Promise<void> {\n if (this._status === \"open\" || this._status === \"connecting\") return;\n if (this.socket) this.teardownSocket(this.socket);\n this.setStatus(\"connecting\");\n\n // Construct the socket synchronously so callers (and tests) can interact\n // with it before the promise resolves. For async token providers, the\n // construction must be deferred: call `connect()` with a sync token\n // provider or pre-resolve the token.\n const syncToken = this.token ? await this.token() : null;\n const url = syncToken ? withToken(this.url, syncToken) : this.url;\n\n const socket = this.socketFactory(url, this.protocols);\n this.socket = socket;\n\n return new Promise<void>((resolve, reject) => {\n const onOpen = () => {\n socket.removeEventListener(\"open\", onOpen);\n socket.removeEventListener(\"error\", onError);\n this.setStatus(\"open\");\n resolve();\n };\n const onError = () => {\n socket.removeEventListener(\"open\", onOpen);\n socket.removeEventListener(\"error\", onError);\n this.setStatus(\"closed\");\n reject(new Error(`Failed to connect to ${this.url}`));\n };\n socket.addEventListener(\"open\", onOpen);\n socket.addEventListener(\"error\", onError);\n socket.addEventListener(\"message\", this.handleMessage);\n socket.addEventListener(\"close\", this.handleClose);\n });\n }\n\n async close(): Promise<void> {\n const socket = this.socket;\n if (!socket || this._status === \"closed\" || this._status === \"idle\") return;\n this.setStatus(\"closing\");\n\n await new Promise<void>((resolve) => {\n const done = () => {\n socket.removeEventListener(\"close\", done);\n resolve();\n };\n socket.addEventListener(\"close\", done);\n // readyState 0 (CONNECTING) cannot be closed gracefully; drop it.\n if (socket.readyState === WebSocket.CONNECTING) {\n socket.close();\n } else if (socket.readyState === WebSocket.OPEN) {\n socket.close(1000, \"client closing\");\n } else {\n resolve();\n }\n });\n\n this.teardownSocket(socket);\n this.setStatus(\"closed\");\n }\n\n send(message: ProtocolMessage): void {\n if (this._status !== \"open\" || !this.socket) {\n throw new Error(`Cannot send on a ${this._status} protocol`);\n }\n if (!isJsonSerializable(message.payload)) {\n throw new Error(\"ProtocolMessage.payload must be JSON-serializable\");\n }\n this.socket.send(JSON.stringify(message));\n }\n\n onMessage(listener: ProtocolListener): () => void {\n this.messageListeners.add(listener);\n return () => {\n this.messageListeners.delete(listener);\n };\n }\n\n onStatus(listener: StatusListener): () => void {\n this.statusListeners.add(listener);\n return () => {\n this.statusListeners.delete(listener);\n };\n }\n\n private readonly handleMessage = (event: MessageEvent): void => {\n const raw = typeof event.data === \"string\" ? event.data : \"\";\n if (!raw) return;\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return;\n }\n if (!isProtocolMessage(parsed)) return;\n for (const listener of [...this.messageListeners]) {\n try {\n listener(parsed);\n } catch {\n // A faulty listener must not prevent delivery to others.\n }\n }\n };\n\n private readonly handleClose = (): void => {\n if (this._status !== \"closed\") this.setStatus(\"closed\");\n if (this.socket) this.teardownSocket(this.socket);\n };\n\n private teardownSocket(socket: WebSocket): void {\n socket.removeEventListener(\"message\", this.handleMessage);\n socket.removeEventListener(\"close\", this.handleClose);\n this.socket = null;\n }\n\n private setStatus(next: ProtocolStatus): void {\n if (this._status === next) return;\n this._status = next;\n for (const listener of [...this.statusListeners]) {\n try {\n listener(next);\n } catch {\n // ignore\n }\n }\n }\n}\n\nfunction isProtocolMessage(value: unknown): value is ProtocolMessage {\n if (!value || typeof value !== \"object\") return false;\n const record = value as Record<string, unknown>;\n return typeof record.type === \"string\";\n}\n\n/** Append or replace `?token=\u2026` on a URL. */\nfunction withToken(url: string, token: string): string {\n try {\n const parsed = new URL(url);\n parsed.searchParams.set(\"token\", token);\n return parsed.toString();\n } catch {\n // Fall back to a literal concat for relative or malformed URLs (tests).\n const sep = url.includes(\"?\") ? \"&\" : \"?\";\n return `${url}${sep}token=${encodeURIComponent(token)}`;\n }\n}"],
5
+ "mappings": "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",
6
+ "names": ["websocket_exports", "__export", "WebSocketProtocol", "__toCommonJS", "import_json", "WebSocketProtocol", "__name", "options", "url", "protocols", "syncToken", "withToken", "socket", "resolve", "reject", "onOpen", "onError", "done", "message", "listener", "event", "raw", "parsed", "isProtocolMessage", "next", "value", "token", "sep"]
7
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";var r=Object.defineProperty;var i=Object.getOwnPropertyDescriptor;var a=Object.getOwnPropertyNames;var c=Object.prototype.hasOwnProperty;var l=(e,o,n,s)=>{if(o&&typeof o=="object"||typeof o=="function")for(let t of a(o))!c.call(e,t)&&t!==n&&r(e,t,{get:()=>o[t],enumerable:!(s=i(o,t))||s.enumerable});return e};var d=e=>l(r({},"__esModule",{value:!0}),e);var p={};module.exports=d(p);
2
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/client/protocol.ts"],
4
+ "sourcesContent": ["/**\n * Wire-level contract between a `PipeflowClient` and a Pipeflow server.\n *\n * A `Protocol` is responsible only for transport: it opens a connection,\n * delivers `ProtocolMessage`s, and reports its status. The client sits on\n * top and turns those messages into typed events.\n *\n * Swap protocols to change the wire (WebSocket today; SSE, HTTP, or an\n * in-memory pair for tests tomorrow) without touching application code.\n */\n\n/** Lifecycle status of a protocol connection. */\nexport type ProtocolStatus =\n | \"idle\"\n | \"connecting\"\n | \"open\"\n | \"closing\"\n | \"closed\";\n\n/**\n * A message exchanged over a `Protocol`. Symmetric: both sides send and\n * receive. `payload` is intentionally `unknown`: payloads are JSON-shaped\n * server domain types (turns, transcripts, audio chunks, \u2026) that the client\n * decodes against its event map at the boundary.\n */\nexport interface ProtocolMessage {\n type: string;\n payload: unknown;\n}\n\nexport type ProtocolListener = (message: ProtocolMessage) => void;\nexport type StatusListener = (status: ProtocolStatus) => void;\n\n/**\n * A bidirectional wire between a Pipeflow client and server.\n *\n * Implementations must be safe to subscribe to before `connect()`: messages\n * received before the listener is attached are buffered or dropped per the\n * implementation's policy, and a listener attached after `connect()` only sees\n * subsequent messages. This mirrors the WebSocket contract.\n */\nexport interface Protocol {\n /** Current lifecycle status. */\n readonly status: ProtocolStatus;\n\n /** Open the underlying connection. Idempotent: a second call is a no-op. */\n connect(): Promise<void>;\n\n /** Close the connection. Safe to call from any state. */\n close(): Promise<void>;\n\n /** Send a message. Throws if the connection is not `open`. */\n send(message: ProtocolMessage): void;\n\n /** Subscribe to incoming messages. Returns an unsubscribe function. */\n onMessage(listener: ProtocolListener): () => void;\n\n /** Subscribe to status transitions. Returns an unsubscribe function. */\n onStatus(listener: StatusListener): () => void;\n}"],
5
+ "mappings": "+WAAA,IAAAA,EAAA,kBAAAC,EAAAD",
6
+ "names": ["protocol_exports", "__toCommonJS"]
7
+ }
@@ -1,2 +1,2 @@
1
- "use strict";var d=Object.defineProperty;var l=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var m=Object.prototype.hasOwnProperty;var c=(s,t)=>d(s,"name",{value:t,configurable:!0});var I=(s,t)=>{for(var e in t)d(s,e,{get:t[e],enumerable:!0})},f=(s,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of v(t))!m.call(s,n)&&n!==e&&d(s,n,{get:()=>t[n],enumerable:!(i=l(t,n))||i.enumerable});return s};var g=s=>f(d({},"__esModule",{value:!0}),s);var y={};I(y,{Conversation:()=>C});module.exports=g(y);var p=require("../orchestration/orchestrator/orchestrator.js"),h=require("../types.js"),u=require("../transcription/transcription.js");function a(s){return s.timing??={startedAt:s.startedAt}}c(a,"initTiming");const T={firstToken:"firstTokenAt",firstTtsText:"firstTtsTextAt",firstTtsRequest:"firstTtsRequestAt",firstTtsAudio:"firstTtsAudioAt"};class C{static{c(this,"Conversation")}id;agents;transcription;state;persistence;stt;tts;listeners=new Map;pendingToolCallsById=new Map;subGenerations=new Map;nextAudioSequence=0;constructor(t){this.id=t.id,this.agents=t.agents??[],this.persistence=t.persistence,this.stt=t.stt,this.tts=t.tts,this.transcription=new u.Transcription(this.id),this.state=(0,h.createConversationState)()}get status(){return this.state.status}get participants(){return[...this.state.participants.values()]}async start(){if(this.state.status==="stopped")throw new Error(`Conversation "${this.id}" has already been stopped`);if(this.state.status==="started")return;const t=this.buildRealtime();this.state.status="started",this.emit("start",{conversationId:this.id}),this.emitState(),await t?.start()}async stop(){if(this.state.status==="stopped")return;this.state.status="stopped",this.pendingToolCallsById.clear();const t=this.cancelCurrentGeneration();t&&await this.persistence?.appendGeneration(this.id,t);for(const e of[...this.subGenerations.values()])e.status==="streaming"&&(e.status="cancelled",e.endedAt=Date.now(),await this.persistence?.appendGeneration(this.id,e));await this.persistence?.finalizeConversation(this.id,Date.now()),this.emit("stop",{conversationId:this.id}),this.emitState()}async participate(t){if(this.state.status==="stopped")throw new Error(`Conversation "${this.id}" has already been stopped`);const e=Array.isArray(t)?t:[t],i=new Set;for(const r of e){if(i.has(r.userId))throw new Error(`Duplicate participant "${r.userId}" in batch`);if(i.add(r.userId),this.state.participants.has(r.userId))throw new Error(`Participant "${r.userId}" already exists`)}const n=[];for(const r of e){const o={userId:r.userId,aliases:[...r.aliases??[]],joinedAt:Date.now()};this.state.participants.set(o.userId,o),await this.persistence?.addParticipant(this.id,o),n.push(o),this.emit("participant",{conversationId:this.id,participant:o})}return this.emitState(),n}listen(t){if(this.state.status!=="started")throw new Error(`Conversation "${this.id}" is not started`);if(!this.state.participants.has(t.userId))throw new Error(`Unknown participant "${t.userId}"`);const e={data:t.audio,timestamp:Date.now(),sequence:this.nextAudioSequence++};this.emit("audio-in",{conversationId:this.id,userId:t.userId,audio:e})}send(t){if(this.state.status!=="started")throw new Error(`Conversation "${this.id}" is not started`);if(!this.state.participants.has(t.userId))throw new Error(`Unknown participant "${t.userId}"`);this.emit("text-in",{conversationId:this.id,userId:t.userId,text:t.text})}interrupt(){const t=this.cancelCurrentGeneration();t&&this.persistence?.appendGeneration(this.id,t).catch(()=>{}),this.emit("interrupt",{conversationId:this.id}),this.emitState()}on(t,e){let i=this.listeners.get(t);return i||(i=new Set,this.listeners.set(t,i)),i.add(e),()=>{i.delete(e)}}emit(t,e){const i=this.listeners.get(t);if(i)for(const n of[...i])n(e)}pushAudio(t){const e=this.state.currentGeneration;if(e&&e.status==="streaming"){const i=a(e);i.firstAudioAt===void 0&&(i.firstAudioAt=Date.now(),this.persistence?.appendGeneration(this.id,e).catch(()=>{}),this.emitState())}this.emit("audio",{conversationId:this.id,audio:t})}async pushTurn(t){this.state.currentTurn=t,this.emit("turn",{conversationId:this.id,turn:t}),await this.persistence?.appendTurn(this.id,t),this.emitState()}async pushTranscript(t){const e=this.transcription.append({...t});return this.state.transcriptCount++,this.emit("transcript",{conversationId:this.id,entry:e}),await this.persistence?.appendTranscript(this.id,e),this.emitState(),e}pushTextDelta(t){const e=this.state.currentGeneration?.agentName;this.emit("text-delta",{conversationId:this.id,text:t,agentName:e})}async pushGeneration(t){a(t),this.state.currentGeneration=t,this.emit("generation",{conversationId:this.id,generation:t}),await this.persistence?.appendGeneration(this.id,t),this.emitState()}noteTiming(t,e){const i=e?this.subGenerations.get(e):this.state.currentGeneration;if(!i||i.status!=="streaming")return;const n=a(i),r=T[t];n[r]===void 0&&(n[r]=Date.now(),this.persistence?.appendGeneration(this.id,i).catch(()=>{}),this.emitState())}get pendingToolCalls(){return[...this.pendingToolCallsById.values()]}requestToolCall(t){this.pendingToolCallsById.set(t.id,t),this.emit("tool-call",{conversationId:this.id,call:t}),this.emitState()}resolveToolCall(t){if(!this.pendingToolCallsById.has(t.id))throw new Error(`No pending tool call "${t.id}"`);this.pendingToolCallsById.delete(t.id),this.emit("tool-call-result",{conversationId:this.id,result:t}),this.emitState()}async completeGeneration(t){const e=this.state.currentGeneration;e&&e.status==="streaming"&&(t!==void 0&&(e.text=t),e.status="completed",e.endedAt=Date.now(),a(e).completedAt=Date.now(),await this.persistence?.appendGeneration(this.id,e),this.emitState(),this.emit("generation-complete",{conversationId:this.id,generation:e}))}async pushSubGeneration(t){a(t),this.subGenerations.set(t.id,t),this.emit("generation",{conversationId:this.id,generation:t}),await this.persistence?.appendGeneration(this.id,t),this.emitState()}async completeSubGeneration(t,e){const i=this.subGenerations.get(t);!i||i.status!=="streaming"||(e!==void 0&&(i.text=e),i.status="completed",i.endedAt=Date.now(),a(i).completedAt=Date.now(),await this.persistence?.appendGeneration(this.id,i),this.emitState())}async cancelSubGeneration(t){const e=this.subGenerations.get(t);!e||e.status!=="streaming"||(e.status="cancelled",e.endedAt=Date.now(),await this.persistence?.appendGeneration(this.id,e),this.emitState())}transcript(){return this.transcription.list()}cancelCurrentGeneration(){const t=this.state.currentGeneration;return t&&t.status==="streaming"&&(t.status="cancelled",t.endedAt=Date.now()),this.state.currentGeneration=null,t??null}buildRealtime(){if(!this.stt&&!this.agents.some(e=>e.llm))return null;const t={conversation:this,stt:this.stt,persistence:this.persistence};return this.agents.length>0?new p.Orchestrator({...t,agents:[...this.agents],tts:this.tts}):new p.Orchestrator(t)}emitState(){this.emit("state",{conversationId:this.id,state:this.state})}}
1
+ "use strict";var d=Object.defineProperty;var l=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var m=Object.prototype.hasOwnProperty;var c=(s,t)=>d(s,"name",{value:t,configurable:!0});var I=(s,t)=>{for(var e in t)d(s,e,{get:t[e],enumerable:!0})},f=(s,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of v(t))!m.call(s,n)&&n!==e&&d(s,n,{get:()=>t[n],enumerable:!(i=l(t,n))||i.enumerable});return s};var T=s=>f(d({},"__esModule",{value:!0}),s);var y={};I(y,{Conversation:()=>C});module.exports=T(y);var u=require("../orchestration/orchestrator/orchestrator.js"),p=require("../types.js"),h=require("../transcription/transcription.js");function a(s){return s.timing??={startedAt:s.startedAt}}c(a,"initTiming");const g={firstToken:"firstTokenAt",firstTtsText:"firstTtsTextAt",firstTtsRequest:"firstTtsRequestAt",firstTtsAudio:"firstTtsAudioAt"};class C{static{c(this,"Conversation")}id;agents;transcription;state;persistence;stt;tts;autoExecuteTools;maxConcurrentTtsRequests;listeners=new Map;pendingToolCallsById=new Map;subGenerations=new Map;audioReorderMs;audioReorder=new Map;nextAudioSequence=0;constructor(t){this.id=t.id,this.agents=t.agents??[],this.persistence=t.persistence,this.stt=t.stt,this.tts=t.tts,this.autoExecuteTools=t.autoExecuteTools??!0,this.maxConcurrentTtsRequests=t.maxConcurrentTtsRequests,this.audioReorderMs=t.audioReorderMs??100,this.transcription=new h.Transcription(this.id),this.state=(0,p.createConversationState)()}get status(){return this.state.status}get participants(){return[...this.state.participants.values()]}async start(){if(this.state.status==="stopped")throw new Error(`Conversation "${this.id}" has already been stopped`);if(this.state.status==="started")return;const t=this.buildRealtime();this.state.status="started",this.emit("start",{conversationId:this.id}),this.emitState(),await t?.start()}async stop(){if(this.state.status==="stopped")return;this.state.status="stopped";for(const e of this.audioReorder.values())this.clearReorderTimer(e);this.audioReorder.clear(),this.pendingToolCallsById.clear();const t=this.cancelCurrentGeneration();t&&await this.persistence?.appendGeneration(this.id,t);for(const e of[...this.subGenerations.values()])e.status==="streaming"&&(e.status="cancelled",e.endedAt=Date.now(),await this.persistence?.appendGeneration(this.id,e));await this.persistence?.finalizeConversation(this.id,Date.now()),this.emit("stop",{conversationId:this.id}),this.emitState()}async participate(t){if(this.state.status==="stopped")throw new Error(`Conversation "${this.id}" has already been stopped`);const e=Array.isArray(t)?t:[t],i=new Set;for(const r of e){if(i.has(r.userId))throw new Error(`Duplicate participant "${r.userId}" in batch`);if(i.add(r.userId),this.state.participants.has(r.userId))throw new Error(`Participant "${r.userId}" already exists`)}const n=[];for(const r of e){const o={userId:r.userId,aliases:[...r.aliases??[]],joinedAt:Date.now()};this.state.participants.set(o.userId,o),await this.persistence?.addParticipant(this.id,o),n.push(o),this.emit("participant",{conversationId:this.id,participant:o})}return this.emitState(),n}listen(t){if(this.state.status!=="started")throw new Error(`Conversation "${this.id}" is not started`);if(!this.state.participants.has(t.userId))throw new Error(`Unknown participant "${t.userId}"`);if(t.sequence===void 0){this.emitAudioIn(t.userId,t.audio);return}this.reorderAudio(t.userId,t.audio,t.sequence)}emitAudioIn(t,e){const i={data:e,timestamp:Date.now(),sequence:this.nextAudioSequence++};this.emit("audio-in",{conversationId:this.id,userId:t,audio:i})}reorderAudio(t,e,i){let n=this.audioReorder.get(t);if(n||(n={next:null,pending:new Map,timer:null},this.audioReorder.set(t,n)),n.next===null&&(n.next=i),!(i<n.next)){if(i===n.next){for(this.clearReorderTimer(n),this.emitAudioIn(t,e),n.next++;n.pending.has(n.next);){const r=n.pending.get(n.next);n.pending.delete(n.next),this.emitAudioIn(t,r),n.next++}return}n.pending.set(i,e),n.timer||(n.timer=setTimeout(()=>{n.timer=null,this.releaseReorderBuffer(t)},this.audioReorderMs))}}releaseReorderBuffer(t){const e=this.audioReorder.get(t);if(e)for(this.clearReorderTimer(e);e.pending.size>0;){const i=Math.min(...e.pending.keys()),n=e.pending.get(i);e.pending.delete(i),e.next=i+1,this.emitAudioIn(t,n)}}clearReorderTimer(t){t.timer&&(clearTimeout(t.timer),t.timer=null)}send(t){if(this.state.status!=="started")throw new Error(`Conversation "${this.id}" is not started`);if(!this.state.participants.has(t.userId))throw new Error(`Unknown participant "${t.userId}"`);this.emit("text-in",{conversationId:this.id,userId:t.userId,text:t.text})}interrupt(){const t=this.cancelCurrentGeneration();t&&this.persistence?.appendGeneration(this.id,t).catch(()=>{}),this.emit("interrupt",{conversationId:this.id}),this.emitState()}on(t,e){let i=this.listeners.get(t);return i||(i=new Set,this.listeners.set(t,i)),i.add(e),()=>{i.delete(e)}}emit(t,e){const i=this.listeners.get(t);if(i)for(const n of[...i])n(e)}pushAudio(t){const e=this.state.currentGeneration;if(e&&e.status==="streaming"){const i=a(e);i.firstAudioAt===void 0&&(i.firstAudioAt=Date.now(),this.persistence?.appendGeneration(this.id,e).catch(()=>{}),this.emitState())}this.emit("audio",{conversationId:this.id,audio:t})}async pushTurn(t){this.state.currentTurn=t,this.emit("turn",{conversationId:this.id,turn:t}),await this.persistence?.appendTurn(this.id,t),this.emitState()}async pushTranscript(t){const e=this.transcription.append({...t});return this.state.transcriptCount++,this.emit("transcript",{conversationId:this.id,entry:e}),await this.persistence?.appendTranscript(this.id,e),this.emitState(),e}pushTextDelta(t){const e=this.state.currentGeneration?.agentName;this.emit("text-delta",{conversationId:this.id,text:t,agentName:e})}async pushGeneration(t){a(t),this.state.currentGeneration=t,this.emit("generation",{conversationId:this.id,generation:t}),await this.persistence?.appendGeneration(this.id,t),this.emitState()}noteTiming(t,e){const i=e?this.subGenerations.get(e):this.state.currentGeneration;if(!i||i.status!=="streaming")return;const n=a(i),r=g[t];n[r]===void 0&&(n[r]=Date.now(),this.persistence?.appendGeneration(this.id,i).catch(()=>{}),this.emitState())}get pendingToolCalls(){return[...this.pendingToolCallsById.values()]}requestToolCall(t){this.pendingToolCallsById.set(t.id,t),this.emit("tool-call",{conversationId:this.id,call:t}),this.emitState()}resolveToolCall(t){if(!this.pendingToolCallsById.has(t.id))throw new Error(`No pending tool call "${t.id}"`);this.pendingToolCallsById.delete(t.id),this.emit("tool-call-result",{conversationId:this.id,result:t}),this.emitState()}async completeGeneration(t){const e=this.state.currentGeneration;e&&e.status==="streaming"&&(t!==void 0&&(e.text=t),e.status="completed",e.endedAt=Date.now(),a(e).completedAt=Date.now(),await this.persistence?.appendGeneration(this.id,e),this.emitState(),this.emit("generation-complete",{conversationId:this.id,generation:e}))}async pushSubGeneration(t){a(t),this.subGenerations.set(t.id,t),this.emit("generation",{conversationId:this.id,generation:t}),await this.persistence?.appendGeneration(this.id,t),this.emitState()}async completeSubGeneration(t,e){const i=this.subGenerations.get(t);!i||i.status!=="streaming"||(e!==void 0&&(i.text=e),i.status="completed",i.endedAt=Date.now(),a(i).completedAt=Date.now(),await this.persistence?.appendGeneration(this.id,i),this.emitState())}async cancelSubGeneration(t){const e=this.subGenerations.get(t);!e||e.status!=="streaming"||(e.status="cancelled",e.endedAt=Date.now(),await this.persistence?.appendGeneration(this.id,e),this.emitState())}transcript(){return this.transcription.list()}cancelCurrentGeneration(){const t=this.state.currentGeneration;return t&&t.status==="streaming"&&(t.status="cancelled",t.endedAt=Date.now()),this.state.currentGeneration=null,t??null}buildRealtime(){if(!this.stt&&!this.agents.some(e=>e.llm))return null;const t={conversation:this,stt:this.stt,persistence:this.persistence};return this.agents.length>0?new u.Orchestrator({...t,agents:[...this.agents],tts:this.tts,autoExecuteTools:this.autoExecuteTools,...this.maxConcurrentTtsRequests!==void 0?{maxConcurrentTtsRequests:this.maxConcurrentTtsRequests}:{}}):new u.Orchestrator(t)}emitState(){this.emit("state",{conversationId:this.id,state:this.state})}}
2
2
  //# sourceMappingURL=conversation.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/conversations/conversation/conversation.ts"],
4
- "sourcesContent": ["import type { Agent } from \"../../agents/agent\";\nimport type { Persistence } from \"../../persistence/persistence\";\nimport { Orchestrator } from \"../orchestration/orchestrator/orchestrator\";\nimport type { STT } from \"../../providers/stt/types\";\nimport type { TTS } from \"../../providers/tts/types\";\nimport type {\n AudioChunk,\n ConversationId,\n ConversationState,\n ConversationStatus,\n Generation,\n GenerationTiming,\n Participant,\n ParticipantInput,\n Turn,\n UserId,\n} from \"../types\";\nimport { createConversationState } from \"../types\";\nimport {\n Transcription,\n type TranscriptEntry,\n type TranscriptEntryInput,\n} from \"../transcription/transcription\";\nimport type { ToolCall, ToolCallResult } from \"../types\";\n\n/** Ensure a generation carries a timing record, initialized from its start. */\nfunction initTiming(generation: Generation): GenerationTiming {\n return (generation.timing ??= { startedAt: generation.startedAt });\n}\n\n/** Timing points a generation can record, keyed by the stream event. */\ntype TimingPoint = \"firstToken\" | \"firstTtsText\" | \"firstTtsRequest\" | \"firstTtsAudio\";\n\nconst timingField: Record<TimingPoint, keyof GenerationTiming> = {\n firstToken: \"firstTokenAt\",\n firstTtsText: \"firstTtsTextAt\",\n firstTtsRequest: \"firstTtsRequestAt\",\n firstTtsAudio: \"firstTtsAudioAt\",\n};\n\nexport interface ConversationOptions {\n id: ConversationId;\n agents?: Agent[];\n persistence?: Persistence;\n /**\n * STT provider used to attach realtime processing automatically on\n * `start()`.\n */\n stt?: STT;\n tts?: TTS;\n}\n\nexport interface ConversationEvents {\n start: { conversationId: ConversationId };\n stop: { conversationId: ConversationId };\n participant: { conversationId: ConversationId; participant: Participant };\n /** Raw audio fed in via `listen()`. Consumed by the orchestrator. */\n \"audio-in\": { conversationId: ConversationId; userId: UserId; audio: AudioChunk };\n /** A finalized text turn via `send()`. Consumed by the orchestrator. */\n \"text-in\": { conversationId: ConversationId; userId: UserId; text: string };\n /** Generated audio to be played by the application. */\n audio: { conversationId: ConversationId; audio: AudioChunk };\n turn: { conversationId: ConversationId; turn: Turn };\n transcript: { conversationId: ConversationId; entry: TranscriptEntry };\n generation: { conversationId: ConversationId; generation: Generation };\n /**\n * The current generation's text, streamed as LLM deltas arrive. Emitted by\n * the orchestrator; the agent name is included when the generation is known.\n */\n \"text-delta\": {\n conversationId: ConversationId;\n text: string;\n agentName?: string;\n };\n /** The current (top-level) generation finished generating. */\n \"generation-complete\": {\n conversationId: ConversationId;\n generation: Generation;\n };\n /** Live STT partials, for captions and fast UX. Not persisted. */\n \"partial-transcript\": {\n conversationId: ConversationId;\n userId: UserId;\n text: string;\n };\n /** The model requested a tool call. Resolve it with `resolveToolCall()`. */\n \"tool-call\": { conversationId: ConversationId; call: ToolCall };\n /** A pending tool call was resolved by the application. */\n \"tool-call-result\": { conversationId: ConversationId; result: ToolCallResult };\n interrupt: { conversationId: ConversationId };\n /** A provider failure (STT, LLM, or TTS). */\n error: { conversationId: ConversationId; error: Error };\n state: { conversationId: ConversationId; state: ConversationState };\n}\n\n/**\n * The public realtime conversation API.\n *\n * A `Conversation` is a runtime handle to a persistent conversation:\n * lifecycle (`start`/`stop`), participants, audio intake (`listen`), text\n * turns (`send`), and events. When an STT provider or agents are configured,\n * `start()` automatically attaches the orchestrator, which runs the\n * STT/LLM/TTS pipeline (or routes text turns) and pushes generated audio,\n * turns, transcripts, and tool calls back through conversation events.\n */\nexport class Conversation {\n readonly id: ConversationId;\n readonly agents: readonly Agent[];\n readonly transcription: Transcription;\n readonly state: ConversationState;\n private readonly persistence: Persistence | undefined;\n private readonly stt: STT | undefined;\n private readonly tts: TTS | undefined;\n private readonly listeners = new Map<\n keyof ConversationEvents,\n Set<(payload: never) => void>\n >();\n private readonly pendingToolCallsById = new Map<string, ToolCall>();\n private readonly subGenerations = new Map<string, Generation>();\n private nextAudioSequence = 0;\n\n constructor(options: ConversationOptions) {\n this.id = options.id;\n this.agents = options.agents ?? [];\n this.persistence = options.persistence;\n this.stt = options.stt;\n this.tts = options.tts;\n this.transcription = new Transcription(this.id);\n this.state = createConversationState();\n }\n\n get status(): ConversationStatus {\n return this.state.status;\n }\n\n get participants(): Participant[] {\n return [...this.state.participants.values()];\n }\n\n /**\n * Move the conversation into the started state. This attaches the\n * orchestrator whenever there is realtime work: an STT provider (voice\n * pipeline) or agents to route text turns to (`send()`).\n */\n async start(): Promise<void> {\n if (this.state.status === \"stopped\") {\n throw new Error(`Conversation \"${this.id}\" has already been stopped`);\n }\n if (this.state.status === \"started\") return;\n\n // Validate the realtime configuration before mutating any state.\n const orchestrator = this.buildRealtime();\n\n this.state.status = \"started\";\n this.emit(\"start\", { conversationId: this.id });\n this.emitState();\n\n await orchestrator?.start();\n }\n\n async stop(): Promise<void> {\n if (this.state.status === \"stopped\") return;\n this.state.status = \"stopped\";\n this.pendingToolCallsById.clear();\n const cancelled = this.cancelCurrentGeneration();\n if (cancelled) {\n await this.persistence?.appendGeneration(this.id, cancelled);\n }\n for (const sub of [...this.subGenerations.values()]) {\n if (sub.status === \"streaming\") {\n sub.status = \"cancelled\";\n sub.endedAt = Date.now();\n await this.persistence?.appendGeneration(this.id, sub);\n }\n }\n await this.persistence?.finalizeConversation(this.id, Date.now());\n this.emit(\"stop\", { conversationId: this.id });\n this.emitState();\n }\n\n async participate(\n input: ParticipantInput | ParticipantInput[],\n ): Promise<Participant[]> {\n if (this.state.status === \"stopped\") {\n throw new Error(`Conversation \"${this.id}\" has already been stopped`);\n }\n\n const inputs = Array.isArray(input) ? input : [input];\n\n // Validate the whole batch before mutating any state.\n const seen = new Set<UserId>();\n for (const item of inputs) {\n if (seen.has(item.userId)) {\n throw new Error(`Duplicate participant \"${item.userId}\" in batch`);\n }\n seen.add(item.userId);\n if (this.state.participants.has(item.userId)) {\n throw new Error(`Participant \"${item.userId}\" already exists`);\n }\n }\n\n const added: Participant[] = [];\n for (const item of inputs) {\n const participant: Participant = {\n userId: item.userId,\n aliases: [...(item.aliases ?? [])],\n joinedAt: Date.now(),\n };\n this.state.participants.set(participant.userId, participant);\n await this.persistence?.addParticipant(this.id, participant);\n added.push(participant);\n this.emit(\"participant\", { conversationId: this.id, participant });\n }\n this.emitState();\n return added;\n }\n\n /**\n * Send an audio packet. Intentionally synchronous: it means \"send this\n * packet\", not \"wait for this utterance to finish\".\n */\n listen(input: { userId: UserId; audio: Uint8Array }): void {\n if (this.state.status !== \"started\") {\n throw new Error(`Conversation \"${this.id}\" is not started`);\n }\n if (!this.state.participants.has(input.userId)) {\n throw new Error(`Unknown participant \"${input.userId}\"`);\n }\n const chunk: AudioChunk = {\n data: input.audio,\n timestamp: Date.now(),\n sequence: this.nextAudioSequence++,\n };\n this.emit(\"audio-in\", { conversationId: this.id, userId: input.userId, audio: chunk });\n }\n\n /**\n * Send a finalized text turn, bypassing STT. Triggers the same pipeline as\n * a transcribed utterance (routing, coordination, generation, resume) \u2014\n * useful for text-first integrations such as chat or Discord text channels.\n */\n send(input: { userId: UserId; text: string }): void {\n if (this.state.status !== \"started\") {\n throw new Error(`Conversation \"${this.id}\" is not started`);\n }\n if (!this.state.participants.has(input.userId)) {\n throw new Error(`Unknown participant \"${input.userId}\"`);\n }\n this.emit(\"text-in\", {\n conversationId: this.id,\n userId: input.userId,\n text: input.text,\n });\n }\n\n /**\n * Stop the currently active generation and prevent any further generated\n * audio from reaching the conversation. The orchestrator cancels the LLM\n * and TTS streams, force-resolves pending tool calls, and discards queued\n * audio; the next participant turn starts fresh. Interrupt responsiveness\n * is a tested invariant \u2014 audio stops within ~100ms of this call.\n */\n interrupt(): void {\n const cancelled = this.cancelCurrentGeneration();\n // Persist asynchronously: interruption must stay synchronous for the\n // realtime path.\n if (cancelled) {\n void this.persistence?.appendGeneration(this.id, cancelled).catch(() => {});\n }\n this.emit(\"interrupt\", { conversationId: this.id });\n this.emitState();\n }\n\n on<K extends keyof ConversationEvents>(\n event: K,\n listener: (payload: ConversationEvents[K]) => void,\n ): () => void {\n let set = this.listeners.get(event);\n if (!set) {\n set = new Set();\n this.listeners.set(event, set);\n }\n set.add(listener as (payload: never) => void);\n return () => {\n set!.delete(listener as (payload: never) => void);\n };\n }\n\n /**\n * Emit an event. Primarily used by the orchestrator to push output\n * (audio, turns, transcripts) into the conversation.\n */\n emit<K extends keyof ConversationEvents>(\n event: K,\n payload: ConversationEvents[K],\n ): void {\n const set = this.listeners.get(event);\n if (!set) return;\n for (const listener of [...set]) {\n listener(payload as never);\n }\n }\n\n // -------------------------------------------------------------------------\n // Orchestrator-facing output\n // -------------------------------------------------------------------------\n\n /** Push a chunk of generated audio to the application. */\n pushAudio(audio: AudioChunk): void {\n // Record when the first synthesized chunk reached the application for the\n // in-flight generation (speech-to-delivery latency).\n const generation = this.state.currentGeneration;\n if (generation && generation.status === \"streaming\") {\n const timing = initTiming(generation);\n if (timing.firstAudioAt === undefined) {\n timing.firstAudioAt = Date.now();\n void this.persistence?.appendGeneration(this.id, generation).catch(() => {});\n this.emitState();\n }\n }\n this.emit(\"audio\", { conversationId: this.id, audio });\n }\n\n /** Push a completed participant turn into the conversation. */\n async pushTurn(turn: Turn): Promise<void> {\n this.state.currentTurn = turn;\n this.emit(\"turn\", { conversationId: this.id, turn });\n await this.persistence?.appendTurn(this.id, turn);\n this.emitState();\n }\n\n /** Append a transcript entry and notify listeners. */\n async pushTranscript(\n input: Omit<TranscriptEntryInput, \"conversationId\">,\n ): Promise<TranscriptEntry> {\n const entry = this.transcription.append({ ...input });\n this.state.transcriptCount++;\n this.emit(\"transcript\", { conversationId: this.id, entry });\n await this.persistence?.appendTranscript(this.id, entry);\n this.emitState();\n return entry;\n }\n\n /**\n * Stream the current generation's text, as produced by the orchestrator\n * from LLM deltas. Emits the `text-delta` event so applications can render\n * or act on partial replies.\n */\n pushTextDelta(text: string): void {\n const agentName = this.state.currentGeneration?.agentName;\n this.emit(\"text-delta\", { conversationId: this.id, text, agentName });\n }\n\n /** Push an agent generation into the conversation. */\n async pushGeneration(generation: Generation): Promise<void> {\n initTiming(generation);\n this.state.currentGeneration = generation;\n this.emit(\"generation\", { conversationId: this.id, generation });\n await this.persistence?.appendGeneration(this.id, generation);\n this.emitState();\n }\n\n /**\n * Record a timing point on a generation. Without an id this targets the\n * current generation; with one, a dispatched sub-generation. Each point is\n * recorded at most once.\n */\n noteTiming(point: TimingPoint, generationId?: string): void {\n const generation = generationId\n ? this.subGenerations.get(generationId)\n : this.state.currentGeneration;\n if (!generation || generation.status !== \"streaming\") return;\n const timing = initTiming(generation);\n const field = timingField[point];\n if (timing[field] !== undefined) return;\n timing[field] = Date.now();\n void this.persistence?.appendGeneration(this.id, generation).catch(() => {});\n this.emitState();\n }\n\n // -------------------------------------------------------------------------\n // Orchestrator-facing tool calls\n // -------------------------------------------------------------------------\n\n /** Tool calls requested by the model that are awaiting resolution. */\n get pendingToolCalls(): ToolCall[] {\n return [...this.pendingToolCallsById.values()];\n }\n\n /**\n * Track and emit a tool call requested by the model. The application\n * listens for `tool-call` events, executes the tool in its own backend,\n * and reports back through `resolveToolCall()`.\n */\n requestToolCall(call: ToolCall): void {\n this.pendingToolCallsById.set(call.id, call);\n this.emit(\"tool-call\", { conversationId: this.id, call });\n this.emitState();\n }\n\n /** Resolve a pending tool call with a result or an error. */\n resolveToolCall(result: ToolCallResult): void {\n if (!this.pendingToolCallsById.has(result.id)) {\n throw new Error(`No pending tool call \"${result.id}\"`);\n }\n this.pendingToolCallsById.delete(result.id);\n this.emit(\"tool-call-result\", { conversationId: this.id, result });\n this.emitState();\n }\n\n /** Mark the current generation as completed, recording its final text. */\n async completeGeneration(text?: string): Promise<void> {\n const generation = this.state.currentGeneration;\n if (generation && generation.status === \"streaming\") {\n if (text !== undefined) generation.text = text;\n generation.status = \"completed\";\n generation.endedAt = Date.now();\n initTiming(generation).completedAt = Date.now();\n await this.persistence?.appendGeneration(this.id, generation);\n this.emitState();\n this.emit(\"generation-complete\", { conversationId: this.id, generation });\n }\n }\n\n // -------------------------------------------------------------------------\n // Dispatched sub-generations\n // -------------------------------------------------------------------------\n\n /**\n * Push a sub-generation: a task the coordinator dispatched to another\n * agent. Unlike `pushGeneration`, it does not become the conversation's\n * current generation, so parallel tasks don't clobber the coordinator's\n * own in-flight generation.\n */\n async pushSubGeneration(generation: Generation): Promise<void> {\n initTiming(generation);\n this.subGenerations.set(generation.id, generation);\n this.emit(\"generation\", { conversationId: this.id, generation });\n await this.persistence?.appendGeneration(this.id, generation);\n this.emitState();\n }\n\n /** Mark a dispatched sub-generation as completed with its final text. */\n async completeSubGeneration(id: string, text?: string): Promise<void> {\n const generation = this.subGenerations.get(id);\n if (!generation || generation.status !== \"streaming\") return;\n if (text !== undefined) generation.text = text;\n generation.status = \"completed\";\n generation.endedAt = Date.now();\n initTiming(generation).completedAt = Date.now();\n await this.persistence?.appendGeneration(this.id, generation);\n this.emitState();\n }\n\n /** Mark a dispatched sub-generation as cancelled (e.g. barge-in). */\n async cancelSubGeneration(id: string): Promise<void> {\n const generation = this.subGenerations.get(id);\n if (!generation || generation.status !== \"streaming\") return;\n generation.status = \"cancelled\";\n generation.endedAt = Date.now();\n await this.persistence?.appendGeneration(this.id, generation);\n this.emitState();\n }\n\n /** The live in-memory transcript. */\n transcript(): TranscriptEntry[] {\n return this.transcription.list();\n }\n\n private cancelCurrentGeneration(): Generation | null {\n const generation = this.state.currentGeneration;\n if (generation && generation.status === \"streaming\") {\n generation.status = \"cancelled\";\n generation.endedAt = Date.now();\n }\n this.state.currentGeneration = null;\n return generation ?? null;\n }\n\n private buildRealtime(): Orchestrator | null {\n // Attach the orchestrator whenever there is realtime work: an STT\n // provider for voice, or agents with an LLM to route text turns to\n // (`send()`). Agents without an LLM have nothing to generate.\n if (!this.stt && !this.agents.some((agent) => agent.llm)) return null;\n const common = {\n conversation: this,\n stt: this.stt,\n persistence: this.persistence,\n };\n // With agents the orchestrator coordinates the roster, routing each turn\n // to an agent by name/alias. Without agents it runs transcription-only.\n return this.agents.length > 0\n ? new Orchestrator({ ...common, agents: [...this.agents], tts: this.tts })\n : new Orchestrator(common);\n }\n\n private emitState(): void {\n this.emit(\"state\", { conversationId: this.id, state: this.state });\n }\n}\n"],
5
- "mappings": "4dAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,kBAAAE,IAAA,eAAAC,EAAAH,GAEA,IAAAI,EAA6B,sDAe7BC,EAAwC,oBACxCC,EAIO,0CAIP,SAASC,EAAWC,EAA0C,CAC5D,OAAQA,EAAW,SAAW,CAAE,UAAWA,EAAW,SAAU,CAClE,CAFSC,EAAAF,EAAA,cAOT,MAAMG,EAA2D,CAC/D,WAAY,eACZ,aAAc,iBACd,gBAAiB,oBACjB,cAAe,iBACjB,EAmEO,MAAMC,CAAa,CAzG1B,MAyG0B,CAAAF,EAAA,qBACf,GACA,OACA,cACA,MACQ,YACA,IACA,IACA,UAAY,IAAI,IAIhB,qBAAuB,IAAI,IAC3B,eAAiB,IAAI,IAC9B,kBAAoB,EAE5B,YAAYG,EAA8B,CACxC,KAAK,GAAKA,EAAQ,GAClB,KAAK,OAASA,EAAQ,QAAU,CAAC,EACjC,KAAK,YAAcA,EAAQ,YAC3B,KAAK,IAAMA,EAAQ,IACnB,KAAK,IAAMA,EAAQ,IACnB,KAAK,cAAgB,IAAI,gBAAc,KAAK,EAAE,EAC9C,KAAK,SAAQ,2BAAwB,CACvC,CAEA,IAAI,QAA6B,CAC/B,OAAO,KAAK,MAAM,MACpB,CAEA,IAAI,cAA8B,CAChC,MAAO,CAAC,GAAG,KAAK,MAAM,aAAa,OAAO,CAAC,CAC7C,CAOA,MAAM,OAAuB,CAC3B,GAAI,KAAK,MAAM,SAAW,UACxB,MAAM,IAAI,MAAM,iBAAiB,KAAK,EAAE,4BAA4B,EAEtE,GAAI,KAAK,MAAM,SAAW,UAAW,OAGrC,MAAMC,EAAe,KAAK,cAAc,EAExC,KAAK,MAAM,OAAS,UACpB,KAAK,KAAK,QAAS,CAAE,eAAgB,KAAK,EAAG,CAAC,EAC9C,KAAK,UAAU,EAEf,MAAMA,GAAc,MAAM,CAC5B,CAEA,MAAM,MAAsB,CAC1B,GAAI,KAAK,MAAM,SAAW,UAAW,OACrC,KAAK,MAAM,OAAS,UACpB,KAAK,qBAAqB,MAAM,EAChC,MAAMC,EAAY,KAAK,wBAAwB,EAC3CA,GACF,MAAM,KAAK,aAAa,iBAAiB,KAAK,GAAIA,CAAS,EAE7D,UAAWC,IAAO,CAAC,GAAG,KAAK,eAAe,OAAO,CAAC,EAC5CA,EAAI,SAAW,cACjBA,EAAI,OAAS,YACbA,EAAI,QAAU,KAAK,IAAI,EACvB,MAAM,KAAK,aAAa,iBAAiB,KAAK,GAAIA,CAAG,GAGzD,MAAM,KAAK,aAAa,qBAAqB,KAAK,GAAI,KAAK,IAAI,CAAC,EAChE,KAAK,KAAK,OAAQ,CAAE,eAAgB,KAAK,EAAG,CAAC,EAC7C,KAAK,UAAU,CACjB,CAEA,MAAM,YACJC,EACwB,CACxB,GAAI,KAAK,MAAM,SAAW,UACxB,MAAM,IAAI,MAAM,iBAAiB,KAAK,EAAE,4BAA4B,EAGtE,MAAMC,EAAS,MAAM,QAAQD,CAAK,EAAIA,EAAQ,CAACA,CAAK,EAG9CE,EAAO,IAAI,IACjB,UAAWC,KAAQF,EAAQ,CACzB,GAAIC,EAAK,IAAIC,EAAK,MAAM,EACtB,MAAM,IAAI,MAAM,0BAA0BA,EAAK,MAAM,YAAY,EAGnE,GADAD,EAAK,IAAIC,EAAK,MAAM,EAChB,KAAK,MAAM,aAAa,IAAIA,EAAK,MAAM,EACzC,MAAM,IAAI,MAAM,gBAAgBA,EAAK,MAAM,kBAAkB,CAEjE,CAEA,MAAMC,EAAuB,CAAC,EAC9B,UAAWD,KAAQF,EAAQ,CACzB,MAAMI,EAA2B,CAC/B,OAAQF,EAAK,OACb,QAAS,CAAC,GAAIA,EAAK,SAAW,CAAC,CAAE,EACjC,SAAU,KAAK,IAAI,CACrB,EACA,KAAK,MAAM,aAAa,IAAIE,EAAY,OAAQA,CAAW,EAC3D,MAAM,KAAK,aAAa,eAAe,KAAK,GAAIA,CAAW,EAC3DD,EAAM,KAAKC,CAAW,EACtB,KAAK,KAAK,cAAe,CAAE,eAAgB,KAAK,GAAI,YAAAA,CAAY,CAAC,CACnE,CACA,YAAK,UAAU,EACRD,CACT,CAMA,OAAOJ,EAAoD,CACzD,GAAI,KAAK,MAAM,SAAW,UACxB,MAAM,IAAI,MAAM,iBAAiB,KAAK,EAAE,kBAAkB,EAE5D,GAAI,CAAC,KAAK,MAAM,aAAa,IAAIA,EAAM,MAAM,EAC3C,MAAM,IAAI,MAAM,wBAAwBA,EAAM,MAAM,GAAG,EAEzD,MAAMM,EAAoB,CACxB,KAAMN,EAAM,MACZ,UAAW,KAAK,IAAI,EACpB,SAAU,KAAK,mBACjB,EACA,KAAK,KAAK,WAAY,CAAE,eAAgB,KAAK,GAAI,OAAQA,EAAM,OAAQ,MAAOM,CAAM,CAAC,CACvF,CAOA,KAAKN,EAA+C,CAClD,GAAI,KAAK,MAAM,SAAW,UACxB,MAAM,IAAI,MAAM,iBAAiB,KAAK,EAAE,kBAAkB,EAE5D,GAAI,CAAC,KAAK,MAAM,aAAa,IAAIA,EAAM,MAAM,EAC3C,MAAM,IAAI,MAAM,wBAAwBA,EAAM,MAAM,GAAG,EAEzD,KAAK,KAAK,UAAW,CACnB,eAAgB,KAAK,GACrB,OAAQA,EAAM,OACd,KAAMA,EAAM,IACd,CAAC,CACH,CASA,WAAkB,CAChB,MAAMF,EAAY,KAAK,wBAAwB,EAG3CA,GACG,KAAK,aAAa,iBAAiB,KAAK,GAAIA,CAAS,EAAE,MAAM,IAAM,CAAC,CAAC,EAE5E,KAAK,KAAK,YAAa,CAAE,eAAgB,KAAK,EAAG,CAAC,EAClD,KAAK,UAAU,CACjB,CAEA,GACES,EACAC,EACY,CACZ,IAAIC,EAAM,KAAK,UAAU,IAAIF,CAAK,EAClC,OAAKE,IACHA,EAAM,IAAI,IACV,KAAK,UAAU,IAAIF,EAAOE,CAAG,GAE/BA,EAAI,IAAID,CAAoC,EACrC,IAAM,CACXC,EAAK,OAAOD,CAAoC,CAClD,CACF,CAMA,KACED,EACAG,EACM,CACN,MAAMD,EAAM,KAAK,UAAU,IAAIF,CAAK,EACpC,GAAKE,EACL,UAAWD,IAAY,CAAC,GAAGC,CAAG,EAC5BD,EAASE,CAAgB,CAE7B,CAOA,UAAUC,EAAyB,CAGjC,MAAMnB,EAAa,KAAK,MAAM,kBAC9B,GAAIA,GAAcA,EAAW,SAAW,YAAa,CACnD,MAAMoB,EAASrB,EAAWC,CAAU,EAChCoB,EAAO,eAAiB,SAC1BA,EAAO,aAAe,KAAK,IAAI,EAC1B,KAAK,aAAa,iBAAiB,KAAK,GAAIpB,CAAU,EAAE,MAAM,IAAM,CAAC,CAAC,EAC3E,KAAK,UAAU,EAEnB,CACA,KAAK,KAAK,QAAS,CAAE,eAAgB,KAAK,GAAI,MAAAmB,CAAM,CAAC,CACvD,CAGA,MAAM,SAASE,EAA2B,CACxC,KAAK,MAAM,YAAcA,EACzB,KAAK,KAAK,OAAQ,CAAE,eAAgB,KAAK,GAAI,KAAAA,CAAK,CAAC,EACnD,MAAM,KAAK,aAAa,WAAW,KAAK,GAAIA,CAAI,EAChD,KAAK,UAAU,CACjB,CAGA,MAAM,eACJb,EAC0B,CAC1B,MAAMc,EAAQ,KAAK,cAAc,OAAO,CAAE,GAAGd,CAAM,CAAC,EACpD,YAAK,MAAM,kBACX,KAAK,KAAK,aAAc,CAAE,eAAgB,KAAK,GAAI,MAAAc,CAAM,CAAC,EAC1D,MAAM,KAAK,aAAa,iBAAiB,KAAK,GAAIA,CAAK,EACvD,KAAK,UAAU,EACRA,CACT,CAOA,cAAcC,EAAoB,CAChC,MAAMC,EAAY,KAAK,MAAM,mBAAmB,UAChD,KAAK,KAAK,aAAc,CAAE,eAAgB,KAAK,GAAI,KAAAD,EAAM,UAAAC,CAAU,CAAC,CACtE,CAGA,MAAM,eAAexB,EAAuC,CAC1DD,EAAWC,CAAU,EACrB,KAAK,MAAM,kBAAoBA,EAC/B,KAAK,KAAK,aAAc,CAAE,eAAgB,KAAK,GAAI,WAAAA,CAAW,CAAC,EAC/D,MAAM,KAAK,aAAa,iBAAiB,KAAK,GAAIA,CAAU,EAC5D,KAAK,UAAU,CACjB,CAOA,WAAWyB,EAAoBC,EAA6B,CAC1D,MAAM1B,EAAa0B,EACf,KAAK,eAAe,IAAIA,CAAY,EACpC,KAAK,MAAM,kBACf,GAAI,CAAC1B,GAAcA,EAAW,SAAW,YAAa,OACtD,MAAMoB,EAASrB,EAAWC,CAAU,EAC9B2B,EAAQzB,EAAYuB,CAAK,EAC3BL,EAAOO,CAAK,IAAM,SACtBP,EAAOO,CAAK,EAAI,KAAK,IAAI,EACpB,KAAK,aAAa,iBAAiB,KAAK,GAAI3B,CAAU,EAAE,MAAM,IAAM,CAAC,CAAC,EAC3E,KAAK,UAAU,EACjB,CAOA,IAAI,kBAA+B,CACjC,MAAO,CAAC,GAAG,KAAK,qBAAqB,OAAO,CAAC,CAC/C,CAOA,gBAAgB4B,EAAsB,CACpC,KAAK,qBAAqB,IAAIA,EAAK,GAAIA,CAAI,EAC3C,KAAK,KAAK,YAAa,CAAE,eAAgB,KAAK,GAAI,KAAAA,CAAK,CAAC,EACxD,KAAK,UAAU,CACjB,CAGA,gBAAgBC,EAA8B,CAC5C,GAAI,CAAC,KAAK,qBAAqB,IAAIA,EAAO,EAAE,EAC1C,MAAM,IAAI,MAAM,yBAAyBA,EAAO,EAAE,GAAG,EAEvD,KAAK,qBAAqB,OAAOA,EAAO,EAAE,EAC1C,KAAK,KAAK,mBAAoB,CAAE,eAAgB,KAAK,GAAI,OAAAA,CAAO,CAAC,EACjE,KAAK,UAAU,CACjB,CAGA,MAAM,mBAAmBN,EAA8B,CACrD,MAAMvB,EAAa,KAAK,MAAM,kBAC1BA,GAAcA,EAAW,SAAW,cAClCuB,IAAS,SAAWvB,EAAW,KAAOuB,GAC1CvB,EAAW,OAAS,YACpBA,EAAW,QAAU,KAAK,IAAI,EAC9BD,EAAWC,CAAU,EAAE,YAAc,KAAK,IAAI,EAC9C,MAAM,KAAK,aAAa,iBAAiB,KAAK,GAAIA,CAAU,EAC5D,KAAK,UAAU,EACf,KAAK,KAAK,sBAAuB,CAAE,eAAgB,KAAK,GAAI,WAAAA,CAAW,CAAC,EAE5E,CAYA,MAAM,kBAAkBA,EAAuC,CAC7DD,EAAWC,CAAU,EACrB,KAAK,eAAe,IAAIA,EAAW,GAAIA,CAAU,EACjD,KAAK,KAAK,aAAc,CAAE,eAAgB,KAAK,GAAI,WAAAA,CAAW,CAAC,EAC/D,MAAM,KAAK,aAAa,iBAAiB,KAAK,GAAIA,CAAU,EAC5D,KAAK,UAAU,CACjB,CAGA,MAAM,sBAAsB8B,EAAYP,EAA8B,CACpE,MAAMvB,EAAa,KAAK,eAAe,IAAI8B,CAAE,EACzC,CAAC9B,GAAcA,EAAW,SAAW,cACrCuB,IAAS,SAAWvB,EAAW,KAAOuB,GAC1CvB,EAAW,OAAS,YACpBA,EAAW,QAAU,KAAK,IAAI,EAC9BD,EAAWC,CAAU,EAAE,YAAc,KAAK,IAAI,EAC9C,MAAM,KAAK,aAAa,iBAAiB,KAAK,GAAIA,CAAU,EAC5D,KAAK,UAAU,EACjB,CAGA,MAAM,oBAAoB8B,EAA2B,CACnD,MAAM9B,EAAa,KAAK,eAAe,IAAI8B,CAAE,EACzC,CAAC9B,GAAcA,EAAW,SAAW,cACzCA,EAAW,OAAS,YACpBA,EAAW,QAAU,KAAK,IAAI,EAC9B,MAAM,KAAK,aAAa,iBAAiB,KAAK,GAAIA,CAAU,EAC5D,KAAK,UAAU,EACjB,CAGA,YAAgC,CAC9B,OAAO,KAAK,cAAc,KAAK,CACjC,CAEQ,yBAA6C,CACnD,MAAMA,EAAa,KAAK,MAAM,kBAC9B,OAAIA,GAAcA,EAAW,SAAW,cACtCA,EAAW,OAAS,YACpBA,EAAW,QAAU,KAAK,IAAI,GAEhC,KAAK,MAAM,kBAAoB,KACxBA,GAAc,IACvB,CAEQ,eAAqC,CAI3C,GAAI,CAAC,KAAK,KAAO,CAAC,KAAK,OAAO,KAAM+B,GAAUA,EAAM,GAAG,EAAG,OAAO,KACjE,MAAMC,EAAS,CACb,aAAc,KACd,IAAK,KAAK,IACV,YAAa,KAAK,WACpB,EAGA,OAAO,KAAK,OAAO,OAAS,EACxB,IAAI,eAAa,CAAE,GAAGA,EAAQ,OAAQ,CAAC,GAAG,KAAK,MAAM,EAAG,IAAK,KAAK,GAAI,CAAC,EACvE,IAAI,eAAaA,CAAM,CAC7B,CAEQ,WAAkB,CACxB,KAAK,KAAK,QAAS,CAAE,eAAgB,KAAK,GAAI,MAAO,KAAK,KAAM,CAAC,CACnE,CACF",
6
- "names": ["conversation_exports", "__export", "Conversation", "__toCommonJS", "import_orchestrator", "import_types", "import_transcription", "initTiming", "generation", "__name", "timingField", "Conversation", "options", "orchestrator", "cancelled", "sub", "input", "inputs", "seen", "item", "added", "participant", "chunk", "event", "listener", "set", "payload", "audio", "timing", "turn", "entry", "text", "agentName", "point", "generationId", "field", "call", "result", "id", "agent", "common"]
4
+ "sourcesContent": ["import type { Agent } from \"../../agents/agent\";\nimport type { Persistence } from \"../../persistence/persistence\";\nimport { Orchestrator } from \"../orchestration/orchestrator/orchestrator\";\nimport type { STT } from \"../../providers/stt/types\";\nimport type { TTS } from \"../../providers/tts/types\";\nimport type {\n AudioChunk,\n ConversationId,\n ConversationState,\n ConversationStatus,\n Generation,\n GenerationTiming,\n Participant,\n ParticipantInput,\n Turn,\n UserId,\n} from \"../types\";\nimport { createConversationState } from \"../types\";\nimport {\n Transcription,\n type TranscriptEntry,\n type TranscriptEntryInput,\n} from \"../transcription/transcription\";\nimport type { ToolCall, ToolCallResult } from \"../types\";\n\n/** Ensure a generation carries a timing record, initialized from its start. */\nfunction initTiming(generation: Generation): GenerationTiming {\n return (generation.timing ??= { startedAt: generation.startedAt });\n}\n\n/** Timing points a generation can record, keyed by the stream event. */\ntype TimingPoint = \"firstToken\" | \"firstTtsText\" | \"firstTtsRequest\" | \"firstTtsAudio\";\n\nconst timingField: Record<TimingPoint, keyof GenerationTiming> = {\n firstToken: \"firstTokenAt\",\n firstTtsText: \"firstTtsTextAt\",\n firstTtsRequest: \"firstTtsRequestAt\",\n firstTtsAudio: \"firstTtsAudioAt\",\n};\n\nexport interface ConversationOptions {\n id: ConversationId;\n agents?: Agent[];\n persistence?: Persistence;\n /**\n * STT provider used to attach realtime processing automatically on\n * `start()`.\n */\n stt?: STT;\n tts?: TTS;\n /**\n * Execute the agents' tools automatically (default `true`), feeding each\n * tool's result back into the model loop. Set `false` to resolve tool\n * calls from your own backend via `resolveToolCall()`.\n */\n autoExecuteTools?: boolean;\n /**\n * TTS synthesis concurrency (default 2): more in-flight requests let a\n * multi-sentence reply synthesize in parallel, removing the gaps between\n * sentences, at the cost of provider concurrency. See\n * `SpeechPipelineOptions.maxConcurrentRequests`.\n */\n maxConcurrentTtsRequests?: number;\n /**\n * Hold window (ms) for out-of-order audio (default 100). When `listen()`\n * receives a chunk whose `sequence` skips ahead of the expected one, the\n * chunk is held briefly so a packet still in flight can fill the gap \u2014\n * netcode-style reordering, like client-side prediction in multiplayer\n * games. When the window expires the gap is skipped and the buffered\n * chunks are released in order. In-order audio (the common case) is\n * emitted immediately, with no added latency.\n */\n audioReorderMs?: number;\n}\n\n/** Per-participant reorder state for sequenced audio (`listen({ sequence })`). */\ninterface AudioReorderState {\n /**\n * Next sequence expected to be emitted. `null` until the user's first\n * chunk adopts its sequence as the baseline \u2014 earlier history is unknown.\n */\n next: number | null;\n /** Chunks received ahead of `next`, keyed by sequence. */\n pending: Map<number, Uint8Array>;\n /** Timer that releases `pending` when a gap outlives the hold window. */\n timer: ReturnType<typeof setTimeout> | null;\n}\n\nexport interface ConversationEvents {\n start: { conversationId: ConversationId };\n stop: { conversationId: ConversationId };\n participant: { conversationId: ConversationId; participant: Participant };\n /** Raw audio fed in via `listen()`. Consumed by the orchestrator. */\n \"audio-in\": { conversationId: ConversationId; userId: UserId; audio: AudioChunk };\n /** A finalized text turn via `send()`. Consumed by the orchestrator. */\n \"text-in\": { conversationId: ConversationId; userId: UserId; text: string };\n /** Generated audio to be played by the application. */\n audio: { conversationId: ConversationId; audio: AudioChunk };\n turn: { conversationId: ConversationId; turn: Turn };\n transcript: { conversationId: ConversationId; entry: TranscriptEntry };\n generation: { conversationId: ConversationId; generation: Generation };\n /**\n * The current generation's text, streamed as LLM deltas arrive. Emitted by\n * the orchestrator; the agent name is included when the generation is known.\n */\n \"text-delta\": {\n conversationId: ConversationId;\n text: string;\n agentName?: string;\n };\n /** The current (top-level) generation finished generating. */\n \"generation-complete\": {\n conversationId: ConversationId;\n generation: Generation;\n };\n /** Live STT partials, for captions and fast UX. Not persisted. */\n \"partial-transcript\": {\n conversationId: ConversationId;\n userId: UserId;\n text: string;\n };\n /** The model requested a tool call. Resolve it with `resolveToolCall()`. */\n \"tool-call\": { conversationId: ConversationId; call: ToolCall };\n /** A pending tool call was resolved by the application. */\n \"tool-call-result\": { conversationId: ConversationId; result: ToolCallResult };\n interrupt: { conversationId: ConversationId };\n /** A provider failure (STT, LLM, or TTS). */\n error: { conversationId: ConversationId; error: Error };\n state: { conversationId: ConversationId; state: ConversationState };\n}\n\n/**\n * The public realtime conversation API.\n *\n * A `Conversation` is a runtime handle to a persistent conversation:\n * lifecycle (`start`/`stop`), participants, audio intake (`listen`), text\n * turns (`send`), and events. When an STT provider or agents are configured,\n * `start()` automatically attaches the orchestrator, which runs the\n * STT/LLM/TTS pipeline (or routes text turns) and pushes generated audio,\n * turns, transcripts, and tool calls back through conversation events.\n */\nexport class Conversation {\n readonly id: ConversationId;\n readonly agents: readonly Agent[];\n readonly transcription: Transcription;\n readonly state: ConversationState;\n private readonly persistence: Persistence | undefined;\n private readonly stt: STT | undefined;\n private readonly tts: TTS | undefined;\n private readonly autoExecuteTools: boolean;\n private readonly maxConcurrentTtsRequests: number | undefined;\n private readonly listeners = new Map<\n keyof ConversationEvents,\n Set<(payload: never) => void>\n >();\n private readonly pendingToolCallsById = new Map<string, ToolCall>();\n private readonly subGenerations = new Map<string, Generation>();\n private readonly audioReorderMs: number;\n private readonly audioReorder = new Map<UserId, AudioReorderState>();\n private nextAudioSequence = 0;\n\n constructor(options: ConversationOptions) {\n this.id = options.id;\n this.agents = options.agents ?? [];\n this.persistence = options.persistence;\n this.stt = options.stt;\n this.tts = options.tts;\n this.autoExecuteTools = options.autoExecuteTools ?? true;\n this.maxConcurrentTtsRequests = options.maxConcurrentTtsRequests;\n this.audioReorderMs = options.audioReorderMs ?? 100;\n this.transcription = new Transcription(this.id);\n this.state = createConversationState();\n }\n\n get status(): ConversationStatus {\n return this.state.status;\n }\n\n get participants(): Participant[] {\n return [...this.state.participants.values()];\n }\n\n /**\n * Move the conversation into the started state. This attaches the\n * orchestrator whenever there is realtime work: an STT provider (voice\n * pipeline) or agents to route text turns to (`send()`).\n */\n async start(): Promise<void> {\n if (this.state.status === \"stopped\") {\n throw new Error(`Conversation \"${this.id}\" has already been stopped`);\n }\n if (this.state.status === \"started\") return;\n\n // Validate the realtime configuration before mutating any state.\n const orchestrator = this.buildRealtime();\n\n this.state.status = \"started\";\n this.emit(\"start\", { conversationId: this.id });\n this.emitState();\n\n await orchestrator?.start();\n }\n\n async stop(): Promise<void> {\n if (this.state.status === \"stopped\") return;\n this.state.status = \"stopped\";\n for (const state of this.audioReorder.values()) this.clearReorderTimer(state);\n this.audioReorder.clear();\n this.pendingToolCallsById.clear();\n const cancelled = this.cancelCurrentGeneration();\n if (cancelled) {\n await this.persistence?.appendGeneration(this.id, cancelled);\n }\n for (const sub of [...this.subGenerations.values()]) {\n if (sub.status === \"streaming\") {\n sub.status = \"cancelled\";\n sub.endedAt = Date.now();\n await this.persistence?.appendGeneration(this.id, sub);\n }\n }\n await this.persistence?.finalizeConversation(this.id, Date.now());\n this.emit(\"stop\", { conversationId: this.id });\n this.emitState();\n }\n\n async participate(\n input: ParticipantInput | ParticipantInput[],\n ): Promise<Participant[]> {\n if (this.state.status === \"stopped\") {\n throw new Error(`Conversation \"${this.id}\" has already been stopped`);\n }\n\n const inputs = Array.isArray(input) ? input : [input];\n\n // Validate the whole batch before mutating any state.\n const seen = new Set<UserId>();\n for (const item of inputs) {\n if (seen.has(item.userId)) {\n throw new Error(`Duplicate participant \"${item.userId}\" in batch`);\n }\n seen.add(item.userId);\n if (this.state.participants.has(item.userId)) {\n throw new Error(`Participant \"${item.userId}\" already exists`);\n }\n }\n\n const added: Participant[] = [];\n for (const item of inputs) {\n const participant: Participant = {\n userId: item.userId,\n aliases: [...(item.aliases ?? [])],\n joinedAt: Date.now(),\n };\n this.state.participants.set(participant.userId, participant);\n await this.persistence?.addParticipant(this.id, participant);\n added.push(participant);\n this.emit(\"participant\", { conversationId: this.id, participant });\n }\n this.emitState();\n return added;\n }\n\n /**\n * Send an audio packet. Intentionally synchronous: it means \"send this\n * packet\", not \"wait for this utterance to finish\".\n *\n * Pass the sender's `sequence` number to enable netcode-style reordering:\n * chunks arriving ahead of a gap are held for `audioReorderMs` so an\n * earlier packet still in flight can fill it, then released in order\n * (gaps are skipped when the window expires). The orchestrator and STT\n * always see the participant's audio in sequence order. Without a\n * `sequence`, the chunk is emitted on arrival, as-is.\n */\n listen(input: { userId: UserId; audio: Uint8Array; sequence?: number }): void {\n if (this.state.status !== \"started\") {\n throw new Error(`Conversation \"${this.id}\" is not started`);\n }\n if (!this.state.participants.has(input.userId)) {\n throw new Error(`Unknown participant \"${input.userId}\"`);\n }\n if (input.sequence === undefined) {\n this.emitAudioIn(input.userId, input.audio);\n return;\n }\n this.reorderAudio(input.userId, input.audio, input.sequence);\n }\n\n /** Emit an audio-in event with the conversation's own sequence numbering. */\n private emitAudioIn(userId: UserId, data: Uint8Array): void {\n const chunk: AudioChunk = {\n data,\n timestamp: Date.now(),\n sequence: this.nextAudioSequence++,\n };\n this.emit(\"audio-in\", { conversationId: this.id, userId, audio: chunk });\n }\n\n /** Hold/drain logic for sequenced audio, keyed per participant. */\n private reorderAudio(userId: UserId, data: Uint8Array, sequence: number): void {\n let state = this.audioReorder.get(userId);\n if (!state) {\n state = { next: null, pending: new Map(), timer: null };\n this.audioReorder.set(userId, state);\n }\n // The participant's first chunk anchors the stream: anything before it\n // is unknown history.\n if (state.next === null) state.next = sequence;\n if (sequence < state.next) return; // late or duplicate \u2014 already released\n if (sequence === state.next) {\n this.clearReorderTimer(state);\n this.emitAudioIn(userId, data);\n state.next++;\n while (state.pending.has(state.next)) {\n const held = state.pending.get(state.next)!;\n state.pending.delete(state.next);\n this.emitAudioIn(userId, held);\n state.next++;\n }\n return;\n }\n // Ahead of the expected sequence: hold so the gap can fill, and start\n // the release timer if it is not already running.\n state.pending.set(sequence, data);\n if (!state.timer) {\n state.timer = setTimeout(() => {\n state.timer = null;\n this.releaseReorderBuffer(userId);\n }, this.audioReorderMs);\n }\n }\n\n /**\n * Release everything a participant has buffered, in sequence order,\n * skipping the gap that never filled.\n */\n private releaseReorderBuffer(userId: UserId): void {\n const state = this.audioReorder.get(userId);\n if (!state) return;\n this.clearReorderTimer(state);\n while (state.pending.size > 0) {\n const sequence = Math.min(...state.pending.keys());\n const data = state.pending.get(sequence)!;\n state.pending.delete(sequence);\n state.next = sequence + 1;\n this.emitAudioIn(userId, data);\n }\n }\n\n private clearReorderTimer(state: AudioReorderState): void {\n if (state.timer) {\n clearTimeout(state.timer);\n state.timer = null;\n }\n }\n\n /**\n * Send a finalized text turn, bypassing STT. Triggers the same pipeline as\n * a transcribed utterance (routing, coordination, generation, resume) \u2014\n * useful for text-first integrations such as chat or Discord text channels.\n */\n send(input: { userId: UserId; text: string }): void {\n if (this.state.status !== \"started\") {\n throw new Error(`Conversation \"${this.id}\" is not started`);\n }\n if (!this.state.participants.has(input.userId)) {\n throw new Error(`Unknown participant \"${input.userId}\"`);\n }\n this.emit(\"text-in\", {\n conversationId: this.id,\n userId: input.userId,\n text: input.text,\n });\n }\n\n /**\n * Stop the currently active generation and prevent any further generated\n * audio from reaching the conversation. The orchestrator cancels the LLM\n * and TTS streams, force-resolves pending tool calls, and discards queued\n * audio; the next participant turn starts fresh. Interrupt responsiveness\n * is a tested invariant \u2014 audio stops within ~100ms of this call.\n */\n interrupt(): void {\n const cancelled = this.cancelCurrentGeneration();\n // Persist asynchronously: interruption must stay synchronous for the\n // realtime path.\n if (cancelled) {\n void this.persistence?.appendGeneration(this.id, cancelled).catch(() => {});\n }\n this.emit(\"interrupt\", { conversationId: this.id });\n this.emitState();\n }\n\n on<K extends keyof ConversationEvents>(\n event: K,\n listener: (payload: ConversationEvents[K]) => void,\n ): () => void {\n let set = this.listeners.get(event);\n if (!set) {\n set = new Set();\n this.listeners.set(event, set);\n }\n set.add(listener as (payload: never) => void);\n return () => {\n set!.delete(listener as (payload: never) => void);\n };\n }\n\n /**\n * Emit an event. Primarily used by the orchestrator to push output\n * (audio, turns, transcripts) into the conversation.\n */\n emit<K extends keyof ConversationEvents>(\n event: K,\n payload: ConversationEvents[K],\n ): void {\n const set = this.listeners.get(event);\n if (!set) return;\n for (const listener of [...set]) {\n listener(payload as never);\n }\n }\n\n // -------------------------------------------------------------------------\n // Orchestrator-facing output\n // -------------------------------------------------------------------------\n\n /** Push a chunk of generated audio to the application. */\n pushAudio(audio: AudioChunk): void {\n // Record when the first synthesized chunk reached the application for the\n // in-flight generation (speech-to-delivery latency).\n const generation = this.state.currentGeneration;\n if (generation && generation.status === \"streaming\") {\n const timing = initTiming(generation);\n if (timing.firstAudioAt === undefined) {\n timing.firstAudioAt = Date.now();\n void this.persistence?.appendGeneration(this.id, generation).catch(() => {});\n this.emitState();\n }\n }\n this.emit(\"audio\", { conversationId: this.id, audio });\n }\n\n /** Push a completed participant turn into the conversation. */\n async pushTurn(turn: Turn): Promise<void> {\n this.state.currentTurn = turn;\n this.emit(\"turn\", { conversationId: this.id, turn });\n await this.persistence?.appendTurn(this.id, turn);\n this.emitState();\n }\n\n /** Append a transcript entry and notify listeners. */\n async pushTranscript(\n input: Omit<TranscriptEntryInput, \"conversationId\">,\n ): Promise<TranscriptEntry> {\n const entry = this.transcription.append({ ...input });\n this.state.transcriptCount++;\n this.emit(\"transcript\", { conversationId: this.id, entry });\n await this.persistence?.appendTranscript(this.id, entry);\n this.emitState();\n return entry;\n }\n\n /**\n * Stream the current generation's text, as produced by the orchestrator\n * from LLM deltas. Emits the `text-delta` event so applications can render\n * or act on partial replies.\n */\n pushTextDelta(text: string): void {\n const agentName = this.state.currentGeneration?.agentName;\n this.emit(\"text-delta\", { conversationId: this.id, text, agentName });\n }\n\n /** Push an agent generation into the conversation. */\n async pushGeneration(generation: Generation): Promise<void> {\n initTiming(generation);\n this.state.currentGeneration = generation;\n this.emit(\"generation\", { conversationId: this.id, generation });\n await this.persistence?.appendGeneration(this.id, generation);\n this.emitState();\n }\n\n /**\n * Record a timing point on a generation. Without an id this targets the\n * current generation; with one, a dispatched sub-generation. Each point is\n * recorded at most once.\n */\n noteTiming(point: TimingPoint, generationId?: string): void {\n const generation = generationId\n ? this.subGenerations.get(generationId)\n : this.state.currentGeneration;\n if (!generation || generation.status !== \"streaming\") return;\n const timing = initTiming(generation);\n const field = timingField[point];\n if (timing[field] !== undefined) return;\n timing[field] = Date.now();\n void this.persistence?.appendGeneration(this.id, generation).catch(() => {});\n this.emitState();\n }\n\n // -------------------------------------------------------------------------\n // Orchestrator-facing tool calls\n // -------------------------------------------------------------------------\n\n /** Tool calls requested by the model that are awaiting resolution. */\n get pendingToolCalls(): ToolCall[] {\n return [...this.pendingToolCallsById.values()];\n }\n\n /**\n * Track and emit a tool call requested by the model. The application\n * listens for `tool-call` events, executes the tool in its own backend,\n * and reports back through `resolveToolCall()`.\n */\n requestToolCall(call: ToolCall): void {\n this.pendingToolCallsById.set(call.id, call);\n this.emit(\"tool-call\", { conversationId: this.id, call });\n this.emitState();\n }\n\n /** Resolve a pending tool call with a result or an error. */\n resolveToolCall(result: ToolCallResult): void {\n if (!this.pendingToolCallsById.has(result.id)) {\n throw new Error(`No pending tool call \"${result.id}\"`);\n }\n this.pendingToolCallsById.delete(result.id);\n this.emit(\"tool-call-result\", { conversationId: this.id, result });\n this.emitState();\n }\n\n /** Mark the current generation as completed, recording its final text. */\n async completeGeneration(text?: string): Promise<void> {\n const generation = this.state.currentGeneration;\n if (generation && generation.status === \"streaming\") {\n if (text !== undefined) generation.text = text;\n generation.status = \"completed\";\n generation.endedAt = Date.now();\n initTiming(generation).completedAt = Date.now();\n await this.persistence?.appendGeneration(this.id, generation);\n this.emitState();\n this.emit(\"generation-complete\", { conversationId: this.id, generation });\n }\n }\n\n // -------------------------------------------------------------------------\n // Dispatched sub-generations\n // -------------------------------------------------------------------------\n\n /**\n * Push a sub-generation: a task the coordinator dispatched to another\n * agent. Unlike `pushGeneration`, it does not become the conversation's\n * current generation, so parallel tasks don't clobber the coordinator's\n * own in-flight generation.\n */\n async pushSubGeneration(generation: Generation): Promise<void> {\n initTiming(generation);\n this.subGenerations.set(generation.id, generation);\n this.emit(\"generation\", { conversationId: this.id, generation });\n await this.persistence?.appendGeneration(this.id, generation);\n this.emitState();\n }\n\n /** Mark a dispatched sub-generation as completed with its final text. */\n async completeSubGeneration(id: string, text?: string): Promise<void> {\n const generation = this.subGenerations.get(id);\n if (!generation || generation.status !== \"streaming\") return;\n if (text !== undefined) generation.text = text;\n generation.status = \"completed\";\n generation.endedAt = Date.now();\n initTiming(generation).completedAt = Date.now();\n await this.persistence?.appendGeneration(this.id, generation);\n this.emitState();\n }\n\n /** Mark a dispatched sub-generation as cancelled (e.g. barge-in). */\n async cancelSubGeneration(id: string): Promise<void> {\n const generation = this.subGenerations.get(id);\n if (!generation || generation.status !== \"streaming\") return;\n generation.status = \"cancelled\";\n generation.endedAt = Date.now();\n await this.persistence?.appendGeneration(this.id, generation);\n this.emitState();\n }\n\n /** The live in-memory transcript. */\n transcript(): TranscriptEntry[] {\n return this.transcription.list();\n }\n\n private cancelCurrentGeneration(): Generation | null {\n const generation = this.state.currentGeneration;\n if (generation && generation.status === \"streaming\") {\n generation.status = \"cancelled\";\n generation.endedAt = Date.now();\n }\n this.state.currentGeneration = null;\n return generation ?? null;\n }\n\n private buildRealtime(): Orchestrator | null {\n // Attach the orchestrator whenever there is realtime work: an STT\n // provider for voice, or agents with an LLM to route text turns to\n // (`send()`). Agents without an LLM have nothing to generate.\n if (!this.stt && !this.agents.some((agent) => agent.llm)) return null;\n const common = {\n conversation: this,\n stt: this.stt,\n persistence: this.persistence,\n };\n // With agents the orchestrator coordinates the roster, routing each turn\n // to an agent by name/alias. Without agents it runs transcription-only.\n return this.agents.length > 0\n ? new Orchestrator({\n ...common,\n agents: [...this.agents],\n tts: this.tts,\n autoExecuteTools: this.autoExecuteTools,\n ...(this.maxConcurrentTtsRequests !== undefined\n ? { maxConcurrentTtsRequests: this.maxConcurrentTtsRequests }\n : {}),\n })\n : new Orchestrator(common);\n }\n\n private emitState(): void {\n this.emit(\"state\", { conversationId: this.id, state: this.state });\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["conversation_exports", "__export", "Conversation", "__toCommonJS", "import_orchestrator", "import_types", "import_transcription", "initTiming", "generation", "__name", "timingField", "Conversation", "options", "orchestrator", "state", "cancelled", "sub", "input", "inputs", "seen", "item", "added", "participant", "userId", "data", "chunk", "sequence", "held", "event", "listener", "set", "payload", "audio", "timing", "turn", "entry", "text", "agentName", "point", "generationId", "field", "call", "result", "id", "agent", "common"]
7
7
  }
@@ -1,2 +1,2 @@
1
- "use strict";var i=Object.defineProperty;var p=Object.getOwnPropertyDescriptor;var c=Object.getOwnPropertyNames;var v=Object.prototype.hasOwnProperty;var a=(e,t)=>i(e,"name",{value:t,configurable:!0});var d=(e,t)=>{for(var s in t)i(e,s,{get:t[s],enumerable:!0})},m=(e,t,s,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of c(t))!v.call(e,r)&&r!==s&&i(e,r,{get:()=>t[r],enumerable:!(n=p(t,r))||n.enumerable});return e};var C=e=>m(i({},"__esModule",{value:!0}),e);var T={};d(T,{Conversations:()=>y});module.exports=C(T);var o=require("./conversation/conversation.js");class y{static{a(this,"Conversations")}persistence;stt;tts;constructor(t){this.persistence=t.persistence,this.stt=t.stt,this.tts=t.tts}async create(t={}){const s=await this.persistence.createConversation({agentNames:(t.agents??[]).map(n=>n.name)});return new o.Conversation({id:s.id,agents:t.agents,persistence:this.persistence,stt:this.stt,tts:this.tts})}async transcript(t){return await this.requireConversation(t),this.persistence.listTranscript(t)}async get(t){const s=await this.persistence.getConversation(t);return s?new o.Conversation({id:s.id,persistence:this.persistence}):null}async requireConversation(t){if(!await this.persistence.getConversation(t))throw new Error(`Conversation "${t}" not found`)}}
1
+ "use strict";var o=Object.defineProperty;var c=Object.getOwnPropertyDescriptor;var u=Object.getOwnPropertyNames;var p=Object.prototype.hasOwnProperty;var a=(t,e)=>o(t,"name",{value:e,configurable:!0});var d=(t,e)=>{for(var r in e)o(t,r,{get:e[r],enumerable:!0})},m=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of u(e))!p.call(t,s)&&s!==r&&o(t,s,{get:()=>e[s],enumerable:!(n=c(e,s))||n.enumerable});return t};var T=t=>m(o({},"__esModule",{value:!0}),t);var C={};d(C,{Conversations:()=>v});module.exports=T(C);var i=require("./conversation/conversation.js");class v{static{a(this,"Conversations")}persistence;stt;tts;autoExecuteTools;constructor(e){this.persistence=e.persistence,this.stt=e.stt,this.tts=e.tts,this.autoExecuteTools=e.autoExecuteTools??!0}async create(e={}){const r=await this.persistence.createConversation({agentNames:(e.agents??[]).map(n=>n.name)});return new i.Conversation({id:r.id,agents:e.agents,persistence:this.persistence,stt:this.stt,tts:this.tts,autoExecuteTools:e.autoExecuteTools??this.autoExecuteTools,...e.maxConcurrentTtsRequests!==void 0?{maxConcurrentTtsRequests:e.maxConcurrentTtsRequests}:{},...e.audioReorderMs!==void 0?{audioReorderMs:e.audioReorderMs}:{}})}async transcript(e){return await this.requireConversation(e),this.persistence.listTranscript(e)}async get(e){const r=await this.persistence.getConversation(e);return r?new i.Conversation({id:r.id,persistence:this.persistence}):null}async requireConversation(e){if(!await this.persistence.getConversation(e))throw new Error(`Conversation "${e}" not found`)}}
2
2
  //# sourceMappingURL=conversations.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/conversations/conversations.ts"],
4
- "sourcesContent": ["import type { Agent } from \"../agents/agent\";\nimport type { Persistence } from \"../persistence/persistence\";\nimport type { STT } from \"../providers/stt/types\";\nimport type { TTS } from \"../providers/tts/types\";\nimport type { ConversationId } from \"./types\";\nimport type { TranscriptEntry } from \"./transcription/transcription\";\nimport { Conversation } from \"./conversation/conversation\";\n\nexport interface CreateConversationOptions {\n agents?: Agent[];\n}\n\nexport interface ConversationsOptions {\n persistence: Persistence;\n /** Passed to conversations so `start()` can attach realtime processing. */\n stt?: STT;\n tts?: TTS;\n}\n\n/**\n * The conversations API surface of a Pipeflow instance: create persistent\n * conversations and retrieve their transcripts.\n */\nexport class Conversations {\n private readonly persistence: Persistence;\n private readonly stt: STT | undefined;\n private readonly tts: TTS | undefined;\n\n constructor(options: ConversationsOptions) {\n this.persistence = options.persistence;\n this.stt = options.stt;\n this.tts = options.tts;\n }\n\n /** Create a persistent conversation. Realtime execution is separate. */\n async create(options: CreateConversationOptions = {}): Promise<Conversation> {\n const record = await this.persistence.createConversation({\n agentNames: (options.agents ?? []).map((agent) => agent.name),\n });\n return new Conversation({\n id: record.id,\n agents: options.agents,\n persistence: this.persistence,\n stt: this.stt,\n tts: this.tts,\n });\n }\n\n /** Retrieve the persisted transcript of a conversation. */\n async transcript(id: ConversationId): Promise<TranscriptEntry[]> {\n await this.requireConversation(id);\n return this.persistence.listTranscript(id);\n }\n\n /** Rehydrate a runtime handle for an existing conversation. */\n async get(id: ConversationId): Promise<Conversation | null> {\n const record = await this.persistence.getConversation(id);\n if (!record) return null;\n return new Conversation({ id: record.id, persistence: this.persistence });\n }\n\n private async requireConversation(id: ConversationId): Promise<void> {\n const record = await this.persistence.getConversation(id);\n if (!record) {\n throw new Error(`Conversation \"${id}\" not found`);\n }\n }\n}\n"],
5
- "mappings": "4dAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,mBAAAE,IAAA,eAAAC,EAAAH,GAMA,IAAAI,EAA6B,uCAiBtB,MAAMC,CAAc,CAvB3B,MAuB2B,CAAAC,EAAA,sBACR,YACA,IACA,IAEjB,YAAYC,EAA+B,CACzC,KAAK,YAAcA,EAAQ,YAC3B,KAAK,IAAMA,EAAQ,IACnB,KAAK,IAAMA,EAAQ,GACrB,CAGA,MAAM,OAAOA,EAAqC,CAAC,EAA0B,CAC3E,MAAMC,EAAS,MAAM,KAAK,YAAY,mBAAmB,CACvD,YAAaD,EAAQ,QAAU,CAAC,GAAG,IAAKE,GAAUA,EAAM,IAAI,CAC9D,CAAC,EACD,OAAO,IAAI,eAAa,CACtB,GAAID,EAAO,GACX,OAAQD,EAAQ,OAChB,YAAa,KAAK,YAClB,IAAK,KAAK,IACV,IAAK,KAAK,GACZ,CAAC,CACH,CAGA,MAAM,WAAWG,EAAgD,CAC/D,aAAM,KAAK,oBAAoBA,CAAE,EAC1B,KAAK,YAAY,eAAeA,CAAE,CAC3C,CAGA,MAAM,IAAIA,EAAkD,CAC1D,MAAMF,EAAS,MAAM,KAAK,YAAY,gBAAgBE,CAAE,EACxD,OAAKF,EACE,IAAI,eAAa,CAAE,GAAIA,EAAO,GAAI,YAAa,KAAK,WAAY,CAAC,EADpD,IAEtB,CAEA,MAAc,oBAAoBE,EAAmC,CAEnE,GAAI,CADW,MAAM,KAAK,YAAY,gBAAgBA,CAAE,EAEtD,MAAM,IAAI,MAAM,iBAAiBA,CAAE,aAAa,CAEpD,CACF",
4
+ "sourcesContent": ["import type { Agent } from \"../agents/agent\";\nimport type { Persistence } from \"../persistence/persistence\";\nimport type { STT } from \"../providers/stt/types\";\nimport type { TTS } from \"../providers/tts/types\";\nimport type { ConversationId } from \"./types\";\nimport type { TranscriptEntry } from \"./transcription/transcription\";\nimport { Conversation } from \"./conversation/conversation\";\n\nexport interface CreateConversationOptions {\n agents?: Agent[];\n /**\n * Execute the agents' tools automatically (default `true`). Set `false`\n * for this conversation to resolve tool calls from your own backend via\n * `resolveToolCall()`. Overrides the `Conversations`-level default.\n */\n autoExecuteTools?: boolean;\n /**\n * TTS synthesis concurrency (default 2). More in-flight requests let a\n * multi-sentence reply synthesize in parallel, removing the gaps between\n * sentences, at the cost of provider concurrency \u2014 some free TTS variants\n * rate-limit concurrent requests.\n */\n maxConcurrentTtsRequests?: number;\n /**\n * Hold window (ms) for out-of-order audio (default 100). See\n * `ConversationOptions.audioReorderMs`.\n */\n audioReorderMs?: number;\n}\n\nexport interface ConversationsOptions {\n persistence: Persistence;\n /** Passed to conversations so `start()` can attach realtime processing. */\n stt?: STT;\n tts?: TTS;\n /**\n * Default for created conversations: auto-execute the agents' tools\n * (default `true`). See `CreateConversationOptions.autoExecuteTools`.\n */\n autoExecuteTools?: boolean;\n}\n\n/**\n * The conversations API surface of a Pipeflow instance: create persistent\n * conversations and retrieve their transcripts.\n */\nexport class Conversations {\n private readonly persistence: Persistence;\n private readonly stt: STT | undefined;\n private readonly tts: TTS | undefined;\n private readonly autoExecuteTools: boolean;\n\n constructor(options: ConversationsOptions) {\n this.persistence = options.persistence;\n this.stt = options.stt;\n this.tts = options.tts;\n this.autoExecuteTools = options.autoExecuteTools ?? true;\n }\n\n /** Create a persistent conversation. Realtime execution is separate. */\n async create(options: CreateConversationOptions = {}): Promise<Conversation> {\n const record = await this.persistence.createConversation({\n agentNames: (options.agents ?? []).map((agent) => agent.name),\n });\n return new Conversation({\n id: record.id,\n agents: options.agents,\n persistence: this.persistence,\n stt: this.stt,\n tts: this.tts,\n autoExecuteTools: options.autoExecuteTools ?? this.autoExecuteTools,\n ...(options.maxConcurrentTtsRequests !== undefined\n ? { maxConcurrentTtsRequests: options.maxConcurrentTtsRequests }\n : {}),\n ...(options.audioReorderMs !== undefined\n ? { audioReorderMs: options.audioReorderMs }\n : {}),\n });\n }\n\n /** Retrieve the persisted transcript of a conversation. */\n async transcript(id: ConversationId): Promise<TranscriptEntry[]> {\n await this.requireConversation(id);\n return this.persistence.listTranscript(id);\n }\n\n /** Rehydrate a runtime handle for an existing conversation. */\n async get(id: ConversationId): Promise<Conversation | null> {\n const record = await this.persistence.getConversation(id);\n if (!record) return null;\n return new Conversation({ id: record.id, persistence: this.persistence });\n }\n\n private async requireConversation(id: ConversationId): Promise<void> {\n const record = await this.persistence.getConversation(id);\n if (!record) {\n throw new Error(`Conversation \"${id}\" not found`);\n }\n }\n}\n"],
5
+ "mappings": "4dAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,mBAAAE,IAAA,eAAAC,EAAAH,GAMA,IAAAI,EAA6B,uCAwCtB,MAAMC,CAAc,CA9C3B,MA8C2B,CAAAC,EAAA,sBACR,YACA,IACA,IACA,iBAEjB,YAAYC,EAA+B,CACzC,KAAK,YAAcA,EAAQ,YAC3B,KAAK,IAAMA,EAAQ,IACnB,KAAK,IAAMA,EAAQ,IACnB,KAAK,iBAAmBA,EAAQ,kBAAoB,EACtD,CAGA,MAAM,OAAOA,EAAqC,CAAC,EAA0B,CAC3E,MAAMC,EAAS,MAAM,KAAK,YAAY,mBAAmB,CACvD,YAAaD,EAAQ,QAAU,CAAC,GAAG,IAAKE,GAAUA,EAAM,IAAI,CAC9D,CAAC,EACD,OAAO,IAAI,eAAa,CACtB,GAAID,EAAO,GACX,OAAQD,EAAQ,OAChB,YAAa,KAAK,YAClB,IAAK,KAAK,IACV,IAAK,KAAK,IACV,iBAAkBA,EAAQ,kBAAoB,KAAK,iBACnD,GAAIA,EAAQ,2BAA6B,OACrC,CAAE,yBAA0BA,EAAQ,wBAAyB,EAC7D,CAAC,EACL,GAAIA,EAAQ,iBAAmB,OAC3B,CAAE,eAAgBA,EAAQ,cAAe,EACzC,CAAC,CACP,CAAC,CACH,CAGA,MAAM,WAAWG,EAAgD,CAC/D,aAAM,KAAK,oBAAoBA,CAAE,EAC1B,KAAK,YAAY,eAAeA,CAAE,CAC3C,CAGA,MAAM,IAAIA,EAAkD,CAC1D,MAAMF,EAAS,MAAM,KAAK,YAAY,gBAAgBE,CAAE,EACxD,OAAKF,EACE,IAAI,eAAa,CAAE,GAAIA,EAAO,GAAI,YAAa,KAAK,WAAY,CAAC,EADpD,IAEtB,CAEA,MAAc,oBAAoBE,EAAmC,CAEnE,GAAI,CADW,MAAM,KAAK,YAAY,gBAAgBA,CAAE,EAEtD,MAAM,IAAI,MAAM,iBAAiBA,CAAE,aAAa,CAEpD,CACF",
6
6
  "names": ["conversations_exports", "__export", "Conversations", "__toCommonJS", "import_conversation", "Conversations", "__name", "options", "record", "agent", "id"]
7
7
  }
@@ -1,2 +1,2 @@
1
- "use strict";var c=Object.defineProperty;var M=Object.getOwnPropertyDescriptor;var b=Object.getOwnPropertyNames;var h=Object.prototype.hasOwnProperty;var p=(t,e)=>c(t,"name",{value:e,configurable:!0});var y=(t,e)=>{for(var a in e)c(t,a,{get:e[a],enumerable:!0})},v=(t,e,a,l)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of b(e))!h.call(t,o)&&o!==a&&c(t,o,{get:()=>e[o],enumerable:!(l=M(e,o))||l.enumerable});return t};var G=t=>v(c({},"__esModule",{value:!0}),t);var w={};y(w,{GenerationRunner:()=>k});module.exports=G(w);class k{static{p(this,"GenerationRunner")}async run(e){const{agentName:a,llm:l,messages:o,tools:d,temperature:L,maxTokens:g,maxToolIterations:T,isCurrent:i,onDelta:x,resolveToolCalls:C}=e;let n="";try{for(let u=0;u<T;u++){if(!i())return{text:n,status:"interrupted"};const m=[];let f=!1;for await(const r of l.stream({messages:o,tools:d,temperature:L,maxTokens:g})){if(!i())return{text:n,status:"interrupted"};switch(r.type){case"delta":{const s=n;n+=r.content,r.content.length>0&&x?.(r.content,s);break}case"tool_call":m.push({id:r.id,name:r.name,arguments:r.arguments});break;case"error":throw r.error;case"done":f=!0}}if(!i())return{text:n,status:"interrupted"};if(m.length>0){o.push({role:"assistant",name:a,content:n,toolCalls:m});const r=await C(m);if(!i())return{text:n,status:"interrupted"};for(const s of r)o.push({role:"tool",toolCallId:s.id,name:s.name,content:JSON.stringify(s.error!==void 0?{error:s.error}:s.result)});continue}if(f)break}return{text:n,status:"done"}}catch(u){return{text:n,status:"error",error:u}}}}
1
+ "use strict";var c=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var v=Object.prototype.hasOwnProperty;var d=(o,e)=>c(o,"name",{value:e,configurable:!0});var w=(o,e)=>{for(var t in e)c(o,t,{get:e[t],enumerable:!0})},G=(o,e,t,l)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of k(e))!v.call(o,r)&&r!==t&&c(o,r,{get:()=>e[r],enumerable:!(l=y(e,r))||l.enumerable});return o};var N=o=>G(c({},"__esModule",{value:!0}),o);var I={};w(I,{GenerationRunner:()=>R});module.exports=N(I);class R{static{d(this,"GenerationRunner")}async run(e){const{agentName:t,llm:l,messages:r,tools:L,temperature:g,maxTokens:M,maxToolIterations:T,isCurrent:i,agentNames:b=!0,onDelta:x,resolveToolCalls:C}=e;let s="",f=0;try{for(let u=0;u<T;u++){if(!i())return{text:s,status:"interrupted"};const m=[];let p=!1;const h=b?r:D(r);for await(const n of l.stream({messages:h,tools:L,temperature:g,maxTokens:M})){if(!i())return{text:s,status:"interrupted"};switch(n.type){case"delta":{const a=s;s+=n.content,n.content.length>0&&x?.(n.content,a);break}case"tool_call":m.push({id:n.id,name:n.name,arguments:n.arguments});break;case"error":throw n.error;case"done":p=!0}}if(!i())return{text:s,status:"interrupted"};if(m.length>0){f++,r.push({role:"assistant",name:t,content:s,toolCalls:m});const n=await C(m);if(!i())return{text:s,status:"interrupted"};for(const a of n)r.push({role:"tool",toolCallId:a.id,name:a.name,content:JSON.stringify(a.error!==void 0?{error:a.error}:a.result)});continue}if(p)break}return{text:s,status:"done"}}catch(u){return{text:s,status:"error",error:u,...f>0?{toolRounds:f}:{}}}}}function D(o){let e=!1;for(const t of o)if(t.name&&(t.role==="system"||t.role==="assistant")){e=!0;break}return e?o.map(t=>t.name&&(t.role==="system"||t.role==="assistant")?{...t,name:void 0}:t):o}d(D,"withoutAgentNames");
2
2
  //# sourceMappingURL=generation.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/conversations/orchestration/orchestrator/generation/generation.ts"],
4
- "sourcesContent": ["import type {\n LLM,\n LLMToolCall,\n LLMToolDefinition,\n LLMMessage,\n} from \"../../../../providers/llm/types\";\nimport type { ResolvedToolCall } from \"../tools/tools\";\n\nexport interface GenerationRequest {\n /** Display name stamped on assistant tool-call messages. */\n agentName: string;\n llm: LLM;\n /**\n * The conversation messages; the loop appends the assistant tool-call\n * message and the resolved tool results onto this array.\n */\n messages: LLMMessage[];\n tools: LLMToolDefinition[];\n temperature?: number;\n maxTokens?: number;\n /** Safety bound on tool-call round trips per generation. */\n maxToolIterations: number;\n /** False once the run is stale (interrupt/stop); aborts the loop. */\n isCurrent(): boolean;\n /** Streamed text; `textBefore` is the running text prior to this delta. */\n onDelta?(delta: string, textBefore: string): void;\n /** Hand tool calls to the application and resolve their results. */\n resolveToolCalls(calls: LLMToolCall[]): Promise<ResolvedToolCall[]>;\n}\n\nexport type GenerationStatus = \"done\" | \"interrupted\" | \"error\";\n\nexport interface GenerationOutcome {\n /** Everything the model produced (or produced before failing). */\n text: string;\n status: GenerationStatus;\n /** The provider error, when `status` is \"error\". */\n error?: unknown;\n}\n\n/**\n * Owns the LLM tool loop shared by top-level generations and delegated\n * sub-generations: stream deltas, collect tool calls, resolve them through\n * the application, and repeat up to `maxToolIterations`.\n *\n * It is deliberately ignorant of the conversation lifecycle \u2014 persistence,\n * transcripts, speech, and history are the caller's concern, decided from\n * the returned `GenerationOutcome`.\n */\nexport class GenerationRunner {\n async run(request: GenerationRequest): Promise<GenerationOutcome> {\n const {\n agentName,\n llm,\n messages,\n tools,\n temperature,\n maxTokens,\n maxToolIterations,\n isCurrent,\n onDelta,\n resolveToolCalls,\n } = request;\n\n let text = \"\";\n\n try {\n for (let iteration = 0; iteration < maxToolIterations; iteration++) {\n if (!isCurrent()) return { text, status: \"interrupted\" };\n\n const toolCalls: LLMToolCall[] = [];\n let done = false;\n\n for await (const event of llm.stream({ messages, tools, temperature, maxTokens })) {\n if (!isCurrent()) return { text, status: \"interrupted\" };\n switch (event.type) {\n case \"delta\": {\n const before = text;\n text += event.content;\n if (event.content.length > 0) onDelta?.(event.content, before);\n break;\n }\n case \"tool_call\":\n toolCalls.push({ id: event.id, name: event.name, arguments: event.arguments });\n break;\n case \"error\":\n throw event.error;\n case \"done\":\n done = true;\n }\n }\n\n if (!isCurrent()) return { text, status: \"interrupted\" };\n\n if (toolCalls.length > 0) {\n // Pause the response: hand the calls to the application, then\n // resume once they are resolved.\n messages.push({ role: \"assistant\", name: agentName, content: text, toolCalls });\n const results = await resolveToolCalls(toolCalls);\n if (!isCurrent()) return { text, status: \"interrupted\" };\n for (const result of results) {\n messages.push({\n role: \"tool\",\n toolCallId: result.id,\n name: result.name,\n content: JSON.stringify(\n result.error !== undefined ? { error: result.error } : result.result,\n ),\n });\n }\n continue;\n }\n\n if (done) break;\n }\n\n return { text, status: \"done\" };\n } catch (error) {\n return { text, status: \"error\", error };\n }\n }\n}\n"],
5
- "mappings": "4dAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,sBAAAE,IAAA,eAAAC,EAAAH,GAiDO,MAAMI,CAAiB,CAjD9B,MAiD8B,CAAAC,EAAA,yBAC5B,MAAM,IAAIC,EAAwD,CAChE,KAAM,CACJ,UAAAC,EACA,IAAAC,EACA,SAAAC,EACA,MAAAC,EACA,YAAAC,EACA,UAAAC,EACA,kBAAAC,EACA,UAAAC,EACA,QAAAC,EACA,iBAAAC,CACF,EAAIV,EAEJ,IAAIW,EAAO,GAEX,GAAI,CACF,QAASC,EAAY,EAAGA,EAAYL,EAAmBK,IAAa,CAClE,GAAI,CAACJ,EAAU,EAAG,MAAO,CAAE,KAAAG,EAAM,OAAQ,aAAc,EAEvD,MAAME,EAA2B,CAAC,EAClC,IAAIC,EAAO,GAEX,gBAAiBC,KAASb,EAAI,OAAO,CAAE,SAAAC,EAAU,MAAAC,EAAO,YAAAC,EAAa,UAAAC,CAAU,CAAC,EAAG,CACjF,GAAI,CAACE,EAAU,EAAG,MAAO,CAAE,KAAAG,EAAM,OAAQ,aAAc,EACvD,OAAQI,EAAM,KAAM,CAClB,IAAK,QAAS,CACZ,MAAMC,EAASL,EACfA,GAAQI,EAAM,QACVA,EAAM,QAAQ,OAAS,GAAGN,IAAUM,EAAM,QAASC,CAAM,EAC7D,KACF,CACA,IAAK,YACHH,EAAU,KAAK,CAAE,GAAIE,EAAM,GAAI,KAAMA,EAAM,KAAM,UAAWA,EAAM,SAAU,CAAC,EAC7E,MACF,IAAK,QACH,MAAMA,EAAM,MACd,IAAK,OACHD,EAAO,EACX,CACF,CAEA,GAAI,CAACN,EAAU,EAAG,MAAO,CAAE,KAAAG,EAAM,OAAQ,aAAc,EAEvD,GAAIE,EAAU,OAAS,EAAG,CAGxBV,EAAS,KAAK,CAAE,KAAM,YAAa,KAAMF,EAAW,QAASU,EAAM,UAAAE,CAAU,CAAC,EAC9E,MAAMI,EAAU,MAAMP,EAAiBG,CAAS,EAChD,GAAI,CAACL,EAAU,EAAG,MAAO,CAAE,KAAAG,EAAM,OAAQ,aAAc,EACvD,UAAWO,KAAUD,EACnBd,EAAS,KAAK,CACZ,KAAM,OACN,WAAYe,EAAO,GACnB,KAAMA,EAAO,KACb,QAAS,KAAK,UACZA,EAAO,QAAU,OAAY,CAAE,MAAOA,EAAO,KAAM,EAAIA,EAAO,MAChE,CACF,CAAC,EAEH,QACF,CAEA,GAAIJ,EAAM,KACZ,CAEA,MAAO,CAAE,KAAAH,EAAM,OAAQ,MAAO,CAChC,OAASQ,EAAO,CACd,MAAO,CAAE,KAAAR,EAAM,OAAQ,QAAS,MAAAQ,CAAM,CACxC,CACF,CACF",
6
- "names": ["generation_exports", "__export", "GenerationRunner", "__toCommonJS", "GenerationRunner", "__name", "request", "agentName", "llm", "messages", "tools", "temperature", "maxTokens", "maxToolIterations", "isCurrent", "onDelta", "resolveToolCalls", "text", "iteration", "toolCalls", "done", "event", "before", "results", "result", "error"]
4
+ "sourcesContent": ["import type {\n LLM,\n LLMToolCall,\n LLMToolDefinition,\n LLMMessage,\n} from \"../../../../providers/llm/types\";\nimport type { ResolvedToolCall } from \"../tools/tools\";\n\nexport interface GenerationRequest {\n /** Display name stamped on assistant tool-call messages. */\n agentName: string;\n llm: LLM;\n /**\n * The conversation messages; the loop appends the assistant tool-call\n * message and the resolved tool results onto this array.\n */\n messages: LLMMessage[];\n tools: LLMToolDefinition[];\n temperature?: number;\n maxTokens?: number;\n /** Safety bound on tool-call round trips per generation. */\n maxToolIterations: number;\n /** False once the run is stale (interrupt/stop); aborts the loop. */\n isCurrent(): boolean;\n /**\n * Whether outgoing messages keep per-agent `name` fields on system and\n * assistant messages. Multi-agent conversations need them so the model can\n * tell which agent said what in the shared history. Single-agent\n * conversations drop them: with one agent the names carry no information,\n * and some providers render a message `name` as a role header (\"Scout:\")\n * that weaker models imitate \u2014 every reply then starts by speaking the\n * agent's name. Default true.\n */\n agentNames?: boolean;\n /** Streamed text; `textBefore` is the running text prior to this delta. */\n onDelta?(delta: string, textBefore: string): void;\n /** Hand tool calls to the application and resolve their results. */\n resolveToolCalls(calls: LLMToolCall[]): Promise<ResolvedToolCall[]>;\n}\n\nexport type GenerationStatus = \"done\" | \"interrupted\" | \"error\";\n\nexport interface GenerationOutcome {\n /** Everything the model produced (or produced before failing). */\n text: string;\n status: GenerationStatus;\n /** The provider error, when `status` is \"error\". */\n error?: unknown;\n /**\n * How many tool rounds were resolved before the run ended (only present\n * when at least one). Lets callers retry a no-output failure safely:\n * re-running a generation whose tools already executed would repeat their\n * side effects.\n */\n toolRounds?: number;\n}\n\n/**\n * Owns the LLM tool loop shared by top-level generations and delegated\n * sub-generations: stream deltas, collect tool calls, resolve them through\n * the application, and repeat up to `maxToolIterations`.\n *\n * It is deliberately ignorant of the conversation lifecycle \u2014 persistence,\n * transcripts, speech, and history are the caller's concern, decided from\n * the returned `GenerationOutcome`.\n */\nexport class GenerationRunner {\n async run(request: GenerationRequest): Promise<GenerationOutcome> {\n const {\n agentName,\n llm,\n messages,\n tools,\n temperature,\n maxTokens,\n maxToolIterations,\n isCurrent,\n agentNames = true,\n onDelta,\n resolveToolCalls,\n } = request;\n\n let text = \"\";\n let toolRounds = 0;\n\n try {\n for (let iteration = 0; iteration < maxToolIterations; iteration++) {\n if (!isCurrent()) return { text, status: \"interrupted\" };\n\n const toolCalls: LLMToolCall[] = [];\n let done = false;\n\n // Single-agent conversations don't need per-agent `name` fields (see\n // GenerationRequest.agentNames): strip them before every call, since\n // the tool loop appends fresh messages between iterations. Never\n // mutate the caller's array.\n const wireMessages = agentNames ? messages : withoutAgentNames(messages);\n\n for await (const event of llm.stream({ messages: wireMessages, tools, temperature, maxTokens })) {\n if (!isCurrent()) return { text, status: \"interrupted\" };\n switch (event.type) {\n case \"delta\": {\n const before = text;\n text += event.content;\n if (event.content.length > 0) onDelta?.(event.content, before);\n break;\n }\n case \"tool_call\":\n toolCalls.push({ id: event.id, name: event.name, arguments: event.arguments });\n break;\n case \"error\":\n throw event.error;\n case \"done\":\n done = true;\n }\n }\n\n if (!isCurrent()) return { text, status: \"interrupted\" };\n\n if (toolCalls.length > 0) {\n toolRounds++;\n // Pause the response: hand the calls to the application, then\n // resume once they are resolved.\n messages.push({ role: \"assistant\", name: agentName, content: text, toolCalls });\n const results = await resolveToolCalls(toolCalls);\n if (!isCurrent()) return { text, status: \"interrupted\" };\n for (const result of results) {\n messages.push({\n role: \"tool\",\n toolCallId: result.id,\n name: result.name,\n content: JSON.stringify(\n result.error !== undefined ? { error: result.error } : result.result,\n ),\n });\n }\n continue;\n }\n\n if (done) break;\n }\n\n return { text, status: \"done\" };\n } catch (error) {\n return {\n text,\n status: \"error\",\n error,\n ...(toolRounds > 0 ? { toolRounds } : {}),\n };\n }\n }\n}\n\n/**\n * Clone the messages with per-agent `name` fields removed from system and\n * assistant messages. Tool messages keep their `name` (the wire format\n * requires it on `role: \"tool\"`), and user messages never carry one.\n */\nfunction withoutAgentNames(messages: LLMMessage[]): LLMMessage[] {\n let hasNames = false;\n for (const message of messages) {\n if (message.name && (message.role === \"system\" || message.role === \"assistant\")) {\n hasNames = true;\n break;\n }\n }\n if (!hasNames) return messages;\n return messages.map((message) =>\n message.name && (message.role === \"system\" || message.role === \"assistant\")\n ? { ...message, name: undefined }\n : message,\n );\n}\n"],
5
+ "mappings": "4dAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,sBAAAE,IAAA,eAAAC,EAAAH,GAkEO,MAAMI,CAAiB,CAlE9B,MAkE8B,CAAAC,EAAA,yBAC5B,MAAM,IAAIC,EAAwD,CAChE,KAAM,CACJ,UAAAC,EACA,IAAAC,EACA,SAAAC,EACA,MAAAC,EACA,YAAAC,EACA,UAAAC,EACA,kBAAAC,EACA,UAAAC,EACA,WAAAC,EAAa,GACb,QAAAC,EACA,iBAAAC,CACF,EAAIX,EAEJ,IAAIY,EAAO,GACPC,EAAa,EAEjB,GAAI,CACF,QAASC,EAAY,EAAGA,EAAYP,EAAmBO,IAAa,CAClE,GAAI,CAACN,EAAU,EAAG,MAAO,CAAE,KAAAI,EAAM,OAAQ,aAAc,EAEvD,MAAMG,EAA2B,CAAC,EAClC,IAAIC,EAAO,GAMX,MAAMC,EAAeR,EAAaN,EAAWe,EAAkBf,CAAQ,EAEvE,gBAAiBgB,KAASjB,EAAI,OAAO,CAAE,SAAUe,EAAc,MAAAb,EAAO,YAAAC,EAAa,UAAAC,CAAU,CAAC,EAAG,CAC/F,GAAI,CAACE,EAAU,EAAG,MAAO,CAAE,KAAAI,EAAM,OAAQ,aAAc,EACvD,OAAQO,EAAM,KAAM,CAClB,IAAK,QAAS,CACZ,MAAMC,EAASR,EACfA,GAAQO,EAAM,QACVA,EAAM,QAAQ,OAAS,GAAGT,IAAUS,EAAM,QAASC,CAAM,EAC7D,KACF,CACA,IAAK,YACHL,EAAU,KAAK,CAAE,GAAII,EAAM,GAAI,KAAMA,EAAM,KAAM,UAAWA,EAAM,SAAU,CAAC,EAC7E,MACF,IAAK,QACH,MAAMA,EAAM,MACd,IAAK,OACHH,EAAO,EACX,CACF,CAEA,GAAI,CAACR,EAAU,EAAG,MAAO,CAAE,KAAAI,EAAM,OAAQ,aAAc,EAEvD,GAAIG,EAAU,OAAS,EAAG,CACxBF,IAGAV,EAAS,KAAK,CAAE,KAAM,YAAa,KAAMF,EAAW,QAASW,EAAM,UAAAG,CAAU,CAAC,EAC9E,MAAMM,EAAU,MAAMV,EAAiBI,CAAS,EAChD,GAAI,CAACP,EAAU,EAAG,MAAO,CAAE,KAAAI,EAAM,OAAQ,aAAc,EACvD,UAAWU,KAAUD,EACnBlB,EAAS,KAAK,CACZ,KAAM,OACN,WAAYmB,EAAO,GACnB,KAAMA,EAAO,KACb,QAAS,KAAK,UACZA,EAAO,QAAU,OAAY,CAAE,MAAOA,EAAO,KAAM,EAAIA,EAAO,MAChE,CACF,CAAC,EAEH,QACF,CAEA,GAAIN,EAAM,KACZ,CAEA,MAAO,CAAE,KAAAJ,EAAM,OAAQ,MAAO,CAChC,OAASW,EAAO,CACd,MAAO,CACL,KAAAX,EACA,OAAQ,QACR,MAAAW,EACA,GAAIV,EAAa,EAAI,CAAE,WAAAA,CAAW,EAAI,CAAC,CACzC,CACF,CACF,CACF,CAOA,SAASK,EAAkBf,EAAsC,CAC/D,IAAIqB,EAAW,GACf,UAAWC,KAAWtB,EACpB,GAAIsB,EAAQ,OAASA,EAAQ,OAAS,UAAYA,EAAQ,OAAS,aAAc,CAC/ED,EAAW,GACX,KACF,CAEF,OAAKA,EACErB,EAAS,IAAKsB,GACnBA,EAAQ,OAASA,EAAQ,OAAS,UAAYA,EAAQ,OAAS,aAC3D,CAAE,GAAGA,EAAS,KAAM,MAAU,EAC9BA,CACN,EALsBtB,CAMxB,CAdSJ,EAAAmB,EAAA",
6
+ "names": ["generation_exports", "__export", "GenerationRunner", "__toCommonJS", "GenerationRunner", "__name", "request", "agentName", "llm", "messages", "tools", "temperature", "maxTokens", "maxToolIterations", "isCurrent", "agentNames", "onDelta", "resolveToolCalls", "text", "toolRounds", "iteration", "toolCalls", "done", "wireMessages", "withoutAgentNames", "event", "before", "results", "result", "error", "hasNames", "message"]
7
7
  }