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 CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.63",
3
+ "version": "0.1.64",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -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
- agentConfigId: z.ZodString;
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
- agentConfigId: string;
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
- agentConfigId: string;
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(agentConfigId: string, deps: Pick<ChannelInvokeDeps, "getAgent" | "getAllAgentIds">): Agent | undefined;
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;AA8BxB,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiBrC,CAAC;AAiCH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAMpC,CAAC;AAEH,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAYtC,CAAC;AAQH,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;EASxB,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAC9E,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEhF,KAAK,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AACrE,KAAK,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAE3D,MAAM,WAAW,iBAAiB;IAChC,sBAAsB,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,KAAK,GAAG,SAAS,CAAC;IAC5C,cAAc,EAAE,MAAM,MAAM,EAAE,CAAC;CAChC;AAED,eAAO,MAAM,wBAAwB,EAAE,iBAItC,CAAC;AAyCF,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;IACP,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,GACA,OAAO,CAAC,cAAc,CAAC,CA2DzB;AAqCD,wBAAgB,sCAAsC,CACpD,QAAQ,EAAE,oBAAoB,CAAC,qBAAqB,CAAC,GACpD,OAAO,EAAE,CAUX;AAED,wBAAgB,yBAAyB,CACvC,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,IAAI,CAAC,iBAAiB,EAAE,UAAU,GAAG,gBAAgB,CAAC,GAC3D,KAAK,GAAG,SAAS,CA2BnB;AAsDD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,aAAa,GAAG,mBAAmB,EAAE,CAiDxF;AAgBD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,oBAAoB,EAC7B,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,qBAAqB,CAAC,CAkEhC"}
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
- export const ChannelInvokeRequestSchema = z.object({
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
- agentConfigId: z.string().min(1),
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(agentConfigId, deps) {
221
- const exactAgent = deps.getAgent(agentConfigId);
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.agentConfigId, deps);
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
- requestedAgentConfigId: payload.agentConfigId,
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
- agentConfigId: payload.agentConfigId,
383
+ assistantId: payload.assistantId,
368
384
  channel: payload.inboundMessage,
369
385
  },
370
386
  ...(payload.generation?.maxResponseTokens
@@ -1,4 +1,5 @@
1
1
  import type { IntegrationEndpoint } from "./types.js";
2
+ export declare function validateEndpointUrl(url: string): void;
2
3
  interface ExecutionContext {
3
4
  integration: string;
4
5
  toolId: string;
@@ -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;AAGtD,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"}
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;AAyF7D,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,CAwanF;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;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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.63",
3
+ "version": "0.1.64",
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.63",
3
+ "version": "0.1.64",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -34,11 +34,11 @@ const channelInvokeHistoryMessageSchema = z.object({
34
34
  createdAt: z.string().optional(),
35
35
  });
36
36
 
37
- export const ChannelInvokeRequestSchema = z.object({
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
- agentConfigId: z.string().min(1),
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
- agentConfigId: string,
353
+ assistantId: string,
304
354
  deps: Pick<ChannelInvokeDeps, "getAgent" | "getAllAgentIds">,
305
355
  ): Agent | undefined {
306
- const exactAgent = deps.getAgent(agentConfigId);
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.agentConfigId, deps);
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
- requestedAgentConfigId: payload.agentConfigId,
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
- agentConfigId: payload.agentConfigId,
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(),