veryfront 0.1.64 → 0.1.67
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/index.d.ts +1 -0
- package/esm/src/agent/runtime/index.d.ts.map +1 -1
- package/esm/src/agent/runtime/index.js +10 -2
- package/esm/src/channels/control-plane.d.ts +259 -0
- package/esm/src/channels/control-plane.d.ts.map +1 -0
- package/esm/src/channels/control-plane.js +212 -0
- package/esm/src/channels/invoke.d.ts +3 -40
- package/esm/src/channels/invoke.d.ts.map +1 -1
- package/esm/src/channels/invoke.js +9 -106
- package/esm/src/internal-agents/ag-ui-sse.d.ts +35 -0
- package/esm/src/internal-agents/ag-ui-sse.d.ts.map +1 -0
- package/esm/src/internal-agents/ag-ui-sse.js +263 -0
- package/esm/src/internal-agents/control-plane-auth.d.ts +20 -0
- package/esm/src/internal-agents/control-plane-auth.d.ts.map +1 -0
- package/esm/src/internal-agents/control-plane-auth.js +56 -0
- package/esm/src/internal-agents/request-body.d.ts +9 -0
- package/esm/src/internal-agents/request-body.d.ts.map +1 -0
- package/esm/src/internal-agents/request-body.js +28 -0
- package/esm/src/internal-agents/run-stream.d.ts +14 -0
- package/esm/src/internal-agents/run-stream.d.ts.map +1 -0
- package/esm/src/internal-agents/run-stream.js +259 -0
- package/esm/src/internal-agents/schema.d.ts +268 -0
- package/esm/src/internal-agents/schema.d.ts.map +1 -0
- package/esm/src/internal-agents/schema.js +71 -0
- package/esm/src/internal-agents/session-manager.d.ts +63 -0
- package/esm/src/internal-agents/session-manager.d.ts.map +1 -0
- package/esm/src/internal-agents/session-manager.js +258 -0
- package/esm/src/platform/adapters/runtime/deno/adapter.d.ts.map +1 -1
- package/esm/src/platform/adapters/runtime/deno/adapter.js +4 -13
- package/esm/src/platform/compat/process.d.ts.map +1 -1
- package/esm/src/platform/compat/process.js +42 -5
- package/esm/src/server/bootstrap.js +9 -1
- package/esm/src/server/handlers/request/agent-run-cancel.handler.d.ts +11 -0
- package/esm/src/server/handlers/request/agent-run-cancel.handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/agent-run-cancel.handler.js +62 -0
- package/esm/src/server/handlers/request/agent-run-resume.handler.d.ts +11 -0
- package/esm/src/server/handlers/request/agent-run-resume.handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/agent-run-resume.handler.js +77 -0
- package/esm/src/server/handlers/request/agent-stream.handler.d.ts +14 -0
- package/esm/src/server/handlers/request/agent-stream.handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/agent-stream.handler.js +86 -0
- package/esm/src/server/handlers/request/{channel-assistants.handler.d.ts → internal-agents-list.handler.d.ts} +4 -4
- package/esm/src/server/handlers/request/internal-agents-list.handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/internal-agents-list.handler.js +73 -0
- package/esm/src/server/runtime-handler/index.d.ts.map +1 -1
- package/esm/src/server/runtime-handler/index.js +8 -2
- package/package.json +1 -1
- package/src/deno.js +1 -1
- package/src/src/agent/runtime/index.ts +12 -2
- package/src/src/channels/control-plane.ts +332 -0
- package/src/src/channels/invoke.ts +12 -157
- package/src/src/internal-agents/ag-ui-sse.ts +327 -0
- package/src/src/internal-agents/control-plane-auth.ts +82 -0
- package/src/src/internal-agents/request-body.ts +42 -0
- package/src/src/internal-agents/run-stream.ts +354 -0
- package/src/src/internal-agents/schema.ts +102 -0
- package/src/src/internal-agents/session-manager.ts +358 -0
- package/src/src/platform/adapters/runtime/deno/adapter.ts +9 -11
- package/src/src/platform/compat/process.ts +56 -3
- package/src/src/server/bootstrap.ts +13 -1
- package/src/src/server/handlers/request/agent-run-cancel.handler.ts +86 -0
- package/src/src/server/handlers/request/agent-run-resume.handler.ts +108 -0
- package/src/src/server/handlers/request/agent-stream.handler.ts +125 -0
- package/src/src/server/handlers/request/internal-agents-list.handler.ts +100 -0
- package/src/src/server/runtime-handler/index.ts +8 -2
- package/esm/src/server/handlers/request/channel-assistants.handler.d.ts.map +0 -1
- package/esm/src/server/handlers/request/channel-assistants.handler.js +0 -71
- package/src/src/server/handlers/request/channel-assistants.handler.ts +0 -94
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as dntShim from "../../../../_dnt.shims.js";
|
|
2
|
+
import { defaultChannelInvokeDeps } from "../../../channels/invoke.js";
|
|
3
|
+
import { createRuntimeAgentStreamResponse, } from "../../../internal-agents/run-stream.js";
|
|
4
|
+
import { ControlPlaneRequestError, verifyControlPlaneRequest, } from "../../../internal-agents/control-plane-auth.js";
|
|
5
|
+
import { INTERNAL_AGENT_STREAM_MAX_BODY_BYTES, InternalAgentRequestBodyTooLargeError, readInternalAgentRequestBody, } from "../../../internal-agents/request-body.js";
|
|
6
|
+
import { AgentRunAlreadyExistsError, agentRunSessionManager, } from "../../../internal-agents/session-manager.js";
|
|
7
|
+
import { RuntimeRunAgentInputSchema } from "../../../internal-agents/schema.js";
|
|
8
|
+
import { BaseHandler } from "../response/base.js";
|
|
9
|
+
import { PRIORITY_MEDIUM_API } from "../../../utils/constants/index.js";
|
|
10
|
+
const defaultDeps = {
|
|
11
|
+
...defaultChannelInvokeDeps,
|
|
12
|
+
sessionManager: agentRunSessionManager,
|
|
13
|
+
};
|
|
14
|
+
function applyBuilderHeaders(target, source) {
|
|
15
|
+
const headers = new dntShim.Headers(target.headers);
|
|
16
|
+
for (const [key, value] of source.entries()) {
|
|
17
|
+
if (!headers.has(key)) {
|
|
18
|
+
headers.set(key, value);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return new dntShim.Response(target.body, {
|
|
22
|
+
status: target.status,
|
|
23
|
+
statusText: target.statusText,
|
|
24
|
+
headers,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export class AgentStreamHandler extends BaseHandler {
|
|
28
|
+
deps;
|
|
29
|
+
metadata = {
|
|
30
|
+
name: "AgentStreamHandler",
|
|
31
|
+
priority: PRIORITY_MEDIUM_API,
|
|
32
|
+
patterns: [{ pattern: "/internal/agents/stream", exact: true, method: "POST" }],
|
|
33
|
+
};
|
|
34
|
+
constructor(deps = defaultDeps) {
|
|
35
|
+
super();
|
|
36
|
+
this.deps = deps;
|
|
37
|
+
}
|
|
38
|
+
async handle(req, ctx) {
|
|
39
|
+
if (!this.shouldHandle(req, ctx)) {
|
|
40
|
+
return this.continue();
|
|
41
|
+
}
|
|
42
|
+
return this.withProxyContext(ctx, async () => {
|
|
43
|
+
const builder = this.createResponseBuilder(ctx)
|
|
44
|
+
.withCORS(req, ctx.securityConfig?.cors)
|
|
45
|
+
.withSecurity(ctx.securityConfig ?? undefined, req);
|
|
46
|
+
try {
|
|
47
|
+
const rawBody = await readInternalAgentRequestBody(req, INTERNAL_AGENT_STREAM_MAX_BODY_BYTES);
|
|
48
|
+
const payload = RuntimeRunAgentInputSchema.parse(JSON.parse(rawBody));
|
|
49
|
+
await verifyControlPlaneRequest(req, ctx, rawBody, {
|
|
50
|
+
expectedSubject: payload.runId,
|
|
51
|
+
expectedSurface: "studio",
|
|
52
|
+
});
|
|
53
|
+
await this.deps.ensureProjectDiscovery(ctx);
|
|
54
|
+
const agent = this.deps.getAgent(payload.agentId);
|
|
55
|
+
if (!agent) {
|
|
56
|
+
return this.respond(builder.json({ error: "Agent not found" }, 404));
|
|
57
|
+
}
|
|
58
|
+
const response = await createRuntimeAgentStreamResponse(payload, agent, this.deps);
|
|
59
|
+
return this.respond(applyBuilderHeaders(response, builder.headers));
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (error instanceof InternalAgentRequestBodyTooLargeError) {
|
|
63
|
+
return this.respond(builder.json({ error: error.message }, error.status));
|
|
64
|
+
}
|
|
65
|
+
if (error instanceof ControlPlaneRequestError) {
|
|
66
|
+
return this.respond(builder.json({ error: error.message }, error.status));
|
|
67
|
+
}
|
|
68
|
+
if (error instanceof SyntaxError) {
|
|
69
|
+
return this.respond(builder.json({ error: "Invalid internal agent stream request" }, 400));
|
|
70
|
+
}
|
|
71
|
+
if (error instanceof AgentRunAlreadyExistsError) {
|
|
72
|
+
return this.respond(builder.json({ error: error.message }, 409));
|
|
73
|
+
}
|
|
74
|
+
if (error instanceof Error && error.name === "ZodError") {
|
|
75
|
+
return this.respond(builder.json({ error: "Invalid internal agent stream request" }, 400));
|
|
76
|
+
}
|
|
77
|
+
this.logWarn("Internal agent stream request failed", {
|
|
78
|
+
error: error instanceof Error ? error.message : String(error),
|
|
79
|
+
projectId: ctx.projectId,
|
|
80
|
+
projectSlug: ctx.projectSlug,
|
|
81
|
+
});
|
|
82
|
+
return this.respond(builder.json({ error: "Internal agent stream failed" }, 500));
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as dntShim from "../../../../_dnt.shims.js";
|
|
2
2
|
import { BaseHandler } from "../response/base.js";
|
|
3
3
|
import type { HandlerContext, HandlerMetadata, HandlerResult } from "../types.js";
|
|
4
|
-
import { type
|
|
5
|
-
export declare class
|
|
4
|
+
import { type RuntimeAgentDiscoveryDeps } from "../../../channels/control-plane.js";
|
|
5
|
+
export declare class InternalAgentsListHandler extends BaseHandler {
|
|
6
6
|
private readonly deps;
|
|
7
7
|
metadata: HandlerMetadata;
|
|
8
|
-
constructor(deps?:
|
|
8
|
+
constructor(deps?: RuntimeAgentDiscoveryDeps);
|
|
9
9
|
handle(req: dntShim.Request, ctx: HandlerContext): Promise<HandlerResult>;
|
|
10
10
|
}
|
|
11
|
-
//# sourceMappingURL=
|
|
11
|
+
//# sourceMappingURL=internal-agents-list.handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal-agents-list.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/request/internal-agents-list.handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAmB,aAAa,EAAE,MAAM,aAAa,CAAC;AACnG,OAAO,EAIL,KAAK,yBAAyB,EAC/B,MAAM,oCAAoC,CAAC;AAc5C,qBAAa,yBAA0B,SAAQ,WAAW;IAO5C,OAAO,CAAC,QAAQ,CAAC,IAAI;IANjC,QAAQ,EAAE,eAAe,CAIvB;gBAE2B,IAAI,GAAE,yBAAoD;IAIjF,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CAkEhF"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { BaseHandler } from "../response/base.js";
|
|
2
|
+
import { ControlPlaneAgentsListRequestSchema, listRuntimeAgents, } from "../../../channels/control-plane.js";
|
|
3
|
+
import { defaultChannelInvokeDeps } from "../../../channels/invoke.js";
|
|
4
|
+
import { ControlPlaneRequestError, verifyControlPlaneRequest, } from "../../../internal-agents/control-plane-auth.js";
|
|
5
|
+
import { INTERNAL_AGENT_CONTROL_PLANE_MAX_BODY_BYTES, InternalAgentRequestBodyTooLargeError, readInternalAgentRequestBody, } from "../../../internal-agents/request-body.js";
|
|
6
|
+
import { PRIORITY_MEDIUM_API } from "../../../utils/constants/index.js";
|
|
7
|
+
import { ZodError } from "zod";
|
|
8
|
+
export class InternalAgentsListHandler extends BaseHandler {
|
|
9
|
+
deps;
|
|
10
|
+
metadata = {
|
|
11
|
+
name: "InternalAgentsListHandler",
|
|
12
|
+
priority: PRIORITY_MEDIUM_API,
|
|
13
|
+
patterns: [{ pattern: "/internal/agents/list", exact: true, method: "POST" }],
|
|
14
|
+
};
|
|
15
|
+
constructor(deps = defaultChannelInvokeDeps) {
|
|
16
|
+
super();
|
|
17
|
+
this.deps = deps;
|
|
18
|
+
}
|
|
19
|
+
async handle(req, ctx) {
|
|
20
|
+
if (!this.shouldHandle(req, ctx)) {
|
|
21
|
+
return this.continue();
|
|
22
|
+
}
|
|
23
|
+
return this.withProxyContext(ctx, async () => {
|
|
24
|
+
const builder = this.createResponseBuilder(ctx)
|
|
25
|
+
.withCORS(req, ctx.securityConfig?.cors)
|
|
26
|
+
.withSecurity(ctx.securityConfig ?? undefined, req);
|
|
27
|
+
try {
|
|
28
|
+
const rawBody = await readInternalAgentRequestBody(req, INTERNAL_AGENT_CONTROL_PLANE_MAX_BODY_BYTES);
|
|
29
|
+
const payload = ControlPlaneAgentsListRequestSchema.parse(JSON.parse(rawBody));
|
|
30
|
+
const claims = await verifyControlPlaneRequest(req, ctx, rawBody, {
|
|
31
|
+
expectedSubject: payload.requestId,
|
|
32
|
+
expectedSurface: payload.surface,
|
|
33
|
+
});
|
|
34
|
+
if (payload.projectId !== claims.project_id ||
|
|
35
|
+
(ctx.projectId !== undefined && payload.projectId !== ctx.projectId)) {
|
|
36
|
+
this.logWarn("Internal agents list request body did not match signed claims", {
|
|
37
|
+
projectSlug: ctx.projectSlug,
|
|
38
|
+
projectId: ctx.projectId,
|
|
39
|
+
requestId: payload.requestId,
|
|
40
|
+
signedRequestId: claims.sub,
|
|
41
|
+
surface: payload.surface,
|
|
42
|
+
signedSurface: claims.surface,
|
|
43
|
+
});
|
|
44
|
+
return this.respond(builder.json({ error: "Invalid control-plane signature" }, 401));
|
|
45
|
+
}
|
|
46
|
+
const response = await listRuntimeAgents(ctx, this.deps);
|
|
47
|
+
return this.respond(builder.json(response, 200));
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (error instanceof InternalAgentRequestBodyTooLargeError) {
|
|
51
|
+
return this.respond(builder.json({ error: error.message }, error.status));
|
|
52
|
+
}
|
|
53
|
+
if (error instanceof ControlPlaneRequestError) {
|
|
54
|
+
this.logWarn("Internal agents list signature verification failed", {
|
|
55
|
+
error: error.message,
|
|
56
|
+
projectSlug: ctx.projectSlug,
|
|
57
|
+
projectId: ctx.projectId,
|
|
58
|
+
});
|
|
59
|
+
return this.respond(builder.json({ error: error.message }, error.status));
|
|
60
|
+
}
|
|
61
|
+
if (error instanceof SyntaxError || error instanceof ZodError) {
|
|
62
|
+
this.logWarn("Internal agents list request validation failed", {
|
|
63
|
+
error: error instanceof Error ? error.message : String(error),
|
|
64
|
+
projectSlug: ctx.projectSlug,
|
|
65
|
+
projectId: ctx.projectId,
|
|
66
|
+
});
|
|
67
|
+
return this.respond(builder.json({ error: "Invalid internal agents request" }, 400));
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/src/server/runtime-handler/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAC;AAQlD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/src/server/runtime-handler/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAC;AAQlD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AA6F7D,OAAO,EAAE,qBAAqB,EAAE,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAMtF,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,wEAAwE;IACxE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,oFAAoF;IACpF,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,sFAAsF;IACtF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,uGAAuG;IACvG,kBAAkB,CAAC,EAAE,SAAS,GAAG,YAAY,CAAC;CAC/C;AAED,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,cAAc,EACvB,IAAI,GAAE,qBAAsC,GAC3C,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CA4anF;AAGD,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC"}
|
|
@@ -42,7 +42,10 @@ import { HMRHandler } from "../handlers/preview/hmr.handler.js";
|
|
|
42
42
|
import { MarkdownPreviewHandler } from "../handlers/preview/markdown-preview.handler.js";
|
|
43
43
|
import { OpenAPIHandler } from "../handlers/request/openapi.handler.js";
|
|
44
44
|
import { OpenAPIDocsHandler } from "../handlers/request/openapi-docs.handler.js";
|
|
45
|
-
import {
|
|
45
|
+
import { InternalAgentsListHandler } from "../handlers/request/internal-agents-list.handler.js";
|
|
46
|
+
import { AgentStreamHandler } from "../handlers/request/agent-stream.handler.js";
|
|
47
|
+
import { AgentRunResumeHandler } from "../handlers/request/agent-run-resume.handler.js";
|
|
48
|
+
import { AgentRunCancelHandler } from "../handlers/request/agent-run-cancel.handler.js";
|
|
46
49
|
import { ChannelInvokeHandler } from "../handlers/request/channel-invoke.handler.js";
|
|
47
50
|
import { DevDashboardHandler } from "../handlers/dev/dashboard/index.js";
|
|
48
51
|
import { ProjectsHandler } from "../handlers/dev/projects/index.js";
|
|
@@ -125,7 +128,10 @@ export function createVeryfrontHandler(projectDir, adapter, opts = { projectDir
|
|
|
125
128
|
new DebugContextHandler(),
|
|
126
129
|
new OpenAPIHandler(),
|
|
127
130
|
new OpenAPIDocsHandler(),
|
|
128
|
-
new
|
|
131
|
+
new InternalAgentsListHandler(),
|
|
132
|
+
new AgentStreamHandler(),
|
|
133
|
+
new AgentRunResumeHandler(),
|
|
134
|
+
new AgentRunCancelHandler(),
|
|
129
135
|
new ChannelInvokeHandler(),
|
|
130
136
|
new DevDashboardHandler(),
|
|
131
137
|
new ProjectsHandler(),
|
package/package.json
CHANGED
package/src/deno.js
CHANGED
|
@@ -243,6 +243,7 @@ export class AgentRuntime {
|
|
|
243
243
|
callbacks?: {
|
|
244
244
|
onToolCall?: (toolCall: ToolCall) => void;
|
|
245
245
|
onChunk?: (chunk: string) => void;
|
|
246
|
+
onFinish?: (response: AgentResponse) => void;
|
|
246
247
|
},
|
|
247
248
|
modelOverride?: string,
|
|
248
249
|
maxOutputTokensOverride?: number,
|
|
@@ -298,7 +299,7 @@ export class AgentRuntime {
|
|
|
298
299
|
});
|
|
299
300
|
sendSSE(controller, encoder, { type: "text-start", id: textPartId });
|
|
300
301
|
|
|
301
|
-
await this.executeAgentLoopStreaming(
|
|
302
|
+
const response = await this.executeAgentLoopStreaming(
|
|
302
303
|
systemPrompt,
|
|
303
304
|
memoryMessages,
|
|
304
305
|
controller,
|
|
@@ -310,6 +311,7 @@ export class AgentRuntime {
|
|
|
310
311
|
languageModel,
|
|
311
312
|
maxOutputTokensOverride,
|
|
312
313
|
);
|
|
314
|
+
callbacks?.onFinish?.(response);
|
|
313
315
|
|
|
314
316
|
sendSSE(controller, encoder, { type: "text-end", id: textPartId });
|
|
315
317
|
sendSSE(controller, encoder, { type: "message-finish" });
|
|
@@ -479,7 +481,10 @@ export class AgentRuntime {
|
|
|
479
481
|
toolCall.status = "executing";
|
|
480
482
|
const startTime = Date.now();
|
|
481
483
|
|
|
482
|
-
const result = await executeTool(tc.toolName, toolCall.args, {
|
|
484
|
+
const result = await executeTool(tc.toolName, toolCall.args, {
|
|
485
|
+
agentId: this.id,
|
|
486
|
+
toolCallId: tc.toolCallId,
|
|
487
|
+
});
|
|
483
488
|
|
|
484
489
|
toolCall.status = "completed";
|
|
485
490
|
toolCall.result = result;
|
|
@@ -561,6 +566,7 @@ export class AgentRuntime {
|
|
|
561
566
|
callbacks?: {
|
|
562
567
|
onToolCall?: (toolCall: ToolCall) => void;
|
|
563
568
|
onChunk?: (chunk: string) => void;
|
|
569
|
+
onFinish?: (response: AgentResponse) => void;
|
|
564
570
|
},
|
|
565
571
|
textPartId?: string,
|
|
566
572
|
toolContext?: Record<string, unknown>,
|
|
@@ -590,6 +596,7 @@ export class AgentRuntime {
|
|
|
590
596
|
|
|
591
597
|
// Request-scoped skill policy (not class-level mutable state)
|
|
592
598
|
let activeSkillPolicy: string[] | undefined;
|
|
599
|
+
let finalFinishReason: string | undefined;
|
|
593
600
|
|
|
594
601
|
for (let step = 0; step < maxSteps; step++) {
|
|
595
602
|
sendSSE(controller, encoder, { type: "step-start" });
|
|
@@ -617,6 +624,7 @@ export class AgentRuntime {
|
|
|
617
624
|
onChunk: callbacks?.onChunk,
|
|
618
625
|
onUsage: (usage) => accumulateUsage(totalUsage, usage),
|
|
619
626
|
});
|
|
627
|
+
finalFinishReason = state.finishReason ?? finalFinishReason;
|
|
620
628
|
|
|
621
629
|
const streamParts: MessagePart[] = [];
|
|
622
630
|
if (state.accumulatedText) streamParts.push({ type: "text", text: state.accumulatedText });
|
|
@@ -707,6 +715,7 @@ export class AgentRuntime {
|
|
|
707
715
|
|
|
708
716
|
const result = await executeTool(tc.name, toolCall.args, {
|
|
709
717
|
agentId: this.id,
|
|
718
|
+
toolCallId: tc.id,
|
|
710
719
|
...toolContext,
|
|
711
720
|
});
|
|
712
721
|
|
|
@@ -768,6 +777,7 @@ export class AgentRuntime {
|
|
|
768
777
|
toolCalls,
|
|
769
778
|
status: "completed",
|
|
770
779
|
usage: totalUsage,
|
|
780
|
+
metadata: finalFinishReason ? { finishReason: finalFinishReason } : undefined,
|
|
771
781
|
};
|
|
772
782
|
}
|
|
773
783
|
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import * as dntShim from "../../_dnt.shims.js";
|
|
2
|
+
import type { Agent } from "../agent/index.js";
|
|
3
|
+
import type { HandlerContext } from "../types/index.js";
|
|
4
|
+
import { skillRegistry } from "../skill/registry.js";
|
|
5
|
+
import { base64urlEncodeBytes } from "../utils/base64url.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
const SIGNATURE_SKEW_SECONDS = 5;
|
|
9
|
+
|
|
10
|
+
const compactJwsHeaderSchema = z.object({
|
|
11
|
+
alg: z.literal("EdDSA"),
|
|
12
|
+
typ: z.string().optional(),
|
|
13
|
+
kid: z.string().optional(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const ControlPlaneSurfaceSchema = z.enum(["studio", "channels", "a2a", "mcp"]);
|
|
17
|
+
|
|
18
|
+
export const ControlPlaneAgentsListRequestSchema = z.object({
|
|
19
|
+
requestId: z.string().min(1),
|
|
20
|
+
projectId: z.string().min(1),
|
|
21
|
+
surface: ControlPlaneSurfaceSchema,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const RuntimeAgentSkillSchema = z.object({
|
|
25
|
+
id: z.string().min(1),
|
|
26
|
+
name: z.string().min(1),
|
|
27
|
+
description: z.string().optional(),
|
|
28
|
+
tags: z.array(z.string()).optional(),
|
|
29
|
+
examples: z.array(z.string()).optional(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const RuntimeAgentSchema = z.object({
|
|
33
|
+
id: z.string().min(1),
|
|
34
|
+
name: z.string().min(1),
|
|
35
|
+
description: z.string().nullable().optional(),
|
|
36
|
+
model: z.string().nullable().optional(),
|
|
37
|
+
version: z.string().nullable().optional(),
|
|
38
|
+
skills: z.array(RuntimeAgentSkillSchema).optional(),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const RuntimeAgentListResponseSchema = z.object({
|
|
42
|
+
agents: z.array(RuntimeAgentSchema),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const dispatchClaimsSchema = z.object({
|
|
46
|
+
iss: z.string(),
|
|
47
|
+
aud: z.string(),
|
|
48
|
+
sub: z.string(),
|
|
49
|
+
project_id: z.string(),
|
|
50
|
+
platform: z.string(),
|
|
51
|
+
body_sha256: z.string(),
|
|
52
|
+
iat: z.number().int(),
|
|
53
|
+
exp: z.number().int(),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const controlPlaneClaimsSchema = z.object({
|
|
57
|
+
iss: z.string(),
|
|
58
|
+
aud: z.string(),
|
|
59
|
+
sub: z.string(),
|
|
60
|
+
surface: ControlPlaneSurfaceSchema,
|
|
61
|
+
project_id: z.string(),
|
|
62
|
+
request_hash: z.string(),
|
|
63
|
+
iat: z.number().int(),
|
|
64
|
+
exp: z.number().int(),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export type ControlPlaneSurface = z.infer<typeof ControlPlaneSurfaceSchema>;
|
|
68
|
+
export type ControlPlaneAgentsListRequest = z.infer<typeof ControlPlaneAgentsListRequestSchema>;
|
|
69
|
+
export type RuntimeAgentSkill = z.infer<typeof RuntimeAgentSkillSchema>;
|
|
70
|
+
export type RuntimeAgent = z.infer<typeof RuntimeAgentSchema>;
|
|
71
|
+
export type RuntimeAgentListResponse = z.infer<typeof RuntimeAgentListResponseSchema>;
|
|
72
|
+
export type DispatchClaims = z.infer<typeof dispatchClaimsSchema>;
|
|
73
|
+
export type ControlPlaneClaims = z.infer<typeof controlPlaneClaimsSchema>;
|
|
74
|
+
|
|
75
|
+
export interface RuntimeAgentDiscoveryDeps {
|
|
76
|
+
ensureProjectDiscovery: (ctx: HandlerContext) => Promise<void>;
|
|
77
|
+
getAgent: (id: string) => Agent | undefined;
|
|
78
|
+
getAllAgentIds: () => string[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
type SignedRequestClaims = {
|
|
82
|
+
aud: string;
|
|
83
|
+
exp: number;
|
|
84
|
+
iat: number;
|
|
85
|
+
project_id: string;
|
|
86
|
+
sub: string;
|
|
87
|
+
} & Record<string, unknown>;
|
|
88
|
+
|
|
89
|
+
function base64urlDecodeToBytes(input: string): ArrayBuffer {
|
|
90
|
+
const normalized = input
|
|
91
|
+
.replaceAll("-", "+")
|
|
92
|
+
.replaceAll("_", "/")
|
|
93
|
+
.padEnd(Math.ceil(input.length / 4) * 4, "=");
|
|
94
|
+
|
|
95
|
+
return toArrayBuffer(Uint8Array.from(atob(normalized), (char) => char.charCodeAt(0)));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function toArrayBuffer(bytes: Uint8Array): ArrayBuffer {
|
|
99
|
+
const buffer = new ArrayBuffer(bytes.byteLength);
|
|
100
|
+
new Uint8Array(buffer).set(bytes);
|
|
101
|
+
return buffer;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function pemToDer(pem: string, label: string): ArrayBuffer {
|
|
105
|
+
const body = pem
|
|
106
|
+
.replace(`-----BEGIN ${label}-----`, "")
|
|
107
|
+
.replace(`-----END ${label}-----`, "")
|
|
108
|
+
.replace(/\s/g, "");
|
|
109
|
+
|
|
110
|
+
return toArrayBuffer(Uint8Array.from(atob(body), (char) => char.charCodeAt(0)));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function importEd25519PublicKey(pem: string): Promise<dntShim.CryptoKey> {
|
|
114
|
+
return dntShim.crypto.subtle.importKey(
|
|
115
|
+
"spki",
|
|
116
|
+
pemToDer(pem, "PUBLIC KEY"),
|
|
117
|
+
"Ed25519",
|
|
118
|
+
false,
|
|
119
|
+
["verify"],
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function sha256Base64url(body: string): Promise<string> {
|
|
124
|
+
const hash = await dntShim.crypto.subtle.digest("SHA-256", new TextEncoder().encode(body));
|
|
125
|
+
return base64urlEncodeBytes(new Uint8Array(hash));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function verifySignedRequestJws<TClaims extends SignedRequestClaims>(
|
|
129
|
+
jws: string,
|
|
130
|
+
body: string,
|
|
131
|
+
options: {
|
|
132
|
+
audience: string;
|
|
133
|
+
claimsSchema: z.ZodType<TClaims>;
|
|
134
|
+
expectedProjectId?: string;
|
|
135
|
+
expectedSubject?: string;
|
|
136
|
+
hashClaimKey: keyof TClaims & string;
|
|
137
|
+
maxAgeSeconds: number;
|
|
138
|
+
publicKeyPem: string;
|
|
139
|
+
scopedClaim?: {
|
|
140
|
+
key: keyof TClaims & string;
|
|
141
|
+
label: string;
|
|
142
|
+
value: string;
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
): Promise<TClaims> {
|
|
146
|
+
const parts = jws.split(".");
|
|
147
|
+
if (parts.length !== 3) {
|
|
148
|
+
throw new Error("Control-plane signature must be a compact JWS");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const encodedHeader = parts[0];
|
|
152
|
+
const encodedPayload = parts[1];
|
|
153
|
+
const encodedSignature = parts[2];
|
|
154
|
+
if (!encodedHeader || !encodedPayload || !encodedSignature) {
|
|
155
|
+
throw new Error("Control-plane signature must include header, payload, and signature");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const header = compactJwsHeaderSchema.parse(
|
|
159
|
+
JSON.parse(new TextDecoder().decode(base64urlDecodeToBytes(encodedHeader))),
|
|
160
|
+
);
|
|
161
|
+
const claims = options.claimsSchema.parse(
|
|
162
|
+
JSON.parse(new TextDecoder().decode(base64urlDecodeToBytes(encodedPayload))),
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
if (header.alg !== "EdDSA") {
|
|
166
|
+
throw new Error("Unsupported control-plane JWS algorithm");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const signingInput = new TextEncoder().encode(`${encodedHeader}.${encodedPayload}`);
|
|
170
|
+
const signature = base64urlDecodeToBytes(encodedSignature);
|
|
171
|
+
const publicKey = await importEd25519PublicKey(options.publicKeyPem);
|
|
172
|
+
const verified = await dntShim.crypto.subtle.verify("Ed25519", publicKey, signature, signingInput);
|
|
173
|
+
|
|
174
|
+
if (!verified) {
|
|
175
|
+
throw new Error("Control-plane signature verification failed");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (claims.iss !== "veryfront-api") {
|
|
179
|
+
throw new Error("Control-plane issuer mismatch");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (claims.aud !== options.audience) {
|
|
183
|
+
throw new Error("Control-plane audience mismatch");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (options.expectedProjectId && claims.project_id !== options.expectedProjectId) {
|
|
187
|
+
throw new Error("Control-plane project mismatch");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (options.expectedSubject && claims.sub !== options.expectedSubject) {
|
|
191
|
+
throw new Error("Control-plane subject mismatch");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (options.scopedClaim && claims[options.scopedClaim.key] !== options.scopedClaim.value) {
|
|
195
|
+
throw new Error(`Control-plane ${options.scopedClaim.label} mismatch`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const now = Math.floor(Date.now() / 1000);
|
|
199
|
+
if (claims.exp <= now) {
|
|
200
|
+
throw new Error("Control-plane signature expired");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (claims.iat > now + SIGNATURE_SKEW_SECONDS) {
|
|
204
|
+
throw new Error("Control-plane signature issued in the future");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (now - claims.iat > options.maxAgeSeconds) {
|
|
208
|
+
throw new Error("Control-plane signature is too old");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const requestHash = claims[options.hashClaimKey];
|
|
212
|
+
if (typeof requestHash !== "string") {
|
|
213
|
+
throw new Error("Control-plane request hash is missing");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const bodyHash = await sha256Base64url(body);
|
|
217
|
+
if (requestHash !== bodyHash) {
|
|
218
|
+
throw new Error("Control-plane body hash mismatch");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return claims;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function resolveAgentSkills(agent: Agent): RuntimeAgentSkill[] {
|
|
225
|
+
if (!agent.config.skills) {
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return Array.from(skillRegistry.resolveForAgent(agent.config.skills).values())
|
|
230
|
+
.map((skill) =>
|
|
231
|
+
RuntimeAgentSkillSchema.parse({
|
|
232
|
+
id: skill.id,
|
|
233
|
+
name: skill.metadata.name || skill.id,
|
|
234
|
+
...(skill.metadata.description ? { description: skill.metadata.description } : {}),
|
|
235
|
+
})
|
|
236
|
+
)
|
|
237
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function getRuntimeAgentMetadata(agent: Agent): RuntimeAgent {
|
|
241
|
+
const rawConfig = agent.config as unknown as Record<string, unknown>;
|
|
242
|
+
|
|
243
|
+
return RuntimeAgentSchema.parse({
|
|
244
|
+
id: agent.id,
|
|
245
|
+
name: typeof rawConfig.name === "string" && rawConfig.name.trim().length > 0
|
|
246
|
+
? rawConfig.name
|
|
247
|
+
: agent.id,
|
|
248
|
+
description: typeof rawConfig.description === "string" ? rawConfig.description : null,
|
|
249
|
+
model: agent.config.model ?? null,
|
|
250
|
+
version: typeof rawConfig.version === "string" ? rawConfig.version : null,
|
|
251
|
+
skills: resolveAgentSkills(agent),
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export async function listRuntimeAgents(
|
|
256
|
+
ctx: HandlerContext,
|
|
257
|
+
deps: RuntimeAgentDiscoveryDeps,
|
|
258
|
+
): Promise<RuntimeAgentListResponse> {
|
|
259
|
+
await deps.ensureProjectDiscovery(ctx);
|
|
260
|
+
|
|
261
|
+
const agents = deps.getAllAgentIds()
|
|
262
|
+
.map((id) => deps.getAgent(id))
|
|
263
|
+
.filter((agent): agent is Agent => Boolean(agent))
|
|
264
|
+
.map(getRuntimeAgentMetadata)
|
|
265
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
266
|
+
|
|
267
|
+
return RuntimeAgentListResponseSchema.parse({ agents });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export async function verifyDispatchJws(
|
|
271
|
+
jws: string,
|
|
272
|
+
body: string,
|
|
273
|
+
options: {
|
|
274
|
+
audience: string;
|
|
275
|
+
expectedPlatform?: string;
|
|
276
|
+
expectedProjectId?: string;
|
|
277
|
+
expectedSubject?: string;
|
|
278
|
+
maxAgeSeconds: number;
|
|
279
|
+
publicKeyPem: string;
|
|
280
|
+
},
|
|
281
|
+
): Promise<DispatchClaims> {
|
|
282
|
+
return verifySignedRequestJws(jws, body, {
|
|
283
|
+
audience: options.audience,
|
|
284
|
+
claimsSchema: dispatchClaimsSchema,
|
|
285
|
+
expectedProjectId: options.expectedProjectId,
|
|
286
|
+
...(options.expectedSubject ? { expectedSubject: options.expectedSubject } : {}),
|
|
287
|
+
hashClaimKey: "body_sha256",
|
|
288
|
+
maxAgeSeconds: options.maxAgeSeconds,
|
|
289
|
+
publicKeyPem: options.publicKeyPem,
|
|
290
|
+
...(options.expectedPlatform
|
|
291
|
+
? {
|
|
292
|
+
scopedClaim: {
|
|
293
|
+
key: "platform" as const,
|
|
294
|
+
label: "platform",
|
|
295
|
+
value: options.expectedPlatform,
|
|
296
|
+
},
|
|
297
|
+
}
|
|
298
|
+
: {}),
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export async function verifyControlPlaneJws(
|
|
303
|
+
jws: string,
|
|
304
|
+
body: string,
|
|
305
|
+
options: {
|
|
306
|
+
audience: string;
|
|
307
|
+
expectedProjectId?: string;
|
|
308
|
+
expectedSubject?: string;
|
|
309
|
+
expectedSurface?: ControlPlaneSurface;
|
|
310
|
+
maxAgeSeconds: number;
|
|
311
|
+
publicKeyPem: string;
|
|
312
|
+
},
|
|
313
|
+
): Promise<ControlPlaneClaims> {
|
|
314
|
+
return verifySignedRequestJws(jws, body, {
|
|
315
|
+
audience: options.audience,
|
|
316
|
+
claimsSchema: controlPlaneClaimsSchema,
|
|
317
|
+
expectedProjectId: options.expectedProjectId,
|
|
318
|
+
...(options.expectedSubject ? { expectedSubject: options.expectedSubject } : {}),
|
|
319
|
+
hashClaimKey: "request_hash",
|
|
320
|
+
maxAgeSeconds: options.maxAgeSeconds,
|
|
321
|
+
publicKeyPem: options.publicKeyPem,
|
|
322
|
+
...(options.expectedSurface
|
|
323
|
+
? {
|
|
324
|
+
scopedClaim: {
|
|
325
|
+
key: "surface" as const,
|
|
326
|
+
label: "surface",
|
|
327
|
+
value: options.expectedSurface,
|
|
328
|
+
},
|
|
329
|
+
}
|
|
330
|
+
: {}),
|
|
331
|
+
});
|
|
332
|
+
}
|