veryfront 0.1.102 → 0.1.104

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 (30) hide show
  1. package/esm/deno.js +1 -1
  2. package/esm/src/agent/runtime/ai-stream-handler.d.ts +1 -1
  3. package/esm/src/agent/runtime/ai-stream-handler.d.ts.map +1 -1
  4. package/esm/src/agent/runtime/ai-stream-handler.js +16 -1
  5. package/esm/src/agent/runtime/index.d.ts +2 -2
  6. package/esm/src/agent/runtime/index.d.ts.map +1 -1
  7. package/esm/src/agent/runtime/index.js +58 -9
  8. package/esm/src/agent/runtime/sse-utils.d.ts +1 -0
  9. package/esm/src/agent/runtime/sse-utils.d.ts.map +1 -1
  10. package/esm/src/agent/runtime/sse-utils.js +15 -0
  11. package/esm/src/internal-agents/run-stream.d.ts +1 -1
  12. package/esm/src/internal-agents/run-stream.d.ts.map +1 -1
  13. package/esm/src/internal-agents/run-stream.js +1 -1
  14. package/esm/src/internal-agents/runtime-owner.d.ts +17 -0
  15. package/esm/src/internal-agents/runtime-owner.d.ts.map +1 -0
  16. package/esm/src/internal-agents/runtime-owner.js +119 -0
  17. package/esm/src/server/handlers/request/agent-stream.handler.d.ts +2 -0
  18. package/esm/src/server/handlers/request/agent-stream.handler.d.ts.map +1 -1
  19. package/esm/src/server/handlers/request/agent-stream.handler.js +17 -1
  20. package/esm/src/utils/version.d.ts +1 -1
  21. package/esm/src/utils/version.js +1 -1
  22. package/package.json +1 -1
  23. package/src/deno.js +1 -1
  24. package/src/src/agent/runtime/ai-stream-handler.ts +25 -0
  25. package/src/src/agent/runtime/index.ts +65 -6
  26. package/src/src/agent/runtime/sse-utils.ts +17 -0
  27. package/src/src/internal-agents/run-stream.ts +6 -0
  28. package/src/src/internal-agents/runtime-owner.ts +189 -0
  29. package/src/src/server/handlers/request/agent-stream.handler.ts +28 -2
  30. package/src/src/utils/version.ts +1 -1
package/esm/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.102",
3
+ "version": "0.1.104",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -42,5 +42,5 @@ export declare function createStreamState(): AIStreamState;
42
42
  * - tool-call → tool-input-available SSE (accumulated input)
43
43
  * - finish → captures finishReason and usage
44
44
  */
45
- export declare function processStream(result: StreamTextResult<ToolSet, never>, state: AIStreamState, controller: ReadableStreamDefaultController, encoder: TextEncoder, textPartId: string | undefined, callbacks?: AIStreamCallbacks): Promise<void>;
45
+ export declare function processStream(result: StreamTextResult<ToolSet, never>, state: AIStreamState, controller: ReadableStreamDefaultController, encoder: TextEncoder, textPartId: string | undefined, callbacks?: AIStreamCallbacks, abortSignal?: AbortSignal): Promise<void>;
46
46
  //# sourceMappingURL=ai-stream-handler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ai-stream-handler.d.ts","sourceRoot":"","sources":["../../../../src/src/agent/runtime/ai-stream-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAQpD,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC1C,KAAK,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;CAChF;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE;QAChB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,KAAK,IAAI,CAAC;CACZ;AAED,wBAAgB,iBAAiB,IAAI,aAAa,CAOjD;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,KAAK,CAAC,EACxC,KAAK,EAAE,aAAa,EACpB,UAAU,EAAE,+BAA+B,EAC3C,OAAO,EAAE,WAAW,EACpB,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,SAAS,CAAC,EAAE,iBAAiB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAiHf"}
