@pi-unipi/background-tasks 2.6.3 → 2.9.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/package.json +6 -5
- package/src/__tests__/anthropic-attribution.test.ts +0 -195
- package/src/__tests__/config.test.ts +0 -137
- package/src/__tests__/core.test.ts +0 -493
- package/src/__tests__/delegate-artifacts.test.ts +0 -528
- package/src/__tests__/delegate-budget.test.ts +0 -456
- package/src/__tests__/delegate-launch.test.ts +0 -676
- package/src/__tests__/delegate-result-package.test.ts +0 -350
- package/src/__tests__/delegate-seed.test.ts +0 -392
- package/src/__tests__/durable-fs.test.ts +0 -559
- package/src/__tests__/extension-api.test.ts +0 -579
- package/src/__tests__/fusion-artifacts.test.ts +0 -1039
- package/src/__tests__/fusion-budget.test.ts +0 -1356
- package/src/__tests__/fusion-claude-cache.test.ts +0 -320
- package/src/__tests__/fusion-config.test.ts +0 -335
- package/src/__tests__/fusion-context-prompts.test.ts +0 -670
- package/src/__tests__/fusion-evaluation.test.ts +0 -315
- package/src/__tests__/fusion-extraction-equivalence.test.ts +0 -58
- package/src/__tests__/fusion-golden-bytes.test.ts +0 -35
- package/src/__tests__/fusion-high-cardinality.test.ts +0 -192
- package/src/__tests__/fusion-model-selector.test.ts +0 -205
- package/src/__tests__/fusion-orchestrator.test.ts +0 -1194
- package/src/__tests__/fusion-rpc.test.ts +0 -369
- package/src/__tests__/fusion-sdk.test.ts +0 -1226
- package/src/__tests__/fusion-v5-core.test.ts +0 -219
- package/src/__tests__/fusion-validate-orchestrator.test.ts +0 -240
- package/src/__tests__/fusion-web-fetch.test.ts +0 -485
- package/src/__tests__/fusion-workflows.test.ts +0 -59
- package/src/__tests__/helpers/delegate-deterministic-seed.ts +0 -109
- package/src/__tests__/helpers/delegate-seed-subprocess.ts +0 -10
- package/src/__tests__/helpers/fusion-canonical-subprocess.ts +0 -21
- package/src/__tests__/helpers/fusion-canonical.ts +0 -140
- package/src/__tests__/helpers/fusion-fake-pi.ts +0 -279
- package/src/__tests__/helpers/fusion-golden-corpus.ts +0 -500
- package/src/__tests__/helpers/fusion-high-cardinality.ts +0 -140
- package/src/__tests__/helpers/normalize.ts +0 -22
- package/src/__tests__/helpers/pi-hook-contract-evidence.json +0 -18
- package/src/__tests__/pi-launch.test.ts +0 -202
- package/src/__tests__/registry.test.ts +0 -1580
- package/src/__tests__/scripted-provider/delegate-ambient-provider.test.ts +0 -130
- package/src/__tests__/scripted-provider/delegate-child-guard.test.ts +0 -631
- package/src/__tests__/scripted-provider/delegate-guard-provider.ts +0 -403
- package/src/__tests__/scripted-provider/follow-up.test.ts +0 -448
- package/src/__tests__/scripted-provider/fusion-output-recovery.test.ts +0 -132
- package/src/__tests__/scripted-provider/fusion-reason.test.ts +0 -310
- package/src/__tests__/scripted-provider/fusion-runtime-guard.test.ts +0 -163
- package/src/__tests__/scripted-provider/hook-contract-provider.ts +0 -179
- package/src/__tests__/scripted-provider/hook-probe-a.ts +0 -3
- package/src/__tests__/scripted-provider/hook-probe-b.ts +0 -3
- package/src/__tests__/scripted-provider/hook-probe-extension.ts +0 -126
- package/src/__tests__/scripted-provider/output-recovery-provider.ts +0 -153
- package/src/__tests__/scripted-provider/pi-hook-contract-evidence.json +0 -18
- package/src/__tests__/scripted-provider/pi-hook-contract.test.ts +0 -477
- package/src/__tests__/scripted-provider/runtime-guard-probe.ts +0 -28
- package/src/__tests__/scripted-provider/runtime-guard-provider.ts +0 -49
- package/src/__tests__/scripted-provider/scripted-provider-extension.ts +0 -408
- package/src/__tests__/task-manager.test.ts +0 -479
- package/src/__tests__/windows-taskkill.test.ts +0 -161
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
import { describe, it } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { Type } from 'typebox';
|
|
4
|
-
import { streamSimple as streamSimpleAnthropic } from '@earendil-works/pi-ai/api/anthropic-messages';
|
|
5
|
-
import type { Context, Model } from '@earendil-works/pi-ai';
|
|
6
|
-
import { isJsonObject, type JsonObject } from '../types.js';
|
|
7
|
-
import { rewriteAnthropicRequestPayload } from '../anthropic-attribution.js';
|
|
8
|
-
import {
|
|
9
|
-
FUSION_CLAUDE_CACHE_BREAKPOINT_LIMIT,
|
|
10
|
-
FUSION_CLAUDE_CACHE_OBSERVATION_SCHEMA_VERSION,
|
|
11
|
-
FUSION_CLAUDE_CACHE_RETENTION_ENV,
|
|
12
|
-
FUSION_CLAUDE_PROMPT_CACHING_SCOPE_BETA,
|
|
13
|
-
applyFusionClaudePromptCachingScopeHeader,
|
|
14
|
-
nonAnthropicFusionCacheObservation,
|
|
15
|
-
normalizeFusionClaudeCachePayload,
|
|
16
|
-
resolveFusionClaudeCachePolicy,
|
|
17
|
-
type FusionClaudeCacheNormalization,
|
|
18
|
-
} from '../fusion/claude-cache.js';
|
|
19
|
-
|
|
20
|
-
const SHORT = { type: 'ephemeral' } as const;
|
|
21
|
-
const LONG = { type: 'ephemeral', ttl: '1h' } as const;
|
|
22
|
-
|
|
23
|
-
function controls(payload: JsonObject): unknown[] {
|
|
24
|
-
const found: unknown[] = [];
|
|
25
|
-
const inspect = (value: unknown): void => {
|
|
26
|
-
if (
|
|
27
|
-
typeof value === 'object' &&
|
|
28
|
-
value !== null &&
|
|
29
|
-
!Array.isArray(value) &&
|
|
30
|
-
Object.hasOwn(value, 'cache_control')
|
|
31
|
-
) {
|
|
32
|
-
found.push(Reflect.get(value, 'cache_control'));
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
if (Array.isArray(payload['system'])) payload['system'].forEach(inspect);
|
|
36
|
-
if (Array.isArray(payload['tools'])) payload['tools'].forEach(inspect);
|
|
37
|
-
if (Array.isArray(payload['messages'])) {
|
|
38
|
-
for (const message of payload['messages']) {
|
|
39
|
-
if (!isJsonObject(message)) continue;
|
|
40
|
-
const content = message['content'];
|
|
41
|
-
if (Array.isArray(content)) content.forEach(inspect);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return found;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function nativePayload(): JsonObject {
|
|
48
|
-
return {
|
|
49
|
-
model: 'claude-opus-4-8',
|
|
50
|
-
system: [
|
|
51
|
-
{ type: 'text', text: 'Claude Code identity', cache_control: SHORT },
|
|
52
|
-
{ type: 'text', text: 'Fusion system', cache_control: SHORT },
|
|
53
|
-
],
|
|
54
|
-
tools: [{ name: 'read', input_schema: {}, cache_control: SHORT }],
|
|
55
|
-
messages: [
|
|
56
|
-
{
|
|
57
|
-
role: 'user',
|
|
58
|
-
content: [{ type: 'text', text: 'request', cache_control: SHORT }],
|
|
59
|
-
},
|
|
60
|
-
],
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
void describe('Fusion Claude cache policy', () => {
|
|
65
|
-
void it('defaults to one hour and upgrades only Pi-selected native breakpoints', () => {
|
|
66
|
-
const original = nativePayload();
|
|
67
|
-
const normalized = normalizeFusionClaudeCachePayload({
|
|
68
|
-
payload: original,
|
|
69
|
-
requestOrdinal: 3,
|
|
70
|
-
env: {},
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
assert.deepEqual(resolveFusionClaudeCachePolicy({}), {
|
|
74
|
-
retention: 'long',
|
|
75
|
-
source: 'default',
|
|
76
|
-
});
|
|
77
|
-
assert.equal(
|
|
78
|
-
normalized.observation.schema_version,
|
|
79
|
-
FUSION_CLAUDE_CACHE_OBSERVATION_SCHEMA_VERSION,
|
|
80
|
-
);
|
|
81
|
-
assert.equal(normalized.observation.requested_retention, 'long');
|
|
82
|
-
assert.equal(normalized.observation.effective_retention, 'long');
|
|
83
|
-
assert.equal(normalized.observation.breakpoint_count, FUSION_CLAUDE_CACHE_BREAKPOINT_LIMIT);
|
|
84
|
-
assert.equal(normalized.observation.request_ordinal, 3);
|
|
85
|
-
assert.deepEqual(controls(normalized.payload), [LONG, LONG, LONG, LONG]);
|
|
86
|
-
assert.deepEqual(controls(original), [SHORT, SHORT, SHORT, SHORT]);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
void it("upgrades the exact four breakpoints emitted by Pi's OAuth Anthropic adapter", async () => {
|
|
90
|
-
const model: Model<'anthropic-messages'> = {
|
|
91
|
-
id: 'claude-opus-4-8',
|
|
92
|
-
name: 'Claude Opus 4.8',
|
|
93
|
-
api: 'anthropic-messages',
|
|
94
|
-
provider: 'anthropic',
|
|
95
|
-
baseUrl: 'http://127.0.0.1:1',
|
|
96
|
-
reasoning: false,
|
|
97
|
-
input: ['text'],
|
|
98
|
-
cost: { input: 1, output: 1, cacheRead: 1, cacheWrite: 1 },
|
|
99
|
-
contextWindow: 200_000,
|
|
100
|
-
maxTokens: 64_000,
|
|
101
|
-
};
|
|
102
|
-
const context: Context = {
|
|
103
|
-
systemPrompt: 'Fusion system',
|
|
104
|
-
messages: [{ role: 'user', content: 'request', timestamp: 1 }],
|
|
105
|
-
tools: [
|
|
106
|
-
{
|
|
107
|
-
name: 'read',
|
|
108
|
-
description: 'Read a file',
|
|
109
|
-
parameters: Type.Object({ path: Type.String() }),
|
|
110
|
-
},
|
|
111
|
-
],
|
|
112
|
-
};
|
|
113
|
-
let incoming: unknown;
|
|
114
|
-
let normalized: FusionClaudeCacheNormalization | undefined;
|
|
115
|
-
const stream = streamSimpleAnthropic(model, context, {
|
|
116
|
-
apiKey: 'sk-ant-oat-test-token',
|
|
117
|
-
env: {},
|
|
118
|
-
onPayload(payload) {
|
|
119
|
-
incoming = payload;
|
|
120
|
-
normalized = normalizeFusionClaudeCachePayload({
|
|
121
|
-
payload,
|
|
122
|
-
requestOrdinal: 1,
|
|
123
|
-
env: {},
|
|
124
|
-
});
|
|
125
|
-
throw new Error('cache payload captured before transport');
|
|
126
|
-
},
|
|
127
|
-
});
|
|
128
|
-
const result = await stream.result();
|
|
129
|
-
|
|
130
|
-
assert.equal(result.stopReason, 'error');
|
|
131
|
-
assert.match(result.errorMessage ?? '', /captured before transport/);
|
|
132
|
-
assert.ok(isJsonObject(incoming));
|
|
133
|
-
assert.deepEqual(controls(incoming), [SHORT, SHORT, SHORT, SHORT]);
|
|
134
|
-
assert.ok(normalized);
|
|
135
|
-
assert.deepEqual(controls(normalized.payload), [LONG, LONG, LONG, LONG]);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
void it('has Pi create one-hour breakpoints when Fusion sets the child policy env', async () => {
|
|
139
|
-
const model: Model<'anthropic-messages'> = {
|
|
140
|
-
id: 'claude-opus-4-8',
|
|
141
|
-
name: 'Claude Opus 4.8',
|
|
142
|
-
api: 'anthropic-messages',
|
|
143
|
-
provider: 'anthropic',
|
|
144
|
-
baseUrl: 'http://127.0.0.1:1',
|
|
145
|
-
reasoning: false,
|
|
146
|
-
input: ['text'],
|
|
147
|
-
cost: { input: 1, output: 1, cacheRead: 1, cacheWrite: 1 },
|
|
148
|
-
contextWindow: 200_000,
|
|
149
|
-
maxTokens: 64_000,
|
|
150
|
-
};
|
|
151
|
-
const context: Context = {
|
|
152
|
-
systemPrompt: 'Fusion system',
|
|
153
|
-
messages: [{ role: 'user', content: 'request', timestamp: 1 }],
|
|
154
|
-
tools: [
|
|
155
|
-
{
|
|
156
|
-
name: 'read',
|
|
157
|
-
description: 'Read a file',
|
|
158
|
-
parameters: Type.Object({ path: Type.String() }),
|
|
159
|
-
},
|
|
160
|
-
],
|
|
161
|
-
};
|
|
162
|
-
let payload: unknown;
|
|
163
|
-
const stream = streamSimpleAnthropic(model, context, {
|
|
164
|
-
apiKey: 'sk-ant-oat-test-token',
|
|
165
|
-
env: { [FUSION_CLAUDE_CACHE_RETENTION_ENV]: 'long' },
|
|
166
|
-
onPayload(value) {
|
|
167
|
-
payload = value;
|
|
168
|
-
throw new Error('native long cache payload captured before transport');
|
|
169
|
-
},
|
|
170
|
-
});
|
|
171
|
-
const result = await stream.result();
|
|
172
|
-
|
|
173
|
-
assert.equal(result.stopReason, 'error');
|
|
174
|
-
assert.match(result.errorMessage ?? '', /captured before transport/);
|
|
175
|
-
assert.ok(isJsonObject(payload));
|
|
176
|
-
assert.deepEqual(controls(payload), [LONG, LONG, LONG, LONG]);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
void it('preserves the provider call-level none posture through attribution rewrite', () => {
|
|
180
|
-
const payload: JsonObject = {
|
|
181
|
-
model: 'claude-opus-4-8',
|
|
182
|
-
system: [{ type: 'text', text: 'compaction system' }],
|
|
183
|
-
messages: [{ role: 'user', content: [{ type: 'text', text: 'summarize' }] }],
|
|
184
|
-
};
|
|
185
|
-
const rewritten = rewriteAnthropicRequestPayload({
|
|
186
|
-
payload,
|
|
187
|
-
ctx: {
|
|
188
|
-
model: { provider: 'anthropic', id: 'claude-opus-4-8' },
|
|
189
|
-
sessionManager: {
|
|
190
|
-
getSessionId: () => '11111111-2222-4333-8444-555555555555',
|
|
191
|
-
getBranch: () => [],
|
|
192
|
-
},
|
|
193
|
-
},
|
|
194
|
-
account: {
|
|
195
|
-
deviceId: 'd'.repeat(64),
|
|
196
|
-
accountUuid: 'aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee',
|
|
197
|
-
},
|
|
198
|
-
env: { [FUSION_CLAUDE_CACHE_RETENTION_ENV]: 'long' },
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
assert.ok(isJsonObject(rewritten));
|
|
202
|
-
assert.deepEqual(controls(rewritten), []);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
void it('adds the subscription prompt-caching scope beta exactly once', () => {
|
|
206
|
-
const headers: Record<string, string | null> = {
|
|
207
|
-
'Anthropic-Beta': 'claude-code-20250219,oauth-2025-04-20',
|
|
208
|
-
};
|
|
209
|
-
assert.equal(applyFusionClaudePromptCachingScopeHeader(headers), true);
|
|
210
|
-
assert.equal(applyFusionClaudePromptCachingScopeHeader(headers), true);
|
|
211
|
-
assert.equal(
|
|
212
|
-
headers['Anthropic-Beta'],
|
|
213
|
-
`claude-code-20250219,oauth-2025-04-20,${FUSION_CLAUDE_PROMPT_CACHING_SCOPE_BETA}`,
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
const empty: Record<string, string | null> = {};
|
|
217
|
-
applyFusionClaudePromptCachingScopeHeader(empty);
|
|
218
|
-
assert.equal(empty['anthropic-beta'], FUSION_CLAUDE_PROMPT_CACHING_SCOPE_BETA);
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
void it('supports explicit short and none without adding new breakpoints', () => {
|
|
222
|
-
const short = normalizeFusionClaudeCachePayload({
|
|
223
|
-
payload: {
|
|
224
|
-
...nativePayload(),
|
|
225
|
-
system: [{ type: 'text', text: 'system', cache_control: LONG }],
|
|
226
|
-
},
|
|
227
|
-
requestOrdinal: 1,
|
|
228
|
-
env: { [FUSION_CLAUDE_CACHE_RETENTION_ENV]: 'short' },
|
|
229
|
-
});
|
|
230
|
-
assert.equal(short.observation.source, FUSION_CLAUDE_CACHE_RETENTION_ENV);
|
|
231
|
-
assert.equal(short.observation.effective_retention, 'short');
|
|
232
|
-
assert.deepEqual(controls(short.payload), [SHORT, SHORT, SHORT]);
|
|
233
|
-
|
|
234
|
-
const disabled = normalizeFusionClaudeCachePayload({
|
|
235
|
-
payload: nativePayload(),
|
|
236
|
-
requestOrdinal: 1,
|
|
237
|
-
env: { [FUSION_CLAUDE_CACHE_RETENTION_ENV]: 'none' },
|
|
238
|
-
});
|
|
239
|
-
assert.equal(disabled.observation.requested_retention, 'none');
|
|
240
|
-
assert.equal(disabled.observation.effective_retention, 'none');
|
|
241
|
-
assert.equal(disabled.observation.breakpoint_count, 0);
|
|
242
|
-
assert.deepEqual(controls(disabled.payload), []);
|
|
243
|
-
|
|
244
|
-
const compaction = normalizeFusionClaudeCachePayload({
|
|
245
|
-
payload: { model: 'claude-opus-4-8', system: [{ type: 'text', text: 'summary' }] },
|
|
246
|
-
requestOrdinal: 2,
|
|
247
|
-
env: {},
|
|
248
|
-
});
|
|
249
|
-
assert.equal(compaction.observation.requested_retention, 'long');
|
|
250
|
-
assert.equal(compaction.observation.effective_retention, 'none');
|
|
251
|
-
assert.deepEqual(controls(compaction.payload), []);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
void it('falls back to short when the active model rejects long retention', () => {
|
|
255
|
-
const normalized = normalizeFusionClaudeCachePayload({
|
|
256
|
-
payload: nativePayload(),
|
|
257
|
-
requestOrdinal: 1,
|
|
258
|
-
env: {},
|
|
259
|
-
supportsLongCacheRetention: false,
|
|
260
|
-
});
|
|
261
|
-
assert.equal(normalized.observation.requested_retention, 'long');
|
|
262
|
-
assert.equal(normalized.observation.effective_retention, 'short');
|
|
263
|
-
assert.deepEqual(controls(normalized.payload), [SHORT, SHORT, SHORT, SHORT]);
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
void it('rejects malformed policy, controls, ordinals, and excess breakpoints', () => {
|
|
267
|
-
assert.throws(
|
|
268
|
-
() =>
|
|
269
|
-
resolveFusionClaudeCachePolicy({
|
|
270
|
-
[FUSION_CLAUDE_CACHE_RETENTION_ENV]: 'forever',
|
|
271
|
-
}),
|
|
272
|
-
/must be one of none, short, or long/,
|
|
273
|
-
);
|
|
274
|
-
assert.throws(
|
|
275
|
-
() =>
|
|
276
|
-
normalizeFusionClaudeCachePayload({
|
|
277
|
-
payload: { system: [{ cache_control: { type: 'forever' } }] },
|
|
278
|
-
requestOrdinal: 1,
|
|
279
|
-
env: {},
|
|
280
|
-
}),
|
|
281
|
-
/cache_control\.type/,
|
|
282
|
-
);
|
|
283
|
-
assert.throws(
|
|
284
|
-
() =>
|
|
285
|
-
normalizeFusionClaudeCachePayload({
|
|
286
|
-
payload: {},
|
|
287
|
-
requestOrdinal: 0,
|
|
288
|
-
env: {},
|
|
289
|
-
}),
|
|
290
|
-
/positive safe integer/,
|
|
291
|
-
);
|
|
292
|
-
assert.throws(
|
|
293
|
-
() =>
|
|
294
|
-
normalizeFusionClaudeCachePayload({
|
|
295
|
-
payload: {
|
|
296
|
-
system: Array.from({ length: 5 }, () => ({
|
|
297
|
-
type: 'text',
|
|
298
|
-
text: 'x',
|
|
299
|
-
cache_control: SHORT,
|
|
300
|
-
})),
|
|
301
|
-
},
|
|
302
|
-
requestOrdinal: 1,
|
|
303
|
-
env: {},
|
|
304
|
-
}),
|
|
305
|
-
/at most 4/,
|
|
306
|
-
);
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
void it('emits a closed not-applicable observation for non-Anthropic routes', () => {
|
|
310
|
-
assert.deepEqual(nonAnthropicFusionCacheObservation(7), {
|
|
311
|
-
schema_version: FUSION_CLAUDE_CACHE_OBSERVATION_SCHEMA_VERSION,
|
|
312
|
-
applicability: 'not_applicable',
|
|
313
|
-
source: 'not_applicable',
|
|
314
|
-
requested_retention: null,
|
|
315
|
-
effective_retention: null,
|
|
316
|
-
breakpoint_count: 0,
|
|
317
|
-
request_ordinal: 7,
|
|
318
|
-
});
|
|
319
|
-
});
|
|
320
|
-
});
|
|
@@ -1,335 +0,0 @@
|
|
|
1
|
-
import { describe, it } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { mkdtemp, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { tmpdir } from 'node:os';
|
|
6
|
-
import type { Api, Model } from '@earendil-works/pi-ai';
|
|
7
|
-
import {
|
|
8
|
-
CURRENT_MODEL_SELECTION,
|
|
9
|
-
defaultFusionModelConfig,
|
|
10
|
-
loadFusionModelConfig,
|
|
11
|
-
parseFusionModelConfig,
|
|
12
|
-
resolveFusionModels,
|
|
13
|
-
saveFusionModelConfig,
|
|
14
|
-
type FusionModelRegistry,
|
|
15
|
-
} from '../fusion/config.js';
|
|
16
|
-
import { parseJsonText } from '../types.js';
|
|
17
|
-
import { FUSION_MODEL_CONFIG_SCHEMA_VERSION, FusionError } from '../fusion/types.js';
|
|
18
|
-
|
|
19
|
-
function model(
|
|
20
|
-
provider: string,
|
|
21
|
-
id: string,
|
|
22
|
-
contextWindow = 1000,
|
|
23
|
-
baseUrl = provider === 'anthropic'
|
|
24
|
-
? 'https://api.anthropic.com'
|
|
25
|
-
: provider === 'openai-codex'
|
|
26
|
-
? 'https://chatgpt.com/backend-api'
|
|
27
|
-
: 'https://example.invalid',
|
|
28
|
-
): Model<Api> {
|
|
29
|
-
return {
|
|
30
|
-
id,
|
|
31
|
-
name: id,
|
|
32
|
-
api: 'openai-codex-responses',
|
|
33
|
-
provider,
|
|
34
|
-
baseUrl,
|
|
35
|
-
reasoning: true,
|
|
36
|
-
input: ['text'],
|
|
37
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
38
|
-
contextWindow,
|
|
39
|
-
maxTokens: 100,
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function registry(models: readonly Model<Api>[], oauth = true): FusionModelRegistry {
|
|
44
|
-
return {
|
|
45
|
-
getAll: () => [...models],
|
|
46
|
-
getAvailable: () => [...models],
|
|
47
|
-
find: (provider, modelId) =>
|
|
48
|
-
models.find((entry) => entry.provider === provider && entry.id === modelId),
|
|
49
|
-
isUsingOAuth: () => oauth,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
void describe('fusion model config', () => {
|
|
54
|
-
void it('defaults only when the config file is absent', async () => {
|
|
55
|
-
const dir = await mkdtemp(join(tmpdir(), 'pi-fusion-config-'));
|
|
56
|
-
try {
|
|
57
|
-
const path = join(dir, 'fusion-models.json');
|
|
58
|
-
const loaded = await loadFusionModelConfig(path);
|
|
59
|
-
assert.deepEqual(loaded.config, defaultFusionModelConfig());
|
|
60
|
-
assert.equal(loaded.revision.exists, false);
|
|
61
|
-
await writeFile(path, '{ broken', 'utf8');
|
|
62
|
-
await assert.rejects(loadFusionModelConfig(path), /not valid JSON/);
|
|
63
|
-
} finally {
|
|
64
|
-
await rm(dir, { recursive: true, force: true });
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
void it('parses the closed five-slot schema and rejects invalid shapes', () => {
|
|
69
|
-
const valid = {
|
|
70
|
-
schema_version: FUSION_MODEL_CONFIG_SCHEMA_VERSION,
|
|
71
|
-
candidates: [CURRENT_MODEL_SELECTION, 'openai-codex/gpt-5.5', 'anthropic/claude/opus'],
|
|
72
|
-
evaluator: CURRENT_MODEL_SELECTION,
|
|
73
|
-
merger: 'openai-codex/gpt-5.5',
|
|
74
|
-
};
|
|
75
|
-
assert.deepEqual(parseFusionModelConfig(valid), valid);
|
|
76
|
-
assert.throws(() => parseFusionModelConfig({ ...valid, extra: true }), /unknown key extra/);
|
|
77
|
-
assert.throws(
|
|
78
|
-
() => parseFusionModelConfig({ ...valid, candidates: [CURRENT_MODEL_SELECTION] }),
|
|
79
|
-
/exactly three/,
|
|
80
|
-
);
|
|
81
|
-
assert.throws(
|
|
82
|
-
() => parseFusionModelConfig({ ...valid, evaluator: 'gpt-5.5' }),
|
|
83
|
-
/qualified provider\/model/,
|
|
84
|
-
);
|
|
85
|
-
assert.throws(
|
|
86
|
-
() => parseFusionModelConfig({ ...valid, schema_version: 'v0' }),
|
|
87
|
-
/schema_version mismatch/,
|
|
88
|
-
);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
void it('resolves current, duplicate, and slash-containing model keys exactly', () => {
|
|
92
|
-
const current = model('openai-codex', 'gpt-5.5');
|
|
93
|
-
const slash = model('anthropic', 'claude/opus');
|
|
94
|
-
const config = parseFusionModelConfig({
|
|
95
|
-
schema_version: FUSION_MODEL_CONFIG_SCHEMA_VERSION,
|
|
96
|
-
candidates: [CURRENT_MODEL_SELECTION, 'anthropic/claude/opus', 'anthropic/claude/opus'],
|
|
97
|
-
evaluator: CURRENT_MODEL_SELECTION,
|
|
98
|
-
merger: 'anthropic/claude/opus',
|
|
99
|
-
});
|
|
100
|
-
const resolved = resolveFusionModels({
|
|
101
|
-
config,
|
|
102
|
-
modelRegistry: registry([current, slash]),
|
|
103
|
-
currentModel: current,
|
|
104
|
-
thinkingLevel: 'high',
|
|
105
|
-
});
|
|
106
|
-
assert.deepEqual(
|
|
107
|
-
resolved.candidates.map((entry) => entry.qualifiedId),
|
|
108
|
-
['openai-codex/gpt-5.5', 'anthropic/claude/opus', 'anthropic/claude/opus'],
|
|
109
|
-
);
|
|
110
|
-
assert.equal(resolved.evaluator.source, 'current');
|
|
111
|
-
assert.equal(resolved.merger.model, 'claude/opus');
|
|
112
|
-
assert.equal(resolved.merger.thinkingLevel, 'high');
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
void it('caps Anthropic route capacity to the attributed 200K subscription transport', () => {
|
|
116
|
-
const claude = model('anthropic', 'claude-sonnet-4-6', 1_000_000);
|
|
117
|
-
const config = parseFusionModelConfig({
|
|
118
|
-
schema_version: FUSION_MODEL_CONFIG_SCHEMA_VERSION,
|
|
119
|
-
candidates: [
|
|
120
|
-
'anthropic/claude-sonnet-4-6',
|
|
121
|
-
'anthropic/claude-sonnet-4-6',
|
|
122
|
-
'anthropic/claude-sonnet-4-6',
|
|
123
|
-
],
|
|
124
|
-
evaluator: 'anthropic/claude-sonnet-4-6',
|
|
125
|
-
merger: 'anthropic/claude-sonnet-4-6',
|
|
126
|
-
});
|
|
127
|
-
const resolved = resolveFusionModels({
|
|
128
|
-
config,
|
|
129
|
-
modelRegistry: registry([claude]),
|
|
130
|
-
currentModel: claude,
|
|
131
|
-
thinkingLevel: 'high',
|
|
132
|
-
});
|
|
133
|
-
assert.equal(resolved.candidates[0].contextWindow, 200_000);
|
|
134
|
-
assert.equal(resolved.evaluator.contextWindow, 200_000);
|
|
135
|
-
assert.equal(resolved.merger.contextWindow, 200_000);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
void it('rejects frontier models unless they use a supported subscription OAuth route', () => {
|
|
139
|
-
const codex = model('openai-codex', 'gpt-5.5');
|
|
140
|
-
assert.throws(
|
|
141
|
-
() =>
|
|
142
|
-
resolveFusionModels({
|
|
143
|
-
config: defaultFusionModelConfig(),
|
|
144
|
-
modelRegistry: registry([codex], false),
|
|
145
|
-
currentModel: codex,
|
|
146
|
-
thinkingLevel: 'medium',
|
|
147
|
-
}),
|
|
148
|
-
/not using subscription OAuth/,
|
|
149
|
-
);
|
|
150
|
-
const metered = model('openrouter', 'openai/gpt-5.5');
|
|
151
|
-
assert.throws(
|
|
152
|
-
() =>
|
|
153
|
-
resolveFusionModels({
|
|
154
|
-
config: defaultFusionModelConfig(),
|
|
155
|
-
modelRegistry: registry([metered], true),
|
|
156
|
-
currentModel: metered,
|
|
157
|
-
thinkingLevel: 'medium',
|
|
158
|
-
}),
|
|
159
|
-
/frontier-model API channel/,
|
|
160
|
-
);
|
|
161
|
-
for (const azureModel of ['o1', 'o3', 'o4-mini']) {
|
|
162
|
-
const azure = model('azure-openai-responses', azureModel);
|
|
163
|
-
assert.throws(
|
|
164
|
-
() =>
|
|
165
|
-
resolveFusionModels({
|
|
166
|
-
config: defaultFusionModelConfig(),
|
|
167
|
-
modelRegistry: registry([azure], true),
|
|
168
|
-
currentModel: azure,
|
|
169
|
-
thinkingLevel: 'medium',
|
|
170
|
-
}),
|
|
171
|
-
/frontier-model API channel/,
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
const aliasedOModel = model('custom-metered', 'o3-mini');
|
|
175
|
-
assert.throws(
|
|
176
|
-
() =>
|
|
177
|
-
resolveFusionModels({
|
|
178
|
-
config: defaultFusionModelConfig(),
|
|
179
|
-
modelRegistry: registry([aliasedOModel], true),
|
|
180
|
-
currentModel: aliasedOModel,
|
|
181
|
-
thinkingLevel: 'medium',
|
|
182
|
-
}),
|
|
183
|
-
/frontier-model API channel/,
|
|
184
|
-
);
|
|
185
|
-
for (const endpoint of [
|
|
186
|
-
'https://api.openai.com/v1',
|
|
187
|
-
'https://api.anthropic.com',
|
|
188
|
-
'https://openrouter.ai/api/v1',
|
|
189
|
-
'https://chatgpt.com/backend-api',
|
|
190
|
-
'https://tenant.openai.azure.com/openai/v1',
|
|
191
|
-
'https://tenant.cognitiveservices.azure.com/openai/v1',
|
|
192
|
-
'https://tenant.ai.azure.com/openai/v1',
|
|
193
|
-
]) {
|
|
194
|
-
const endpointAlias = model('custom', 'prod', 1000, endpoint);
|
|
195
|
-
assert.throws(
|
|
196
|
-
() =>
|
|
197
|
-
resolveFusionModels({
|
|
198
|
-
config: defaultFusionModelConfig(),
|
|
199
|
-
modelRegistry: registry([endpointAlias], true),
|
|
200
|
-
currentModel: endpointAlias,
|
|
201
|
-
thinkingLevel: 'medium',
|
|
202
|
-
}),
|
|
203
|
-
/frontier-model API channel/,
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
void it('binds subscription OAuth routes to trusted endpoints and provider authentication', () => {
|
|
209
|
-
const redirected = model('openai-codex', 'gpt-5.5', 1000, 'https://example.invalid');
|
|
210
|
-
assert.throws(
|
|
211
|
-
() =>
|
|
212
|
-
resolveFusionModels({
|
|
213
|
-
config: defaultFusionModelConfig(),
|
|
214
|
-
modelRegistry: registry([redirected]),
|
|
215
|
-
currentModel: redirected,
|
|
216
|
-
thinkingLevel: 'medium',
|
|
217
|
-
}),
|
|
218
|
-
/trusted Pi subscription endpoint/,
|
|
219
|
-
);
|
|
220
|
-
const overridden = {
|
|
221
|
-
...model('anthropic', 'claude-opus-5'),
|
|
222
|
-
headers: { Authorization: 'Bearer direct-credential' },
|
|
223
|
-
};
|
|
224
|
-
assert.throws(
|
|
225
|
-
() =>
|
|
226
|
-
resolveFusionModels({
|
|
227
|
-
config: defaultFusionModelConfig(),
|
|
228
|
-
modelRegistry: registry([overridden]),
|
|
229
|
-
currentModel: overridden,
|
|
230
|
-
thinkingLevel: 'medium',
|
|
231
|
-
}),
|
|
232
|
-
/overrides subscription authentication header/,
|
|
233
|
-
);
|
|
234
|
-
const codex = model('openai-codex', 'gpt-5.5', 1000, 'https://chatgpt.com/backend-api/');
|
|
235
|
-
assert.doesNotThrow(() =>
|
|
236
|
-
resolveFusionModels({
|
|
237
|
-
config: defaultFusionModelConfig(),
|
|
238
|
-
modelRegistry: registry([codex]),
|
|
239
|
-
currentModel: codex,
|
|
240
|
-
thinkingLevel: 'medium',
|
|
241
|
-
}),
|
|
242
|
-
);
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
void it('fails loudly for missing current or unavailable configured models', () => {
|
|
246
|
-
const current = model('openai-codex', 'gpt-5.5');
|
|
247
|
-
assert.throws(
|
|
248
|
-
() =>
|
|
249
|
-
resolveFusionModels({
|
|
250
|
-
config: defaultFusionModelConfig(),
|
|
251
|
-
modelRegistry: registry([current]),
|
|
252
|
-
currentModel: undefined,
|
|
253
|
-
thinkingLevel: 'medium',
|
|
254
|
-
}),
|
|
255
|
-
/no current model/,
|
|
256
|
-
);
|
|
257
|
-
const config = parseFusionModelConfig({
|
|
258
|
-
schema_version: FUSION_MODEL_CONFIG_SCHEMA_VERSION,
|
|
259
|
-
candidates: [CURRENT_MODEL_SELECTION, CURRENT_MODEL_SELECTION, CURRENT_MODEL_SELECTION],
|
|
260
|
-
evaluator: CURRENT_MODEL_SELECTION,
|
|
261
|
-
merger: 'anthropic/missing',
|
|
262
|
-
});
|
|
263
|
-
assert.throws(
|
|
264
|
-
() =>
|
|
265
|
-
resolveFusionModels({
|
|
266
|
-
config,
|
|
267
|
-
modelRegistry: registry([current]),
|
|
268
|
-
currentModel: current,
|
|
269
|
-
thinkingLevel: 'medium',
|
|
270
|
-
}),
|
|
271
|
-
/unavailable/,
|
|
272
|
-
);
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
void it('saves atomically with a revision guard and private file mode', async () => {
|
|
276
|
-
const dir = await mkdtemp(join(tmpdir(), 'pi-fusion-config-save-'));
|
|
277
|
-
try {
|
|
278
|
-
const path = join(dir, 'fusion-models.json');
|
|
279
|
-
const loaded = await loadFusionModelConfig(path);
|
|
280
|
-
const next = defaultFusionModelConfig();
|
|
281
|
-
const revision = await saveFusionModelConfig(path, next, loaded.revision);
|
|
282
|
-
assert.equal(revision.exists, true);
|
|
283
|
-
assert.match(await readFile(path, 'utf8'), /fusion-models\.v1/);
|
|
284
|
-
const mode = (await stat(path)).mode & 0o777;
|
|
285
|
-
// Windows has no POSIX permission bits; NTFS ACLs are not modelled here.
|
|
286
|
-
if (process.platform !== 'win32') assert.equal(mode, 0o600);
|
|
287
|
-
await writeFile(path, JSON.stringify(next), 'utf8');
|
|
288
|
-
const conflict = saveFusionModelConfig(path, next, revision);
|
|
289
|
-
await assert.rejects(conflict, (error: unknown) => {
|
|
290
|
-
assert.ok(error instanceof FusionError);
|
|
291
|
-
assert.equal(error.code, 'config_conflict');
|
|
292
|
-
return true;
|
|
293
|
-
});
|
|
294
|
-
} finally {
|
|
295
|
-
await rm(dir, { recursive: true, force: true });
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
void it('serializes concurrent saves so one stale writer fails instead of replacing the other', async () => {
|
|
300
|
-
const dir = await mkdtemp(join(tmpdir(), 'pi-fusion-config-race-'));
|
|
301
|
-
try {
|
|
302
|
-
const path = join(dir, 'fusion-models.json');
|
|
303
|
-
const loaded = await loadFusionModelConfig(path);
|
|
304
|
-
const first = parseFusionModelConfig({
|
|
305
|
-
...defaultFusionModelConfig(),
|
|
306
|
-
candidates: ['openai-codex/one', CURRENT_MODEL_SELECTION, CURRENT_MODEL_SELECTION],
|
|
307
|
-
});
|
|
308
|
-
const second = parseFusionModelConfig({
|
|
309
|
-
...defaultFusionModelConfig(),
|
|
310
|
-
candidates: ['openai-codex/two', CURRENT_MODEL_SELECTION, CURRENT_MODEL_SELECTION],
|
|
311
|
-
});
|
|
312
|
-
const results = await Promise.allSettled([
|
|
313
|
-
saveFusionModelConfig(path, first, loaded.revision),
|
|
314
|
-
saveFusionModelConfig(path, second, loaded.revision),
|
|
315
|
-
]);
|
|
316
|
-
assert.equal(results.filter((result) => result.status === 'fulfilled').length, 1);
|
|
317
|
-
const rejected = results.find((result) => result.status === 'rejected');
|
|
318
|
-
assert.ok(rejected);
|
|
319
|
-
if (rejected.status === 'rejected') {
|
|
320
|
-
assert.ok(rejected.reason instanceof FusionError);
|
|
321
|
-
assert.equal(rejected.reason.code, 'config_conflict');
|
|
322
|
-
}
|
|
323
|
-
const saved = parseFusionModelConfig(parseJsonText(await readFile(path, 'utf8')));
|
|
324
|
-
assert.ok(
|
|
325
|
-
saved.candidates[0] === 'openai-codex/one' || saved.candidates[0] === 'openai-codex/two',
|
|
326
|
-
);
|
|
327
|
-
assert.deepEqual(
|
|
328
|
-
(await readdir(dir)).filter((entry) => entry.endsWith('.lock')),
|
|
329
|
-
[],
|
|
330
|
-
);
|
|
331
|
-
} finally {
|
|
332
|
-
await rm(dir, { recursive: true, force: true });
|
|
333
|
-
}
|
|
334
|
-
});
|
|
335
|
-
});
|