@open-mercato/cezar 0.9.2 → 0.10.0-pr892.1298
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 +21 -0
- package/README.md +169 -38
- package/dist/agent-config/account-identity.d.ts +44 -0
- package/dist/agent-config/account-identity.js +128 -0
- package/dist/agent-config/account-identity.js.map +1 -0
- package/dist/agent-config/catalog.d.ts +8 -0
- package/dist/agent-config/catalog.js +18 -0
- package/dist/agent-config/catalog.js.map +1 -1
- package/dist/agent-config/model-settings/claude.d.ts +2 -0
- package/dist/agent-config/model-settings/claude.js +11 -0
- package/dist/agent-config/model-settings/claude.js.map +1 -0
- package/dist/agent-config/model-settings/codex.d.ts +2 -0
- package/dist/agent-config/model-settings/codex.js +16 -0
- package/dist/agent-config/model-settings/codex.js.map +1 -0
- package/dist/agent-config/model-settings/opencode.d.ts +2 -0
- package/dist/agent-config/model-settings/opencode.js +8 -0
- package/dist/agent-config/model-settings/opencode.js.map +1 -0
- package/dist/agent-config/model-settings/pi.d.ts +12 -0
- package/dist/agent-config/model-settings/pi.js +18 -0
- package/dist/agent-config/model-settings/pi.js.map +1 -0
- package/dist/agent-config/model-settings/shared.d.ts +9 -0
- package/dist/agent-config/model-settings/shared.js +93 -0
- package/dist/agent-config/model-settings/shared.js.map +1 -0
- package/dist/agent-config/model-settings/types.d.ts +9 -0
- package/dist/agent-config/model-settings/types.js +2 -0
- package/dist/agent-config/model-settings/types.js.map +1 -0
- package/dist/agent-config/models.d.ts +9 -0
- package/dist/agent-config/models.js +32 -0
- package/dist/agent-config/models.js.map +1 -0
- package/dist/agent-config/service.js +4 -4
- package/dist/agent-config/service.js.map +1 -1
- package/dist/automations/types.d.ts +4 -0
- package/dist/automations/types.js +2 -1
- package/dist/automations/types.js.map +1 -1
- package/dist/config.d.ts +10 -1
- package/dist/config.js +48 -5
- package/dist/config.js.map +1 -1
- package/dist/contract/agent-config.d.ts +2 -0
- package/dist/contract/agent-profiles.d.ts +347 -0
- package/dist/contract/automations.d.ts +24 -0
- package/dist/contract/events.d.ts +59 -0
- package/dist/contract/github.d.ts +88 -0
- package/dist/contract/health.d.ts +7 -1
- package/dist/contract/index.d.ts +1 -0
- package/dist/contract/index.js +1005 -555
- package/dist/contract/projects.d.ts +33 -5
- package/dist/contract/runs.d.ts +247 -0
- package/dist/contract/skills.d.ts +7 -0
- package/dist/contract/workflows.d.ts +6 -0
- package/dist/contract/workspace.d.ts +137 -2
- package/dist/core/agent-env.js +41 -16
- package/dist/core/agent-env.js.map +1 -1
- package/dist/core/agent-model-policy.d.ts +10 -0
- package/dist/core/agent-model-policy.js +36 -0
- package/dist/core/agent-model-policy.js.map +1 -0
- package/dist/core/agent-profiles.d.ts +62 -0
- package/dist/core/agent-profiles.js +90 -0
- package/dist/core/agent-profiles.js.map +1 -0
- package/dist/core/agent-runner.d.ts +37 -6
- package/dist/core/agent-runner.js +35 -3
- package/dist/core/agent-runner.js.map +1 -1
- package/dist/core/backend-detect.d.ts +5 -5
- package/dist/core/backend-detect.js +44 -7
- package/dist/core/backend-detect.js.map +1 -1
- package/dist/core/claude-cli-runner.js +10 -5
- package/dist/core/claude-cli-runner.js.map +1 -1
- package/dist/core/codex-app-server-runner.js +13 -3
- package/dist/core/codex-app-server-runner.js.map +1 -1
- package/dist/core/codex-app-server-transport.js +7 -2
- package/dist/core/codex-app-server-transport.js.map +1 -1
- package/dist/core/model-identity.d.ts +23 -11
- package/dist/core/model-identity.js +41 -24
- package/dist/core/model-identity.js.map +1 -1
- package/dist/core/model-presets.d.ts +8 -2
- package/dist/core/model-presets.js +51 -8
- package/dist/core/model-presets.js.map +1 -1
- package/dist/core/opencode-model-catalog.d.ts +32 -0
- package/dist/core/opencode-model-catalog.js +153 -0
- package/dist/core/opencode-model-catalog.js.map +1 -0
- package/dist/core/opencode-server-runner.d.ts +2 -0
- package/dist/core/opencode-server-runner.js +42 -11
- package/dist/core/opencode-server-runner.js.map +1 -1
- package/dist/core/pi-runner.d.ts +24 -0
- package/dist/core/pi-runner.js +349 -0
- package/dist/core/pi-runner.js.map +1 -0
- package/dist/core/pi-ui-mapper.d.ts +34 -0
- package/dist/core/pi-ui-mapper.js +270 -0
- package/dist/core/pi-ui-mapper.js.map +1 -0
- package/dist/core/provider-auth.d.ts +95 -3
- package/dist/core/provider-auth.js +234 -15
- package/dist/core/provider-auth.js.map +1 -1
- package/dist/core/provider-availability.d.ts +1 -1
- package/dist/core/provider-availability.js +2 -1
- package/dist/core/provider-availability.js.map +1 -1
- package/dist/core/runner-factory.js +3 -0
- package/dist/core/runner-factory.js.map +1 -1
- package/dist/core/shell-env.d.ts +28 -0
- package/dist/core/shell-env.js +56 -0
- package/dist/core/shell-env.js.map +1 -0
- package/dist/core/ui-events.d.ts +1 -1
- package/dist/core/usage-limit.d.ts +42 -0
- package/dist/core/usage-limit.js +209 -0
- package/dist/core/usage-limit.js.map +1 -0
- package/dist/git-diff-base.d.ts +51 -26
- package/dist/git-diff-base.js +163 -37
- package/dist/git-diff-base.js.map +1 -1
- package/dist/git-worktree.d.ts +15 -6
- package/dist/git-worktree.js +10 -3
- package/dist/git-worktree.js.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/paths.d.ts +53 -5
- package/dist/paths.js +63 -9
- package/dist/paths.js.map +1 -1
- package/dist/planner.js +6 -0
- package/dist/planner.js.map +1 -1
- package/dist/release/snapshot.d.ts +13 -2
- package/dist/release/snapshot.js +25 -1
- package/dist/release/snapshot.js.map +1 -1
- package/dist/runs/agent-tmpdir.d.ts +41 -0
- package/dist/runs/agent-tmpdir.js +183 -0
- package/dist/runs/agent-tmpdir.js.map +1 -0
- package/dist/runs/auto-name.js +6 -1
- package/dist/runs/auto-name.js.map +1 -1
- package/dist/runs/event-history.d.ts +50 -0
- package/dist/runs/event-history.js +613 -0
- package/dist/runs/event-history.js.map +1 -0
- package/dist/runs/run-index.d.ts +19 -0
- package/dist/runs/run-index.js +43 -0
- package/dist/runs/run-index.js.map +1 -0
- package/dist/runs/store.d.ts +78 -19
- package/dist/runs/store.js +175 -43
- package/dist/runs/store.js.map +1 -1
- package/dist/server/capabilities.d.ts +15 -1
- package/dist/server/capabilities.js +16 -1
- package/dist/server/capabilities.js.map +1 -1
- package/dist/server/forge/github.d.ts +169 -0
- package/dist/server/forge/github.js +511 -0
- package/dist/server/forge/github.js.map +1 -1
- package/dist/server/forge/index.d.ts +9 -0
- package/dist/server/forge/index.js +14 -0
- package/dist/server/forge/index.js.map +1 -1
- package/dist/server/git-changes.d.ts +7 -5
- package/dist/server/git-changes.js +15 -10
- package/dist/server/git-changes.js.map +1 -1
- package/dist/server/github.d.ts +2 -2
- package/dist/server/github.js +1 -1
- package/dist/server/github.js.map +1 -1
- package/dist/server/open-in-app.d.ts +2 -1
- package/dist/server/open-in-app.js +3 -1
- package/dist/server/open-in-app.js.map +1 -1
- package/dist/server/open-in-terminal.d.ts +46 -1
- package/dist/server/open-in-terminal.js +72 -13
- package/dist/server/open-in-terminal.js.map +1 -1
- package/dist/server/provider-action-gate.js +3 -1
- package/dist/server/provider-action-gate.js.map +1 -1
- package/dist/server/server.d.ts +2206 -434
- package/dist/server/server.js +1219 -136
- package/dist/server/server.js.map +1 -1
- package/dist/server-install/engine.js +5 -18
- package/dist/server-install/engine.js.map +1 -1
- package/dist/server-install/steps.js +6 -2
- package/dist/server-install/steps.js.map +1 -1
- package/dist/skills.js +4 -0
- package/dist/skills.js.map +1 -1
- package/dist/workflows/run.d.ts +229 -9
- package/dist/workflows/run.js +877 -147
- package/dist/workflows/run.js.map +1 -1
- package/dist/workflows/types.d.ts +3 -0
- package/dist/workflows/types.js +11 -2
- package/dist/workflows/types.js.map +1 -1
- package/dist/workspace/agent-accounts.d.ts +153 -0
- package/dist/workspace/agent-accounts.js +304 -0
- package/dist/workspace/agent-accounts.js.map +1 -0
- package/dist/workspace/agent-profiles.d.ts +78 -0
- package/dist/workspace/agent-profiles.js +115 -0
- package/dist/workspace/agent-profiles.js.map +1 -0
- package/dist/workspace/config.d.ts +27 -1
- package/dist/workspace/config.js +83 -2
- package/dist/workspace/config.js.map +1 -1
- package/dist/workspace/projects-cli.js +54 -4
- package/dist/workspace/projects-cli.js.map +1 -1
- package/dist/workspace/projects.d.ts +19 -1
- package/dist/workspace/projects.js +34 -1
- package/dist/workspace/projects.js.map +1 -1
- package/dist/workspace/semaphore.d.ts +46 -1
- package/dist/workspace/semaphore.js +41 -4
- package/dist/workspace/semaphore.js.map +1 -1
- package/dist/workspace/ui-state.d.ts +3 -2
- package/dist/workspace/ui-state.js +3 -2
- package/dist/workspace/ui-state.js.map +1 -1
- package/package.json +3 -3
- package/scripts/mock-claude.mjs +20 -0
- package/scripts/mock-pi-rpc.mjs +83 -0
- package/web/dist/assets/{alert-dialog-ghffK6g9.js → alert-dialog-Byp42_IG.js} +1 -1
- package/web/dist/assets/arrow-down-CGDbDYKD.js +1 -0
- package/web/dist/assets/arrow-left-ESCjr1Hq.js +1 -0
- package/web/dist/assets/centered-state-LfAW-igm.js +43 -0
- package/web/dist/assets/chevron-right-Du-P1IUd.js +1 -0
- package/web/dist/assets/{collapsible-B0JVzd0B.js → collapsible-CBiiqDJM.js} +1 -1
- package/web/dist/assets/{commit-list-wm6DPHNs.js → commit-list-6PPve7PE.js} +1 -1
- package/web/dist/assets/{compare-variants-DbdP4Y5T.js → compare-variants-BDnp6mXJ.js} +1 -1
- package/web/dist/assets/{diff-Bizc96qe.js → diff-_jK0OWpF.js} +2 -2
- package/web/dist/assets/{diff-stat-Dyfg3ltF.js → diff-stat-CIrTu4Ym.js} +1 -1
- package/web/dist/assets/{diff-view-CG8ek6tC.js → diff-view-COA_5IyY.js} +1 -1
- package/web/dist/assets/dropdown-menu-BofYpFFw.js +1 -0
- package/web/dist/assets/editable-title-_J0zFDl-.js +1 -0
- package/web/dist/assets/{ellipsis-vertical-DpGTRqC2.js → ellipsis-vertical-BeMR-l62.js} +1 -1
- package/web/dist/assets/{file-DlUGpUlb.js → file-DY996Pee.js} +1 -1
- package/web/dist/assets/{folder-BAY6Vddy.js → folder-NO3Ec-TY.js} +1 -1
- package/web/dist/assets/git-toolbar-BqJeUWcD.js +1 -0
- package/web/dist/assets/github-C96HPV9E.js +1 -0
- package/web/dist/assets/{image-preview-BWGk5fPX.js → image-preview-D9cCDfKQ.js} +1 -1
- package/web/dist/assets/index-Bx-grqa9.css +2 -0
- package/web/dist/assets/index-DoVvGDfW.js +7 -0
- package/web/dist/assets/{markdown-BGBOGCA_.js → markdown-DDzk11zj.js} +1 -1
- package/web/dist/assets/new-task-form-BNLLgC6P.js +1 -0
- package/web/dist/assets/pill-CVM1EmV4.js +1 -0
- package/web/dist/assets/{project-router-Cx8NPoqn.js → project-router-LRCtP48W.js} +1 -1
- package/web/dist/assets/prompt-templates-DFOvYgtW.js +15 -0
- package/web/dist/assets/{refresh-cw-BoGdAykg.js → refresh-cw-D1ys6QY2.js} +1 -1
- package/web/dist/assets/repo-git-CzUnsgWY.js +1 -0
- package/web/dist/assets/{run-diff-EcOkf3ti.js → run-diff-BlnqwfSS.js} +2 -2
- package/web/dist/assets/run-header-Bn7cN0Z1.js +1 -0
- package/web/dist/assets/{search-x-X3EpVhgs.js → search-x-D6FSpxId.js} +1 -1
- package/web/dist/assets/{skill-empty-hint-CZLALx6l.js → skill-empty-hint-iwpCcqgI.js} +1 -1
- package/web/dist/assets/skills-CezbOaol.js +1 -0
- package/web/dist/assets/{sparkles-Dt4q7pbG.js → sparkles-MrQrgCI9.js} +1 -1
- package/web/dist/assets/{square-terminal-hwBL8XsC.js → square-terminal-TTH64lO7.js} +1 -1
- package/web/dist/assets/tab-link-FYksz037.js +1 -0
- package/web/dist/assets/task-changes-ClWlnzBL.js +1 -0
- package/web/dist/assets/{task-commits-BTTA5k5T.js → task-commits-Crbrl86b.js} +1 -1
- package/web/dist/assets/{task-files-Da8_4C9g.js → task-files-uSCWyD0-.js} +2 -2
- package/web/dist/assets/task-thread-DPmamBA1.js +9 -0
- package/web/dist/assets/{textarea-CUdokhkv.js → textarea-B9Ahj4E4.js} +1 -1
- package/web/dist/assets/thread-loading-BH_ypQoy.js +1 -0
- package/web/dist/assets/{trash-2-DhVIdWv8.js → trash-2-D8S3V3fu.js} +1 -1
- package/web/dist/assets/{triangle-alert-XYT0DGCI.js → triangle-alert-bB7p9n5E.js} +1 -1
- package/web/dist/assets/{upload-BKjN59-1.js → upload-Bd5oRLPV.js} +1 -1
- package/web/dist/assets/{use-desktop-Bf5LS_P4.js → use-desktop-BNvbRBeZ.js} +1 -1
- package/web/dist/assets/{use-submit-shortcut-CzVyoAMd.js → use-submit-shortcut-B-xhu4Bf.js} +1 -1
- package/web/dist/assets/utils-BhpbVMwN.js +64 -0
- package/web/dist/assets/{workflows-o2QU_pvU.js → workflows-RmfbM9Vy.js} +3 -3
- package/web/dist/assets/{zoomable-image-7WyUiaVD.js → zoomable-image-BCW6ZBV0.js} +1 -1
- package/web/dist/index.html +24 -23
- package/web/dist/assets/arrow-down-Cz8qb71e.js +0 -1
- package/web/dist/assets/arrow-left-wcYKQXI_.js +0 -1
- package/web/dist/assets/centered-state-ZtnFHA-n.js +0 -43
- package/web/dist/assets/chevron-right-G4caF2QV.js +0 -1
- package/web/dist/assets/circle-check--KvBxXWp.js +0 -1
- package/web/dist/assets/circle-x-C3EFZ3xf.js +0 -1
- package/web/dist/assets/dropdown-menu-C1Dlf35P.js +0 -1
- package/web/dist/assets/editable-title-5rC_QRBZ.js +0 -1
- package/web/dist/assets/git-pull-request-agOG0iGM.js +0 -1
- package/web/dist/assets/git-toolbar-DGiNMmtY.js +0 -1
- package/web/dist/assets/github-Vp7lxYGQ.js +0 -1
- package/web/dist/assets/index-DSxyJpuK.css +0 -2
- package/web/dist/assets/index-DiawI6RX.js +0 -6
- package/web/dist/assets/new-task-form-BpAw_Dks.js +0 -1
- package/web/dist/assets/pill-Dvs-P0xy.js +0 -1
- package/web/dist/assets/prompt-templates-CSBYBR1I.js +0 -15
- package/web/dist/assets/repo-git-PmX-BNMo.js +0 -1
- package/web/dist/assets/run-header-BGiv6_qX.js +0 -1
- package/web/dist/assets/skills-CC91ouGH.js +0 -1
- package/web/dist/assets/tab-link-DIlR5T07.js +0 -1
- package/web/dist/assets/task-changes-XkHDxzQA.js +0 -1
- package/web/dist/assets/task-thread-DoNXUsHr.js +0 -9
- package/web/dist/assets/thread-loading-B8q8ukXJ.js +0 -1
- package/web/dist/assets/utils-DsLuRXwV.js +0 -64
|
@@ -40,12 +40,13 @@ export declare class ModelIdentityError extends Error {
|
|
|
40
40
|
constructor(message: string);
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
44
|
-
* provider in an explicit `provider/model` string
|
|
45
|
-
* future models and new backends are never silently rejected
|
|
43
|
+
* No provider allowlist lives here on purpose (#548). `resolveModelIdentity`
|
|
44
|
+
* lets an unknown provider in an explicit `provider/model` string pass through,
|
|
45
|
+
* so future models and new backends are never silently rejected — an exported
|
|
46
|
+
* roster of "providers cezar names today" would gate nothing, and an unused
|
|
47
|
+
* public export reads as a contract someone must keep current. The per-backend
|
|
48
|
+
* knowledge that IS load-bearing lives in `BACKEND_MODEL_MAP` below.
|
|
46
49
|
*/
|
|
47
|
-
export declare const KNOWN_PROVIDERS: readonly ['anthropic', 'openai', 'google', 'openrouter'];
|
|
48
|
-
export type KnownProvider = (typeof KNOWN_PROVIDERS)[number];
|
|
49
50
|
interface BackendModelMap {
|
|
50
51
|
/**
|
|
51
52
|
* The provider a bare (provider-less) model id belongs to for this backend.
|
|
@@ -54,6 +55,10 @@ interface BackendModelMap {
|
|
|
54
55
|
* It also selects the wire form in {@link toBackendModel}.
|
|
55
56
|
*/
|
|
56
57
|
readonly defaultProvider?: string;
|
|
58
|
+
/** Whether explicit foreign provider/model ids are valid backend-native ids. */
|
|
59
|
+
readonly allowExplicitProvider?: boolean;
|
|
60
|
+
/** Whether the backend wire model keeps the explicit provider prefix. */
|
|
61
|
+
readonly wireProviderQualified?: boolean;
|
|
57
62
|
/**
|
|
58
63
|
* Bare model id → provider override, for the rare backend whose bare ids
|
|
59
64
|
* span providers. Checked before `defaultProvider`; empty for today's set.
|
|
@@ -63,7 +68,11 @@ interface BackendModelMap {
|
|
|
63
68
|
/**
|
|
64
69
|
* Per-backend mapping table — the one place #387 extends for the `pi` runner.
|
|
65
70
|
* `claude-cli` is the legacy id kept so old run records normalise identically
|
|
66
|
-
* to `claude`.
|
|
71
|
+
* to `claude`. That is now true end to end: `storedRunnerSchema`
|
|
72
|
+
* (`runs/store.ts`) parses the legacy spelling out of `runs.json` and folds it
|
|
73
|
+
* to `claude` (#547), so the entry below is what a record carrying it maps
|
|
74
|
+
* through on the way in — not, as the comment used to imply, a mapping for a
|
|
75
|
+
* value the store could never load.
|
|
67
76
|
*/
|
|
68
77
|
export declare const BACKEND_MODEL_MAP: Readonly<Record<AgentBackend, BackendModelMap>>;
|
|
69
78
|
/** Serialize a canonical identity to its `provider/model` string form. */
|
|
@@ -84,14 +93,15 @@ export declare function parseModelIdentity(raw: string | undefined | null): Mode
|
|
|
84
93
|
* explicit choice, not a silent fallback);
|
|
85
94
|
* - an explicit `provider/model` → that identity, for a multi-provider backend
|
|
86
95
|
* or when the provider is the single-provider backend's own;
|
|
87
|
-
* - an explicit `provider/model` naming a FOREIGN provider
|
|
88
|
-
* backend
|
|
89
|
-
* yet persisted, asserting a provider that never ran — #405 review M2);
|
|
96
|
+
* - an explicit `provider/model` naming a FOREIGN provider is preserved when
|
|
97
|
+
* the backend supports custom/gateway model ids;
|
|
90
98
|
* - a bare id → the backend's default provider;
|
|
91
99
|
* - a bare id on a backend with no default provider → throws
|
|
92
100
|
* {@link ModelIdentityError}.
|
|
93
101
|
*/
|
|
94
|
-
export declare function resolveModelIdentity(backend: AgentBackend, raw: string | undefined
|
|
102
|
+
export declare function resolveModelIdentity(backend: AgentBackend, raw: string | undefined, options?: {
|
|
103
|
+
configuredProvider?: string;
|
|
104
|
+
}): ModelIdentity | undefined;
|
|
95
105
|
/**
|
|
96
106
|
* The model string to hand `backend`'s CLI/API for a canonical identity — the
|
|
97
107
|
* inverse of {@link resolveModelIdentity}. Single-provider backends
|
|
@@ -106,7 +116,9 @@ export declare function toBackendModel(backend: AgentBackend, id: ModelIdentity)
|
|
|
106
116
|
* `undefined` for empty/auto. Throws {@link ModelIdentityError} on an
|
|
107
117
|
* unresolvable model — the single fail-loud gate the run wiring calls.
|
|
108
118
|
*/
|
|
109
|
-
export declare function normalizeModelForBackend(backend: AgentBackend, raw: string | undefined
|
|
119
|
+
export declare function normalizeModelForBackend(backend: AgentBackend, raw: string | undefined, options?: {
|
|
120
|
+
configuredProvider?: string;
|
|
121
|
+
}): {
|
|
110
122
|
backendModel: string;
|
|
111
123
|
identity: ModelIdentity;
|
|
112
124
|
} | undefined;
|
|
@@ -10,24 +10,31 @@ export class ModelIdentityError extends Error {
|
|
|
10
10
|
this.name = 'ModelIdentityError';
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
-
/**
|
|
14
|
-
* Providers cezar names today. Informational and extensible — an unknown
|
|
15
|
-
* provider in an explicit `provider/model` string still passes through, so
|
|
16
|
-
* future models and new backends are never silently rejected.
|
|
17
|
-
*/
|
|
18
|
-
export const KNOWN_PROVIDERS = ['anthropic', 'openai', 'google', 'openrouter'];
|
|
19
13
|
/**
|
|
20
14
|
* Per-backend mapping table — the one place #387 extends for the `pi` runner.
|
|
21
15
|
* `claude-cli` is the legacy id kept so old run records normalise identically
|
|
22
|
-
* to `claude`.
|
|
16
|
+
* to `claude`. That is now true end to end: `storedRunnerSchema`
|
|
17
|
+
* (`runs/store.ts`) parses the legacy spelling out of `runs.json` and folds it
|
|
18
|
+
* to `claude` (#547), so the entry below is what a record carrying it maps
|
|
19
|
+
* through on the way in — not, as the comment used to imply, a mapping for a
|
|
20
|
+
* value the store could never load.
|
|
23
21
|
*/
|
|
24
22
|
export const BACKEND_MODEL_MAP = {
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
// Claude Code supports custom/gateway model ids such as DeepSeek via an
|
|
24
|
+
// Anthropic-compatible endpoint, so preserve an explicit provider/model.
|
|
25
|
+
claude: { defaultProvider: 'anthropic', allowExplicitProvider: true, wireProviderQualified: true },
|
|
26
|
+
'claude-cli': { defaultProvider: 'anthropic', allowExplicitProvider: true, wireProviderQualified: true },
|
|
27
|
+
// Codex selects the provider from model_provider in config.toml. A foreign
|
|
28
|
+
// explicit prefix is accepted only when the caller proves it matches that
|
|
29
|
+
// effective setting; the provider is then stripped from the wire model id.
|
|
27
30
|
codex: { defaultProvider: 'openai' },
|
|
28
31
|
// opencode selects across providers, so a bare model is ambiguous: reject it
|
|
29
32
|
// loudly rather than let the server pick a default the user never asked for.
|
|
30
33
|
opencode: {},
|
|
34
|
+
// pi selects across providers with the same `provider/model` convention as
|
|
35
|
+
// opencode (#387) — no default provider, so a bare model is rejected loudly
|
|
36
|
+
// and `toBackendModel` hands pi the full `provider/model` on its `--model`.
|
|
37
|
+
pi: {},
|
|
31
38
|
};
|
|
32
39
|
const SLASH = '/';
|
|
33
40
|
/** Serialize a canonical identity to its `provider/model` string form. */
|
|
@@ -59,31 +66,34 @@ export function parseModelIdentity(raw) {
|
|
|
59
66
|
* explicit choice, not a silent fallback);
|
|
60
67
|
* - an explicit `provider/model` → that identity, for a multi-provider backend
|
|
61
68
|
* or when the provider is the single-provider backend's own;
|
|
62
|
-
* - an explicit `provider/model` naming a FOREIGN provider
|
|
63
|
-
* backend
|
|
64
|
-
* yet persisted, asserting a provider that never ran — #405 review M2);
|
|
69
|
+
* - an explicit `provider/model` naming a FOREIGN provider is preserved when
|
|
70
|
+
* the backend supports custom/gateway model ids;
|
|
65
71
|
* - a bare id → the backend's default provider;
|
|
66
72
|
* - a bare id on a backend with no default provider → throws
|
|
67
73
|
* {@link ModelIdentityError}.
|
|
68
74
|
*/
|
|
69
|
-
export function resolveModelIdentity(backend, raw) {
|
|
75
|
+
export function resolveModelIdentity(backend, raw, options = {}) {
|
|
70
76
|
if (!raw || !raw.trim())
|
|
71
77
|
return undefined;
|
|
72
78
|
const map = BACKEND_MODEL_MAP[backend] ?? {};
|
|
79
|
+
const configuredProvider = options.configuredProvider?.trim().toLowerCase() || undefined;
|
|
80
|
+
const effectiveProvider = configuredProvider ?? map.defaultProvider;
|
|
73
81
|
const explicit = parseModelIdentity(raw);
|
|
74
82
|
if (explicit) {
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
throw new ModelIdentityError(
|
|
83
|
+
// Most single-provider backends need their bare wire form, but Claude Code
|
|
84
|
+
// also accepts explicit gateway/custom provider ids. Keep rejecting foreign
|
|
85
|
+
// providers for backends that do not advertise that capability.
|
|
86
|
+
if (map.defaultProvider !== undefined &&
|
|
87
|
+
explicit.provider !== effectiveProvider &&
|
|
88
|
+
!map.allowExplicitProvider) {
|
|
89
|
+
throw new ModelIdentityError(configuredProvider
|
|
90
|
+
? `provider "${explicit.provider}" doesn't match the ${backend} runner's configured provider "${configuredProvider}" — use "${configuredProvider}/${explicit.model}" or a bare model id`
|
|
91
|
+
: `provider "${explicit.provider}" can't be verified for the ${backend} runner (serves ${map.defaultProvider}) — use "${map.defaultProvider}/${explicit.model}" or a bare model id`);
|
|
82
92
|
}
|
|
83
93
|
return explicit;
|
|
84
94
|
}
|
|
85
95
|
const model = raw.trim();
|
|
86
|
-
const provider = map.providerByModel?.[model] ??
|
|
96
|
+
const provider = map.providerByModel?.[model] ?? effectiveProvider;
|
|
87
97
|
if (!provider) {
|
|
88
98
|
throw new ModelIdentityError(`model "${model}" is ambiguous for the ${backend} runner — name the provider as "provider/model" (e.g. anthropic/${model})`);
|
|
89
99
|
}
|
|
@@ -98,7 +108,14 @@ export function resolveModelIdentity(backend, raw) {
|
|
|
98
108
|
*/
|
|
99
109
|
export function toBackendModel(backend, id) {
|
|
100
110
|
const map = BACKEND_MODEL_MAP[backend] ?? {};
|
|
101
|
-
|
|
111
|
+
if (map.defaultProvider === undefined)
|
|
112
|
+
return formatModelIdentity(id);
|
|
113
|
+
if (map.allowExplicitProvider &&
|
|
114
|
+
map.wireProviderQualified &&
|
|
115
|
+
id.provider !== map.defaultProvider) {
|
|
116
|
+
return formatModelIdentity(id);
|
|
117
|
+
}
|
|
118
|
+
return id.model;
|
|
102
119
|
}
|
|
103
120
|
/**
|
|
104
121
|
* Normalise a raw model string for `backend` in one step: the backend-native
|
|
@@ -106,8 +123,8 @@ export function toBackendModel(backend, id) {
|
|
|
106
123
|
* `undefined` for empty/auto. Throws {@link ModelIdentityError} on an
|
|
107
124
|
* unresolvable model — the single fail-loud gate the run wiring calls.
|
|
108
125
|
*/
|
|
109
|
-
export function normalizeModelForBackend(backend, raw) {
|
|
110
|
-
const identity = resolveModelIdentity(backend, raw);
|
|
126
|
+
export function normalizeModelForBackend(backend, raw, options = {}) {
|
|
127
|
+
const identity = resolveModelIdentity(backend, raw, options);
|
|
111
128
|
if (!identity)
|
|
112
129
|
return undefined;
|
|
113
130
|
return { backendModel: toBackendModel(backend, identity), identity };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-identity.js","sourceRoot":"","sources":["../../src/core/model-identity.ts"],"names":[],"mappings":"AAkCA;;;;;GAKG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;
|
|
1
|
+
{"version":3,"file":"model-identity.js","sourceRoot":"","sources":["../../src/core/model-identity.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,8 +8,14 @@ import type { RunnerId } from './agent-runner.ts';
|
|
|
8
8
|
* and config presets must keep working), so the only thing ever rejected is a model that is
|
|
9
9
|
* recognizably ANOTHER runner's preset — the corruption a client/server resolution mismatch
|
|
10
10
|
* can produce (#401 review). Unknown ids never conflict (fail-open).
|
|
11
|
+
*
|
|
12
|
+
* OpenCode lists nothing here on purpose (#794): its models are discovered from the host
|
|
13
|
+
* (`opencode-model-catalog.ts`), so any hard-coded list would be one release away from naming
|
|
14
|
+
* models the user's provider does not have. Its half of the guard is the structural check in
|
|
15
|
+
* {@link modelConflictsWithRunner} instead, which needs no vendor knowledge at all.
|
|
11
16
|
*/
|
|
12
17
|
export declare const KNOWN_PRESETS_BY_RUNNER: Record<RunnerId, readonly string[]>;
|
|
13
|
-
/** True when `model` is recognizably a preset of a runner OTHER than `runner` (and not also
|
|
14
|
-
*
|
|
18
|
+
/** True when `model` is recognizably a preset of a runner OTHER than `runner` (and not also one
|
|
19
|
+
* of `runner`'s own presets), or when its provider prefix is one this runner is known not to
|
|
20
|
+
* serve. `''`/unknown/custom ids never conflict. */
|
|
15
21
|
export declare function modelConflictsWithRunner(model: string, runner: RunnerId): boolean;
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import { BACKEND_MODEL_MAP, parseModelIdentity } from './model-identity.js';
|
|
2
|
+
/**
|
|
3
|
+
* The vendor prefixes this module treats as evidence of a mis-paired model. Deliberately local
|
|
4
|
+
* rather than an export of `model-identity.ts` (#548/#831): nothing else may gate on it, because
|
|
5
|
+
* `resolveModelIdentity` lets an unknown provider through on purpose. It is a heuristic for
|
|
6
|
+
* {@link namesAnotherKnownProvider} alone, and being short is a feature — every entry costs a
|
|
7
|
+
* false refusal if cezar is ever wrong about who serves it.
|
|
8
|
+
*/
|
|
9
|
+
const KNOWN_PROVIDERS = ['anthropic', 'openai', 'google', 'openrouter'];
|
|
1
10
|
/**
|
|
2
11
|
* The model-preset ids each runner's picker offers — the ids of the web composer's
|
|
3
12
|
* `MODELS_BY_RUNNER` (packages/web/src/routes/new-task-form.ts), hand-mirrored the same way the
|
|
@@ -7,6 +16,11 @@
|
|
|
7
16
|
* and config presets must keep working), so the only thing ever rejected is a model that is
|
|
8
17
|
* recognizably ANOTHER runner's preset — the corruption a client/server resolution mismatch
|
|
9
18
|
* can produce (#401 review). Unknown ids never conflict (fail-open).
|
|
19
|
+
*
|
|
20
|
+
* OpenCode lists nothing here on purpose (#794): its models are discovered from the host
|
|
21
|
+
* (`opencode-model-catalog.ts`), so any hard-coded list would be one release away from naming
|
|
22
|
+
* models the user's provider does not have. Its half of the guard is the structural check in
|
|
23
|
+
* {@link modelConflictsWithRunner} instead, which needs no vendor knowledge at all.
|
|
10
24
|
*/
|
|
11
25
|
export const KNOWN_PRESETS_BY_RUNNER = {
|
|
12
26
|
claude: [
|
|
@@ -19,20 +33,49 @@ export const KNOWN_PRESETS_BY_RUNNER = {
|
|
|
19
33
|
'claude-haiku-4-5',
|
|
20
34
|
],
|
|
21
35
|
codex: ['gpt-5.1-codex', 'gpt-5.1-codex-mini', 'gpt-5-codex'],
|
|
22
|
-
opencode: [
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
opencode: [],
|
|
37
|
+
// pi lists nothing for the same reason OpenCode does (#794), plus one of its own: it picks a
|
|
38
|
+
// model with the canonical `provider/model` convention and has no default provider, so the
|
|
39
|
+
// structural check in {@link modelConflictsWithRunner} already guards it without vendor
|
|
40
|
+
// knowledge. Listing pi's composer presets here would actively break OpenCode — the ids
|
|
41
|
+
// overlap (`anthropic/claude-sonnet-5`), and a shared id present in pi's list but absent from
|
|
42
|
+
// OpenCode's empty one would read as "another runner's preset" and be refused.
|
|
43
|
+
pi: [],
|
|
28
44
|
};
|
|
29
|
-
/**
|
|
30
|
-
*
|
|
45
|
+
/**
|
|
46
|
+
* True when the `provider/` prefix on `model` names a provider cezar knows that this
|
|
47
|
+
* single-provider runner does not serve — `anthropic/…` handed to codex.
|
|
48
|
+
*
|
|
49
|
+
* Deliberately narrower than the run-time gate in `model-identity.ts`: this check is
|
|
50
|
+
* synchronous and therefore cannot read the runner's `configuredProvider`, so an UNFAMILIAR
|
|
51
|
+
* prefix (`my-org/custom-tune`) is left alone — it may well be the user's own gateway, which
|
|
52
|
+
* the run-time gate accepts once it has read the config. Only a prefix naming another known
|
|
53
|
+
* vendor is evidence enough to refuse the pairing up front.
|
|
54
|
+
*/
|
|
55
|
+
function namesAnotherKnownProvider(model, runner) {
|
|
56
|
+
const map = BACKEND_MODEL_MAP[runner];
|
|
57
|
+
// Nothing to contradict: OpenCode spans providers (no default of its own), and Claude Code
|
|
58
|
+
// accepts explicit gateway/custom provider ids by design.
|
|
59
|
+
if (map?.defaultProvider === undefined || map.allowExplicitProvider)
|
|
60
|
+
return false;
|
|
61
|
+
const explicit = parseModelIdentity(model);
|
|
62
|
+
if (!explicit || explicit.provider === map.defaultProvider)
|
|
63
|
+
return false;
|
|
64
|
+
return KNOWN_PROVIDERS.includes(explicit.provider);
|
|
65
|
+
}
|
|
66
|
+
/** True when `model` is recognizably a preset of a runner OTHER than `runner` (and not also one
|
|
67
|
+
* of `runner`'s own presets), or when its provider prefix is one this runner is known not to
|
|
68
|
+
* serve. `''`/unknown/custom ids never conflict. */
|
|
31
69
|
export function modelConflictsWithRunner(model, runner) {
|
|
32
70
|
if (!model)
|
|
33
71
|
return false;
|
|
34
72
|
if (KNOWN_PRESETS_BY_RUNNER[runner]?.includes(model))
|
|
35
73
|
return false;
|
|
74
|
+
// The provider half of the guard needs no vendor model knowledge, so — unlike the preset
|
|
75
|
+
// lists — it keeps holding for models that do not exist yet. That is what lets OpenCode's
|
|
76
|
+
// entry above be empty (#794) without losing the protection it used to provide.
|
|
77
|
+
if (namesAnotherKnownProvider(model, runner))
|
|
78
|
+
return true;
|
|
36
79
|
return Object.entries(KNOWN_PRESETS_BY_RUNNER).some(([other, presets]) => other !== runner && presets.includes(model));
|
|
37
80
|
}
|
|
38
81
|
//# sourceMappingURL=model-presets.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-presets.js","sourceRoot":"","sources":["../../src/core/model-presets.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"model-presets.js","sourceRoot":"","sources":["../../src/core/model-presets.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE5E;;;;;;GAMG;AACH,MAAM,eAAe,GAAG,CAAC,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,CAAU,CAAC;AAEjF;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAwC;IAC1E,MAAM,EAAE;QACN,MAAM;QACN,QAAQ;QACR,OAAO;QACP,gBAAgB;QAChB,iBAAiB;QACjB,iBAAiB;QACjB,kBAAkB;KACnB;IACD,KAAK,EAAE,CAAC,eAAe,EAAE,oBAAoB,EAAE,aAAa,CAAC;IAC7D,QAAQ,EAAE,EAAE;IACZ,6FAA6F;IAC7F,2FAA2F;IAC3F,wFAAwF;IACxF,wFAAwF;IACxF,8FAA8F;IAC9F,+EAA+E;IAC/E,EAAE,EAAE,EAAE;CACP,CAAC;AAEF;;;;;;;;;GASG;AACH,SAAS,yBAAyB,CAAC,KAAa,EAAE,MAAgB;IAChE,MAAM,GAAG,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IACtC,2FAA2F;IAC3F,0DAA0D;IAC1D,IAAI,GAAG,EAAE,eAAe,KAAK,SAAS,IAAI,GAAG,CAAC,qBAAqB;QAAE,OAAO,KAAK,CAAC;IAClF,MAAM,QAAQ,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAC3C,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,KAAK,GAAG,CAAC,eAAe;QAAE,OAAO,KAAK,CAAC;IACzE,OAAQ,eAAqC,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC5E,CAAC;AAED;;qDAEqD;AACrD,MAAM,UAAU,wBAAwB,CAAC,KAAa,EAAE,MAAgB;IACtE,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,IAAI,uBAAuB,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACnE,yFAAyF;IACzF,0FAA0F;IAC1F,gFAAgF;IAChF,IAAI,yBAAyB,CAAC,KAAK,EAAE,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1D,OAAO,MAAM,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC,IAAI,CACjD,CAAC,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,MAAM,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAClE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type ChildProcessWithoutNullStreams } from 'node:child_process';
|
|
2
|
+
import type { ModelOption } from './runner-model-catalog.ts';
|
|
3
|
+
export interface OpencodeModelDiscoveryOptions {
|
|
4
|
+
cwd: string;
|
|
5
|
+
bin?: string;
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
spawn?: (bin: string, args: readonly string[], cwd: string) => ChildProcessWithoutNullStreams;
|
|
8
|
+
}
|
|
9
|
+
/** Grace between the probe's SIGTERM and the SIGKILL that follows it. */
|
|
10
|
+
export declare const KILL_GRACE_MS = 2000;
|
|
11
|
+
/** The host binary, resolved exactly like `OpencodeServerRunner` and the backend probe. */
|
|
12
|
+
export declare function resolveOpencodeExecutable(bin?: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Discover the models the host's own OpenCode installation offers, by asking it: `opencode
|
|
15
|
+
* models` lists every `provider/model` id the configured providers expose, which is the same
|
|
16
|
+
* list OpenCode's own picker routes to.
|
|
17
|
+
*
|
|
18
|
+
* Best-effort by contract — `RunnerModelCatalog` turns any throw here into a cached or
|
|
19
|
+
* `unavailable` answer, and `auto` stays selectable either way. No config is read or written,
|
|
20
|
+
* no session is started; the child is short-lived and bounded by a deadline, a stdout cap and
|
|
21
|
+
* a model cap.
|
|
22
|
+
*/
|
|
23
|
+
export declare function discoverOpencodeModels(options: OpencodeModelDiscoveryOptions): Promise<ModelOption[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Turn `opencode models` output into picker options, preserving OpenCode's own order.
|
|
26
|
+
*
|
|
27
|
+
* Empty output is a legitimate answer (no provider configured yet) and yields no models, so the
|
|
28
|
+
* picker shows `auto` alone. Output that contains lines but NO recognizable id is treated as a
|
|
29
|
+
* failure instead: the CLI said something we cannot read, and reporting "unavailable" is more
|
|
30
|
+
* honest than an empty catalog that looks like "you have no models".
|
|
31
|
+
*/
|
|
32
|
+
export declare function parseOpencodeModels(stdout: string): ModelOption[];
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
2
|
+
import { trackChildExit } from './agent-runner.js';
|
|
3
|
+
import { buildChildEnv } from './agent-env.js';
|
|
4
|
+
const DEFAULT_DISCOVERY_TIMEOUT_MS = 10_000;
|
|
5
|
+
/** Grace between the probe's SIGTERM and the SIGKILL that follows it. */
|
|
6
|
+
export const KILL_GRACE_MS = 2_000;
|
|
7
|
+
const MAX_MODELS = 500;
|
|
8
|
+
/** Defensive cap on what we buffer from a misbehaving child (characters of stdout). */
|
|
9
|
+
const MAX_OUTPUT_CHARS = 512 * 1_024;
|
|
10
|
+
/**
|
|
11
|
+
* One `provider/model` id per line — the shape `opencode models` prints (#794). Deliberately
|
|
12
|
+
* strict: anything else (a banner, a prompt, a stack trace, a bare model name that OpenCode
|
|
13
|
+
* could not route) is NOT a model id, and silently turning it into a picker entry would
|
|
14
|
+
* recreate the very defect this replaces.
|
|
15
|
+
*/
|
|
16
|
+
const MODEL_LINE_RE = /^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._:/-]*$/i;
|
|
17
|
+
/** SGR/CSI sequences, in case a future CLI colorizes even a piped stdout. */
|
|
18
|
+
const ANSI_RE = /\u001B\[[0-9;]*[A-Za-z]/g;
|
|
19
|
+
/** The host binary, resolved exactly like `OpencodeServerRunner` and the backend probe. */
|
|
20
|
+
export function resolveOpencodeExecutable(bin) {
|
|
21
|
+
return bin ?? process.env.CEZ_OPENCODE_BIN ?? 'opencode';
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Discover the models the host's own OpenCode installation offers, by asking it: `opencode
|
|
25
|
+
* models` lists every `provider/model` id the configured providers expose, which is the same
|
|
26
|
+
* list OpenCode's own picker routes to.
|
|
27
|
+
*
|
|
28
|
+
* Best-effort by contract — `RunnerModelCatalog` turns any throw here into a cached or
|
|
29
|
+
* `unavailable` answer, and `auto` stays selectable either way. No config is read or written,
|
|
30
|
+
* no session is started; the child is short-lived and bounded by a deadline, a stdout cap and
|
|
31
|
+
* a model cap.
|
|
32
|
+
*/
|
|
33
|
+
export async function discoverOpencodeModels(options) {
|
|
34
|
+
const bin = resolveOpencodeExecutable(options.bin);
|
|
35
|
+
const args = ['models'];
|
|
36
|
+
const child = (options.spawn ?? spawnOpencode)(bin, args, options.cwd);
|
|
37
|
+
const kill = teardown(child);
|
|
38
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_DISCOVERY_TIMEOUT_MS;
|
|
39
|
+
let timeout;
|
|
40
|
+
try {
|
|
41
|
+
return await new Promise((resolve, reject) => {
|
|
42
|
+
let stdout = '';
|
|
43
|
+
let overflowed = false;
|
|
44
|
+
const fail = (message) => {
|
|
45
|
+
reject(new Error(message));
|
|
46
|
+
kill();
|
|
47
|
+
};
|
|
48
|
+
timeout = setTimeout(() => fail('OpenCode model discovery timed out'), timeoutMs);
|
|
49
|
+
timeout.unref?.();
|
|
50
|
+
child.stdout.setEncoding('utf8');
|
|
51
|
+
child.stdout.on('data', (chunk) => {
|
|
52
|
+
if (overflowed)
|
|
53
|
+
return;
|
|
54
|
+
stdout += chunk;
|
|
55
|
+
if (stdout.length > MAX_OUTPUT_CHARS) {
|
|
56
|
+
overflowed = true;
|
|
57
|
+
fail('OpenCode model discovery exceeded the output limit');
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
// Drained but ignored: OpenCode prints provider warnings here, and an unread pipe would
|
|
61
|
+
// eventually stall the child.
|
|
62
|
+
child.stderr.resume();
|
|
63
|
+
child.once('error', () => fail('OpenCode model discovery child failed'));
|
|
64
|
+
child.once('close', (code) => {
|
|
65
|
+
if (overflowed)
|
|
66
|
+
return;
|
|
67
|
+
if (code !== 0) {
|
|
68
|
+
fail(`OpenCode model discovery child exited (${code ?? 'unknown'})`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
resolve(parseOpencodeModels(stdout));
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
if (timeout)
|
|
82
|
+
clearTimeout(timeout);
|
|
83
|
+
kill();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Turn `opencode models` output into picker options, preserving OpenCode's own order.
|
|
88
|
+
*
|
|
89
|
+
* Empty output is a legitimate answer (no provider configured yet) and yields no models, so the
|
|
90
|
+
* picker shows `auto` alone. Output that contains lines but NO recognizable id is treated as a
|
|
91
|
+
* failure instead: the CLI said something we cannot read, and reporting "unavailable" is more
|
|
92
|
+
* honest than an empty catalog that looks like "you have no models".
|
|
93
|
+
*/
|
|
94
|
+
export function parseOpencodeModels(stdout) {
|
|
95
|
+
const lines = stdout
|
|
96
|
+
.replace(ANSI_RE, '')
|
|
97
|
+
.split('\n')
|
|
98
|
+
.map((line) => line.trim())
|
|
99
|
+
.filter((line) => line.length > 0);
|
|
100
|
+
const models = [];
|
|
101
|
+
const ids = new Set();
|
|
102
|
+
for (const line of lines) {
|
|
103
|
+
if (!MODEL_LINE_RE.test(line) || ids.has(line))
|
|
104
|
+
continue;
|
|
105
|
+
if (models.length >= MAX_MODELS)
|
|
106
|
+
throw new Error('OpenCode model discovery exceeded the size limit');
|
|
107
|
+
ids.add(line);
|
|
108
|
+
const provider = line.slice(0, line.indexOf('/'));
|
|
109
|
+
models.push({ id: line, label: line, description: `via ${provider}` });
|
|
110
|
+
}
|
|
111
|
+
if (models.length === 0 && lines.length > 0) {
|
|
112
|
+
throw new Error('OpenCode model discovery returned unrecognized output');
|
|
113
|
+
}
|
|
114
|
+
return models;
|
|
115
|
+
}
|
|
116
|
+
function spawnOpencode(bin, args, cwd) {
|
|
117
|
+
const child = nodeSpawn(bin, [...args], {
|
|
118
|
+
cwd,
|
|
119
|
+
env: buildChildEnv({ backend: 'opencode' }),
|
|
120
|
+
});
|
|
121
|
+
// Nothing is ever written to it, and an open stdin is what makes a CLI that expects a TTY
|
|
122
|
+
// sit and wait instead of printing its list.
|
|
123
|
+
child.stdin.end();
|
|
124
|
+
return child;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Returns the probe's teardown: SIGTERM now, SIGKILL once the grace window elapses.
|
|
128
|
+
*
|
|
129
|
+
* Both steps gate on the child's *real* termination, never on `child.killed` — Node flips that
|
|
130
|
+
* flag the moment a signal is delivered, so the old `exitCode === null && !child.killed` test
|
|
131
|
+
* went false the instant our own SIGTERM landed. A probe that installs a SIGTERM handler then
|
|
132
|
+
* survived every teardown, leaking one process per cache refresh (#858, the shape #841 fixed for
|
|
133
|
+
* the Claude probe in `a67b327d`).
|
|
134
|
+
*
|
|
135
|
+
* Called from both the failure path and the `finally`, so the escalation is armed at most once.
|
|
136
|
+
*/
|
|
137
|
+
function teardown(child) {
|
|
138
|
+
const hasExited = trackChildExit(child);
|
|
139
|
+
let signalled = false;
|
|
140
|
+
return () => {
|
|
141
|
+
if (signalled || hasExited())
|
|
142
|
+
return;
|
|
143
|
+
signalled = true;
|
|
144
|
+
child.kill('SIGTERM');
|
|
145
|
+
const escalation = setTimeout(() => {
|
|
146
|
+
if (hasExited())
|
|
147
|
+
return;
|
|
148
|
+
child.kill('SIGKILL');
|
|
149
|
+
}, KILL_GRACE_MS);
|
|
150
|
+
escalation.unref?.();
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=opencode-model-catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opencode-model-catalog.js","sourceRoot":"","sources":["../../src/core/opencode-model-catalog.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,6 +6,8 @@ export interface OpencodeRunnerOptions {
|
|
|
6
6
|
/** Wall-clock timeout for a run (ms); per-spec `timeoutMs` still wins. */
|
|
7
7
|
timeoutMs?: number;
|
|
8
8
|
}
|
|
9
|
+
/** Grace between the teardown SIGTERM and the SIGKILL that follows it. */
|
|
10
|
+
export declare const KILL_GRACE_MS = 4000;
|
|
9
11
|
/**
|
|
10
12
|
* `AgentRunner` over `opencode serve` — a headless HTTP server (the same one
|
|
11
13
|
* the opencode TUI talks to) with an SSE event stream. One server per session,
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { spawn as nodeSpawn } from 'node:child_process';
|
|
2
|
-
import { prependSystemPrompt } from './agent-runner.js';
|
|
2
|
+
import { prependSystemPrompt, trackChildExit } from './agent-runner.js';
|
|
3
3
|
import { buildChildEnv } from './agent-env.js';
|
|
4
4
|
import { AUTO_END_DELAY_MS, DEFAULT_RUN_TIMEOUT_MS } from './claude-cli-runner.js';
|
|
5
5
|
import { parseModelIdentity } from './model-identity.js';
|
|
6
6
|
import { V1TextCoalescer } from './v1-text-coalescer.js';
|
|
7
7
|
import { createOpencodeUiState, mapOpencodeEvent, opencodeSessionStarted, opencodeTurnStarted, } from './opencode-ui-mapper.js';
|
|
8
8
|
const SERVER_START_TIMEOUT_MS = 30_000;
|
|
9
|
+
/** Grace between the teardown SIGTERM and the SIGKILL that follows it. */
|
|
10
|
+
export const KILL_GRACE_MS = 4_000;
|
|
9
11
|
/**
|
|
10
12
|
* `AgentRunner` over `opencode serve` — a headless HTTP server (the same one
|
|
11
13
|
* the opencode TUI talks to) with an SSE event stream. One server per session,
|
|
@@ -47,6 +49,9 @@ class OpencodeSession {
|
|
|
47
49
|
opts;
|
|
48
50
|
result;
|
|
49
51
|
child;
|
|
52
|
+
/** "Has the server actually terminated?" — never `child.killed`, which only
|
|
53
|
+
* reports delivery and would disarm the escalation (#844/#858). */
|
|
54
|
+
hasExited;
|
|
50
55
|
serverOpen = true;
|
|
51
56
|
baseUrl;
|
|
52
57
|
sessionId;
|
|
@@ -81,6 +86,8 @@ class OpencodeSession {
|
|
|
81
86
|
autoEndTimer;
|
|
82
87
|
spawnFailed = null;
|
|
83
88
|
timedOut = false;
|
|
89
|
+
/** One teardown per session — see `terminate()`. */
|
|
90
|
+
signalled = false;
|
|
84
91
|
constructor(bin, timeoutMs, spec, onEvent, opts) {
|
|
85
92
|
this.bin = bin;
|
|
86
93
|
this.spec = spec;
|
|
@@ -97,6 +104,7 @@ class OpencodeSession {
|
|
|
97
104
|
catch (err) {
|
|
98
105
|
throw wrapSpawnError(err, bin);
|
|
99
106
|
}
|
|
107
|
+
this.hasExited = trackChildExit(this.child);
|
|
100
108
|
this.child.on('error', (err) => {
|
|
101
109
|
this.spawnFailed = wrapSpawnError(err, bin);
|
|
102
110
|
});
|
|
@@ -142,8 +150,7 @@ class OpencodeSession {
|
|
|
142
150
|
clearTimeout(this.autoEndTimer);
|
|
143
151
|
this.sse.abort();
|
|
144
152
|
this.serverOpen = false;
|
|
145
|
-
|
|
146
|
-
this.child.kill('SIGTERM');
|
|
153
|
+
this.terminate();
|
|
147
154
|
}
|
|
148
155
|
await this.exited;
|
|
149
156
|
if (this.spawnFailed)
|
|
@@ -196,12 +203,7 @@ class OpencodeSession {
|
|
|
196
203
|
return;
|
|
197
204
|
this.serverOpen = false;
|
|
198
205
|
this.sse.abort();
|
|
199
|
-
|
|
200
|
-
this.child.kill('SIGTERM');
|
|
201
|
-
setTimeout(() => {
|
|
202
|
-
if (this.child.exitCode == null && !this.child.killed)
|
|
203
|
-
this.child.kill('SIGKILL');
|
|
204
|
-
}, 4_000).unref?.();
|
|
206
|
+
this.terminate();
|
|
205
207
|
}
|
|
206
208
|
interrupt() {
|
|
207
209
|
this.serverOpen = false;
|
|
@@ -209,8 +211,37 @@ class OpencodeSession {
|
|
|
209
211
|
void this.http('POST', `/session/${this.sessionId}/abort`, undefined).catch(() => undefined);
|
|
210
212
|
}
|
|
211
213
|
this.sse.abort();
|
|
212
|
-
|
|
213
|
-
|
|
214
|
+
this.terminate();
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* The one place either signal is sent: SIGTERM now, SIGKILL once the grace
|
|
218
|
+
* window elapses.
|
|
219
|
+
*
|
|
220
|
+
* Both steps gate on `hasExited()`, never on `child.killed` — the latter
|
|
221
|
+
* flips the moment SIGTERM is *delivered*, so the old nested
|
|
222
|
+
* `exitCode == null && !killed` guard disarmed the escalation for exactly the
|
|
223
|
+
* server it was written for: one that installs its own SIGTERM handler stayed
|
|
224
|
+
* alive with `killed = true` and `exitCode === null`, outliving the whole
|
|
225
|
+
* window (#858, the same defect #844 fixed for the other two backends). Every
|
|
226
|
+
* caller here is followed by `await this.exited`, so a server that survived
|
|
227
|
+
* SIGTERM did not just leak — it hung the session's result forever.
|
|
228
|
+
*
|
|
229
|
+
* One teardown per session: all three call sites can run for the same session
|
|
230
|
+
* (`interrupt()` on the deadline, then the result promise's `finally`), and
|
|
231
|
+
* once SIGTERM is out with SIGKILL armed there is nothing a second pass adds.
|
|
232
|
+
* The old `!child.killed` test deduplicated this as a side effect of being
|
|
233
|
+
* wrong; `signalled` keeps that property on purpose.
|
|
234
|
+
*/
|
|
235
|
+
terminate() {
|
|
236
|
+
if (this.signalled || this.hasExited())
|
|
237
|
+
return;
|
|
238
|
+
this.signalled = true;
|
|
239
|
+
this.child.kill('SIGTERM');
|
|
240
|
+
setTimeout(() => {
|
|
241
|
+
if (this.hasExited())
|
|
242
|
+
return;
|
|
243
|
+
this.child.kill('SIGKILL');
|
|
244
|
+
}, KILL_GRACE_MS).unref?.();
|
|
214
245
|
}
|
|
215
246
|
// ---- server lifecycle ---------------------------------------------------
|
|
216
247
|
waitForServerUrl(fallbackPort) {
|