@wrenai/genbi 0.0.1
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/LICENSE +201 -0
- package/README.md +141 -0
- package/bin/genbi.mjs +31 -0
- package/dist/assets/echarts-LTFC-ysd.js +40 -0
- package/dist/assets/index-Bo--QMUN.js +441 -0
- package/dist/assets/index-Xs4q7pNo.css +1 -0
- package/dist/index.html +14 -0
- package/dist/wrenai_logo.png +0 -0
- package/dist/wrenai_logo_white.png +0 -0
- package/dist-server/harness/auth/detect.d.ts +18 -0
- package/dist-server/harness/auth/detect.js +37 -0
- package/dist-server/harness/auth/index.d.ts +4 -0
- package/dist-server/harness/auth/index.js +2 -0
- package/dist-server/harness/auth/probe.d.ts +18 -0
- package/dist-server/harness/auth/probe.js +52 -0
- package/dist-server/harness/auth/types.d.ts +42 -0
- package/dist-server/harness/auth/types.js +9 -0
- package/dist-server/harness/bundle/loader.d.ts +32 -0
- package/dist-server/harness/bundle/loader.js +43 -0
- package/dist-server/harness/bundle/schema.d.ts +199 -0
- package/dist-server/harness/bundle/schema.js +127 -0
- package/dist-server/harness/bundle/version.d.ts +10 -0
- package/dist-server/harness/bundle/version.js +43 -0
- package/dist-server/harness/capability/gate.d.ts +6 -0
- package/dist-server/harness/capability/gate.js +29 -0
- package/dist-server/harness/capability/registry.d.ts +6 -0
- package/dist-server/harness/capability/registry.js +17 -0
- package/dist-server/harness/cli-args.d.ts +123 -0
- package/dist-server/harness/cli-args.js +247 -0
- package/dist-server/harness/cli.d.ts +2 -0
- package/dist-server/harness/cli.js +151 -0
- package/dist-server/harness/compile/cache.d.ts +23 -0
- package/dist-server/harness/compile/cache.js +109 -0
- package/dist-server/harness/compile/compose-profile.d.ts +34 -0
- package/dist-server/harness/compile/compose-profile.js +95 -0
- package/dist-server/harness/compile/errors.d.ts +22 -0
- package/dist-server/harness/compile/errors.js +41 -0
- package/dist-server/harness/compile/fingerprint.d.ts +22 -0
- package/dist-server/harness/compile/fingerprint.js +84 -0
- package/dist-server/harness/compile/index.d.ts +8 -0
- package/dist-server/harness/compile/index.js +7 -0
- package/dist-server/harness/compile/pipeline.d.ts +66 -0
- package/dist-server/harness/compile/pipeline.js +176 -0
- package/dist-server/harness/compile/resolve-binary.d.ts +55 -0
- package/dist-server/harness/compile/resolve-binary.js +133 -0
- package/dist-server/harness/compile/types.d.ts +107 -0
- package/dist-server/harness/compile/types.js +1 -0
- package/dist-server/harness/compile/warble-identity.d.ts +12 -0
- package/dist-server/harness/compile/warble-identity.js +33 -0
- package/dist-server/harness/compliance/gate.d.ts +35 -0
- package/dist-server/harness/compliance/gate.js +48 -0
- package/dist-server/harness/compliance/index.d.ts +2 -0
- package/dist-server/harness/compliance/index.js +1 -0
- package/dist-server/harness/compliance/types.d.ts +16 -0
- package/dist-server/harness/compliance/types.js +1 -0
- package/dist-server/harness/events/emitter.d.ts +34 -0
- package/dist-server/harness/events/emitter.js +25 -0
- package/dist-server/harness/events/index.d.ts +4 -0
- package/dist-server/harness/events/index.js +2 -0
- package/dist-server/harness/events/sse.d.ts +20 -0
- package/dist-server/harness/events/sse.js +48 -0
- package/dist-server/harness/events/types.d.ts +131 -0
- package/dist-server/harness/events/types.js +1 -0
- package/dist-server/harness/exec/errors.d.ts +16 -0
- package/dist-server/harness/exec/errors.js +29 -0
- package/dist-server/harness/exec/index.d.ts +5 -0
- package/dist-server/harness/exec/index.js +2 -0
- package/dist-server/harness/exec/local.d.ts +32 -0
- package/dist-server/harness/exec/local.js +134 -0
- package/dist-server/harness/exec/policy.d.ts +12 -0
- package/dist-server/harness/exec/policy.js +1 -0
- package/dist-server/harness/exec/types.d.ts +63 -0
- package/dist-server/harness/exec/types.js +1 -0
- package/dist-server/harness/guardrails/index.d.ts +2 -0
- package/dist-server/harness/guardrails/index.js +1 -0
- package/dist-server/harness/guardrails/policy.d.ts +45 -0
- package/dist-server/harness/guardrails/policy.js +57 -0
- package/dist-server/harness/index.d.ts +37 -0
- package/dist-server/harness/index.js +19 -0
- package/dist-server/harness/loop/errors.d.ts +53 -0
- package/dist-server/harness/loop/errors.js +72 -0
- package/dist-server/harness/loop/executor.d.ts +123 -0
- package/dist-server/harness/loop/executor.js +380 -0
- package/dist-server/harness/loop/guard.d.ts +16 -0
- package/dist-server/harness/loop/guard.js +1 -0
- package/dist-server/harness/loop/index.d.ts +5 -0
- package/dist-server/harness/loop/index.js +3 -0
- package/dist-server/harness/native-producer/index.d.ts +109 -0
- package/dist-server/harness/native-producer/index.js +302 -0
- package/dist-server/harness/npm-package-resolve.d.ts +28 -0
- package/dist-server/harness/npm-package-resolve.js +47 -0
- package/dist-server/harness/providers/adapters/anthropic.d.ts +7 -0
- package/dist-server/harness/providers/adapters/anthropic.js +6 -0
- package/dist-server/harness/providers/adapters/mock.d.ts +4 -0
- package/dist-server/harness/providers/adapters/mock.js +5 -0
- package/dist-server/harness/providers/adapters/openai-compatible.d.ts +10 -0
- package/dist-server/harness/providers/adapters/openai-compatible.js +10 -0
- package/dist-server/harness/providers/binding.d.ts +23 -0
- package/dist-server/harness/providers/binding.js +13 -0
- package/dist-server/harness/providers/errors.d.ts +6 -0
- package/dist-server/harness/providers/errors.js +12 -0
- package/dist-server/harness/providers/index.d.ts +18 -0
- package/dist-server/harness/providers/index.js +22 -0
- package/dist-server/harness/providers/registry.d.ts +16 -0
- package/dist-server/harness/providers/registry.js +24 -0
- package/dist-server/harness/render/envelope.d.ts +145 -0
- package/dist-server/harness/render/envelope.js +428 -0
- package/dist-server/harness/render/errors.d.ts +33 -0
- package/dist-server/harness/render/errors.js +46 -0
- package/dist-server/harness/render/index.d.ts +5 -0
- package/dist-server/harness/render/index.js +3 -0
- package/dist-server/harness/render/validate.d.ts +13 -0
- package/dist-server/harness/render/validate.js +88 -0
- package/dist-server/harness/route/adapter-spec.d.ts +40 -0
- package/dist-server/harness/route/adapter-spec.js +61 -0
- package/dist-server/harness/route/agent-sdk-cli.d.ts +30 -0
- package/dist-server/harness/route/agent-sdk-cli.js +86 -0
- package/dist-server/harness/route/agent-sdk-manifest.d.ts +31 -0
- package/dist-server/harness/route/agent-sdk-manifest.js +56 -0
- package/dist-server/harness/route/artifact-content.d.ts +53 -0
- package/dist-server/harness/route/artifact-content.js +83 -0
- package/dist-server/harness/route/chat-event-mapper.d.ts +95 -0
- package/dist-server/harness/route/chat-event-mapper.js +164 -0
- package/dist-server/harness/route/codex-ask-events.d.ts +25 -0
- package/dist-server/harness/route/codex-ask-events.js +395 -0
- package/dist-server/harness/route/codex-ask.d.ts +15 -0
- package/dist-server/harness/route/codex-ask.js +242 -0
- package/dist-server/harness/route/codex-local-cli.d.ts +20 -0
- package/dist-server/harness/route/codex-local-cli.js +78 -0
- package/dist-server/harness/route/codex-local-manifest.d.ts +22 -0
- package/dist-server/harness/route/codex-local-manifest.js +180 -0
- package/dist-server/harness/route/describe.d.ts +54 -0
- package/dist-server/harness/route/describe.js +84 -0
- package/dist-server/harness/route/dispatched.d.ts +138 -0
- package/dist-server/harness/route/dispatched.js +339 -0
- package/dist-server/harness/route/in-process.d.ts +43 -0
- package/dist-server/harness/route/in-process.js +106 -0
- package/dist-server/harness/route/index.d.ts +19 -0
- package/dist-server/harness/route/index.js +12 -0
- package/dist-server/harness/route/profile-source.d.ts +37 -0
- package/dist-server/harness/route/profile-source.js +76 -0
- package/dist-server/harness/route/route.d.ts +33 -0
- package/dist-server/harness/route/route.js +114 -0
- package/dist-server/harness/route/tier-binding.d.ts +36 -0
- package/dist-server/harness/route/tier-binding.js +63 -0
- package/dist-server/harness/route/types.d.ts +286 -0
- package/dist-server/harness/route/types.js +1 -0
- package/dist-server/harness/session/errors.d.ts +4 -0
- package/dist-server/harness/session/errors.js +7 -0
- package/dist-server/harness/session/gate.d.ts +11 -0
- package/dist-server/harness/session/gate.js +13 -0
- package/dist-server/harness/session/index.d.ts +5 -0
- package/dist-server/harness/session/index.js +3 -0
- package/dist-server/harness/session/run.d.ts +60 -0
- package/dist-server/harness/session/run.js +333 -0
- package/dist-server/harness/session/types.d.ts +29 -0
- package/dist-server/harness/session/types.js +1 -0
- package/dist-server/harness/setup/codex-events.d.ts +12 -0
- package/dist-server/harness/setup/codex-events.js +108 -0
- package/dist-server/harness/setup/runner.d.ts +497 -0
- package/dist-server/harness/setup/runner.js +1314 -0
- package/dist-server/harness/tools/errors.d.ts +107 -0
- package/dist-server/harness/tools/errors.js +141 -0
- package/dist-server/harness/tools/index.d.ts +13 -0
- package/dist-server/harness/tools/index.js +8 -0
- package/dist-server/harness/tools/mcp.d.ts +26 -0
- package/dist-server/harness/tools/mcp.js +15 -0
- package/dist-server/harness/tools/native.d.ts +179 -0
- package/dist-server/harness/tools/native.js +322 -0
- package/dist-server/harness/tools/parse.d.ts +14 -0
- package/dist-server/harness/tools/parse.js +18 -0
- package/dist-server/harness/tools/resolve-wren-binary.d.ts +12 -0
- package/dist-server/harness/tools/resolve-wren-binary.js +21 -0
- package/dist-server/harness/tools/resolve.d.ts +51 -0
- package/dist-server/harness/tools/resolve.js +79 -0
- package/dist-server/harness/tools/setup-native.d.ts +84 -0
- package/dist-server/harness/tools/setup-native.js +353 -0
- package/dist-server/harness/tools/wiring.d.ts +10 -0
- package/dist-server/harness/tools/wiring.js +16 -0
- package/dist-server/providers/setup.provider.yaml +31 -0
- package/dist-server/providers/wren.provider.yaml +33 -0
- package/dist-server/server/adopt.d.ts +87 -0
- package/dist-server/server/adopt.js +263 -0
- package/dist-server/server/app.d.ts +6 -0
- package/dist-server/server/app.js +2521 -0
- package/dist-server/server/auth-choice.d.ts +16 -0
- package/dist-server/server/auth-choice.js +36 -0
- package/dist-server/server/bin.d.ts +2 -0
- package/dist-server/server/bin.js +601 -0
- package/dist-server/server/bootstrap-project-binding.d.ts +31 -0
- package/dist-server/server/bootstrap-project-binding.js +71 -0
- package/dist-server/server/clarify.d.ts +10 -0
- package/dist-server/server/clarify.js +17 -0
- package/dist-server/server/codex-setup-mcp.d.ts +2 -0
- package/dist-server/server/codex-setup-mcp.js +147 -0
- package/dist-server/server/compose.d.ts +89 -0
- package/dist-server/server/compose.js +271 -0
- package/dist-server/server/conn-config.d.ts +30 -0
- package/dist-server/server/conn-config.js +253 -0
- package/dist-server/server/context-files.d.ts +12 -0
- package/dist-server/server/context-files.js +117 -0
- package/dist-server/server/context-map.d.ts +30 -0
- package/dist-server/server/context-map.js +95 -0
- package/dist-server/server/context-source.d.ts +68 -0
- package/dist-server/server/context-source.js +85 -0
- package/dist-server/server/db.d.ts +862 -0
- package/dist-server/server/db.js +1839 -0
- package/dist-server/server/enrichment-runner.d.ts +184 -0
- package/dist-server/server/enrichment-runner.js +213 -0
- package/dist-server/server/enrichment-verify.d.ts +73 -0
- package/dist-server/server/enrichment-verify.js +290 -0
- package/dist-server/server/enrichment.d.ts +232 -0
- package/dist-server/server/enrichment.js +199 -0
- package/dist-server/server/env-detect.d.ts +8 -0
- package/dist-server/server/env-detect.js +17 -0
- package/dist-server/server/env-field-hints.d.ts +46 -0
- package/dist-server/server/env-field-hints.js +48 -0
- package/dist-server/server/fold.d.ts +133 -0
- package/dist-server/server/fold.js +407 -0
- package/dist-server/server/harness-profile-sources.d.ts +3 -0
- package/dist-server/server/harness-profile-sources.js +24 -0
- package/dist-server/server/harness.d.ts +19 -0
- package/dist-server/server/harness.js +439 -0
- package/dist-server/server/impact.d.ts +18 -0
- package/dist-server/server/impact.js +85 -0
- package/dist-server/server/interactive-terminal.d.ts +175 -0
- package/dist-server/server/interactive-terminal.js +336 -0
- package/dist-server/server/native-artifacts.d.ts +1004 -0
- package/dist-server/server/native-artifacts.js +564 -0
- package/dist-server/server/native-dispatch-registry.d.ts +42 -0
- package/dist-server/server/native-dispatch-registry.js +45 -0
- package/dist-server/server/native-producer-fence.d.ts +14 -0
- package/dist-server/server/native-producer-fence.js +48 -0
- package/dist-server/server/native-session-resume.d.ts +6 -0
- package/dist-server/server/native-session-resume.js +103 -0
- package/dist-server/server/native-session-workspace.d.ts +40 -0
- package/dist-server/server/native-session-workspace.js +175 -0
- package/dist-server/server/native-sessions.d.ts +374 -0
- package/dist-server/server/native-sessions.js +1517 -0
- package/dist-server/server/native-wren-runtime.d.ts +20 -0
- package/dist-server/server/native-wren-runtime.js +259 -0
- package/dist-server/server/node-pty-host.d.ts +31 -0
- package/dist-server/server/node-pty-host.js +100 -0
- package/dist-server/server/route-intent.d.ts +40 -0
- package/dist-server/server/route-intent.js +23 -0
- package/dist-server/server/runtime-binding.d.ts +52 -0
- package/dist-server/server/runtime-binding.js +258 -0
- package/dist-server/server/source-catalog.d.ts +84 -0
- package/dist-server/server/source-catalog.js +221 -0
- package/dist-server/server/source-driver.d.ts +35 -0
- package/dist-server/server/source-driver.js +124 -0
- package/dist-server/server/spa.d.ts +2 -0
- package/dist-server/server/spa.js +51 -0
- package/dist-server/server/subscription-model-catalog.d.ts +27 -0
- package/dist-server/server/subscription-model-catalog.js +203 -0
- package/dist-server/server/turn.d.ts +249 -0
- package/dist-server/server/turn.js +1074 -0
- package/dist-server/server/wire-types.d.ts +799 -0
- package/dist-server/server/wire-types.js +1 -0
- package/dist-server/server/wren-home.d.ts +19 -0
- package/dist-server/server/wren-home.js +33 -0
- package/dist-server/server/wren-profiles.d.ts +43 -0
- package/dist-server/server/wren-profiles.js +123 -0
- package/package.json +93 -0
- package/profiles/genbi-default/context/binding.yml +9 -0
- package/profiles/genbi-default/ir.golden.json +506 -0
- package/profiles/genbi-default/profile.yml +16 -0
- package/profiles/genbi-enrich-context/components/apply_enrichment/component.yml +42 -0
- package/profiles/genbi-enrich-context/components/draft_enrichment/component.yml +30 -0
- package/profiles/genbi-enrich-context/components/draft_enrichment/steps/draft.md +34 -0
- package/profiles/genbi-enrich-context/components/inspect_context/component.yml +32 -0
- package/profiles/genbi-enrich-context/components/inspect_context/steps/inspect.md +13 -0
- package/profiles/genbi-enrich-context/context/binding.yml +4 -0
- package/profiles/genbi-enrich-context/ir.golden.json +302 -0
- package/profiles/genbi-enrich-context/profile.yml +15 -0
- package/profiles/genbi-monitor/context/binding.yml +7 -0
- package/profiles/genbi-monitor/ir.golden.json +188 -0
- package/profiles/genbi-monitor/profile.yml +27 -0
- package/profiles/genbi-setup/components/build_context/component.yml +44 -0
- package/profiles/genbi-setup/components/build_context/steps/build.md +14 -0
- package/profiles/genbi-setup/components/connect_source/component.yml +51 -0
- package/profiles/genbi-setup/components/connect_source/steps/connect.md +23 -0
- package/profiles/genbi-setup/context/binding.yml +8 -0
- package/profiles/genbi-setup/ir.golden.json +140 -0
- package/profiles/genbi-setup/profile.yml +18 -0
- package/profiles/genbi-setup/raw/schema.json +5 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { tool } from "ai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { UnknownNativeToolError, WrenBinaryNotFoundError, WrenIntrospectExecutionError, WrenQueryExecutionError, } from "./errors.js";
|
|
4
|
+
export function createNativeToolRegistry() {
|
|
5
|
+
const factories = new Map();
|
|
6
|
+
return {
|
|
7
|
+
register(name, factory) {
|
|
8
|
+
factories.set(name, factory);
|
|
9
|
+
},
|
|
10
|
+
has(name) {
|
|
11
|
+
return factories.has(name);
|
|
12
|
+
},
|
|
13
|
+
create(name) {
|
|
14
|
+
const factory = factories.get(name);
|
|
15
|
+
if (!factory) {
|
|
16
|
+
throw new UnknownNativeToolError(name);
|
|
17
|
+
}
|
|
18
|
+
return factory();
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export const WRITE_ARTIFACT_TOOL_NAME = "write_artifact";
|
|
23
|
+
/**
|
|
24
|
+
* `write_artifact`: routes the write through the injected `ExecutionEnv`,
|
|
25
|
+
* confined to `policy.artifactWriteScope`. The policy is the
|
|
26
|
+
* `deriveEnforcement`-computed `EnforcementPolicy` for the owning
|
|
27
|
+
* agent — a locked `scoped_write` guardrail is what grants
|
|
28
|
+
* `artifactWriteScope` in the first place, so an agent with no such
|
|
29
|
+
* guardrail gets a policy with no scope and every call fails closed with
|
|
30
|
+
* `WriteScopeNotGrantedError` (thrown by the `ExecutionEnv`, not here).
|
|
31
|
+
* This coexists with a locked `read_only_execution` guardrail by design:
|
|
32
|
+
* `readOnly` gates *data-access* execution (`exec`/non-allowlisted
|
|
33
|
+
* `fetch`), never the scoped artifact write.
|
|
34
|
+
*/
|
|
35
|
+
export function createWriteArtifactTool(env, policy) {
|
|
36
|
+
return tool({
|
|
37
|
+
description: "Write an artifact to the agent's scoped workspace.",
|
|
38
|
+
inputSchema: z.object({
|
|
39
|
+
path: z.string(),
|
|
40
|
+
content: z.string(),
|
|
41
|
+
}),
|
|
42
|
+
execute: async (input) => {
|
|
43
|
+
await env.writeFile(input.path, input.content, policy);
|
|
44
|
+
return {
|
|
45
|
+
written: true,
|
|
46
|
+
path: input.path,
|
|
47
|
+
bytes: input.content.length,
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/** A native-tool registry pre-populated with the built-in `write_artifact` and `build_dashboard` tools, bound to `env`/`policy`. */
|
|
53
|
+
export function createDefaultNativeToolRegistry(env, policy) {
|
|
54
|
+
const registry = createNativeToolRegistry();
|
|
55
|
+
registry.register(WRITE_ARTIFACT_TOOL_NAME, () => createWriteArtifactTool(env, policy));
|
|
56
|
+
registry.register(BUILD_DASHBOARD_TOOL_NAME, () => createBuildDashboardTool());
|
|
57
|
+
return registry;
|
|
58
|
+
}
|
|
59
|
+
export const BUILD_DASHBOARD_TOOL_NAME = "build_dashboard";
|
|
60
|
+
/**
|
|
61
|
+
* A dashboard row, as the model may hand it in: either a column-keyed object
|
|
62
|
+
* (the shape `generate_dashboard`'s `chart`/`table` render blocks require),
|
|
63
|
+
* or a positional array aligned to that panel's known key order (`columns`
|
|
64
|
+
* for a table, `[x, ...series]` for a chart). Accepting both keeps the
|
|
65
|
+
* schema forgiving of how a model naturally emits rows it just queried,
|
|
66
|
+
* while `normalizeDashboardRow` below deterministically reshapes either
|
|
67
|
+
* form into the object shape the output contract needs.
|
|
68
|
+
*/
|
|
69
|
+
const dashboardRowSchema = z.union([z.record(z.string(), z.unknown()), z.array(z.unknown())]);
|
|
70
|
+
const kpiCardInputSchema = z.object({
|
|
71
|
+
label: z.string(),
|
|
72
|
+
value: z.union([z.number(), z.string()]),
|
|
73
|
+
unit: z.string().optional(),
|
|
74
|
+
delta: z.number().optional(),
|
|
75
|
+
});
|
|
76
|
+
const chartInputSchema = z.object({
|
|
77
|
+
chart_type: z.enum(["bar", "line", "pie", "area", "scatter"]),
|
|
78
|
+
x: z.string(),
|
|
79
|
+
series: z.array(z.string()),
|
|
80
|
+
rows: z.array(dashboardRowSchema),
|
|
81
|
+
});
|
|
82
|
+
const tableInputSchema = z.object({
|
|
83
|
+
columns: z.array(z.string()),
|
|
84
|
+
rows: z.array(dashboardRowSchema),
|
|
85
|
+
});
|
|
86
|
+
const definitionInputSchema = z.object({
|
|
87
|
+
sql: z.string(),
|
|
88
|
+
source_tables: z.array(z.string()).optional(),
|
|
89
|
+
filters: z.array(z.string()).optional(),
|
|
90
|
+
});
|
|
91
|
+
export const buildDashboardInputSchema = z.object({
|
|
92
|
+
kpi_cards: z.array(kpiCardInputSchema).optional(),
|
|
93
|
+
chart: chartInputSchema.optional(),
|
|
94
|
+
table: tableInputSchema.optional(),
|
|
95
|
+
definition: definitionInputSchema.optional(),
|
|
96
|
+
summary: z.string().optional(),
|
|
97
|
+
});
|
|
98
|
+
/** Reshapes a row into a plain object: passes an already-object row through; zips a positional array row against `keys`. */
|
|
99
|
+
function normalizeDashboardRow(row, keys) {
|
|
100
|
+
if (!Array.isArray(row))
|
|
101
|
+
return row;
|
|
102
|
+
return Object.fromEntries(keys.map((key, index) => [key, row[index]]));
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Deterministically assembles the typed panels the model composed
|
|
106
|
+
* (`kpi_cards`/`chart`/`table`/`definition`) into `generate_dashboard`'s
|
|
107
|
+
* `{blocks}` render contract — one `kpi_card` block per KPI, then `chart`,
|
|
108
|
+
* then `table`, then `definition` (this is the "sensible dashboard order":
|
|
109
|
+
* headline numbers first, then the visual trend/breakdown, then the
|
|
110
|
+
* supporting detail, then the query provenance), each only when the
|
|
111
|
+
* corresponding panel is present in `input`. No LLM call, no I/O — pure
|
|
112
|
+
* reshaping of model-supplied, schema-validated structure, which is what
|
|
113
|
+
* makes this safe to trust as `verified: true` (see `WrenQueryResult`'s own
|
|
114
|
+
* reasoning for why a real, non-erroring data-access result earns
|
|
115
|
+
* `verified`): every panel here was composed by the model from its own
|
|
116
|
+
* queried results, not fabricated by this function.
|
|
117
|
+
*/
|
|
118
|
+
export function assembleDashboardBlocks(input) {
|
|
119
|
+
const blocks = [];
|
|
120
|
+
for (const kpi of input.kpi_cards ?? []) {
|
|
121
|
+
blocks.push({
|
|
122
|
+
type: "kpi_card",
|
|
123
|
+
label: kpi.label,
|
|
124
|
+
value: kpi.value,
|
|
125
|
+
...(kpi.unit !== undefined ? { unit: kpi.unit } : {}),
|
|
126
|
+
...(kpi.delta !== undefined ? { delta: kpi.delta } : {}),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (input.chart) {
|
|
130
|
+
const keys = [input.chart.x, ...input.chart.series];
|
|
131
|
+
blocks.push({
|
|
132
|
+
type: "chart",
|
|
133
|
+
chart_type: input.chart.chart_type,
|
|
134
|
+
x: input.chart.x,
|
|
135
|
+
series: input.chart.series,
|
|
136
|
+
rows: input.chart.rows.map((row) => normalizeDashboardRow(row, keys)),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
if (input.table) {
|
|
140
|
+
const { columns } = input.table;
|
|
141
|
+
blocks.push({
|
|
142
|
+
type: "table",
|
|
143
|
+
columns,
|
|
144
|
+
rows: input.table.rows.map((row) => normalizeDashboardRow(row, columns)),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (input.definition) {
|
|
148
|
+
blocks.push({
|
|
149
|
+
type: "definition",
|
|
150
|
+
sql: input.definition.sql,
|
|
151
|
+
source_tables: input.definition.source_tables ?? [],
|
|
152
|
+
filters: input.definition.filters ?? [],
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
blocks,
|
|
157
|
+
...(input.summary !== undefined ? { summary: input.summary } : {}),
|
|
158
|
+
verified: true,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* `build_dashboard` (native realization): a schema-enforced, purely
|
|
163
|
+
* deterministic assembly tool. The zod `inputSchema` is what makes the model
|
|
164
|
+
* reliably supply *structured* panels (typed kpi/chart/table/definition
|
|
165
|
+
* shapes) instead of prose it would otherwise have to be trusted to format
|
|
166
|
+
* correctly; `execute` then just reshapes that already-validated input into
|
|
167
|
+
* the render contract — no LLM call, no I/O, so it can never itself fail
|
|
168
|
+
* non-deterministically the way a render-LLM synthesis step can.
|
|
169
|
+
*/
|
|
170
|
+
export function createBuildDashboardTool() {
|
|
171
|
+
return tool({
|
|
172
|
+
description: "Assemble queried dashboard panels (KPI cards, a chart, a table, and/or the query definition) " +
|
|
173
|
+
"into the final dashboard's render blocks, in dashboard order.",
|
|
174
|
+
inputSchema: buildDashboardInputSchema,
|
|
175
|
+
execute: async (input) => assembleDashboardBlocks(input),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
export const WREN_QUERY_TOOL_NAME = "query";
|
|
179
|
+
/**
|
|
180
|
+
* Clamps the model-volunteered `requestedLimit` to the guardrail-derived
|
|
181
|
+
* `policyLimit` (`EnforcementPolicy.rowLimit`), always preferring the
|
|
182
|
+
* smaller. When the model volunteers nothing, the policy limit itself
|
|
183
|
+
* becomes the applied `-l` — a locked-in cap, not just a ceiling on a value
|
|
184
|
+
* the model chose not to supply. When there is no policy limit at all (an
|
|
185
|
+
* agent whose guardrails carry no `row_limit` threshold), the model's
|
|
186
|
+
* requested limit (if any) passes through unclamped.
|
|
187
|
+
*/
|
|
188
|
+
function clampRowLimit(policyLimit, requestedLimit) {
|
|
189
|
+
if (policyLimit === undefined)
|
|
190
|
+
return requestedLimit;
|
|
191
|
+
if (requestedLimit === undefined)
|
|
192
|
+
return policyLimit;
|
|
193
|
+
return Math.min(policyLimit, requestedLimit);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* `query` (native realization): runs a read-only SQL query through the real
|
|
197
|
+
* `wren` CLI — `wren -q -o json [-l <limit>] -s '<sql>'` — executed via the
|
|
198
|
+
* injected `ExecutionEnv` with `cwd: projectDir`, exactly like
|
|
199
|
+
* `write_artifact` routes through `env.writeFile` instead of touching the
|
|
200
|
+
* filesystem directly. Declared `mode: "read"` so a locked
|
|
201
|
+
* `read_only_execution` guardrail (which `answer_query` always carries)
|
|
202
|
+
* never blocks it — only `mode: "write"` commands are rejected by a
|
|
203
|
+
* read-only policy (see `ExecutionEnv.exec`'s doc comment).
|
|
204
|
+
*
|
|
205
|
+
* `policy.rowLimit` (guardrail-enforcement fix): clamps `input.limit` via
|
|
206
|
+
* `clampRowLimit`, so a locked `row_limit` guardrail actually bounds `-l`
|
|
207
|
+
* instead of being derived and then ignored. `policy.statementTimeoutSec`
|
|
208
|
+
* becomes `ExecCommand.timeoutMs` on the `wren` invocation, so a locked
|
|
209
|
+
* `statement_timeout` guardrail actually kills a runaway query instead of
|
|
210
|
+
* letting it run unbounded.
|
|
211
|
+
*
|
|
212
|
+
* `wren -o json` prints one JSON object per result row (JSONL, not a JSON
|
|
213
|
+
* array) — this parses that into `{ columns, rows }`, where `columns` are
|
|
214
|
+
* the first row's keys (an empty result set yields `columns: []`).
|
|
215
|
+
*/
|
|
216
|
+
export function createWrenQueryTool(options) {
|
|
217
|
+
const { env, policy, projectDir } = options;
|
|
218
|
+
return tool({
|
|
219
|
+
description: "Run a read-only SQL query against the wren semantic layer via the native `wren` CLI.",
|
|
220
|
+
inputSchema: z.object({
|
|
221
|
+
sql: z.string(),
|
|
222
|
+
limit: z.number().int().positive().optional(),
|
|
223
|
+
}),
|
|
224
|
+
execute: async (input) => {
|
|
225
|
+
const limit = clampRowLimit(policy.rowLimit, input.limit);
|
|
226
|
+
const args = ["-q", "-o", "json", ...(limit !== undefined ? ["-l", String(limit)] : []), "-s", input.sql];
|
|
227
|
+
const timeoutMs = policy.statementTimeoutSec !== undefined ? policy.statementTimeoutSec * 1000 : undefined;
|
|
228
|
+
const result = await env.exec({ mode: "read", command: "wren", args, cwd: projectDir, ...(timeoutMs !== undefined ? { timeoutMs } : {}) }, policy);
|
|
229
|
+
// A mid-run disappearance of the `wren` binary (e.g.
|
|
230
|
+
// uninstalled/moved after `resolveWrenBinary`'s preflight passed)
|
|
231
|
+
// surfaces here as `ExecResult.notFound`, not just a nonzero
|
|
232
|
+
// `exitCode` — checked first so it throws the specific
|
|
233
|
+
// `WrenBinaryNotFoundError` instead of the generic
|
|
234
|
+
// `WrenQueryExecutionError`, which the repair-fold loop treats as a
|
|
235
|
+
// retryable tool error even though retrying can never help.
|
|
236
|
+
if (result.notFound) {
|
|
237
|
+
throw new WrenBinaryNotFoundError(`"wren" was not found while running this query (spawn ENOENT)`);
|
|
238
|
+
}
|
|
239
|
+
if (result.exitCode !== 0) {
|
|
240
|
+
throw new WrenQueryExecutionError(input.sql, result.exitCode, result.stderr);
|
|
241
|
+
}
|
|
242
|
+
return parseWrenJsonl(result.stdout);
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
function parseWrenJsonl(stdout) {
|
|
247
|
+
const rows = stdout
|
|
248
|
+
.split("\n")
|
|
249
|
+
.map((line) => line.trim())
|
|
250
|
+
.filter((line) => line.length > 0)
|
|
251
|
+
.map((line) => JSON.parse(line));
|
|
252
|
+
const columns = rows.length > 0 ? Object.keys(rows[0]) : [];
|
|
253
|
+
return { columns, rows };
|
|
254
|
+
}
|
|
255
|
+
export const SEMANTIC_INTROSPECT_TOOL_NAME = "semantic_introspect";
|
|
256
|
+
/**
|
|
257
|
+
* `semantic_introspect` (native realization): introspects the bound
|
|
258
|
+
* semantic layer via the real `wren` CLI — `wren context show -o json` —
|
|
259
|
+
* executed via the injected `ExecutionEnv` with `cwd: projectDir`, the same
|
|
260
|
+
* shape as {@link createWrenQueryTool}. Declared `mode: "read"` for the same
|
|
261
|
+
* reason `query` is: `explore_model` always carries a locked
|
|
262
|
+
* `read_only_execution` guardrail, and this must never be blocked by it.
|
|
263
|
+
*
|
|
264
|
+
* Unlike `query`, `wren context show -o json` prints a SINGLE JSON object
|
|
265
|
+
* (not JSONL), so this parses with a plain `JSON.parse` rather than
|
|
266
|
+
* `parseWrenJsonl`, and takes no per-call input (introspection covers the
|
|
267
|
+
* whole project, not one statement) — `inputSchema` is deliberately empty.
|
|
268
|
+
*
|
|
269
|
+
* NOTE: `wren cube list` enrichment (cubes/metrics, "if available" per the
|
|
270
|
+
* component's step prompt) is intentionally deferred — it prints plain,
|
|
271
|
+
* non-JSON text (e.g. "No cubes defined.") rather than structured output,
|
|
272
|
+
* which would need its own ad hoc parser for a v1 that's optional per the
|
|
273
|
+
* step prompt anyway.
|
|
274
|
+
*/
|
|
275
|
+
export function createWrenSemanticIntrospectTool(options) {
|
|
276
|
+
const { env, policy, projectDir } = options;
|
|
277
|
+
return tool({
|
|
278
|
+
description: "Introspect the wren semantic layer's models, columns, relationships, and metrics via the native `wren` CLI.",
|
|
279
|
+
inputSchema: z.object({}),
|
|
280
|
+
execute: async () => {
|
|
281
|
+
const timeoutMs = policy.statementTimeoutSec !== undefined ? policy.statementTimeoutSec * 1000 : undefined;
|
|
282
|
+
const result = await env.exec({
|
|
283
|
+
mode: "read",
|
|
284
|
+
command: "wren",
|
|
285
|
+
args: ["context", "show", "-o", "json"],
|
|
286
|
+
cwd: projectDir,
|
|
287
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
288
|
+
}, policy);
|
|
289
|
+
// Same reasoning as `query`: a mid-run disappearance of the
|
|
290
|
+
// `wren` binary surfaces as `notFound`, checked first so it throws the
|
|
291
|
+
// specific `WrenBinaryNotFoundError` rather than the generic
|
|
292
|
+
// `WrenIntrospectExecutionError`.
|
|
293
|
+
if (result.notFound) {
|
|
294
|
+
throw new WrenBinaryNotFoundError(`"wren" was not found while introspecting the semantic layer (spawn ENOENT)`);
|
|
295
|
+
}
|
|
296
|
+
if (result.exitCode !== 0) {
|
|
297
|
+
throw new WrenIntrospectExecutionError(`exit code ${result.exitCode}\n${result.stderr}`);
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
return JSON.parse(result.stdout);
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
throw new WrenIntrospectExecutionError(`could not parse "wren context show -o json" output as JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* A native-tool registry carrying both the built-in `write_artifact` tool
|
|
310
|
+
* (bound to `options.env`/`options.policy`, same as
|
|
311
|
+
* `createDefaultNativeToolRegistry`) and the real `query` tool wired to
|
|
312
|
+
* `options.projectDir`. Pass this as `RunAgentContext.nativeTools` to
|
|
313
|
+
* resolve an `answer_query` agent whose `query` tool declares
|
|
314
|
+
* `source: "native"` to a real `wren` CLI invocation instead of an MCP
|
|
315
|
+
* server.
|
|
316
|
+
*/
|
|
317
|
+
export function createWrenNativeToolRegistry(options) {
|
|
318
|
+
const registry = createDefaultNativeToolRegistry(options.env, options.policy);
|
|
319
|
+
registry.register(WREN_QUERY_TOOL_NAME, () => createWrenQueryTool(options));
|
|
320
|
+
registry.register(SEMANTIC_INTROSPECT_TOOL_NAME, () => createWrenSemanticIntrospectTool(options));
|
|
321
|
+
return registry;
|
|
322
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** A tool declaration's `source` field, parsed into its harness-meaningful shape. */
|
|
2
|
+
export type ParsedToolSource = {
|
|
3
|
+
readonly kind: "native";
|
|
4
|
+
} | {
|
|
5
|
+
readonly kind: "mcp";
|
|
6
|
+
readonly server: string;
|
|
7
|
+
readonly toolName: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Parses an `agent.tools[].source` string. Two shapes are recognized:
|
|
11
|
+
* `"native"` and `"mcp:<server>/<name>"` (e.g. `"mcp:wren/query"`). Anything
|
|
12
|
+
* else is a load-time error — bundle producers only ever emit these two.
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseToolSource(source: string): ParsedToolSource;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { UnsupportedToolSourceError } from "./errors.js";
|
|
2
|
+
const MCP_SOURCE_PATTERN = /^mcp:([^/]+)\/(.+)$/;
|
|
3
|
+
/**
|
|
4
|
+
* Parses an `agent.tools[].source` string. Two shapes are recognized:
|
|
5
|
+
* `"native"` and `"mcp:<server>/<name>"` (e.g. `"mcp:wren/query"`). Anything
|
|
6
|
+
* else is a load-time error — bundle producers only ever emit these two.
|
|
7
|
+
*/
|
|
8
|
+
export function parseToolSource(source) {
|
|
9
|
+
if (source === "native") {
|
|
10
|
+
return { kind: "native" };
|
|
11
|
+
}
|
|
12
|
+
const match = MCP_SOURCE_PATTERN.exec(source);
|
|
13
|
+
if (match) {
|
|
14
|
+
const [, server, toolName] = match;
|
|
15
|
+
return { kind: "mcp", server: server, toolName: toolName };
|
|
16
|
+
}
|
|
17
|
+
throw new UnsupportedToolSourceError(source);
|
|
18
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preflights that a `wren` binary is resolvable on `PATH` (`wren --version`
|
|
3
|
+
* exits 0) — mirrors `resolveWarbleBinary`'s PATH tier
|
|
4
|
+
* (`../compile/resolve-binary.js`) for the `warble` binary. `wren` is a
|
|
5
|
+
* separately-installed CLI, not a sibling repo this package sits beside, so
|
|
6
|
+
* there is no explicit-override or sibling-repo-build tier here — just the
|
|
7
|
+
* one PATH check. Throws {@link WrenBinaryNotFoundError} (loud-fail) so a
|
|
8
|
+
* missing `wren` fails clearly before a native-tool run even starts, instead
|
|
9
|
+
* of surfacing later as an opaque exec failure once the tool loop is already
|
|
10
|
+
* mid-run (see `ExecResult.notFound` for the mid-run counterpart).
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveWrenBinary(): Promise<void>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { WrenBinaryNotFoundError } from "./errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* Preflights that a `wren` binary is resolvable on `PATH` (`wren --version`
|
|
5
|
+
* exits 0) — mirrors `resolveWarbleBinary`'s PATH tier
|
|
6
|
+
* (`../compile/resolve-binary.js`) for the `warble` binary. `wren` is a
|
|
7
|
+
* separately-installed CLI, not a sibling repo this package sits beside, so
|
|
8
|
+
* there is no explicit-override or sibling-repo-build tier here — just the
|
|
9
|
+
* one PATH check. Throws {@link WrenBinaryNotFoundError} (loud-fail) so a
|
|
10
|
+
* missing `wren` fails clearly before a native-tool run even starts, instead
|
|
11
|
+
* of surfacing later as an opaque exec failure once the tool loop is already
|
|
12
|
+
* mid-run (see `ExecResult.notFound` for the mid-run counterpart).
|
|
13
|
+
*/
|
|
14
|
+
export async function resolveWrenBinary() {
|
|
15
|
+
const onPath = await new Promise((resolve) => {
|
|
16
|
+
execFile("wren", ["--version"], (error) => resolve(!error));
|
|
17
|
+
});
|
|
18
|
+
if (!onPath) {
|
|
19
|
+
throw new WrenBinaryNotFoundError(`not found on PATH (tried running "wren --version")`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ToolSet } from "ai";
|
|
2
|
+
import type { Agent } from "../bundle/schema.js";
|
|
3
|
+
import { type ExecutionEnv, type ExecutionPolicy } from "../exec/index.js";
|
|
4
|
+
import { type McpServerConfigMap } from "./mcp.js";
|
|
5
|
+
import { type NativeToolRegistry } from "./native.js";
|
|
6
|
+
export interface ResolveToolsContext {
|
|
7
|
+
/**
|
|
8
|
+
* Defaults to a registry built from `executionEnv`/`enforcementPolicy`
|
|
9
|
+
* (built-in `write_artifact` tool only). Supplying this bypasses both of
|
|
10
|
+
* those fields entirely.
|
|
11
|
+
*/
|
|
12
|
+
readonly nativeTools?: NativeToolRegistry;
|
|
13
|
+
/** Runtime-injected MCP server connection config, keyed by server id. Defaults to `{}`. */
|
|
14
|
+
readonly mcpServers?: McpServerConfigMap;
|
|
15
|
+
/** Backend native tools execute side effects through. Defaults to `createLocalExecutionEnv()`. */
|
|
16
|
+
readonly executionEnv?: ExecutionEnv;
|
|
17
|
+
/**
|
|
18
|
+
* The guardrail-derived policy passed to every native-tool call. Defaults
|
|
19
|
+
* to `{ readOnly: false }` (no scope, no allowlist) — the secure-by-default
|
|
20
|
+
* fallback: without a real `deriveEnforcement(agent)` result threaded in
|
|
21
|
+
* (as `runAgent` does), `write_artifact` rejects every call.
|
|
22
|
+
*/
|
|
23
|
+
readonly enforcementPolicy?: ExecutionPolicy;
|
|
24
|
+
}
|
|
25
|
+
export interface ResolvedTools {
|
|
26
|
+
/** The AI SDK tool set the loop executor consumes as `ExecuteAgentContext.tools`. */
|
|
27
|
+
readonly tools: ToolSet;
|
|
28
|
+
/** Shuts down every MCP client this resolution opened. Always safe to call once. */
|
|
29
|
+
close(): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Turns `agent.tools[]` into an AI SDK `ToolSet`, connecting whatever MCP
|
|
33
|
+
* servers the agent's tools declare and discovering their tools. This is
|
|
34
|
+
* the tool-level "second door" (mirroring the capability gate):
|
|
35
|
+
* every declared tool is resolved and validated *before* the loop executor
|
|
36
|
+
* ever runs a step, so a misconfigured or drifted tool fails loud at
|
|
37
|
+
* session start rather than surfacing as an opaque tool-call error mid-run.
|
|
38
|
+
*
|
|
39
|
+
* Fail-fast is two passes:
|
|
40
|
+
* 1. Before connecting anything: unknown `native` names
|
|
41
|
+
* ({@link UnknownNativeToolError}) and MCP server ids with no entry in
|
|
42
|
+
* `mcpServers` ({@link McpServerNotConfiguredError}).
|
|
43
|
+
* 2. After connecting + discovering each referenced server's tools: any
|
|
44
|
+
* declared `mcp:<server>/<name>` whose server doesn't actually expose
|
|
45
|
+
* `<name>` ({@link McpToolNotExposedError}).
|
|
46
|
+
*
|
|
47
|
+
* On any failure (including a later tool in the list) every MCP client this
|
|
48
|
+
* call opened is closed before the error propagates — resolution never
|
|
49
|
+
* leaks a connected client.
|
|
50
|
+
*/
|
|
51
|
+
export declare function resolveTools(agent: Agent, ctx?: ResolveToolsContext): Promise<ResolvedTools>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { createLocalExecutionEnv } from "../exec/index.js";
|
|
2
|
+
import { McpServerNotConfiguredError, McpToolNotExposedError, UnknownNativeToolError } from "./errors.js";
|
|
3
|
+
import { connectMcpServer } from "./mcp.js";
|
|
4
|
+
import { createDefaultNativeToolRegistry } from "./native.js";
|
|
5
|
+
import { parseToolSource } from "./parse.js";
|
|
6
|
+
/**
|
|
7
|
+
* Turns `agent.tools[]` into an AI SDK `ToolSet`, connecting whatever MCP
|
|
8
|
+
* servers the agent's tools declare and discovering their tools. This is
|
|
9
|
+
* the tool-level "second door" (mirroring the capability gate):
|
|
10
|
+
* every declared tool is resolved and validated *before* the loop executor
|
|
11
|
+
* ever runs a step, so a misconfigured or drifted tool fails loud at
|
|
12
|
+
* session start rather than surfacing as an opaque tool-call error mid-run.
|
|
13
|
+
*
|
|
14
|
+
* Fail-fast is two passes:
|
|
15
|
+
* 1. Before connecting anything: unknown `native` names
|
|
16
|
+
* ({@link UnknownNativeToolError}) and MCP server ids with no entry in
|
|
17
|
+
* `mcpServers` ({@link McpServerNotConfiguredError}).
|
|
18
|
+
* 2. After connecting + discovering each referenced server's tools: any
|
|
19
|
+
* declared `mcp:<server>/<name>` whose server doesn't actually expose
|
|
20
|
+
* `<name>` ({@link McpToolNotExposedError}).
|
|
21
|
+
*
|
|
22
|
+
* On any failure (including a later tool in the list) every MCP client this
|
|
23
|
+
* call opened is closed before the error propagates — resolution never
|
|
24
|
+
* leaks a connected client.
|
|
25
|
+
*/
|
|
26
|
+
export async function resolveTools(agent, ctx = {}) {
|
|
27
|
+
const executionEnv = ctx.executionEnv ?? createLocalExecutionEnv();
|
|
28
|
+
const enforcementPolicy = ctx.enforcementPolicy ?? { readOnly: false };
|
|
29
|
+
const nativeTools = ctx.nativeTools ?? createDefaultNativeToolRegistry(executionEnv, enforcementPolicy);
|
|
30
|
+
const mcpServers = ctx.mcpServers ?? {};
|
|
31
|
+
const specs = agent.tools.map((toolSpec) => ({
|
|
32
|
+
toolSpec,
|
|
33
|
+
source: parseToolSource(toolSpec.source),
|
|
34
|
+
}));
|
|
35
|
+
const serverIds = new Set();
|
|
36
|
+
for (const { toolSpec, source } of specs) {
|
|
37
|
+
if (source.kind === "native") {
|
|
38
|
+
if (!nativeTools.has(toolSpec.name)) {
|
|
39
|
+
throw new UnknownNativeToolError(toolSpec.name);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (!mcpServers[source.server]) {
|
|
43
|
+
throw new McpServerNotConfiguredError(source.server);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
serverIds.add(source.server);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const clients = new Map();
|
|
50
|
+
const discoveredByServer = new Map();
|
|
51
|
+
async function closeAll() {
|
|
52
|
+
await Promise.all([...clients.values()].map((client) => client.close().catch(() => undefined)));
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
for (const serverId of serverIds) {
|
|
56
|
+
const client = await connectMcpServer(mcpServers[serverId]);
|
|
57
|
+
clients.set(serverId, client);
|
|
58
|
+
discoveredByServer.set(serverId, (await client.tools()));
|
|
59
|
+
}
|
|
60
|
+
const tools = {};
|
|
61
|
+
for (const { toolSpec, source } of specs) {
|
|
62
|
+
if (source.kind === "native") {
|
|
63
|
+
tools[toolSpec.name] = nativeTools.create(toolSpec.name);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const serverTools = discoveredByServer.get(source.server);
|
|
67
|
+
const discoveredTool = serverTools[source.toolName];
|
|
68
|
+
if (!discoveredTool) {
|
|
69
|
+
throw new McpToolNotExposedError(source.server, source.toolName);
|
|
70
|
+
}
|
|
71
|
+
tools[toolSpec.name] = discoveredTool;
|
|
72
|
+
}
|
|
73
|
+
return { tools, close: closeAll };
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
await closeAll();
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { type Tool } from "ai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { ExecutionEnv, ExecutionPolicy } from "../exec/index.js";
|
|
4
|
+
export declare const SETUP_EXECUTION_TOOL_NAME = "setup_execution";
|
|
5
|
+
export interface SetupExecutionToolOptions {
|
|
6
|
+
/** Backend the tool's side effects route through — never a direct child_process/fs call. */
|
|
7
|
+
readonly env: ExecutionEnv;
|
|
8
|
+
readonly policy: ExecutionPolicy;
|
|
9
|
+
/**
|
|
10
|
+
* The setup workspace root — the default (and outer bound) for the `exec`
|
|
11
|
+
* action's subprocess `cwd`, and the `write` action's scope boundary.
|
|
12
|
+
* Always caller-supplied and fixed as the ceiling; the `exec` action MAY
|
|
13
|
+
* narrow its own cwd via an optional per-call `cwd` field (see
|
|
14
|
+
* `resolveExecCwd`), but that value is contained by the same two-phase
|
|
15
|
+
* check as the write path and can never resolve outside this root — a
|
|
16
|
+
* command/path can narrow its own working directory within the root, but
|
|
17
|
+
* never broaden either its cwd or its write scope beyond it.
|
|
18
|
+
*/
|
|
19
|
+
readonly workspaceRoot: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A single flat top-level object, NOT a `z.discriminatedUnion` — a
|
|
23
|
+
* discriminated union serializes (via `z.toJSONSchema`) to a schema whose
|
|
24
|
+
* TOP level is `{"oneOf": [...]}` with no top-level `"type": "object"`, which
|
|
25
|
+
* both OpenAI's function-calling API and Anthropic's `input_schema` reject
|
|
26
|
+
* outright (confirmed against a real OpenAI `gpt-4.1` call; see
|
|
27
|
+
* `test/native-tool-schema-shape.test.ts` for the offline, no-API-key-spent
|
|
28
|
+
* regression coverage). `command`/`path`/`content` are therefore all
|
|
29
|
+
* optional here — the per-action required-field combination a discriminated
|
|
30
|
+
* union would enforce at the schema level is instead checked in
|
|
31
|
+
* `createSetupExecutionTool`'s `execute`, before any side effect.
|
|
32
|
+
*
|
|
33
|
+
* Flattening the schema drops the model-facing pairing a discriminated union
|
|
34
|
+
* used to encode for free (`exec` needs `command`; `write` needs `path` +
|
|
35
|
+
* `content`) — nothing about the field TYPES says which action requires
|
|
36
|
+
* which field. Since in-process has no turn budget (unlike dispatched's fixed
|
|
37
|
+
* `--max-turns` cap on setup), an under-described schema makes that pairing
|
|
38
|
+
* discoverable only by the model guessing wrong and reading a thrown
|
|
39
|
+
* `SetupExecutionInputError` back — unbounded trial-and-error on the user's
|
|
40
|
+
* own API spend. The `.describe()` calls below and the tool `description` in
|
|
41
|
+
* `createSetupExecutionTool` spell the pairing out explicitly so the model
|
|
42
|
+
* can satisfy it on the first call; the `execute`-time checks remain as the
|
|
43
|
+
* enforced (not just described) guarantee.
|
|
44
|
+
*/
|
|
45
|
+
export declare const setupExecutionInputSchema: z.ZodObject<{
|
|
46
|
+
action: z.ZodEnum<{
|
|
47
|
+
exec: "exec";
|
|
48
|
+
write: "write";
|
|
49
|
+
}>;
|
|
50
|
+
command: z.ZodOptional<z.ZodString>;
|
|
51
|
+
cwd: z.ZodOptional<z.ZodString>;
|
|
52
|
+
path: z.ZodOptional<z.ZodString>;
|
|
53
|
+
content: z.ZodOptional<z.ZodString>;
|
|
54
|
+
}, z.core.$strip>;
|
|
55
|
+
export type SetupExecutionInput = z.infer<typeof setupExecutionInputSchema>;
|
|
56
|
+
export type SetupExecutionResult = {
|
|
57
|
+
exitCode: number;
|
|
58
|
+
stdout: string;
|
|
59
|
+
stderr: string;
|
|
60
|
+
notFound?: boolean;
|
|
61
|
+
timedOut?: boolean;
|
|
62
|
+
maxBufferExceeded?: boolean;
|
|
63
|
+
} | {
|
|
64
|
+
written: true;
|
|
65
|
+
path: string;
|
|
66
|
+
bytes: number;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Builds the combined `setup_execution` tool: `action: "exec"` runs a shell
|
|
70
|
+
* command via `/bin/sh -c`, cwd defaulting to `workspaceRoot` but narrowable
|
|
71
|
+
* per-call via the optional `cwd` field (see {@link resolveExecCwd} — always
|
|
72
|
+
* contained within `workspaceRoot`, never able to escape it), broadened
|
|
73
|
+
* beyond a single fixed binary the way the dispatcher-side `setup_execution`
|
|
74
|
+
* guardrail broadens Bash — but still subject to the same denylist, checked
|
|
75
|
+
* first, never relaxed. `action: "write"` writes a file relative to the same
|
|
76
|
+
* optional cwd (workspaceRoot by default), scoped to `workspaceRoot` by the
|
|
77
|
+
* two-phase containment check above, then delegates the actual write to
|
|
78
|
+
* `env.writeFile` (which applies its OWN, nominal-only
|
|
79
|
+
* `policy.artifactWriteScope` check — this tool's pre-flight check is
|
|
80
|
+
* additional, not a replacement, since `env.writeFile` has no realpath
|
|
81
|
+
* re-check of its own; see `harness/exec/local.ts`).
|
|
82
|
+
*/
|
|
83
|
+
export declare function executeSetupExecution(input: SetupExecutionInput, options: SetupExecutionToolOptions): Promise<SetupExecutionResult>;
|
|
84
|
+
export declare function createSetupExecutionTool(options: SetupExecutionToolOptions): Tool;
|