@the-open-engine/zeroshot 6.15.0 → 6.17.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 +10 -7
- package/cli/commands/providers.js +1 -0
- package/cli/index.js +45 -4
- package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.js +14 -2
- package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
- package/lib/agent-cli-provider/adapters/common.d.ts +1 -0
- package/lib/agent-cli-provider/adapters/common.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/common.js +14 -0
- package/lib/agent-cli-provider/adapters/common.js.map +1 -1
- package/lib/agent-cli-provider/adapters/index.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/index.js +6 -1
- package/lib/agent-cli-provider/adapters/index.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.map +1 -1
- package/lib/agent-cli-provider/adapters/opencode.js +52 -3
- package/lib/agent-cli-provider/adapters/opencode.js.map +1 -1
- package/lib/agent-cli-provider/contract-actions.d.ts.map +1 -1
- package/lib/agent-cli-provider/contract-actions.js +19 -8
- package/lib/agent-cli-provider/contract-actions.js.map +1 -1
- package/lib/agent-cli-provider/contract-fallback.d.ts.map +1 -1
- package/lib/agent-cli-provider/contract-fallback.js +9 -0
- package/lib/agent-cli-provider/contract-fallback.js.map +1 -1
- package/lib/agent-cli-provider/contract-options.d.ts.map +1 -1
- package/lib/agent-cli-provider/contract-options.js +7 -0
- package/lib/agent-cli-provider/contract-options.js.map +1 -1
- package/lib/agent-cli-provider/errors.d.ts +7 -0
- package/lib/agent-cli-provider/errors.d.ts.map +1 -1
- package/lib/agent-cli-provider/errors.js +14 -0
- package/lib/agent-cli-provider/errors.js.map +1 -1
- package/lib/agent-cli-provider/index.d.ts +1 -1
- package/lib/agent-cli-provider/index.d.ts.map +1 -1
- package/lib/agent-cli-provider/index.js.map +1 -1
- package/lib/agent-cli-provider/provider-registry.d.ts +66 -3
- package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
- package/lib/agent-cli-provider/provider-registry.js +54 -2
- package/lib/agent-cli-provider/provider-registry.js.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.d.ts +14 -2
- package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.js +97 -22
- package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
- package/lib/agent-cli-provider/types.d.ts +30 -2
- package/lib/agent-cli-provider/types.d.ts.map +1 -1
- package/lib/agent-cli-provider/types.js.map +1 -1
- package/package.json +1 -1
- package/src/agent/agent-lifecycle.js +52 -1
- package/src/agent/agent-task-executor.js +8 -3
- package/src/agent-cli-provider/adapters/codex.ts +23 -2
- package/src/agent-cli-provider/adapters/common.ts +14 -0
- package/src/agent-cli-provider/adapters/index.ts +16 -2
- package/src/agent-cli-provider/adapters/omp.ts +448 -0
- package/src/agent-cli-provider/adapters/opencode.ts +60 -4
- package/src/agent-cli-provider/contract-actions.ts +20 -9
- package/src/agent-cli-provider/contract-fallback.ts +9 -0
- package/src/agent-cli-provider/contract-options.ts +7 -0
- package/src/agent-cli-provider/errors.ts +14 -0
- package/src/agent-cli-provider/index.ts +2 -0
- package/src/agent-cli-provider/provider-registry.ts +59 -2
- package/src/agent-cli-provider/single-agent-runtime.ts +144 -24
- package/src/agent-cli-provider/types.ts +32 -1
- package/src/claude-task-runner.js +11 -0
- package/src/orchestrator.js +8 -3
- package/src/providers/index.js +6 -0
- package/src/task-run-model-args.js +58 -40
- package/src/task-spawn-cleanup-ownership.js +3 -0
- package/src/task-startup-error.js +68 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { UnsupportedProviderCapabilityError } from '../errors';
|
|
1
2
|
import { getString, isRecord, tryParseJson } from '../json';
|
|
2
3
|
import { appendJsonSchemaPrompt, writeStrictOutputSchemaFile } from '../schema';
|
|
3
4
|
import {
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
classifyBaseProviderError,
|
|
18
19
|
commandSpec,
|
|
19
20
|
createParserState,
|
|
21
|
+
isCliVersionAtLeast,
|
|
20
22
|
optionFeatures,
|
|
21
23
|
resolveModelSpecWithConfig,
|
|
22
24
|
validateModelIdFromCatalog,
|
|
@@ -46,19 +48,25 @@ function supports(help: string, pattern: RegExp): boolean {
|
|
|
46
48
|
return help ? pattern.test(help) : true;
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
function detectCliFeatures(
|
|
51
|
+
function detectCliFeatures(
|
|
52
|
+
helpText?: string | null,
|
|
53
|
+
versionText?: string | null
|
|
54
|
+
): CodexCliFeatures {
|
|
50
55
|
const help = helpText ?? '';
|
|
51
56
|
const unknown = !help;
|
|
57
|
+
const supportsConfigOverride = supports(help, /--config\b/);
|
|
52
58
|
return {
|
|
53
59
|
provider: 'codex',
|
|
54
60
|
supportsJson: supports(help, /--json\b/),
|
|
55
61
|
supportsOutputSchema: supports(help, /--output-schema\b/),
|
|
56
62
|
supportsAutoApprove: supports(help, /--dangerously-bypass-approvals-and-sandbox\b/),
|
|
57
63
|
supportsCwd: supports(help, /\s-C\b/) || supports(help, /--cwd\b/),
|
|
58
|
-
supportsConfigOverride
|
|
64
|
+
supportsConfigOverride,
|
|
59
65
|
supportsModel: supports(help, /\s-m\b/) || supports(help, /--model\b/),
|
|
60
66
|
supportsSkipGitRepoCheck: supports(help, /--skip-git-repo-check\b/),
|
|
61
67
|
supportsResume: supports(help, /\bresume\b/),
|
|
68
|
+
supportsWebSearch:
|
|
69
|
+
!unknown && supportsConfigOverride && isCliVersionAtLeast(versionText, '0.146.0'),
|
|
62
70
|
unknown,
|
|
63
71
|
};
|
|
64
72
|
}
|
|
@@ -178,6 +186,18 @@ function collectWarnings(options: BuildProviderCommandOptions): WarningMetadata[
|
|
|
178
186
|
return warnings;
|
|
179
187
|
}
|
|
180
188
|
|
|
189
|
+
function addWebSearchArgs(args: string[], options: BuildProviderCommandOptions): void {
|
|
190
|
+
if (options.webSearch !== true) return;
|
|
191
|
+
if (optionFeatures(options).supportsWebSearch !== true) {
|
|
192
|
+
throw new UnsupportedProviderCapabilityError(
|
|
193
|
+
'codex',
|
|
194
|
+
'webSearch',
|
|
195
|
+
'Codex web search requires nonempty `codex exec --help` support for `--config` and a parseable Codex CLI version >= 0.146.0. Update @openai/codex or set providerSettings.codex.webSearch to false.'
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
args.push('--config', 'web_search="live"');
|
|
199
|
+
}
|
|
200
|
+
|
|
181
201
|
function buildCommand(context: string, options: BuildProviderCommandOptions = {}): CommandSpec {
|
|
182
202
|
if (options.resumeSessionId && optionFeatures(options).supportsResume === false) {
|
|
183
203
|
throw new Error(
|
|
@@ -190,6 +210,7 @@ function buildCommand(context: string, options: BuildProviderCommandOptions = {}
|
|
|
190
210
|
options.resumeSessionId && optionFeatures(options).supportsResume !== false
|
|
191
211
|
? options.resumeSessionId
|
|
192
212
|
: null;
|
|
213
|
+
addWebSearchArgs(args, options);
|
|
193
214
|
if (resumeSessionId) {
|
|
194
215
|
args.push('resume');
|
|
195
216
|
}
|
|
@@ -123,3 +123,17 @@ export function optionFeatures(
|
|
|
123
123
|
): CliFeatureOverrides {
|
|
124
124
|
return options?.cliFeatures ?? {};
|
|
125
125
|
}
|
|
126
|
+
|
|
127
|
+
export function isCliVersionAtLeast(versionText: string | null | undefined, floor: string): boolean {
|
|
128
|
+
const actualMatch = /(?:^|[^\w.])v?(\d+)\.(\d+)\.(\d+)(?![\w.-])/.exec(
|
|
129
|
+
versionText ?? ''
|
|
130
|
+
);
|
|
131
|
+
const floorMatch = /^(\d+)\.(\d+)\.(\d+)$/.exec(floor);
|
|
132
|
+
if (!actualMatch || !floorMatch) return false;
|
|
133
|
+
for (let index = 1; index <= 3; index += 1) {
|
|
134
|
+
const actual = Number(actualMatch[index]);
|
|
135
|
+
const minimum = Number(floorMatch[index]);
|
|
136
|
+
if (actual !== minimum) return actual > minimum;
|
|
137
|
+
}
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { stripTimestampPrefix } from '../log-prefix';
|
|
2
|
-
import {
|
|
2
|
+
import { UnsupportedProviderCapabilityError } from '../errors';
|
|
3
|
+
import {
|
|
4
|
+
getProviderRegistryEntry,
|
|
5
|
+
normalizeProviderName,
|
|
6
|
+
providerIds,
|
|
7
|
+
supportsProviderCapability,
|
|
8
|
+
} from '../provider-registry';
|
|
3
9
|
import type {
|
|
4
10
|
BuildProviderCommandOptions,
|
|
5
11
|
CommandSpec,
|
|
@@ -52,7 +58,15 @@ export function buildProviderCommand(
|
|
|
52
58
|
context: string,
|
|
53
59
|
options?: BuildProviderCommandOptions
|
|
54
60
|
): CommandSpec {
|
|
55
|
-
|
|
61
|
+
const adapter = getProviderAdapter(providerName);
|
|
62
|
+
if (options?.webSearch !== undefined && !supportsProviderCapability(adapter.id, 'webSearch')) {
|
|
63
|
+
throw new UnsupportedProviderCapabilityError(
|
|
64
|
+
adapter.id,
|
|
65
|
+
'webSearch',
|
|
66
|
+
`Provider ${adapter.id} does not expose provider-controlled native web search; remove options.webSearch.`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return adapter.buildCommand(context, options);
|
|
56
70
|
}
|
|
57
71
|
|
|
58
72
|
export function parseProviderChunk(
|
|
@@ -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
|
+
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { contractError } from '../contract-errors';
|
|
2
|
+
import { UnsupportedProviderCapabilityError } from '../errors';
|
|
1
3
|
import { appendJsonSchemaPrompt } from '../schema';
|
|
2
4
|
import {
|
|
3
5
|
getNumber,
|
|
@@ -20,8 +22,8 @@ import {
|
|
|
20
22
|
classifyBaseProviderError,
|
|
21
23
|
commandSpec,
|
|
22
24
|
createParserState,
|
|
25
|
+
isCliVersionAtLeast,
|
|
23
26
|
optionFeatures,
|
|
24
|
-
unsupportedSessionControlWarnings,
|
|
25
27
|
warning,
|
|
26
28
|
} from './common';
|
|
27
29
|
import {
|
|
@@ -32,7 +34,10 @@ import {
|
|
|
32
34
|
validateModelId,
|
|
33
35
|
} from './opencode-models';
|
|
34
36
|
|
|
35
|
-
function detectCliFeatures(
|
|
37
|
+
function detectCliFeatures(
|
|
38
|
+
helpText?: string | null,
|
|
39
|
+
versionText?: string | null
|
|
40
|
+
): OpencodeCliFeatures {
|
|
36
41
|
const help = helpText ?? '';
|
|
37
42
|
const unknown = !help;
|
|
38
43
|
return {
|
|
@@ -43,6 +48,8 @@ function detectCliFeatures(helpText?: string | null): OpencodeCliFeatures {
|
|
|
43
48
|
supportsDir: unknown ? false : /--dir\b/.test(help),
|
|
44
49
|
supportsCwd: unknown ? false : /--cwd\b/.test(help),
|
|
45
50
|
supportsAutoApprove: false,
|
|
51
|
+
supportsResume: !unknown && /--session\b/.test(help) && /--continue\b/.test(help),
|
|
52
|
+
supportsWebSearch: isCliVersionAtLeast(versionText, '1.0.137'),
|
|
46
53
|
unknown,
|
|
47
54
|
};
|
|
48
55
|
}
|
|
@@ -76,7 +83,7 @@ function addOpencodeOptionalArgs(args: string[], options: BuildProviderCommandOp
|
|
|
76
83
|
|
|
77
84
|
function collectOpencodeWarnings(options: BuildProviderCommandOptions): WarningMetadata[] {
|
|
78
85
|
const features = optionFeatures(options);
|
|
79
|
-
const warnings: WarningMetadata[] =
|
|
86
|
+
const warnings: WarningMetadata[] = [];
|
|
80
87
|
if (options.modelSpec?.reasoningEffort && features.supportsVariant === false) {
|
|
81
88
|
warnings.push(
|
|
82
89
|
warning(
|
|
@@ -89,19 +96,67 @@ function collectOpencodeWarnings(options: BuildProviderCommandOptions): WarningM
|
|
|
89
96
|
return warnings;
|
|
90
97
|
}
|
|
91
98
|
|
|
99
|
+
function addSessionArgs(args: string[], options: BuildProviderCommandOptions): void {
|
|
100
|
+
const hasResumeSessionId = options.resumeSessionId !== undefined;
|
|
101
|
+
if (!hasResumeSessionId && !options.continueSession) return;
|
|
102
|
+
const field = hasResumeSessionId ? 'options.resumeSessionId' : 'options.continueSession';
|
|
103
|
+
if (hasResumeSessionId && options.resumeSessionId?.trim().length === 0) {
|
|
104
|
+
throw contractError({
|
|
105
|
+
code: 'invalid-field',
|
|
106
|
+
field,
|
|
107
|
+
exitCode: 2,
|
|
108
|
+
message: 'options.resumeSessionId must be a non-empty OpenCode session ID.',
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (optionFeatures(options).supportsResume === false) {
|
|
112
|
+
throw contractError({
|
|
113
|
+
code: 'invalid-field',
|
|
114
|
+
field,
|
|
115
|
+
exitCode: 2,
|
|
116
|
+
message:
|
|
117
|
+
'OpenCode CLI cannot safely run continuation context because this installation lacks run --session/--continue.',
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
if (options.resumeSessionId) {
|
|
121
|
+
args.push('--session', options.resumeSessionId);
|
|
122
|
+
} else {
|
|
123
|
+
args.push('--continue');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function webSearchEnv(options: BuildProviderCommandOptions): Readonly<Record<string, string>> {
|
|
128
|
+
if (options.webSearch !== true) return {};
|
|
129
|
+
if (optionFeatures(options).supportsWebSearch !== true) {
|
|
130
|
+
throw new UnsupportedProviderCapabilityError(
|
|
131
|
+
'opencode',
|
|
132
|
+
'webSearch',
|
|
133
|
+
'OpenCode web search requires a parseable OpenCode CLI version >= 1.0.137. Update OpenCode or set providerSettings.opencode.webSearch to false.'
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
return { OPENCODE_ENABLE_EXA: '1' };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function extractSessionId(line: string): string | null {
|
|
140
|
+
const event = tryParseJson(line.trim());
|
|
141
|
+
if (!isRecord(event)) return null;
|
|
142
|
+
const sessionId = getString(event, 'sessionID') ?? getString(event, 'sessionId');
|
|
143
|
+
return sessionId?.trim() || null;
|
|
144
|
+
}
|
|
145
|
+
|
|
92
146
|
function buildCommand(context: string, options: BuildProviderCommandOptions = {}): CommandSpec {
|
|
93
147
|
const finalContext = options.jsonSchema
|
|
94
148
|
? appendJsonSchemaPrompt(context, options.jsonSchema)
|
|
95
149
|
: context;
|
|
96
150
|
const args: string[] = ['run'];
|
|
97
151
|
addOpencodeOptionalArgs(args, options);
|
|
152
|
+
addSessionArgs(args, options);
|
|
98
153
|
|
|
99
154
|
args.push(finalContext);
|
|
100
155
|
|
|
101
156
|
return commandSpec({
|
|
102
157
|
binary: 'opencode',
|
|
103
158
|
args,
|
|
104
|
-
env:
|
|
159
|
+
env: webSearchEnv(options),
|
|
105
160
|
...(options.cwd === undefined ? {} : { cwd: options.cwd }),
|
|
106
161
|
warnings: collectOpencodeWarnings(options),
|
|
107
162
|
});
|
|
@@ -226,6 +281,7 @@ export const opencodeAdapter: ProviderAdapter = {
|
|
|
226
281
|
defaultMinLevel: 'level1',
|
|
227
282
|
detectCliFeatures,
|
|
228
283
|
buildCommand,
|
|
284
|
+
extractSessionId,
|
|
229
285
|
parseEvent,
|
|
230
286
|
createParserState: () => createParserState('opencode'),
|
|
231
287
|
resolveModelSpec,
|