veryfront 0.1.103 → 0.1.105

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 (33) 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 +70 -33
  8. package/esm/src/agent/runtime/model-resolution.d.ts +11 -0
  9. package/esm/src/agent/runtime/model-resolution.d.ts.map +1 -1
  10. package/esm/src/agent/runtime/model-resolution.js +48 -0
  11. package/esm/src/agent/runtime/sse-utils.d.ts +1 -0
  12. package/esm/src/agent/runtime/sse-utils.d.ts.map +1 -1
  13. package/esm/src/agent/runtime/sse-utils.js +15 -0
  14. package/esm/src/internal-agents/run-stream.d.ts +1 -1
  15. package/esm/src/internal-agents/run-stream.d.ts.map +1 -1
  16. package/esm/src/internal-agents/run-stream.js +1 -1
  17. package/esm/src/proxy/handler.d.ts.map +1 -1
  18. package/esm/src/proxy/handler.js +13 -3
  19. package/esm/src/server/handlers/request/api/project-discovery.d.ts.map +1 -1
  20. package/esm/src/server/handlers/request/api/project-discovery.js +4 -1
  21. package/esm/src/utils/version.d.ts +2 -2
  22. package/esm/src/utils/version.d.ts.map +1 -1
  23. package/esm/src/utils/version.js +3 -3
  24. package/package.json +1 -1
  25. package/src/deno.js +1 -1
  26. package/src/src/agent/runtime/ai-stream-handler.ts +25 -0
  27. package/src/src/agent/runtime/index.ts +81 -33
  28. package/src/src/agent/runtime/model-resolution.ts +62 -0
  29. package/src/src/agent/runtime/sse-utils.ts +17 -0
  30. package/src/src/internal-agents/run-stream.ts +6 -0
  31. package/src/src/proxy/handler.ts +16 -4
  32. package/src/src/server/handlers/request/api/project-discovery.ts +4 -1
  33. package/src/src/utils/version.ts +3 -3
package/esm/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.103",
3
+ "version": "0.1.105",
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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"}
@@ -11,7 +11,7 @@
11
11
  * @module ai/agent/runtime
12
12
  */
13
13
  import { getTextFromParts, } from "../types.js";
14
- import { ensureModelReady, findAvailableCloudModel, resolveModel } from "../../provider/index.js";
14
+ import { ensureModelReady, resolveModel } from "../../provider/index.js";
15
15
  import { generateId } from "../../utils/id.js";
16
16
  import { detectPlatform, getPlatformCapabilities } from "../../platform/core-platform.js";
17
17
  import { createMemory } from "../memory/index.js";
