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.
Files changed (69) hide show
  1. package/esm/deno.js +1 -1
  2. package/esm/src/agent/runtime/index.d.ts +1 -0
  3. package/esm/src/agent/runtime/index.d.ts.map +1 -1
  4. package/esm/src/agent/runtime/index.js +10 -2
  5. package/esm/src/channels/control-plane.d.ts +259 -0
  6. package/esm/src/channels/control-plane.d.ts.map +1 -0
  7. package/esm/src/channels/control-plane.js +212 -0
  8. package/esm/src/channels/invoke.d.ts +3 -40
  9. package/esm/src/channels/invoke.d.ts.map +1 -1
  10. package/esm/src/channels/invoke.js +9 -106
  11. package/esm/src/internal-agents/ag-ui-sse.d.ts +35 -0
  12. package/esm/src/internal-agents/ag-ui-sse.d.ts.map +1 -0
  13. package/esm/src/internal-agents/ag-ui-sse.js +263 -0
  14. package/esm/src/internal-agents/control-plane-auth.d.ts +20 -0
  15. package/esm/src/internal-agents/control-plane-auth.d.ts.map +1 -0
  16. package/esm/src/internal-agents/control-plane-auth.js +56 -0
  17. package/esm/src/internal-agents/request-body.d.ts +9 -0
  18. package/esm/src/internal-agents/request-body.d.ts.map +1 -0
  19. package/esm/src/internal-agents/request-body.js +28 -0
  20. package/esm/src/internal-agents/run-stream.d.ts +14 -0
  21. package/esm/src/internal-agents/run-stream.d.ts.map +1 -0
  22. package/esm/src/internal-agents/run-stream.js +259 -0
  23. package/esm/src/internal-agents/schema.d.ts +268 -0
  24. package/esm/src/internal-agents/schema.d.ts.map +1 -0
  25. package/esm/src/internal-agents/schema.js +71 -0
  26. package/esm/src/internal-agents/session-manager.d.ts +63 -0
  27. package/esm/src/internal-agents/session-manager.d.ts.map +1 -0
  28. package/esm/src/internal-agents/session-manager.js +258 -0
  29. package/esm/src/platform/adapters/runtime/deno/adapter.d.ts.map +1 -1
  30. package/esm/src/platform/adapters/runtime/deno/adapter.js +4 -13
  31. package/esm/src/platform/compat/process.d.ts.map +1 -1
  32. package/esm/src/platform/compat/process.js +42 -5
  33. package/esm/src/server/bootstrap.js +9 -1
  34. package/esm/src/server/handlers/request/agent-run-cancel.handler.d.ts +11 -0
  35. package/esm/src/server/handlers/request/agent-run-cancel.handler.d.ts.map +1 -0
  36. package/esm/src/server/handlers/request/agent-run-cancel.handler.js +62 -0
  37. package/esm/src/server/handlers/request/agent-run-resume.handler.d.ts +11 -0
  38. package/esm/src/server/handlers/request/agent-run-resume.handler.d.ts.map +1 -0
  39. package/esm/src/server/handlers/request/agent-run-resume.handler.js +77 -0
  40. package/esm/src/server/handlers/request/agent-stream.handler.d.ts +14 -0
  41. package/esm/src/server/handlers/request/agent-stream.handler.d.ts.map +1 -0
  42. package/esm/src/server/handlers/request/agent-stream.handler.js +86 -0
  43. package/esm/src/server/handlers/request/{channel-assistants.handler.d.ts → internal-agents-list.handler.d.ts} +4 -4
  44. package/esm/src/server/handlers/request/internal-agents-list.handler.d.ts.map +1 -0
  45. package/esm/src/server/handlers/request/internal-agents-list.handler.js +73 -0
  46. package/esm/src/server/runtime-handler/index.d.ts.map +1 -1
  47. package/esm/src/server/runtime-handler/index.js +8 -2
  48. package/package.json +1 -1
  49. package/src/deno.js +1 -1
  50. package/src/src/agent/runtime/index.ts +12 -2
  51. package/src/src/channels/control-plane.ts +332 -0
  52. package/src/src/channels/invoke.ts +12 -157
  53. package/src/src/internal-agents/ag-ui-sse.ts +327 -0
  54. package/src/src/internal-agents/control-plane-auth.ts +82 -0
  55. package/src/src/internal-agents/request-body.ts +42 -0
  56. package/src/src/internal-agents/run-stream.ts +354 -0
  57. package/src/src/internal-agents/schema.ts +102 -0
  58. package/src/src/internal-agents/session-manager.ts +358 -0
  59. package/src/src/platform/adapters/runtime/deno/adapter.ts +9 -11
  60. package/src/src/platform/compat/process.ts +56 -3
  61. package/src/src/server/bootstrap.ts +13 -1
  62. package/src/src/server/handlers/request/agent-run-cancel.handler.ts +86 -0
  63. package/src/src/server/handlers/request/agent-run-resume.handler.ts +108 -0
  64. package/src/src/server/handlers/request/agent-stream.handler.ts +125 -0
  65. package/src/src/server/handlers/request/internal-agents-list.handler.ts +100 -0
  66. package/src/src/server/runtime-handler/index.ts +8 -2
  67. package/esm/src/server/handlers/request/channel-assistants.handler.d.ts.map +0 -1
  68. package/esm/src/server/handlers/request/channel-assistants.handler.js +0 -71
  69. 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 ChannelInvokeDeps } from "../../../channels/invoke.js";
5
- export declare class ChannelAssistantsHandler extends BaseHandler {
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?: ChannelInvokeDeps);
8
+ constructor(deps?: RuntimeAgentDiscoveryDeps);
9
9
  handle(req: dntShim.Request, ctx: HandlerContext): Promise<HandlerResult>;
10
10
  }
11
- //# sourceMappingURL=channel-assistants.handler.d.ts.map
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;AA0F7D,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,CAyanF;AAGD,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,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 { ChannelAssistantsHandler } from "../handlers/request/channel-assistants.handler.js";
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 ChannelAssistantsHandler(),
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.64",
3
+ "version": "0.1.67",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",
package/src/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.64",
3
+ "version": "0.1.67",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -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, { agentId: this.id });
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
+ }