@principles/pd-cli 1.109.2 → 1.109.3
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/package.json
CHANGED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for resolve-runtime-from-pd-config.ts — PRI-393, PRI-402
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - buildProfileLabel: pi-ai and openclaw profile label formatting
|
|
6
|
+
* - resolveRuntimeFromPdConfig: successful config loading, malformed config, legacy warnings
|
|
7
|
+
* - resolveRuntimeWithOverrides: CLI flag overrides on top of config values
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect } from 'vitest';
|
|
11
|
+
import * as fs from 'node:fs';
|
|
12
|
+
import * as path from 'node:path';
|
|
13
|
+
import * as os from 'node:os';
|
|
14
|
+
import * as yaml from 'js-yaml';
|
|
15
|
+
import { isRuntimeConfigError } from '@principles/core/runtime-v2';
|
|
16
|
+
import {
|
|
17
|
+
resolveRuntimeFromPdConfig,
|
|
18
|
+
resolveRuntimeWithOverrides,
|
|
19
|
+
} from '../../src/services/resolve-runtime-from-pd-config.js';
|
|
20
|
+
|
|
21
|
+
function mkTmpDir(): string {
|
|
22
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'pd-resolve-runtime-test-'));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function rmTmpDir(dir: string): void {
|
|
26
|
+
try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function writeConfig(workspaceDir: string, content: string): void {
|
|
30
|
+
const configDir = path.join(workspaceDir, '.pd');
|
|
31
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
32
|
+
fs.writeFileSync(path.join(configDir, 'config.yaml'), content, 'utf8');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const mockEnvWithKeys = (name: string): string | undefined => {
|
|
36
|
+
if (name === 'ANTHROPIC_API_KEY') return 'sk-ant-test-key';
|
|
37
|
+
if (name === 'OPENROUTER_API_KEY') return 'sk-or-test-key';
|
|
38
|
+
if (name === 'TEST_API_KEY') return 'sk-test-key';
|
|
39
|
+
return undefined;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function makeValidOpenClawConfigYaml(workspaceDir: string): string {
|
|
43
|
+
return yaml.dump({
|
|
44
|
+
version: 1,
|
|
45
|
+
features: {
|
|
46
|
+
prompt: { category: 'core', enabled: true },
|
|
47
|
+
code_tool_hook: { category: 'core', enabled: true },
|
|
48
|
+
defer_archive: { category: 'core', enabled: true },
|
|
49
|
+
},
|
|
50
|
+
workspace: {
|
|
51
|
+
default: workspaceDir,
|
|
52
|
+
},
|
|
53
|
+
runtimeProfiles: {
|
|
54
|
+
'openclaw.default': { type: 'openclaw', source: 'default' },
|
|
55
|
+
'openclaw.model.lmstudio.qwen3': { type: 'openclaw', provider: 'lmstudio', model: 'qwen3.6-27b-mtp' },
|
|
56
|
+
},
|
|
57
|
+
internalAgents: {
|
|
58
|
+
defaultRuntime: 'openclaw.default',
|
|
59
|
+
agents: {
|
|
60
|
+
diagnostician: { enabled: true, runtimeProfile: 'openclaw.model.lmstudio.qwen3' },
|
|
61
|
+
dreamer: { enabled: true },
|
|
62
|
+
scribe: { enabled: true },
|
|
63
|
+
artificer: { enabled: true },
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function makeValidPiAiConfigYaml(workspaceDir: string): string {
|
|
70
|
+
return yaml.dump({
|
|
71
|
+
version: 1,
|
|
72
|
+
features: {
|
|
73
|
+
prompt: { category: 'core', enabled: true },
|
|
74
|
+
code_tool_hook: { category: 'core', enabled: true },
|
|
75
|
+
defer_archive: { category: 'core', enabled: true },
|
|
76
|
+
},
|
|
77
|
+
workspace: {
|
|
78
|
+
default: workspaceDir,
|
|
79
|
+
},
|
|
80
|
+
runtimeProfiles: {
|
|
81
|
+
'pd.anthropic-sonnet': { type: 'pi-ai', provider: 'anthropic', model: 'claude-3-5-sonnet', apiKeyEnv: 'ANTHROPIC_API_KEY', timeoutMs: 300000, maxRetries: 3 },
|
|
82
|
+
},
|
|
83
|
+
internalAgents: {
|
|
84
|
+
defaultRuntime: 'pd.anthropic-sonnet',
|
|
85
|
+
agents: {
|
|
86
|
+
diagnostician: { enabled: true, runtimeProfile: 'pd.anthropic-sonnet' },
|
|
87
|
+
dreamer: { enabled: true },
|
|
88
|
+
scribe: { enabled: true },
|
|
89
|
+
artificer: { enabled: true },
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
describe('buildProfileLabel', () => {
|
|
96
|
+
it('formats pi-ai profile with provider and model', () => {
|
|
97
|
+
const tmp = mkTmpDir();
|
|
98
|
+
writeConfig(tmp, yaml.dump({
|
|
99
|
+
version: 1,
|
|
100
|
+
features: {
|
|
101
|
+
prompt: { category: 'core', enabled: true },
|
|
102
|
+
code_tool_hook: { category: 'core', enabled: true },
|
|
103
|
+
defer_archive: { category: 'core', enabled: true },
|
|
104
|
+
},
|
|
105
|
+
workspace: { default: tmp },
|
|
106
|
+
runtimeProfiles: {
|
|
107
|
+
'pi-ai.test': { type: 'pi-ai', provider: 'openrouter', model: 'anthropic/claude-sonnet-4', apiKeyEnv: 'OPENROUTER_API_KEY' },
|
|
108
|
+
},
|
|
109
|
+
internalAgents: {
|
|
110
|
+
defaultRuntime: 'pi-ai.test',
|
|
111
|
+
agents: {
|
|
112
|
+
diagnostician: { enabled: true, runtimeProfile: 'pi-ai.test' },
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
}));
|
|
116
|
+
try {
|
|
117
|
+
const result = resolveRuntimeFromPdConfig(tmp, mockEnvWithKeys);
|
|
118
|
+
expect(result.runtimeProfileId).toBe('pi-ai.test');
|
|
119
|
+
expect(result.runtimeProfileLabel).toBe('pi-ai: openrouter/anthropic/claude-sonnet-4');
|
|
120
|
+
} finally { rmTmpDir(tmp); }
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('returns null profile info when pi-ai profile is missing required provider/model', () => {
|
|
124
|
+
const tmp = mkTmpDir();
|
|
125
|
+
writeConfig(tmp, yaml.dump({
|
|
126
|
+
version: 1,
|
|
127
|
+
features: {
|
|
128
|
+
prompt: { category: 'core', enabled: true },
|
|
129
|
+
code_tool_hook: { category: 'core', enabled: true },
|
|
130
|
+
defer_archive: { category: 'core', enabled: true },
|
|
131
|
+
},
|
|
132
|
+
workspace: { default: tmp },
|
|
133
|
+
runtimeProfiles: {
|
|
134
|
+
'pi-ai.missing': { type: 'pi-ai', apiKeyEnv: 'TEST_API_KEY' },
|
|
135
|
+
},
|
|
136
|
+
internalAgents: {
|
|
137
|
+
defaultRuntime: 'pi-ai.missing',
|
|
138
|
+
agents: {
|
|
139
|
+
diagnostician: { enabled: true, runtimeProfile: 'pi-ai.missing' },
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
}));
|
|
143
|
+
try {
|
|
144
|
+
const result = resolveRuntimeFromPdConfig(tmp, mockEnvWithKeys);
|
|
145
|
+
expect(result.runtimeProfileId).toBe(null);
|
|
146
|
+
expect(result.runtimeProfileLabel).toBe(null);
|
|
147
|
+
} finally { rmTmpDir(tmp); }
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('formats openclaw profile with provider and model', () => {
|
|
151
|
+
const tmp = mkTmpDir();
|
|
152
|
+
writeConfig(tmp, yaml.dump({
|
|
153
|
+
version: 1,
|
|
154
|
+
features: {
|
|
155
|
+
prompt: { category: 'core', enabled: true },
|
|
156
|
+
code_tool_hook: { category: 'core', enabled: true },
|
|
157
|
+
defer_archive: { category: 'core', enabled: true },
|
|
158
|
+
},
|
|
159
|
+
workspace: { default: tmp },
|
|
160
|
+
runtimeProfiles: {
|
|
161
|
+
'openclaw.model.lmstudio.qwen3': { type: 'openclaw', provider: 'lmstudio', model: 'qwen3.6-27b-mtp' },
|
|
162
|
+
},
|
|
163
|
+
internalAgents: {
|
|
164
|
+
defaultRuntime: 'openclaw.model.lmstudio.qwen3',
|
|
165
|
+
agents: {
|
|
166
|
+
diagnostician: { enabled: true, runtimeProfile: 'openclaw.model.lmstudio.qwen3' },
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
}));
|
|
170
|
+
try {
|
|
171
|
+
const result = resolveRuntimeFromPdConfig(tmp);
|
|
172
|
+
expect(result.runtimeProfileId).toBe('openclaw.model.lmstudio.qwen3');
|
|
173
|
+
expect(result.runtimeProfileLabel).toBe('openclaw: lmstudio: qwen3.6-27b-mtp');
|
|
174
|
+
} finally { rmTmpDir(tmp); }
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('formats openclaw profile with source when no provider/model', () => {
|
|
178
|
+
const tmp = mkTmpDir();
|
|
179
|
+
writeConfig(tmp, yaml.dump({
|
|
180
|
+
version: 1,
|
|
181
|
+
features: {
|
|
182
|
+
prompt: { category: 'core', enabled: true },
|
|
183
|
+
code_tool_hook: { category: 'core', enabled: true },
|
|
184
|
+
defer_archive: { category: 'core', enabled: true },
|
|
185
|
+
},
|
|
186
|
+
workspace: { default: tmp },
|
|
187
|
+
runtimeProfiles: {
|
|
188
|
+
'openclaw.default': { type: 'openclaw', source: 'default' },
|
|
189
|
+
},
|
|
190
|
+
internalAgents: {
|
|
191
|
+
defaultRuntime: 'openclaw.default',
|
|
192
|
+
agents: {
|
|
193
|
+
diagnostician: { enabled: true, runtimeProfile: 'openclaw.default' },
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
}));
|
|
197
|
+
try {
|
|
198
|
+
const result = resolveRuntimeFromPdConfig(tmp);
|
|
199
|
+
expect(result.runtimeProfileId).toBe('openclaw.default');
|
|
200
|
+
expect(result.runtimeProfileLabel).toBe('openclaw: default');
|
|
201
|
+
} finally { rmTmpDir(tmp); }
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('resolveRuntimeFromPdConfig', () => {
|
|
206
|
+
it('returns resolved runtime config from valid openclaw config.yaml', () => {
|
|
207
|
+
const tmp = mkTmpDir();
|
|
208
|
+
writeConfig(tmp, makeValidOpenClawConfigYaml(tmp));
|
|
209
|
+
try {
|
|
210
|
+
const result = resolveRuntimeFromPdConfig(tmp);
|
|
211
|
+
expect(isRuntimeConfigError(result.result)).toBe(false);
|
|
212
|
+
expect(result.configSource).toBe('.pd/config.yaml');
|
|
213
|
+
expect(result.legacyWarnings).toEqual([]);
|
|
214
|
+
} finally { rmTmpDir(tmp); }
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('returns resolved runtime config from valid pi-ai config.yaml with env vars', () => {
|
|
218
|
+
const tmp = mkTmpDir();
|
|
219
|
+
writeConfig(tmp, makeValidPiAiConfigYaml(tmp));
|
|
220
|
+
try {
|
|
221
|
+
const result = resolveRuntimeFromPdConfig(tmp, mockEnvWithKeys);
|
|
222
|
+
expect(isRuntimeConfigError(result.result)).toBe(false);
|
|
223
|
+
expect(result.configSource).toBe('.pd/config.yaml');
|
|
224
|
+
} finally { rmTmpDir(tmp); }
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('returns legacy warnings when legacy files detected', () => {
|
|
228
|
+
const tmp = mkTmpDir();
|
|
229
|
+
writeConfig(tmp, makeValidOpenClawConfigYaml(tmp));
|
|
230
|
+
const stateDir = path.join(tmp, '.state');
|
|
231
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
232
|
+
fs.writeFileSync(path.join(stateDir, 'workflows.yaml'), 'version: 1\n', 'utf8');
|
|
233
|
+
try {
|
|
234
|
+
const result = resolveRuntimeFromPdConfig(tmp);
|
|
235
|
+
expect(result.legacyWarnings.length).toBeGreaterThan(0);
|
|
236
|
+
expect(result.legacyWarnings[0]).toContain('Legacy config files detected');
|
|
237
|
+
expect(result.legacyWarnings[0]).toContain('workflows.yaml');
|
|
238
|
+
} finally { rmTmpDir(tmp); }
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('returns error result when config.yaml is malformed', () => {
|
|
242
|
+
const tmp = mkTmpDir();
|
|
243
|
+
writeConfig(tmp, 'version: [unterminated');
|
|
244
|
+
try {
|
|
245
|
+
const result = resolveRuntimeFromPdConfig(tmp);
|
|
246
|
+
expect(isRuntimeConfigError(result.result)).toBe(true);
|
|
247
|
+
if (!isRuntimeConfigError(result.result)) throw new Error('Expected RuntimeConfigError');
|
|
248
|
+
expect(result.result.reason).toContain('config_malformed');
|
|
249
|
+
expect(result.runtimeProfileId).toBe(null);
|
|
250
|
+
expect(result.runtimeProfileLabel).toBe(null);
|
|
251
|
+
} finally { rmTmpDir(tmp); }
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('returns error result when config.yaml validation fails', () => {
|
|
255
|
+
const tmp = mkTmpDir();
|
|
256
|
+
writeConfig(tmp, yaml.dump({ version: 99, features: {}, runtimeProfiles: {}, internalAgents: { defaultRuntime: 'x', agents: {} } }));
|
|
257
|
+
try {
|
|
258
|
+
const result = resolveRuntimeFromPdConfig(tmp);
|
|
259
|
+
expect(isRuntimeConfigError(result.result)).toBe(true);
|
|
260
|
+
if (!isRuntimeConfigError(result.result)) throw new Error('Expected RuntimeConfigError');
|
|
261
|
+
expect(result.result.reason).toContain('config_malformed');
|
|
262
|
+
} finally { rmTmpDir(tmp); }
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('returns defaults and profile info when config.yaml is missing', () => {
|
|
266
|
+
const tmp = mkTmpDir();
|
|
267
|
+
try {
|
|
268
|
+
const result = resolveRuntimeFromPdConfig(tmp);
|
|
269
|
+
expect(isRuntimeConfigError(result.result)).toBe(false);
|
|
270
|
+
expect(result.configSource).toBe('.pd/config.yaml');
|
|
271
|
+
expect(result.runtimeProfileId).toBe('openclaw.default');
|
|
272
|
+
expect(typeof result.runtimeProfileLabel).toBe('string');
|
|
273
|
+
} finally { rmTmpDir(tmp); }
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('includes legacy warnings even when config is malformed', () => {
|
|
277
|
+
const tmp = mkTmpDir();
|
|
278
|
+
writeConfig(tmp, 'version: [unterminated');
|
|
279
|
+
const stateDir = path.join(tmp, '.state');
|
|
280
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
281
|
+
fs.writeFileSync(path.join(stateDir, 'workflows.yaml'), 'version: 1\n', 'utf8');
|
|
282
|
+
try {
|
|
283
|
+
const result = resolveRuntimeFromPdConfig(tmp);
|
|
284
|
+
expect(isRuntimeConfigError(result.result)).toBe(true);
|
|
285
|
+
expect(result.legacyWarnings.length).toBeGreaterThan(0);
|
|
286
|
+
} finally { rmTmpDir(tmp); }
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('returns not_ready error when pi-ai config is valid but apiKeyEnv not set', () => {
|
|
290
|
+
const tmp = mkTmpDir();
|
|
291
|
+
writeConfig(tmp, makeValidPiAiConfigYaml(tmp));
|
|
292
|
+
try {
|
|
293
|
+
const result = resolveRuntimeFromPdConfig(tmp, () => undefined);
|
|
294
|
+
expect(isRuntimeConfigError(result.result)).toBe(true);
|
|
295
|
+
if (!isRuntimeConfigError(result.result)) throw new Error('Expected RuntimeConfigError');
|
|
296
|
+
expect(result.result.reason).toBe('not_ready');
|
|
297
|
+
} finally { rmTmpDir(tmp); }
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe('resolveRuntimeWithOverrides', () => {
|
|
302
|
+
it('returns merged config with CLI overrides applied', () => {
|
|
303
|
+
const tmp = mkTmpDir();
|
|
304
|
+
writeConfig(tmp, makeValidPiAiConfigYaml(tmp));
|
|
305
|
+
try {
|
|
306
|
+
const result = resolveRuntimeWithOverrides(tmp, {
|
|
307
|
+
provider: 'openrouter',
|
|
308
|
+
model: 'anthropic/claude-sonnet-4',
|
|
309
|
+
apiKeyEnv: 'OPENROUTER_API_KEY',
|
|
310
|
+
}, mockEnvWithKeys);
|
|
311
|
+
expect(result.mergedConfig).not.toBeNull();
|
|
312
|
+
if (!result.mergedConfig) throw new Error('Expected mergedConfig');
|
|
313
|
+
expect(result.mergedConfig.provider).toBe('openrouter');
|
|
314
|
+
expect(result.mergedConfig.model).toBe('anthropic/claude-sonnet-4');
|
|
315
|
+
expect(result.mergedConfig.apiKeyEnv).toBe('OPENROUTER_API_KEY');
|
|
316
|
+
} finally { rmTmpDir(tmp); }
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('partial CLI overrides merge with config values', () => {
|
|
320
|
+
const tmp = mkTmpDir();
|
|
321
|
+
writeConfig(tmp, yaml.dump({
|
|
322
|
+
version: 1,
|
|
323
|
+
features: {
|
|
324
|
+
prompt: { category: 'core', enabled: true },
|
|
325
|
+
code_tool_hook: { category: 'core', enabled: true },
|
|
326
|
+
defer_archive: { category: 'core', enabled: true },
|
|
327
|
+
},
|
|
328
|
+
workspace: { default: tmp },
|
|
329
|
+
runtimeProfiles: {
|
|
330
|
+
'pi-ai.test': { type: 'pi-ai', provider: 'anthropic', model: 'claude-3-5-sonnet', apiKeyEnv: 'ANTHROPIC_API_KEY', timeoutMs: 300000, maxRetries: 3 },
|
|
331
|
+
},
|
|
332
|
+
internalAgents: {
|
|
333
|
+
defaultRuntime: 'pi-ai.test',
|
|
334
|
+
agents: {
|
|
335
|
+
diagnostician: { enabled: true, runtimeProfile: 'pi-ai.test' },
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
}));
|
|
339
|
+
try {
|
|
340
|
+
const result = resolveRuntimeWithOverrides(tmp, {
|
|
341
|
+
model: 'claude-3-opus',
|
|
342
|
+
}, mockEnvWithKeys);
|
|
343
|
+
expect(result.mergedConfig).not.toBeNull();
|
|
344
|
+
if (!result.mergedConfig) throw new Error('Expected mergedConfig');
|
|
345
|
+
expect(result.mergedConfig.provider).toBe('anthropic');
|
|
346
|
+
expect(result.mergedConfig.model).toBe('claude-3-opus');
|
|
347
|
+
expect(result.mergedConfig.apiKeyEnv).toBe('ANTHROPIC_API_KEY');
|
|
348
|
+
expect(result.mergedConfig.timeoutMs).toBe(300000);
|
|
349
|
+
expect(result.mergedConfig.maxRetries).toBe(3);
|
|
350
|
+
} finally { rmTmpDir(tmp); }
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('returns mergedConfig as null when config resolution fails', () => {
|
|
354
|
+
const tmp = mkTmpDir();
|
|
355
|
+
writeConfig(tmp, 'version: [unterminated');
|
|
356
|
+
try {
|
|
357
|
+
const result = resolveRuntimeWithOverrides(tmp, { provider: 'openrouter' });
|
|
358
|
+
expect(isRuntimeConfigError(result.result)).toBe(true);
|
|
359
|
+
expect(result.mergedConfig).toBe(null);
|
|
360
|
+
} finally { rmTmpDir(tmp); }
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it('merges maxRetries and timeoutMs with nullish coalescing', () => {
|
|
364
|
+
const tmp = mkTmpDir();
|
|
365
|
+
writeConfig(tmp, yaml.dump({
|
|
366
|
+
version: 1,
|
|
367
|
+
features: {
|
|
368
|
+
prompt: { category: 'core', enabled: true },
|
|
369
|
+
code_tool_hook: { category: 'core', enabled: true },
|
|
370
|
+
defer_archive: { category: 'core', enabled: true },
|
|
371
|
+
},
|
|
372
|
+
workspace: { default: tmp },
|
|
373
|
+
runtimeProfiles: {
|
|
374
|
+
'pi-ai.test': { type: 'pi-ai', provider: 'anthropic', model: 'claude-3-5-sonnet', apiKeyEnv: 'ANTHROPIC_API_KEY', timeoutMs: 300000, maxRetries: 3 },
|
|
375
|
+
},
|
|
376
|
+
internalAgents: {
|
|
377
|
+
defaultRuntime: 'pi-ai.test',
|
|
378
|
+
agents: {
|
|
379
|
+
diagnostician: { enabled: true, runtimeProfile: 'pi-ai.test' },
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
}));
|
|
383
|
+
try {
|
|
384
|
+
const result = resolveRuntimeWithOverrides(tmp, {
|
|
385
|
+
maxRetries: 0,
|
|
386
|
+
timeoutMs: 0,
|
|
387
|
+
}, mockEnvWithKeys);
|
|
388
|
+
expect(result.mergedConfig).not.toBeNull();
|
|
389
|
+
if (!result.mergedConfig) throw new Error('Expected mergedConfig');
|
|
390
|
+
expect(result.mergedConfig.maxRetries).toBe(0);
|
|
391
|
+
expect(result.mergedConfig.timeoutMs).toBe(0);
|
|
392
|
+
} finally { rmTmpDir(tmp); }
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it('returns profile info from config even when CLI overrides are provided', () => {
|
|
396
|
+
const tmp = mkTmpDir();
|
|
397
|
+
writeConfig(tmp, yaml.dump({
|
|
398
|
+
version: 1,
|
|
399
|
+
features: {
|
|
400
|
+
prompt: { category: 'core', enabled: true },
|
|
401
|
+
code_tool_hook: { category: 'core', enabled: true },
|
|
402
|
+
defer_archive: { category: 'core', enabled: true },
|
|
403
|
+
},
|
|
404
|
+
workspace: { default: tmp },
|
|
405
|
+
runtimeProfiles: {
|
|
406
|
+
'pi-ai.test': { type: 'pi-ai', provider: 'anthropic', model: 'claude-3-5-sonnet', apiKeyEnv: 'ANTHROPIC_API_KEY' },
|
|
407
|
+
},
|
|
408
|
+
internalAgents: {
|
|
409
|
+
defaultRuntime: 'pi-ai.test',
|
|
410
|
+
agents: {
|
|
411
|
+
diagnostician: { enabled: true, runtimeProfile: 'pi-ai.test' },
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
}));
|
|
415
|
+
try {
|
|
416
|
+
const result = resolveRuntimeWithOverrides(tmp, {
|
|
417
|
+
provider: 'openrouter',
|
|
418
|
+
model: 'anthropic/claude-sonnet-4',
|
|
419
|
+
}, mockEnvWithKeys);
|
|
420
|
+
expect(result.runtimeProfileId).toBe('pi-ai.test');
|
|
421
|
+
expect(result.runtimeProfileLabel).toBe('pi-ai: anthropic/claude-3-5-sonnet');
|
|
422
|
+
} finally { rmTmpDir(tmp); }
|
|
423
|
+
});
|
|
424
|
+
});
|