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.
- package/esm/deno.js +1 -1
- package/esm/src/agent/runtime/ai-stream-handler.d.ts +1 -1
- package/esm/src/agent/runtime/ai-stream-handler.d.ts.map +1 -1
- package/esm/src/agent/runtime/ai-stream-handler.js +16 -1
- package/esm/src/agent/runtime/index.d.ts +2 -2
- package/esm/src/agent/runtime/index.d.ts.map +1 -1
- package/esm/src/agent/runtime/index.js +70 -33
- package/esm/src/agent/runtime/model-resolution.d.ts +11 -0
- package/esm/src/agent/runtime/model-resolution.d.ts.map +1 -1
- package/esm/src/agent/runtime/model-resolution.js +48 -0
- package/esm/src/agent/runtime/sse-utils.d.ts +1 -0
- package/esm/src/agent/runtime/sse-utils.d.ts.map +1 -1
- package/esm/src/agent/runtime/sse-utils.js +15 -0
- package/esm/src/internal-agents/run-stream.d.ts +1 -1
- package/esm/src/internal-agents/run-stream.d.ts.map +1 -1
- package/esm/src/internal-agents/run-stream.js +1 -1
- package/esm/src/proxy/handler.d.ts.map +1 -1
- package/esm/src/proxy/handler.js +13 -3
- package/esm/src/server/handlers/request/api/project-discovery.d.ts.map +1 -1
- package/esm/src/server/handlers/request/api/project-discovery.js +4 -1
- package/esm/src/utils/version.d.ts +2 -2
- package/esm/src/utils/version.d.ts.map +1 -1
- package/esm/src/utils/version.js +3 -3
- package/package.json +1 -1
- package/src/deno.js +1 -1
- package/src/src/agent/runtime/ai-stream-handler.ts +25 -0
- package/src/src/agent/runtime/index.ts +81 -33
- package/src/src/agent/runtime/model-resolution.ts +62 -0
- package/src/src/agent/runtime/sse-utils.ts +17 -0
- package/src/src/internal-agents/run-stream.ts +6 -0
- package/src/src/proxy/handler.ts +16 -4
- package/src/src/server/handlers/request/api/project-discovery.ts +4 -1
- package/src/src/utils/version.ts +3 -3
package/esm/deno.js
CHANGED
|
@@ -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;
|
|
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":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,KAAK,WAAW,EAEhB,KAAK,aAAa,EAGlB,KAAK,OAAO,EAEZ,KAAK,QAAQ,EACd,MAAM,aAAa,CAAC;AAIrB,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAe/D,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/src/agent/runtime/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,KAAK,WAAW,EAEhB,KAAK,aAAa,EAGlB,KAAK,OAAO,EAEZ,KAAK,QAAQ,EACd,MAAM,aAAa,CAAC;AAIrB,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAe/D,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,aAAa,EACb,aAAa,GACd,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC1E,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAClG,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,gBAAgB,CAAC;AAmDxB;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,EAAE,GAAG,SAAS,CA6BxE;AAED,gEAAgE;AAChE,KAAK,iBAAiB,GAClB;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GACjB;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtC;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,iBAAiB,EAAE,MAAM,EAAE,GAAG,SAAS,EACvC,kBAAkB,EAAE,OAAO,GAC1B,iBAAiB,CAiBnB;AAcD,qBAAa,YAAY;IACvB,OAAO,CAAC,EAAE,CAAS;IACnB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,MAAM,CAAuB;gBAEzB,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW;IAS3C;;OAEG;IACG,QAAQ,CACZ,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE,EACzB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,aAAa,CAAC,EAAE,MAAM,EACtB,uBAAuB,CAAC,EAAE,MAAM,GAC/B,OAAO,CAAC,aAAa,CAAC;IA2CzB;;;OAGG;IACG,MAAM,CACV,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,SAAS,CAAC,EAAE;QACV,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;QAC1C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;QAClC,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAC;KAC9C,EACD,aAAa,CAAC,EAAE,MAAM,EACtB,uBAAuB,CAAC,EAAE,MAAM,EAChC,WAAW,CAAC,EAAE,WAAW,GACxB,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;IAgHtC;;OAEG;YACW,gBAAgB;IAkO9B;;;;OAIG;YACW,yBAAyB;IA2OvC;;OAEG;YACW,eAAe;IAqC7B;;OAEG;YACW,mBAAmB;IAOjC;;OAEG;IACH,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,sBAAsB;IAY9B;;OAEG;IACH,SAAS,IAAI,MAAM,CAAC,OAAO,CAAC;IAI5B;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC;QAC9B,aAAa,EAAE,MAAM,CAAC;QACtB,eAAe,EAAE,MAAM,CAAC;QACxB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IAIF;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;CAGnC"}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* @module ai/agent/runtime
|
|
12
12
|
*/
|
|
13
13
|
import { getTextFromParts, } from "../types.js";
|
|
14
|
-
import { ensureModelReady,
|
|
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 =
|
|
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
|
-
|
|
165
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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":"
|
|
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;
|
|
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,
|
|
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;
|
|
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"}
|
package/esm/src/proxy/handler.js
CHANGED
|
@@ -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
|
-
|
|
53
|
-
remoteJwksByUrl.set(cacheKey,
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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.
|
|
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(
|
|
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,
|
|
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"}
|
package/esm/src/utils/version.js
CHANGED
|
@@ -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.
|
|
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(
|
|
32
|
+
export function createBuildVersion(projectUpdated) {
|
|
33
33
|
return {
|
|
34
34
|
framework: RUNTIME_VERSION,
|
|
35
35
|
serverStart: SERVER_START_TIME,
|
|
36
|
-
projectUpdated
|
|
36
|
+
projectUpdated,
|
|
37
37
|
};
|
|
38
38
|
}
|
package/package.json
CHANGED
package/src/deno.js
CHANGED
|
@@ -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,
|
|
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 =
|
|
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
|
-
|
|
262
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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);
|
package/src/src/proxy/handler.ts
CHANGED
|
@@ -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
|
-
|
|
79
|
-
remoteJwksByUrl.set(cacheKey,
|
|
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
|
-
|
|
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.
|
|
116
|
+
const current = discoveredProjects.get(key);
|
|
117
|
+
if (current === promise) {
|
|
118
|
+
discoveredProjects.delete(key);
|
|
119
|
+
}
|
|
117
120
|
}
|
|
118
121
|
}
|
|
119
122
|
}
|
package/src/src/utils/version.ts
CHANGED
|
@@ -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.
|
|
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(
|
|
48
|
+
export function createBuildVersion(projectUpdated?: string): BuildVersion {
|
|
49
49
|
return {
|
|
50
50
|
framework: RUNTIME_VERSION,
|
|
51
51
|
serverStart: SERVER_START_TIME,
|
|
52
|
-
projectUpdated
|
|
52
|
+
projectUpdated,
|
|
53
53
|
};
|
|
54
54
|
}
|