@mono-agent/agent-runtime 0.20.11 → 0.21.0
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/ARCHITECTURE.md +50 -11
- package/MIGRATION.md +288 -26
- package/README.md +352 -477
- package/package.json +13 -44
- package/src/agent/tool-bloat.js +145 -9
- package/src/agent/tools/agent-tool.js +108 -9
- package/src/agent/tools/bash.js +11 -26
- package/src/agent/tools/codex-subscription-search.js +123 -29
- package/src/agent/tools/exec.js +10 -2
- package/src/agent/tools/index.js +7 -0
- package/src/agent/tools/monitor.js +149 -0
- package/src/agent/tools/pi-bridge.js +123 -19
- package/src/agent/tools/shared/bash-environment.js +31 -0
- package/src/agent/tools/shared/monitors.js +293 -0
- package/src/agent/tools/shared/path-resolver.js +25 -6
- package/src/agent/tools/shared/process-jobs.js +6 -1
- package/src/agent/tools/shared/process-runner.js +26 -6
- package/src/agent/tools/shared/tool-context.js +8 -0
- package/src/agent/tools/web-access-interstitial.js +70 -0
- package/src/agent/tools/web-browser-render.js +83 -58
- package/src/agent/tools/web-controller.js +112 -21
- package/src/agent/tools/web-document-extractor.js +379 -0
- package/src/agent/tools/web-fetch.js +271 -243
- package/src/agent/tools/web-request.js +65 -0
- package/src/agent/tools/web-search-output.js +165 -0
- package/src/agent/tools/web-search-state.js +75 -0
- package/src/agent/tools/web-search.js +532 -71
- package/src/ai/cost.js +13 -68
- package/src/ai/failure.js +3 -3
- package/src/ai/index.js +5 -17
- package/src/ai/observer.js +8 -0
- package/src/ai/pi-interop.js +221 -1
- package/src/ai/pi-oauth-compat.js +1 -1
- package/src/ai/provider-check.js +131 -0
- 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 +94 -42
- package/src/ai/providers/pi-native/compaction-summary.js +140 -0
- package/src/ai/providers/pi-native/harness-adapter.js +376 -0
- package/src/ai/providers/pi-native/prompt-cache-diagnostics.js +103 -0
- package/src/ai/providers/pi-native/provider-attribution.js +102 -0
- package/src/ai/providers/pi-native/result-builder.js +38 -14
- package/src/ai/providers/pi-native/session-lifecycle.js +253 -55
- package/src/ai/providers/pi-native/stream-subscriber.js +52 -6
- package/src/ai/providers/pi-native/terminal-recovery.js +40 -0
- package/src/ai/providers/pi-native/turn-runner.js +279 -28
- package/src/ai/providers/pi-native.js +206 -61
- package/src/ai/runtime/capabilities.js +11 -56
- package/src/ai/runtime/live-input-events.js +250 -54
- package/src/ai/runtime/model-refs.js +118 -153
- package/src/ai/runtime/registry.js +22 -56
- package/src/ai/runtime/router.js +76 -417
- 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/tool-lifecycle.js +32 -18
- package/src/ai/types.js +37 -112
- package/src/index.js +0 -6
- package/src/runtime.js +29 -16
- package/types/agent/tool-bloat.d.ts +1 -1
- package/types/agent/tools/agent-tool.d.ts +4 -2
- package/types/agent/tools/bash.d.ts +5 -3
- package/types/agent/tools/codex-subscription-search.d.ts +7 -3
- package/types/agent/tools/exec.d.ts +5 -3
- package/types/agent/tools/index.d.ts +1 -0
- package/types/agent/tools/monitor.d.ts +47 -0
- package/types/agent/tools/pi-bridge.d.ts +7 -4
- package/types/agent/tools/shared/bash-environment.d.ts +4 -0
- package/types/agent/tools/shared/monitors.d.ts +98 -0
- package/types/agent/tools/shared/process-jobs.d.ts +5 -1
- package/types/agent/tools/shared/process-runner.d.ts +14 -4
- package/types/agent/tools/shared/tool-context.d.ts +2 -0
- package/types/agent/tools/web-access-interstitial.d.ts +23 -0
- package/types/agent/tools/web-browser-render.d.ts +4 -1
- package/types/agent/tools/web-controller.d.ts +4 -2
- package/types/agent/tools/web-document-extractor.d.ts +27 -0
- package/types/agent/tools/web-fetch.d.ts +19 -24
- package/types/agent/tools/web-request.d.ts +20 -0
- package/types/agent/tools/web-search-output.d.ts +31 -0
- package/types/agent/tools/web-search-state.d.ts +21 -0
- package/types/agent/tools/web-search.d.ts +10 -45
- package/types/ai/cost.d.ts +1 -2
- package/types/ai/index.d.ts +2 -4
- package/types/ai/observer.d.ts +6 -0
- package/types/ai/pi-interop.d.ts +81 -0
- package/types/ai/provider-check.d.ts +53 -0
- package/types/ai/providers/codex/app-server-client.d.ts +37 -0
- package/types/ai/providers/pi-native/compaction-driver.d.ts +2 -1
- package/types/ai/providers/pi-native/compaction-summary.d.ts +19 -0
- package/types/ai/providers/pi-native/harness-adapter.d.ts +58 -0
- package/types/ai/providers/pi-native/prompt-cache-diagnostics.d.ts +3 -0
- package/types/ai/providers/pi-native/provider-attribution.d.ts +26 -0
- package/types/ai/providers/pi-native/result-builder.d.ts +14 -4
- package/types/ai/providers/pi-native/session-lifecycle.d.ts +25 -6
- package/types/ai/providers/pi-native/stream-subscriber.d.ts +2 -2
- package/types/ai/providers/pi-native/terminal-recovery.d.ts +2 -0
- package/types/ai/providers/pi-native/turn-runner.d.ts +68 -10
- package/types/ai/providers/pi-native.d.ts +21 -4
- package/types/ai/runtime/capabilities.d.ts +21 -70
- package/types/ai/runtime/live-input-events.d.ts +32 -8
- 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/tool-lifecycle.d.ts +4 -3
- package/types/ai/types.d.ts +162 -256
- 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,68 +1,102 @@
|
|
|
1
|
-
// Metadata-only live-input
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// intentionally reused by the fallback router across provider attempts.
|
|
1
|
+
// Metadata-only live-input lifecycle instrumentation and logical-run replay fencing.
|
|
2
|
+
// Stable ids identify one logical delivery across provider retries. The first
|
|
3
|
+
// occurrence owns the body and identity; later same-id occurrences are invalid
|
|
4
|
+
// duplicates unless an exact opaque logical-owner token proves they carry a new
|
|
5
|
+
// callback lease for that first owner. Anonymous input remains legal but is
|
|
6
|
+
// never replayed.
|
|
8
7
|
|
|
9
8
|
// @ts-check
|
|
10
9
|
|
|
11
|
-
const
|
|
10
|
+
const LIVE_INPUT_INSTRUMENTED = Symbol("mono-agent.live-input-instrumented");
|
|
11
|
+
const MAX_DIAGNOSTICS_PER_KIND = 100;
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* @typedef {{
|
|
15
|
-
* @typedef {{
|
|
14
|
+
* @typedef {{providerEntryId?: string, providerRunId?: string}} RuntimeLiveInputEvidence
|
|
15
|
+
* @typedef {{reason: "delivery_uncertain", providerEntryId?: string, providerRunId?: string}} RuntimeLiveInputUncertainty
|
|
16
|
+
* @typedef {{body: string, id?: string, receivedAt?: string, logicalOwner?: object, accepted?: (evidence?: RuntimeLiveInputEvidence) => unknown, acknowledge?: (evidence?: RuntimeLiveInputEvidence) => unknown, uncertain?: (details: RuntimeLiveInputUncertainty) => unknown, reject?: (reason?: unknown) => unknown}} RuntimeLiveInputMessage
|
|
17
|
+
* @typedef {{message: RuntimeLiveInputMessage, callbackMessage: RuntimeLiveInputMessage, logicalOwner?: object, inputId: string, receivedAt?: string, phase: "available"|"leased"|"native_accepted"|"consumed"|"uncertain", attempt: number, generation: number}} LiveInputOwner
|
|
18
|
+
* @typedef {{type: string, [key: string]: unknown}} LiveInputEvent
|
|
16
19
|
*/
|
|
17
20
|
|
|
18
21
|
/**
|
|
19
22
|
* @param {AsyncIterable<RuntimeLiveInputMessage>|undefined} liveInput
|
|
20
|
-
* @param {(event:
|
|
23
|
+
* @param {(event: LiveInputEvent) => void} onEvent
|
|
21
24
|
* @returns {AsyncIterable<RuntimeLiveInputMessage>|undefined}
|
|
22
25
|
*/
|
|
23
|
-
export function instrumentLiveInputAppliedEvents(liveInput,
|
|
26
|
+
export function instrumentLiveInputAppliedEvents(liveInput, onEvent) {
|
|
24
27
|
if (liveInput === undefined || isInstrumented(liveInput)) return liveInput;
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
/** @type {Map<string, LiveInputOwner>} */
|
|
30
|
+
const owners = new Map();
|
|
31
|
+
const diagnosticCounts = new Map();
|
|
32
|
+
let generation = 0;
|
|
33
|
+
|
|
27
34
|
const instrumented = {
|
|
28
|
-
[
|
|
35
|
+
[LIVE_INPUT_INSTRUMENTED]: true,
|
|
29
36
|
[Symbol.asyncIterator]() {
|
|
37
|
+
generation += 1;
|
|
38
|
+
const iteratorGeneration = generation;
|
|
30
39
|
const iterator = liveInput[Symbol.asyncIterator]();
|
|
40
|
+
const yieldedIds = new Set();
|
|
31
41
|
let ordinal = 0;
|
|
42
|
+
|
|
32
43
|
return {
|
|
33
44
|
async next() {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
45
|
+
while (true) {
|
|
46
|
+
const next = await iterator.next();
|
|
47
|
+
if (next.done === true) return next;
|
|
48
|
+
ordinal += 1;
|
|
49
|
+
const message = next.value;
|
|
50
|
+
const stableId = normalizedStableId(message?.id);
|
|
51
|
+
|
|
52
|
+
if (stableId === undefined) {
|
|
53
|
+
if (iteratorGeneration > 1) {
|
|
54
|
+
emitBounded("anonymous_identity", {
|
|
55
|
+
type: "live_input_replay_suppressed",
|
|
56
|
+
reason: "anonymous_identity",
|
|
57
|
+
generation: iteratorGeneration,
|
|
58
|
+
ordinal,
|
|
59
|
+
});
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const owner = createOwner(message, `anonymous:${iteratorGeneration}:${ordinal}`, iteratorGeneration);
|
|
63
|
+
return { done: false, value: lease(owner) };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const existing = owners.get(stableId);
|
|
67
|
+
if (existing === undefined) {
|
|
68
|
+
const owner = createOwner(message, stableId, iteratorGeneration);
|
|
69
|
+
owners.set(stableId, owner);
|
|
70
|
+
yieldedIds.add(stableId);
|
|
71
|
+
return { done: false, value: lease(owner) };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
emitBounded("duplicate_id", {
|
|
75
|
+
type: "live_input_duplicate_suppressed",
|
|
76
|
+
inputId: stableId,
|
|
77
|
+
generation: iteratorGeneration,
|
|
78
|
+
});
|
|
79
|
+
if (
|
|
80
|
+
iteratorGeneration > existing.generation
|
|
81
|
+
&& existing.phase === "available"
|
|
82
|
+
&& !yieldedIds.has(stableId)
|
|
83
|
+
) {
|
|
84
|
+
// Suppress the later occurrence itself. It merely reveals that the
|
|
85
|
+
// first owner is present in this replay generation; replay the
|
|
86
|
+
// first owner's immutable body and identity.
|
|
87
|
+
yieldedIds.add(stableId);
|
|
88
|
+
existing.generation = iteratorGeneration;
|
|
89
|
+
if (
|
|
90
|
+
existing.logicalOwner !== undefined
|
|
91
|
+
&& existing.logicalOwner === validLogicalOwner(message?.logicalOwner)
|
|
92
|
+
) {
|
|
93
|
+
// The logical owner remains the first occurrence. This is only
|
|
94
|
+
// its fresh, independently fenced host callback lease.
|
|
95
|
+
existing.callbackMessage = message;
|
|
96
|
+
}
|
|
97
|
+
return { done: false, value: lease(existing) };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
66
100
|
},
|
|
67
101
|
async return(value) {
|
|
68
102
|
return typeof iterator.return === "function"
|
|
@@ -76,19 +110,181 @@ export function instrumentLiveInputAppliedEvents(liveInput, onApplied) {
|
|
|
76
110
|
};
|
|
77
111
|
},
|
|
78
112
|
};
|
|
113
|
+
|
|
79
114
|
return /** @type {AsyncIterable<RuntimeLiveInputMessage>} */ (instrumented);
|
|
115
|
+
|
|
116
|
+
/** @param {RuntimeLiveInputMessage} message @param {string} inputId @param {number} ownerGeneration */
|
|
117
|
+
function createOwner(message, inputId, ownerGeneration) {
|
|
118
|
+
const receivedAt = validString(message?.receivedAt);
|
|
119
|
+
const logicalOwner = validLogicalOwner(message?.logicalOwner);
|
|
120
|
+
return {
|
|
121
|
+
message,
|
|
122
|
+
callbackMessage: message,
|
|
123
|
+
...(logicalOwner === undefined ? {} : { logicalOwner }),
|
|
124
|
+
inputId,
|
|
125
|
+
...(receivedAt === undefined ? {} : { receivedAt }),
|
|
126
|
+
phase: /** @type {const} */ ("available"),
|
|
127
|
+
attempt: 0,
|
|
128
|
+
generation: ownerGeneration,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** @param {LiveInputOwner} owner */
|
|
133
|
+
function lease(owner) {
|
|
134
|
+
owner.phase = "leased";
|
|
135
|
+
owner.attempt += 1;
|
|
136
|
+
const attempt = owner.attempt;
|
|
137
|
+
const host = owner.callbackMessage;
|
|
138
|
+
const base = eventBase(owner);
|
|
139
|
+
|
|
140
|
+
/** @type {RuntimeLiveInputMessage} */
|
|
141
|
+
const wrapped = {
|
|
142
|
+
...owner.message,
|
|
143
|
+
accepted(evidence) {
|
|
144
|
+
if (!isCurrent(owner, attempt, "leased")) return "ignored";
|
|
145
|
+
owner.phase = "native_accepted";
|
|
146
|
+
const disposition = callHost(host.accepted, host, evidence);
|
|
147
|
+
emit({
|
|
148
|
+
type: "live_input_native_accepted",
|
|
149
|
+
...base,
|
|
150
|
+
...safeEvidence(evidence),
|
|
151
|
+
settlementDisposition: disposition,
|
|
152
|
+
});
|
|
153
|
+
return publicDisposition(disposition);
|
|
154
|
+
},
|
|
155
|
+
acknowledge(evidence) {
|
|
156
|
+
if (!isCurrent(owner, attempt, "leased", "native_accepted")) {
|
|
157
|
+
emit({ type: "live_input_consumed", ...base, ...safeEvidence(evidence), late: true });
|
|
158
|
+
return "ignored";
|
|
159
|
+
}
|
|
160
|
+
owner.phase = "consumed";
|
|
161
|
+
emit({ type: "live_input_consumed", ...base, ...safeEvidence(evidence) });
|
|
162
|
+
const disposition = callHost(host.acknowledge, host, evidence);
|
|
163
|
+
if (disposition === "recorded") {
|
|
164
|
+
emit({ type: "live_input_applied", ...base, ...safeEvidence(evidence) });
|
|
165
|
+
} else {
|
|
166
|
+
emit({
|
|
167
|
+
type: "live_input_settlement_unconfirmed",
|
|
168
|
+
...base,
|
|
169
|
+
phase: "consumed",
|
|
170
|
+
settlementDisposition: disposition,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return publicDisposition(disposition);
|
|
174
|
+
},
|
|
175
|
+
uncertain(details) {
|
|
176
|
+
if (!isCurrent(owner, attempt, "leased", "native_accepted")) return "ignored";
|
|
177
|
+
owner.phase = "uncertain";
|
|
178
|
+
const normalized = {
|
|
179
|
+
reason: /** @type {const} */ ("delivery_uncertain"),
|
|
180
|
+
...safeEvidence(details),
|
|
181
|
+
};
|
|
182
|
+
const disposition = callHost(host.uncertain, host, normalized);
|
|
183
|
+
emit({
|
|
184
|
+
type: "live_input_uncertain",
|
|
185
|
+
...base,
|
|
186
|
+
...normalized,
|
|
187
|
+
settlementDisposition: disposition,
|
|
188
|
+
});
|
|
189
|
+
return publicDisposition(disposition);
|
|
190
|
+
},
|
|
191
|
+
reject(reason) {
|
|
192
|
+
if (!isCurrent(owner, attempt, "leased", "native_accepted")) return "ignored";
|
|
193
|
+
if (owner.phase === "native_accepted" && !isNativeQueueRemoved(reason)) {
|
|
194
|
+
return wrapped.uncertain?.({ reason: "delivery_uncertain" });
|
|
195
|
+
}
|
|
196
|
+
const disposition = callHost(host.reject, host, reason);
|
|
197
|
+
// "ignored" is affirmative host evidence that this attempt no longer
|
|
198
|
+
// owns settlement (normally because the mailbox already sealed it).
|
|
199
|
+
// Legacy void and thrown callbacks remain replay-compatible because
|
|
200
|
+
// native removal still proves the provider did not consume the input.
|
|
201
|
+
owner.phase = disposition === "ignored" ? "uncertain" : "available";
|
|
202
|
+
return publicDisposition(disposition);
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
return wrapped;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** @param {string} kind @param {LiveInputEvent} event */
|
|
209
|
+
function emitBounded(kind, event) {
|
|
210
|
+
const count = diagnosticCounts.get(kind) ?? 0;
|
|
211
|
+
if (count >= MAX_DIAGNOSTICS_PER_KIND) return;
|
|
212
|
+
diagnosticCounts.set(kind, count + 1);
|
|
213
|
+
emit(event);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** @param {LiveInputEvent} event */
|
|
217
|
+
function emit(event) {
|
|
218
|
+
try { onEvent(event); } catch { /* telemetry never changes delivery */ }
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** @param {unknown} callback @param {RuntimeLiveInputMessage} receiver @param {unknown} argument */
|
|
223
|
+
function callHost(callback, receiver, argument) {
|
|
224
|
+
if (typeof callback !== "function") return "unconfirmed";
|
|
225
|
+
try {
|
|
226
|
+
const result = callback.call(receiver, argument);
|
|
227
|
+
return result === "recorded" || result === "ignored" ? result : "unconfirmed";
|
|
228
|
+
} catch {
|
|
229
|
+
return "threw";
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** @param {string} disposition */
|
|
234
|
+
function publicDisposition(disposition) {
|
|
235
|
+
return disposition === "recorded" || disposition === "ignored" ? disposition : undefined;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** @param {LiveInputOwner} owner */
|
|
239
|
+
function eventBase(owner) {
|
|
240
|
+
return {
|
|
241
|
+
inputId: owner.inputId,
|
|
242
|
+
...(owner.receivedAt === undefined ? {} : { receivedAt: owner.receivedAt }),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** @param {LiveInputOwner} owner @param {number} attempt @param {...string} phases */
|
|
247
|
+
function isCurrent(owner, attempt, ...phases) {
|
|
248
|
+
return owner.attempt === attempt && phases.includes(owner.phase);
|
|
80
249
|
}
|
|
81
250
|
|
|
82
251
|
/** @param {unknown} value */
|
|
83
|
-
function
|
|
84
|
-
return typeof value === "
|
|
85
|
-
&& value !== null
|
|
86
|
-
&& value[LIVE_INPUT_APPLIED_INSTRUMENTED] === true;
|
|
252
|
+
function normalizedStableId(value) {
|
|
253
|
+
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
87
254
|
}
|
|
88
255
|
|
|
89
|
-
/** @param {unknown} value
|
|
90
|
-
function
|
|
91
|
-
return typeof value === "
|
|
256
|
+
/** @param {unknown} value */
|
|
257
|
+
function validLogicalOwner(value) {
|
|
258
|
+
return value !== null && (typeof value === "object" || typeof value === "function")
|
|
92
259
|
? value
|
|
93
|
-
:
|
|
260
|
+
: undefined;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** @param {unknown} value */
|
|
264
|
+
function validString(value) {
|
|
265
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** @param {unknown} value */
|
|
269
|
+
function safeEvidence(value) {
|
|
270
|
+
if (typeof value !== "object" || value === null) return {};
|
|
271
|
+
const evidence = /** @type {{providerEntryId?: unknown, providerRunId?: unknown}} */ (value);
|
|
272
|
+
return {
|
|
273
|
+
...(validString(evidence.providerEntryId) === undefined ? {} : { providerEntryId: evidence.providerEntryId }),
|
|
274
|
+
...(validString(evidence.providerRunId) === undefined ? {} : { providerRunId: evidence.providerRunId }),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** @param {unknown} reason */
|
|
279
|
+
function isNativeQueueRemoved(reason) {
|
|
280
|
+
return typeof reason === "object"
|
|
281
|
+
&& reason !== null
|
|
282
|
+
&& /** @type {{code?: unknown}} */ (reason).code === "native_queue_removed";
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** @param {unknown} value */
|
|
286
|
+
function isInstrumented(value) {
|
|
287
|
+
return typeof value === "object"
|
|
288
|
+
&& value !== null
|
|
289
|
+
&& value[LIVE_INPUT_INSTRUMENTED] === true;
|
|
94
290
|
}
|
|
@@ -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
|
}
|