@the-open-engine/zeroshot 6.7.2 → 6.8.1
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/lib/agent-cli-provider/adapters/claude.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/claude.js +1 -0
- package/lib/agent-cli-provider/adapters/claude.js.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.js +3 -0
- package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
- package/lib/agent-cli-provider/adapters/common.js +1 -1
- package/lib/agent-cli-provider/adapters/common.js.map +1 -1
- package/lib/agent-cli-provider/adapters/opencode-models.d.ts +7 -0
- package/lib/agent-cli-provider/adapters/opencode-models.d.ts.map +1 -0
- package/lib/agent-cli-provider/adapters/opencode-models.js +87 -0
- package/lib/agent-cli-provider/adapters/opencode-models.js.map +1 -0
- package/lib/agent-cli-provider/adapters/opencode.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/opencode.js +6 -58
- package/lib/agent-cli-provider/adapters/opencode.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 +27 -13
- package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
- package/lib/agent-cli-provider/types.d.ts +1 -0
- 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/scripts/assert-release-published.js +145 -35
- package/src/agent/agent-task-executor.js +23 -10
- package/src/agent-cli-provider/adapters/claude.ts +1 -0
- package/src/agent-cli-provider/adapters/codex.ts +3 -0
- package/src/agent-cli-provider/adapters/common.ts +1 -1
- package/src/agent-cli-provider/adapters/opencode-models.ts +100 -0
- package/src/agent-cli-provider/adapters/opencode.ts +8 -65
- package/src/agent-cli-provider/single-agent-runtime.ts +51 -33
- package/src/agent-cli-provider/types.ts +1 -0
- package/src/agent-wrapper.js +14 -2
- package/src/claude-task-runner.js +121 -37
- package/src/config-validator.js +2 -2
- package/src/isolation-manager.js +4 -1
- package/src/providers/index.js +5 -0
- package/src/task-run-model-args.js +155 -0
- package/task-lib/runner.js +66 -26
package/package.json
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const https = require('https');
|
|
5
|
+
const os = require('os');
|
|
5
6
|
const path = require('path');
|
|
6
7
|
const { execFileSync } = require('child_process');
|
|
7
8
|
const { releaseTypeForMessages } = require('./release-preflight');
|
|
@@ -9,8 +10,8 @@ const { releaseTypeForMessages } = require('./release-preflight');
|
|
|
9
10
|
const DEFAULT_ATTEMPTS = 24;
|
|
10
11
|
const DEFAULT_DELAY_MS = 5000;
|
|
11
12
|
|
|
12
|
-
function run(command, args) {
|
|
13
|
-
return execFileSync(command, args, { encoding: 'utf8' }).trim();
|
|
13
|
+
function run(command, args, options = {}) {
|
|
14
|
+
return execFileSync(command, args, { encoding: 'utf8', ...options }).trim();
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
function packageName() {
|
|
@@ -85,21 +86,50 @@ function verifyCuratedNotes(tag, release) {
|
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
function verifyInstalledCli(name, version) {
|
|
89
|
+
function verifyInstalledCli(name, version, options = {}) {
|
|
89
90
|
const packageSpec = `${name}@${version}`;
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
91
|
+
const execute = options.execute || run;
|
|
92
|
+
const makeTempRoot =
|
|
93
|
+
options.makeTempRoot ||
|
|
94
|
+
(() => fs.mkdtempSync(path.join(os.tmpdir(), 'zeroshot-release-cli-')));
|
|
95
|
+
const removeTempRoot =
|
|
96
|
+
options.removeTempRoot ||
|
|
97
|
+
((root) => {
|
|
98
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
99
|
+
});
|
|
100
|
+
const platform = options.platform || process.platform;
|
|
101
|
+
const prefix = makeTempRoot();
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
execute('npm', [
|
|
105
|
+
'install',
|
|
106
|
+
'--global',
|
|
107
|
+
'--prefix',
|
|
108
|
+
prefix,
|
|
109
|
+
'--no-audit',
|
|
110
|
+
'--no-fund',
|
|
111
|
+
packageSpec,
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
const executable = path.join(
|
|
115
|
+
prefix,
|
|
116
|
+
platform === 'win32' ? 'zeroshot.cmd' : 'bin',
|
|
117
|
+
...(platform === 'win32' ? [] : ['zeroshot'])
|
|
118
|
+
);
|
|
119
|
+
const isolatedEnv = {
|
|
120
|
+
...process.env,
|
|
121
|
+
HOME: prefix,
|
|
122
|
+
USERPROFILE: prefix,
|
|
123
|
+
};
|
|
124
|
+
const reported = execute(executable, ['--version'], { env: isolatedEnv });
|
|
125
|
+
if (!reported.split(/\s+/).includes(version)) {
|
|
126
|
+
throw new Error(`installed CLI reported ${reported}; expected ${version}`);
|
|
127
|
+
}
|
|
128
|
+
execute(executable, ['--help'], { env: isolatedEnv });
|
|
129
|
+
execute(executable, ['list'], { env: isolatedEnv });
|
|
130
|
+
} finally {
|
|
131
|
+
removeTempRoot(prefix);
|
|
100
132
|
}
|
|
101
|
-
run('npm', ['exec', '--yes', `--package=${packageSpec}`, '--', 'zeroshot', '--help']);
|
|
102
|
-
run('npm', ['exec', '--yes', `--package=${packageSpec}`, '--', 'zeroshot', 'list']);
|
|
103
133
|
}
|
|
104
134
|
|
|
105
135
|
function tagsPointingAtHead() {
|
|
@@ -149,28 +179,74 @@ function sleep(ms) {
|
|
|
149
179
|
});
|
|
150
180
|
}
|
|
151
181
|
|
|
182
|
+
function nextRetryDelay(attempt, attempts, delayMs, options) {
|
|
183
|
+
if (attempt >= attempts) return null;
|
|
184
|
+
if (options.deadline === undefined) return delayMs;
|
|
185
|
+
|
|
186
|
+
const now = options.now || Date.now;
|
|
187
|
+
const remainingMs = options.deadline - now();
|
|
188
|
+
if (remainingMs <= 0) return null;
|
|
189
|
+
return Math.min(delayMs, remainingMs);
|
|
190
|
+
}
|
|
191
|
+
|
|
152
192
|
async function waitForNpmLatest(name, expectedVersion, options = {}) {
|
|
153
193
|
const attempts =
|
|
154
194
|
options.attempts || Number(process.env.RELEASE_ASSERT_ATTEMPTS || DEFAULT_ATTEMPTS);
|
|
155
195
|
const delayMs =
|
|
156
196
|
options.delayMs || Number(process.env.RELEASE_ASSERT_DELAY_MS || DEFAULT_DELAY_MS);
|
|
197
|
+
const wait = options.sleep || sleep;
|
|
157
198
|
|
|
158
199
|
let latest = null;
|
|
159
200
|
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
160
201
|
latest = npmLatest(name);
|
|
161
202
|
if (latest === expectedVersion) return latest;
|
|
162
203
|
|
|
163
|
-
|
|
204
|
+
const retryDelay = nextRetryDelay(attempt, attempts, delayMs, options);
|
|
205
|
+
if (retryDelay !== null) {
|
|
164
206
|
console.log(
|
|
165
207
|
`npm latest for ${name} is ${latest}; waiting for ${expectedVersion} (${attempt}/${attempts})`
|
|
166
208
|
);
|
|
167
|
-
await
|
|
209
|
+
await wait(retryDelay);
|
|
210
|
+
} else {
|
|
211
|
+
break;
|
|
168
212
|
}
|
|
169
213
|
}
|
|
170
214
|
|
|
171
215
|
throw new Error(`expected npm latest for ${name} to be ${expectedVersion}, got ${latest}`);
|
|
172
216
|
}
|
|
173
217
|
|
|
218
|
+
async function waitForPublishedArtifact(label, check, options = {}) {
|
|
219
|
+
const attempts =
|
|
220
|
+
options.attempts || Number(process.env.RELEASE_ASSERT_ATTEMPTS || DEFAULT_ATTEMPTS);
|
|
221
|
+
const delayMs =
|
|
222
|
+
options.delayMs || Number(process.env.RELEASE_ASSERT_DELAY_MS || DEFAULT_DELAY_MS);
|
|
223
|
+
const wait = options.sleep || sleep;
|
|
224
|
+
let lastError = null;
|
|
225
|
+
let attemptsMade = 0;
|
|
226
|
+
|
|
227
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
228
|
+
attemptsMade = attempt;
|
|
229
|
+
try {
|
|
230
|
+
return await check();
|
|
231
|
+
} catch (error) {
|
|
232
|
+
lastError = error;
|
|
233
|
+
const retryDelay = nextRetryDelay(attempt, attempts, delayMs, options);
|
|
234
|
+
if (retryDelay !== null) {
|
|
235
|
+
console.log(
|
|
236
|
+
`${label} is not ready: ${error.message}; retrying (${attempt}/${attempts})`
|
|
237
|
+
);
|
|
238
|
+
await wait(retryDelay);
|
|
239
|
+
} else {
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
throw new Error(
|
|
246
|
+
`${label} did not become ready after ${attemptsMade} attempts: ${lastError?.message || 'unknown error'}`
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
174
250
|
async function main() {
|
|
175
251
|
const name = packageName();
|
|
176
252
|
const headTags = tagsPointingAtHead();
|
|
@@ -188,30 +264,62 @@ async function main() {
|
|
|
188
264
|
|
|
189
265
|
console.log(`tags on HEAD: ${headTags.join(', ') || '(none)'}`);
|
|
190
266
|
const expectedVersion = expectedTag.slice(1);
|
|
191
|
-
const
|
|
267
|
+
const retryAttempts = Number(process.env.RELEASE_ASSERT_ATTEMPTS || DEFAULT_ATTEMPTS);
|
|
268
|
+
const retryDelayMs = Number(process.env.RELEASE_ASSERT_DELAY_MS || DEFAULT_DELAY_MS);
|
|
269
|
+
const retryOptions = {
|
|
270
|
+
attempts: retryAttempts,
|
|
271
|
+
delayMs: retryDelayMs,
|
|
272
|
+
deadline: Date.now() + retryAttempts * retryDelayMs,
|
|
273
|
+
};
|
|
274
|
+
const latest = await waitForNpmLatest(name, expectedVersion, retryOptions);
|
|
192
275
|
|
|
193
276
|
console.log(`npm latest for ${name}: ${latest}`);
|
|
194
277
|
|
|
195
278
|
const expectedCommit = run('git', ['rev-parse', 'HEAD']);
|
|
196
|
-
const metadata =
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
279
|
+
const metadata = await waitForPublishedArtifact(
|
|
280
|
+
'npm release metadata',
|
|
281
|
+
() => {
|
|
282
|
+
const result = npmReleaseMetadata(name, expectedVersion);
|
|
283
|
+
if (result.version !== expectedVersion) {
|
|
284
|
+
throw new Error(`npm metadata returned ${result.version}; expected ${expectedVersion}`);
|
|
285
|
+
}
|
|
286
|
+
if (result.gitHead !== expectedCommit) {
|
|
287
|
+
throw new Error(`npm gitHead ${result.gitHead || '(missing)'} does not match HEAD`);
|
|
288
|
+
}
|
|
289
|
+
if (!result['dist.attestations']?.url) {
|
|
290
|
+
throw new Error('npm attestation URL is missing');
|
|
291
|
+
}
|
|
292
|
+
return result;
|
|
293
|
+
},
|
|
294
|
+
retryOptions
|
|
295
|
+
);
|
|
203
296
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
297
|
+
await waitForPublishedArtifact(
|
|
298
|
+
'npm provenance',
|
|
299
|
+
async () => {
|
|
300
|
+
const attestations = await httpsJson(metadata['dist.attestations'].url);
|
|
301
|
+
verifyProvenance(provenanceStatement(attestations), expectedCommit);
|
|
302
|
+
},
|
|
303
|
+
retryOptions
|
|
304
|
+
);
|
|
208
305
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
306
|
+
await waitForPublishedArtifact(
|
|
307
|
+
'GitHub Release',
|
|
308
|
+
() => {
|
|
309
|
+
const release = githubRelease(expectedTag);
|
|
310
|
+
if (release.tagName !== expectedTag) {
|
|
311
|
+
throw new Error(`GitHub Release tag ${release.tagName} does not match ${expectedTag}`);
|
|
312
|
+
}
|
|
313
|
+
verifyCuratedNotes(expectedTag, release);
|
|
314
|
+
},
|
|
315
|
+
retryOptions
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
await waitForPublishedArtifact(
|
|
319
|
+
'installed CLI',
|
|
320
|
+
() => verifyInstalledCli(name, expectedVersion),
|
|
321
|
+
retryOptions
|
|
322
|
+
);
|
|
215
323
|
|
|
216
324
|
console.log(`Release publication verified: ${name}@${latest}`);
|
|
217
325
|
}
|
|
@@ -230,6 +338,8 @@ module.exports = {
|
|
|
230
338
|
npmLatest,
|
|
231
339
|
provenanceStatement,
|
|
232
340
|
tagsPointingAtHead,
|
|
341
|
+
verifyInstalledCli,
|
|
233
342
|
verifyProvenance,
|
|
234
343
|
waitForNpmLatest,
|
|
344
|
+
waitForPublishedArtifact,
|
|
235
345
|
};
|
|
@@ -23,6 +23,10 @@ const {
|
|
|
23
23
|
prepareClaudeConfigDir,
|
|
24
24
|
resolveRepoMcpConfigPath,
|
|
25
25
|
} = require('../worktree-claude-config.js');
|
|
26
|
+
const {
|
|
27
|
+
appendTaskRunModelArgs,
|
|
28
|
+
wrapTaskRunWithIsolatedSettings,
|
|
29
|
+
} = require('../task-run-model-args.js');
|
|
26
30
|
const { buildRawLogOnlyMetadata } = require('./context-replay-policy');
|
|
27
31
|
|
|
28
32
|
function runCommandWithTimeout(command, args, options = {}, callback = null) {
|
|
@@ -722,14 +726,10 @@ function resolveOutputFormatConfig(agent) {
|
|
|
722
726
|
|
|
723
727
|
function buildTaskRunArgs({ agent, providerName, modelSpec, runOutputFormat }) {
|
|
724
728
|
const args = ['task', 'run', '--output-format', runOutputFormat, '--provider', providerName];
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
if (modelSpec?.reasoningEffort) {
|
|
731
|
-
args.push('--reasoning-effort', modelSpec.reasoningEffort);
|
|
732
|
-
}
|
|
729
|
+
const modelSpecSource = agent._resolveModelSpecSource
|
|
730
|
+
? agent._resolveModelSpecSource()
|
|
731
|
+
: 'direct';
|
|
732
|
+
appendTaskRunModelArgs(args, modelSpec, modelSpecSource);
|
|
733
733
|
|
|
734
734
|
// Add verification mode flag if configured
|
|
735
735
|
if (agent.config.verificationMode) {
|
|
@@ -875,8 +875,11 @@ function spawnTaskProcess({ agent, ctPath, args, cwd, spawnEnv }) {
|
|
|
875
875
|
// Timeout for spawn phase - if CLI hangs during init (e.g., opencode 429 bug), kill it
|
|
876
876
|
const SPAWN_TIMEOUT_MS = 30000; // 30 seconds to spawn task
|
|
877
877
|
|
|
878
|
+
// spawn() throws on null bytes in argv; strip them before they get there.
|
|
879
|
+
const safeArgs = args.map((arg) => (typeof arg === 'string' ? arg.replace(/\0/g, '') : arg));
|
|
880
|
+
|
|
878
881
|
return new Promise((resolve, reject) => {
|
|
879
|
-
const proc = spawn(ctPath,
|
|
882
|
+
const proc = spawn(ctPath, safeArgs, {
|
|
880
883
|
cwd,
|
|
881
884
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
882
885
|
env: spawnEnv,
|
|
@@ -1509,11 +1512,14 @@ async function spawnClaudeTaskIsolated(agent, context) {
|
|
|
1509
1512
|
const { manager, clusterId } = agent.isolation;
|
|
1510
1513
|
const providerName = agent._resolveProvider ? agent._resolveProvider() : 'claude';
|
|
1511
1514
|
const modelSpec = resolveAgentModelSpec(agent);
|
|
1515
|
+
const modelSpecSource = agent._resolveModelSpecSource
|
|
1516
|
+
? agent._resolveModelSpecSource()
|
|
1517
|
+
: 'direct';
|
|
1512
1518
|
|
|
1513
1519
|
agent._log(`📦 Agent ${agent.id}: Running task in isolated container using zeroshot task run...`);
|
|
1514
1520
|
|
|
1515
1521
|
const { desiredOutputFormat, runOutputFormat } = resolveOutputFormatConfig(agent);
|
|
1516
|
-
|
|
1522
|
+
let command = [
|
|
1517
1523
|
'zeroshot',
|
|
1518
1524
|
...buildTaskRunArgs({
|
|
1519
1525
|
agent,
|
|
@@ -1531,6 +1537,12 @@ async function spawnClaudeTaskIsolated(agent, context) {
|
|
|
1531
1537
|
});
|
|
1532
1538
|
|
|
1533
1539
|
command.push(finalContext);
|
|
1540
|
+
command = wrapTaskRunWithIsolatedSettings(command, {
|
|
1541
|
+
providerName,
|
|
1542
|
+
settings: loadSettings(),
|
|
1543
|
+
modelSpecSource,
|
|
1544
|
+
modelSpec,
|
|
1545
|
+
});
|
|
1534
1546
|
|
|
1535
1547
|
// STEP 1: Spawn task and extract task ID (same as non-isolated mode)
|
|
1536
1548
|
// Timeout for spawn phase - if CLI hangs during init (e.g., opencode 429 bug), kill it
|
|
@@ -2357,6 +2369,7 @@ async function killIsolatedTask(agent, currentTask, taskId, reason, code) {
|
|
|
2357
2369
|
module.exports = {
|
|
2358
2370
|
ensureAskUserQuestionHook,
|
|
2359
2371
|
spawnClaudeTask,
|
|
2372
|
+
spawnTaskProcess,
|
|
2360
2373
|
followClaudeTaskLogs,
|
|
2361
2374
|
followClaudeTaskLogsIsolated,
|
|
2362
2375
|
waitForTaskReady,
|
|
@@ -40,6 +40,7 @@ const MODEL_CATALOG: Readonly<Record<string, ModelCatalogEntry>> = {
|
|
|
40
40
|
'claude-opus-4-6': { rank: 3 },
|
|
41
41
|
'claude-opus-4-7': { rank: 3 },
|
|
42
42
|
'claude-opus-4-8': { rank: 3 },
|
|
43
|
+
'claude-opus-5': { rank: 3 },
|
|
43
44
|
fable: { rank: 3 },
|
|
44
45
|
'claude-fable-5': { rank: 3 },
|
|
45
46
|
'claude-mythos-5': { rank: 3 },
|
|
@@ -29,8 +29,11 @@ const MODEL_CATALOG: Readonly<Record<string, ModelCatalogEntry>> = {
|
|
|
29
29
|
'gpt-5.5': { rank: 3 },
|
|
30
30
|
'gpt-5.6': { rank: 3 },
|
|
31
31
|
'gpt-5.6-sol': { rank: 3 },
|
|
32
|
+
'openai.gpt-5.6-sol': { rank: 3 },
|
|
32
33
|
'gpt-5.6-terra': { rank: 2 },
|
|
34
|
+
'openai.gpt-5.6-terra': { rank: 2 },
|
|
33
35
|
'gpt-5.6-luna': { rank: 1 },
|
|
36
|
+
'openai.gpt-5.6-luna': { rank: 1 },
|
|
34
37
|
};
|
|
35
38
|
|
|
36
39
|
const LEVEL_MAPPING: Readonly<Record<ModelLevel, LevelModelSpec>> = {
|
|
@@ -99,7 +99,7 @@ export function validateModelIdFromCatalog(
|
|
|
99
99
|
modelId: string | null | undefined
|
|
100
100
|
): string | null | undefined {
|
|
101
101
|
if (!modelId) return modelId;
|
|
102
|
-
if (catalog
|
|
102
|
+
if (Object.prototype.hasOwnProperty.call(catalog, modelId)) return modelId;
|
|
103
103
|
const validModels = Object.keys(catalog).join(', ');
|
|
104
104
|
throw new InvalidProviderModelError(
|
|
105
105
|
`Invalid model "${modelId}" for provider "${provider}". Valid models: ${validModels}.`
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { unknownToMessage } from '../json';
|
|
2
|
+
import {
|
|
3
|
+
InvalidProviderModelError,
|
|
4
|
+
type LevelModelSpec,
|
|
5
|
+
type LevelOverrides,
|
|
6
|
+
type ModelCatalogEntry,
|
|
7
|
+
type ModelLevel,
|
|
8
|
+
type ResolvedModelSpec,
|
|
9
|
+
} from '../types';
|
|
10
|
+
import { validateModelIdFromCatalog } from './common';
|
|
11
|
+
|
|
12
|
+
export const MODEL_CATALOG: Readonly<Record<string, ModelCatalogEntry>> = {
|
|
13
|
+
'opencode/big-pickle': { rank: 1 },
|
|
14
|
+
'opencode/glm-4.7-free': { rank: 1 },
|
|
15
|
+
'opencode/gpt-5-nano': { rank: 1 },
|
|
16
|
+
'opencode/grok-code': { rank: 1 },
|
|
17
|
+
'opencode/minimax-m2.1-free': { rank: 1 },
|
|
18
|
+
'google/gemini-1.5-flash': { rank: 1 },
|
|
19
|
+
'google/gemini-1.5-flash-8b': { rank: 1 },
|
|
20
|
+
'google/gemini-1.5-pro': { rank: 1 },
|
|
21
|
+
'google/gemini-2.0-flash': { rank: 1 },
|
|
22
|
+
'google/gemini-2.0-flash-lite': { rank: 1 },
|
|
23
|
+
'google/gemini-2.5-flash': { rank: 1 },
|
|
24
|
+
'google/gemini-2.5-flash-image': { rank: 1 },
|
|
25
|
+
'google/gemini-2.5-flash-image-preview': { rank: 1 },
|
|
26
|
+
'google/gemini-2.5-flash-lite': { rank: 1 },
|
|
27
|
+
'google/gemini-2.5-flash-lite-preview-06-17': { rank: 1 },
|
|
28
|
+
'google/gemini-2.5-flash-lite-preview-09-2025': { rank: 1 },
|
|
29
|
+
'google/gemini-2.5-flash-preview-04-17': { rank: 1 },
|
|
30
|
+
'google/gemini-2.5-flash-preview-05-20': { rank: 1 },
|
|
31
|
+
'google/gemini-2.5-flash-preview-09-2025': { rank: 1 },
|
|
32
|
+
'google/gemini-2.5-flash-preview-tts': { rank: 1 },
|
|
33
|
+
'google/gemini-2.5-pro': { rank: 1 },
|
|
34
|
+
'google/gemini-2.5-pro-preview-05-06': { rank: 1 },
|
|
35
|
+
'google/gemini-2.5-pro-preview-06-05': { rank: 1 },
|
|
36
|
+
'google/gemini-2.5-pro-preview-tts': { rank: 1 },
|
|
37
|
+
'google/gemini-3-flash-preview': { rank: 1 },
|
|
38
|
+
'google/gemini-3-pro-preview': { rank: 1 },
|
|
39
|
+
'google/gemini-embedding-001': { rank: 1 },
|
|
40
|
+
'google/gemini-flash-latest': { rank: 1 },
|
|
41
|
+
'google/gemini-flash-lite-latest': { rank: 1 },
|
|
42
|
+
'google/gemini-live-2.5-flash': { rank: 1 },
|
|
43
|
+
'google/gemini-live-2.5-flash-preview-native-audio': { rank: 1 },
|
|
44
|
+
'openai/gpt-5.1-codex-max': { rank: 1 },
|
|
45
|
+
'openai/gpt-5.1-codex-mini': { rank: 1 },
|
|
46
|
+
'openai/gpt-5.2': { rank: 1 },
|
|
47
|
+
'openai/gpt-5.2-codex': { rank: 1 },
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const LEVEL_MAPPING: Readonly<Record<ModelLevel, LevelModelSpec>> = {
|
|
51
|
+
level1: { rank: 1, model: null, reasoningEffort: 'low' },
|
|
52
|
+
level2: { rank: 2, model: null, reasoningEffort: 'medium' },
|
|
53
|
+
level3: { rank: 3, model: null, reasoningEffort: 'high' },
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function containsControlCharacter(value: string): boolean {
|
|
57
|
+
return Array.from(value).some((character) => {
|
|
58
|
+
const codePoint = character.codePointAt(0);
|
|
59
|
+
return codePoint !== undefined && (codePoint <= 0x1f || codePoint === 0x7f);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function resolveModelSpec(level: ModelLevel, overrides?: LevelOverrides): ResolvedModelSpec {
|
|
64
|
+
const base = LEVEL_MAPPING[level] ?? LEVEL_MAPPING.level2;
|
|
65
|
+
const override = overrides?.[level];
|
|
66
|
+
const selectedModel = override?.model ?? base.model;
|
|
67
|
+
return {
|
|
68
|
+
level,
|
|
69
|
+
model:
|
|
70
|
+
override?.model === undefined || override.model === null
|
|
71
|
+
? (validateModelId(selectedModel) ?? null)
|
|
72
|
+
: (validateConfiguredModelId(selectedModel) ?? null),
|
|
73
|
+
reasoningEffort: override?.reasoningEffort ?? base.reasoningEffort,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function validateConfiguredModelId(
|
|
78
|
+
modelId: string | null | undefined
|
|
79
|
+
): string | null | undefined {
|
|
80
|
+
if (modelId && Object.prototype.hasOwnProperty.call(MODEL_CATALOG, modelId)) return modelId;
|
|
81
|
+
if (typeof modelId !== 'string') return validateModelId(modelId);
|
|
82
|
+
const segments = modelId.split('/');
|
|
83
|
+
if (
|
|
84
|
+
segments.length >= 2 &&
|
|
85
|
+
segments.every(Boolean) &&
|
|
86
|
+
!/\s/u.test(modelId) &&
|
|
87
|
+
!containsControlCharacter(modelId)
|
|
88
|
+
) {
|
|
89
|
+
return modelId;
|
|
90
|
+
}
|
|
91
|
+
throw new InvalidProviderModelError(
|
|
92
|
+
`Invalid configured model "${unknownToMessage(
|
|
93
|
+
modelId
|
|
94
|
+
)}" for provider "opencode". Expected "provider/model" with no whitespace, control characters, or empty path segments.`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function validateModelId(modelId: string | null | undefined): string | null | undefined {
|
|
99
|
+
return validateModelIdFromCatalog('opencode', MODEL_CATALOG, modelId);
|
|
100
|
+
}
|
|
@@ -11,14 +11,9 @@ import {
|
|
|
11
11
|
type BuildProviderCommandOptions,
|
|
12
12
|
type CommandSpec,
|
|
13
13
|
type ErrorClassification,
|
|
14
|
-
type LevelModelSpec,
|
|
15
|
-
type LevelOverrides,
|
|
16
|
-
type ModelCatalogEntry,
|
|
17
|
-
type ModelLevel,
|
|
18
14
|
type OpencodeCliFeatures,
|
|
19
15
|
type OutputEvent,
|
|
20
16
|
type ProviderAdapter,
|
|
21
|
-
type ResolvedModelSpec,
|
|
22
17
|
type WarningMetadata,
|
|
23
18
|
} from '../types';
|
|
24
19
|
import {
|
|
@@ -26,55 +21,16 @@ import {
|
|
|
26
21
|
commandSpec,
|
|
27
22
|
createParserState,
|
|
28
23
|
optionFeatures,
|
|
29
|
-
resolveModelSpecWithConfig,
|
|
30
24
|
unsupportedSessionControlWarnings,
|
|
31
|
-
validateModelIdFromCatalog,
|
|
32
25
|
warning,
|
|
33
26
|
} from './common';
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
'google/gemini-1.5-flash': { rank: 1 },
|
|
42
|
-
'google/gemini-1.5-flash-8b': { rank: 1 },
|
|
43
|
-
'google/gemini-1.5-pro': { rank: 1 },
|
|
44
|
-
'google/gemini-2.0-flash': { rank: 1 },
|
|
45
|
-
'google/gemini-2.0-flash-lite': { rank: 1 },
|
|
46
|
-
'google/gemini-2.5-flash': { rank: 1 },
|
|
47
|
-
'google/gemini-2.5-flash-image': { rank: 1 },
|
|
48
|
-
'google/gemini-2.5-flash-image-preview': { rank: 1 },
|
|
49
|
-
'google/gemini-2.5-flash-lite': { rank: 1 },
|
|
50
|
-
'google/gemini-2.5-flash-lite-preview-06-17': { rank: 1 },
|
|
51
|
-
'google/gemini-2.5-flash-lite-preview-09-2025': { rank: 1 },
|
|
52
|
-
'google/gemini-2.5-flash-preview-04-17': { rank: 1 },
|
|
53
|
-
'google/gemini-2.5-flash-preview-05-20': { rank: 1 },
|
|
54
|
-
'google/gemini-2.5-flash-preview-09-2025': { rank: 1 },
|
|
55
|
-
'google/gemini-2.5-flash-preview-tts': { rank: 1 },
|
|
56
|
-
'google/gemini-2.5-pro': { rank: 1 },
|
|
57
|
-
'google/gemini-2.5-pro-preview-05-06': { rank: 1 },
|
|
58
|
-
'google/gemini-2.5-pro-preview-06-05': { rank: 1 },
|
|
59
|
-
'google/gemini-2.5-pro-preview-tts': { rank: 1 },
|
|
60
|
-
'google/gemini-3-flash-preview': { rank: 1 },
|
|
61
|
-
'google/gemini-3-pro-preview': { rank: 1 },
|
|
62
|
-
'google/gemini-embedding-001': { rank: 1 },
|
|
63
|
-
'google/gemini-flash-latest': { rank: 1 },
|
|
64
|
-
'google/gemini-flash-lite-latest': { rank: 1 },
|
|
65
|
-
'google/gemini-live-2.5-flash': { rank: 1 },
|
|
66
|
-
'google/gemini-live-2.5-flash-preview-native-audio': { rank: 1 },
|
|
67
|
-
'openai/gpt-5.1-codex-max': { rank: 1 },
|
|
68
|
-
'openai/gpt-5.1-codex-mini': { rank: 1 },
|
|
69
|
-
'openai/gpt-5.2': { rank: 1 },
|
|
70
|
-
'openai/gpt-5.2-codex': { rank: 1 },
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const LEVEL_MAPPING: Readonly<Record<ModelLevel, LevelModelSpec>> = {
|
|
74
|
-
level1: { rank: 1, model: null, reasoningEffort: 'low' },
|
|
75
|
-
level2: { rank: 2, model: null, reasoningEffort: 'medium' },
|
|
76
|
-
level3: { rank: 3, model: null, reasoningEffort: 'high' },
|
|
77
|
-
};
|
|
27
|
+
import {
|
|
28
|
+
LEVEL_MAPPING,
|
|
29
|
+
MODEL_CATALOG,
|
|
30
|
+
resolveModelSpec,
|
|
31
|
+
validateConfiguredModelId,
|
|
32
|
+
validateModelId,
|
|
33
|
+
} from './opencode-models';
|
|
78
34
|
|
|
79
35
|
function detectCliFeatures(helpText?: string | null): OpencodeCliFeatures {
|
|
80
36
|
const help = helpText ?? '';
|
|
@@ -244,20 +200,6 @@ function parseEvent(line: string): OutputEvent | null {
|
|
|
244
200
|
return null;
|
|
245
201
|
}
|
|
246
202
|
|
|
247
|
-
function resolveModelSpec(level: ModelLevel, overrides?: LevelOverrides): ResolvedModelSpec {
|
|
248
|
-
return resolveModelSpecWithConfig({
|
|
249
|
-
mapping: LEVEL_MAPPING,
|
|
250
|
-
defaultLevel: 'level2',
|
|
251
|
-
level,
|
|
252
|
-
overrides,
|
|
253
|
-
validateModelId,
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function validateModelId(modelId: string | null | undefined): string | null | undefined {
|
|
258
|
-
return validateModelIdFromCatalog('opencode', MODEL_CATALOG, modelId);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
203
|
function classifyError(error: unknown): ErrorClassification {
|
|
262
204
|
return classifyBaseProviderError(error, [], []);
|
|
263
205
|
}
|
|
@@ -285,5 +227,6 @@ export const opencodeAdapter: ProviderAdapter = {
|
|
|
285
227
|
createParserState: () => createParserState('opencode'),
|
|
286
228
|
resolveModelSpec,
|
|
287
229
|
validateModelId,
|
|
230
|
+
validateConfiguredModelId,
|
|
288
231
|
classifyError,
|
|
289
232
|
};
|