1
+ {"version":3,"file":"ai-stream-handler.d.ts","sourceRoot":"","sources":["../../../../src/src/agent/runtime/ai-stream-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAQpD,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC1C,KAAK,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;CAChF;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE;QAChB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,KAAK,IAAI,CAAC;CACZ;AAmBD,wBAAgB,iBAAiB,IAAI,aAAa,CAOjD;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,KAAK,CAAC,EACxC,KAAK,EAAE,aAAa,EACpB,UAAU,EAAE,+BAA+B,EAC3C,OAAO,EAAE,WAAW,EACpB,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,SAAS,CAAC,EAAE,iBAAiB,EAC7B,WAAW,CAAC,EAAE,WAAW,GACxB,OAAO,CAAC,IAAI,CAAC,CAwHf"}
@@ -12,6 +12,17 @@ import { isDynamicTool } from "./tool-helpers.js";
12
12
  import { serverLogger } from "../../utils/index.js";
13
13
  import { setActiveSpanAttributes, withSpan } from "../../observability/tracing/otlp-setup.js";
14
14
  const logger = serverLogger.component("agent");
15
+ function createAbortError(reason) {
16
+ if (reason instanceof Error) {
17
+ return reason;
18
+ }
19
+ return new DOMException(typeof reason === "string" && reason.length > 0 ? reason : "The operation was aborted", "AbortError");
20
+ }
21
+ function throwIfAborted(abortSignal) {
22
+ if (abortSignal?.aborted) {
23
+ throw createAbortError(abortSignal.reason);
24
+ }
25
+ }
15
26
  export function createStreamState() {
16
27
  return {
17
28
  accumulatedText: "",
@@ -30,10 +41,12 @@ export function createStreamState() {
30
41
  * - tool-call → tool-input-available SSE (accumulated input)
31
42
  * - finish → captures finishReason and usage
32
43
  */
33
- export function processStream(result, state, controller, encoder, textPartId, callbacks) {
44
+ export function processStream(result, state, controller, encoder, textPartId, callbacks, abortSignal) {
34
45
  return withSpan("agent.runtime.processStream", async () => {
35
46
  let eventCount = 0;
47
+ throwIfAborted(abortSignal);
36
48
  for await (const part of result.fullStream) {
49
+ throwIfAborted(abortSignal);
37
50
  eventCount++;
38
51
  switch (part.type) {
39
52
  case "text-delta": {
@@ -124,7 +137,9 @@ export function processStream(result, state, controller, encoder, textPartId, ca
124
137
  // Ignore other stream parts (source, file, reasoning-*, etc.)
125
138
  break;
126
139
  }
140
+ throwIfAborted(abortSignal);
127
141
  }
142
+ throwIfAborted(abortSignal);
128
143
  setActiveSpanAttributes({
129
144
  "stream.event_count": eventCount,
130
145
  "stream.tool_calls": state.toolCalls.size,
@@ -12,7 +12,7 @@
12
12
  */
13
13
  import { type AgentConfig, type AgentResponse, type Message, type ToolCall } from "../types.js";
14
14
  import { type Memory } from "../memory/index.js";
15
- export { generateMessageId, sendSSE } from "./sse-utils.js";
15
+ export { closeSSEStream, generateMessageId, sendSSE } from "./sse-utils.js";
16
16
  export { executeConfiguredTool, getAvailableTools, isDynamicTool, parseToolArgs, } from "./tool-helpers.js";
17
17
  export type { ParsedToolArgs, ToolConfigEntry } from "./tool-helpers.js";
18
18
  export { accumulateUsage, getMaxSteps, normalizeInput } from "./input-utils.js";
@@ -55,7 +55,7 @@ export declare class AgentRuntime {
55
55
  onToolCall?: (toolCall: ToolCall) => void;
56
56
  onChunk?: (chunk: string) => void;
57
57
  onFinish?: (response: AgentResponse) => void;
58
- }, modelOverride?: string, maxOutputTokensOverride?: number): Promise<ReadableStream<Uint8Array>>;
58
+ }, modelOverride?: string, maxOutputTokensOverride?: number, abortSignal?: AbortSignal): Promise<ReadableStream<Uint8Array>>;
59
59
  /**
60
60
  * Execute agent loop (with tool calling)
61
61
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/src/agent/runtime/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/src/agent/runtime/index.ts"],"names":[],"mappings":"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"}
@@ -24,19 +24,36 @@ import { MiddlewareChain } from "../middleware/chain.js";
24
24
  import { generateText, streamText } from "ai";
25
25
  import { AGENT_DEFAULTS } from "../ai-defaults.js";
26
26
  // Re-export from submodules
27
- export { generateMessageId, sendSSE } from "./sse-utils.js";
27
+ export { closeSSEStream, generateMessageId, sendSSE } from "./sse-utils.js";
28
28
  export { executeConfiguredTool, getAvailableTools, isDynamicTool, parseToolArgs, } from "./tool-helpers.js";
29
29
  export { accumulateUsage, getMaxSteps, normalizeInput } from "./input-utils.js";
30
30
  export { createStreamState, processStream } from "./ai-stream-handler.js";
31
31
  export { DEFAULT_MAX_STEPS, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE, MAX_STREAM_BUFFER_SIZE, } from "./constants.js";
32
32
  import { DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE } from "./constants.js";
33
- import { generateMessageId, sendSSE } from "./sse-utils.js";
33
+ import { closeSSEStream, generateMessageId, sendSSE } from "./sse-utils.js";
34
34
  import { executeConfiguredTool, getAvailableTools, isDynamicTool, parseToolArgs, } from "./tool-helpers.js";
35
35
  import { accumulateUsage, getMaxSteps, normalizeInput } from "./input-utils.js";
36
36
  import { filterToolsForSkill, isToolAllowedBySkill, validateAllowedToolPatterns, } from "../../skill/allowed-tools.js";
37
37
  import { resolveConfiguredAgentModel } from "./model-resolution.js";
38
38
  const logger = serverLogger.component("agent");
39
39
  const LOAD_SKILL_TOOL_ID = "load-skill";
40
+ function createAbortError(reason) {
41
+ if (reason instanceof Error) {
42
+ return reason;
43
+ }
44
+ return new DOMException(typeof reason === "string" && reason.length > 0 ? reason : "The operation was aborted", "AbortError");
45
+ }
46
+ function throwIfAborted(abortSignal) {
47
+ if (abortSignal?.aborted) {
48
+ throw createAbortError(abortSignal.reason);
49
+ }
50
+ }
51
+ function isAbortError(error, abortSignal) {
52
+ if (abortSignal?.aborted && error === abortSignal.reason) {
53
+ return true;
54
+ }
55
+ return error instanceof DOMException && error.name === "AbortError";
56
+ }
40
57
  function getSkillActivationRequiredError(toolName) {
41
58
  return `Tool "${toolName}" cannot run before load-skill succeeds in the same step. ` +
42
59
  `Call "${LOAD_SKILL_TOOL_ID}" first to establish the active skill context.`;
@@ -159,7 +176,7 @@ export class AgentRuntime {
159
176
  * Stream a response
160
177
  * Returns a ReadableStream in the veryfront stream event format.
161
178
  */
162
- async stream(messages, context, callbacks, modelOverride, maxOutputTokensOverride) {
179
+ async stream(messages, context, callbacks, modelOverride, maxOutputTokensOverride, abortSignal) {
163
180
  const requestedModel = resolveConfiguredAgentModel(modelOverride || this.config.model);
164
181
  // Auto-upgrade local/* to a cloud provider when API keys are available.
165
182
  const resolvedModelString = maybeUpgradeLocalModel(requestedModel);
@@ -168,7 +185,20 @@ export class AgentRuntime {
168
185
  const memoryMessages = await this.memory.getMessages();
169
186
  const systemPrompt = await this.resolveSystemPrompt();
170
187
  const encoder = new TextEncoder();
171
- const toolContext = { agentId: this.id, ...context };
188
+ const streamAbortController = new AbortController();
189
+ const forwardAbort = () => {
190
+ streamAbortController.abort(abortSignal?.reason);
191
+ };
192
+ if (abortSignal) {
193
+ if (abortSignal.aborted) {
194
+ streamAbortController.abort(abortSignal.reason);
195
+ }
196
+ else {
197
+ abortSignal.addEventListener("abort", forwardAbort, { once: true });
198
+ }
199
+ }
200
+ const streamAbortSignal = streamAbortController.signal;
201
+ const toolContext = { agentId: this.id, abortSignal: streamAbortSignal, ...context };
172
202
  const textPartId = generateId("text");
173
203
  // Resolve model BEFORE creating the ReadableStream — if this throws
174
204
  // (e.g., no_ai_available), the error propagates to the caller who can
@@ -186,6 +216,7 @@ export class AgentRuntime {
186
216
  return new ReadableStream({
187
217
  start: async (controller) => {
188
218
  try {
219
+ throwIfAborted(streamAbortSignal);
189
220
  this.status = "streaming";
190
221
  const messageId = generateMessageId();
191
222
  sendSSE(controller, encoder, { type: "message-start", messageId });
@@ -203,21 +234,33 @@ export class AgentRuntime {
203
234
  },
204
235
  });
205
236
  sendSSE(controller, encoder, { type: "text-start", id: textPartId });
206
- const response = await this.executeAgentLoopStreaming(systemPrompt, memoryMessages, controller, encoder, callbacks, textPartId, toolContext, resolvedModelString, languageModel, maxOutputTokensOverride);
237
+ const response = await this.executeAgentLoopStreaming(systemPrompt, memoryMessages, controller, encoder, callbacks, textPartId, toolContext, resolvedModelString, languageModel, maxOutputTokensOverride, streamAbortSignal);
238
+ throwIfAborted(streamAbortSignal);
207
239
  callbacks?.onFinish?.(response);
240
+ throwIfAborted(streamAbortSignal);
208
241
  sendSSE(controller, encoder, { type: "text-end", id: textPartId });
209
242
  sendSSE(controller, encoder, { type: "message-finish" });
210
- controller.close();
243
+ closeSSEStream(controller);
211
244
  }
212
245
  catch (error) {
246
+ if (isAbortError(error, streamAbortSignal)) {
247
+ closeSSEStream(controller);
248
+ return;
249
+ }
213
250
  this.status = "error";
214
251
  logger.error("Agent stream error", { error });
215
252
  sendSSE(controller, encoder, {
216
253
  type: "error",
217
254
  error: "An internal error occurred",
218
255
  });
219
- controller.close();
256
+ closeSSEStream(controller);
220
257
  }
258
+ finally {
259
+ abortSignal?.removeEventListener("abort", forwardAbort);
260
+ }
261
+ },
262
+ cancel(reason) {
263
+ streamAbortController.abort(reason);
221
264
  },
222
265
  });
223
266
  }
@@ -414,7 +457,7 @@ export class AgentRuntime {
414
457
  * Emits veryfront stream events (message-start/message-finish + step-start/step-end)
415
458
  * while consuming AI SDK `streamText()` parts internally.
416
459
  */
417
- async executeAgentLoopStreaming(systemPrompt, messages, controller, encoder, callbacks, textPartId, toolContext, modelString, resolvedModel, maxOutputTokensOverride) {
460
+ async executeAgentLoopStreaming(systemPrompt, messages, controller, encoder, callbacks, textPartId, toolContext, modelString, resolvedModel, maxOutputTokensOverride, abortSignal) {
418
461
  const { maxAgentSteps } = getPlatformCapabilities();
419
462
  const maxSteps = this.computeMaxSteps(maxAgentSteps);
420
463
  const effectiveModel = resolveConfiguredAgentModel(modelString || this.config.model);
@@ -434,6 +477,7 @@ export class AgentRuntime {
434
477
  let activeSkillPolicy;
435
478
  let finalFinishReason;
436
479
  for (let step = 0; step < maxSteps; step++) {
480
+ throwIfAborted(abortSignal);
437
481
  sendSSE(controller, encoder, { type: "step-start" });
438
482
  let tools = isLocalStreaming ? [] : getAvailableTools(this.config.tools, {
439
483
  includeSkillTools: Boolean(this.config.skills),
@@ -449,12 +493,14 @@ export class AgentRuntime {
449
493
  tools: convertToolsToAISDK(tools),
450
494
  maxOutputTokens: this.resolveMaxOutputTokens(maxOutputTokensOverride),
451
495
  temperature: DEFAULT_TEMPERATURE,
496
+ abortSignal,
452
497
  });
453
498
  const state = createStreamState();
454
499
  await processStream(result, state, controller, encoder, textPartId, {
455
500
  onChunk: callbacks?.onChunk,
456
501
  onUsage: (usage) => accumulateUsage(totalUsage, usage),
457
- });
502
+ }, abortSignal);
503
+ throwIfAborted(abortSignal);
458
504
  finalFinishReason = state.finishReason ?? finalFinishReason;
459
505
  const streamParts = [];
460
506
  if (state.accumulatedText)
@@ -492,6 +538,7 @@ export class AgentRuntime {
492
538
  Boolean(this.config.skills) &&
493
539
  streamedToolCalls.some((tc) => tc.name === LOAD_SKILL_TOOL_ID);
494
540
  for (const tc of streamedToolCalls) {
541
+ throwIfAborted(abortSignal);
495
542
  const { args, error: argError } = parseToolArgs(tc.arguments);
496
543
  const toolCall = { id: tc.id, name: tc.name, args, status: "pending" };
497
544
  if (argError) {
@@ -523,6 +570,7 @@ export class AgentRuntime {
523
570
  toolCallId: tc.id,
524
571
  ...toolContext,
525
572
  });
573
+ throwIfAborted(abortSignal);
526
574
  toolCall.status = "completed";
527
575
  toolCall.result = result;
528
576
  toolCall.executionTime = Date.now() - startTime;
@@ -560,6 +608,7 @@ export class AgentRuntime {
560
608
  await this.recordToolError(toolCall, errorStr, controller, encoder, currentMessages, toolCalls);
561
609
  }
562
610
  }
611
+ throwIfAborted(abortSignal);
563
612
  sendSSE(controller, encoder, { type: "step-end" });
564
613
  this.status = "thinking";
565
614
  }
@@ -10,6 +10,7 @@
10
10
  * Formats event as: data: {json}\n\n
11
11
  */
12
12
  export declare function sendSSE(controller: ReadableStreamDefaultController, encoder: TextEncoder, event: Record<string, unknown>): void;
13
+ export declare function closeSSEStream(controller: ReadableStreamDefaultController): void;
13
14
  /**
14
15
  * Generate a unique message ID for streaming.
15
16
  */
@@ -1 +1 @@
1
- {"version":3,"file":"sse-utils.d.ts","sourceRoot":"","sources":["../../../../src/src/agent/runtime/sse-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;GAGG;AACH,wBAAgB,OAAO,CACrB,UAAU,EAAE,+BAA+B,EAC3C,OAAO,EAAE,WAAW,EACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,IAAI,CAEN;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C"}
1
+ {"version":3,"file":"sse-utils.d.ts","sourceRoot":"","sources":["../../../../src/src/agent/runtime/sse-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH;;;GAGG;AACH,wBAAgB,OAAO,CACrB,UAAU,EAAE,+BAA+B,EAC3C,OAAO,EAAE,WAAW,EACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,IAAI,CAEN;AAED,wBAAgB,cAAc,CAAC,UAAU,EAAE,+BAA+B,GAAG,IAAI,CAUhF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C"}
@@ -5,6 +5,10 @@
5
5
  *
6
6
  * @module ai/agent/runtime/sse-utils
7
7
  */
8
+ function isClosedStreamControllerError(error) {
9
+ return error instanceof TypeError &&
10
+ error.message.includes("The stream controller cannot close or enqueue");
11
+ }
8
12
  /**
9
13
  * Encode and enqueue a Server-Sent Event (SSE) to the stream controller.
10
14
  * Formats event as: data: {json}\n\n
@@ -12,6 +16,17 @@
12
16
  export function sendSSE(controller, encoder, event) {
13
17
  controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
14
18
  }
19
+ export function closeSSEStream(controller) {
20
+ try {
21
+ controller.close();
22
+ }
23
+ catch (error) {
24
+ if (isClosedStreamControllerError(error)) {
25
+ return;
26
+ }
27
+ throw error;
28
+ }
29
+ }
15
30
  /**
16
31
  * Generate a unique message ID for streaming.
17
32
  */
@@ -7,7 +7,7 @@ export interface RuntimeAgentStreamExecutionDeps {
7
7
  createRuntime?: (agent: Agent, mergedTools: Agent["config"]["tools"]) => {
8
8
  stream: (messages: Message[], context?: Record<string, unknown>, callbacks?: {
9
9
  onFinish?: (response: AgentResponse) => void;
10
- }) => Promise<ReadableStream<Uint8Array>>;
10
+ }, modelOverride?: string, maxOutputTokensOverride?: number, abortSignal?: AbortSignal) => Promise<ReadableStream<Uint8Array>>;
11
11
  };
12
12
  }
13
13
  export declare function createRuntimeAgentStreamResponse(input: RuntimeRunAgentInput, agent: Agent, deps: RuntimeAgentStreamExecutionDeps): Promise<dntShim.Response>;
@@ -1 +1 @@
1
- {"version":3,"file":"run-stream.d.ts","sourceRoot":"","sources":["../../../src/src/internal-agents/run-stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EACL,KAAK,KAAK,EACV,KAAK,YAAY,IAAI,OAAO,EAC5B,KAAK,aAAa,EAEnB,MAAM,mBAAmB,CAAC;AAW3B,OAAO,EAA0B,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAIxD,MAAM,WAAW,+BAA+B;IAC9C,cAAc,EAAE,sBAAsB,CAAC;IACvC,aAAa,CAAC,EAAE,CACd,KAAK,EAAE,KAAK,EACZ,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,KAClC;QACH,MAAM,EAAE,CACN,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,SAAS,CAAC,EAAE;YACV,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAC;SAC9C,KACE,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC;KAC1C,CAAC;CACH;AAsKD,wBAAsB,gCAAgC,CACpD,KAAK,EAAE,oBAAoB,EAC3B,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,+BAA+B,GACpC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAmJ3B"}
1
+ {"version":3,"file":"run-stream.d.ts","sourceRoot":"","sources":["../../../src/src/internal-agents/run-stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EACL,KAAK,KAAK,EACV,KAAK,YAAY,IAAI,OAAO,EAC5B,KAAK,aAAa,EAEnB,MAAM,mBAAmB,CAAC;AAW3B,OAAO,EAA0B,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAIxD,MAAM,WAAW,+BAA+B;IAC9C,cAAc,EAAE,sBAAsB,CAAC;IACvC,aAAa,CAAC,EAAE,CACd,KAAK,EAAE,KAAK,EACZ,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,KAClC;QACH,MAAM,EAAE,CACN,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,SAAS,CAAC,EAAE;YACV,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAC;SAC9C,EACD,aAAa,CAAC,EAAE,MAAM,EACtB,uBAAuB,CAAC,EAAE,MAAM,EAChC,WAAW,CAAC,EAAE,WAAW,KACtB,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC;KAC1C,CAAC;CACH;AAsKD,wBAAsB,gCAAgC,CACpD,KAAK,EAAE,oBAAoB,EAC3B,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,+BAA+B,GACpC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAsJ3B"}
@@ -150,7 +150,7 @@ export async function createRuntimeAgentStreamResponse(input, agent, deps) {
150
150
  onFinish: (response) => {
151
151
  completedResponse = response;
152
152
  },
153
- });
153
+ }, undefined, undefined, abortSignal);
154
154
  }
155
155
  catch (error) {
156
156
  deps.sessionManager.failRun(input.runId);
@@ -0,0 +1,17 @@
1
+ import * as dntShim from "../../_dnt.shims.js";
2
+ import { dynamicImport } from "../platform/compat/dynamic-import.js";
3
+ import { getHostEnv } from "../platform/compat/process.js";
4
+ export declare const RUNTIME_OWNER_INVOKE_URL_HEADER = "x-veryfront-runtime-owner-invoke-url";
5
+ type DenoNetworkInterface = {
6
+ address: string;
7
+ family?: string | number;
8
+ internal?: boolean;
9
+ };
10
+ interface RuntimeOwnerResolverDeps {
11
+ getHostEnv?: typeof getHostEnv;
12
+ dynamicImport?: typeof dynamicImport;
13
+ getDenoNetworkInterfaces?: () => ReadonlyArray<DenoNetworkInterface>;
14
+ }
15
+ export declare function resolveRuntimeOwnerInvokeUrl(req: dntShim.Request, deps?: RuntimeOwnerResolverDeps): Promise<string | null>;
16
+ export {};
17
+ //# sourceMappingURL=runtime-owner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-owner.d.ts","sourceRoot":"","sources":["../../../src/src/internal-agents/runtime-owner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAI3D,eAAO,MAAM,+BAA+B,yCAAyC,CAAC;AAQtF,KAAK,oBAAoB,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAYF,UAAU,wBAAwB;IAChC,UAAU,CAAC,EAAE,OAAO,UAAU,CAAC;IAC/B,aAAa,CAAC,EAAE,OAAO,aAAa,CAAC;IACrC,wBAAwB,CAAC,EAAE,MAAM,aAAa,CAAC,oBAAoB,CAAC,CAAC;CACtE;AAgJD,wBAAsB,4BAA4B,CAChD,GAAG,EAAE,OAAO,CAAC,OAAO,EACpB,IAAI,GAAE,wBAA6B,GAClC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOxB"}
@@ -0,0 +1,119 @@
1
+ import * as dntShim from "../../_dnt.shims.js";
2
+ import { dynamicImport } from "../platform/compat/dynamic-import.js";
3
+ import { getHostEnv } from "../platform/compat/process.js";
4
+ const DEFAULT_PROXY_RUNTIME_PORT = "20000";
5
+ export const RUNTIME_OWNER_INVOKE_URL_HEADER = "x-veryfront-runtime-owner-invoke-url";
6
+ function parsePort(value) {
7
+ if (!value) {
8
+ return null;
9
+ }
10
+ const trimmed = value.trim();
11
+ if (!/^\d+$/.test(trimmed)) {
12
+ return null;
13
+ }
14
+ const port = Number(trimmed);
15
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
16
+ return null;
17
+ }
18
+ return String(port);
19
+ }
20
+ function isTruthyEnv(value) {
21
+ if (!value) {
22
+ return false;
23
+ }
24
+ const normalized = value.trim().toLowerCase();
25
+ return normalized === "1" || normalized === "true" || normalized === "yes";
26
+ }
27
+ function isIpv4Address(value) {
28
+ if (!value) {
29
+ return false;
30
+ }
31
+ const parts = value.split(".");
32
+ if (parts.length !== 4) {
33
+ return false;
34
+ }
35
+ return parts.every((part) => {
36
+ if (!/^\d+$/.test(part)) {
37
+ return false;
38
+ }
39
+ const octet = Number(part);
40
+ return octet >= 0 && octet <= 255;
41
+ });
42
+ }
43
+ function isLoopbackIpv4(address) {
44
+ return address.startsWith("127.");
45
+ }
46
+ function selectNetworkIpv4Address(candidates) {
47
+ for (const candidate of candidates) {
48
+ if (candidate.internal === true ||
49
+ !isIpv4Address(candidate.address) ||
50
+ isLoopbackIpv4(candidate.address)) {
51
+ continue;
52
+ }
53
+ return candidate.address;
54
+ }
55
+ return null;
56
+ }
57
+ function getDenoNetworkInterfacesSafe(deps) {
58
+ try {
59
+ return deps.getDenoNetworkInterfaces?.() ??
60
+ (dntShim.dntGlobalThis.Deno?.networkInterfaces?.() ?? []);
61
+ }
62
+ catch {
63
+ return [];
64
+ }
65
+ }
66
+ async function detectRuntimeOwnerHost(deps) {
67
+ const readHostEnv = deps.getHostEnv ?? getHostEnv;
68
+ const explicitHost = readHostEnv("VERYFRONT_RUNTIME_OWNER_HOST") ??
69
+ readHostEnv("POD_IP") ??
70
+ null;
71
+ if (explicitHost?.trim()) {
72
+ return explicitHost.trim();
73
+ }
74
+ const denoInterfaces = getDenoNetworkInterfacesSafe(deps);
75
+ const denoHost = selectNetworkIpv4Address(denoInterfaces);
76
+ if (denoHost) {
77
+ return denoHost;
78
+ }
79
+ try {
80
+ const nodeOs = await (deps.dynamicImport ?? dynamicImport)("node:os");
81
+ const nodeInterfaces = Object.values(nodeOs.networkInterfaces()).flatMap((entries) => entries ?? []);
82
+ const nodeHost = selectNetworkIpv4Address(nodeInterfaces);
83
+ if (nodeHost) {
84
+ return nodeHost;
85
+ }
86
+ }
87
+ catch {
88
+ // Ignore runtime-specific interface lookup failures and fall through.
89
+ }
90
+ return null;
91
+ }
92
+ function resolveRuntimeOwnerPort(req, deps) {
93
+ const readHostEnv = deps.getHostEnv ?? getHostEnv;
94
+ return (parsePort(readHostEnv("VERYFRONT_RUNTIME_OWNER_PORT")) ??
95
+ parsePort(readHostEnv("VERYFRONT_SERVER_PORT")) ??
96
+ parsePort(new URL(req.url).port) ??
97
+ parsePort(readHostEnv("PORT")) ??
98
+ (isTruthyEnv(readHostEnv("PROXY_MODE")) ? DEFAULT_PROXY_RUNTIME_PORT : null));
99
+ }
100
+ function buildRuntimeOwnerInvokeUrl(host, port) {
101
+ try {
102
+ const url = new URL("http://127.0.0.1/channels/invoke");
103
+ url.hostname = host;
104
+ if (port) {
105
+ url.port = port;
106
+ }
107
+ return url.toString();
108
+ }
109
+ catch {
110
+ return null;
111
+ }
112
+ }
113
+ export async function resolveRuntimeOwnerInvokeUrl(req, deps = {}) {
114
+ const host = await detectRuntimeOwnerHost(deps);
115
+ if (!host) {
116
+ return null;
117
+ }
118
+ return buildRuntimeOwnerInvokeUrl(host, resolveRuntimeOwnerPort(req, deps));
119
+ }
@@ -1,9 +1,11 @@
1
1
  import * as dntShim from "../../../../_dnt.shims.js";
2
2
  import { type RuntimeAgentDiscoveryDeps } from "../../../channels/control-plane.js";
3
3
  import { type RuntimeAgentStreamExecutionDeps } from "../../../internal-agents/run-stream.js";
4
+ import { resolveRuntimeOwnerInvokeUrl } from "../../../internal-agents/runtime-owner.js";
4
5
  import { BaseHandler } from "../response/base.js";
5
6
  import type { HandlerContext, HandlerMetadata, HandlerResult } from "../types.js";
6
7
  export interface AgentStreamHandlerDeps extends RuntimeAgentDiscoveryDeps, RuntimeAgentStreamExecutionDeps {
8
+ resolveRuntimeOwnerInvokeUrl?: typeof resolveRuntimeOwnerInvokeUrl;
7
9
  }
8
10
  export declare class AgentStreamHandler extends BaseHandler {
9
11
  private readonly deps;
@@ -1 +1 @@
1
- {"version":3,"file":"agent-stream.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/request/agent-stream.handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AAGrD,OAAO,EAAE,KAAK,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AACpF,OAAO,EAEL,KAAK,+BAA+B,EACrC,MAAM,wCAAwC,CAAC;AAkBhD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAmB,aAAa,EAAE,MAAM,aAAa,CAAC;AAInG,MAAM,WAAW,sBACf,SAAQ,yBAAyB,EAAE,+BAA+B;CAAG;AAgEvE,qBAAa,kBAAmB,SAAQ,WAAW;IAOrC,OAAO,CAAC,QAAQ,CAAC,IAAI;IANjC,QAAQ,EAAE,eAAe,CAIvB;gBAE2B,IAAI,GAAE,sBAAoC;IAIvE,OAAO,CAAC,sBAAsB;IAwBxB,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CA0EhF"}
1
+ {"version":3,"file":"agent-stream.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/request/agent-stream.handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AAGrD,OAAO,EAAE,KAAK,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AACpF,OAAO,EAEL,KAAK,+BAA+B,EACrC,MAAM,wCAAwC,CAAC;AAChD,OAAO,EACL,4BAA4B,EAE7B,MAAM,2CAA2C,CAAC;AAkBnD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAmB,aAAa,EAAE,MAAM,aAAa,CAAC;AAInG,MAAM,WAAW,sBACf,SAAQ,yBAAyB,EAAE,+BAA+B;IAClE,4BAA4B,CAAC,EAAE,OAAO,4BAA4B,CAAC;CACpE;AA2ED,qBAAa,kBAAmB,SAAQ,WAAW;IAOrC,OAAO,CAAC,QAAQ,CAAC,IAAI;IANjC,QAAQ,EAAE,eAAe,CAIvB;gBAE2B,IAAI,GAAE,sBAAoC;IAIvE,OAAO,CAAC,sBAAsB;IAwBxB,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CAmFhF"}
@@ -1,6 +1,7 @@
1
1
  import * as dntShim from "../../../../_dnt.shims.js";
2
2
  import { defaultChannelInvokeDeps } from "../../../channels/invoke.js";
3
3
  import { createRuntimeAgentStreamResponse, } from "../../../internal-agents/run-stream.js";
4
+ import { resolveRuntimeOwnerInvokeUrl, RUNTIME_OWNER_INVOKE_URL_HEADER, } from "../../../internal-agents/runtime-owner.js";
4
5
  import { ControlPlaneRequestError, verifyControlPlaneRequest, } from "../../../internal-agents/control-plane-auth.js";
5
6
  import { INTERNAL_AGENT_STREAM_MAX_BODY_BYTES, InternalAgentRequestBodyTooLargeError, readInternalAgentRequestBody, } from "../../../internal-agents/request-body.js";
6
7
  import { AgentRunAlreadyExistsError, agentRunSessionManager, } from "../../../internal-agents/session-manager.js";
@@ -11,6 +12,7 @@ import { getHostEnv } from "../../../platform/compat/process.js";
11
12
  const defaultDeps = {
12
13
  ...defaultChannelInvokeDeps,
13
14
  sessionManager: agentRunSessionManager,
15
+ resolveRuntimeOwnerInvokeUrl,
14
16
  };
15
17
  function buildAgentSourceRunOptions(sourceContext) {
16
18
  switch (sourceContext.type) {
@@ -45,6 +47,15 @@ function applyBuilderHeaders(target, source) {
45
47
  headers,
46
48
  });
47
49
  }
50
+ function setResponseHeader(target, key, value) {
51
+ const headers = new dntShim.Headers(target.headers);
52
+ headers.set(key, value);
53
+ return new dntShim.Response(target.body, {
54
+ status: target.status,
55
+ statusText: target.statusText,
56
+ headers,
57
+ });
58
+ }
48
59
  export class AgentStreamHandler extends BaseHandler {
49
60
  deps;
50
61
  metadata = {
@@ -89,7 +100,12 @@ export class AgentStreamHandler extends BaseHandler {
89
100
  return this.respond(builder.json({ error: "Agent not found" }, 404));
90
101
  }
91
102
  const response = await createRuntimeAgentStreamResponse(payload, agent, this.deps);
92
- return this.respond(applyBuilderHeaders(response, builder.headers));
103
+ const runtimeOwnerInvokeUrl = await this.deps.resolveRuntimeOwnerInvokeUrl?.(req) ??
104
+ null;
105
+ const responseWithOwner = runtimeOwnerInvokeUrl
106
+ ? setResponseHeader(response, RUNTIME_OWNER_INVOKE_URL_HEADER, runtimeOwnerInvokeUrl)
107
+ : response;
108
+ return this.respond(applyBuilderHeaders(responseWithOwner, builder.headers));
93
109
  });
94
110
  }
95
111
  catch (error) {
@@ -1,4 +1,4 @@
1
- export declare const VERSION = "0.1.102";
1
+ export declare const VERSION = "0.1.104";
2
2
  export declare function normalizeVeryfrontVersion(version: string | undefined): string | undefined;
3
3
  export declare function resolveRuntimeVersion(options?: {
4
4
  veryfrontVersion?: string;
@@ -2,7 +2,7 @@ import denoConfig from "../../deno.js";
2
2
  import { getEnv } from "../platform/compat/process.js";
3
3
  // Keep in sync with deno.json version.
4
4
  // scripts/release.ts updates this constant during releases.
5
- export const VERSION = "0.1.102";
5
+ export const VERSION = "0.1.104";
6
6
  export function normalizeVeryfrontVersion(version) {
7
7
  if (!version)
8
8
  return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.102",
3
+ "version": "0.1.104",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",
package/src/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.102",
3
+ "version": "0.1.104",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -38,6 +38,23 @@ export interface AIStreamCallbacks {
38
38
  }) => void;
39
39
  }
40
40
 
41
+ function createAbortError(reason?: unknown): Error {
42
+ if (reason instanceof Error) {
43
+ return reason;
44
+ }
45
+
46
+ return new DOMException(
47
+ typeof reason === "string" && reason.length > 0 ? reason : "The operation was aborted",
48
+ "AbortError",
49
+ );
50
+ }
51
+
52
+ function throwIfAborted(abortSignal?: AbortSignal): void {
53
+ if (abortSignal?.aborted) {
54
+ throw createAbortError(abortSignal.reason);
55
+ }
56
+ }
57
+
41
58
  export function createStreamState(): AIStreamState {
42
59
  return {
43
60
  accumulatedText: "",
@@ -64,11 +81,15 @@ export function processStream(
64
81
  encoder: TextEncoder,
65
82
  textPartId: string | undefined,
66
83
  callbacks?: AIStreamCallbacks,
84
+ abortSignal?: AbortSignal,
67
85
  ): Promise<void> {
68
86
  return withSpan("agent.runtime.processStream", async () => {
69
87
  let eventCount = 0;
70
88
 
89
+ throwIfAborted(abortSignal);
90
+
71
91
  for await (const part of result.fullStream) {
92
+ throwIfAborted(abortSignal);
72
93
  eventCount++;
73
94
 
74
95
  switch (part.type) {
@@ -169,8 +190,12 @@ export function processStream(
169
190
  // Ignore other stream parts (source, file, reasoning-*, etc.)
170
191
  break;
171
192
  }
193
+
194
+ throwIfAborted(abortSignal);
172
195
  }
173
196
 
197
+ throwIfAborted(abortSignal);
198
+
174
199
  setActiveSpanAttributes({
175
200
  "stream.event_count": eventCount,
176
201
  "stream.tool_calls": state.toolCalls.size,
@@ -39,7 +39,7 @@ import { generateText, type LanguageModel, streamText } from "ai";
39
39
  import { AGENT_DEFAULTS } from "../ai-defaults.js";
40
40
 
41
41
  // Re-export from submodules
42
- export { generateMessageId, sendSSE } from "./sse-utils.js";
42
+ export { closeSSEStream, generateMessageId, sendSSE } from "./sse-utils.js";
43
43
  export {
44
44
  executeConfiguredTool,
45
45
  getAvailableTools,
@@ -58,7 +58,7 @@ export {
58
58
  } from "./constants.js";
59
59
 
60
60
  import { DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE } from "./constants.js";
61
- import { generateMessageId, sendSSE } from "./sse-utils.js";
61
+ import { closeSSEStream, generateMessageId, sendSSE } from "./sse-utils.js";
62
62
  import {
63
63
  executeConfiguredTool,
64
64
  getAvailableTools,
@@ -76,6 +76,31 @@ import { resolveConfiguredAgentModel } from "./model-resolution.js";
76
76
  const logger = serverLogger.component("agent");
77
77
  const LOAD_SKILL_TOOL_ID = "load-skill";
78
78
 
79
+ function createAbortError(reason?: unknown): Error {
80
+ if (reason instanceof Error) {
81
+ return reason;
82
+ }
83
+
84
+ return new DOMException(
85
+ typeof reason === "string" && reason.length > 0 ? reason : "The operation was aborted",
86
+ "AbortError",
87
+ );
88
+ }
89
+
90
+ function throwIfAborted(abortSignal?: AbortSignal): void {
91
+ if (abortSignal?.aborted) {
92
+ throw createAbortError(abortSignal.reason);
93
+ }
94
+ }
95
+
96
+ function isAbortError(error: unknown, abortSignal?: AbortSignal): boolean {
97
+ if (abortSignal?.aborted && error === abortSignal.reason) {
98
+ return true;
99
+ }
100
+
101
+ return error instanceof DOMException && error.name === "AbortError";
102
+ }
103
+
79
104
  function getSkillActivationRequiredError(toolName: string): string {
80
105
  return `Tool "${toolName}" cannot run before load-skill succeeds in the same step. ` +
81
106
  `Call "${LOAD_SKILL_TOOL_ID}" first to establish the active skill context.`;
@@ -256,6 +281,7 @@ export class AgentRuntime {
256
281
  },
257
282
  modelOverride?: string,
258
283
  maxOutputTokensOverride?: number,
284
+ abortSignal?: AbortSignal,
259
285
  ): Promise<ReadableStream<Uint8Array>> {
260
286
  const requestedModel = resolveConfiguredAgentModel(modelOverride || this.config.model);
261
287
  // Auto-upgrade local/* to a cloud provider when API keys are available.
@@ -267,7 +293,19 @@ export class AgentRuntime {
267
293
  const systemPrompt = await this.resolveSystemPrompt();
268
294
 
269
295
  const encoder = new TextEncoder();
270
- const toolContext = { agentId: this.id, ...context };
296
+ const streamAbortController = new AbortController();
297
+ const forwardAbort = () => {
298
+ streamAbortController.abort(abortSignal?.reason);
299
+ };
300
+ if (abortSignal) {
301
+ if (abortSignal.aborted) {
302
+ streamAbortController.abort(abortSignal.reason);
303
+ } else {
304
+ abortSignal.addEventListener("abort", forwardAbort, { once: true });
305
+ }
306
+ }
307
+ const streamAbortSignal = streamAbortController.signal;
308
+ const toolContext = { agentId: this.id, abortSignal: streamAbortSignal, ...context };
271
309
  const textPartId = generateId("text");
272
310
 
273
311
  // Resolve model BEFORE creating the ReadableStream — if this throws
@@ -289,6 +327,7 @@ export class AgentRuntime {
289
327
  return new ReadableStream<Uint8Array>({
290
328
  start: async (controller) => {
291
329
  try {
330
+ throwIfAborted(streamAbortSignal);
292
331
  this.status = "streaming";
293
332
 
294
333
  const messageId = generateMessageId();
@@ -319,22 +358,35 @@ export class AgentRuntime {
319
358
  resolvedModelString,
320
359
  languageModel,
321
360
  maxOutputTokensOverride,
361
+ streamAbortSignal,
322
362
  );
363
+ throwIfAborted(streamAbortSignal);
323
364
  callbacks?.onFinish?.(response);
365
+ throwIfAborted(streamAbortSignal);
324
366
 
325
367
  sendSSE(controller, encoder, { type: "text-end", id: textPartId });
326
368
  sendSSE(controller, encoder, { type: "message-finish" });
327
- controller.close();
369
+ closeSSEStream(controller);
328
370
  } catch (error) {
371
+ if (isAbortError(error, streamAbortSignal)) {
372
+ closeSSEStream(controller);
373
+ return;
374
+ }
375
+
329
376
  this.status = "error";
330
377
  logger.error("Agent stream error", { error });
331
378
  sendSSE(controller, encoder, {
332
379
  type: "error",
333
380
  error: "An internal error occurred",
334
381
  });
335
- controller.close();
382
+ closeSSEStream(controller);
383
+ } finally {
384
+ abortSignal?.removeEventListener("abort", forwardAbort);
336
385
  }
337
386
  },
387
+ cancel(reason) {
388
+ streamAbortController.abort(reason);
389
+ },
338
390
  });
339
391
  }
340
392
 
@@ -587,6 +639,7 @@ export class AgentRuntime {
587
639
  modelString?: string,
588
640
  resolvedModel?: LanguageModel,
589
641
  maxOutputTokensOverride?: number,
642
+ abortSignal?: AbortSignal,
590
643
  ): Promise<AgentResponse> {
591
644
  const { maxAgentSteps } = getPlatformCapabilities();
592
645
  const maxSteps = this.computeMaxSteps(maxAgentSteps);
@@ -613,6 +666,7 @@ export class AgentRuntime {
613
666
  let finalFinishReason: string | undefined;
614
667
 
615
668
  for (let step = 0; step < maxSteps; step++) {
669
+ throwIfAborted(abortSignal);
616
670
  sendSSE(controller, encoder, { type: "step-start" });
617
671
 
618
672
  let tools = isLocalStreaming ? [] : getAvailableTools(this.config.tools, {
@@ -631,13 +685,15 @@ export class AgentRuntime {
631
685
  tools: convertToolsToAISDK(tools),
632
686
  maxOutputTokens: this.resolveMaxOutputTokens(maxOutputTokensOverride),
633
687
  temperature: DEFAULT_TEMPERATURE,
688
+ abortSignal,
634
689
  });
635
690
 
636
691
  const state = createStreamState();
637
692
  await processStream(result, state, controller, encoder, textPartId, {
638
693
  onChunk: callbacks?.onChunk,
639
694
  onUsage: (usage) => accumulateUsage(totalUsage, usage),
640
- });
695
+ }, abortSignal);
696
+ throwIfAborted(abortSignal);
641
697
  finalFinishReason = state.finishReason ?? finalFinishReason;
642
698
 
643
699
  const streamParts: MessagePart[] = [];
@@ -680,6 +736,7 @@ export class AgentRuntime {
680
736
  streamedToolCalls.some((tc) => tc.name === LOAD_SKILL_TOOL_ID);
681
737
 
682
738
  for (const tc of streamedToolCalls) {
739
+ throwIfAborted(abortSignal);
683
740
  const { args, error: argError } = parseToolArgs(tc.arguments);
684
741
  const toolCall: ToolCall = { id: tc.id, name: tc.name, args, status: "pending" };
685
742
 
@@ -737,6 +794,7 @@ export class AgentRuntime {
737
794
  ...toolContext,
738
795
  },
739
796
  );
797
+ throwIfAborted(abortSignal);
740
798
 
741
799
  toolCall.status = "completed";
742
800
  toolCall.result = result;
@@ -785,6 +843,7 @@ export class AgentRuntime {
785
843
  }
786
844
  }
787
845
 
846
+ throwIfAborted(abortSignal);
788
847
  sendSSE(controller, encoder, { type: "step-end" });
789
848
  this.status = "thinking";
790
849
  }
@@ -6,6 +6,11 @@
6
6
  * @module ai/agent/runtime/sse-utils
7
7
  */
8
8
 
9
+ function isClosedStreamControllerError(error: unknown): error is TypeError {
10
+ return error instanceof TypeError &&
11
+ error.message.includes("The stream controller cannot close or enqueue");
12
+ }
13
+
9
14
  /**
10
15
  * Encode and enqueue a Server-Sent Event (SSE) to the stream controller.
11
16
  * Formats event as: data: {json}\n\n
@@ -18,6 +23,18 @@ export function sendSSE(
18
23
  controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
19
24
  }
20
25
 
26
+ export function closeSSEStream(controller: ReadableStreamDefaultController): void {
27
+ try {
28
+ controller.close();
29
+ } catch (error) {
30
+ if (isClosedStreamControllerError(error)) {
31
+ return;
32
+ }
33
+
34
+ throw error;
35
+ }
36
+ }
37
+
21
38
  /**
22
39
  * Generate a unique message ID for streaming.
23
40
  */
@@ -32,6 +32,9 @@ export interface RuntimeAgentStreamExecutionDeps {
32
32
  callbacks?: {
33
33
  onFinish?: (response: AgentResponse) => void;
34
34
  },
35
+ modelOverride?: string,
36
+ maxOutputTokensOverride?: number,
37
+ abortSignal?: AbortSignal,
35
38
  ) => Promise<ReadableStream<Uint8Array>>;
36
39
  };
37
40
  }
@@ -234,6 +237,9 @@ export async function createRuntimeAgentStreamResponse(
234
237
  completedResponse = response;
235
238
  },
236
239
  },
240
+ undefined,
241
+ undefined,
242
+ abortSignal,
237
243
  );
238
244
  } catch (error) {
239
245
  deps.sessionManager.failRun(input.runId);
@@ -0,0 +1,189 @@
1
+ import * as dntShim from "../../_dnt.shims.js";
2
+ import { dynamicImport } from "../platform/compat/dynamic-import.js";
3
+ import { getHostEnv } from "../platform/compat/process.js";
4
+
5
+ const DEFAULT_PROXY_RUNTIME_PORT = "20000";
6
+
7
+ export const RUNTIME_OWNER_INVOKE_URL_HEADER = "x-veryfront-runtime-owner-invoke-url";
8
+
9
+ interface NetworkAddressInfo {
10
+ address?: string;
11
+ family?: string | number;
12
+ internal?: boolean;
13
+ }
14
+
15
+ type DenoNetworkInterface = {
16
+ address: string;
17
+ family?: string | number;
18
+ internal?: boolean;
19
+ };
20
+
21
+ type NodeOsModule = {
22
+ networkInterfaces(): Record<string, NetworkAddressInfo[] | undefined>;
23
+ };
24
+
25
+ type RuntimeOwnerGlobal = typeof dntShim.dntGlobalThis & {
26
+ Deno?: {
27
+ networkInterfaces?: () => ReadonlyArray<DenoNetworkInterface>;
28
+ };
29
+ };
30
+
31
+ interface RuntimeOwnerResolverDeps {
32
+ getHostEnv?: typeof getHostEnv;
33
+ dynamicImport?: typeof dynamicImport;
34
+ getDenoNetworkInterfaces?: () => ReadonlyArray<DenoNetworkInterface>;
35
+ }
36
+
37
+ function parsePort(value: string | null | undefined): string | null {
38
+ if (!value) {
39
+ return null;
40
+ }
41
+
42
+ const trimmed = value.trim();
43
+ if (!/^\d+$/.test(trimmed)) {
44
+ return null;
45
+ }
46
+
47
+ const port = Number(trimmed);
48
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
49
+ return null;
50
+ }
51
+
52
+ return String(port);
53
+ }
54
+
55
+ function isTruthyEnv(value: string | null | undefined): boolean {
56
+ if (!value) {
57
+ return false;
58
+ }
59
+
60
+ const normalized = value.trim().toLowerCase();
61
+ return normalized === "1" || normalized === "true" || normalized === "yes";
62
+ }
63
+
64
+ function isIpv4Address(value: string | undefined): value is string {
65
+ if (!value) {
66
+ return false;
67
+ }
68
+
69
+ const parts = value.split(".");
70
+ if (parts.length !== 4) {
71
+ return false;
72
+ }
73
+
74
+ return parts.every((part) => {
75
+ if (!/^\d+$/.test(part)) {
76
+ return false;
77
+ }
78
+
79
+ const octet = Number(part);
80
+ return octet >= 0 && octet <= 255;
81
+ });
82
+ }
83
+
84
+ function isLoopbackIpv4(address: string): boolean {
85
+ return address.startsWith("127.");
86
+ }
87
+
88
+ function selectNetworkIpv4Address(
89
+ candidates: ReadonlyArray<NetworkAddressInfo | DenoNetworkInterface>,
90
+ ): string | null {
91
+ for (const candidate of candidates) {
92
+ if (
93
+ candidate.internal === true ||
94
+ !isIpv4Address(candidate.address) ||
95
+ isLoopbackIpv4(candidate.address)
96
+ ) {
97
+ continue;
98
+ }
99
+
100
+ return candidate.address;
101
+ }
102
+
103
+ return null;
104
+ }
105
+
106
+ function getDenoNetworkInterfacesSafe(
107
+ deps: RuntimeOwnerResolverDeps,
108
+ ): ReadonlyArray<DenoNetworkInterface> {
109
+ try {
110
+ return deps.getDenoNetworkInterfaces?.() ??
111
+ ((dntShim.dntGlobalThis as RuntimeOwnerGlobal).Deno?.networkInterfaces?.() ?? []);
112
+ } catch {
113
+ return [];
114
+ }
115
+ }
116
+
117
+ async function detectRuntimeOwnerHost(
118
+ deps: RuntimeOwnerResolverDeps,
119
+ ): Promise<string | null> {
120
+ const readHostEnv = deps.getHostEnv ?? getHostEnv;
121
+ const explicitHost = readHostEnv("VERYFRONT_RUNTIME_OWNER_HOST") ??
122
+ readHostEnv("POD_IP") ??
123
+ null;
124
+
125
+ if (explicitHost?.trim()) {
126
+ return explicitHost.trim();
127
+ }
128
+
129
+ const denoInterfaces = getDenoNetworkInterfacesSafe(deps);
130
+ const denoHost = selectNetworkIpv4Address(denoInterfaces);
131
+ if (denoHost) {
132
+ return denoHost;
133
+ }
134
+
135
+ try {
136
+ const nodeOs = await (deps.dynamicImport ?? dynamicImport)<NodeOsModule>("node:os");
137
+ const nodeInterfaces = Object.values(nodeOs.networkInterfaces()).flatMap(
138
+ (entries) => entries ?? [],
139
+ );
140
+ const nodeHost = selectNetworkIpv4Address(nodeInterfaces);
141
+ if (nodeHost) {
142
+ return nodeHost;
143
+ }
144
+ } catch {
145
+ // Ignore runtime-specific interface lookup failures and fall through.
146
+ }
147
+
148
+ return null;
149
+ }
150
+
151
+ function resolveRuntimeOwnerPort(
152
+ req: dntShim.Request,
153
+ deps: RuntimeOwnerResolverDeps,
154
+ ): string | null {
155
+ const readHostEnv = deps.getHostEnv ?? getHostEnv;
156
+
157
+ return (
158
+ parsePort(readHostEnv("VERYFRONT_RUNTIME_OWNER_PORT")) ??
159
+ parsePort(readHostEnv("VERYFRONT_SERVER_PORT")) ??
160
+ parsePort(new URL(req.url).port) ??
161
+ parsePort(readHostEnv("PORT")) ??
162
+ (isTruthyEnv(readHostEnv("PROXY_MODE")) ? DEFAULT_PROXY_RUNTIME_PORT : null)
163
+ );
164
+ }
165
+
166
+ function buildRuntimeOwnerInvokeUrl(host: string, port: string | null): string | null {
167
+ try {
168
+ const url = new URL("http://127.0.0.1/channels/invoke");
169
+ url.hostname = host;
170
+ if (port) {
171
+ url.port = port;
172
+ }
173
+ return url.toString();
174
+ } catch {
175
+ return null;
176
+ }
177
+ }
178
+
179
+ export async function resolveRuntimeOwnerInvokeUrl(
180
+ req: dntShim.Request,
181
+ deps: RuntimeOwnerResolverDeps = {},
182
+ ): Promise<string | null> {
183
+ const host = await detectRuntimeOwnerHost(deps);
184
+ if (!host) {
185
+ return null;
186
+ }
187
+
188
+ return buildRuntimeOwnerInvokeUrl(host, resolveRuntimeOwnerPort(req, deps));
189
+ }
@@ -6,6 +6,10 @@ import {
6
6
  createRuntimeAgentStreamResponse,
7
7
  type RuntimeAgentStreamExecutionDeps,
8
8
  } from "../../../internal-agents/run-stream.js";
9
+ import {
10
+ resolveRuntimeOwnerInvokeUrl,
11
+ RUNTIME_OWNER_INVOKE_URL_HEADER,
12
+ } from "../../../internal-agents/runtime-owner.js";
9
13
  import {
10
14
  ControlPlaneRequestError,
11
15
  verifyControlPlaneRequest,
@@ -29,11 +33,14 @@ import { PRIORITY_MEDIUM_API } from "../../../utils/constants/index.js";
29
33
  import { getHostEnv } from "../../../platform/compat/process.js";
30
34
 
31
35
  export interface AgentStreamHandlerDeps
32
- extends RuntimeAgentDiscoveryDeps, RuntimeAgentStreamExecutionDeps {}
36
+ extends RuntimeAgentDiscoveryDeps, RuntimeAgentStreamExecutionDeps {
37
+ resolveRuntimeOwnerInvokeUrl?: typeof resolveRuntimeOwnerInvokeUrl;
38
+ }
33
39
 
34
40
  const defaultDeps: AgentStreamHandlerDeps = {
35
41
  ...defaultChannelInvokeDeps,
36
42
  sessionManager: agentRunSessionManager,
43
+ resolveRuntimeOwnerInvokeUrl,
37
44
  };
38
45
 
39
46
  type SourceContextFsWrapper = {
@@ -93,6 +100,16 @@ function applyBuilderHeaders(target: dntShim.Response, source: dntShim.Headers):
93
100
  });
94
101
  }
95
102
 
103
+ function setResponseHeader(target: dntShim.Response, key: string, value: string): dntShim.Response {
104
+ const headers = new dntShim.Headers(target.headers);
105
+ headers.set(key, value);
106
+ return new dntShim.Response(target.body, {
107
+ status: target.status,
108
+ statusText: target.statusText,
109
+ headers,
110
+ });
111
+ }
112
+
96
113
  export class AgentStreamHandler extends BaseHandler {
97
114
  metadata: HandlerMetadata = {
98
115
  name: "AgentStreamHandler",
@@ -165,7 +182,16 @@ export class AgentStreamHandler extends BaseHandler {
165
182
  agent as Agent,
166
183
  this.deps,
167
184
  );
168
- return this.respond(applyBuilderHeaders(response, builder.headers));
185
+ const runtimeOwnerInvokeUrl = await this.deps.resolveRuntimeOwnerInvokeUrl?.(req) ??
186
+ null;
187
+ const responseWithOwner = runtimeOwnerInvokeUrl
188
+ ? setResponseHeader(
189
+ response,
190
+ RUNTIME_OWNER_INVOKE_URL_HEADER,
191
+ runtimeOwnerInvokeUrl,
192
+ )
193
+ : response;
194
+ return this.respond(applyBuilderHeaders(responseWithOwner, builder.headers));
169
195
  },
170
196
  );
171
197
  } catch (error) {
@@ -3,7 +3,7 @@ import { getEnv } from "../platform/compat/process.js";
3
3
 
4
4
  // Keep in sync with deno.json version.
5
5
  // scripts/release.ts updates this constant during releases.
6
- export const VERSION = "0.1.102";
6
+ export const VERSION = "0.1.104";
7
7
 
8
8
  export function normalizeVeryfrontVersion(version: string | undefined): string | undefined {
9
9
  if (!version) return undefined;