veryfront 0.1.511 → 0.1.513

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +46 -5
  2. package/esm/deno.d.ts +0 -4
  3. package/esm/deno.js +1 -5
  4. package/esm/src/agent/agent-service-config.d.ts +54 -0
  5. package/esm/src/agent/agent-service-config.d.ts.map +1 -1
  6. package/esm/src/agent/agent-service-config.js +24 -0
  7. package/esm/src/agent/agent-service-registration.d.ts +110 -0
  8. package/esm/src/agent/agent-service-registration.d.ts.map +1 -0
  9. package/esm/src/agent/agent-service-registration.js +228 -0
  10. package/esm/src/agent/agent-service-runtime.d.ts +3 -1
  11. package/esm/src/agent/agent-service-runtime.d.ts.map +1 -1
  12. package/esm/src/agent/agent-service-runtime.js +17 -2
  13. package/esm/src/agent/index.d.ts +1 -0
  14. package/esm/src/agent/index.d.ts.map +1 -1
  15. package/esm/src/agent/index.js +1 -0
  16. package/esm/src/agent/runtime-agent-definition-files.d.ts +5 -0
  17. package/esm/src/agent/runtime-agent-definition-files.d.ts.map +1 -1
  18. package/esm/src/agent/runtime-agent-definition-files.js +37 -9
  19. package/esm/src/agent/veryfront-cloud-agent-service.d.ts +6 -1
  20. package/esm/src/agent/veryfront-cloud-agent-service.d.ts.map +1 -1
  21. package/esm/src/agent/veryfront-cloud-agent-service.js +144 -37
  22. package/esm/src/oauth/providers/base.d.ts +10 -0
  23. package/esm/src/oauth/providers/base.d.ts.map +1 -1
  24. package/esm/src/oauth/providers/base.js +32 -1
  25. package/esm/src/utils/version-constant.d.ts +1 -1
  26. package/esm/src/utils/version-constant.js +1 -1
  27. package/package.json +1 -1
  28. package/src/deno.js +1 -5
  29. package/src/src/agent/agent-service-config.ts +23 -0
  30. package/src/src/agent/agent-service-registration.ts +320 -0
  31. package/src/src/agent/agent-service-runtime.ts +25 -2
  32. package/src/src/agent/index.ts +15 -0
  33. package/src/src/agent/runtime-agent-definition-files.ts +52 -9
  34. package/src/src/agent/veryfront-cloud-agent-service.ts +194 -42
  35. package/src/src/oauth/providers/base.ts +33 -1
  36. package/src/src/utils/version-constant.ts +1 -1
@@ -40,6 +40,16 @@ export declare class OAuthService extends OAuthProvider {
40
40
  * tokens. See VULN-AUTH-2.
41
41
  */
42
42
  getAccessToken(userId: string): Promise<string | null>;
43
+ /**
44
+ * Resolve `endpoint` against `apiBaseUrl`, validating that absolute URLs
45
+ * share the configured origin.
46
+ *
47
+ * Without this check, a caller that forwards user-controlled data as
48
+ * `endpoint` could cause `fetch()` to issue requests to arbitrary hosts
49
+ * (including cloud metadata services and internal infrastructure). See
50
+ * SEC-003 in the security audit.
51
+ */
52
+ private resolveEndpointUrl;
43
53
  fetch<T>(userId: string, endpoint: string, options?: RequestInit): Promise<T>;
44
54
  }