@@ -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
- import { resolveConfiguredAgentModel } from "./model-resolution.js";
37
+ import { resolveConfiguredAgentModel, resolveRuntimeModel } 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.`;
@@ -88,23 +105,6 @@ export function enforceSkillPolicy(toolName, activeSkillPolicy, mustLoadSkillFir
88
105
  }
89
106
  return { allowed: true };
90
107
  }
91
- /**
92
- * Auto-upgrade a local model string to a cloud provider when API keys are available.
93
- *
94
- * Returns the upgraded "provider/model" string, or the original string unchanged
95
- * if no cloud provider is available. This keeps resolveModel as a pure resolver
96
- * while the runtime owns the upgrade policy.
97
- */
98
- function maybeUpgradeLocalModel(modelString) {
99
- if (!modelString.startsWith("local/"))
100
- return modelString;
101
- const cloud = findAvailableCloudModel();
102
- if (cloud) {
103
- logger.info(`⚡ Cloud AI API key found — using "${cloud}" instead of local model.`);
104
- return cloud;
105
- }
106
- return modelString;
107
- }
108
108
  /**
109
109
  * Check whether a resolved LanguageModel is a local inference model.
110
110
  * Checks the model object properties rather than the requested string,
@@ -133,7 +133,10 @@ export class AgentRuntime {
133
133
  */
134
134
  async generate(input, context, modelOverride, maxOutputTokensOverride) {
135
135
  const requestedModel = resolveConfiguredAgentModel(modelOverride || this.config.model);
136
- const resolvedModelString = maybeUpgradeLocalModel(requestedModel);
136
+ const resolvedModelString = resolveRuntimeModel(modelOverride || this.config.model);
137
+ if (resolvedModelString !== requestedModel) {
138
+ logger.info(`⚡ Using runtime model "${resolvedModelString}" instead of "${requestedModel}".`);
139
+ }
137
140
  return withSpan("agent.generate", async (span) => {
138
141
  setSpanAttributes(span, {
139
142
  "agent.id": this.id,
@@ -159,16 +162,31 @@ export class AgentRuntime {
159
162
  * Stream a response
160
163
  * Returns a ReadableStream in the veryfront stream event format.
161
164
  */
162
- async stream(messages, context, callbacks, modelOverride, maxOutputTokensOverride) {
165
+ async stream(messages, context, callbacks, modelOverride, maxOutputTokensOverride, abortSignal) {
163
166
  const requestedModel = resolveConfiguredAgentModel(modelOverride || this.config.model);
164
- // Auto-upgrade local/* to a cloud provider when API keys are available.
165
- const resolvedModelString = maybeUpgradeLocalModel(requestedModel);
167
+ const resolvedModelString = resolveRuntimeModel(modelOverride || this.config.model);
168
+ if (resolvedModelString !== requestedModel) {
169
+ logger.info(`⚡ Using runtime model "${resolvedModelString}" instead of "${requestedModel}".`);
170
+ }
166
171
  for (const msg of messages)
167
172
  await this.memory.add(msg);
168
173
  const memoryMessages = await this.memory.getMessages();
169
174
  const systemPrompt = await this.resolveSystemPrompt();
170
175
  const encoder = new TextEncoder();
171
- const toolContext = { agentId: this.id, ...context };
176
+ const streamAbortController = new AbortController();
177
+ const forwardAbort = () => {
178
+ streamAbortController.abort(abortSignal?.reason);
179
+ };
180
+ if (abortSignal) {
181
+ if (abortSignal.aborted) {
182
+ streamAbortController.abort(abortSignal.reason);
183
+ }
184
+ else {
185
+ abortSignal.addEventListener("abort", forwardAbort, { once: true });
186
+ }
187
+ }
188
+ const streamAbortSignal = streamAbortController.signal;
189
+ const toolContext = { agentId: this.id, abortSignal: streamAbortSignal, ...context };
172
190
  const textPartId = generateId("text");
173
191
  // Resolve model BEFORE creating the ReadableStream — if this throws
174
192
  // (e.g., no_ai_available), the error propagates to the caller who can
@@ -186,6 +204,7 @@ export class AgentRuntime {
186
204
  return new ReadableStream({
187
205
  start: async (controller) => {
188
206
  try {
207
+ throwIfAborted(streamAbortSignal);
189
208
  this.status = "streaming";
190
209
  const messageId = generateMessageId();
191
210
  sendSSE(controller, encoder, { type: "message-start", messageId });
@@ -203,22 +222,34 @@ export class AgentRuntime {
203
222
  },
204
223
  });
205
224
  sendSSE(controller, encoder, { type: "text-start", id: textPartId });
206
- const response = await this.executeAgentLoopStreaming(systemPrompt, memoryMessages, controller, encoder, callbacks, textPartId, toolContext, resolvedModelString, languageModel, maxOutputTokensOverride);
225
+ const response = await this.executeAgentLoopStreaming(systemPrompt, memoryMessages, controller, encoder, callbacks, textPartId, toolContext, resolvedModelString, languageModel, maxOutputTokensOverride, streamAbortSignal);
226
+ throwIfAborted(streamAbortSignal);
207
227
  callbacks?.onFinish?.(response);
228
+ throwIfAborted(streamAbortSignal);
208
229
  sendSSE(controller, encoder, { type: "text-end", id: textPartId });
209
230
  sendSSE(controller, encoder, { type: "message-finish" });
210
- controller.close();
231
+ closeSSEStream(controller);
211
232
  }
212
233
  catch (error) {
234
+ if (isAbortError(error, streamAbortSignal)) {
235
+ closeSSEStream(controller);
236
+ return;
237
+ }
213
238
  this.status = "error";
214
239
  logger.error("Agent stream error", { error });
215
240
  sendSSE(controller, encoder, {
216
241
  type: "error",
217
242
  error: "An internal error occurred",
218
243
  });
219
- controller.close();
244
+ closeSSEStream(controller);
245
+ }
246
+ finally {
247
+ abortSignal?.removeEventListener("abort", forwardAbort);
220
248
  }
221
249
  },
250
+ cancel(reason) {
251
+ streamAbortController.abort(reason);
252
+ },
222
253
  });
223
254
  }
224
255
  /**
@@ -228,7 +259,7 @@ export class AgentRuntime {
228
259
  return withSpan("agent.execution_loop", async (loopSpan) => {
229
260
  const { maxAgentSteps } = getPlatformCapabilities();
230
261
  const maxSteps = this.computeMaxSteps(maxAgentSteps);
231
- const effectiveModel = resolveConfiguredAgentModel(modelString || this.config.model);
262
+ const effectiveModel = resolveRuntimeModel(modelString || this.config.model);
232
263
  const languageModel = resolveModel(effectiveModel);
233
264
  const toolCalls = [];
234
265
  const currentMessages = [...messages];
@@ -414,10 +445,10 @@ export class AgentRuntime {
414
445
  * Emits veryfront stream events (message-start/message-finish + step-start/step-end)
415
446
  * while consuming AI SDK `streamText()` parts internally.
416
447
  */
417
- async executeAgentLoopStreaming(systemPrompt, messages, controller, encoder, callbacks, textPartId, toolContext, modelString, resolvedModel, maxOutputTokensOverride) {
448
+ async executeAgentLoopStreaming(systemPrompt, messages, controller, encoder, callbacks, textPartId, toolContext, modelString, resolvedModel, maxOutputTokensOverride, abortSignal) {
418
449
  const { maxAgentSteps } = getPlatformCapabilities();
419
450
  const maxSteps = this.computeMaxSteps(maxAgentSteps);
420
- const effectiveModel = resolveConfiguredAgentModel(modelString || this.config.model);
451
+ const effectiveModel = resolveRuntimeModel(modelString || this.config.model);
421
452
  const languageModel = resolvedModel ?? resolveModel(effectiveModel);
422
453
  const toolCalls = [];
423
454
  const currentMessages = [...messages];
@@ -434,6 +465,7 @@ export class AgentRuntime {
434
465
  let activeSkillPolicy;
435
466
  let finalFinishReason;
436
467
  for (let step = 0; step < maxSteps; step++) {
468
+ throwIfAborted(abortSignal);
437
469
  sendSSE(controller, encoder, { type: "step-start" });
438
470
  let tools = isLocalStreaming ? [] : getAvailableTools(this.config.tools, {
439
471
  includeSkillTools: Boolean(this.config.skills),
@@ -449,12 +481,14 @@ export class AgentRuntime {
449
481
  tools: convertToolsToAISDK(tools),
450
482
  maxOutputTokens: this.resolveMaxOutputTokens(maxOutputTokensOverride),
451
483
  temperature: DEFAULT_TEMPERATURE,
484
+ abortSignal,
452
485
  });
453
486
  const state = createStreamState();
454
487
  await processStream(result, state, controller, encoder, textPartId, {
455
488
  onChunk: callbacks?.onChunk,
456
489
  onUsage: (usage) => accumulateUsage(totalUsage, usage),
457
- });
490
+ }, abortSignal);
491
+ throwIfAborted(abortSignal);
458
492
  finalFinishReason = state.finishReason ?? finalFinishReason;
459
493
  const streamParts = [];
460
494
  if (state.accumulatedText)
@@ -492,6 +526,7 @@ export class AgentRuntime {
492
526
  Boolean(this.config.skills) &&
493
527
  streamedToolCalls.some((tc) => tc.name === LOAD_SKILL_TOOL_ID);
494
528
  for (const tc of streamedToolCalls) {
529
+ throwIfAborted(abortSignal);
495
530
  const { args, error: argError } = parseToolArgs(tc.arguments);
496
531
  const toolCall = { id: tc.id, name: tc.name, args, status: "pending" };
497
532
  if (argError) {
@@ -523,6 +558,7 @@ export class AgentRuntime {
523
558
  toolCallId: tc.id,
524
559
  ...toolContext,
525
560
  });
561
+ throwIfAborted(abortSignal);
526
562
  toolCall.status = "completed";
527
563
  toolCall.result = result;
528
564
  toolCall.executionTime = Date.now() - startTime;
@@ -560,6 +596,7 @@ export class AgentRuntime {
560
596
  await this.recordToolError(toolCall, errorStr, controller, encoder, currentMessages, toolCalls);
561
597
  }
562
598
  }
599
+ throwIfAborted(abortSignal);
563
600
  sendSSE(controller, encoder, { type: "step-end" });
564
601
  this.status = "thinking";
565
602
  }
@@ -1,4 +1,15 @@
1
1
  export declare const AUTO_AGENT_MODEL = "auto";
2
2
  export declare function normalizeAgentModelConfig(model?: string): string;
3
3
  export declare function resolveConfiguredAgentModel(model?: string): string;
4
+ /**
5
+ * Resolve the effective runtime model string for agent execution.
6
+ *
7
+ * Runtime-only rewrites happen here:
8
+ * - `auto` still defaults to `local/*`
9
+ * - `local/*` upgrades to the first available cloud model when bootstrap exists
10
+ * - explicit hosted-provider models (`openai/*`, `anthropic/*`, `google/*`)
11
+ * transparently route through `veryfront-cloud/*` when the runtime has
12
+ * request-scoped Veryfront bootstrap but no direct provider API key
13
+ */
14
+ export declare function resolveRuntimeModel(model?: string): string;
4
15
  //# sourceMappingURL=model-resolution.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"model-resolution.d.ts","sourceRoot":"","sources":["../../../../src/src/agent/runtime/model-resolution.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,gBAAgB,SAAS,CAAC;AAEvC,wBAAgB,yBAAyB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAGhE;AAED,wBAAgB,2BAA2B,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAGlE"}
