veryfront 0.1.63 → 0.1.65

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 (68) 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 +71 -44
  9. package/esm/src/channels/invoke.d.ts.map +1 -1
  10. package/esm/src/channels/invoke.js +33 -114
  11. package/esm/src/integrations/endpoint-executor.d.ts +1 -0
  12. package/esm/src/integrations/endpoint-executor.d.ts.map +1 -1
  13. package/esm/src/integrations/endpoint-executor.js +44 -0
  14. package/esm/src/internal-agents/ag-ui-sse.d.ts +35 -0
  15. package/esm/src/internal-agents/ag-ui-sse.d.ts.map +1 -0
  16. package/esm/src/internal-agents/ag-ui-sse.js +263 -0
  17. package/esm/src/internal-agents/control-plane-auth.d.ts +20 -0
  18. package/esm/src/internal-agents/control-plane-auth.d.ts.map +1 -0
  19. package/esm/src/internal-agents/control-plane-auth.js +56 -0
  20. package/esm/src/internal-agents/request-body.d.ts +9 -0
  21. package/esm/src/internal-agents/request-body.d.ts.map +1 -0
  22. package/esm/src/internal-agents/request-body.js +28 -0
  23. package/esm/src/internal-agents/run-stream.d.ts +14 -0
  24. package/esm/src/internal-agents/run-stream.d.ts.map +1 -0
  25. package/esm/src/internal-agents/run-stream.js +259 -0
  26. package/esm/src/internal-agents/schema.d.ts +268 -0
  27. package/esm/src/internal-agents/schema.d.ts.map +1 -0
  28. package/esm/src/internal-agents/schema.js +71 -0
  29. package/esm/src/internal-agents/session-manager.d.ts +63 -0
  30. package/esm/src/internal-agents/session-manager.d.ts.map +1 -0
  31. package/esm/src/internal-agents/session-manager.js +258 -0
  32. package/esm/src/platform/adapters/runtime/deno/adapter.d.ts.map +1 -1
  33. package/esm/src/platform/adapters/runtime/deno/adapter.js +4 -13
  34. package/esm/src/platform/compat/process.d.ts.map +1 -1
  35. package/esm/src/platform/compat/process.js +42 -5
  36. package/esm/src/server/handlers/request/agent-run-cancel.handler.d.ts +11 -0
  37. package/esm/src/server/handlers/request/agent-run-cancel.handler.d.ts.map +1 -0
  38. package/esm/src/server/handlers/request/agent-run-cancel.handler.js +62 -0
  39. package/esm/src/server/handlers/request/agent-run-resume.handler.d.ts +11 -0
  40. package/esm/src/server/handlers/request/agent-run-resume.handler.d.ts.map +1 -0
  41. package/esm/src/server/handlers/request/agent-run-resume.handler.js +77 -0
  42. package/esm/src/server/handlers/request/agent-stream.handler.d.ts +14 -0
  43. package/esm/src/server/handlers/request/agent-stream.handler.d.ts.map +1 -0
  44. package/esm/src/server/handlers/request/agent-stream.handler.js +86 -0
  45. package/esm/src/server/handlers/request/internal-agents-list.handler.d.ts +11 -0
  46. package/esm/src/server/handlers/request/internal-agents-list.handler.d.ts.map +1 -0
  47. package/esm/src/server/handlers/request/internal-agents-list.handler.js +73 -0
  48. package/esm/src/server/runtime-handler/index.d.ts.map +1 -1
  49. package/esm/src/server/runtime-handler/index.js +8 -0
  50. package/package.json +1 -1
  51. package/src/deno.js +1 -1
  52. package/src/src/agent/runtime/index.ts +12 -2
  53. package/src/src/channels/control-plane.ts +332 -0
  54. package/src/src/channels/invoke.ts +44 -164
  55. package/src/src/integrations/endpoint-executor.ts +51 -0
  56. package/src/src/internal-agents/ag-ui-sse.ts +327 -0
  57. package/src/src/internal-agents/control-plane-auth.ts +82 -0
  58. package/src/src/internal-agents/request-body.ts +42 -0
  59. package/src/src/internal-agents/run-stream.ts +354 -0
  60. package/src/src/internal-agents/schema.ts +102 -0
  61. package/src/src/internal-agents/session-manager.ts +358 -0
  62. package/src/src/platform/adapters/runtime/deno/adapter.ts +9 -11
  63. package/src/src/platform/compat/process.ts +56 -3
  64. package/src/src/server/handlers/request/agent-run-cancel.handler.ts +86 -0
  65. package/src/src/server/handlers/request/agent-run-resume.handler.ts +108 -0
  66. package/src/src/server/handlers/request/agent-stream.handler.ts +125 -0
  67. package/src/src/server/handlers/request/internal-agents-list.handler.ts +100 -0
  68. package/src/src/server/runtime-handler/index.ts +8 -0
