@polpo-ai/server 0.15.76 → 0.15.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/routes/completions/chat-handler.d.ts +10 -0
  6. package/dist/routes/completions/chat-handler.d.ts.map +1 -1
  7. package/dist/routes/completions/chat-handler.js +436 -436
  8. package/dist/routes/completions/chat-handler.js.map +1 -1
  9. package/dist/routes/completions/chat-via-run-handler.d.ts +2 -1
  10. package/dist/routes/completions/chat-via-run-handler.d.ts.map +1 -1
  11. package/dist/routes/completions/chat-via-run-handler.js +102 -91
  12. package/dist/routes/completions/chat-via-run-handler.js.map +1 -1
  13. package/dist/routes/completions/durable-stream.d.ts +39 -0
  14. package/dist/routes/completions/durable-stream.d.ts.map +1 -0
  15. package/dist/routes/completions/durable-stream.js +83 -0
  16. package/dist/routes/completions/durable-stream.js.map +1 -0
  17. package/dist/routes/completions/durable-stream.test.d.ts +2 -0
  18. package/dist/routes/completions/durable-stream.test.d.ts.map +1 -0
  19. package/dist/routes/completions/durable-stream.test.js +140 -0
  20. package/dist/routes/completions/durable-stream.test.js.map +1 -0
  21. package/dist/routes/completions/project-loop-runner.d.ts +6 -2
  22. package/dist/routes/completions/project-loop-runner.d.ts.map +1 -1
  23. package/dist/routes/completions/project-loop-runner.js +121 -113
  24. package/dist/routes/completions/project-loop-runner.js.map +1 -1
  25. package/dist/routes/completions/schemas.d.ts +12 -0
  26. package/dist/routes/completions/schemas.d.ts.map +1 -1
  27. package/dist/routes/completions/schemas.js +5 -0
  28. package/dist/routes/completions/schemas.js.map +1 -1
  29. package/dist/routes/completions/schemas.test.js +20 -0
  30. package/dist/routes/completions/schemas.test.js.map +1 -1
  31. package/dist/routes/completions-durable-delivery.test.d.ts +2 -0
  32. package/dist/routes/completions-durable-delivery.test.d.ts.map +1 -0
  33. package/dist/routes/completions-durable-delivery.test.js +154 -0
  34. package/dist/routes/completions-durable-delivery.test.js.map +1 -0
  35. package/dist/routes/completions.d.ts +13 -0
  36. package/dist/routes/completions.d.ts.map +1 -1
  37. package/dist/routes/completions.js +107 -5
  38. package/dist/routes/completions.js.map +1 -1
  39. package/dist/routes/run-delivery.d.ts +15 -0
  40. package/dist/routes/run-delivery.d.ts.map +1 -0
  41. package/dist/routes/run-delivery.js +202 -0
  42. package/dist/routes/run-delivery.js.map +1 -0
  43. package/dist/routes/run-delivery.test.d.ts +2 -0
  44. package/dist/routes/run-delivery.test.d.ts.map +1 -0
  45. package/dist/routes/run-delivery.test.js +124 -0
  46. package/dist/routes/run-delivery.test.js.map +1 -0
  47. package/package.json +7 -7
