@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,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A minimal, structural validator for the JSON Schema subset an
|
|
3
|
+
* `agent.output_schema` document actually uses (`type`, `properties`,
|
|
4
|
+
* `required`, `items`, `const`, and a `type` array for nullable unions like
|
|
5
|
+
* `["string", "null"]`). This is deliberately not a general-purpose JSON
|
|
6
|
+
* Schema engine — `jsonSchema()` from the AI SDK performs *no* runtime
|
|
7
|
+
* validation on its own unless given a `validate` callback (confirmed by
|
|
8
|
+
* reading `safeValidateTypes`: a schema with no `.validate` always reports
|
|
9
|
+
* success), so this is what gives the envelope stage real teeth: a
|
|
10
|
+
* malformed model response is rejected, not silently passed through.
|
|
11
|
+
*/
|
|
12
|
+
export type JsonSchemaDocument = Record<string, unknown>;
|
|
13
|
+
export declare function collectJsonSchemaErrors(schema: JsonSchemaDocument, value: unknown): string[];
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export function collectJsonSchemaErrors(schema, value) {
|
|
2
|
+
const errors = [];
|
|
3
|
+
validateNode(schema, value, "$", errors);
|
|
4
|
+
return errors;
|
|
5
|
+
}
|
|
6
|
+
function validateNode(schema, value, path, errors) {
|
|
7
|
+
if ("const" in schema) {
|
|
8
|
+
if (value !== schema.const) {
|
|
9
|
+
errors.push(`${path}: expected const ${JSON.stringify(schema.const)}, got ${JSON.stringify(value)}`);
|
|
10
|
+
}
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const type = schema.type;
|
|
14
|
+
if (type !== undefined && !matchesType(type, value)) {
|
|
15
|
+
errors.push(`${path}: expected type ${JSON.stringify(type)}, got ${describeType(value)}`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (includesType(type, "object") && isPlainObject(value)) {
|
|
19
|
+
validateObjectShape(schema, value, path, errors);
|
|
20
|
+
}
|
|
21
|
+
if (includesType(type, "array") && Array.isArray(value)) {
|
|
22
|
+
validateArrayItems(schema, value, path, errors);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function validateObjectShape(schema, value, path, errors) {
|
|
26
|
+
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
27
|
+
for (const key of required) {
|
|
28
|
+
if (typeof key === "string" && !(key in value)) {
|
|
29
|
+
errors.push(`${path}: missing required property "${key}"`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const properties = isPlainObject(schema.properties) ? schema.properties : undefined;
|
|
33
|
+
if (!properties)
|
|
34
|
+
return;
|
|
35
|
+
for (const [key, propSchema] of Object.entries(properties)) {
|
|
36
|
+
if (key in value && isPlainObject(propSchema)) {
|
|
37
|
+
validateNode(propSchema, value[key], `${path}.${key}`, errors);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function validateArrayItems(schema, value, path, errors) {
|
|
42
|
+
if (!isPlainObject(schema.items))
|
|
43
|
+
return;
|
|
44
|
+
const itemsSchema = schema.items;
|
|
45
|
+
value.forEach((item, index) => validateNode(itemsSchema, item, `${path}[${index}]`, errors));
|
|
46
|
+
}
|
|
47
|
+
function matchesType(type, value) {
|
|
48
|
+
if (Array.isArray(type)) {
|
|
49
|
+
return type.some((candidate) => matchesSingleType(candidate, value));
|
|
50
|
+
}
|
|
51
|
+
return matchesSingleType(type, value);
|
|
52
|
+
}
|
|
53
|
+
function includesType(type, candidate) {
|
|
54
|
+
if (type === undefined)
|
|
55
|
+
return true;
|
|
56
|
+
return Array.isArray(type) ? type.includes(candidate) : type === candidate;
|
|
57
|
+
}
|
|
58
|
+
function matchesSingleType(type, value) {
|
|
59
|
+
switch (type) {
|
|
60
|
+
case "object":
|
|
61
|
+
return isPlainObject(value);
|
|
62
|
+
case "array":
|
|
63
|
+
return Array.isArray(value);
|
|
64
|
+
case "string":
|
|
65
|
+
return typeof value === "string";
|
|
66
|
+
case "number":
|
|
67
|
+
case "integer":
|
|
68
|
+
return typeof value === "number";
|
|
69
|
+
case "boolean":
|
|
70
|
+
return typeof value === "boolean";
|
|
71
|
+
case "null":
|
|
72
|
+
return value === null;
|
|
73
|
+
default:
|
|
74
|
+
// Unknown/unsupported schema type keyword — don't fail closed on
|
|
75
|
+
// something this minimal validator doesn't understand.
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function isPlainObject(value) {
|
|
80
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
81
|
+
}
|
|
82
|
+
function describeType(value) {
|
|
83
|
+
if (value === null)
|
|
84
|
+
return "null";
|
|
85
|
+
if (Array.isArray(value))
|
|
86
|
+
return "array";
|
|
87
|
+
return typeof value;
|
|
88
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { AuthChoice } from "../auth/index.js";
|
|
2
|
+
import type { AdapterSpec } from "../providers/index.js";
|
|
3
|
+
/** Default local endpoint used when a `LocalAuthChoice` omits `endpoint` (Ollama's default OpenAI-compatible port). */
|
|
4
|
+
export declare const DEFAULT_LOCAL_ENDPOINT = "http://localhost:11434/v1";
|
|
5
|
+
/**
|
|
6
|
+
* Default local model used when neither `LocalAuthChoice` (which has no
|
|
7
|
+
* `model` field of its own — see the API-surface mismatch note in this
|
|
8
|
+
* ticket's report) nor `InProcessOptions.model` supplies one.
|
|
9
|
+
*/
|
|
10
|
+
export declare const DEFAULT_LOCAL_MODEL = "llama3.1";
|
|
11
|
+
export interface DeriveAdapterSpecOptions {
|
|
12
|
+
/** Only consulted for `authChoice.mode === "local"`. See `DEFAULT_LOCAL_MODEL`. */
|
|
13
|
+
readonly model?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Derives a concrete `AdapterSpec` (adapter id + config) for in-process's three
|
|
17
|
+
* `AuthChoice` variants:
|
|
18
|
+
*
|
|
19
|
+
* - `api-key` — passed straight through: `authChoice.adapter`/`config` already
|
|
20
|
+
* name a `ProviderRegistry` adapter id and its config (70a's `toAuthChoice`
|
|
21
|
+
* only emits a placeholder `adapter: ""`; a real choice is expected to name
|
|
22
|
+
* a registered adapter such as `"anthropic"`).
|
|
23
|
+
* - `local` — mapped onto the built-in `openai-compatible` adapter, since
|
|
24
|
+
* "a model served on the local machine/network" (70a's doc comment) is
|
|
25
|
+
* exactly what that adapter targets. `endpoint` becomes `baseURL`; `model`
|
|
26
|
+
* comes from `options.model` (falling back to `DEFAULT_LOCAL_MODEL`) since
|
|
27
|
+
* `LocalAuthChoice` itself carries no model field.
|
|
28
|
+
* - `gateway` — also mapped onto `openai-compatible` (an operator-managed
|
|
29
|
+
* gateway is, from this adapter's point of view, just another OpenAI-compatible
|
|
30
|
+
* endpoint); `authChoice.config` must already carry `baseURL`/`model`
|
|
31
|
+
* (and optionally `apiKey`) matching `OpenAICompatibleAdapterConfig` — 70a
|
|
32
|
+
* leaves this config shape unspecified, so this is this ticket's own
|
|
33
|
+
* assumption, not a contract 70a promises. Unlike `local` (which defaults
|
|
34
|
+
* endpoint/model), `gateway` has no sensible default, so a missing/empty
|
|
35
|
+
* `baseURL`/`model` is a loud `wren-harness`-level error here rather than a
|
|
36
|
+
* bare "Invalid URL" deep inside the adapter.
|
|
37
|
+
*/
|
|
38
|
+
export declare function deriveAdapterSpec(authChoice: Extract<AuthChoice, {
|
|
39
|
+
mode: "api-key" | "local" | "gateway";
|
|
40
|
+
}>, options?: DeriveAdapterSpecOptions): AdapterSpec;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { OPENAI_COMPATIBLE_ADAPTER_ID } from "../providers/index.js";
|
|
2
|
+
/** Default local endpoint used when a `LocalAuthChoice` omits `endpoint` (Ollama's default OpenAI-compatible port). */
|
|
3
|
+
export const DEFAULT_LOCAL_ENDPOINT = "http://localhost:11434/v1";
|
|
4
|
+
/**
|
|
5
|
+
* Default local model used when neither `LocalAuthChoice` (which has no
|
|
6
|
+
* `model` field of its own — see the API-surface mismatch note in this
|
|
7
|
+
* ticket's report) nor `InProcessOptions.model` supplies one.
|
|
8
|
+
*/
|
|
9
|
+
export const DEFAULT_LOCAL_MODEL = "llama3.1";
|
|
10
|
+
/**
|
|
11
|
+
* Derives a concrete `AdapterSpec` (adapter id + config) for in-process's three
|
|
12
|
+
* `AuthChoice` variants:
|
|
13
|
+
*
|
|
14
|
+
* - `api-key` — passed straight through: `authChoice.adapter`/`config` already
|
|
15
|
+
* name a `ProviderRegistry` adapter id and its config (70a's `toAuthChoice`
|
|
16
|
+
* only emits a placeholder `adapter: ""`; a real choice is expected to name
|
|
17
|
+
* a registered adapter such as `"anthropic"`).
|
|
18
|
+
* - `local` — mapped onto the built-in `openai-compatible` adapter, since
|
|
19
|
+
* "a model served on the local machine/network" (70a's doc comment) is
|
|
20
|
+
* exactly what that adapter targets. `endpoint` becomes `baseURL`; `model`
|
|
21
|
+
* comes from `options.model` (falling back to `DEFAULT_LOCAL_MODEL`) since
|
|
22
|
+
* `LocalAuthChoice` itself carries no model field.
|
|
23
|
+
* - `gateway` — also mapped onto `openai-compatible` (an operator-managed
|
|
24
|
+
* gateway is, from this adapter's point of view, just another OpenAI-compatible
|
|
25
|
+
* endpoint); `authChoice.config` must already carry `baseURL`/`model`
|
|
26
|
+
* (and optionally `apiKey`) matching `OpenAICompatibleAdapterConfig` — 70a
|
|
27
|
+
* leaves this config shape unspecified, so this is this ticket's own
|
|
28
|
+
* assumption, not a contract 70a promises. Unlike `local` (which defaults
|
|
29
|
+
* endpoint/model), `gateway` has no sensible default, so a missing/empty
|
|
30
|
+
* `baseURL`/`model` is a loud `wren-harness`-level error here rather than a
|
|
31
|
+
* bare "Invalid URL" deep inside the adapter.
|
|
32
|
+
*/
|
|
33
|
+
export function deriveAdapterSpec(authChoice, options = {}) {
|
|
34
|
+
switch (authChoice.mode) {
|
|
35
|
+
case "api-key":
|
|
36
|
+
return { adapter: authChoice.adapter, config: authChoice.config ?? {} };
|
|
37
|
+
case "local":
|
|
38
|
+
return {
|
|
39
|
+
adapter: OPENAI_COMPATIBLE_ADAPTER_ID,
|
|
40
|
+
config: {
|
|
41
|
+
baseURL: authChoice.endpoint ?? DEFAULT_LOCAL_ENDPOINT,
|
|
42
|
+
model: options.model ?? DEFAULT_LOCAL_MODEL,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
case "gateway": {
|
|
46
|
+
const config = authChoice.config ?? {};
|
|
47
|
+
const missing = [];
|
|
48
|
+
if (!isNonEmptyString(config["baseURL"]))
|
|
49
|
+
missing.push("baseURL");
|
|
50
|
+
if (!isNonEmptyString(config["model"]))
|
|
51
|
+
missing.push("model");
|
|
52
|
+
if (missing.length > 0) {
|
|
53
|
+
throw new Error(`gateway mode requires ${missing.join(" and ")} in config (pass --endpoint and --model)`);
|
|
54
|
+
}
|
|
55
|
+
return { adapter: OPENAI_COMPATIBLE_ADAPTER_ID, config };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function isNonEmptyString(value) {
|
|
60
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
61
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** A resolved CLI invocation: the executable plus any args that must precede the caller's own args (e.g. a script path for a `tsx <script>` dev-mode invocation). */
|
|
2
|
+
export interface ResolvedCli {
|
|
3
|
+
readonly command: string;
|
|
4
|
+
readonly prefixArgs: readonly string[];
|
|
5
|
+
}
|
|
6
|
+
export declare class AgentSdkCliNotFoundError extends Error {
|
|
7
|
+
constructor(attempts: readonly string[]);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Resolves the warble `claude-agent-sdk` dispatcher CLI (dispatched's back-end)
|
|
11
|
+
* in four tiers, mirroring `resolveWarbleBinary`:
|
|
12
|
+
*
|
|
13
|
+
* 1. `explicit` — if given, treated as a ready-to-run command with no args
|
|
14
|
+
* prefix (no existence check — a bare name is trusted to resolve on
|
|
15
|
+
* `PATH` at spawn time, matching a documented override).
|
|
16
|
+
* 2. The pinned `@warble/claude-agent-sdk` npm package this workspace depends on, run via
|
|
17
|
+
* `node <dist/cli.js>` rather than relying on the file's own shebang/executable bit.
|
|
18
|
+
* 3. `PATH` — a bare `"warble-agent-sdk"` that `warble-agent-sdk --help` succeeds against. Kept as
|
|
19
|
+
* a fallback for a standalone install on a machine that hasn't run `pnpm install`.
|
|
20
|
+
* 4. A sibling `warble` repo's dev-mode invocation, gated behind the explicit
|
|
21
|
+
* `WREN_HARNESS_ALLOW_WARBLE_SIBLING_CHECKOUT=1` opt-in (see
|
|
22
|
+
* `isWarbleSiblingCheckoutDevModeEnabled`):
|
|
23
|
+
* `<ancestor's parent>/warble/dispatcher/claude-agent-sdk`'s
|
|
24
|
+
* `node_modules/.bin/tsx src/cli.ts`. Like `resolveWarbleBinary`'s equivalent tier, this only
|
|
25
|
+
* resolves in a local development layout with a `warble` checkout next to this repo, and must
|
|
26
|
+
* not silently win over the pinned package now that tier 2 exists.
|
|
27
|
+
*
|
|
28
|
+
* Throws {@link AgentSdkCliNotFoundError} (loud-fail) if none of the four work.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveAgentSdkCli(explicit?: string): Promise<ResolvedCli>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { isWarbleSiblingCheckoutDevModeEnabled, resolveInstalledPackageBin } from "../npm-package-resolve.js";
|
|
6
|
+
export class AgentSdkCliNotFoundError extends Error {
|
|
7
|
+
constructor(attempts) {
|
|
8
|
+
super(`could not resolve the warble-agent-sdk CLI:\n${attempts.map((attempt) => ` - ${attempt}`).join("\n")}`);
|
|
9
|
+
this.name = "AgentSdkCliNotFoundError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Resolves the warble `claude-agent-sdk` dispatcher CLI (dispatched's back-end)
|
|
14
|
+
* in four tiers, mirroring `resolveWarbleBinary`:
|
|
15
|
+
*
|
|
16
|
+
* 1. `explicit` — if given, treated as a ready-to-run command with no args
|
|
17
|
+
* prefix (no existence check — a bare name is trusted to resolve on
|
|
18
|
+
* `PATH` at spawn time, matching a documented override).
|
|
19
|
+
* 2. The pinned `@warble/claude-agent-sdk` npm package this workspace depends on, run via
|
|
20
|
+
* `node <dist/cli.js>` rather than relying on the file's own shebang/executable bit.
|
|
21
|
+
* 3. `PATH` — a bare `"warble-agent-sdk"` that `warble-agent-sdk --help` succeeds against. Kept as
|
|
22
|
+
* a fallback for a standalone install on a machine that hasn't run `pnpm install`.
|
|
23
|
+
* 4. A sibling `warble` repo's dev-mode invocation, gated behind the explicit
|
|
24
|
+
* `WREN_HARNESS_ALLOW_WARBLE_SIBLING_CHECKOUT=1` opt-in (see
|
|
25
|
+
* `isWarbleSiblingCheckoutDevModeEnabled`):
|
|
26
|
+
* `<ancestor's parent>/warble/dispatcher/claude-agent-sdk`'s
|
|
27
|
+
* `node_modules/.bin/tsx src/cli.ts`. Like `resolveWarbleBinary`'s equivalent tier, this only
|
|
28
|
+
* resolves in a local development layout with a `warble` checkout next to this repo, and must
|
|
29
|
+
* not silently win over the pinned package now that tier 2 exists.
|
|
30
|
+
*
|
|
31
|
+
* Throws {@link AgentSdkCliNotFoundError} (loud-fail) if none of the four work.
|
|
32
|
+
*/
|
|
33
|
+
export async function resolveAgentSdkCli(explicit) {
|
|
34
|
+
if (explicit !== undefined) {
|
|
35
|
+
return { command: explicit, prefixArgs: [] };
|
|
36
|
+
}
|
|
37
|
+
const attempts = [];
|
|
38
|
+
const packageBin = resolveInstalledPackageBin("@warble/claude-agent-sdk", "warble-agent-sdk");
|
|
39
|
+
if (packageBin !== undefined) {
|
|
40
|
+
return { command: process.execPath, prefixArgs: [packageBin] };
|
|
41
|
+
}
|
|
42
|
+
attempts.push(`"@warble/claude-agent-sdk" is not installed (or declares no "warble-agent-sdk" bin) in this workspace`);
|
|
43
|
+
if (await isExecutableOnPath("warble-agent-sdk")) {
|
|
44
|
+
return { command: "warble-agent-sdk", prefixArgs: [] };
|
|
45
|
+
}
|
|
46
|
+
attempts.push('not found on PATH (tried running "warble-agent-sdk --help")');
|
|
47
|
+
if (isWarbleSiblingCheckoutDevModeEnabled()) {
|
|
48
|
+
const sibling = findSiblingDevInvocation();
|
|
49
|
+
if (sibling !== undefined) {
|
|
50
|
+
return sibling;
|
|
51
|
+
}
|
|
52
|
+
attempts.push('no sibling "warble/dispatcher/claude-agent-sdk" package found with a built tsx binary ' +
|
|
53
|
+
'(searched ancestors of this package for "<ancestor\'s parent>/warble/dispatcher/claude-agent-sdk")');
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
attempts.push(`sibling "warble" checkout dev mode is disabled (set WREN_HARNESS_ALLOW_WARBLE_SIBLING_CHECKOUT=1 to enable it)`);
|
|
57
|
+
}
|
|
58
|
+
throw new AgentSdkCliNotFoundError(attempts);
|
|
59
|
+
}
|
|
60
|
+
function isExecutableOnPath(bin) {
|
|
61
|
+
return new Promise((resolve) => {
|
|
62
|
+
execFile(bin, ["--help"], (error) => resolve(!error));
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Walks up from this package's own directory (two levels below the package
|
|
67
|
+
* root, same convention as `resolveWarbleBinary`'s sibling lookup) looking
|
|
68
|
+
* for `warble/dispatcher/claude-agent-sdk`'s dev-mode `tsx` entry point.
|
|
69
|
+
*/
|
|
70
|
+
function findSiblingDevInvocation() {
|
|
71
|
+
let dir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
72
|
+
const maxDepth = 10;
|
|
73
|
+
for (let i = 0; i < maxDepth; i += 1) {
|
|
74
|
+
const parent = path.dirname(dir);
|
|
75
|
+
const pkgDir = path.join(parent, "warble", "dispatcher", "claude-agent-sdk");
|
|
76
|
+
const tsx = path.join(pkgDir, "node_modules", ".bin", "tsx");
|
|
77
|
+
const entry = path.join(pkgDir, "src", "cli.ts");
|
|
78
|
+
if (existsSync(tsx) && existsSync(entry)) {
|
|
79
|
+
return { command: tsx, prefixArgs: [entry] };
|
|
80
|
+
}
|
|
81
|
+
if (parent === dir)
|
|
82
|
+
break; // reached filesystem root
|
|
83
|
+
dir = parent;
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ResolvedCli } from "./agent-sdk-cli.js";
|
|
2
|
+
export interface AgentSdkManifestCommand {
|
|
3
|
+
readonly command: string;
|
|
4
|
+
readonly args: readonly string[];
|
|
5
|
+
}
|
|
6
|
+
export interface BuildAgentSdkManifestArgsOptions {
|
|
7
|
+
readonly irPath: string;
|
|
8
|
+
/** Omitted for a raw bootstrap profile, which has no bound project yet. */
|
|
9
|
+
readonly userProject?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Builds the argv for a `warble-agent-sdk manifest` invocation without
|
|
13
|
+
* spawning anything: `<agent-sdk-cli> manifest <ir.json> --project
|
|
14
|
+
* <userProject> --include-unavailable`. The display-only flag keeps an
|
|
15
|
+
* unsupported declared component visible without making it executable.
|
|
16
|
+
* Omitting `--out` is deliberate — the dispatcher's
|
|
17
|
+
* `manifest` subcommand writes the manifest JSON to stdout only when no
|
|
18
|
+
* `--out` is given (resolution summaries always go to stderr), so stdout
|
|
19
|
+
* stays pure JSON for `runAgentSdkManifest` to parse.
|
|
20
|
+
*/
|
|
21
|
+
export declare function buildAgentSdkManifestArgs(cli: ResolvedCli, options: BuildAgentSdkManifestArgsOptions): AgentSdkManifestCommand;
|
|
22
|
+
/**
|
|
23
|
+
* Runs a `warble-agent-sdk manifest` invocation (built by
|
|
24
|
+
* {@link buildAgentSdkManifestArgs}) to completion and returns its stdout — a
|
|
25
|
+
* single JSON blob, not a stream, so this mirrors `compile/pipeline.ts`'s
|
|
26
|
+
* buffered `runWarble` rather than `dispatched.ts`'s streaming `spawnChat`
|
|
27
|
+
* (there's no incremental per-step output to relay for a describe/introspect
|
|
28
|
+
* call). Reuses `WarbleCommandFailedError` since it's already the generic
|
|
29
|
+
* "an external warble-family CLI exited non-zero" error shape.
|
|
30
|
+
*/
|
|
31
|
+
export declare function runAgentSdkManifest(command: string, args: readonly string[]): Promise<string>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { WarbleCommandFailedError } from "../compile/errors.js";
|
|
3
|
+
// Mirrors `compile/pipeline.ts`'s `WARBLE_COMMAND_TIMEOUT_MS` — a generous
|
|
4
|
+
// ceiling for a local, normally-sub-second command; guards against a hang,
|
|
5
|
+
// not meant to ever fire in normal operation.
|
|
6
|
+
const AGENT_SDK_MANIFEST_TIMEOUT_MS = 2 * 60 * 1000;
|
|
7
|
+
/**
|
|
8
|
+
* Builds the argv for a `warble-agent-sdk manifest` invocation without
|
|
9
|
+
* spawning anything: `<agent-sdk-cli> manifest <ir.json> --project
|
|
10
|
+
* <userProject> --include-unavailable`. The display-only flag keeps an
|
|
11
|
+
* unsupported declared component visible without making it executable.
|
|
12
|
+
* Omitting `--out` is deliberate — the dispatcher's
|
|
13
|
+
* `manifest` subcommand writes the manifest JSON to stdout only when no
|
|
14
|
+
* `--out` is given (resolution summaries always go to stderr), so stdout
|
|
15
|
+
* stays pure JSON for `runAgentSdkManifest` to parse.
|
|
16
|
+
*/
|
|
17
|
+
export function buildAgentSdkManifestArgs(cli, options) {
|
|
18
|
+
return {
|
|
19
|
+
command: cli.command,
|
|
20
|
+
args: [
|
|
21
|
+
...cli.prefixArgs,
|
|
22
|
+
"manifest",
|
|
23
|
+
options.irPath,
|
|
24
|
+
"--include-unavailable",
|
|
25
|
+
...(options.userProject !== undefined ? ["--project", options.userProject] : []),
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Runs a `warble-agent-sdk manifest` invocation (built by
|
|
31
|
+
* {@link buildAgentSdkManifestArgs}) to completion and returns its stdout — a
|
|
32
|
+
* single JSON blob, not a stream, so this mirrors `compile/pipeline.ts`'s
|
|
33
|
+
* buffered `runWarble` rather than `dispatched.ts`'s streaming `spawnChat`
|
|
34
|
+
* (there's no incremental per-step output to relay for a describe/introspect
|
|
35
|
+
* call). Reuses `WarbleCommandFailedError` since it's already the generic
|
|
36
|
+
* "an external warble-family CLI exited non-zero" error shape.
|
|
37
|
+
*/
|
|
38
|
+
export function runAgentSdkManifest(command, args) {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
execFile(command, [...args], { maxBuffer: 32 * 1024 * 1024, timeout: AGENT_SDK_MANIFEST_TIMEOUT_MS }, (error, stdout, stderr) => {
|
|
41
|
+
if (error) {
|
|
42
|
+
const exitCode = typeof error.code === "number" ? error.code : 1;
|
|
43
|
+
const clarifiedStderr = stderr.length > 0
|
|
44
|
+
? stderr
|
|
45
|
+
: error.killed === true
|
|
46
|
+
? `warble-agent-sdk manifest subprocess timed out after ${AGENT_SDK_MANIFEST_TIMEOUT_MS}ms`
|
|
47
|
+
: error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER"
|
|
48
|
+
? "warble-agent-sdk manifest subprocess exceeded the 32 MiB output buffer"
|
|
49
|
+
: stderr;
|
|
50
|
+
reject(new WarbleCommandFailedError(command, args, exitCode, clarifiedStderr, stdout));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
resolve(stdout);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { RenderEnvelope } from "../render/envelope.js";
|
|
2
|
+
export type ArtifactContentUnavailableReason = "missing" | "unreadable" | "outside_root" | "too_large";
|
|
3
|
+
/**
|
|
4
|
+
* `GET /api/artifacts/:id/content`'s response shape. An artifact's
|
|
5
|
+
* `location` is read back and classified into exactly one of these forms —
|
|
6
|
+
* never a fabricated fourth shape:
|
|
7
|
+
* - `envelope`: the file parses as JSON and is envelope-shaped (has a
|
|
8
|
+
* `blocks` array) — this is what dispatched (`generate_dashboard`/report/chart
|
|
9
|
+
* agents) always persists.
|
|
10
|
+
* - `text`: the file exists and is readable but isn't envelope-shaped JSON —
|
|
11
|
+
* this is the common case for in-process's `write_artifact` tool, which lets
|
|
12
|
+
* the model write arbitrary content (HTML, markdown, plain JSON, ...).
|
|
13
|
+
* - `unavailable`: nothing could be read back; `reason` says why. Every
|
|
14
|
+
* `unavailable` is a caller-visible degrade to metadata-only, never a
|
|
15
|
+
* thrown error — a missing/unreadable/outside-root/too-large artifact is
|
|
16
|
+
* an expected, honestly-reported state, not a route failure.
|
|
17
|
+
*/
|
|
18
|
+
export type ArtifactContentDto = {
|
|
19
|
+
readonly form: "envelope";
|
|
20
|
+
readonly envelope: RenderEnvelope;
|
|
21
|
+
} | {
|
|
22
|
+
readonly form: "text";
|
|
23
|
+
readonly text: string;
|
|
24
|
+
readonly truncated: boolean;
|
|
25
|
+
} | {
|
|
26
|
+
readonly form: "unavailable";
|
|
27
|
+
readonly reason: ArtifactContentUnavailableReason;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Resolves an `ArtifactRow.location` back to its on-disk content, scoped to
|
|
31
|
+
* `artifactsRoot` (the same root `resolveArtifactsDir` computes for in-process/B
|
|
32
|
+
* writes — see `in-process.ts` and `turn.ts`'s `maybeCreateDispatchedArtifact`).
|
|
33
|
+
* `location` may be relative (in-process, written under the model's chosen
|
|
34
|
+
* `write_artifact` path, itself scoped to `artifactsRoot` at write time — see
|
|
35
|
+
* `resolveWithinScope` in `exec/local.ts`) or absolute (dispatched always writes
|
|
36
|
+
* an absolute `location`).
|
|
37
|
+
*
|
|
38
|
+
* Containment is checked twice, mirroring `resolveWithinScope`'s
|
|
39
|
+
* `path.relative()`-based escape check (never `startsWith`, which a sibling
|
|
40
|
+
* directory that merely shares the root as a string prefix — e.g.
|
|
41
|
+
* `<root>-evil` — would defeat):
|
|
42
|
+
*
|
|
43
|
+
* 1. On the nominal (pre-existence) resolved path — so a location that's
|
|
44
|
+
* outside the root by construction (`../../etc/passwd`, or the sibling-
|
|
45
|
+
* prefix case above) is refused with zero filesystem access.
|
|
46
|
+
* 2. On the `realpath`-resolved path — so a symlink that lives inside the
|
|
47
|
+
* root but *points* outside it is also refused, after resolving the
|
|
48
|
+
* symlink (metadata only) but still before any content read.
|
|
49
|
+
*
|
|
50
|
+
* The read size is capped (`MAX_CONTENT_BYTES`) via `statSync` before the
|
|
51
|
+
* file is ever read or parsed.
|
|
52
|
+
*/
|
|
53
|
+
export declare function resolveArtifactContent(artifactsRoot: string, location: string): ArtifactContentDto;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { readFileSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/** Read-size cap applied before any read/parse — an artifact past
|
|
4
|
+
* this is refused as `too_large` rather than loaded whole into memory. */
|
|
5
|
+
const MAX_CONTENT_BYTES = 2 * 1024 * 1024; // 2 MiB
|
|
6
|
+
/**
|
|
7
|
+
* Resolves an `ArtifactRow.location` back to its on-disk content, scoped to
|
|
8
|
+
* `artifactsRoot` (the same root `resolveArtifactsDir` computes for in-process/B
|
|
9
|
+
* writes — see `in-process.ts` and `turn.ts`'s `maybeCreateDispatchedArtifact`).
|
|
10
|
+
* `location` may be relative (in-process, written under the model's chosen
|
|
11
|
+
* `write_artifact` path, itself scoped to `artifactsRoot` at write time — see
|
|
12
|
+
* `resolveWithinScope` in `exec/local.ts`) or absolute (dispatched always writes
|
|
13
|
+
* an absolute `location`).
|
|
14
|
+
*
|
|
15
|
+
* Containment is checked twice, mirroring `resolveWithinScope`'s
|
|
16
|
+
* `path.relative()`-based escape check (never `startsWith`, which a sibling
|
|
17
|
+
* directory that merely shares the root as a string prefix — e.g.
|
|
18
|
+
* `<root>-evil` — would defeat):
|
|
19
|
+
*
|
|
20
|
+
* 1. On the nominal (pre-existence) resolved path — so a location that's
|
|
21
|
+
* outside the root by construction (`../../etc/passwd`, or the sibling-
|
|
22
|
+
* prefix case above) is refused with zero filesystem access.
|
|
23
|
+
* 2. On the `realpath`-resolved path — so a symlink that lives inside the
|
|
24
|
+
* root but *points* outside it is also refused, after resolving the
|
|
25
|
+
* symlink (metadata only) but still before any content read.
|
|
26
|
+
*
|
|
27
|
+
* The read size is capped (`MAX_CONTENT_BYTES`) via `statSync` before the
|
|
28
|
+
* file is ever read or parsed.
|
|
29
|
+
*/
|
|
30
|
+
export function resolveArtifactContent(artifactsRoot, location) {
|
|
31
|
+
const nominal = path.isAbsolute(location) ? path.resolve(location) : path.resolve(artifactsRoot, location);
|
|
32
|
+
if (escapesRoot(artifactsRoot, nominal))
|
|
33
|
+
return { form: "unavailable", reason: "outside_root" };
|
|
34
|
+
let realRoot;
|
|
35
|
+
let realTarget;
|
|
36
|
+
try {
|
|
37
|
+
realRoot = realpathSync(artifactsRoot);
|
|
38
|
+
realTarget = realpathSync(nominal);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return { form: "unavailable", reason: "missing" };
|
|
42
|
+
}
|
|
43
|
+
if (escapesRoot(realRoot, realTarget))
|
|
44
|
+
return { form: "unavailable", reason: "outside_root" };
|
|
45
|
+
let size;
|
|
46
|
+
try {
|
|
47
|
+
size = statSync(realTarget).size;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return { form: "unavailable", reason: "unreadable" };
|
|
51
|
+
}
|
|
52
|
+
if (size > MAX_CONTENT_BYTES)
|
|
53
|
+
return { form: "unavailable", reason: "too_large" };
|
|
54
|
+
let raw;
|
|
55
|
+
try {
|
|
56
|
+
raw = readFileSync(realTarget, "utf-8");
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return { form: "unavailable", reason: "unreadable" };
|
|
60
|
+
}
|
|
61
|
+
const envelope = tryParseEnvelope(raw);
|
|
62
|
+
return envelope ? { form: "envelope", envelope } : { form: "text", text: raw, truncated: false };
|
|
63
|
+
}
|
|
64
|
+
/** Same escape condition as `exec/local.ts`'s `resolveWithinScope`. */
|
|
65
|
+
function escapesRoot(root, target) {
|
|
66
|
+
const relative = path.relative(root, target);
|
|
67
|
+
return relative.startsWith("..") || path.isAbsolute(relative);
|
|
68
|
+
}
|
|
69
|
+
function tryParseEnvelope(raw) {
|
|
70
|
+
let parsed;
|
|
71
|
+
try {
|
|
72
|
+
parsed = JSON.parse(raw);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
return hasBlocksArray(parsed) ? parsed : undefined;
|
|
78
|
+
}
|
|
79
|
+
/** Mirrors the private `hasBlocksArray` shape-check in `render/envelope.ts` — reimplemented
|
|
80
|
+
* locally rather than exported from there, to keep that module's blast radius unchanged. */
|
|
81
|
+
function hasBlocksArray(value) {
|
|
82
|
+
return typeof value === "object" && value !== null && Array.isArray(value.blocks);
|
|
83
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dispatched's own mapping FROM warble's `chat --stream-json` NDJSON stream TO this harness's
|
|
3
|
+
* mode-agnostic `AgentEvent` union (`harness/events/types.ts`). Two things live here:
|
|
4
|
+
*
|
|
5
|
+
* - `WarbleChatEventLike` — a LOCAL structural mirror of warble's own `WarbleChatEvent` vocabulary
|
|
6
|
+
* (`dispatcher/claude-agent-sdk/src/events.ts` in the warble repo). It is intentionally NOT
|
|
7
|
+
* imported from warble: warble is a separate process/package this harness only ever talks to
|
|
8
|
+
* over a subprocess's stdout, never a library dependency, so the wire contract is re-declared
|
|
9
|
+
* here by hand rather than crossing that boundary with a type import. Keep it in sync with
|
|
10
|
+
* warble's vocabulary if that ever changes.
|
|
11
|
+
* - `mapChatEventToAgentEvent` — a PURE function turning one parsed `WarbleChatEventLike` plus a
|
|
12
|
+
* small piece of running state (the current enclosing step id, and callId/stepId -> name
|
|
13
|
+
* memory) into zero or one `AgentEventInput`. Pure so it's unit-testable with plain data, no
|
|
14
|
+
* process/stream involved.
|
|
15
|
+
*
|
|
16
|
+
* Step/tool id mapping: warble's own mapper emits ONE enclosing `step_start` per turn (id = the
|
|
17
|
+
* Dispatched component's verb) and groups every `tool_call`/`tool_result` under it — see that
|
|
18
|
+
* module's doc comment for why. This function carries that same single `stepId` forward onto
|
|
19
|
+
* every `tool.call`/`tool.result` it produces (`ToolCallEvent`/`ToolResultEvent` both require a
|
|
20
|
+
* `stepId`), tracked in `state.currentStepId` as `step_start` events arrive.
|
|
21
|
+
*
|
|
22
|
+
* `answer` is deliberately mapped to `undefined` — the turn's final answer text is handled by the
|
|
23
|
+
* caller (`runDispatchedDefault`'s existing `answer`/`run.finish` emission), not duplicated here. `session`
|
|
24
|
+
* is likewise mapped to `undefined` for the same reason: it is session-resume metadata (the SDK's
|
|
25
|
+
* `session_id`, forwarded on both a successful AND a failed/`error_max_turns` turn), not a work-log
|
|
26
|
+
* event, and `spawnChat` intercepts it directly rather than routing it through this mapper.
|
|
27
|
+
*/
|
|
28
|
+
import type { AgentEventInput } from "../events/index.js";
|
|
29
|
+
export type WarbleChatEventLike = {
|
|
30
|
+
readonly t: "step_start";
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly name: string;
|
|
33
|
+
readonly parent: string | null;
|
|
34
|
+
readonly depth: number;
|
|
35
|
+
} | {
|
|
36
|
+
readonly t: "step_finish";
|
|
37
|
+
readonly id: string;
|
|
38
|
+
readonly ok: boolean;
|
|
39
|
+
readonly detail?: string;
|
|
40
|
+
} | {
|
|
41
|
+
readonly t: "tool_call";
|
|
42
|
+
readonly id: string;
|
|
43
|
+
readonly name: string;
|
|
44
|
+
readonly input?: unknown;
|
|
45
|
+
readonly parent: string | null;
|
|
46
|
+
readonly depth: number;
|
|
47
|
+
} | {
|
|
48
|
+
readonly t: "tool_result";
|
|
49
|
+
readonly id: string;
|
|
50
|
+
readonly ok: boolean;
|
|
51
|
+
readonly summary?: string;
|
|
52
|
+
readonly error?: string;
|
|
53
|
+
} | {
|
|
54
|
+
readonly t: "answer";
|
|
55
|
+
readonly text: string;
|
|
56
|
+
} | {
|
|
57
|
+
/**
|
|
58
|
+
* The turn's SDK session id (multi-turn resume anchor), emitted once per turn by
|
|
59
|
+
* `warble-agent-sdk chat --stream-json` on success AND on a failed turn (e.g.
|
|
60
|
+
* `error_max_turns`) — mirrors warble's own `WarbleChatEvent`'s `session` variant
|
|
61
|
+
* (`dispatcher/claude-agent-sdk/src/events.ts`). `id` is null only if the SDK never
|
|
62
|
+
* produced a session id at all.
|
|
63
|
+
*/
|
|
64
|
+
readonly t: "session";
|
|
65
|
+
readonly id: string | null;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Parse + structurally validate one NDJSON line from `warble-agent-sdk chat --stream-json`.
|
|
69
|
+
* Returns `undefined` for a blank line, invalid JSON, or a shape that doesn't match any known
|
|
70
|
+
* `WarbleChatEventLike` variant — the caller drops such lines rather than failing the whole turn
|
|
71
|
+
* over one malformed/forward-incompatible line.
|
|
72
|
+
*/
|
|
73
|
+
export declare function parseWarbleChatEventLine(line: string): WarbleChatEventLike | undefined;
|
|
74
|
+
/**
|
|
75
|
+
* Running state threaded across a turn's whole NDJSON stream. `currentStepId` is warble's single
|
|
76
|
+
* enclosing step for the turn (set on `step_start`); `stepNames`/`toolNames` remember the
|
|
77
|
+
* human-readable name for a given step/call id so a later `step_finish`/`tool_result` — which
|
|
78
|
+
* carry no name of their own in warble's vocabulary — can still populate `StepFinishEvent.name` /
|
|
79
|
+
* `ToolResultEvent.tool`.
|
|
80
|
+
*/
|
|
81
|
+
export interface ChatEventMapperState {
|
|
82
|
+
currentStepId: string | undefined;
|
|
83
|
+
readonly stepNames: Map<string, string>;
|
|
84
|
+
readonly toolNames: Map<string, string>;
|
|
85
|
+
}
|
|
86
|
+
export declare function createChatEventMapperState(): ChatEventMapperState;
|
|
87
|
+
/**
|
|
88
|
+
* Maps one parsed warble chat event to the harness's `AgentEvent` vocabulary, mutating `state` as
|
|
89
|
+
* it goes (remembering step/tool names, tracking the current enclosing step). Returns `undefined`
|
|
90
|
+
* for `answer` (handled by the caller) and for a `tool_call`/`tool_result` this mapper cannot
|
|
91
|
+
* place under any known step — that should never happen given warble always opens its enclosing
|
|
92
|
+
* step before emitting a tool event, but falling back to the raw id rather than throwing keeps a
|
|
93
|
+
* single malformed/reordered line from crashing an otherwise-healthy live stream.
|
|
94
|
+
*/
|
|
95
|
+
export declare function mapChatEventToAgentEvent(raw: WarbleChatEventLike, state: ChatEventMapperState): AgentEventInput | undefined;
|