veryfront 0.1.63 → 0.1.64
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/channels/invoke.d.ts +68 -4
- package/esm/src/channels/invoke.d.ts.map +1 -1
- package/esm/src/channels/invoke.js +42 -26
- package/esm/src/integrations/endpoint-executor.d.ts +1 -0
- package/esm/src/integrations/endpoint-executor.d.ts.map +1 -1
- package/esm/src/integrations/endpoint-executor.js +44 -0
- package/esm/src/server/handlers/request/channel-assistants.handler.d.ts +11 -0
- package/esm/src/server/handlers/request/channel-assistants.handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/channel-assistants.handler.js +71 -0
- package/esm/src/server/runtime-handler/index.d.ts.map +1 -1
- package/esm/src/server/runtime-handler/index.js +2 -0
- package/package.json +1 -1
- package/src/deno.js +1 -1
- package/src/src/channels/invoke.ts +57 -32
- package/src/src/integrations/endpoint-executor.ts +51 -0
- package/src/src/server/handlers/request/channel-assistants.handler.ts +94 -0
- package/src/src/server/runtime-handler/index.ts +2 -0
package/esm/deno.js
CHANGED
|
@@ -5,7 +5,7 @@ export declare const ChannelInvokeRequestSchema: z.ZodObject<{
|
|
|
5
5
|
dispatchId: z.ZodString;
|
|
6
6
|
conversationId: z.ZodString;
|
|
7
7
|
projectId: z.ZodString;
|
|
8
|
-
|
|
8
|
+
assistantId: z.ZodString;
|
|
9
9
|
platform: z.ZodLiteral<"slack">;
|
|
10
10
|
inboundMessage: z.ZodObject<{
|
|
11
11
|
text: z.ZodString;
|
|
@@ -97,7 +97,7 @@ export declare const ChannelInvokeRequestSchema: z.ZodObject<{
|
|
|
97
97
|
projectId: string;
|
|
98
98
|
dispatchId: string;
|
|
99
99
|
conversationId: string;
|
|
100
|
-
|
|
100
|
+
assistantId: string;
|
|
101
101
|
inboundMessage: {
|
|
102
102
|
text: string;
|
|
103
103
|
userId: string;
|
|
@@ -128,7 +128,7 @@ export declare const ChannelInvokeRequestSchema: z.ZodObject<{
|
|
|
128
128
|
projectId: string;
|
|
129
129
|
dispatchId: string;
|
|
130
130
|
conversationId: string;
|
|
131
|
-
|
|
131
|
+
assistantId: string;
|
|
132
132
|
inboundMessage: {
|
|
133
133
|
text: string;
|
|
134
134
|
userId: string;
|
|
@@ -155,6 +155,67 @@ export declare const ChannelInvokeRequestSchema: z.ZodObject<{
|
|
|
155
155
|
maxResponseTokens?: number | undefined;
|
|
156
156
|
} | undefined;
|
|
157
157
|
}>;
|
|
158
|
+
export declare const ChannelAssistantsRequestSchema: z.ZodObject<{
|
|
159
|
+
requestId: z.ZodString;
|
|
160
|
+
projectId: z.ZodString;
|
|
161
|
+
platform: z.ZodLiteral<"slack">;
|
|
162
|
+
}, "strip", z.ZodTypeAny, {
|
|
163
|
+
requestId: string;
|
|
164
|
+
platform: "slack";
|
|
165
|
+
projectId: string;
|
|
166
|
+
}, {
|
|
167
|
+
requestId: string;
|
|
168
|
+
platform: "slack";
|
|
169
|
+
projectId: string;
|
|
170
|
+
}>;
|
|
171
|
+
export declare const ChannelAssistantSchema: z.ZodObject<{
|
|
172
|
+
id: z.ZodString;
|
|
173
|
+
name: z.ZodString;
|
|
174
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
175
|
+
model: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
176
|
+
}, "strip", z.ZodTypeAny, {
|
|
177
|
+
name: string;
|
|
178
|
+
id: string;
|
|
179
|
+
description?: string | null | undefined;
|
|
180
|
+
model?: string | null | undefined;
|
|
181
|
+
}, {
|
|
182
|
+
name: string;
|
|
183
|
+
id: string;
|
|
184
|
+
description?: string | null | undefined;
|
|
185
|
+
model?: string | null | undefined;
|
|
186
|
+
}>;
|
|
187
|
+
export declare const ChannelAssistantsResponseSchema: z.ZodObject<{
|
|
188
|
+
assistants: z.ZodArray<z.ZodObject<{
|
|
189
|
+
id: z.ZodString;
|
|
190
|
+
name: z.ZodString;
|
|
191
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
192
|
+
model: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
193
|
+
}, "strip", z.ZodTypeAny, {
|
|
194
|
+
name: string;
|
|
195
|
+
id: string;
|
|
196
|
+
description?: string | null | undefined;
|
|
197
|
+
model?: string | null | undefined;
|
|
198
|
+
}, {
|
|
199
|
+
name: string;
|
|
200
|
+
id: string;
|
|
201
|
+
description?: string | null | undefined;
|
|
202
|
+
model?: string | null | undefined;
|
|
203
|
+
}>, "many">;
|
|
204
|
+
}, "strip", z.ZodTypeAny, {
|
|
205
|
+
assistants: {
|
|
206
|
+
name: string;
|
|
207
|
+
id: string;
|
|
208
|
+
description?: string | null | undefined;
|
|
209
|
+
model?: string | null | undefined;
|
|
210
|
+
}[];
|
|
211
|
+
}, {
|
|
212
|
+
assistants: {
|
|
213
|
+
name: string;
|
|
214
|
+
id: string;
|
|
215
|
+
description?: string | null | undefined;
|
|
216
|
+
model?: string | null | undefined;
|
|
217
|
+
}[];
|
|
218
|
+
}>;
|
|
158
219
|
export declare const ChannelResponsePartSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
159
220
|
type: z.ZodLiteral<"text">;
|
|
160
221
|
text: z.ZodString;
|
|
@@ -405,6 +466,8 @@ declare const dispatchClaimsSchema: z.ZodObject<{
|
|
|
405
466
|
}>;
|
|
406
467
|
export type ChannelInvokeRequest = z.infer<typeof ChannelInvokeRequestSchema>;
|
|
407
468
|
export type ChannelInvokeResponse = z.infer<typeof ChannelInvokeResponseSchema>;
|
|
469
|
+
export type ChannelAssistantsRequest = z.infer<typeof ChannelAssistantsRequestSchema>;
|
|
470
|
+
export type ChannelAssistantsResponse = z.infer<typeof ChannelAssistantsResponseSchema>;
|
|
408
471
|
type ChannelResponsePart = z.infer<typeof ChannelResponsePartSchema>;
|
|
409
472
|
type DispatchClaims = z.infer<typeof dispatchClaimsSchema>;
|
|
410
473
|
export interface ChannelInvokeDeps {
|
|
@@ -413,6 +476,7 @@ export interface ChannelInvokeDeps {
|
|
|
413
476
|
getAllAgentIds: () => string[];
|
|
414
477
|
}
|
|
415
478
|
export declare const defaultChannelInvokeDeps: ChannelInvokeDeps;
|
|
479
|
+
export declare function listChannelAssistants(ctx: HandlerContext, deps: ChannelInvokeDeps): Promise<ChannelAssistantsResponse>;
|
|
416
480
|
export declare function verifyDispatchJws(jws: string, body: string, options: {
|
|
417
481
|
audience: string;
|
|
418
482
|
publicKeyPem: string;
|
|
@@ -420,7 +484,7 @@ export declare function verifyDispatchJws(jws: string, body: string, options: {
|
|
|
420
484
|
expectedProjectId?: string;
|
|
421
485
|
}): Promise<DispatchClaims>;
|
|
422
486
|
export declare function normalizeConversationHistoryForRuntime(messages: ChannelInvokeRequest["conversationHistory"]): Message[];
|
|
423
|
-
export declare function resolveChannelInvokeAgent(
|
|
487
|
+
export declare function resolveChannelInvokeAgent(assistantId: string, deps: Pick<ChannelInvokeDeps, "getAgent" | "getAllAgentIds">): Agent | undefined;
|
|
424
488
|
export declare function buildChannelResponseParts(response: AgentResponse): ChannelResponsePart[];
|
|
425
489
|
export declare function executeChannelInvoke(payload: ChannelInvokeRequest, ctx: HandlerContext, deps: ChannelInvokeDeps): Promise<ChannelInvokeResponse>;
|
|
426
490
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invoke.d.ts","sourceRoot":"","sources":["../../../src/src/channels/invoke.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,IAAI,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"invoke.d.ts","sourceRoot":"","sources":["../../../src/src/channels/invoke.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,11 +24,11 @@ const channelInvokeHistoryMessageSchema = z.object({
|
|
|
24
24
|
metadata: z.record(z.unknown()).optional(),
|
|
25
25
|
createdAt: z.string().optional(),
|
|
26
26
|
});
|
|
27
|
-
|
|
27
|
+
const channelInvokeRequestWireSchema = z.object({
|
|
28
28
|
dispatchId: z.string().min(1),
|
|
29
29
|
conversationId: z.string().min(1),
|
|
30
30
|
projectId: z.string().min(1),
|
|
31
|
-
|
|
31
|
+
assistantId: z.string().min(1),
|
|
32
32
|
platform: z.literal("slack"),
|
|
33
33
|
inboundMessage: z.object({
|
|
34
34
|
text: z.string(),
|
|
@@ -42,6 +42,21 @@ export const ChannelInvokeRequestSchema = z.object({
|
|
|
42
42
|
maxResponseTokens: z.number().int().positive().max(16384).optional(),
|
|
43
43
|
}).optional(),
|
|
44
44
|
});
|
|
45
|
+
export const ChannelInvokeRequestSchema = channelInvokeRequestWireSchema;
|
|
46
|
+
export const ChannelAssistantsRequestSchema = z.object({
|
|
47
|
+
requestId: z.string().min(1),
|
|
48
|
+
projectId: z.string().min(1),
|
|
49
|
+
platform: z.literal("slack"),
|
|
50
|
+
});
|
|
51
|
+
export const ChannelAssistantSchema = z.object({
|
|
52
|
+
id: z.string().min(1),
|
|
53
|
+
name: z.string().min(1),
|
|
54
|
+
description: z.string().nullable().optional(),
|
|
55
|
+
model: z.string().nullable().optional(),
|
|
56
|
+
});
|
|
57
|
+
export const ChannelAssistantsResponseSchema = z.object({
|
|
58
|
+
assistants: z.array(ChannelAssistantSchema),
|
|
59
|
+
});
|
|
45
60
|
const channelTextPartSchema = z.object({
|
|
46
61
|
type: z.literal("text"),
|
|
47
62
|
text: z.string(),
|
|
@@ -108,6 +123,26 @@ export const defaultChannelInvokeDeps = {
|
|
|
108
123
|
getAgent: getRegisteredAgent,
|
|
109
124
|
getAllAgentIds: getRegisteredAgentIds,
|
|
110
125
|
};
|
|
126
|
+
function getAssistantMetadata(agent) {
|
|
127
|
+
const rawConfig = agent.config;
|
|
128
|
+
return ChannelAssistantSchema.parse({
|
|
129
|
+
id: agent.id,
|
|
130
|
+
name: typeof rawConfig.name === "string" && rawConfig.name.trim().length > 0
|
|
131
|
+
? rawConfig.name
|
|
132
|
+
: agent.id,
|
|
133
|
+
description: typeof rawConfig.description === "string" ? rawConfig.description : null,
|
|
134
|
+
model: agent.config.model ?? null,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
export async function listChannelAssistants(ctx, deps) {
|
|
138
|
+
await deps.ensureProjectDiscovery(ctx);
|
|
139
|
+
const assistants = deps.getAllAgentIds()
|
|
140
|
+
.map((id) => deps.getAgent(id))
|
|
141
|
+
.filter((agent) => Boolean(agent))
|
|
142
|
+
.map(getAssistantMetadata)
|
|
143
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
144
|
+
return ChannelAssistantsResponseSchema.parse({ assistants });
|
|
145
|
+
}
|
|
111
146
|
function base64urlDecodeToBytes(input) {
|
|
112
147
|
const normalized = input
|
|
113
148
|
.replaceAll("-", "+")
|
|
@@ -217,27 +252,8 @@ export function normalizeConversationHistoryForRuntime(messages) {
|
|
|
217
252
|
...(message.metadata ? { metadata: message.metadata } : {}),
|
|
218
253
|
}));
|
|
219
254
|
}
|
|
220
|
-
export function resolveChannelInvokeAgent(
|
|
221
|
-
|
|
222
|
-
if (exactAgent) {
|
|
223
|
-
return exactAgent;
|
|
224
|
-
}
|
|
225
|
-
const agentIds = deps.getAllAgentIds();
|
|
226
|
-
if (agentIds.length !== 1) {
|
|
227
|
-
return undefined;
|
|
228
|
-
}
|
|
229
|
-
const onlyAgentId = agentIds[0];
|
|
230
|
-
if (!onlyAgentId) {
|
|
231
|
-
return undefined;
|
|
232
|
-
}
|
|
233
|
-
const onlyAgent = deps.getAgent(onlyAgentId);
|
|
234
|
-
if (onlyAgent) {
|
|
235
|
-
logger.warn("Channel invoke fell back to the only discovered runtime agent because agentConfigId did not match a registry id", {
|
|
236
|
-
requestedAgentConfigId: agentConfigId,
|
|
237
|
-
resolvedAgentId: onlyAgentId,
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
return onlyAgent;
|
|
255
|
+
export function resolveChannelInvokeAgent(assistantId, deps) {
|
|
256
|
+
return deps.getAgent(assistantId);
|
|
241
257
|
}
|
|
242
258
|
function normalizeToolCallState(status) {
|
|
243
259
|
switch (status) {
|
|
@@ -338,10 +354,10 @@ function classifyRuntimeError(error) {
|
|
|
338
354
|
}
|
|
339
355
|
export async function executeChannelInvoke(payload, ctx, deps) {
|
|
340
356
|
await deps.ensureProjectDiscovery(ctx);
|
|
341
|
-
const agent = resolveChannelInvokeAgent(payload.
|
|
357
|
+
const agent = resolveChannelInvokeAgent(payload.assistantId, deps);
|
|
342
358
|
if (!agent) {
|
|
343
359
|
logger.error("Channel invoke could not resolve a runtime agent for the request", {
|
|
344
|
-
|
|
360
|
+
requestedAssistantId: payload.assistantId,
|
|
345
361
|
discoveredAgentIds: deps.getAllAgentIds(),
|
|
346
362
|
projectSlug: ctx.projectSlug,
|
|
347
363
|
projectId: ctx.projectId,
|
|
@@ -364,7 +380,7 @@ export async function executeChannelInvoke(payload, ctx, deps) {
|
|
|
364
380
|
dispatchId: payload.dispatchId,
|
|
365
381
|
conversationId: payload.conversationId,
|
|
366
382
|
projectId: payload.projectId,
|
|
367
|
-
|
|
383
|
+
assistantId: payload.assistantId,
|
|
368
384
|
channel: payload.inboundMessage,
|
|
369
385
|
},
|
|
370
386
|
...(payload.generation?.maxResponseTokens
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"endpoint-executor.d.ts","sourceRoot":"","sources":["../../../src/src/integrations/endpoint-executor.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"endpoint-executor.d.ts","sourceRoot":"","sources":["../../../src/src/integrations/endpoint-executor.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAetD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAiCrD;AAED,UAAU,gBAAgB;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,eAAe,CACnC,QAAQ,EAAE,mBAAmB,EAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,gBAAgB,GACpB,OAAO,CAAC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAK9C"}
|
|
@@ -8,6 +8,48 @@
|
|
|
8
8
|
import * as dntShim from "../../_dnt.shims.js";
|
|
9
9
|
import { logger } from "../utils/index.js";
|
|
10
10
|
import { INVALID_ARGUMENT } from "../errors/index.js";
|
|
11
|
+
const PRIVATE_IP_RANGES = [
|
|
12
|
+
/^127\./, // 127.0.0.0/8
|
|
13
|
+
/^10\./, // 10.0.0.0/8
|
|
14
|
+
/^172\.(1[6-9]|2\d|3[01])\./, // 172.16.0.0/12
|
|
15
|
+
/^192\.168\./, // 192.168.0.0/16
|
|
16
|
+
/^169\.254\./, // 169.254.0.0/16
|
|
17
|
+
/^0\./, // 0.0.0.0/8
|
|
18
|
+
/^::1$/, // IPv6 loopback
|
|
19
|
+
/^f[cd][0-9a-f]{2}:/i, // IPv6 unique local (fc00::/7)
|
|
20
|
+
/^fe80:/i, // IPv6 link-local (fe80::/10)
|
|
21
|
+
];
|
|
22
|
+
export function validateEndpointUrl(url) {
|
|
23
|
+
let parsed;
|
|
24
|
+
try {
|
|
25
|
+
parsed = new URL(url);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
throw INVALID_ARGUMENT.create({ detail: `Invalid endpoint URL: ${url}` });
|
|
29
|
+
}
|
|
30
|
+
if (parsed.protocol !== "https:") {
|
|
31
|
+
throw INVALID_ARGUMENT.create({
|
|
32
|
+
detail: `Endpoint URL must use HTTPS: ${parsed.protocol}`,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
36
|
+
if (hostname === "localhost") {
|
|
37
|
+
throw INVALID_ARGUMENT.create({
|
|
38
|
+
detail: "Endpoint URL must not target localhost",
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
// Strip IPv6 brackets for regex matching
|
|
42
|
+
const bare = hostname.startsWith("[") && hostname.endsWith("]")
|
|
43
|
+
? hostname.slice(1, -1)
|
|
44
|
+
: hostname;
|
|
45
|
+
for (const range of PRIVATE_IP_RANGES) {
|
|
46
|
+
if (range.test(bare)) {
|
|
47
|
+
throw INVALID_ARGUMENT.create({
|
|
48
|
+
detail: "Endpoint URL must not target private/internal networks",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
11
53
|
export async function executeEndpoint(endpoint, args, accessToken, ctx) {
|
|
12
54
|
if (endpoint.type === "graphql") {
|
|
13
55
|
return executeGraphQL(endpoint, args, accessToken, ctx);
|
|
@@ -40,6 +82,7 @@ async function executeGraphQL(endpoint, args, accessToken, ctx) {
|
|
|
40
82
|
headers[key] = args[key] !== undefined ? String(args[key]) : String(def.default ?? "");
|
|
41
83
|
}
|
|
42
84
|
}
|
|
85
|
+
validateEndpointUrl(endpoint.url);
|
|
43
86
|
logger.debug("Executing GraphQL endpoint", {
|
|
44
87
|
integration: ctx.integration,
|
|
45
88
|
tool: ctx.toolId,
|
|
@@ -112,6 +155,7 @@ async function executeRest(endpoint, args, accessToken, ctx) {
|
|
|
112
155
|
body = JSON.stringify(bodyObj);
|
|
113
156
|
headers["Content-Type"] = endpoint.contentType ?? "application/json";
|
|
114
157
|
}
|
|
158
|
+
validateEndpointUrl(urlObj.toString());
|
|
115
159
|
logger.debug("Executing REST endpoint", {
|
|
116
160
|
integration: ctx.integration,
|
|
117
161
|
tool: ctx.toolId,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as dntShim from "../../../../_dnt.shims.js";
|
|
2
|
+
import { BaseHandler } from "../response/base.js";
|
|
3
|
+
import type { HandlerContext, HandlerMetadata, HandlerResult } from "../types.js";
|
|
4
|
+
import { type ChannelInvokeDeps } from "../../../channels/invoke.js";
|
|
5
|
+
export declare class ChannelAssistantsHandler extends BaseHandler {
|
|
6
|
+
private readonly deps;
|
|
7
|
+
metadata: HandlerMetadata;
|
|
8
|
+
constructor(deps?: ChannelInvokeDeps);
|
|
9
|
+
handle(req: dntShim.Request, ctx: HandlerContext): Promise<HandlerResult>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=channel-assistants.handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-assistants.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/request/channel-assistants.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,EAEL,KAAK,iBAAiB,EAIvB,MAAM,6BAA6B,CAAC;AASrC,qBAAa,wBAAyB,SAAQ,WAAW;IAO3C,OAAO,CAAC,QAAQ,CAAC,IAAI;IANjC,QAAQ,EAAE,eAAe,CAIvB;gBAE2B,IAAI,GAAE,iBAA4C;IAIzE,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CAgEhF"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { BaseHandler } from "../response/base.js";
|
|
2
|
+
import { ChannelAssistantsRequestSchema, defaultChannelInvokeDeps, listChannelAssistants, verifyDispatchJws, } from "../../../channels/invoke.js";
|
|
3
|
+
import { HTTP_INTERNAL_SERVER_ERROR, PRIORITY_MEDIUM_API, } from "../../../utils/constants/index.js";
|
|
4
|
+
const DISPATCH_JWS_HEADER = "x-veryfront-dispatch-jws";
|
|
5
|
+
const MAX_DISPATCH_SIGNATURE_AGE_SECONDS = 60;
|
|
6
|
+
export class ChannelAssistantsHandler extends BaseHandler {
|
|
7
|
+
deps;
|
|
8
|
+
metadata = {
|
|
9
|
+
name: "ChannelAssistantsHandler",
|
|
10
|
+
priority: PRIORITY_MEDIUM_API,
|
|
11
|
+
patterns: [{ pattern: "/channels/assistants", exact: true, method: "POST" }],
|
|
12
|
+
};
|
|
13
|
+
constructor(deps = defaultChannelInvokeDeps) {
|
|
14
|
+
super();
|
|
15
|
+
this.deps = deps;
|
|
16
|
+
}
|
|
17
|
+
async handle(req, ctx) {
|
|
18
|
+
if (!this.shouldHandle(req, ctx)) {
|
|
19
|
+
return this.continue();
|
|
20
|
+
}
|
|
21
|
+
return this.withProxyContext(ctx, async () => {
|
|
22
|
+
const builder = this.createResponseBuilder(ctx)
|
|
23
|
+
.withCORS(req, ctx.securityConfig?.cors)
|
|
24
|
+
.withSecurity(ctx.securityConfig ?? undefined, req);
|
|
25
|
+
const publicKeyPem = ctx.adapter.env.get("CHANNEL_DISPATCH_SIGNING_PUBLIC_KEY");
|
|
26
|
+
if (!publicKeyPem) {
|
|
27
|
+
this.logWarn("Missing CHANNEL_DISPATCH_SIGNING_PUBLIC_KEY for channel assistants endpoint");
|
|
28
|
+
return this.respond(builder.json({ error: "Channel dispatch verification is not configured" }, HTTP_INTERNAL_SERVER_ERROR));
|
|
29
|
+
}
|
|
30
|
+
const projectSlug = ctx.projectSlug;
|
|
31
|
+
if (!projectSlug) {
|
|
32
|
+
this.logWarn("Channel assistants request arrived without resolved project slug");
|
|
33
|
+
return this.respond(builder.json({ error: "Project context is unavailable" }, 400));
|
|
34
|
+
}
|
|
35
|
+
const dispatchJws = req.headers.get(DISPATCH_JWS_HEADER);
|
|
36
|
+
if (!dispatchJws) {
|
|
37
|
+
return this.respond(builder.json({ error: "Missing dispatch signature" }, 401));
|
|
38
|
+
}
|
|
39
|
+
const rawBody = await req.text();
|
|
40
|
+
try {
|
|
41
|
+
await verifyDispatchJws(dispatchJws, rawBody, {
|
|
42
|
+
audience: projectSlug,
|
|
43
|
+
expectedProjectId: ctx.projectId,
|
|
44
|
+
publicKeyPem,
|
|
45
|
+
maxAgeSeconds: MAX_DISPATCH_SIGNATURE_AGE_SECONDS,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
this.logWarn("Channel assistants signature verification failed", {
|
|
50
|
+
error: error instanceof Error ? error.message : String(error),
|
|
51
|
+
projectSlug,
|
|
52
|
+
projectId: ctx.projectId,
|
|
53
|
+
});
|
|
54
|
+
return this.respond(builder.json({ error: "Invalid dispatch signature" }, 401));
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
ChannelAssistantsRequestSchema.parse(JSON.parse(rawBody));
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
this.logWarn("Channel assistants request validation failed", {
|
|
61
|
+
error: error instanceof Error ? error.message : String(error),
|
|
62
|
+
projectSlug,
|
|
63
|
+
projectId: ctx.projectId,
|
|
64
|
+
});
|
|
65
|
+
return this.respond(builder.json({ error: "Invalid channel assistants request" }, 400));
|
|
66
|
+
}
|
|
67
|
+
const response = await listChannelAssistants(ctx, this.deps);
|
|
68
|
+
return this.respond(builder.json(response, 200));
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -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;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"}
|
|
@@ -42,6 +42,7 @@ 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
46
|
import { ChannelInvokeHandler } from "../handlers/request/channel-invoke.handler.js";
|
|
46
47
|
import { DevDashboardHandler } from "../handlers/dev/dashboard/index.js";
|
|
47
48
|
import { ProjectsHandler } from "../handlers/dev/projects/index.js";
|
|
@@ -124,6 +125,7 @@ export function createVeryfrontHandler(projectDir, adapter, opts = { projectDir
|
|
|
124
125
|
new DebugContextHandler(),
|
|
125
126
|
new OpenAPIHandler(),
|
|
126
127
|
new OpenAPIDocsHandler(),
|
|
128
|
+
new ChannelAssistantsHandler(),
|
|
127
129
|
new ChannelInvokeHandler(),
|
|
128
130
|
new DevDashboardHandler(),
|
|
129
131
|
new ProjectsHandler(),
|
package/package.json
CHANGED
package/src/deno.js
CHANGED
|
@@ -34,11 +34,11 @@ const channelInvokeHistoryMessageSchema = z.object({
|
|
|
34
34
|
createdAt: z.string().optional(),
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
const channelInvokeRequestWireSchema = z.object({
|
|
38
38
|
dispatchId: z.string().min(1),
|
|
39
39
|
conversationId: z.string().min(1),
|
|
40
40
|
projectId: z.string().min(1),
|
|
41
|
-
|
|
41
|
+
assistantId: z.string().min(1),
|
|
42
42
|
platform: z.literal("slack"),
|
|
43
43
|
inboundMessage: z.object({
|
|
44
44
|
text: z.string(),
|
|
@@ -53,6 +53,25 @@ export const ChannelInvokeRequestSchema = z.object({
|
|
|
53
53
|
}).optional(),
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
+
export const ChannelInvokeRequestSchema = channelInvokeRequestWireSchema;
|
|
57
|
+
|
|
58
|
+
export const ChannelAssistantsRequestSchema = z.object({
|
|
59
|
+
requestId: z.string().min(1),
|
|
60
|
+
projectId: z.string().min(1),
|
|
61
|
+
platform: z.literal("slack"),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export const ChannelAssistantSchema = z.object({
|
|
65
|
+
id: z.string().min(1),
|
|
66
|
+
name: z.string().min(1),
|
|
67
|
+
description: z.string().nullable().optional(),
|
|
68
|
+
model: z.string().nullable().optional(),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
export const ChannelAssistantsResponseSchema = z.object({
|
|
72
|
+
assistants: z.array(ChannelAssistantSchema),
|
|
73
|
+
});
|
|
74
|
+
|
|
56
75
|
const channelTextPartSchema = z.object({
|
|
57
76
|
type: z.literal("text"),
|
|
58
77
|
text: z.string(),
|
|
@@ -125,9 +144,12 @@ const dispatchClaimsSchema = z.object({
|
|
|
125
144
|
|
|
126
145
|
export type ChannelInvokeRequest = z.infer<typeof ChannelInvokeRequestSchema>;
|
|
127
146
|
export type ChannelInvokeResponse = z.infer<typeof ChannelInvokeResponseSchema>;
|
|
147
|
+
export type ChannelAssistantsRequest = z.infer<typeof ChannelAssistantsRequestSchema>;
|
|
148
|
+
export type ChannelAssistantsResponse = z.infer<typeof ChannelAssistantsResponseSchema>;
|
|
128
149
|
|
|
129
150
|
type ChannelResponsePart = z.infer<typeof ChannelResponsePartSchema>;
|
|
130
151
|
type DispatchClaims = z.infer<typeof dispatchClaimsSchema>;
|
|
152
|
+
type ChannelAssistant = z.infer<typeof ChannelAssistantSchema>;
|
|
131
153
|
|
|
132
154
|
export interface ChannelInvokeDeps {
|
|
133
155
|
ensureProjectDiscovery: (ctx: HandlerContext) => Promise<void>;
|
|
@@ -141,6 +163,34 @@ export const defaultChannelInvokeDeps: ChannelInvokeDeps = {
|
|
|
141
163
|
getAllAgentIds: getRegisteredAgentIds,
|
|
142
164
|
};
|
|
143
165
|
|
|
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
|
+
export async function listChannelAssistants(
|
|
180
|
+
ctx: HandlerContext,
|
|
181
|
+
deps: ChannelInvokeDeps,
|
|
182
|
+
): Promise<ChannelAssistantsResponse> {
|
|
183
|
+
await deps.ensureProjectDiscovery(ctx);
|
|
184
|
+
|
|
185
|
+
const assistants = deps.getAllAgentIds()
|
|
186
|
+
.map((id) => deps.getAgent(id))
|
|
187
|
+
.filter((agent): agent is Agent => Boolean(agent))
|
|
188
|
+
.map(getAssistantMetadata)
|
|
189
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
190
|
+
|
|
191
|
+
return ChannelAssistantsResponseSchema.parse({ assistants });
|
|
192
|
+
}
|
|
193
|
+
|
|
144
194
|
function base64urlDecodeToBytes(input: string): ArrayBuffer {
|
|
145
195
|
const normalized = input
|
|
146
196
|
.replaceAll("-", "+")
|
|
@@ -300,35 +350,10 @@ export function normalizeConversationHistoryForRuntime(
|
|
|
300
350
|
}
|
|
301
351
|
|
|
302
352
|
export function resolveChannelInvokeAgent(
|
|
303
|
-
|
|
353
|
+
assistantId: string,
|
|
304
354
|
deps: Pick<ChannelInvokeDeps, "getAgent" | "getAllAgentIds">,
|
|
305
355
|
): Agent | undefined {
|
|
306
|
-
|
|
307
|
-
if (exactAgent) {
|
|
308
|
-
return exactAgent;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const agentIds = deps.getAllAgentIds();
|
|
312
|
-
if (agentIds.length !== 1) {
|
|
313
|
-
return undefined;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const onlyAgentId = agentIds[0];
|
|
317
|
-
if (!onlyAgentId) {
|
|
318
|
-
return undefined;
|
|
319
|
-
}
|
|
320
|
-
const onlyAgent = deps.getAgent(onlyAgentId);
|
|
321
|
-
if (onlyAgent) {
|
|
322
|
-
logger.warn(
|
|
323
|
-
"Channel invoke fell back to the only discovered runtime agent because agentConfigId did not match a registry id",
|
|
324
|
-
{
|
|
325
|
-
requestedAgentConfigId: agentConfigId,
|
|
326
|
-
resolvedAgentId: onlyAgentId,
|
|
327
|
-
},
|
|
328
|
-
);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
return onlyAgent;
|
|
356
|
+
return deps.getAgent(assistantId);
|
|
332
357
|
}
|
|
333
358
|
|
|
334
359
|
function normalizeToolCallState(status: string): "pending" | "completed" | "error" {
|
|
@@ -455,10 +480,10 @@ export async function executeChannelInvoke(
|
|
|
455
480
|
): Promise<ChannelInvokeResponse> {
|
|
456
481
|
await deps.ensureProjectDiscovery(ctx);
|
|
457
482
|
|
|
458
|
-
const agent = resolveChannelInvokeAgent(payload.
|
|
483
|
+
const agent = resolveChannelInvokeAgent(payload.assistantId, deps);
|
|
459
484
|
if (!agent) {
|
|
460
485
|
logger.error("Channel invoke could not resolve a runtime agent for the request", {
|
|
461
|
-
|
|
486
|
+
requestedAssistantId: payload.assistantId,
|
|
462
487
|
discoveredAgentIds: deps.getAllAgentIds(),
|
|
463
488
|
projectSlug: ctx.projectSlug,
|
|
464
489
|
projectId: ctx.projectId,
|
|
@@ -483,7 +508,7 @@ export async function executeChannelInvoke(
|
|
|
483
508
|
dispatchId: payload.dispatchId,
|
|
484
509
|
conversationId: payload.conversationId,
|
|
485
510
|
projectId: payload.projectId,
|
|
486
|
-
|
|
511
|
+
assistantId: payload.assistantId,
|
|
487
512
|
channel: payload.inboundMessage,
|
|
488
513
|
},
|
|
489
514
|
...(payload.generation?.maxResponseTokens
|
|
@@ -12,6 +12,53 @@ import { logger } from "../utils/index.js";
|
|
|
12
12
|
import type { IntegrationEndpoint } from "./types.js";
|
|
13
13
|
import { INVALID_ARGUMENT } from "../errors/index.js";
|
|
14
14
|
|
|
15
|
+
const PRIVATE_IP_RANGES = [
|
|
16
|
+
/^127\./, // 127.0.0.0/8
|
|
17
|
+
/^10\./, // 10.0.0.0/8
|
|
18
|
+
/^172\.(1[6-9]|2\d|3[01])\./, // 172.16.0.0/12
|
|
19
|
+
/^192\.168\./, // 192.168.0.0/16
|
|
20
|
+
/^169\.254\./, // 169.254.0.0/16
|
|
21
|
+
/^0\./, // 0.0.0.0/8
|
|
22
|
+
/^::1$/, // IPv6 loopback
|
|
23
|
+
/^f[cd][0-9a-f]{2}:/i, // IPv6 unique local (fc00::/7)
|
|
24
|
+
/^fe80:/i, // IPv6 link-local (fe80::/10)
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export function validateEndpointUrl(url: string): void {
|
|
28
|
+
let parsed: URL;
|
|
29
|
+
try {
|
|
30
|
+
parsed = new URL(url);
|
|
31
|
+
} catch {
|
|
32
|
+
throw INVALID_ARGUMENT.create({ detail: `Invalid endpoint URL: ${url}` });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (parsed.protocol !== "https:") {
|
|
36
|
+
throw INVALID_ARGUMENT.create({
|
|
37
|
+
detail: `Endpoint URL must use HTTPS: ${parsed.protocol}`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
42
|
+
if (hostname === "localhost") {
|
|
43
|
+
throw INVALID_ARGUMENT.create({
|
|
44
|
+
detail: "Endpoint URL must not target localhost",
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Strip IPv6 brackets for regex matching
|
|
49
|
+
const bare = hostname.startsWith("[") && hostname.endsWith("]")
|
|
50
|
+
? hostname.slice(1, -1)
|
|
51
|
+
: hostname;
|
|
52
|
+
|
|
53
|
+
for (const range of PRIVATE_IP_RANGES) {
|
|
54
|
+
if (range.test(bare)) {
|
|
55
|
+
throw INVALID_ARGUMENT.create({
|
|
56
|
+
detail: "Endpoint URL must not target private/internal networks",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
15
62
|
interface ExecutionContext {
|
|
16
63
|
integration: string;
|
|
17
64
|
toolId: string;
|
|
@@ -63,6 +110,8 @@ async function executeGraphQL(
|
|
|
63
110
|
}
|
|
64
111
|
}
|
|
65
112
|
|
|
113
|
+
validateEndpointUrl(endpoint.url);
|
|
114
|
+
|
|
66
115
|
logger.debug("Executing GraphQL endpoint", {
|
|
67
116
|
integration: ctx.integration,
|
|
68
117
|
tool: ctx.toolId,
|
|
@@ -149,6 +198,8 @@ async function executeRest(
|
|
|
149
198
|
headers["Content-Type"] = endpoint.contentType ?? "application/json";
|
|
150
199
|
}
|
|
151
200
|
|
|
201
|
+
validateEndpointUrl(urlObj.toString());
|
|
202
|
+
|
|
152
203
|
logger.debug("Executing REST endpoint", {
|
|
153
204
|
integration: ctx.integration,
|
|
154
205
|
tool: ctx.toolId,
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import * as dntShim from "../../../../_dnt.shims.js";
|
|
2
|
+
import { BaseHandler } from "../response/base.js";
|
|
3
|
+
import type { HandlerContext, HandlerMetadata, HandlerPriority, HandlerResult } from "../types.js";
|
|
4
|
+
import {
|
|
5
|
+
ChannelAssistantsRequestSchema,
|
|
6
|
+
type ChannelInvokeDeps,
|
|
7
|
+
defaultChannelInvokeDeps,
|
|
8
|
+
listChannelAssistants,
|
|
9
|
+
verifyDispatchJws,
|
|
10
|
+
} from "../../../channels/invoke.js";
|
|
11
|
+
import {
|
|
12
|
+
HTTP_INTERNAL_SERVER_ERROR,
|
|
13
|
+
PRIORITY_MEDIUM_API,
|
|
14
|
+
} from "../../../utils/constants/index.js";
|
|
15
|
+
|
|
16
|
+
const DISPATCH_JWS_HEADER = "x-veryfront-dispatch-jws";
|
|
17
|
+
const MAX_DISPATCH_SIGNATURE_AGE_SECONDS = 60;
|
|
18
|
+
|
|
19
|
+
export class ChannelAssistantsHandler extends BaseHandler {
|
|
20
|
+
metadata: HandlerMetadata = {
|
|
21
|
+
name: "ChannelAssistantsHandler",
|
|
22
|
+
priority: PRIORITY_MEDIUM_API as HandlerPriority,
|
|
23
|
+
patterns: [{ pattern: "/channels/assistants", exact: true, method: "POST" }],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
constructor(private readonly deps: ChannelInvokeDeps = defaultChannelInvokeDeps) {
|
|
27
|
+
super();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async handle(req: dntShim.Request, ctx: HandlerContext): Promise<HandlerResult> {
|
|
31
|
+
if (!this.shouldHandle(req, ctx)) {
|
|
32
|
+
return this.continue();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return this.withProxyContext(ctx, async () => {
|
|
36
|
+
const builder = this.createResponseBuilder(ctx)
|
|
37
|
+
.withCORS(req, ctx.securityConfig?.cors)
|
|
38
|
+
.withSecurity(ctx.securityConfig ?? undefined, req);
|
|
39
|
+
|
|
40
|
+
const publicKeyPem = ctx.adapter.env.get("CHANNEL_DISPATCH_SIGNING_PUBLIC_KEY");
|
|
41
|
+
if (!publicKeyPem) {
|
|
42
|
+
this.logWarn("Missing CHANNEL_DISPATCH_SIGNING_PUBLIC_KEY for channel assistants endpoint");
|
|
43
|
+
return this.respond(
|
|
44
|
+
builder.json(
|
|
45
|
+
{ error: "Channel dispatch verification is not configured" },
|
|
46
|
+
HTTP_INTERNAL_SERVER_ERROR,
|
|
47
|
+
),
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const projectSlug = ctx.projectSlug;
|
|
52
|
+
if (!projectSlug) {
|
|
53
|
+
this.logWarn("Channel assistants request arrived without resolved project slug");
|
|
54
|
+
return this.respond(builder.json({ error: "Project context is unavailable" }, 400));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const dispatchJws = req.headers.get(DISPATCH_JWS_HEADER);
|
|
58
|
+
if (!dispatchJws) {
|
|
59
|
+
return this.respond(builder.json({ error: "Missing dispatch signature" }, 401));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const rawBody = await req.text();
|
|
63
|
+
try {
|
|
64
|
+
await verifyDispatchJws(dispatchJws, rawBody, {
|
|
65
|
+
audience: projectSlug,
|
|
66
|
+
expectedProjectId: ctx.projectId,
|
|
67
|
+
publicKeyPem,
|
|
68
|
+
maxAgeSeconds: MAX_DISPATCH_SIGNATURE_AGE_SECONDS,
|
|
69
|
+
});
|
|
70
|
+
} catch (error) {
|
|
71
|
+
this.logWarn("Channel assistants signature verification failed", {
|
|
72
|
+
error: error instanceof Error ? error.message : String(error),
|
|
73
|
+
projectSlug,
|
|
74
|
+
projectId: ctx.projectId,
|
|
75
|
+
});
|
|
76
|
+
return this.respond(builder.json({ error: "Invalid dispatch signature" }, 401));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
ChannelAssistantsRequestSchema.parse(JSON.parse(rawBody));
|
|
81
|
+
} catch (error) {
|
|
82
|
+
this.logWarn("Channel assistants request validation failed", {
|
|
83
|
+
error: error instanceof Error ? error.message : String(error),
|
|
84
|
+
projectSlug,
|
|
85
|
+
projectId: ctx.projectId,
|
|
86
|
+
});
|
|
87
|
+
return this.respond(builder.json({ error: "Invalid channel assistants request" }, 400));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const response = await listChannelAssistants(ctx, this.deps);
|
|
91
|
+
return this.respond(builder.json(response, 200));
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -53,6 +53,7 @@ import { HMRHandler } from "../handlers/preview/hmr.handler.js";
|
|
|
53
53
|
import { MarkdownPreviewHandler } from "../handlers/preview/markdown-preview.handler.js";
|
|
54
54
|
import { OpenAPIHandler } from "../handlers/request/openapi.handler.js";
|
|
55
55
|
import { OpenAPIDocsHandler } from "../handlers/request/openapi-docs.handler.js";
|
|
56
|
+
import { ChannelAssistantsHandler } from "../handlers/request/channel-assistants.handler.js";
|
|
56
57
|
import { ChannelInvokeHandler } from "../handlers/request/channel-invoke.handler.js";
|
|
57
58
|
import { DevDashboardHandler } from "../handlers/dev/dashboard/index.js";
|
|
58
59
|
import { ProjectsHandler } from "../handlers/dev/projects/index.js";
|
|
@@ -205,6 +206,7 @@ export function createVeryfrontHandler(
|
|
|
205
206
|
new DebugContextHandler(),
|
|
206
207
|
new OpenAPIHandler(),
|
|
207
208
|
new OpenAPIDocsHandler(),
|
|
209
|
+
new ChannelAssistantsHandler(),
|
|
208
210
|
new ChannelInvokeHandler(),
|
|
209
211
|
new DevDashboardHandler(),
|
|
210
212
|
new ProjectsHandler(),
|