@the-open-engine/zeroshot 6.22.0 → 6.24.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/docker/zeroshot-cluster/Dockerfile +7 -0
- package/lib/agent-cli-provider/adapters/omp.d.ts +3 -1
- package/lib/agent-cli-provider/adapters/omp.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/omp.js +252 -269
- package/lib/agent-cli-provider/adapters/omp.js.map +1 -1
- package/lib/agent-cli-provider/contract-invoke.d.ts.map +1 -1
- package/lib/agent-cli-provider/contract-invoke.js +68 -14
- package/lib/agent-cli-provider/contract-invoke.js.map +1 -1
- package/lib/agent-cli-provider/contract-options.d.ts.map +1 -1
- package/lib/agent-cli-provider/contract-options.js +5 -3
- package/lib/agent-cli-provider/contract-options.js.map +1 -1
- package/lib/agent-cli-provider/index.d.ts +6 -0
- package/lib/agent-cli-provider/index.d.ts.map +1 -1
- package/lib/agent-cli-provider/index.js +12 -1
- package/lib/agent-cli-provider/index.js.map +1 -1
- package/lib/agent-cli-provider/omp-release.d.ts +6 -1
- package/lib/agent-cli-provider/omp-release.d.ts.map +1 -1
- package/lib/agent-cli-provider/omp-release.js +41 -2
- package/lib/agent-cli-provider/omp-release.js.map +1 -1
- package/lib/agent-cli-provider/omp-rpc-bounds.d.ts +7 -0
- package/lib/agent-cli-provider/omp-rpc-bounds.d.ts.map +1 -0
- package/lib/agent-cli-provider/omp-rpc-bounds.js +27 -0
- package/lib/agent-cli-provider/omp-rpc-bounds.js.map +1 -0
- package/lib/agent-cli-provider/omp-rpc-driver.d.ts +40 -0
- package/lib/agent-cli-provider/omp-rpc-driver.d.ts.map +1 -0
- package/lib/agent-cli-provider/omp-rpc-driver.js +494 -0
- package/lib/agent-cli-provider/omp-rpc-driver.js.map +1 -0
- package/lib/agent-cli-provider/omp-rpc-events.d.ts +28 -0
- package/lib/agent-cli-provider/omp-rpc-events.d.ts.map +1 -0
- package/lib/agent-cli-provider/omp-rpc-events.js +264 -0
- package/lib/agent-cli-provider/omp-rpc-events.js.map +1 -0
- package/lib/agent-cli-provider/omp-rpc-protocol.d.ts +1 -1
- package/lib/agent-cli-provider/omp-rpc-protocol.d.ts.map +1 -1
- package/lib/agent-cli-provider/omp-rpc-protocol.js +8 -3
- package/lib/agent-cli-provider/omp-rpc-protocol.js.map +1 -1
- package/lib/agent-cli-provider/omp-rpc-session.d.ts +17 -0
- package/lib/agent-cli-provider/omp-rpc-session.d.ts.map +1 -0
- package/lib/agent-cli-provider/omp-rpc-session.js +6 -0
- package/lib/agent-cli-provider/omp-rpc-session.js.map +1 -0
- package/lib/agent-cli-provider/provider-registry.d.ts +33 -17
- package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
- package/lib/agent-cli-provider/provider-registry.js +63 -11
- package/lib/agent-cli-provider/provider-registry.js.map +1 -1
- package/lib/agent-cli-provider/types.d.ts +13 -11
- package/lib/agent-cli-provider/types.d.ts.map +1 -1
- package/lib/agent-cli-provider/types.js.map +1 -1
- package/lib/docker-config.js +122 -5
- package/package.json +2 -1
- package/src/agent/agent-lifecycle.js +12 -1
- package/src/agent/provider-session.js +1 -0
- package/src/agent-cli-provider/adapters/omp.ts +276 -329
- package/src/agent-cli-provider/contract-invoke.ts +86 -15
- package/src/agent-cli-provider/contract-options.ts +5 -3
- package/src/agent-cli-provider/index.ts +13 -0
- package/src/agent-cli-provider/omp-release.ts +50 -1
- package/src/agent-cli-provider/omp-rpc-bounds.ts +28 -0
- package/src/agent-cli-provider/omp-rpc-driver.ts +611 -0
- package/src/agent-cli-provider/omp-rpc-events.ts +318 -0
- package/src/agent-cli-provider/omp-rpc-protocol.ts +8 -3
- package/src/agent-cli-provider/omp-rpc-session.ts +20 -0
- package/src/agent-cli-provider/provider-registry.ts +118 -23
- package/src/agent-cli-provider/types.ts +14 -11
- package/src/command-cleanup-ownership.js +307 -0
- package/src/isolation-manager.js +535 -89
- package/src/omp-config-overlay.js +96 -0
- package/src/orchestrator.js +31 -6
- package/src/preflight.js +50 -5
- package/src/watcher-prompt-channel.js +209 -0
- package/task-lib/command-spec-cleanup.js +1 -262
- package/task-lib/provider-helper-runtime.js +6 -0
- package/task-lib/rpc-watcher.js +186 -0
- package/task-lib/runner.js +40 -6
- package/task-lib/watcher-output-runtime.js +32 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { OmpRpcProtocolError, type OmpRpcInboundFrame } from './omp-rpc-protocol';
|
|
2
|
+
import { MAX_NORMALIZED_OUTPUT_BYTES } from './omp-rpc-bounds';
|
|
3
|
+
import {
|
|
4
|
+
getArray,
|
|
5
|
+
getBoolean,
|
|
6
|
+
getNumber,
|
|
7
|
+
getOptionalString,
|
|
8
|
+
getRecord,
|
|
9
|
+
getString,
|
|
10
|
+
isRecord,
|
|
11
|
+
stringifyContent,
|
|
12
|
+
tryParseJson,
|
|
13
|
+
} from './json';
|
|
14
|
+
import type { OutputEvent, ProviderParseResult, ProviderParserState } from './types';
|
|
15
|
+
|
|
16
|
+
export interface OmpRpcEventState {
|
|
17
|
+
lastAssistantText: string;
|
|
18
|
+
lastAssistantThinking: string;
|
|
19
|
+
lastToolId: string | null | undefined;
|
|
20
|
+
normalizedBytes: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function createOmpRpcEventState(): OmpRpcEventState {
|
|
24
|
+
return {
|
|
25
|
+
lastAssistantText: '',
|
|
26
|
+
lastAssistantThinking: '',
|
|
27
|
+
lastToolId: undefined,
|
|
28
|
+
normalizedBytes: 0,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function assistantSnapshot(message: Record<string, unknown>): { text: string; thinking: string } {
|
|
33
|
+
if (getString(message, 'role') !== 'assistant') return { text: '', thinking: '' };
|
|
34
|
+
|
|
35
|
+
let text = '';
|
|
36
|
+
let thinking = '';
|
|
37
|
+
for (const item of getArray(message, 'content')) {
|
|
38
|
+
if (!isRecord(item)) continue;
|
|
39
|
+
const type = getString(item, 'type');
|
|
40
|
+
if (type === 'text') {
|
|
41
|
+
text += getString(item, 'text') ?? '';
|
|
42
|
+
} else if (type === 'thinking') {
|
|
43
|
+
thinking += getString(item, 'thinking') ?? '';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { text, thinking };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function snapshotDelta(previous: string, current: string): string | null {
|
|
51
|
+
if (!current || previous === current) return null;
|
|
52
|
+
if (current.startsWith(previous)) return current.slice(previous.length) || null;
|
|
53
|
+
return current;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function chargeBytes(state: OmpRpcEventState, value: unknown): void {
|
|
57
|
+
const byteLength = Buffer.byteLength(stringifyContent(value), 'utf8');
|
|
58
|
+
state.normalizedBytes += byteLength;
|
|
59
|
+
if (state.normalizedBytes > MAX_NORMALIZED_OUTPUT_BYTES) {
|
|
60
|
+
throw new OmpRpcProtocolError(
|
|
61
|
+
'output-bound-exceeded',
|
|
62
|
+
`Normalized OMP RPC output exceeded the ${MAX_NORMALIZED_OUTPUT_BYTES}-byte cap for this task.`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function emitText(state: OmpRpcEventState, text: string): OutputEvent {
|
|
68
|
+
chargeBytes(state, text);
|
|
69
|
+
return { type: 'text', text };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function emitThinking(state: OmpRpcEventState, text: string): OutputEvent {
|
|
73
|
+
chargeBytes(state, text);
|
|
74
|
+
return { type: 'thinking', text };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function normalizeMessageStart(
|
|
78
|
+
frame: OmpRpcInboundFrame,
|
|
79
|
+
state: OmpRpcEventState
|
|
80
|
+
): readonly OutputEvent[] {
|
|
81
|
+
const message = getRecord(frame, 'message');
|
|
82
|
+
if (message !== null && getString(message, 'role') === 'assistant') {
|
|
83
|
+
state.lastAssistantText = '';
|
|
84
|
+
state.lastAssistantThinking = '';
|
|
85
|
+
}
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function normalizeAssistantMessageEvent(
|
|
90
|
+
event: Record<string, unknown>,
|
|
91
|
+
state: OmpRpcEventState
|
|
92
|
+
): readonly OutputEvent[] {
|
|
93
|
+
const type = getString(event, 'type');
|
|
94
|
+
if (type === 'text_delta') {
|
|
95
|
+
const delta = getString(event, 'delta');
|
|
96
|
+
if (!delta) return [];
|
|
97
|
+
state.lastAssistantText += delta;
|
|
98
|
+
return [emitText(state, delta)];
|
|
99
|
+
}
|
|
100
|
+
if (type === 'thinking_delta') {
|
|
101
|
+
const delta = getString(event, 'delta');
|
|
102
|
+
if (!delta) return [];
|
|
103
|
+
state.lastAssistantThinking += delta;
|
|
104
|
+
return [emitThinking(state, delta)];
|
|
105
|
+
}
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function normalizeMessageSnapshot(
|
|
110
|
+
message: Record<string, unknown>,
|
|
111
|
+
state: OmpRpcEventState
|
|
112
|
+
): readonly OutputEvent[] {
|
|
113
|
+
const snapshot = assistantSnapshot(message);
|
|
114
|
+
const events: OutputEvent[] = [];
|
|
115
|
+
|
|
116
|
+
const textDelta = snapshotDelta(state.lastAssistantText, snapshot.text);
|
|
117
|
+
if (textDelta) events.push(emitText(state, textDelta));
|
|
118
|
+
if (snapshot.text) state.lastAssistantText = snapshot.text;
|
|
119
|
+
|
|
120
|
+
const thinkingDelta = snapshotDelta(state.lastAssistantThinking, snapshot.thinking);
|
|
121
|
+
if (thinkingDelta) events.push(emitThinking(state, thinkingDelta));
|
|
122
|
+
if (snapshot.thinking) state.lastAssistantThinking = snapshot.thinking;
|
|
123
|
+
|
|
124
|
+
return events;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function normalizeMessageUpdate(
|
|
128
|
+
frame: OmpRpcInboundFrame,
|
|
129
|
+
state: OmpRpcEventState
|
|
130
|
+
): readonly OutputEvent[] {
|
|
131
|
+
const assistantMessageEvent = getRecord(frame, 'assistantMessageEvent');
|
|
132
|
+
if (assistantMessageEvent !== null) {
|
|
133
|
+
const events = normalizeAssistantMessageEvent(assistantMessageEvent, state);
|
|
134
|
+
if (events.length > 0) return events;
|
|
135
|
+
}
|
|
136
|
+
const message = getRecord(frame, 'message');
|
|
137
|
+
return message === null ? [] : normalizeMessageSnapshot(message, state);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function normalizeMessageEnd(
|
|
141
|
+
frame: OmpRpcInboundFrame,
|
|
142
|
+
state: OmpRpcEventState
|
|
143
|
+
): readonly OutputEvent[] {
|
|
144
|
+
const message = getRecord(frame, 'message');
|
|
145
|
+
return message === null ? [] : normalizeMessageSnapshot(message, state);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function normalizeToolExecutionStart(
|
|
149
|
+
frame: OmpRpcInboundFrame,
|
|
150
|
+
state: OmpRpcEventState
|
|
151
|
+
): readonly OutputEvent[] {
|
|
152
|
+
const toolId = getOptionalString(frame, 'toolCallId');
|
|
153
|
+
state.lastToolId = toolId;
|
|
154
|
+
const input = frame.args ?? {};
|
|
155
|
+
chargeBytes(state, input);
|
|
156
|
+
return [
|
|
157
|
+
{
|
|
158
|
+
type: 'tool_call',
|
|
159
|
+
toolName: getOptionalString(frame, 'toolName'),
|
|
160
|
+
toolId,
|
|
161
|
+
input,
|
|
162
|
+
},
|
|
163
|
+
];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function normalizeToolExecutionUpdate(
|
|
167
|
+
frame: OmpRpcInboundFrame,
|
|
168
|
+
state: OmpRpcEventState
|
|
169
|
+
): readonly OutputEvent[] {
|
|
170
|
+
const toolId = getOptionalString(frame, 'toolCallId') ?? state.lastToolId;
|
|
171
|
+
if (toolId !== undefined) state.lastToolId = toolId;
|
|
172
|
+
if (!Object.prototype.hasOwnProperty.call(frame, 'partialResult')) return [];
|
|
173
|
+
chargeBytes(state, frame.partialResult);
|
|
174
|
+
return [
|
|
175
|
+
{
|
|
176
|
+
type: 'tool_result',
|
|
177
|
+
toolId,
|
|
178
|
+
content: frame.partialResult,
|
|
179
|
+
isError: false,
|
|
180
|
+
},
|
|
181
|
+
];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function normalizeToolExecutionEnd(
|
|
185
|
+
frame: OmpRpcInboundFrame,
|
|
186
|
+
state: OmpRpcEventState
|
|
187
|
+
): readonly OutputEvent[] {
|
|
188
|
+
const toolId = getOptionalString(frame, 'toolCallId') ?? state.lastToolId;
|
|
189
|
+
const content = frame.result ?? '';
|
|
190
|
+
chargeBytes(state, content);
|
|
191
|
+
return [
|
|
192
|
+
{
|
|
193
|
+
type: 'tool_result',
|
|
194
|
+
toolId,
|
|
195
|
+
content,
|
|
196
|
+
isError: getBoolean(frame, 'isError') ?? false,
|
|
197
|
+
},
|
|
198
|
+
];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function normalizeTurnEnd(
|
|
202
|
+
frame: OmpRpcInboundFrame,
|
|
203
|
+
state: OmpRpcEventState
|
|
204
|
+
): readonly OutputEvent[] {
|
|
205
|
+
const message = getRecord(frame, 'message');
|
|
206
|
+
const usage = message ? (getRecord(message, 'usage') ?? {}) : {};
|
|
207
|
+
const stopReason = message ? getString(message, 'stopReason') : null;
|
|
208
|
+
const errorMessage = message ? getString(message, 'errorMessage') : null;
|
|
209
|
+
const snapshot = message ? assistantSnapshot(message) : { text: '', thinking: '' };
|
|
210
|
+
const success = stopReason !== 'error' && stopReason !== 'aborted' && !errorMessage;
|
|
211
|
+
const result = success ? snapshot.text || state.lastAssistantText || null : null;
|
|
212
|
+
chargeBytes(state, { result, error: errorMessage ?? stopReason ?? null });
|
|
213
|
+
return [
|
|
214
|
+
{
|
|
215
|
+
type: 'result',
|
|
216
|
+
success,
|
|
217
|
+
result,
|
|
218
|
+
error: success ? null : (errorMessage ?? stopReason ?? 'OMP turn failed'),
|
|
219
|
+
inputTokens: getNumber(usage, 'input') ?? 0,
|
|
220
|
+
outputTokens: getNumber(usage, 'output') ?? 0,
|
|
221
|
+
cacheReadInputTokens: getNumber(usage, 'cacheRead') ?? 0,
|
|
222
|
+
cacheCreationInputTokens: getNumber(usage, 'cacheWrite') ?? 0,
|
|
223
|
+
cost: getRecord(usage, 'cost') ?? null,
|
|
224
|
+
modelUsage: usage,
|
|
225
|
+
},
|
|
226
|
+
];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Normalize one decoded OMP RPC v2 event frame into zero or more OutputEvents. Only the frame
|
|
231
|
+
* types below produce output; every other known-pre-negotiation frame (agent_start/agent_end,
|
|
232
|
+
* turn_start, ready, response, session_info_update, subagent_*, ...) is protocol/control plumbing
|
|
233
|
+
* the driver consumes directly and must never leak into log/attach/ledger output.
|
|
234
|
+
*/
|
|
235
|
+
export function normalizeOmpRpcFrame(
|
|
236
|
+
frame: OmpRpcInboundFrame,
|
|
237
|
+
state: OmpRpcEventState
|
|
238
|
+
): readonly OutputEvent[] {
|
|
239
|
+
switch (frame.type) {
|
|
240
|
+
case 'message_start':
|
|
241
|
+
return normalizeMessageStart(frame, state);
|
|
242
|
+
case 'message_update':
|
|
243
|
+
return normalizeMessageUpdate(frame, state);
|
|
244
|
+
case 'message_end':
|
|
245
|
+
return normalizeMessageEnd(frame, state);
|
|
246
|
+
case 'tool_execution_start':
|
|
247
|
+
return normalizeToolExecutionStart(frame, state);
|
|
248
|
+
case 'tool_execution_update':
|
|
249
|
+
return normalizeToolExecutionUpdate(frame, state);
|
|
250
|
+
case 'tool_execution_end':
|
|
251
|
+
return normalizeToolExecutionEnd(frame, state);
|
|
252
|
+
case 'turn_end':
|
|
253
|
+
return normalizeTurnEnd(frame, state);
|
|
254
|
+
default:
|
|
255
|
+
return [];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Parse one line of the rpc-stdio lane's already-normalized OutputEvent JSON stream (produced
|
|
261
|
+
* from `OmpRpcTaskResult.events` by `contract-invoke.ts` / `rpc-watcher.js`). This is the
|
|
262
|
+
* `ProviderAdapter.parseEvent` counterpart to `normalizeOmpRpcFrame` above: the driver normalizes
|
|
263
|
+
* raw RPC frames to OutputEvent once, and this function is a validating passthrough over that
|
|
264
|
+
* already-normalized shape so generic log/attach tooling built on `adapter.parseEvent` keeps
|
|
265
|
+
* working for the omp provider without re-deriving events from raw frames. Each case rebuilds a
|
|
266
|
+
* literal matching its OutputEvent variant instead of casting the parsed record, so an
|
|
267
|
+
* unexpectedly-shaped line fails closed (returns null) rather than passing through untyped.
|
|
268
|
+
*/
|
|
269
|
+
export function parseNormalizedOmpRpcEventLine(
|
|
270
|
+
line: string,
|
|
271
|
+
_state: ProviderParserState
|
|
272
|
+
): ProviderParseResult {
|
|
273
|
+
const parsed = tryParseJson(line);
|
|
274
|
+
if (!isRecord(parsed)) return null;
|
|
275
|
+
switch (parsed.type) {
|
|
276
|
+
case 'text':
|
|
277
|
+
case 'thinking': {
|
|
278
|
+
const text = getString(parsed, 'text');
|
|
279
|
+
return text === null ? null : { type: parsed.type, text };
|
|
280
|
+
}
|
|
281
|
+
case 'tool_call':
|
|
282
|
+
return {
|
|
283
|
+
type: 'tool_call',
|
|
284
|
+
toolName: getOptionalString(parsed, 'toolName'),
|
|
285
|
+
toolId: getOptionalString(parsed, 'toolId'),
|
|
286
|
+
input: parsed.input,
|
|
287
|
+
};
|
|
288
|
+
case 'tool_result':
|
|
289
|
+
return {
|
|
290
|
+
type: 'tool_result',
|
|
291
|
+
toolId: getOptionalString(parsed, 'toolId'),
|
|
292
|
+
content: parsed.content,
|
|
293
|
+
isError: parsed.isError,
|
|
294
|
+
};
|
|
295
|
+
case 'result': {
|
|
296
|
+
if (typeof parsed.success !== 'boolean') return null;
|
|
297
|
+
const inputTokens = getNumber(parsed, 'inputTokens');
|
|
298
|
+
const outputTokens = getNumber(parsed, 'outputTokens');
|
|
299
|
+
const cacheReadInputTokens = getNumber(parsed, 'cacheReadInputTokens');
|
|
300
|
+
const cacheCreationInputTokens = getNumber(parsed, 'cacheCreationInputTokens');
|
|
301
|
+
return {
|
|
302
|
+
type: 'result',
|
|
303
|
+
success: parsed.success,
|
|
304
|
+
result: parsed.result,
|
|
305
|
+
error: parsed.error,
|
|
306
|
+
cost: parsed.cost,
|
|
307
|
+
duration: parsed.duration,
|
|
308
|
+
...(inputTokens === null ? {} : { inputTokens }),
|
|
309
|
+
...(outputTokens === null ? {} : { outputTokens }),
|
|
310
|
+
...(cacheReadInputTokens === null ? {} : { cacheReadInputTokens }),
|
|
311
|
+
...(cacheCreationInputTokens === null ? {} : { cacheCreationInputTokens }),
|
|
312
|
+
modelUsage: parsed.modelUsage,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
default:
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
@@ -218,7 +218,11 @@ export class OmpRpcFrameDecoder {
|
|
|
218
218
|
this.inflightReassemblyBytes = 0;
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
|
|
221
|
+
// `negotiatedV2` defaults to true so callers that don't care about pre-negotiation gating
|
|
222
|
+
// (e.g. the fixture-replay tests below) see unchanged behavior; the RPC driver passes its
|
|
223
|
+
// live negotiation state so a bare `rpc_chunk` physical frame is rejected before negotiation
|
|
224
|
+
// succeeds, even though the decoder would otherwise buffer it silently pending reassembly.
|
|
225
|
+
push(chunk: Uint8Array, negotiatedV2 = true): readonly OmpRpcInboundFrame[] {
|
|
222
226
|
if (this.finished) {
|
|
223
227
|
throw new OmpRpcProtocolError('decoder-finished', 'push() called after finish().');
|
|
224
228
|
}
|
|
@@ -247,7 +251,7 @@ export class OmpRpcFrameDecoder {
|
|
|
247
251
|
);
|
|
248
252
|
}
|
|
249
253
|
|
|
250
|
-
const frame = this.consumeLine(lineBytes);
|
|
254
|
+
const frame = this.consumeLine(lineBytes, negotiatedV2);
|
|
251
255
|
if (frame !== null) frames.push(frame);
|
|
252
256
|
}
|
|
253
257
|
|
|
@@ -270,7 +274,7 @@ export class OmpRpcFrameDecoder {
|
|
|
270
274
|
}
|
|
271
275
|
}
|
|
272
276
|
|
|
273
|
-
private consumeLine(lineBytes: Buffer): OmpRpcInboundFrame | null {
|
|
277
|
+
private consumeLine(lineBytes: Buffer, negotiatedV2: boolean): OmpRpcInboundFrame | null {
|
|
274
278
|
let value: unknown;
|
|
275
279
|
try {
|
|
276
280
|
value = JSON.parse(lineBytes.toString('utf8'));
|
|
@@ -285,6 +289,7 @@ export class OmpRpcFrameDecoder {
|
|
|
285
289
|
}
|
|
286
290
|
|
|
287
291
|
const type = getString(value, 'type');
|
|
292
|
+
assertNoPreNegotiationRpcChunk(type ?? '', negotiatedV2);
|
|
288
293
|
if (type !== 'rpc_chunk') {
|
|
289
294
|
if (this.pendingReassemblies.size > 0) {
|
|
290
295
|
throw new OmpRpcProtocolError(
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Session-launch types for the OMP RPC v2 driver. This slice launches sessionless
|
|
2
|
+
// (`--no-session`) only; `fresh`/`resume` are typed for Subissue 4's session-flag activation but
|
|
3
|
+
// are unreachable from any caller in this slice.
|
|
4
|
+
|
|
5
|
+
export interface VerifiedOmpPartition {
|
|
6
|
+
readonly path: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface VerifiedOmpSessionFile {
|
|
10
|
+
readonly path: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type OmpSessionLaunch =
|
|
14
|
+
| { readonly kind: 'none' }
|
|
15
|
+
| { readonly kind: 'fresh'; readonly partition: VerifiedOmpPartition }
|
|
16
|
+
| {
|
|
17
|
+
readonly kind: 'resume';
|
|
18
|
+
readonly partition: VerifiedOmpPartition;
|
|
19
|
+
readonly file: VerifiedOmpSessionFile;
|
|
20
|
+
};
|
|
@@ -2,13 +2,22 @@ import { createAcpAdapter } from './adapters/acp';
|
|
|
2
2
|
import { claudeAdapter } from './adapters/claude';
|
|
3
3
|
import { codexAdapter } from './adapters/codex';
|
|
4
4
|
import { copilotAdapter } from './adapters/copilot';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
gatewayAdapter,
|
|
7
|
+
gatewaySettingsDefaults,
|
|
8
|
+
validateGatewaySettings,
|
|
9
|
+
} from './adapters/gateway';
|
|
6
10
|
import { geminiAdapter } from './adapters/gemini';
|
|
7
11
|
import { opencodeAdapter } from './adapters/opencode';
|
|
8
12
|
import { ompAdapter } from './adapters/omp';
|
|
9
13
|
import { piAdapter } from './adapters/pi';
|
|
10
14
|
import { resolveClaudeCommand } from './claude-command';
|
|
11
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
OMP_AUTH_INSTRUCTIONS,
|
|
17
|
+
OMP_DOCKER_INSTALL_COMMAND,
|
|
18
|
+
OMP_DOCKER_PLATFORM,
|
|
19
|
+
OMP_INSTALL_COMMAND,
|
|
20
|
+
} from './omp-release';
|
|
12
21
|
import type { ModelLevel, ProviderAdapter, StructuredOutputRecoveryAdapter } from './types';
|
|
13
22
|
|
|
14
23
|
export type ProviderCapabilityState = boolean | 'experimental';
|
|
@@ -44,7 +53,15 @@ export interface AcpStdioProviderInvokeSpec {
|
|
|
44
53
|
readonly transport: 'stdio';
|
|
45
54
|
}
|
|
46
55
|
|
|
47
|
-
export
|
|
56
|
+
export interface RpcStdioProviderInvokeSpec {
|
|
57
|
+
readonly lane: 'rpc-stdio';
|
|
58
|
+
readonly protocol: 'omp-v2';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type ProviderInvokeSpec =
|
|
62
|
+
| SpawnProviderInvokeSpec
|
|
63
|
+
| AcpStdioProviderInvokeSpec
|
|
64
|
+
| RpcStdioProviderInvokeSpec;
|
|
48
65
|
|
|
49
66
|
export type ProviderCommandSpec = FixedProviderCommandSpec | ConfiguredClaudeCommandSpec;
|
|
50
67
|
|
|
@@ -59,8 +76,24 @@ export interface ProviderDockerMountPreset {
|
|
|
59
76
|
readonly readonly: boolean;
|
|
60
77
|
}
|
|
61
78
|
|
|
79
|
+
export interface ProviderDockerEnvAuth {
|
|
80
|
+
// At least one of these env vars must carry a usable (non-empty, non-whitespace) value for the
|
|
81
|
+
// effective container plan to be considered automatically authenticated. This is the AUTOMATIC
|
|
82
|
+
// allowlist only; a registry-known credential outside it is accepted when — and only when — the
|
|
83
|
+
// user explicitly opted it in (dockerEnvPassthrough / --mount).
|
|
84
|
+
// Providers with no `mount` (env-only) fail closed when nothing is satisfied.
|
|
85
|
+
readonly requireOneOf: readonly string[];
|
|
86
|
+
// Each inner group must be all-set-or-all-unset (e.g. a broker URL + token pair); a partial
|
|
87
|
+
// group is treated as malformed auth, not "missing".
|
|
88
|
+
readonly requireTogether?: readonly (readonly string[])[];
|
|
89
|
+
// Env vars whose value must parse as an absolute http(s) URL to count as set. A non-URL value
|
|
90
|
+
// is malformed auth, not "missing".
|
|
91
|
+
readonly requireUrl?: readonly string[];
|
|
92
|
+
}
|
|
93
|
+
|
|
62
94
|
export interface ProviderDockerMetadata {
|
|
63
|
-
|
|
95
|
+
// Omitted for env-only providers with zero automatic credential mounts (e.g. omp).
|
|
96
|
+
readonly mount?: ProviderDockerMountPreset;
|
|
64
97
|
readonly envPassthrough: readonly string[];
|
|
65
98
|
// False when the mounted dir doesn't hold the secret (auth is via an envPassthrough token).
|
|
66
99
|
readonly credentialInMount?: boolean;
|
|
@@ -68,6 +101,14 @@ export interface ProviderDockerMetadata {
|
|
|
68
101
|
// a docker-cached build layer for the per-provider image variant. Omit for providers already
|
|
69
102
|
// baked into the base image (e.g. Claude) or not installable via a single command.
|
|
70
103
|
readonly install?: string;
|
|
104
|
+
// Docker platform (e.g. 'linux/amd64') passed to both image build and container run. Omitted
|
|
105
|
+
// providers keep today's unset (host-native) behavior.
|
|
106
|
+
readonly platform?: string;
|
|
107
|
+
// $HOME-placeholder directories created owner-only inside the container for this provider's
|
|
108
|
+
// config/session state (never mounted/copied from the host).
|
|
109
|
+
readonly configRoots?: readonly string[];
|
|
110
|
+
// Fail-closed env/broker auth requirement, checked against the effective container env plan.
|
|
111
|
+
readonly envAuth?: ProviderDockerEnvAuth;
|
|
71
112
|
}
|
|
72
113
|
|
|
73
114
|
interface ProviderRegistryEntryBase {
|
|
@@ -146,6 +187,10 @@ const ACP_STDIO_INVOKE = Object.freeze({
|
|
|
146
187
|
lane: 'acp-stdio',
|
|
147
188
|
transport: 'stdio',
|
|
148
189
|
}) as AcpStdioProviderInvokeSpec;
|
|
190
|
+
const RPC_STDIO_INVOKE = Object.freeze({
|
|
191
|
+
lane: 'rpc-stdio',
|
|
192
|
+
protocol: 'omp-v2',
|
|
193
|
+
}) as RpcStdioProviderInvokeSpec;
|
|
149
194
|
|
|
150
195
|
const kiroAdapter = createAcpAdapter({
|
|
151
196
|
provider: 'kiro',
|
|
@@ -393,8 +438,7 @@ export const providerRegistry = [
|
|
|
393
438
|
binary: 'pi',
|
|
394
439
|
command: { kind: 'fixed', command: 'pi', args: [] },
|
|
395
440
|
invoke: SPAWN_INVOKE,
|
|
396
|
-
installInstructions:
|
|
397
|
-
'npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.80.3',
|
|
441
|
+
installInstructions: 'npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.80.3',
|
|
398
442
|
authInstructions: 'pi\n/login',
|
|
399
443
|
credentialPaths: ['~/.pi'],
|
|
400
444
|
credentialEnvKeys: piAdapter.credentialEnvKeys,
|
|
@@ -428,34 +472,79 @@ export const providerRegistry = [
|
|
|
428
472
|
{
|
|
429
473
|
id: 'omp',
|
|
430
474
|
default: false,
|
|
431
|
-
aliases: [],
|
|
432
|
-
displayName: 'OMP',
|
|
475
|
+
aliases: ['oh-my-pi'],
|
|
476
|
+
displayName: 'OMP (Oh My Pi)',
|
|
433
477
|
binary: 'omp',
|
|
434
478
|
command: { kind: 'fixed', command: 'omp', args: [] },
|
|
435
|
-
invoke:
|
|
479
|
+
invoke: RPC_STDIO_INVOKE,
|
|
436
480
|
installInstructions: OMP_INSTALL_COMMAND,
|
|
437
|
-
authInstructions:
|
|
481
|
+
authInstructions: OMP_AUTH_INSTRUCTIONS,
|
|
438
482
|
credentialPaths: ['~/.omp'],
|
|
439
483
|
credentialEnvKeys: ompAdapter.credentialEnvKeys,
|
|
440
484
|
settingsFields: [],
|
|
441
485
|
availabilityProbe: 'help-or-version',
|
|
486
|
+
// Written out explicitly rather than spread from STANDARD_CAPABILITIES, which defaults
|
|
487
|
+
// dockerIsolation to true; OMP's Docker path is env/broker-only and sessionless (see
|
|
488
|
+
// AGENTS.md OMP Docker section) rather than the standard credential-mount + resume shape.
|
|
442
489
|
capabilities: {
|
|
443
|
-
|
|
490
|
+
dockerIsolation: true,
|
|
491
|
+
worktreeIsolation: true,
|
|
444
492
|
mcpServers: false,
|
|
445
493
|
jsonSchema: false,
|
|
494
|
+
streamJson: true,
|
|
495
|
+
thinkingMode: true,
|
|
446
496
|
reasoningEffort: true,
|
|
497
|
+
sessionResume: false,
|
|
498
|
+
webSearch: false,
|
|
447
499
|
},
|
|
448
500
|
docs: {
|
|
449
501
|
label: 'OMP',
|
|
450
502
|
setupHeading: 'OMP Setup',
|
|
451
503
|
},
|
|
452
504
|
docker: {
|
|
453
|
-
mount
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
505
|
+
// No `mount`: OMP's Docker credential surface is env/broker-only with zero automatic
|
|
506
|
+
// mounts. `~/.omp`, agent.db, WAL/SHM files, and host refresh tokens are never mounted or
|
|
507
|
+
// copied into the container.
|
|
508
|
+
//
|
|
509
|
+
// envPassthrough is deliberately narrower than `credentialEnvKeys` above (the full adapter
|
|
510
|
+
// credential inventory, used for host inspection/redaction). Exact 5-name automatic
|
|
511
|
+
// allowlist per the maintainer's authoritative clarification (verified verbatim via
|
|
512
|
+
// `gh api repos/the-open-engine/zeroshot/issues/comments/5160272623`): "the exact automatic
|
|
513
|
+
// OMP Docker environment allowlist is only ANTHROPIC_API_KEY, OPENAI_API_KEY,
|
|
514
|
+
// GEMINI_API_KEY, OMP_AUTH_BROKER_URL, and OMP_AUTH_BROKER_TOKEN ... ANTHROPIC_OAUTH_TOKEN,
|
|
515
|
+
// ANTHROPIC_FOUNDRY_API_KEY, GOOGLE_API_KEY, OPENROUTER_API_KEY, and every other
|
|
516
|
+
// credential/path require explicit dockerEnvPassthrough/mount opt-in; OAuth users should
|
|
517
|
+
// prefer the auth broker so host refresh/access tokens do not cross automatically." This
|
|
518
|
+
// supersedes PLAN_READY step 2's nine-name list. Any validator flagging this as missing the
|
|
519
|
+
// four excluded names is checking stale plan text against a clarification it never read.
|
|
520
|
+
platform: OMP_DOCKER_PLATFORM,
|
|
521
|
+
install: OMP_DOCKER_INSTALL_COMMAND,
|
|
522
|
+
configRoots: ['$HOME/.omp'],
|
|
523
|
+
credentialInMount: false,
|
|
524
|
+
envPassthrough: [
|
|
525
|
+
'ANTHROPIC_API_KEY',
|
|
526
|
+
'GEMINI_API_KEY',
|
|
527
|
+
'OMP_AUTH_BROKER_TOKEN',
|
|
528
|
+
'OMP_AUTH_BROKER_URL',
|
|
529
|
+
'OPENAI_API_KEY',
|
|
530
|
+
],
|
|
531
|
+
envAuth: {
|
|
532
|
+
// The four automatic *credential* names (the fifth allowlist entry, OMP_AUTH_BROKER_URL,
|
|
533
|
+
// is a locator, not a credential — it authenticates nothing on its own). Any other
|
|
534
|
+
// registry-known OMP credential (ANTHROPIC_OAUTH_TOKEN, OPENROUTER_API_KEY, …) is usable
|
|
535
|
+
// only via explicit dockerEnvPassthrough/--mount opt-in, never automatic forwarding.
|
|
536
|
+
requireOneOf: [
|
|
537
|
+
'ANTHROPIC_API_KEY',
|
|
538
|
+
'GEMINI_API_KEY',
|
|
539
|
+
'OMP_AUTH_BROKER_TOKEN',
|
|
540
|
+
'OPENAI_API_KEY',
|
|
541
|
+
],
|
|
542
|
+
requireTogether: [['OMP_AUTH_BROKER_URL', 'OMP_AUTH_BROKER_TOKEN']],
|
|
543
|
+
// Per OMP v17.2.1 docs/environment-variables.md, OMP_AUTH_BROKER_URL is the broker's base
|
|
544
|
+
// URL (e.g. https://broker.tailnet:8765); anything that is not an http(s) URL is
|
|
545
|
+
// malformed broker config, and OMP hard-errors on a broker URL with no resolvable token.
|
|
546
|
+
requireUrl: ['OMP_AUTH_BROKER_URL'],
|
|
457
547
|
},
|
|
458
|
-
envPassthrough: [],
|
|
459
548
|
},
|
|
460
549
|
defaultLevels: {
|
|
461
550
|
min: ompAdapter.defaultMinLevel,
|
|
@@ -559,12 +648,16 @@ function validateWebSearchSettings(
|
|
|
559
648
|
type RegistryProviderId = (typeof providerRegistry)[number]['id'];
|
|
560
649
|
type RegistryProviderAlias = (typeof providerRegistry)[number]['aliases'][number];
|
|
561
650
|
|
|
562
|
-
export const providerIds = providerRegistry.map(
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
)[];
|
|
651
|
+
export const providerIds = providerRegistry.map(
|
|
652
|
+
(entry) => entry.id
|
|
653
|
+
) as readonly RegistryProviderId[];
|
|
654
|
+
export const providerAliases = providerRegistry.flatMap(
|
|
655
|
+
(entry) => entry.aliases
|
|
656
|
+
) as readonly RegistryProviderAlias[];
|
|
657
|
+
export const knownProviderNames = providerRegistry.flatMap((entry) => [
|
|
658
|
+
entry.id,
|
|
659
|
+
...entry.aliases,
|
|
660
|
+
]) as readonly (RegistryProviderId | RegistryProviderAlias)[];
|
|
568
661
|
|
|
569
662
|
export const providerAliasMap: Readonly<Record<string, RegistryProviderId>> = Object.freeze(
|
|
570
663
|
providerRegistry.reduce<Record<string, RegistryProviderId>>((result, entry) => {
|
|
@@ -602,7 +695,9 @@ export function listProviderRegistryEntries(): readonly ProviderRegistryEntry[]
|
|
|
602
695
|
return providerRegistry;
|
|
603
696
|
}
|
|
604
697
|
|
|
605
|
-
export function findProviderRegistryEntry(
|
|
698
|
+
export function findProviderRegistryEntry(
|
|
699
|
+
name: string | null | undefined
|
|
700
|
+
): ProviderRegistryEntry | undefined {
|
|
606
701
|
if (!name) return undefined;
|
|
607
702
|
const normalized = normalizeProviderName(name);
|
|
608
703
|
return providerRegistry.find((entry) => entry.id === normalized);
|
|
@@ -168,16 +168,16 @@ export interface PiCliFeatures extends BaseCliFeatures {
|
|
|
168
168
|
|
|
169
169
|
export interface OmpCliFeatures extends BaseCliFeatures {
|
|
170
170
|
readonly provider: 'omp';
|
|
171
|
-
readonly
|
|
172
|
-
readonly
|
|
173
|
-
readonly supportsCwd: boolean;
|
|
174
|
-
readonly supportsAutoApprove: boolean;
|
|
171
|
+
readonly supportsRpcMode: boolean;
|
|
172
|
+
readonly supportsConfig: boolean;
|
|
175
173
|
readonly supportsModel: boolean;
|
|
176
174
|
readonly supportsThinking: boolean;
|
|
177
|
-
readonly
|
|
178
|
-
readonly supportsNoSkills: boolean;
|
|
179
|
-
readonly supportsNoRules: boolean;
|
|
175
|
+
readonly supportsApprovalMode: boolean;
|
|
180
176
|
readonly supportsNoTitle: boolean;
|
|
177
|
+
readonly supportsNoSession: boolean;
|
|
178
|
+
readonly supportsSessionDir: boolean;
|
|
179
|
+
readonly supportsResume: boolean;
|
|
180
|
+
readonly versionMatches: boolean;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
export interface CopilotCliFeatures extends BaseCliFeatures {
|
|
@@ -256,11 +256,13 @@ export interface CliFeatureOverrides {
|
|
|
256
256
|
readonly supportsRecoveryIsolation?: boolean;
|
|
257
257
|
readonly supportsNoContextFiles?: boolean;
|
|
258
258
|
readonly supportsNoApprove?: boolean;
|
|
259
|
-
readonly
|
|
260
|
-
readonly
|
|
259
|
+
readonly supportsRpcMode?: boolean;
|
|
260
|
+
readonly supportsConfig?: boolean;
|
|
261
261
|
readonly supportsThinking?: boolean;
|
|
262
|
-
readonly
|
|
262
|
+
readonly supportsApprovalMode?: boolean;
|
|
263
263
|
readonly supportsNoTitle?: boolean;
|
|
264
|
+
readonly supportsSessionDir?: boolean;
|
|
265
|
+
readonly versionMatches?: boolean;
|
|
264
266
|
readonly supportsJsonOutput?: boolean;
|
|
265
267
|
readonly supportsAllowAll?: boolean;
|
|
266
268
|
readonly supportsNoAskUser?: boolean;
|
|
@@ -412,7 +414,8 @@ export type ErrorClassificationKind =
|
|
|
412
414
|
| 'code-retryable'
|
|
413
415
|
| 'permanent-pattern'
|
|
414
416
|
| 'retryable-pattern'
|
|
415
|
-
| 'unknown-retryable'
|
|
417
|
+
| 'unknown-retryable'
|
|
418
|
+
| 'cancelled';
|
|
416
419
|
|
|
417
420
|
export interface ErrorClassification {
|
|
418
421
|
readonly retryable: boolean;
|