@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.
- package/README.md +39 -17
- package/dist/cjs/client/client.js +2 -0
- package/dist/cjs/client/client.js.map +7 -0
- package/dist/cjs/client/index.js +2 -0
- package/dist/cjs/client/index.js.map +7 -0
- package/dist/cjs/client/json.js +2 -0
- package/dist/cjs/client/json.js.map +7 -0
- package/dist/cjs/client/protocol/websocket.js +2 -0
- package/dist/cjs/client/protocol/websocket.js.map +7 -0
- package/dist/cjs/client/protocol.js +2 -0
- package/dist/cjs/client/protocol.js.map +7 -0
- package/dist/cjs/conversations/conversation/conversation.js +1 -1
- package/dist/cjs/conversations/conversation/conversation.js.map +3 -3
- package/dist/cjs/conversations/conversations.js +1 -1
- package/dist/cjs/conversations/conversations.js.map +2 -2
- package/dist/cjs/conversations/orchestration/orchestrator/generation/generation.js +1 -1
- package/dist/cjs/conversations/orchestration/orchestrator/generation/generation.js.map +3 -3
- package/dist/cjs/conversations/orchestration/orchestrator/orchestrator.js +1 -1
- package/dist/cjs/conversations/orchestration/orchestrator/orchestrator.js.map +3 -3
- package/dist/cjs/conversations/orchestration/orchestrator/speech/speech.js +1 -1
- package/dist/cjs/conversations/orchestration/orchestrator/speech/speech.js.map +3 -3
- package/dist/cjs/conversations/orchestration/orchestrator/tools/tools.js +1 -1
- package/dist/cjs/conversations/orchestration/orchestrator/tools/tools.js.map +3 -3
- package/dist/cjs/conversations/orchestration/test-harness.js +1 -1
- package/dist/cjs/conversations/orchestration/test-harness.js.map +2 -2
- package/dist/cjs/pipeflow/pipeflow.js +1 -1
- package/dist/cjs/pipeflow/pipeflow.js.map +2 -2
- package/dist/cjs/providers/llm/adapters/openai-compatible.js +6 -6
- package/dist/cjs/providers/llm/adapters/openai-compatible.js.map +3 -3
- package/dist/cjs/providers/llm/types.js.map +2 -2
- package/dist/cjs/providers/stt/adapters/openrouter/openrouter.js +1 -1
- package/dist/cjs/providers/stt/adapters/openrouter/openrouter.js.map +3 -3
- package/dist/cjs/providers/tts/adapters/kokoro/kokoro.js +3 -3
- package/dist/cjs/providers/tts/adapters/kokoro/kokoro.js.map +3 -3
- package/dist/cjs/providers/tts/adapters/openrouter/openrouter.js +1 -1
- package/dist/cjs/providers/tts/adapters/openrouter/openrouter.js.map +3 -3
- package/dist/esm/client/client.js +2 -0
- package/dist/esm/client/client.js.map +7 -0
- package/dist/esm/client/index.js +2 -0
- package/dist/esm/client/index.js.map +7 -0
- package/dist/esm/client/json.js +2 -0
- package/dist/esm/client/json.js.map +7 -0
- package/dist/esm/client/protocol/websocket.js +2 -0
- package/dist/esm/client/protocol/websocket.js.map +7 -0
- package/dist/esm/client/protocol.js +1 -0
- package/dist/esm/client/protocol.js.map +7 -0
- package/dist/esm/conversations/conversation/conversation.js +1 -1
- package/dist/esm/conversations/conversation/conversation.js.map +3 -3
- package/dist/esm/conversations/conversations.js +1 -1
- package/dist/esm/conversations/conversations.js.map +2 -2
- package/dist/esm/conversations/orchestration/orchestrator/generation/generation.js +1 -1
- package/dist/esm/conversations/orchestration/orchestrator/generation/generation.js.map +3 -3
- package/dist/esm/conversations/orchestration/orchestrator/orchestrator.js +1 -1
- package/dist/esm/conversations/orchestration/orchestrator/orchestrator.js.map +3 -3
- package/dist/esm/conversations/orchestration/orchestrator/speech/speech.js +1 -1
- package/dist/esm/conversations/orchestration/orchestrator/speech/speech.js.map +3 -3
- package/dist/esm/conversations/orchestration/orchestrator/tools/tools.js +1 -1
- package/dist/esm/conversations/orchestration/orchestrator/tools/tools.js.map +3 -3
- package/dist/esm/conversations/orchestration/test-harness.js +1 -1
- package/dist/esm/conversations/orchestration/test-harness.js.map +2 -2
- package/dist/esm/pipeflow/pipeflow.js +1 -1
- package/dist/esm/pipeflow/pipeflow.js.map +2 -2
- package/dist/esm/providers/llm/adapters/openai-compatible.js +6 -6
- package/dist/esm/providers/llm/adapters/openai-compatible.js.map +3 -3
- package/dist/esm/providers/llm/types.js.map +2 -2
- package/dist/esm/providers/stt/adapters/openrouter/openrouter.js +1 -1
- package/dist/esm/providers/stt/adapters/openrouter/openrouter.js.map +3 -3
- package/dist/esm/providers/tts/adapters/kokoro/kokoro.js +3 -3
- package/dist/esm/providers/tts/adapters/kokoro/kokoro.js.map +3 -3
- package/dist/esm/providers/tts/adapters/openrouter/openrouter.js +1 -1
- package/dist/esm/providers/tts/adapters/openrouter/openrouter.js.map +3 -3
- package/dist/types/client/client.d.ts +186 -0
- package/dist/types/client/index.d.ts +31 -0
- package/dist/types/client/json.d.ts +13 -0
- package/dist/types/client/protocol/websocket.d.ts +56 -0
- package/dist/types/client/protocol.d.ts +46 -0
- package/dist/types/conversations/conversation/conversation.d.ts +45 -0
- package/dist/types/conversations/conversations.d.ts +24 -0
- package/dist/types/conversations/orchestration/orchestrator/generation/generation.d.ts +17 -0
- package/dist/types/conversations/orchestration/orchestrator/orchestrator.d.ts +25 -6
- package/dist/types/conversations/orchestration/orchestrator/speech/speech.d.ts +31 -2
- package/dist/types/conversations/orchestration/orchestrator/tools/tools.d.ts +41 -7
- package/dist/types/conversations/orchestration/test-harness.d.ts +4 -0
- package/dist/types/pipeflow/pipeflow.d.ts +8 -0
- package/dist/types/providers/llm/types.d.ts +4 -1
- package/dist/types/providers/stt/adapters/openrouter/openrouter.d.ts +103 -1
- package/dist/types/providers/tts/adapters/kokoro/kokoro.d.ts +1 -1
- package/dist/types/providers/tts/adapters/openrouter/openrouter.d.ts +26 -7
- package/package.json +7 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var p=Object.defineProperty;var T=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var x=Object.prototype.hasOwnProperty;var a=(r,t)=>p(r,"name",{value:t,configurable:!0});var S=(r,t)=>{for(var e in t)p(r,e,{get:t[e],enumerable:!0})},C=(r,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of w(t))!x.call(r,i)&&i!==e&&p(r,i,{get:()=>t[i],enumerable:!(n=T(t,i))||n.enumerable});return r};var I=r=>C(p({},"__esModule",{value:!0}),r);var P={};S(P,{Orchestrator:()=>L});module.exports=I(P);var m=require("./history/history.js"),l=require("./routing/routing.js"),v=require("./generation/generation.js"),g=require("./speech/speech.js"),f=require("./tools/tools.js"),y=require("./coordination-runner/coordination-runner.js");const b=1;function A(r){return r instanceof Error?/aborted the stream|timed out|idle timeout|\b(429|50[0-9])\b/i.test(r.message):!1}a(A,"isTransientProviderError");class L{static{a(this,"Orchestrator")}conversation;agents;llm;stt;tts;persistence;maxToolIterations;historyWindow;temperature;maxTokens;history;speech;tools;generation;coordination;started=!1;generating=!1;epoch=0;generationChain=Promise.resolve();unsubscribers=[];sttSessions=new Map;pendingTurns=0;pendingGenerations=0;turnSequence=0;constructor(t){const e=t.agents??[],n=t.llm??e[0]?.llm;if(e.length>0&&!n)throw new Error("Orchestrator requires an LLM when agents are attached: pass one explicitly or configure an agent with one");this.conversation=t.conversation,this.agents=e,this.llm=n,this.stt=t.stt,this.tts=t.tts,this.persistence=t.persistence,this.maxToolIterations=t.maxToolIterations??10,this.historyWindow=t.historyWindow??{maxTurns:5,maxChars:4e3},this.temperature=t.temperature,this.maxTokens=t.maxTokens,this.history=new m.ConversationHistory,this.speech=new g.SpeechPipeline({tts:this.tts,conversation:this.conversation,isCurrent:a(o=>this.started&&o===this.epoch,"isCurrent"),...t.maxConcurrentTtsRequests!==void 0?{maxConcurrentRequests:t.maxConcurrentTtsRequests}:{}});const i=new Map;for(const o of e)for(const h of o.tools)i.set(h.name,h);this.tools=new f.ToolCallManager(this.conversation,t.toolTimeoutMs??3e4,{tools:i,autoExecute:t.autoExecuteTools??!0}),this.generation=new v.GenerationRunner,this.coordination=new y.CoordinationRunner({conversation:this.conversation,agents:a(()=>this.agents,"agents"),llm:a(()=>this.llm,"llm"),history:this.history,historyWindow:this.historyWindow,speech:this.speech,generation:this.generation,tools:this.tools,maxCoordinationSteps:t.maxCoordinationSteps??20,maxToolIterations:this.maxToolIterations,temperature:this.temperature,maxTokens:this.maxTokens,currentEpoch:a(()=>this.epoch,"currentEpoch"),isCurrent:a(o=>this.started&&o===this.epoch,"isCurrent")}),this.coordination.register(t.coordinations??{},e)}async start(){if(!this.started){if(this.started=!0,this.persistence){const[t,e]=await Promise.all([this.persistence.listTurns(this.conversation.id),this.persistence.listGenerations(this.conversation.id)]);this.history.rehydrate(t,e,this.conversation)}this.unsubscribers.push(this.conversation.on("stop",()=>{this.stop()}),this.conversation.on("audio-in",t=>this.onAudioIn(t)),this.conversation.on("text-in",({userId:t,text:e})=>this.onFinal(t,e)),this.conversation.on("interrupt",()=>this.onInterrupt()),this.conversation.on("tool-call-result",({result:t})=>this.tools.handleResult(t)))}}async stop(){if(!this.started)return;this.started=!1,this.epoch++,this.coordination.cancel(),this.stopLlms(),this.speech.stop(),this.tools.cancelAll("conversation stopped");for(const e of this.unsubscribers)e();this.unsubscribers.length=0;const t=[...this.sttSessions.values()];this.sttSessions.clear();for(const{session:e}of t)await e.end().catch(()=>{})}async whenIdle(){for(;;){for(;this.pendingTurns>0||this.pendingGenerations>0||this.generating;)await Bun.sleep(1);if(await Promise.resolve(),this.pendingTurns===0&&this.pendingGenerations===0&&!this.generating)return}}onAudioIn(t){if(!this.stt)return;let e=this.sttSessions.get(t.userId);if(!e){const n=this.stt.start({});n.on("partial",i=>this.onPartial(t.userId,i)),n.on("final",i=>this.onFinal(t.userId,i)),n.on("error",i=>this.onProviderError(i)),e={session:n,turnStartedAt:null},this.sttSessions.set(t.userId,e)}if(e.turnStartedAt===null&&(e.turnStartedAt=Date.now()),e.session.write(t.audio.data),this.coordination.hasPending()){this.speech.stop();return}this.pendingGenerations>0&&this.conversation.interrupt()}onPartial(t,e){const n=e.trim();n&&this.conversation.emit("partial-transcript",{conversationId:this.conversation.id,userId:t,text:n})}onFinal(t,e){const n=e.trim();if(!n)return;const i=this.conversation.state.participants.get(t);if(!i)return;const o=this.sttSessions.get(t),h=o?.turnStartedAt??Date.now();o&&(o.turnStartedAt=null);const d={id:crypto.randomUUID(),conversationId:this.conversation.id,participantId:t,participantName:i.aliases[0]??t,text:n,sequence:this.turnSequence++,startedAt:h,endedAt:Date.now()};this.pendingTurns++,this.processTurn(d)}async processTurn(t){try{if(await this.conversation.pushTurn(t),await this.conversation.pushTranscript({speaker:t.participantName,speakerKind:"participant",text:t.text}),this.history.addUserTurn(t,this.conversation),this.agents.length===0)return;if(this.coordination.hasPending()){this.enqueueCoordinationRun(()=>this.coordination.resume(t));return}if(this.agents.length===1||(0,l.findAddressedAgent)(this.agents,t.text)){this.queueGeneration(t);return}this.enqueueCoordinationRun(()=>this.coordination.runDefault(t))}finally{this.pendingTurns--}}onInterrupt(){this.epoch++,this.coordination.cancel(),this.stopLlms(),this.speech.stop(),this.tools.cancelAll("interrupted")}stopLlms(){const t=new Set;this.llm&&t.add(this.llm);for(const e of this.agents)e.llm&&t.add(e.llm);for(const e of t)e.stop()}onProviderError(t){this.conversation.emit("error",{conversationId:this.conversation.id,error:t})}queueGeneration(t){this.pendingGenerations++;const e=this.epoch;this.generationChain=this.generationChain.then(async()=>{if(this.epoch!==e){this.pendingGenerations--;return}try{await this.generate(t)}finally{this.pendingGenerations--}})}async generate(t){if(this.agents.length===0)return;const e=(0,l.pickAgent)(this.agents,t.text);if(!e)return;const n=e.llm??this.llm;if(n){this.generating=!0;try{await this.runGeneration(this.epoch,t,e,n)}finally{this.generating=!1}}}async runGeneration(t,e,n,i){const o=crypto.randomUUID();await this.conversation.pushGeneration({id:o,conversationId:this.conversation.id,agentName:n.name,text:"",status:"streaming",startedAt:Date.now()});const h=n.tools.map(c=>({name:c.name,description:c.description,parameters:c.parameters??{type:"object",properties:{}}})),d=[];n.context&&d.push({role:"system",name:n.name,content:n.context}),d.push(...this.history.windowed(this.historyWindow));let s;for(let c=0;;c++){if(s=await this.generation.run({agentName:n.name,llm:i,messages:d,tools:h,temperature:this.temperature,maxTokens:this.maxTokens,maxToolIterations:this.maxToolIterations,isCurrent:a(()=>this.epoch===t,"isCurrent"),agentNames:this.agents.length>1,onDelta:a(u=>this.speech.feed(u,t),"onDelta"),resolveToolCalls:a(u=>this.tools.resolveCalls(u),"resolveToolCalls")}),this.epoch!==t)return;if(!(c<b&&s.status==="error"&&s.text.length===0&&(s.toolRounds??0)===0&&A(s.error)))break}if(this.epoch===t&&s.status!=="interrupted"){if(s.status==="error"){this.conversation.emit("error",{conversationId:this.conversation.id,error:s.error instanceof Error?s.error:new Error(String(s.error))}),await this.conversation.completeGeneration(s.text),s.text&&(await this.conversation.pushTranscript({speaker:n.name,speakerKind:"agent",text:s.text}),this.history.addAssistant(n.name,s.text));return}this.speech.flush(t),await this.speech.waitForIdle(),this.epoch===t&&(await this.conversation.completeGeneration(s.text),await this.conversation.pushTranscript({speaker:n.name,speakerKind:"agent",text:s.text}),this.history.addAssistant(n.name,s.text))}}enqueueCoordinationRun(t){this.pendingGenerations++,this.generationChain=this.generationChain.then(async()=>{this.generating=!0;try{await t()}finally{this.generating=!1,this.pendingGenerations--}})}}
|
|
2
2
|
//# sourceMappingURL=orchestrator.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/conversations/orchestration/orchestrator/orchestrator.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Agent } from \"../../../agents/agent\";\nimport type { Conversation } from \"../../conversation/conversation\";\nimport type { Persistence } from \"../../../persistence/persistence\";\nimport type {\n LLM,\n LLMMessage,\n LLMToolDefinition,\n} from \"../../../providers/llm/types\";\nimport type { STT, STTSession } from \"../../../providers/stt/types\";\nimport type { TTS } from \"../../../providers/tts/types\";\nimport type { AudioChunk, Turn, UserId } from \"../../types\";\nimport type { CoordinationRegistration } from \"../coordination/coordination\";\nimport { ConversationHistory, type HistoryWindow } from \"./history/history\";\nimport { findAddressedAgent, pickAgent } from \"./routing/routing\";\nimport { GenerationRunner } from \"./generation/generation\";\nimport { SpeechPipeline } from \"./speech/speech\";\nimport { ToolCallManager } from \"./tools/tools\";\nimport { CoordinationRunner } from \"./coordination-runner/coordination-runner\";\n\nexport interface OrchestratorOptions {\n conversation: Conversation;\n /**\n * The agents the orchestrator routes turns to. With more than one agent,\n * unaddressed turns run through the built-in `understand` coordination,\n * which can delegate to agents, ask the user, or answer directly. Omit\n * for transcription-only mode (audio in, turns and transcripts out \u2014 no\n * LLM or TTS required).\n */\n agents?: Agent[];\n /** Defaults to the first agent's LLM. */\n llm?: LLM;\n /** STT for voice turns. Omit for text-only conversations (`send()`). */\n stt?: STT;\n /** TTS for spoken output. Not required for text-only conversations. */\n tts?: TTS;\n /** Used to rehydrate conversation history on start. */\n persistence?: Persistence;\n /** How long to wait for the application to resolve a tool call. */\n toolTimeoutMs?: number;\n /** Safety bound on tool-call round trips per generation. */\n maxToolIterations?: number;\n /**\n * Additional coordinations the runtime can delegate to, registered by\n * name (the key is the coordination's name).\n */\n coordinations?: Record<string, CoordinationRegistration>;\n /** Safety bound on LLM reasoning steps per coordination execution. */\n maxCoordinationSteps?: number;\n temperature?: number;\n maxTokens?: number;\n /**\n * Bounds how much conversation history each LLM request carries (default\n * `{ maxTurns: 5, maxChars: 4000 }` \u2014 provider TTFT grows with input size,\n * and a bounded window keeps requests in the fast regime). Pass `false` to\n * always send the full history.\n */\n historyWindow?: HistoryWindow | false;\n}\n\ninterface SttSessionEntry {\n session: STTSession;\n turnStartedAt: number | null;\n}\n\n/**\n * The realtime conversation state machine and multi-agent coordinator.\n *\n * Wires the conversation to the providers and routes each turn to an agent.\n * The heavy machinery lives in focused collaborators \u2014 history, routing,\n * generation, speech, tools, and coordination \u2014 so this class owns only the\n * lifecycle and event wiring:\n *\n * ```text\n * audio-in \u2500\u2500\u25BA STT \u2500\u2500\u25BA turn \u2500\u2500\u25BA coordinator \u2500\u2500\u25BA agent \u2500\u2500\u25BA LLM \u2500\u2500\u25BA TTS \u2500\u2500\u25BA audio-out\n * \u2502\n * \u2514\u2500\u25BA tool-call \u2500\u2500\u25BA app resolves \u2500\u2500\u25BA resume\n * ```\n *\n * Deltas stream to TTS immediately (so the agent can narrate while a tool\n * runs), tool calls pause the generation until the application resolves\n * them, and interruptions cancel the current generation \u2014 discarding any\n * stale tool results or audio via a generation epoch.\n */\nexport class Orchestrator {\n private readonly conversation: Conversation;\n private readonly agents: Agent[];\n private readonly llm: LLM | undefined;\n private readonly stt: STT | undefined;\n private readonly tts: TTS | undefined;\n private readonly persistence: Persistence | undefined;\n private readonly maxToolIterations: number;\n private readonly historyWindow: HistoryWindow | false;\n private readonly temperature: number | undefined;\n private readonly maxTokens: number | undefined;\n\n private readonly history: ConversationHistory;\n private readonly speech: SpeechPipeline;\n private readonly tools: ToolCallManager;\n private readonly generation: GenerationRunner;\n private readonly coordination: CoordinationRunner;\n\n private started = false;\n private generating = false;\n private epoch = 0;\n private generationChain: Promise<void> = Promise.resolve();\n private readonly unsubscribers: (() => void)[] = [];\n private readonly sttSessions = new Map<UserId, SttSessionEntry>();\n private pendingTurns = 0;\n private pendingGenerations = 0;\n private turnSequence = 0;\n\n constructor(options: OrchestratorOptions) {\n const agents = options.agents ?? [];\n const llm = options.llm ?? agents[0]?.llm;\n if (agents.length > 0 && !llm) {\n throw new Error(\n \"Orchestrator requires an LLM when agents are attached: \" +\n \"pass one explicitly or configure an agent with one\",\n );\n }\n this.conversation = options.conversation;\n this.agents = agents;\n this.llm = llm;\n this.stt = options.stt;\n this.tts = options.tts;\n this.persistence = options.persistence;\n this.maxToolIterations = options.maxToolIterations ?? 10;\n this.historyWindow = options.historyWindow ?? { maxTurns: 5, maxChars: 4_000 };\n this.temperature = options.temperature;\n this.maxTokens = options.maxTokens;\n\n this.history = new ConversationHistory();\n this.speech = new SpeechPipeline({\n tts: this.tts,\n conversation: this.conversation,\n isCurrent: (epoch) => this.started && epoch === this.epoch,\n });\n this.tools = new ToolCallManager(this.conversation, options.toolTimeoutMs ?? 30_000);\n this.generation = new GenerationRunner();\n this.coordination = new CoordinationRunner({\n conversation: this.conversation,\n agents: () => this.agents,\n llm: () => this.llm,\n history: this.history,\n historyWindow: this.historyWindow,\n speech: this.speech,\n generation: this.generation,\n tools: this.tools,\n maxCoordinationSteps: options.maxCoordinationSteps ?? 20,\n maxToolIterations: this.maxToolIterations,\n temperature: this.temperature,\n maxTokens: this.maxTokens,\n currentEpoch: () => this.epoch,\n isCurrent: (epoch) => this.started && epoch === this.epoch,\n });\n this.coordination.register(options.coordinations ?? {}, agents);\n }\n\n /**\n * Attach to the conversation: rehydrate history from persistence and\n * subscribe to conversation events.\n */\n async start(): Promise<void> {\n if (this.started) return;\n this.started = true;\n\n if (this.persistence) {\n const [turns, generations] = await Promise.all([\n this.persistence.listTurns(this.conversation.id),\n this.persistence.listGenerations(this.conversation.id),\n ]);\n this.history.rehydrate(turns, generations, this.conversation);\n }\n\n this.unsubscribers.push(\n this.conversation.on(\"stop\", () => void this.stop()),\n this.conversation.on(\"audio-in\", (payload) => this.onAudioIn(payload)),\n this.conversation.on(\"text-in\", ({ userId, text }) => this.onFinal(userId, text)),\n this.conversation.on(\"interrupt\", () => this.onInterrupt()),\n this.conversation.on(\"tool-call-result\", ({ result }) => this.tools.handleResult(result)),\n );\n }\n\n async stop(): Promise<void> {\n if (!this.started) return;\n this.started = false;\n this.epoch++;\n this.coordination.cancel();\n this.stopLlms();\n this.speech.stop();\n this.tools.cancelAll(\"conversation stopped\");\n for (const unsubscribe of this.unsubscribers) unsubscribe();\n this.unsubscribers.length = 0;\n const sessions = [...this.sttSessions.values()];\n this.sttSessions.clear();\n for (const { session } of sessions) {\n await session.end().catch(() => {});\n }\n }\n\n /**\n * Resolve once every queued and in-flight turn and generation has\n * finished processing. Useful for tests and graceful shutdown.\n */\n async whenIdle(): Promise<void> {\n for (;;) {\n while (\n this.pendingTurns > 0 ||\n this.pendingGenerations > 0 ||\n this.generating\n ) {\n await Bun.sleep(1);\n }\n // Give work scheduled in the same microtask turn a chance to register.\n await Promise.resolve();\n if (\n this.pendingTurns === 0 &&\n this.pendingGenerations === 0 &&\n !this.generating\n ) {\n return;\n }\n }\n }\n\n // -------------------------------------------------------------------------\n // Conversation event handlers\n // -------------------------------------------------------------------------\n\n private onAudioIn(payload: { userId: UserId; audio: AudioChunk }): void {\n if (!this.stt) return; // text-only conversation\n let entry = this.sttSessions.get(payload.userId);\n if (!entry) {\n const session = this.stt.start({});\n session.on(\"partial\", (text) => this.onPartial(payload.userId, text));\n session.on(\"final\", (text) => this.onFinal(payload.userId, text));\n session.on(\"error\", (error) => this.onProviderError(error));\n entry = { session, turnStartedAt: null };\n this.sttSessions.set(payload.userId, entry);\n }\n\n if (entry.turnStartedAt === null) {\n entry.turnStartedAt = Date.now();\n }\n entry.session.write(payload.audio.data);\n\n if (this.coordination.hasPending()) {\n // The participant is answering a pending question \u2014 this audio is the\n // answer, not an interruption. Stop the question's playback only; the\n // coordination stays parked and resumes on the final transcript.\n this.speech.stop();\n return;\n }\n\n // Barge-in: a participant speaks while the agent is responding (or about\n // to). The interrupting audio is already queued to STT above.\n if (this.pendingGenerations > 0) {\n this.conversation.interrupt();\n }\n }\n\n private onPartial(userId: UserId, text: string): void {\n const trimmed = text.trim();\n if (!trimmed) return;\n this.conversation.emit(\"partial-transcript\", {\n conversationId: this.conversation.id,\n userId,\n text: trimmed,\n });\n }\n\n private onFinal(userId: UserId, text: string): void {\n const trimmed = text.trim();\n if (!trimmed) return;\n const participant = this.conversation.state.participants.get(userId);\n if (!participant) return;\n\n const entry = this.sttSessions.get(userId);\n const startedAt = entry?.turnStartedAt ?? Date.now();\n if (entry) entry.turnStartedAt = null;\n\n const turn: Turn = {\n id: crypto.randomUUID(),\n conversationId: this.conversation.id,\n participantId: userId,\n participantName: participant.aliases[0] ?? userId,\n text: trimmed,\n sequence: this.turnSequence++,\n startedAt,\n endedAt: Date.now(),\n };\n\n // Track synchronously so `whenIdle()` can observe in-flight work.\n this.pendingTurns++;\n void this.processTurn(turn);\n }\n\n private async processTurn(turn: Turn): Promise<void> {\n try {\n await this.conversation.pushTurn(turn);\n await this.conversation.pushTranscript({\n speaker: turn.participantName,\n speakerKind: \"participant\",\n text: turn.text,\n });\n this.history.addUserTurn(turn, this.conversation);\n if (this.agents.length === 0) return;\n\n if (this.coordination.hasPending()) {\n // The user answered a pending coordination question: resume it with\n // this turn instead of starting a fresh generation.\n this.enqueueCoordinationRun(() => this.coordination.resume(turn));\n return;\n }\n\n if (this.agents.length === 1 || findAddressedAgent(this.agents, turn.text)) {\n // Single-agent conversations, and turns that explicitly address an\n // agent by name/alias, route straight to that agent.\n this.queueGeneration(turn);\n return;\n }\n\n // Otherwise the built-in `understand` coordination decides: delegate to\n // agents, ask the user, or answer directly.\n this.enqueueCoordinationRun(() => this.coordination.runDefault(turn));\n } finally {\n this.pendingTurns--;\n }\n }\n\n private onInterrupt(): void {\n this.epoch++;\n this.coordination.cancel();\n this.stopLlms();\n this.speech.stop();\n this.tools.cancelAll(\"interrupted\");\n }\n\n /** Cancel every LLM in play: the shared one and each agent's own. */\n private stopLlms(): void {\n const seen = new Set<LLM>();\n if (this.llm) seen.add(this.llm);\n for (const agent of this.agents) {\n if (agent.llm) seen.add(agent.llm);\n }\n for (const llm of seen) llm.stop();\n }\n\n private onProviderError(error: Error): void {\n this.conversation.emit(\"error\", { conversationId: this.conversation.id, error });\n }\n\n // -------------------------------------------------------------------------\n // Generation\n // -------------------------------------------------------------------------\n\n private queueGeneration(turn: Turn): void {\n this.pendingGenerations++;\n const epoch = this.epoch;\n this.generationChain = this.generationChain.then(async () => {\n if (this.epoch !== epoch) {\n // Superseded by an interruption while queued.\n this.pendingGenerations--;\n return;\n }\n try {\n await this.generate(turn);\n } finally {\n this.pendingGenerations--;\n }\n });\n }\n\n private async generate(turn: Turn): Promise<void> {\n if (this.agents.length === 0) return;\n // The coordinator routes this turn to an agent by name/alias, falling\n // back to the first agent in the roster.\n const agent = pickAgent(this.agents, turn.text);\n if (!agent) return;\n // Prefer the routed agent's own LLM so agents with different providers\n // keep their intelligence; fall back to the shared LLM.\n const llm = agent.llm ?? this.llm;\n if (!llm) return;\n this.generating = true;\n try {\n await this.runGeneration(this.epoch, turn, agent, llm);\n } finally {\n this.generating = false;\n }\n }\n\n private async runGeneration(\n epoch: number,\n turn: Turn,\n agent: Agent,\n llm: LLM,\n ): Promise<void> {\n const generationId = crypto.randomUUID();\n await this.conversation.pushGeneration({\n id: generationId,\n conversationId: this.conversation.id,\n agentName: agent.name,\n text: \"\",\n status: \"streaming\",\n startedAt: Date.now(),\n });\n\n const definitions: LLMToolDefinition[] = agent.tools.map((tool) => ({\n name: tool.name,\n description: tool.description,\n parameters: tool.parameters ?? { type: \"object\", properties: {} },\n }));\n\n const messages: LLMMessage[] = [];\n if (agent.context) {\n messages.push({ role: \"system\", name: agent.name, content: agent.context });\n }\n messages.push(...this.history.windowed(this.historyWindow));\n\n const outcome = await this.generation.run({\n agentName: agent.name,\n llm,\n messages,\n tools: definitions,\n temperature: this.temperature,\n maxTokens: this.maxTokens,\n maxToolIterations: this.maxToolIterations,\n isCurrent: () => this.epoch === epoch,\n onDelta: (delta) => this.speech.feed(delta, epoch),\n resolveToolCalls: (calls) => this.tools.resolveCalls(calls),\n });\n\n // A stale run (interrupt/stop) is discarded entirely \u2014 the conversation\n // already marked the generation cancelled.\n if (this.epoch !== epoch) return;\n if (outcome.status === \"interrupted\") return;\n\n if (outcome.status === \"error\") {\n this.conversation.emit(\"error\", {\n conversationId: this.conversation.id,\n error: outcome.error instanceof Error ? outcome.error : new Error(String(outcome.error)),\n });\n // Finalize whatever was generated so the conversation state stays\n // consistent even though the provider failed.\n await this.conversation.completeGeneration(outcome.text);\n if (outcome.text) {\n await this.conversation.pushTranscript({\n speaker: agent.name,\n speakerKind: \"agent\",\n text: outcome.text,\n });\n this.history.addAssistant(agent.name, outcome.text);\n }\n return;\n }\n\n this.speech.flush(epoch);\n await this.speech.waitForIdle();\n if (this.epoch !== epoch) return;\n\n await this.conversation.completeGeneration(outcome.text);\n await this.conversation.pushTranscript({\n speaker: agent.name,\n speakerKind: \"agent\",\n text: outcome.text,\n });\n this.history.addAssistant(agent.name, outcome.text);\n }\n\n // -------------------------------------------------------------------------\n // Coordination (serialized with agent generations: one track at a time)\n // -------------------------------------------------------------------------\n\n private enqueueCoordinationRun(run: () => Promise<void>): void {\n this.pendingGenerations++;\n this.generationChain = this.generationChain.then(async () => {\n this.generating = true;\n try {\n await run();\n } finally {\n this.generating = false;\n this.pendingGenerations--;\n }\n });\n }\n}\n"],
|
|
5
|
-
"mappings": "4dAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,kBAAAE,IAAA,eAAAC,EAAAH,
|
|
6
|
-
"names": ["orchestrator_exports", "__export", "Orchestrator", "__toCommonJS", "import_history", "import_routing", "import_generation", "import_speech", "import_tools", "import_coordination_runner", "
|
|
4
|
+
"sourcesContent": ["import type { Agent } from \"../../../agents/agent\";\nimport type { Tool } from \"../../../agents/tools/tools\";\nimport type { Conversation } from \"../../conversation/conversation\";\nimport type { Persistence } from \"../../../persistence/persistence\";\nimport type {\n LLM,\n LLMMessage,\n LLMToolDefinition,\n} from \"../../../providers/llm/types\";\nimport type { STT, STTSession } from \"../../../providers/stt/types\";\nimport type { TTS } from \"../../../providers/tts/types\";\nimport type { AudioChunk, Turn, UserId } from \"../../types\";\nimport type { CoordinationRegistration } from \"../coordination/coordination\";\nimport { ConversationHistory, type HistoryWindow } from \"./history/history\";\nimport { findAddressedAgent, pickAgent } from \"./routing/routing\";\nimport { GenerationRunner } from \"./generation/generation\";\nimport { SpeechPipeline } from \"./speech/speech\";\nimport { ToolCallManager } from \"./tools/tools\";\nimport { CoordinationRunner } from \"./coordination-runner/coordination-runner\";\n\n/**\n * Extra attempts after a transient provider failure that produced no output.\n * Kept tiny: each attempt is a fresh LLM request (latency + cost), and the\n * failure is usually a one-off upstream abort.\n */\nconst MAX_TRANSIENT_GENERATION_RETRIES = 1;\n\n/**\n * True for provider failures worth one silent retry: mid-stream aborts (the\n * Bedrock-via-OpenRouter failure nova models show as \"provider aborted the\n * stream\"), idle timeouts, and HTTP 429/5xx. Real failures (400, auth, bad\n * requests) are not retried.\n */\nfunction isTransientProviderError(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n return /aborted the stream|timed out|idle timeout|\\b(429|50[0-9])\\b/i.test(error.message);\n}\n\nexport interface OrchestratorOptions {\n conversation: Conversation;\n /**\n * The agents the orchestrator routes turns to. With more than one agent,\n * unaddressed turns run through the built-in `understand` coordination,\n * which can delegate to agents, ask the user, or answer directly. Omit\n * for transcription-only mode (audio in, turns and transcripts out \u2014 no\n * LLM or TTS required).\n */\n agents?: Agent[];\n /** Defaults to the first agent's LLM. */\n llm?: LLM;\n /** STT for voice turns. Omit for text-only conversations (`send()`). */\n stt?: STT;\n /** TTS for spoken output. Not required for text-only conversations. */\n tts?: TTS;\n /** Used to rehydrate conversation history on start. */\n persistence?: Persistence;\n /** How long to wait for a tool call to resolve (auto-executed or app-managed). */\n toolTimeoutMs?: number;\n /** Safety bound on tool-call round trips per generation. */\n maxToolIterations?: number;\n /**\n * Execute the agents' tools automatically (default `true`), feeding each\n * tool's result \u2014 or a caught error \u2014 back into the model loop, like\n * `Agent.run()` does. Set `false` for the application-managed contract:\n * listen for `tool-call` events and resolve each call yourself with\n * `conversation.resolveToolCall()`. The `tool-call` event fires in both\n * modes, so apps can observe every call.\n */\n autoExecuteTools?: boolean;\n /**\n * Additional coordinations the runtime can delegate to, registered by\n * name (the key is the coordination's name).\n */\n coordinations?: Record<string, CoordinationRegistration>;\n /** Safety bound on LLM reasoning steps per coordination execution. */\n maxCoordinationSteps?: number;\n temperature?: number;\n maxTokens?: number;\n /**\n * How much conversation history each LLM request carries (default\n * `{ maxTurns: 5, maxChars: 4000 }` \u2014 provider TTFT grows with input size,\n * and a bounded window keeps requests in the fast regime). Pass `false` to\n * always send the full history.\n */\n historyWindow?: HistoryWindow | false;\n /**\n * How many TTS synthesis requests may be in flight at once (default 2).\n * More lets the sentences of a multi-sentence reply synthesize in parallel\n * (kills the gaps between them) at the cost of provider concurrency \u2014 some\n * free TTS variants rate-limit concurrent requests. Passed through to the\n * speech pipeline; see `SpeechPipelineOptions.maxConcurrentRequests`.\n */\n maxConcurrentTtsRequests?: number;\n}\n\ninterface SttSessionEntry {\n session: STTSession;\n turnStartedAt: number | null;\n}\n\n/**\n * The realtime conversation state machine and multi-agent coordinator.\n *\n * Wires the conversation to the providers and routes each turn to an agent.\n * The heavy machinery lives in focused collaborators \u2014 history, routing,\n * generation, speech, tools, and coordination \u2014 so this class owns only the\n * lifecycle and event wiring:\n *\n * ```text\n * audio-in \u2500\u2500\u25BA STT \u2500\u2500\u25BA turn \u2500\u2500\u25BA coordinator \u2500\u2500\u25BA agent \u2500\u2500\u25BA LLM \u2500\u2500\u25BA TTS \u2500\u2500\u25BA audio-out\n * \u2502\n * \u2514\u2500\u25BA tool-call \u2500\u2500\u25BA tool runs \u2500\u2500\u25BA resume\n * ```\n *\n * Deltas stream to TTS immediately (so the agent can narrate while a tool\n * runs), tool calls pause the generation while the tool executes \u2014 the\n * framework runs the agent's own tools by default, or the application\n * resolves them when `autoExecuteTools` is off \u2014 and interruptions cancel\n * the current generation, discarding any stale tool results or audio via a\n * generation epoch.\n */\nexport class Orchestrator {\n private readonly conversation: Conversation;\n private readonly agents: Agent[];\n private readonly llm: LLM | undefined;\n private readonly stt: STT | undefined;\n private readonly tts: TTS | undefined;\n private readonly persistence: Persistence | undefined;\n private readonly maxToolIterations: number;\n private readonly historyWindow: HistoryWindow | false;\n private readonly temperature: number | undefined;\n private readonly maxTokens: number | undefined;\n\n private readonly history: ConversationHistory;\n private readonly speech: SpeechPipeline;\n private readonly tools: ToolCallManager;\n private readonly generation: GenerationRunner;\n private readonly coordination: CoordinationRunner;\n\n private started = false;\n private generating = false;\n private epoch = 0;\n private generationChain: Promise<void> = Promise.resolve();\n private readonly unsubscribers: (() => void)[] = [];\n private readonly sttSessions = new Map<UserId, SttSessionEntry>();\n private pendingTurns = 0;\n private pendingGenerations = 0;\n private turnSequence = 0;\n\n constructor(options: OrchestratorOptions) {\n const agents = options.agents ?? [];\n const llm = options.llm ?? agents[0]?.llm;\n if (agents.length > 0 && !llm) {\n throw new Error(\n \"Orchestrator requires an LLM when agents are attached: \" +\n \"pass one explicitly or configure an agent with one\",\n );\n }\n this.conversation = options.conversation;\n this.agents = agents;\n this.llm = llm;\n this.stt = options.stt;\n this.tts = options.tts;\n this.persistence = options.persistence;\n this.maxToolIterations = options.maxToolIterations ?? 10;\n this.historyWindow = options.historyWindow ?? { maxTurns: 5, maxChars: 4_000 };\n this.temperature = options.temperature;\n this.maxTokens = options.maxTokens;\n\n this.history = new ConversationHistory();\n this.speech = new SpeechPipeline({\n tts: this.tts,\n conversation: this.conversation,\n isCurrent: (epoch) => this.started && epoch === this.epoch,\n ...(options.maxConcurrentTtsRequests !== undefined\n ? { maxConcurrentRequests: options.maxConcurrentTtsRequests }\n : {}),\n });\n // The registry spans every agent's tools, so top-level generations and\n // delegated sub-generations both auto-execute the tools the routed agent\n // actually registered (the definitions each LLM sees are scoped to that\n // agent; on a name collision the last agent wins).\n const toolRegistry = new Map<string, Tool<never, unknown>>();\n for (const agent of agents) {\n for (const tool of agent.tools) toolRegistry.set(tool.name, tool);\n }\n this.tools = new ToolCallManager(this.conversation, options.toolTimeoutMs ?? 30_000, {\n tools: toolRegistry,\n autoExecute: options.autoExecuteTools ?? true,\n });\n this.generation = new GenerationRunner();\n this.coordination = new CoordinationRunner({\n conversation: this.conversation,\n agents: () => this.agents,\n llm: () => this.llm,\n history: this.history,\n historyWindow: this.historyWindow,\n speech: this.speech,\n generation: this.generation,\n tools: this.tools,\n maxCoordinationSteps: options.maxCoordinationSteps ?? 20,\n maxToolIterations: this.maxToolIterations,\n temperature: this.temperature,\n maxTokens: this.maxTokens,\n currentEpoch: () => this.epoch,\n isCurrent: (epoch) => this.started && epoch === this.epoch,\n });\n this.coordination.register(options.coordinations ?? {}, agents);\n }\n\n /**\n * Attach to the conversation: rehydrate history from persistence and\n * subscribe to conversation events.\n */\n async start(): Promise<void> {\n if (this.started) return;\n this.started = true;\n\n if (this.persistence) {\n const [turns, generations] = await Promise.all([\n this.persistence.listTurns(this.conversation.id),\n this.persistence.listGenerations(this.conversation.id),\n ]);\n this.history.rehydrate(turns, generations, this.conversation);\n }\n\n this.unsubscribers.push(\n this.conversation.on(\"stop\", () => void this.stop()),\n this.conversation.on(\"audio-in\", (payload) => this.onAudioIn(payload)),\n this.conversation.on(\"text-in\", ({ userId, text }) => this.onFinal(userId, text)),\n this.conversation.on(\"interrupt\", () => this.onInterrupt()),\n this.conversation.on(\"tool-call-result\", ({ result }) => this.tools.handleResult(result)),\n );\n }\n\n async stop(): Promise<void> {\n if (!this.started) return;\n this.started = false;\n this.epoch++;\n this.coordination.cancel();\n this.stopLlms();\n this.speech.stop();\n this.tools.cancelAll(\"conversation stopped\");\n for (const unsubscribe of this.unsubscribers) unsubscribe();\n this.unsubscribers.length = 0;\n const sessions = [...this.sttSessions.values()];\n this.sttSessions.clear();\n for (const { session } of sessions) {\n await session.end().catch(() => {});\n }\n }\n\n /**\n * Resolve once every queued and in-flight turn and generation has\n * finished processing. Useful for tests and graceful shutdown.\n */\n async whenIdle(): Promise<void> {\n for (;;) {\n while (\n this.pendingTurns > 0 ||\n this.pendingGenerations > 0 ||\n this.generating\n ) {\n await Bun.sleep(1);\n }\n // Give work scheduled in the same microtask turn a chance to register.\n await Promise.resolve();\n if (\n this.pendingTurns === 0 &&\n this.pendingGenerations === 0 &&\n !this.generating\n ) {\n return;\n }\n }\n }\n\n // -------------------------------------------------------------------------\n // Conversation event handlers\n // -------------------------------------------------------------------------\n\n private onAudioIn(payload: { userId: UserId; audio: AudioChunk }): void {\n if (!this.stt) return; // text-only conversation\n let entry = this.sttSessions.get(payload.userId);\n if (!entry) {\n const session = this.stt.start({});\n session.on(\"partial\", (text) => this.onPartial(payload.userId, text));\n session.on(\"final\", (text) => this.onFinal(payload.userId, text));\n session.on(\"error\", (error) => this.onProviderError(error));\n entry = { session, turnStartedAt: null };\n this.sttSessions.set(payload.userId, entry);\n }\n\n if (entry.turnStartedAt === null) {\n entry.turnStartedAt = Date.now();\n }\n entry.session.write(payload.audio.data);\n\n if (this.coordination.hasPending()) {\n // The participant is answering a pending question \u2014 this audio is the\n // answer, not an interruption. Stop the question's playback only; the\n // coordination stays parked and resumes on the final transcript.\n this.speech.stop();\n return;\n }\n\n // Barge-in: a participant speaks while the agent is responding (or about\n // to). The interrupting audio is already queued to STT above.\n if (this.pendingGenerations > 0) {\n this.conversation.interrupt();\n }\n }\n\n private onPartial(userId: UserId, text: string): void {\n const trimmed = text.trim();\n if (!trimmed) return;\n this.conversation.emit(\"partial-transcript\", {\n conversationId: this.conversation.id,\n userId,\n text: trimmed,\n });\n }\n\n private onFinal(userId: UserId, text: string): void {\n const trimmed = text.trim();\n if (!trimmed) return;\n const participant = this.conversation.state.participants.get(userId);\n if (!participant) return;\n\n const entry = this.sttSessions.get(userId);\n const startedAt = entry?.turnStartedAt ?? Date.now();\n if (entry) entry.turnStartedAt = null;\n\n const turn: Turn = {\n id: crypto.randomUUID(),\n conversationId: this.conversation.id,\n participantId: userId,\n participantName: participant.aliases[0] ?? userId,\n text: trimmed,\n sequence: this.turnSequence++,\n startedAt,\n endedAt: Date.now(),\n };\n\n // Track synchronously so `whenIdle()` can observe in-flight work.\n this.pendingTurns++;\n void this.processTurn(turn);\n }\n\n private async processTurn(turn: Turn): Promise<void> {\n try {\n await this.conversation.pushTurn(turn);\n await this.conversation.pushTranscript({\n speaker: turn.participantName,\n speakerKind: \"participant\",\n text: turn.text,\n });\n this.history.addUserTurn(turn, this.conversation);\n if (this.agents.length === 0) return;\n\n if (this.coordination.hasPending()) {\n // The user answered a pending coordination question: resume it with\n // this turn instead of starting a fresh generation.\n this.enqueueCoordinationRun(() => this.coordination.resume(turn));\n return;\n }\n\n if (this.agents.length === 1 || findAddressedAgent(this.agents, turn.text)) {\n // Single-agent conversations, and turns that explicitly address an\n // agent by name/alias, route straight to that agent.\n this.queueGeneration(turn);\n return;\n }\n\n // Otherwise the built-in `understand` coordination decides: delegate to\n // agents, ask the user, or answer directly.\n this.enqueueCoordinationRun(() => this.coordination.runDefault(turn));\n } finally {\n this.pendingTurns--;\n }\n }\n\n private onInterrupt(): void {\n this.epoch++;\n this.coordination.cancel();\n this.stopLlms();\n this.speech.stop();\n this.tools.cancelAll(\"interrupted\");\n }\n\n /** Cancel every LLM in play: the shared one and each agent's own. */\n private stopLlms(): void {\n const seen = new Set<LLM>();\n if (this.llm) seen.add(this.llm);\n for (const agent of this.agents) {\n if (agent.llm) seen.add(agent.llm);\n }\n for (const llm of seen) llm.stop();\n }\n\n private onProviderError(error: Error): void {\n this.conversation.emit(\"error\", { conversationId: this.conversation.id, error });\n }\n\n // -------------------------------------------------------------------------\n // Generation\n // -------------------------------------------------------------------------\n\n private queueGeneration(turn: Turn): void {\n this.pendingGenerations++;\n const epoch = this.epoch;\n this.generationChain = this.generationChain.then(async () => {\n if (this.epoch !== epoch) {\n // Superseded by an interruption while queued.\n this.pendingGenerations--;\n return;\n }\n try {\n await this.generate(turn);\n } finally {\n this.pendingGenerations--;\n }\n });\n }\n\n private async generate(turn: Turn): Promise<void> {\n if (this.agents.length === 0) return;\n // The coordinator routes this turn to an agent by name/alias, falling\n // back to the first agent in the roster.\n const agent = pickAgent(this.agents, turn.text);\n if (!agent) return;\n // Prefer the routed agent's own LLM so agents with different providers\n // keep their intelligence; fall back to the shared LLM.\n const llm = agent.llm ?? this.llm;\n if (!llm) return;\n this.generating = true;\n try {\n await this.runGeneration(this.epoch, turn, agent, llm);\n } finally {\n this.generating = false;\n }\n }\n\n private async runGeneration(\n epoch: number,\n turn: Turn,\n agent: Agent,\n llm: LLM,\n ): Promise<void> {\n const generationId = crypto.randomUUID();\n await this.conversation.pushGeneration({\n id: generationId,\n conversationId: this.conversation.id,\n agentName: agent.name,\n text: \"\",\n status: \"streaming\",\n startedAt: Date.now(),\n });\n\n const definitions: LLMToolDefinition[] = agent.tools.map((tool) => ({\n name: tool.name,\n description: tool.description,\n parameters: tool.parameters ?? { type: \"object\", properties: {} },\n }));\n\n const messages: LLMMessage[] = [];\n if (agent.context) {\n messages.push({ role: \"system\", name: agent.name, content: agent.context });\n }\n messages.push(...this.history.windowed(this.historyWindow));\n\n // Run the generation, retrying transient provider failures that happen\n // before anything was produced (e.g. the Bedrock-via-OpenRouter mid-stream\n // abort nova models show as \"provider aborted the stream\"). Retrying is\n // safe only when nothing was streamed and no tool ran \u2014 otherwise the\n // user would hear the partial reply twice or a tool would re-execute.\n let outcome: Awaited<ReturnType<GenerationRunner[\"run\"]>>;\n for (let attempt = 0; ; attempt++) {\n outcome = await this.generation.run({\n agentName: agent.name,\n llm,\n messages,\n tools: definitions,\n temperature: this.temperature,\n maxTokens: this.maxTokens,\n maxToolIterations: this.maxToolIterations,\n isCurrent: () => this.epoch === epoch,\n // With one agent every assistant turn is the same speaker, so the\n // per-agent `name` fields are redundant \u2014 and some providers render\n // them as role headers (\"Scout:\") that weaker models imitate by\n // prefixing every reply with the agent's name.\n agentNames: this.agents.length > 1,\n onDelta: (delta) => this.speech.feed(delta, epoch),\n resolveToolCalls: (calls) => this.tools.resolveCalls(calls),\n });\n if (this.epoch !== epoch) return;\n const retryable =\n attempt < MAX_TRANSIENT_GENERATION_RETRIES &&\n outcome.status === \"error\" &&\n outcome.text.length === 0 &&\n (outcome.toolRounds ?? 0) === 0 &&\n isTransientProviderError(outcome.error);\n if (!retryable) break;\n }\n\n // A stale run (interrupt/stop) is discarded entirely \u2014 the conversation\n // already marked the generation cancelled.\n if (this.epoch !== epoch) return;\n if (outcome.status === \"interrupted\") return;\n\n if (outcome.status === \"error\") {\n this.conversation.emit(\"error\", {\n conversationId: this.conversation.id,\n error: outcome.error instanceof Error ? outcome.error : new Error(String(outcome.error)),\n });\n // Finalize whatever was generated so the conversation state stays\n // consistent even though the provider failed.\n await this.conversation.completeGeneration(outcome.text);\n if (outcome.text) {\n await this.conversation.pushTranscript({\n speaker: agent.name,\n speakerKind: \"agent\",\n text: outcome.text,\n });\n this.history.addAssistant(agent.name, outcome.text);\n }\n return;\n }\n\n this.speech.flush(epoch);\n await this.speech.waitForIdle();\n if (this.epoch !== epoch) return;\n\n await this.conversation.completeGeneration(outcome.text);\n await this.conversation.pushTranscript({\n speaker: agent.name,\n speakerKind: \"agent\",\n text: outcome.text,\n });\n this.history.addAssistant(agent.name, outcome.text);\n }\n\n // -------------------------------------------------------------------------\n // Coordination (serialized with agent generations: one track at a time)\n // -------------------------------------------------------------------------\n\n private enqueueCoordinationRun(run: () => Promise<void>): void {\n this.pendingGenerations++;\n this.generationChain = this.generationChain.then(async () => {\n this.generating = true;\n try {\n await run();\n } finally {\n this.generating = false;\n this.pendingGenerations--;\n }\n });\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["orchestrator_exports", "__export", "Orchestrator", "__toCommonJS", "import_history", "import_routing", "import_generation", "import_speech", "import_tools", "import_coordination_runner", "MAX_TRANSIENT_GENERATION_RETRIES", "isTransientProviderError", "error", "__name", "Orchestrator", "options", "agents", "llm", "epoch", "toolRegistry", "agent", "tool", "turns", "generations", "payload", "userId", "text", "result", "unsubscribe", "sessions", "session", "entry", "trimmed", "participant", "startedAt", "turn", "seen", "generationId", "definitions", "messages", "outcome", "attempt", "delta", "calls", "run"]
|
|
7
7
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var o=Object.defineProperty;var
|
|
1
|
+
"use strict";var o=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var f=Object.getOwnPropertyNames;var l=Object.prototype.hasOwnProperty;var u=(s,t)=>o(s,"name",{value:t,configurable:!0});var d=(s,t)=>{for(var e in t)o(s,e,{get:t[e],enumerable:!0})},T=(s,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of f(t))!l.call(s,i)&&i!==e&&o(s,i,{get:()=>t[i],enumerable:!(n=m(t,i))||n.enumerable});return s};var g=s=>T(o({},"__esModule",{value:!0}),s);var x={};d(x,{SpeechPipeline:()=>C});module.exports=g(x);var p=require("../../text-chunker/text-chunker.js");class C{static{u(this,"SpeechPipeline")}tts;conversation;isCurrent;maxConcurrent;chunker=new p.TextChunker;speechEpoch=0;chain=Promise.resolve();audioSequence=0;queue=[];inFlight=0;constructor(t){this.tts=t.tts,this.conversation=t.conversation,this.isCurrent=t.isCurrent,this.maxConcurrent=Math.max(1,t.maxConcurrentRequests??2)}feed(t,e){if(t.length!==0){this.conversation.noteTiming("firstToken"),this.conversation.pushTextDelta(t);for(const n of this.chunker.push(t))this.speak(n,e)}}flush(t){const e=this.chunker.flush();e&&this.speak(e,t)}speak(t,e){if(!this.tts)return;this.conversation.noteTiming("firstTtsText");const i=this.speechEpoch;this.queue.push({sentence:t,epoch:e,speechEpoch:i}),this.startNext()}startNext(){const t=this.tts;for(;t&&this.inFlight<this.maxConcurrent&&this.queue.length>0;){const{sentence:e,epoch:n,speechEpoch:i}=this.queue.shift();this.inFlight++;const h=t.stream({text:e}),a=h.next();a.catch(()=>{}),this.chain=this.chain.then(async()=>{try{if(!this.isCurrent(n)||this.speechEpoch!==i)return;this.conversation.noteTiming("firstTtsRequest");let r=!0;const c=await a;c.done||(r=!1,this.conversation.noteTiming("firstTtsAudio"),this.conversation.pushAudio({data:c.value,timestamp:Date.now(),sequence:this.audioSequence++}));for await(const v of h){if(!this.isCurrent(n)||this.speechEpoch!==i)break;r&&(r=!1,this.conversation.noteTiming("firstTtsAudio")),this.conversation.pushAudio({data:v,timestamp:Date.now(),sequence:this.audioSequence++})}}catch(r){if(!this.isCurrent(n)||this.speechEpoch!==i)return;this.conversation.emit("error",{conversationId:this.conversation.id,error:r instanceof Error?r:new Error(String(r))})}finally{this.inFlight--,this.startNext()}})}}stop(){this.speechEpoch++,this.chunker.clear(),this.queue.length=0,this.tts?.stop()}async waitForIdle(){for(;;){if(this.queue.length===0&&this.inFlight===0)return;await this.chain}}}
|
|
2
2
|
//# sourceMappingURL=speech.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/conversations/orchestration/orchestrator/speech/speech.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Conversation } from \"../../../conversation/conversation\";\nimport type { TTS } from \"../../../../providers/tts/types\";\nimport { TextChunker } from \"../../text-chunker/text-chunker\";\n\nexport interface SpeechPipelineOptions {\n /** TTS provider; omitted for text-only conversations. */\n tts?: TTS;\n conversation: Conversation;\n /**\n * True while the generation that produced the speech is still current.\n * The captured generation epoch is passed back in, so an interrupt (epoch\n * bump) or a stop drops queued and in-flight synthesis.\n */\n isCurrent(epoch: number): boolean;\n}\n\n/**\n * Turns the LLM delta stream into speakable audio: expose the text stream\n * semantically (`text-delta`), buffer sentences through the `TextChunker`,\n * and synthesize each chunk through the TTS provider on a serial chain.\n *\n * Owns the chunker, the speech epoch (bumped by `stop()`), the synthesis\n * chain, and the audio sequence \u2014 the orchestrator only decides *when*\n * speech is current.\n */\nexport class SpeechPipeline {\n private readonly tts: TTS | undefined;\n private readonly conversation: Conversation;\n private readonly isCurrent: (epoch: number) => boolean;\n private readonly chunker = new TextChunker();\n private speechEpoch = 0;\n private chain: Promise<void> = Promise.resolve();\n private audioSequence = 0;\n\n constructor(options: SpeechPipelineOptions) {\n this.tts = options.tts;\n this.conversation = options.conversation;\n this.isCurrent = options.isCurrent;\n }\n\n /**\n * Stream an LLM delta to the application and the TTS pipeline. Empty\n * deltas (reasoning-only frames) are skipped so they never reach the\n * text-delta event or the chunker.\n */\n feed(delta: string, epoch: number): void {\n if (delta.length === 0) return;\n // First-token latency for the in-flight generation.\n this.conversation.noteTiming(\"firstToken\");\n // Expose the text stream semantically: applications can render or act on\n // partial replies without waiting for the generation to complete.\n this.conversation.pushTextDelta(delta);\n // The chunker turns the token stream into speakable chunks: strong\n // sentence boundaries flush immediately, long clauses flush at soft\n // boundaries, and nothing waits indefinitely for punctuation.\n for (const chunk of this.chunker.push(delta)) {\n this.speak(chunk, epoch);\n }\n }\n\n /** Speak any remaining buffered text as a final chunk. */\n flush(epoch: number): void {\n const rest = this.chunker.flush();\n if (rest) this.speak(rest, epoch);\n }\n\n
|
|
5
|
-
"mappings": "4dAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,oBAAAE,IAAA,eAAAC,EAAAH,GAEA,IAAAI,EAA4B,
|
|
6
|
-
"names": ["speech_exports", "__export", "SpeechPipeline", "__toCommonJS", "import_text_chunker", "SpeechPipeline", "__name", "options", "delta", "epoch", "chunk", "rest", "sentence", "tts", "
|
|
4
|
+
"sourcesContent": ["import type { Conversation } from \"../../../conversation/conversation\";\nimport type { TTS } from \"../../../../providers/tts/types\";\nimport { TextChunker } from \"../../text-chunker/text-chunker\";\n\nexport interface SpeechPipelineOptions {\n /** TTS provider; omitted for text-only conversations. */\n tts?: TTS;\n conversation: Conversation;\n /**\n * True while the generation that produced the speech is still current.\n * The captured generation epoch is passed back in, so an interrupt (epoch\n * bump) or a stop drops queued and in-flight synthesis.\n */\n isCurrent(epoch: number): boolean;\n /**\n * How many synthesis requests may be in flight at once. Sentences are\n * started as soon as a slot is free \u2014 the next one synthesizes while the\n * current one is still streaming \u2014 so playback never waits for the whole\n * reply, but a burst of many sentences can't flood the provider (free TTS\n * variants rate-limit concurrent requests). Default 2.\n */\n maxConcurrentRequests?: number;\n}\n\n/**\n * Turns the LLM delta stream into speakable audio: expose the text stream\n * semantically (`text-delta`), buffer sentences through the `TextChunker`,\n * and synthesize each chunk through the TTS provider on a serial chain.\n *\n * Owns the chunker, the speech epoch (bumped by `stop()`), the synthesis\n * chain, and the audio sequence \u2014 the orchestrator only decides *when*\n * speech is current.\n */\nexport class SpeechPipeline {\n private readonly tts: TTS | undefined;\n private readonly conversation: Conversation;\n private readonly isCurrent: (epoch: number) => boolean;\n private readonly maxConcurrent: number;\n private readonly chunker = new TextChunker();\n private speechEpoch = 0;\n private chain: Promise<void> = Promise.resolve();\n private audioSequence = 0;\n /** Flushed sentences still waiting for a synthesis slot. */\n private readonly queue: {\n sentence: string;\n epoch: number;\n speechEpoch: number;\n }[] = [];\n /** Synthesis requests started but not yet fully delivered. */\n private inFlight = 0;\n\n constructor(options: SpeechPipelineOptions) {\n this.tts = options.tts;\n this.conversation = options.conversation;\n this.isCurrent = options.isCurrent;\n this.maxConcurrent = Math.max(1, options.maxConcurrentRequests ?? 2);\n }\n\n /**\n * Stream an LLM delta to the application and the TTS pipeline. Empty\n * deltas (reasoning-only frames) are skipped so they never reach the\n * text-delta event or the chunker.\n */\n feed(delta: string, epoch: number): void {\n if (delta.length === 0) return;\n // First-token latency for the in-flight generation.\n this.conversation.noteTiming(\"firstToken\");\n // Expose the text stream semantically: applications can render or act on\n // partial replies without waiting for the generation to complete.\n this.conversation.pushTextDelta(delta);\n // The chunker turns the token stream into speakable chunks: strong\n // sentence boundaries flush immediately, long clauses flush at soft\n // boundaries, and nothing waits indefinitely for punctuation.\n for (const chunk of this.chunker.push(delta)) {\n this.speak(chunk, epoch);\n }\n }\n\n /** Speak any remaining buffered text as a final chunk. */\n flush(epoch: number): void {\n const rest = this.chunker.flush();\n if (rest) this.speak(rest, epoch);\n }\n\n /**\n * Queue a sentence for synthesis, starting it if a slot is free. The text\n * order is preserved end to end: requests start in flush order, and delivery\n * below is serialized through `chain`, so sentence N never overtakes N-1.\n */\n speak(sentence: string, epoch: number): void {\n const tts = this.tts;\n if (!tts) return;\n // Buffering boundary: the first sentence flushed to TTS.\n this.conversation.noteTiming(\"firstTtsText\");\n const speechEpoch = this.speechEpoch;\n this.queue.push({ sentence, epoch, speechEpoch });\n this.startNext();\n }\n\n /**\n * Start synthesis for queued sentences up to the concurrency bound. A\n * sentence's request is fired as soon as a slot is free \u2014 while the previous\n * sentence is still streaming/playing \u2014 so the first piece is ready first\n * and later pieces overlap it, without ever flooding the provider with one\n * request per sentence of a long reply.\n */\n private startNext(): void {\n const tts = this.tts;\n while (tts && this.inFlight < this.maxConcurrent && this.queue.length > 0) {\n const { sentence, epoch, speechEpoch } = this.queue.shift()!;\n this.inFlight++;\n // Begin synthesis immediately \u2014 the provider request overlaps the\n // previous sentence's playback. Consuming the first chunk eagerly is\n // what starts the request; delivery still happens in order through the\n // serial chain below.\n const stream = tts.stream({ text: sentence });\n const firstChunk = stream.next();\n void firstChunk.catch(() => {});\n this.chain = this.chain.then(async () => {\n try {\n if (!this.isCurrent(epoch) || this.speechEpoch !== speechEpoch) return;\n // The TTS provider was asked to synthesize.\n this.conversation.noteTiming(\"firstTtsRequest\");\n let first = true;\n const head = await firstChunk;\n if (!head.done) {\n first = false;\n // The provider produced its first audio chunk.\n this.conversation.noteTiming(\"firstTtsAudio\");\n this.conversation.pushAudio({\n data: head.value,\n timestamp: Date.now(),\n sequence: this.audioSequence++,\n });\n }\n // The generator's async iterator is the generator itself, so this\n // continues from where the eager `next()` left off.\n for await (const chunk of stream) {\n if (!this.isCurrent(epoch) || this.speechEpoch !== speechEpoch) {\n break;\n }\n if (first) {\n first = false;\n // The provider produced its first audio chunk.\n this.conversation.noteTiming(\"firstTtsAudio\");\n }\n this.conversation.pushAudio({\n data: chunk,\n timestamp: Date.now(),\n sequence: this.audioSequence++,\n });\n }\n } catch (error) {\n // A stop (speech epoch bump) or an interrupt (generation epoch bump)\n // aborts the stream \u2014 that's not a provider failure. Anything else\n // reaches the application as an error.\n if (!this.isCurrent(epoch) || this.speechEpoch !== speechEpoch) return;\n this.conversation.emit(\"error\", {\n conversationId: this.conversation.id,\n error: error instanceof Error ? error : new Error(String(error)),\n });\n } finally {\n // The request is fully read and delivered: free the slot for the\n // next queued sentence.\n this.inFlight--;\n this.startNext();\n }\n });\n }\n }\n\n /**\n * Stop the current TTS playback and drop buffered text without cancelling\n * the generation (barge-in, interrupt, stop).\n */\n stop(): void {\n this.speechEpoch++;\n this.chunker.clear();\n this.queue.length = 0;\n this.tts?.stop();\n }\n\n /**\n * Resolve when every queued and in-flight sentence has been synthesized and\n * delivered. Unlike the delivery chain alone, this also waits for sentences\n * still waiting on a synthesis slot.\n */\n async waitForIdle(): Promise<void> {\n for (;;) {\n if (this.queue.length === 0 && this.inFlight === 0) return;\n await this.chain;\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["speech_exports", "__export", "SpeechPipeline", "__toCommonJS", "import_text_chunker", "SpeechPipeline", "__name", "options", "delta", "epoch", "chunk", "rest", "sentence", "speechEpoch", "tts", "stream", "firstChunk", "first", "head", "error"]
|
|
7
7
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var s=Object.defineProperty;var u=Object.getOwnPropertyDescriptor;var d=Object.getOwnPropertyNames;var v=Object.prototype.hasOwnProperty;var a=(n,e)=>s(n,"name",{value:e,configurable:!0});var m=(n,e)=>{for(var t in e)s(n,t,{get:e[t],enumerable:!0})},c=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of d(e))!v.call(n,o)&&o!==t&&s(n,o,{get:()=>e[o],enumerable:!(r=u(e,o))||r.enumerable});return n};var p=n=>c(s({},"__esModule",{value:!0}),n);var h={};m(h,{ToolCallManager:()=>g});module.exports=p(h);class g{static{a(this,"ToolCallManager")}conversation;timeoutMs;tools;autoExecute;waiters=new Map;constructor(e,t,r={}){this.conversation=e,this.timeoutMs=t,this.tools=r.tools,this.autoExecute=r.autoExecute??!0}async resolveCalls(e){const t=e.map(o=>this.wait(o.id));for(const o of e){const i=this.parseArguments(o.arguments);this.conversation.requestToolCall({id:o.id,name:o.name,arguments:i}),this.autoExecute&&this.runToolAndResolve(o,i)}return(await Promise.all(t)).map((o,i)=>{const l=e[i];return{id:o.id,name:l.name,result:"result"in o?o.result:void 0,error:"error"in o?o.error:void 0}})}handleResult(e){const t=this.waiters.get(e.id);t&&(this.waiters.delete(e.id),t(e))}cancelAll(e){for(const[t,r]of[...this.waiters])this.waiters.delete(t),r({id:t,error:e})}async runToolAndResolve(e,t){if(this.isPending(e.id))try{const r=this.tools?.get(e.name);if(!r){this.resolveIfPending({id:e.id,error:`Unknown tool "${e.name}"`});return}const o=await r.execute(t);this.resolveIfPending({id:e.id,result:o})}catch(r){this.resolveIfPending({id:e.id,error:r instanceof Error?r.message:String(r)})}}resolveIfPending(e){this.isPending(e.id)&&this.conversation.resolveToolCall(e)}isPending(e){return this.conversation.pendingToolCalls.some(t=>t.id===e)}parseArguments(e){try{return JSON.parse(e)}catch{return e}}wait(e){return new Promise(t=>{const r=setTimeout(()=>{this.waiters.delete(e),t({id:e,error:`Tool call "${e}" timed out`})},this.timeoutMs);this.waiters.set(e,o=>{clearTimeout(r),t(o)})})}}
|
|
2
2
|
//# sourceMappingURL=tools.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/conversations/orchestration/orchestrator/tools/tools.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Conversation } from \"../../../conversation/conversation\";\nimport type { LLMToolCall } from \"../../../../providers/llm/types\";\nimport type { ToolCallResult } from \"../../../types\";\n\nexport interface ResolvedToolCall {\n id: string;\n name: string;\n result?: unknown;\n error?: string;\n}\n\n/**\n *
|
|
5
|
-
"mappings": "4dAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,qBAAAE,IAAA,eAAAC,EAAAH,
|
|
6
|
-
"names": ["tools_exports", "__export", "ToolCallManager", "__toCommonJS", "ToolCallManager", "__name", "conversation", "timeoutMs", "calls", "resolutions", "call", "args", "result", "index", "resolve", "reason", "id", "timeout"]
|
|
4
|
+
"sourcesContent": ["import type { Conversation } from \"../../../conversation/conversation\";\nimport type { LLMToolCall } from \"../../../../providers/llm/types\";\nimport type { ToolCallResult } from \"../../../types\";\nimport type { Tool } from \"../../../../agents/tools/tools\";\n\nexport interface ResolvedToolCall {\n id: string;\n name: string;\n result?: unknown;\n error?: string;\n}\n\nexport interface ToolCallManagerOptions {\n /**\n * The tools the framework can execute on the agent's behalf, keyed by\n * name. Built from the orchestrated agents' tool registries.\n */\n tools?: ReadonlyMap<string, Tool<never, unknown>>;\n /**\n * Execute tool calls automatically (default `true`), feeding each tool's\n * result \u2014 or a caught error \u2014 back into the model loop. Set `false` to\n * keep the application-managed contract: listen for `tool-call`, execute\n * the tool in your own backend, and report back with\n * `resolveToolCall()`. `tool-call` events are still emitted for\n * visibility in either mode.\n */\n autoExecute?: boolean;\n}\n\n/**\n * Resolves the tool calls a generation issues. By default it runs the\n * matching tool itself (the conversation agents' tools auto-execute, exactly\n * like `Agent.run()`); with `autoExecute: false` it hands the calls to the\n * application and resolves them once the application reports back (or a\n * timeout fires). Stale results \u2014 timed out, already resolved, or superseded\n * by an interrupt \u2014 are dropped.\n */\nexport class ToolCallManager {\n private readonly conversation: Conversation;\n private readonly timeoutMs: number;\n private readonly tools: ReadonlyMap<string, Tool<never, unknown>> | undefined;\n private readonly autoExecute: boolean;\n private readonly waiters = new Map<string, (result: ToolCallResult) => void>();\n\n constructor(\n conversation: Conversation,\n timeoutMs: number,\n options: ToolCallManagerOptions = {},\n ) {\n this.conversation = conversation;\n this.timeoutMs = timeoutMs;\n this.tools = options.tools;\n this.autoExecute = options.autoExecute ?? true;\n }\n\n /**\n * Resolve a batch of tool calls. In auto-execute mode each call's tool\n * runs on the agent's behalf (errors caught and returned to the model);\n * otherwise the application resolves them. Either way the calls are\n * emitted as `tool-call` events and resolved in call order, with either a\n * result or an error string.\n */\n async resolveCalls(calls: LLMToolCall[]): Promise<ResolvedToolCall[]> {\n const resolutions = calls.map((call) => this.wait(call.id));\n for (const call of calls) {\n const args = this.parseArguments(call.arguments);\n this.conversation.requestToolCall({\n id: call.id,\n name: call.name,\n arguments: args,\n });\n if (this.autoExecute) {\n // Run the tool on the agent's behalf. The waiter above still bounds\n // the round trip if the tool hangs.\n void this.runToolAndResolve(call, args);\n }\n }\n\n const results = await Promise.all(resolutions);\n return results.map((result, index) => {\n const call = calls[index]!;\n return {\n id: result.id,\n name: call.name,\n result: \"result\" in result ? result.result : undefined,\n error: \"error\" in result ? result.error : undefined,\n };\n });\n }\n\n /** Resolve a waiter from a `tool-call-result` event; stale results drop. */\n handleResult(result: ToolCallResult): void {\n const resolve = this.waiters.get(result.id);\n if (!resolve) return;\n this.waiters.delete(result.id);\n resolve(result);\n }\n\n /** Force-resolve every pending call (interrupt, stop). */\n cancelAll(reason: string): void {\n for (const [id, resolve] of [...this.waiters]) {\n this.waiters.delete(id);\n resolve({ id, error: reason });\n }\n }\n\n /**\n * Execute one tool call and resolve it with the tool's result (or a\n * caught error). If the application already resolved the call in its\n * `tool-call` handler \u2014 the app-managed path \u2014 the tool is not run twice.\n */\n private async runToolAndResolve(call: LLMToolCall, args: unknown): Promise<void> {\n if (!this.isPending(call.id)) return;\n try {\n const tool = this.tools?.get(call.name);\n if (!tool) {\n this.resolveIfPending({\n id: call.id,\n error: `Unknown tool \"${call.name}\"`,\n });\n return;\n }\n const result = await tool.execute(args as never);\n this.resolveIfPending({ id: call.id, result });\n } catch (error) {\n this.resolveIfPending({\n id: call.id,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n /** Resolve through the conversation only while the call is still pending. */\n private resolveIfPending(result: ToolCallResult): void {\n if (!this.isPending(result.id)) return;\n this.conversation.resolveToolCall(result);\n }\n\n private isPending(id: string): boolean {\n return this.conversation.pendingToolCalls.some((call) => call.id === id);\n }\n\n private parseArguments(argumentsJson: string): unknown {\n try {\n return JSON.parse(argumentsJson);\n } catch {\n return argumentsJson;\n }\n }\n\n private wait(id: string): Promise<ToolCallResult> {\n return new Promise((resolve) => {\n const timeout = setTimeout(() => {\n this.waiters.delete(id);\n resolve({ id, error: `Tool call \"${id}\" timed out` });\n }, this.timeoutMs);\n this.waiters.set(id, (result) => {\n clearTimeout(timeout);\n resolve(result);\n });\n });\n }\n}\n"],
|
|
5
|
+
"mappings": "4dAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,qBAAAE,IAAA,eAAAC,EAAAH,GAqCO,MAAMI,CAAgB,CArC7B,MAqC6B,CAAAC,EAAA,wBACV,aACA,UACA,MACA,YACA,QAAU,IAAI,IAE/B,YACEC,EACAC,EACAC,EAAkC,CAAC,EACnC,CACA,KAAK,aAAeF,EACpB,KAAK,UAAYC,EACjB,KAAK,MAAQC,EAAQ,MACrB,KAAK,YAAcA,EAAQ,aAAe,EAC5C,CASA,MAAM,aAAaC,EAAmD,CACpE,MAAMC,EAAcD,EAAM,IAAKE,GAAS,KAAK,KAAKA,EAAK,EAAE,CAAC,EAC1D,UAAWA,KAAQF,EAAO,CACxB,MAAMG,EAAO,KAAK,eAAeD,EAAK,SAAS,EAC/C,KAAK,aAAa,gBAAgB,CAChC,GAAIA,EAAK,GACT,KAAMA,EAAK,KACX,UAAWC,CACb,CAAC,EACG,KAAK,aAGF,KAAK,kBAAkBD,EAAMC,CAAI,CAE1C,CAGA,OADgB,MAAM,QAAQ,IAAIF,CAAW,GAC9B,IAAI,CAACG,EAAQC,IAAU,CACpC,MAAMH,EAAOF,EAAMK,CAAK,EACxB,MAAO,CACL,GAAID,EAAO,GACX,KAAMF,EAAK,KACX,OAAQ,WAAYE,EAASA,EAAO,OAAS,OAC7C,MAAO,UAAWA,EAASA,EAAO,MAAQ,MAC5C,CACF,CAAC,CACH,CAGA,aAAaA,EAA8B,CACzC,MAAME,EAAU,KAAK,QAAQ,IAAIF,EAAO,EAAE,EACrCE,IACL,KAAK,QAAQ,OAAOF,EAAO,EAAE,EAC7BE,EAAQF,CAAM,EAChB,CAGA,UAAUG,EAAsB,CAC9B,SAAW,CAACC,EAAIF,CAAO,GAAK,CAAC,GAAG,KAAK,OAAO,EAC1C,KAAK,QAAQ,OAAOE,CAAE,EACtBF,EAAQ,CAAE,GAAAE,EAAI,MAAOD,CAAO,CAAC,CAEjC,CAOA,MAAc,kBAAkBL,EAAmBC,EAA8B,CAC/E,GAAK,KAAK,UAAUD,EAAK,EAAE,EAC3B,GAAI,CACF,MAAMO,EAAO,KAAK,OAAO,IAAIP,EAAK,IAAI,EACtC,GAAI,CAACO,EAAM,CACT,KAAK,iBAAiB,CACpB,GAAIP,EAAK,GACT,MAAO,iBAAiBA,EAAK,IAAI,GACnC,CAAC,EACD,MACF,CACA,MAAME,EAAS,MAAMK,EAAK,QAAQN,CAAa,EAC/C,KAAK,iBAAiB,CAAE,GAAID,EAAK,GAAI,OAAAE,CAAO,CAAC,CAC/C,OAASM,EAAO,CACd,KAAK,iBAAiB,CACpB,GAAIR,EAAK,GACT,MAAOQ,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,CAC9D,CAAC,CACH,CACF,CAGQ,iBAAiBN,EAA8B,CAChD,KAAK,UAAUA,EAAO,EAAE,GAC7B,KAAK,aAAa,gBAAgBA,CAAM,CAC1C,CAEQ,UAAUI,EAAqB,CACrC,OAAO,KAAK,aAAa,iBAAiB,KAAMN,GAASA,EAAK,KAAOM,CAAE,CACzE,CAEQ,eAAeG,EAAgC,CACrD,GAAI,CACF,OAAO,KAAK,MAAMA,CAAa,CACjC,MAAQ,CACN,OAAOA,CACT,CACF,CAEQ,KAAKH,EAAqC,CAChD,OAAO,IAAI,QAASF,GAAY,CAC9B,MAAMM,EAAU,WAAW,IAAM,CAC/B,KAAK,QAAQ,OAAOJ,CAAE,EACtBF,EAAQ,CAAE,GAAAE,EAAI,MAAO,cAAcA,CAAE,aAAc,CAAC,CACtD,EAAG,KAAK,SAAS,EACjB,KAAK,QAAQ,IAAIA,EAAKJ,GAAW,CAC/B,aAAaQ,CAAO,EACpBN,EAAQF,CAAM,CAChB,CAAC,CACH,CAAC,CACH,CACF",
|
|
6
|
+
"names": ["tools_exports", "__export", "ToolCallManager", "__toCommonJS", "ToolCallManager", "__name", "conversation", "timeoutMs", "options", "calls", "resolutions", "call", "args", "result", "index", "resolve", "reason", "id", "tool", "error", "argumentsJson", "timeout"]
|
|
7
7
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var u=Object.defineProperty;var M=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var b=Object.prototype.hasOwnProperty;var i=(r,t)=>u(r,"name",{value:t,configurable:!0});var k=(r,t)=>{for(var e in t)u(r,e,{get:t[e],enumerable:!0})},A=(r,t,e,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of g(t))!b.call(r,s)&&s!==e&&u(r,s,{get:()=>t[s],enumerable:!(o=M(t,s))||o.enumerable});return r};var E=r=>A(u({},"__esModule",{value:!0}),r);var O={};k(O,{FakeLLM:()=>d,FakeSTT:()=>v,FakeSTTSession:()=>L,FakeTTS:()=>S,SlowTTS:()=>F,respond:()=>x,setup:()=>R,setupRoster:()=>H,speak:()=>q,waitFor:()=>C});module.exports=E(O);var p=require("../../agents/agent.js"),U=require("../../agents/tools/tools.js"),w=require("../conversation/conversation.js"),T=require("../../persistence/adapters/memory/memory.js"),h=require("./orchestrator/orchestrator.js");class d{constructor(t){this.script=t}script;static{i(this,"FakeLLM")}requests=[];stopCalls=[];controllers=new Set;async*stream(t){const e=new AbortController;this.controllers.add(e),this.requests.push(t);try{for await(const o of this.script(t,e.signal)){if(e.signal.aborted)throw new DOMException("The operation was aborted.","AbortError");yield o}}finally{this.controllers.delete(e)}}stop(){this.stopCalls.push(Date.now());for(const t of this.controllers)t.abort()}}class v{static{i(this,"FakeSTT")}sessions=[];startOptions=[];start(t={}){const e=new L;return this.sessions.push(e),this.startOptions.push(t),e}cancel(){}}class L{static{i(this,"FakeSTTSession")}written=[];ended=0;listeners={partial:new Set,final:new Set,error:new Set};write(t){this.written.push(t)}async end(){this.ended++}on(t,e){this.listeners[t].add(e)}emitPartial(t){for(const e of this.listeners.partial)e(t)}emitFinal(t){for(const e of this.listeners.final)e(t)}emitError(t){for(const e of this.listeners.error)e(t)}}class S{constructor(t){this.chunksFor=t}chunksFor;static{i(this,"FakeTTS")}requests=[];controllers=new Set;async*stream(t){this.requests.push(t);const e=new AbortController;this.controllers.add(e);try{for(const o of this.chunksFor(t.text)){if(e.signal.aborted)throw new DOMException("The operation was aborted.","AbortError");yield o}}finally{this.controllers.delete(e)}}stop(){for(const t of this.controllers)t.abort()}}class F{constructor(t,e=20){this.chunksFor=t;this.delayMs=e}chunksFor;delayMs;static{i(this,"SlowTTS")}requests=[];controllers=new Set;async*stream(t){this.requests.push(t);const e=new AbortController;this.controllers.add(e);try{for(const o of this.chunksFor(t.text)){if(await Bun.sleep(this.delayMs),e.signal.aborted)throw new DOMException("The operation was aborted.","AbortError");yield o}}finally{this.controllers.delete(e)}}stop(){for(const t of this.controllers)t.abort()}}function R(r){return(async()=>{const t=new T.MemoryPersistence,e=new w.Conversation({id:"conv-1",persistence:t}),o=r.llm??new d(r.script),s=new v,a=r.tts??new S(r.ttsChunks??(m=>[new TextEncoder().encode(m)])),c=new p.Agent({name:"Jarvis",context:r.context??"Be concise.",llm:o,tools:r.tools}),l=new h.Orchestrator({conversation:e,agents:[c],llm:o,stt:s,tts:a,persistence:t,toolTimeoutMs:r.toolTimeoutMs??3e4,historyWindow:r.historyWindow,autoExecuteTools:r.autoExecuteTools});return e.start(),await e.participate({userId:"alice",aliases:["al"]}),await l.start(),{conversation:e,orchestrator:l,llm:o,stt:s,tts:a,persistence:t}})()}i(R,"setup");async function C(r,t=2e3){const e=Date.now();for(;!r();){if(Date.now()-e>t)throw new Error("waitFor timed out");await Bun.sleep(2)}}i(C,"waitFor");async function q(r,t,e){r.conversation.listen({userId:t,audio:new Uint8Array([1])}),r.stt.sessions.at(-1).emitFinal(e),await r.orchestrator.whenIdle()}i(q,"speak");function x(r){return async function*(){yield{type:"delta",content:r},yield{type:"done"}}}i(x,"respond");function H(r){return(async()=>{const t=new T.MemoryPersistence,e=new w.Conversation({id:"conv-1",persistence:t}),o=new v,s=new S(n=>[new TextEncoder().encode(n)]),a=new d(r.coordinatorScript),c=new Map([["Jarvis",a]]),l=[new p.Agent({name:"Jarvis",context:"Be concise.",llm:a})];for(const[n,y]of Object.entries(r.scripts)){const f=new d(y);c.set(n,f),l.push(new p.Agent({name:n,context:`You are ${n}.`,llm:f,tools:r.tools?.[n]}))}if(l.length===1){const n=new d(x("ok"));c.set("Helper",n),l.push(new p.Agent({name:"Helper",context:"You help.",llm:n}))}for(const[n,y]of Object.entries(r.coordinations??{}))c.set(n,y.llm);const m=new h.Orchestrator({conversation:e,agents:l,llm:a,coordinations:r.coordinations,maxCoordinationSteps:r.maxCoordinationSteps,autoExecuteTools:r.autoExecuteTools,stt:o,tts:s,persistence:t});return e.start(),await e.participate({userId:"alice",aliases:["al"]}),await m.start(),{conversation:e,orchestrator:m,llm:a,stt:o,tts:s,persistence:t,llms:c}})()}i(H,"setupRoster");
|
|
2
2
|
//# sourceMappingURL=test-harness.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/conversations/orchestration/test-harness.ts"],
|
|
4
|
-
"sourcesContent": ["// Shared test doubles and harnesses for the orchestrator integration suites.\n// Import from here rather than re-implementing fakes per test file.\n\nimport { Agent } from \"../../agents/agent\";\nimport { Tool } from \"../../agents/tools/tools\";\nimport { Conversation } from \"../conversation/conversation\";\nimport { MemoryPersistence } from \"../../persistence/adapters/memory/memory\";\nimport { Orchestrator } from \"./orchestrator/orchestrator\";\nimport type { HistoryWindow } from \"./orchestrator/history/history\";\nimport type {\n LLM,\n LLMEvent,\n LLMRequest,\n} from \"../../providers/llm/types\";\nimport type { STT, STTOptions, STTSession } from \"../../providers/stt/types\";\nimport type { TTS, TTSRequest } from \"../../providers/tts/types\";\n\n// ---------------------------------------------------------------------------\n// Fake providers\n// ---------------------------------------------------------------------------\n\nexport type LLMScript = (request: LLMRequest, signal: AbortSignal) => AsyncIterable<LLMEvent>;\n\nexport class FakeLLM implements LLM {\n readonly requests: LLMRequest[] = [];\n readonly stopCalls: number[] = [];\n private readonly controllers = new Set<AbortController>();\n\n constructor(private readonly script: LLMScript) {}\n\n async *stream(request: LLMRequest): AsyncGenerator<LLMEvent> {\n const controller = new AbortController();\n this.controllers.add(controller);\n this.requests.push(request);\n try {\n for await (const event of this.script(request, controller.signal)) {\n if (controller.signal.aborted) {\n throw new DOMException(\"The operation was aborted.\", \"AbortError\");\n }\n yield event;\n }\n } finally {\n this.controllers.delete(controller);\n }\n }\n\n stop(): void {\n this.stopCalls.push(Date.now());\n for (const controller of this.controllers) controller.abort();\n }\n}\n\nexport class FakeSTT implements STT {\n readonly sessions: FakeSTTSession[] = [];\n readonly startOptions: STTOptions[] = [];\n\n start(options: STTOptions = {}): FakeSTTSession {\n const session = new FakeSTTSession();\n this.sessions.push(session);\n this.startOptions.push(options);\n return session;\n }\n\n cancel(): void {}\n}\n\nexport type FakeSessionEvent = \"partial\" | \"final\" | \"error\";\n\nexport class FakeSTTSession implements STTSession {\n readonly written: Uint8Array[] = [];\n ended = 0;\n private readonly listeners: Record<\n FakeSessionEvent,\n Set<(...args: any[]) => void>\n > = {\n partial: new Set(),\n final: new Set(),\n error: new Set(),\n };\n\n write(audio: Uint8Array): void {\n this.written.push(audio);\n }\n\n async end(): Promise<void> {\n this.ended++;\n }\n\n on(event: \"partial\", listener: (transcript: string) => void): void;\n on(event: \"final\", listener: (transcript: string) => void): void;\n on(event: \"error\", listener: (error: Error) => void): void;\n on(event: FakeSessionEvent, listener: (...args: any[]) => void): void {\n this.listeners[event].add(listener);\n }\n\n emitPartial(text: string): void {\n for (const listener of this.listeners.partial) listener(text);\n }\n\n emitFinal(text: string): void {\n for (const listener of this.listeners.final) listener(text);\n }\n\n emitError(error: Error): void {\n for (const listener of this.listeners.error) listener(error);\n }\n}\n\nexport class FakeTTS implements TTS {\n readonly requests: TTSRequest[] = [];\n private readonly controllers = new Set<AbortController>();\n\n constructor(private readonly chunksFor: (text: string) => Uint8Array[]) {}\n\n async *stream(request: TTSRequest): AsyncGenerator<Uint8Array> {\n this.requests.push(request);\n const controller = new AbortController();\n this.controllers.add(controller);\n try {\n for (const chunk of this.chunksFor(request.text)) {\n if (controller.signal.aborted) {\n throw new DOMException(\"The operation was aborted.\", \"AbortError\");\n }\n yield chunk;\n }\n } finally {\n this.controllers.delete(controller);\n }\n }\n\n stop(): void {\n for (const controller of this.controllers) controller.abort();\n }\n}\n\n/**\n * A TTS that streams one chunk at a time with a delay, so audio is still\n * mid-flight when an interrupt arrives.\n */\nexport class SlowTTS implements TTS {\n readonly requests: TTSRequest[] = [];\n private readonly controllers = new Set<AbortController>();\n\n constructor(\n private readonly chunksFor: (text: string) => Uint8Array[],\n private readonly delayMs = 20,\n ) {}\n\n async *stream(request: TTSRequest): AsyncGenerator<Uint8Array> {\n this.requests.push(request);\n const controller = new AbortController();\n this.controllers.add(controller);\n try {\n for (const chunk of this.chunksFor(request.text)) {\n await Bun.sleep(this.delayMs);\n if (controller.signal.aborted) {\n throw new DOMException(\"The operation was aborted.\", \"AbortError\");\n }\n yield chunk;\n }\n } finally {\n this.controllers.delete(controller);\n }\n }\n\n stop(): void {\n for (const controller of this.controllers) controller.abort();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Harness\n// ---------------------------------------------------------------------------\n\nexport type TtsHarness = FakeTTS | SlowTTS;\n\nexport interface Harness {\n conversation: Conversation;\n orchestrator: Orchestrator;\n llm: FakeLLM;\n stt: FakeSTT;\n tts: TtsHarness;\n persistence: MemoryPersistence;\n}\n\nexport function setup(options: {\n script: LLMScript;\n ttsChunks?: (text: string) => Uint8Array[];\n tts?: TtsHarness;\n tools?: Tool<never, unknown>[];\n toolTimeoutMs?: number;\n context?: string;\n /** Inject a real LLM (e.g. an adapter over a mocked fetch) instead of a scripted fake. */\n llm?: LLM;\n /** Override the conversation-history window (default 5 turns / 4k chars). */\n historyWindow?: HistoryWindow | false;\n}): Promise<Harness> {\n return (async () => {\n const persistence = new MemoryPersistence();\n const conversation = new Conversation({ id: \"conv-1\", persistence });\n const llm = options.llm ?? new FakeLLM(options.script);\n const stt = new FakeSTT();\n const tts =\n options.tts ??\n new FakeTTS(\n options.ttsChunks ?? ((text) => [new TextEncoder().encode(text)]),\n );\n const agent = new Agent({\n name: \"Jarvis\",\n context: options.context ?? \"Be concise.\",\n llm,\n tools: options.tools,\n });\n const orchestrator = new Orchestrator({\n conversation,\n agents: [agent],\n llm,\n stt,\n tts,\n persistence,\n toolTimeoutMs: options.toolTimeoutMs ?? 30_000,\n historyWindow: options.historyWindow,\n });\n\n conversation.start();\n await conversation.participate({ userId: \"alice\", aliases: [\"al\"] });\n await orchestrator.start();\n\n return {\n conversation,\n orchestrator,\n // When a real LLM is injected (the stall-recovery suite) it replaces\n // the fake; that suite never reads `.requests`, so the type stays the\n // fake's for the rest of the file.\n llm: llm as FakeLLM,\n stt,\n tts,\n persistence,\n };\n })();\n}\n\nexport async function waitFor(condition: () => boolean, timeoutMs = 2000): Promise<void> {\n const start = Date.now();\n while (!condition()) {\n if (Date.now() - start > timeoutMs) {\n throw new Error(\"waitFor timed out\");\n }\n await Bun.sleep(2);\n }\n}\n\nexport async function speak(harness: Harness, userId: string, text: string): Promise<void> {\n harness.conversation.listen({ userId, audio: new Uint8Array([1]) });\n harness.stt.sessions.at(-1)!.emitFinal(text);\n await harness.orchestrator.whenIdle();\n}\n\nexport function respond(text: string): LLMScript {\n return async function* () {\n yield { type: \"delta\", content: text };\n yield { type: \"done\" };\n };\n}\n\n// ---------------------------------------------------------------------------\n// Multi-agent roster harness (coordinator + named specialists)\n// ---------------------------------------------------------------------------\n\nexport type RosterHarness = Harness & { llms: Map<string, FakeLLM> };\n\nexport function setupRoster(options: {\n coordinatorScript: LLMScript;\n scripts: Record<string, LLMScript>;\n tools?: Record<string, Tool<never, unknown>[]>;\n coordinations?: Record<string, { prompt: string; llm: FakeLLM }>;\n maxCoordinationSteps?: number;\n}): Promise<RosterHarness> {\n return (async () => {\n const persistence = new MemoryPersistence();\n const conversation = new Conversation({ id: \"conv-1\", persistence });\n const stt = new FakeSTT();\n const tts = new FakeTTS((text) => [new TextEncoder().encode(text)]);\n\n const coordinatorLlm = new FakeLLM(options.coordinatorScript);\n const llms = new Map<string, FakeLLM>([[\"Jarvis\", coordinatorLlm]]);\n const agents: Agent[] = [\n new Agent({ name: \"Jarvis\", context: \"Be concise.\", llm: coordinatorLlm }),\n ];\n for (const [name, script] of Object.entries(options.scripts)) {\n const llm = new FakeLLM(script);\n llms.set(name, llm);\n agents.push(\n new Agent({\n name,\n context: `You are ${name}.`,\n llm,\n tools: options.tools?.[name],\n }),\n );\n }\n // The built-in `understand` coordination only activates with a roster of\n // more than one agent, so guarantee one for tests that only exercise the\n // coordination itself.\n if (agents.length === 1) {\n const helperLlm = new FakeLLM(respond(\"ok\"));\n llms.set(\"Helper\", helperLlm);\n agents.push(new Agent({ name: \"Helper\", context: \"You help.\", llm: helperLlm }));\n }\n for (const [name, coordination] of Object.entries(options.coordinations ?? {})) {\n llms.set(name, coordination.llm);\n }\n\n const orchestrator = new Orchestrator({\n conversation,\n agents,\n llm: coordinatorLlm,\n coordinations: options.coordinations,\n maxCoordinationSteps: options.maxCoordinationSteps,\n stt,\n tts,\n persistence,\n });\n\n conversation.start();\n await conversation.participate({ userId: \"alice\", aliases: [\"al\"] });\n await orchestrator.start();\n\n return { conversation, orchestrator, llm: coordinatorLlm, stt, tts, persistence, llms };\n })();\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["// Shared test doubles and harnesses for the orchestrator integration suites.\n// Import from here rather than re-implementing fakes per test file.\n\nimport { Agent } from \"../../agents/agent\";\nimport { Tool } from \"../../agents/tools/tools\";\nimport { Conversation } from \"../conversation/conversation\";\nimport { MemoryPersistence } from \"../../persistence/adapters/memory/memory\";\nimport { Orchestrator } from \"./orchestrator/orchestrator\";\nimport type { HistoryWindow } from \"./orchestrator/history/history\";\nimport type {\n LLM,\n LLMEvent,\n LLMRequest,\n} from \"../../providers/llm/types\";\nimport type { STT, STTOptions, STTSession } from \"../../providers/stt/types\";\nimport type { TTS, TTSRequest } from \"../../providers/tts/types\";\n\n// ---------------------------------------------------------------------------\n// Fake providers\n// ---------------------------------------------------------------------------\n\nexport type LLMScript = (request: LLMRequest, signal: AbortSignal) => AsyncIterable<LLMEvent>;\n\nexport class FakeLLM implements LLM {\n readonly requests: LLMRequest[] = [];\n readonly stopCalls: number[] = [];\n private readonly controllers = new Set<AbortController>();\n\n constructor(private readonly script: LLMScript) {}\n\n async *stream(request: LLMRequest): AsyncGenerator<LLMEvent> {\n const controller = new AbortController();\n this.controllers.add(controller);\n this.requests.push(request);\n try {\n for await (const event of this.script(request, controller.signal)) {\n if (controller.signal.aborted) {\n throw new DOMException(\"The operation was aborted.\", \"AbortError\");\n }\n yield event;\n }\n } finally {\n this.controllers.delete(controller);\n }\n }\n\n stop(): void {\n this.stopCalls.push(Date.now());\n for (const controller of this.controllers) controller.abort();\n }\n}\n\nexport class FakeSTT implements STT {\n readonly sessions: FakeSTTSession[] = [];\n readonly startOptions: STTOptions[] = [];\n\n start(options: STTOptions = {}): FakeSTTSession {\n const session = new FakeSTTSession();\n this.sessions.push(session);\n this.startOptions.push(options);\n return session;\n }\n\n cancel(): void {}\n}\n\nexport type FakeSessionEvent = \"partial\" | \"final\" | \"error\";\n\nexport class FakeSTTSession implements STTSession {\n readonly written: Uint8Array[] = [];\n ended = 0;\n private readonly listeners: Record<\n FakeSessionEvent,\n Set<(...args: any[]) => void>\n > = {\n partial: new Set(),\n final: new Set(),\n error: new Set(),\n };\n\n write(audio: Uint8Array): void {\n this.written.push(audio);\n }\n\n async end(): Promise<void> {\n this.ended++;\n }\n\n on(event: \"partial\", listener: (transcript: string) => void): void;\n on(event: \"final\", listener: (transcript: string) => void): void;\n on(event: \"error\", listener: (error: Error) => void): void;\n on(event: FakeSessionEvent, listener: (...args: any[]) => void): void {\n this.listeners[event].add(listener);\n }\n\n emitPartial(text: string): void {\n for (const listener of this.listeners.partial) listener(text);\n }\n\n emitFinal(text: string): void {\n for (const listener of this.listeners.final) listener(text);\n }\n\n emitError(error: Error): void {\n for (const listener of this.listeners.error) listener(error);\n }\n}\n\nexport class FakeTTS implements TTS {\n readonly requests: TTSRequest[] = [];\n private readonly controllers = new Set<AbortController>();\n\n constructor(private readonly chunksFor: (text: string) => Uint8Array[]) {}\n\n async *stream(request: TTSRequest): AsyncGenerator<Uint8Array> {\n this.requests.push(request);\n const controller = new AbortController();\n this.controllers.add(controller);\n try {\n for (const chunk of this.chunksFor(request.text)) {\n if (controller.signal.aborted) {\n throw new DOMException(\"The operation was aborted.\", \"AbortError\");\n }\n yield chunk;\n }\n } finally {\n this.controllers.delete(controller);\n }\n }\n\n stop(): void {\n for (const controller of this.controllers) controller.abort();\n }\n}\n\n/**\n * A TTS that streams one chunk at a time with a delay, so audio is still\n * mid-flight when an interrupt arrives.\n */\nexport class SlowTTS implements TTS {\n readonly requests: TTSRequest[] = [];\n private readonly controllers = new Set<AbortController>();\n\n constructor(\n private readonly chunksFor: (text: string) => Uint8Array[],\n private readonly delayMs = 20,\n ) {}\n\n async *stream(request: TTSRequest): AsyncGenerator<Uint8Array> {\n this.requests.push(request);\n const controller = new AbortController();\n this.controllers.add(controller);\n try {\n for (const chunk of this.chunksFor(request.text)) {\n await Bun.sleep(this.delayMs);\n if (controller.signal.aborted) {\n throw new DOMException(\"The operation was aborted.\", \"AbortError\");\n }\n yield chunk;\n }\n } finally {\n this.controllers.delete(controller);\n }\n }\n\n stop(): void {\n for (const controller of this.controllers) controller.abort();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Harness\n// ---------------------------------------------------------------------------\n\nexport type TtsHarness = FakeTTS | SlowTTS;\n\nexport interface Harness {\n conversation: Conversation;\n orchestrator: Orchestrator;\n llm: FakeLLM;\n stt: FakeSTT;\n tts: TtsHarness;\n persistence: MemoryPersistence;\n}\n\nexport function setup(options: {\n script: LLMScript;\n ttsChunks?: (text: string) => Uint8Array[];\n tts?: TtsHarness;\n tools?: Tool<never, unknown>[];\n toolTimeoutMs?: number;\n context?: string;\n /** Inject a real LLM (e.g. an adapter over a mocked fetch) instead of a scripted fake. */\n llm?: LLM;\n /** Override the conversation-history window (default 5 turns / 4k chars). */\n historyWindow?: HistoryWindow | false;\n /** Auto-execute the agent's tools (default true); see OrchestratorOptions. */\n autoExecuteTools?: boolean;\n}): Promise<Harness> {\n return (async () => {\n const persistence = new MemoryPersistence();\n const conversation = new Conversation({ id: \"conv-1\", persistence });\n const llm = options.llm ?? new FakeLLM(options.script);\n const stt = new FakeSTT();\n const tts =\n options.tts ??\n new FakeTTS(\n options.ttsChunks ?? ((text) => [new TextEncoder().encode(text)]),\n );\n const agent = new Agent({\n name: \"Jarvis\",\n context: options.context ?? \"Be concise.\",\n llm,\n tools: options.tools,\n });\n const orchestrator = new Orchestrator({\n conversation,\n agents: [agent],\n llm,\n stt,\n tts,\n persistence,\n toolTimeoutMs: options.toolTimeoutMs ?? 30_000,\n historyWindow: options.historyWindow,\n autoExecuteTools: options.autoExecuteTools,\n });\n\n conversation.start();\n await conversation.participate({ userId: \"alice\", aliases: [\"al\"] });\n await orchestrator.start();\n\n return {\n conversation,\n orchestrator,\n // When a real LLM is injected (the stall-recovery suite) it replaces\n // the fake; that suite never reads `.requests`, so the type stays the\n // fake's for the rest of the file.\n llm: llm as FakeLLM,\n stt,\n tts,\n persistence,\n };\n })();\n}\n\nexport async function waitFor(condition: () => boolean, timeoutMs = 2000): Promise<void> {\n const start = Date.now();\n while (!condition()) {\n if (Date.now() - start > timeoutMs) {\n throw new Error(\"waitFor timed out\");\n }\n await Bun.sleep(2);\n }\n}\n\nexport async function speak(harness: Harness, userId: string, text: string): Promise<void> {\n harness.conversation.listen({ userId, audio: new Uint8Array([1]) });\n harness.stt.sessions.at(-1)!.emitFinal(text);\n await harness.orchestrator.whenIdle();\n}\n\nexport function respond(text: string): LLMScript {\n return async function* () {\n yield { type: \"delta\", content: text };\n yield { type: \"done\" };\n };\n}\n\n// ---------------------------------------------------------------------------\n// Multi-agent roster harness (coordinator + named specialists)\n// ---------------------------------------------------------------------------\n\nexport type RosterHarness = Harness & { llms: Map<string, FakeLLM> };\n\nexport function setupRoster(options: {\n coordinatorScript: LLMScript;\n scripts: Record<string, LLMScript>;\n tools?: Record<string, Tool<never, unknown>[]>;\n coordinations?: Record<string, { prompt: string; llm: FakeLLM }>;\n maxCoordinationSteps?: number;\n /** Auto-execute the agents' tools (default true); see OrchestratorOptions. */\n autoExecuteTools?: boolean;\n}): Promise<RosterHarness> {\n return (async () => {\n const persistence = new MemoryPersistence();\n const conversation = new Conversation({ id: \"conv-1\", persistence });\n const stt = new FakeSTT();\n const tts = new FakeTTS((text) => [new TextEncoder().encode(text)]);\n\n const coordinatorLlm = new FakeLLM(options.coordinatorScript);\n const llms = new Map<string, FakeLLM>([[\"Jarvis\", coordinatorLlm]]);\n const agents: Agent[] = [\n new Agent({ name: \"Jarvis\", context: \"Be concise.\", llm: coordinatorLlm }),\n ];\n for (const [name, script] of Object.entries(options.scripts)) {\n const llm = new FakeLLM(script);\n llms.set(name, llm);\n agents.push(\n new Agent({\n name,\n context: `You are ${name}.`,\n llm,\n tools: options.tools?.[name],\n }),\n );\n }\n // The built-in `understand` coordination only activates with a roster of\n // more than one agent, so guarantee one for tests that only exercise the\n // coordination itself.\n if (agents.length === 1) {\n const helperLlm = new FakeLLM(respond(\"ok\"));\n llms.set(\"Helper\", helperLlm);\n agents.push(new Agent({ name: \"Helper\", context: \"You help.\", llm: helperLlm }));\n }\n for (const [name, coordination] of Object.entries(options.coordinations ?? {})) {\n llms.set(name, coordination.llm);\n }\n\n const orchestrator = new Orchestrator({\n conversation,\n agents,\n llm: coordinatorLlm,\n coordinations: options.coordinations,\n maxCoordinationSteps: options.maxCoordinationSteps,\n autoExecuteTools: options.autoExecuteTools,\n stt,\n tts,\n persistence,\n });\n\n conversation.start();\n await conversation.participate({ userId: \"alice\", aliases: [\"al\"] });\n await orchestrator.start();\n\n return { conversation, orchestrator, llm: coordinatorLlm, stt, tts, persistence, llms };\n })();\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["test_harness_exports", "__export", "FakeLLM", "FakeSTT", "FakeSTTSession", "FakeTTS", "SlowTTS", "respond", "setup", "setupRoster", "speak", "waitFor", "__toCommonJS", "import_agent", "import_tools", "import_conversation", "import_memory", "import_orchestrator", "FakeLLM", "script", "__name", "request", "controller", "event", "FakeSTT", "options", "session", "FakeSTTSession", "audio", "listener", "text", "error", "FakeTTS", "chunksFor", "chunk", "SlowTTS", "delayMs", "setup", "persistence", "conversation", "llm", "stt", "tts", "agent", "orchestrator", "waitFor", "condition", "timeoutMs", "start", "speak", "harness", "userId", "respond", "setupRoster", "coordinatorLlm", "llms", "agents", "name", "helperLlm", "coordination"]
|
|
7
7
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var o=Object.defineProperty;var c=Object.getOwnPropertyDescriptor;var a=Object.getOwnPropertyNames;var T=Object.prototype.hasOwnProperty;var i=(e,t)=>o(e,"name",{value:t,configurable:!0});var f=(e,t)=>{for(var s in t)o(e,s,{get:t[s],enumerable:!0})},d=(e,t,s,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of a(t))!T.call(e,r)&&r!==s&&o(e,r,{get:()=>t[r],enumerable:!(n=c(t,r))||n.enumerable});return e};var y=e=>d(o({},"__esModule",{value:!0}),e);var L={};f(L,{Pipeflow:()=>u});module.exports=y(L);var l=require("../agents/agent.js"),m=require("../conversations/conversations.js"),p=require("../persistence/adapters/memory/memory.js");class u{static{i(this,"Pipeflow")}llm;stt;tts;conversations;constructor(t={}){this.llm=t.llm,this.stt=t.stt,this.tts=t.tts;const s=t.persistence??new p.MemoryPersistence;this.conversations=new m.Conversations({persistence:s,stt:t.stt,tts:t.tts,autoExecuteTools:t.autoExecuteTools})}agent(t){return new l.Agent({...t,llm:t.llm??this.llm})}}
|
|
2
2
|
//# sourceMappingURL=pipeflow.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/pipeflow/pipeflow.ts"],
|
|
4
|
-
"sourcesContent": ["import { Agent, type AgentOptions } from \"../agents/agent\";\nimport { Conversations } from \"../conversations/conversations\";\nimport { MemoryPersistence } from \"../persistence/adapters/memory/memory\";\nimport type { Persistence } from \"../persistence/persistence\";\nimport type { LLM } from \"../providers/llm/types\";\nimport type { STT } from \"../providers/stt/types\";\nimport type { TTS } from \"../providers/tts/types\";\n\nexport interface PipeflowOptions {\n llm?: LLM;\n stt?: STT;\n tts?: TTS;\n persistence?: Persistence;\n}\n\n/**\n * The Pipeflow entry point.\n *\n * ```ts\n * const pipeflow = new Pipeflow({ llm });\n * const agent = pipeflow.agent({ name: \"Jarvis\", context: \"...\" });\n * const conversation = await pipeflow.conversations.create({ agents: [agent] });\n * ```\n */\nexport class Pipeflow {\n readonly llm: LLM | undefined;\n readonly stt: STT | undefined;\n readonly tts: TTS | undefined;\n readonly conversations: Conversations;\n\n constructor(options: PipeflowOptions = {}) {\n this.llm = options.llm;\n this.stt = options.stt;\n this.tts = options.tts;\n const persistence = options.persistence ?? new MemoryPersistence();\n this.conversations = new Conversations({\n persistence,\n stt: options.stt,\n tts: options.tts,\n });\n }\n\n /**\n * Create an agent. Agents inherit the Pipeflow instance's LLM provider so\n * `agent.run()` works out of the box.\n */\n agent(options: Omit<AgentOptions, \"llm\"> & { llm?: LLM }): Agent {\n return new Agent({ ...options, llm: options.llm ?? this.llm });\n }\n}\n"],
|
|
5
|
-
"mappings": "4dAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,cAAAE,IAAA,eAAAC,EAAAH,GAAA,IAAAI,EAAyC,2BACzCC,EAA8B,0CAC9BC,EAAkC,
|
|
4
|
+
"sourcesContent": ["import { Agent, type AgentOptions } from \"../agents/agent\";\nimport { Conversations } from \"../conversations/conversations\";\nimport { MemoryPersistence } from \"../persistence/adapters/memory/memory\";\nimport type { Persistence } from \"../persistence/persistence\";\nimport type { LLM } from \"../providers/llm/types\";\nimport type { STT } from \"../providers/stt/types\";\nimport type { TTS } from \"../providers/tts/types\";\n\nexport interface PipeflowOptions {\n llm?: LLM;\n stt?: STT;\n tts?: TTS;\n persistence?: Persistence;\n /**\n * Default for created conversations: auto-execute the agents' tools\n * (default `true`), feeding each tool's result back into the model loop.\n * Set `false` to resolve tool calls from your own backend via\n * `resolveToolCall()`. Override per conversation in\n * `pipeflow.conversations.create()`.\n */\n autoExecuteTools?: boolean;\n}\n\n/**\n * The Pipeflow entry point.\n *\n * ```ts\n * const pipeflow = new Pipeflow({ llm });\n * const agent = pipeflow.agent({ name: \"Jarvis\", context: \"...\" });\n * const conversation = await pipeflow.conversations.create({ agents: [agent] });\n * ```\n */\nexport class Pipeflow {\n readonly llm: LLM | undefined;\n readonly stt: STT | undefined;\n readonly tts: TTS | undefined;\n readonly conversations: Conversations;\n\n constructor(options: PipeflowOptions = {}) {\n this.llm = options.llm;\n this.stt = options.stt;\n this.tts = options.tts;\n const persistence = options.persistence ?? new MemoryPersistence();\n this.conversations = new Conversations({\n persistence,\n stt: options.stt,\n tts: options.tts,\n autoExecuteTools: options.autoExecuteTools,\n });\n }\n\n /**\n * Create an agent. Agents inherit the Pipeflow instance's LLM provider so\n * `agent.run()` works out of the box.\n */\n agent(options: Omit<AgentOptions, \"llm\"> & { llm?: LLM }): Agent {\n return new Agent({ ...options, llm: options.llm ?? this.llm });\n }\n}\n"],
|
|
5
|
+
"mappings": "4dAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,cAAAE,IAAA,eAAAC,EAAAH,GAAA,IAAAI,EAAyC,2BACzCC,EAA8B,0CAC9BC,EAAkC,iDA8B3B,MAAMC,CAAS,CAhCtB,MAgCsB,CAAAC,EAAA,iBACX,IACA,IACA,IACA,cAET,YAAYC,EAA2B,CAAC,EAAG,CACzC,KAAK,IAAMA,EAAQ,IACnB,KAAK,IAAMA,EAAQ,IACnB,KAAK,IAAMA,EAAQ,IACnB,MAAMC,EAAcD,EAAQ,aAAe,IAAI,oBAC/C,KAAK,cAAgB,IAAI,gBAAc,CACrC,YAAAC,EACA,IAAKD,EAAQ,IACb,IAAKA,EAAQ,IACb,iBAAkBA,EAAQ,gBAC5B,CAAC,CACH,CAMA,MAAMA,EAA2D,CAC/D,OAAO,IAAI,QAAM,CAAE,GAAGA,EAAS,IAAKA,EAAQ,KAAO,KAAK,GAAI,CAAC,CAC/D,CACF",
|
|
6
6
|
"names": ["pipeflow_exports", "__export", "Pipeflow", "__toCommonJS", "import_agent", "import_conversations", "import_memory", "Pipeflow", "__name", "options", "persistence"]
|
|
7
7
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`+JSON.stringify(
|
|
3
|
-
The JSON must be either {"answer": "..."} or {"calls": [{"name": "...", "arguments": {...}}]}.`}
|
|
1
|
+
"use strict";var M=Object.defineProperty;var W=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var P=Object.prototype.hasOwnProperty;var d=(o,n)=>M(o,"name",{value:n,configurable:!0});var Y=(o,n)=>{for(var e in n)M(o,e,{get:n[e],enumerable:!0})},B=(o,n,e,i)=>{if(n&&typeof n=="object"||typeof n=="function")for(let f of j(n))!P.call(o,f)&&f!==e&&M(o,f,{get:()=>n[f],enumerable:!(i=W(n,f))||i.enumerable});return o};var F=o=>B(M({},"__esModule",{value:!0}),o);var Z={};Y(Z,{openAICompatibleStream:()=>V});module.exports=F(Z);function U(o){return{type:"object",properties:{answer:{type:"string",description:"Your spoken reply when no tool call is needed."},calls:{type:"array",description:"The tool calls to execute, when the request needs tools.",items:{type:"object",properties:{name:{type:"string",enum:o.map(n=>n.name)},arguments:{description:"The arguments for the named tool, per its schema.",oneOf:o.map(n=>n.parameters??{type:"object"})}},required:["name","arguments"],additionalProperties:!1}}}}}d(U,"buildEnvelopeSchema");function z(o){return`When the user's request requires a tool, respond with ONLY valid JSON matching this schema, with no prose and no markdown fences:
|
|
2
|
+
`+JSON.stringify(U(o))+`
|
|
3
|
+
The JSON must be either {"answer": "..."} or {"calls": [{"name": "...", "arguments": {...}}]}. When no tool is needed, respond in plain conversational text with no JSON.`}d(z,"promptedEnvelopeInstruction");function G(o){const n=o.trim();return n.startsWith("{")||n.startsWith("[")||/^```(?:json)?/i.test(n)}d(G,"looksLikeJsonAttempt");function H(o){const n=o.replace(/```(?:json)?/gi,"").replace(/^[^{]*/,"").replace(/[^}]*$/,"");if(n.length===0)return null;const e=n.indexOf("{"),i=n.lastIndexOf("}");return e===-1||i===-1||i<=e?null:n.slice(e,i+1)}d(H,"extractEnvelope");function K(){const o="<thinking>",n="</thinking>";let e="",i=!1;const f=d((r,a)=>{const m=Math.min(r.length,a.length-1);for(let l=m;l>=1;l--)if(a.startsWith(r.slice(-l)))return l;return 0},"prefixTail");return{push(r){let a="",m="";for(e+=r;;){if(i){const c=e.indexOf(n);if(c!==-1){m+=e.slice(0,c),e=e.slice(c+n.length),i=!1;continue}const w=f(e,n);return m+=e.slice(0,e.length-w),e=e.slice(e.length-w),{text:a,reasoning:m}}const l=e.indexOf(o);if(l!==-1){a+=e.slice(0,l),e=e.slice(l+o.length),i=!0;continue}const g=f(e,o);return a+=e.slice(0,e.length-g),e=e.slice(e.length-g),{text:a,reasoning:m}}},flush(){let r="",a="";return i?a=e:e.length>0&&!o.startsWith(e)&&(r=e),e="",i=!1,{text:r,reasoning:a}}}}d(K,"inlineThinking");function Q(o){return`You have access to the following tools. When the user's request requires one, respond with ONLY the tool-call JSON envelope shown in the request \u2014 no prose, no markdown, no tool-call syntax. When no tool is needed, reply conversationally in plain text \u2014 no JSON at all.
|
|
4
4
|
|
|
5
5
|
Available tools:
|
|
6
|
-
`+
|
|
7
|
-
`)}
|
|
6
|
+
`+o.map(e=>`- ${e.name}: ${e.description}`).join(`
|
|
7
|
+
`)}d(Q,"buildToolContract");async function*V(o){const{baseUrl:n,apiKey:e,model:i,fetchImpl:f,request:r,signal:a,extraHeaders:m,extraBody:l,label:g,onTiming:c,idleTimeoutMs:w,onUsage:k}=o;c?.("request-start");const p=r.toolMode??o.toolMode??"native",u=p!=="native"&&(r.tools?.length??0)>0,h=u?Q(r.tools):null,N=r.messages.findIndex(t=>t.role!=="system"),J=N===-1?r.messages.length:N,C=[];r.messages.forEach((t,s,L)=>{s===J&&h!==null&&C.push({role:"system",content:h});const b={role:t.role,content:t.content};t.name&&(b.name=t.name),t.toolCallId&&(b.tool_call_id=t.toolCallId),t.toolCalls&&(b.tool_calls=t.toolCalls.map(O=>({id:O.id,type:"function",function:{name:O.name,arguments:O.arguments}}))),p==="prompted"&&u&&s===L.length-1&&(b.content=`${t.content}
|
|
8
8
|
|
|
9
|
-
${
|
|
9
|
+
${z(r.tools)}`),C.push(b)}),J===r.messages.length&&h!==null&&C.push({role:"system",content:h});const T=await f(`${n}/chat/completions`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e}`,...m},body:JSON.stringify({model:i,messages:C,...!u&&r.tools&&r.tools.length>0?{tools:r.tools.map(t=>({type:"function",function:t}))}:{},...u&&p==="envelope"?{response_format:{type:"json_schema",json_schema:{name:"tool_envelope",strict:!1,schema:U(r.tools)}}}:{},stream:!0,temperature:r.temperature,max_tokens:r.maxTokens,stream_options:{include_usage:!0},...l}),signal:a});if(!T.ok){const t=await T.text();throw new Error(`${g} request failed (${T.status}): ${t}`)}if(!T.body)throw new Error(`${g} returned an empty body`);c?.("headers");const _=new Map;let E=!1,v=0,q=!1,D=!1,x="";const R=K();for await(const t of X(T.body,a,c,w,g)){const s=t.choices?.[0];if(t.error){yield{type:"error",error:new Error(`${g} provider aborted the stream: ${t.error.message??JSON.stringify(t.error)}`)};break}if(t.usage&&(t.usage.prompt_tokens??0)>0&&(q=!0,v=0,k?.({promptTokens:t.usage.prompt_tokens??0,completionTokens:t.usage.completion_tokens??0})),!s)continue;const L=s.delta,b=R.push(L?.content??""),A=(L?.reasoning??L?.reasoning_content??"")+b.reasoning;u?x+=b.text:(b.text.length>0||A.length>0)&&(yield{type:"delta",content:b.text,...A.length>0?{reasoning:A}:{}});for(const y of L?.tool_calls??[]){const I=y.index??0,S=_.get(I)??{id:"",name:"",arguments:""};y.id&&(S.id=y.id),y.function?.name&&(S.name+=y.function.name),y.function?.arguments&&(S.arguments+=y.function.arguments),_.set(I,S)}if(s.finish_reason==="tool_calls"&&!D){for(const y of _.values())yield{type:"tool_call",id:y.id,name:y.name,arguments:y.arguments};D=!0,E=!0}else if(s.finish_reason==="stop")E=!0;else if(s.finish_reason==="error"||s.finish_reason==="content_filter"){yield{type:"error",error:new Error(`${g} stream finished with "${s.finish_reason}"`+(_.size>0?"":" and produced no output"))};break}if(E&&(q||(v===0&&(v=Date.now()),Date.now()-v>500)))break}const $=R.flush();if($.text&&(u?x+=$.text:yield{type:"delta",content:$.text}),u){let t;try{t=JSON.parse(x)}catch{const s=H(x);if(s!==null)try{t=JSON.parse(s)}catch{t=void 0}if(t===void 0)try{t=JSON.parse(`{${x}`)}catch{t=void 0}}if(t===void 0)if(p==="prompted"&&!G(x)){const s=x.trim();s.length>0&&(yield{type:"delta",content:s})}else yield{type:"error",error:new Error(`${g} ${p} tool mode: model did not return a JSON envelope (${x.slice(0,120)})`)};else{typeof t.answer=="string"&&t.answer.length>0&&(yield{type:"delta",content:t.answer});for(const s of t.calls??[])typeof s.name=="string"&&(yield{type:"tool_call",id:crypto.randomUUID(),name:s.name,arguments:JSON.stringify(s.arguments??{})})}}yield{type:"done"}}d(V,"openAICompatibleStream");async function*X(o,n,e,i=8e3,f="LLM"){const r=o.getReader(),a=new TextDecoder;let m="",l=!1;const g=d(()=>{let c;return new Promise((w,k)=>{const p=d(()=>{c!==void 0&&clearTimeout(c),n.removeEventListener("abort",u)},"cleanup"),u=d(()=>{p(),k(new DOMException("The operation was aborted.","AbortError"))},"onAbort");r.read().then(h=>{p(),w(h)},h=>{p(),k(h)}),c=setTimeout(()=>{p(),r.cancel().catch(()=>{}),k(new Error(`${f} stream idle for ${i}ms \u2014 no data received; aborting the stalled stream`))},i),n.addEventListener("abort",u,{once:!0})})},"readWithTimeout");for(;;){if(n.aborted)throw new DOMException("The operation was aborted.","AbortError");const{done:c,value:w}=await g();if(c)break;m+=a.decode(w,{stream:!0});const k=m.split(/\r?\n\r?\n/);m=k.pop()??"";for(const p of k)for(const u of p.split(/\r?\n/)){if(!u.startsWith("data:"))continue;const h=u.slice(5).trim();if(h==="[DONE]")return;l||(l=!0,e?.("first-chunk"));try{yield JSON.parse(h)}catch{}}}}d(X,"parseSSE");
|
|
10
10
|
//# sourceMappingURL=openai-compatible.js.map
|