@the-open-engine/zeroshot 6.16.0 → 6.18.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.
Files changed (82) hide show
  1. package/README.md +2 -2
  2. package/cli/index.js +9 -13
  3. package/lib/agent-cli-provider/adapters/claude.d.ts +2 -2
  4. package/lib/agent-cli-provider/adapters/claude.d.ts.map +1 -1
  5. package/lib/agent-cli-provider/adapters/claude.js +30 -0
  6. package/lib/agent-cli-provider/adapters/claude.js.map +1 -1
  7. package/lib/agent-cli-provider/adapters/codex.d.ts +2 -2
  8. package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
  9. package/lib/agent-cli-provider/adapters/codex.js +37 -0
  10. package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
  11. package/lib/agent-cli-provider/adapters/gemini.d.ts +2 -2
  12. package/lib/agent-cli-provider/adapters/gemini.d.ts.map +1 -1
  13. package/lib/agent-cli-provider/adapters/gemini.js +115 -4
  14. package/lib/agent-cli-provider/adapters/gemini.js.map +1 -1
  15. package/lib/agent-cli-provider/adapters/omp.d.ts +3 -0
  16. package/lib/agent-cli-provider/adapters/omp.d.ts.map +1 -0
  17. package/lib/agent-cli-provider/adapters/omp.js +356 -0
  18. package/lib/agent-cli-provider/adapters/omp.js.map +1 -0
  19. package/lib/agent-cli-provider/adapters/opencode.d.ts +2 -2
  20. package/lib/agent-cli-provider/adapters/opencode.d.ts.map +1 -1
  21. package/lib/agent-cli-provider/adapters/opencode.js +108 -0
  22. package/lib/agent-cli-provider/adapters/opencode.js.map +1 -1
  23. package/lib/agent-cli-provider/contract-options.d.ts.map +1 -1
  24. package/lib/agent-cli-provider/contract-options.js +17 -0
  25. package/lib/agent-cli-provider/contract-options.js.map +1 -1
  26. package/lib/agent-cli-provider/index.d.ts +2 -2
  27. package/lib/agent-cli-provider/index.d.ts.map +1 -1
  28. package/lib/agent-cli-provider/index.js +2 -1
  29. package/lib/agent-cli-provider/index.js.map +1 -1
  30. package/lib/agent-cli-provider/provider-registry.d.ts +65 -7
  31. package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
  32. package/lib/agent-cli-provider/provider-registry.js +43 -0
  33. package/lib/agent-cli-provider/provider-registry.js.map +1 -1
  34. package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
  35. package/lib/agent-cli-provider/single-agent-runtime.js +35 -4
  36. package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
  37. package/lib/agent-cli-provider/types.d.ts +49 -4
  38. package/lib/agent-cli-provider/types.d.ts.map +1 -1
  39. package/lib/agent-cli-provider/types.js.map +1 -1
  40. package/lib/cluster/errors.cjs +4 -1
  41. package/lib/cluster/errors.d.ts +2 -0
  42. package/lib/cluster/errors.mjs +2 -0
  43. package/lib/cluster/index.cjs +8 -1
  44. package/lib/cluster/index.d.ts +4 -1
  45. package/lib/cluster/index.mjs +4 -1
  46. package/lib/cluster/json-source.cjs +61 -0
  47. package/lib/cluster/json-source.d.ts +4 -0
  48. package/lib/cluster/json-source.mjs +55 -0
  49. package/lib/cluster/payload-value.cjs +76 -0
  50. package/lib/cluster/payload-value.d.ts +7 -0
  51. package/lib/cluster/payload-value.mjs +72 -0
  52. package/lib/cluster/validators.cjs +21 -0
  53. package/lib/cluster/validators.d.ts +4 -1
  54. package/lib/cluster/validators.mjs +19 -1
  55. package/lib/provider-names.js +5 -0
  56. package/package.json +2 -2
  57. package/src/agent/agent-task-executor.js +194 -410
  58. package/src/agent/output-extraction.js +39 -14
  59. package/src/agent/output-reformatter.js +154 -111
  60. package/src/agent-cli-provider/adapters/claude.ts +39 -2
  61. package/src/agent-cli-provider/adapters/codex.ts +56 -2
  62. package/src/agent-cli-provider/adapters/gemini.ts +102 -6
  63. package/src/agent-cli-provider/adapters/omp.ts +448 -0
  64. package/src/agent-cli-provider/adapters/opencode.ts +85 -2
  65. package/src/agent-cli-provider/contract-options.ts +21 -0
  66. package/src/agent-cli-provider/index.ts +5 -0
  67. package/src/agent-cli-provider/provider-registry.ts +62 -3
  68. package/src/agent-cli-provider/single-agent-runtime.ts +50 -4
  69. package/src/agent-cli-provider/types.ts +56 -2
  70. package/src/agent-wrapper.js +2 -2
  71. package/src/cluster/errors.ts +1 -0
  72. package/src/cluster/index.ts +4 -0
  73. package/src/cluster/json-source.ts +71 -0
  74. package/src/cluster/payload-value.ts +84 -0
  75. package/src/cluster/validators.ts +31 -2
  76. package/src/orchestrator.js +33 -13
  77. package/task-lib/attachable-watcher.js +1 -0
  78. package/task-lib/command-spec-cleanup.js +57 -11
  79. package/task-lib/commands/run.js +1 -0
  80. package/task-lib/provider-session-capture.js +8 -0
  81. package/task-lib/runner.js +9 -6
  82. package/task-lib/watcher.js +1 -0