45
55
  //# sourceMappingURL=base.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/src/oauth/providers/base.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,uBAAuB,EACvB,mBAAmB,EACnB,kBAAkB,EAClB,UAAU,EAEV,oBAAoB,EACpB,mBAAmB,EACnB,UAAU,EACX,MAAM,aAAa,CAAC;AAUrB,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;AAuB5D,qBAAa,aAAa;IACxB,SAAS,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACtC,SAAS,CAAC,SAAS,EAAE,SAAS,CAAC;gBAEnB,MAAM,EAAE,mBAAmB,EAAE,SAAS,GAAE,SAAkB;IAKtE,WAAW,IAAI,MAAM,GAAG,IAAI;IAI5B,eAAe,IAAI,MAAM,GAAG,IAAI;IAIhC,YAAY,IAAI,OAAO;IAIjB,sBAAsB,CAC1B,OAAO,GAAE,uBAAuB,GAAG;QAAE,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;KAAO,GACnE,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,CAAC;IA0C9C,OAAO,CAAC,iBAAiB;IAazB,OAAO,CAAC,kBAAkB;YAyCZ,gBAAgB;IAe9B,OAAO,CAAC,4BAA4B;YAQtB,aAAa;IA+BrB,YAAY,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA+BzE,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAwBjE,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAiBnD;AAED,qBAAa,YAAa,SAAQ,aAAa;IAC7C,SAAS,CAAC,aAAa,EAAE,kBAAkB,CAAC;IAC5C,SAAS,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;gBAEtB,MAAM,EAAE,kBAAkB,EAAE,UAAU,CAAC,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,SAAS;IAMtF,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,IAAI,UAAU,IAAI,MAAM,CAEvB;IAEQ,sBAAsB,CAC7B,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,CAAC;IAO9C;;;;;;OAMG;IACG,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAmBtD,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,CAAC,CAAC;CA4BxF"}
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/src/oauth/providers/base.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,uBAAuB,EACvB,mBAAmB,EACnB,kBAAkB,EAClB,UAAU,EAEV,oBAAoB,EACpB,mBAAmB,EACnB,UAAU,EACX,MAAM,aAAa,CAAC;AAUrB,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;AAuB5D,qBAAa,aAAa;IACxB,SAAS,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACtC,SAAS,CAAC,SAAS,EAAE,SAAS,CAAC;gBAEnB,MAAM,EAAE,mBAAmB,EAAE,SAAS,GAAE,SAAkB;IAKtE,WAAW,IAAI,MAAM,GAAG,IAAI;IAI5B,eAAe,IAAI,MAAM,GAAG,IAAI;IAIhC,YAAY,IAAI,OAAO;IAIjB,sBAAsB,CAC1B,OAAO,GAAE,uBAAuB,GAAG;QAAE,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;KAAO,GACnE,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,CAAC;IA0C9C,OAAO,CAAC,iBAAiB;IAazB,OAAO,CAAC,kBAAkB;YAyCZ,gBAAgB;IAe9B,OAAO,CAAC,4BAA4B;YAQtB,aAAa;IA+BrB,YAAY,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA+BzE,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAwBjE,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAiBnD;AAED,qBAAa,YAAa,SAAQ,aAAa;IAC7C,SAAS,CAAC,aAAa,EAAE,kBAAkB,CAAC;IAC5C,SAAS,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;gBAEtB,MAAM,EAAE,kBAAkB,EAAE,UAAU,CAAC,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,SAAS;IAMtF,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,IAAI,UAAU,IAAI,MAAM,CAEvB;IAEQ,sBAAsB,CAC7B,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,CAAC;IAO9C;;;;;;OAMG;IACG,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAmB5D;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IAuBpB,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,CAAC,CAAC;CA4BxF"}
@@ -249,6 +249,37 @@ export class OAuthService extends OAuthProvider {
249
249
  await this.tokenStore.setTokens(this.serviceId, userId, result.tokens);
250
250
  return result.tokens.accessToken;
251
251
  }
252
+ /**
253
+ * Resolve `endpoint` against `apiBaseUrl`, validating that absolute URLs
254
+ * share the configured origin.
255
+ *
256
+ * Without this check, a caller that forwards user-controlled data as
257
+ * `endpoint` could cause `fetch()` to issue requests to arbitrary hosts
258
+ * (including cloud metadata services and internal infrastructure). See
259
+ * SEC-003 in the security audit.
260
+ */
261
+ resolveEndpointUrl(endpoint) {
262
+ if (!endpoint.startsWith("http")) {
263
+ return `${this.apiBaseUrl}${endpoint}`;
264
+ }
265
+ let target;
266
+ let allowed;
267
+ try {
268
+ target = new URL(endpoint);
269
+ allowed = new URL(this.apiBaseUrl);
270
+ }
271
+ catch {
272
+ throw INVALID_ARGUMENT.create({
273
+ detail: `Invalid OAuth endpoint URL`,
274
+ });
275
+ }
276
+ if (target.origin !== allowed.origin) {
277
+ throw INVALID_ARGUMENT.create({
278
+ detail: `OAuth endpoint origin ${target.origin} does not match configured ${allowed.origin}`,
279
+ });
280
+ }
281
+ return endpoint;
282
+ }
252
283
  async fetch(userId, endpoint, options = {}) {
253
284
  const token = await this.getAccessToken(userId);
254
285
  if (!token) {
@@ -256,7 +287,7 @@ export class OAuthService extends OAuthProvider {
256
287
  detail: `Not authenticated with ${this.serviceConfig.displayName}`,
257
288
  });
258
289
  }
