forgeos 0.1.0-alpha.2 → 0.1.0-alpha.4

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 (180) hide show
  1. package/AGENTS.md +38 -3
  2. package/CHANGELOG.md +29 -0
  3. package/README.md +25 -10
  4. package/package.json +8 -5
  5. package/src/forge/_generated/actionSubscriptions.json +2 -2
  6. package/src/forge/_generated/actionSubscriptions.ts +3 -3
  7. package/src/forge/_generated/agentAdapterManifest.json +2 -2
  8. package/src/forge/_generated/agentAdapterManifest.ts +3 -3
  9. package/src/forge/_generated/agentContract.json +2 -2
  10. package/src/forge/_generated/agentContract.ts +183 -50
  11. package/src/forge/_generated/agentQuickstart.md +3 -1
  12. package/src/forge/_generated/agentTools.json +2 -0
  13. package/src/forge/_generated/agentTools.md +16 -0
  14. package/src/forge/_generated/agentTools.ts +12 -0
  15. package/src/forge/_generated/aiContext.ts +67 -1
  16. package/src/forge/_generated/aiModels.json +2 -2
  17. package/src/forge/_generated/aiModels.ts +17 -1
  18. package/src/forge/_generated/aiProviders.json +1 -1
  19. package/src/forge/_generated/aiProviders.ts +1 -1
  20. package/src/forge/_generated/aiRegistry.json +2 -2
  21. package/src/forge/_generated/aiRegistry.ts +7 -5
  22. package/src/forge/_generated/api.json +2 -2
  23. package/src/forge/_generated/api.ts +1 -1
  24. package/src/forge/_generated/appGraph.json +2 -2
  25. package/src/forge/_generated/appGraph.ts +512 -260
  26. package/src/forge/_generated/appMap.md +21 -1
  27. package/src/forge/_generated/artifactManifest.json +2 -2
  28. package/src/forge/_generated/artifactManifest.ts +2 -2
  29. package/src/forge/_generated/authClaims.json +1 -1
  30. package/src/forge/_generated/authClaims.ts +1 -1
  31. package/src/forge/_generated/authConfig.json +1 -1
  32. package/src/forge/_generated/authConfig.ts +1 -1
  33. package/src/forge/_generated/authContext.ts +1 -1
  34. package/src/forge/_generated/authRegistry.json +1 -1
  35. package/src/forge/_generated/authRegistry.ts +1 -1
  36. package/src/forge/_generated/buildInfo.json +2 -2
  37. package/src/forge/_generated/buildInfo.ts +4 -4
  38. package/src/forge/_generated/capabilityMap.json +2 -2
  39. package/src/forge/_generated/capabilityMap.md +1 -1
  40. package/src/forge/_generated/capabilityMap.ts +2 -2
  41. package/src/forge/_generated/client.ts +1 -1
  42. package/src/forge/_generated/clientApi.ts +1 -1
  43. package/src/forge/_generated/clientManifest.json +2 -2
  44. package/src/forge/_generated/clientManifest.ts +3 -3
  45. package/src/forge/_generated/clientTypes.ts +1 -1
  46. package/src/forge/_generated/configRegistry.json +1 -1
  47. package/src/forge/_generated/configRegistry.ts +1 -1
  48. package/src/forge/_generated/dataGraph.json +2 -2
  49. package/src/forge/_generated/dataGraph.ts +3 -3
  50. package/src/forge/_generated/db.json +1 -1
  51. package/src/forge/_generated/db.ts +1 -1
  52. package/src/forge/_generated/dbSecurityManifest.json +1 -1
  53. package/src/forge/_generated/dbSecurityManifest.ts +1 -1
  54. package/src/forge/_generated/dbSessionContext.json +1 -1
  55. package/src/forge/_generated/dbSessionContext.ts +1 -1
  56. package/src/forge/_generated/deployManifest.json +2 -2
  57. package/src/forge/_generated/deployManifest.ts +7 -7
  58. package/src/forge/_generated/devManifest.json +2 -2
  59. package/src/forge/_generated/devManifest.ts +18 -3
  60. package/src/forge/_generated/envSchema.json +1 -1
  61. package/src/forge/_generated/envSchema.ts +1 -1
  62. package/src/forge/_generated/frontendGraph.json +1 -1
  63. package/src/forge/_generated/frontendGraph.ts +1 -1
  64. package/src/forge/_generated/importGuards.json +1 -1
  65. package/src/forge/_generated/importGuards.ts +1 -1
  66. package/src/forge/_generated/index.ts +2 -1
  67. package/src/forge/_generated/liveProductionManifest.json +1 -1
  68. package/src/forge/_generated/liveProductionManifest.ts +1 -1
  69. package/src/forge/_generated/liveProtocol.json +1 -1
  70. package/src/forge/_generated/liveProtocol.ts +1 -1
  71. package/src/forge/_generated/liveQueryRegistry.json +2 -2
  72. package/src/forge/_generated/liveQueryRegistry.ts +3 -3
  73. package/src/forge/_generated/liveTransportConfig.json +1 -1
  74. package/src/forge/_generated/liveTransportConfig.ts +1 -1
  75. package/src/forge/_generated/makeRegistry.json +2 -2
  76. package/src/forge/_generated/makeRegistry.ts +16 -2
  77. package/src/forge/_generated/makeTemplates.json +2 -2
  78. package/src/forge/_generated/makeTemplates.ts +6 -1
  79. package/src/forge/_generated/mockMap.json +1 -1
  80. package/src/forge/_generated/mockMap.ts +1 -1
  81. package/src/forge/_generated/operationPlaybooks.md +34 -14
  82. package/src/forge/_generated/packageGraph.json +2 -2
  83. package/src/forge/_generated/packageGraph.ts +8808 -4723
  84. package/src/forge/_generated/packageUpgradeRegistry.json +2 -2
  85. package/src/forge/_generated/packageUpgradeRegistry.ts +2 -2
  86. package/src/forge/_generated/permissionMatrix.json +2 -2
  87. package/src/forge/_generated/permissionMatrix.ts +3 -3
  88. package/src/forge/_generated/policyRegistry.json +2 -2
  89. package/src/forge/_generated/policyRegistry.ts +3 -3
  90. package/src/forge/_generated/queryRegistry.json +2 -2
  91. package/src/forge/_generated/queryRegistry.ts +3 -3
  92. package/src/forge/_generated/react.d.ts +1 -1
  93. package/src/forge/_generated/react.ts +1 -1
  94. package/src/forge/_generated/reactManifest.json +2 -2
  95. package/src/forge/_generated/reactManifest.ts +3 -3
  96. package/src/forge/_generated/releaseManifest.json +2 -2
  97. package/src/forge/_generated/releaseManifest.ts +3 -3
  98. package/src/forge/_generated/rlsPolicies.json +1 -1
  99. package/src/forge/_generated/rlsPolicies.sql +1 -1
  100. package/src/forge/_generated/rlsPolicies.ts +1 -1
  101. package/src/forge/_generated/runtimeGraph.json +2 -2
  102. package/src/forge/_generated/runtimeGraph.ts +3 -3
  103. package/src/forge/_generated/runtimeMatrix.json +2 -2
  104. package/src/forge/_generated/runtimeMatrix.ts +8684 -1939
  105. package/src/forge/_generated/runtimeRegistry.ts +1 -1
  106. package/src/forge/_generated/runtimeRules.md +13 -1
  107. package/src/forge/_generated/secretRegistry.json +1 -1
  108. package/src/forge/_generated/secretRegistry.ts +1 -1
  109. package/src/forge/_generated/secretsContext.ts +1 -1
  110. package/src/forge/_generated/serverApi.ts +1 -1
  111. package/src/forge/_generated/sourceMapManifest.json +2 -2
  112. package/src/forge/_generated/sourceMapManifest.ts +2 -2
  113. package/src/forge/_generated/sqlPlan.json +1 -1
  114. package/src/forge/_generated/sqlPlan.ts +1 -1
  115. package/src/forge/_generated/subscriptionManifest.json +2 -2
  116. package/src/forge/_generated/subscriptionManifest.ts +3 -3
  117. package/src/forge/_generated/symbolicationManifest.json +2 -2
  118. package/src/forge/_generated/symbolicationManifest.ts +2 -2
  119. package/src/forge/_generated/telemetryRegistry.json +2 -2
  120. package/src/forge/_generated/telemetryRegistry.ts +3 -3
  121. package/src/forge/_generated/telemetrySinks.json +2 -2
  122. package/src/forge/_generated/telemetrySinks.ts +2 -2
  123. package/src/forge/_generated/tenantScope.json +2 -2
  124. package/src/forge/_generated/tenantScope.ts +3 -3
  125. package/src/forge/_generated/testGraph.json +2 -2
  126. package/src/forge/_generated/testGraph.ts +339 -17
  127. package/src/forge/_generated/testPlanRegistry.json +2 -2
  128. package/src/forge/_generated/testPlanRegistry.ts +2 -2
  129. package/src/forge/_generated/uiRoutes.json +1 -1
  130. package/src/forge/_generated/uiRoutes.ts +1 -1
  131. package/src/forge/_generated/uiScenarios.json +1 -1
  132. package/src/forge/_generated/uiScenarios.ts +1 -1
  133. package/src/forge/_generated/uiTestManifest.json +2 -2
  134. package/src/forge/_generated/uiTestManifest.ts +2 -2
  135. package/src/forge/_generated/workflowRegistry.json +2 -2
  136. package/src/forge/_generated/workflowRegistry.ts +3 -3
  137. package/src/forge/_generated/workflowSubscriptions.json +2 -2
  138. package/src/forge/_generated/workflowSubscriptions.ts +3 -3
  139. package/src/forge/cli/ai.ts +351 -1
  140. package/src/forge/cli/auth.ts +36 -1
  141. package/src/forge/cli/commands.ts +19 -0
  142. package/src/forge/cli/parse.ts +67 -8
  143. package/src/forge/cli/rls.ts +529 -17
  144. package/src/forge/cli/secrets.ts +46 -1
  145. package/src/forge/cli/security.ts +269 -0
  146. package/src/forge/compiler/agent-contract/build.ts +289 -8
  147. package/src/forge/compiler/agent-contract/types.ts +43 -0
  148. package/src/forge/compiler/ai-registry/build.ts +62 -1
  149. package/src/forge/compiler/ai-registry/constants.ts +1 -1
  150. package/src/forge/compiler/ai-registry/parse.ts +98 -4
  151. package/src/forge/compiler/app-graph/forge-apis.ts +1 -0
  152. package/src/forge/compiler/dev-manifest/build.ts +3 -0
  153. package/src/forge/compiler/diagnostics/codes.ts +15 -0
  154. package/src/forge/compiler/diagnostics/create.ts +1 -1
  155. package/src/forge/compiler/make-registry/build.ts +13 -0
  156. package/src/forge/compiler/orchestrator/plan.ts +11 -0
  157. package/src/forge/compiler/orchestrator/serialize.ts +68 -0
  158. package/src/forge/compiler/package-graph/compiler.ts +13 -3
  159. package/src/forge/compiler/types/ai-registry.ts +25 -1
  160. package/src/forge/compiler/types/app-graph.ts +1 -0
  161. package/src/forge/compiler/types/cli.ts +1 -0
  162. package/src/forge/compiler/types/dev-manifest.ts +3 -0
  163. package/src/forge/dev/server.ts +508 -1
  164. package/src/forge/make/index.ts +126 -3
  165. package/src/forge/make/templates.ts +188 -0
  166. package/src/forge/make/types.ts +1 -0
  167. package/src/forge/runtime/ai/context.ts +210 -5
  168. package/src/forge/runtime/ai/types.ts +70 -0
  169. package/src/forge/runtime/auth/claims.ts +32 -0
  170. package/src/forge/runtime/auth/errors.ts +2 -0
  171. package/src/forge/runtime/context/create-context.ts +30 -6
  172. package/src/forge/runtime/db/memory-adapter.ts +2 -2
  173. package/src/forge/runtime/telemetry/scrubber.ts +56 -5
  174. package/src/forge/runtime/webhooks/security.ts +184 -0
  175. package/src/forge/server.ts +93 -0
  176. package/src/forge/version.ts +1 -1
  177. package/templates/b2b-support-web/package.json +1 -0
  178. package/templates/b2b-support-web/tsconfig.json +4 -1
  179. package/templates/minimal-web/package.json +1 -0
  180. package/templates/minimal-web/tsconfig.json +3 -1
