ai-cli-mcp 2.14.0 → 2.15.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/.github/dependabot.yml +28 -0
- package/.github/workflows/ci.yml +4 -1
- package/.github/workflows/dependency-review.yml +22 -0
- package/CHANGELOG.md +14 -0
- package/README.ja.md +25 -6
- package/README.md +25 -7
- package/dist/__tests__/app-cli.test.js +24 -4
- package/dist/__tests__/cli-bin-smoke.test.js +43 -0
- package/dist/__tests__/cli-builder.test.js +92 -14
- package/dist/__tests__/cli-process-service.test.js +187 -0
- package/dist/__tests__/cli-utils.test.js +31 -0
- package/dist/__tests__/e2e.test.js +77 -51
- package/dist/__tests__/mcp-contract.test.js +154 -0
- package/dist/__tests__/parsers.test.js +62 -1
- package/dist/__tests__/process-management.test.js +1 -1
- package/dist/__tests__/server.test.js +35 -6
- package/dist/__tests__/utils/opencode-mock.js +91 -0
- package/dist/__tests__/validation.test.js +40 -2
- package/dist/app/cli.js +4 -4
- package/dist/app/mcp.js +8 -4
- package/dist/cli-builder.js +66 -27
- package/dist/cli-parse.js +11 -5
- package/dist/cli-process-service.js +158 -25
- package/dist/cli-utils.js +14 -23
- package/dist/cli.js +6 -4
- package/dist/model-catalog.js +13 -1
- package/dist/parsers.js +57 -26
- package/dist/process-result.js +9 -2
- package/dist/process-service.js +23 -17
- package/dist/server.js +1 -2
- package/package.json +9 -6
- package/src/__tests__/app-cli.test.ts +24 -4
- package/src/__tests__/cli-bin-smoke.test.ts +62 -1
- package/src/__tests__/cli-builder.test.ts +110 -14
- package/src/__tests__/cli-process-service.test.ts +217 -0
- package/src/__tests__/cli-utils.test.ts +34 -0
- package/src/__tests__/e2e.test.ts +85 -54
- package/src/__tests__/mcp-contract.test.ts +179 -0
- package/src/__tests__/parsers.test.ts +73 -1
- package/src/__tests__/process-management.test.ts +1 -1
- package/src/__tests__/server.test.ts +45 -10
- package/src/__tests__/utils/opencode-mock.ts +108 -0
- package/src/__tests__/validation.test.ts +48 -2
- package/src/app/cli.ts +4 -4
- package/src/app/mcp.ts +8 -4
- package/src/cli-builder.ts +90 -31
- package/src/cli-parse.ts +11 -5
- package/src/cli-process-service.ts +193 -22
- package/src/cli-utils.ts +37 -33
- package/src/cli.ts +6 -4
- package/src/model-catalog.ts +24 -1
- package/src/parsers.ts +77 -31
- package/src/process-result.ts +11 -2
- package/src/process-service.ts +28 -15
- package/src/server.ts +2 -2
- package/vitest.config.unit.ts +2 -3
package/dist/process-service.js
CHANGED
|
@@ -1,7 +1,28 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { buildCliCommand } from './cli-builder.js';
|
|
3
|
-
import { parseClaudeOutput, parseCodexOutput, parseForgeOutput, parseGeminiOutput } from './parsers.js';
|
|
3
|
+
import { parseClaudeOutput, parseCodexOutput, parseForgeOutput, parseGeminiOutput, parseOpenCodeOutput } from './parsers.js';
|
|
4
4
|
import { buildProcessResult } from './process-result.js';
|
|
5
|
+
function parseAgentOutput(agent, stdout, stderr) {
|
|
6
|
+
if (agent === 'codex') {
|
|
7
|
+
return parseCodexOutput(`${stdout || ''}\n${stderr || ''}`);
|
|
8
|
+
}
|
|
9
|
+
if (!stdout) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
if (agent === 'claude') {
|
|
13
|
+
return parseClaudeOutput(stdout);
|
|
14
|
+
}
|
|
15
|
+
if (agent === 'gemini') {
|
|
16
|
+
return parseGeminiOutput(stdout);
|
|
17
|
+
}
|
|
18
|
+
if (agent === 'forge') {
|
|
19
|
+
return parseForgeOutput(stdout);
|
|
20
|
+
}
|
|
21
|
+
if (agent === 'opencode') {
|
|
22
|
+
return parseOpenCodeOutput(stdout);
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
5
26
|
export class ProcessService {
|
|
6
27
|
processManager = new Map();
|
|
7
28
|
cliPaths;
|
|
@@ -85,22 +106,7 @@ export class ProcessService {
|
|
|
85
106
|
if (!process) {
|
|
86
107
|
throw new Error(`Process with PID ${pid} not found`);
|
|
87
108
|
}
|
|
88
|
-
|
|
89
|
-
if (process.toolType === 'codex') {
|
|
90
|
-
const combinedOutput = (process.stdout || '') + '\n' + (process.stderr || '');
|
|
91
|
-
agentOutput = parseCodexOutput(combinedOutput);
|
|
92
|
-
}
|
|
93
|
-
else if (process.stdout) {
|
|
94
|
-
if (process.toolType === 'claude') {
|
|
95
|
-
agentOutput = parseClaudeOutput(process.stdout);
|
|
96
|
-
}
|
|
97
|
-
else if (process.toolType === 'gemini') {
|
|
98
|
-
agentOutput = parseGeminiOutput(process.stdout);
|
|
99
|
-
}
|
|
100
|
-
else if (process.toolType === 'forge') {
|
|
101
|
-
agentOutput = parseForgeOutput(process.stdout);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
109
|
+
const agentOutput = parseAgentOutput(process.toolType, process.stdout, process.stderr);
|
|
104
110
|
return buildProcessResult({
|
|
105
111
|
pid,
|
|
106
112
|
agent: process.toolType,
|
package/dist/server.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export { debugLog, findClaudeCli, findCodexCli, findForgeCli, findGeminiCli } from './cli-utils.js';
|
|
1
|
+
export { debugLog, findClaudeCli, findCodexCli, findForgeCli, findGeminiCli, findOpencodeCli } from './cli-utils.js';
|
|
3
2
|
export { resolveModelAlias } from './cli-builder.js';
|
|
4
3
|
export { ClaudeCodeServer, runMcpServer, spawnAsync } from './app/mcp.js';
|
|
5
4
|
import { runMcpServer } from './app/mcp.js';
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-cli-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.15.0",
|
|
4
4
|
"mcpName": "io.github.mkXultra/ai-cli-mcp",
|
|
5
|
-
"description": "MCP server for AI CLI tools (Claude, Codex, Gemini, and
|
|
5
|
+
"description": "MCP server for AI CLI tools (Claude, Codex, Gemini, Forge, and OpenCode) with background process management",
|
|
6
6
|
"author": "mkXultra",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"main": "dist/server.js",
|
|
@@ -25,21 +25,24 @@
|
|
|
25
25
|
"prepare": "husky"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
28
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
29
29
|
"zod": "^3.24.4"
|
|
30
30
|
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
33
|
+
},
|
|
31
34
|
"type": "module",
|
|
32
35
|
"devDependencies": {
|
|
33
36
|
"@eslint/js": "^9.26.0",
|
|
34
37
|
"@semantic-release/changelog": "^6.0.3",
|
|
35
38
|
"@semantic-release/git": "^10.0.1",
|
|
36
39
|
"@types/node": "^22.15.17",
|
|
37
|
-
"@vitest/coverage-v8": "^
|
|
40
|
+
"@vitest/coverage-v8": "^4.1.3",
|
|
38
41
|
"husky": "^9.1.7",
|
|
39
|
-
"semantic-release": "^25.0.
|
|
42
|
+
"semantic-release": "^25.0.3",
|
|
40
43
|
"tsx": "^4.19.4",
|
|
41
44
|
"typescript": "^5.8.3",
|
|
42
|
-
"vitest": "^
|
|
45
|
+
"vitest": "^4.1.3"
|
|
43
46
|
},
|
|
44
47
|
"repository": {
|
|
45
48
|
"type": "git",
|
|
@@ -236,12 +236,22 @@ describe('ai-cli app', () => {
|
|
|
236
236
|
const stderr = vi.fn();
|
|
237
237
|
|
|
238
238
|
const exitCode = await runCli(['models'], { stdout, stderr });
|
|
239
|
+
const payload = JSON.parse(stdout.mock.calls[0][0]);
|
|
239
240
|
|
|
240
241
|
expect(exitCode).toBe(0);
|
|
241
|
-
expect(
|
|
242
|
-
expect(
|
|
243
|
-
expect(
|
|
244
|
-
expect(
|
|
242
|
+
expect(payload.aliases).toEqual(expect.any(Array));
|
|
243
|
+
expect(payload.claude).toContain('sonnet');
|
|
244
|
+
expect(payload.codex).toContain('gpt-5.4');
|
|
245
|
+
expect(payload.forge).toEqual(['forge']);
|
|
246
|
+
expect(payload.opencode).toEqual(['opencode']);
|
|
247
|
+
expect(payload.dynamicModelBackends).toEqual({
|
|
248
|
+
opencode: {
|
|
249
|
+
explicitPrefix: 'oc-',
|
|
250
|
+
explicitPattern: 'oc-<provider/model>',
|
|
251
|
+
discoveryCommand: 'opencode models',
|
|
252
|
+
modelsAreDynamic: true,
|
|
253
|
+
},
|
|
254
|
+
});
|
|
245
255
|
expect(stderr).not.toHaveBeenCalled();
|
|
246
256
|
});
|
|
247
257
|
|
|
@@ -273,6 +283,12 @@ describe('ai-cli app', () => {
|
|
|
273
283
|
available: true,
|
|
274
284
|
lookup: 'path',
|
|
275
285
|
},
|
|
286
|
+
opencode: {
|
|
287
|
+
configuredCommand: 'opencode',
|
|
288
|
+
resolvedPath: '/tmp/bin/opencode',
|
|
289
|
+
available: true,
|
|
290
|
+
lookup: 'path',
|
|
291
|
+
},
|
|
276
292
|
});
|
|
277
293
|
|
|
278
294
|
const exitCode = await runCli(['doctor'], { stdout, stderr, getDoctorStatus });
|
|
@@ -307,6 +323,8 @@ describe('ai-cli app', () => {
|
|
|
307
323
|
expect(stdout).toHaveBeenCalledWith(expect.stringContaining('gpt-5.2-codex'));
|
|
308
324
|
expect(stdout).toHaveBeenCalledWith(expect.stringContaining('gemini-2.5-pro'));
|
|
309
325
|
expect(stdout).toHaveBeenCalledWith(expect.stringContaining('forge'));
|
|
326
|
+
expect(stdout).toHaveBeenCalledWith(expect.stringContaining('opencode'));
|
|
327
|
+
expect(stdout).toHaveBeenCalledWith(expect.stringContaining('oc-openai/gpt-5.4'));
|
|
310
328
|
expect(stderr).not.toHaveBeenCalled();
|
|
311
329
|
});
|
|
312
330
|
|
|
@@ -355,6 +373,7 @@ describe('ai-cli app', () => {
|
|
|
355
373
|
|
|
356
374
|
expect(exitCode).toBe(0);
|
|
357
375
|
expect(stdout).toHaveBeenCalledWith(DOCTOR_HELP_TEXT);
|
|
376
|
+
expect(stdout).toHaveBeenCalledWith(expect.stringContaining('OpenCode'));
|
|
358
377
|
expect(stderr).not.toHaveBeenCalled();
|
|
359
378
|
});
|
|
360
379
|
|
|
@@ -366,6 +385,7 @@ describe('ai-cli app', () => {
|
|
|
366
385
|
|
|
367
386
|
expect(exitCode).toBe(0);
|
|
368
387
|
expect(stdout).toHaveBeenCalledWith(DOCTOR_HELP_TEXT);
|
|
388
|
+
expect(stdout).toHaveBeenCalledWith(expect.stringContaining('OpenCode'));
|
|
369
389
|
expect(stderr).not.toHaveBeenCalled();
|
|
370
390
|
});
|
|
371
391
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
|
-
import { chmodSync,
|
|
2
|
+
import { chmodSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { delimiter, join } from 'node:path';
|
|
5
5
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
@@ -24,6 +24,62 @@ afterEach(() => {
|
|
|
24
24
|
}
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
+
describe('cli helper entrypoint smoke', () => {
|
|
28
|
+
it('prints help for cli.run with OpenCode examples', () => {
|
|
29
|
+
const output = execFileSync(
|
|
30
|
+
'node',
|
|
31
|
+
['--import', 'tsx', 'src/cli.ts', '--help'],
|
|
32
|
+
{
|
|
33
|
+
cwd: process.cwd(),
|
|
34
|
+
encoding: 'utf8',
|
|
35
|
+
env: process.env,
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
expect(output).toContain('Usage: npm run -s cli.run -- --model <model> --workFolder <path> --prompt "..." [options]');
|
|
40
|
+
expect(output).toContain('opencode');
|
|
41
|
+
expect(output).toContain('oc-openai/gpt-5.4');
|
|
42
|
+
expect(output).toContain('OpenCode');
|
|
43
|
+
expect(output).toContain('npm run -s cli.run.parse -- --agent opencode < raw.txt');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('prints help for cli.run.parse with OpenCode agent support', () => {
|
|
47
|
+
const output = execFileSync(
|
|
48
|
+
'node',
|
|
49
|
+
['--import', 'tsx', 'src/cli-parse.ts', '--help'],
|
|
50
|
+
{
|
|
51
|
+
cwd: process.cwd(),
|
|
52
|
+
encoding: 'utf8',
|
|
53
|
+
env: process.env,
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
expect(output).toContain('Usage: npm run -s cli.run.parse -- --agent <claude|codex|gemini|forge|opencode>');
|
|
58
|
+
expect(output).toContain('Agent type: claude, codex, gemini, forge, or opencode');
|
|
59
|
+
expect(output).toContain('npm run -s cli.run.parse -- --agent opencode < raw.txt');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('parses OpenCode NDJSON through cli.run.parse', () => {
|
|
63
|
+
const output = execFileSync(
|
|
64
|
+
'node',
|
|
65
|
+
['--import', 'tsx', 'src/cli-parse.ts', '--agent', 'opencode'],
|
|
66
|
+
{
|
|
67
|
+
cwd: process.cwd(),
|
|
68
|
+
encoding: 'utf8',
|
|
69
|
+
env: process.env,
|
|
70
|
+
input: '{"type":"step_start","sessionID":"ses_cli_parse"}\n{"type":"text","sessionID":"ses_cli_parse","part":{"type":"text","text":"Hello from cli.parse"}}\n{"type":"step_finish","sessionID":"ses_cli_parse","part":{"type":"step-finish","tokens":{"total":9},"cost":1}}\n',
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
expect(JSON.parse(output)).toEqual({
|
|
75
|
+
message: 'Hello from cli.parse',
|
|
76
|
+
session_id: 'ses_cli_parse',
|
|
77
|
+
tokens: { total: 9 },
|
|
78
|
+
cost: 1,
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
27
83
|
describe('ai-cli entrypoint smoke', () => {
|
|
28
84
|
it('prints doctor output for the ai-cli entrypoint', () => {
|
|
29
85
|
const fakeBinDir = makeTempDir('ai-cli-bin-');
|
|
@@ -31,6 +87,7 @@ describe('ai-cli entrypoint smoke', () => {
|
|
|
31
87
|
writeExecutable(fakeBinDir, 'codex');
|
|
32
88
|
writeExecutable(fakeBinDir, 'gemini');
|
|
33
89
|
writeExecutable(fakeBinDir, 'forge');
|
|
90
|
+
writeExecutable(fakeBinDir, 'opencode');
|
|
34
91
|
|
|
35
92
|
const output = execFileSync(
|
|
36
93
|
'node',
|
|
@@ -45,6 +102,7 @@ describe('ai-cli entrypoint smoke', () => {
|
|
|
45
102
|
CODEX_CLI_NAME: 'codex',
|
|
46
103
|
GEMINI_CLI_NAME: 'gemini',
|
|
47
104
|
FORGE_CLI_NAME: 'forge',
|
|
105
|
+
OPENCODE_CLI_NAME: 'opencode',
|
|
48
106
|
},
|
|
49
107
|
}
|
|
50
108
|
);
|
|
@@ -53,6 +111,7 @@ describe('ai-cli entrypoint smoke', () => {
|
|
|
53
111
|
expect(output).toContain('"codex"');
|
|
54
112
|
expect(output).toContain('"gemini"');
|
|
55
113
|
expect(output).toContain('"forge"');
|
|
114
|
+
expect(output).toContain('"opencode"');
|
|
56
115
|
expect(output).toContain('"available": true');
|
|
57
116
|
});
|
|
58
117
|
|
|
@@ -71,5 +130,7 @@ describe('ai-cli entrypoint smoke', () => {
|
|
|
71
130
|
expect(output).toContain('--model <model>');
|
|
72
131
|
expect(output).toContain('claude-ultra');
|
|
73
132
|
expect(output).toContain('forge');
|
|
133
|
+
expect(output).toContain('opencode');
|
|
134
|
+
expect(output).toContain('oc-openai/gpt-5.4');
|
|
74
135
|
});
|
|
75
136
|
});
|
|
@@ -23,6 +23,7 @@ const DEFAULT_CLI_PATHS = {
|
|
|
23
23
|
codex: '/usr/bin/codex',
|
|
24
24
|
gemini: '/usr/bin/gemini',
|
|
25
25
|
forge: '/usr/bin/forge',
|
|
26
|
+
opencode: '/usr/bin/opencode',
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
describe('cli-builder', () => {
|
|
@@ -104,6 +105,15 @@ describe('cli-builder', () => {
|
|
|
104
105
|
'reasoning_effort is not supported for forge.'
|
|
105
106
|
);
|
|
106
107
|
});
|
|
108
|
+
|
|
109
|
+
it('should reject reasoning_effort for opencode explicitly', () => {
|
|
110
|
+
expect(() => getReasoningEffort('opencode', 'high')).toThrow(
|
|
111
|
+
'reasoning_effort is not supported for opencode.'
|
|
112
|
+
);
|
|
113
|
+
expect(() => getReasoningEffort('oc-openai/gpt-5.4', 'high')).toThrow(
|
|
114
|
+
'reasoning_effort is not supported for opencode.'
|
|
115
|
+
);
|
|
116
|
+
});
|
|
107
117
|
});
|
|
108
118
|
|
|
109
119
|
describe('buildCliCommand', () => {
|
|
@@ -409,43 +419,129 @@ describe('cli-builder', () => {
|
|
|
409
419
|
});
|
|
410
420
|
});
|
|
411
421
|
|
|
412
|
-
describe('
|
|
413
|
-
it('should build
|
|
422
|
+
describe('opencode agent', () => {
|
|
423
|
+
it('should build default opencode command without --model', () => {
|
|
414
424
|
const cmd = buildCliCommand({
|
|
415
425
|
prompt: 'test',
|
|
416
426
|
workFolder: '/tmp',
|
|
417
|
-
model: '
|
|
427
|
+
model: 'opencode',
|
|
418
428
|
cliPaths: DEFAULT_CLI_PATHS,
|
|
419
429
|
});
|
|
420
430
|
|
|
421
|
-
expect(cmd.agent).toBe('
|
|
422
|
-
expect(cmd.cliPath).toBe('/usr/bin/
|
|
423
|
-
expect(cmd.
|
|
424
|
-
expect(cmd.args).toEqual(['
|
|
431
|
+
expect(cmd.agent).toBe('opencode');
|
|
432
|
+
expect(cmd.cliPath).toBe('/usr/bin/opencode');
|
|
433
|
+
expect(cmd.cwd).toBe('/tmp');
|
|
434
|
+
expect(cmd.args).toEqual(['run', '--format', 'json', '--dir', '/tmp', 'test']);
|
|
435
|
+
expect(cmd.args).not.toContain('--model');
|
|
425
436
|
});
|
|
426
437
|
|
|
427
|
-
it('should
|
|
438
|
+
it('should route valid explicit OpenCode model syntax', () => {
|
|
428
439
|
const cmd = buildCliCommand({
|
|
429
440
|
prompt: 'test',
|
|
430
441
|
workFolder: '/tmp',
|
|
431
|
-
model: '
|
|
432
|
-
session_id: 'forge-conv-123',
|
|
442
|
+
model: 'oc-openai/gpt-5.4',
|
|
433
443
|
cliPaths: DEFAULT_CLI_PATHS,
|
|
434
444
|
});
|
|
435
445
|
|
|
436
|
-
expect(cmd.
|
|
446
|
+
expect(cmd.agent).toBe('opencode');
|
|
447
|
+
expect(cmd.resolvedModel).toBe('oc-openai/gpt-5.4');
|
|
448
|
+
expect(cmd.args).toEqual([
|
|
449
|
+
'run',
|
|
450
|
+
'--format',
|
|
451
|
+
'json',
|
|
452
|
+
'--dir',
|
|
453
|
+
'/tmp',
|
|
454
|
+
'--model',
|
|
455
|
+
'openai/gpt-5.4',
|
|
456
|
+
'test',
|
|
457
|
+
]);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it.each([
|
|
461
|
+
'oc-',
|
|
462
|
+
'oc-openai',
|
|
463
|
+
'oc-/gpt-5.4',
|
|
464
|
+
'oc-openai/',
|
|
465
|
+
])('should reject invalid explicit OpenCode syntax: %s', (model) => {
|
|
466
|
+
expect(() =>
|
|
467
|
+
buildCliCommand({
|
|
468
|
+
prompt: 'test',
|
|
469
|
+
workFolder: '/tmp',
|
|
470
|
+
model,
|
|
471
|
+
cliPaths: DEFAULT_CLI_PATHS,
|
|
472
|
+
})
|
|
473
|
+
).toThrow('Invalid OpenCode model. Expected exact syntax oc-<provider/model>.');
|
|
437
474
|
});
|
|
438
475
|
|
|
439
|
-
it('
|
|
476
|
+
it.each([' oc-openai/gpt-5.4', 'oc-openai/gpt-5.4 '])(
|
|
477
|
+
'should reject explicit OpenCode models with surrounding whitespace: %s',
|
|
478
|
+
(model) => {
|
|
479
|
+
expect(() =>
|
|
480
|
+
buildCliCommand({
|
|
481
|
+
prompt: 'test',
|
|
482
|
+
workFolder: '/tmp',
|
|
483
|
+
model,
|
|
484
|
+
cliPaths: DEFAULT_CLI_PATHS,
|
|
485
|
+
})
|
|
486
|
+
).toThrow('Invalid OpenCode model. Expected exact syntax oc-<provider/model>.');
|
|
487
|
+
}
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
it('should reject reasoning_effort for OpenCode in command building', () => {
|
|
440
491
|
expect(() =>
|
|
441
492
|
buildCliCommand({
|
|
442
493
|
prompt: 'test',
|
|
443
494
|
workFolder: '/tmp',
|
|
444
|
-
model: '
|
|
495
|
+
model: 'opencode',
|
|
445
496
|
reasoning_effort: 'high',
|
|
446
497
|
cliPaths: DEFAULT_CLI_PATHS,
|
|
447
498
|
})
|
|
448
|
-
).toThrow('reasoning_effort is not supported for
|
|
499
|
+
).toThrow('reasoning_effort is not supported for opencode.');
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it('should build resumed default OpenCode command', () => {
|
|
503
|
+
const cmd = buildCliCommand({
|
|
504
|
+
prompt: 'resume prompt',
|
|
505
|
+
workFolder: '/tmp',
|
|
506
|
+
model: 'opencode',
|
|
507
|
+
session_id: 'ses-123',
|
|
508
|
+
cliPaths: DEFAULT_CLI_PATHS,
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
expect(cmd.args).toEqual([
|
|
512
|
+
'run',
|
|
513
|
+
'--format',
|
|
514
|
+
'json',
|
|
515
|
+
'--dir',
|
|
516
|
+
'/tmp',
|
|
517
|
+
'--session',
|
|
518
|
+
'ses-123',
|
|
519
|
+
'resume prompt',
|
|
520
|
+
]);
|
|
521
|
+
expect(cmd.args).not.toContain('--model');
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it('should build resumed explicit OpenCode command', () => {
|
|
525
|
+
const cmd = buildCliCommand({
|
|
526
|
+
prompt: 'resume prompt',
|
|
527
|
+
workFolder: '/tmp',
|
|
528
|
+
model: 'oc-openai/gpt-5.4',
|
|
529
|
+
session_id: 'ses-456',
|
|
530
|
+
cliPaths: DEFAULT_CLI_PATHS,
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
expect(cmd.args).toEqual([
|
|
534
|
+
'run',
|
|
535
|
+
'--format',
|
|
536
|
+
'json',
|
|
537
|
+
'--dir',
|
|
538
|
+
'/tmp',
|
|
539
|
+
'--session',
|
|
540
|
+
'ses-456',
|
|
541
|
+
'--model',
|
|
542
|
+
'openai/gpt-5.4',
|
|
543
|
+
'resume prompt',
|
|
544
|
+
]);
|
|
449
545
|
});
|
|
450
546
|
});
|
|
451
547
|
});
|