@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.
Files changed (58) hide show
  1. package/package.json +6 -5
  2. package/src/__tests__/anthropic-attribution.test.ts +0 -195
  3. package/src/__tests__/config.test.ts +0 -137
  4. package/src/__tests__/core.test.ts +0 -493
  5. package/src/__tests__/delegate-artifacts.test.ts +0 -528
  6. package/src/__tests__/delegate-budget.test.ts +0 -456
  7. package/src/__tests__/delegate-launch.test.ts +0 -676
  8. package/src/__tests__/delegate-result-package.test.ts +0 -350
  9. package/src/__tests__/delegate-seed.test.ts +0 -392
  10. package/src/__tests__/durable-fs.test.ts +0 -559
  11. package/src/__tests__/extension-api.test.ts +0 -579
  12. package/src/__tests__/fusion-artifacts.test.ts +0 -1039
  13. package/src/__tests__/fusion-budget.test.ts +0 -1356
  14. package/src/__tests__/fusion-claude-cache.test.ts +0 -320
  15. package/src/__tests__/fusion-config.test.ts +0 -335
  16. package/src/__tests__/fusion-context-prompts.test.ts +0 -670
  17. package/src/__tests__/fusion-evaluation.test.ts +0 -315
  18. package/src/__tests__/fusion-extraction-equivalence.test.ts +0 -58
  19. package/src/__tests__/fusion-golden-bytes.test.ts +0 -35
  20. package/src/__tests__/fusion-high-cardinality.test.ts +0 -192
  21. package/src/__tests__/fusion-model-selector.test.ts +0 -205
  22. package/src/__tests__/fusion-orchestrator.test.ts +0 -1194
  23. package/src/__tests__/fusion-rpc.test.ts +0 -369
  24. package/src/__tests__/fusion-sdk.test.ts +0 -1226
  25. package/src/__tests__/fusion-v5-core.test.ts +0 -219
  26. package/src/__tests__/fusion-validate-orchestrator.test.ts +0 -240
  27. package/src/__tests__/fusion-web-fetch.test.ts +0 -485
  28. package/src/__tests__/fusion-workflows.test.ts +0 -59
  29. package/src/__tests__/helpers/delegate-deterministic-seed.ts +0 -109
  30. package/src/__tests__/helpers/delegate-seed-subprocess.ts +0 -10
  31. package/src/__tests__/helpers/fusion-canonical-subprocess.ts +0 -21
  32. package/src/__tests__/helpers/fusion-canonical.ts +0 -140
  33. package/src/__tests__/helpers/fusion-fake-pi.ts +0 -279
  34. package/src/__tests__/helpers/fusion-golden-corpus.ts +0 -500
  35. package/src/__tests__/helpers/fusion-high-cardinality.ts +0 -140
  36. package/src/__tests__/helpers/normalize.ts +0 -22
  37. package/src/__tests__/helpers/pi-hook-contract-evidence.json +0 -18
  38. package/src/__tests__/pi-launch.test.ts +0 -202
  39. package/src/__tests__/registry.test.ts +0 -1580
  40. package/src/__tests__/scripted-provider/delegate-ambient-provider.test.ts +0 -130
  41. package/src/__tests__/scripted-provider/delegate-child-guard.test.ts +0 -631
  42. package/src/__tests__/scripted-provider/delegate-guard-provider.ts +0 -403
  43. package/src/__tests__/scripted-provider/follow-up.test.ts +0 -448
  44. package/src/__tests__/scripted-provider/fusion-output-recovery.test.ts +0 -132
  45. package/src/__tests__/scripted-provider/fusion-reason.test.ts +0 -310
  46. package/src/__tests__/scripted-provider/fusion-runtime-guard.test.ts +0 -163
  47. package/src/__tests__/scripted-provider/hook-contract-provider.ts +0 -179
  48. package/src/__tests__/scripted-provider/hook-probe-a.ts +0 -3
  49. package/src/__tests__/scripted-provider/hook-probe-b.ts +0 -3
  50. package/src/__tests__/scripted-provider/hook-probe-extension.ts +0 -126
  51. package/src/__tests__/scripted-provider/output-recovery-provider.ts +0 -153
  52. package/src/__tests__/scripted-provider/pi-hook-contract-evidence.json +0 -18
  53. package/src/__tests__/scripted-provider/pi-hook-contract.test.ts +0 -477
  54. package/src/__tests__/scripted-provider/runtime-guard-probe.ts +0 -28
  55. package/src/__tests__/scripted-provider/runtime-guard-provider.ts +0 -49
  56. package/src/__tests__/scripted-provider/scripted-provider-extension.ts +0 -408
  57. package/src/__tests__/task-manager.test.ts +0 -479
  58. package/src/__tests__/windows-taskkill.test.ts +0 -161
