veryfront 0.1.159 → 0.1.161
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/index.d.ts +1 -1
- package/esm/src/agent/index.d.ts.map +1 -1
- package/esm/src/agent/runtime/index.d.ts +2 -12
- package/esm/src/agent/runtime/index.d.ts.map +1 -1
- package/esm/src/agent/runtime/index.js +62 -25
- package/esm/src/agent/types.d.ts +37 -0
- package/esm/src/agent/types.d.ts.map +1 -1
- package/esm/src/mcp/http-transport.d.ts +33 -0
- package/esm/src/mcp/http-transport.d.ts.map +1 -0
- package/esm/src/mcp/http-transport.js +97 -0
- package/esm/src/mcp/server.d.ts.map +1 -1
- package/esm/src/mcp/server.js +14 -107
- package/esm/src/platform/adapters/fs/veryfront/stat-operations.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/stat-operations.js +13 -45
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +1 -1
- package/src/deno.js +1 -1
- package/src/src/agent/index.ts +6 -0
- package/src/src/agent/runtime/index.ts +118 -11
- package/src/src/agent/types.ts +47 -0
- package/src/src/mcp/http-transport.ts +163 -0
- package/src/src/mcp/server.ts +15 -123
- package/src/src/platform/adapters/fs/veryfront/stat-operations.ts +19 -49
- package/src/src/utils/version-constant.ts +1 -1
package/esm/src/mcp/server.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as dntShim from "../../_dnt.shims.js";
|
|
2
1
|
import { getMCPRegistry } from "./registry.js";
|
|
3
2
|
import { executeTool } from "../tool/index.js";
|
|
4
3
|
import { zodToJsonSchema } from "../tool/schema/index.js";
|
|
@@ -7,15 +6,12 @@ import { promptRegistry } from "../prompt/index.js";
|
|
|
7
6
|
import { createError, toError } from "../errors/veryfront-error.js";
|
|
8
7
|
import { withSpan } from "../observability/tracing/otlp-setup.js";
|
|
9
8
|
import { VERSION } from "../utils/version.js";
|
|
10
|
-
import { validateContentType } from "../security/input-validation/limits.js";
|
|
11
|
-
import { VeryfrontError } from "../security/input-validation/errors.js";
|
|
12
9
|
import { logger as baseLogger } from "../utils/index.js";
|
|
10
|
+
import { createMCPHTTPHandler } from "./http-transport.js";
|
|
13
11
|
import { SessionManager } from "./session.js";
|
|
14
12
|
import { TaskStore } from "./task-store.js";
|
|
15
13
|
const logger = baseLogger.component("mcp-server");
|
|
16
|
-
const MAX_REQUEST_BODY_SIZE = 1_048_576; // 1 MB
|
|
17
14
|
const MAX_CONTEXT_HEADER_LENGTH = 255;
|
|
18
|
-
const JSON_CONTENT_TYPE = "application/json";
|
|
19
15
|
const END_USER_ID_PATTERN = /^[a-zA-Z0-9._@-]+$/;
|
|
20
16
|
const PROJECT_ID_PATTERN = /^[a-zA-Z0-9._-]+$/;
|
|
21
17
|
class JsonRpcError extends Error {
|
|
@@ -46,18 +42,6 @@ function toParamsRecord(params) {
|
|
|
46
42
|
return {};
|
|
47
43
|
return params;
|
|
48
44
|
}
|
|
49
|
-
function createJSONResponse(body, init) {
|
|
50
|
-
const headers = new dntShim.Headers(init?.headers);
|
|
51
|
-
headers.set("Content-Type", JSON_CONTENT_TYPE);
|
|
52
|
-
return new dntShim.Response(JSON.stringify(body), { ...init, headers });
|
|
53
|
-
}
|
|
54
|
-
function createJSONRPCErrorResponse(status, code, message) {
|
|
55
|
-
return createJSONResponse({
|
|
56
|
-
jsonrpc: "2.0",
|
|
57
|
-
id: null,
|
|
58
|
-
error: { code, message },
|
|
59
|
-
}, { status });
|
|
60
|
-
}
|
|
61
45
|
function readAllowedHeader(request, headerName, pattern) {
|
|
62
46
|
const value = request.headers.get(headerName);
|
|
63
47
|
if (!value || value.length > MAX_CONTEXT_HEADER_LENGTH || !pattern.test(value)) {
|
|
@@ -485,96 +469,19 @@ export class MCPServer {
|
|
|
485
469
|
return Promise.all(this.pendingTasks.values()).then(() => { });
|
|
486
470
|
}
|
|
487
471
|
createHTTPHandler() {
|
|
488
|
-
return
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
if (this.config.auth?.type && this.config.auth.type !== "none") {
|
|
502
|
-
const authorized = await this.validateAuth(request);
|
|
503
|
-
if (!authorized)
|
|
504
|
-
return new dntShim.Response("Unauthorized", { status: 401 });
|
|
505
|
-
}
|
|
506
|
-
// DELETE = terminate session
|
|
507
|
-
if (request.method === "DELETE") {
|
|
508
|
-
const sessionId = request.headers.get("MCP-Session-Id");
|
|
509
|
-
if (sessionId) {
|
|
510
|
-
this.sessionManager.terminate(sessionId);
|
|
511
|
-
this.sessionCapabilities.delete(sessionId);
|
|
512
|
-
}
|
|
513
|
-
return new dntShim.Response(null, { status: 200, headers: this.getCORSHeaders(requestOrigin) });
|
|
514
|
-
}
|
|
515
|
-
// Only POST allowed for JSON-RPC messages
|
|
516
|
-
if (request.method !== "POST") {
|
|
517
|
-
return new dntShim.Response("Method Not Allowed", { status: 405 });
|
|
518
|
-
}
|
|
519
|
-
// Enforce request body size limit (fast path via Content-Length header)
|
|
520
|
-
const contentLength = request.headers.get("content-length");
|
|
521
|
-
if (contentLength && Number(contentLength) > MAX_REQUEST_BODY_SIZE) {
|
|
522
|
-
return createJSONRPCErrorResponse(413, -32600, "Request body too large");
|
|
523
|
-
}
|
|
524
|
-
try {
|
|
525
|
-
validateContentType(request, JSON_CONTENT_TYPE);
|
|
526
|
-
}
|
|
527
|
-
catch (error) {
|
|
528
|
-
const message = error instanceof VeryfrontError ? error.message : "Invalid Content-Type";
|
|
529
|
-
return createJSONRPCErrorResponse(400, -32700, message);
|
|
530
|
-
}
|
|
531
|
-
let rpcRequest;
|
|
532
|
-
try {
|
|
533
|
-
const bodyText = await request.text();
|
|
534
|
-
if (bodyText.length > MAX_REQUEST_BODY_SIZE) {
|
|
535
|
-
return createJSONRPCErrorResponse(413, -32600, "Request body too large");
|
|
536
|
-
}
|
|
537
|
-
rpcRequest = JSON.parse(bodyText);
|
|
538
|
-
}
|
|
539
|
-
catch (_) {
|
|
540
|
-
// expected: malformed JSON in request body
|
|
541
|
-
return createJSONRPCErrorResponse(400, -32700, "Parse error");
|
|
542
|
-
}
|
|
543
|
-
// Session management: initialize creates session, everything else requires it
|
|
544
|
-
const responseHeaders = {
|
|
545
|
-
...this.getCORSHeaders(requestOrigin),
|
|
546
|
-
};
|
|
547
|
-
if (rpcRequest.method === "initialize") {
|
|
548
|
-
const context = this.extractRequestContext(request);
|
|
549
|
-
const rpcResponse = await this.handleRequest(rpcRequest, context);
|
|
550
|
-
const clientCaps = toParamsRecord(rpcRequest.params).capabilities ??
|
|
551
|
-
{};
|
|
552
|
-
const sessionId = this.sessionManager.create();
|
|
553
|
-
this.sessionCapabilities.set(sessionId, clientCaps);
|
|
554
|
-
responseHeaders["MCP-Session-Id"] = sessionId;
|
|
555
|
-
return createJSONResponse(rpcResponse, { headers: responseHeaders });
|
|
556
|
-
}
|
|
557
|
-
// Post-init: require session ID when sessions are active
|
|
558
|
-
if (this.sessionManager.size > 0) {
|
|
559
|
-
const sessionId = request.headers.get("MCP-Session-Id");
|
|
560
|
-
if (!sessionId) {
|
|
561
|
-
return createJSONRPCErrorResponse(400, -32600, "Missing MCP-Session-Id header");
|
|
562
|
-
}
|
|
563
|
-
if (!this.sessionManager.isValid(sessionId)) {
|
|
564
|
-
return createJSONRPCErrorResponse(404, -32600, "Session not found or expired");
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
// Notifications have no id member — return 202 Accepted
|
|
568
|
-
// Note: id:0 is a valid request ID per JSON-RPC 2.0, so check for undefined
|
|
569
|
-
if (rpcRequest.id === undefined) {
|
|
570
|
-
const context = this.extractRequestContext(request);
|
|
571
|
-
await this.handleRequest(rpcRequest, context);
|
|
572
|
-
return new dntShim.Response(null, { status: 202, headers: responseHeaders });
|
|
573
|
-
}
|
|
574
|
-
const context = this.extractRequestContext(request);
|
|
575
|
-
const rpcResponse = await this.handleRequest(rpcRequest, context);
|
|
576
|
-
return createJSONResponse(rpcResponse, { headers: responseHeaders });
|
|
577
|
-
};
|
|
472
|
+
return createMCPHTTPHandler({
|
|
473
|
+
authEnabled: Boolean(this.config.auth?.type && this.config.auth.type !== "none"),
|
|
474
|
+
getCORSHeaders: (requestOrigin) => this.getCORSHeaders(requestOrigin),
|
|
475
|
+
validateAuth: (request) => this.validateAuth(request),
|
|
476
|
+
handleRequest: (request, context) => this.handleRequest(request, context),
|
|
477
|
+
extractRequestContext: (request) => this.extractRequestContext(request),
|
|
478
|
+
isOriginAllowed: (requestOrigin) => !requestOrigin ||
|
|
479
|
+
!this.config.cors?.enabled ||
|
|
480
|
+
!this.config.cors.origins?.length ||
|
|
481
|
+
this.config.cors.origins.includes(requestOrigin),
|
|
482
|
+
sessionCapabilities: this.sessionCapabilities,
|
|
483
|
+
sessionManager: this.sessionManager,
|
|
484
|
+
});
|
|
578
485
|
}
|
|
579
486
|
extractRequestContext(request) {
|
|
580
487
|
const context = {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stat-operations.d.ts","sourceRoot":"","sources":["../../../../../../src/src/platform/adapters/fs/veryfront/stat-operations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAElE,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AA2B/D,qBAAa,cAAe,SAAQ,uBAAuB;IACzD,OAAO,CAAC,SAAS,CAAyC;IAC1D,OAAO,CAAC,cAAc,CAA4B;IAClD,OAAO,CAAC,aAAa,CAA8B;IAEnD,OAAO,CAAC,sBAAsB,CAA6B;IAC3D,OAAO,CAAC,qBAAqB,CAA8B;IAE3D,OAAO,CAAC,WAAW,CAAkC;IAErD,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAGrC;IAEH,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;YAiHvB,gBAAgB;YAwChB,UAAU;IAkDxB,UAAU,IAAI,IAAI;IAMlB,kBAAkB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM;YAIpC,cAAc;IAqE5B,OAAO,CAAC,0BAA0B;
|
|
1
|
+
{"version":3,"file":"stat-operations.d.ts","sourceRoot":"","sources":["../../../../../../src/src/platform/adapters/fs/veryfront/stat-operations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAElE,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AA2B/D,qBAAa,cAAe,SAAQ,uBAAuB;IACzD,OAAO,CAAC,SAAS,CAAyC;IAC1D,OAAO,CAAC,cAAc,CAA4B;IAClD,OAAO,CAAC,aAAa,CAA8B;IAEnD,OAAO,CAAC,sBAAsB,CAA6B;IAC3D,OAAO,CAAC,qBAAqB,CAA8B;IAE3D,OAAO,CAAC,WAAW,CAAkC;IAErD,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAGrC;IAEH,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;YAiHvB,gBAAgB;YAwChB,UAAU;IAkDxB,UAAU,IAAI,IAAI;IAMlB,kBAAkB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM;YAIpC,cAAc;IAqE5B,OAAO,CAAC,0BAA0B;IAgClC,OAAO,CAAC,qBAAqB;YAQf,sBAAsB;YA8EtB,iBAAiB;IASzB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWtC,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CAkI1F"}
|
|
@@ -255,7 +255,7 @@ export class StatOperations extends VeryfrontOperationsBase {
|
|
|
255
255
|
this.cache.set(cacheKey, files);
|
|
256
256
|
return files;
|
|
257
257
|
}
|
|
258
|
-
buildResolveSearchPatterns(normalizedPath, options) {
|
|
258
|
+
buildResolveSearchPatterns(normalizedPath, options, knownExtensionFallback = "exact") {
|
|
259
259
|
const patterns = new Set();
|
|
260
260
|
const pathWithoutExt = stripKnownExtension(normalizedPath, EXTENSION_PRIORITY);
|
|
261
261
|
const allowPagesPrefix = options?.allowPagesPrefix !== false;
|
|
@@ -264,7 +264,7 @@ export class StatOperations extends VeryfrontOperationsBase {
|
|
|
264
264
|
patterns.add(pattern);
|
|
265
265
|
};
|
|
266
266
|
if (EXTENSION_PRIORITY.some((ext) => normalizedPath.endsWith(ext))) {
|
|
267
|
-
addPattern(normalizedPath);
|
|
267
|
+
addPattern(knownExtensionFallback === "wildcard" ? `${pathWithoutExt}.*` : normalizedPath);
|
|
268
268
|
return [...patterns];
|
|
269
269
|
}
|
|
270
270
|
addPattern(`${pathWithoutExt}.*`);
|
|
@@ -282,7 +282,7 @@ export class StatOperations extends VeryfrontOperationsBase {
|
|
|
282
282
|
path: normalizeIndexedFilePath(match).normalizedPath,
|
|
283
283
|
}));
|
|
284
284
|
}
|
|
285
|
-
async tryResolveViaApiSearch(normalizedPath, options) {
|
|
285
|
+
async tryResolveViaApiSearch(normalizedPath, options, knownExtensionFallback = "exact") {
|
|
286
286
|
if (isFrameworkSourcePath(normalizedPath)) {
|
|
287
287
|
logger.debug("Skipping API search for framework path", { normalizedPath });
|
|
288
288
|
return null;
|
|
@@ -291,7 +291,7 @@ export class StatOperations extends VeryfrontOperationsBase {
|
|
|
291
291
|
logger.warn("API search circuit breaker open, skipping", { normalizedPath });
|
|
292
292
|
return undefined;
|
|
293
293
|
}
|
|
294
|
-
const patterns = this.buildResolveSearchPatterns(normalizedPath, options);
|
|
294
|
+
const patterns = this.buildResolveSearchPatterns(normalizedPath, options, knownExtensionFallback);
|
|
295
295
|
let sawSuccessfulSearch = false;
|
|
296
296
|
for (const pattern of patterns) {
|
|
297
297
|
try {
|
|
@@ -459,49 +459,17 @@ export class StatOperations extends VeryfrontOperationsBase {
|
|
|
459
459
|
logger.debug("Skipping API search for framework path", { normalizedPath });
|
|
460
460
|
return null;
|
|
461
461
|
}
|
|
462
|
-
// NOTE:
|
|
463
|
-
//
|
|
464
|
-
//
|
|
465
|
-
|
|
466
|
-
if (
|
|
467
|
-
|
|
468
|
-
return
|
|
469
|
-
}
|
|
470
|
-
const searchPattern = `${pathWithoutExt}.*`;
|
|
471
|
-
logger.debug("Searching for file via API", {
|
|
472
|
-
pattern: searchPattern,
|
|
473
|
-
normalizedPath,
|
|
474
|
-
});
|
|
475
|
-
try {
|
|
476
|
-
const matches = await this.client.searchFiles(searchPattern);
|
|
477
|
-
this.apiSearchCircuitBreaker.recordSuccess();
|
|
478
|
-
logger.debug("API search result", {
|
|
479
|
-
pattern: searchPattern,
|
|
480
|
-
matchCount: matches.length,
|
|
481
|
-
matches: matches.map((m) => m.path).slice(0, 5),
|
|
482
|
-
});
|
|
483
|
-
const sortedMatches = sortPathsByExtensionPriority(matches, EXTENSION_PRIORITY);
|
|
484
|
-
const first = sortedMatches[0];
|
|
485
|
-
if (first) {
|
|
486
|
-
logger.debug("resolveFile found via API search", { path: first.path });
|
|
487
|
-
this.cache.set(cacheKey, first.path);
|
|
488
|
-
return first.path;
|
|
489
|
-
}
|
|
462
|
+
// NOTE: Keep the post-index API fallback aligned with the pre-index helper for extensionless
|
|
463
|
+
// paths, while preserving the older wildcard sibling-extension lookup for known-extension
|
|
464
|
+
// paths. Incomplete file-list snapshots otherwise hide valid files until the cache refreshes.
|
|
465
|
+
const apiResolved = await this.tryResolveViaApiSearch(normalizedPath, options, "wildcard");
|
|
466
|
+
if (typeof apiResolved === "string") {
|
|
467
|
+
this.cache.set(cacheKey, apiResolved);
|
|
468
|
+
return apiResolved;
|
|
490
469
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
if (result.tripped) {
|
|
494
|
-
logger.warn("API search circuit breaker tripped", {
|
|
495
|
-
failures: result.failures,
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
logger.error("API pattern search failed", { pattern: searchPattern, error });
|
|
470
|
+
if (apiResolved === null) {
|
|
471
|
+
this.cache.set(cacheKey, NOT_FOUND_SENTINEL);
|
|
499
472
|
}
|
|
500
|
-
logger.debug("resolveFile not found after API search", {
|
|
501
|
-
normalizedPath,
|
|
502
|
-
pathWithoutExt,
|
|
503
|
-
});
|
|
504
|
-
this.cache.set(cacheKey, NOT_FOUND_SENTINEL);
|
|
505
473
|
return null;
|
|
506
474
|
}
|
|
507
475
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.1.
|
|
1
|
+
export declare const VERSION = "0.1.161";
|
|
2
2
|
//# sourceMappingURL=version-constant.d.ts.map
|
package/package.json
CHANGED
package/src/deno.js
CHANGED
package/src/src/agent/index.ts
CHANGED
|
@@ -97,7 +97,13 @@ export type {
|
|
|
97
97
|
MessagePart,
|
|
98
98
|
ModelProvider,
|
|
99
99
|
ModelString,
|
|
100
|
+
ModelTransportRequest,
|
|
101
|
+
ModelTransportResolver,
|
|
100
102
|
ResolvedAgentConfig,
|
|
103
|
+
ResolvedModelTransport,
|
|
104
|
+
ResolvedRuntimeState,
|
|
105
|
+
RuntimeStateRequest,
|
|
106
|
+
RuntimeStateResolver,
|
|
101
107
|
StreamToolCall,
|
|
102
108
|
ToolCall,
|
|
103
109
|
ToolCallPart,
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @module ai/agent/runtime
|
|
12
12
|
*/
|
|
13
|
+
import * as dntShim from "../../../_dnt.shims.js";
|
|
14
|
+
|
|
13
15
|
|
|
14
16
|
import {
|
|
15
17
|
type AgentConfig,
|
|
@@ -19,6 +21,7 @@ import {
|
|
|
19
21
|
getTextFromParts,
|
|
20
22
|
type Message,
|
|
21
23
|
type MessagePart,
|
|
24
|
+
type ResolvedRuntimeState,
|
|
22
25
|
type ToolCall,
|
|
23
26
|
type ToolResultPart,
|
|
24
27
|
} from "../types.js";
|
|
@@ -264,6 +267,19 @@ function getRuntimeAllowedRemoteTools(config: AgentConfig): string[] | undefined
|
|
|
264
267
|
return raw.every((toolName) => typeof toolName === "string") ? raw : [];
|
|
265
268
|
}
|
|
266
269
|
|
|
270
|
+
type ResolvedModelTransport = {
|
|
271
|
+
requestedModel: string;
|
|
272
|
+
resolvedModelString: string;
|
|
273
|
+
languageModel: ModelRuntime;
|
|
274
|
+
headers?: dntShim.HeadersInit;
|
|
275
|
+
providerOptions?: Record<string, unknown>;
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
type RuntimeStepState = {
|
|
279
|
+
systemPrompt: string;
|
|
280
|
+
context?: Record<string, unknown>;
|
|
281
|
+
};
|
|
282
|
+
|
|
267
283
|
export class AgentRuntime {
|
|
268
284
|
private id: string;
|
|
269
285
|
private config: AgentConfig;
|
|
@@ -279,6 +295,52 @@ export class AgentRuntime {
|
|
|
279
295
|
this.memory = createMemory<Message>(memoryConfig);
|
|
280
296
|
}
|
|
281
297
|
|
|
298
|
+
private async resolveModelTransport(
|
|
299
|
+
context: Record<string, unknown> | undefined,
|
|
300
|
+
modelOverride: string | undefined,
|
|
301
|
+
mode: "generate" | "stream",
|
|
302
|
+
): Promise<ResolvedModelTransport> {
|
|
303
|
+
const requestedModel = resolveConfiguredAgentModel(modelOverride || this.config.model);
|
|
304
|
+
const resolvedModelString = resolveRuntimeModel(modelOverride || this.config.model);
|
|
305
|
+
const transport = await this.config.resolveModelTransport?.({
|
|
306
|
+
agentId: this.id,
|
|
307
|
+
requestedModel,
|
|
308
|
+
resolvedModel: resolvedModelString,
|
|
309
|
+
context,
|
|
310
|
+
mode,
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
requestedModel,
|
|
315
|
+
resolvedModelString,
|
|
316
|
+
languageModel: transport?.model ?? resolveModel(resolvedModelString),
|
|
317
|
+
headers: transport?.headers,
|
|
318
|
+
providerOptions: transport?.providerOptions,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private async resolveRuntimeState(
|
|
323
|
+
messages: Message[],
|
|
324
|
+
context: Record<string, unknown> | undefined,
|
|
325
|
+
mode: "generate" | "stream",
|
|
326
|
+
step: number,
|
|
327
|
+
systemPrompt: string,
|
|
328
|
+
): Promise<RuntimeStepState> {
|
|
329
|
+
const refreshed: ResolvedRuntimeState | undefined = await this.config.resolveRuntimeState?.({
|
|
330
|
+
agentId: this.id,
|
|
331
|
+
mode,
|
|
332
|
+
step,
|
|
333
|
+
system: systemPrompt,
|
|
334
|
+
messages: [...messages],
|
|
335
|
+
context,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
systemPrompt: refreshed?.system ?? systemPrompt,
|
|
340
|
+
context: refreshed?.context ?? context,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
282
344
|
/**
|
|
283
345
|
* Generate a response (non-streaming)
|
|
284
346
|
*/
|
|
@@ -288,8 +350,9 @@ export class AgentRuntime {
|
|
|
288
350
|
modelOverride?: string,
|
|
289
351
|
maxOutputTokensOverride?: number,
|
|
290
352
|
): Promise<AgentResponse> {
|
|
291
|
-
const
|
|
292
|
-
const
|
|
353
|
+
const transport = await this.resolveModelTransport(context, modelOverride, "generate");
|
|
354
|
+
const requestedModel = transport.requestedModel;
|
|
355
|
+
const resolvedModelString = transport.resolvedModelString;
|
|
293
356
|
if (resolvedModelString !== requestedModel) {
|
|
294
357
|
logger.info(
|
|
295
358
|
`⚡ Using runtime model "${resolvedModelString}" instead of "${requestedModel}".`,
|
|
@@ -326,9 +389,12 @@ export class AgentRuntime {
|
|
|
326
389
|
{
|
|
327
390
|
agentId: this.id,
|
|
328
391
|
projectId: tryGetCacheKeyContext()?.projectId,
|
|
329
|
-
...context,
|
|
330
392
|
},
|
|
393
|
+
context,
|
|
331
394
|
resolvedModelString,
|
|
395
|
+
transport.languageModel,
|
|
396
|
+
transport.headers,
|
|
397
|
+
transport.providerOptions,
|
|
332
398
|
maxOutputTokensOverride,
|
|
333
399
|
),
|
|
334
400
|
);
|
|
@@ -351,8 +417,9 @@ export class AgentRuntime {
|
|
|
351
417
|
maxOutputTokensOverride?: number,
|
|
352
418
|
abortSignal?: AbortSignal,
|
|
353
419
|
): Promise<ReadableStream<Uint8Array>> {
|
|
354
|
-
const
|
|
355
|
-
const
|
|
420
|
+
const transport = await this.resolveModelTransport(context, modelOverride, "stream");
|
|
421
|
+
const requestedModel = transport.requestedModel;
|
|
422
|
+
const resolvedModelString = transport.resolvedModelString;
|
|
356
423
|
if (resolvedModelString !== requestedModel) {
|
|
357
424
|
logger.info(
|
|
358
425
|
`⚡ Using runtime model "${resolvedModelString}" instead of "${requestedModel}".`,
|
|
@@ -389,7 +456,7 @@ export class AgentRuntime {
|
|
|
389
456
|
// Resolve model BEFORE creating the ReadableStream — if this throws
|
|
390
457
|
// (e.g., no_ai_available), the error propagates to the caller who can
|
|
391
458
|
// return a proper error response (503) instead of a 200 with an error event.
|
|
392
|
-
const languageModel =
|
|
459
|
+
const languageModel = transport.languageModel;
|
|
393
460
|
|
|
394
461
|
// Determine inference mode from the resolved model object (not the string),
|
|
395
462
|
// because resolveModel may internally fall back from cloud to local.
|
|
@@ -433,8 +500,11 @@ export class AgentRuntime {
|
|
|
433
500
|
callbacks,
|
|
434
501
|
textPartId,
|
|
435
502
|
toolContext,
|
|
503
|
+
context,
|
|
436
504
|
resolvedModelString,
|
|
437
505
|
languageModel,
|
|
506
|
+
transport.headers,
|
|
507
|
+
transport.providerOptions,
|
|
438
508
|
maxOutputTokensOverride,
|
|
439
509
|
streamAbortSignal,
|
|
440
510
|
);
|
|
@@ -474,15 +544,19 @@ export class AgentRuntime {
|
|
|
474
544
|
private async executeAgentLoop(
|
|
475
545
|
systemPrompt: string,
|
|
476
546
|
messages: Message[],
|
|
477
|
-
|
|
547
|
+
toolContextBase?: ToolExecutionContext,
|
|
548
|
+
runtimeContext?: Record<string, unknown>,
|
|
478
549
|
modelString?: string,
|
|
550
|
+
resolvedModel?: ModelRuntime,
|
|
551
|
+
headers?: dntShim.HeadersInit,
|
|
552
|
+
providerOptions?: Record<string, unknown>,
|
|
479
553
|
maxOutputTokensOverride?: number,
|
|
480
554
|
): Promise<AgentResponse> {
|
|
481
555
|
return withSpan("agent.execution_loop", async (loopSpan) => {
|
|
482
556
|
const { maxAgentSteps } = getPlatformCapabilities();
|
|
483
557
|
const maxSteps = this.computeMaxSteps(maxAgentSteps);
|
|
484
558
|
const effectiveModel = resolveRuntimeModel(modelString || this.config.model);
|
|
485
|
-
const languageModel = resolveModel(effectiveModel);
|
|
559
|
+
const languageModel = resolvedModel ?? resolveModel(effectiveModel);
|
|
486
560
|
|
|
487
561
|
const toolCalls: ToolCall[] = [];
|
|
488
562
|
const currentMessages = [...messages];
|
|
@@ -502,11 +576,24 @@ export class AgentRuntime {
|
|
|
502
576
|
// Request-scoped skill policy (not class-level mutable state)
|
|
503
577
|
let activeSkillPolicy: string[] | undefined;
|
|
504
578
|
const allowedRemoteToolNames = getRuntimeAllowedRemoteTools(this.config);
|
|
579
|
+
let currentSystemPrompt = systemPrompt;
|
|
580
|
+
let currentRuntimeContext = runtimeContext;
|
|
505
581
|
|
|
506
582
|
for (let step = 0; step < maxSteps; step++) {
|
|
507
583
|
this.status = "thinking";
|
|
508
584
|
addSpanEvent(loopSpan, "step_start", { step });
|
|
509
585
|
|
|
586
|
+
const runtimeState = await this.resolveRuntimeState(
|
|
587
|
+
currentMessages,
|
|
588
|
+
currentRuntimeContext,
|
|
589
|
+
"generate",
|
|
590
|
+
step,
|
|
591
|
+
currentSystemPrompt,
|
|
592
|
+
);
|
|
593
|
+
currentSystemPrompt = runtimeState.systemPrompt;
|
|
594
|
+
currentRuntimeContext = runtimeState.context;
|
|
595
|
+
const toolContext = { ...toolContextBase, ...currentRuntimeContext };
|
|
596
|
+
|
|
510
597
|
let tools = isLocal ? [] : await getAvailableTools(this.config.tools, {
|
|
511
598
|
includeSkillTools: Boolean(this.config.skills),
|
|
512
599
|
allowedRemoteToolNames,
|
|
@@ -526,7 +613,7 @@ export class AgentRuntime {
|
|
|
526
613
|
});
|
|
527
614
|
return generateText({
|
|
528
615
|
model: languageModel,
|
|
529
|
-
system:
|
|
616
|
+
system: currentSystemPrompt,
|
|
530
617
|
messages: convertToModelMessages(currentMessages),
|
|
531
618
|
tools: convertToolsToRuntimeTools(tools, {
|
|
532
619
|
model: effectiveModel,
|
|
@@ -535,6 +622,8 @@ export class AgentRuntime {
|
|
|
535
622
|
experimental_repairToolCall: repairToolCall,
|
|
536
623
|
maxOutputTokens: this.resolveMaxOutputTokens(maxOutputTokensOverride),
|
|
537
624
|
temperature: DEFAULT_TEMPERATURE,
|
|
625
|
+
...(headers ? { headers } : {}),
|
|
626
|
+
...(providerOptions ? { providerOptions } : {}),
|
|
538
627
|
});
|
|
539
628
|
});
|
|
540
629
|
|
|
@@ -764,9 +853,12 @@ export class AgentRuntime {
|
|
|
764
853
|
onFinish?: (response: AgentResponse) => void;
|
|
765
854
|
},
|
|
766
855
|
textPartId?: string,
|
|
767
|
-
|
|
856
|
+
toolContextBase?: Record<string, unknown>,
|
|
857
|
+
runtimeContext?: Record<string, unknown>,
|
|
768
858
|
modelString?: string,
|
|
769
859
|
resolvedModel?: ModelRuntime,
|
|
860
|
+
headers?: dntShim.HeadersInit,
|
|
861
|
+
providerOptions?: Record<string, unknown>,
|
|
770
862
|
maxOutputTokensOverride?: number,
|
|
771
863
|
abortSignal?: AbortSignal,
|
|
772
864
|
): Promise<AgentResponse> {
|
|
@@ -795,12 +887,25 @@ export class AgentRuntime {
|
|
|
795
887
|
let finalFinishReason: string | undefined;
|
|
796
888
|
let latestAssistantText = "";
|
|
797
889
|
const allowedRemoteToolNames = getRuntimeAllowedRemoteTools(this.config);
|
|
890
|
+
let currentSystemPrompt = systemPrompt;
|
|
891
|
+
let currentRuntimeContext = runtimeContext;
|
|
798
892
|
|
|
799
893
|
for (let step = 0; step < maxSteps; step++) {
|
|
800
894
|
throwIfAborted(abortSignal);
|
|
801
895
|
sendSSE(controller, encoder, { type: "step-start" });
|
|
802
896
|
const currentStepToolResults = new Map<string, ToolResultPart>();
|
|
803
897
|
|
|
898
|
+
const runtimeState = await this.resolveRuntimeState(
|
|
899
|
+
currentMessages,
|
|
900
|
+
currentRuntimeContext,
|
|
901
|
+
"stream",
|
|
902
|
+
step,
|
|
903
|
+
currentSystemPrompt,
|
|
904
|
+
);
|
|
905
|
+
currentSystemPrompt = runtimeState.systemPrompt;
|
|
906
|
+
currentRuntimeContext = runtimeState.context;
|
|
907
|
+
const toolContext = { ...toolContextBase, ...currentRuntimeContext };
|
|
908
|
+
|
|
804
909
|
let tools = isLocalStreaming ? [] : await getAvailableTools(this.config.tools, {
|
|
805
910
|
includeSkillTools: Boolean(this.config.skills),
|
|
806
911
|
allowedRemoteToolNames,
|
|
@@ -815,7 +920,7 @@ export class AgentRuntime {
|
|
|
815
920
|
|
|
816
921
|
const result = streamText({
|
|
817
922
|
model: languageModel,
|
|
818
|
-
system:
|
|
923
|
+
system: currentSystemPrompt,
|
|
819
924
|
messages: convertToModelMessages(currentMessages),
|
|
820
925
|
tools: convertToolsToRuntimeTools(tools, {
|
|
821
926
|
model: effectiveModel,
|
|
@@ -824,6 +929,8 @@ export class AgentRuntime {
|
|
|
824
929
|
experimental_repairToolCall: repairToolCall,
|
|
825
930
|
maxOutputTokens: this.resolveMaxOutputTokens(maxOutputTokensOverride),
|
|
826
931
|
temperature: DEFAULT_TEMPERATURE,
|
|
932
|
+
...(headers ? { headers } : {}),
|
|
933
|
+
...(providerOptions ? { providerOptions } : {}),
|
|
827
934
|
abortSignal,
|
|
828
935
|
});
|
|
829
936
|
|
package/src/src/agent/types.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import * as dntShim from "../../_dnt.shims.js";
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
import type { ModelRuntime } from "../provider/types.js";
|
|
7
8
|
import type { RemoteToolSource, Tool } from "../tool/index.js";
|
|
8
9
|
import { INVALID_ARGUMENT } from "../errors/error-registry.js";
|
|
9
10
|
import type { Memory } from "./memory/memory-interface.js";
|
|
@@ -91,6 +92,16 @@ export interface AgentConfig {
|
|
|
91
92
|
};
|
|
92
93
|
/** Restrict runtime model overrides to these "provider/model" strings. */
|
|
93
94
|
allowedModels?: ModelString[];
|
|
95
|
+
/**
|
|
96
|
+
* Optional request-aware hook for overriding the resolved model runtime and
|
|
97
|
+
* provider transport options on a per-call basis.
|
|
98
|
+
*/
|
|
99
|
+
resolveModelTransport?: ModelTransportResolver;
|
|
100
|
+
/**
|
|
101
|
+
* Optional step-boundary hook for refreshing the runtime system prompt and
|
|
102
|
+
* host-owned context during a long-lived run.
|
|
103
|
+
*/
|
|
104
|
+
resolveRuntimeState?: RuntimeStateResolver;
|
|
94
105
|
/**
|
|
95
106
|
* Enable skills for this agent.
|
|
96
107
|
* - true: include all discovered skills from skills/ directory
|
|
@@ -108,6 +119,42 @@ export interface AgentConfig {
|
|
|
108
119
|
|
|
109
120
|
export type ResolvedAgentConfig = AgentConfig & { model: ModelString };
|
|
110
121
|
|
|
122
|
+
export interface ModelTransportRequest {
|
|
123
|
+
agentId: string;
|
|
124
|
+
requestedModel: ModelString;
|
|
125
|
+
resolvedModel: ModelString;
|
|
126
|
+
context?: Record<string, unknown>;
|
|
127
|
+
mode: "generate" | "stream";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface ResolvedModelTransport {
|
|
131
|
+
model?: ModelRuntime;
|
|
132
|
+
headers?: dntShim.HeadersInit;
|
|
133
|
+
providerOptions?: Record<string, unknown>;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export type ModelTransportResolver = (
|
|
137
|
+
request: ModelTransportRequest,
|
|
138
|
+
) => ResolvedModelTransport | Promise<ResolvedModelTransport>;
|
|
139
|
+
|
|
140
|
+
export interface RuntimeStateRequest {
|
|
141
|
+
agentId: string;
|
|
142
|
+
mode: "generate" | "stream";
|
|
143
|
+
step: number;
|
|
144
|
+
system: string;
|
|
145
|
+
messages: Message[];
|
|
146
|
+
context?: Record<string, unknown>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface ResolvedRuntimeState {
|
|
150
|
+
system?: string;
|
|
151
|
+
context?: Record<string, unknown>;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export type RuntimeStateResolver = (
|
|
155
|
+
request: RuntimeStateRequest,
|
|
156
|
+
) => ResolvedRuntimeState | undefined | Promise<ResolvedRuntimeState | undefined>;
|
|
157
|
+
|
|
111
158
|
// Import for use in AgentMiddleware
|
|
112
159
|
import type { AgentContext, AgentResponse } from "./schemas/index.js";
|
|
113
160
|
|