@@ -1,3 +1,9 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import * as fs from 'node:fs';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
5
+
6
+ import { UnsupportedProviderCapabilityError } from '../errors';
1
7
  import { appendJsonSchemaPrompt } from '../schema';
2
8
  import {
3
9
  getOrStringFromKeys,
@@ -16,7 +22,7 @@ import {
16
22
  type ModelCatalogEntry,
17
23
  type ModelLevel,
18
24
  type OutputEvent,
19
- type ProviderAdapter,
25
+ type StructuredOutputRecoveryAdapter,
20
26
  type ProviderParserState,
21
27
  type ResolvedModelSpec,
22
28
  type WarningMetadata,
@@ -52,6 +58,7 @@ function detectCliFeatures(helpText?: string | null): GeminiCliFeatures {
52
58
  supportsAutoApprove: unknown ? true : /--yolo\b/.test(help),
53
59
  supportsCwd: unknown ? true : /--cwd\b/.test(help),
54
60
  supportsModel: unknown ? true : /\s-m\b/.test(help) || /--model\b/.test(help),
61
+ supportsAdminPolicy: !unknown && /--admin-policy\b/.test(help),
55
62
  unknown,
56
63
  };
57
64
  }
@@ -109,6 +116,75 @@ function buildCommand(context: string, options: BuildProviderCommandOptions = {}
109
116
  });
110
117
  }
111
118
 
