forgeos 0.1.0-alpha.2 → 0.1.0-alpha.3

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 (163) hide show
  1. package/AGENTS.md +38 -3
  2. package/README.md +6 -5
  3. package/package.json +5 -4
  4. package/src/forge/_generated/actionSubscriptions.json +2 -2
  5. package/src/forge/_generated/actionSubscriptions.ts +3 -3
  6. package/src/forge/_generated/agentAdapterManifest.json +2 -2
  7. package/src/forge/_generated/agentAdapterManifest.ts +3 -3
  8. package/src/forge/_generated/agentContract.json +2 -2
  9. package/src/forge/_generated/agentContract.ts +183 -50
  10. package/src/forge/_generated/agentQuickstart.md +3 -1
  11. package/src/forge/_generated/agentTools.json +2 -0
  12. package/src/forge/_generated/agentTools.md +16 -0
  13. package/src/forge/_generated/agentTools.ts +12 -0
  14. package/src/forge/_generated/aiContext.ts +67 -1
  15. package/src/forge/_generated/aiModels.json +2 -2
  16. package/src/forge/_generated/aiModels.ts +17 -1
  17. package/src/forge/_generated/aiProviders.json +1 -1
  18. package/src/forge/_generated/aiProviders.ts +1 -1
  19. package/src/forge/_generated/aiRegistry.json +2 -2
  20. package/src/forge/_generated/aiRegistry.ts +7 -5
  21. package/src/forge/_generated/api.json +2 -2
  22. package/src/forge/_generated/api.ts +1 -1
  23. package/src/forge/_generated/appGraph.json +2 -2
  24. package/src/forge/_generated/appGraph.ts +288 -180
  25. package/src/forge/_generated/appMap.md +21 -1
  26. package/src/forge/_generated/artifactManifest.json +2 -2
  27. package/src/forge/_generated/artifactManifest.ts +2 -2
  28. package/src/forge/_generated/authClaims.json +1 -1
  29. package/src/forge/_generated/authClaims.ts +1 -1
  30. package/src/forge/_generated/authConfig.json +1 -1
  31. package/src/forge/_generated/authConfig.ts +1 -1
  32. package/src/forge/_generated/authContext.ts +1 -1
  33. package/src/forge/_generated/authRegistry.json +1 -1
  34. package/src/forge/_generated/authRegistry.ts +1 -1
  35. package/src/forge/_generated/buildInfo.json +2 -2
  36. package/src/forge/_generated/buildInfo.ts +4 -4
  37. package/src/forge/_generated/capabilityMap.json +2 -2
  38. package/src/forge/_generated/capabilityMap.md +1 -1
  39. package/src/forge/_generated/capabilityMap.ts +2 -2
  40. package/src/forge/_generated/client.ts +1 -1
  41. package/src/forge/_generated/clientApi.ts +1 -1
  42. package/src/forge/_generated/clientManifest.json +2 -2
  43. package/src/forge/_generated/clientManifest.ts +3 -3
  44. package/src/forge/_generated/clientTypes.ts +1 -1
  45. package/src/forge/_generated/configRegistry.json +1 -1
  46. package/src/forge/_generated/configRegistry.ts +1 -1
  47. package/src/forge/_generated/dataGraph.json +2 -2
  48. package/src/forge/_generated/dataGraph.ts +3 -3
  49. package/src/forge/_generated/db.json +1 -1
  50. package/src/forge/_generated/db.ts +1 -1
  51. package/src/forge/_generated/dbSecurityManifest.json +1 -1
  52. package/src/forge/_generated/dbSecurityManifest.ts +1 -1
  53. package/src/forge/_generated/dbSessionContext.json +1 -1
  54. package/src/forge/_generated/dbSessionContext.ts +1 -1
  55. package/src/forge/_generated/deployManifest.json +2 -2
  56. package/src/forge/_generated/deployManifest.ts +7 -7
  57. package/src/forge/_generated/devManifest.json +2 -2
  58. package/src/forge/_generated/devManifest.ts +18 -3
  59. package/src/forge/_generated/envSchema.json +1 -1
  60. package/src/forge/_generated/envSchema.ts +1 -1
  61. package/src/forge/_generated/frontendGraph.json +1 -1
  62. package/src/forge/_generated/frontendGraph.ts +1 -1
  63. package/src/forge/_generated/importGuards.json +1 -1
  64. package/src/forge/_generated/importGuards.ts +1 -1
  65. package/src/forge/_generated/index.ts +2 -1
  66. package/src/forge/_generated/liveProductionManifest.json +1 -1
  67. package/src/forge/_generated/liveProductionManifest.ts +1 -1
  68. package/src/forge/_generated/liveProtocol.json +1 -1
  69. package/src/forge/_generated/liveProtocol.ts +1 -1
  70. package/src/forge/_generated/liveQueryRegistry.json +2 -2
  71. package/src/forge/_generated/liveQueryRegistry.ts +3 -3
  72. package/src/forge/_generated/liveTransportConfig.json +1 -1
  73. package/src/forge/_generated/liveTransportConfig.ts +1 -1
  74. package/src/forge/_generated/makeRegistry.json +2 -2
  75. package/src/forge/_generated/makeRegistry.ts +16 -2
  76. package/src/forge/_generated/makeTemplates.json +2 -2
  77. package/src/forge/_generated/makeTemplates.ts +6 -1
  78. package/src/forge/_generated/mockMap.json +1 -1
  79. package/src/forge/_generated/mockMap.ts +1 -1
  80. package/src/forge/_generated/operationPlaybooks.md +34 -14
  81. package/src/forge/_generated/packageGraph.json +2 -2
  82. package/src/forge/_generated/packageGraph.ts +8808 -4723
  83. package/src/forge/_generated/packageUpgradeRegistry.json +2 -2
  84. package/src/forge/_generated/packageUpgradeRegistry.ts +2 -2
  85. package/src/forge/_generated/permissionMatrix.json +2 -2
  86. package/src/forge/_generated/permissionMatrix.ts +3 -3
  87. package/src/forge/_generated/policyRegistry.json +2 -2
  88. package/src/forge/_generated/policyRegistry.ts +3 -3
  89. package/src/forge/_generated/queryRegistry.json +2 -2
  90. package/src/forge/_generated/queryRegistry.ts +3 -3
  91. package/src/forge/_generated/react.d.ts +1 -1
  92. package/src/forge/_generated/react.ts +1 -1
  93. package/src/forge/_generated/reactManifest.json +2 -2
  94. package/src/forge/_generated/reactManifest.ts +3 -3
  95. package/src/forge/_generated/releaseManifest.json +2 -2
  96. package/src/forge/_generated/releaseManifest.ts +3 -3
  97. package/src/forge/_generated/rlsPolicies.json +1 -1
  98. package/src/forge/_generated/rlsPolicies.sql +1 -1
  99. package/src/forge/_generated/rlsPolicies.ts +1 -1
  100. package/src/forge/_generated/runtimeGraph.json +2 -2
  101. package/src/forge/_generated/runtimeGraph.ts +3 -3
  102. package/src/forge/_generated/runtimeMatrix.json +2 -2
  103. package/src/forge/_generated/runtimeMatrix.ts +8684 -1939
  104. package/src/forge/_generated/runtimeRegistry.ts +1 -1
  105. package/src/forge/_generated/runtimeRules.md +13 -1
  106. package/src/forge/_generated/secretRegistry.json +1 -1
  107. package/src/forge/_generated/secretRegistry.ts +1 -1
  108. package/src/forge/_generated/secretsContext.ts +1 -1
  109. package/src/forge/_generated/serverApi.ts +1 -1
  110. package/src/forge/_generated/sourceMapManifest.json +2 -2
  111. package/src/forge/_generated/sourceMapManifest.ts +2 -2
  112. package/src/forge/_generated/sqlPlan.json +1 -1
  113. package/src/forge/_generated/sqlPlan.ts +1 -1
  114. package/src/forge/_generated/subscriptionManifest.json +2 -2
  115. package/src/forge/_generated/subscriptionManifest.ts +3 -3
  116. package/src/forge/_generated/symbolicationManifest.json +2 -2
  117. package/src/forge/_generated/symbolicationManifest.ts +2 -2
  118. package/src/forge/_generated/telemetryRegistry.json +2 -2
  119. package/src/forge/_generated/telemetryRegistry.ts +3 -3
  120. package/src/forge/_generated/telemetrySinks.json +2 -2
  121. package/src/forge/_generated/telemetrySinks.ts +2 -2
  122. package/src/forge/_generated/tenantScope.json +2 -2
  123. package/src/forge/_generated/tenantScope.ts +3 -3
  124. package/src/forge/_generated/testGraph.json +2 -2
  125. package/src/forge/_generated/testGraph.ts +17 -7
  126. package/src/forge/_generated/testPlanRegistry.json +2 -2
  127. package/src/forge/_generated/testPlanRegistry.ts +2 -2
  128. package/src/forge/_generated/uiRoutes.json +1 -1
  129. package/src/forge/_generated/uiRoutes.ts +1 -1
  130. package/src/forge/_generated/uiScenarios.json +1 -1
  131. package/src/forge/_generated/uiScenarios.ts +1 -1
  132. package/src/forge/_generated/uiTestManifest.json +2 -2
  133. package/src/forge/_generated/uiTestManifest.ts +2 -2
  134. package/src/forge/_generated/workflowRegistry.json +2 -2
  135. package/src/forge/_generated/workflowRegistry.ts +3 -3
  136. package/src/forge/_generated/workflowSubscriptions.json +2 -2
  137. package/src/forge/_generated/workflowSubscriptions.ts +3 -3
  138. package/src/forge/cli/ai.ts +186 -1
  139. package/src/forge/cli/commands.ts +5 -0
  140. package/src/forge/cli/parse.ts +30 -3
  141. package/src/forge/compiler/agent-contract/build.ts +281 -8
  142. package/src/forge/compiler/agent-contract/types.ts +41 -0
  143. package/src/forge/compiler/ai-registry/build.ts +62 -1
  144. package/src/forge/compiler/ai-registry/constants.ts +1 -1
  145. package/src/forge/compiler/ai-registry/parse.ts +98 -4
  146. package/src/forge/compiler/app-graph/forge-apis.ts +1 -0
  147. package/src/forge/compiler/dev-manifest/build.ts +3 -0
  148. package/src/forge/compiler/make-registry/build.ts +13 -0
  149. package/src/forge/compiler/orchestrator/plan.ts +11 -0
  150. package/src/forge/compiler/orchestrator/serialize.ts +68 -0
  151. package/src/forge/compiler/types/ai-registry.ts +25 -1
  152. package/src/forge/compiler/types/app-graph.ts +1 -0
  153. package/src/forge/compiler/types/cli.ts +1 -0
  154. package/src/forge/compiler/types/dev-manifest.ts +3 -0
  155. package/src/forge/dev/server.ts +508 -1
  156. package/src/forge/make/index.ts +126 -3
  157. package/src/forge/make/templates.ts +188 -0
  158. package/src/forge/make/types.ts +1 -0
  159. package/src/forge/runtime/ai/context.ts +210 -5
  160. package/src/forge/runtime/ai/types.ts +70 -0
  161. package/src/forge/runtime/context/create-context.ts +30 -6
  162. package/src/forge/server.ts +82 -0
  163. package/src/forge/version.ts +1 -1
