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,7 +40,10 @@ import {
40
40
  } from "./agent-service-bootstrap.js";
41
41
  import { loadAgentServiceEnvFiles } from "./agent-service-env-files.js";
42
42
  import { createHostedFormInputTool } from "./hosted-form-input-tool.js";
43
- import { createHostedAgentProjectSteering } from "./hosted-agent-project-steering.js";
43
+ import {
44
+ createHostedAgentProjectSteering,
45
+ type HostedAgentProjectSteering,
46
+ } from "./hosted-agent-project-steering.js";
44
47
  import { type HostedChatRuntimeCreationResult } from "./hosted-chat-runtime-contract.js";
45
48
  import type { HostedConversationRootRunContext } from "./conversation-root-run-lifecycle.js";
46
49
  import { type AgentRuntimeMessage } from "./agent-runtime-message-adapter.js";
@@ -65,10 +68,7 @@ import type { AgentServiceMcpServerConfig } from "./agent-service-mcp-server-con
65
68
  import type { RuntimeLoadSkillToolContext } from "./runtime-load-skill-tool.js";
66
69
  import type { RuntimeProjectSteeringLookup } from "./runtime-project-skill-catalog.js";
67
70
  import type { RuntimeSkillDefinition } from "./runtime-skill-metadata.js";
68
- import {
69
- loadRuntimeAgentMarkdownDefinitionFromFile,
70
- resolveRuntimeAgentDefinitionsDir,
71
- } from "./runtime-agent-definition-files.js";
71
+ import { listRuntimeAgentMarkdownDefinitionIds } from "./runtime-agent-definition-files.js";
72
72
  import type { RuntimeAgentMarkdownDefinition } from "./runtime-agent-definition.js";