1
+ {"version":3,"file":"model-resolution.d.ts","sourceRoot":"","sources":["../../../../src/src/agent/runtime/model-resolution.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,gBAAgB,SAAS,CAAC;AAIvC,wBAAgB,yBAAyB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAGhE;AAED,wBAAgB,2BAA2B,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAGlE;AAeD;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CA4B1D"}
@@ -1,5 +1,9 @@
1
+ import { getAnthropicEnvConfig, getGoogleGenAIEnvConfig, getOpenAIEnvConfig, } from "../../config/env.js";
2
+ import { findAvailableCloudModel } from "../../provider/index.js";
1
3
  import { DEFAULT_LOCAL_MODEL } from "../../provider/local/model-catalog.js";
4
+ import { isVeryfrontCloudEnabled } from "../../platform/cloud/resolver.js";
2
5
  export const AUTO_AGENT_MODEL = "auto";
6
+ const HOSTED_PROVIDER_NAMES = new Set(["anthropic", "google", "openai"]);
3
7
  export function normalizeAgentModelConfig(model) {
4
8
  const normalized = model?.trim();
5
9
  return normalized && normalized.length > 0 ? normalized : AUTO_AGENT_MODEL;
@@ -8,3 +12,47 @@ export function resolveConfiguredAgentModel(model) {
8
12
  const normalized = normalizeAgentModelConfig(model);
9
13
  return normalized === AUTO_AGENT_MODEL ? `local/${DEFAULT_LOCAL_MODEL}` : normalized;
10
14
  }
15
+ function hasDirectProviderCredentials(provider) {
16
+ switch (provider) {
17
+ case "anthropic":
18
+ return Boolean(getAnthropicEnvConfig().apiKey);
19
+ case "google":
20
+ return Boolean(getGoogleGenAIEnvConfig().apiKey);
21
+ case "openai":
22
+ return Boolean(getOpenAIEnvConfig().apiKey);
23
+ default:
24
+ return false;
25
+ }
26
+ }
27
+ /**
28
+ * Resolve the effective runtime model string for agent execution.
29
+ *
30
+ * Runtime-only rewrites happen here:
31
+ * - `auto` still defaults to `local/*`
32
+ * - `local/*` upgrades to the first available cloud model when bootstrap exists
33
+ * - explicit hosted-provider models (`openai/*`, `anthropic/*`, `google/*`)
34
+ * transparently route through `veryfront-cloud/*` when the runtime has
35
+ * request-scoped Veryfront bootstrap but no direct provider API key
36
+ */
37
+ export function resolveRuntimeModel(model) {
38
+ const configuredModel = resolveConfiguredAgentModel(model);
39
+ if (configuredModel.startsWith("veryfront-cloud/")) {
40
+ return configuredModel;
41
+ }
42
+ if (configuredModel.startsWith("local/")) {
43
+ return findAvailableCloudModel() ?? configuredModel;
44
+ }
45
+ const slashIndex = configuredModel.indexOf("/");
46
+ if (slashIndex === -1) {
47
+ return configuredModel;
48
+ }
49
+ const provider = configuredModel.slice(0, slashIndex);
50
+ const modelId = configuredModel.slice(slashIndex + 1);
51
+ if (!HOSTED_PROVIDER_NAMES.has(provider) || !modelId) {
52
+ return configuredModel;
53
+ }
54
+ if (!isVeryfrontCloudEnabled() || hasDirectProviderCredentials(provider)) {
55
+ return configuredModel;
56
+ }
57
+ return `veryfront-cloud/${provider}/${modelId}`;
58
+ }
@@ -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);
@@ -1 +1 @@
1
- {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/src/proxy/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,qBAAqB,CAAC;AAE/C,OAAO,EAAE,KAAK,YAAY,EAAsB,MAAM,kCAAkC,CAAC;AACzF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAQnD,eAAO,MAAM,sBAAsB,0MAYzB,CAAC;AAgIX,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,SAAS,GAAG,YAAY,CAAC;IACtC,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,YAAY,CAAC;IAC3B,cAAc,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC9D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7D,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CAC9E;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAoFD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB;0BA6L1B,OAAO,CAAC,OAAO,KAAG,OAAO,CAAC,YAAY,CAAC;0BA2QvC,OAAO,CAAC,OAAO,KAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;;;;;;;;0BA7YrD,MAAM,EAAE;;EA2bpC;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEjE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAwB7F"}
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/src/proxy/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,qBAAqB,CAAC;AAE/C,OAAO,EAAE,KAAK,YAAY,EAAsB,MAAM,kCAAkC,CAAC;AACzF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAQnD,eAAO,MAAM,sBAAsB,0MAYzB,CAAC;AAmIX,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,SAAS,GAAG,YAAY,CAAC;IACtC,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,YAAY,CAAC;IAC3B,cAAc,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC9D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7D,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CAC9E;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAoFD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB;0BAsM1B,OAAO,CAAC,OAAO,KAAG,OAAO,CAAC,YAAY,CAAC;0BA2QvC,OAAO,CAAC,OAAO,KAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;;;;;;;;0BAtZrD,MAAM,EAAE;;EAocpC;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEjE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAwB7F"}
@@ -47,10 +47,13 @@ function getApiJwks(apiBaseUrl, logger) {
47
47
  const normalizedBaseUrl = apiBaseUrl.endsWith("/") ? apiBaseUrl : `${apiBaseUrl}/`;
48
48
  const jwksUrl = new URL(".well-known/jwks.json", normalizedBaseUrl);
49
49
  const cacheKey = jwksUrl.toString();
50
+ // Lazily initialize and cache JWKS in a single, idempotent step to avoid
51
+ // unsynchronized read/then-write on the shared Map across concurrent calls.
50
52
  let jwks = remoteJwksByUrl.get(cacheKey);
51
53
  if (!jwks) {
52
- jwks = createRemoteJWKSet(jwksUrl);
53
- remoteJwksByUrl.set(cacheKey, jwks);
54
+ const created = createRemoteJWKSet(jwksUrl);
55
+ remoteJwksByUrl.set(cacheKey, created);
56
+ jwks = created;
54
57
  }
55
58
  return jwks;
56
59
  }
@@ -246,7 +249,14 @@ export function createProxyHandler(options) {
246
249
  const url = new URL(req.url);
247
250
  // Collapse leading slashes to prevent protocol-relative open redirects (e.g. "//evil.com/path")
248
251
  const safePath = url.pathname.replace(/^\/\/+/, "/");
249
- const returnPath = safePath + url.search;
252
+ let returnPath = safePath + url.search;
253
+ // Ensure the return path stays within the application and is not an absolute URL.
254
+ // - It must start with "/".
255
+ // - It must not contain a scheme delimiter ("://").
256
+ // If it fails validation, fall back to the root path.
257
+ if (!returnPath.startsWith("/") || returnPath.includes("://")) {
258
+ returnPath = "/";
259
+ }
250
260
  return `https://veryfront.com/sign-in?from=${encodeURIComponent(returnPath)}`;
251
261
  }
252
262
  async function checkProtectedAccess(req, matchingEnv, userToken, users, logContext) {
@@ -1 +1 @@
1
- {"version":3,"file":"project-discovery.d.ts","sourceRoot":"","sources":["../../../../../../src/src/server/handlers/request/api/project-discovery.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AA8CrD;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA+D/E"}
1
+ {"version":3,"file":"project-discovery.d.ts","sourceRoot":"","sources":["../../../../../../src/src/server/handlers/request/api/project-discovery.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AA8CrD;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAkE/E"}
@@ -97,7 +97,10 @@ export async function ensureProjectDiscovery(ctx) {
97
97
  }
98
98
  finally {
99
99
  if (!cacheCompletedDiscovery) {
100
- discoveredProjects.delete(key);
100
+ const current = discoveredProjects.get(key);
101
+ if (current === promise) {
102
+ discoveredProjects.delete(key);
103
+ }
101
104
  }
102
105
  }
103
106
  }
@@ -1,4 +1,4 @@
1
- export declare const VERSION = "0.1.103";
1
+ export declare const VERSION = "0.1.105";
2
2
  export declare function normalizeVeryfrontVersion(version: string | undefined): string | undefined;
3
3
  export declare function resolveRuntimeVersion(options?: {
4
4
  veryfrontVersion?: string;
@@ -13,5 +13,5 @@ export interface BuildVersion {
13
13
  serverStart: number;
14
14
  projectUpdated?: string;
15
15
  }
16
- export declare function createBuildVersion(projectUpdatedAt?: string): BuildVersion;
16
+ export declare function createBuildVersion(projectUpdated?: string): BuildVersion;
17
17
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../../src/src/utils/version.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,OAAO,YAAY,CAAC;AAEjC,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGzF;AAUD,wBAAgB,qBAAqB,CAAC,OAAO,GAAE;IAC7C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,MAAM,CAKd;AAED,eAAO,MAAM,eAAe,QAK1B,CAAC;AAEH,eAAO,MAAM,iBAAiB,EAAE,MAAmB,CAAC;AAEpD,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,kBAAkB,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,YAAY,CAM1E"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../../src/src/utils/version.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,OAAO,YAAY,CAAC;AAEjC,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGzF;AAUD,wBAAgB,qBAAqB,CAAC,OAAO,GAAE;IAC7C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,MAAM,CAKd;AAED,eAAO,MAAM,eAAe,QAK1B,CAAC;AAEH,eAAO,MAAM,iBAAiB,EAAE,MAAmB,CAAC;AAEpD,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,kBAAkB,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,YAAY,CAMxE"}
@@ -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.103";
5
+ export const VERSION = "0.1.105";
6
6
  export function normalizeVeryfrontVersion(version) {
7
7
  if (!version)
8
8
  return undefined;
@@ -29,10 +29,10 @@ export const RUNTIME_VERSION = resolveRuntimeVersion({
29
29
  fallbackVersion: VERSION,
30
30
  });
31
31
  export const SERVER_START_TIME = Date.now();
32
- export function createBuildVersion(projectUpdatedAt) {
32
+ export function createBuildVersion(projectUpdated) {
33
33
  return {
34
34
  framework: RUNTIME_VERSION,
35
35
  serverStart: SERVER_START_TIME,
36
- projectUpdated: projectUpdatedAt,
36
+ projectUpdated,
37
37
  };
38
38
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.103",
3
+ "version": "0.1.105",
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.103",
3
+ "version": "0.1.105",
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,
@@ -21,7 +21,7 @@ import {
21
21
  type MessagePart,
22
22
  type ToolCall,
23
23
  } from "../types.js";
24
- import { ensureModelReady, findAvailableCloudModel, resolveModel } from "../../provider/index.js";
24
+ import { ensureModelReady, resolveModel } from "../../provider/index.js";
25
25
  import { generateId } from "../../utils/id.js";
26
26
  import { detectPlatform, getPlatformCapabilities } from "../../platform/core-platform.js";
27
27
  import { createMemory, type Memory } from "../memory/index.js";
@@ -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,
@@ -71,11 +71,36 @@ import {
71
71
  isToolAllowedBySkill,
72
72
  validateAllowedToolPatterns,
73
73
  } from "../../skill/allowed-tools.js";
74
- import { resolveConfiguredAgentModel } from "./model-resolution.js";
74
+ import { resolveConfiguredAgentModel, resolveRuntimeModel } from "./model-resolution.js";
75
75
 
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.`;
@@ -149,26 +174,6 @@ export function enforceSkillPolicy(
149
174
  return { allowed: true };
150
175
  }
151
176
 
152
- /**
153
- * Auto-upgrade a local model string to a cloud provider when API keys are available.
154
- *
155
- * Returns the upgraded "provider/model" string, or the original string unchanged
156
- * if no cloud provider is available. This keeps resolveModel as a pure resolver
157
- * while the runtime owns the upgrade policy.
158
- */
159
- function maybeUpgradeLocalModel(modelString: string): string {
160
- if (!modelString.startsWith("local/")) return modelString;
161
-
162
- const cloud = findAvailableCloudModel();
163
- if (cloud) {
164
- logger.info(
165
- `⚡ Cloud AI API key found — using "${cloud}" instead of local model.`,
166
- );
167
- return cloud;
168
- }
169
- return modelString;
170
- }
171
-
172
177
  /**
173
178
  * Check whether a resolved LanguageModel is a local inference model.
174
179
  * Checks the model object properties rather than the requested string,
@@ -206,7 +211,12 @@ export class AgentRuntime {
206
211
  maxOutputTokensOverride?: number,
207
212
  ): Promise<AgentResponse> {
208
213
  const requestedModel = resolveConfiguredAgentModel(modelOverride || this.config.model);
209
- const resolvedModelString = maybeUpgradeLocalModel(requestedModel);
214
+ const resolvedModelString = resolveRuntimeModel(modelOverride || this.config.model);
215
+ if (resolvedModelString !== requestedModel) {
216
+ logger.info(
217
+ `⚡ Using runtime model "${resolvedModelString}" instead of "${requestedModel}".`,
218
+ );
219
+ }
210
220
 
211
221
  return withSpan("agent.generate", async (span) => {
212
222
  setSpanAttributes(span, {
@@ -256,10 +266,15 @@ export class AgentRuntime {
256
266
  },
257
267
  modelOverride?: string,
258
268
  maxOutputTokensOverride?: number,
269
+ abortSignal?: AbortSignal,
259
270
  ): Promise<ReadableStream<Uint8Array>> {
260
271
  const requestedModel = resolveConfiguredAgentModel(modelOverride || this.config.model);
261
- // Auto-upgrade local/* to a cloud provider when API keys are available.
262
- const resolvedModelString = maybeUpgradeLocalModel(requestedModel);
272
+ const resolvedModelString = resolveRuntimeModel(modelOverride || this.config.model);
273
+ if (resolvedModelString !== requestedModel) {
274
+ logger.info(
275
+ `⚡ Using runtime model "${resolvedModelString}" instead of "${requestedModel}".`,
276
+ );
277
+ }
263
278
 
264
279
  for (const msg of messages) await this.memory.add(msg);
265
280
 
@@ -267,7 +282,19 @@ export class AgentRuntime {
267
282
  const systemPrompt = await this.resolveSystemPrompt();
268
283
 
269
284
  const encoder = new TextEncoder();
270
- const toolContext = { agentId: this.id, ...context };
285
+ const streamAbortController = new AbortController();
286
+ const forwardAbort = () => {
287
+ streamAbortController.abort(abortSignal?.reason);
288
+ };
289
+ if (abortSignal) {
290
+ if (abortSignal.aborted) {
291
+ streamAbortController.abort(abortSignal.reason);
292
+ } else {
293
+ abortSignal.addEventListener("abort", forwardAbort, { once: true });
294
+ }
295
+ }
296
+ const streamAbortSignal = streamAbortController.signal;
297
+ const toolContext = { agentId: this.id, abortSignal: streamAbortSignal, ...context };
271
298
  const textPartId = generateId("text");
272
299
 
273
300
  // Resolve model BEFORE creating the ReadableStream — if this throws
@@ -289,6 +316,7 @@ export class AgentRuntime {
289
316
  return new ReadableStream<Uint8Array>({
290
317
  start: async (controller) => {
291
318
  try {
319
+ throwIfAborted(streamAbortSignal);
292
320
  this.status = "streaming";
293
321
 
294
322
  const messageId = generateMessageId();
@@ -319,22 +347,35 @@ export class AgentRuntime {
319
347
  resolvedModelString,
320
348
  languageModel,
321
349
  maxOutputTokensOverride,
350
+ streamAbortSignal,
322
351
  );
352
+ throwIfAborted(streamAbortSignal);
323
353
  callbacks?.onFinish?.(response);
354
+ throwIfAborted(streamAbortSignal);
324
355
 
325
356
  sendSSE(controller, encoder, { type: "text-end", id: textPartId });
326
357
  sendSSE(controller, encoder, { type: "message-finish" });
327
- controller.close();
358
+ closeSSEStream(controller);
328
359
  } catch (error) {
360
+ if (isAbortError(error, streamAbortSignal)) {
361
+ closeSSEStream(controller);
362
+ return;
363
+ }
364
+
329
365
  this.status = "error";
330
366
  logger.error("Agent stream error", { error });
331
367
  sendSSE(controller, encoder, {
332
368
  type: "error",
333
369
  error: "An internal error occurred",
334
370
  });
335
- controller.close();
371
+ closeSSEStream(controller);
372
+ } finally {
373
+ abortSignal?.removeEventListener("abort", forwardAbort);
336
374
  }
337
375
  },
376
+ cancel(reason) {
377
+ streamAbortController.abort(reason);
378
+ },
338
379
  });
339
380
  }
340
381
 
@@ -350,7 +391,7 @@ export class AgentRuntime {
350
391
  return withSpan("agent.execution_loop", async (loopSpan) => {
351
392
  const { maxAgentSteps } = getPlatformCapabilities();
352
393
  const maxSteps = this.computeMaxSteps(maxAgentSteps);
353
- const effectiveModel = resolveConfiguredAgentModel(modelString || this.config.model);
394
+ const effectiveModel = resolveRuntimeModel(modelString || this.config.model);
354
395
  const languageModel = resolveModel(effectiveModel);
355
396
 
356
397
  const toolCalls: ToolCall[] = [];
@@ -587,10 +628,11 @@ export class AgentRuntime {
587
628
  modelString?: string,
588
629
  resolvedModel?: LanguageModel,
589
630
  maxOutputTokensOverride?: number,
631
+ abortSignal?: AbortSignal,
590
632
  ): Promise<AgentResponse> {
591
633
  const { maxAgentSteps } = getPlatformCapabilities();
592
634
  const maxSteps = this.computeMaxSteps(maxAgentSteps);
593
- const effectiveModel = resolveConfiguredAgentModel(modelString || this.config.model);
635
+ const effectiveModel = resolveRuntimeModel(modelString || this.config.model);
594
636
  const languageModel = resolvedModel ?? resolveModel(effectiveModel);
595
637
 
596
638
  const toolCalls: ToolCall[] = [];
@@ -613,6 +655,7 @@ export class AgentRuntime {
613
655
  let finalFinishReason: string | undefined;
614
656
 
615
657
  for (let step = 0; step < maxSteps; step++) {
658
+ throwIfAborted(abortSignal);
616
659
  sendSSE(controller, encoder, { type: "step-start" });
617
660
 
618
661
  let tools = isLocalStreaming ? [] : getAvailableTools(this.config.tools, {
@@ -631,13 +674,15 @@ export class AgentRuntime {
631
674
  tools: convertToolsToAISDK(tools),
632
675
  maxOutputTokens: this.resolveMaxOutputTokens(maxOutputTokensOverride),
633
676
  temperature: DEFAULT_TEMPERATURE,
677
+ abortSignal,
634
678
  });
635
679
 
636
680
  const state = createStreamState();
637
681
  await processStream(result, state, controller, encoder, textPartId, {
638
682
  onChunk: callbacks?.onChunk,
639
683
  onUsage: (usage) => accumulateUsage(totalUsage, usage),
640
- });
684
+ }, abortSignal);
685
+ throwIfAborted(abortSignal);
641
686
  finalFinishReason = state.finishReason ?? finalFinishReason;
642
687
 
643
688
  const streamParts: MessagePart[] = [];
@@ -680,6 +725,7 @@ export class AgentRuntime {
680
725
  streamedToolCalls.some((tc) => tc.name === LOAD_SKILL_TOOL_ID);
681
726
 
682
727
  for (const tc of streamedToolCalls) {
728
+ throwIfAborted(abortSignal);
683
729
  const { args, error: argError } = parseToolArgs(tc.arguments);
684
730
  const toolCall: ToolCall = { id: tc.id, name: tc.name, args, status: "pending" };
685
731
 
@@ -737,6 +783,7 @@ export class AgentRuntime {
737
783
  ...toolContext,
738
784
  },
739
785
  );
786
+ throwIfAborted(abortSignal);
740
787
 
741
788
  toolCall.status = "completed";
742
789
  toolCall.result = result;
@@ -785,6 +832,7 @@ export class AgentRuntime {
785
832
  }
786
833
  }
787
834
 
835
+ throwIfAborted(abortSignal);
788
836
  sendSSE(controller, encoder, { type: "step-end" });
789
837
  this.status = "thinking";
790
838
  }
@@ -1,7 +1,16 @@
1
+ import {
2
+ getAnthropicEnvConfig,
3
+ getGoogleGenAIEnvConfig,
4
+ getOpenAIEnvConfig,
5
+ } from "../../config/env.js";
6
+ import { findAvailableCloudModel } from "../../provider/index.js";
1
7
  import { DEFAULT_LOCAL_MODEL } from "../../provider/local/model-catalog.js";
8
+ import { isVeryfrontCloudEnabled } from "../../platform/cloud/resolver.js";
2
9
 
3
10
  export const AUTO_AGENT_MODEL = "auto";
4
11
 
12
+ const HOSTED_PROVIDER_NAMES = new Set(["anthropic", "google", "openai"]);
13
+
5
14
  export function normalizeAgentModelConfig(model?: string): string {
6
15
  const normalized = model?.trim();
7
16
  return normalized && normalized.length > 0 ? normalized : AUTO_AGENT_MODEL;
@@ -11,3 +20,56 @@ export function resolveConfiguredAgentModel(model?: string): string {
11
20
  const normalized = normalizeAgentModelConfig(model);
12
21
  return normalized === AUTO_AGENT_MODEL ? `local/${DEFAULT_LOCAL_MODEL}` : normalized;
13
22
  }
23
+
24
+ function hasDirectProviderCredentials(provider: string): boolean {
25
+ switch (provider) {
26
+ case "anthropic":
27
+ return Boolean(getAnthropicEnvConfig().apiKey);
28
+ case "google":
29
+ return Boolean(getGoogleGenAIEnvConfig().apiKey);
30
+ case "openai":
31
+ return Boolean(getOpenAIEnvConfig().apiKey);
32
+ default:
33
+ return false;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Resolve the effective runtime model string for agent execution.
39
+ *
40
+ * Runtime-only rewrites happen here:
41
+ * - `auto` still defaults to `local/*`
42
+ * - `local/*` upgrades to the first available cloud model when bootstrap exists
43
+ * - explicit hosted-provider models (`openai/*`, `anthropic/*`, `google/*`)
44
+ * transparently route through `veryfront-cloud/*` when the runtime has
45
+ * request-scoped Veryfront bootstrap but no direct provider API key
46
+ */
47
+ export function resolveRuntimeModel(model?: string): string {
48
+ const configuredModel = resolveConfiguredAgentModel(model);
49
+
50
+ if (configuredModel.startsWith("veryfront-cloud/")) {
51
+ return configuredModel;
52
+ }
53
+
54
+ if (configuredModel.startsWith("local/")) {
55
+ return findAvailableCloudModel() ?? configuredModel;
56
+ }
57
+
58
+ const slashIndex = configuredModel.indexOf("/");
59
+ if (slashIndex === -1) {
60
+ return configuredModel;
61
+ }
62
+
63
+ const provider = configuredModel.slice(0, slashIndex);
64
+ const modelId = configuredModel.slice(slashIndex + 1);
65
+
66
+ if (!HOSTED_PROVIDER_NAMES.has(provider) || !modelId) {
67
+ return configuredModel;
68
+ }
69
+
70
+ if (!isVeryfrontCloudEnabled() || hasDirectProviderCredentials(provider)) {
71
+ return configuredModel;
72
+ }
73
+
74
+ return `veryfront-cloud/${provider}/${modelId}`;
75
+ }
@@ -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);
@@ -72,11 +72,14 @@ function getApiJwks(apiBaseUrl: string, logger?: ProxyLogger) {
72
72
  const normalizedBaseUrl = apiBaseUrl.endsWith("/") ? apiBaseUrl : `${apiBaseUrl}/`;
73
73
  const jwksUrl = new URL(".well-known/jwks.json", normalizedBaseUrl);
74
74
  const cacheKey = jwksUrl.toString();
75
- let jwks = remoteJwksByUrl.get(cacheKey);
76
75
 
76
+ // Lazily initialize and cache JWKS in a single, idempotent step to avoid
77
+ // unsynchronized read/then-write on the shared Map across concurrent calls.
78
+ let jwks = remoteJwksByUrl.get(cacheKey);
77
79
  if (!jwks) {
78
- jwks = createRemoteJWKSet(jwksUrl);
79
- remoteJwksByUrl.set(cacheKey, jwks);
80
+ const created = createRemoteJWKSet(jwksUrl);
81
+ remoteJwksByUrl.set(cacheKey, created);
82
+ jwks = created;
80
83
  }
81
84
 
82
85
  return jwks;
@@ -372,7 +375,16 @@ export function createProxyHandler(options: ProxyHandlerOptions) {
372
375
  const url = new URL(req.url);
373
376
  // Collapse leading slashes to prevent protocol-relative open redirects (e.g. "//evil.com/path")
374
377
  const safePath = url.pathname.replace(/^\/\/+/, "/");
375
- const returnPath = safePath + url.search;
378
+ let returnPath = safePath + url.search;
379
+
380
+ // Ensure the return path stays within the application and is not an absolute URL.
381
+ // - It must start with "/".
382
+ // - It must not contain a scheme delimiter ("://").
383
+ // If it fails validation, fall back to the root path.
384
+ if (!returnPath.startsWith("/") || returnPath.includes("://")) {
385
+ returnPath = "/";
386
+ }
387
+
376
388
  return `https://veryfront.com/sign-in?from=${encodeURIComponent(returnPath)}`;
377
389
  }
378
390
 
@@ -113,7 +113,10 @@ export async function ensureProjectDiscovery(ctx: HandlerContext): Promise<void>
113
113
  });
114
114
  } finally {
115
115
  if (!cacheCompletedDiscovery) {
116
- discoveredProjects.delete(key);
116
+ const current = discoveredProjects.get(key);
117
+ if (current === promise) {
118
+ discoveredProjects.delete(key);
119
+ }
117
120
  }
118
121
  }
119
122
  }
@@ -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.103";
6
+ export const VERSION = "0.1.105";
7
7
 
8
8
  export function normalizeVeryfrontVersion(version: string | undefined): string | undefined {
9
9
  if (!version) return undefined;
@@ -45,10 +45,10 @@ export interface BuildVersion {
45
45
  projectUpdated?: string;
46
46
  }
47
47
 
48
- export function createBuildVersion(projectUpdatedAt?: string): BuildVersion {
48
+ export function createBuildVersion(projectUpdated?: string): BuildVersion {
49
49
  return {
50
50
  framework: RUNTIME_VERSION,
51
51
  serverStart: SERVER_START_TIME,
52
- projectUpdated: projectUpdatedAt,
52
+ projectUpdated,
53
53
  };
54
54
  }