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.
- package/AGENTS.md +38 -3
- package/CHANGELOG.md +29 -0
- package/README.md +25 -10
- package/package.json +8 -5
- package/src/forge/_generated/actionSubscriptions.json +2 -2
- package/src/forge/_generated/actionSubscriptions.ts +3 -3
- package/src/forge/_generated/agentAdapterManifest.json +2 -2
- package/src/forge/_generated/agentAdapterManifest.ts +3 -3
- package/src/forge/_generated/agentContract.json +2 -2
- package/src/forge/_generated/agentContract.ts +183 -50
- package/src/forge/_generated/agentQuickstart.md +3 -1
- package/src/forge/_generated/agentTools.json +2 -0
- package/src/forge/_generated/agentTools.md +16 -0
- package/src/forge/_generated/agentTools.ts +12 -0
- package/src/forge/_generated/aiContext.ts +67 -1
- package/src/forge/_generated/aiModels.json +2 -2
- package/src/forge/_generated/aiModels.ts +17 -1
- package/src/forge/_generated/aiProviders.json +1 -1
- package/src/forge/_generated/aiProviders.ts +1 -1
- package/src/forge/_generated/aiRegistry.json +2 -2
- package/src/forge/_generated/aiRegistry.ts +7 -5
- package/src/forge/_generated/api.json +2 -2
- package/src/forge/_generated/api.ts +1 -1
- package/src/forge/_generated/appGraph.json +2 -2
- package/src/forge/_generated/appGraph.ts +512 -260
- package/src/forge/_generated/appMap.md +21 -1
- package/src/forge/_generated/artifactManifest.json +2 -2
- package/src/forge/_generated/artifactManifest.ts +2 -2
- package/src/forge/_generated/authClaims.json +1 -1
- package/src/forge/_generated/authClaims.ts +1 -1
- package/src/forge/_generated/authConfig.json +1 -1
- package/src/forge/_generated/authConfig.ts +1 -1
- package/src/forge/_generated/authContext.ts +1 -1
- package/src/forge/_generated/authRegistry.json +1 -1
- package/src/forge/_generated/authRegistry.ts +1 -1
- package/src/forge/_generated/buildInfo.json +2 -2
- package/src/forge/_generated/buildInfo.ts +4 -4
- package/src/forge/_generated/capabilityMap.json +2 -2
- package/src/forge/_generated/capabilityMap.md +1 -1
- package/src/forge/_generated/capabilityMap.ts +2 -2
- package/src/forge/_generated/client.ts +1 -1
- package/src/forge/_generated/clientApi.ts +1 -1
- package/src/forge/_generated/clientManifest.json +2 -2
- package/src/forge/_generated/clientManifest.ts +3 -3
- package/src/forge/_generated/clientTypes.ts +1 -1
- package/src/forge/_generated/configRegistry.json +1 -1
- package/src/forge/_generated/configRegistry.ts +1 -1
- package/src/forge/_generated/dataGraph.json +2 -2
- package/src/forge/_generated/dataGraph.ts +3 -3
- package/src/forge/_generated/db.json +1 -1
- package/src/forge/_generated/db.ts +1 -1
- package/src/forge/_generated/dbSecurityManifest.json +1 -1
- package/src/forge/_generated/dbSecurityManifest.ts +1 -1
- package/src/forge/_generated/dbSessionContext.json +1 -1
- package/src/forge/_generated/dbSessionContext.ts +1 -1
- package/src/forge/_generated/deployManifest.json +2 -2
- package/src/forge/_generated/deployManifest.ts +7 -7
- package/src/forge/_generated/devManifest.json +2 -2
- package/src/forge/_generated/devManifest.ts +18 -3
- package/src/forge/_generated/envSchema.json +1 -1
- package/src/forge/_generated/envSchema.ts +1 -1
- package/src/forge/_generated/frontendGraph.json +1 -1
- package/src/forge/_generated/frontendGraph.ts +1 -1
- package/src/forge/_generated/importGuards.json +1 -1
- package/src/forge/_generated/importGuards.ts +1 -1
- package/src/forge/_generated/index.ts +2 -1
- package/src/forge/_generated/liveProductionManifest.json +1 -1
- package/src/forge/_generated/liveProductionManifest.ts +1 -1
- package/src/forge/_generated/liveProtocol.json +1 -1
- package/src/forge/_generated/liveProtocol.ts +1 -1
- package/src/forge/_generated/liveQueryRegistry.json +2 -2
- package/src/forge/_generated/liveQueryRegistry.ts +3 -3
- package/src/forge/_generated/liveTransportConfig.json +1 -1
- package/src/forge/_generated/liveTransportConfig.ts +1 -1
- package/src/forge/_generated/makeRegistry.json +2 -2
- package/src/forge/_generated/makeRegistry.ts +16 -2
- package/src/forge/_generated/makeTemplates.json +2 -2
- package/src/forge/_generated/makeTemplates.ts +6 -1
- package/src/forge/_generated/mockMap.json +1 -1
- package/src/forge/_generated/mockMap.ts +1 -1
- package/src/forge/_generated/operationPlaybooks.md +34 -14
- package/src/forge/_generated/packageGraph.json +2 -2
- package/src/forge/_generated/packageGraph.ts +8808 -4723
- package/src/forge/_generated/packageUpgradeRegistry.json +2 -2
- package/src/forge/_generated/packageUpgradeRegistry.ts +2 -2
- package/src/forge/_generated/permissionMatrix.json +2 -2
- package/src/forge/_generated/permissionMatrix.ts +3 -3
- package/src/forge/_generated/policyRegistry.json +2 -2
- package/src/forge/_generated/policyRegistry.ts +3 -3
- package/src/forge/_generated/queryRegistry.json +2 -2
- package/src/forge/_generated/queryRegistry.ts +3 -3
- package/src/forge/_generated/react.d.ts +1 -1
- package/src/forge/_generated/react.ts +1 -1
- package/src/forge/_generated/reactManifest.json +2 -2
- package/src/forge/_generated/reactManifest.ts +3 -3
- package/src/forge/_generated/releaseManifest.json +2 -2
- package/src/forge/_generated/releaseManifest.ts +3 -3
- package/src/forge/_generated/rlsPolicies.json +1 -1
- package/src/forge/_generated/rlsPolicies.sql +1 -1
- package/src/forge/_generated/rlsPolicies.ts +1 -1
- package/src/forge/_generated/runtimeGraph.json +2 -2
- package/src/forge/_generated/runtimeGraph.ts +3 -3
- package/src/forge/_generated/runtimeMatrix.json +2 -2
- package/src/forge/_generated/runtimeMatrix.ts +8684 -1939
- package/src/forge/_generated/runtimeRegistry.ts +1 -1
- package/src/forge/_generated/runtimeRules.md +13 -1
- package/src/forge/_generated/secretRegistry.json +1 -1
- package/src/forge/_generated/secretRegistry.ts +1 -1
- package/src/forge/_generated/secretsContext.ts +1 -1
- package/src/forge/_generated/serverApi.ts +1 -1
- package/src/forge/_generated/sourceMapManifest.json +2 -2
- package/src/forge/_generated/sourceMapManifest.ts +2 -2
- package/src/forge/_generated/sqlPlan.json +1 -1
- package/src/forge/_generated/sqlPlan.ts +1 -1
- package/src/forge/_generated/subscriptionManifest.json +2 -2
- package/src/forge/_generated/subscriptionManifest.ts +3 -3
- package/src/forge/_generated/symbolicationManifest.json +2 -2
- package/src/forge/_generated/symbolicationManifest.ts +2 -2
- package/src/forge/_generated/telemetryRegistry.json +2 -2
- package/src/forge/_generated/telemetryRegistry.ts +3 -3
- package/src/forge/_generated/telemetrySinks.json +2 -2
- package/src/forge/_generated/telemetrySinks.ts +2 -2
- package/src/forge/_generated/tenantScope.json +2 -2
- package/src/forge/_generated/tenantScope.ts +3 -3
- package/src/forge/_generated/testGraph.json +2 -2
- package/src/forge/_generated/testGraph.ts +339 -17
- package/src/forge/_generated/testPlanRegistry.json +2 -2
- package/src/forge/_generated/testPlanRegistry.ts +2 -2
- package/src/forge/_generated/uiRoutes.json +1 -1
- package/src/forge/_generated/uiRoutes.ts +1 -1
- package/src/forge/_generated/uiScenarios.json +1 -1
- package/src/forge/_generated/uiScenarios.ts +1 -1
- package/src/forge/_generated/uiTestManifest.json +2 -2
- package/src/forge/_generated/uiTestManifest.ts +2 -2
- package/src/forge/_generated/workflowRegistry.json +2 -2
- package/src/forge/_generated/workflowRegistry.ts +3 -3
- package/src/forge/_generated/workflowSubscriptions.json +2 -2
- package/src/forge/_generated/workflowSubscriptions.ts +3 -3
- package/src/forge/cli/ai.ts +351 -1
- package/src/forge/cli/auth.ts +36 -1
- package/src/forge/cli/commands.ts +19 -0
- package/src/forge/cli/parse.ts +67 -8
- package/src/forge/cli/rls.ts +529 -17
- package/src/forge/cli/secrets.ts +46 -1
- package/src/forge/cli/security.ts +269 -0
- package/src/forge/compiler/agent-contract/build.ts +289 -8
- package/src/forge/compiler/agent-contract/types.ts +43 -0
- package/src/forge/compiler/ai-registry/build.ts +62 -1
- package/src/forge/compiler/ai-registry/constants.ts +1 -1
- package/src/forge/compiler/ai-registry/parse.ts +98 -4
- package/src/forge/compiler/app-graph/forge-apis.ts +1 -0
- package/src/forge/compiler/dev-manifest/build.ts +3 -0
- package/src/forge/compiler/diagnostics/codes.ts +15 -0
- package/src/forge/compiler/diagnostics/create.ts +1 -1
- package/src/forge/compiler/make-registry/build.ts +13 -0
- package/src/forge/compiler/orchestrator/plan.ts +11 -0
- package/src/forge/compiler/orchestrator/serialize.ts +68 -0
- package/src/forge/compiler/package-graph/compiler.ts +13 -3
- package/src/forge/compiler/types/ai-registry.ts +25 -1
- package/src/forge/compiler/types/app-graph.ts +1 -0
- package/src/forge/compiler/types/cli.ts +1 -0
- package/src/forge/compiler/types/dev-manifest.ts +3 -0
- package/src/forge/dev/server.ts +508 -1
- package/src/forge/make/index.ts +126 -3
- package/src/forge/make/templates.ts +188 -0
- package/src/forge/make/types.ts +1 -0
- package/src/forge/runtime/ai/context.ts +210 -5
- package/src/forge/runtime/ai/types.ts +70 -0
- package/src/forge/runtime/auth/claims.ts +32 -0
- package/src/forge/runtime/auth/errors.ts +2 -0
- package/src/forge/runtime/context/create-context.ts +30 -6
- package/src/forge/runtime/db/memory-adapter.ts +2 -2
- package/src/forge/runtime/telemetry/scrubber.ts +56 -5
- package/src/forge/runtime/webhooks/security.ts +184 -0
- package/src/forge/server.ts +93 -0
- package/src/forge/version.ts +1 -1
- package/templates/b2b-support-web/package.json +1 -0
- package/templates/b2b-support-web/tsconfig.json +4 -1
- package/templates/minimal-web/package.json +1 -0
- package/templates/minimal-web/tsconfig.json +3 -1
package/src/forge/dev/server.ts
CHANGED
|
@@ -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({
|