73
73
  import {
74
74
  buildVeryfrontCloudRuntimeInstructions,
@@ -86,6 +86,11 @@ import {
86
86
  startNodeAgentService,
87
87
  type StartNodeAgentServiceResult,
88
88
  } from "./agent-service-runtime.js";
89
+ import type { AgentServiceServerLifecycle } from "./agent-service-server.js";
90
+ import {
91
+ createAgentServiceRegistrationLifecycle,
92
+ resolveAgentServiceRegistrationInput,
93
+ } from "./agent-service-registration.js";
89
94
  import { createDetachedRunTracker } from "./detached-run-tracker.js";
90
95
  import type { AgUiResumeValue } from "./ag-ui-tool-shared.js";
91
96
  import type { ParsedHostedChatRequest } from "./hosted-chat-request-parser.js";
@@ -132,7 +137,11 @@ type AgentServicePathOption = string | URL;
132
137
 
133
138
  export type NodeVeryfrontCloudAgentServiceOptions = {
134
139
  serviceName: string;
135
- agentId: string;
140
+ /**
141
+ * Default agent served by requests that do not provide an agent id. When
142
+ * omitted, the service selects the only discovered code or markdown agent.
143
+ */
144
+ agentId?: string;
136
145
  /**
137
146
  * Project/discovery root. Defaults to the process cwd when neither baseDir
138
147
  * nor an entrypoint URL is provided.
@@ -155,6 +164,7 @@ export type NodeVeryfrontCloudAgentServiceOptions = {
155
164
  processTarget?: NodeVeryfrontCloudAgentServiceProcessTarget;
156
165
  drainTimeoutMs?: number;
157
166
  hardShutdownTimeoutMs?: number;
167
+ signals?: readonly NodeJS.Signals[];
158
168
  };
159
169
 
160
170
  export type VeryfrontCloudAgentServiceOptions = NodeVeryfrontCloudAgentServiceOptions;
@@ -310,13 +320,6 @@ function createNodeVeryfrontCloudAgentServiceContext(
310
320
  ): TResult | Promise<TResult> {
311
321
  return infrastructure.tracer.trace(operationName, operation);
312
322
  }
313
- const projectSteering = createHostedAgentProjectSteering({
314
- baseDir: resolveBaseDir(options),
315
- agentId: options.agentId,
316
- getApiUrl: () => infrastructure.getConfig().VERYFRONT_API_URL,
317
- logger: infrastructure.logger,
318
- trace,
319
- });
320
323
 
321
324
  return {
322
325
  options,
@@ -324,7 +327,8 @@ function createNodeVeryfrontCloudAgentServiceContext(
324
327
  projectDir: resolveProjectDir(options),
325
328
  infrastructure,
326
329
  trace,
327
- projectSteering,
330
+ defaultAgentId: null as string | null,
331
+ projectSteeringByAgentId: new Map<string, HostedAgentProjectSteering>(),
328
332
  tracker: createDetachedRunTracker<AgUiResumeValue>(),
329
333
  discoveryResult: null as DiscoveryResult | null,
330
334
  agentConfig: null as RuntimeAgentMarkdownDefinition | null,
@@ -351,27 +355,16 @@ async function createRuntimeAgentDefinitionFromCodeAgent(
351
355
 
352
356
  function getMarkdownAgentConfig(
353
357
  context: NodeVeryfrontCloudAgentServiceContext,
358
+ agentId: string,
354
359
  ): RuntimeAgentMarkdownDefinition {
355
- return context.projectSteering.getAgentConfig();
360
+ return getProjectSteering(context, agentId).getAgentConfig();
356
361
  }
357
362
 
358
363
  function loadMarkdownAgentConfig(
359
364
  context: NodeVeryfrontCloudAgentServiceContext,
360
365
  agentId: string,
361
366
  ): RuntimeAgentMarkdownDefinition {
362
- if (agentId === context.options.agentId) {
363
- return getMarkdownAgentConfig(context);
364
- }
365
-
366
- const agentsDir = resolveRuntimeAgentDefinitionsDir({
367
- baseDir: resolveBaseDir(context.options),
368
- id: agentId,
369
- });
370
-
371
- return loadRuntimeAgentMarkdownDefinitionFromFile({
372
- agentsDir,
373
- id: agentId,
374
- });
367
+ return getMarkdownAgentConfig(context, agentId);
375
368
  }
376
369
 
377
370
  async function resolveAgentConfig(
@@ -427,11 +420,102 @@ async function discoverProjectPrimitives(
427
420
  context.discoveryResult = await discoverAll(discoveryOptions);
428
421
  }
429
422
 
423
+ function getDiscoveredCodeAgentIds(context: NodeVeryfrontCloudAgentServiceContext): string[] {
424
+ return [...(context.discoveryResult?.agents.keys() ?? [])].sort((left, right) =>
425
+ left.localeCompare(right)
426
+ );
427
+ }
428
+
429
+ function getDiscoveredMarkdownAgentIds(context: NodeVeryfrontCloudAgentServiceContext): string[] {
430
+ return listRuntimeAgentMarkdownDefinitionIds({
431
+ baseDir: resolveBaseDir(context.options),
432
+ });
433
+ }
434
+
435
+ function describeAgentIdCandidates(input: {
436
+ codeAgentIds: string[];
437
+ markdownAgentIds: string[];
438
+ }): string {
439
+ const ids = [...new Set([...input.codeAgentIds, ...input.markdownAgentIds])]
440
+ .sort((left, right) => left.localeCompare(right));
441
+
442
+ return ids.length > 0 ? ids.join(", ") : "none";
443
+ }
444
+
445
+ function resolveSingleAgentId(input: {
446
+ codeAgentIds: string[];
447
+ markdownAgentIds: string[];
448
+ source: NodeVeryfrontCloudAgentServiceAgentSource;
449
+ }): string | null {
450
+ if (input.source === "code") {
451
+ return input.codeAgentIds.length === 1 ? input.codeAgentIds[0] ?? null : null;
452
+ }
453
+
454
+ if (input.source === "markdown") {
455
+ return input.markdownAgentIds.length === 1 ? input.markdownAgentIds[0] ?? null : null;
456
+ }
457
+
458
+ const candidateIds = [...new Set([...input.codeAgentIds, ...input.markdownAgentIds])];
459
+ return candidateIds.length === 1 ? candidateIds[0] ?? null : null;
460
+ }
461
+
462
+ function resolveDefaultAgentId(context: NodeVeryfrontCloudAgentServiceContext): string {
463
+ if (context.options.agentId) {
464
+ return context.options.agentId;
465
+ }
466
+
467
+ const source = context.options.agentSource ?? "auto";
468
+ const codeAgentIds = getDiscoveredCodeAgentIds(context);
469
+ const markdownAgentIds = getDiscoveredMarkdownAgentIds(context);
470
+ const agentId = resolveSingleAgentId({ codeAgentIds, markdownAgentIds, source });
471
+
472
+ if (agentId) {
473
+ return agentId;
474
+ }
475
+
476
+ throw new Error(
477
+ [
478
+ "agentId is required when agent discovery does not resolve to exactly one agent.",
479
+ `Discovered agents: ${describeAgentIdCandidates({ codeAgentIds, markdownAgentIds })}.`,
480
+ ].join(" "),
481
+ );
482
+ }
483
+
430
484
  async function initializeNodeVeryfrontCloudAgentServiceContext(
431
485
  context: NodeVeryfrontCloudAgentServiceContext,
432
486
  ): Promise<void> {
433
487
  await discoverProjectPrimitives(context);
434
- context.agentConfig = await resolveAgentConfig(context, context.options.agentId);
488
+ context.defaultAgentId = resolveDefaultAgentId(context);
489
+ context.agentConfig = await resolveAgentConfig(context, context.defaultAgentId);
490
+ }
491
+
492
+ function getDefaultAgentId(context: NodeVeryfrontCloudAgentServiceContext): string {
493
+ if (!context.defaultAgentId) {
494
+ throw new Error("Agent service context has not been initialized.");
495
+ }
496
+
497
+ return context.defaultAgentId;
498
+ }
499
+
500
+ function getProjectSteering(
501
+ context: NodeVeryfrontCloudAgentServiceContext,
502
+ agentId: string = getDefaultAgentId(context),
503
+ ): HostedAgentProjectSteering {
504
+ const cachedProjectSteering = context.projectSteeringByAgentId.get(agentId);
505
+ if (cachedProjectSteering) {
506
+ return cachedProjectSteering;
507
+ }
508
+
509
+ const projectSteering = createHostedAgentProjectSteering({
510
+ baseDir: resolveBaseDir(context.options),
511
+ agentId,
512
+ getApiUrl: () => context.infrastructure.getConfig().VERYFRONT_API_URL,
513
+ logger: context.infrastructure.logger,
514
+ trace: context.trace,
515
+ });
516
+
517
+ context.projectSteeringByAgentId.set(agentId, projectSteering);
518
+ return projectSteering;
435
519
  }
436
520
 
437
521
  function getDiscoveredHostTools(): HostToolSet {
@@ -445,7 +529,7 @@ function getProjectInstructions(
445
529
  lookup: RuntimeProjectSteeringLookup,
446
530
  ): Promise<string> {
447
531
  return context.trace("chat.getProjectInstructions", async () => {
448
- return await context.projectSteering.getProjectInstructions(lookup);
532
+ return await getProjectSteering(context).getProjectInstructions(lookup);
449
533
  });
450
534
  }
451
535
 
@@ -454,7 +538,7 @@ function getSkillsConfig(
454
538
  lookup: RuntimeProjectSteeringLookup,
455
539
  ): Promise<RuntimeSkillDefinition[]> {
456
540
  return context.trace("chat.getSkillsConfig", async () => {
457
- return await context.projectSteering.getSkillsConfig(lookup);
541
+ return await getProjectSteering(context).getSkillsConfig(lookup);
458
542
  });
459
543
  }
460
544
 
@@ -462,14 +546,14 @@ function createLoadSkillTool(
462
546
  context: NodeVeryfrontCloudAgentServiceContext,
463
547
  toolContext: RuntimeLoadSkillToolContext,
464
548
  ) {
465
- return context.projectSteering.createLoadSkillTool(toolContext);
549
+ return getProjectSteering(context).createLoadSkillTool(toolContext);
466
550
  }
467
551
 
468
552
  async function refreshProjectSkillIds(
469
553
  context: NodeVeryfrontCloudAgentServiceContext,
470
554
  skillContext: HostedProjectSkillIdsContext,
471
555
  ): Promise<void> {
472
- await context.projectSteering.refreshProjectSkillIds(skillContext);
556
+ await getProjectSteering(context).refreshProjectSkillIds(skillContext);
473
557
  }
474
558
 
475
559
  function setFilteredTraceAttributes(
@@ -683,7 +767,7 @@ async function prepareChatExecution(
683
767
 
684
768
  setPrepareChatExecutionStartAttributes(context, { projectId, userId });
685
769
 
686
- const agentConfig = await resolveAgentConfig(context, req.agentId ?? context.options.agentId);
770
+ const agentConfig = await resolveAgentConfig(context, req.agentId ?? getDefaultAgentId(context));
687
771
  const {
688
772
  effectiveMessages,
689
773
  rootRunContext,
@@ -760,6 +844,58 @@ function createPreparedExecutionRuntimeOptions(
760
844
  });
761
845
  }
762
846
 
847
+ function resolveAgentServiceRuntimeName(): string {
848
+ if (Reflect.get(dntShim.dntGlobalThis, "Bun")) {
849
+ return "bun";
850
+ }
851
+ if (Reflect.get(dntShim.dntGlobalThis, "Deno")) {
852
+ return "deno";
853
+ }
854
+ return "node";
855
+ }
856
+
857
+ function getAgentServiceVersion(
858
+ context: NodeVeryfrontCloudAgentServiceContext,
859
+ ): string | undefined {
860
+ return context.options.env?.npm_package_version;
861
+ }
862
+
863
+ async function createControlPlaneRegistrationLifecycle(
864
+ context: NodeVeryfrontCloudAgentServiceContext,
865
+ ): Promise<AgentServiceServerLifecycle | undefined> {
866
+ const config = context.infrastructure.getConfig();
867
+ const registrationInput = await resolveAgentServiceRegistrationInput({
868
+ config,
869
+ serviceName: context.options.serviceName,
870
+ agentId: getDefaultAgentId(context),
871
+ version: getAgentServiceVersion(context),
872
+ runtime: resolveAgentServiceRuntimeName(),
873
+ });
874
+
875
+ if (!registrationInput) {
876
+ return undefined;
877
+ }
878
+
879
+ try {
880
+ const lifecycle = await createAgentServiceRegistrationLifecycle({
881
+ ...registrationInput,
882
+ logger: context.infrastructure.logger,
883
+ });
884
+ return {
885
+ stop: () => lifecycle.stop(),
886
+ };
887
+ } catch (error) {
888
+ if (config.VERYFRONT_AGENT_SERVICE_REGISTRATION === "enabled") {
889
+ throw error;
890
+ }
891
+
892
+ context.infrastructure.logger.warn("Agent service registration skipped", {
893
+ error: error instanceof Error ? error.message : String(error),
894
+ });
895
+ return undefined;
896
+ }
897
+ }
898
+
763
899
  function createNodeVeryfrontCloudAgentServiceRuntimeOptions(
764
900
  context: NodeVeryfrontCloudAgentServiceContext,
765
901
  ): CreateAgentServiceRuntimeOptions<NodeVeryfrontCloudAgentServicePreparedExecution> {
@@ -818,10 +954,18 @@ export async function startNodeVeryfrontCloudAgentService(
818
954
  const resolvedOptions = await resolveNodeVeryfrontCloudAgentServiceOptions(options);
819
955
  const context = createNodeVeryfrontCloudAgentServiceContext(resolvedOptions);
820
956
  await initializeNodeVeryfrontCloudAgentServiceContext(context);
821
- return await startNodeAgentService({
822
- ...createNodeVeryfrontCloudAgentServiceRuntimeOptions(context),
823
- hardShutdownTimeoutMs: options.hardShutdownTimeoutMs ?? DEFAULT_HARD_SHUTDOWN_TIMEOUT_MS,
824
- });
957
+ const registrationLifecycle = await createControlPlaneRegistrationLifecycle(context);
958
+ try {
959
+ return await startNodeAgentService({
960
+ ...createNodeVeryfrontCloudAgentServiceRuntimeOptions(context),
961
+ lifecycle: registrationLifecycle,
962
+ signals: options.signals,
963
+ hardShutdownTimeoutMs: options.hardShutdownTimeoutMs ?? DEFAULT_HARD_SHUTDOWN_TIMEOUT_MS,
964
+ });
965
+ } catch (error) {
966
+ await registrationLifecycle?.stop?.();
967
+ throw error;
968
+ }
825
969
  }
826
970
 
827
971
  export async function startAgentService(
@@ -856,10 +1000,18 @@ export async function startAgentService(
856
1000
  __registerTraceContextGetter(getter);
857
1001
  },
858
1002
  start: async () => {
859
- await startAgentServiceRuntime({
860
- ...createNodeVeryfrontCloudAgentServiceRuntimeOptions(context),
861
- hardShutdownTimeoutMs: options.hardShutdownTimeoutMs ?? DEFAULT_HARD_SHUTDOWN_TIMEOUT_MS,
862
- });
1003
+ const registrationLifecycle = await createControlPlaneRegistrationLifecycle(context);
1004
+ try {
1005
+ await startAgentServiceRuntime({
1006
+ ...createNodeVeryfrontCloudAgentServiceRuntimeOptions(context),
1007
+ lifecycle: registrationLifecycle,
1008
+ signals: options.signals,
1009
+ hardShutdownTimeoutMs: options.hardShutdownTimeoutMs ?? DEFAULT_HARD_SHUTDOWN_TIMEOUT_MS,
1010
+ });
1011
+ } catch (error) {
1012
+ await registrationLifecycle?.stop?.();
1013
+ throw error;
1014
+ }
863
1015
  },
864
1016
  onStartupError: (error) => {
865
1017
  console.error("Error in server startup:", error);
@@ -341,6 +341,38 @@ export class OAuthService extends OAuthProvider {
341
341
  return result.tokens.accessToken;
342
342
  }
343
343
 
344
+ /**
345
+ * Resolve `endpoint` against `apiBaseUrl`, validating that absolute URLs
346
+ * share the configured origin.
347
+ *
348
+ * Without this check, a caller that forwards user-controlled data as
349
+ * `endpoint` could cause `fetch()` to issue requests to arbitrary hosts
350
+ * (including cloud metadata services and internal infrastructure). See
351
+ * SEC-003 in the security audit.
352
+ */
353
+ private resolveEndpointUrl(endpoint: string): string {
354
+ if (!endpoint.startsWith("http")) {
355
+ return `${this.apiBaseUrl}${endpoint}`;
356
+ }
357
+ let target: URL;
358
+ let allowed: URL;
359
+ try {
360
+ target = new URL(endpoint);
361
+ allowed = new URL(this.apiBaseUrl);
362
+ } catch {
363
+ throw INVALID_ARGUMENT.create({
364
+ detail: `Invalid OAuth endpoint URL`,
365
+ });
366
+ }
367
+ if (target.origin !== allowed.origin) {
368
+ throw INVALID_ARGUMENT.create({
369
+ detail:
370
+ `OAuth endpoint origin ${target.origin} does not match configured ${allowed.origin}`,
371
+ });
372
+ }
373
+ return endpoint;
374
+ }
375
+
344
376
  async fetch<T>(userId: string, endpoint: string, options: RequestInit = {}): Promise<T> {
345
377
  const token = await this.getAccessToken(userId);
346
378
  if (!token) {
@@ -349,7 +381,7 @@ export class OAuthService extends OAuthProvider {
349
381
  });
350
382
  }
351
383
 
352
- const url = endpoint.startsWith("http") ? endpoint : `${this.apiBaseUrl}${endpoint}`;
384
+ const url = this.resolveEndpointUrl(endpoint);
353
385
 
354
386
  const response = await fetch(url, {
355
387
  ...options,
@@ -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";