@@ -0,0 +1,83 @@
1
+ import { RunEventJournal, followRunEvents, } from "@polpo-ai/core/run-delivery-follower";
2
+ import { executeOwnedRun, } from "@polpo-ai/core/run-delivery-owner";
3
+ export function createDurableCompletionWriter(runId, scope) {
4
+ const journal = new RunEventJournal(scope.eventStore, scope.notifier);
5
+ let writeChain = Promise.resolve();
6
+ return {
7
+ write: async () => undefined,
8
+ writeSSE: (message) => {
9
+ writeChain = writeChain.then(() => journal.append(runId, {
10
+ type: message.data === "[DONE]" ? "response.done" : "response.chunk",
11
+ data: {
12
+ data: message.data,
13
+ ...(message.event === undefined ? {} : { event: message.event }),
14
+ ...(message.id === undefined ? {} : { id: message.id }),
15
+ },
16
+ }));
17
+ return writeChain;
18
+ },
19
+ };
20
+ }
21
+ export function startDurableCompletion(options) {
22
+ const journal = new RunEventJournal(options.scope.eventStore, options.scope.notifier);
23
+ return executeOwnedRun({
24
+ runId: options.runId,
25
+ owner: options.scope.owner,
26
+ token: options.scope.token,
27
+ journal,
28
+ leaseStore: options.scope.leaseStore,
29
+ cancellationStore: options.scope.cancellationStore,
30
+ producer: async ({ signal }) => {
31
+ await options.producer({
32
+ signal,
33
+ writer: createDurableCompletionWriter(options.runId, options.scope),
34
+ });
35
+ },
36
+ });
37
+ }
38
+ export async function streamDurableCompletionFrames(options) {
39
+ let responseDone = false;
40
+ for await (const event of followRunEvents({
41
+ runId: options.runId,
42
+ store: options.scope.eventStore,
43
+ notifier: options.scope.notifier,
44
+ cursor: options.cursor,
45
+ signal: options.signal,
46
+ })) {
47
+ if (event.type === "response.chunk" || event.type === "response.done") {
48
+ const data = typeof event.data.data === "string" ? event.data.data : undefined;
49
+ if (data === undefined)
50
+ continue;
51
+ responseDone ||= event.type === "response.done";
52
+ await options.write({
53
+ data,
54
+ cursor: String(event.sequence),
55
+ ...(typeof event.data.event === "string" ? { event: event.data.event } : {}),
56
+ ...(typeof event.data.id === "string" ? { id: event.data.id } : {}),
57
+ });
58
+ continue;
59
+ }
60
+ if (event.type === "run.failed") {
61
+ const message = typeof event.data.message === "string"
62
+ ? event.data.message
63
+ : "Run execution failed";
64
+ await options.write({
65
+ event: "error",
66
+ cursor: String(event.sequence),
67
+ data: JSON.stringify({
68
+ error: {
69
+ message,
70
+ type: "model_error",
71
+ code: "model_request_failed",
72
+ },
73
+ }),
74
+ });
75
+ continue;
76
+ }
77
+ if (!responseDone && (event.type === "run.completed" || event.type === "run.cancelled")) {
78
+ responseDone = true;
79
+ await options.write({ data: "[DONE]", cursor: String(event.sequence) });
80
+ }
81
+ }
82
+ }
83
+ //# sourceMappingURL=durable-stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-stream.js","sourceRoot":"","sources":["../../../src/routes/completions/durable-stream.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=durable-stream.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-stream.test.d.ts","sourceRoot":"","sources":["../../../src/routes/completions/durable-stream.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,140 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { InMemoryRunCancellationStore, InMemoryRunEventStore, InMemoryRunExecutionLeaseStore, } from "@polpo-ai/core/run-delivery";
3
+ import { InMemoryRunEventNotifier } from "@polpo-ai/core/run-delivery-follower";
4
+ import { createDurableCompletionWriter, startDurableCompletion, streamDurableCompletionFrames, } from "./durable-stream.js";
5
+ function scope() {
6
+ return {
7
+ eventStore: new InMemoryRunEventStore(),
8
+ leaseStore: new InMemoryRunExecutionLeaseStore(),
9
+ cancellationStore: new InMemoryRunCancellationStore(),
10
+ notifier: new InMemoryRunEventNotifier(),
11
+ owner: "worker-a",
12
+ token: "token-a",
13
+ };
14
+ }
15
+ describe("durable completion stream", () => {
16
+ it("persists OpenAI frames and replays them without lifecycle internals", async () => {
17
+ const delivery = scope();
18
+ const execution = startDurableCompletion({
19
+ runId: "run-a",
20
+ scope: delivery,
21
+ producer: async ({ writer }) => {
22
+ await writer.writeSSE({ data: '{"choices":[{"delta":{"content":"hello"}}]}' });
23
+ await writer.writeSSE({ data: "[DONE]" });
24
+ },
25
+ });
26
+ const frames = [];
27
+ await streamDurableCompletionFrames({
28
+ runId: "run-a",
29
+ scope: delivery,
30
+ write: async (frame) => { frames.push(frame.data); },
31
+ });
32
+ await expect(execution).resolves.toEqual({ status: "completed" });
33
+ expect(frames).toEqual([
34
+ '{"choices":[{"delta":{"content":"hello"}}]}',
35
+ "[DONE]",
36
+ ]);
37
+ expect((await delivery.eventStore.listAfter("run-a")).events.map((event) => event.type)).toEqual([
38
+ "run.started",
39
+ "response.chunk",
40
+ "response.done",
41
+ "run.completed",
42
+ ]);
43
+ });
44
+ it("subscriber disconnect aborts only the follower while the producer completes", async () => {
45
+ const delivery = scope();
46
+ let releaseProducer;
47
+ const producerGate = new Promise((resolve) => { releaseProducer = resolve; });
48
+ let producerSignal;
49
+ const execution = startDurableCompletion({
50
+ runId: "run-a",
51
+ scope: delivery,
52
+ producer: async ({ writer, signal }) => {
53
+ producerSignal = signal;
54
+ await writer.writeSSE({ data: '{"chunk":1}' });
55
+ await producerGate;
56
+ await writer.writeSSE({ data: "[DONE]" });
57
+ },
58
+ });
59
+ const follower = new AbortController();
60
+ const frames = [];
61
+ const streaming = streamDurableCompletionFrames({
62
+ runId: "run-a",
63
+ scope: delivery,
64
+ signal: follower.signal,
65
+ write: async (frame) => {
66
+ frames.push(frame.data);
67
+ follower.abort();
68
+ },
69
+ });
70
+ await streaming;
71
+ expect(producerSignal?.aborted).toBe(false);
72
+ releaseProducer();
73
+ await expect(execution).resolves.toEqual({ status: "completed" });
74
+ expect(producerSignal?.aborted).toBe(false);
75
+ expect(frames).toEqual(['{"chunk":1}']);
76
+ });
77
+ it("serializes concurrent writer calls in invocation order", async () => {
78
+ const delivery = scope();
79
+ const writer = createDurableCompletionWriter("run-a", delivery);
80
+ await Promise.all([
81
+ writer.writeSSE({ data: "one" }),
82
+ writer.writeSSE({ data: "two" }),
83
+ writer.writeSSE({ data: "[DONE]" }),
84
+ ]);
85
+ const events = (await delivery.eventStore.listAfter("run-a")).events;
86
+ expect(events.map((event) => event.data.data)).toEqual(["one", "two", "[DONE]"]);
87
+ });
88
+ it("projects unexpected producer failures to the initial subscriber", async () => {
89
+ const delivery = scope();
90
+ const execution = startDurableCompletion({
91
+ runId: "run-a",
92
+ scope: delivery,
93
+ producer: async () => { throw new Error("provider unavailable"); },
94
+ });
95
+ const frames = [];
96
+ await streamDurableCompletionFrames({
97
+ runId: "run-a",
98
+ scope: delivery,
99
+ write: async (frame) => {
100
+ frames.push({
101
+ data: frame.data,
102
+ ...(frame.event === undefined ? {} : { event: frame.event }),
103
+ });
104
+ },
105
+ });
106
+ await expect(execution).resolves.toEqual({
107
+ status: "failed",
108
+ error: "provider unavailable",
109
+ });
110
+ expect(frames).toEqual([{
111
+ event: "error",
112
+ data: JSON.stringify({
113
+ error: {
114
+ message: "provider unavailable",
115
+ type: "model_error",
116
+ code: "model_request_failed",
117
+ },
118
+ }),
119
+ }]);
120
+ });
121
+ it("emits a terminal frame when a producer completes without writing DONE", async () => {
122
+ const delivery = scope();
123
+ const execution = startDurableCompletion({
124
+ runId: "run-a",
125
+ scope: delivery,
126
+ producer: async ({ writer }) => {
127
+ await writer.writeSSE({ data: '{"choices":[]}' });
128
+ },
129
+ });
130
+ const frames = [];
131
+ await streamDurableCompletionFrames({
132
+ runId: "run-a",
133
+ scope: delivery,
134
+ write: async (frame) => { frames.push(frame.data); },
135
+ });
136
+ await expect(execution).resolves.toEqual({ status: "completed" });
137
+ expect(frames).toEqual(['{"choices":[]}', "[DONE]"]);
138
+ });
139
+ });
140
+ //# sourceMappingURL=durable-stream.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-stream.test.js","sourceRoot":"","sources":["../../../src/routes/completions/durable-stream.test.ts"],"names":[],"mappings":"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"}
@@ -11,6 +11,7 @@ import type { LanguageModelUsage } from "ai";
11
11
  import type { CompletionRouteDeps, CompletionRuntimeInvocation } from "../completions.js";
