pi-background-tasks 1.0.4 → 1.0.7
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/BACKGROUND-TASKS-INSTRUCTIONS.md +2 -1
- package/README.md +2 -1
- package/TESTING.md +2 -2
- package/TEST_PLAN.md +10 -9
- package/docs/manifest.json +8 -4
- package/docs/operations/configuration.md +10 -0
- package/docs/operations/testing.md +2 -1
- package/docs/operations/troubleshooting.md +3 -1
- package/docs/read-before-edit.md +1 -0
- package/docs/reference/runtime-contracts.md +66 -61
- package/docs/subsystems/docs-freshness-gate.md +5 -5
- package/docs/subsystems/fusion.md +19 -14
- package/package.json +2 -1
- package/src/core/fusion/artifacts.ts +41 -0
- package/src/core/fusion/budget.ts +23 -8
- package/src/core/fusion/child-protocol.ts +137 -12
- package/src/core/fusion/claude-cache.ts +186 -0
- package/src/core/fusion/config.ts +13 -0
- package/src/core/fusion/evaluation.ts +61 -0
- package/src/core/fusion/orchestrator.ts +126 -15
- package/src/core/fusion/pi-child.ts +564 -14
- package/src/core/fusion/prompts.ts +1 -0
- package/src/core/fusion/types.ts +39 -8
- package/src/fusion-child-extension.ts +413 -4
|
@@ -190,6 +190,7 @@ Return only JSON matching this exact closed schema:
|
|
|
190
190
|
"limitations": ["non-blank statement of what you could not cover"]
|
|
191
191
|
}
|
|
192
192
|
Use an empty findings array when no issues were found; do not omit verified or limitations.
|
|
193
|
+
Do not wrap the JSON in Markdown fences or prose. Emit exactly one bare JSON object.
|
|
193
194
|
|
|
194
195
|
Do not inflate severity and do not invent issues to appear thorough. If the work is correct, say so plainly in verified/limitations. A report with no findings that names the evidence behind that conclusion is a valid and valuable result; a padded report is not.
|
|
195
196
|
|
package/src/core/fusion/types.ts
CHANGED
|
@@ -14,16 +14,19 @@ export const FUSION_MODEL_CONFIG_SCHEMA_VERSION = 'pi-background-tasks.fusion-mo
|
|
|
14
14
|
export const FUSION_LEGACY_INPUT_SCHEMA_VERSION = 'pi-background-tasks.fusion-input.v4';
|
|
15
15
|
export const FUSION_INPUT_SCHEMA_VERSION = 'pi-background-tasks.fusion-input.v5';
|
|
16
16
|
export const FUSION_EVALUATION_SCHEMA_VERSION = 'pi-background-tasks.fusion-evaluation.v1';
|
|
17
|
-
export const FUSION_VALIDATE_CANDIDATE_SCHEMA_VERSION =
|
|
17
|
+
export const FUSION_VALIDATE_CANDIDATE_SCHEMA_VERSION =
|
|
18
|
+
'pi-background-tasks.fusion-validation-candidate.v1';
|
|
18
19
|
export const FUSION_LEGACY_RESULT_SCHEMA_VERSION = 'pi-background-tasks.fusion-result.v4';
|
|
19
20
|
export const FUSION_RESULT_SCHEMA_VERSION = 'pi-background-tasks.fusion-result.v5';
|
|
20
21
|
export const FUSION_LEGACY_MANIFEST_SCHEMA_VERSION = 'pi-background-tasks.fusion-manifest.v3';
|
|
21
22
|
export const FUSION_MANIFEST_SCHEMA_VERSION = 'pi-background-tasks.fusion-manifest.v4';
|
|
22
23
|
export const FUSION_CONTEXT_LEDGER_SCHEMA_VERSION = 'pi-background-tasks.fusion-context-ledger.v2';
|
|
23
24
|
export const FUSION_SOURCE_POLICY_SCHEMA_VERSION = 'pi-background-tasks.fusion-source-policy.v1';
|
|
24
|
-
export const FUSION_BUDGET_PLAN_SCHEMA_VERSION = 'pi-background-tasks.fusion-budget-plan.
|
|
25
|
+
export const FUSION_BUDGET_PLAN_SCHEMA_VERSION = 'pi-background-tasks.fusion-budget-plan.v4';
|
|
25
26
|
export const FUSION_CALIBRATION_VIOLATION_SCHEMA_VERSION =
|
|
26
|
-
'pi-background-tasks.fusion-calibration-violation.
|
|
27
|
+
'pi-background-tasks.fusion-calibration-violation.v2';
|
|
28
|
+
export const FUSION_VALIDATE_CANDIDATE_CONTRACT_EVENT_SCHEMA_VERSION =
|
|
29
|
+
'pi-background-tasks.fusion-validation-candidate-contract-event.v1';
|
|
27
30
|
export const FUSION_TOOL_CALL_LOG_SCHEMA_VERSION = 'pi-background-tasks.fusion-tool-call.v1';
|
|
28
31
|
|
|
29
32
|
/**
|
|
@@ -186,6 +189,7 @@ export interface ResolvedFusionModel {
|
|
|
186
189
|
qualifiedId: string;
|
|
187
190
|
thinkingLevel: FusionThinkingLevel;
|
|
188
191
|
contextWindow: number;
|
|
192
|
+
maxOutputTokens: number;
|
|
189
193
|
}
|
|
190
194
|
|
|
191
195
|
export interface ResolvedFusionModels {
|
|
@@ -556,7 +560,11 @@ export interface FusionResultDetails {
|
|
|
556
560
|
source: FusionSource;
|
|
557
561
|
status: 'completed';
|
|
558
562
|
context: { kind: FusionContextKind; policy_id: string };
|
|
559
|
-
tool_policy: {
|
|
563
|
+
tool_policy: {
|
|
564
|
+
candidate_tools: readonly string[];
|
|
565
|
+
evaluation_tools: readonly [];
|
|
566
|
+
merge_tools: readonly [];
|
|
567
|
+
};
|
|
560
568
|
artifact_dir: string;
|
|
561
569
|
models: {
|
|
562
570
|
candidates: readonly [string, string, string];
|
|
@@ -595,6 +603,8 @@ export type FusionErrorCode =
|
|
|
595
603
|
| 'child_stdin_failed'
|
|
596
604
|
| 'child_event_invalid'
|
|
597
605
|
| 'child_exit_failed'
|
|
606
|
+
| 'child_runtime_budget_exceeded'
|
|
607
|
+
| 'child_cache_policy_invalid'
|
|
598
608
|
| 'child_timeout'
|
|
599
609
|
| 'child_output_cap'
|
|
600
610
|
| 'child_cancelled'
|
|
@@ -761,7 +771,12 @@ export interface FusionChildRunResult {
|
|
|
761
771
|
model: string;
|
|
762
772
|
qualifiedId: string;
|
|
763
773
|
text: string;
|
|
774
|
+
/** Aggregate usage across the complete child agent loop. */
|
|
764
775
|
usage: FusionUsage;
|
|
776
|
+
/** First provider request, used for like-for-like prompt forecast calibration. */
|
|
777
|
+
firstRequestUsage?: FusionUsage;
|
|
778
|
+
/** Number of provider requests represented by aggregate usage. */
|
|
779
|
+
providerRequestCount?: number;
|
|
765
780
|
events: Buffer;
|
|
766
781
|
stderr: Buffer;
|
|
767
782
|
exitCode: number;
|
|
@@ -815,8 +830,17 @@ export interface FusionArtifactManifest {
|
|
|
815
830
|
evaluation: FusionCapability;
|
|
816
831
|
merge: FusionCapability;
|
|
817
832
|
};
|
|
818
|
-
context: {
|
|
819
|
-
|
|
833
|
+
context: {
|
|
834
|
+
kind: FusionContextKind;
|
|
835
|
+
policy_id: string;
|
|
836
|
+
ledger_artifact?: string;
|
|
837
|
+
source_policy_artifact?: string;
|
|
838
|
+
};
|
|
839
|
+
tool_policy: {
|
|
840
|
+
candidate_tools: readonly string[];
|
|
841
|
+
evaluation_tools: readonly [];
|
|
842
|
+
merge_tools: readonly [];
|
|
843
|
+
};
|
|
820
844
|
usage: FusionUsage;
|
|
821
845
|
attempts: readonly FusionAttemptArtifactRecord[];
|
|
822
846
|
artifacts: Readonly<Record<string, FusionArtifactRef>>;
|
|
@@ -900,7 +924,11 @@ export interface FusionBudgetPlanV1 {
|
|
|
900
924
|
workflow: FusionWorkflowId;
|
|
901
925
|
context: { kind: FusionContextKind; policy_id: string };
|
|
902
926
|
fixed_candidate_policy: { capability: FusionCapability; tools: readonly string[] };
|
|
903
|
-
tool_policy: {
|
|
927
|
+
tool_policy: {
|
|
928
|
+
candidate_tools: readonly string[];
|
|
929
|
+
evaluation_tools: readonly [];
|
|
930
|
+
merge_tools: readonly [];
|
|
931
|
+
};
|
|
904
932
|
policy: FusionBudgetPolicyDescriptor;
|
|
905
933
|
routes: readonly FusionRouteCapacity[];
|
|
906
934
|
stages: readonly FusionStageBudgetPlanEntry[];
|
|
@@ -913,7 +941,8 @@ export interface FusionBudgetPlanV1 {
|
|
|
913
941
|
|
|
914
942
|
/** Documented, versioned budget policy. */
|
|
915
943
|
export interface FusionBudgetPolicyDescriptor {
|
|
916
|
-
id: 'fusion-budget-policy-
|
|
944
|
+
id: 'fusion-budget-policy-v4';
|
|
945
|
+
route_output_reserve_strategy: 'max_fusion_contract_or_model_max';
|
|
917
946
|
calibration_version: string;
|
|
918
947
|
calibration_table: Readonly<Record<TokenBudgetFamily, TokenBudgetFamilyCalibration>>;
|
|
919
948
|
reserved_output_tokens: number;
|
|
@@ -940,6 +969,8 @@ export interface FusionCalibrationViolation {
|
|
|
940
969
|
rate_source: TokenBudgetRateSource;
|
|
941
970
|
prompt_utf8_bytes: number;
|
|
942
971
|
prompt_sha256: string;
|
|
972
|
+
observation_scope: 'first_provider_request';
|
|
973
|
+
provider_request_count: number;
|
|
943
974
|
forecast_input_tokens: number;
|
|
944
975
|
billed_input_tokens: number;
|
|
945
976
|
billed_input_breakdown: {
|
|
@@ -9,8 +9,14 @@ import {
|
|
|
9
9
|
writeSync,
|
|
10
10
|
} from 'node:fs';
|
|
11
11
|
import { dirname } from 'node:path';
|
|
12
|
+
import { parseJsonText } from './core/common.js';
|
|
12
13
|
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
13
14
|
import { Type, type Static } from 'typebox';
|
|
15
|
+
import {
|
|
16
|
+
estimateInputTokens,
|
|
17
|
+
knownJsonSegment,
|
|
18
|
+
resolveTokenBudgetFamily,
|
|
19
|
+
} from './core/context/token-budget.js';
|
|
14
20
|
import {
|
|
15
21
|
FUSION_TOOL_CALL_LOG_SCHEMA_VERSION,
|
|
16
22
|
FUSION_WEB_FETCH_TOOL_NAME,
|
|
@@ -26,32 +32,75 @@ import {
|
|
|
26
32
|
parseFusionSourcePolicy,
|
|
27
33
|
} from './core/fusion/source-policy.js';
|
|
28
34
|
import {
|
|
35
|
+
nonAnthropicFusionCacheObservation,
|
|
36
|
+
normalizeFusionClaudeCachePayload,
|
|
37
|
+
type FusionClaudeCacheObservation,
|
|
38
|
+
} from './core/fusion/claude-cache.js';
|
|
39
|
+
import {
|
|
40
|
+
FUSION_CHILD_MAX_PROVIDER_REQUESTS,
|
|
41
|
+
FUSION_CHILD_MAX_TOOL_CALLS,
|
|
29
42
|
FUSION_CHILD_MAX_TOTAL_TOOL_RESULT_BYTES,
|
|
43
|
+
FUSION_CHILD_MIN_OUTPUT_RESERVE_TOKENS,
|
|
30
44
|
FUSION_CHILD_RESULT_PREFIX,
|
|
45
|
+
FUSION_CHILD_SAFETY_RESERVE_TOKENS,
|
|
46
|
+
FUSION_CHILD_SETTLEMENT_PREFIX,
|
|
31
47
|
FUSION_RESEARCH_ENABLED_ENV,
|
|
48
|
+
FUSION_RUNTIME_GUARD_PREFIX,
|
|
49
|
+
FUSION_RUNTIME_GUARD_SCHEMA_VERSION,
|
|
32
50
|
FUSION_SOURCE_POLICY_PATH_ENV,
|
|
33
51
|
FUSION_SOURCE_POLICY_SHA256_ENV,
|
|
34
52
|
FUSION_TOOL_CALL_LOG_PATH_ENV,
|
|
35
53
|
FUSION_TOOL_CALL_SEAL_SCHEMA_VERSION,
|
|
36
54
|
FUSION_TOOL_CALL_SEAL_SUFFIX,
|
|
37
55
|
buildFusionChildResultMetadata,
|
|
56
|
+
buildFusionChildSettlement,
|
|
38
57
|
type FusionChildResultMetadata,
|
|
58
|
+
type FusionChildSettlementRecord,
|
|
59
|
+
type FusionRuntimeGuardCode,
|
|
60
|
+
type FusionRuntimeGuardRecord,
|
|
39
61
|
} from './core/fusion/child-protocol.js';
|
|
40
62
|
|
|
41
63
|
export {
|
|
64
|
+
FUSION_CLAUDE_CACHE_BREAKPOINT_LIMIT,
|
|
65
|
+
FUSION_CLAUDE_CACHE_DEFAULT_RETENTION,
|
|
66
|
+
FUSION_CLAUDE_CACHE_OBSERVATION_SCHEMA_VERSION,
|
|
67
|
+
FUSION_CLAUDE_CACHE_RETENTION_ENV,
|
|
68
|
+
nonAnthropicFusionCacheObservation,
|
|
69
|
+
normalizeFusionClaudeCachePayload,
|
|
70
|
+
resolveFusionClaudeCachePolicy,
|
|
71
|
+
type FusionClaudeCacheNormalization,
|
|
72
|
+
type FusionClaudeCacheObservation,
|
|
73
|
+
type FusionClaudeCachePolicySource,
|
|
74
|
+
type FusionClaudeCacheRetention,
|
|
75
|
+
} from './core/fusion/claude-cache.js';
|
|
76
|
+
|
|
77
|
+
export {
|
|
78
|
+
FUSION_CHILD_MAX_PROVIDER_REQUESTS,
|
|
79
|
+
FUSION_CHILD_MAX_TOOL_CALLS,
|
|
42
80
|
FUSION_CHILD_MAX_TOTAL_TOOL_RESULT_BYTES,
|
|
81
|
+
FUSION_CHILD_MIN_OUTPUT_RESERVE_TOKENS,
|
|
43
82
|
FUSION_CHILD_RESULT_PREFIX,
|
|
44
83
|
FUSION_CHILD_RESULT_SCHEMA_VERSION,
|
|
84
|
+
FUSION_CHILD_SAFETY_RESERVE_TOKENS,
|
|
85
|
+
FUSION_CHILD_SETTLEMENT_PREFIX,
|
|
86
|
+
FUSION_CHILD_SETTLEMENT_SCHEMA_VERSION,
|
|
45
87
|
FUSION_RESEARCH_ENABLED_ENV,
|
|
88
|
+
FUSION_RUNTIME_GUARD_PREFIX,
|
|
89
|
+
FUSION_RUNTIME_GUARD_SCHEMA_VERSION,
|
|
46
90
|
FUSION_SOURCE_POLICY_PATH_ENV,
|
|
47
91
|
FUSION_SOURCE_POLICY_SHA256_ENV,
|
|
48
92
|
FUSION_TOOL_CALL_LOG_PATH_ENV,
|
|
49
93
|
FUSION_TOOL_CALL_SEAL_SCHEMA_VERSION,
|
|
50
94
|
FUSION_TOOL_CALL_SEAL_SUFFIX,
|
|
51
95
|
buildFusionChildResultMetadata,
|
|
96
|
+
buildFusionChildSettlement,
|
|
52
97
|
type FusionChildResultMetadata,
|
|
53
98
|
type FusionChildResultUsageMetadata,
|
|
99
|
+
type FusionChildSettlementFailureReason,
|
|
100
|
+
type FusionChildSettlementRecord,
|
|
54
101
|
type FusionChildTextBlockMetadata,
|
|
102
|
+
type FusionRuntimeGuardCode,
|
|
103
|
+
type FusionRuntimeGuardRecord,
|
|
55
104
|
} from './core/fusion/child-protocol.js';
|
|
56
105
|
|
|
57
106
|
const FUSION_CHILD_O_NOFOLLOW = typeof constants.O_NOFOLLOW === 'number' ? constants.O_NOFOLLOW : 0;
|
|
@@ -263,6 +312,237 @@ async function writeMetadata(record: FusionChildResultMetadata): Promise<void> {
|
|
|
263
312
|
});
|
|
264
313
|
}
|
|
265
314
|
|
|
315
|
+
async function writeSettlement(record: FusionChildSettlementRecord): Promise<void> {
|
|
316
|
+
const line = `${FUSION_CHILD_SETTLEMENT_PREFIX}${JSON.stringify(record)}\n`;
|
|
317
|
+
await new Promise<void>((resolve, reject) => {
|
|
318
|
+
process.stderr.write(line, (error) => {
|
|
319
|
+
if (error) reject(error);
|
|
320
|
+
else resolve();
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async function writeRuntimeGuard(record: FusionRuntimeGuardRecord): Promise<void> {
|
|
326
|
+
const line = `${FUSION_RUNTIME_GUARD_PREFIX}${JSON.stringify(record)}\n`;
|
|
327
|
+
await new Promise<void>((resolve, reject) => {
|
|
328
|
+
process.stderr.write(line, (error) => {
|
|
329
|
+
if (error) reject(error);
|
|
330
|
+
else resolve();
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export interface FusionRuntimeRequestEvaluationInput {
|
|
336
|
+
payload: unknown;
|
|
337
|
+
provider: string | undefined;
|
|
338
|
+
model: string | undefined;
|
|
339
|
+
contextWindowTokens: number | undefined;
|
|
340
|
+
maxOutputTokens: number | undefined;
|
|
341
|
+
requestOrdinal: number;
|
|
342
|
+
toolCallCount: number;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function invalidFusionRuntimeRequest(
|
|
346
|
+
input: FusionRuntimeRequestEvaluationInput,
|
|
347
|
+
detail: string,
|
|
348
|
+
code: Extract<
|
|
349
|
+
FusionRuntimeGuardCode,
|
|
350
|
+
'provider_payload_invalid' | 'claude_cache_policy'
|
|
351
|
+
> = 'provider_payload_invalid',
|
|
352
|
+
): FusionRuntimeGuardRecord {
|
|
353
|
+
const contextWindowTokens =
|
|
354
|
+
input.contextWindowTokens !== undefined &&
|
|
355
|
+
Number.isSafeInteger(input.contextWindowTokens) &&
|
|
356
|
+
input.contextWindowTokens > 0
|
|
357
|
+
? input.contextWindowTokens
|
|
358
|
+
: 0;
|
|
359
|
+
const modelOutputTokens =
|
|
360
|
+
input.maxOutputTokens !== undefined &&
|
|
361
|
+
Number.isSafeInteger(input.maxOutputTokens) &&
|
|
362
|
+
input.maxOutputTokens > 0
|
|
363
|
+
? input.maxOutputTokens
|
|
364
|
+
: 0;
|
|
365
|
+
const reservedOutputTokens = Math.max(FUSION_CHILD_MIN_OUTPUT_RESERVE_TOKENS, modelOutputTokens);
|
|
366
|
+
const allowedInputTokens = Math.max(
|
|
367
|
+
0,
|
|
368
|
+
contextWindowTokens - reservedOutputTokens - FUSION_CHILD_SAFETY_RESERVE_TOKENS,
|
|
369
|
+
);
|
|
370
|
+
const emptyPayload = Buffer.alloc(0);
|
|
371
|
+
return {
|
|
372
|
+
schema_version: FUSION_RUNTIME_GUARD_SCHEMA_VERSION,
|
|
373
|
+
code,
|
|
374
|
+
provider: input.provider ?? 'unknown',
|
|
375
|
+
model: input.model ?? 'unknown',
|
|
376
|
+
request_ordinal: input.requestOrdinal,
|
|
377
|
+
tool_call_count: input.toolCallCount,
|
|
378
|
+
payload_bytes: 0,
|
|
379
|
+
payload_sha256: sha256(emptyPayload),
|
|
380
|
+
estimated_input_tokens: 0,
|
|
381
|
+
context_window_tokens: contextWindowTokens,
|
|
382
|
+
reserved_output_tokens: reservedOutputTokens,
|
|
383
|
+
safety_reserve_tokens: FUSION_CHILD_SAFETY_RESERVE_TOKENS,
|
|
384
|
+
allowed_input_tokens: allowedInputTokens,
|
|
385
|
+
message: `fusion child could not validate provider request ${String(input.requestOrdinal)}: ${detail}`,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export interface PreparedFusionRuntimeRequest {
|
|
390
|
+
payload: unknown;
|
|
391
|
+
guard: FusionRuntimeGuardRecord | undefined;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export function prepareFusionRuntimeRequest(
|
|
395
|
+
input: FusionRuntimeRequestEvaluationInput,
|
|
396
|
+
): PreparedFusionRuntimeRequest {
|
|
397
|
+
try {
|
|
398
|
+
const serialized: unknown = JSON.stringify(input.payload);
|
|
399
|
+
if (typeof serialized !== 'string') {
|
|
400
|
+
throw new Error('provider payload serialized to a non-string value');
|
|
401
|
+
}
|
|
402
|
+
const payload = parseJsonText(serialized);
|
|
403
|
+
if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) {
|
|
404
|
+
throw new Error('provider payload must serialize to a JSON object');
|
|
405
|
+
}
|
|
406
|
+
if (JSON.stringify(payload) !== serialized) {
|
|
407
|
+
throw new Error('provider payload does not have a stable JSON serialization');
|
|
408
|
+
}
|
|
409
|
+
return { payload, guard: evaluateFusionRuntimeRequest({ ...input, payload }) };
|
|
410
|
+
} catch (error) {
|
|
411
|
+
return {
|
|
412
|
+
payload: input.payload,
|
|
413
|
+
guard: invalidFusionRuntimeRequest(
|
|
414
|
+
input,
|
|
415
|
+
error instanceof Error ? error.message : String(error),
|
|
416
|
+
),
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export function evaluateFusionRuntimeRequest(
|
|
422
|
+
input: FusionRuntimeRequestEvaluationInput,
|
|
423
|
+
): FusionRuntimeGuardRecord | undefined {
|
|
424
|
+
let code: FusionRuntimeGuardCode | undefined;
|
|
425
|
+
let message = '';
|
|
426
|
+
let payloadBytes = Buffer.alloc(0);
|
|
427
|
+
let estimatedInputTokens = 0;
|
|
428
|
+
let allowedInputTokens = 0;
|
|
429
|
+
const provider = input.provider ?? 'unknown';
|
|
430
|
+
const model = input.model ?? 'unknown';
|
|
431
|
+
const contextWindowTokens = input.contextWindowTokens ?? 0;
|
|
432
|
+
const reservedOutputTokens = Math.max(
|
|
433
|
+
FUSION_CHILD_MIN_OUTPUT_RESERVE_TOKENS,
|
|
434
|
+
input.maxOutputTokens ?? 0,
|
|
435
|
+
);
|
|
436
|
+
try {
|
|
437
|
+
if (input.provider === undefined || input.model === undefined) {
|
|
438
|
+
throw new Error('active model is unavailable');
|
|
439
|
+
}
|
|
440
|
+
if (!Number.isSafeInteger(contextWindowTokens) || contextWindowTokens <= 0) {
|
|
441
|
+
throw new Error('active model context window is unavailable');
|
|
442
|
+
}
|
|
443
|
+
if (
|
|
444
|
+
input.maxOutputTokens === undefined ||
|
|
445
|
+
!Number.isSafeInteger(input.maxOutputTokens) ||
|
|
446
|
+
input.maxOutputTokens <= 0
|
|
447
|
+
) {
|
|
448
|
+
throw new Error('active model maximum output tokens are unavailable');
|
|
449
|
+
}
|
|
450
|
+
allowedInputTokens =
|
|
451
|
+
contextWindowTokens - reservedOutputTokens - FUSION_CHILD_SAFETY_RESERVE_TOKENS;
|
|
452
|
+
if (allowedInputTokens <= 0) {
|
|
453
|
+
throw new Error('active model has no safe provider input capacity');
|
|
454
|
+
}
|
|
455
|
+
const payloadText = JSON.stringify(input.payload);
|
|
456
|
+
if (payloadText === undefined) throw new Error('provider payload serialized to undefined');
|
|
457
|
+
payloadBytes = Buffer.from(payloadText, 'utf8');
|
|
458
|
+
const family = resolveTokenBudgetFamily({ provider, model });
|
|
459
|
+
estimatedInputTokens = estimateInputTokens({
|
|
460
|
+
family: family.family,
|
|
461
|
+
calibrationBacked: family.backed,
|
|
462
|
+
familyResolution: family.resolution,
|
|
463
|
+
allowedInputTokens,
|
|
464
|
+
scope: 'fusion',
|
|
465
|
+
segments: [knownJsonSegment(payloadText)],
|
|
466
|
+
}).tokens;
|
|
467
|
+
if (input.requestOrdinal > FUSION_CHILD_MAX_PROVIDER_REQUESTS) {
|
|
468
|
+
code = 'provider_request_limit';
|
|
469
|
+
message = `fusion child reached provider request ${String(input.requestOrdinal)}, exceeding the ${String(FUSION_CHILD_MAX_PROVIDER_REQUESTS)}-request execution limit`;
|
|
470
|
+
} else if (estimatedInputTokens > allowedInputTokens) {
|
|
471
|
+
code = 'provider_request_budget';
|
|
472
|
+
message = `fusion child blocked provider request ${String(input.requestOrdinal)} before transport: exact final payload is ${String(payloadBytes.length)} UTF-8 bytes (estimated <= ${String(estimatedInputTokens)} input tokens), exceeding ${String(allowedInputTokens)} allowed input tokens after reserving ${String(reservedOutputTokens)} model output + ${String(FUSION_CHILD_SAFETY_RESERVE_TOKENS)} safety from the ${String(contextWindowTokens)}-token context window`;
|
|
473
|
+
}
|
|
474
|
+
} catch (error) {
|
|
475
|
+
code = 'provider_payload_invalid';
|
|
476
|
+
message = `fusion child could not validate provider request ${String(input.requestOrdinal)}: ${error instanceof Error ? error.message : String(error)}`;
|
|
477
|
+
}
|
|
478
|
+
if (code === undefined) return undefined;
|
|
479
|
+
return {
|
|
480
|
+
schema_version: FUSION_RUNTIME_GUARD_SCHEMA_VERSION,
|
|
481
|
+
code,
|
|
482
|
+
provider,
|
|
483
|
+
model,
|
|
484
|
+
request_ordinal: input.requestOrdinal,
|
|
485
|
+
tool_call_count: input.toolCallCount,
|
|
486
|
+
payload_bytes: payloadBytes.length,
|
|
487
|
+
payload_sha256: sha256(payloadBytes),
|
|
488
|
+
estimated_input_tokens: estimatedInputTokens,
|
|
489
|
+
context_window_tokens: contextWindowTokens,
|
|
490
|
+
reserved_output_tokens: reservedOutputTokens,
|
|
491
|
+
safety_reserve_tokens: FUSION_CHILD_SAFETY_RESERVE_TOKENS,
|
|
492
|
+
allowed_input_tokens: allowedInputTokens,
|
|
493
|
+
message,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
export interface FusionRuntimeToolLimitEvaluationInput {
|
|
498
|
+
provider: string | undefined;
|
|
499
|
+
model: string | undefined;
|
|
500
|
+
contextWindowTokens: number | undefined;
|
|
501
|
+
maxOutputTokens: number | undefined;
|
|
502
|
+
requestOrdinal: number;
|
|
503
|
+
toolCallCount: number;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export function evaluateFusionRuntimeToolLimit(
|
|
507
|
+
input: FusionRuntimeToolLimitEvaluationInput,
|
|
508
|
+
): FusionRuntimeGuardRecord | undefined {
|
|
509
|
+
if (input.toolCallCount <= FUSION_CHILD_MAX_TOOL_CALLS) return undefined;
|
|
510
|
+
const contextWindowTokens =
|
|
511
|
+
input.contextWindowTokens !== undefined &&
|
|
512
|
+
Number.isSafeInteger(input.contextWindowTokens) &&
|
|
513
|
+
input.contextWindowTokens > 0
|
|
514
|
+
? input.contextWindowTokens
|
|
515
|
+
: 0;
|
|
516
|
+
const modelOutputTokens =
|
|
517
|
+
input.maxOutputTokens !== undefined &&
|
|
518
|
+
Number.isSafeInteger(input.maxOutputTokens) &&
|
|
519
|
+
input.maxOutputTokens > 0
|
|
520
|
+
? input.maxOutputTokens
|
|
521
|
+
: 0;
|
|
522
|
+
const reservedOutputTokens = Math.max(FUSION_CHILD_MIN_OUTPUT_RESERVE_TOKENS, modelOutputTokens);
|
|
523
|
+
const allowedInputTokens = Math.max(
|
|
524
|
+
0,
|
|
525
|
+
contextWindowTokens - reservedOutputTokens - FUSION_CHILD_SAFETY_RESERVE_TOKENS,
|
|
526
|
+
);
|
|
527
|
+
const emptyPayload = Buffer.alloc(0);
|
|
528
|
+
return {
|
|
529
|
+
schema_version: FUSION_RUNTIME_GUARD_SCHEMA_VERSION,
|
|
530
|
+
code: 'tool_call_limit',
|
|
531
|
+
provider: input.provider ?? 'unknown',
|
|
532
|
+
model: input.model ?? 'unknown',
|
|
533
|
+
request_ordinal: input.requestOrdinal,
|
|
534
|
+
tool_call_count: input.toolCallCount,
|
|
535
|
+
payload_bytes: 0,
|
|
536
|
+
payload_sha256: sha256(emptyPayload),
|
|
537
|
+
estimated_input_tokens: 0,
|
|
538
|
+
context_window_tokens: contextWindowTokens,
|
|
539
|
+
reserved_output_tokens: reservedOutputTokens,
|
|
540
|
+
safety_reserve_tokens: FUSION_CHILD_SAFETY_RESERVE_TOKENS,
|
|
541
|
+
allowed_input_tokens: allowedInputTokens,
|
|
542
|
+
message: `fusion child reached tool call ${String(input.toolCallCount)}, exceeding the ${String(FUSION_CHILD_MAX_TOOL_CALLS)}-call execution limit`,
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
|
|
266
546
|
function strictFusionWebFetchArgs(args: unknown): FusionWebFetchParamsValue {
|
|
267
547
|
if (typeof args !== 'object' || args === null || Array.isArray(args)) {
|
|
268
548
|
throw new Error(`${FUSION_WEB_FETCH_TOOL_NAME} arguments must be an object`);
|
|
@@ -401,6 +681,82 @@ export default function fusionChildExtension(pi: ExtensionAPI): void {
|
|
|
401
681
|
}
|
|
402
682
|
const declaredResearchUrls = researchEnabled === '1' ? loadDeclaredResearchUrls() : undefined;
|
|
403
683
|
const fetchAuditMetadata = new Map<string, FusionWebFetchAuditMetadata>();
|
|
684
|
+
let providerRequestCount = 0;
|
|
685
|
+
let toolCallCount = 0;
|
|
686
|
+
let runtimeGuardFailed = false;
|
|
687
|
+
let settlementPublished = false;
|
|
688
|
+
const childResultRecords: FusionChildResultMetadata[] = [];
|
|
689
|
+
let pendingCacheObservation: FusionClaudeCacheObservation | undefined;
|
|
690
|
+
|
|
691
|
+
pi.on('before_provider_request', async (event, ctx) => {
|
|
692
|
+
providerRequestCount += 1;
|
|
693
|
+
if (runtimeGuardFailed) {
|
|
694
|
+
ctx.abort();
|
|
695
|
+
return event.payload;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const model = ctx.model;
|
|
699
|
+
let cacheNormalizedPayload = event.payload;
|
|
700
|
+
let cacheObservation: FusionClaudeCacheObservation;
|
|
701
|
+
try {
|
|
702
|
+
if (model?.provider === 'anthropic') {
|
|
703
|
+
const supportsLongCacheRetention =
|
|
704
|
+
model.compat !== undefined && 'supportsLongCacheRetention' in model.compat
|
|
705
|
+
? model.compat.supportsLongCacheRetention
|
|
706
|
+
: undefined;
|
|
707
|
+
const normalized = normalizeFusionClaudeCachePayload({
|
|
708
|
+
payload: event.payload,
|
|
709
|
+
requestOrdinal: providerRequestCount,
|
|
710
|
+
supportsLongCacheRetention:
|
|
711
|
+
typeof supportsLongCacheRetention === 'boolean'
|
|
712
|
+
? supportsLongCacheRetention
|
|
713
|
+
: undefined,
|
|
714
|
+
});
|
|
715
|
+
cacheNormalizedPayload = normalized.payload;
|
|
716
|
+
cacheObservation = normalized.observation;
|
|
717
|
+
} else {
|
|
718
|
+
cacheObservation = nonAnthropicFusionCacheObservation(providerRequestCount);
|
|
719
|
+
}
|
|
720
|
+
} catch (error) {
|
|
721
|
+
const guard = invalidFusionRuntimeRequest(
|
|
722
|
+
{
|
|
723
|
+
payload: event.payload,
|
|
724
|
+
provider: model?.provider,
|
|
725
|
+
model: model?.id,
|
|
726
|
+
contextWindowTokens: model?.contextWindow,
|
|
727
|
+
maxOutputTokens: model?.maxTokens,
|
|
728
|
+
requestOrdinal: providerRequestCount,
|
|
729
|
+
toolCallCount,
|
|
730
|
+
},
|
|
731
|
+
`Claude cache policy rejected the final payload: ${error instanceof Error ? error.message : String(error)}`,
|
|
732
|
+
'claude_cache_policy',
|
|
733
|
+
);
|
|
734
|
+
runtimeGuardFailed = true;
|
|
735
|
+
latchAuditProcessFailure();
|
|
736
|
+
ctx.abort();
|
|
737
|
+
await writeRuntimeGuard(guard);
|
|
738
|
+
return event.payload;
|
|
739
|
+
}
|
|
740
|
+
pendingCacheObservation = cacheObservation;
|
|
741
|
+
|
|
742
|
+
const prepared = prepareFusionRuntimeRequest({
|
|
743
|
+
payload: cacheNormalizedPayload,
|
|
744
|
+
provider: model?.provider,
|
|
745
|
+
model: model?.id,
|
|
746
|
+
contextWindowTokens: model?.contextWindow,
|
|
747
|
+
maxOutputTokens: model?.maxTokens,
|
|
748
|
+
requestOrdinal: providerRequestCount,
|
|
749
|
+
toolCallCount,
|
|
750
|
+
});
|
|
751
|
+
const guard = prepared.guard;
|
|
752
|
+
if (guard === undefined) return prepared.payload;
|
|
753
|
+
runtimeGuardFailed = true;
|
|
754
|
+
latchAuditProcessFailure();
|
|
755
|
+
ctx.abort();
|
|
756
|
+
await writeRuntimeGuard(guard);
|
|
757
|
+
return prepared.payload;
|
|
758
|
+
});
|
|
759
|
+
|
|
404
760
|
if (toolCallLogPath !== undefined) {
|
|
405
761
|
// Establish the audit file before tools can run. Exclusive creation makes a reused
|
|
406
762
|
// attempt path or redirected file loud instead of appending to untrusted history.
|
|
@@ -441,7 +797,8 @@ export default function fusionChildExtension(pi: ExtensionAPI): void {
|
|
|
441
797
|
}
|
|
442
798
|
phase = 'finalizing';
|
|
443
799
|
const unmatchedStarts = starts.size;
|
|
444
|
-
const complete =
|
|
800
|
+
const complete =
|
|
801
|
+
normalSettlement && !auditFailed && !runtimeGuardFailed && unmatchedStarts === 0;
|
|
445
802
|
if (!complete) {
|
|
446
803
|
auditFailed = true;
|
|
447
804
|
latchAuditProcessFailure();
|
|
@@ -460,9 +817,30 @@ export default function fusionChildExtension(pi: ExtensionAPI): void {
|
|
|
460
817
|
}
|
|
461
818
|
};
|
|
462
819
|
|
|
463
|
-
pi.on('tool_call', (event) => {
|
|
820
|
+
pi.on('tool_call', async (event, ctx) => {
|
|
464
821
|
try {
|
|
465
822
|
requireOpen('tool_call');
|
|
823
|
+
if (runtimeGuardFailed) {
|
|
824
|
+
ctx.abort();
|
|
825
|
+
return { block: true, reason: 'fusion child runtime guard already refused the run' };
|
|
826
|
+
}
|
|
827
|
+
toolCallCount += 1;
|
|
828
|
+
const model = ctx.model;
|
|
829
|
+
const guard = evaluateFusionRuntimeToolLimit({
|
|
830
|
+
provider: model?.provider,
|
|
831
|
+
model: model?.id,
|
|
832
|
+
contextWindowTokens: model?.contextWindow,
|
|
833
|
+
maxOutputTokens: model?.maxTokens,
|
|
834
|
+
requestOrdinal: providerRequestCount,
|
|
835
|
+
toolCallCount,
|
|
836
|
+
});
|
|
837
|
+
if (guard !== undefined) {
|
|
838
|
+
runtimeGuardFailed = true;
|
|
839
|
+
latchAuditProcessFailure();
|
|
840
|
+
ctx.abort();
|
|
841
|
+
await writeRuntimeGuard(guard);
|
|
842
|
+
return { block: true, reason: guard.message };
|
|
843
|
+
}
|
|
466
844
|
if (starts.has(event.toolCallId)) {
|
|
467
845
|
throw new Error(`fusion tool-call log duplicate start for ${event.toolCallId}`);
|
|
468
846
|
}
|
|
@@ -470,8 +848,9 @@ export default function fusionChildExtension(pi: ExtensionAPI): void {
|
|
|
470
848
|
startedAt: Date.now(),
|
|
471
849
|
toolName: event.toolName,
|
|
472
850
|
});
|
|
851
|
+
return undefined;
|
|
473
852
|
} catch (error) {
|
|
474
|
-
failAudit(error);
|
|
853
|
+
return failAudit(error);
|
|
475
854
|
}
|
|
476
855
|
});
|
|
477
856
|
pi.on('tool_result', (event) => {
|
|
@@ -597,6 +976,36 @@ export default function fusionChildExtension(pi: ExtensionAPI): void {
|
|
|
597
976
|
|
|
598
977
|
pi.on('message_end', async (event) => {
|
|
599
978
|
if (event.message.role !== 'assistant') return;
|
|
600
|
-
|
|
979
|
+
const cacheObservation = pendingCacheObservation;
|
|
980
|
+
if (cacheObservation === undefined) {
|
|
981
|
+
latchAuditProcessFailure();
|
|
982
|
+
throw new Error('fusion child assistant result has no matching cache-policy observation');
|
|
983
|
+
}
|
|
984
|
+
pendingCacheObservation = undefined;
|
|
985
|
+
const record = buildFusionChildResultMetadata(event.message, cacheObservation);
|
|
986
|
+
await writeMetadata(record);
|
|
987
|
+
childResultRecords.push(record);
|
|
988
|
+
});
|
|
989
|
+
pi.on('agent_settled', async (_event, ctx) => {
|
|
990
|
+
if (settlementPublished) {
|
|
991
|
+
latchAuditProcessFailure();
|
|
992
|
+
throw new Error('fusion child received duplicate agent_settled for result settlement');
|
|
993
|
+
}
|
|
994
|
+
if (!ctx.isIdle()) {
|
|
995
|
+
latchAuditProcessFailure();
|
|
996
|
+
throw new Error('fusion child result settlement observed agent_settled while not idle');
|
|
997
|
+
}
|
|
998
|
+
settlementPublished = true;
|
|
999
|
+
const cacheObservationFailed = pendingCacheObservation !== undefined;
|
|
1000
|
+
const settlement = buildFusionChildSettlement(
|
|
1001
|
+
childResultRecords,
|
|
1002
|
+
runtimeGuardFailed,
|
|
1003
|
+
cacheObservationFailed,
|
|
1004
|
+
);
|
|
1005
|
+
if (settlement.status !== 'complete') latchAuditProcessFailure();
|
|
1006
|
+
await writeSettlement(settlement);
|
|
1007
|
+
});
|
|
1008
|
+
pi.on('session_shutdown', () => {
|
|
1009
|
+
if (!settlementPublished) latchAuditProcessFailure();
|
|
601
1010
|
});
|
|
602
1011
|
}
|