259
- const url = endpoint.startsWith("http") ? endpoint : `${this.apiBaseUrl}${endpoint}`;
290
+ const url = this.resolveEndpointUrl(endpoint);
260
291
  const response = await fetch(url, {
261
292
  ...options,
262
293
  headers: {
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.1.511";
1
+ export declare const VERSION = "0.1.513";
2
2
  //# sourceMappingURL=version-constant.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Keep in sync with deno.json version.
2
2
  // scripts/release.ts updates this constant during releases.
3
- export const VERSION = "0.1.511";
3
+ export const VERSION = "0.1.513";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.511",
3
+ "version": "0.1.513",
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.511",
3
+ "version": "0.1.513",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "workspace": [
@@ -284,12 +284,8 @@ export default {
284
284
  "@opentelemetry/sdk-node": "npm:@opentelemetry/sdk-node@0.217.0",
285
285
  "@opentelemetry/sdk-trace-base": "npm:@opentelemetry/sdk-trace-base@2.7.1",
286
286
  "@mdx-js/mdx": "npm:@mdx-js/mdx@3.1.1",
287
- "@babel/parser": "npm:@babel/parser@7.29.2",
288
287
  "gray-matter": "npm:gray-matter@4.0.3",
289
288
  "zod": "npm:zod@4.3.6",
290
- "mdast": "npm:@types/mdast@4.0.3",
291
- "hast": "npm:@types/hast@3.0.3",
292
- "unist": "npm:@types/unist@3.0.2",
293
289
  "unified": "npm:unified@11.0.5",
294
290
  "class-variance-authority": "npm:class-variance-authority@0.7.1",
295
291
  "tailwind-merge": "npm:tailwind-merge@3.5.0",
@@ -10,8 +10,24 @@ function splitAllowedOrigins(value: string): string[] {
10
10
 
11
11
  const booleanFlagSchema = z.string().default("false").transform(parseBooleanFlag);
12
12
 
13
+ const agentServiceRegistrationModeInputSchema = z
14
+ .enum(["auto", "enabled", "disabled", "true", "false"])
15
+ .default("auto")
16
+ .transform((value) => {
17
+ if (value === "true") return "enabled";
18
+ if (value === "false") return "disabled";
19
+ return value;
20
+ });
21
+
13
22
  export const agentServiceConfigSchema = z.object({
14
23
  VERYFRONT_API_URL: z.string().url().default("https://api.veryfront.com"),
24
+ VERYFRONT_API_TOKEN: z.string().min(1).optional(),
25
+ VERYFRONT_PROJECT_ID: z.string().min(1).optional(),
26
+ VERYFRONT_AGENT_SERVICE_URL: z.string().url().optional(),
27
+ VERYFRONT_AGENT_SERVICE_KEY: z.string().min(1).max(128).optional(),
28
+ VERYFRONT_AGENT_SERVICE_REGISTRATION: agentServiceRegistrationModeInputSchema,
29
+ VERYFRONT_AGENT_SERVICE_HEARTBEAT_INTERVAL_MS: z.coerce.number().positive().default(30_000),
30
+ VERYFRONT_AGENT_SERVICE_REGION: z.string().min(1).max(128).optional(),
15
31
  NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
16
32
  PORT: z.coerce.number().default(3001),
17
33
  OAUTH_PUBLIC_KEY: z.string().optional(),
@@ -24,6 +40,13 @@ export const agentServiceConfigSchema = z.object({
24
40
  }).transform((env) => ({
25
41
  VERYFRONT_API_URL: env.VERYFRONT_API_URL,
26
42
  VERYFRONT_MCP_URL: `${env.VERYFRONT_API_URL}/mcp`,
43
+ VERYFRONT_API_TOKEN: env.VERYFRONT_API_TOKEN,
44
+ VERYFRONT_PROJECT_ID: env.VERYFRONT_PROJECT_ID,
45
+ VERYFRONT_AGENT_SERVICE_URL: env.VERYFRONT_AGENT_SERVICE_URL,
46
+ VERYFRONT_AGENT_SERVICE_KEY: env.VERYFRONT_AGENT_SERVICE_KEY,
47
+ VERYFRONT_AGENT_SERVICE_REGISTRATION: env.VERYFRONT_AGENT_SERVICE_REGISTRATION,
48
+ VERYFRONT_AGENT_SERVICE_HEARTBEAT_INTERVAL_MS: env.VERYFRONT_AGENT_SERVICE_HEARTBEAT_INTERVAL_MS,
49
+ VERYFRONT_AGENT_SERVICE_REGION: env.VERYFRONT_AGENT_SERVICE_REGION,
27
50
  VERYFRONT_STUDIO_MCP_URL: env.VERYFRONT_STUDIO_MCP_URL,
28
51
  VERYFRONT_ENABLE_DURABLE_INVOKE_AGENT: env.VERYFRONT_ENABLE_DURABLE_INVOKE_AGENT,
29
52
  VERYFRONT_ENABLE_DURABLE_TASK: env.VERYFRONT_ENABLE_DURABLE_TASK,
@@ -0,0 +1,320 @@
1
+ import * as dntShim from "../../_dnt.shims.js";
2
+ import { z } from "zod";
3
+
4
+ export const agentServiceRegistrationModeSchema = z.enum([
5
+ "auto",
6
+ "enabled",
7
+ "disabled",
8
+ ]);
9
+
10
+ export const agentServiceRegistrationConfigSchema = z.object({
11
+ VERYFRONT_API_URL: z.string().url(),
12
+ VERYFRONT_API_TOKEN: z.string().min(1).optional(),
13
+ VERYFRONT_PROJECT_ID: z.string().min(1).optional(),
14
+ VERYFRONT_AGENT_SERVICE_URL: z.string().url().optional(),
15
+ VERYFRONT_AGENT_SERVICE_KEY: z.string().min(1).max(128).optional(),
16
+ VERYFRONT_AGENT_SERVICE_REGISTRATION: agentServiceRegistrationModeSchema,
17
+ VERYFRONT_AGENT_SERVICE_HEARTBEAT_INTERVAL_MS: z.number().positive(),
18
+ VERYFRONT_AGENT_SERVICE_REGION: z.string().min(1).max(128).optional(),
19
+ });
20
+
21
+ export const resolvedAgentServiceRegistrationInputSchema = z.object({
22
+ apiUrl: z.string().url(),
23
+ authToken: z.string().min(1),
24
+ serviceName: z.string().min(1).max(128),
25
+ serviceKey: z.string().min(1).max(128),
26
+ scopeKind: z.enum(["global", "project"]),
27
+ projectId: z.string().min(1).optional(),
28
+ agentId: z.string().min(1).max(128).optional(),
29
+ baseUrl: z.string().url(),
30
+ invokeUrl: z.string().url(),
31
+ version: z.string().min(1).max(128).optional(),
32
+ runtime: z.string().min(1).max(128).optional(),
33
+ region: z.string().min(1).max(128).optional(),
34
+ heartbeatIntervalMs: z.number().positive(),
35
+ });
36
+
37
+ const agentPushRuntimeServiceRestSchema = z.object({
38
+ id: z.string().uuid(),
39
+ service_name: z.string(),
40
+ service_key: z.string(),
41
+ scope_kind: z.enum(["global", "project"]),
42
+ scope_key: z.string(),
43
+ project_id: z.string().nullable(),
44
+ agent_id: z.string().nullable(),
45
+ base_url: z.string().url(),
46
+ invoke_url: z.string().url(),
47
+ status: z.enum(["active", "disabled"]),
48
+ capabilities: z.unknown().nullable(),
49
+ metadata: z.unknown().nullable(),
50
+ version: z.string().nullable(),
51
+ runtime: z.string().nullable(),
52
+ region: z.string().nullable(),
53
+ last_heartbeat_at: z.string().nullable(),
54
+ created_at: z.string(),
55
+ updated_at: z.string(),
56
+ });
57
+
58
+ const agentPushRuntimeServiceResponseSchema = z.object({
59
+ service: agentPushRuntimeServiceRestSchema,
60
+ });
61
+
62
+ const registerAgentPushRuntimeServiceRequestSchema = z.object({
63
+ service_name: z.string().min(1).max(128),
64
+ service_key: z.string().min(1).max(128),
65
+ scope_kind: z.enum(["global", "project"]),
66
+ project_id: z.string().optional(),
67
+ agent_id: z.string().optional(),
68
+ base_url: z.string().url(),
69
+ invoke_url: z.string().url(),
70
+ version: z.string().optional(),
71
+ runtime: z.string().optional(),
72
+ region: z.string().optional(),
73
+ });
74
+
75
+ export type AgentServiceRegistrationMode = z.infer<typeof agentServiceRegistrationModeSchema>;
76
+ export type AgentServiceRegistrationConfig = z.infer<typeof agentServiceRegistrationConfigSchema>;
77
+ export type ResolvedAgentServiceRegistrationInput = z.infer<
78
+ typeof resolvedAgentServiceRegistrationInputSchema
79
+ >;
80
+ export type AgentPushRuntimeServiceRest = z.infer<typeof agentPushRuntimeServiceRestSchema>;
81
+ export type RegisterAgentPushRuntimeServiceRequest = z.infer<
82
+ typeof registerAgentPushRuntimeServiceRequestSchema
83
+ >;
84
+
85
+ export type AgentServiceRegistrationLogger = {
86
+ info?: (message: string, metadata?: Record<string, unknown>) => void;
87
+ warn?: (message: string, metadata?: Record<string, unknown>) => void;
88
+ error?: (message: string, metadata?: Record<string, unknown>) => void;
89
+ };
90
+
91
+ export type ResolveAgentServiceRegistrationInputOptions = {
92
+ config: AgentServiceRegistrationConfig;
93
+ serviceName: string;
94
+ agentId?: string;
95
+ version?: string;
96
+ runtime?: string;
97
+ };
98
+
99
+ export type AgentServiceRegistrationLifecycle = {
100
+ serviceId: string;
101
+ service: AgentPushRuntimeServiceRest;
102
+ heartbeat: () => Promise<void>;
103
+ stop: () => void;
104
+ };
105
+
106
+ export type CreateAgentServiceRegistrationLifecycleOptions =
107
+ & ResolvedAgentServiceRegistrationInput
108
+ & {
109
+ fetch?: typeof globalThis.fetch;
110
+ logger?: AgentServiceRegistrationLogger;
111
+ };
112
+
113
+ function normalizeBaseUrl(value: string): string {
114
+ const url = new URL(value);
115
+ url.pathname = url.pathname.replace(/\/+$/, "");
116
+ url.search = "";
117
+ url.hash = "";
118
+ return url.toString().replace(/\/$/, "");
119
+ }
120
+
121
+ function defaultInvokeUrl(baseUrl: string): string {
122
+ return new URL("/api/runs", baseUrl).toString();
123
+ }
124
+
125
+ function getRegistrationEndpoint(apiUrl: string): string {
126
+ return new URL("/agent-runtimes/push-services", apiUrl).toString();
127
+ }
128
+
129
+ function getHeartbeatEndpoint(apiUrl: string, serviceId: string): string {
130
+ return new URL(`/agent-runtimes/push-services/${serviceId}/heartbeat`, apiUrl).toString();
131
+ }
132
+
133
+ function getErrorMessage(error: unknown): string {
134
+ return error instanceof Error ? error.message : String(error);
135
+ }
136
+
137
+ async function stableServiceKey(input: {
138
+ serviceName: string;
139
+ agentId?: string;
140
+ baseUrl: string;
141
+ scopeKind: "global" | "project";
142
+ projectId?: string;
143
+ }): Promise<string> {
144
+ const keySource = [
145
+ input.serviceName,
146
+ input.agentId ?? "default",
147
+ input.scopeKind,
148
+ input.projectId ?? "global",
149
+ input.baseUrl,
150
+ ].join("|");
151
+ const digest = await dntShim.crypto.subtle.digest("SHA-256", new TextEncoder().encode(keySource));
152
+ const hash = Array.from(new Uint8Array(digest))
153
+ .map((byte) => byte.toString(16).padStart(2, "0"))
154
+ .join("")
155
+ .slice(0, 32);
156
+ return `${input.serviceName}:${hash}`.slice(0, 128);
157
+ }
158
+
159
+ function requireExplicitRegistrationValue(
160
+ value: string | undefined,
161
+ envName: string,
162
+ ): string {
163
+ if (!value) {
164
+ throw new Error(`${envName} is required when VERYFRONT_AGENT_SERVICE_REGISTRATION=enabled`);
165
+ }
166
+ return value;
167
+ }
168
+
169
+ export async function resolveAgentServiceRegistrationInput(
170
+ options: ResolveAgentServiceRegistrationInputOptions,
171
+ ): Promise<ResolvedAgentServiceRegistrationInput | null> {
172
+ const config = agentServiceRegistrationConfigSchema.parse(options.config);
173
+ const enabled = config.VERYFRONT_AGENT_SERVICE_REGISTRATION === "enabled";
174
+ const token = enabled
175
+ ? requireExplicitRegistrationValue(config.VERYFRONT_API_TOKEN, "VERYFRONT_API_TOKEN")
176
+ : config.VERYFRONT_API_TOKEN;
177
+ const serviceUrl = enabled
178
+ ? requireExplicitRegistrationValue(
179
+ config.VERYFRONT_AGENT_SERVICE_URL,
180
+ "VERYFRONT_AGENT_SERVICE_URL",
181
+ )
182
+ : config.VERYFRONT_AGENT_SERVICE_URL;
183
+
184
+ if (config.VERYFRONT_AGENT_SERVICE_REGISTRATION === "disabled") {
185
+ return null;
186
+ }
187
+ if (!token || !serviceUrl) {
188
+ return null;
189
+ }
190
+
191
+ const scopeKind = config.VERYFRONT_PROJECT_ID ? "project" : "global";
192
+ const baseUrl = normalizeBaseUrl(serviceUrl);
193
+ const serviceKey = config.VERYFRONT_AGENT_SERVICE_KEY ?? await stableServiceKey({
194
+ serviceName: options.serviceName,
195
+ agentId: options.agentId,
196
+ baseUrl,
197
+ scopeKind,
198
+ projectId: config.VERYFRONT_PROJECT_ID,
199
+ });
200
+
201
+ return resolvedAgentServiceRegistrationInputSchema.parse({
202
+ apiUrl: config.VERYFRONT_API_URL,
203
+ authToken: token,
204
+ serviceName: options.serviceName,
205
+ serviceKey,
206
+ scopeKind,
207
+ projectId: config.VERYFRONT_PROJECT_ID,
208
+ agentId: options.agentId,
209
+ baseUrl,
210
+ invokeUrl: defaultInvokeUrl(baseUrl),
211
+ version: options.version,
212
+ runtime: options.runtime,
213
+ region: config.VERYFRONT_AGENT_SERVICE_REGION,
214
+ heartbeatIntervalMs: config.VERYFRONT_AGENT_SERVICE_HEARTBEAT_INTERVAL_MS,
215
+ });
216
+ }
217
+
218
+ async function readAgentPushRuntimeServiceResponse(
219
+ response: Response,
220
+ ): Promise<AgentPushRuntimeServiceRest> {
221
+ if (!response.ok) {
222
+ throw new Error(`Agent runtime registration request failed with HTTP ${response.status}`);
223
+ }
224
+
225
+ const parsed = agentPushRuntimeServiceResponseSchema.parse(await response.json());
226
+ return parsed.service;
227
+ }
228
+
229
+ function createHeaders(authToken: string): Headers {
230
+ const headers = new Headers();
231
+ headers.set("Authorization", `Bearer ${authToken}`);
232
+ headers.set("Content-Type", "application/json");
233
+ return headers;
234
+ }
235
+
236
+ function buildRegistrationRequest(
237
+ input: ResolvedAgentServiceRegistrationInput,
238
+ ): RegisterAgentPushRuntimeServiceRequest {
239
+ return registerAgentPushRuntimeServiceRequestSchema.parse({
240
+ service_name: input.serviceName,
241
+ service_key: input.serviceKey,
242
+ scope_kind: input.scopeKind,
243
+ project_id: input.projectId,
244
+ agent_id: input.agentId,
245
+ base_url: input.baseUrl,
246
+ invoke_url: input.invokeUrl,
247
+ version: input.version,
248
+ runtime: input.runtime,
249
+ region: input.region,
250
+ });
251
+ }
252
+
253
+ async function registerAgentPushRuntimeService(
254
+ input: ResolvedAgentServiceRegistrationInput,
255
+ fetchImpl: typeof globalThis.fetch,
256
+ ): Promise<AgentPushRuntimeServiceRest> {
257
+ const response = await fetchImpl(getRegistrationEndpoint(input.apiUrl), {
258
+ method: "POST",
259
+ headers: createHeaders(input.authToken),
260
+ body: JSON.stringify(buildRegistrationRequest(input)),
261
+ });
262
+ return await readAgentPushRuntimeServiceResponse(response);
263
+ }
264
+
265
+ async function heartbeatAgentPushRuntimeService(
266
+ input: { apiUrl: string; authToken: string; serviceId: string },
267
+ fetchImpl: typeof globalThis.fetch,
268
+ ): Promise<AgentPushRuntimeServiceRest> {
269
+ const response = await fetchImpl(getHeartbeatEndpoint(input.apiUrl, input.serviceId), {
270
+ method: "POST",
271
+ headers: createHeaders(input.authToken),
272
+ });
273
+ return await readAgentPushRuntimeServiceResponse(response);
274
+ }
275
+
276
+ export async function createAgentServiceRegistrationLifecycle(
277
+ options: CreateAgentServiceRegistrationLifecycleOptions,
278
+ ): Promise<AgentServiceRegistrationLifecycle> {
279
+ const fetchImpl = options.fetch ?? globalThis.fetch;
280
+ const input = resolvedAgentServiceRegistrationInputSchema.parse(options);
281
+ const service = await registerAgentPushRuntimeService(input, fetchImpl);
282
+ let stopped = false;
283
+
284
+ const heartbeat = async () => {
285
+ if (stopped) {
286
+ return;
287
+ }
288
+ await heartbeatAgentPushRuntimeService({
289
+ apiUrl: input.apiUrl,
290
+ authToken: input.authToken,
291
+ serviceId: service.id,
292
+ }, fetchImpl);
293
+ };
294
+
295
+ const interval = dntShim.setInterval(() => {
296
+ void heartbeat().catch((error: unknown) => {
297
+ options.logger?.warn?.("Agent service heartbeat failed", {
298
+ serviceId: service.id,
299
+ error: getErrorMessage(error),
300
+ });
301
+ });
302
+ }, input.heartbeatIntervalMs);
303
+
304
+ options.logger?.info?.("Agent service registered with control plane", {
305
+ serviceId: service.id,
306
+ serviceName: service.service_name,
307
+ scopeKind: service.scope_kind,
308
+ projectId: service.project_id,
309
+ });
310
+
311
+ return {
312
+ serviceId: service.id,
313
+ service,
314
+ heartbeat,
315
+ stop: () => {
316
+ stopped = true;
317
+ clearInterval(interval);
318
+ },
319
+ };
320
+ }
@@ -28,6 +28,7 @@ import type { AgUiResumeValue } from "./ag-ui-tool-shared.js";
28
28
  import type { RuntimeAgentMarkdownDefinition } from "./runtime-agent-definition.js";
29
29
  import {
30
30
  type AgentServiceServer,
31
+ type AgentServiceServerLifecycle,
31
32
  type NodeAgentServiceServer,
32
33
  startAgentServiceServer,
33
34
  startNodeAgentServiceServer,
@@ -110,6 +111,7 @@ export type StartNodeHostedAgentServiceOptions<
110
111
  bindAddress?: string;
111
112
  hardShutdownTimeoutMs?: number;
112
113
  signals?: readonly NodeJS.Signals[];
114
+ lifecycle?: AgentServiceServerLifecycle;
113
115
  };
114
116
 
115
117
  export type StartNodeAgentServiceOptions<
@@ -124,6 +126,7 @@ export type StartAgentServiceRuntimeOptions<
124
126
  bindAddress?: string;
125
127
  hardShutdownTimeoutMs?: number;
126
128
  signals?: readonly NodeJS.Signals[];
129
+ lifecycle?: AgentServiceServerLifecycle;
127
130
  };
128
131
 
129
132
  export type StartNodeHostedAgentServiceResult<
@@ -152,6 +155,26 @@ function defaultTrace<TResult>(
152
155
  return operation();
153
156
  }
154
157
 
158
+ function combineAgentServiceLifecycle(
159
+ primary: AgentServiceServerLifecycle,
160
+ secondary: AgentServiceServerLifecycle | undefined,
161
+ ): AgentServiceServerLifecycle {
162
+ if (!secondary) {
163
+ return primary;
164
+ }
165
+
166
+ return {
167
+ setShuttingDown: () => {
168
+ primary.setShuttingDown?.();
169
+ secondary.setShuttingDown?.();
170
+ },
171
+ stop: async () => {
172
+ await primary.stop?.();
173
+ await secondary.stop?.();
174
+ },
175
+ };
176
+ }
177
+
155
178
  export function createAgentServiceRuntime<
156
179
  TExecution extends object,
157
180
  TConfig extends AgentServiceRuntimeConfig = AgentServiceRuntimeConfig,
@@ -232,7 +255,7 @@ export async function startNodeAgentService<
232
255
  const nodeServer = await startNodeAgentServiceServer({
233
256
  runtime: bundle.runtime,
234
257
  serviceName: options.serviceName,
235
- lifecycle: bundle.lifecycle,
258
+ lifecycle: combineAgentServiceLifecycle(bundle.lifecycle, options.lifecycle),
236
259
  port: bundle.config.PORT,
237
260
  bindAddress: options.bindAddress,
238
261
  signals: options.signals,
@@ -265,7 +288,7 @@ export async function startAgentServiceRuntime<
265
288
  const server = await startAgentServiceServer({
266
289
  runtime: bundle.runtime,
267
290
  serviceName: options.serviceName,
268
- lifecycle: bundle.lifecycle,
291
+ lifecycle: combineAgentServiceLifecycle(bundle.lifecycle, options.lifecycle),
269
292
  port: bundle.config.PORT,
270
293
  bindAddress: options.bindAddress,
271
294
  signals: options.signals,
@@ -347,6 +347,21 @@ export {
347
347
  veryfrontMcpServer,
348
348
  type VeryfrontMcpServerKind,
349
349
  } from "./veryfront-cloud-agent-service.js";
350
+ export {
351
+ type AgentPushRuntimeServiceRest,
352
+ type AgentServiceRegistrationConfig,
353
+ agentServiceRegistrationConfigSchema,
354
+ type AgentServiceRegistrationLifecycle,
355
+ type AgentServiceRegistrationLogger,
356
+ type AgentServiceRegistrationMode,
357
+ createAgentServiceRegistrationLifecycle,
358
+ type CreateAgentServiceRegistrationLifecycleOptions,
359
+ type RegisterAgentPushRuntimeServiceRequest,
360
+ resolveAgentServiceRegistrationInput,
361
+ type ResolveAgentServiceRegistrationInputOptions,
362
+ type ResolvedAgentServiceRegistrationInput,
363
+ resolvedAgentServiceRegistrationInputSchema,
364
+ } from "./agent-service-registration.js";
350
365
  export {
351
366
  type AgentServiceConfig,
352
367
  type AgentServiceConfigInput,
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync } from "node:fs";
1
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { basename, resolve } from "node:path";
3
3
  import { z } from "zod";
4
4
  import {
@@ -19,6 +19,14 @@ export type ResolveRuntimeAgentDefinitionsDirInput = z.infer<
19
19
  typeof resolveRuntimeAgentDefinitionsDirInputSchema
20
20
  >;
21
21
 
22
+ export const listRuntimeAgentMarkdownDefinitionIdsInputSchema = z.object({
23
+ baseDir: z.string().min(1),
24
+ });
25
+
26
+ export type ListRuntimeAgentMarkdownDefinitionIdsInput = z.infer<
27
+ typeof listRuntimeAgentMarkdownDefinitionIdsInputSchema
28
+ >;
29
+
22
30
  export const loadRuntimeAgentMarkdownDefinitionFromFileInputSchema = z.object({
23
31
  agentsDir: z.string().min(1),
24
32
  id: runtimeAgentDefinitionFileIdSchema,
@@ -40,27 +48,62 @@ function hasRuntimeAgentDefinitionFile(path: string, fileName: string): boolean
40
48
  return existsSync(resolve(path, fileName));
41
49
  }
42
50
 
51
+ function getRuntimeAgentDefinitionsDirCandidates(baseDir: string): string[] {
52
+ const firstCandidate = resolve(baseDir, "agents");
53
+ const sourceLayoutCandidate = resolve(baseDir, "../agents");
54
+ const candidates = [
55
+ firstCandidate,
56
+ sourceLayoutCandidate,
57
+ resolve(baseDir, "../../agents"),
58
+ resolve(baseDir, "../../../agents"),
59
+ ];
60
+
61
+ return [...new Set(candidates)];
62
+ }
63
+
43
64
  export function resolveRuntimeAgentDefinitionsDir(
44
65
  input: ResolveRuntimeAgentDefinitionsDirInput,
45
66
  ): string {
46
67
  const parsedInput = resolveRuntimeAgentDefinitionsDirInputSchema.parse(input);
47
68
  const fileName = getRuntimeAgentDefinitionFileName(parsedInput);
48
- const firstCandidate = resolve(parsedInput.baseDir, "agents");
69
+ const candidates = getRuntimeAgentDefinitionsDirCandidates(parsedInput.baseDir);
49
70
  const sourceLayoutCandidate = resolve(parsedInput.baseDir, "../agents");
50
- const candidates = [
51
- firstCandidate,
52
- sourceLayoutCandidate,
53
- resolve(parsedInput.baseDir, "../../agents"),
54
- resolve(parsedInput.baseDir, "../../../agents"),
55
- ];
56
71
  const fallbackCandidate = basename(parsedInput.baseDir) === "src"
57
72
  ? sourceLayoutCandidate
58
- : firstCandidate;
73
+ : resolve(parsedInput.baseDir, "agents");
59
74
 
60
75
  return candidates.find((candidate) => hasRuntimeAgentDefinitionFile(candidate, fileName)) ??
61
76
  fallbackCandidate;
62
77
  }
63
78
 
79
+ export function listRuntimeAgentMarkdownDefinitionIds(
80
+ input: ListRuntimeAgentMarkdownDefinitionIdsInput,
81
+ ): string[] {
82
+ const parsedInput = listRuntimeAgentMarkdownDefinitionIdsInputSchema.parse(input);
83
+ const ids = new Set<string>();
84
+
85
+ for (const dir of getRuntimeAgentDefinitionsDirCandidates(parsedInput.baseDir)) {
86
+ if (!existsSync(dir)) {
87
+ continue;
88
+ }
89
+
90
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
91
+ if (!entry.isFile()) {
92
+ continue;
93
+ }
94
+
95
+ const parseResult = runtimeAgentDefinitionFileNameSchema.safeParse(entry.name);
96
+ if (!parseResult.success) {
97
+ continue;
98
+ }
99
+
100
+ ids.add(parseResult.data.slice(0, -".md".length));
101
+ }
102
+ }
103
+
104
+ return [...ids].sort((left, right) => left.localeCompare(right));
105
+ }
106
+
64
107
  export function resolveRuntimeAgentMarkdownDefinitionFilePath(
65
108
  input: LoadRuntimeAgentMarkdownDefinitionFromFileInput,
66
109
  ): string {