12
12
  import { type CompletionResolvedModelInfo } from "./agent-step-runner.js";
13
13
  import { type LoopRuntimeToolCall } from "./tool-mapping.js";
14
+ import type { CompletionSseWriter } from "./chat-handler.js";
14
15
  export interface ProjectLoopRunResult {
15
16
  text: string;
16
17
  usage: LanguageModelUsage;
@@ -63,7 +64,7 @@ export declare function resumeProjectLoopRun(options: {
63
64
  * pipeline and adapts it to the OpenAI response surface (SSE stream with
64
65
  * tool_call / loop_trace chunks, or a single JSON body).
65
66
  */
66
- export declare function handleProjectLoopCompletion(c: any, options: {
67
+ export interface ProjectLoopCompletionOptions {
67
68
  deps: CompletionRouteDeps;
68
69
  body: {
69
70
  stream?: boolean;
@@ -84,5 +85,8 @@ export declare function handleProjectLoopCompletion(c: any, options: {
84
85
  runtimePlan?: RuntimePlan;
85
86
  executionRoute?: ResolvedExecutionRoute;
86
87
  activatedSkills?: readonly string[];
87
- }): Promise<any>;
88
+ }
89
+ export declare function handleProjectLoopCompletion(c: any, options: ProjectLoopCompletionOptions): Promise<any>;
90
+ /** Transport-independent loop producer used by attached and durable followers. */
91
+ export declare function executeStreamingProjectLoopCompletion(stream: CompletionSseWriter, options: ProjectLoopCompletionOptions, signal: AbortSignal): Promise<void>;
88
92
  //# sourceMappingURL=project-loop-runner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"project-loop-runner.d.ts","sourceRoot":"","sources":["../../../src/routes/completions/project-loop-runner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"project-loop-runner.d.ts","sourceRoot":"","sources":["../../../src/routes/completions/project-loop-runner.ts"],"names":[],"mappings":"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"}
@@ -628,15 +628,8 @@ export async function resumeProjectLoopRun(options) {
628
628
  throw new Error(`Loop run "${run.id}" disappeared after resume`);
629
629
  return updated;
630
630
  }
631
- /**
632
- * HTTP wiring for loop-mode completion requests: runs the project loop
633
- * pipeline and adapts it to the OpenAI response surface (SSE stream with
634
- * tool_call / loop_trace chunks, or a single JSON body).
635
- */
636
631
  export async function handleProjectLoopCompletion(c, options) {
637
- const { deps, body, completionId, agentConfig, projectLoop, aiMessages, extraSystemParts, runtimeContext, runtimeInvocation, sessionStore, sessionId, runtimePlan, executionRoute, activatedSkills, } = options;
638
- const contextTrust = normalizeRuntimeContextTrustMode(options.contextTrust ?? deps.getConfig()?.settings?.contextTrust);
639
- if (body.stream) {
632
+ if (options.body.stream) {
640
633
  return streamSSE(c, async (stream) => {
641
634
  const abortController = new AbortController();
642
635
  stream.onAbort(() => { abortController.abort(); });
@@ -649,118 +642,133 @@ export async function handleProjectLoopCompletion(c, options) {
649
642
  clearInterval(heartbeatInterval);
650
643
  });
651
644
  }, 20_000);
652
- let assistantMsgId = null;
653
- let finalText = "";
654
- let runUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
655
- let runModel = agentConfigForModelPrimary(agentConfig, deps.getConfig()?.settings).model ?? "polpo";
656
- let resolvedModel;
657
- let providerMetadata;
658
- let toolCalls = [];
659
- const outputMode = streamingOutputPolicyMode(deps.runOutputPolicy);
660
645
  try {
661
- await stream.writeSSE({ data: sseChunk(completionId, { role: "assistant" }) });
662
- if (sessionStore && sessionId) {
663
- const placeholder = await sessionStore.addMessage(sessionId, "assistant", "");
664
- assistantMsgId = placeholder.id;
665
- }
666
- const run = await runProjectLoopCompletion({
667
- deps,
668
- agentConfig,
669
- projectLoop,
670
- aiMessages,
671
- extraSystemParts,
672
- contextTrust,
673
- runtimeContext,
674
- runtimeInvocation,
675
- sessionId,
676
- user: body.user,
677
- requestMetadata: body.metadata,
678
- runtimePlan,
679
- signal: abortController.signal,
680
- executionRoute,
681
- activatedSkills,
682
- onToolCall: async (toolCall) => {
683
- if (abortController.signal.aborted)
684
- return;
685
- await stream.writeSSE({
686
- data: sseChunk(completionId, {}, null, { tool_call: toolCall }),
687
- });
688
- },
689
- onTrace: async (event) => {
690
- if (abortController.signal.aborted)
691
- return;
692
- await stream.writeSSE({
693
- data: sseChunk(completionId, {}, null, { loop_trace: event }),
694
- });
695
- },
696
- });
697
- finalText = run.text;
698
- runUsage = run.usage;
699
- runModel = run.model;
700
- resolvedModel = run.resolvedModel;
701
- providerMetadata = run.providerMetadata;
702
- toolCalls = run.toolCalls;
703
- finalText = await applyCompletionOutputPolicy({
704
- outputPolicy: deps.runOutputPolicy,
705
- text: finalText,
706
- mode: outputMode === "buffer" ? "enforce" : "audit",
707
- runtimePlan,
708
- agent: body.agent,
709
- runId: run.loopRunId,
710
- sessionId,
711
- signal: abortController.signal,
712
- });
713
- if (!abortController.signal.aborted && finalText) {
714
- await stream.writeSSE({ data: sseChunk(completionId, { content: finalText }) });
715
- }
716
- if (!abortController.signal.aborted) {
717
- await stream.writeSSE({ data: sseChunk(completionId, {}, "stop", { loop_run_id: run.loopRunId }) });
718
- await stream.writeSSE({ data: "[DONE]" });
719
- }
720
- }
721
- catch (err) {
722
- if ((err instanceof DOMException && err.name === "AbortError") || abortController.signal.aborted) {
723
- return;
724
- }
725
- const guardrailError = guardrailErrorEnvelope(err);
726
- const notFound = modelNotFoundEnvelope(err, runModel, body.agent);
727
- if (guardrailError) {
728
- finalText = guardrailError.message;
729
- await stream.writeSSE({ data: sseChunk(completionId, {}, "stop", { error: guardrailError }) });
730
- await stream.writeSSE({ data: "[DONE]" });
731
- return;
732
- }
733
- if (notFound) {
734
- await stream.writeSSE({ data: sseChunk(completionId, {}, "stop", { error: notFound }) });
735
- await stream.writeSSE({ data: "[DONE]" });
736
- return;
737
- }
738
- const loopError = loopRuntimeErrorEnvelope(err);
739
- if (loopError) {
740
- await stream.writeSSE({ data: sseChunk(completionId, {}, "stop", { error: loopError }) });
741
- await stream.writeSSE({ data: "[DONE]" });
742
- return;
743
- }
744
- throw err;
646
+ await executeStreamingProjectLoopCompletion(stream, options, abortController.signal);
745
647
  }
746
648
  finally {
747
649
  clearInterval(heartbeatInterval);
748
- await persistAssistantMessage(sessionStore, sessionId, assistantMsgId, finalText, toolCalls);
749
- try {
750
- deps.onCompletionFinished?.({
751
- usage: runUsage,
752
- model: runModel,
753
- resolvedModel,
754
- agent: body.agent,
755
- sessionId: sessionId ?? undefined,
756
- user: body.user,
757
- providerMetadata,
758
- });
759
- }
760
- catch { /* never fail on callback */ }
761
650
  }
762
651
  });
763
652
  }
653
+ return runNonStreamingProjectLoopCompletion(c, options);
654
+ }
655
+ /** Transport-independent loop producer used by attached and durable followers. */
656
+ export async function executeStreamingProjectLoopCompletion(stream, options, signal) {
657
+ const { deps, body, completionId, agentConfig, projectLoop, aiMessages, extraSystemParts, runtimeContext, runtimeInvocation, sessionStore, sessionId, runtimePlan, executionRoute, activatedSkills, } = options;
658
+ const contextTrust = normalizeRuntimeContextTrustMode(options.contextTrust ?? deps.getConfig()?.settings?.contextTrust);
659
+ let assistantMsgId = null;
660
+ let finalText = "";
661
+ let runUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
662
+ let runModel = agentConfigForModelPrimary(agentConfig, deps.getConfig()?.settings).model ?? "polpo";
663
+ let resolvedModel;
664
+ let providerMetadata;
665
+ let toolCalls = [];
666
+ const outputMode = streamingOutputPolicyMode(deps.runOutputPolicy);
667
+ try {
668
+ await stream.writeSSE({ data: sseChunk(completionId, { role: "assistant" }) });
669
+ if (sessionStore && sessionId) {
670
+ const placeholder = await sessionStore.addMessage(sessionId, "assistant", "");
671
+ assistantMsgId = placeholder.id;
672
+ }
673
+ const run = await runProjectLoopCompletion({
674
+ deps,
675
+ agentConfig,
676
+ projectLoop,
677
+ aiMessages,
678
+ extraSystemParts,
679
+ contextTrust,
680
+ runtimeContext,
681
+ runtimeInvocation,
682
+ sessionId,
683
+ user: body.user,
684
+ requestMetadata: body.metadata,
685
+ runtimePlan,
686
+ signal,
687
+ executionRoute,
688
+ activatedSkills,
689
+ onToolCall: async (toolCall) => {
690
+ if (signal.aborted)
691
+ return;
692
+ await stream.writeSSE({
693
+ data: sseChunk(completionId, {}, null, { tool_call: toolCall }),
694
+ });
695
+ },
696
+ onTrace: async (event) => {
697
+ if (signal.aborted)
698
+ return;
699
+ await stream.writeSSE({
700
+ data: sseChunk(completionId, {}, null, { loop_trace: event }),
701
+ });
702
+ },
703
+ });
704
+ finalText = run.text;
705
+ runUsage = run.usage;
706
+ runModel = run.model;
707
+ resolvedModel = run.resolvedModel;
708
+ providerMetadata = run.providerMetadata;
709
+ toolCalls = run.toolCalls;
710
+ finalText = await applyCompletionOutputPolicy({
711
+ outputPolicy: deps.runOutputPolicy,
712
+ text: finalText,
713
+ mode: outputMode === "buffer" ? "enforce" : "audit",
714
+ runtimePlan,
715
+ agent: body.agent,
716
+ runId: run.loopRunId,
717
+ sessionId,
718
+ signal,
719
+ });
720
+ if (!signal.aborted && finalText) {
721
+ await stream.writeSSE({ data: sseChunk(completionId, { content: finalText }) });
722
+ }
723
+ if (!signal.aborted) {
724
+ await stream.writeSSE({ data: sseChunk(completionId, {}, "stop", { loop_run_id: run.loopRunId }) });
725
+ await stream.writeSSE({ data: "[DONE]" });
726
+ }
727
+ }
728
+ catch (err) {
729
+ if ((err instanceof DOMException && err.name === "AbortError") || signal.aborted) {
730
+ return;
731
+ }
732
+ const guardrailError = guardrailErrorEnvelope(err);
733
+ const notFound = modelNotFoundEnvelope(err, runModel, body.agent);
734
+ if (guardrailError) {
735
+ finalText = guardrailError.message;
736
+ await stream.writeSSE({ data: sseChunk(completionId, {}, "stop", { error: guardrailError }) });
737
+ await stream.writeSSE({ data: "[DONE]" });
738
+ return;
739
+ }
740
+ if (notFound) {
741
+ await stream.writeSSE({ data: sseChunk(completionId, {}, "stop", { error: notFound }) });
742
+ await stream.writeSSE({ data: "[DONE]" });
743
+ return;
744
+ }
745
+ const loopError = loopRuntimeErrorEnvelope(err);
746
+ if (loopError) {
747
+ await stream.writeSSE({ data: sseChunk(completionId, {}, "stop", { error: loopError }) });
748
+ await stream.writeSSE({ data: "[DONE]" });
749
+ return;
750
+ }
751
+ throw err;
752
+ }
753
+ finally {
754
+ await persistAssistantMessage(sessionStore, sessionId, assistantMsgId, finalText, toolCalls);
755
+ try {
756
+ deps.onCompletionFinished?.({
757
+ usage: runUsage,
758
+ model: runModel,
759
+ resolvedModel,
760
+ agent: body.agent,
761
+ sessionId: sessionId ?? undefined,
762
+ user: body.user,
763
+ providerMetadata,
764
+ });
765
+ }
766
+ catch { /* never fail on callback */ }
767
+ }
768
+ }
769
+ async function runNonStreamingProjectLoopCompletion(c, options) {
770
+ const { deps, body, completionId, agentConfig, projectLoop, aiMessages, extraSystemParts, runtimeContext, runtimeInvocation, sessionStore, sessionId, runtimePlan, executionRoute, activatedSkills, } = options;
771
+ const contextTrust = normalizeRuntimeContextTrustMode(options.contextTrust ?? deps.getConfig()?.settings?.contextTrust);
764
772
  let assistantMsgId = null;
765
773
  let runUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
766
774
  let runModel = agentConfigForModelPrimary(agentConfig, deps.getConfig()?.settings).model ?? "polpo";