@@ -46,6 +46,7 @@ import type {
46
46
  AgentHttpEndpointInfo,
47
47
  AgentIntegrationInfo,
48
48
  AgentRuntimeRule,
49
+ AgentToolRegistry,
49
50
  AgentPlaybook,
50
51
  } from "./types.ts";
51
52
 
@@ -78,9 +79,11 @@ export interface AgentContractInput {
78
79
  export interface AgentContractArtifacts {
79
80
  contract: AgentContract;
80
81
  capabilityMap: AgentCapabilityMap;
82
+ toolRegistry: AgentToolRegistry;
81
83
  agentsMd: string;
82
84
  appMapMd: string;
83
85
  capabilityMapMd: string;
86
+ agentToolsMd: string;
84
87
  runtimeRulesMd: string;
85
88
  operationPlaybooksMd: string;
86
89
  agentQuickstartMd: string;
@@ -245,26 +248,26 @@ function runtimeRules(): AgentRuntimeRule[] {
245
248
  {
246
249
  context: "command",
247
250
  allowed: ["ctx.db writes", "ctx.emit", "ctx.telemetry buffered events"],
248
- forbidden: ["network packages", "ctx.secrets", "ctx.ai", "process.env", "filesystem access"],
251
+ forbidden: ["network packages", "ctx.secrets", "ctx.ai", "ctx.ai.runAgent", "ctx.agent.run", "process.env", "filesystem access"],
249
252
  },
250
253
  {
251
254
  context: "query",
252
255
  allowed: ["ctx.db reads", "ctx.telemetry buffered events"],
253
- forbidden: ["insert/update/delete", "ctx.emit", "ctx.secrets", "ctx.ai", "network integrations"],
256
+ forbidden: ["insert/update/delete", "ctx.emit", "ctx.secrets", "ctx.ai", "ctx.ai.runAgent", "ctx.agent.run", "network integrations"],
254
257
  },
255
258
  {
256
259
  context: "liveQuery",
257
260
  allowed: ["ctx.db reads", "tenant-scoped subscriptions"],
258
- forbidden: ["insert/update/delete", "ctx.emit", "ctx.secrets", "ctx.ai", "network integrations"],
261
+ forbidden: ["insert/update/delete", "ctx.emit", "ctx.secrets", "ctx.ai", "ctx.ai.runAgent", "ctx.agent.run", "network integrations"],
259
262
  },
260
263
  {
261
264
  context: "action",
262
- allowed: ["ctx.secrets", "integrations", "ctx.ai", "ctx.db reads/writes", "network packages"],
265
+ allowed: ["ctx.secrets", "integrations", "ctx.ai", "ctx.ai.runAgent", "ctx.agent.run", "AI SDK tools", "ctx.db reads/writes", "network packages"],
263
266
  forbidden: ["uncommitted transactional side effects"],
264
267
  },
265
268
  {
266
269
  context: "workflow",
267
- allowed: ["durable steps", "ctx.secrets", "integrations", "ctx.ai", "retries"],
270
+ allowed: ["durable steps", "ctx.secrets", "integrations", "ctx.ai", "ctx.ai.runAgent", "ctx.agent.run", "AI SDK ToolLoopAgent", "retries"],
268
271
  forbidden: ["non-idempotent step behavior without guards"],
269
272
  },
270
273
  ];
@@ -320,6 +323,28 @@ function playbooks(): AgentPlaybook[] {
320
323
  "Reconnect with Last-Event-ID or ?lastRevision=<revision> to verify resume behavior.",
321
324
  ],
322
325
  },
326
+ {
327
+ title: "Add an AI tool",
328
+ steps: [
329
+ "Add a server-only file under src/ai or src/tools.",
330
+ "Export aiTool({ description, inputSchema, outputSchema, risk, needsApproval, handler }).",
331
+ "Use zod schemas for inputSchema and outputSchema.",
332
+ "Access secrets through the tool context, not process.env.",
333
+ "Mark destructive or external side effects with risk and needsApproval.",
334
+ "Run forge generate and inspect src/forge/_generated/aiRegistry.json.",
335
+ ],
336
+ },
337
+ {
338
+ title: "Add an agent",
339
+ steps: [
340
+ "Export agent({ provider, model, instructions, tools, stopWhen }) from server-only source.",
341
+ "Prefer AI SDK ToolLoopAgent semantics through ctx.agent.run or ctx.ai.runAgent instead of custom loops.",
342
+ "Use stopWhen with stepCount or terminal tool calls to prevent unbounded loops.",
343
+ "Run agents only in actions, workflows, endpoints, or server code.",
344
+ "Run forge inspect all --json and confirm agentContract.ai.agents lists the agent.",
345
+ "Use forge ai trace <traceId> --json to inspect agent runs and tool calls.",
346
+ ],
347
+ },
323
348
  {
324
349
  title: "Add a table",
325
350
  steps: [
@@ -355,7 +380,8 @@ function playbooks(): AgentPlaybook[] {
355
380
  title: "Safely refactor a feature",
356
381
  steps: [
357
382
  "Run forge refactor rename field <table.field> <table.field> --dry-run --json.",
358
- "Rename codemods are AST-aware for extract-action, rename field, and rename table.",
383
+ "Run forge refactor rename command <oldName> <newName> --dry-run --json when renaming runtime entrypoints.",
384
+ "Rename codemods are AST-aware for extract-action, rename command, rename field, and rename table.",
359
385
  "Field renames are scoped to the target table, so tickets.priority only rewrites references linked to tickets.",
360
386
  "Review filesToModify, migrationPlan, diagnostics, and risk.",
361
387
  "Use --allow-high-risk only for intentional high-risk refactors.",
@@ -428,6 +454,7 @@ function playbooks(): AgentPlaybook[] {
428
454
  title: "Add or update frontend",
429
455
  steps: [
430
456
  "Run forge make ui --framework vite --dry-run --json when the app does not have a web root.",
457
+ "Run forge make ai-chat support --dry-run --json to add a chat surface backed by /ai/agents/chat streaming and /ai/agents/run JSON automation.",
431
458
  "Use web/lib/forge.ts as the generated client bridge.",
432
459
  "Mount ForgeProvider once in the web app provider/layout layer; use devAuth for local development.",
433
460
  "Use useQuery, useCommand, and useLiveQuery instead of raw /commands or /queries fetches.",
@@ -656,6 +683,72 @@ function buildCapabilityMap(contract: AgentContract): AgentCapabilityMap {
656
683
  };
657
684
  }
658
685
 
686
+ function autoToolName(kind: "command" | "query" | "liveQuery", name: string): string {
687
+ return `forge_${kind}_${name}`.replace(/[^A-Za-z0-9_$]/g, "_");
688
+ }
689
+
690
+ function buildAgentToolRegistry(contract: AgentContract): AgentToolRegistry {
691
+ const autoTools: AgentToolRegistry["autoTools"] = [
692
+ ...contract.commands.map((command) => ({
693
+ name: autoToolName("command", command.name),
694
+ sourceKind: "command" as const,
695
+ sourceName: command.name,
696
+ ...(command.policy ? { policy: command.policy } : {}),
697
+ file: command.file,
698
+ http: command.http,
699
+ frontend: command.frontend,
700
+ tablesRead: command.tablesRead,
701
+ tablesWritten: command.tablesWritten,
702
+ emits: command.emits,
703
+ dependencies: [],
704
+ readOnly: false,
705
+ requiresAuth: command.policy !== undefined && command.policy !== "public",
706
+ execution: "forge-runtime-endpoint" as const,
707
+ })),
708
+ ...contract.queries.map((query) => ({
709
+ name: autoToolName("query", query.name),
710
+ sourceKind: "query" as const,
711
+ sourceName: query.name,
712
+ ...(query.policy ? { policy: query.policy } : {}),
713
+ file: query.file,
714
+ http: query.http,
715
+ frontend: query.frontend,
716
+ tablesRead: query.tablesRead,
717
+ tablesWritten: [],
718
+ emits: [],
719
+ dependencies: [],
720
+ readOnly: true,
721
+ requiresAuth: query.policy !== undefined && query.policy !== "public",
722
+ execution: "forge-runtime-endpoint" as const,
723
+ })),
724
+ ...contract.liveQueries.map((liveQuery) => ({
725
+ name: autoToolName("liveQuery", liveQuery.name),
726
+ sourceKind: "liveQuery" as const,
727
+ sourceName: liveQuery.name,
728
+ ...(liveQuery.policy ? { policy: liveQuery.policy } : {}),
729
+ file: liveQuery.file,
730
+ http: liveQuery.http,
731
+ frontend: liveQuery.frontend,
732
+ tablesRead: liveQuery.tablesRead,
733
+ tablesWritten: [],
734
+ emits: [],
735
+ dependencies: liveQuery.dependencies,
736
+ readOnly: true,
737
+ requiresAuth: liveQuery.policy !== undefined && liveQuery.policy !== "public",
738
+ execution: "forge-runtime-endpoint" as const,
739
+ })),
740
+ ].sort((a, b) => a.name.localeCompare(b.name));
741
+
742
+ return {
743
+ schemaVersion: "0.1.0",
744
+ generatorVersion: GENERATOR_VERSION,
745
+ project: contract.project,
746
+ explicitTools: contract.ai.tools,
747
+ autoTools,
748
+ agents: contract.ai.agents,
749
+ };
750
+ }
751
+
659
752
  function jsAccess(group: string, name: string): string {
660
753
  return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)
661
754
  ? `api.${group}.${name}`
@@ -951,6 +1044,23 @@ export function buildAgentContractArtifacts(
951
1044
  ...(generation.purpose ? { purpose: generation.purpose } : {}),
952
1045
  }))
953
1046
  .sort((a, b) => `${a.file}:${a.method}:${a.model}`.localeCompare(`${b.file}:${b.method}:${b.model}`)),
1047
+ tools: input.aiRegistry.tools.map((tool) => ({
1048
+ name: tool.name,
1049
+ file: tool.file,
1050
+ ...(tool.description ? { description: tool.description } : {}),
1051
+ risk: tool.risk,
1052
+ strict: tool.strict,
1053
+ needsApproval: tool.needsApproval,
1054
+ })),
1055
+ agents: input.aiRegistry.agents.map((agent) => ({
1056
+ name: agent.name,
1057
+ file: agent.file,
1058
+ provider: agent.provider,
1059
+ model: agent.model,
1060
+ ...(agent.instructions ? { instructions: agent.instructions } : {}),
1061
+ tools: agent.tools,
1062
+ stopWhen: agent.stopWhen,
1063
+ })),
954
1064
  },
955
1065
  client: {
956
1066
  queries: input.clientManifest.queries,
@@ -1038,14 +1148,17 @@ export function buildAgentContractArtifacts(
1038
1148
  : null;
1039
1149
  const userNotes = extractUserNotes(existingAgents);
1040
1150
  const agentsMd = renderAgentsMd(contract, userNotes);
1151
+ const toolRegistry = buildAgentToolRegistry(contract);
1041
1152
  const capabilityMap = buildCapabilityMap(contract);
1042
1153
  const capabilityMapMd = renderCapabilityMapMd(capabilityMap);
1154
+ const agentToolsMd = renderAgentToolsMd(toolRegistry);
1043
1155
  const appMapMd = renderAppMapMd(contract);
1044
1156
  const runtimeRulesMd = renderRuntimeRulesMd(contract.rules);
1045
1157
  const operationPlaybooksMd = renderOperationPlaybooksMd(contract.playbooks);
1046
1158
  const agentQuickstartMd = renderAgentQuickstartMd();
1047
1159
  const diagnostics = scanAgentContractForLeaks(contract, [
1048
1160
  agentsMd,
1161
+ agentToolsMd,
1049
1162
  capabilityMapMd,
1050
1163
  appMapMd,
1051
1164
  runtimeRulesMd,
@@ -1056,9 +1169,11 @@ export function buildAgentContractArtifacts(
1056
1169
  return {
1057
1170
  contract,
1058
1171
  capabilityMap,
1172
+ toolRegistry,
1059
1173
  agentsMd,
1060
1174
  appMapMd,
1061
1175
  capabilityMapMd,
1176
+ agentToolsMd,
1062
1177
  runtimeRulesMd,
1063
1178
  operationPlaybooksMd,
1064
1179
  agentQuickstartMd,
@@ -1099,6 +1214,15 @@ export function serializeCapabilityMapTs(capabilityMap: AgentCapabilityMap): str
1099
1214
  return `export const capabilityMap = ${JSON.stringify(parsed, null, 2)} as const;\n`;
1100
1215
  }
1101
1216
 
1217
+ export function serializeAgentToolRegistryJson(registry: AgentToolRegistry): string {
1218
+ return serializeCanonical(registry);
1219
+ }
1220
+
1221
+ export function serializeAgentToolRegistryTs(registry: AgentToolRegistry): string {
1222
+ const parsed = JSON.parse(serializeAgentToolRegistryJson(registry)) as unknown;
1223
+ return `export const agentTools = ${JSON.stringify(parsed, null, 2)} as const;\n`;
1224
+ }
1225
+
1102
1226
  function renderAgentsMd(contract: AgentContract, userNotes: string): string {
1103
1227
  const tenantTables = contract.data.tables
1104
1228
  .filter((table) => table.tenantScoped)
@@ -1107,6 +1231,12 @@ function renderAgentsMd(contract: AgentContract, userNotes: string): string {
1107
1231
  `${policy.name}: ${policy.roles.length > 0 ? policy.roles.join(", ") : policy.kind}`,
1108
1232
  );
1109
1233
  const secrets = contract.secrets.map((secret) => `${secret.name}${secret.required ? " (required)" : " (optional)"}`);
1234
+ const aiTools = contract.ai.tools.map((tool) =>
1235
+ `${tool.name}: ${tool.description ?? "no description"} (${tool.risk}${tool.needsApproval ? ", approval" : ""})`,
1236
+ );
1237
+ const aiAgents = contract.ai.agents.map((agent) =>
1238
+ `${agent.name}: ${agent.provider}/${agent.model} with ${agent.tools.length > 0 ? agent.tools.join(", ") : "no tools"}`,
1239
+ );
1110
1240
 
1111
1241
  return normalizeNewlines(`# AGENTS.md
1112
1242
 
@@ -1178,6 +1308,7 @@ forge inspect app --json
1178
1308
  forge inspect all --json
1179
1309
  forge inspect frontend --json
1180
1310
  forge inspect capabilities --json
1311
+ forge inspect agent-tools --json
1181
1312
  forge deps inspect <package> --json
1182
1313
  forge deps api <package> <symbol> --json
1183
1314
  forge deps trace <package> --json
@@ -1191,6 +1322,9 @@ forge doctor
1191
1322
  forge doctor windows --json
1192
1323
  forge setup windows --json
1193
1324
  forge agent print-context --json
1325
+ forge ai tools --json
1326
+ forge ai agents --json
1327
+ forge ai trace <traceId> --json
1194
1328
  forge verify --smoke
1195
1329
  forge verify --standard
1196
1330
  forge verify --strict
@@ -1210,6 +1344,21 @@ ${renderList(policies)}
1210
1344
 
1211
1345
  ${renderList(secrets)}
1212
1346
 
1347
+ ## AI Tools And Agents
1348
+
1349
+ - AI SDK engine: Vercel AI SDK v6.
1350
+ - Forge layer: generated registry, runtime rules, telemetry, secrets, tenant/auth context, and agent contract.
1351
+ - Use \`ctx.agent.run\` or \`ctx.ai.runAgent\` only in actions, workflows, endpoints, and server code.
1352
+ - Do not create custom tool loops; use Forge tools and AI SDK \`ToolLoopAgent\` through the Forge runtime.
1353
+
1354
+ Tools:
1355
+
1356
+ ${renderList(aiTools)}
1357
+
1358
+ Agents:
1359
+
1360
+ ${renderList(aiAgents)}
1361
+
1213
1362
  ## Auth
1214
1363
 
1215
1364
  - Modes: ${contract.auth.modes.join(", ")}
@@ -1268,6 +1417,7 @@ Use:
1268
1417
  forge make resource <name> --fields title:text,status:enum(open,closed) --dry-run --json
1269
1418
  forge make resource <name> --fields title:text,status:enum(open,closed) --with-ui --yes
1270
1419
  forge make ui --framework vite --dry-run --json
1420
+ forge make ai-chat support --dry-run --json
1271
1421
  \`\`\`
1272
1422
 
1273
1423
  Review the plan before applying when the resource touches schema or policies.
@@ -1304,11 +1454,13 @@ Use:
1304
1454
  \`\`\`bash
1305
1455
  forge refactor rename field tickets.priority tickets.urgency --dry-run --json
1306
1456
  forge refactor rename field tickets.priority tickets.urgency --yes
1457
+ forge refactor rename command createTicket openTicket --dry-run --json
1458
+ forge refactor rename command createTicket openTicket --yes
1307
1459
  \`\`\`
1308
1460
 
1309
- These codemods are AST-aware for \`extract-action\`, \`rename field\`, and \`rename table\`. Field renames are scoped to the target table, so \`tickets.priority\` only rewrites references linked to \`tickets\`.
1461
+ These codemods are AST-aware for \`extract-action\`, \`rename command\`, \`rename field\`, and \`rename table\`. Command renames update runtime registries, generated client references, frontend hooks, tests, and string references where safe. Field renames are scoped to the target table, so \`tickets.priority\` only rewrites references linked to \`tickets\`.
1310
1462
 
1311
- Never edit \`src/forge/_generated/**\` directly. Review migration hints before applying field or table renames.
1463
+ Never edit \`src/forge/_generated/**\` directly. Review migration hints before applying command, field, or table renames.
1312
1464
 
1313
1465
  ### Plan impact-based tests
1314
1466
 
@@ -1334,6 +1486,19 @@ forge repair plan --from-last-test-run --write
1334
1486
 
1335
1487
  Apply only high-confidence deterministic repairs automatically. Review medium or low confidence repairs before changing code.
1336
1488
 
1489
+ ### Add AI tools or agents
1490
+
1491
+ Use:
1492
+
1493
+ \`\`\`bash
1494
+ forge generate
1495
+ forge inspect all --json
1496
+ forge ai check --json
1497
+ forge ai trace <traceId> --json
1498
+ \`\`\`
1499
+
1500
+ Define tools with \`aiTool({ inputSchema, outputSchema, risk, needsApproval, handler })\` and agents with \`agent({ provider, model, instructions, tools, stopWhen })\`. Execute agents with \`ctx.agent.run\` or \`ctx.ai.runAgent\` only from actions, workflows, endpoints, or server code. In dev, POST \`/ai/agents/run\` returns JSON for automation and POST \`/ai/agents/chat\` returns an AI SDK UIMessage stream for React \`useChat\`; both accept \`agent: "<exportedAgentName>"\` and use generated auto-tools from \`agentTools.json\`.
1501
+
1337
1502
  ### Export agent adapters
1338
1503
 
1339
1504
  Use:
@@ -1471,6 +1636,48 @@ function renderAppMapMd(contract: AgentContract): string {
1471
1636
  lines.push(`### ${workflow.name}`, `Trigger: ${workflow.trigger ?? "manual"}`, "Steps:", ...renderList(workflow.steps).split("\n"), "");
1472
1637
  }
1473
1638
 
1639
+ lines.push("## AI", "");
1640
+ lines.push("### Providers", "", ...renderList(contract.ai.providers).split("\n"), "");
1641
+ lines.push("### Generations", "");
1642
+ for (const generation of contract.ai.generations) {
1643
+ lines.push(
1644
+ `- ${generation.method}: ${generation.provider}/${generation.model} in ${generation.file}${generation.purpose ? ` (${generation.purpose})` : ""}`,
1645
+ );
1646
+ }
1647
+ if (contract.ai.generations.length === 0) {
1648
+ lines.push("- none");
1649
+ }
1650
+ lines.push("", "### Tools", "");
1651
+ for (const tool of contract.ai.tools) {
1652
+ lines.push(
1653
+ `#### ${tool.name}`,
1654
+ `File: ${tool.file}`,
1655
+ `Risk: ${tool.risk}`,
1656
+ `Strict: ${tool.strict ? "yes" : "no"}`,
1657
+ `Needs approval: ${String(tool.needsApproval)}`,
1658
+ `Description: ${tool.description ?? "none"}`,
1659
+ "",
1660
+ );
1661
+ }
1662
+ if (contract.ai.tools.length === 0) {
1663
+ lines.push("- none", "");
1664
+ }
1665
+ lines.push("### Agents", "");
1666
+ for (const agent of contract.ai.agents) {
1667
+ lines.push(
1668
+ `#### ${agent.name}`,
1669
+ `File: ${agent.file}`,
1670
+ `Model: ${agent.provider}/${agent.model}`,
1671
+ "Tools:",
1672
+ ...renderList(agent.tools).split("\n"),
1673
+ `Stop when: ${JSON.stringify(agent.stopWhen)}`,
1674
+ "",
1675
+ );
1676
+ }
1677
+ if (contract.ai.agents.length === 0) {
1678
+ lines.push("- none", "");
1679
+ }
1680
+
1474
1681
  lines.push("## Frontend", "");
1475
1682
  lines.push(`Present: ${contract.frontend.present ? "yes" : "no"}`);
1476
1683
  lines.push(`Framework: ${contract.frontend.framework}`);
@@ -1631,6 +1838,70 @@ function renderCapabilityMapMd(capabilityMap: AgentCapabilityMap): string {
1631
1838
  return normalizeNewlines(lines.join("\n"));
1632
1839
  }
1633
1840
 
1841
+ function renderAgentToolsMd(registry: AgentToolRegistry): string {
1842
+ const lines = [
1843
+ "# Agent Tools",
1844
+ "",
1845
+ `Project: ${registry.project.name}`,
1846
+ "",
1847
+ "## Explicit AI Tools",
1848
+ "",
1849
+ ];
1850
+
1851
+ for (const tool of registry.explicitTools) {
1852
+ lines.push(
1853
+ `### ${tool.name}`,
1854
+ `File: ${tool.file}`,
1855
+ `Risk: ${tool.risk}`,
1856
+ `Strict: ${tool.strict ? "yes" : "no"}`,
1857
+ `Needs approval: ${String(tool.needsApproval)}`,
1858
+ `Description: ${tool.description ?? "none"}`,
1859
+ "",
1860
+ );
1861
+ }
1862
+ if (registry.explicitTools.length === 0) {
1863
+ lines.push("- none", "");
1864
+ }
1865
+
1866
+ lines.push("## Auto Tools From Forge Runtime", "");
1867
+ for (const tool of registry.autoTools) {
1868
+ lines.push(
1869
+ `### ${tool.name}`,
1870
+ `Source: ${tool.sourceKind} ${tool.sourceName}`,
1871
+ `File: ${tool.file}`,
1872
+ `HTTP: ${tool.http.method} ${tool.http.path}`,
1873
+ `Policy: ${tool.policy ?? "none"}`,
1874
+ `Requires auth: ${tool.requiresAuth ? "yes" : "no"}`,
1875
+ `Read-only: ${tool.readOnly ? "yes" : "no"}`,
1876
+ `Frontend hook: \`${tool.frontend.hook}\``,
1877
+ `Reads: ${tool.tablesRead.length > 0 ? tool.tablesRead.join(", ") : "none"}`,
1878
+ `Writes: ${tool.tablesWritten.length > 0 ? tool.tablesWritten.join(", ") : "none"}`,
1879
+ `Emits: ${tool.emits.length > 0 ? tool.emits.join(", ") : "none"}`,
1880
+ "",
1881
+ );
1882
+ }
1883
+ if (registry.autoTools.length === 0) {
1884
+ lines.push("- none", "");
1885
+ }
1886
+
1887
+ lines.push("## Agents", "");
1888
+ for (const agent of registry.agents) {
1889
+ lines.push(
1890
+ `### ${agent.name}`,
1891
+ `File: ${agent.file}`,
1892
+ `Model: ${agent.provider}/${agent.model}`,
1893
+ `Tools: ${agent.tools.length > 0 ? agent.tools.join(", ") : "none"}`,
1894
+ `Stop when: ${JSON.stringify(agent.stopWhen)}`,
1895
+ "",
1896
+ );
1897
+ }
1898
+ if (registry.agents.length === 0) {
1899
+ lines.push("- none", "");
1900
+ }
1901
+
1902
+ return normalizeNewlines(lines.join("\n"));
1903
+ }
1904
+
1634
1905
  function renderOperationPlaybooksMd(playbookEntries: AgentPlaybook[]): string {
1635
1906
  const lines = ["# Operation Playbooks", ""];
1636
1907
  for (const playbook of playbookEntries) {
@@ -1657,7 +1928,9 @@ forge dev
1657
1928
  forge inspect all --json
1658
1929
  forge inspect frontend --json
1659
1930
  forge inspect capabilities --json
1931
+ forge inspect agent-tools --json
1660
1932
  forge check --json
1933
+ forge ai trace <traceId> --json
1661
1934
  \`\`\`
1662
1935
 
1663
1936
  Never edit:
@@ -145,6 +145,23 @@ export interface AgentAiInfo {
145
145
  file: string;
146
146
  purpose?: string;
147
147
  }>;
148
+ tools: Array<{
149
+ name: string;
150
+ file: string;
151
+ description?: string;
152
+ risk: string;
153
+ strict: boolean;
154
+ needsApproval: boolean | "dynamic";
155
+ }>;
156
+ agents: Array<{
157
+ name: string;
158
+ file: string;
159
+ provider: string;
160
+ model: string;
161
+ instructions?: string;
162
+ tools: string[];
163
+ stopWhen: unknown;
164
+ }>;
148
165
  }
149
166
 
150
167
  export interface AgentClientInfo {
@@ -306,6 +323,30 @@ export interface AgentCapabilityMap {
306
323
  diagnostics: Diagnostic[];
307
324
  }
308
325
 
326
+ export interface AgentToolRegistry {
327
+ schemaVersion: "0.1.0";
328
+ generatorVersion: string;
329
+ project: AgentProjectInfo;
330
+ explicitTools: AgentAiInfo["tools"];
331
+ autoTools: Array<{
332
+ name: string;
333
+ sourceKind: "command" | "query" | "liveQuery";
334
+ sourceName: string;
335
+ policy?: string;
336
+ file: string;
337
+ http: AgentHttpEndpointInfo;
338
+ frontend: AgentFrontendUsageInfo;
339
+ tablesRead: string[];
340
+ tablesWritten: string[];
341
+ emits: string[];
342
+ dependencies: Array<{ table: string; scope: "tenant" | "global" }>;
343
+ readOnly: boolean;
344
+ requiresAuth: boolean;
345
+ execution: "forge-runtime-endpoint";
346
+ }>;
347
+ agents: AgentAiInfo["agents"];
348
+ }
349
+
309
350
  export interface AgentPlaybook {
310
351
  title: string;
311
352
  steps: string[];
@@ -4,9 +4,11 @@ import { canonicalJson } from "../primitives/serialize.ts";
4
4
  import type { AppGraph } from "../types/app-graph.ts";
5
5
  import type {
6
6
  AiGenerationCall,
7
+ AiAgentDefinition,
7
8
  AiModelDefinition,
8
9
  AiProviderDefinition,
9
10
  AiRegistry,
11
+ AiToolDefinition,
10
12
  ForgeAiProvider,
11
13
  } from "../types/ai-registry.ts";
12
14
  import type { ClassifiedPackage } from "../classifier/runtime-matrix.ts";
@@ -15,9 +17,17 @@ import {
15
17
  AI_REGISTRY_ANALYZER_VERSION,
16
18
  AI_REGISTRY_SCHEMA_VERSION,
17
19
  } from "./constants.ts";
18
- import { parseAiCallsFromSlice } from "./parse.ts";
20
+ import {
21
+ parseAiAgentMeta,
22
+ parseAiCallsFromSlice,
23
+ parseAiToolMeta,
24
+ } from "./parse.ts";
19
25
 
20
26
  const KNOWN_MODELS: AiModelDefinition[] = [
27
+ {
28
+ provider: "openai",
29
+ model: "gpt-5.4",
30
+ },
21
31
  {
22
32
  provider: "openai",
23
33
  model: "gpt-4o",
@@ -30,6 +40,10 @@ const KNOWN_MODELS: AiModelDefinition[] = [
30
40
  inputCostPer1kTokensUsd: 0.00015,
31
41
  outputCostPer1kTokensUsd: 0.0006,
32
42
  },
43
+ {
44
+ provider: "anthropic",
45
+ model: "claude-sonnet-4.5",
46
+ },
33
47
  {
34
48
  provider: "anthropic",
35
49
  model: "claude-3-5-sonnet-20241022",
@@ -42,6 +56,14 @@ const KNOWN_MODELS: AiModelDefinition[] = [
42
56
  inputCostPer1kTokensUsd: 0.0008,
43
57
  outputCostPer1kTokensUsd: 0.004,
44
58
  },
59
+ {
60
+ provider: "gateway",
61
+ model: "openai/gpt-5.4",
62
+ },
63
+ {
64
+ provider: "gateway",
65
+ model: "anthropic/claude-sonnet-4.5",
66
+ },
45
67
  {
46
68
  provider: "gateway",
47
69
  model: "openai/gpt-4o",
@@ -123,12 +145,39 @@ export function buildAiRegistry(
123
145
  classified: ClassifiedPackage[],
124
146
  ): AiRegistry {
125
147
  const generations: AiGenerationCall[] = [];
148
+ const tools: AiToolDefinition[] = [];
149
+ const agents: AiAgentDefinition[] = [];
126
150
 
127
151
  for (const symbol of appGraph.symbols) {
128
152
  const sourceSlice =
129
153
  typeof symbol.meta.sourceSlice === "string" ? symbol.meta.sourceSlice : "";
130
154
  if (sourceSlice.length === 0) continue;
131
155
 
156
+ if (symbol.kind === "aiTool") {
157
+ const meta = parseAiToolMeta(sourceSlice);
158
+ tools.push({
159
+ name: symbol.name,
160
+ file: symbol.file,
161
+ ...(meta.description ? { description: meta.description } : {}),
162
+ risk: meta.risk,
163
+ strict: meta.strict,
164
+ needsApproval: meta.needsApproval,
165
+ });
166
+ }
167
+
168
+ if (symbol.kind === "agent") {
169
+ const meta = parseAiAgentMeta(sourceSlice);
170
+ agents.push({
171
+ name: symbol.name,
172
+ file: symbol.file,
173
+ provider: meta.provider ?? "gateway",
174
+ model: meta.model ?? "unknown",
175
+ ...(meta.instructions ? { instructions: meta.instructions } : {}),
176
+ tools: meta.tools,
177
+ stopWhen: meta.stopWhen,
178
+ });
179
+ }
180
+
132
181
  for (const call of parseAiCallsFromSlice(sourceSlice)) {
133
182
  generations.push({
134
183
  provider: call.provider ?? "openai",
@@ -145,6 +194,16 @@ export function buildAiRegistry(
145
194
  if (fileCmp !== 0) return fileCmp;
146
195
  return a.method.localeCompare(b.method);
147
196
  });
197
+ tools.sort((a, b) => {
198
+ const nameCmp = a.name.localeCompare(b.name);
199
+ if (nameCmp !== 0) return nameCmp;
200
+ return a.file.localeCompare(b.file);
201
+ });
202
+ agents.sort((a, b) => {
203
+ const nameCmp = a.name.localeCompare(b.name);
204
+ if (nameCmp !== 0) return nameCmp;
205
+ return a.file.localeCompare(b.file);
206
+ });
148
207
 
149
208
  return {
150
209
  schemaVersion: AI_REGISTRY_SCHEMA_VERSION,
@@ -158,6 +217,8 @@ export function buildAiRegistry(
158
217
  ),
159
218
  providers: buildProviders(classified),
160
219
  generations,
220
+ tools,
221
+ agents,
161
222
  diagnostics: [],
162
223
  };
163
224
  }
@@ -1,2 +1,2 @@
1
1
  export const AI_REGISTRY_SCHEMA_VERSION = "1";
2
- export const AI_REGISTRY_ANALYZER_VERSION = "1.0.0";
2
+ export const AI_REGISTRY_ANALYZER_VERSION = "1.1.0";