ai-cli-mcp 2.19.0 → 2.20.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/CHANGELOG.md +26 -0
- package/README.ja.md +34 -8
- package/README.md +41 -8
- package/dist/app/cli.js +1 -0
- package/dist/app/mcp.js +64 -12
- package/dist/cli-builder.js +13 -6
- package/dist/cli-process-service.js +76 -91
- package/dist/cli-utils.js +6 -0
- package/dist/cli.js +1 -1
- package/dist/model-catalog.js +3 -2
- package/dist/parsers.js +8 -2
- package/package.json +27 -3
- package/server.json +3 -3
- package/.gemini/settings.json +0 -11
- package/.github/dependabot.yml +0 -28
- package/.github/pull_request_template.md +0 -28
- package/.github/workflows/ci.yml +0 -34
- package/.github/workflows/dependency-review.yml +0 -22
- package/.github/workflows/publish.yml +0 -89
- package/.github/workflows/test.yml +0 -20
- package/.github/workflows/watch-session-prs.yml +0 -276
- package/.husky/pre-commit +0 -1
- package/.mcp.json +0 -11
- package/.releaserc.json +0 -18
- package/.vscode/settings.json +0 -3
- package/CONTRIBUTING.md +0 -81
- package/dist/__tests__/app-cli.test.js +0 -392
- package/dist/__tests__/cli-bin-smoke.test.js +0 -101
- package/dist/__tests__/cli-builder.test.js +0 -442
- package/dist/__tests__/cli-process-service.test.js +0 -655
- package/dist/__tests__/cli-utils.test.js +0 -171
- package/dist/__tests__/e2e.test.js +0 -256
- package/dist/__tests__/edge-cases.test.js +0 -130
- package/dist/__tests__/error-cases.test.js +0 -292
- package/dist/__tests__/mcp-contract.test.js +0 -636
- package/dist/__tests__/mocks.js +0 -32
- package/dist/__tests__/model-alias.test.js +0 -36
- package/dist/__tests__/parsers.test.js +0 -646
- package/dist/__tests__/peek.test.js +0 -36
- package/dist/__tests__/process-management.test.js +0 -949
- package/dist/__tests__/server.test.js +0 -809
- package/dist/__tests__/setup.js +0 -11
- package/dist/__tests__/utils/claude-mock.js +0 -80
- package/dist/__tests__/utils/mcp-client.js +0 -121
- package/dist/__tests__/utils/opencode-mock.js +0 -91
- package/dist/__tests__/utils/persistent-mock.js +0 -28
- package/dist/__tests__/utils/test-helpers.js +0 -11
- package/dist/__tests__/validation.test.js +0 -308
- package/dist/__tests__/version-print.test.js +0 -65
- package/dist/__tests__/wait.test.js +0 -260
- package/docs/RELEASE_CHECKLIST.md +0 -65
- package/docs/cli-architecture.md +0 -275
- package/docs/concept.md +0 -154
- package/docs/development.md +0 -156
- package/docs/e2e-testing.md +0 -148
- package/docs/prd.md +0 -146
- package/docs/session-stacking.md +0 -67
- package/src/__tests__/app-cli.test.ts +0 -495
- package/src/__tests__/cli-bin-smoke.test.ts +0 -136
- package/src/__tests__/cli-builder.test.ts +0 -549
- package/src/__tests__/cli-process-service.test.ts +0 -759
- package/src/__tests__/cli-utils.test.ts +0 -200
- package/src/__tests__/e2e.test.ts +0 -311
- package/src/__tests__/edge-cases.test.ts +0 -176
- package/src/__tests__/error-cases.test.ts +0 -370
- package/src/__tests__/mcp-contract.test.ts +0 -755
- package/src/__tests__/mocks.ts +0 -35
- package/src/__tests__/model-alias.test.ts +0 -44
- package/src/__tests__/parsers.test.ts +0 -730
- package/src/__tests__/peek.test.ts +0 -44
- package/src/__tests__/process-management.test.ts +0 -1129
- package/src/__tests__/server.test.ts +0 -1020
- package/src/__tests__/setup.ts +0 -13
- package/src/__tests__/utils/claude-mock.ts +0 -87
- package/src/__tests__/utils/mcp-client.ts +0 -159
- package/src/__tests__/utils/opencode-mock.ts +0 -108
- package/src/__tests__/utils/persistent-mock.ts +0 -33
- package/src/__tests__/utils/test-helpers.ts +0 -13
- package/src/__tests__/validation.test.ts +0 -369
- package/src/__tests__/version-print.test.ts +0 -81
- package/src/__tests__/wait.test.ts +0 -302
- package/src/app/cli.ts +0 -424
- package/src/app/mcp.ts +0 -466
- package/src/bin/ai-cli-mcp.ts +0 -7
- package/src/bin/ai-cli.ts +0 -11
- package/src/cli-builder.ts +0 -274
- package/src/cli-parse.ts +0 -105
- package/src/cli-process-service.ts +0 -709
- package/src/cli-utils.ts +0 -258
- package/src/cli.ts +0 -124
- package/src/model-catalog.ts +0 -87
- package/src/parsers.ts +0 -965
- package/src/peek.ts +0 -95
- package/src/process-result.ts +0 -88
- package/src/process-service.ts +0 -368
- package/src/server.ts +0 -10
- package/tsconfig.json +0 -16
- package/vitest.config.e2e.ts +0 -27
- package/vitest.config.ts +0 -22
- package/vitest.config.unit.ts +0 -28
|
@@ -1,549 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
-
|
|
4
|
-
// Mock dependencies
|
|
5
|
-
vi.mock('node:fs');
|
|
6
|
-
vi.mock('node:path', () => ({
|
|
7
|
-
resolve: vi.fn((...args: string[]) => args[args.length - 1]),
|
|
8
|
-
isAbsolute: vi.fn((p: string) => p.startsWith('/')),
|
|
9
|
-
}));
|
|
10
|
-
|
|
11
|
-
const mockExistsSync = vi.mocked(existsSync);
|
|
12
|
-
const mockReadFileSync = vi.mocked(readFileSync);
|
|
13
|
-
|
|
14
|
-
// Import after mocks
|
|
15
|
-
import {
|
|
16
|
-
buildCliCommand,
|
|
17
|
-
resolveModelAlias,
|
|
18
|
-
getReasoningEffort,
|
|
19
|
-
} from '../cli-builder.js';
|
|
20
|
-
|
|
21
|
-
const DEFAULT_CLI_PATHS = {
|
|
22
|
-
claude: '/usr/bin/claude',
|
|
23
|
-
codex: '/usr/bin/codex',
|
|
24
|
-
gemini: '/usr/bin/gemini',
|
|
25
|
-
forge: '/usr/bin/forge',
|
|
26
|
-
opencode: '/usr/bin/opencode',
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
describe('cli-builder', () => {
|
|
30
|
-
beforeEach(() => {
|
|
31
|
-
vi.clearAllMocks();
|
|
32
|
-
// By default, workFolder exists
|
|
33
|
-
mockExistsSync.mockReturnValue(true);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
describe('resolveModelAlias', () => {
|
|
37
|
-
it('should resolve claude-ultra to opus', () => {
|
|
38
|
-
expect(resolveModelAlias('claude-ultra')).toBe('opus');
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('should resolve codex-ultra to gpt-5.4', () => {
|
|
42
|
-
expect(resolveModelAlias('codex-ultra')).toBe('gpt-5.4');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('should resolve gemini-ultra to gemini-3.1-pro-preview', () => {
|
|
46
|
-
expect(resolveModelAlias('gemini-ultra')).toBe('gemini-3.1-pro-preview');
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should pass through non-alias model names', () => {
|
|
50
|
-
expect(resolveModelAlias('sonnet')).toBe('sonnet');
|
|
51
|
-
expect(resolveModelAlias('gpt-5.2-codex')).toBe('gpt-5.2-codex');
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('should pass through empty string', () => {
|
|
55
|
-
expect(resolveModelAlias('')).toBe('');
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
describe('getReasoningEffort', () => {
|
|
60
|
-
it('should return empty string for non-string input', () => {
|
|
61
|
-
expect(getReasoningEffort('gpt-5.2', undefined)).toBe('');
|
|
62
|
-
expect(getReasoningEffort('gpt-5.2', null)).toBe('');
|
|
63
|
-
expect(getReasoningEffort('gpt-5.2', 123)).toBe('');
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('should return empty string for empty/whitespace input', () => {
|
|
67
|
-
expect(getReasoningEffort('gpt-5.2', '')).toBe('');
|
|
68
|
-
expect(getReasoningEffort('gpt-5.2', ' ')).toBe('');
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('should normalize to lowercase', () => {
|
|
72
|
-
expect(getReasoningEffort('gpt-5.2', 'HIGH')).toBe('high');
|
|
73
|
-
expect(getReasoningEffort('gpt-5.2', 'Low')).toBe('low');
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('should accept valid values', () => {
|
|
77
|
-
expect(getReasoningEffort('gpt-5.2', 'low')).toBe('low');
|
|
78
|
-
expect(getReasoningEffort('gpt-5.2', 'medium')).toBe('medium');
|
|
79
|
-
expect(getReasoningEffort('gpt-5.2', 'high')).toBe('high');
|
|
80
|
-
expect(getReasoningEffort('gpt-5.2', 'xhigh')).toBe('xhigh');
|
|
81
|
-
expect(getReasoningEffort('sonnet', 'high')).toBe('high');
|
|
82
|
-
expect(getReasoningEffort('', 'low')).toBe('low');
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('should throw for invalid reasoning effort value', () => {
|
|
86
|
-
expect(() => getReasoningEffort('gpt-5.2', 'ultra')).toThrow(
|
|
87
|
-
'Invalid reasoning_effort: ultra. Allowed values: low, medium, high, xhigh.'
|
|
88
|
-
);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('should reject xhigh for claude models', () => {
|
|
92
|
-
expect(() => getReasoningEffort('sonnet', 'xhigh')).toThrow(
|
|
93
|
-
'Claude reasoning_effort supports only low, medium, high.'
|
|
94
|
-
);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('should throw for unsupported model families', () => {
|
|
98
|
-
expect(() => getReasoningEffort('gemini-2.5-pro', 'high')).toThrow(
|
|
99
|
-
'reasoning_effort is only supported for Claude and Codex models.'
|
|
100
|
-
);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('should reject reasoning_effort for forge explicitly', () => {
|
|
104
|
-
expect(() => getReasoningEffort('forge', 'high')).toThrow(
|
|
105
|
-
'reasoning_effort is not supported for forge.'
|
|
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
|
-
});
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
describe('buildCliCommand', () => {
|
|
120
|
-
describe('validation', () => {
|
|
121
|
-
it('should throw when workFolder is missing', () => {
|
|
122
|
-
expect(() =>
|
|
123
|
-
buildCliCommand({
|
|
124
|
-
prompt: 'hello',
|
|
125
|
-
workFolder: '',
|
|
126
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
127
|
-
})
|
|
128
|
-
).toThrow('Missing or invalid required parameter: workFolder');
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('should throw when neither prompt nor prompt_file is provided', () => {
|
|
132
|
-
expect(() =>
|
|
133
|
-
buildCliCommand({
|
|
134
|
-
workFolder: '/tmp',
|
|
135
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
136
|
-
})
|
|
137
|
-
).toThrow('Either prompt or prompt_file must be provided');
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it('should throw when both prompt and prompt_file are provided', () => {
|
|
141
|
-
expect(() =>
|
|
142
|
-
buildCliCommand({
|
|
143
|
-
prompt: 'hello',
|
|
144
|
-
prompt_file: '/tmp/prompt.txt',
|
|
145
|
-
workFolder: '/tmp',
|
|
146
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
147
|
-
})
|
|
148
|
-
).toThrow('Cannot specify both prompt and prompt_file');
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('should throw when prompt_file does not exist', () => {
|
|
152
|
-
mockExistsSync.mockImplementation((p) => {
|
|
153
|
-
if (p === '/tmp/nonexistent.txt') return false;
|
|
154
|
-
return true; // workFolder exists
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
expect(() =>
|
|
158
|
-
buildCliCommand({
|
|
159
|
-
prompt_file: '/tmp/nonexistent.txt',
|
|
160
|
-
workFolder: '/tmp',
|
|
161
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
162
|
-
})
|
|
163
|
-
).toThrow('Prompt file does not exist');
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('should throw when workFolder does not exist', () => {
|
|
167
|
-
mockExistsSync.mockReturnValue(false);
|
|
168
|
-
|
|
169
|
-
expect(() =>
|
|
170
|
-
buildCliCommand({
|
|
171
|
-
prompt: 'hello',
|
|
172
|
-
workFolder: '/nonexistent',
|
|
173
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
174
|
-
})
|
|
175
|
-
).toThrow('Working folder does not exist');
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it('should read prompt from file', () => {
|
|
179
|
-
mockExistsSync.mockReturnValue(true);
|
|
180
|
-
mockReadFileSync.mockReturnValue('prompt from file');
|
|
181
|
-
|
|
182
|
-
const cmd = buildCliCommand({
|
|
183
|
-
prompt_file: '/tmp/prompt.txt',
|
|
184
|
-
workFolder: '/tmp',
|
|
185
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
expect(cmd.prompt).toBe('prompt from file');
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
describe('claude agent', () => {
|
|
193
|
-
it('should build claude command with default model', () => {
|
|
194
|
-
const cmd = buildCliCommand({
|
|
195
|
-
prompt: 'hello world',
|
|
196
|
-
workFolder: '/tmp',
|
|
197
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
expect(cmd.agent).toBe('claude');
|
|
201
|
-
expect(cmd.cliPath).toBe('/usr/bin/claude');
|
|
202
|
-
expect(cmd.args).toEqual([
|
|
203
|
-
'--dangerously-skip-permissions',
|
|
204
|
-
'--output-format',
|
|
205
|
-
'stream-json',
|
|
206
|
-
'--verbose',
|
|
207
|
-
'-p',
|
|
208
|
-
'hello world',
|
|
209
|
-
]);
|
|
210
|
-
expect(cmd.resolvedModel).toBe('');
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it('should build claude command with model', () => {
|
|
214
|
-
const cmd = buildCliCommand({
|
|
215
|
-
prompt: 'test',
|
|
216
|
-
workFolder: '/tmp',
|
|
217
|
-
model: 'sonnet',
|
|
218
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
expect(cmd.agent).toBe('claude');
|
|
222
|
-
expect(cmd.args).toContain('--model');
|
|
223
|
-
expect(cmd.args).toContain('sonnet');
|
|
224
|
-
expect(cmd.resolvedModel).toBe('sonnet');
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it('should build claude command with session_id', () => {
|
|
228
|
-
const cmd = buildCliCommand({
|
|
229
|
-
prompt: 'test',
|
|
230
|
-
workFolder: '/tmp',
|
|
231
|
-
session_id: 'ses-123',
|
|
232
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
expect(cmd.args).toContain('-r');
|
|
236
|
-
expect(cmd.args).toContain('ses-123');
|
|
237
|
-
expect(cmd.args).toContain('--fork-session');
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
it('should resolve claude-ultra alias to opus', () => {
|
|
241
|
-
const cmd = buildCliCommand({
|
|
242
|
-
prompt: 'test',
|
|
243
|
-
workFolder: '/tmp',
|
|
244
|
-
model: 'claude-ultra',
|
|
245
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
expect(cmd.agent).toBe('claude');
|
|
249
|
-
expect(cmd.resolvedModel).toBe('opus');
|
|
250
|
-
expect(cmd.args).toContain('opus');
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
it('should resolve claude-ultra and default to high effort', () => {
|
|
254
|
-
const cmd = buildCliCommand({
|
|
255
|
-
prompt: 'test',
|
|
256
|
-
workFolder: '/tmp',
|
|
257
|
-
model: 'claude-ultra',
|
|
258
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
expect(cmd.args).toContain('--effort');
|
|
262
|
-
expect(cmd.args).toContain('high');
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
it('should build claude command with reasoning_effort using --effort', () => {
|
|
266
|
-
const cmd = buildCliCommand({
|
|
267
|
-
prompt: 'test',
|
|
268
|
-
workFolder: '/tmp',
|
|
269
|
-
model: 'sonnet',
|
|
270
|
-
reasoning_effort: 'medium',
|
|
271
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
expect(cmd.args).toContain('--effort');
|
|
275
|
-
expect(cmd.args).toContain('medium');
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
it('should reject xhigh reasoning_effort for claude', () => {
|
|
279
|
-
expect(() =>
|
|
280
|
-
buildCliCommand({
|
|
281
|
-
prompt: 'test',
|
|
282
|
-
workFolder: '/tmp',
|
|
283
|
-
model: 'sonnet',
|
|
284
|
-
reasoning_effort: 'xhigh',
|
|
285
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
286
|
-
})
|
|
287
|
-
).toThrow('Claude reasoning_effort supports only low, medium, high.');
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
it('should allow overriding reasoning_effort for claude-ultra', () => {
|
|
291
|
-
const cmd = buildCliCommand({
|
|
292
|
-
prompt: 'test',
|
|
293
|
-
workFolder: '/tmp',
|
|
294
|
-
model: 'claude-ultra',
|
|
295
|
-
reasoning_effort: 'low',
|
|
296
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
expect(cmd.args).toContain('--effort');
|
|
300
|
-
expect(cmd.args).toContain('low');
|
|
301
|
-
expect(cmd.args).not.toContain('high');
|
|
302
|
-
});
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
describe('codex agent', () => {
|
|
306
|
-
it('should build codex command', () => {
|
|
307
|
-
const cmd = buildCliCommand({
|
|
308
|
-
prompt: 'test',
|
|
309
|
-
workFolder: '/tmp',
|
|
310
|
-
model: 'gpt-5.2-codex',
|
|
311
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
expect(cmd.agent).toBe('codex');
|
|
315
|
-
expect(cmd.cliPath).toBe('/usr/bin/codex');
|
|
316
|
-
expect(cmd.args).toContain('exec');
|
|
317
|
-
expect(cmd.args).toContain('--dangerously-bypass-approvals-and-sandbox');
|
|
318
|
-
expect(cmd.args).not.toContain('--full-auto');
|
|
319
|
-
expect(cmd.args).toContain('--json');
|
|
320
|
-
expect(cmd.args).toContain('--model');
|
|
321
|
-
expect(cmd.args).toContain('gpt-5.2-codex');
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
it('should build codex command with session_id using exec resume', () => {
|
|
325
|
-
const cmd = buildCliCommand({
|
|
326
|
-
prompt: 'test',
|
|
327
|
-
workFolder: '/tmp',
|
|
328
|
-
model: 'gpt-5.2',
|
|
329
|
-
session_id: 'codex-ses-456',
|
|
330
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
expect(cmd.args[0]).toBe('exec');
|
|
334
|
-
expect(cmd.args[1]).toBe('resume');
|
|
335
|
-
expect(cmd.args[2]).toBe('codex-ses-456');
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
it('should build codex command with reasoning_effort', () => {
|
|
339
|
-
const cmd = buildCliCommand({
|
|
340
|
-
prompt: 'test',
|
|
341
|
-
workFolder: '/tmp',
|
|
342
|
-
model: 'gpt-5.2-codex',
|
|
343
|
-
reasoning_effort: 'high',
|
|
344
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
expect(cmd.args).toContain('-c');
|
|
348
|
-
expect(cmd.args).toContain('model_reasoning_effort=high');
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
it('should resolve codex-ultra and default to xhigh reasoning', () => {
|
|
352
|
-
const cmd = buildCliCommand({
|
|
353
|
-
prompt: 'test',
|
|
354
|
-
workFolder: '/tmp',
|
|
355
|
-
model: 'codex-ultra',
|
|
356
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
expect(cmd.agent).toBe('codex');
|
|
360
|
-
expect(cmd.resolvedModel).toBe('gpt-5.4');
|
|
361
|
-
expect(cmd.args).toContain('-c');
|
|
362
|
-
expect(cmd.args).toContain('model_reasoning_effort=xhigh');
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
it('should allow overriding reasoning_effort for codex-ultra', () => {
|
|
366
|
-
const cmd = buildCliCommand({
|
|
367
|
-
prompt: 'test',
|
|
368
|
-
workFolder: '/tmp',
|
|
369
|
-
model: 'codex-ultra',
|
|
370
|
-
reasoning_effort: 'low',
|
|
371
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
expect(cmd.args).toContain('model_reasoning_effort=low');
|
|
375
|
-
expect(cmd.args).not.toContain('model_reasoning_effort=xhigh');
|
|
376
|
-
});
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
describe('gemini agent', () => {
|
|
380
|
-
it('should build gemini command', () => {
|
|
381
|
-
const cmd = buildCliCommand({
|
|
382
|
-
prompt: 'test',
|
|
383
|
-
workFolder: '/tmp',
|
|
384
|
-
model: 'gemini-2.5-pro',
|
|
385
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
expect(cmd.agent).toBe('gemini');
|
|
389
|
-
expect(cmd.cliPath).toBe('/usr/bin/gemini');
|
|
390
|
-
expect(cmd.args).toContain('-y');
|
|
391
|
-
expect(cmd.args).toContain('--output-format');
|
|
392
|
-
expect(cmd.args).toContain('stream-json');
|
|
393
|
-
expect(cmd.args).toContain('--model');
|
|
394
|
-
expect(cmd.args).toContain('gemini-2.5-pro');
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
it('should build gemini command with session_id', () => {
|
|
398
|
-
const cmd = buildCliCommand({
|
|
399
|
-
prompt: 'test',
|
|
400
|
-
workFolder: '/tmp',
|
|
401
|
-
model: 'gemini-2.5-pro',
|
|
402
|
-
session_id: 'gem-789',
|
|
403
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
expect(cmd.args).toContain('-r');
|
|
407
|
-
expect(cmd.args).toContain('gem-789');
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
it('should resolve gemini-ultra alias', () => {
|
|
411
|
-
const cmd = buildCliCommand({
|
|
412
|
-
prompt: 'test',
|
|
413
|
-
workFolder: '/tmp',
|
|
414
|
-
model: 'gemini-ultra',
|
|
415
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
expect(cmd.agent).toBe('gemini');
|
|
419
|
-
expect(cmd.resolvedModel).toBe('gemini-3.1-pro-preview');
|
|
420
|
-
});
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
describe('opencode agent', () => {
|
|
424
|
-
it('should build default opencode command without --model', () => {
|
|
425
|
-
const cmd = buildCliCommand({
|
|
426
|
-
prompt: 'test',
|
|
427
|
-
workFolder: '/tmp',
|
|
428
|
-
model: 'opencode',
|
|
429
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
expect(cmd.agent).toBe('opencode');
|
|
433
|
-
expect(cmd.cliPath).toBe('/usr/bin/opencode');
|
|
434
|
-
expect(cmd.cwd).toBe('/tmp');
|
|
435
|
-
expect(cmd.args).toEqual(['run', '--format', 'json', '--dir', '/tmp', 'test']);
|
|
436
|
-
expect(cmd.args).not.toContain('--model');
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
it('should route valid explicit OpenCode model syntax', () => {
|
|
440
|
-
const cmd = buildCliCommand({
|
|
441
|
-
prompt: 'test',
|
|
442
|
-
workFolder: '/tmp',
|
|
443
|
-
model: 'oc-openai/gpt-5.4',
|
|
444
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
expect(cmd.agent).toBe('opencode');
|
|
448
|
-
expect(cmd.resolvedModel).toBe('oc-openai/gpt-5.4');
|
|
449
|
-
expect(cmd.args).toEqual([
|
|
450
|
-
'run',
|
|
451
|
-
'--format',
|
|
452
|
-
'json',
|
|
453
|
-
'--dir',
|
|
454
|
-
'/tmp',
|
|
455
|
-
'--model',
|
|
456
|
-
'openai/gpt-5.4',
|
|
457
|
-
'test',
|
|
458
|
-
]);
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
it.each([
|
|
462
|
-
'oc-',
|
|
463
|
-
'oc-openai',
|
|
464
|
-
'oc-/gpt-5.4',
|
|
465
|
-
'oc-openai/',
|
|
466
|
-
])('should reject invalid explicit OpenCode syntax: %s', (model) => {
|
|
467
|
-
expect(() =>
|
|
468
|
-
buildCliCommand({
|
|
469
|
-
prompt: 'test',
|
|
470
|
-
workFolder: '/tmp',
|
|
471
|
-
model,
|
|
472
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
473
|
-
})
|
|
474
|
-
).toThrow('Invalid OpenCode model. Expected exact syntax oc-<provider/model>.');
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
it.each([' oc-openai/gpt-5.4', 'oc-openai/gpt-5.4 '])(
|
|
478
|
-
'should reject explicit OpenCode models with surrounding whitespace: %s',
|
|
479
|
-
(model) => {
|
|
480
|
-
expect(() =>
|
|
481
|
-
buildCliCommand({
|
|
482
|
-
prompt: 'test',
|
|
483
|
-
workFolder: '/tmp',
|
|
484
|
-
model,
|
|
485
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
486
|
-
})
|
|
487
|
-
).toThrow('Invalid OpenCode model. Expected exact syntax oc-<provider/model>.');
|
|
488
|
-
}
|
|
489
|
-
);
|
|
490
|
-
|
|
491
|
-
it('should reject reasoning_effort for OpenCode in command building', () => {
|
|
492
|
-
expect(() =>
|
|
493
|
-
buildCliCommand({
|
|
494
|
-
prompt: 'test',
|
|
495
|
-
workFolder: '/tmp',
|
|
496
|
-
model: 'opencode',
|
|
497
|
-
reasoning_effort: 'high',
|
|
498
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
499
|
-
})
|
|
500
|
-
).toThrow('reasoning_effort is not supported for opencode.');
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
it('should build resumed default OpenCode command', () => {
|
|
504
|
-
const cmd = buildCliCommand({
|
|
505
|
-
prompt: 'resume prompt',
|
|
506
|
-
workFolder: '/tmp',
|
|
507
|
-
model: 'opencode',
|
|
508
|
-
session_id: 'ses-123',
|
|
509
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
expect(cmd.args).toEqual([
|
|
513
|
-
'run',
|
|
514
|
-
'--format',
|
|
515
|
-
'json',
|
|
516
|
-
'--dir',
|
|
517
|
-
'/tmp',
|
|
518
|
-
'--session',
|
|
519
|
-
'ses-123',
|
|
520
|
-
'resume prompt',
|
|
521
|
-
]);
|
|
522
|
-
expect(cmd.args).not.toContain('--model');
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
it('should build resumed explicit OpenCode command', () => {
|
|
526
|
-
const cmd = buildCliCommand({
|
|
527
|
-
prompt: 'resume prompt',
|
|
528
|
-
workFolder: '/tmp',
|
|
529
|
-
model: 'oc-openai/gpt-5.4',
|
|
530
|
-
session_id: 'ses-456',
|
|
531
|
-
cliPaths: DEFAULT_CLI_PATHS,
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
expect(cmd.args).toEqual([
|
|
535
|
-
'run',
|
|
536
|
-
'--format',
|
|
537
|
-
'json',
|
|
538
|
-
'--dir',
|
|
539
|
-
'/tmp',
|
|
540
|
-
'--session',
|
|
541
|
-
'ses-456',
|
|
542
|
-
'--model',
|
|
543
|
-
'openai/gpt-5.4',
|
|
544
|
-
'resume prompt',
|
|
545
|
-
]);
|
|
546
|
-
});
|
|
547
|
-
});
|
|
548
|
-
});
|
|
549
|
-
});
|