119
+ function standardAdminPolicyDirectory(): string {
120
+ if (process.platform === 'darwin') {
121
+ return '/Library/Application Support/GeminiCli/policies';
122
+ }
123
+ if (process.platform === 'win32') {
124
+ return path.join(process.env.ProgramData || 'C:\\ProgramData', 'gemini-cli', 'policies');
125
+ }
126
+ return '/etc/gemini-cli/policies';
127
+ }
128
+
129
+ function assertSupplementalAdminPolicyEffective(): void {
130
+ const directory = standardAdminPolicyDirectory();
131
+ let entries: fs.Dirent[];
132
+ try {
133
+ entries = fs.readdirSync(directory, { withFileTypes: true });
134
+ } catch (error) {
135
+ if (isRecord(error) && error.code === 'ENOENT') return;
136
+ throw new UnsupportedProviderCapabilityError(
137
+ 'gemini',
138
+ 'structuredOutputRecovery',
139
+ `Gemini structured-output recovery cannot inspect the standard admin-policy directory ${directory}; refuse a supplemental policy that may be ignored.`
140
+ );
141
+ }
142
+ if (!entries.some((entry) => entry.isFile() && entry.name.endsWith('.toml'))) return;
143
+ throw new UnsupportedProviderCapabilityError(
144
+ 'gemini',
145
+ 'structuredOutputRecovery',
146
+ `Gemini ignores --admin-policy when ${directory} contains TOML policies. Remove the conflict or use an administrator-managed deny-all policy before retrying.`
147
+ );
148
+ }
149
+
150
+ function writeRecoveryAdminPolicy(): string {
151
+ const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'zeroshot-gemini-policy-'));
152
+ const policyPath = path.join(directory, `${randomUUID()}.toml`);
153
+ fs.writeFileSync(policyPath, '[[rule]]\ntoolName = "*"\ndecision = "deny"\npriority = 999\n', {
154
+ flag: 'wx',
155
+ mode: 0o600,
156
+ });
157
+ return policyPath;
158
+ }
159
+
160
+ function buildStructuredOutputRecoveryCommand(
161
+ context: string,
162
+ options: BuildProviderCommandOptions = {}
163
+ ): CommandSpec {
164
+ if (optionFeatures(options).supportsAdminPolicy !== true) {
165
+ throw new UnsupportedProviderCapabilityError(
166
+ 'gemini',
167
+ 'structuredOutputRecovery',
168
+ 'Gemini structured-output recovery requires CLI evidence for --admin-policy. Upgrade @google/gemini-cli before retrying.'
169
+ );
170
+ }
171
+ assertSupplementalAdminPolicyEffective();
172
+ const recoveryOptions = { ...options };
173
+ delete recoveryOptions.resumeSessionId;
174
+ delete recoveryOptions.continueSession;
175
+ const spec = buildCommand(context, { ...recoveryOptions, autoApprove: false });
176
+ const policyPath = writeRecoveryAdminPolicy();
177
+ return {
178
+ ...spec,
179
+ args: ['--admin-policy', policyPath, ...spec.args],
180
+ cleanup: [...(spec.cleanup || []), policyPath],
181
+ cleanupMetadata: [
182
+ ...spec.cleanupMetadata,
183
+ { kind: 'temp-file', provider: 'gemini', path: policyPath, reason: 'admin-policy' },
184
+ ],
185
+ };
186
+ }
187
+
112
188
  function normalizeMessageContent(content: unknown): string {
113
189
  if (typeof content === 'string') return content;
114
190
  if (Array.isArray(content)) {
@@ -148,6 +224,11 @@ function parseToolUseEvent(
148
224
  };
149
225
  }
150
226
 
227
+ function geminiErrorMessage(event: Record<string, unknown>, fallback: string): string {
228
+ const error = isRecord(event.error) ? event.error : null;
229
+ return (error && getString(error, 'message')) || getString(event, 'message') || fallback;
230
+ }
231
+
151
232
  function parseToolResultEvent(
152
233
  event: Record<string, unknown>,
153
234
  state: ProviderParserState
@@ -157,20 +238,32 @@ function parseToolResultEvent(
157
238
  ['tool_call_id', 'tool_id', 'id'],
158
239
  state.lastToolId
159
240
  );
241
+ const isError = getString(event, 'status') === 'error';
160
242
  return {
161
243
  type: 'tool_result',
162
244
  toolId,
163
- content: event.output ?? event.content ?? '',
164
- isError: event.success === false,
245
+ content: event.output ?? (isError ? geminiErrorMessage(event, 'Tool failed') : ''),
246
+ isError,
165
247
  };
166
248
  }
167
249
 
168
250
  function parseResultEvent(event: Record<string, unknown>): OutputEvent {
251
+ const success = getString(event, 'status') === 'success';
169
252
  return {
170
253
  type: 'result',
171
- success: event.success !== false,
254
+ success,
172
255
  result: event.result || '',
173
- error: event.success === false ? (getString(event, 'error') ?? 'Result failed') : null,
256
+ error: success ? null : geminiErrorMessage(event, 'Result failed'),
257
+ };
258
+ }
259
+
260
+ function parseErrorEvent(event: Record<string, unknown>): OutputEvent | null {
261
+ if (getString(event, 'severity') !== 'error') return null;
262
+ return {
263
+ type: 'result',
264
+ success: false,
265
+ result: '',
266
+ error: getString(event, 'message') || 'Gemini CLI error',
174
267
  };
175
268
  }
176
269
 
@@ -189,6 +282,8 @@ function parseEvent(line: string, state: ProviderParserState): OutputEvent | nul
189
282
  return parseToolResultEvent(event, state);
190
283
  case 'result':
191
284
  return parseResultEvent(event);
285
+ case 'error':
286
+ return parseErrorEvent(event);
192
287
  default:
193
288
  return null;
194
289
  }
