@mono-agent/agent-runtime 0.20.11 → 0.20.14
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/MIGRATION.md +264 -25
- package/README.md +139 -448
- package/package.json +7 -43
- package/src/agent/tools/agent-tool.js +5 -5
- package/src/agent/tools/bash.js +1 -24
- package/src/agent/tools/codex-subscription-search.js +1 -1
- package/src/agent/tools/index.js +7 -0
- package/src/agent/tools/monitor.js +140 -0
- package/src/agent/tools/pi-bridge.js +92 -7
- package/src/agent/tools/shared/bash-environment.js +31 -0
- package/src/agent/tools/shared/monitors.js +274 -0
- package/src/agent/tools/shared/process-runner.js +23 -5
- package/src/ai/cost.js +13 -68
- package/src/ai/index.js +4 -17
- package/src/ai/pi-interop.js +65 -1
- package/src/ai/pi-oauth-compat.js +1 -1
- package/src/ai/providers/codex/app-server-client.js +592 -0
- package/src/ai/providers/pi-models.js +18 -10
- package/src/ai/providers/pi-native/compaction-driver.js +49 -21
- package/src/ai/providers/pi-native/harness-adapter.js +338 -0
- package/src/ai/providers/pi-native/result-builder.js +10 -10
- package/src/ai/providers/pi-native/session-lifecycle.js +95 -40
- package/src/ai/providers/pi-native/stream-subscriber.js +23 -5
- package/src/ai/providers/pi-native/turn-runner.js +34 -15
- package/src/ai/providers/pi-native.js +48 -22
- package/src/ai/runtime/capabilities.js +11 -56
- package/src/ai/runtime/model-refs.js +118 -153
- package/src/ai/runtime/registry.js +22 -56
- package/src/ai/runtime/router.js +48 -408
- package/src/ai/runtime/session-liveness.js +3 -4
- package/src/ai/runtime/sessions.js +4 -5
- package/src/ai/runtime/tool-policy.js +0 -2
- package/src/ai/types.js +12 -108
- package/src/index.js +0 -6
- package/src/runtime.js +8 -14
- package/types/agent/tools/agent-tool.d.ts +1 -2
- package/types/agent/tools/codex-subscription-search.d.ts +1 -1
- package/types/agent/tools/index.d.ts +1 -0
- package/types/agent/tools/monitor.d.ts +44 -0
- package/types/agent/tools/pi-bridge.d.ts +3 -2
- package/types/agent/tools/shared/bash-environment.d.ts +4 -0
- package/types/agent/tools/shared/monitors.d.ts +83 -0
- package/types/agent/tools/shared/process-runner.d.ts +11 -2
- package/types/ai/cost.d.ts +1 -2
- package/types/ai/index.d.ts +1 -4
- package/types/ai/pi-interop.d.ts +20 -0
- package/types/ai/providers/codex/app-server-client.d.ts +37 -0
- package/types/ai/providers/pi-native/harness-adapter.d.ts +56 -0
- package/types/ai/providers/pi-native/result-builder.d.ts +3 -3
- package/types/ai/providers/pi-native/session-lifecycle.d.ts +2 -1
- package/types/ai/providers/pi-native/stream-subscriber.d.ts +2 -2
- package/types/ai/providers/pi-native/turn-runner.d.ts +33 -6
- package/types/ai/providers/pi-native.d.ts +21 -4
- package/types/ai/runtime/capabilities.d.ts +21 -70
- package/types/ai/runtime/model-refs.d.ts +0 -24
- package/types/ai/runtime/router.d.ts +3 -10
- package/types/ai/runtime/tool-policy.d.ts +0 -2
- package/types/ai/types.d.ts +28 -250
- package/types/index.d.ts +0 -1
- package/src/ai/providers/acp-client.js +0 -1149
- package/src/ai/providers/acp-privacy.js +0 -124
- package/src/ai/providers/acp-public.js +0 -21
- package/src/ai/providers/acp-session-tokens.js +0 -282
- package/src/ai/providers/acp-transport.js +0 -356
- package/src/ai/providers/acp.js +0 -543
- package/src/ai/providers/claude-cli.js +0 -883
- package/src/ai/providers/claude-sandbox.js +0 -71
- package/src/ai/providers/claude-sdk-discovery-worker.js +0 -53
- package/src/ai/providers/claude-sdk-discovery.js +0 -352
- package/src/ai/providers/claude-sdk.js +0 -1127
- package/src/ai/providers/claude-subagent-activity.js +0 -719
- package/src/ai/providers/claude-subagents.js +0 -88
- package/src/ai/providers/codex-app.js +0 -2946
- package/src/ai/providers/opencode-app.js +0 -1109
- package/src/ai/providers/opencode-discovery.js +0 -39
- package/src/ai/providers/opencode-server.js +0 -508
- package/src/ai/runtime/context-windows.js +0 -46
- package/src/ai/runtime/fast-mode.js +0 -8
- package/src/ai/streaming/codex-events.js +0 -146
- package/src/ai/streaming/opencode-events.js +0 -59
- package/types/ai/providers/acp-client.d.ts +0 -227
- package/types/ai/providers/acp-privacy.d.ts +0 -25
- package/types/ai/providers/acp-public.d.ts +0 -7
- package/types/ai/providers/acp-session-tokens.d.ts +0 -41
- package/types/ai/providers/acp-transport.d.ts +0 -45
- package/types/ai/providers/acp.d.ts +0 -93
- package/types/ai/providers/claude-cli.d.ts +0 -305
- package/types/ai/providers/claude-sandbox.d.ts +0 -79
- package/types/ai/providers/claude-sdk-discovery-worker.d.ts +0 -1
- package/types/ai/providers/claude-sdk-discovery.d.ts +0 -97
- package/types/ai/providers/claude-sdk.d.ts +0 -138
- package/types/ai/providers/claude-subagent-activity.d.ts +0 -53
- package/types/ai/providers/claude-subagents.d.ts +0 -18
- package/types/ai/providers/codex-app.d.ts +0 -151
- package/types/ai/providers/opencode-app.d.ts +0 -96
- package/types/ai/providers/opencode-discovery.d.ts +0 -4
- package/types/ai/providers/opencode-server.d.ts +0 -20
- package/types/ai/runtime/context-windows.d.ts +0 -9
- package/types/ai/runtime/fast-mode.d.ts +0 -2
- package/types/ai/streaming/codex-events.d.ts +0 -40
- package/types/ai/streaming/opencode-events.d.ts +0 -42
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
TOOL_POLICY_ALLOW_ALL_ONLY,
|
|
3
|
-
TOOL_POLICY_PROJECTED,
|
|
4
|
-
} from "./tool-policy.js";
|
|
1
|
+
import { TOOL_POLICY_PROJECTED } from "./tool-policy.js";
|
|
5
2
|
|
|
6
3
|
export const COMMON_CAPABILITIES = {
|
|
7
4
|
streaming: true,
|
|
@@ -15,18 +12,9 @@ export const COMMON_CAPABILITIES = {
|
|
|
15
12
|
supports_live_input: true,
|
|
16
13
|
supports_native_subagents: true,
|
|
17
14
|
supports_request_tool_environment: false,
|
|
18
|
-
supports_fast_mode: false,
|
|
19
|
-
tool_policy: TOOL_POLICY_PROJECTED,
|
|
20
15
|
};
|
|
21
16
|
|
|
22
17
|
export const RUNTIME_CAPABILITIES = {
|
|
23
|
-
claude: {
|
|
24
|
-
runtime: "sdk",
|
|
25
|
-
...COMMON_CAPABILITIES,
|
|
26
|
-
// The Claude Agent SDK persists sessions on disk; the bridge resumes
|
|
27
|
-
// them via queryOptions.resume when options.sessionId is supplied.
|
|
28
|
-
supports_session_resume: true,
|
|
29
|
-
},
|
|
30
18
|
pi: {
|
|
31
19
|
runtime: "pi-agent",
|
|
32
20
|
...COMMON_CAPABILITIES,
|
|
@@ -38,50 +26,17 @@ export const RUNTIME_CAPABILITIES = {
|
|
|
38
26
|
supports_native_subagents: false,
|
|
39
27
|
supports_request_tool_environment: true,
|
|
40
28
|
supports_mcp_apps: true,
|
|
41
|
-
|
|
42
|
-
codex: {
|
|
43
|
-
runtime: "cli",
|
|
44
|
-
...COMMON_CAPABILITIES,
|
|
45
|
-
// The codex-app bridge keeps the app-server subprocess + thread alive
|
|
46
|
-
// when options.sessionKeepAlive is set; resumed turns reuse the thread.
|
|
47
|
-
supports_session_resume: true,
|
|
48
|
-
supports_fast_mode: true,
|
|
49
|
-
tool_policy: TOOL_POLICY_ALLOW_ALL_ONLY,
|
|
50
|
-
},
|
|
51
|
-
opencode: {
|
|
52
|
-
runtime: "cli",
|
|
53
|
-
...COMMON_CAPABILITIES,
|
|
54
|
-
structured_output: false,
|
|
55
|
-
supports_session_resume: false,
|
|
56
|
-
supports_mcp: false,
|
|
57
|
-
supports_skills: false,
|
|
58
|
-
supports_live_input: false,
|
|
59
|
-
supports_native_subagents: false,
|
|
60
|
-
tool_policy: TOOL_POLICY_ALLOW_ALL_ONLY,
|
|
61
|
-
},
|
|
62
|
-
acp: {
|
|
63
|
-
runtime: "acp-stdio",
|
|
64
|
-
...COMMON_CAPABILITIES,
|
|
65
|
-
structured_output: false,
|
|
66
|
-
supports_session_resume: true,
|
|
67
|
-
// Runtime request-scoped MCP servers are not projected into ACP sessions.
|
|
68
|
-
// Profiles may own static ACP MCP configuration, but that is not the
|
|
69
|
-
// supports_mcp contract advertised to the route capability gate.
|
|
70
|
-
supports_mcp: false,
|
|
71
|
-
supports_skills: false,
|
|
72
|
-
supports_builtin_tools: false,
|
|
73
|
-
supports_live_input: false,
|
|
74
|
-
supports_native_subagents: false,
|
|
75
|
-
supports_request_tool_environment: false,
|
|
76
|
-
tool_policy: TOOL_POLICY_ALLOW_ALL_ONLY,
|
|
29
|
+
tool_policy: TOOL_POLICY_PROJECTED,
|
|
77
30
|
},
|
|
78
31
|
};
|
|
79
32
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
33
|
+
/**
|
|
34
|
+
* @param {*} [_legacySelection]
|
|
35
|
+
* @returns {{kind: "pi"} & typeof RUNTIME_CAPABILITIES.pi}
|
|
36
|
+
*/
|
|
37
|
+
export function runtimeCapabilities(_legacySelection) {
|
|
38
|
+
// Capability lookup no longer selects a backend. The ignored optional value
|
|
39
|
+
// keeps the still-separate router migration type-safe without allowing its
|
|
40
|
+
// legacy route ids to influence the sole surviving runtime contract.
|
|
41
|
+
return { kind: "pi", ...RUNTIME_CAPABILITIES.pi };
|
|
87
42
|
}
|
|
@@ -2,70 +2,139 @@
|
|
|
2
2
|
|
|
3
3
|
/** @typedef {import('../types.js').RuntimeModelRef} RuntimeModelRef */
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
const ACTIVE_RUNTIME_IDS = new Set(["claude", "pi", "codex", "opencode", "acp"]);
|
|
7
|
-
const ACP_PROFILE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
5
|
+
const PROVIDER_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Control (`Cc`), format (`Cf`), line- and paragraph-separator code points. Every one of them
|
|
9
|
+
* either moves the cursor or is invisible, which is what lets a model id restyle or extend
|
|
10
|
+
* the diagnostic that quotes it -- and an *accepted* reference is quoted verbatim, without
|
|
11
|
+
* re-validation, by `mono-agent validate`, `doctor`, the TUI, the web console, the daemon log
|
|
12
|
+
* and launchd's captured stdout, all of them line-oriented and durable.
|
|
13
|
+
*
|
|
14
|
+
* Deliberately the same set that `DIAGNOSTIC_UNSAFE_CHARACTERS` in @mono-agent/runtime-adapter
|
|
15
|
+
* escapes when it quotes a *rejected* value: that layer makes an unparseable value safe to
|
|
16
|
+
* print, this one makes a parsed value safe to print. runtime-adapter's model-reference bound
|
|
17
|
+
* suite asserts the two agree code point by code point rather than by comment.
|
|
18
|
+
*/
|
|
19
|
+
const UNQUOTABLE_REFERENCE_CHARACTERS = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} provider
|
|
23
|
+
* @param {string} model
|
|
24
|
+
*/
|
|
25
|
+
function rejectRemovedRuntimeReference(provider, model) {
|
|
26
|
+
if (provider === "codex") {
|
|
27
|
+
throw new Error(`codex is no longer a runtime backend; use openai-codex:${model}`);
|
|
28
|
+
}
|
|
29
|
+
if (provider === "claude" || provider === "claude-code") {
|
|
30
|
+
throw new Error(`${provider} is no longer a runtime backend; use anthropic:${model}`);
|
|
31
|
+
}
|
|
32
|
+
if (provider === "codex-cli") {
|
|
33
|
+
throw new Error(`codex-cli is no longer a runtime backend; use openai-codex:${model}`);
|
|
34
|
+
}
|
|
35
|
+
if (provider === "acp") {
|
|
36
|
+
throw new Error(
|
|
37
|
+
"ACP is no longer a runtime backend; use <provider>:<model> for Pi models, or mono-agent bridge acp to serve mono-agent over ACP",
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (provider === "vercel") {
|
|
41
|
+
const replacement = model.includes(":") ? model : "<provider>:<model>";
|
|
42
|
+
throw new Error(`vercel:<provider>:<model> is no longer supported; use ${replacement} directly`);
|
|
43
|
+
}
|
|
44
|
+
if (provider === "opencode" && model.includes(":")) {
|
|
45
|
+
throw new Error(`the opencode:<provider>:<model> runtime form is no longer supported; use ${model}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** @param {string} provider */
|
|
50
|
+
function requireProvider(provider) {
|
|
51
|
+
if (!PROVIDER_ID_RE.test(provider)) {
|
|
52
|
+
throw new Error("invalid provider id; expected [A-Za-z0-9][A-Za-z0-9._-]*");
|
|
12
53
|
}
|
|
13
|
-
return value;
|
|
14
54
|
}
|
|
15
55
|
|
|
16
|
-
|
|
56
|
+
/** @param {string} model */
|
|
57
|
+
function requireModel(model) {
|
|
58
|
+
if (!model || model.trim() !== model) {
|
|
59
|
+
throw new Error("model id must be a non-empty trimmed string");
|
|
60
|
+
}
|
|
17
61
|
if (["haiku", "sonnet", "opus"].includes(model)) {
|
|
18
|
-
throw new Error("tier aliases are not valid model
|
|
62
|
+
throw new Error("tier aliases are not valid model ids; use an exact model id");
|
|
19
63
|
}
|
|
20
64
|
}
|
|
21
65
|
|
|
22
66
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
67
|
+
* The one thing a parsed reference is guaranteed to CONTAIN nothing of.
|
|
68
|
+
*
|
|
69
|
+
* A reference this parser returns is handed to every operator surface that names a model and
|
|
70
|
+
* quoted verbatim by each of them, without re-validation: `mono-agent validate`, `doctor`, the
|
|
71
|
+
* TUI, the web console, the daemon log, launchd's captured stdout. All of them are
|
|
72
|
+
* line-oriented and most are durable, so a control or formatting code point in the value is
|
|
73
|
+
* enough to restyle the diagnostic quoting it or forge a second line inside it. No legitimate
|
|
74
|
+
* model id contains one, nothing downstream can repair one after the fact, and escaping it at
|
|
75
|
+
* six renderers is six chances to miss. So it is refused here, at the source, absolutely.
|
|
76
|
+
*
|
|
77
|
+
* There is deliberately NO length rule to go with it. A grammar layer does not get to decide
|
|
78
|
+
* what a provider may call a model, and three rounds of trying produced three wrong answers:
|
|
79
|
+
* 96 bytes refused a Hugging Face GGUF repo Ollama serves today, 160 bytes refused an
|
|
80
|
+
* `ollama:<model>:<tag>` reference whose two halves Ollama itself validates at 80 bytes each,
|
|
81
|
+
* and no maximum is published in common across Ollama, LM Studio, OpenRouter and custom
|
|
82
|
+
* `openai_compat` endpoints from which a third guess would be any better. mono-agent's own
|
|
83
|
+
* `discoverLocalProviderModels` was returning ids this parser then refused.
|
|
84
|
+
*
|
|
85
|
+
* The requirements that ceiling was carrying are all met by the layers that render or transmit
|
|
86
|
+
* a reference, where the answer to "too long" is a shorter STRING rather than a lost route:
|
|
87
|
+
* - a diagnostic echo is clamped by truncation, marking the cut -- `sanitizeModelReferenceText`
|
|
88
|
+
* on `MODEL_REFERENCE_ECHO_MAX_BYTES`, @mono-agent/runtime-adapter;
|
|
89
|
+
* - an adapter error body is clamped by `sendJsonError`;
|
|
90
|
+
* - the `/v1/info` payload is bounded by per-contributor measured budgets and a total
|
|
91
|
+
* serializer fence that sheds whole fields (`channel-drivers/tui.ts`, `sendBoundedInfo`).
|
|
92
|
+
* Each of those is asserted at its own layer; none of them is asserted here, because none of
|
|
93
|
+
* them is this function's job.
|
|
94
|
+
*
|
|
95
|
+
* Runs last, after `rejectRemovedRuntimeReference`, so a retired backend still gets its
|
|
96
|
+
* concrete repair named (`codex:x` -> `openai-codex:x`) instead of a generic shape complaint.
|
|
97
|
+
* Rejection messages are operator-supplied text too, and are bounded where they are rendered.
|
|
98
|
+
*
|
|
99
|
+
* @param {string} reference
|
|
25
100
|
*/
|
|
26
|
-
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
if (value.startsWith("openai:")) {
|
|
30
|
-
const model = requirePart(value.slice("openai:".length), "model id required");
|
|
31
|
-
return `pi:openai:${model}`;
|
|
32
|
-
}
|
|
33
|
-
if (value.startsWith("codex:")) {
|
|
34
|
-
const model = requirePart(value.slice("codex:".length), "model id required");
|
|
35
|
-
return `codex:${model}`;
|
|
36
|
-
}
|
|
37
|
-
if (value.startsWith("vercel:")) {
|
|
38
|
-
const rest = value.slice("vercel:".length);
|
|
39
|
-
const i = rest.indexOf(":");
|
|
40
|
-
if (i <= 0 || i === rest.length - 1) {
|
|
41
|
-
throw new Error("invalid vercel model reference; expected vercel:<providerId>:<modelName>");
|
|
42
|
-
}
|
|
43
|
-
const provider = requirePart(rest.slice(0, i), "provider id required");
|
|
44
|
-
const model = requirePart(rest.slice(i + 1), "model name required");
|
|
45
|
-
return `pi:${provider}:${model}`;
|
|
46
|
-
}
|
|
47
|
-
if (value.startsWith("claude-code:")) {
|
|
48
|
-
const model = requirePart(value.slice("claude-code:".length), "model id required");
|
|
49
|
-
return `claude:${model}`;
|
|
101
|
+
function requireQuotableReference(reference) {
|
|
102
|
+
if (UNQUOTABLE_REFERENCE_CHARACTERS.test(reference)) {
|
|
103
|
+
throw new Error("model reference must not contain control or formatting characters");
|
|
50
104
|
}
|
|
51
|
-
return value;
|
|
52
105
|
}
|
|
53
106
|
|
|
54
107
|
/**
|
|
108
|
+
* Parse the provider/model pair after the optional legacy `pi:` wrapper has
|
|
109
|
+
* been removed. Pi model ids are opaque and commonly contain further colons,
|
|
110
|
+
* so only the first colon is structural.
|
|
111
|
+
*
|
|
55
112
|
* @param {string} value
|
|
56
113
|
* @returns {RuntimeModelRef}
|
|
57
114
|
*/
|
|
58
|
-
|
|
59
|
-
|
|
115
|
+
function parseCanonicalReference(value) {
|
|
116
|
+
const separator = value.indexOf(":");
|
|
117
|
+
if (separator <= 0 || separator === value.length - 1) {
|
|
118
|
+
throw new Error("invalid model reference; expected <provider>:<model>");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const provider = value.slice(0, separator);
|
|
122
|
+
const model = value.slice(separator + 1);
|
|
123
|
+
const reference = `${provider}:${model}`;
|
|
124
|
+
requireProvider(provider);
|
|
125
|
+
requireModel(model);
|
|
126
|
+
rejectRemovedRuntimeReference(provider, model);
|
|
127
|
+
requireQuotableReference(reference);
|
|
128
|
+
|
|
129
|
+
return { provider, model, reference };
|
|
60
130
|
}
|
|
61
131
|
|
|
62
132
|
/**
|
|
63
133
|
* @param {string} value
|
|
64
|
-
* @returns {RuntimeModelRef
|
|
134
|
+
* @returns {RuntimeModelRef}
|
|
65
135
|
*/
|
|
66
|
-
export function
|
|
67
|
-
|
|
68
|
-
return parsed.sdk;
|
|
136
|
+
export function normalizeRuntimeModelReference(value) {
|
|
137
|
+
return parseRuntimeModelReference(value);
|
|
69
138
|
}
|
|
70
139
|
|
|
71
140
|
/**
|
|
@@ -73,121 +142,17 @@ export function sdkFromModelReference(value) {
|
|
|
73
142
|
* @returns {RuntimeModelRef}
|
|
74
143
|
*/
|
|
75
144
|
export function parseRuntimeModelReference(value) {
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
if (value.startsWith("acp:")) {
|
|
79
|
-
const profileId = requirePart(value.slice("acp:".length), "ACP profile id required");
|
|
80
|
-
if (!ACP_PROFILE_ID_RE.test(profileId)) {
|
|
81
|
-
throw new Error("invalid acp model reference; expected acp:<profile-id>");
|
|
82
|
-
}
|
|
83
|
-
return { sdk: "acp", model: profileId, reference: value };
|
|
145
|
+
if (typeof value !== "string" || value.length === 0 || value.trim() !== value) {
|
|
146
|
+
throw new Error("model reference must be a non-empty trimmed string");
|
|
84
147
|
}
|
|
85
148
|
|
|
86
149
|
if (value.startsWith("pi:")) {
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
throw new Error("invalid pi model reference; expected pi:<providerId>:<modelName>");
|
|
91
|
-
}
|
|
92
|
-
const provider = requirePart(rest.slice(0, i), "provider id required");
|
|
93
|
-
const model = requirePart(rest.slice(i + 1), "model id required");
|
|
94
|
-
return { sdk: "pi", provider, model, reference: value };
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (value.startsWith("opencode:")) {
|
|
98
|
-
// opencode:<providerID>:<modelID> — providerID/modelID come from OpenCode's own
|
|
99
|
-
// provider registry (auth.json). Only the first colon separates them; modelID may
|
|
100
|
-
// contain slashes (e.g. openrouter's `anthropic/claude-3.5-sonnet`).
|
|
101
|
-
const rest = value.slice("opencode:".length);
|
|
102
|
-
const i = rest.indexOf(":");
|
|
103
|
-
if (i <= 0 || i === rest.length - 1) {
|
|
104
|
-
throw new Error("invalid opencode model reference; expected opencode:<providerId>:<modelId>");
|
|
105
|
-
}
|
|
106
|
-
const provider = requirePart(rest.slice(0, i), "provider id required");
|
|
107
|
-
const model = requirePart(rest.slice(i + 1), "model id required");
|
|
108
|
-
return { sdk: "opencode", provider, model, reference: value };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const i = value.indexOf(":");
|
|
112
|
-
if (i <= 0 || i === value.length - 1) {
|
|
113
|
-
throw new Error("invalid model reference; expected <sdk>:<modelId>");
|
|
114
|
-
}
|
|
115
|
-
const sdk = value.slice(0, i);
|
|
116
|
-
const model = requirePart(value.slice(i + 1), "model id required");
|
|
117
|
-
|
|
118
|
-
if (RESERVED_RUNTIME_IDS.has(sdk)) {
|
|
119
|
-
throw new Error(`reserved runtime id: ${sdk}; use a canonical pi:*, claude:*, or codex:* model reference`);
|
|
120
|
-
}
|
|
121
|
-
if (!ACTIVE_RUNTIME_IDS.has(sdk)) {
|
|
122
|
-
throw new Error(`unknown sdk: ${sdk}`);
|
|
123
|
-
}
|
|
124
|
-
rejectTierAlias(model);
|
|
125
|
-
return { sdk, model, reference: value };
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export const ACTIVE_RUNTIME_KINDS = [...ACTIVE_RUNTIME_IDS];
|
|
129
|
-
export const RESERVED_RUNTIME_KINDS = [...RESERVED_RUNTIME_IDS];
|
|
130
|
-
|
|
131
|
-
// intelligence-ramp: which model refs can run under which execution_mode.
|
|
132
|
-
// sdk='acp' → ACP only (dedicated stdio client mode)
|
|
133
|
-
// sdk='claude' → CLI (claude binary) or SDK (Anthropic)
|
|
134
|
-
// sdk='codex' → CLI only (codex app-server)
|
|
135
|
-
// sdk='opencode' → CLI only (opencode server via @opencode-ai/sdk)
|
|
136
|
-
// sdk='pi' → SDK only (the pi-native bridge handles openai-codex and other providers)
|
|
137
|
-
|
|
138
|
-
// Returns null when the combo is fine; otherwise a short reason string the
|
|
139
|
-
// UI / API can show.
|
|
140
|
-
/**
|
|
141
|
-
* @param {string|RuntimeModelRef} modelRefOrParsed
|
|
142
|
-
* @param {string} executionMode
|
|
143
|
-
* @returns {string|null}
|
|
144
|
-
*/
|
|
145
|
-
export function executionModeIncompatibilityReason(modelRefOrParsed, executionMode) {
|
|
146
|
-
let parsed;
|
|
147
|
-
try {
|
|
148
|
-
parsed = typeof modelRefOrParsed === "string"
|
|
149
|
-
? parseRuntimeModelReference(modelRefOrParsed)
|
|
150
|
-
: modelRefOrParsed;
|
|
151
|
-
} catch {
|
|
152
|
-
return null;
|
|
153
|
-
}
|
|
154
|
-
if (!parsed) return null;
|
|
155
|
-
if (!executionMode) return null;
|
|
156
|
-
if (executionMode === "acp") {
|
|
157
|
-
return parsed.sdk === "acp"
|
|
158
|
-
? null
|
|
159
|
-
: `sdk \`${parsed.sdk}\` is not supported under ACP execution mode.`;
|
|
160
|
-
}
|
|
161
|
-
if (executionMode === "sdk") {
|
|
162
|
-
if (parsed.sdk === "codex") {
|
|
163
|
-
return "Codex CLI requires CLI execution mode.";
|
|
150
|
+
const wrapped = value.slice("pi:".length);
|
|
151
|
+
if (wrapped.indexOf(":") <= 0 || wrapped.endsWith(":")) {
|
|
152
|
+
throw new Error("invalid pi model reference; use pi:<provider>:<model>");
|
|
164
153
|
}
|
|
165
|
-
|
|
166
|
-
return "OpenCode CLI requires CLI execution mode.";
|
|
167
|
-
}
|
|
168
|
-
if (parsed.sdk === "acp") {
|
|
169
|
-
return "ACP profiles require ACP execution mode.";
|
|
170
|
-
}
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
if (executionMode !== "cli") return null;
|
|
174
|
-
if (parsed.sdk === "claude") return null;
|
|
175
|
-
if (parsed.sdk === "codex") return null;
|
|
176
|
-
if (parsed.sdk === "opencode") return null;
|
|
177
|
-
if (parsed.sdk === "acp") return "ACP profiles require ACP execution mode.";
|
|
178
|
-
if (parsed.sdk === "pi") {
|
|
179
|
-
const provider = parsed.provider || "unknown";
|
|
180
|
-
const suffix = provider === "openai-codex" ? "; use codex:<model> for Codex CLI" : "";
|
|
181
|
-
return `Provider \`${provider}\` only runs under SDK execution mode${suffix}.`;
|
|
154
|
+
return parseCanonicalReference(wrapped);
|
|
182
155
|
}
|
|
183
|
-
return `sdk \`${parsed.sdk}\` is not supported under CLI execution mode.`;
|
|
184
|
-
}
|
|
185
156
|
|
|
186
|
-
|
|
187
|
-
* @param {string|RuntimeModelRef} modelRefOrParsed
|
|
188
|
-
* @param {string} executionMode
|
|
189
|
-
* @returns {boolean}
|
|
190
|
-
*/
|
|
191
|
-
export function isModelCompatibleWithExecutionMode(modelRefOrParsed, executionMode) {
|
|
192
|
-
return executionModeIncompatibilityReason(modelRefOrParsed, executionMode) === null;
|
|
157
|
+
return parseCanonicalReference(value);
|
|
193
158
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { runtimeCapabilities } from "./capabilities.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* `RuntimeModelRef` is referenced inline (not aliased with a top-level
|
|
@@ -20,64 +20,19 @@ import { COMMON_CAPABILITIES, runtimeCapabilities } from "./capabilities.js";
|
|
|
20
20
|
* @property {(options?: Object) => Promise<RuntimeBridge>} load
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
// CLI bridges are checked first when execution_mode='cli'. Without that flag
|
|
24
|
-
// the resolver falls through to the SDK bridges below, preserving the
|
|
25
|
-
// pre-Phase-2 behaviour for any agent that hasn't opted in.
|
|
26
23
|
/** @type {Object<string, BridgeSpec>} */
|
|
27
24
|
const builtinBridgeSpecs = {
|
|
28
|
-
"acp-stdio": {
|
|
29
|
-
id: "acp-stdio",
|
|
30
|
-
supports: (ref, options) => ref?.sdk === "acp" && options?.executionMode === "acp",
|
|
31
|
-
capabilities: () => runtimeCapabilities("acp"),
|
|
32
|
-
load: async () => (await import("../providers/acp.js")).acpRuntimeBridge,
|
|
33
|
-
},
|
|
34
|
-
"claude-code": {
|
|
35
|
-
id: "claude-code",
|
|
36
|
-
supports: (ref, options) => ref?.sdk === "claude" && options?.executionMode === "cli",
|
|
37
|
-
// The claude CLI resumes prior sessions via `--resume <sessionId>`.
|
|
38
|
-
capabilities: () => ({
|
|
39
|
-
kind: "claude-code",
|
|
40
|
-
runtime: "cli",
|
|
41
|
-
...COMMON_CAPABILITIES,
|
|
42
|
-
supports_session_resume: true,
|
|
43
|
-
// The one-shot CLI bridge has no bidirectional stdin steering channel.
|
|
44
|
-
supports_live_input: false,
|
|
45
|
-
}),
|
|
46
|
-
load: async () => (await import("../providers/claude-cli.js")).claudeCodeRuntimeBridge,
|
|
47
|
-
},
|
|
48
|
-
"codex-app": {
|
|
49
|
-
id: "codex-app",
|
|
50
|
-
supports: (ref, options) => ref?.sdk === "codex" && options?.executionMode === "cli",
|
|
51
|
-
// The codex-app bridge keeps the app-server subprocess + thread alive
|
|
52
|
-
// across turns when options.sessionKeepAlive is set.
|
|
53
|
-
capabilities: () => {
|
|
54
|
-
const { kind: _sdkKind, ...capabilities } = runtimeCapabilities("codex");
|
|
55
|
-
return { kind: "codex-app", ...capabilities };
|
|
56
|
-
},
|
|
57
|
-
load: async () => (await import("../providers/codex-app.js")).codexAppRuntimeBridge,
|
|
58
|
-
},
|
|
59
|
-
"opencode-app": {
|
|
60
|
-
id: "opencode-app",
|
|
61
|
-
supports: (ref, options) => ref?.sdk === "opencode" && options?.executionMode === "cli",
|
|
62
|
-
capabilities: () => {
|
|
63
|
-
const { kind: _sdkKind, ...capabilities } = runtimeCapabilities("opencode");
|
|
64
|
-
return { kind: "opencode-app", ...capabilities };
|
|
65
|
-
},
|
|
66
|
-
load: async () => (await import("../providers/opencode-app.js")).opencodeAppRuntimeBridge,
|
|
67
|
-
},
|
|
68
|
-
claude: {
|
|
69
|
-
id: "claude",
|
|
70
|
-
supports: (ref) => ref?.sdk === "claude",
|
|
71
|
-
capabilities: () => runtimeCapabilities("claude"),
|
|
72
|
-
load: async () => (await import("../providers/claude-sdk.js")).claudeRuntimeBridge,
|
|
73
|
-
},
|
|
74
25
|
pi: {
|
|
75
26
|
id: "pi",
|
|
76
|
-
supports: (
|
|
77
|
-
capabilities: () => runtimeCapabilities(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
27
|
+
supports: () => true,
|
|
28
|
+
capabilities: () => runtimeCapabilities(),
|
|
29
|
+
load: async () => {
|
|
30
|
+
const { piNativeRuntimeBridge } = await import("../providers/pi-native.js");
|
|
31
|
+
// JavaScript widens the bridge object's `id` property to string. Keep the
|
|
32
|
+
// literal-safe registry contract here so the Pi provider module remains
|
|
33
|
+
// untouched by this bridge-removal work package.
|
|
34
|
+
return /** @type {RuntimeBridge} */ (piNativeRuntimeBridge);
|
|
35
|
+
},
|
|
81
36
|
},
|
|
82
37
|
};
|
|
83
38
|
|
|
@@ -98,10 +53,21 @@ export function listRuntimeBridges() {
|
|
|
98
53
|
* @returns {Promise<RuntimeBridge>}
|
|
99
54
|
*/
|
|
100
55
|
export async function resolveRuntimeBridge(modelRef, options = {}) {
|
|
56
|
+
// Direct kernel callers may bypass the parser, so malformed references must
|
|
57
|
+
// still fail before the sole bridge receives them.
|
|
58
|
+
if (
|
|
59
|
+
typeof modelRef?.provider !== "string"
|
|
60
|
+
|| modelRef.provider.length === 0
|
|
61
|
+
|| typeof modelRef.model !== "string"
|
|
62
|
+
|| modelRef.model.length === 0
|
|
63
|
+
|| modelRef.reference !== `${modelRef.provider}:${modelRef.model}`
|
|
64
|
+
) {
|
|
65
|
+
throw new Error("unsupported model reference: expected <provider>:<model>");
|
|
66
|
+
}
|
|
101
67
|
for (const spec of Object.values(builtinBridgeSpecs)) {
|
|
102
68
|
if (spec.supports(modelRef, options)) return spec.load(options);
|
|
103
69
|
}
|
|
104
|
-
throw new Error(`unsupported
|
|
70
|
+
throw new Error(`unsupported model reference: ${modelRef.reference}`);
|
|
105
71
|
}
|
|
106
72
|
|
|
107
73
|
export { RUNTIME_CAPABILITIES, runtimeCapabilities } from "./capabilities.js";
|