@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.
- package/README.md +2 -2
- package/cli/index.js +9 -13
- package/lib/agent-cli-provider/adapters/claude.d.ts +2 -2
- package/lib/agent-cli-provider/adapters/claude.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/claude.js +30 -0
- package/lib/agent-cli-provider/adapters/claude.js.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.d.ts +2 -2
- package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.js +37 -0
- package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
- package/lib/agent-cli-provider/adapters/gemini.d.ts +2 -2
- package/lib/agent-cli-provider/adapters/gemini.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/gemini.js +115 -4
- package/lib/agent-cli-provider/adapters/gemini.js.map +1 -1
- package/lib/agent-cli-provider/adapters/omp.d.ts +3 -0
- package/lib/agent-cli-provider/adapters/omp.d.ts.map +1 -0
- package/lib/agent-cli-provider/adapters/omp.js +356 -0
- package/lib/agent-cli-provider/adapters/omp.js.map +1 -0
- package/lib/agent-cli-provider/adapters/opencode.d.ts +2 -2
- package/lib/agent-cli-provider/adapters/opencode.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/opencode.js +108 -0
- package/lib/agent-cli-provider/adapters/opencode.js.map +1 -1
- package/lib/agent-cli-provider/contract-options.d.ts.map +1 -1
- package/lib/agent-cli-provider/contract-options.js +17 -0
- package/lib/agent-cli-provider/contract-options.js.map +1 -1
- package/lib/agent-cli-provider/index.d.ts +2 -2
- package/lib/agent-cli-provider/index.d.ts.map +1 -1
- package/lib/agent-cli-provider/index.js +2 -1
- package/lib/agent-cli-provider/index.js.map +1 -1
- package/lib/agent-cli-provider/provider-registry.d.ts +65 -7
- package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
- package/lib/agent-cli-provider/provider-registry.js +43 -0
- package/lib/agent-cli-provider/provider-registry.js.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.js +35 -4
- package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
- package/lib/agent-cli-provider/types.d.ts +49 -4
- package/lib/agent-cli-provider/types.d.ts.map +1 -1
- package/lib/agent-cli-provider/types.js.map +1 -1
- package/lib/cluster/errors.cjs +4 -1
- package/lib/cluster/errors.d.ts +2 -0
- package/lib/cluster/errors.mjs +2 -0
- package/lib/cluster/index.cjs +8 -1
- package/lib/cluster/index.d.ts +4 -1
- package/lib/cluster/index.mjs +4 -1
- package/lib/cluster/json-source.cjs +61 -0
- package/lib/cluster/json-source.d.ts +4 -0
- package/lib/cluster/json-source.mjs +55 -0
- package/lib/cluster/payload-value.cjs +76 -0
- package/lib/cluster/payload-value.d.ts +7 -0
- package/lib/cluster/payload-value.mjs +72 -0
- package/lib/cluster/validators.cjs +21 -0
- package/lib/cluster/validators.d.ts +4 -1
- package/lib/cluster/validators.mjs +19 -1
- package/lib/provider-names.js +5 -0
- package/package.json +2 -2
- package/src/agent/agent-task-executor.js +194 -410
- package/src/agent/output-extraction.js +39 -14
- package/src/agent/output-reformatter.js +154 -111
- package/src/agent-cli-provider/adapters/claude.ts +39 -2
- package/src/agent-cli-provider/adapters/codex.ts +56 -2
- package/src/agent-cli-provider/adapters/gemini.ts +102 -6
- package/src/agent-cli-provider/adapters/omp.ts +448 -0
- package/src/agent-cli-provider/adapters/opencode.ts +85 -2
- package/src/agent-cli-provider/contract-options.ts +21 -0
- package/src/agent-cli-provider/index.ts +5 -0
- package/src/agent-cli-provider/provider-registry.ts +62 -3
- package/src/agent-cli-provider/single-agent-runtime.ts +50 -4
- package/src/agent-cli-provider/types.ts +56 -2
- package/src/agent-wrapper.js +2 -2
- package/src/cluster/errors.ts +1 -0
- package/src/cluster/index.ts +4 -0
- package/src/cluster/json-source.ts +71 -0
- package/src/cluster/payload-value.ts +84 -0
- package/src/cluster/validators.ts +31 -2
- package/src/orchestrator.js +33 -13
- package/task-lib/attachable-watcher.js +1 -0
- package/task-lib/command-spec-cleanup.js +57 -11
- package/task-lib/commands/run.js +1 -0
- package/task-lib/provider-session-capture.js +8 -0
- package/task-lib/runner.js +9 -6
- 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
|
|
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
|
|
164
|
-
isError
|
|
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
|
|
254
|
+
success,
|
|
172
255
|
result: event.result || '',
|
|
173
|
-
error:
|
|
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:
|
|
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
|
+
};
|