@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,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compiles the Setup runtime form into the binding actually consumed by the
|
|
3
|
+
* harness. Settings deliberately contain no credential values; this module
|
|
4
|
+
* reads an API key only while constructing an in-memory in-process adapter spec.
|
|
5
|
+
*/
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { resolveHubDir, resolveWarbleBinary, runWarble } from "../harness/index.js";
|
|
12
|
+
export class RuntimeBindingError extends Error {
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The Claude Agent SDK accepts concrete model ids for its top-level driver,
|
|
16
|
+
* but its per-step `agents[].model` surface is deliberately narrower. Keep
|
|
17
|
+
* this product-side contract explicit so an invalid saved Runtime is rejected
|
|
18
|
+
* before a user turn reaches the dispatcher.
|
|
19
|
+
*/
|
|
20
|
+
export const CLAUDE_AGENT_SDK_PER_STEP_MODELS = ["sonnet", "opus", "haiku", "inherit"];
|
|
21
|
+
const CLAUDE_AGENT_SDK_PER_STEP_MODEL_SET = new Set(CLAUDE_AGENT_SDK_PER_STEP_MODELS);
|
|
22
|
+
function nonEmpty(value) {
|
|
23
|
+
const trimmed = value?.trim();
|
|
24
|
+
return trimmed ? trimmed : undefined;
|
|
25
|
+
}
|
|
26
|
+
function absoluteHttpUrl(value) {
|
|
27
|
+
try {
|
|
28
|
+
const protocol = new URL(value).protocol;
|
|
29
|
+
return protocol === "http:" || protocol === "https:";
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function bindingByTier(settings) {
|
|
36
|
+
const bindings = new Map();
|
|
37
|
+
for (const binding of settings.tierModels) {
|
|
38
|
+
if (!nonEmpty(binding.tier))
|
|
39
|
+
throw new RuntimeBindingError("A tier binding must name a tier.");
|
|
40
|
+
if (bindings.has(binding.tier))
|
|
41
|
+
throw new RuntimeBindingError(`Duplicate tier binding: ${binding.tier}`);
|
|
42
|
+
bindings.set(binding.tier, binding);
|
|
43
|
+
}
|
|
44
|
+
return bindings;
|
|
45
|
+
}
|
|
46
|
+
/** The old single Model field is the default for every tier without a model override. */
|
|
47
|
+
export function effectiveTierModel(binding, settings) {
|
|
48
|
+
return nonEmpty(binding.model) ?? nonEmpty(settings.apiKeyModel);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Returns a browser-safe correction for an already persisted Runtime. This is
|
|
52
|
+
* intentionally narrower than catalog validation: provider catalog entries
|
|
53
|
+
* remain free-form wherever their dispatcher accepts concrete model ids. Only
|
|
54
|
+
* Claude's SDK subagent tier field has the closed alias union below.
|
|
55
|
+
*/
|
|
56
|
+
export function runtimeSettingsCorrection(settings) {
|
|
57
|
+
if (settings.authMode !== "subscription" || settings.subscriptionProvider !== "claude")
|
|
58
|
+
return undefined;
|
|
59
|
+
for (const binding of settings.tierModels) {
|
|
60
|
+
const model = effectiveTierModel(binding, settings);
|
|
61
|
+
if (model && !CLAUDE_AGENT_SDK_PER_STEP_MODEL_SET.has(model)) {
|
|
62
|
+
return `Runtime needs correction in Setup: Claude per-step tier "${binding.tier}" must use one of ${CLAUDE_AGENT_SDK_PER_STEP_MODELS.join(", ")}.`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
/** Throws the same actionable error at every persisted-runtime dispatch boundary. */
|
|
68
|
+
export function assertRuntimeSettingsDispatchable(settings) {
|
|
69
|
+
const correction = runtimeSettingsCorrection(settings);
|
|
70
|
+
if (correction)
|
|
71
|
+
throw new RuntimeBindingError(correction);
|
|
72
|
+
}
|
|
73
|
+
function requireModel(tier, binding, settings) {
|
|
74
|
+
const model = effectiveTierModel(binding, settings);
|
|
75
|
+
if (!model)
|
|
76
|
+
throw new RuntimeBindingError(`Tier "${tier}" needs a model override or the default Model.`);
|
|
77
|
+
if (absoluteHttpUrl(model)) {
|
|
78
|
+
throw new RuntimeBindingError(`Tier "${tier}": Model must be a model name, not a URL — got "${model}". Check that Model and Base URL weren't swapped.`);
|
|
79
|
+
}
|
|
80
|
+
return model;
|
|
81
|
+
}
|
|
82
|
+
function requireBaseUrl(tier, binding, settings) {
|
|
83
|
+
// A row without an explicit adapter is using the legacy/default runtime;
|
|
84
|
+
// retain its single Base URL as the default just as Model is a default.
|
|
85
|
+
const baseURL = nonEmpty(binding.baseURL) ?? (binding.adapter === undefined ? nonEmpty(settings.apiKeyBaseURL) : undefined);
|
|
86
|
+
const adapter = effectiveAdapter(binding, settings);
|
|
87
|
+
if (!baseURL)
|
|
88
|
+
throw new RuntimeBindingError(`A Base URL is required for the ${adapter} adapter on tier "${tier}" — there is no default.`);
|
|
89
|
+
if (!absoluteHttpUrl(baseURL)) {
|
|
90
|
+
throw new RuntimeBindingError(`Tier "${tier}": Base URL must be an absolute http(s) URL — got "${baseURL}". Check that Model and Base URL weren't swapped.`);
|
|
91
|
+
}
|
|
92
|
+
return baseURL;
|
|
93
|
+
}
|
|
94
|
+
function effectiveAdapter(binding, settings) {
|
|
95
|
+
if (binding.adapter)
|
|
96
|
+
return binding.adapter;
|
|
97
|
+
if (settings.authMode === "local")
|
|
98
|
+
return "local";
|
|
99
|
+
return settings.apiKeyAdapter ?? "anthropic";
|
|
100
|
+
}
|
|
101
|
+
/** Credential env vars used by the materialized in-process rows (local rows need none). */
|
|
102
|
+
export function requiredInProcessCredentialEnvVars(settings) {
|
|
103
|
+
const required = new Set();
|
|
104
|
+
for (const binding of settings.tierModels) {
|
|
105
|
+
const adapter = effectiveAdapter(binding, settings);
|
|
106
|
+
if (adapter === "anthropic")
|
|
107
|
+
required.add("ANTHROPIC_API_KEY");
|
|
108
|
+
if (adapter === "openai-compatible")
|
|
109
|
+
required.add("OPENAI_API_KEY");
|
|
110
|
+
}
|
|
111
|
+
return [...required];
|
|
112
|
+
}
|
|
113
|
+
/** Validates the complete bundle map before Store/auth/live-route mutation. */
|
|
114
|
+
export function validateRuntimeTierBindings(settings, bundleTiers) {
|
|
115
|
+
const expected = new Set(bundleTiers);
|
|
116
|
+
if (expected.size === 0)
|
|
117
|
+
throw new RuntimeBindingError("The compiled bundle declares no step tiers.");
|
|
118
|
+
const bindings = bindingByTier(settings);
|
|
119
|
+
const missing = bundleTiers.filter((tier) => !bindings.has(tier));
|
|
120
|
+
const extra = [...bindings.keys()].filter((tier) => !expected.has(tier));
|
|
121
|
+
if (missing.length || extra.length) {
|
|
122
|
+
const parts = [
|
|
123
|
+
...(missing.length ? [`missing: ${missing.join(", ")}`] : []),
|
|
124
|
+
...(extra.length ? [`unknown: ${extra.join(", ")}`] : []),
|
|
125
|
+
];
|
|
126
|
+
throw new RuntimeBindingError(`Tier bindings must exactly match the compiled bundle (${parts.join("; ")}).`);
|
|
127
|
+
}
|
|
128
|
+
for (const tier of bundleTiers) {
|
|
129
|
+
const binding = bindings.get(tier);
|
|
130
|
+
requireModel(tier, binding, settings);
|
|
131
|
+
const adapter = effectiveAdapter(binding, settings);
|
|
132
|
+
if (adapter === "openai-compatible" || adapter === "local")
|
|
133
|
+
requireBaseUrl(tier, binding, settings);
|
|
134
|
+
}
|
|
135
|
+
if (settings.authMode === "subscription" && !nonEmpty(settings.subscriptionDriverModel)) {
|
|
136
|
+
throw new RuntimeBindingError("A subscription dispatcher driver model is required.");
|
|
137
|
+
}
|
|
138
|
+
assertRuntimeSettingsDispatchable(settings);
|
|
139
|
+
}
|
|
140
|
+
function inProcessSpec(tier, binding, settings) {
|
|
141
|
+
const model = requireModel(tier, binding, settings);
|
|
142
|
+
switch (effectiveAdapter(binding, settings)) {
|
|
143
|
+
case "anthropic":
|
|
144
|
+
// The Anthropic adapter reads ANTHROPIC_API_KEY from its environment.
|
|
145
|
+
return { adapter: "anthropic", config: { model } };
|
|
146
|
+
case "openai-compatible":
|
|
147
|
+
// The transient AdapterSpec may carry the env-backed credential, but the
|
|
148
|
+
// persisted settings and generated YAML never do.
|
|
149
|
+
return { adapter: "openai-compatible", config: { model, baseURL: requireBaseUrl(tier, binding, settings), apiKey: process.env["OPENAI_API_KEY"] ?? "" } };
|
|
150
|
+
case "local":
|
|
151
|
+
return { adapter: "openai-compatible", config: { model, baseURL: requireBaseUrl(tier, binding, settings) } };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function dispatchedTierYaml(tier, binding, settings) {
|
|
155
|
+
const model = requireModel(tier, binding, settings);
|
|
156
|
+
const adapter = effectiveAdapter(binding, settings);
|
|
157
|
+
const key = JSON.stringify(tier);
|
|
158
|
+
if (adapter === "anthropic")
|
|
159
|
+
return [` ${key}: ${JSON.stringify(model)}`];
|
|
160
|
+
return [
|
|
161
|
+
` ${key}:`,
|
|
162
|
+
" provider: openai_compat",
|
|
163
|
+
` endpoint: ${JSON.stringify(requireBaseUrl(tier, binding, settings))}`,
|
|
164
|
+
` model: ${JSON.stringify(model)}`,
|
|
165
|
+
];
|
|
166
|
+
}
|
|
167
|
+
/** Generates the dispatcher-owned models config without ever serializing an API key. */
|
|
168
|
+
export function writeClaudeModelsConfig(settings, bundleTiers) {
|
|
169
|
+
const bindings = bindingByTier(settings);
|
|
170
|
+
const lines = ["tiers:"];
|
|
171
|
+
for (const tier of bundleTiers)
|
|
172
|
+
lines.push(...dispatchedTierYaml(tier, bindings.get(tier), settings));
|
|
173
|
+
// `orchestrator` is a dispatcher driver entry, deliberately separate from
|
|
174
|
+
// the bundle tier rows that the user edits.
|
|
175
|
+
lines.push(` "orchestrator": ${JSON.stringify(nonEmpty(settings.subscriptionDriverModel) ?? "")}`, "");
|
|
176
|
+
const contents = lines.join("\n");
|
|
177
|
+
const dir = path.join(tmpdir(), "wren-harness-runtime");
|
|
178
|
+
mkdirSync(dir, { recursive: true });
|
|
179
|
+
const filename = `models-${createHash("sha256").update(contents).digest("hex").slice(0, 16)}.yaml`;
|
|
180
|
+
const output = path.join(dir, filename);
|
|
181
|
+
writeFileSync(output, contents, { mode: 0o600 });
|
|
182
|
+
return output;
|
|
183
|
+
}
|
|
184
|
+
export function codexModelsForRuntime(settings) {
|
|
185
|
+
const bindings = bindingByTier(settings);
|
|
186
|
+
const model = (tier) => requireModel(tier, bindings.get(tier) ?? { tier }, settings);
|
|
187
|
+
return {
|
|
188
|
+
orchestrator: nonEmpty(settings.subscriptionDriverModel) ?? "",
|
|
189
|
+
cheap: model("cheap"),
|
|
190
|
+
strong: model("strong"),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/** Materializes the already-validated live binding for the selected runtime. */
|
|
194
|
+
export function materializeRuntimeRouteOptions(settings, authChoice) {
|
|
195
|
+
const tiers = settings.tierModels.map((binding) => binding.tier);
|
|
196
|
+
if (authChoice.mode === "subscription") {
|
|
197
|
+
if (authChoice.provider === "codex")
|
|
198
|
+
return { codexModels: codexModelsForRuntime(settings) };
|
|
199
|
+
return { modelsConfig: writeClaudeModelsConfig(settings, tiers) };
|
|
200
|
+
}
|
|
201
|
+
const bindings = bindingByTier(settings);
|
|
202
|
+
return {
|
|
203
|
+
tierBinding: Object.fromEntries(tiers.map((tier) => [tier, inProcessSpec(tier, bindings.get(tier), settings)])),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/** Bundle-owned tier names, in first-seen order. */
|
|
207
|
+
export function collectBundleTierNames(bundle) {
|
|
208
|
+
const seen = new Set();
|
|
209
|
+
for (const agent of bundle.agents)
|
|
210
|
+
for (const step of agent.steps)
|
|
211
|
+
seen.add(step.tier);
|
|
212
|
+
return [...seen];
|
|
213
|
+
}
|
|
214
|
+
/** Reads the tier contract directly from a compiled Warble IR document. */
|
|
215
|
+
export function collectIrTierNames(ir) {
|
|
216
|
+
if (ir === null || typeof ir !== "object" || !("components" in ir) || !Array.isArray(ir.components)) {
|
|
217
|
+
throw new RuntimeBindingError("Compiled profile IR must contain a components array.");
|
|
218
|
+
}
|
|
219
|
+
const seen = new Set();
|
|
220
|
+
for (const component of ir.components) {
|
|
221
|
+
if (component === null || typeof component !== "object" || !("llm_calls" in component) || !Array.isArray(component.llm_calls)) {
|
|
222
|
+
throw new RuntimeBindingError("Every compiled profile component must contain an llm_calls array.");
|
|
223
|
+
}
|
|
224
|
+
for (const call of component.llm_calls) {
|
|
225
|
+
if (call === null || typeof call !== "object" || !("tier" in call) || typeof call.tier !== "string" || !call.tier.trim()) {
|
|
226
|
+
throw new RuntimeBindingError("Every compiled profile llm_call must name a non-empty tier.");
|
|
227
|
+
}
|
|
228
|
+
seen.add(call.tier.trim());
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (seen.size === 0)
|
|
232
|
+
throw new RuntimeBindingError("The compiled profile declares no llm_call tiers.");
|
|
233
|
+
return [...seen];
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Compiles the canonical profile source without rebinding its context and
|
|
237
|
+
* extracts its tier contract from IR. This is intentionally independent of
|
|
238
|
+
* auth choice and of any user project: the Setup form must discover the rows
|
|
239
|
+
* before either exists.
|
|
240
|
+
*/
|
|
241
|
+
export async function compileUnboundProfileTierNames(options) {
|
|
242
|
+
const workDir = await mkdtemp(path.join(tmpdir(), "wren-harness-runtime-tiers-"));
|
|
243
|
+
try {
|
|
244
|
+
const irPath = path.join(workDir, "ir.json");
|
|
245
|
+
const warbleBin = await resolveWarbleBinary(options.warbleBin);
|
|
246
|
+
// Same reason as the compile pipeline's own invocation (see `resolveHubDir`): name the Hub root
|
|
247
|
+
// the resolved binary belongs to, so a `warble` that isn't a checkout-local build can't
|
|
248
|
+
// silently resolve components against the wrong library — or none.
|
|
249
|
+
const hubDir = resolveHubDir(warbleBin);
|
|
250
|
+
const hubDirArgs = hubDir !== undefined ? ["--hub-dir", hubDir] : [];
|
|
251
|
+
await runWarble(warbleBin, ["compile", path.resolve(options.profileSource), "-o", irPath, ...hubDirArgs]);
|
|
252
|
+
const ir = JSON.parse(await readFile(irPath, "utf8"));
|
|
253
|
+
return collectIrTierNames(ir);
|
|
254
|
+
}
|
|
255
|
+
finally {
|
|
256
|
+
await rm(workDir, { recursive: true, force: true });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/** One connection field, as wren's own JSON-Schema-shaped output describes it. */
|
|
2
|
+
export interface SourceField {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly label: string;
|
|
5
|
+
readonly required: boolean;
|
|
6
|
+
/** wren marks credential fields with `format: "password"`, including inside an `anyOf`. */
|
|
7
|
+
readonly secret: boolean;
|
|
8
|
+
/** wren's own prose for the field, e.g. BigQuery's "Base64 encode `credentials.json`". */
|
|
9
|
+
readonly description?: string;
|
|
10
|
+
/** wren's first worked example, shown as the input's placeholder. */
|
|
11
|
+
readonly example?: string;
|
|
12
|
+
/**
|
|
13
|
+
* A value wren fixes for this field. Variant discriminators carry one —
|
|
14
|
+
* `bigquery_type` is `const: "dataset"` on the dataset variant — so asking
|
|
15
|
+
* the user to type it is asking them to guess a value that is already known.
|
|
16
|
+
*/
|
|
17
|
+
readonly fixedValue?: string;
|
|
18
|
+
/** wren's default, when it has one and the field is not fixed. */
|
|
19
|
+
readonly defaultValue?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A source's connection shape. `bigquery`, `redshift` and `databricks` each
|
|
23
|
+
* publish several — one per authentication style — under `variants`; every
|
|
24
|
+
* other source publishes exactly one, which we normalize to a single unnamed
|
|
25
|
+
* variant so consumers need no special case.
|
|
26
|
+
*/
|
|
27
|
+
export interface SourceVariant {
|
|
28
|
+
readonly name?: string;
|
|
29
|
+
readonly fields: readonly SourceField[];
|
|
30
|
+
/**
|
|
31
|
+
* How wren tells this variant apart: the field it fixes and the value it
|
|
32
|
+
* fixes it to — `bigquery_type: "dataset"`, `redshift_type: "redshift_iam"`.
|
|
33
|
+
* The value is the stable, human-meaningful handle for the variant (the
|
|
34
|
+
* schema's own name is a Python class name), so it is what the UI offers and
|
|
35
|
+
* what travels on the wire.
|
|
36
|
+
*/
|
|
37
|
+
readonly discriminator?: {
|
|
38
|
+
readonly field: string;
|
|
39
|
+
readonly value: string;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export interface CatalogSource {
|
|
43
|
+
readonly key: string;
|
|
44
|
+
readonly label: string;
|
|
45
|
+
readonly variants: readonly SourceVariant[];
|
|
46
|
+
}
|
|
47
|
+
export interface SourceCatalog {
|
|
48
|
+
readonly sources: readonly CatalogSource[];
|
|
49
|
+
/**
|
|
50
|
+
* True when this came from the wren CLI. False means the CLI could not be
|
|
51
|
+
* read and `sources` is the built-in floor below — a state the API reports
|
|
52
|
+
* rather than hides, because silently offering four sources when wren
|
|
53
|
+
* supports twenty-one is the bug this module exists to remove.
|
|
54
|
+
*/
|
|
55
|
+
readonly fromCli: boolean;
|
|
56
|
+
/** Why the CLI read failed, when `fromCli` is false. */
|
|
57
|
+
readonly degradedReason?: string;
|
|
58
|
+
}
|
|
59
|
+
export declare class SourceCatalogError extends Error {
|
|
60
|
+
}
|
|
61
|
+
/** Parses the CLI's JSON. Exported so its edge cases are testable without a wren install. */
|
|
62
|
+
export declare function parseSourceCatalog(raw: string): readonly CatalogSource[];
|
|
63
|
+
/**
|
|
64
|
+
* Reads the catalog once per process. The registry only changes when the wren
|
|
65
|
+
* install does, which cannot happen under a running BFF, so a cache costs
|
|
66
|
+
* nothing in freshness and saves a subprocess on every Setup page load.
|
|
67
|
+
*/
|
|
68
|
+
export declare function loadSourceCatalog(options?: {
|
|
69
|
+
readonly refresh?: boolean;
|
|
70
|
+
}): Promise<SourceCatalog>;
|
|
71
|
+
/** Test seam: drops the per-process cache. */
|
|
72
|
+
export declare function resetSourceCatalogCache(): void;
|
|
73
|
+
/** The keys Setup will accept for `sourceType`, replacing the old hardcoded allowlist. */
|
|
74
|
+
export declare function supportedSourceTypes(): Promise<ReadonlySet<string>>;
|
|
75
|
+
/**
|
|
76
|
+
* The `.env` variable names a source's chosen connection shape needs, in the
|
|
77
|
+
* `<SOURCE>_<FIELD>` form the connect turn writes — with the prefix collapsed
|
|
78
|
+
* where the field name already carries it (`bigquery_type` -> BIGQUERY_TYPE,
|
|
79
|
+
* not BIGQUERY_BIGQUERY_TYPE).
|
|
80
|
+
*
|
|
81
|
+
* Naming them removes the guesswork that made the same source produce a
|
|
82
|
+
* different credential form on different runs.
|
|
83
|
+
*/
|
|
84
|
+
export declare function connectionVariableNames(sourceKey: string, variantValue: string): Promise<readonly string[] | undefined>;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The set of data sources Setup may offer, read from the one place that
|
|
3
|
+
* actually decides it: the wren CLI's own connector registry, via
|
|
4
|
+
* `wren docs connection-info --format json`.
|
|
5
|
+
*
|
|
6
|
+
* Before this module the same list existed in four hand-maintained copies —
|
|
7
|
+
* the Setup picker (4 entries), this package's connect/adopt allowlist (8),
|
|
8
|
+
* a duplicate of that allowlist in the adopt tests (8), and wren's registry
|
|
9
|
+
* (21) — and they had drifted apart. `app.ts` said so itself: "No canonical
|
|
10
|
+
* connector-type list is currently exported anywhere in this repo (or in
|
|
11
|
+
* warble) to import instead — extend this list as wren gains connectors."
|
|
12
|
+
* There is one now, and it is not a copy.
|
|
13
|
+
*
|
|
14
|
+
* The catalog is descriptive, never prescriptive: it reports what wren
|
|
15
|
+
* supports and what each source's connection needs. Whether a given source's
|
|
16
|
+
* driver is installed is a separate question, answered by `driver.ts`.
|
|
17
|
+
*/
|
|
18
|
+
import { execFile } from "node:child_process";
|
|
19
|
+
export class SourceCatalogError extends Error {
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The floor used only when the CLI cannot be read. Deliberately the four the
|
|
23
|
+
* picker shipped before this module, not a guess at the full set: if wren is
|
|
24
|
+
* unreachable we have no basis for claiming more, and under-offering is
|
|
25
|
+
* recoverable where offering a source that cannot exist is not.
|
|
26
|
+
*/
|
|
27
|
+
const FALLBACK_SOURCE_KEYS = ["postgres", "bigquery", "snowflake", "duckdb"];
|
|
28
|
+
/** Display names for the sources whose registry key does not read well raw. */
|
|
29
|
+
const DISPLAY_LABELS = {
|
|
30
|
+
postgres: "PostgreSQL",
|
|
31
|
+
mysql: "MySQL",
|
|
32
|
+
mssql: "SQL Server",
|
|
33
|
+
bigquery: "BigQuery",
|
|
34
|
+
clickhouse: "ClickHouse",
|
|
35
|
+
duckdb: "Local file (CSV / DuckDB)",
|
|
36
|
+
local_file: "Local file",
|
|
37
|
+
s3_file: "S3 files",
|
|
38
|
+
gcs_file: "Google Cloud Storage files",
|
|
39
|
+
minio_file: "MinIO files",
|
|
40
|
+
datafusion: "DataFusion (in-process files)",
|
|
41
|
+
canner: "Canner Enterprise",
|
|
42
|
+
doris: "Apache Doris",
|
|
43
|
+
spark: "Apache Spark",
|
|
44
|
+
trino: "Trino",
|
|
45
|
+
athena: "Amazon Athena",
|
|
46
|
+
redshift: "Amazon Redshift",
|
|
47
|
+
databricks: "Databricks",
|
|
48
|
+
snowflake: "Snowflake",
|
|
49
|
+
oracle: "Oracle",
|
|
50
|
+
// Not a database: a raw connection string that bypasses per-source field
|
|
51
|
+
// validation. Named so nobody reads it as "some product called Connection URL".
|
|
52
|
+
connection_url: "Other — connection URL",
|
|
53
|
+
};
|
|
54
|
+
function labelFor(key) {
|
|
55
|
+
return DISPLAY_LABELS[key] ?? key;
|
|
56
|
+
}
|
|
57
|
+
/** `format: "password"` may sit on the property or inside its `anyOf` branches. */
|
|
58
|
+
function isSecret(property) {
|
|
59
|
+
if (typeof property !== "object" || property === null)
|
|
60
|
+
return false;
|
|
61
|
+
const record = property;
|
|
62
|
+
if (record["format"] === "password")
|
|
63
|
+
return true;
|
|
64
|
+
const anyOf = record["anyOf"];
|
|
65
|
+
return Array.isArray(anyOf) && anyOf.some((branch) => isSecret(branch));
|
|
66
|
+
}
|
|
67
|
+
function stringOf(property, key) {
|
|
68
|
+
if (typeof property !== "object" || property === null)
|
|
69
|
+
return undefined;
|
|
70
|
+
const value = property[key];
|
|
71
|
+
if (typeof value === "string" && value.length > 0)
|
|
72
|
+
return value;
|
|
73
|
+
return typeof value === "number" ? String(value) : undefined;
|
|
74
|
+
}
|
|
75
|
+
function firstExample(property) {
|
|
76
|
+
if (typeof property !== "object" || property === null)
|
|
77
|
+
return undefined;
|
|
78
|
+
const examples = property["examples"];
|
|
79
|
+
if (!Array.isArray(examples) || examples.length === 0)
|
|
80
|
+
return undefined;
|
|
81
|
+
const [first] = examples;
|
|
82
|
+
return typeof first === "string" ? first : typeof first === "number" ? String(first) : undefined;
|
|
83
|
+
}
|
|
84
|
+
function titleOf(property, name) {
|
|
85
|
+
if (typeof property === "object" && property !== null) {
|
|
86
|
+
const title = property["title"];
|
|
87
|
+
if (typeof title === "string" && title.length > 0)
|
|
88
|
+
return title;
|
|
89
|
+
}
|
|
90
|
+
return name;
|
|
91
|
+
}
|
|
92
|
+
function parseVariant(schema, name) {
|
|
93
|
+
const properties = schema["properties"];
|
|
94
|
+
const requiredRaw = schema["required"];
|
|
95
|
+
const required = new Set(Array.isArray(requiredRaw) ? requiredRaw.filter((entry) => typeof entry === "string") : []);
|
|
96
|
+
const fields = [];
|
|
97
|
+
if (typeof properties === "object" && properties !== null) {
|
|
98
|
+
for (const [fieldName, property] of Object.entries(properties)) {
|
|
99
|
+
const description = stringOf(property, "description");
|
|
100
|
+
const example = firstExample(property);
|
|
101
|
+
const fixedValue = stringOf(property, "const");
|
|
102
|
+
const defaultValue = fixedValue === undefined ? stringOf(property, "default") : undefined;
|
|
103
|
+
fields.push({
|
|
104
|
+
name: fieldName,
|
|
105
|
+
label: titleOf(property, fieldName),
|
|
106
|
+
required: required.has(fieldName),
|
|
107
|
+
secret: isSecret(property),
|
|
108
|
+
...(description !== undefined ? { description } : {}),
|
|
109
|
+
...(example !== undefined ? { example } : {}),
|
|
110
|
+
...(fixedValue !== undefined ? { fixedValue } : {}),
|
|
111
|
+
...(defaultValue !== undefined ? { defaultValue } : {}),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const discriminatorField = fields.find((field) => field.fixedValue !== undefined);
|
|
116
|
+
const discriminator = discriminatorField?.fixedValue !== undefined ? { field: discriminatorField.name, value: discriminatorField.fixedValue } : undefined;
|
|
117
|
+
return {
|
|
118
|
+
...(name !== undefined ? { name } : {}),
|
|
119
|
+
fields,
|
|
120
|
+
...(discriminator !== undefined ? { discriminator } : {}),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/** Parses the CLI's JSON. Exported so its edge cases are testable without a wren install. */
|
|
124
|
+
export function parseSourceCatalog(raw) {
|
|
125
|
+
let document;
|
|
126
|
+
try {
|
|
127
|
+
document = JSON.parse(raw);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
throw new SourceCatalogError(`wren docs connection-info did not return JSON: ${error.message}`);
|
|
131
|
+
}
|
|
132
|
+
if (typeof document !== "object" || document === null || Array.isArray(document)) {
|
|
133
|
+
throw new SourceCatalogError("wren docs connection-info returned a non-object document");
|
|
134
|
+
}
|
|
135
|
+
const sources = [];
|
|
136
|
+
for (const [key, value] of Object.entries(document)) {
|
|
137
|
+
if (typeof value !== "object" || value === null)
|
|
138
|
+
continue;
|
|
139
|
+
const entry = value;
|
|
140
|
+
const variantsRaw = entry["variants"];
|
|
141
|
+
if (typeof variantsRaw === "object" && variantsRaw !== null) {
|
|
142
|
+
const variants = Object.entries(variantsRaw)
|
|
143
|
+
.filter((pair) => typeof pair[1] === "object" && pair[1] !== null)
|
|
144
|
+
.map(([variantName, schema]) => parseVariant(schema, variantName));
|
|
145
|
+
if (variants.length > 0)
|
|
146
|
+
sources.push({ key, label: labelFor(key), variants });
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
sources.push({ key, label: labelFor(key), variants: [parseVariant(entry)] });
|
|
150
|
+
}
|
|
151
|
+
if (sources.length === 0)
|
|
152
|
+
throw new SourceCatalogError("wren docs connection-info listed no data sources");
|
|
153
|
+
return sources;
|
|
154
|
+
}
|
|
155
|
+
function runWrenDocs() {
|
|
156
|
+
return new Promise((resolve) => {
|
|
157
|
+
execFile("wren", ["docs", "connection-info", "--format", "json"], { maxBuffer: 32 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
158
|
+
resolve({ stdout, stderr, error: error });
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function fallbackCatalog(reason) {
|
|
163
|
+
return {
|
|
164
|
+
sources: FALLBACK_SOURCE_KEYS.map((key) => ({ key, label: labelFor(key), variants: [] })),
|
|
165
|
+
fromCli: false,
|
|
166
|
+
degradedReason: reason,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
let cached;
|
|
170
|
+
/**
|
|
171
|
+
* Reads the catalog once per process. The registry only changes when the wren
|
|
172
|
+
* install does, which cannot happen under a running BFF, so a cache costs
|
|
173
|
+
* nothing in freshness and saves a subprocess on every Setup page load.
|
|
174
|
+
*/
|
|
175
|
+
export async function loadSourceCatalog(options = {}) {
|
|
176
|
+
if (cached !== undefined && options.refresh !== true)
|
|
177
|
+
return cached;
|
|
178
|
+
const { stdout, stderr, error } = await runWrenDocs();
|
|
179
|
+
if (error !== null) {
|
|
180
|
+
const detail = error.code === "ENOENT" ? "the wren CLI is not on PATH" : (stderr.trim() || error.message);
|
|
181
|
+
cached = fallbackCatalog(`could not read wren's connector registry (${detail})`);
|
|
182
|
+
return cached;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
cached = { sources: parseSourceCatalog(stdout), fromCli: true };
|
|
186
|
+
}
|
|
187
|
+
catch (parseError) {
|
|
188
|
+
cached = fallbackCatalog(parseError.message);
|
|
189
|
+
}
|
|
190
|
+
return cached;
|
|
191
|
+
}
|
|
192
|
+
/** Test seam: drops the per-process cache. */
|
|
193
|
+
export function resetSourceCatalogCache() {
|
|
194
|
+
cached = undefined;
|
|
195
|
+
}
|
|
196
|
+
/** The keys Setup will accept for `sourceType`, replacing the old hardcoded allowlist. */
|
|
197
|
+
export async function supportedSourceTypes() {
|
|
198
|
+
const catalog = await loadSourceCatalog();
|
|
199
|
+
return new Set(catalog.sources.map((source) => source.key));
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* The `.env` variable names a source's chosen connection shape needs, in the
|
|
203
|
+
* `<SOURCE>_<FIELD>` form the connect turn writes — with the prefix collapsed
|
|
204
|
+
* where the field name already carries it (`bigquery_type` -> BIGQUERY_TYPE,
|
|
205
|
+
* not BIGQUERY_BIGQUERY_TYPE).
|
|
206
|
+
*
|
|
207
|
+
* Naming them removes the guesswork that made the same source produce a
|
|
208
|
+
* different credential form on different runs.
|
|
209
|
+
*/
|
|
210
|
+
export async function connectionVariableNames(sourceKey, variantValue) {
|
|
211
|
+
const catalog = await loadSourceCatalog();
|
|
212
|
+
const source = catalog.sources.find((entry) => entry.key === sourceKey);
|
|
213
|
+
const variant = source?.variants.find((entry) => entry.discriminator?.value === variantValue);
|
|
214
|
+
if (variant === undefined)
|
|
215
|
+
return undefined;
|
|
216
|
+
const prefix = sourceKey.toUpperCase();
|
|
217
|
+
return variant.fields.map((field) => {
|
|
218
|
+
const upper = field.name.toUpperCase();
|
|
219
|
+
return upper.startsWith(`${prefix}_`) || upper === prefix ? upper : `${prefix}_${upper}`;
|
|
220
|
+
});
|
|
221
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type DriverProvisionStatus = "not_needed" | "ready" | "installed" | "failed" | "unavailable";
|
|
2
|
+
export interface DriverProvisionResult {
|
|
3
|
+
readonly status: DriverProvisionStatus;
|
|
4
|
+
/** The extra that was (or would be) installed; absent when the source needs none. */
|
|
5
|
+
readonly extra?: string;
|
|
6
|
+
/** Human-readable reason, always set for `failed` and `unavailable`. */
|
|
7
|
+
readonly detail?: string;
|
|
8
|
+
}
|
|
9
|
+
/** The extra a source's driver ships under, or undefined when it needs none. */
|
|
10
|
+
export declare function driverExtraFor(sourceKey: string): string | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* The interpreter of the wren install on PATH. `wren` is a console script inside
|
|
13
|
+
* its environment's `bin/`, so its realpath's sibling `python` is that
|
|
14
|
+
* environment — the one whose site-packages a connector import will search.
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveWrenVenvPython(): string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Installs the source's driver extra into the wren environment. Idempotent:
|
|
19
|
+
* `uv pip install` on an already-satisfied extra makes no changes, so this is
|
|
20
|
+
* safe to run before every connect turn without probing first.
|
|
21
|
+
*
|
|
22
|
+
* Installs the extra (`wrenai[postgres]`) rather than the driver package
|
|
23
|
+
* directly, so the pins come from wren rather than from a second list here.
|
|
24
|
+
* uv resolves the already-installed editable `wrenai` as satisfying itself, so
|
|
25
|
+
* this adds the driver without disturbing a local editable checkout.
|
|
26
|
+
*/
|
|
27
|
+
export declare function provisionSourceDriver(sourceKey: string, options?: {
|
|
28
|
+
readonly run?: (args: readonly string[]) => Promise<{
|
|
29
|
+
stdout: string;
|
|
30
|
+
stderr: string;
|
|
31
|
+
error: NodeJS.ErrnoException | null;
|
|
32
|
+
}>;
|
|
33
|
+
}): Promise<DriverProvisionResult>;
|
|
34
|
+
/** One line describing the outcome for the composed prompt — verifiable, never a promise. */
|
|
35
|
+
export declare function driverProvisionNote(sourceKey: string, result: DriverProvisionResult): string;
|