akm-cli 0.9.31 → 0.10.261002-beta
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/CHANGELOG.md +420 -0
- package/STABILITY.md +99 -57
- package/dist/api-node.mjs +20 -0
- package/dist/api.d.ts +23 -0
- package/dist/api.js +45 -0
- package/dist/assets/hints/cli-hints-full.md +15 -29
- package/dist/assets/hints/cli-hints-short.md +2 -2
- package/dist/assets/improve-strategies/default.json +3 -3
- package/dist/assets/prompts/consolidate-pair.md +9 -7
- package/dist/assets/stash-skeleton/README.md +1 -1
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +1 -1
- package/dist/cli/shared.js +31 -48
- package/dist/cli/unknown-flags.js +6 -43
- package/dist/cli.js +8 -34
- package/dist/commands/agent/contribute-cli.js +1 -1
- package/dist/commands/completions.js +1 -2
- package/dist/commands/config-cli.js +5 -28
- package/dist/commands/env/env-cli.js +5 -5
- package/dist/commands/env/secret-cli.js +2 -2
- package/dist/commands/feedback-cli.js +75 -49
- package/dist/commands/health/checks.js +41 -16
- package/dist/commands/health/plugin-staleness.js +52 -22
- package/dist/commands/improve/autonomy-gate.js +5 -3
- package/dist/commands/improve/consolidate/chunking.js +3 -10
- package/dist/commands/improve/consolidate/continuity-check.js +4 -4
- package/dist/commands/improve/consolidate/pair-pass.js +29 -10
- package/dist/commands/improve/consolidate.js +10 -65
- package/dist/commands/improve/distill/lesson-update.js +91 -0
- package/dist/commands/improve/distill-guards.js +1 -43
- package/dist/commands/improve/distill.js +148 -21
- package/dist/commands/improve/eligibility.js +14 -1
- package/dist/commands/improve/extract-cli.js +1 -1
- package/dist/commands/improve/extract-prompt.js +17 -7
- package/dist/commands/improve/extract.js +5 -7
- package/dist/commands/improve/improve-cli.js +92 -106
- package/dist/commands/improve/improve-strategies.js +8 -12
- package/dist/commands/improve/improve.js +134 -52
- package/dist/commands/improve/loop-stages.js +18 -5
- package/dist/commands/improve/planner.js +0 -1
- package/dist/commands/improve/preparation.js +40 -99
- package/dist/commands/improve/proactive-maintenance.js +2 -2
- package/dist/commands/improve/reflect-noise.js +3 -55
- package/dist/commands/improve/reflect.js +29 -34
- package/dist/commands/improve/retrieval-queries.js +37 -0
- package/dist/commands/improve/retrieval-scope.js +2 -2
- package/dist/commands/improve/salience.js +2 -9
- package/dist/commands/improve/session-asset.js +4 -1
- package/dist/commands/improve/stage.js +23 -12
- package/dist/commands/lint/index.js +5 -20
- package/dist/commands/observability-cli.js +3 -9
- package/dist/commands/proposal/drain.js +43 -6
- package/dist/commands/proposal/proposal-cli.js +11 -31
- package/dist/commands/proposal/repository.js +20 -14
- package/dist/commands/proposal/validators/proposal-quality-validators.js +0 -3
- package/dist/commands/read/knowledge.js +3 -3
- package/dist/commands/read/remember-cli.js +0 -13
- package/dist/commands/read/search-cli.js +29 -97
- package/dist/commands/read/search.js +0 -11
- package/dist/commands/read/show.js +0 -19
- package/dist/commands/sources/plugin-upgrade.js +164 -101
- package/dist/commands/sources/sources-cli.js +1 -15
- package/dist/commands/sources/stash-cli.js +6 -14
- package/dist/commands/tasks/tasks-cli.js +4 -115
- package/dist/commands/workflow/plan.js +11 -33
- package/dist/commands/workflow-cli.js +2 -7
- package/dist/core/adapter/adapter-ids.js +1 -2
- package/dist/core/adapter/adapters/agent-skills-adapter.js +0 -4
- package/dist/core/adapter/adapters/akm-adapter.js +9 -48
- package/dist/core/adapter/adapters/akm-lint.js +10 -26
- package/dist/core/adapter/adapters/akm-metadata.js +2 -72
- package/dist/core/adapter/adapters/akm-task-adapter.js +0 -5
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +9 -28
- package/dist/core/adapter/adapters/dotenv-adapter.js +1 -20
- package/dist/core/adapter/adapters/generic-files-adapter.js +0 -10
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +0 -4
- package/dist/core/adapter/adapters/tool-dir-shared.js +0 -24
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -4
- package/dist/core/adapter/recognize-match.js +3 -4
- package/dist/core/common.js +18 -2
- package/dist/core/config/config-schema.js +12 -15
- package/dist/core/config/config-types.js +6 -15
- package/dist/core/config/config-walker.js +6 -23
- package/dist/core/config/config.js +17 -80
- package/dist/core/config/engine-semantics.js +7 -3
- package/dist/core/config/schema/engines.js +7 -7
- package/dist/core/config/schema/improve-processes.js +10 -63
- package/dist/core/config/schema/improve.js +2 -21
- package/dist/core/config/schema/index-config.js +6 -39
- package/dist/core/env-secret-ref.js +2 -45
- package/dist/core/errors.js +1 -2
- package/dist/core/extra-params.js +2 -86
- package/dist/core/file-lock.js +2 -6
- package/dist/core/improve-result.js +13 -1
- package/dist/core/mutation-target.js +2 -2
- package/dist/core/paths.js +4 -0
- package/dist/core/recognition-util.js +4 -5
- package/dist/core/run-lock.js +1 -1
- package/dist/core/subprocess.js +49 -0
- package/dist/core/warn.js +21 -4
- package/dist/core/write-source.js +3 -3
- package/dist/execution/target-ref.js +1 -2
- package/dist/indexer/indexer.js +15 -6
- package/dist/indexer/passes/memory-inference.js +15 -6
- package/dist/indexer/search/db-search.js +49 -36
- package/dist/indexer/search/fts-query.js +0 -19
- package/dist/indexer/search/ranking.js +29 -1
- package/dist/indexer/usage/usage-events.js +21 -16
- package/dist/integrations/agent/engine-resolution.js +5 -2
- package/dist/integrations/agent/index.js +1 -1
- package/dist/integrations/agent/profiles.js +41 -98
- package/dist/integrations/agent/request-lowering.js +3 -3
- package/dist/integrations/harnesses/aider/index.js +5 -0
- package/dist/integrations/harnesses/amazonq/index.js +1 -0
- package/dist/integrations/harnesses/claude/config-import.js +2 -3
- package/dist/integrations/harnesses/claude/index.js +5 -0
- package/dist/integrations/harnesses/codex/index.js +1 -0
- package/dist/integrations/harnesses/copilot/index.js +1 -0
- package/dist/integrations/harnesses/gemini/index.js +1 -0
- package/dist/integrations/harnesses/index.js +18 -39
- package/dist/integrations/harnesses/opencode/agent-builder.js +60 -35
- package/dist/integrations/harnesses/opencode/config-import.js +121 -52
- package/dist/integrations/harnesses/opencode/index.js +22 -0
- package/dist/integrations/harnesses/opencode/plugin-layout.js +68 -0
- package/dist/integrations/harnesses/opencode/session-log-types.js +12 -0
- package/dist/integrations/harnesses/opencode/session-log-v1.js +72 -0
- package/dist/integrations/harnesses/opencode/session-log-v2.js +140 -0
- package/dist/integrations/harnesses/opencode/session-log.js +104 -115
- package/dist/integrations/harnesses/opencode/version.js +98 -0
- package/dist/integrations/harnesses/opencode-sdk/{harness.js → index.js} +5 -17
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +105 -206
- package/dist/integrations/harnesses/opencode-sdk/v1-adapter.js +199 -0
- package/dist/integrations/harnesses/opencode-sdk/v2-adapter.js +254 -0
- package/dist/integrations/harnesses/opencode-sdk/wire.js +66 -0
- package/dist/integrations/harnesses/openhands/index.js +1 -0
- package/dist/integrations/harnesses/pi/index.js +1 -0
- package/dist/integrations/harnesses/shared.js +2 -0
- package/dist/integrations/harnesses/types.js +2 -0
- package/dist/llm/client.js +41 -11
- package/dist/output/cli-hints.js +1 -1
- package/dist/output/context.js +10 -19
- package/dist/output/format-exempt.js +5 -8
- package/dist/output/shapes/helpers.js +1 -22
- package/dist/output/shapes/passthrough.js +0 -1
- package/dist/output/shapes.js +3 -31
- package/dist/output/stdout.js +5 -1
- package/dist/output/text/command-format.js +6 -1
- package/dist/output/text/workflow-format.js +4 -22
- package/dist/scripts/akm-migrate-node.js +15745 -16711
- package/dist/scripts/akm-migrate.js +16318 -17280
- package/dist/setup/detect.js +10 -4
- package/dist/setup/harness-config-import.js +5 -14
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/tasks.js +12 -13
- package/dist/sources/providers/git-stash.js +113 -16
- package/dist/storage/repositories/index-utility-repository.js +2 -32
- package/dist/storage/repositories/index-vec-repository.js +4 -0
- package/dist/storage/repositories/task-history-repository.js +0 -5
- package/dist/storage/repositories/workflow-runs-repository.js +5 -15
- package/dist/tasks/embedded.js +2 -1
- package/dist/tasks/run/run-command-task.js +9 -1
- package/dist/tasks/run/run-workflow-task.js +4 -3
- package/dist/tasks/run/task-history.js +0 -2
- package/dist/tasks/run/task-log.js +38 -1
- package/dist/tasks/schedule-gates.js +39 -0
- package/dist/tasks/scheduler-binding.js +0 -16
- package/dist/tasks/scheduler-sync.js +3 -97
- package/dist/tasks/source/bounded-document.js +1 -1
- package/dist/tasks/source/task-source-v4.js +2 -12
- package/dist/tasks/task-run-reserved-flags.js +2 -31
- package/dist/workflows/authoring/authoring.js +6 -6
- package/dist/workflows/compile.js +8 -65
- package/dist/workflows/exec/child-workflow.js +22 -3
- package/dist/workflows/exec/native-executor.js +12 -147
- package/dist/workflows/exec/run-workflow.js +31 -103
- package/dist/workflows/exec/step-work.js +9 -178
- package/dist/workflows/exec/unit-dispatch.js +0 -10
- package/dist/workflows/freeze/environment.js +3 -44
- package/dist/workflows/freeze/freeze.js +8 -41
- package/dist/workflows/freeze/targets/child-workflow.js +27 -63
- package/dist/workflows/freeze/targets/command.js +2 -16
- package/dist/workflows/freeze/targets/shell.js +0 -2
- package/dist/workflows/freeze/task-bindings.js +2 -28
- package/dist/workflows/ir/params.js +12 -2
- package/dist/workflows/parser.js +82 -232
- package/dist/workflows/plan.js +1 -1
- package/dist/workflows/renderer.js +10 -19
- package/dist/workflows/runtime/run-plan.js +11 -53
- package/dist/workflows/runtime/runs.js +4 -27
- package/dist/workflows/source-files.js +12 -16
- package/dist/workflows/source-semantics.js +1 -93
- package/docs/README.md +1 -1
- package/docs/agents/README.md +3 -3
- package/docs/agents/agent-install.md +1 -1
- package/docs/architecture/0.10-process-rules.md +827 -0
- package/docs/architecture/adapters.md +17 -31
- package/docs/architecture/architecture.md +4 -5
- package/docs/architecture/improvement.md +115 -76
- package/docs/architecture/internals/improve-workflow.md +22 -16
- package/docs/architecture/internals/indexing.md +1 -1
- package/docs/architecture/internals/search.md +1 -2
- package/docs/architecture/internals/storage-locations.md +6 -6
- package/docs/architecture/persisted-data-compat.md +13 -4
- package/docs/architecture/specs/0.9.0-decisions.md +6 -2
- package/docs/architecture/specs/improve-collapse-churn-detector-design.md +2 -2
- package/docs/architecture/specs/workflow-format-unification.md +1 -6
- package/docs/architecture/testing/manual-testing-checklist.md +21 -30
- package/docs/architecture/testing/task-workflow-manual-tests.md +106 -203
- package/docs/architecture/testing/testing-workflow.md +1 -1
- package/docs/architecture/workflow-engine.md +5 -56
- package/docs/guides/author-workflows.md +39 -69
- package/docs/guides/bundles.md +3 -1
- package/docs/guides/capture-knowledge.md +1 -1
- package/docs/guides/claude-code-vs-akm-workflows.md +1 -2
- package/docs/guides/discover-and-load.md +4 -4
- package/docs/guides/getting-started.md +1 -1
- package/docs/guides/improve-the-library.md +10 -9
- package/docs/guides/recipes/headless-install.md +2 -2
- package/docs/guides/run-workflows.md +1 -1
- package/docs/guides/scheduling.md +9 -1
- package/docs/guides/use-with-any-agent.md +49 -4
- package/docs/integration/bundling-akm.md +7 -6
- package/docs/maintainers/curate-workmap.md +1 -2
- package/docs/maintainers/release-checklist.md +33 -1
- package/docs/migration/README.md +2 -0
- package/docs/migration/release-notes/0.10.0.md +201 -0
- package/docs/migration/release-notes/README.md +1 -0
- package/docs/migration/v0.9-to-v0.10.md +507 -0
- package/docs/posts/akm-command-tour.md +3 -3
- package/docs/product-surface.md +1 -1
- package/docs/reference/README.md +1 -0
- package/docs/reference/api.md +65 -0
- package/docs/reference/asset-types.md +3 -5
- package/docs/reference/bundle-types.md +2 -4
- package/docs/reference/cli.md +160 -188
- package/docs/reference/configuration.md +101 -20
- package/docs/reference/data-and-telemetry.md +2 -2
- package/docs/reference/env-and-secrets.md +2 -2
- package/docs/reference/supported-formats.md +3 -4
- package/docs/reference/tasks.md +12 -19
- package/docs/reference/website-sources.md +1 -1
- package/docs/reference/workflow-schema.md +89 -317
- package/docs/reference/workflows.md +20 -48
- package/package.json +12 -2
- package/schemas/akm-config.json +2 -243
- package/schemas/akm-task.json +2 -3
- package/schemas/akm-workflow.json +47 -84
- package/dist/assets/improve-strategies/catchup.json +0 -41
- package/dist/assets/improve-strategies/proactive-maintenance.json +0 -24
- package/dist/assets/improve-strategies/quick.json +0 -17
- package/dist/assets/improve-strategies/reflect-distill.json +0 -29
- package/dist/assets/improve-strategies/thorough.json +0 -49
- package/dist/assets/prompts/retrieval-relevance-judge.md +0 -6
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +0 -6
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +0 -4
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +0 -4
- package/dist/cli/retired-commands.js +0 -118
- package/dist/commands/improve/retrieval-gate.js +0 -132
- package/dist/commands/tasks/validate.js +0 -176
- package/dist/core/config/legacy-source-shape-shim.js +0 -117
- package/dist/indexer/feedback/utility-policy.js +0 -75
- package/dist/integrations/harnesses/ids.js +0 -28
- package/dist/tasks/source/task-source-v3-frozen.js +0 -424
- package/dist/tasks/source/task-to-v4.js +0 -863
- package/dist/workflows/exec/worktree.js +0 -372
- package/dist/workflows/freeze/child-output-references.js +0 -83
- package/dist/workflows/freeze/identity.js +0 -22
- package/dist/workflows/freeze/targets/script.js +0 -50
- package/dist/workflows/freeze/targets/task.js +0 -124
- package/dist/workflows/github-yaml.js +0 -506
- package/dist/workflows/runtime/run-outputs.js +0 -80
|
@@ -16,6 +16,17 @@
|
|
|
16
16
|
* 'opencode-sdk'`). It is the dispatch path for SDK runner specs; it exposes
|
|
17
17
|
* no native session logs of its own (`capabilities.sessionLogs = false`).
|
|
18
18
|
*
|
|
19
|
+
* ## Two OpenCode majors, one runner (#1049)
|
|
20
|
+
*
|
|
21
|
+
* This file owns what both majors share: the managed `opencode serve` child
|
|
22
|
+
* (spawn, port, registry, cleanup, {@link closeServer}), the child environment,
|
|
23
|
+
* per-workspace isolation, deadlines, abort handling and result shaping. The
|
|
24
|
+
* per-major wire protocol is an adapter behind `./wire.ts`, chosen once per
|
|
25
|
+
* dispatch from the major the engine's binary reports
|
|
26
|
+
* (`../opencode/version.ts`); the major is part of the server registry key.
|
|
27
|
+
* Where the sections below name an OpenCode 1 wire shape (`query.directory`,
|
|
28
|
+
* the prompt body), `v2-adapter.ts` has the OpenCode 2 equivalent.
|
|
29
|
+
*
|
|
19
30
|
* ## Sessions are kept (#1100)
|
|
20
31
|
*
|
|
21
32
|
* One session is created per dispatch and is NEVER deleted: it stays in
|
|
@@ -84,7 +95,7 @@
|
|
|
84
95
|
* until the child ACTUALLY exits — and a real `opencode serve` (a live HTTP
|
|
85
96
|
* server with provider children) can outlive SIGTERM long enough to hang the
|
|
86
97
|
* caller indefinitely. {@link createManagedOpencode} therefore owns the spawn
|
|
87
|
-
* (the SDK package is used only for
|
|
98
|
+
* (the SDK package is used only for its client).
|
|
88
99
|
*
|
|
89
100
|
* Owning the spawn changes the LIFECYCLE, not the REQUIREMENT: the SDK's
|
|
90
101
|
* `createOpencodeServer` is itself a `spawn("opencode", ["serve", ...])`, and
|
|
@@ -102,11 +113,14 @@
|
|
|
102
113
|
*/
|
|
103
114
|
import { spawn } from "node:child_process";
|
|
104
115
|
import { createHash } from "node:crypto";
|
|
105
|
-
import { isRecord } from "../../../core/common.js";
|
|
106
116
|
import { COMMON_SPAWN_ENV_PASSTHROUGH, OPENCODE_ENV_PASSTHROUGH, spawnEnvNamesFor } from "../../../core/spawn-env.js";
|
|
107
117
|
import { DEFAULT_AGENT_TIMEOUT_MS } from "../../agent/config.js";
|
|
108
118
|
import { opencodeInferenceConfig } from "../opencode/model-config.js";
|
|
109
119
|
import { MODEL_WORK_OPENCODE_AGENT, modelWorkOpencodeConfig, modelWorkPluginEnv } from "../opencode/model-work-agent.js";
|
|
120
|
+
import { detectOpencodeMajor } from "../opencode/version.js";
|
|
121
|
+
import { V1_ADAPTER } from "./v1-adapter.js";
|
|
122
|
+
import { V2_ADAPTER } from "./v2-adapter.js";
|
|
123
|
+
import { parseReadiness, } from "./wire.js";
|
|
110
124
|
// Server registry — one server per complete server-material signature. Caller
|
|
111
125
|
// deadlines race the shared promise independently; they never become startup
|
|
112
126
|
// configuration inherited by later callers.
|
|
@@ -299,9 +313,10 @@ export function buildSdkConfig(profile, llmConfig, modelWork = false, requestInf
|
|
|
299
313
|
}
|
|
300
314
|
return modelWork ? { ...sdkConfig, ...modelWorkOpencodeConfig(agentOptions) } : sdkConfig;
|
|
301
315
|
}
|
|
302
|
-
/** Digest the executable and exact environment received by the child. */
|
|
303
|
-
function serverRegistryKey(profile, env) {
|
|
304
|
-
|
|
316
|
+
/** Digest the OpenCode major, the executable and the exact environment received by the child. */
|
|
317
|
+
function serverRegistryKey(adapter, profile, env) {
|
|
318
|
+
// The major is part of the identity: a v1 and a v2 server never share an entry.
|
|
319
|
+
const material = { major: adapter.major, bin: profile.bin, env };
|
|
305
320
|
return createHash("sha256")
|
|
306
321
|
.update(JSON.stringify(canonicalize(material)))
|
|
307
322
|
.digest("hex");
|
|
@@ -425,13 +440,13 @@ export function __setServeCommand(argv) {
|
|
|
425
440
|
* - handshake failure → the child is killed and unref'ed before rejecting.
|
|
426
441
|
*/
|
|
427
442
|
async function createManagedOpencode(options) {
|
|
428
|
-
const {
|
|
429
|
-
throw new Error("OpenCode SDK not available. Install @opencode-ai/sdk or configure a CLI agent instead.");
|
|
430
|
-
}));
|
|
443
|
+
const { adapter } = options;
|
|
431
444
|
const port = options.port ?? DEFAULT_SDK_PORT;
|
|
432
|
-
const argv = _serveCommand ??
|
|
445
|
+
const argv = _serveCommand ?? adapter.serveArgv(options.bin ?? "opencode", port);
|
|
446
|
+
// The adapter may add to the child's environment (V2: the server password akm chose).
|
|
447
|
+
const authorization = adapter.authorize();
|
|
433
448
|
const proc = spawn(argv[0], argv.slice(1), {
|
|
434
|
-
env: options.env,
|
|
449
|
+
env: { ...options.env, ...authorization.env },
|
|
435
450
|
stdio: ["ignore", "pipe", "pipe"],
|
|
436
451
|
});
|
|
437
452
|
let closeStarted = false;
|
|
@@ -516,15 +531,14 @@ async function createManagedOpencode(options) {
|
|
|
516
531
|
const onStdoutData = (chunk) => {
|
|
517
532
|
output += chunk.toString();
|
|
518
533
|
for (const line of output.split("\n")) {
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
succeed(
|
|
526
|
-
|
|
527
|
-
}
|
|
534
|
+
const verdict = parseReadiness(line, adapter.major);
|
|
535
|
+
if (!verdict)
|
|
536
|
+
continue;
|
|
537
|
+
if ("error" in verdict)
|
|
538
|
+
fail(new Error(verdict.error));
|
|
539
|
+
else
|
|
540
|
+
succeed(verdict.url);
|
|
541
|
+
return;
|
|
528
542
|
}
|
|
529
543
|
};
|
|
530
544
|
const onStderrData = (chunk) => {
|
|
@@ -553,62 +567,18 @@ async function createManagedOpencode(options) {
|
|
|
553
567
|
proc.stdout?.destroy();
|
|
554
568
|
proc.stderr?.destroy();
|
|
555
569
|
proc.unref();
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
* cannot be closed safely: aborting it leaves an unhandled `AbortError`
|
|
563
|
-
* rejection (from its un-awaited `reader.cancel()`), which akm's global
|
|
564
|
-
* handler turns into a process exit. This reader ends quietly on abort.
|
|
565
|
-
*/
|
|
566
|
-
async function subscribeEvents(baseUrl, args) {
|
|
567
|
-
const eventUrl = new URL("/event", baseUrl);
|
|
568
|
-
if (args.query?.directory)
|
|
569
|
-
eventUrl.searchParams.set("directory", args.query.directory);
|
|
570
|
-
const response = await fetch(eventUrl, { signal: args.signal, headers: { accept: "text/event-stream" } });
|
|
571
|
-
if (!response.ok || !response.body)
|
|
572
|
-
throw new Error(`OpenCode event stream failed: HTTP ${response.status}`);
|
|
573
|
-
const reader = response.body.getReader();
|
|
574
|
-
async function* stream() {
|
|
575
|
-
const decoder = new TextDecoder();
|
|
576
|
-
let buffer = "";
|
|
577
|
-
try {
|
|
578
|
-
while (true) {
|
|
579
|
-
const { done, value } = await reader.read();
|
|
580
|
-
if (done)
|
|
581
|
-
return;
|
|
582
|
-
buffer += decoder.decode(value, { stream: true });
|
|
583
|
-
const frames = buffer.split("\n\n");
|
|
584
|
-
buffer = frames.pop() ?? "";
|
|
585
|
-
for (const frame of frames) {
|
|
586
|
-
const data = frame
|
|
587
|
-
.split("\n")
|
|
588
|
-
.filter((line) => line.startsWith("data:"))
|
|
589
|
-
.map((line) => line.replace(/^data:\s*/, ""))
|
|
590
|
-
.join("\n");
|
|
591
|
-
if (!data)
|
|
592
|
-
continue;
|
|
593
|
-
try {
|
|
594
|
-
yield JSON.parse(data);
|
|
595
|
-
}
|
|
596
|
-
catch {
|
|
597
|
-
/* not JSON: not an event we act on */
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
catch (err) {
|
|
603
|
-
if (!args.signal.aborted)
|
|
604
|
-
throw err;
|
|
605
|
-
}
|
|
570
|
+
try {
|
|
571
|
+
return { client: await adapter.connect(url, authorization.credentials), server: { close: closeManaged } };
|
|
572
|
+
}
|
|
573
|
+
catch (err) {
|
|
574
|
+
closeManaged();
|
|
575
|
+
throw err;
|
|
606
576
|
}
|
|
607
|
-
return { stream: stream() };
|
|
608
577
|
}
|
|
609
|
-
async function startServer(profile, sdkConfig, env, registryKey, startupSignal) {
|
|
578
|
+
async function startServer(adapter, profile, sdkConfig, env, registryKey, startupSignal) {
|
|
610
579
|
const factory = _serverFactory ?? createManagedOpencode;
|
|
611
580
|
const options = {
|
|
581
|
+
adapter,
|
|
612
582
|
bin: profile.bin,
|
|
613
583
|
...(Object.keys(sdkConfig).length > 0 ? { config: sdkConfig } : {}),
|
|
614
584
|
env,
|
|
@@ -651,17 +621,17 @@ async function startServer(profile, sdkConfig, env, registryKey, startupSignal)
|
|
|
651
621
|
* start (the registry stores the in-flight promise). A failed start is
|
|
652
622
|
* evicted so the next call can retry instead of caching the error forever.
|
|
653
623
|
*/
|
|
654
|
-
function getOrStartServer(profile, llmConfig, env, envSource = process.env, modelWork = false, inference) {
|
|
624
|
+
function getOrStartServer(adapter, profile, llmConfig, env, envSource = process.env, modelWork = false, inference) {
|
|
655
625
|
if (_testServer)
|
|
656
626
|
return { promise: Promise.resolve(_testServer), release() { } };
|
|
657
627
|
const sdkConfig = buildSdkConfig(profile, llmConfig, modelWork, inference);
|
|
658
628
|
const serverEnv = buildServerEnv(profile, sdkConfig, env, envSource, modelWork);
|
|
659
|
-
const key = serverRegistryKey(profile, serverEnv);
|
|
629
|
+
const key = serverRegistryKey(adapter, profile, serverEnv);
|
|
660
630
|
let entry = _servers.get(key);
|
|
661
631
|
if (!entry) {
|
|
662
632
|
const controller = new AbortController();
|
|
663
633
|
entry = {
|
|
664
|
-
promise: startServer(profile, sdkConfig, serverEnv, key, controller.signal),
|
|
634
|
+
promise: startServer(adapter, profile, sdkConfig, serverEnv, key, controller.signal),
|
|
665
635
|
controller,
|
|
666
636
|
waiters: 0,
|
|
667
637
|
};
|
|
@@ -699,25 +669,6 @@ function getOrStartServer(profile, llmConfig, env, envSource = process.env, mode
|
|
|
699
669
|
},
|
|
700
670
|
};
|
|
701
671
|
}
|
|
702
|
-
/**
|
|
703
|
-
* Extract best-effort token usage from a prompt response. Only numeric
|
|
704
|
-
* fields the server actually reported are copied; returns undefined when
|
|
705
|
-
* nothing usable is present (older servers, test fakes).
|
|
706
|
-
*/
|
|
707
|
-
function extractUsage(info) {
|
|
708
|
-
const tokens = info?.tokens;
|
|
709
|
-
if (!tokens)
|
|
710
|
-
return undefined;
|
|
711
|
-
const usage = {};
|
|
712
|
-
if (typeof tokens.input === "number" && Number.isFinite(tokens.input))
|
|
713
|
-
usage.inputTokens = tokens.input;
|
|
714
|
-
if (typeof tokens.output === "number" && Number.isFinite(tokens.output))
|
|
715
|
-
usage.outputTokens = tokens.output;
|
|
716
|
-
if (typeof tokens.reasoning === "number" && Number.isFinite(tokens.reasoning)) {
|
|
717
|
-
usage.reasoningTokens = tokens.reasoning;
|
|
718
|
-
}
|
|
719
|
-
return Object.keys(usage).length > 0 ? usage : undefined;
|
|
720
|
-
}
|
|
721
672
|
const SDK_OPERATION_TIMED_OUT = Symbol("opencode-sdk-operation-timeout");
|
|
722
673
|
const SDK_OPERATION_ABORTED = Symbol("opencode-sdk-operation-aborted");
|
|
723
674
|
async function raceSdkOperation(operation, opts) {
|
|
@@ -753,29 +704,6 @@ async function raceSdkOperation(operation, opts) {
|
|
|
753
704
|
function errorText(err) {
|
|
754
705
|
return err instanceof Error ? err.message : String(err);
|
|
755
706
|
}
|
|
756
|
-
/**
|
|
757
|
-
* Map an SDK `{ error }` result or a reply's `info.error` onto a failure
|
|
758
|
-
* (#1015). Both are opencode NamedErrors, `{ name, data: { message } }`. An
|
|
759
|
-
* aborted message is `aborted`, a reply cut off at the output limit is
|
|
760
|
-
* `parse_error`, and every other error (auth, API, unknown, an HTTP error
|
|
761
|
-
* body) is `non_zero_exit`.
|
|
762
|
-
*/
|
|
763
|
-
function sdkErrorFailure(error) {
|
|
764
|
-
if (!isRecord(error) || typeof error.name !== "string") {
|
|
765
|
-
return { reason: "non_zero_exit", message: typeof error === "string" ? error : JSON.stringify(error) };
|
|
766
|
-
}
|
|
767
|
-
const detail = isRecord(error.data) ? error.data.message : undefined;
|
|
768
|
-
const message = typeof detail === "string" ? `${error.name}: ${detail}` : error.name;
|
|
769
|
-
if (error.name === "MessageAbortedError")
|
|
770
|
-
return { reason: "aborted", message };
|
|
771
|
-
if (error.name === "MessageOutputLengthError")
|
|
772
|
-
return { reason: "parse_error", message };
|
|
773
|
-
return { reason: "non_zero_exit", message };
|
|
774
|
-
}
|
|
775
|
-
/** Stop a server-side session that the dispatch has given up on, so it stops calling the model. */
|
|
776
|
-
function abortSessionBestEffort(client, sessionId, query) {
|
|
777
|
-
void client.session.abort?.({ path: { id: sessionId }, ...(query ? { query } : {}) }).catch(() => { });
|
|
778
|
-
}
|
|
779
707
|
/**
|
|
780
708
|
* Answer every OpenCode permission request of the dispatch's session (and of
|
|
781
709
|
* the sub-sessions it spawns) with "reject", once each. This is what a
|
|
@@ -785,78 +713,52 @@ function abortSessionBestEffort(client, sessionId, query) {
|
|
|
785
713
|
* model as a tool error it can work around. Each request and answer is
|
|
786
714
|
* appended to `notes`. The same stream reports OpenCode's retries (#1108):
|
|
787
715
|
* each is noted, and one caused by a usage limit, or due after `deadline`,
|
|
788
|
-
* ends the run through `stopForRetry` instead of a silent wait. `ready`
|
|
716
|
+
* ends the run through `stopForRetry` instead of a silent wait. `ready`
|
|
789
717
|
* settles once the subscription is open and never rejects; `stop` closes it.
|
|
718
|
+
* The wire format (event shapes, the reject call) is the adapter's.
|
|
790
719
|
*/
|
|
791
|
-
function rejectPermissionRequests(client, sessionId,
|
|
792
|
-
const subscribe = client.event?.subscribe;
|
|
793
|
-
const reply = client.postSessionIdPermissionsPermissionId;
|
|
720
|
+
function rejectPermissionRequests(adapter, client, sessionId, spec, notes, deadline, stopForRetry) {
|
|
794
721
|
const controller = new AbortController();
|
|
795
722
|
const stop = () => controller.abort();
|
|
796
|
-
if (
|
|
723
|
+
if (adapter.canWatch?.(client) === false)
|
|
797
724
|
return { stop };
|
|
798
725
|
const sessions = new Set([sessionId]);
|
|
799
726
|
const answered = new Set();
|
|
800
727
|
const consume = async (stream) => {
|
|
801
|
-
for await (const
|
|
802
|
-
|
|
728
|
+
for await (const raw of stream) {
|
|
729
|
+
const event = adapter.decodeEvent(raw);
|
|
730
|
+
if (!event)
|
|
803
731
|
continue;
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
if (typeof id === "string" && typeof parentID === "string" && sessions.has(parentID))
|
|
808
|
-
sessions.add(id);
|
|
732
|
+
if (event.kind === "session") {
|
|
733
|
+
if (event.parentId !== undefined && sessions.has(event.parentId))
|
|
734
|
+
sessions.add(event.id);
|
|
809
735
|
continue;
|
|
810
736
|
}
|
|
811
|
-
if (event.
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
const next = typeof status.next === "number" ? status.next : undefined;
|
|
819
|
-
const retryAt = next === undefined ? "unknown time" : new Date(next).toISOString();
|
|
820
|
-
// `action` is not present in the SDK's retry type yet, but current
|
|
821
|
-
// OpenCode servers include it for provider/account limits.
|
|
822
|
-
const action = isRecord(status.action) ? status.action : undefined;
|
|
823
|
-
const provider = typeof action?.provider === "string" ? action.provider : undefined;
|
|
824
|
-
notes.push(`OpenCode retry attempt ${attempt}${provider ? ` (${provider})` : ""}: ${message}; retrying at ${retryAt}`);
|
|
825
|
-
if (action?.reason === "account_rate_limit") {
|
|
826
|
-
stopForRetry(`OpenCode provider limit stopped this run${provider ? ` (${provider})` : ""}: ${message}; reset at ${retryAt}`);
|
|
737
|
+
if (!sessions.has(event.sessionId))
|
|
738
|
+
continue;
|
|
739
|
+
if (event.kind === "retry") {
|
|
740
|
+
const retryAt = event.next === undefined ? "unknown time" : new Date(event.next).toISOString();
|
|
741
|
+
notes.push(`OpenCode retry attempt ${event.attempt ?? "unknown"}${event.provider ? ` (${event.provider})` : ""}: ${event.message}; retrying at ${retryAt}`);
|
|
742
|
+
if (event.accountLimit) {
|
|
743
|
+
stopForRetry(`OpenCode provider limit stopped this run${event.provider ? ` (${event.provider})` : ""}: ${event.message}; reset at ${retryAt}`);
|
|
827
744
|
}
|
|
828
|
-
else if (next !== undefined && deadline !== null && next > deadline) {
|
|
829
|
-
stopForRetry(`OpenCode's next retry (${retryAt}) is after this run's deadline: ${message}`);
|
|
745
|
+
else if (event.next !== undefined && deadline !== null && event.next > deadline) {
|
|
746
|
+
stopForRetry(`OpenCode's next retry (${retryAt}) is after this run's deadline: ${event.message}`);
|
|
830
747
|
}
|
|
831
748
|
continue;
|
|
832
749
|
}
|
|
833
|
-
|
|
834
|
-
if (event.type !== "permission.asked" && event.type !== "permission.updated")
|
|
750
|
+
if (answered.has(event.requestId))
|
|
835
751
|
continue;
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
answered.add(id);
|
|
842
|
-
const patterns = Array.isArray(props.patterns) ? props.patterns : props.pattern ? [props.pattern] : [];
|
|
843
|
-
notes.push(`permission requested: ${String(props.permission ?? props.type ?? "unknown")} (${patterns.join(", ")}); auto-rejecting`);
|
|
844
|
-
reply
|
|
845
|
-
.call(client, {
|
|
846
|
-
path: { id: sessionID, permissionID: id },
|
|
847
|
-
body: { response: "reject" },
|
|
848
|
-
...(query ? { query } : {}),
|
|
849
|
-
})
|
|
850
|
-
.then((r) => {
|
|
851
|
-
if (r.error)
|
|
852
|
-
notes.push(`permission reply for ${id} failed: ${JSON.stringify(r.error)}`);
|
|
853
|
-
})
|
|
854
|
-
.catch((err) => notes.push(`permission reply for ${id} failed: ${errorText(err)}`));
|
|
752
|
+
answered.add(event.requestId);
|
|
753
|
+
notes.push(`permission requested: ${event.description}; auto-rejecting`);
|
|
754
|
+
adapter.rejectPermission(client, event, spec).catch((err) => {
|
|
755
|
+
notes.push(`permission reply for ${event.requestId} failed: ${errorText(err)}`);
|
|
756
|
+
});
|
|
855
757
|
}
|
|
856
758
|
};
|
|
857
|
-
const ready =
|
|
858
|
-
.
|
|
859
|
-
.then((
|
|
759
|
+
const ready = adapter
|
|
760
|
+
.subscribe(client, spec, controller.signal)
|
|
761
|
+
.then((stream) => {
|
|
860
762
|
void consume(stream).catch((err) => {
|
|
861
763
|
if (!controller.signal.aborted)
|
|
862
764
|
notes.push(`permission event stream ended: ${errorText(err)}`);
|
|
@@ -867,6 +769,11 @@ function rejectPermissionRequests(client, sessionId, query, notes, deadline, sto
|
|
|
867
769
|
});
|
|
868
770
|
return { ready, stop };
|
|
869
771
|
}
|
|
772
|
+
const ADAPTERS = { 1: V1_ADAPTER, 2: V2_ADAPTER };
|
|
773
|
+
/** The wire adapter for the OpenCode major the engine's binary reports. */
|
|
774
|
+
function adapterFor(profile) {
|
|
775
|
+
return ADAPTERS[detectOpencodeMajor(profile.bin).major];
|
|
776
|
+
}
|
|
870
777
|
function abortedBeforeSdkStart(profile) {
|
|
871
778
|
return {
|
|
872
779
|
ok: false,
|
|
@@ -888,12 +795,13 @@ export async function runOpencodeSdk(profile, prompt, opts = {}, llmConfig) {
|
|
|
888
795
|
if (opts.signal?.aborted)
|
|
889
796
|
return abortedBeforeSdkStart(profile);
|
|
890
797
|
const modelWork = opts.dispatch?.modelWork === true;
|
|
798
|
+
const adapter = adapterFor(profile);
|
|
891
799
|
let client;
|
|
892
800
|
if (_testServer) {
|
|
893
801
|
client = _testServer.client;
|
|
894
802
|
}
|
|
895
803
|
else {
|
|
896
|
-
const startupHandle = getOrStartServer(profile, llmConfig, opts.env, opts.envSource, modelWork, opts.dispatch?.inference);
|
|
804
|
+
const startupHandle = getOrStartServer(adapter, profile, llmConfig, opts.env, opts.envSource, modelWork, opts.dispatch?.inference);
|
|
897
805
|
try {
|
|
898
806
|
const startup = await raceSdkOperation(startupHandle.promise, {
|
|
899
807
|
timeoutMs: remainingTimeoutMs(),
|
|
@@ -945,10 +853,23 @@ export async function runOpencodeSdk(profile, prompt, opts = {}, llmConfig) {
|
|
|
945
853
|
// local-model endpoint or wedged server could block the caller indefinitely.
|
|
946
854
|
// The same absolute deadline covers server startup, session creation, and
|
|
947
855
|
// prompting. null disables every dispatch timer.
|
|
948
|
-
// Per-call working directory (module doc):
|
|
949
|
-
//
|
|
950
|
-
//
|
|
951
|
-
|
|
856
|
+
// Per-call working directory (module doc): the adapter scopes the session to
|
|
857
|
+
// it, so worktree-isolated units run in their own checkout without a
|
|
858
|
+
// per-cwd server.
|
|
859
|
+
// Forward the exact native agent selector, systemPrompt, and tools from the abstract
|
|
860
|
+
// dispatch request. Both were once accepted on AgentDispatchRequest but silently dropped
|
|
861
|
+
// on the SDK path (#564). Model work runs the confined agent its server config defines;
|
|
862
|
+
// its tools are that agent's.
|
|
863
|
+
const dispatch = opts.dispatch;
|
|
864
|
+
const agent = modelWork ? MODEL_WORK_OPENCODE_AGENT : dispatch?.agent;
|
|
865
|
+
const system = dispatch?.systemPrompt;
|
|
866
|
+
const tools = toolsToSdkAllowlist(dispatch?.tools);
|
|
867
|
+
const spec = {
|
|
868
|
+
...(opts.cwd ? { directory: opts.cwd } : {}),
|
|
869
|
+
...(agent ? { agent } : {}),
|
|
870
|
+
...(system ? { system } : {}),
|
|
871
|
+
...(tools ? { tools } : {}),
|
|
872
|
+
};
|
|
952
873
|
// One session per call — do NOT reuse (history accumulates, token costs grow).
|
|
953
874
|
// Session creation is startup plumbing, so failures map to spawn_failed rather
|
|
954
875
|
// than bubbling out as a generic workflow dispatch exception.
|
|
@@ -962,7 +883,7 @@ export async function runOpencodeSdk(profile, prompt, opts = {}, llmConfig) {
|
|
|
962
883
|
const abortSignal = runController.signal;
|
|
963
884
|
let sessionId;
|
|
964
885
|
try {
|
|
965
|
-
const created = await raceSdkOperation(
|
|
886
|
+
const created = await raceSdkOperation(adapter.createSession(client, spec), {
|
|
966
887
|
timeoutMs: remainingTimeoutMs(),
|
|
967
888
|
setTimeoutFn: setTimeoutImpl,
|
|
968
889
|
clearTimeoutFn: clearTimeoutImpl,
|
|
@@ -990,7 +911,7 @@ export async function runOpencodeSdk(profile, prompt, opts = {}, llmConfig) {
|
|
|
990
911
|
error: `opencode-sdk agent "${profile.name}" timed out creating a session after ${timeoutMs}ms`,
|
|
991
912
|
};
|
|
992
913
|
}
|
|
993
|
-
sessionId = created
|
|
914
|
+
sessionId = created;
|
|
994
915
|
}
|
|
995
916
|
catch (err) {
|
|
996
917
|
return {
|
|
@@ -1014,27 +935,11 @@ export async function runOpencodeSdk(profile, prompt, opts = {}, llmConfig) {
|
|
|
1014
935
|
error: "Failed to create OpenCode session",
|
|
1015
936
|
};
|
|
1016
937
|
}
|
|
1017
|
-
// Forward the exact native agent selector, systemPrompt, and tools from the abstract
|
|
1018
|
-
// dispatch request. Both were previously accepted on AgentDispatchRequest but
|
|
1019
|
-
// silently dropped on the SDK path, so SDK-mode dispatch ignored agent-asset
|
|
1020
|
-
// system prompts and tool policies entirely (the CLI path honours both).
|
|
1021
|
-
// Model work runs the confined agent its server config defines; its tools are that agent's.
|
|
1022
|
-
const dispatch = opts.dispatch;
|
|
1023
|
-
const agent = modelWork ? MODEL_WORK_OPENCODE_AGENT : dispatch?.agent;
|
|
1024
|
-
const system = dispatch?.systemPrompt;
|
|
1025
|
-
const tools = toolsToSdkAllowlist(dispatch?.tools);
|
|
1026
|
-
const body = { parts: [{ type: "text", text: prompt }] };
|
|
1027
|
-
if (agent)
|
|
1028
|
-
body.agent = agent;
|
|
1029
|
-
if (system)
|
|
1030
|
-
body.system = system;
|
|
1031
|
-
if (tools)
|
|
1032
|
-
body.tools = tools;
|
|
1033
938
|
let result;
|
|
1034
939
|
// Subscribed before the prompt so no request can be missed; closed in `finally`.
|
|
1035
940
|
const permissionNotes = [];
|
|
1036
941
|
let retryFailure;
|
|
1037
|
-
const permissions = rejectPermissionRequests(client, sessionId,
|
|
942
|
+
const permissions = rejectPermissionRequests(adapter, client, sessionId, spec, permissionNotes, deadline, (failure) => {
|
|
1038
943
|
retryFailure ??= failure;
|
|
1039
944
|
runController.abort();
|
|
1040
945
|
});
|
|
@@ -1048,7 +953,7 @@ export async function runOpencodeSdk(profile, prompt, opts = {}, llmConfig) {
|
|
|
1048
953
|
signal: abortSignal,
|
|
1049
954
|
});
|
|
1050
955
|
}
|
|
1051
|
-
const prompted = await raceSdkOperation(
|
|
956
|
+
const prompted = await raceSdkOperation(adapter.prompt(client, sessionId, prompt, spec, abortSignal), {
|
|
1052
957
|
timeoutMs: remainingTimeoutMs(),
|
|
1053
958
|
setTimeoutFn: setTimeoutImpl,
|
|
1054
959
|
clearTimeoutFn: clearTimeoutImpl,
|
|
@@ -1056,7 +961,7 @@ export async function runOpencodeSdk(profile, prompt, opts = {}, llmConfig) {
|
|
|
1056
961
|
});
|
|
1057
962
|
// A session the dispatch stops early is aborted on the server too.
|
|
1058
963
|
if (prompted === SDK_OPERATION_ABORTED || prompted === SDK_OPERATION_TIMED_OUT) {
|
|
1059
|
-
|
|
964
|
+
adapter.abort(client, sessionId, spec);
|
|
1060
965
|
}
|
|
1061
966
|
if (prompted === SDK_OPERATION_ABORTED && retryFailure) {
|
|
1062
967
|
result = {
|
|
@@ -1095,16 +1000,8 @@ export async function runOpencodeSdk(profile, prompt, opts = {}, llmConfig) {
|
|
|
1095
1000
|
};
|
|
1096
1001
|
}
|
|
1097
1002
|
else {
|
|
1098
|
-
const
|
|
1099
|
-
|
|
1100
|
-
const stdout = parts.filter((p) => p.type === "text").at(-1)?.text ?? "";
|
|
1101
|
-
// Token accounting from the AssistantMessage (previously discarded) —
|
|
1102
|
-
// the seam that makes workflow budget.maxTokens meterable on the
|
|
1103
|
-
// default sdk runner.
|
|
1104
|
-
const usage = extractUsage(prompted.data?.info);
|
|
1105
|
-
const sdkError = prompted.error ?? prompted.data?.info?.error;
|
|
1106
|
-
if (sdkError) {
|
|
1107
|
-
const failure = sdkErrorFailure(sdkError);
|
|
1003
|
+
const { text: stdout, usage, failure } = prompted;
|
|
1004
|
+
if (failure) {
|
|
1108
1005
|
result = {
|
|
1109
1006
|
ok: false,
|
|
1110
1007
|
stdout,
|
|
@@ -1145,6 +1042,8 @@ export async function runOpencodeSdk(profile, prompt, opts = {}, llmConfig) {
|
|
|
1145
1042
|
finally {
|
|
1146
1043
|
permissions.stop();
|
|
1147
1044
|
callerSignal?.removeEventListener("abort", abortForCaller);
|
|
1045
|
+
// The dispatch is over: cancel any request still in flight.
|
|
1046
|
+
runController.abort();
|
|
1148
1047
|
}
|
|
1149
1048
|
if (permissionNotes.length > 0) {
|
|
1150
1049
|
result.stderr = [result.stderr, ...permissionNotes].filter(Boolean).join("\n");
|