@@ -0,0 +1,327 @@
1
+ import * as dntShim from "../../_dnt.shims.js";
2
+ import type { AgentResponse } from "../agent/index.js";
3
+ import { serverLogger } from "../utils/index.js";
4
+ import { z } from "zod";
5
+
6
+ const encoder = new TextEncoder();
7
+ const logger = serverLogger.component("internal-agents-ag-ui-sse");
8
+
9
+ type RuntimeDataEvent = Record<string, unknown> & { type: string };
10
+
11
+ export interface RunFinishedMetadata {
12
+ provider?: string;
13
+ model?: string;
14
+ inputTokens?: number;
15
+ outputTokens?: number;
16
+ totalTokens?: number;
17
+ finishReason?: string;
18
+ }
19
+
20
+ export interface StreamTransformState {
21
+ messageId: string | null;
22
+ textOpen: boolean;
23
+ stepIndex: number;
24
+ sawTerminalError: boolean;
25
+ metadata: RunFinishedMetadata;
26
+ }
27
+
28
+ export function createStreamTransformState(): StreamTransformState {
29
+ return {
30
+ messageId: null,
31
+ textOpen: false,
32
+ stepIndex: 0,
33
+ sawTerminalError: false,
34
+ metadata: {},
35
+ };
36
+ }
37
+
38
+ const agUiEventPayloadSchemas = {
39
+ RunStarted: z.object({
40
+ runId: z.string().min(1),
41
+ threadId: z.string().min(1),
42
+ agentId: z.string().min(1),
43
+ }),
44
+ TextMessageStart: z.object({
45
+ messageId: z.string().min(1),
46
+ role: z.literal("assistant"),
47
+ }),
48
+ TextMessageContent: z.object({
49
+ messageId: z.string().min(1),
50
+ delta: z.string(),
51
+ }),
52
+ TextMessageEnd: z.object({
53
+ messageId: z.string().min(1),
54
+ }),
55
+ ToolCallStart: z.object({
56
+ toolCallId: z.string().min(1),
57
+ toolCallName: z.string().min(1),
58
+ }),
59
+ ToolCallArgs: z.object({
60
+ toolCallId: z.string().min(1),
61
+ delta: z.string(),
62
+ }),
63
+ ToolCallEnd: z.object({
64
+ toolCallId: z.string().min(1),
65
+ }),
66
+ ToolCallResult: z.object({
67
+ toolCallId: z.string().min(1),
68
+ result: z.unknown(),
69
+ isError: z.boolean().optional(),
70
+ }),
71
+ StepStarted: z.object({
72
+ stepIndex: z.number().int().positive(),
73
+ }),
74
+ StepFinished: z.object({
75
+ stepIndex: z.number().int().positive(),
76
+ }),
77
+ RunError: z.object({
78
+ code: z.string().min(1).optional(),
79
+ message: z.string().min(1),
80
+ }),
81
+ RunFinished: z.object({
82
+ metadata: z.object({
83
+ provider: z.string().optional(),
84
+ model: z.string().optional(),
85
+ inputTokens: z.number().int().nonnegative().optional(),
86
+ outputTokens: z.number().int().nonnegative().optional(),
87
+ totalTokens: z.number().int().nonnegative().optional(),
88
+ finishReason: z.string().optional(),
89
+ }),
90
+ }),
91
+ } as const satisfies Record<string, z.ZodType<Record<string, unknown>>>;
92
+
93
+ type AgUiEventName = keyof typeof agUiEventPayloadSchemas;
94
+
95
+ export function formatAgUiEvent(event: string, payload: Record<string, unknown>): Uint8Array {
96
+ const schema = agUiEventPayloadSchemas[event as AgUiEventName];
97
+ const validatedPayload = schema ? schema.parse(payload) : payload;
98
+ return encoder.encode(`event: ${event}\ndata: ${JSON.stringify(validatedPayload)}\n\n`);
99
+ }
100
+
101
+ export function parseSseJsonEvents(chunk: string): {
102
+ events: RuntimeDataEvent[];
103
+ remainder: string;
104
+ } {
105
+ const blocks = chunk.split("\n\n");
106
+ const remainder = blocks.pop() ?? "";
107
+ const events = blocks.flatMap((block) => {
108
+ const dataLines = block.split("\n")
109
+ .filter((line) => line.startsWith("data:"))
110
+ .map((line) => line.slice(5).trimStart());
111
+
112
+ if (!dataLines.length) {
113
+ return [];
114
+ }
115
+
116
+ try {
117
+ const payload = JSON.parse(dataLines.join("\n")) as RuntimeDataEvent;
118
+ return [payload];
119
+ } catch (error) {
120
+ logger.warn("Skipping malformed runtime SSE event payload", {
121
+ error,
122
+ dataLength: dataLines.join("\n").length,
123
+ });
124
+ return [];
125
+ }
126
+ });
127
+
128
+ return { events, remainder };
129
+ }
130
+
131
+ function getMessageId(state: StreamTransformState, event: RuntimeDataEvent): string {
132
+ if (typeof event.messageId === "string") {
133
+ state.messageId = event.messageId;
134
+ return event.messageId;
135
+ }
136
+
137
+ if (!state.messageId && typeof event.id === "string") {
138
+ state.messageId = event.id;
139
+ }
140
+
141
+ if (!state.messageId) {
142
+ state.messageId = dntShim.crypto.randomUUID();
143
+ }
144
+
145
+ return state.messageId;
146
+ }
147
+
148
+ function applyDataMetadata(state: StreamTransformState, event: RuntimeDataEvent): void {
149
+ const data = event.data && typeof event.data === "object" && !Array.isArray(event.data)
150
+ ? event.data as Record<string, unknown>
151
+ : event;
152
+
153
+ if (typeof data.model === "string") {
154
+ state.metadata.model = data.model;
155
+ const provider = data.model.split("/")[0];
156
+ if (provider) {
157
+ state.metadata.provider = provider;
158
+ }
159
+ }
160
+ }
161
+
162
+ function applyResponseMetadata(state: StreamTransformState, response: AgentResponse | null): void {
163
+ if (!response) return;
164
+
165
+ if (response.usage) {
166
+ state.metadata.inputTokens = response.usage.promptTokens;
167
+ state.metadata.outputTokens = response.usage.completionTokens;
168
+ state.metadata.totalTokens = response.usage.totalTokens;
169
+ }
170
+
171
+ const finishReason = response.metadata && typeof response.metadata === "object"
172
+ ? response.metadata.finishReason
173
+ : undefined;
174
+ if (typeof finishReason === "string") {
175
+ state.metadata.finishReason = finishReason;
176
+ }
177
+ }
178
+
179
+ export function mapRuntimeEventToAgUi(
180
+ state: StreamTransformState,
181
+ event: RuntimeDataEvent,
182
+ ): Array<{ event: string; payload: Record<string, unknown> }> {
183
+ switch (event.type) {
184
+ case "message-start":
185
+ getMessageId(state, event);
186
+ return [];
187
+
188
+ case "text-start": {
189
+ if (state.textOpen) return [];
190
+ const messageId = getMessageId(state, event);
191
+ state.textOpen = true;
192
+ return [{
193
+ event: "TextMessageStart",
194
+ payload: { messageId, role: "assistant" },
195
+ }];
196
+ }
197
+
198
+ case "text-delta": {
199
+ const messageId = getMessageId(state, event);
200
+ if (!state.textOpen) {
201
+ state.textOpen = true;
202
+ return [
203
+ { event: "TextMessageStart", payload: { messageId, role: "assistant" } },
204
+ {
205
+ event: "TextMessageContent",
206
+ payload: { messageId, delta: typeof event.delta === "string" ? event.delta : "" },
207
+ },
208
+ ];
209
+ }
210
+
211
+ return [{
212
+ event: "TextMessageContent",
213
+ payload: { messageId, delta: typeof event.delta === "string" ? event.delta : "" },
214
+ }];
215
+ }
216
+
217
+ case "text-end": {
218
+ if (!state.textOpen) return [];
219
+ state.textOpen = false;
220
+ return [{
221
+ event: "TextMessageEnd",
222
+ payload: { messageId: getMessageId(state, event) },
223
+ }];
224
+ }
225
+
226
+ case "tool-input-start":
227
+ return [{
228
+ event: "ToolCallStart",
229
+ payload: {
230
+ toolCallId: event.toolCallId,
231
+ toolCallName: event.toolName,
232
+ },
233
+ }];
234
+
235
+ case "tool-input-delta":
236
+ return [{
237
+ event: "ToolCallArgs",
238
+ payload: {
239
+ toolCallId: event.toolCallId,
240
+ delta: typeof event.inputTextDelta === "string" ? event.inputTextDelta : "",
241
+ },
242
+ }];
243
+
244
+ case "tool-input-available":
245
+ return [{
246
+ event: "ToolCallEnd",
247
+ payload: { toolCallId: event.toolCallId },
248
+ }];
249
+
250
+ case "tool-output-available":
251
+ return [{
252
+ event: "ToolCallResult",
253
+ payload: {
254
+ toolCallId: event.toolCallId,
255
+ result: event.output,
256
+ },
257
+ }];
258
+
259
+ case "tool-output-error":
260
+ return [{
261
+ event: "ToolCallResult",
262
+ payload: {
263
+ toolCallId: event.toolCallId,
264
+ result: { error: event.errorText },
265
+ isError: true,
266
+ },
267
+ }];
268
+
269
+ case "step-start":
270
+ state.stepIndex += 1;
271
+ return [{
272
+ event: "StepStarted",
273
+ payload: { stepIndex: state.stepIndex },
274
+ }];
275
+
276
+ case "step-end":
277
+ return [{
278
+ event: "StepFinished",
279
+ payload: { stepIndex: state.stepIndex },
280
+ }];
281
+
282
+ case "data":
283
+ applyDataMetadata(state, event);
284
+ return [];
285
+
286
+ case "error":
287
+ state.sawTerminalError = true;
288
+ return [{
289
+ event: "RunError",
290
+ payload: {
291
+ message: typeof event.error === "string" ? event.error : "Agent run failed",
292
+ },
293
+ }];
294
+
295
+ default:
296
+ return [];
297
+ }
298
+ }
299
+
300
+ export function finalizeRunEvents(
301
+ state: StreamTransformState,
302
+ response: AgentResponse | null,
303
+ ): Array<{ event: string; payload: Record<string, unknown> }> {
304
+ applyResponseMetadata(state, response);
305
+
306
+ if (state.sawTerminalError) {
307
+ return [];
308
+ }
309
+
310
+ const events: Array<{ event: string; payload: Record<string, unknown> }> = [];
311
+ if (state.textOpen) {
312
+ state.textOpen = false;
313
+ events.push({
314
+ event: "TextMessageEnd",
315
+ payload: { messageId: getMessageId(state, { type: "text-end" }) },
316
+ });
317
+ }
318
+
319
+ events.push({
320
+ event: "RunFinished",
321
+ payload: {
322
+ metadata: state.metadata,
323
+ },
324
+ });
325
+
326
+ return events;
327
+ }
@@ -0,0 +1,82 @@
1
+ import * as dntShim from "../../_dnt.shims.js";
2
+ import { verifyControlPlaneJws } from "../channels/control-plane.js";
3
+ import type { HandlerContext } from "../types/index.js";
4
+ import { serverLogger } from "../utils/index.js";
5
+ import { HTTP_INTERNAL_SERVER_ERROR } from "../utils/constants/index.js";
6
+
7
+ const CONTROL_PLANE_JWS_HEADER = "x-veryfront-control-plane-jws";
8
+ const MAX_CONTROL_PLANE_SIGNATURE_AGE_SECONDS = 60;
9
+ const logger = serverLogger.component("internal-agents-auth");
10
+
11
+ export class ControlPlaneRequestError extends Error {
12
+ readonly status: number;
13
+
14
+ constructor(status: number, message: string) {
15
+ super(message);
16
+ this.status = status;
17
+ this.name = "ControlPlaneRequestError";
18
+ }
19
+ }
20
+
21
+ export async function verifyControlPlaneRequest(
22
+ req: dntShim.Request,
23
+ ctx: HandlerContext,
24
+ rawBody: string,
25
+ options: {
26
+ expectedSubject?: string;
27
+ expectedSurface?: "studio" | "channels" | "a2a" | "mcp";
28
+ } = {},
29
+ ) {
30
+ const publicKeyPem = ctx.adapter.env.get("CHANNEL_DISPATCH_SIGNING_PUBLIC_KEY");
31
+ if (!publicKeyPem) {
32
+ throw new ControlPlaneRequestError(
33
+ HTTP_INTERNAL_SERVER_ERROR,
34
+ "Control-plane verification is not configured",
35
+ );
36
+ }
37
+
38
+ const projectSlug = ctx.projectSlug;
39
+ if (!projectSlug) {
40
+ throw new ControlPlaneRequestError(400, "Project context is unavailable");
41
+ }
42
+
43
+ const controlPlaneJws = req.headers.get(CONTROL_PLANE_JWS_HEADER);
44
+ if (!controlPlaneJws) {
45
+ throw new ControlPlaneRequestError(401, "Missing control-plane signature");
46
+ }
47
+
48
+ try {
49
+ return await verifyControlPlaneJws(controlPlaneJws, rawBody, {
50
+ audience: projectSlug,
51
+ expectedProjectId: ctx.projectId,
52
+ expectedSubject: options.expectedSubject,
53
+ expectedSurface: options.expectedSurface,
54
+ publicKeyPem,
55
+ maxAgeSeconds: MAX_CONTROL_PLANE_SIGNATURE_AGE_SECONDS,
56
+ });
57
+ } catch (error) {
58
+ const isAuthError = error instanceof Error && (
59
+ error.message.includes("Control-plane") ||
60
+ error.message.includes("Unsupported")
61
+ );
62
+
63
+ if (isAuthError) {
64
+ logger.warn("Invalid control-plane signature", {
65
+ error,
66
+ projectSlug,
67
+ expectedSubject: options.expectedSubject,
68
+ expectedSurface: options.expectedSurface,
69
+ });
70
+ throw new ControlPlaneRequestError(401, "Invalid control-plane signature");
71
+ }
72
+
73
+ logger.error("Unexpected error during control-plane verification", {
74
+ error,
75
+ projectSlug,
76
+ });
77
+ throw new ControlPlaneRequestError(
78
+ HTTP_INTERNAL_SERVER_ERROR,
79
+ "Internal error during request verification",
80
+ );
81
+ }
82
+ }
@@ -0,0 +1,42 @@
1
+ import * as dntShim from "../../_dnt.shims.js";
2
+ import { VeryfrontError } from "../errors/types.js";
3
+ import { readBodyWithLimit } from "../security/index.js";
4
+ import {
5
+ DEFAULT_MAX_BODY_SIZE_BYTES,
6
+ HTTP_PAYLOAD_TOO_LARGE,
7
+ } from "../utils/constants/index.js";
8
+
9
+ export const INTERNAL_AGENT_STREAM_MAX_BODY_BYTES = DEFAULT_MAX_BODY_SIZE_BYTES;
10
+ export const INTERNAL_AGENT_CONTROL_PLANE_MAX_BODY_BYTES = 128 * 1024;
11
+
12
+ export class InternalAgentRequestBodyTooLargeError extends Error {
13
+ readonly status = HTTP_PAYLOAD_TOO_LARGE;
14
+
15
+ constructor(message = "Payload too large") {
16
+ super(message);
17
+ this.name = "InternalAgentRequestBodyTooLargeError";
18
+ }
19
+ }
20
+
21
+ export async function readInternalAgentRequestBody(
22
+ request: dntShim.Request,
23
+ maxBodyBytes = INTERNAL_AGENT_STREAM_MAX_BODY_BYTES,
24
+ ): Promise<string> {
25
+ if (!request.body) {
26
+ return "";
27
+ }
28
+
29
+ try {
30
+ return await readBodyWithLimit(request, maxBodyBytes);
31
+ } catch (error) {
32
+ if (
33
+ error instanceof VeryfrontError &&
34
+ error.slug === "input-validation-failed" &&
35
+ error.detail === "Request body exceeds size limit"
36
+ ) {
37
+ throw new InternalAgentRequestBodyTooLargeError();
38
+ }
39
+
40
+ throw error;
41
+ }
42
+ }