@@ -1,1356 +0,0 @@
1
- import { describe, it } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { spawnSync } from 'node:child_process';
4
- import { mkdtemp, readFile, rm } from 'node:fs/promises';
5
- import { join } from 'node:path';
6
- import { tmpdir } from 'node:os';
7
- import { fileURLToPath } from 'node:url';
8
- import { parseJsonText } from '../types.js';
9
- import {
10
- FUSION_CALIBRATED_BYTES_PER_TOKEN,
11
- FUSION_CANDIDATE_MAX_OUTPUT_BYTES,
12
- FUSION_MIN_CANONICAL_INPUT_TOKENS,
13
- FUSION_MIN_CONTEXT_WINDOW_TOKENS,
14
- FUSION_DIAGNOSTICS_MAX_BYTES,
15
- FUSION_EVALUATION_MAX_OUTPUT_BYTES,
16
- FUSION_FRAMING_RESERVE_TOKENS,
17
- FUSION_RESERVED_OUTPUT_TOKENS,
18
- FUSION_SAFETY_RESERVE_TOKENS,
19
- FUSION_UTILIZATION_WARNING_THRESHOLD_BASIS_POINTS,
20
- FusionBudget,
21
- assertChildOutputWithinContract,
22
- fusionLimitingRoute,
23
- fusionRouteCapacities,
24
- fusionTokenUpperBound,
25
- } from '../fusion/budget.js';
26
- import { FusionOrchestrator, type FusionChildRunner } from '../fusion/orchestrator.js';
27
- import {
28
- TOKEN_BUDGET_AFFINE_F_TOKENS,
29
- TOKEN_BUDGET_CALIBRATION_CORPUS_MIN_WHITESPACE_FRACTION_X10000,
30
- TOKEN_BUDGET_CONSERVATIVE_RATE_X100,
31
- TOKEN_BUDGET_DENSE_ASCII_WHITESPACE_THRESHOLD_X10000,
32
- TOKEN_BUDGET_FAMILY_CALIBRATIONS,
33
- TOKEN_BUDGET_HAIRCUT_BASIS_POINTS,
34
- TOKEN_BUDGET_LARGE_PROMPT_MIN_BYTES,
35
- TOKEN_BUDGET_PROVABLE_RATE_X100,
36
- estimateInputTokens,
37
- knownTextSegment,
38
- maxKnownTextBytesForTokens,
39
- resolveTokenBudgetFamily,
40
- utf8ByteClassBreakdown,
41
- } from '../context-token-budget.js';
42
- import { defaultFusionModelConfig } from '../fusion/config.js';
43
- import { buildFusionCanonicalInput } from '../fusion/context.js';
44
- import { buildFusionCleanTaskCanonicalInput } from '../fusion/clean-context.js';
45
- import {
46
- FUSION_CANDIDATE_INSPECT_SYSTEM_PROMPT,
47
- FUSION_CANDIDATE_RESEARCH_SYSTEM_PROMPT,
48
- FUSION_CANDIDATE_SYSTEM_PROMPT,
49
- buildCandidatePrompt,
50
- } from '../fusion/prompts.js';
51
- import {
52
- FUSION_COMMAND_CONTEXT_POLICY_ID,
53
- FUSION_CONTEXT_TRANSFORM_ID,
54
- FUSION_EVALUATION_SCHEMA_VERSION,
55
- FUSION_INPUT_SCHEMA_VERSION,
56
- FusionError,
57
- type FusionCanonicalInputV3,
58
- type FusionChildRunResult,
59
- type FusionBudgetStage,
60
- type FusionEvaluationV1,
61
- type FusionStageBudgetPlanEntry,
62
- type ResolvedFusionModel,
63
- type ResolvedFusionModels,
64
- } from '../fusion/types.js';
65
- import type { RunPiChildOptions } from '../fusion/pi-child.js';
66
- import {
67
- FUSION_INVESTIGATE_WORKFLOW,
68
- FUSION_RESEARCH_WORKFLOW,
69
- FUSION_VALIDATE_WORKFLOW,
70
- } from '../fusion/workflows.js';
71
- import { emptyLedger, sessionWith, userMessage } from './helpers/fusion-canonical.js';
72
-
73
- const ledger = emptyLedger(FUSION_COMMAND_CONTEXT_POLICY_ID);
74
- const packageRoot = fileURLToPath(new URL('../', import.meta.url));
75
-
76
- function ceilDiv(numerator: number, denominator: number): number {
77
- if (numerator === 0) return 0;
78
- return Math.floor((numerator - 1) / denominator) + 1;
79
- }
80
-
81
- function resolved(qualifiedId: string, contextWindow: number, maxOutputTokens = 32_768): ResolvedFusionModel {
82
- const slash = qualifiedId.indexOf('/');
83
- return {
84
- selection: '$current',
85
- source: 'current',
86
- provider: qualifiedId.slice(0, slash),
87
- model: qualifiedId.slice(slash + 1),
88
- qualifiedId,
89
- thinkingLevel: 'high',
90
- contextWindow,
91
- maxOutputTokens,
92
- };
93
- }
94
-
95
- /** Mirrors the real reported panel: two large-window models plus a smaller one. */
96
- function models(options: { small?: number; large?: number } = {}): ResolvedFusionModels {
97
- const large = options.large ?? 272_000;
98
- const small = options.small ?? 200_000;
99
- return {
100
- candidates: [
101
- resolved('openai-codex/gpt-5.6-sol', large),
102
- resolved('openai-codex/gpt-5.6-terra', large),
103
- resolved('openai-codex/gpt-5.4-mini', small),
104
- ],
105
- evaluator: resolved('openai-codex/gpt-5.6-sol', large),
106
- merger: resolved('openai-codex/gpt-5.6-sol', large),
107
- };
108
- }
109
-
110
- function canonicalInput(text: string): FusionCanonicalInputV3 {
111
- return {
112
- schema_version: FUSION_INPUT_SCHEMA_VERSION,
113
- cwd: '/tmp/project',
114
- system_prompt: 'system',
115
- request: {
116
- source: 'command',
117
- authority: 'directive_over_projected_conversation',
118
- text: 'solve',
119
- sha256: 'b'.repeat(64),
120
- },
121
- conversation_projection: {
122
- policy: {
123
- id: FUSION_COMMAND_CONTEXT_POLICY_ID,
124
- transform: FUSION_CONTEXT_TRANSFORM_ID,
125
- version: 2,
126
- receipt_format: 'omitted_activity.v2',
127
- user_text: 'verbatim',
128
- assistant_text: 'verbatim',
129
- assistant_thinking: 'ledger_only',
130
- tool_call_arguments: 'ledger_only',
131
- tool_results: 'ledger_only',
132
- tool_payload_preview_bytes: 0,
133
- images: 'marker_or_ledger_only',
134
- unknown_block_behavior: 'error',
135
- },
136
- branch_filter: {
137
- id: 'exclude-active-fusion-subtree-v1',
138
- tool_name: 'fusion_reason',
139
- tool_call_id: null,
140
- active_tool_call_leaf_excluded: false,
141
- },
142
- entries: [['t', 'u', 0, 0, text]],
143
- accounting: {
144
- message_count: 1,
145
- included_text_entry_count: 1,
146
- included_user_text_bytes: Buffer.byteLength(text, 'utf8'),
147
- included_assistant_text_bytes: 0,
148
- included_image_marker_count: 0,
149
- empty_text_block_count: 0,
150
- omitted_run_count: 0,
151
- omitted_event_count: 0,
152
- omitted_thinking_bytes: 0,
153
- omitted_tool_call_count: 0,
154
- omitted_tool_call_argument_bytes: 0,
155
- omitted_tool_result_text_count: 0,
156
- omitted_tool_result_text_bytes: 0,
157
- omitted_tool_result_image_count: 0,
158
- omitted_tool_result_image_bytes: 0,
159
- tool_call_names: [],
160
- ledger_entry_count: 0,
161
- ledger_root_sha256: 'a'.repeat(64),
162
- omission_receipt_utf8_bytes: 0,
163
- },
164
- },
165
- };
166
- }
167
-
168
- function canonicalInputWithPromptBytes(targetBytes: number): FusionCanonicalInputV3 {
169
- let textBytes = targetBytes - Buffer.byteLength(buildCandidatePrompt(canonicalInput('')), 'utf8');
170
- assert.ok(textBytes >= 0, 'target must leave room for fixture text');
171
- for (let attempt = 0; attempt < 10; attempt += 1) {
172
- const input = canonicalInput('x'.repeat(textBytes));
173
- const actual = Buffer.byteLength(buildCandidatePrompt(input), 'utf8');
174
- if (actual === targetBytes) return input;
175
- textBytes += targetBytes - actual;
176
- assert.ok(textBytes >= 0, 'target adjustment must stay non-negative');
177
- }
178
- const finalInput = canonicalInput('x'.repeat(textBytes));
179
- assert.equal(Buffer.byteLength(buildCandidatePrompt(finalInput), 'utf8'), targetBytes);
180
- return finalInput;
181
- }
182
-
183
- function planEntry(
184
- entries: readonly FusionStageBudgetPlanEntry[],
185
- stage: FusionBudgetStage,
186
- slot?: 1 | 2 | 3,
187
- ): FusionStageBudgetPlanEntry {
188
- const found = entries.find((entry) => entry.budget_stage === stage && entry.slot === slot);
189
- assert.ok(found, `${stage} entry must exist`);
190
- return found;
191
- }
192
-
193
- function evaluation(): FusionEvaluationV1 {
194
- const one = (id: 'A' | 'B' | 'C') => ({
195
- candidate_id: id,
196
- summary: id,
197
- strengths: [id],
198
- limitations: [id],
199
- useful_contributions: [id],
200
- risks: [id],
201
- });
202
- return {
203
- schema_version: FUSION_EVALUATION_SCHEMA_VERSION,
204
- candidate_assessments: [one('A'), one('B'), one('C')],
205
- agreements: ['agree'],
206
- conflicts: [],
207
- synthesis_plan: {
208
- must_include: [{ candidate_id: 'A', contribution: 'a' }],
209
- must_resolve: [],
210
- must_avoid: [],
211
- },
212
- };
213
- }
214
-
215
- function childResult(options: RunPiChildOptions, text: string): FusionChildRunResult {
216
- const result: FusionChildRunResult = {
217
- stage: options.stage,
218
- attempt: options.attempt,
219
- provider: options.model.provider,
220
- model: options.model.model,
221
- qualifiedId: options.model.qualifiedId,
222
- text,
223
- usage: {
224
- input: 1,
225
- output: 1,
226
- cacheRead: 0,
227
- cacheWrite: 0,
228
- totalTokens: 2,
229
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
230
- },
231
- events: Buffer.alloc(0),
232
- stderr: Buffer.alloc(0),
233
- exitCode: 0,
234
- signal: null,
235
- };
236
- if (options.slot !== undefined) result.slot = options.slot;
237
- return result;
238
- }
239
-
240
- interface RunOutcome {
241
- error: FusionError;
242
- calls: readonly RunPiChildOptions[];
243
- root: string;
244
- artifactDir: string;
245
- }
246
-
247
- async function runExpectingFailure(
248
- input: FusionCanonicalInputV3,
249
- runner: FusionChildRunner,
250
- resolvedModels: ResolvedFusionModels = models(),
251
- ): Promise<RunOutcome> {
252
- const root = await mkdtemp(join(tmpdir(), 'pi-fusion-budget-'));
253
- const calls: RunPiChildOptions[] = [];
254
- const tracking: FusionChildRunner = async (options) => {
255
- calls.push(options);
256
- return runner(options);
257
- };
258
- const orchestrator = new FusionOrchestrator({ childRunner: tracking });
259
- let thrown: unknown;
260
- try {
261
- await orchestrator.run({
262
- source: 'command',
263
- cwd: root,
264
- canonicalInput: input,
265
- canonicalInputSerialized: JSON.stringify(input),
266
- contextLedger: ledger,
267
- config: defaultFusionModelConfig(),
268
- models: resolvedModels,
269
- });
270
- } catch (error) {
271
- thrown = error;
272
- }
273
- assert.ok(thrown instanceof FusionError, 'run must fail with a FusionError');
274
- return { error: thrown, calls, root, artifactDir: thrown.artifactDir ?? '' };
275
- }
276
-
277
- function assertBudgetError(
278
- error: FusionError,
279
- stage: 'candidate' | 'evaluation' | 'evaluation_repair' | 'merge',
280
- ): void {
281
- assert.ok(
282
- error.code === 'prompt_budget_exceeded_forecast' ||
283
- error.code === 'prompt_budget_exceeded_measured',
284
- );
285
- assert.equal(error.childCreated, false, 'budget rejection must not claim a child was created');
286
- const budget = error.budget;
287
- assert.ok(budget, 'budget failure must carry structured detail');
288
- assert.equal(budget.budget_stage, stage);
289
- assert.ok(budget.measured_utf8_bytes > 0);
290
- assert.ok(budget.measured_input_tokens_upper_bound > budget.allowed_input_tokens);
291
- assert.equal(budget.required_allowed_tokens, budget.measured_input_tokens_upper_bound);
292
- assert.equal(budget.calibration_version, 'pi-background-tasks.input-token-calibration.v1');
293
- assert.ok(budget.rate_source.family.length > 0);
294
- assert.equal(budget.backed, budget.rate_source.backed);
295
- assert.equal(budget.dominant_byte_class, budget.rate_source.dominant_byte_class);
296
- assert.ok(budget.route_table.length > 0);
297
- assert.ok(budget.allowed_input_tokens > 0);
298
- assert.ok(budget.limiting_model.qualified_id.length > 0);
299
- assert.ok(budget.limiting_model.context_window_tokens > 0);
300
- assert.ok(budget.remediation.length > 0);
301
- assert.ok(budget.blockers.length > 0);
302
- // The human-readable message must name every actionable fact too.
303
- assert.match(error.message, /Primary blocking stage/);
304
- assert.doesNotMatch(error.message, /No child was created\./);
305
- assert.match(
306
- error.message,
307
- stage === 'candidate'
308
- ? /The candidate-\d child was not created\./
309
- : stage === 'evaluation_repair'
310
- ? /The evaluator-repair child was not created\./
311
- : stage === 'evaluation'
312
- ? /The evaluator child was not created\./
313
- : /The merger child was not created\./,
314
- );
315
- assert.match(error.message, /Nothing was clipped, dropped, or substituted/);
316
- assert.match(error.message, new RegExp(String(budget.measured_utf8_bytes)));
317
- assert.match(error.message, new RegExp(String(budget.allowed_input_tokens)));
318
- assert.match(error.message, new RegExp(budget.limiting_model.qualified_id));
319
- assert.match(error.message, /Remediation:/);
320
- }
321
-
322
- void describe('fusion stage budgets', () => {
323
- void it('ships the calibrated affine table with strict provenance guards', () => {
324
- assert.equal(fusionTokenUpperBound(0), TOKEN_BUDGET_AFFINE_F_TOKENS);
325
- assert.equal(FUSION_CALIBRATED_BYTES_PER_TOKEN.anthropic.rate_bytes_per_token_x100, 173);
326
- assert.equal(FUSION_CALIBRATED_BYTES_PER_TOKEN['openai-codex'].rate_bytes_per_token_x100, 289);
327
- assert.equal(FUSION_CALIBRATED_BYTES_PER_TOKEN.unknown.rate_bytes_per_token_x100, 100);
328
- for (const [family, entry] of Object.entries(FUSION_CALIBRATED_BYTES_PER_TOKEN)) {
329
- assert.equal(entry.affine_f_tokens, 512, family);
330
- assert.equal(Reflect.has(entry.provenance, 'sessions'), false, family);
331
- assert.equal(Reflect.has(entry.provenance, 'days'), false, family);
332
- if (family !== 'unknown') {
333
- assert.ok(entry.provenance.n >= 50, family);
334
- assert.equal(entry.provenance.backed, true, family);
335
- assert.ok(entry.provenance.observed_min_bpt_x1000 !== null, family);
336
- if (entry.provenance.observed_min_bpt_x1000 !== null) {
337
- const numerator = entry.provenance.observed_min_bpt_x1000 *
338
- (10_000 - TOKEN_BUDGET_HAIRCUT_BASIS_POINTS);
339
- assert.ok(
340
- entry.rate_bytes_per_token_x100 * 100_000 <= numerator,
341
- family,
342
- );
343
- assert.equal(
344
- entry.rate_bytes_per_token_x100,
345
- Math.floor(numerator / 100_000),
346
- `${family} rate must be floor-rounded after the haircut`,
347
- );
348
- }
349
- }
350
- }
351
- const backedRates = Object.values(FUSION_CALIBRATED_BYTES_PER_TOKEN)
352
- .filter((entry) => entry.provenance.backed)
353
- .map((entry) => entry.rate_bytes_per_token_x100);
354
- assert.ok(FUSION_CALIBRATED_BYTES_PER_TOKEN.unknown.rate_bytes_per_token_x100 <= Math.min(...backedRates));
355
- assert.equal(FUSION_CALIBRATED_BYTES_PER_TOKEN.unknown.provenance.backed, false);
356
- assert.ok(
357
- TOKEN_BUDGET_DENSE_ASCII_WHITESPACE_THRESHOLD_X10000 <
358
- TOKEN_BUDGET_CALIBRATION_CORPUS_MIN_WHITESPACE_FRACTION_X10000,
359
- );
360
- assert.deepEqual(FUSION_CALIBRATED_BYTES_PER_TOKEN, TOKEN_BUDGET_FAMILY_CALIBRATIONS);
361
- });
362
-
363
- void it('uses additive segment accounting rather than an unsafe blended divisor', () => {
364
- const estimate = estimateInputTokens({
365
- family: 'openai-codex',
366
- allowedInputTokens: 231_040,
367
- scope: 'fusion',
368
- segments: [
369
- { kind: 'known_text', bytes: 7_500, multibyteBytes: 0, denseBytes: 0 },
370
- { kind: 'known_text', bytes: 2_500, multibyteBytes: 2_500, denseBytes: 0 },
371
- ],
372
- });
373
- const arithmeticBlendRateX10000 = 75 * 289 + 25 * 100;
374
- const blendedTokens = ceilDiv(10_000 * 10_000, arithmeticBlendRateX10000) + 512;
375
- assert.ok(estimate.tokens >= blendedTokens);
376
- assert.equal(estimate.perSegment[1]?.multibyte_tokens, 1_250);
377
- assert.equal(estimate.perSegment[1]?.multibyte_provable_tokens, 2_500);
378
- assert.ok(estimate.advisory.input_tokens_if_multibyte_used_provable_ceiling > estimate.tokens);
379
- const contract = estimateInputTokens({
380
- family: 'openai-codex',
381
- allowedInputTokens: 231_040,
382
- scope: 'fusion',
383
- segments: [{ kind: 'unknown_output_contract', bytes: 4096, denseBytes: 0 }],
384
- });
385
- assert.equal(contract.perSegment[0]?.unknown_output_contract_tokens, 4096);
386
- });
387
-
388
- void it('keeps adversarial forecasts at or below bytes plus F while remaining deterministic', () => {
389
- const fixtures = [
390
- 'YWJjZA=='.repeat(1024),
391
- '0123456789abcdef'.repeat(1024),
392
- '漢字仮名交じり文'.repeat(1024),
393
- '👩‍👩‍👧‍👦'.repeat(1024),
394
- 'const x={a:1,b:[2,3,4]};'.repeat(1024),
395
- '!@#$%^&*()_+-=[]{}|;:,.<>?'.repeat(1024),
396
- ];
397
- for (const text of fixtures) {
398
- const breakdown = utf8ByteClassBreakdown(text);
399
- const estimate = estimateInputTokens({
400
- family: 'openai-codex',
401
- allowedInputTokens: 231_040,
402
- scope: 'fusion',
403
- segments: [
404
- {
405
- kind: 'known_text',
406
- bytes: breakdown.bytes,
407
- multibyteBytes: breakdown.multibyteBytes,
408
- denseBytes: breakdown.denseBytes,
409
- },
410
- ],
411
- });
412
- assert.ok(estimate.tokens <= breakdown.bytes + TOKEN_BUDGET_AFFINE_F_TOKENS);
413
- }
414
- });
415
-
416
- void it('is byte-identical across separate estimator processes', () => {
417
- const script = [
418
- "import { estimateInputTokens } from './context-token-budget.js';",
419
- "const result = estimateInputTokens({ family: 'openai-codex', allowedInputTokens: 231040, scope: 'fusion', segments: [{ kind: 'known_text', bytes: 290099, multibyteBytes: 0, denseBytes: 0 }] });",
420
- 'process.stdout.write(JSON.stringify(result));',
421
- ].join('\n');
422
- // tsx is hoisted to the workspace root in our monorepo
423
- const cli = join(packageRoot, '..', '..', 'node_modules', 'tsx', 'dist', 'cli.mjs');
424
- const first = spawnSync(process.execPath, ['--import', 'tsx', '--eval', script], { cwd: packageRoot, encoding: 'utf8' });
425
- const second = spawnSync(process.execPath, ['--import', 'tsx', '--eval', script], { cwd: packageRoot, encoding: 'utf8' });
426
- assert.equal(first.status, 0, first.stderr);
427
- assert.equal(second.status, 0, second.stderr);
428
- assert.equal(first.stdout, second.stdout);
429
- });
430
-
431
- void it('ceilings once per rate bucket so segmentation cannot add phantom tokens', () => {
432
- const segment = {
433
- kind: 'known_text' as const,
434
- bytes: TOKEN_BUDGET_LARGE_PROMPT_MIN_BYTES + 1_000,
435
- multibyteBytes: 0,
436
- denseBytes: 0,
437
- asciiWhitespaceBytes: 1_000,
438
- };
439
- const one = estimateInputTokens({
440
- family: 'openai-codex',
441
- allowedInputTokens: 231_040,
442
- scope: 'fusion',
443
- segments: [segment],
444
- });
445
- const two = estimateInputTokens({
446
- family: 'openai-codex',
447
- allowedInputTokens: 231_040,
448
- scope: 'fusion',
449
- segments: [
450
- { ...segment, bytes: Math.floor(segment.bytes / 2), asciiWhitespaceBytes: 500 },
451
- { ...segment, bytes: segment.bytes - Math.floor(segment.bytes / 2), asciiWhitespaceBytes: 500 },
452
- ],
453
- });
454
- assert.equal(one.rateSource.source, 'calibrated_large_window');
455
- assert.equal(two.rateSource.source, 'calibrated_large_window');
456
- assert.equal(one.tokens, two.tokens);
457
- assert.deepEqual(one.rate_buckets, two.rate_buckets);
458
- });
459
-
460
- void it('scope guard keeps calibrated codex rates out of small prompts and small windows', () => {
461
- const smallPrompt = estimateInputTokens({
462
- family: 'openai-codex',
463
- allowedInputTokens: 231_040,
464
- scope: 'fusion',
465
- segments: [knownTextSegment('word '.repeat(400))],
466
- });
467
- assert.equal(smallPrompt.byte_class_breakdown.total_bytes, 2_000);
468
- assert.equal(smallPrompt.rateSource.source, 'conservative_small_prompt');
469
- assert.equal(smallPrompt.rateSource.effective_rate_bytes_per_token_x100, TOKEN_BUDGET_CONSERVATIVE_RATE_X100);
470
-
471
- const edge: ResolvedFusionModels = {
472
- candidates: [
473
- resolved('openai-codex/gpt-5.5', FUSION_MIN_CONTEXT_WINDOW_TOKENS),
474
- resolved('openai-codex/gpt-5.5', 272_000),
475
- resolved('openai-codex/gpt-5.5', 272_000),
476
- ],
477
- evaluator: resolved('openai-codex/gpt-5.5', 272_000),
478
- merger: resolved('openai-codex/gpt-5.5', 272_000),
479
- };
480
- const budget = new FusionBudget(edge, FUSION_COMMAND_CONTEXT_POLICY_ID);
481
- const firstRoute = budget.routes[0];
482
- assert.ok(firstRoute);
483
- assert.equal(firstRoute.rate_source.source, 'conservative_capacity_guard');
484
- assert.throws(
485
- () => budget.assertStagePrompt('candidate', '', 'x'.repeat(23_674), 1),
486
- (error: unknown) => error instanceof FusionError && error.code === 'prompt_budget_exceeded_measured',
487
- );
488
- });
489
-
490
- void it('falls back for low-whitespace dense ASCII before a calibrated codex under-forecast can be admitted', () => {
491
- const dense = 'A'.repeat(600_000);
492
- const beforeRelaxedForecast = ceilDiv(600_000 * 100, 289) + TOKEN_BUDGET_AFFINE_F_TOKENS;
493
- assert.equal(beforeRelaxedForecast, 208_125);
494
- const estimate = estimateInputTokens({
495
- family: 'openai-codex',
496
- allowedInputTokens: 231_040,
497
- scope: 'fusion',
498
- segments: [knownTextSegment(dense)],
499
- });
500
- assert.equal(estimate.rateSource.source, 'conservative_dense_ascii_whitespace_gate');
501
- assert.equal(estimate.rateSource.dense_ascii_gate.measured_whitespace_fraction_x10000, 0);
502
- assert.equal(estimate.rateSource.dense_ascii_gate.decision, 'conservative_fallback');
503
- assert.equal(estimate.rateSource.dominant_byte_class, 'dense_ascii');
504
- assert.equal(estimate.tokens, 300_512);
505
- assert.ok(estimate.tokens > 231_040);
506
-
507
- const budget = new FusionBudget(models({ small: 267_904, large: 267_904 }), FUSION_COMMAND_CONTEXT_POLICY_ID);
508
- assert.throws(
509
- () => budget.assertStagePrompt('candidate', '', dense, 1),
510
- (error: unknown) => {
511
- assert.ok(error instanceof FusionError);
512
- assert.equal(error.code, 'prompt_budget_exceeded_measured');
513
- assert.equal(error.budget?.rate_source.source, 'conservative_dense_ascii_whitespace_gate');
514
- assert.equal(error.budget?.dominant_byte_class, 'dense_ascii');
515
- return true;
516
- },
517
- );
518
- });
519
-
520
- void it('splits input-only fatal checks from reservation warnings', () => {
521
- const budget = new FusionBudget(
522
- models({ small: 267_904, large: 267_904 }),
523
- FUSION_COMMAND_CONTEXT_POLICY_ID,
524
- );
525
- const reportedInputOnly = estimateInputTokens({
526
- family: 'openai-codex',
527
- allowedInputTokens: 231_040,
528
- scope: 'fusion',
529
- segments: [knownTextSegment('A'.repeat(290_099))],
530
- });
531
- assert.equal(reportedInputOnly.tokens, 145_562);
532
- assert.ok(reportedInputOnly.tokens <= 231_040);
533
- const warningPlan = budget.plan(canonicalInputWithPromptBytes(290_099));
534
- assert.equal(warningPlan.blockers.length, 0);
535
- assert.equal(warningPlan.warnings.some((entry) => entry.warning_kind === 'worst_case_reservation'), true);
536
- assert.doesNotThrow(() => budget.assertPlanFits(warningPlan, 'unit-test'));
537
-
538
- const fatalPlan = budget.plan(canonicalInputWithPromptBytes(800_000));
539
- assert.ok(fatalPlan.primary_blocker);
540
- assert.throws(
541
- () => budget.assertPlanFits(fatalPlan, 'unit-test'),
542
- (error: unknown) => error instanceof FusionError && error.code === 'prompt_budget_exceeded_forecast',
543
- );
544
- });
545
-
546
- void it('bases safety on the smallest configured byte capacity, not the largest token window', () => {
547
- const routes = fusionRouteCapacities(models({ small: 200_000, large: 1_000_000 }));
548
- const limiting = fusionLimitingRoute(routes);
549
- assert.equal(limiting.qualified_id, 'openai-codex/gpt-5.4-mini');
550
- assert.equal(limiting.context_window_tokens, 200_000);
551
- assert.equal(
552
- limiting.allowed_input_tokens,
553
- 200_000 -
554
- FUSION_RESERVED_OUTPUT_TOKENS -
555
- FUSION_FRAMING_RESERVE_TOKENS -
556
- FUSION_SAFETY_RESERVE_TOKENS,
557
- );
558
- // Even when the small model is the evaluator rather than a candidate.
559
- const evaluatorSmall: ResolvedFusionModels = {
560
- candidates: [
561
- resolved('p/big1', 1_000_000),
562
- resolved('p/big2', 1_000_000),
563
- resolved('p/big3', 1_000_000),
564
- ],
565
- evaluator: resolved('p/small', 200_000),
566
- merger: resolved('p/big1', 1_000_000),
567
- };
568
- assert.equal(
569
- fusionLimitingRoute(fusionRouteCapacities(evaluatorSmall)).qualified_id,
570
- 'p/small',
571
- );
572
- });
573
-
574
- void it('reserves each route configured maximum output instead of assuming the smaller Fusion response contract', () => {
575
- const routeModels: ResolvedFusionModels = {
576
- candidates: [
577
- resolved('openai-codex/gpt-5.6-sol', 272_000, 128_000),
578
- resolved('openai-codex/gpt-5.6-terra', 272_000, 128_000),
579
- resolved('openai-codex/gpt-5.5', 272_000, 128_000),
580
- ],
581
- evaluator: resolved('openai-codex/gpt-5.6-sol', 272_000, 128_000),
582
- merger: resolved('openai-codex/gpt-5.6-sol', 272_000, 128_000),
583
- };
584
- for (const route of fusionRouteCapacities(routeModels)) {
585
- assert.equal(route.reserved_output_tokens, 128_000);
586
- assert.equal(route.allowed_input_tokens, 139_904);
587
- }
588
- });
589
-
590
- void it('selects the byte-capacity limiting route when token ordering flips', () => {
591
- const mixed: ResolvedFusionModels = {
592
- candidates: [
593
- resolved('anthropic/claude-a', 400_000),
594
- resolved('openai-codex/gpt-5.6-sol', 272_000),
595
- resolved('openai-codex/gpt-5.6-terra', 272_000),
596
- ],
597
- evaluator: resolved('openai-codex/gpt-5.6-sol', 272_000),
598
- merger: resolved('openai-codex/gpt-5.6-sol', 272_000),
599
- };
600
- const routes = fusionRouteCapacities(mixed);
601
- const anthropic = routes[0];
602
- const codex = routes[1];
603
- assert.ok(anthropic);
604
- assert.ok(codex);
605
- assert.ok(anthropic.allowed_input_tokens > codex.allowed_input_tokens);
606
- assert.ok(anthropic.byte_capacity_utf8_bytes < codex.byte_capacity_utf8_bytes);
607
- assert.equal(fusionLimitingRoute(routes).qualified_id, 'anthropic/claude-a');
608
- });
609
-
610
- void it('rejects routes whose capacity is unknown or too small to hold input', () => {
611
- for (const contextWindow of [0, -1, Number.NaN, 1_000, 40_000]) {
612
- const bad: ResolvedFusionModels = {
613
- candidates: [
614
- resolved('p/a', 200_000),
615
- resolved('p/b', 200_000),
616
- resolved('p/c', contextWindow),
617
- ],
618
- evaluator: resolved('p/a', 200_000),
619
- merger: resolved('p/a', 200_000),
620
- };
621
- assert.throws(
622
- () => new FusionBudget(bad, FUSION_COMMAND_CONTEXT_POLICY_ID),
623
- (error: unknown) =>
624
- error instanceof FusionError &&
625
- error.code === 'model_capacity_unknown' &&
626
- error.childCreated === false,
627
- `context window ${String(contextWindow)} must be rejected`,
628
- );
629
- }
630
- });
631
-
632
- void it('forecasts each concrete stage against its assigned route', () => {
633
- const budget = new FusionBudget(models(), FUSION_COMMAND_CONTEXT_POLICY_ID);
634
- const input = canonicalInput('small');
635
- const plan = budget.plan(input);
636
- assert.equal(plan.schema_version, 'pi-background-tasks.fusion-budget-plan.v4');
637
- assert.equal(plan.policy.id, 'fusion-budget-policy-v4');
638
- assert.equal(plan.policy.route_output_reserve_strategy, 'max_fusion_contract_or_model_max');
639
- assert.equal(plan.stages.length, 6);
640
- assert.equal(planEntry(plan.stages, 'candidate', 1).route.role, 'candidate-1');
641
- assert.equal(planEntry(plan.stages, 'candidate', 2).route.role, 'candidate-2');
642
- assert.equal(planEntry(plan.stages, 'candidate', 3).route.role, 'candidate-3');
643
- assert.equal(planEntry(plan.stages, 'evaluation').route.role, 'evaluator');
644
- assert.equal(planEntry(plan.stages, 'evaluation_repair').conditional, true);
645
- assert.equal(planEntry(plan.stages, 'merge').route.role, 'merger');
646
- for (const entry of plan.stages) {
647
- assert.equal(entry.input_only_input_tokens_upper_bound, entry.input_only_estimate.tokens);
648
- assert.equal(entry.forecast_input_tokens_upper_bound, entry.reservation_estimate.tokens);
649
- assert.equal(entry.input_only_signed_headroom_tokens, entry.allowed_input_tokens - entry.input_only_input_tokens_upper_bound);
650
- assert.equal(entry.signed_headroom_tokens, entry.allowed_input_tokens - entry.forecast_input_tokens_upper_bound);
651
- }
652
- });
653
-
654
- void it('accepts a safe prompt at the affine boundary and rejects one byte past it', () => {
655
- const budget = new FusionBudget(models(), FUSION_COMMAND_CONTEXT_POLICY_ID);
656
- const allowedBytes = maxKnownTextBytesForTokens({
657
- family: 'openai-codex',
658
- allowedInputTokens: budget.allowedInputTokens,
659
- scope: 'fusion',
660
- });
661
- assert.doesNotThrow(() => {
662
- budget.assertStagePrompt('candidate', '', 'x'.repeat(allowedBytes), 3);
663
- });
664
- assert.throws(
665
- () => {
666
- budget.assertStagePrompt('candidate', '', 'x'.repeat(allowedBytes + 1), 3);
667
- },
668
- (error: unknown) => error instanceof FusionError && error.code === 'prompt_budget_exceeded_measured',
669
- );
670
- });
671
-
672
- void it('counts the child system prompt as input, not free space', () => {
673
- const budget = new FusionBudget(models(), FUSION_COMMAND_CONTEXT_POLICY_ID);
674
- const allowedBytes = maxKnownTextBytesForTokens({
675
- family: 'openai-codex',
676
- allowedInputTokens: budget.allowedInputTokens,
677
- scope: 'fusion',
678
- });
679
- assert.throws(
680
- () => {
681
- budget.assertStagePrompt('candidate', 'ab', 'x'.repeat(allowedBytes - 1), 3);
682
- },
683
- (error: unknown) => error instanceof FusionError && error.code === 'prompt_budget_exceeded_measured',
684
- );
685
- });
686
-
687
- void it('uses conservative multibyte fatal accounting while persisting the provable advisory ceiling', () => {
688
- const cjkEstimate = estimateInputTokens({
689
- family: 'openai-codex',
690
- allowedInputTokens: 231_040,
691
- scope: 'fusion',
692
- segments: [
693
- {
694
- kind: 'known_text',
695
- bytes: 100_000,
696
- multibyteBytes: 100_000,
697
- denseBytes: 0,
698
- asciiWhitespaceBytes: 0,
699
- },
700
- ],
701
- });
702
- assert.equal(cjkEstimate.tokens, 50_512);
703
- assert.equal(cjkEstimate.advisory.input_tokens_if_multibyte_used_provable_ceiling, 100_512);
704
- assert.equal(cjkEstimate.rateSource.dominant_byte_class, 'multibyte');
705
-
706
- const budget = new FusionBudget(models(), FUSION_COMMAND_CONTEXT_POLICY_ID);
707
- const variableAllowance = budget.allowedInputTokens - TOKEN_BUDGET_AFFINE_F_TOKENS;
708
- const chars = Math.floor((variableAllowance * 2) / 3) + 1;
709
- const dense = '漢'.repeat(chars);
710
- const breakdown = utf8ByteClassBreakdown(dense);
711
- assert.equal(breakdown.multibyteBytes, breakdown.bytes);
712
- assert.throws(
713
- () => {
714
- budget.assertStagePrompt('candidate', '', dense, 3);
715
- },
716
- (error: unknown) => {
717
- assert.ok(error instanceof FusionError);
718
- assert.equal(error.code, 'prompt_budget_exceeded_measured');
719
- assert.equal(error.budget?.dominant_byte_class, 'multibyte');
720
- assert.match(error.message, /multibyte UTF-8/);
721
- return true;
722
- },
723
- );
724
- });
725
-
726
- void it('rejects an oversized candidate stage before spawning a child', async () => {
727
- const oversized = canonicalInput('u'.repeat(600_000));
728
- const outcome = await runExpectingFailure(oversized, async (options) =>
729
- childResult(options, 'unreachable'),
730
- );
731
- try {
732
- assertBudgetError(outcome.error, 'candidate');
733
- assert.equal(outcome.calls.length, 0, 'no child may be launched when preflight rejects');
734
- } finally {
735
- await rm(outcome.root, { recursive: true, force: true });
736
- }
737
- });
738
-
739
- void it('allows the reported input-only incident while warning on reservation', async () => {
740
- const root = await mkdtemp(join(tmpdir(), 'pi-fusion-budget-incident-'));
741
- const calls: RunPiChildOptions[] = [];
742
- try {
743
- const runner: FusionChildRunner = async (options) => {
744
- calls.push(options);
745
- if (options.stage === 'candidate') return childResult(options, 'candidate answer');
746
- if (options.stage === 'evaluation') return childResult(options, JSON.stringify(evaluation()));
747
- return childResult(options, 'merged');
748
- };
749
- const input = canonicalInputWithPromptBytes(290_099);
750
- const resolvedModels = models({ small: 267_904, large: 267_904 });
751
- const result = await new FusionOrchestrator({ childRunner: runner }).run({
752
- source: 'command',
753
- cwd: root,
754
- canonicalInput: input,
755
- canonicalInputSerialized: JSON.stringify(input),
756
- contextLedger: ledger,
757
- config: defaultFusionModelConfig(),
758
- models: resolvedModels,
759
- });
760
- assert.equal(result.mergedText, 'merged');
761
- assert.equal(calls.length, 5);
762
- const planText = await readFile(join(root, result.details.artifact_dir, 'budget-plan.json'), 'utf8');
763
- const plan = parseJsonText(planText);
764
- assert.ok(typeof plan === 'object' && plan !== null);
765
- assert.equal(Reflect.get(plan, 'primary_blocker'), undefined);
766
- const stages = Reflect.get(plan, 'stages');
767
- assert.ok(Array.isArray(stages));
768
- assert.equal(
769
- stages.some((stage) =>
770
- Reflect.get(stage, 'warning_kind') === 'worst_case_reservation' ||
771
- Reflect.get(stage, 'reservation_fits') === false,
772
- ),
773
- true,
774
- );
775
- } finally {
776
- await rm(root, { recursive: true, force: true });
777
- }
778
- });
779
-
780
- void it('bounds each child response so downstream stages cannot be overrun', async () => {
781
- // Defence in depth layer 1: a response larger than its stage contract is
782
- // rejected loudly, never sliced and never forwarded to a later stage.
783
- const budget = new FusionBudget(models(), FUSION_COMMAND_CONTEXT_POLICY_ID);
784
- const oversized = 'c'.repeat(FUSION_CANDIDATE_MAX_OUTPUT_BYTES + 1);
785
- const outcome = await runExpectingFailure(canonicalInput('small'), async (options) => {
786
- if (options.stage === 'candidate') return childResult(options, oversized);
787
- return childResult(options, 'unreachable');
788
- });
789
- try {
790
- assert.equal(outcome.error.code, 'child_output_cap');
791
- assert.match(outcome.error.message, /exceeding the \d+-byte output contract/);
792
- assert.match(outcome.error.message, /not forwarded or truncated/);
793
- assert.equal(
794
- outcome.calls.filter((call) => call.stage !== 'candidate').length,
795
- 0,
796
- 'no downstream child may run once a candidate breaks its contract',
797
- );
798
- // The oversized response is preserved as evidence.
799
- const preserved = await readFile(
800
- join(outcome.root, outcome.artifactDir, 'candidate-1.attempt-1.response.md'),
801
- 'utf8',
802
- );
803
- assert.equal(preserved.length, oversized.length);
804
- } finally {
805
- await rm(outcome.root, { recursive: true, force: true });
806
- }
807
- assert.equal(budget.allowedInputTokens > 0, true);
808
- });
809
-
810
- void it('rejects evaluator, repair, and merger expansion before those children spawn', () => {
811
- // Defence in depth layer 2: even if a response somehow reached a later stage,
812
- // the exact rendered prompt is re-measured before that child is created.
813
- const budget = new FusionBudget(models({ small: 200_000, large: 200_000 }), FUSION_COMMAND_CONTEXT_POLICY_ID);
814
- const allowedBytes = maxKnownTextBytesForTokens({
815
- family: 'openai-codex',
816
- allowedInputTokens: budget.allowedInputTokens,
817
- scope: 'fusion',
818
- });
819
- const oversizedPrompt = 'p'.repeat(allowedBytes + 1);
820
- for (const stage of ['evaluation', 'evaluation_repair', 'merge'] as const) {
821
- let thrown: unknown;
822
- try {
823
- budget.assertStagePrompt(stage, '', oversizedPrompt);
824
- } catch (error) {
825
- thrown = error;
826
- }
827
- assert.ok(thrown instanceof FusionError, `${stage} must reject`);
828
- assertBudgetError(thrown, stage);
829
- assert.equal(thrown.budget?.measurement_kind, 'rendered_prompt');
830
- }
831
- });
832
-
833
- void it('states the minimum viable route capacity instead of accepting a route that must fail', () => {
834
- // The policy's uniform conservatism has a real consequence: routes below the
835
- // documented minimum cannot host the workflow. That is surfaced as an
836
- // actionable configuration error, not discovered later at the provider.
837
- assert.equal(
838
- FUSION_MIN_CONTEXT_WINDOW_TOKENS,
839
- FUSION_MIN_CANONICAL_INPUT_TOKENS +
840
- FUSION_RESERVED_OUTPUT_TOKENS +
841
- FUSION_FRAMING_RESERVE_TOKENS +
842
- FUSION_SAFETY_RESERVE_TOKENS,
843
- );
844
- const tooSmall: ResolvedFusionModels = {
845
- candidates: [
846
- resolved('p/ok', 272_000),
847
- resolved('p/ok', 272_000),
848
- resolved('p/small', FUSION_MIN_CONTEXT_WINDOW_TOKENS - 1),
849
- ],
850
- evaluator: resolved('p/ok', 272_000),
851
- merger: resolved('p/ok', 272_000),
852
- };
853
- assert.throws(
854
- () => new FusionBudget(tooSmall, FUSION_COMMAND_CONTEXT_POLICY_ID),
855
- (error: unknown) => {
856
- assert.ok(error instanceof FusionError);
857
- assert.equal(error.code, 'model_capacity_unknown');
858
- assert.equal(error.childCreated, false);
859
- // The error must name the requirement and the remedy.
860
- assert.match(error.message, new RegExp(String(FUSION_MIN_CONTEXT_WINDOW_TOKENS)));
861
- assert.match(error.message, /\/fusion-models/);
862
- return true;
863
- },
864
- );
865
- // Exactly at the minimum is accepted.
866
- const atMinimum: ResolvedFusionModels = {
867
- candidates: [
868
- resolved('p/ok', 272_000),
869
- resolved('p/ok', 272_000),
870
- resolved('p/edge', FUSION_MIN_CONTEXT_WINDOW_TOKENS),
871
- ],
872
- evaluator: resolved('p/ok', 272_000),
873
- merger: resolved('p/ok', 272_000),
874
- };
875
- assert.doesNotThrow(() => new FusionBudget(atMinimum, FUSION_COMMAND_CONTEXT_POLICY_ID));
876
- });
877
-
878
- void it('bounds output contracts in JSON-rendered bytes so escaping cannot bypass them', () => {
879
- // A raw-byte contract would be bypassed by escape-heavy content: control
880
- // characters render as \u00XX (6x) and quotes/backslashes/newlines as 2x.
881
- const rawLimit = FUSION_CANDIDATE_MAX_OUTPUT_BYTES;
882
- for (const [label, unit] of [
883
- ['control chars', '\u0001'],
884
- ['quotes', '"'],
885
- ['backslashes', '\\'],
886
- ['newlines', '\n'],
887
- ] as const) {
888
- // Half the raw limit: trivially inside a raw-byte bound...
889
- const text = unit.repeat(Math.floor(rawLimit / 2));
890
- assert.ok(
891
- Buffer.byteLength(text, 'utf8') < rawLimit,
892
- `${label} fixture must be under the raw limit`,
893
- );
894
- // ...but over the contract once rendered, which is what the reserve covers.
895
- assert.ok(
896
- Buffer.byteLength(JSON.stringify(text), 'utf8') > rawLimit,
897
- `${label} must expand past the raw limit when rendered`,
898
- );
899
- assert.throws(
900
- () => {
901
- assertChildOutputWithinContract('candidate', text);
902
- },
903
- (error: unknown) =>
904
- error instanceof FusionError &&
905
- error.code === 'child_output_cap' &&
906
- /JSON-rendered bytes/.test(error.message),
907
- `${label} must be rejected by the rendered-byte contract`,
908
- );
909
- }
910
- // Ordinary prose of the same raw size is accepted.
911
- assert.doesNotThrow(() => {
912
- assertChildOutputWithinContract('candidate', 'a'.repeat(Math.floor(rawLimit / 2)));
913
- });
914
- });
915
-
916
- void it('adds upstream output contracts to real empty-slot renderings', () => {
917
- const budget = new FusionBudget(models({ small: 272_000, large: 272_000 }), FUSION_COMMAND_CONTEXT_POLICY_ID);
918
- const input = canonicalInputWithPromptBytes(291_748);
919
- const plan = budget.plan(input);
920
- const candidate = planEntry(plan.stages, 'candidate', 1);
921
- const evaluationStage = planEntry(plan.stages, 'evaluation');
922
- const repair = planEntry(plan.stages, 'evaluation_repair');
923
- const merge = planEntry(plan.stages, 'merge');
924
- assert.equal(candidate.fits, true);
925
- assert.equal(evaluationStage.fits, true);
926
- assert.equal(merge.fits, true);
927
- assert.equal(repair.fits, true);
928
- assert.equal(merge.reservation_fits, false);
929
- assert.equal(repair.reservation_fits, false);
930
- assert.equal(plan.blockers.length, 0);
931
- assert.equal(plan.warnings.some((entry) => entry.warning_kind === 'worst_case_reservation'), true);
932
- assert.ok(
933
- repair.forecast_utf8_bytes >
934
- evaluationStage.forecast_utf8_bytes + FUSION_EVALUATION_MAX_OUTPUT_BYTES,
935
- );
936
- assert.ok(repair.forecast_utf8_bytes > merge.forecast_utf8_bytes);
937
- assert.ok(
938
- repair.forecast_utf8_bytes - merge.forecast_utf8_bytes >= FUSION_DIAGNOSTICS_MAX_BYTES,
939
- );
940
- });
941
-
942
- void it('forecasts tool-enabled candidate stages with capability-specific system prompt bytes', () => {
943
- const input = canonicalInput('specific repository fact and public URL requested');
944
- const reasonBudget = new FusionBudget(models(), FUSION_COMMAND_CONTEXT_POLICY_ID, 'reason');
945
- const inspectBudget = new FusionBudget(models(), FUSION_COMMAND_CONTEXT_POLICY_ID, 'inspect');
946
- const researchBudget = new FusionBudget(models(), FUSION_COMMAND_CONTEXT_POLICY_ID, 'research');
947
- const reasonCandidate = planEntry(reasonBudget.plan(input).stages, 'candidate', 1);
948
- const inspectCandidate = planEntry(inspectBudget.plan(input).stages, 'candidate', 1);
949
- const researchCandidate = planEntry(researchBudget.plan(input).stages, 'candidate', 1);
950
- const expectedInspectDelta =
951
- Buffer.byteLength(FUSION_CANDIDATE_INSPECT_SYSTEM_PROMPT, 'utf8') -
952
- Buffer.byteLength(FUSION_CANDIDATE_SYSTEM_PROMPT, 'utf8');
953
- const expectedResearchDelta =
954
- Buffer.byteLength(FUSION_CANDIDATE_RESEARCH_SYSTEM_PROMPT, 'utf8') -
955
- Buffer.byteLength(FUSION_CANDIDATE_SYSTEM_PROMPT, 'utf8');
956
- assert.notEqual(FUSION_CANDIDATE_INSPECT_SYSTEM_PROMPT, FUSION_CANDIDATE_SYSTEM_PROMPT);
957
- assert.notEqual(FUSION_CANDIDATE_RESEARCH_SYSTEM_PROMPT, FUSION_CANDIDATE_SYSTEM_PROMPT);
958
- assert.match(FUSION_CANDIDATE_INSPECT_SYSTEM_PROMPT, /read-only tools: read, grep, find, ls/);
959
- assert.match(FUSION_CANDIDATE_RESEARCH_SYSTEM_PROMPT, /fusion_web_fetch/);
960
- assert.equal(inspectCandidate.input_utf8_bytes - reasonCandidate.input_utf8_bytes, expectedInspectDelta);
961
- assert.equal(researchCandidate.input_utf8_bytes - reasonCandidate.input_utf8_bytes, expectedResearchDelta);
962
- assert.notEqual(inspectCandidate.input_utf8_bytes, reasonCandidate.input_utf8_bytes);
963
- assert.notEqual(researchCandidate.input_utf8_bytes, reasonCandidate.input_utf8_bytes);
964
- });
965
-
966
- void it('gives each clean workflow distinct request-budget remediation', () => {
967
- const request = 'scope '.repeat(180_000);
968
- const profiles = [
969
- { workflow: 'investigate', profile: FUSION_INVESTIGATE_WORKFLOW },
970
- { workflow: 'research', profile: FUSION_RESEARCH_WORKFLOW },
971
- { workflow: 'validate', profile: FUSION_VALIDATE_WORKFLOW },
972
- ] as const;
973
- const remediation = profiles.map(({ workflow, profile }) => {
974
- const built = buildFusionCleanTaskCanonicalInput({
975
- cwd: '/repo',
976
- source: 'tool',
977
- request,
978
- workflow,
979
- ...(workflow === 'research'
980
- ? { declaredSources: [{ url: 'https://example.com/', purpose: 'primary source' }] }
981
- : {}),
982
- });
983
- const budget = new FusionBudget(
984
- models({ small: 200_000, large: 200_000 }),
985
- built.input.context.policy_id,
986
- profile.candidateCapability,
987
- profile,
988
- );
989
- const plan = budget.plan(built.input);
990
- assert.ok(plan.primary_blocker, `${profile.id} fixture must exceed its request budget`);
991
- let captured: FusionError | undefined;
992
- try {
993
- budget.assertPlanFits(plan, 'unit-test');
994
- } catch (error) {
995
- assert.ok(error instanceof FusionError);
996
- captured = error;
997
- }
998
- assert.ok(captured?.budget);
999
- assert.equal(captured.budget.counterfactuals.empty_request.still_fails_with_empty_request, false);
1000
- assert.doesNotMatch(captured.budget.remediation.join('\n'), /fresh (?:Pi )?conversation/i);
1001
- return captured.budget.remediation.join('\n');
1002
- });
1003
- const [investigate, research, validate] = remediation;
1004
- assert.ok(investigate && research && validate);
1005
- assert.match(investigate, /fusion_investigate/);
1006
- assert.match(research, /fusion_research/);
1007
- assert.match(validate, /fusion_validate/);
1008
- assert.equal(new Set(remediation).size, 3);
1009
- assert.doesNotMatch(investigate, /source sets/i);
1010
- assert.doesNotMatch(validate, /source sets/i);
1011
- });
1012
-
1013
- void it('reproduces the compact incident plan as fitting with utilization warnings', () => {
1014
- const budget = new FusionBudget(models({ small: 272_000, large: 272_000 }), FUSION_COMMAND_CONTEXT_POLICY_ID);
1015
- const input = canonicalInputWithPromptBytes(214_068);
1016
- const plan = budget.plan(input);
1017
- assert.equal(plan.blockers.length, 0);
1018
- assert.equal(planEntry(plan.stages, 'candidate', 1).fits, true);
1019
- assert.equal(planEntry(plan.stages, 'evaluation').fits, true);
1020
- assert.equal(planEntry(plan.stages, 'merge').fits, true);
1021
- assert.equal(planEntry(plan.stages, 'evaluation_repair').fits, true);
1022
- assert.ok(planEntry(plan.stages, 'merge').input_only_signed_headroom_tokens > 0);
1023
- assert.ok(planEntry(plan.stages, 'evaluation_repair').input_only_signed_headroom_tokens > 0);
1024
- assert.ok(plan.warnings.length >= 2);
1025
- assert.equal(
1026
- plan.warnings.some((entry) => entry.budget_stage === 'merge'),
1027
- true,
1028
- );
1029
- assert.equal(
1030
- plan.warnings.some((entry) => entry.budget_stage === 'evaluation_repair'),
1031
- true,
1032
- );
1033
- for (const warning of plan.warnings) {
1034
- assert.ok(
1035
- warning.utilization_basis_points >= FUSION_UTILIZATION_WARNING_THRESHOLD_BASIS_POINTS ||
1036
- warning.reservation_fits === false,
1037
- );
1038
- }
1039
- });
1040
-
1041
- void it('emits one advisory utilization progress event on a tight fitting plan', async () => {
1042
- const root = await mkdtemp(join(tmpdir(), 'pi-fusion-budget-warning-'));
1043
- try {
1044
- const events: string[] = [];
1045
- const runner: FusionChildRunner = async (options) => {
1046
- if (options.stage === 'candidate') return childResult(options, 'candidate answer');
1047
- if (options.stage === 'evaluation') return childResult(options, JSON.stringify(evaluation()));
1048
- return childResult(options, 'merged');
1049
- };
1050
- await new FusionOrchestrator({ childRunner: runner }).run({
1051
- source: 'command',
1052
- cwd: root,
1053
- canonicalInput: canonicalInputWithPromptBytes(214_068),
1054
- canonicalInputSerialized: JSON.stringify(canonicalInputWithPromptBytes(214_068)),
1055
- contextLedger: ledger,
1056
- config: defaultFusionModelConfig(),
1057
- models: models({ small: 272_000, large: 272_000 }),
1058
- onProgress: (event) => {
1059
- if (event.type === 'budget_warning') events.push(String(event.warnings.length));
1060
- },
1061
- });
1062
- assert.equal(events.length, 1);
1063
- assert.ok(Number(events[0]) >= 2);
1064
- } finally {
1065
- await rm(root, { recursive: true, force: true });
1066
- }
1067
- });
1068
-
1069
- void it('lets safe prompts proceed through all five calls and persists the budget plan', async () => {
1070
- const root = await mkdtemp(join(tmpdir(), 'pi-fusion-budget-ok-'));
1071
- try {
1072
- const calls: RunPiChildOptions[] = [];
1073
- const runner: FusionChildRunner = async (options) => {
1074
- calls.push(options);
1075
- if (options.stage === 'candidate') return childResult(options, 'candidate answer');
1076
- if (options.stage === 'evaluation')
1077
- return childResult(options, JSON.stringify(evaluation()));
1078
- return childResult(options, 'merged');
1079
- };
1080
- const input = canonicalInput('a reasonable amount of conversation text');
1081
- const result = await new FusionOrchestrator({ childRunner: runner }).run({
1082
- source: 'command',
1083
- cwd: root,
1084
- canonicalInput: input,
1085
- canonicalInputSerialized: JSON.stringify(input),
1086
- contextLedger: ledger,
1087
- config: defaultFusionModelConfig(),
1088
- models: models(),
1089
- });
1090
- assert.equal(result.mergedText, 'merged');
1091
- assert.equal(calls.length, 5);
1092
- assert.equal(result.details.budget.policy_id, 'fusion-budget-policy-v4');
1093
- assert.equal(result.details.budget.calibration_warnings.length, 0);
1094
-
1095
- const planText = await readFile(
1096
- join(root, result.details.artifact_dir, 'budget-plan.json'),
1097
- 'utf8',
1098
- );
1099
- const plan = parseJsonText(planText);
1100
- assert.ok(typeof plan === 'object' && plan !== null);
1101
- const routes = Reflect.get(plan, 'routes');
1102
- assert.ok(Array.isArray(routes));
1103
- assert.equal(routes.length, 5, 'every configured route must be snapshotted');
1104
- assert.equal(
1105
- routes.some((route) => Reflect.get(route, 'qualified_id') === 'openai-codex/gpt-5.4-mini'),
1106
- true,
1107
- );
1108
- assert.equal(Reflect.get(plan, 'schema_version'), 'pi-background-tasks.fusion-budget-plan.v4');
1109
- const stages = Reflect.get(plan, 'stages');
1110
- assert.ok(Array.isArray(stages));
1111
- assert.equal(stages.length, 6);
1112
- assert.equal(Array.isArray(Reflect.get(plan, 'warnings')), true);
1113
-
1114
- const ledgerText = await readFile(
1115
- join(root, result.details.artifact_dir, 'context-omission-ledger.json'),
1116
- 'utf8',
1117
- );
1118
- assert.match(ledgerText, /visible-conversation-ledger-v2/);
1119
- } finally {
1120
- await rm(root, { recursive: true, force: true });
1121
- }
1122
- });
1123
-
1124
- void it('does not let an unrecognised model inherit a known provider calibration', () => {
1125
- const resolvedFamily = resolveTokenBudgetFamily({ provider: 'openai-codex', model: 'future-tokenizer' });
1126
- assert.equal(resolvedFamily.family, 'openai-codex');
1127
- assert.equal(resolvedFamily.backed, false);
1128
- assert.equal(resolvedFamily.resolution, 'known_provider_unbacked_model');
1129
- const unrecognised: ResolvedFusionModels = {
1130
- candidates: [
1131
- resolved('openai-codex/future-tokenizer', 400_000),
1132
- resolved('openai-codex/future-tokenizer', 400_000),
1133
- resolved('openai-codex/future-tokenizer', 400_000),
1134
- ],
1135
- evaluator: resolved('openai-codex/future-tokenizer', 400_000),
1136
- merger: resolved('openai-codex/future-tokenizer', 400_000),
1137
- };
1138
- const budget = new FusionBudget(unrecognised, FUSION_COMMAND_CONTEXT_POLICY_ID);
1139
- assert.equal(budget.routes.every((route) => route.rate_source.source === 'unbacked_model_floor'), true);
1140
- assert.equal(budget.routes.every((route) => route.rate_source.backed === false), true);
1141
- assert.equal(
1142
- budget.routes.every((route) => route.rate_source.effective_rate_bytes_per_token_x100 === TOKEN_BUDGET_PROVABLE_RATE_X100),
1143
- true,
1144
- );
1145
- });
1146
-
1147
- void it('surfaces unknown provider calibration in completed result details', async () => {
1148
- const root = await mkdtemp(join(tmpdir(), 'pi-fusion-budget-unknown-'));
1149
- try {
1150
- const runner: FusionChildRunner = async (options) => {
1151
- if (options.stage === 'candidate') return childResult(options, 'candidate answer');
1152
- if (options.stage === 'evaluation') return childResult(options, JSON.stringify(evaluation()));
1153
- return childResult(options, 'merged');
1154
- };
1155
- const unknownModels: ResolvedFusionModels = {
1156
- candidates: [
1157
- resolved('mystery/a', 400_000),
1158
- resolved('mystery/b', 400_000),
1159
- resolved('mystery/c', 400_000),
1160
- ],
1161
- evaluator: resolved('mystery/e', 400_000),
1162
- merger: resolved('mystery/m', 400_000),
1163
- };
1164
- const input = canonicalInput('small');
1165
- const result = await new FusionOrchestrator({ childRunner: runner }).run({
1166
- source: 'command',
1167
- cwd: root,
1168
- canonicalInput: input,
1169
- canonicalInputSerialized: JSON.stringify(input),
1170
- contextLedger: ledger,
1171
- config: defaultFusionModelConfig(),
1172
- models: unknownModels,
1173
- });
1174
- assert.equal(result.details.budget.unknown_provider_warnings.length, 5);
1175
- assert.equal(result.details.budget.rate_sources.every((source) => source.family === 'unknown'), true);
1176
- assert.match(result.details.budget.unknown_provider_warnings.join('\n'), /unknown provider/);
1177
- } finally {
1178
- await rm(root, { recursive: true, force: true });
1179
- }
1180
- });
1181
-
1182
- void it('writes and surfaces a calibration violation without aborting success', async () => {
1183
- const root = await mkdtemp(join(tmpdir(), 'pi-fusion-budget-breach-'));
1184
- try {
1185
- const events: string[] = [];
1186
- const runner: FusionChildRunner = async (options) => {
1187
- const result =
1188
- options.stage === 'evaluation'
1189
- ? childResult(options, JSON.stringify(evaluation()))
1190
- : options.stage === 'merge'
1191
- ? childResult(options, 'merged')
1192
- : childResult(options, 'candidate answer');
1193
- if (options.stage === 'candidate' && options.slot === 1) {
1194
- result.usage.input = 1_000_000;
1195
- result.usage.totalTokens = 1_000_001;
1196
- result.firstRequestUsage = { ...result.usage, cost: { ...result.usage.cost } };
1197
- result.providerRequestCount = 1;
1198
- }
1199
- return result;
1200
- };
1201
- const input = canonicalInput('small');
1202
- const result = await new FusionOrchestrator({ childRunner: runner }).run({
1203
- source: 'command',
1204
- cwd: root,
1205
- canonicalInput: input,
1206
- canonicalInputSerialized: JSON.stringify(input),
1207
- contextLedger: ledger,
1208
- config: defaultFusionModelConfig(),
1209
- models: models(),
1210
- onProgress: (event) => {
1211
- if (event.type === 'calibration_warning') events.push(event.artifact);
1212
- },
1213
- });
1214
- assert.equal(result.mergedText, 'merged');
1215
- assert.equal(result.details.budget.calibration_warnings.length, 1);
1216
- assert.equal(events.length, 1);
1217
- const artifact = await readFile(
1218
- join(root, result.details.artifact_dir, 'candidate-1.attempt-1.calibration-violation.json'),
1219
- 'utf8',
1220
- );
1221
- const parsed = parseJsonText(artifact);
1222
- assert.ok(typeof parsed === 'object' && parsed !== null);
1223
- assert.equal(Reflect.get(parsed, 'schema_version'), 'pi-background-tasks.fusion-calibration-violation.v2');
1224
- assert.equal(Reflect.get(parsed, 'observation_scope'), 'first_provider_request');
1225
- assert.equal(Reflect.get(parsed, 'provider_request_count'), 1);
1226
- assert.equal(Reflect.get(parsed, 'billed_input_tokens'), 1_000_000);
1227
- } finally {
1228
- await rm(root, { recursive: true, force: true });
1229
- }
1230
- });
1231
-
1232
- void it('does not compare a one-request forecast with cumulative agent-loop cache usage', async () => {
1233
- const root = await mkdtemp(join(tmpdir(), 'pi-fusion-budget-loop-scope-'));
1234
- try {
1235
- const runner: FusionChildRunner = (options) => {
1236
- const result = childResult(
1237
- options,
1238
- options.stage === 'evaluation'
1239
- ? JSON.stringify(evaluation())
1240
- : options.stage === 'merge'
1241
- ? 'merged'
1242
- : 'candidate answer',
1243
- );
1244
- result.firstRequestUsage = {
1245
- input: 2,
1246
- output: 10,
1247
- cacheRead: 0,
1248
- cacheWrite: 100,
1249
- totalTokens: 112,
1250
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
1251
- };
1252
- result.providerRequestCount = 49;
1253
- result.usage = {
1254
- input: 100,
1255
- output: 100_000,
1256
- cacheRead: 4_500_000,
1257
- cacheWrite: 200_000,
1258
- totalTokens: 4_800_100,
1259
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
1260
- };
1261
- return Promise.resolve(result);
1262
- };
1263
- const input = canonicalInput('small');
1264
- const result = await new FusionOrchestrator({ childRunner: runner }).run({
1265
- source: 'command',
1266
- cwd: root,
1267
- canonicalInput: input,
1268
- canonicalInputSerialized: JSON.stringify(input),
1269
- contextLedger: ledger,
1270
- config: defaultFusionModelConfig(),
1271
- models: models(),
1272
- });
1273
- assert.equal(result.details.budget.calibration_warnings.length, 0);
1274
- } finally {
1275
- await rm(root, { recursive: true, force: true });
1276
- }
1277
- });
1278
-
1279
- void it('writes the budget plan before rejecting so the decision stays auditable', async () => {
1280
- const oversized = canonicalInput('u'.repeat(600_000));
1281
- const outcome = await runExpectingFailure(oversized, async (options) =>
1282
- childResult(options, 'unreachable'),
1283
- );
1284
- try {
1285
- const planText = await readFile(
1286
- join(outcome.root, outcome.artifactDir, 'budget-plan.json'),
1287
- 'utf8',
1288
- );
1289
- const plan = parseJsonText(planText);
1290
- assert.ok(typeof plan === 'object' && plan !== null);
1291
- const primary = Reflect.get(plan, 'primary_blocker');
1292
- assert.ok(typeof primary === 'object' && primary !== null);
1293
- assert.equal(Reflect.get(primary, 'fits'), false);
1294
- const composition = Reflect.get(plan, 'primary_blocker_composition');
1295
- assert.ok(typeof composition === 'object' && composition !== null);
1296
- } finally {
1297
- await rm(outcome.root, { recursive: true, force: true });
1298
- }
1299
- });
1300
-
1301
- void it('keeps a real 1 MB tool-heavy session within the smallest configured budget', () => {
1302
- const session = sessionWith([
1303
- userMessage('genuine user question about the failing build'),
1304
- {
1305
- role: 'assistant',
1306
- api: 'openai-codex-responses',
1307
- provider: 'openai-codex',
1308
- model: 'gpt-5.5',
1309
- usage: {
1310
- input: 1,
1311
- output: 1,
1312
- cacheRead: 0,
1313
- cacheWrite: 0,
1314
- totalTokens: 2,
1315
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
1316
- },
1317
- stopReason: 'toolUse',
1318
- content: [
1319
- { type: 'thinking', thinking: 'x'.repeat(10_303) },
1320
- { type: 'text', text: 'assistant analysis kept verbatim' },
1321
- {
1322
- type: 'toolCall',
1323
- id: 'c1',
1324
- name: 'read',
1325
- arguments: { blob: 'a'.repeat(251_508) },
1326
- },
1327
- ],
1328
- timestamp: 2,
1329
- },
1330
- {
1331
- role: 'toolResult',
1332
- toolCallId: 'c1',
1333
- toolName: 'read',
1334
- content: [{ type: 'text', text: 'r'.repeat(696_929) }],
1335
- details: { ok: true },
1336
- isError: false,
1337
- timestamp: 3,
1338
- },
1339
- ]);
1340
- const built = buildFusionCanonicalInput(
1341
- { cwd: '/tmp/project', sessionManager: session, getSystemPrompt: () => 'sys' },
1342
- { source: 'tool', request: 'reproduce the original failure shape' },
1343
- );
1344
- // The pre-fix transcript for this shape was ~1,034,667 bytes.
1345
- assert.ok(
1346
- built.serialized.length < 10_000,
1347
- `projected canonical input must be small, saw ${String(built.serialized.length)}`,
1348
- );
1349
- const budget = new FusionBudget(models(), built.input.conversation_projection.policy.id);
1350
- const plan = budget.plan(built.input);
1351
- assert.equal(plan.blockers.length, 0);
1352
- assert.doesNotThrow(() => {
1353
- budget.assertPlanFits(plan, 'unit-test');
1354
- });
1355
- });
1356
- });