@@ -229,7 +324,7 @@ function classifyError(error: unknown): ErrorClassification {
229
324
  );
230
325
  }
231
326
 
232
- export const geminiAdapter: ProviderAdapter = {
327
+ export const geminiAdapter: StructuredOutputRecoveryAdapter = {
233
328
  id: 'gemini',
234
329
  displayName: 'Gemini',
235
330
  binary: 'gemini',
@@ -242,6 +337,7 @@ export const geminiAdapter: ProviderAdapter = {
242
337
  defaultMinLevel: 'level1',
243
338
  detectCliFeatures,
244
339
  buildCommand,
340
+ buildStructuredOutputRecoveryCommand,
245
341
  parseEvent,
246
342
  createParserState: () => createParserState('gemini'),
247
343
  resolveModelSpec,
@@ -0,0 +1,448 @@
1
+ import { appendJsonSchemaPrompt } from '../schema';
2
+ import { contractError } from '../contract-errors';
3
+ import {
4
+ getArray,
5
+ getBoolean,
6
+ getNumber,
7
+ getOptionalString,
8
+ getRecord,
9
+ getString,
10
+ isRecord,
11
+ tryParseJson,
12
+ unknownToMessage,
13
+ } from '../json';
14
+ import {
15
+ type BuildProviderCommandOptions,
16
+ type CommandSpec,
17
+ type ErrorClassification,
18
+ type LevelModelSpec,
19
+ type LevelOverrides,
20
+ type ModelCatalogEntry,
21
+ type ModelLevel,
22
+ type OmpCliFeatures,
23
+ type OutputEvent,
24
+ type ProviderAdapter,
25
+ type ProviderParserState,
26
+ type ResolvedModelSpec,
27
+ type WarningMetadata,
28
+ } from '../types';
29
+ import {
30
+ classifyBaseProviderError,
31
+ commandSpec,
32
+ createParserState,
33
+ optionFeatures,
34
+ resolveModelSpecWithConfig,
35
+ warning,
36
+ } from './common';
37
+
38
+ const MODEL_CATALOG: Readonly<Record<string, ModelCatalogEntry>> = {};
39
+
40
+ const LEVEL_MAPPING: Readonly<Record<ModelLevel, LevelModelSpec>> = {
41
+ level1: { rank: 1, model: null, reasoningEffort: 'low' },
42
+ level2: { rank: 2, model: null, reasoningEffort: 'medium' },
43
+ level3: { rank: 3, model: null, reasoningEffort: 'high' },
44
+ };
45
+
46
+ const IGNORED_EVENT_TYPES = new Set([
47
+ 'session',
48
+ 'agent_start',
49
+ 'agent_end',
50
+ 'turn_start',
51
+ 'queue_update',
52
+ 'compaction_start',
53
+ 'compaction_end',
54
+ 'auto_retry_start',
55
+ 'auto_retry_end',
56
+ ]);
57
+
58
+ function detectCliFeatures(helpText?: string | null): OmpCliFeatures {
59
+ const help = helpText ?? '';
60
+ const unknown = !help;
61
+ return {
62
+ provider: 'omp',
63
+ supportsModeJson: unknown ? true : /--mode\b/.test(help) && /\bjson\b/.test(help),
64
+ supportsPrint: unknown ? true : /-p\b/.test(help) || /--print\b/.test(help),
65
+ supportsCwd: unknown ? true : /--cwd\b/.test(help),
66
+ supportsAutoApprove: unknown ? true : /--auto-approve\b/.test(help),
67
+ supportsModel: unknown ? true : /--model\b/.test(help),
68
+ supportsThinking: unknown ? true : /--thinking\b/.test(help),
69
+ supportsNoExtensions: unknown ? true : /--no-extensions\b/.test(help),
70
+ supportsNoSkills: unknown ? true : /--no-skills\b/.test(help),
71
+ supportsNoRules: unknown ? true : /--no-rules\b/.test(help),
72
+ supportsNoTitle: unknown ? true : /--no-title\b/.test(help),
73
+ unknown,
74
+ };
75
+ }
76
+
77
+ function assertRequiredOmpFeatures(options: BuildProviderCommandOptions): void {
78
+ const features = optionFeatures(options);
79
+ const required: ReadonlyArray<readonly [boolean | undefined, string]> = [
80
+ [features.supportsModeJson, '--mode json'],
81
+ [features.supportsPrint, '-p/--print'],
82
+ [features.supportsCwd, '--cwd'],
83
+ [features.supportsAutoApprove, '--auto-approve'],
84
+ ];
85
+ const missing = required.filter(([supported]) => supported === false).map(([, flag]) => flag);
86
+ if (missing.length === 0) return;
87
+ throw contractError({
88
+ code: 'unsupported-provider-cli',
89
+ exitCode: 2,
90
+ message:
91
+ 'omp CLI is missing required non-interactive flag(s): ' +
92
+ missing.join(', ') +
93
+ '. Update/install @oh-my-pi/pi-coding-agent; Zeroshot will not silently fall back to Pi or another provider.',
94
+ });
95
+ }
96
+
97
+ function failClosedUnsupportedSessionControl(options: BuildProviderCommandOptions): void {
98
+ const hasResumeSessionId = options.resumeSessionId !== undefined;
99
+ if (!hasResumeSessionId && !options.continueSession) return;
100
+ const field = hasResumeSessionId ? 'options.resumeSessionId' : 'options.continueSession';
101
+ throw contractError({
102
+ code: 'invalid-field',
103
+ field,
104
+ exitCode: 2,
105
+ message:
106
+ 'OMP CLI does not support resume/continue session control; fail closed and start a fresh run instead.',
107
+ });
108
+ }
109
+
110
+ function addOptionalFlags(args: string[], options: BuildProviderCommandOptions): void {
111
+ const features = optionFeatures(options);
112
+ if (features.supportsNoExtensions !== false) args.push('--no-extensions');
113
+ if (features.supportsNoSkills !== false) args.push('--no-skills');
114
+ if (features.supportsNoRules !== false) args.push('--no-rules');
115
+ if (features.supportsNoTitle !== false) args.push('--no-title');
116
+ }
117
+
118
+ function addModelArgs(args: string[], options: BuildProviderCommandOptions): void {
119
+ const features = optionFeatures(options);
120
+ if (options.modelSpec?.model && features.supportsModel !== false) {
121
+ args.push('--model', options.modelSpec.model);
122
+ }
123
+ if (options.modelSpec?.reasoningEffort && features.supportsThinking !== false) {
124
+ args.push('--thinking', options.modelSpec.reasoningEffort);
125
+ }
126
+ }
127
+
128
+ function collectWarnings(options: BuildProviderCommandOptions): WarningMetadata[] {
129
+ const features = optionFeatures(options);
130
+ const warnings: WarningMetadata[] = [];
131
+
132
+ if (options.jsonSchema) {
133
+ warnings.push(
134
+ warning(
135
+ 'omp',
136
+ 'omp-jsonschema',
137
+ 'OMP CLI does not support provider-native JSON schema; appending schema instructions to the prompt.'
138
+ )
139
+ );
140
+ }
141
+ if (options.modelSpec?.model && features.supportsModel === false) {
142
+ warnings.push(
143
+ warning(
144
+ 'omp',
145
+ 'omp-model-unsupported',
146
+ "OMP CLI does not advertise --model; continuing with OMP's own configured model."
147
+ )
148
+ );
149
+ }
150
+ if (options.modelSpec?.reasoningEffort && features.supportsThinking === false) {
151
+ warnings.push(
152
+ warning(
153
+ 'omp',
154
+ 'omp-thinking-unsupported',
155
+ 'OMP CLI does not advertise --thinking; continuing without a reasoning-effort override.'
156
+ )
157
+ );
158
+ }
159
+ return warnings;
160
+ }
161
+
162
+ function buildCommand(context: string, options: BuildProviderCommandOptions = {}): CommandSpec {
163
+ assertRequiredOmpFeatures(options);
164
+ failClosedUnsupportedSessionControl(options);
165
+ const finalContext = options.jsonSchema
166
+ ? appendJsonSchemaPrompt(context, options.jsonSchema)
167
+ : context;
168
+ const args: string[] = ['--mode', 'json', '-p'];
169
+
170
+ if (options.cwd) args.push('--cwd', options.cwd);
171
+ args.push('--auto-approve');
172
+ addOptionalFlags(args, options);
173
+ addModelArgs(args, options);
174
+ args.push(finalContext);
175
+
176
+ return commandSpec({
177
+ binary: 'omp',
178
+ args,
179
+ env: {},
180
+ ...(options.cwd === undefined ? {} : { cwd: options.cwd }),
181
+ warnings: collectWarnings(options),
182
+ });
183
+ }
184
+
185
+ function createOmpState(): ProviderParserState {
186
+ return {
187
+ ...createParserState('omp'),
188
+ lastAssistantText: '',
189
+ lastAssistantThinking: '',
190
+ };
191
+ }
192
+
193
+ function assistantSnapshot(message: Record<string, unknown>): { text: string; thinking: string } {
194
+ if (getString(message, 'role') !== 'assistant') return { text: '', thinking: '' };
195
+
196
+ let text = '';
197
+ let thinking = '';
198
+ for (const item of getArray(message, 'content')) {
199
+ if (!isRecord(item)) continue;
200
+ const type = getString(item, 'type');
201
+ if (type === 'text') {
202
+ text += getString(item, 'text') ?? '';
203
+ } else if (type === 'thinking') {
204
+ thinking += getString(item, 'thinking') ?? '';
205
+ }
206
+ }
207
+
208
+ return { text, thinking };
209
+ }
210
+
211
+ function snapshotDelta(previous: string, current: string): string | null {
212
+ if (!current) return null;
213
+ if (previous === current) return null;
214
+ if (current.startsWith(previous)) return current.slice(previous.length) || null;
215
+ return current;
216
+ }
217
+
218
+ function emitAssistantSnapshot(
219
+ message: Record<string, unknown>,
220
+ state: ProviderParserState
221
+ ): readonly OutputEvent[] {
222
+ const snapshot = assistantSnapshot(message);
223
+ const events: OutputEvent[] = [];
224
+
225
+ const textDelta = snapshotDelta(state.lastAssistantText ?? '', snapshot.text);
226
+ if (textDelta) events.push({ type: 'text', text: textDelta });
227
+ state.lastAssistantText = snapshot.text;
228
+
229
+ const thinkingDelta = snapshotDelta(state.lastAssistantThinking ?? '', snapshot.thinking);
230
+ if (thinkingDelta) events.push({ type: 'thinking', text: thinkingDelta });
231
+ state.lastAssistantThinking = snapshot.thinking;
232
+
233
+ return events;
234
+ }
235
+
236
+ function parseAssistantEvent(
237
+ event: Record<string, unknown>,
238
+ state: ProviderParserState
239
+ ): OutputEvent | null {
240
+ const type = getString(event, 'type');
241
+ if (type === 'text_delta') {
242
+ const delta = getString(event, 'delta');
243
+ if (!delta) return null;
244
+ state.lastAssistantText += delta;
245
+ return { type: 'text', text: delta };
246
+ }
247
+ if (type === 'thinking_delta') {
248
+ const delta = getString(event, 'delta');
249
+ if (!delta) return null;
250
+ state.lastAssistantThinking += delta;
251
+ return { type: 'thinking', text: delta };
252
+ }
253
+ return null;
254
+ }
255
+
256
+ function parseToolExecutionStart(
257
+ event: Record<string, unknown>,
258
+ state: ProviderParserState
259
+ ): OutputEvent {
260
+ const toolId = getOptionalString(event, 'toolCallId');
261
+ state.lastToolId = toolId;
262
+ return {
263
+ type: 'tool_call',
264
+ toolName: getOptionalString(event, 'toolName'),
265
+ toolId,
266
+ input: event.args ?? {},
267
+ };
268
+ }
269
+
270
+ function parseToolExecutionUpdate(
271
+ event: Record<string, unknown>,
272
+ state: ProviderParserState
273
+ ): OutputEvent | null {
274
+ const toolId = getOptionalString(event, 'toolCallId') ?? state.lastToolId;
275
+ if (toolId !== undefined) state.lastToolId = toolId;
276
+ if (!Object.prototype.hasOwnProperty.call(event, 'partialResult')) return null;
277
+ return {
278
+ type: 'tool_result',
279
+ toolId,
280
+ content: event.partialResult,
281
+ isError: false,
282
+ };
283
+ }
284
+
285
+ function parseToolExecutionEnd(
286
+ event: Record<string, unknown>,
287
+ state: ProviderParserState
288
+ ): OutputEvent {
289
+ const toolId = getOptionalString(event, 'toolCallId') ?? state.lastToolId;
290
+ return {
291
+ type: 'tool_result',
292
+ toolId,
293
+ content: event.result ?? '',
294
+ isError: getBoolean(event, 'isError') ?? false,
295
+ };
296
+ }
297
+
298
+ function parseTurnEnd(
299
+ event: Record<string, unknown>,
300
+ state: ProviderParserState
301
+ ): readonly OutputEvent[] {
302
+ const message = getRecord(event, 'message');
303
+ const events: OutputEvent[] = [];
304
+ if (message !== null) {
305
+ events.push(...emitAssistantSnapshot(message, state));
306
+ }
307
+
308
+ const usage = message ? getRecord(message, 'usage') ?? {} : {};
309
+ const stopReason = message ? getString(message, 'stopReason') : null;
310
+ const errorMessage = message ? getString(message, 'errorMessage') : null;
311
+ const snapshot = message ? assistantSnapshot(message) : { text: '', thinking: '' };
312
+ const success = stopReason !== 'error' && stopReason !== 'aborted' && !errorMessage;
313
+ events.push({
314
+ type: 'result',
315
+ success,
316
+ result: success ? snapshot.text || null : null,
317
+ error: success ? null : (errorMessage ?? stopReason ?? 'OMP turn failed'),
318
+ inputTokens: getNumber(usage, 'input') ?? 0,
319
+ outputTokens: getNumber(usage, 'output') ?? 0,
320
+ cacheReadInputTokens: getNumber(usage, 'cacheRead') ?? 0,
321
+ cacheCreationInputTokens: getNumber(usage, 'cacheWrite') ?? 0,
322
+ cost: getRecord(usage, 'cost') ?? null,
323
+ modelUsage: usage,
324
+ });
325
+ return events;
326
+ }
327
+
328
+ function parseMessageEvent(
329
+ type: string,
330
+ event: Record<string, unknown>,
331
+ state: ProviderParserState
332
+ ): readonly OutputEvent[] | OutputEvent | null | undefined {
333
+ if (type === 'message_start') {
334
+ const message = getRecord(event, 'message');
335
+ if (message !== null && getString(message, 'role') === 'assistant') {
336
+ state.lastAssistantText = '';
337
+ state.lastAssistantThinking = '';
338
+ }
339
+ return null;
340
+ }
341
+
342
+ if (type === 'message_update') {
343
+ const assistantMessageEvent = getRecord(event, 'assistantMessageEvent');
344
+ if (assistantMessageEvent !== null) {
345
+ const assistantEvent = parseAssistantEvent(assistantMessageEvent, state);
346
+ if (assistantEvent !== null) return assistantEvent;
347
+ }
348
+ const message = getRecord(event, 'message');
349
+ return message === null ? null : emitAssistantSnapshot(message, state);
350
+ }
351
+
352
+ if (type !== 'message_end') return undefined;
353
+ const message = getRecord(event, 'message');
354
+ return message === null ? null : emitAssistantSnapshot(message, state);
355
+ }
356
+
357
+ function parseToolEvent(
358
+ type: string,
359
+ event: Record<string, unknown>,
360
+ state: ProviderParserState
361
+ ): OutputEvent | null | undefined {
362
+ if (type === 'tool_execution_start') return parseToolExecutionStart(event, state);
363
+ if (type === 'tool_execution_update') return parseToolExecutionUpdate(event, state);
364
+ if (type === 'tool_execution_end') return parseToolExecutionEnd(event, state);
365
+ return undefined;
366
+ }
367
+
368
+ function parseEvent(
369
+ line: string,
370
+ state: ProviderParserState
371
+ ): readonly OutputEvent[] | OutputEvent | null {
372
+ const parsed = tryParseJson(line);
373
+ if (!isRecord(parsed)) return null;
374
+
375
+ const type = getString(parsed, 'type');
376
+ if (type === null || IGNORED_EVENT_TYPES.has(type)) return null;
377
+
378
+ const messageEvent = parseMessageEvent(type, parsed, state);
379
+ if (messageEvent !== undefined) return messageEvent;
380
+
381
+ const toolEvent = parseToolEvent(type, parsed, state);
382
+ if (toolEvent !== undefined) return toolEvent;
383
+
384
+ if (type === 'turn_end') return parseTurnEnd(parsed, state);
385
+ return null;
386
+ }
387
+
388
+ function resolveModelSpec(level: ModelLevel, overrides?: LevelOverrides): ResolvedModelSpec {
389
+ return resolveModelSpecWithConfig({
390
+ mapping: LEVEL_MAPPING,
391
+ defaultLevel: 'level2',
392
+ level,
393
+ overrides,
394
+ validateModelId,
395
+ });
396
+ }
397
+
398
+ function validateModelId(modelId: string | null | undefined): string | null | undefined {
399
+ if (modelId === undefined || modelId === null) return modelId;
400
+ if (typeof modelId !== 'string') {
401
+ throw new Error(`Invalid model "${unknownToMessage(modelId)}" for provider "omp".`);
402
+ }
403
+ return modelId;
404
+ }
405
+
406
+ function classifyError(error: unknown): ErrorClassification {
407
+ return classifyBaseProviderError(
408
+ error,
409
+ [
410
+ /\brate(?:[_ -]?limit| limited)\b/i,
411
+ /\bquota\b/i,
412
+ /\bresource[_ -]?exhausted\b/i,
413
+ /\btemporar(?:y|ily)\b/i,
414
+ /\boverloaded\b/i,
415
+ /\bservice unavailable\b/i,
416
+ ],
417
+ [
418
+ /\b(cancelled|canceled|aborted|interrupted)\b/i,
419
+ /\brun\s*\/login\b/i,
420
+ /\bmissing api key\b/i,
421
+ /\bno valid authentication\b/i,
422
+ /\bunknown option\b/i,
423
+ /\bfailed to load\b/i,
424
+ /\bcannot find module\b/i,
425
+ /\bno such file or directory\b/i,
426
+ ]
427
+ );
428
+ }
429
+
430
+ export const ompAdapter: ProviderAdapter = {
431
+ id: 'omp',
432
+ displayName: 'OMP',
433
+ binary: 'omp',
434
+ adapterVersion: '1',
435
+ credentialEnvKeys: [],
436
+ modelCatalog: MODEL_CATALOG,
437
+ levelMapping: LEVEL_MAPPING,
438
+ defaultLevel: 'level2',
439
+ defaultMaxLevel: 'level3',
440
+ defaultMinLevel: 'level1',
441
+ detectCliFeatures,
442
+ buildCommand,
443
+ parseEvent,
444
+ createParserState: createOmpState,
445
+ resolveModelSpec,
446
+ validateModelId,
447
+ classifyError,
448
+ };