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
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
import * as dntShim from "../../_dnt.shims.js";
|
|
2
1
|
import type { Agent, AgentMessage as Message, AgentResponse } from "../agent/index.js";
|
|
3
2
|
import { fromError } from "../errors/veryfront-error.js";
|
|
4
3
|
import type { HandlerContext } from "../types/index.js";
|
|
5
4
|
import { serverLogger } from "../utils/index.js";
|
|
6
|
-
import { base64urlEncodeBytes } from "../utils/base64url.js";
|
|
7
5
|
import { z } from "zod";
|
|
8
6
|
import {
|
|
9
7
|
getAgent as getRegisteredAgent,
|
|
10
8
|
getAllAgentIds as getRegisteredAgentIds,
|
|
11
9
|
} from "../agent/composition/composition.js";
|
|
10
|
+
import { listRuntimeAgents, type RuntimeAgentDiscoveryDeps } from "./control-plane.js";
|
|
12
11
|
import { ensureProjectDiscovery as ensureProjectDiscoveryForProject } from "../server/handlers/request/api/project-discovery.js";
|
|
13
12
|
|
|
14
13
|
const logger = serverLogger.component("channels-invoke");
|
|
15
|
-
const SIGNATURE_SKEW_SECONDS = 5;
|
|
16
14
|
|
|
17
15
|
const rawHistoryPartSchema = z.object({
|
|
18
16
|
type: z.string(),
|
|
@@ -125,37 +123,13 @@ export const ChannelInvokeResponseSchema = z.object({
|
|
|
125
123
|
}).optional(),
|
|
126
124
|
});
|
|
127
125
|
|
|
128
|
-
const dispatchHeaderSchema = z.object({
|
|
129
|
-
alg: z.literal("EdDSA"),
|
|
130
|
-
typ: z.string().optional(),
|
|
131
|
-
kid: z.string().optional(),
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
const dispatchClaimsSchema = z.object({
|
|
135
|
-
iss: z.string(),
|
|
136
|
-
aud: z.string(),
|
|
137
|
-
sub: z.string(),
|
|
138
|
-
project_id: z.string(),
|
|
139
|
-
platform: z.string(),
|
|
140
|
-
body_sha256: z.string(),
|
|
141
|
-
iat: z.number().int(),
|
|
142
|
-
exp: z.number().int(),
|
|
143
|
-
});
|
|
144
|
-
|
|
145
126
|
export type ChannelInvokeRequest = z.infer<typeof ChannelInvokeRequestSchema>;
|
|
146
127
|
export type ChannelInvokeResponse = z.infer<typeof ChannelInvokeResponseSchema>;
|
|
147
128
|
export type ChannelAssistantsRequest = z.infer<typeof ChannelAssistantsRequestSchema>;
|
|
148
129
|
export type ChannelAssistantsResponse = z.infer<typeof ChannelAssistantsResponseSchema>;
|
|
149
130
|
|
|
150
131
|
type ChannelResponsePart = z.infer<typeof ChannelResponsePartSchema>;
|
|
151
|
-
|
|
152
|
-
type ChannelAssistant = z.infer<typeof ChannelAssistantSchema>;
|
|
153
|
-
|
|
154
|
-
export interface ChannelInvokeDeps {
|
|
155
|
-
ensureProjectDiscovery: (ctx: HandlerContext) => Promise<void>;
|
|
156
|
-
getAgent: (id: string) => Agent | undefined;
|
|
157
|
-
getAllAgentIds: () => string[];
|
|
158
|
-
}
|
|
132
|
+
export interface ChannelInvokeDeps extends RuntimeAgentDiscoveryDeps {}
|
|
159
133
|
|
|
160
134
|
export const defaultChannelInvokeDeps: ChannelInvokeDeps = {
|
|
161
135
|
ensureProjectDiscovery: ensureProjectDiscoveryForProject,
|
|
@@ -163,142 +137,23 @@ export const defaultChannelInvokeDeps: ChannelInvokeDeps = {
|
|
|
163
137
|
getAllAgentIds: getRegisteredAgentIds,
|
|
164
138
|
};
|
|
165
139
|
|
|
166
|
-
function getAssistantMetadata(agent: Agent): ChannelAssistant {
|
|
167
|
-
const rawConfig = agent.config as unknown as Record<string, unknown>;
|
|
168
|
-
|
|
169
|
-
return ChannelAssistantSchema.parse({
|
|
170
|
-
id: agent.id,
|
|
171
|
-
name: typeof rawConfig.name === "string" && rawConfig.name.trim().length > 0
|
|
172
|
-
? rawConfig.name
|
|
173
|
-
: agent.id,
|
|
174
|
-
description: typeof rawConfig.description === "string" ? rawConfig.description : null,
|
|
175
|
-
model: agent.config.model ?? null,
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
|
|
179
140
|
export async function listChannelAssistants(
|
|
180
141
|
ctx: HandlerContext,
|
|
181
142
|
deps: ChannelInvokeDeps,
|
|
182
143
|
): Promise<ChannelAssistantsResponse> {
|
|
183
|
-
await
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return ChannelAssistantsResponseSchema.parse({ assistants });
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function base64urlDecodeToBytes(input: string): ArrayBuffer {
|
|
195
|
-
const normalized = input
|
|
196
|
-
.replaceAll("-", "+")
|
|
197
|
-
.replaceAll("_", "/")
|
|
198
|
-
.padEnd(Math.ceil(input.length / 4) * 4, "=");
|
|
199
|
-
|
|
200
|
-
return toArrayBuffer(Uint8Array.from(atob(normalized), (char) => char.charCodeAt(0)));
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function toArrayBuffer(bytes: Uint8Array): ArrayBuffer {
|
|
204
|
-
const buffer = new ArrayBuffer(bytes.byteLength);
|
|
205
|
-
new Uint8Array(buffer).set(bytes);
|
|
206
|
-
return buffer;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function pemToDer(pem: string, label: string): ArrayBuffer {
|
|
210
|
-
const body = pem
|
|
211
|
-
.replace(`-----BEGIN ${label}-----`, "")
|
|
212
|
-
.replace(`-----END ${label}-----`, "")
|
|
213
|
-
.replace(/\s/g, "");
|
|
214
|
-
|
|
215
|
-
return toArrayBuffer(Uint8Array.from(atob(body), (char) => char.charCodeAt(0)));
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
async function importEd25519PublicKey(pem: string): Promise<dntShim.CryptoKey> {
|
|
219
|
-
return dntShim.crypto.subtle.importKey(
|
|
220
|
-
"spki",
|
|
221
|
-
pemToDer(pem, "PUBLIC KEY"),
|
|
222
|
-
"Ed25519",
|
|
223
|
-
false,
|
|
224
|
-
["verify"],
|
|
144
|
+
const response = await listRuntimeAgents(ctx, deps);
|
|
145
|
+
const assistants = response.agents.map((agent) =>
|
|
146
|
+
ChannelAssistantSchema.parse({
|
|
147
|
+
id: agent.id,
|
|
148
|
+
name: agent.name,
|
|
149
|
+
description: agent.description ?? null,
|
|
150
|
+
model: agent.model ?? null,
|
|
151
|
+
})
|
|
225
152
|
);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
async function sha256Base64url(body: string): Promise<string> {
|
|
229
|
-
const hash = await dntShim.crypto.subtle.digest("SHA-256", new TextEncoder().encode(body));
|
|
230
|
-
return base64urlEncodeBytes(new Uint8Array(hash));
|
|
231
|
-
}
|
|
232
153
|
|
|
233
|
-
|
|
234
|
-
jws: string,
|
|
235
|
-
body: string,
|
|
236
|
-
options: {
|
|
237
|
-
audience: string;
|
|
238
|
-
publicKeyPem: string;
|
|
239
|
-
maxAgeSeconds: number;
|
|
240
|
-
expectedProjectId?: string;
|
|
241
|
-
},
|
|
242
|
-
): Promise<DispatchClaims> {
|
|
243
|
-
const parts = jws.split(".");
|
|
244
|
-
if (parts.length !== 3) {
|
|
245
|
-
throw new Error("Channel dispatch signature must be a compact JWS");
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const encodedHeader = parts[0];
|
|
249
|
-
const encodedPayload = parts[1];
|
|
250
|
-
const encodedSignature = parts[2];
|
|
251
|
-
if (!encodedHeader || !encodedPayload || !encodedSignature) {
|
|
252
|
-
throw new Error("Channel dispatch signature must include header, payload, and signature");
|
|
253
|
-
}
|
|
254
|
-
const header = dispatchHeaderSchema.parse(
|
|
255
|
-
JSON.parse(new TextDecoder().decode(base64urlDecodeToBytes(encodedHeader))),
|
|
256
|
-
);
|
|
257
|
-
const claims = dispatchClaimsSchema.parse(
|
|
258
|
-
JSON.parse(new TextDecoder().decode(base64urlDecodeToBytes(encodedPayload))),
|
|
259
|
-
);
|
|
260
|
-
|
|
261
|
-
if (header.alg !== "EdDSA") {
|
|
262
|
-
throw new Error("Unsupported channel dispatch JWS algorithm");
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const signingInput = new TextEncoder().encode(`${encodedHeader}.${encodedPayload}`);
|
|
266
|
-
const signature = base64urlDecodeToBytes(encodedSignature);
|
|
267
|
-
const publicKey = await importEd25519PublicKey(options.publicKeyPem);
|
|
268
|
-
const verified = await dntShim.crypto.subtle.verify("Ed25519", publicKey, signature, signingInput);
|
|
269
|
-
|
|
270
|
-
if (!verified) {
|
|
271
|
-
throw new Error("Channel dispatch signature verification failed");
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (claims.aud !== options.audience) {
|
|
275
|
-
throw new Error("Channel dispatch audience mismatch");
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (options.expectedProjectId && claims.project_id !== options.expectedProjectId) {
|
|
279
|
-
throw new Error("Channel dispatch project mismatch");
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const now = Math.floor(Date.now() / 1000);
|
|
283
|
-
if (claims.exp <= now) {
|
|
284
|
-
throw new Error("Channel dispatch signature expired");
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (claims.iat > now + SIGNATURE_SKEW_SECONDS) {
|
|
288
|
-
throw new Error("Channel dispatch signature issued in the future");
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (now - claims.iat > options.maxAgeSeconds) {
|
|
292
|
-
throw new Error("Channel dispatch signature is too old");
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const bodySha256 = await sha256Base64url(body);
|
|
296
|
-
if (claims.body_sha256 !== bodySha256) {
|
|
297
|
-
throw new Error("Channel dispatch body hash mismatch");
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
return claims;
|
|
154
|
+
return ChannelAssistantsResponseSchema.parse({ assistants });
|
|
301
155
|
}
|
|
156
|
+
export { verifyDispatchJws } from "./control-plane.js";
|
|
302
157
|
|
|
303
158
|
function normalizeConversationPart(
|
|
304
159
|
part: z.infer<typeof rawHistoryPartSchema>,
|
|
@@ -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
|
+
}
|