@@ -6,6 +6,7 @@ import {
6
6
  type ServerResponse,
7
7
  } from "node:http";
8
8
  import { join } from "node:path";
9
+ import { pathToFileURL } from "node:url";
9
10
  import { createDiagnostic } from "../compiler/diagnostics/create.ts";
10
11
  import {
11
12
  FORGE_AUTH_DEV_HEADERS_IN_PRODUCTION,
@@ -59,9 +60,12 @@ import {
59
60
  import { checkAiProviders, loadAiRegistry } from "../runtime/ai/check.ts";
60
61
  import { isMockAiEnabled } from "../runtime/ai/state.ts";
61
62
  import { createAiContext } from "../runtime/ai/context.ts";
63
+ import type { ForgeAiProvider, ForgeAiToolDefinition } from "../runtime/ai/types.ts";
64
+ import { resolveLanguageModel } from "../runtime/ai/providers.ts";
62
65
  import { createRuntimeSecretsBundle } from "../runtime/secrets/runtime-bundle.ts";
63
- import { createNoopTelemetryContext } from "../runtime/telemetry/context.ts";
66
+ import { createNoopTelemetryContext, createTelemetryContext } from "../runtime/telemetry/context.ts";
64
67
  import { generateTraceId } from "../runtime/telemetry/correlation.ts";
68
+ import type { AgentToolRegistry } from "../compiler/agent-contract/types.ts";
65
69
  import { loadLiveQueryRegistry } from "../runtime/live/registry.ts";
66
70
  import { createLiveSubscriptionManager } from "../runtime/live/subscription-manager.ts";
67
71
  import { createSseResponse } from "../runtime/live/sse.ts";
@@ -402,6 +406,233 @@ function parseInvokeName(pathname: string, prefix: string): string | null {
402
406
  return name.length > 0 ? decodeURIComponent(name) : null;
403
407
  }
404
408
 
409
+ function loadAgentToolRegistry(workspaceRoot: string): AgentToolRegistry | null {
410
+ return readGeneratedJson<AgentToolRegistry>(
411
+ workspaceRoot,
412
+ `${GENERATED_DIR}/agentTools.json`,
413
+ );
414
+ }
415
+
416
+ type RuntimeAgentDefinition = {
417
+ provider?: ForgeAiProvider;
418
+ model: string;
419
+ instructions: string;
420
+ tools?: Record<string, ForgeAiToolDefinition> | string[];
421
+ stopWhen?: { kind: "stepCount"; maxSteps: number } | { kind: "toolCall"; toolName: string };
422
+ maxSteps?: number;
423
+ };
424
+
425
+ async function loadNamedAgentDefinition(
426
+ workspaceRoot: string,
427
+ name: string | undefined,
428
+ ): Promise<RuntimeAgentDefinition | null> {
429
+ if (!name) {
430
+ return null;
431
+ }
432
+ const registry = loadAiRegistry(workspaceRoot);
433
+ const metadata = registry?.agents.find((agent) => agent.name === name);
434
+ if (!metadata) {
435
+ throw new Error(`unknown Forge AI agent '${name}'`);
436
+ }
437
+
438
+ const mod = (await import(pathToFileURL(join(workspaceRoot, metadata.file)).href)) as Record<
439
+ string,
440
+ unknown
441
+ >;
442
+ const definition = mod[metadata.name];
443
+ if (!definition || typeof definition !== "object") {
444
+ throw new Error(`Forge AI agent '${name}' was not exported from ${metadata.file}`);
445
+ }
446
+
447
+ const agent = definition as Partial<RuntimeAgentDefinition>;
448
+ if (!agent.model || !agent.instructions) {
449
+ throw new Error(`Forge AI agent '${name}' is missing model or instructions`);
450
+ }
451
+ return {
452
+ provider: agent.provider ?? metadata.provider,
453
+ model: agent.model,
454
+ instructions: agent.instructions,
455
+ tools: agent.tools,
456
+ stopWhen:
457
+ agent.stopWhen ??
458
+ (metadata.stopWhen.kind === "default" ? undefined : metadata.stopWhen),
459
+ maxSteps: agent.maxSteps,
460
+ };
461
+ }
462
+
463
+ function explicitAgentTools(
464
+ definition: RuntimeAgentDefinition | null,
465
+ ): Record<string, ForgeAiToolDefinition> {
466
+ if (!definition?.tools || Array.isArray(definition.tools)) {
467
+ return {};
468
+ }
469
+ return Object.fromEntries(
470
+ Object.entries(definition.tools).filter(([, toolDefinition]) =>
471
+ Boolean(
472
+ toolDefinition &&
473
+ typeof toolDefinition === "object" &&
474
+ "handler" in toolDefinition &&
475
+ "inputSchema" in toolDefinition,
476
+ ),
477
+ ),
478
+ );
479
+ }
480
+
481
+ function buildAutoAgentTools(input: {
482
+ workspaceRoot: string;
483
+ registry: AgentToolRegistry | null;
484
+ selected?: string[];
485
+ adapter: DevServerState["adapter"];
486
+ tableMap: Record<string, TableMapEntry>;
487
+ auth: Awaited<ReturnType<typeof authenticateHeaders>>;
488
+ liveManager?: ReturnType<typeof createLiveSubscriptionManager> | null;
489
+ json: boolean;
490
+ mock: boolean;
491
+ }): Record<string, ForgeAiToolDefinition> {
492
+ const selected = new Set(input.selected ?? []);
493
+ const includeAll = selected.size === 0;
494
+ const tools: Record<string, ForgeAiToolDefinition> = {};
495
+ for (const toolInfo of input.registry?.autoTools ?? []) {
496
+ if (!includeAll && !selected.has(toolInfo.name) && !selected.has(toolInfo.sourceName)) {
497
+ continue;
498
+ }
499
+ tools[toolInfo.name] = {
500
+ description: `${toolInfo.readOnly ? "Read" : "Execute"} Forge ${toolInfo.sourceKind} '${toolInfo.sourceName}'. Policy: ${toolInfo.policy ?? "none"}.`,
501
+ inputSchema: {
502
+ type: "object",
503
+ properties: {
504
+ args: {
505
+ type: "object",
506
+ additionalProperties: true,
507
+ },
508
+ },
509
+ additionalProperties: true,
510
+ },
511
+ risk: toolInfo.readOnly ? "read" : "write",
512
+ needsApproval: !toolInfo.readOnly,
513
+ handler: async (_ctx, rawInput) => {
514
+ const objectInput =
515
+ rawInput && typeof rawInput === "object" && !Array.isArray(rawInput)
516
+ ? rawInput as { args?: unknown }
517
+ : {};
518
+ const args = objectInput.args ?? rawInput ?? {};
519
+ if (!input.adapter) {
520
+ throw new Error("database not connected");
521
+ }
522
+ if (toolInfo.sourceKind === "query" || toolInfo.sourceKind === "liveQuery") {
523
+ const result = await runQuery(
524
+ input.workspaceRoot,
525
+ toolInfo.sourceName,
526
+ { args, auth: input.auth },
527
+ {
528
+ adapter: input.adapter,
529
+ tableMap: input.tableMap,
530
+ },
531
+ );
532
+ if (!result.ok) {
533
+ throw new Error(result.diagnostics.map((diagnostic) => diagnostic.message).join("; "));
534
+ }
535
+ return { result: result.result, traceId: result.traceId };
536
+ }
537
+ const result = await runEntry(input.workspaceRoot, toolInfo.sourceName, {
538
+ json: input.json,
539
+ mock: input.mock,
540
+ args,
541
+ db: input.adapter,
542
+ auth: input.auth,
543
+ liveManager: input.liveManager ?? undefined,
544
+ });
545
+ if (!result.ok) {
546
+ throw new Error(result.diagnostics.map((diagnostic) => diagnostic.message).join("; "));
547
+ }
548
+ return { result: result.result, traceId: result.traceId };
549
+ },
550
+ };
551
+ }
552
+ return tools;
553
+ }
554
+
555
+ async function buildAiSdkTools(input: {
556
+ tools: Record<string, ForgeAiToolDefinition>;
557
+ provider: ForgeAiProvider;
558
+ model: string;
559
+ purpose?: string;
560
+ traceId: string;
561
+ secrets: ReturnType<typeof createRuntimeSecretsBundle>["secrets"];
562
+ telemetry: ReturnType<typeof createTelemetryContext> | ReturnType<typeof createNoopTelemetryContext>;
563
+ env: Record<string, string | undefined>;
564
+ auth: Awaited<ReturnType<typeof authenticateHeaders>>;
565
+ }): Promise<Record<string, unknown>> {
566
+ const { tool } = await import("ai");
567
+ return Object.fromEntries(
568
+ Object.entries(input.tools).map(([name, definition]) => {
569
+ const needsApproval = definition.needsApproval;
570
+ return [
571
+ name,
572
+ tool({
573
+ description: definition.description,
574
+ inputSchema: definition.inputSchema as never,
575
+ ...(definition.outputSchema ? { outputSchema: definition.outputSchema as never } : {}),
576
+ ...(definition.strict !== undefined ? { strict: definition.strict } : {}),
577
+ ...(needsApproval !== undefined
578
+ ? {
579
+ needsApproval:
580
+ typeof needsApproval === "function"
581
+ ? async ({ args }: { args: unknown }) =>
582
+ Boolean(await needsApproval(args as never))
583
+ : needsApproval,
584
+ }
585
+ : {}),
586
+ execute: async (args: unknown) => {
587
+ const startedAt = Date.now();
588
+ await input.telemetry.capture("forge.ai.tool.started", {
589
+ traceId: input.traceId,
590
+ provider: input.provider,
591
+ model: input.model,
592
+ purpose: input.purpose,
593
+ tool: name,
594
+ risk: definition.risk ?? "external",
595
+ });
596
+ try {
597
+ const output = await definition.handler(
598
+ {
599
+ secrets: input.secrets,
600
+ env: input.env,
601
+ telemetry: input.telemetry,
602
+ auth: input.auth,
603
+ },
604
+ args as never,
605
+ );
606
+ await input.telemetry.capture("forge.ai.tool.completed", {
607
+ traceId: input.traceId,
608
+ provider: input.provider,
609
+ model: input.model,
610
+ purpose: input.purpose,
611
+ tool: name,
612
+ latencyMs: Date.now() - startedAt,
613
+ status: "completed",
614
+ });
615
+ return output;
616
+ } catch (error) {
617
+ await input.telemetry.capture("forge.ai.tool.failed", {
618
+ traceId: input.traceId,
619
+ provider: input.provider,
620
+ model: input.model,
621
+ purpose: input.purpose,
622
+ tool: name,
623
+ latencyMs: Date.now() - startedAt,
624
+ status: "failed",
625
+ error: error instanceof Error ? error.message : String(error),
626
+ });
627
+ throw error;
628
+ }
629
+ },
630
+ } as never),
631
+ ];
632
+ }),
633
+ );
634
+ }
635
+
405
636
  async function parseRequestArgs(request: Request): Promise<unknown> {
406
637
  const contentType = request.headers.get("content-type") ?? "";
407
638
  if (!contentType.includes("application/json")) {
@@ -877,6 +1108,282 @@ export async function startDevServer(
877
1108
  }
878
1109
  }
879
1110
 
1111
+ if (request.method === "POST" && pathname === "/ai/agents/run") {
1112
+ const body = (await request.json().catch(() => ({}))) as {
1113
+ agent?: string;
1114
+ provider?: string;
1115
+ model?: string;
1116
+ instructions?: string;
1117
+ prompt?: string;
1118
+ purpose?: string;
1119
+ maxSteps?: number;
1120
+ tools?: string[];
1121
+ };
1122
+ if (!body.prompt) {
1123
+ return jsonResponse(
1124
+ {
1125
+ ok: false,
1126
+ diagnostics: [
1127
+ createDiagnostic({
1128
+ severity: "error",
1129
+ code: FORGE_DEV_INVOKE_FAILED,
1130
+ message: "POST /ai/agents/run requires a prompt",
1131
+ fixHint: "Send JSON like {\"prompt\":\"...\",\"instructions\":\"...\",\"tools\":[\"forge_query_listTickets\"]}.",
1132
+ }),
1133
+ ],
1134
+ },
1135
+ 400,
1136
+ );
1137
+ }
1138
+ const auth = await authenticateHeaders(request.headers, authConfig);
1139
+ const envStore = getRuntimeEnvStore(workspaceRoot);
1140
+ const secretRegistry = loadSecretRegistry(workspaceRoot);
1141
+ const bundle = createRuntimeSecretsBundle({
1142
+ store: envStore,
1143
+ registry: secretRegistry,
1144
+ envSchema: null,
1145
+ runtimeKind: "server",
1146
+ });
1147
+ const traceId = generateTraceId();
1148
+ const telemetry = serverState.adapter
1149
+ ? createTelemetryContext({
1150
+ adapter: serverState.adapter,
1151
+ traceId,
1152
+ runtime: { kind: "endpoint", name: "ai.agent.run" },
1153
+ bufferInTransaction: false,
1154
+ workspaceRoot,
1155
+ sinks: telemetrySinks,
1156
+ })
1157
+ : createNoopTelemetryContext(traceId);
1158
+ const ai = createAiContext({
1159
+ secrets: bundle.secrets,
1160
+ telemetry,
1161
+ runtimeKind: "endpoint",
1162
+ mockAi: isMockAiEnabled({ mockAi: options.mockAi }),
1163
+ envelope: { traceId, tenantId: auth.kind === "user" || auth.kind === "system" ? auth.tenantId : undefined },
1164
+ toolContext: {
1165
+ env: envStore.snapshot(),
1166
+ auth,
1167
+ },
1168
+ });
1169
+ try {
1170
+ const namedAgent = await loadNamedAgentDefinition(workspaceRoot, body.agent);
1171
+ const tools = {
1172
+ ...explicitAgentTools(namedAgent),
1173
+ ...buildAutoAgentTools({
1174
+ workspaceRoot,
1175
+ registry: loadAgentToolRegistry(workspaceRoot),
1176
+ selected: body.tools,
1177
+ adapter: serverState.adapter,
1178
+ tableMap,
1179
+ auth,
1180
+ liveManager,
1181
+ json: options.json,
1182
+ mock: options.mock,
1183
+ }),
1184
+ };
1185
+ const result = await ai.runAgent({
1186
+ provider: (body.provider ?? namedAgent?.provider ?? "gateway") as ForgeAiProvider,
1187
+ model: body.model ?? namedAgent?.model ?? "openai/gpt-5.4",
1188
+ instructions:
1189
+ body.instructions ??
1190
+ namedAgent?.instructions ??
1191
+ "You are a ForgeOS app agent. Use available Forge tools when useful.",
1192
+ prompt: body.prompt,
1193
+ purpose: body.purpose ?? "dev_agent_run",
1194
+ stopWhen: namedAgent?.stopWhen,
1195
+ maxSteps: body.maxSteps ?? namedAgent?.maxSteps,
1196
+ tools,
1197
+ });
1198
+ return jsonResponse(
1199
+ {
1200
+ ok: true,
1201
+ result,
1202
+ traceId,
1203
+ tools: Object.keys(tools).sort(),
1204
+ },
1205
+ 200,
1206
+ { "x-forge-trace-id": traceId },
1207
+ );
1208
+ } catch (error) {
1209
+ const message = error instanceof Error ? error.message : String(error);
1210
+ return jsonResponse(
1211
+ {
1212
+ ok: false,
1213
+ traceId,
1214
+ diagnostics: [
1215
+ createDiagnostic({
1216
+ severity: "error",
1217
+ code: FORGE_DEV_INVOKE_FAILED,
1218
+ message,
1219
+ fixHint: `Inspect with forge ai trace ${traceId} --json or GET /telemetry/traces/${traceId}.`,
1220
+ }),
1221
+ ],
1222
+ },
1223
+ 400,
1224
+ { "x-forge-trace-id": traceId },
1225
+ );
1226
+ }
1227
+ }
1228
+
1229
+ if (request.method === "POST" && pathname === "/ai/agents/chat") {
1230
+ const body = (await request.json().catch(() => ({}))) as {
1231
+ agent?: string;
1232
+ provider?: string;
1233
+ model?: string;
1234
+ instructions?: string;
1235
+ purpose?: string;
1236
+ maxSteps?: number;
1237
+ tools?: string[];
1238
+ messages?: unknown[];
1239
+ };
1240
+ if (!Array.isArray(body.messages)) {
1241
+ return jsonResponse(
1242
+ {
1243
+ ok: false,
1244
+ diagnostics: [
1245
+ createDiagnostic({
1246
+ severity: "error",
1247
+ code: FORGE_DEV_INVOKE_FAILED,
1248
+ message: "POST /ai/agents/chat requires AI SDK UI messages",
1249
+ fixHint: "Use @ai-sdk/react useChat with DefaultChatTransport({ api: `${forgeUrl}/ai/agents/chat` }).",
1250
+ }),
1251
+ ],
1252
+ },
1253
+ 400,
1254
+ );
1255
+ }
1256
+
1257
+ const purpose = body.purpose ?? "dev_agent_chat";
1258
+ const auth = await authenticateHeaders(request.headers, authConfig);
1259
+ const envStore = getRuntimeEnvStore(workspaceRoot);
1260
+ const secretRegistry = loadSecretRegistry(workspaceRoot);
1261
+ const bundle = createRuntimeSecretsBundle({
1262
+ store: envStore,
1263
+ registry: secretRegistry,
1264
+ envSchema: null,
1265
+ runtimeKind: "server",
1266
+ });
1267
+ const traceId = generateTraceId();
1268
+ const telemetry = serverState.adapter
1269
+ ? createTelemetryContext({
1270
+ adapter: serverState.adapter,
1271
+ traceId,
1272
+ runtime: { kind: "endpoint", name: "ai.agent.chat" },
1273
+ bufferInTransaction: false,
1274
+ workspaceRoot,
1275
+ sinks: telemetrySinks,
1276
+ })
1277
+ : createNoopTelemetryContext(traceId);
1278
+ let provider = (body.provider ?? "gateway") as ForgeAiProvider;
1279
+ let model = body.model ?? "openai/gpt-5.4";
1280
+
1281
+ try {
1282
+ const namedAgent = await loadNamedAgentDefinition(workspaceRoot, body.agent);
1283
+ provider = (body.provider ?? namedAgent?.provider ?? "gateway") as ForgeAiProvider;
1284
+ model = body.model ?? namedAgent?.model ?? "openai/gpt-5.4";
1285
+ await telemetry.capture("forge.ai.agent.started", {
1286
+ traceId,
1287
+ provider,
1288
+ model,
1289
+ purpose,
1290
+ mode: "stream",
1291
+ });
1292
+ const languageModel = await resolveLanguageModel(provider, model, bundle.secrets);
1293
+ const forgeTools = {
1294
+ ...explicitAgentTools(namedAgent),
1295
+ ...buildAutoAgentTools({
1296
+ workspaceRoot,
1297
+ registry: loadAgentToolRegistry(workspaceRoot),
1298
+ selected: body.tools,
1299
+ adapter: serverState.adapter,
1300
+ tableMap,
1301
+ auth,
1302
+ liveManager,
1303
+ json: options.json,
1304
+ mock: options.mock,
1305
+ }),
1306
+ };
1307
+ const tools = await buildAiSdkTools({
1308
+ tools: forgeTools,
1309
+ provider,
1310
+ model,
1311
+ purpose,
1312
+ traceId,
1313
+ secrets: bundle.secrets,
1314
+ telemetry,
1315
+ env: envStore.snapshot(),
1316
+ auth,
1317
+ });
1318
+ const { ToolLoopAgent, createAgentUIStreamResponse, hasToolCall, stepCountIs } = await import("ai");
1319
+ const stopWhen =
1320
+ namedAgent?.stopWhen?.kind === "toolCall"
1321
+ ? (hasToolCall(namedAgent.stopWhen.toolName) as never)
1322
+ : namedAgent?.stopWhen?.kind === "stepCount"
1323
+ ? (stepCountIs(body.maxSteps ?? namedAgent.stopWhen.maxSteps) as never)
1324
+ : (stepCountIs(body.maxSteps ?? namedAgent?.maxSteps ?? 20) as never);
1325
+ const agent = new ToolLoopAgent({
1326
+ model: languageModel as never,
1327
+ instructions:
1328
+ body.instructions ??
1329
+ namedAgent?.instructions ??
1330
+ "You are a ForgeOS app agent. Use available Forge tools when useful.",
1331
+ tools: tools as never,
1332
+ stopWhen,
1333
+ });
1334
+ const response = await createAgentUIStreamResponse({
1335
+ agent: agent as never,
1336
+ uiMessages: body.messages,
1337
+ onStepFinish: async () => {
1338
+ await telemetry.capture("forge.ai.agent.step.completed", {
1339
+ traceId,
1340
+ provider,
1341
+ model,
1342
+ purpose,
1343
+ });
1344
+ },
1345
+ headers: {
1346
+ "Access-Control-Allow-Origin": "*",
1347
+ "x-forge-trace-id": traceId,
1348
+ },
1349
+ });
1350
+ await telemetry.capture("forge.ai.agent.completed", {
1351
+ traceId,
1352
+ provider,
1353
+ model,
1354
+ purpose,
1355
+ status: "streaming",
1356
+ });
1357
+ return response;
1358
+ } catch (error) {
1359
+ const message = error instanceof Error ? error.message : String(error);
1360
+ await telemetry.capture("forge.ai.agent.failed", {
1361
+ traceId,
1362
+ provider,
1363
+ model,
1364
+ purpose,
1365
+ status: "failed",
1366
+ error: message,
1367
+ });
1368
+ return jsonResponse(
1369
+ {
1370
+ ok: false,
1371
+ traceId,
1372
+ diagnostics: [
1373
+ createDiagnostic({
1374
+ severity: "error",
1375
+ code: FORGE_DEV_INVOKE_FAILED,
1376
+ message,
1377
+ fixHint: `Inspect with forge ai trace ${traceId} --json or GET /telemetry/traces/${traceId}.`,
1378
+ }),
1379
+ ],
1380
+ },
1381
+ 400,
1382
+ { "x-forge-trace-id": traceId },
1383
+ );
1384
+ }
1385
+ }
1386
+
880
1387
  if (request.method === "GET" && pathname === "/telemetry") {
881
1388
  if (!serverState.adapter) {
882
1389
  return jsonResponse({