privateer-agent 0.15.4 → 0.15.6
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/README.md +11 -0
- package/bin/boot-bundle.d.mts +2 -0
- package/bin/boot-bundle.mjs +44 -0
- package/bin/fresh-supervisor.d.mts +5 -1
- package/bin/fresh-supervisor.mjs +47 -10
- package/bin/privateer-launch.mjs +27 -3
- package/bin/privateer-subagent.mjs +5 -2
- package/bin/privateer-verify.mjs +12 -0
- package/bin/session-recovery.d.mts +53 -0
- package/bin/session-recovery.mjs +240 -0
- package/bin/session-seal-preload.mjs +17 -0
- package/bin/session-seal.d.mts +25 -0
- package/bin/session-seal.mjs +242 -0
- package/dist-boot/boot.mjs +3 -0
- package/dist-boot/c-27JKSMWL.mjs +1460 -0
- package/dist-boot/c-2BMRFV24.mjs +269 -0
- package/dist-boot/c-2CTF6JA3.mjs +166 -0
- package/dist-boot/c-2G3BBHRJ.mjs +84 -0
- package/dist-boot/c-2IDLKXX6.mjs +250 -0
- package/dist-boot/c-2NJD5VYM.mjs +8 -0
- package/dist-boot/c-2S5EEQPW.mjs +1602 -0
- package/dist-boot/c-2WRDCUVI.mjs +25 -0
- package/dist-boot/c-2YN3M256.mjs +14 -0
- package/dist-boot/c-37GM3GMU.mjs +187 -0
- package/dist-boot/c-3STKRSVO.mjs +2480 -0
- package/dist-boot/c-3TPV6TQQ.mjs +2320 -0
- package/dist-boot/c-3UVHLQDJ.mjs +225 -0
- package/dist-boot/c-42PUUZYC.mjs +697 -0
- package/dist-boot/c-4D5UMWVW.mjs +683 -0
- package/dist-boot/c-4ETZRMTA.mjs +46 -0
- package/dist-boot/c-4QT25BD5.mjs +1482 -0
- package/dist-boot/c-4WVR32AP.mjs +2579 -0
- package/dist-boot/c-5EMM567G.mjs +152 -0
- package/dist-boot/c-5FA4CV63.mjs +145 -0
- package/dist-boot/c-5GV3X3RB.mjs +14 -0
- package/dist-boot/c-5JCEPRIY.mjs +52 -0
- package/dist-boot/c-5NDKIIWT.mjs +16 -0
- package/dist-boot/c-5QL3AR26.mjs +4665 -0
- package/dist-boot/c-5XY6MFKN.mjs +5142 -0
- package/dist-boot/c-65UZIZC7.mjs +2897 -0
- package/dist-boot/c-6FUXKHOK.mjs +1969 -0
- package/dist-boot/c-6JHWQE4V.mjs +18242 -0
- package/dist-boot/c-6LOBR4I6.mjs +5280 -0
- package/dist-boot/c-6QHOZXKQ.mjs +14394 -0
- package/dist-boot/c-6ZO2SADQ.mjs +178 -0
- package/dist-boot/c-6ZORVVZP.mjs +1449 -0
- package/dist-boot/c-76QH4UCP.mjs +2441 -0
- package/dist-boot/c-77GO5L5Y.mjs +20982 -0
- package/dist-boot/c-7M23XFTN.mjs +5052 -0
- package/dist-boot/c-7TC367E2.mjs +32 -0
- package/dist-boot/c-7UOG7POX.mjs +1524 -0
- package/dist-boot/c-7VHP3FLN.mjs +376 -0
- package/dist-boot/c-A73JHZP2.mjs +13207 -0
- package/dist-boot/c-ABXULAFL.mjs +71 -0
- package/dist-boot/c-ANDD2AQO.mjs +74 -0
- package/dist-boot/c-AOY77AZL.mjs +1430 -0
- package/dist-boot/c-AXEPZ22I.mjs +108 -0
- package/dist-boot/c-B2Y7VNHU.mjs +427 -0
- package/dist-boot/c-B7VJUH73.mjs +77 -0
- package/dist-boot/c-BFW6CDVL.mjs +24 -0
- package/dist-boot/c-BHNIF2AR.mjs +188 -0
- package/dist-boot/c-BKF2O6BL.mjs +23 -0
- package/dist-boot/c-BOZXFTZN.mjs +8 -0
- package/dist-boot/c-BRNYJWZL.mjs +28 -0
- package/dist-boot/c-C3N4Z2HO.mjs +93 -0
- package/dist-boot/c-C4IV7ZFG.mjs +717 -0
- package/dist-boot/c-C5NK6UYV.mjs +793 -0
- package/dist-boot/c-C67644MB.mjs +1659 -0
- package/dist-boot/c-C6S2YB53.mjs +620 -0
- package/dist-boot/c-CDBEJSBF.mjs +37 -0
- package/dist-boot/c-CG47HBKU.mjs +435 -0
- package/dist-boot/c-CKCTWCT5.mjs +51 -0
- package/dist-boot/c-CN77DCGF.mjs +60 -0
- package/dist-boot/c-CRBXNKFO.mjs +1795 -0
- package/dist-boot/c-CRIHASQ6.mjs +973 -0
- package/dist-boot/c-CU4NGAUO.mjs +3786 -0
- package/dist-boot/c-CWRFLGQU.mjs +1883 -0
- package/dist-boot/c-CYQB2USO.mjs +32 -0
- package/dist-boot/c-DEE67DJO.mjs +379 -0
- package/dist-boot/c-DIFDBYOP.mjs +1258 -0
- package/dist-boot/c-DOSGVXDZ.mjs +34879 -0
- package/dist-boot/c-DR7XQI32.mjs +381 -0
- package/dist-boot/c-DTGPSB46.mjs +14666 -0
- package/dist-boot/c-DTHDNA4P.mjs +320 -0
- package/dist-boot/c-E4Z3Y5B5.mjs +3864 -0
- package/dist-boot/c-E66Z2TI7.mjs +203 -0
- package/dist-boot/c-EBU6RLRV.mjs +1527 -0
- package/dist-boot/c-EBZZNGSF.mjs +432 -0
- package/dist-boot/c-EC3LJ2KF.mjs +1032 -0
- package/dist-boot/c-EQBKXWNX.mjs +15829 -0
- package/dist-boot/c-EUEPIW4S.mjs +574 -0
- package/dist-boot/c-EX4YVWAT.mjs +378 -0
- package/dist-boot/c-EYZGSAJU.mjs +98 -0
- package/dist-boot/c-F75VRKY4.mjs +172 -0
- package/dist-boot/c-FH4NXKHV.mjs +177 -0
- package/dist-boot/c-FHNUCTEM.mjs +140 -0
- package/dist-boot/c-FJ5T42DS.mjs +5349 -0
- package/dist-boot/c-FPCNRL7G.mjs +1728 -0
- package/dist-boot/c-G4DLN7Q4.mjs +8 -0
- package/dist-boot/c-G56GYQFZ.mjs +2507 -0
- package/dist-boot/c-GLIOERDQ.mjs +1200 -0
- package/dist-boot/c-GMJ5HUBM.mjs +14 -0
- package/dist-boot/c-H3HSGMAQ.mjs +461 -0
- package/dist-boot/c-HIKTO6P7.mjs +275 -0
- package/dist-boot/c-I2MCJ34O.mjs +464 -0
- package/dist-boot/c-I3C6LBW4.mjs +765 -0
- package/dist-boot/c-I54OKQHW.mjs +387 -0
- package/dist-boot/c-IFSP3R4D.mjs +80 -0
- package/dist-boot/c-IZK77VB7.mjs +171 -0
- package/dist-boot/c-J3Y25UGE.mjs +54 -0
- package/dist-boot/c-J5IESS7W.mjs +27750 -0
- package/dist-boot/c-JJWZBY7L.mjs +254 -0
- package/dist-boot/c-JKTFXTGF.mjs +463 -0
- package/dist-boot/c-JN57AXQ4.mjs +77 -0
- package/dist-boot/c-KFIO6LUW.mjs +1096 -0
- package/dist-boot/c-KIEMEFWW.mjs +478 -0
- package/dist-boot/c-KIVXWBYD.mjs +28 -0
- package/dist-boot/c-KP6CRIP4.mjs +580 -0
- package/dist-boot/c-L26VOJX4.mjs +11558 -0
- package/dist-boot/c-L5GHWG5G.mjs +5988 -0
- package/dist-boot/c-M7NSQAKK.mjs +353 -0
- package/dist-boot/c-MCEWOPBW.mjs +56 -0
- package/dist-boot/c-MGPIEG7I.mjs +20 -0
- package/dist-boot/c-NREXJV3M.mjs +393 -0
- package/dist-boot/c-O5PP6CTX.mjs +162 -0
- package/dist-boot/c-OBQDHSXM.mjs +1774 -0
- package/dist-boot/c-OLQHHDD4.mjs +21 -0
- package/dist-boot/c-OXW4JQQH.mjs +319 -0
- package/dist-boot/c-P3D7ZUWN.mjs +1288 -0
- package/dist-boot/c-P4PQZVV5.mjs +948 -0
- package/dist-boot/c-P63T3XL2.mjs +26373 -0
- package/dist-boot/c-P6N5DQGZ.mjs +194 -0
- package/dist-boot/c-P7MLKQFC.mjs +58 -0
- package/dist-boot/c-PB3PZJL5.mjs +1276 -0
- package/dist-boot/c-PTN4S6G2.mjs +1259 -0
- package/dist-boot/c-PYCE5J7U.mjs +1083 -0
- package/dist-boot/c-Q4HYCRLB.mjs +19410 -0
- package/dist-boot/c-QUXMETGG.mjs +68 -0
- package/dist-boot/c-RDMJZCOX.mjs +356 -0
- package/dist-boot/c-ROGJK76M.mjs +17 -0
- package/dist-boot/c-RQJJ26T4.mjs +708 -0
- package/dist-boot/c-RSKCUPJF.mjs +7559 -0
- package/dist-boot/c-RVJU4O6P.mjs +37 -0
- package/dist-boot/c-S2RVHR3M.mjs +165 -0
- package/dist-boot/c-SKMK3PEE.mjs +91120 -0
- package/dist-boot/c-TI7SM4EG.mjs +20 -0
- package/dist-boot/c-TILJ6GXD.mjs +1235 -0
- package/dist-boot/c-TT6BCUJD.mjs +114 -0
- package/dist-boot/c-U4ITOXAN.mjs +2107 -0
- package/dist-boot/c-UGERXVO2.mjs +7120 -0
- package/dist-boot/c-UPBH5AXY.mjs +1363 -0
- package/dist-boot/c-US2PUNZL.mjs +597 -0
- package/dist-boot/c-UU4WZPPE.mjs +20 -0
- package/dist-boot/c-UWVIYPQS.mjs +39 -0
- package/dist-boot/c-V4RHQBPQ.mjs +17683 -0
- package/dist-boot/c-V5MRVK47.mjs +351 -0
- package/dist-boot/c-V6CL266R.mjs +1755 -0
- package/dist-boot/c-VJTMFKYE.mjs +14975 -0
- package/dist-boot/c-VJTRUKIB.mjs +773 -0
- package/dist-boot/c-W4IFSHPW.mjs +43 -0
- package/dist-boot/c-WETGB2CF.mjs +113 -0
- package/dist-boot/c-WO3QSZUU.mjs +1337 -0
- package/dist-boot/c-WQLH73TY.mjs +242 -0
- package/dist-boot/c-XL735JW5.mjs +500 -0
- package/dist-boot/c-Y7HADL5B.mjs +1107 -0
- package/dist-boot/c-YGQHSVSK.mjs +41 -0
- package/dist-boot/c-YMP2AMEB.mjs +10350 -0
- package/dist-boot/c-YRDO2WEF.mjs +12012 -0
- package/dist-boot/c-YUSTRATJ.mjs +8 -0
- package/dist-boot/c-Z2262R22.mjs +2697 -0
- package/dist-boot/c-Z4ALQMJC.mjs +709 -0
- package/dist-boot/c-Z5ZUQVUY.mjs +62 -0
- package/dist-boot/c-ZDZUKXF7.mjs +874 -0
- package/dist-boot/c-ZJYLGIC3.mjs +32 -0
- package/dist-boot/c-ZNA5CDMI.mjs +77 -0
- package/dist-boot/c-ZSO6DI3M.mjs +204 -0
- package/dist-boot/c-ZWUVHKMK.mjs +100 -0
- package/dist-boot/c-ZY4MESEW.mjs +832 -0
- package/dist-boot/c-ZZ5REI4L.mjs +329 -0
- package/dist-boot/cli.mjs +51 -0
- package/dist-boot/ext-anthropic-attribution.mjs +207 -0
- package/dist-boot/ext-pi-background-tasks.mjs +7234 -0
- package/dist-boot/ext-pi-subagents.mjs +37 -0
- package/dist-boot/ext-privateer-account.mjs +4426 -0
- package/dist-boot/ext-privateer-brand.mjs +5274 -0
- package/dist-boot/ext-privateer-codemode.mjs +37 -0
- package/dist-boot/ext-privateer-computer.mjs +626 -0
- package/dist-boot/ext-privateer-connect.mjs +1862 -0
- package/dist-boot/ext-privateer-context.mjs +323 -0
- package/dist-boot/ext-privateer-desktop.mjs +92 -0
- package/dist-boot/ext-privateer-final-audit.mjs +48 -0
- package/dist-boot/ext-privateer-fresh.mjs +81 -0
- package/dist-boot/ext-privateer-gate.mjs +5959 -0
- package/dist-boot/ext-privateer-hints.mjs +236 -0
- package/dist-boot/ext-privateer-media.mjs +2626 -0
- package/dist-boot/ext-privateer-models.mjs +3397 -0
- package/dist-boot/ext-privateer-posture.mjs +3006 -0
- package/dist-boot/ext-privateer-privacy.mjs +4578 -0
- package/dist-boot/ext-privateer-recovery.mjs +287 -0
- package/dist-boot/ext-privateer-speak.mjs +4754 -0
- package/dist-boot/ext-privateer-terminals.mjs +725 -0
- package/dist-boot/ext-privateer-tools.mjs +528 -0
- package/dist-boot/ext-privateer-truncation.mjs +74 -0
- package/dist-boot/ext-privateer-update.mjs +210 -0
- package/dist-boot/ext-privateer-vision.mjs +148 -0
- package/dist-boot/ext-privateer-web.mjs +543 -0
- package/dist-boot/ext-rpiv-ask-user-question.mjs +710 -0
- package/dist-boot/manifest.json +9 -0
- package/extensions/privateer-brand.ts +3 -1
- package/extensions/privateer-context.ts +4 -2
- package/extensions/privateer-final-audit.ts +17 -0
- package/extensions/privateer-gate.ts +55 -9
- package/extensions/privateer-recovery.ts +175 -0
- package/extensions/privateer-web.ts +4 -4
- package/package.json +3 -1
- package/patches/@earendil-works+pi-coding-agent+1.3.0.patch +199 -2
- package/patches/pi-background-tasks+2.6.10.patch +91 -7
- package/src/cli/chat.ts +7 -1
- package/src/config/moatManifest.json +2 -0
- package/src/config/whatsNew.json +4 -4
- package/src/context.ts +33 -5
- package/src/engine/finalAudit.ts +38 -0
- package/src/engine/stallWatchdog.ts +8 -1
- package/src/providers/account.ts +34 -3
- package/src/remote/attentionPush.ts +40 -0
- package/src/remote/liveTaskSession.ts +7 -1
- package/src/remote/relayClient.ts +38 -4
- package/src/remote/remoteBridge.ts +17 -0
- package/src/remote/transcriptSnapshot.ts +104 -0
|
@@ -0,0 +1,4665 @@
|
|
|
1
|
+
import{createRequire as __pvCR}from"node:module";import{fileURLToPath as __pvF}from"node:url";import{dirname as __pvD}from"node:path";const require=__pvCR(import.meta.url);const __pvUrl=(r)=>new URL(r,import.meta.url).href;const __pvFile=(r)=>__pvF(new URL(r,import.meta.url));
|
|
2
|
+
import {
|
|
3
|
+
FusionArtifactStore,
|
|
4
|
+
buildFusionFailureSummary,
|
|
5
|
+
buildFusionRunProgress
|
|
6
|
+
} from "./c-Z4ALQMJC.mjs";
|
|
7
|
+
import {
|
|
8
|
+
FUSION_CANDIDATE_MAX_OUTPUT_BYTES,
|
|
9
|
+
FUSION_DIAGNOSTICS_MAX_BYTES,
|
|
10
|
+
FUSION_EVALUATION_MAX_OUTPUT_BYTES,
|
|
11
|
+
FUSION_MERGE_MAX_OUTPUT_BYTES,
|
|
12
|
+
FUSION_REASON_WORKFLOW,
|
|
13
|
+
assertChildOutputWithinContract,
|
|
14
|
+
assertWorkflowCapability,
|
|
15
|
+
buildBlindEvaluationInput,
|
|
16
|
+
buildCandidatePrompt,
|
|
17
|
+
buildEvaluationPrompt,
|
|
18
|
+
buildEvaluationRepairPrompt,
|
|
19
|
+
buildMergeInput,
|
|
20
|
+
buildMergePrompt,
|
|
21
|
+
fusionJsonRenderedTextBytes,
|
|
22
|
+
fusionWorkflowProfile
|
|
23
|
+
} from "./c-EBZZNGSF.mjs";
|
|
24
|
+
import {
|
|
25
|
+
buildFusionSourcePolicy,
|
|
26
|
+
canonicalizeFusionPublicUrl,
|
|
27
|
+
readFusionSourcePolicyFile,
|
|
28
|
+
sourcePolicyCanonicalBytes
|
|
29
|
+
} from "./c-JJWZBY7L.mjs";
|
|
30
|
+
import {
|
|
31
|
+
FUSION_BUDGET_PLAN_SCHEMA_VERSION,
|
|
32
|
+
FUSION_CALIBRATION_VIOLATION_SCHEMA_VERSION,
|
|
33
|
+
FUSION_CANDIDATE_IDS,
|
|
34
|
+
FUSION_EVALUATION_SCHEMA_VERSION,
|
|
35
|
+
FUSION_FORBIDDEN_TOOLS,
|
|
36
|
+
FUSION_INPUT_SCHEMA_VERSION,
|
|
37
|
+
FUSION_INSPECT_TOOLS,
|
|
38
|
+
FUSION_NO_TOOLS_CAPABILITY,
|
|
39
|
+
FUSION_RESEARCH_TOOLS,
|
|
40
|
+
FUSION_RESULT_SCHEMA_VERSION,
|
|
41
|
+
FUSION_TOOL_CALL_LOG_SCHEMA_VERSION,
|
|
42
|
+
FUSION_VALIDATE_CANDIDATE_SCHEMA_VERSION,
|
|
43
|
+
FUSION_WEB_FETCH_TOOL_NAME,
|
|
44
|
+
FusionError,
|
|
45
|
+
addFusionUsage,
|
|
46
|
+
cloneFusionUsage,
|
|
47
|
+
createEmptyFusionUsage
|
|
48
|
+
} from "./c-37GM3GMU.mjs";
|
|
49
|
+
import {
|
|
50
|
+
TOKEN_BUDGET_AFFINE_F_TOKENS,
|
|
51
|
+
TOKEN_BUDGET_CALIBRATION_VERSION,
|
|
52
|
+
TOKEN_BUDGET_FAMILY_CALIBRATIONS,
|
|
53
|
+
TOKEN_BUDGET_LARGE_PROMPT_MIN_BYTES,
|
|
54
|
+
TOKEN_BUDGET_RATE_SCALE,
|
|
55
|
+
allowedInputTokens,
|
|
56
|
+
estimateInputTokens,
|
|
57
|
+
isUsableContextWindow,
|
|
58
|
+
knownTextSegment,
|
|
59
|
+
maxKnownTextBytesForTokens,
|
|
60
|
+
resolveTokenBudgetFamily,
|
|
61
|
+
unknownOutputContractSegment
|
|
62
|
+
} from "./c-US2PUNZL.mjs";
|
|
63
|
+
import {
|
|
64
|
+
canonicalJson
|
|
65
|
+
} from "./c-KIVXWBYD.mjs";
|
|
66
|
+
import {
|
|
67
|
+
assertWindowsCommandLineWithinLimit,
|
|
68
|
+
piLaunchArgv,
|
|
69
|
+
resolvePiLaunch
|
|
70
|
+
} from "./c-KIEMEFWW.mjs";
|
|
71
|
+
import {
|
|
72
|
+
resolveAnthropicAttributionExtensionPath
|
|
73
|
+
} from "./c-BFW6CDVL.mjs";
|
|
74
|
+
import "./c-DR7XQI32.mjs";
|
|
75
|
+
import {
|
|
76
|
+
isJsonObject,
|
|
77
|
+
parseJsonText
|
|
78
|
+
} from "./c-I3C6LBW4.mjs";
|
|
79
|
+
import "./c-B2Y7VNHU.mjs";
|
|
80
|
+
import "./c-SKMK3PEE.mjs";
|
|
81
|
+
import "./c-I54OKQHW.mjs";
|
|
82
|
+
import "./c-UPBH5AXY.mjs";
|
|
83
|
+
import "./c-7UOG7POX.mjs";
|
|
84
|
+
import "./c-E4Z3Y5B5.mjs";
|
|
85
|
+
import "./c-6ZO2SADQ.mjs";
|
|
86
|
+
import "./c-JKTFXTGF.mjs";
|
|
87
|
+
import "./c-CRBXNKFO.mjs";
|
|
88
|
+
import "./c-KP6CRIP4.mjs";
|
|
89
|
+
import "./c-RSKCUPJF.mjs";
|
|
90
|
+
import "./c-PTN4S6G2.mjs";
|
|
91
|
+
import "./c-P6N5DQGZ.mjs";
|
|
92
|
+
import "./c-6LOBR4I6.mjs";
|
|
93
|
+
import "./c-3STKRSVO.mjs";
|
|
94
|
+
import "./c-YRDO2WEF.mjs";
|
|
95
|
+
import "./c-BHNIF2AR.mjs";
|
|
96
|
+
import "./c-ABXULAFL.mjs";
|
|
97
|
+
import "./c-2G3BBHRJ.mjs";
|
|
98
|
+
import "./c-O5PP6CTX.mjs";
|
|
99
|
+
import "./c-CDBEJSBF.mjs";
|
|
100
|
+
import "./c-DEE67DJO.mjs";
|
|
101
|
+
import "./c-WO3QSZUU.mjs";
|
|
102
|
+
import "./c-S2RVHR3M.mjs";
|
|
103
|
+
import "./c-EQBKXWNX.mjs";
|
|
104
|
+
import "./c-RQJJ26T4.mjs";
|
|
105
|
+
import "./c-L5GHWG5G.mjs";
|
|
106
|
+
import {
|
|
107
|
+
__name
|
|
108
|
+
} from "./c-CN77DCGF.mjs";
|
|
109
|
+
|
|
110
|
+
// node_modules/pi-background-tasks/src/core/fusion/orchestrator.ts?ext=pi-background-tasks
|
|
111
|
+
import { createHash as createHash4, randomBytes as nodeRandomBytes } from "node:crypto";
|
|
112
|
+
|
|
113
|
+
// node_modules/pi-background-tasks/src/core/fusion/budget.ts?ext=pi-background-tasks
|
|
114
|
+
import { createHash } from "node:crypto";
|
|
115
|
+
var FUSION_CALIBRATED_BYTES_PER_TOKEN = TOKEN_BUDGET_FAMILY_CALIBRATIONS;
|
|
116
|
+
var FUSION_OUTPUT_RESERVE_RATE_X100 = 200;
|
|
117
|
+
var FUSION_UTILIZATION_WARNING_THRESHOLD_BASIS_POINTS = 8e3;
|
|
118
|
+
var BASIS_POINTS_DENOMINATOR = 1e4;
|
|
119
|
+
function ceilDiv(numerator, denominator) {
|
|
120
|
+
if (!Number.isSafeInteger(numerator) || numerator < 0) {
|
|
121
|
+
throw new TypeError("ceilDiv numerator must be a non-negative safe integer");
|
|
122
|
+
}
|
|
123
|
+
if (!Number.isSafeInteger(denominator) || denominator <= 0) {
|
|
124
|
+
throw new TypeError("ceilDiv denominator must be a positive safe integer");
|
|
125
|
+
}
|
|
126
|
+
if (numerator === 0) return 0;
|
|
127
|
+
return Math.floor((numerator - 1) / denominator) + 1;
|
|
128
|
+
}
|
|
129
|
+
__name(ceilDiv, "ceilDiv");
|
|
130
|
+
var FUSION_RESERVED_OUTPUT_TOKENS = ceilDiv(
|
|
131
|
+
FUSION_MERGE_MAX_OUTPUT_BYTES * TOKEN_BUDGET_RATE_SCALE,
|
|
132
|
+
FUSION_OUTPUT_RESERVE_RATE_X100
|
|
133
|
+
);
|
|
134
|
+
var FUSION_FRAMING_RESERVE_TOKENS = 0;
|
|
135
|
+
var FUSION_SAFETY_RESERVE_TOKENS = 4096;
|
|
136
|
+
var FUSION_MIN_CANONICAL_INPUT_TOKENS = 8192;
|
|
137
|
+
var FUSION_MIN_CONTEXT_WINDOW_TOKENS = FUSION_MIN_CANONICAL_INPUT_TOKENS + FUSION_RESERVED_OUTPUT_TOKENS + FUSION_FRAMING_RESERVE_TOKENS + FUSION_SAFETY_RESERVE_TOKENS;
|
|
138
|
+
var FUSION_BUDGET_POLICY = {
|
|
139
|
+
id: "fusion-budget-policy-v4",
|
|
140
|
+
route_output_reserve_strategy: "max_fusion_contract_or_model_max",
|
|
141
|
+
calibration_version: TOKEN_BUDGET_CALIBRATION_VERSION,
|
|
142
|
+
calibration_table: FUSION_CALIBRATED_BYTES_PER_TOKEN,
|
|
143
|
+
reserved_output_tokens: FUSION_RESERVED_OUTPUT_TOKENS,
|
|
144
|
+
framing_reserve_tokens: FUSION_FRAMING_RESERVE_TOKENS,
|
|
145
|
+
safety_reserve_tokens: FUSION_SAFETY_RESERVE_TOKENS,
|
|
146
|
+
candidate_output_contract_bytes: FUSION_CANDIDATE_MAX_OUTPUT_BYTES,
|
|
147
|
+
evaluation_output_contract_bytes: FUSION_EVALUATION_MAX_OUTPUT_BYTES,
|
|
148
|
+
merge_output_contract_bytes: FUSION_MERGE_MAX_OUTPUT_BYTES,
|
|
149
|
+
diagnostics_contract_bytes: FUSION_DIAGNOSTICS_MAX_BYTES,
|
|
150
|
+
utilization_warning_threshold_basis_points: FUSION_UTILIZATION_WARNING_THRESHOLD_BASIS_POINTS
|
|
151
|
+
};
|
|
152
|
+
var REASON_EMPTY_REMEDIATION = Object.freeze([
|
|
153
|
+
"Start a fresh Pi conversation, or run fusion_reason earlier in the session.",
|
|
154
|
+
"Raise the route's context window with a larger-context subscription model via /fusion-models.",
|
|
155
|
+
"Restate only the required prior findings in the fusion_reason prompt."
|
|
156
|
+
]);
|
|
157
|
+
var REASON_REQUEST_REMEDIATION = Object.freeze([
|
|
158
|
+
"Provide a shorter fusion_reason prompt.",
|
|
159
|
+
"Start a fresh Pi conversation, or run fusion_reason earlier in the session.",
|
|
160
|
+
"Raise the route's context window with a larger-context subscription model via /fusion-models."
|
|
161
|
+
]);
|
|
162
|
+
var INVESTIGATE_EMPTY_REMEDIATION = Object.freeze([
|
|
163
|
+
"Split the repository investigation into smaller independently complete path or subsystem scopes.",
|
|
164
|
+
"Raise the route's context window with a larger-context subscription model via /fusion-models."
|
|
165
|
+
]);
|
|
166
|
+
var INVESTIGATE_REQUEST_REMEDIATION = Object.freeze([
|
|
167
|
+
"Narrow the fusion_investigate objective, repository scope, or required evidence.",
|
|
168
|
+
"Raise the route's context window with a larger-context subscription model via /fusion-models."
|
|
169
|
+
]);
|
|
170
|
+
var RESEARCH_EMPTY_REMEDIATION = Object.freeze([
|
|
171
|
+
"Split the research into smaller independently complete source sets.",
|
|
172
|
+
"Raise the route's context window with a larger-context subscription model via /fusion-models."
|
|
173
|
+
]);
|
|
174
|
+
var RESEARCH_REQUEST_REMEDIATION = Object.freeze([
|
|
175
|
+
"Narrow the fusion_research question or split large declared-source sets across independent runs.",
|
|
176
|
+
"Raise the route's context window with a larger-context subscription model via /fusion-models."
|
|
177
|
+
]);
|
|
178
|
+
var VALIDATE_EMPTY_REMEDIATION = Object.freeze([
|
|
179
|
+
"Split validation into smaller independently complete change or acceptance-criterion scopes.",
|
|
180
|
+
"Raise the route's context window with a larger-context subscription model via /fusion-models."
|
|
181
|
+
]);
|
|
182
|
+
var VALIDATE_REQUEST_REMEDIATION = Object.freeze([
|
|
183
|
+
"Narrow the fusion_validate scope, acceptance criteria, or supplied verification evidence.",
|
|
184
|
+
"Raise the route's context window with a larger-context subscription model via /fusion-models."
|
|
185
|
+
]);
|
|
186
|
+
function cleanRemediation(profile, requestDeterminesFeasibility) {
|
|
187
|
+
if (profile.id === "investigate") {
|
|
188
|
+
return requestDeterminesFeasibility ? INVESTIGATE_REQUEST_REMEDIATION : INVESTIGATE_EMPTY_REMEDIATION;
|
|
189
|
+
}
|
|
190
|
+
if (profile.id === "research") {
|
|
191
|
+
return requestDeterminesFeasibility ? RESEARCH_REQUEST_REMEDIATION : RESEARCH_EMPTY_REMEDIATION;
|
|
192
|
+
}
|
|
193
|
+
if (profile.id === "validate") {
|
|
194
|
+
return requestDeterminesFeasibility ? VALIDATE_REQUEST_REMEDIATION : VALIDATE_EMPTY_REMEDIATION;
|
|
195
|
+
}
|
|
196
|
+
return requestDeterminesFeasibility ? REASON_REQUEST_REMEDIATION : REASON_EMPTY_REMEDIATION;
|
|
197
|
+
}
|
|
198
|
+
__name(cleanRemediation, "cleanRemediation");
|
|
199
|
+
var RESERVATION_REMEDIATION = Object.freeze([
|
|
200
|
+
"Route the blocking stage to a model with larger byte capacity.",
|
|
201
|
+
"Keep producer output contracts intact; do not shrink or truncate child answers.",
|
|
202
|
+
"Inspect budget-plan.json for the advisory reservation component before retrying."
|
|
203
|
+
]);
|
|
204
|
+
var DENSE_REMEDIATION = Object.freeze([
|
|
205
|
+
"Remove or externalize low-whitespace dense ASCII payloads (base64, minified code, PEM/hex blocks), then retry.",
|
|
206
|
+
"The prompt is preserved; no content was clipped to fit."
|
|
207
|
+
]);
|
|
208
|
+
var MULTIBYTE_REMEDIATION = Object.freeze([
|
|
209
|
+
"This rejection is dominated by multibyte UTF-8 content; use a larger-context route or split the non-Latin/CJK-heavy task.",
|
|
210
|
+
"The budget plan includes the stricter multibyte advisory ceiling separately from the fatal estimate.",
|
|
211
|
+
"The prompt is preserved; no content was clipped to fit."
|
|
212
|
+
]);
|
|
213
|
+
var EMPTY_CANDIDATES = Object.freeze([
|
|
214
|
+
Object.freeze({ candidate_id: "A", response: "" }),
|
|
215
|
+
Object.freeze({ candidate_id: "B", response: "" }),
|
|
216
|
+
Object.freeze({ candidate_id: "C", response: "" })
|
|
217
|
+
]);
|
|
218
|
+
var EMPTY_EVALUATION = Object.freeze({
|
|
219
|
+
schema_version: FUSION_EVALUATION_SCHEMA_VERSION,
|
|
220
|
+
candidate_assessments: Object.freeze([
|
|
221
|
+
Object.freeze({
|
|
222
|
+
candidate_id: "A",
|
|
223
|
+
summary: "",
|
|
224
|
+
strengths: Object.freeze([]),
|
|
225
|
+
limitations: Object.freeze([]),
|
|
226
|
+
useful_contributions: Object.freeze([]),
|
|
227
|
+
risks: Object.freeze([])
|
|
228
|
+
}),
|
|
229
|
+
Object.freeze({
|
|
230
|
+
candidate_id: "B",
|
|
231
|
+
summary: "",
|
|
232
|
+
strengths: Object.freeze([]),
|
|
233
|
+
limitations: Object.freeze([]),
|
|
234
|
+
useful_contributions: Object.freeze([]),
|
|
235
|
+
risks: Object.freeze([])
|
|
236
|
+
}),
|
|
237
|
+
Object.freeze({
|
|
238
|
+
candidate_id: "C",
|
|
239
|
+
summary: "",
|
|
240
|
+
strengths: Object.freeze([]),
|
|
241
|
+
limitations: Object.freeze([]),
|
|
242
|
+
useful_contributions: Object.freeze([]),
|
|
243
|
+
risks: Object.freeze([])
|
|
244
|
+
})
|
|
245
|
+
]),
|
|
246
|
+
agreements: Object.freeze([]),
|
|
247
|
+
conflicts: Object.freeze([]),
|
|
248
|
+
synthesis_plan: Object.freeze({
|
|
249
|
+
must_include: Object.freeze([]),
|
|
250
|
+
must_resolve: Object.freeze([]),
|
|
251
|
+
must_avoid: Object.freeze([])
|
|
252
|
+
})
|
|
253
|
+
});
|
|
254
|
+
function utf8Bytes(value) {
|
|
255
|
+
return Buffer.byteLength(value, "utf8");
|
|
256
|
+
}
|
|
257
|
+
__name(utf8Bytes, "utf8Bytes");
|
|
258
|
+
function sha256Hex(value) {
|
|
259
|
+
return createHash("sha256").update(value, "utf8").digest("hex");
|
|
260
|
+
}
|
|
261
|
+
__name(sha256Hex, "sha256Hex");
|
|
262
|
+
function requirePositiveContextWindow(model, role) {
|
|
263
|
+
const value = model.contextWindow;
|
|
264
|
+
if (!isUsableContextWindow(value)) {
|
|
265
|
+
throw new FusionError(
|
|
266
|
+
`fusion ${role} route ${model.qualifiedId} has no usable context window capacity`,
|
|
267
|
+
{ code: "model_capacity_unknown", childCreated: false }
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
return value;
|
|
271
|
+
}
|
|
272
|
+
__name(requirePositiveContextWindow, "requirePositiveContextWindow");
|
|
273
|
+
function formatRateX100(value) {
|
|
274
|
+
const whole = Math.floor(value / TOKEN_BUDGET_RATE_SCALE);
|
|
275
|
+
const frac = String(value % TOKEN_BUDGET_RATE_SCALE).padStart(2, "0");
|
|
276
|
+
return `${String(whole)}.${frac}`;
|
|
277
|
+
}
|
|
278
|
+
__name(formatRateX100, "formatRateX100");
|
|
279
|
+
function routeCapacity(model, role) {
|
|
280
|
+
const contextWindow = requirePositiveContextWindow(model, role);
|
|
281
|
+
const reservedOutputTokens = Math.max(FUSION_RESERVED_OUTPUT_TOKENS, model.maxOutputTokens);
|
|
282
|
+
const allowed = allowedInputTokens(contextWindow, {
|
|
283
|
+
reservedOutputTokens,
|
|
284
|
+
framingReserveTokens: FUSION_FRAMING_RESERVE_TOKENS,
|
|
285
|
+
safetyReserveTokens: FUSION_SAFETY_RESERVE_TOKENS
|
|
286
|
+
});
|
|
287
|
+
if (allowed < FUSION_MIN_CANONICAL_INPUT_TOKENS) {
|
|
288
|
+
const minimumContextWindow = reservedOutputTokens + FUSION_FRAMING_RESERVE_TOKENS + FUSION_SAFETY_RESERVE_TOKENS + FUSION_MIN_CANONICAL_INPUT_TOKENS;
|
|
289
|
+
throw new FusionError(
|
|
290
|
+
`fusion ${role} route ${model.qualifiedId} has a ${String(contextWindow)}-token context window, but Fusion requires at least ${String(minimumContextWindow)} tokens: ${String(reservedOutputTokens)} reserved for the route's configured maximum output + ${String(FUSION_FRAMING_RESERVE_TOKENS)} framing + ${String(FUSION_SAFETY_RESERVE_TOKENS)} safety + ${String(FUSION_MIN_CANONICAL_INPUT_TOKENS)} usable input. Choose a larger-context or lower-max-output subscription model for this slot with /fusion-models.`,
|
|
291
|
+
{ code: "model_capacity_unknown", childCreated: false }
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
const family = resolveTokenBudgetFamily({ provider: model.provider, model: model.model });
|
|
295
|
+
const rateSource = estimateInputTokens({
|
|
296
|
+
family: family.family,
|
|
297
|
+
calibrationBacked: family.backed,
|
|
298
|
+
familyResolution: family.resolution,
|
|
299
|
+
allowedInputTokens: allowed,
|
|
300
|
+
scope: "fusion",
|
|
301
|
+
segments: [
|
|
302
|
+
{
|
|
303
|
+
kind: "known_text",
|
|
304
|
+
bytes: TOKEN_BUDGET_LARGE_PROMPT_MIN_BYTES,
|
|
305
|
+
multibyteBytes: 0,
|
|
306
|
+
denseBytes: 0,
|
|
307
|
+
asciiWhitespaceBytes: TOKEN_BUDGET_LARGE_PROMPT_MIN_BYTES
|
|
308
|
+
}
|
|
309
|
+
]
|
|
310
|
+
}).rateSource;
|
|
311
|
+
return {
|
|
312
|
+
role,
|
|
313
|
+
provider: model.provider,
|
|
314
|
+
model: model.model,
|
|
315
|
+
qualified_id: model.qualifiedId,
|
|
316
|
+
context_window_tokens: contextWindow,
|
|
317
|
+
reserved_output_tokens: reservedOutputTokens,
|
|
318
|
+
framing_reserve_tokens: FUSION_FRAMING_RESERVE_TOKENS,
|
|
319
|
+
safety_reserve_tokens: FUSION_SAFETY_RESERVE_TOKENS,
|
|
320
|
+
allowed_input_tokens: allowed,
|
|
321
|
+
family: family.family,
|
|
322
|
+
rate_source: rateSource,
|
|
323
|
+
byte_capacity_utf8_bytes: Math.floor(
|
|
324
|
+
allowed * rateSource.effective_rate_bytes_per_token_x100 / TOKEN_BUDGET_RATE_SCALE
|
|
325
|
+
)
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
__name(routeCapacity, "routeCapacity");
|
|
329
|
+
function fusionRouteCapacities(models) {
|
|
330
|
+
return [
|
|
331
|
+
routeCapacity(models.candidates[0], "candidate-1"),
|
|
332
|
+
routeCapacity(models.candidates[1], "candidate-2"),
|
|
333
|
+
routeCapacity(models.candidates[2], "candidate-3"),
|
|
334
|
+
routeCapacity(models.evaluator, "evaluator"),
|
|
335
|
+
routeCapacity(models.merger, "merger")
|
|
336
|
+
];
|
|
337
|
+
}
|
|
338
|
+
__name(fusionRouteCapacities, "fusionRouteCapacities");
|
|
339
|
+
function fusionLimitingRoute(routes) {
|
|
340
|
+
let limiting;
|
|
341
|
+
for (const route of routes) {
|
|
342
|
+
if (limiting === void 0 || route.byte_capacity_utf8_bytes < limiting.byte_capacity_utf8_bytes || route.byte_capacity_utf8_bytes === limiting.byte_capacity_utf8_bytes && route.role.localeCompare(limiting.role) < 0) {
|
|
343
|
+
limiting = route;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (limiting === void 0) {
|
|
347
|
+
throw new FusionError("fusion budget planning received no configured routes", {
|
|
348
|
+
code: "model_capacity_unknown",
|
|
349
|
+
childCreated: false
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
return limiting;
|
|
353
|
+
}
|
|
354
|
+
__name(fusionLimitingRoute, "fusionLimitingRoute");
|
|
355
|
+
function routeByRole(routes, role) {
|
|
356
|
+
const route = routes.find((item) => item.role === role);
|
|
357
|
+
if (route === void 0) {
|
|
358
|
+
throw new FusionError(`fusion budget route ${role} is missing`, {
|
|
359
|
+
code: "model_capacity_unknown",
|
|
360
|
+
childCreated: false
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
return route;
|
|
364
|
+
}
|
|
365
|
+
__name(routeByRole, "routeByRole");
|
|
366
|
+
function candidateRole(slot) {
|
|
367
|
+
if (slot === 1) return "candidate-1";
|
|
368
|
+
if (slot === 2) return "candidate-2";
|
|
369
|
+
return "candidate-3";
|
|
370
|
+
}
|
|
371
|
+
__name(candidateRole, "candidateRole");
|
|
372
|
+
function estimateRouteInput(route, segments) {
|
|
373
|
+
return estimateInputTokens({
|
|
374
|
+
family: route.family,
|
|
375
|
+
calibrationBacked: route.rate_source.backed,
|
|
376
|
+
familyResolution: route.rate_source.model_resolution,
|
|
377
|
+
allowedInputTokens: route.allowed_input_tokens,
|
|
378
|
+
scope: "fusion",
|
|
379
|
+
segments
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
__name(estimateRouteInput, "estimateRouteInput");
|
|
383
|
+
function utilizationBasisPoints(tokens, allowed) {
|
|
384
|
+
return ceilDiv(tokens * BASIS_POINTS_DENOMINATOR, allowed);
|
|
385
|
+
}
|
|
386
|
+
__name(utilizationBasisPoints, "utilizationBasisPoints");
|
|
387
|
+
function forecastEntry(draft) {
|
|
388
|
+
const inputSegments = [knownTextSegment(draft.system_prompt), knownTextSegment(draft.empty_user_prompt)];
|
|
389
|
+
const inputBytes = inputSegments.reduce((sum, segment) => sum + segment.bytes, 0);
|
|
390
|
+
const inputOnly = estimateRouteInput(draft.route, inputSegments);
|
|
391
|
+
const reservationSegments = draft.upstream_output_contract_bytes === 0 ? inputSegments : [...inputSegments, unknownOutputContractSegment(draft.upstream_output_contract_bytes)];
|
|
392
|
+
const reservation = estimateRouteInput(draft.route, reservationSegments);
|
|
393
|
+
const forecastUtf8Bytes = inputBytes + draft.upstream_output_contract_bytes;
|
|
394
|
+
const entry = {
|
|
395
|
+
budget_stage: draft.budget_stage,
|
|
396
|
+
route: draft.route,
|
|
397
|
+
conditional: draft.conditional,
|
|
398
|
+
check_kind: "input_only_preflight",
|
|
399
|
+
input_utf8_bytes: inputBytes,
|
|
400
|
+
upstream_output_contract_bytes: draft.upstream_output_contract_bytes,
|
|
401
|
+
forecast_utf8_bytes: forecastUtf8Bytes,
|
|
402
|
+
input_only_input_tokens_upper_bound: inputOnly.tokens,
|
|
403
|
+
forecast_input_tokens_upper_bound: reservation.tokens,
|
|
404
|
+
allowed_input_tokens: draft.route.allowed_input_tokens,
|
|
405
|
+
input_only_signed_headroom_tokens: draft.route.allowed_input_tokens - inputOnly.tokens,
|
|
406
|
+
signed_headroom_tokens: draft.route.allowed_input_tokens - reservation.tokens,
|
|
407
|
+
input_only_utilization_basis_points: utilizationBasisPoints(
|
|
408
|
+
inputOnly.tokens,
|
|
409
|
+
draft.route.allowed_input_tokens
|
|
410
|
+
),
|
|
411
|
+
utilization_basis_points: utilizationBasisPoints(
|
|
412
|
+
reservation.tokens,
|
|
413
|
+
draft.route.allowed_input_tokens
|
|
414
|
+
),
|
|
415
|
+
input_only_estimate: inputOnly,
|
|
416
|
+
reservation_estimate: reservation,
|
|
417
|
+
fits: inputOnly.tokens <= draft.route.allowed_input_tokens,
|
|
418
|
+
reservation_fits: reservation.tokens <= draft.route.allowed_input_tokens
|
|
419
|
+
};
|
|
420
|
+
if (draft.slot !== void 0) entry.slot = draft.slot;
|
|
421
|
+
return entry;
|
|
422
|
+
}
|
|
423
|
+
__name(forecastEntry, "forecastEntry");
|
|
424
|
+
function maxKnownTextBytes(route) {
|
|
425
|
+
return maxKnownTextBytesForTokens({
|
|
426
|
+
family: route.family,
|
|
427
|
+
calibrationBacked: route.rate_source.backed,
|
|
428
|
+
familyResolution: route.rate_source.model_resolution,
|
|
429
|
+
allowedInputTokens: route.allowed_input_tokens,
|
|
430
|
+
scope: "fusion"
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
__name(maxKnownTextBytes, "maxKnownTextBytes");
|
|
434
|
+
function blockerFromEntry(entry) {
|
|
435
|
+
return {
|
|
436
|
+
...entry,
|
|
437
|
+
overage_tokens: Math.max(0, entry.input_only_input_tokens_upper_bound - entry.allowed_input_tokens),
|
|
438
|
+
bytes_over: Math.max(0, entry.input_utf8_bytes - maxKnownTextBytes(entry.route))
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
__name(blockerFromEntry, "blockerFromEntry");
|
|
442
|
+
function blockerOrder(blocker) {
|
|
443
|
+
if (blocker.budget_stage === "candidate") return blocker.slot ?? 1;
|
|
444
|
+
if (blocker.budget_stage === "evaluation") return 4;
|
|
445
|
+
if (blocker.budget_stage === "merge") return 5;
|
|
446
|
+
return 6;
|
|
447
|
+
}
|
|
448
|
+
__name(blockerOrder, "blockerOrder");
|
|
449
|
+
function selectBlockers(entries) {
|
|
450
|
+
return entries.filter((entry) => !entry.fits).map(blockerFromEntry).sort((left, right) => {
|
|
451
|
+
const byOrder = blockerOrder(left) - blockerOrder(right);
|
|
452
|
+
if (byOrder !== 0) return byOrder;
|
|
453
|
+
return left.route.role.localeCompare(right.route.role);
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
__name(selectBlockers, "selectBlockers");
|
|
457
|
+
function selectPrimaryBlocker(blockers) {
|
|
458
|
+
const mandatory = blockers.find((blocker) => !blocker.conditional);
|
|
459
|
+
return mandatory ?? blockers[0];
|
|
460
|
+
}
|
|
461
|
+
__name(selectPrimaryBlocker, "selectPrimaryBlocker");
|
|
462
|
+
function replaceRequestText(input, text) {
|
|
463
|
+
return {
|
|
464
|
+
...input,
|
|
465
|
+
request: {
|
|
466
|
+
...input.request,
|
|
467
|
+
text,
|
|
468
|
+
sha256: sha256Hex(text)
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
__name(replaceRequestText, "replaceRequestText");
|
|
473
|
+
function visibleTextBytes(input) {
|
|
474
|
+
const projection = input.context?.kind === "session_projection" ? input.context.conversation_projection : "conversation_projection" in input ? input.conversation_projection : void 0;
|
|
475
|
+
if (projection === void 0) return 0;
|
|
476
|
+
let total = 0;
|
|
477
|
+
for (const entry of projection.entries) {
|
|
478
|
+
if (entry[0] === "t") total += utf8Bytes(JSON.stringify(entry[4]));
|
|
479
|
+
}
|
|
480
|
+
return total;
|
|
481
|
+
}
|
|
482
|
+
__name(visibleTextBytes, "visibleTextBytes");
|
|
483
|
+
function omissionReceiptBytes(input) {
|
|
484
|
+
const projection = input.context?.kind === "session_projection" ? input.context.conversation_projection : "conversation_projection" in input ? input.conversation_projection : void 0;
|
|
485
|
+
if (projection === void 0) return 0;
|
|
486
|
+
let total = 0;
|
|
487
|
+
for (const entry of projection.entries) {
|
|
488
|
+
if (entry[0] === "o") total += utf8Bytes(JSON.stringify(entry));
|
|
489
|
+
}
|
|
490
|
+
return total;
|
|
491
|
+
}
|
|
492
|
+
__name(omissionReceiptBytes, "omissionReceiptBytes");
|
|
493
|
+
function warningFromEntry(entry) {
|
|
494
|
+
if (!entry.fits) return void 0;
|
|
495
|
+
if (!entry.reservation_fits) {
|
|
496
|
+
return {
|
|
497
|
+
...entry,
|
|
498
|
+
warning_kind: "worst_case_reservation",
|
|
499
|
+
threshold_basis_points: FUSION_UTILIZATION_WARNING_THRESHOLD_BASIS_POINTS
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
if (entry.utilization_basis_points >= FUSION_UTILIZATION_WARNING_THRESHOLD_BASIS_POINTS) {
|
|
503
|
+
return {
|
|
504
|
+
...entry,
|
|
505
|
+
warning_kind: "input_utilization",
|
|
506
|
+
threshold_basis_points: FUSION_UTILIZATION_WARNING_THRESHOLD_BASIS_POINTS
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
return void 0;
|
|
510
|
+
}
|
|
511
|
+
__name(warningFromEntry, "warningFromEntry");
|
|
512
|
+
function warningsFor(entries) {
|
|
513
|
+
return entries.flatMap((entry) => {
|
|
514
|
+
const warning = warningFromEntry(entry);
|
|
515
|
+
return warning === void 0 ? [] : [warning];
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
__name(warningsFor, "warningsFor");
|
|
519
|
+
function entryLabel(entry) {
|
|
520
|
+
const slot = entry.slot === void 0 ? "" : `-${String(entry.slot)}`;
|
|
521
|
+
const conditional = entry.conditional ? " (conditional)" : "";
|
|
522
|
+
return `${entry.budget_stage}${slot}${conditional}`;
|
|
523
|
+
}
|
|
524
|
+
__name(entryLabel, "entryLabel");
|
|
525
|
+
function blockingChildLabel(entry) {
|
|
526
|
+
if (entry.budget_stage === "candidate") return `candidate-${String(entry.slot ?? 1)}`;
|
|
527
|
+
if (entry.budget_stage === "evaluation_repair") return "evaluator-repair";
|
|
528
|
+
if (entry.budget_stage === "evaluation") return "evaluator";
|
|
529
|
+
return "merger";
|
|
530
|
+
}
|
|
531
|
+
__name(blockingChildLabel, "blockingChildLabel");
|
|
532
|
+
function formatTable(entries) {
|
|
533
|
+
const lines = entries.map(
|
|
534
|
+
(entry) => `${entryLabel(entry)} | route=${entry.route.qualified_id} | input=${String(entry.input_only_input_tokens_upper_bound)} | reserved=${String(entry.forecast_input_tokens_upper_bound)} | allowed=${String(entry.allowed_input_tokens)} | input_headroom=${String(entry.input_only_signed_headroom_tokens)} | reservation_headroom=${String(entry.signed_headroom_tokens)} | ${entry.fits ? "input-fits" : "input-over"} | ${entry.reservation_fits ? "reservation-fits" : "reservation-warn"}`
|
|
535
|
+
);
|
|
536
|
+
return lines.join("\n");
|
|
537
|
+
}
|
|
538
|
+
__name(formatTable, "formatTable");
|
|
539
|
+
function formatComposition(composition) {
|
|
540
|
+
return [
|
|
541
|
+
`visible text ${String(composition.visible_text_bytes)} B`,
|
|
542
|
+
`omission receipts ${String(composition.omission_receipt_bytes)} B`,
|
|
543
|
+
`projection metadata ${String(composition.projection_metadata_bytes)} B`,
|
|
544
|
+
`request ${String(composition.request_bytes)} B`,
|
|
545
|
+
`static stage framing ${String(composition.static_stage_framing_bytes)} B`,
|
|
546
|
+
`upstream output contracts ${String(composition.upstream_output_contract_bytes)} B`
|
|
547
|
+
].join("; ");
|
|
548
|
+
}
|
|
549
|
+
__name(formatComposition, "formatComposition");
|
|
550
|
+
function dominantRemediation(verdict, composition, dominantByteClass, profile) {
|
|
551
|
+
if (dominantByteClass === "dense_ascii") return DENSE_REMEDIATION;
|
|
552
|
+
if (dominantByteClass === "multibyte") return MULTIBYTE_REMEDIATION;
|
|
553
|
+
if (composition === void 0) return remediationFor(verdict, profile);
|
|
554
|
+
const entries = [
|
|
555
|
+
{ name: "visible", bytes: composition.visible_text_bytes },
|
|
556
|
+
{ name: "request", bytes: composition.request_bytes },
|
|
557
|
+
{ name: "reservation", bytes: composition.upstream_output_contract_bytes }
|
|
558
|
+
].sort((left, right) => right.bytes - left.bytes);
|
|
559
|
+
const dominant = entries[0];
|
|
560
|
+
if (dominant?.name === "reservation") return RESERVATION_REMEDIATION;
|
|
561
|
+
if (dominant?.name === "request" && !verdict.still_fails_with_empty_request) {
|
|
562
|
+
return profile.contextKind === "clean_task" ? cleanRemediation(profile, true) : REASON_REQUEST_REMEDIATION;
|
|
563
|
+
}
|
|
564
|
+
if (profile.contextKind === "clean_task") return cleanRemediation(profile, false);
|
|
565
|
+
return REASON_EMPTY_REMEDIATION;
|
|
566
|
+
}
|
|
567
|
+
__name(dominantRemediation, "dominantRemediation");
|
|
568
|
+
function remediationFor(verdict, profile) {
|
|
569
|
+
if (profile.contextKind === "clean_task") {
|
|
570
|
+
return cleanRemediation(profile, !verdict.still_fails_with_empty_request);
|
|
571
|
+
}
|
|
572
|
+
return verdict.still_fails_with_empty_request ? REASON_EMPTY_REMEDIATION : REASON_REQUEST_REMEDIATION;
|
|
573
|
+
}
|
|
574
|
+
__name(remediationFor, "remediationFor");
|
|
575
|
+
function formatEmptyRequestVerdict(verdict) {
|
|
576
|
+
if (verdict.still_fails_with_empty_request) {
|
|
577
|
+
return `Empty-request counterfactual: still fails with ${String(verdict.blockers_with_empty_request.length)} blocking stage(s), so shortening the request cannot make this workflow fit.`;
|
|
578
|
+
}
|
|
579
|
+
if (verdict.minimum_request_byte_reduction === 0) {
|
|
580
|
+
return "Empty-request counterfactual: fits; no request reduction is required by the current plan.";
|
|
581
|
+
}
|
|
582
|
+
return `Empty-request counterfactual: fits, so request size determines feasibility; reduce the request by at least ${String(verdict.minimum_request_byte_reduction)} UTF-8 bytes, leaving at most ${String(verdict.maximum_safe_request_utf8_bytes)} UTF-8 bytes.`;
|
|
583
|
+
}
|
|
584
|
+
__name(formatEmptyRequestVerdict, "formatEmptyRequestVerdict");
|
|
585
|
+
function stageFromBudgetStage(stage) {
|
|
586
|
+
if (stage === "merge") return "merge";
|
|
587
|
+
if (stage === "candidate") return "candidate";
|
|
588
|
+
return "evaluation";
|
|
589
|
+
}
|
|
590
|
+
__name(stageFromBudgetStage, "stageFromBudgetStage");
|
|
591
|
+
function routeTable(routes) {
|
|
592
|
+
return [...routes].sort((left, right) => {
|
|
593
|
+
const byCapacity = left.byte_capacity_utf8_bytes - right.byte_capacity_utf8_bytes;
|
|
594
|
+
if (byCapacity !== 0) return byCapacity;
|
|
595
|
+
return left.role.localeCompare(right.role);
|
|
596
|
+
}).map((route) => ({
|
|
597
|
+
role: route.role,
|
|
598
|
+
qualified_id: route.qualified_id,
|
|
599
|
+
allowed_input_tokens: route.allowed_input_tokens,
|
|
600
|
+
family: route.family,
|
|
601
|
+
effective_rate_bytes_per_token_x100: route.rate_source.effective_rate_bytes_per_token_x100,
|
|
602
|
+
byte_capacity_utf8_bytes: route.byte_capacity_utf8_bytes,
|
|
603
|
+
backed: route.rate_source.backed
|
|
604
|
+
}));
|
|
605
|
+
}
|
|
606
|
+
__name(routeTable, "routeTable");
|
|
607
|
+
function segmentTokensForBytes(route, bytes) {
|
|
608
|
+
if (bytes <= 0) return 0;
|
|
609
|
+
const estimate = estimateRouteInput(route, [
|
|
610
|
+
{ kind: "known_text", bytes, multibyteBytes: 0, denseBytes: 0 }
|
|
611
|
+
]);
|
|
612
|
+
const segment = estimate.perSegment[0];
|
|
613
|
+
return segment === void 0 ? 0 : segment.tokens;
|
|
614
|
+
}
|
|
615
|
+
__name(segmentTokensForBytes, "segmentTokensForBytes");
|
|
616
|
+
function contractTokens(bytes) {
|
|
617
|
+
return bytes;
|
|
618
|
+
}
|
|
619
|
+
__name(contractTokens, "contractTokens");
|
|
620
|
+
function componentBreakdown(composition, route) {
|
|
621
|
+
const empty = {
|
|
622
|
+
visible_text_bytes: 0,
|
|
623
|
+
omission_receipt_bytes: 0,
|
|
624
|
+
projection_metadata_bytes: 0,
|
|
625
|
+
request_bytes: 0,
|
|
626
|
+
static_stage_framing_bytes: 0,
|
|
627
|
+
upstream_output_contract_bytes: 0
|
|
628
|
+
};
|
|
629
|
+
const item = composition ?? empty;
|
|
630
|
+
return {
|
|
631
|
+
visible_text: {
|
|
632
|
+
bytes: item.visible_text_bytes,
|
|
633
|
+
tokens: segmentTokensForBytes(route, item.visible_text_bytes)
|
|
634
|
+
},
|
|
635
|
+
omission_receipts: {
|
|
636
|
+
bytes: item.omission_receipt_bytes,
|
|
637
|
+
tokens: segmentTokensForBytes(route, item.omission_receipt_bytes)
|
|
638
|
+
},
|
|
639
|
+
projection_metadata: {
|
|
640
|
+
bytes: item.projection_metadata_bytes,
|
|
641
|
+
tokens: segmentTokensForBytes(route, item.projection_metadata_bytes)
|
|
642
|
+
},
|
|
643
|
+
request: {
|
|
644
|
+
bytes: item.request_bytes,
|
|
645
|
+
tokens: segmentTokensForBytes(route, item.request_bytes)
|
|
646
|
+
},
|
|
647
|
+
static_stage_framing: {
|
|
648
|
+
bytes: item.static_stage_framing_bytes,
|
|
649
|
+
tokens: segmentTokensForBytes(route, item.static_stage_framing_bytes)
|
|
650
|
+
},
|
|
651
|
+
upstream_output_contracts: {
|
|
652
|
+
bytes: item.upstream_output_contract_bytes,
|
|
653
|
+
tokens: contractTokens(item.upstream_output_contract_bytes)
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
__name(componentBreakdown, "componentBreakdown");
|
|
658
|
+
function medianCounterfactual(primary) {
|
|
659
|
+
if (!primary.route.rate_source.backed) {
|
|
660
|
+
return { forecast_input_tokens_upper_bound: null, signed_headroom_tokens: null, fits: null };
|
|
661
|
+
}
|
|
662
|
+
const median = primary.route.rate_source.provenance.median_bpt_x1000;
|
|
663
|
+
if (median === null) return { forecast_input_tokens_upper_bound: null, signed_headroom_tokens: null, fits: null };
|
|
664
|
+
const variableTokens = ceilDiv(primary.input_utf8_bytes * 1e3, median);
|
|
665
|
+
const tokens = variableTokens + TOKEN_BUDGET_AFFINE_F_TOKENS;
|
|
666
|
+
return {
|
|
667
|
+
forecast_input_tokens_upper_bound: tokens,
|
|
668
|
+
signed_headroom_tokens: primary.allowed_input_tokens - tokens,
|
|
669
|
+
fits: tokens <= primary.allowed_input_tokens
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
__name(medianCounterfactual, "medianCounterfactual");
|
|
673
|
+
function inputCounterfactuals(primary, plan) {
|
|
674
|
+
return {
|
|
675
|
+
empty_request: plan.empty_request,
|
|
676
|
+
without_reservation: {
|
|
677
|
+
forecast_input_tokens_upper_bound: primary.input_only_input_tokens_upper_bound,
|
|
678
|
+
signed_headroom_tokens: primary.input_only_signed_headroom_tokens,
|
|
679
|
+
fits: primary.fits
|
|
680
|
+
},
|
|
681
|
+
at_median_rate: medianCounterfactual(primary)
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
__name(inputCounterfactuals, "inputCounterfactuals");
|
|
685
|
+
function budgetErrorCode(checkKind) {
|
|
686
|
+
return checkKind === "rendered_prompt" ? "prompt_budget_exceeded_measured" : "prompt_budget_exceeded_forecast";
|
|
687
|
+
}
|
|
688
|
+
__name(budgetErrorCode, "budgetErrorCode");
|
|
689
|
+
var FusionBudget = class {
|
|
690
|
+
static {
|
|
691
|
+
__name(this, "FusionBudget");
|
|
692
|
+
}
|
|
693
|
+
routes;
|
|
694
|
+
limiting;
|
|
695
|
+
contextPolicyId;
|
|
696
|
+
candidateCapability;
|
|
697
|
+
profile;
|
|
698
|
+
constructor(models, contextPolicyId, candidateCapability = FUSION_REASON_WORKFLOW.candidateCapability, profile = FUSION_REASON_WORKFLOW) {
|
|
699
|
+
this.routes = fusionRouteCapacities(models);
|
|
700
|
+
this.limiting = fusionLimitingRoute(this.routes);
|
|
701
|
+
this.contextPolicyId = contextPolicyId;
|
|
702
|
+
this.candidateCapability = candidateCapability;
|
|
703
|
+
this.profile = profile;
|
|
704
|
+
}
|
|
705
|
+
get allowedInputTokens() {
|
|
706
|
+
return this.limiting.allowed_input_tokens;
|
|
707
|
+
}
|
|
708
|
+
get resultRateSources() {
|
|
709
|
+
return this.routes.map((route) => route.rate_source);
|
|
710
|
+
}
|
|
711
|
+
get unknownProviderWarnings() {
|
|
712
|
+
return this.routes.flatMap((route) => {
|
|
713
|
+
const warning = route.rate_source.warning;
|
|
714
|
+
return route.rate_source.backed || warning === null ? [] : [`${route.qualified_id}: ${warning}`];
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
routeForStage(stage, slot) {
|
|
718
|
+
if (stage === "candidate") return routeByRole(this.routes, candidateRole(slot ?? 1));
|
|
719
|
+
if (stage === "merge") return routeByRole(this.routes, "merger");
|
|
720
|
+
return routeByRole(this.routes, "evaluator");
|
|
721
|
+
}
|
|
722
|
+
drafts(input) {
|
|
723
|
+
const candidateSystemPrompt = this.profile.candidateSystemPrompt(this.candidateCapability);
|
|
724
|
+
const candidatePrompt = buildCandidatePrompt(input);
|
|
725
|
+
const blindInput = buildBlindEvaluationInput(input, EMPTY_CANDIDATES);
|
|
726
|
+
const evaluationPrompt = buildEvaluationPrompt(blindInput);
|
|
727
|
+
const repairPrompt = buildEvaluationRepairPrompt({
|
|
728
|
+
schema_version: "pi-background-tasks.fusion-evaluation-repair-input.v1",
|
|
729
|
+
original_blind_input: blindInput,
|
|
730
|
+
invalid_output: "",
|
|
731
|
+
validation_errors: []
|
|
732
|
+
});
|
|
733
|
+
const mergePrompt = buildMergePrompt(buildMergeInput(input, EMPTY_CANDIDATES, EMPTY_EVALUATION));
|
|
734
|
+
return [
|
|
735
|
+
{
|
|
736
|
+
budget_stage: "candidate",
|
|
737
|
+
slot: 1,
|
|
738
|
+
route: this.routeForStage("candidate", 1),
|
|
739
|
+
conditional: false,
|
|
740
|
+
system_prompt: candidateSystemPrompt,
|
|
741
|
+
empty_user_prompt: candidatePrompt,
|
|
742
|
+
upstream_output_contract_bytes: 0
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
budget_stage: "candidate",
|
|
746
|
+
slot: 2,
|
|
747
|
+
route: this.routeForStage("candidate", 2),
|
|
748
|
+
conditional: false,
|
|
749
|
+
system_prompt: candidateSystemPrompt,
|
|
750
|
+
empty_user_prompt: candidatePrompt,
|
|
751
|
+
upstream_output_contract_bytes: 0
|
|
752
|
+
},
|
|
753
|
+
{
|
|
754
|
+
budget_stage: "candidate",
|
|
755
|
+
slot: 3,
|
|
756
|
+
route: this.routeForStage("candidate", 3),
|
|
757
|
+
conditional: false,
|
|
758
|
+
system_prompt: candidateSystemPrompt,
|
|
759
|
+
empty_user_prompt: candidatePrompt,
|
|
760
|
+
upstream_output_contract_bytes: 0
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
budget_stage: "evaluation",
|
|
764
|
+
route: this.routeForStage("evaluation"),
|
|
765
|
+
conditional: false,
|
|
766
|
+
system_prompt: this.profile.evaluatorSystemPrompt,
|
|
767
|
+
empty_user_prompt: evaluationPrompt,
|
|
768
|
+
upstream_output_contract_bytes: 3 * FUSION_CANDIDATE_MAX_OUTPUT_BYTES
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
budget_stage: "merge",
|
|
772
|
+
route: this.routeForStage("merge"),
|
|
773
|
+
conditional: false,
|
|
774
|
+
system_prompt: this.profile.mergerSystemPrompt,
|
|
775
|
+
empty_user_prompt: mergePrompt,
|
|
776
|
+
upstream_output_contract_bytes: 3 * FUSION_CANDIDATE_MAX_OUTPUT_BYTES + FUSION_EVALUATION_MAX_OUTPUT_BYTES
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
budget_stage: "evaluation_repair",
|
|
780
|
+
route: this.routeForStage("evaluation_repair"),
|
|
781
|
+
conditional: true,
|
|
782
|
+
system_prompt: this.profile.evaluationRepairSystemPrompt,
|
|
783
|
+
empty_user_prompt: repairPrompt,
|
|
784
|
+
upstream_output_contract_bytes: 3 * FUSION_CANDIDATE_MAX_OUTPUT_BYTES + FUSION_EVALUATION_MAX_OUTPUT_BYTES + FUSION_DIAGNOSTICS_MAX_BYTES
|
|
785
|
+
}
|
|
786
|
+
];
|
|
787
|
+
}
|
|
788
|
+
entries(input) {
|
|
789
|
+
return this.drafts(input).map(forecastEntry);
|
|
790
|
+
}
|
|
791
|
+
composition(input, blocker) {
|
|
792
|
+
const canonicalBytes = utf8Bytes(buildCandidatePrompt(input));
|
|
793
|
+
const emptyRequestCanonicalBytes = utf8Bytes(buildCandidatePrompt(replaceRequestText(input, "")));
|
|
794
|
+
const requestBytes = canonicalBytes - emptyRequestCanonicalBytes;
|
|
795
|
+
const visible = visibleTextBytes(input);
|
|
796
|
+
const omissions = omissionReceiptBytes(input);
|
|
797
|
+
const projectionMetadata = canonicalBytes - requestBytes - visible - omissions;
|
|
798
|
+
const draft = this.drafts(input).find(
|
|
799
|
+
(item) => item.budget_stage === blocker.budget_stage && item.slot === blocker.slot
|
|
800
|
+
);
|
|
801
|
+
if (draft === void 0) {
|
|
802
|
+
throw new FusionError("primary budget blocker disappeared during composition", {
|
|
803
|
+
code: "orchestration_failed",
|
|
804
|
+
childCreated: false
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
return {
|
|
808
|
+
visible_text_bytes: visible,
|
|
809
|
+
omission_receipt_bytes: omissions,
|
|
810
|
+
projection_metadata_bytes: projectionMetadata,
|
|
811
|
+
request_bytes: requestBytes,
|
|
812
|
+
static_stage_framing_bytes: utf8Bytes(draft.system_prompt) + utf8Bytes(draft.empty_user_prompt) - canonicalBytes,
|
|
813
|
+
upstream_output_contract_bytes: draft.upstream_output_contract_bytes
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
emptyRequestVerdict(input, entries) {
|
|
817
|
+
const emptyEntries = this.entries(replaceRequestText(input, ""));
|
|
818
|
+
const emptyBlockers = selectBlockers(emptyEntries);
|
|
819
|
+
let reduction = 0;
|
|
820
|
+
for (const entry of entries) {
|
|
821
|
+
reduction = Math.max(reduction, entry.input_utf8_bytes - maxKnownTextBytes(entry.route));
|
|
822
|
+
}
|
|
823
|
+
reduction = Math.max(0, reduction);
|
|
824
|
+
const requestBytes = utf8Bytes(input.request.text);
|
|
825
|
+
return {
|
|
826
|
+
request_utf8_bytes: requestBytes,
|
|
827
|
+
still_fails_with_empty_request: emptyBlockers.length > 0,
|
|
828
|
+
shortening_request_can_help: emptyBlockers.length === 0,
|
|
829
|
+
minimum_request_byte_reduction: reduction,
|
|
830
|
+
maximum_safe_request_utf8_bytes: Math.max(0, requestBytes - reduction),
|
|
831
|
+
blockers_with_empty_request: emptyBlockers
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
failure(primary, plan, artifactDir, measurementKind, checkKind) {
|
|
835
|
+
const composition = plan.primary_blocker_composition;
|
|
836
|
+
const dominantByteClass = primary.input_only_estimate.rateSource.dominant_byte_class;
|
|
837
|
+
const remediation = dominantRemediation(
|
|
838
|
+
plan.empty_request,
|
|
839
|
+
composition,
|
|
840
|
+
dominantByteClass,
|
|
841
|
+
this.profile
|
|
842
|
+
);
|
|
843
|
+
const tokensOver = primary.input_only_input_tokens_upper_bound - primary.allowed_input_tokens;
|
|
844
|
+
const budget = {
|
|
845
|
+
budget_stage: primary.budget_stage,
|
|
846
|
+
measurement_kind: measurementKind,
|
|
847
|
+
check_kind: checkKind,
|
|
848
|
+
measured_utf8_bytes: primary.input_utf8_bytes,
|
|
849
|
+
measured_input_tokens_upper_bound: primary.input_only_input_tokens_upper_bound,
|
|
850
|
+
allowed_input_tokens: primary.allowed_input_tokens,
|
|
851
|
+
limiting_model: {
|
|
852
|
+
provider: primary.route.provider,
|
|
853
|
+
model: primary.route.model,
|
|
854
|
+
qualified_id: primary.route.qualified_id,
|
|
855
|
+
context_window_tokens: primary.route.context_window_tokens
|
|
856
|
+
},
|
|
857
|
+
rate_source: primary.input_only_estimate.rateSource,
|
|
858
|
+
backed: primary.input_only_estimate.rateSource.backed,
|
|
859
|
+
dominant_byte_class: dominantByteClass,
|
|
860
|
+
component_breakdown: componentBreakdown(composition, primary.route),
|
|
861
|
+
byte_class_breakdown: primary.input_only_estimate.byte_class_breakdown,
|
|
862
|
+
dense_regions: [],
|
|
863
|
+
bytes_over: primary.bytes_over,
|
|
864
|
+
tokens_over: Math.max(0, tokensOver),
|
|
865
|
+
required_allowed_tokens: primary.input_only_input_tokens_upper_bound,
|
|
866
|
+
route_table: routeTable(this.routes),
|
|
867
|
+
counterfactuals: inputCounterfactuals(primary, plan),
|
|
868
|
+
stage_upstream_actuals: [],
|
|
869
|
+
policy_id: FUSION_BUDGET_POLICY.id,
|
|
870
|
+
calibration_version: TOKEN_BUDGET_CALIBRATION_VERSION,
|
|
871
|
+
context_policy_id: this.contextPolicyId,
|
|
872
|
+
remediation,
|
|
873
|
+
blockers: plan.blockers,
|
|
874
|
+
artifact_dir: artifactDir
|
|
875
|
+
};
|
|
876
|
+
if (primary.slot !== void 0) budget.slot = primary.slot;
|
|
877
|
+
const compositionText = composition === void 0 ? "unavailable" : formatComposition(composition);
|
|
878
|
+
const additional = plan.blockers.filter((blocker) => blocker !== primary).map((blocker) => `${entryLabel(blocker)} route=${blocker.route.qualified_id}`).join("; ");
|
|
879
|
+
const rateText = `${primary.route.family} ${formatRateX100(primary.input_only_estimate.rateSource.effective_rate_bytes_per_token_x100)} B/tok + ${String(primary.input_only_estimate.rateSource.affine_f_tokens)} tokens (${primary.input_only_estimate.rateSource.source}, backed=${String(primary.input_only_estimate.rateSource.backed)}, dominant=${dominantByteClass})`;
|
|
880
|
+
const sourceWarning = primary.input_only_estimate.rateSource.warning;
|
|
881
|
+
const routeWarning = sourceWarning === null ? "" : ` Rate warning: ${sourceWarning}.`;
|
|
882
|
+
const checkText = checkKind === "input_only_preflight" ? "input-only preflight forecast" : "exact rendered prompt measurement";
|
|
883
|
+
const dominantText = dominantByteClass === "multibyte" ? " Dominant byte class is multibyte UTF-8; the fatal gate uses the conservative multibyte rate and the plan separately records the provable 1.00 B/tok advisory ceiling." : dominantByteClass === "dense_ascii" ? " Dominant byte class is dense ASCII/low-whitespace content; the whitespace gate is a heuristic token-density proxy, not a bound." : ` Dominant byte class is ${dominantByteClass}.`;
|
|
884
|
+
const message = `Fusion prompt budget exceeded by ${checkText} before ${blockingChildLabel(primary)} child creation. Primary blocking stage: ${entryLabel(primary)} on route ${primary.route.qualified_id}. Forecast ${String(primary.input_utf8_bytes)} UTF-8 bytes (<= ${String(primary.input_only_input_tokens_upper_bound)} input tokens) against ${String(primary.allowed_input_tokens)} allowed input tokens, over by ${String(Math.max(0, tokensOver))} tokens. Estimator: ${rateText}.${routeWarning}${dominantText} The ${blockingChildLabel(primary)} child was not created. Nothing was clipped, dropped, or substituted. Artifact directory: ${artifactDir}.
|
|
885
|
+
Per-stage forecast table:
|
|
886
|
+
${formatTable(plan.stages)}
|
|
887
|
+
Primary blocker byte composition: ${compositionText}.
|
|
888
|
+
Additional blockers: ${additional.length === 0 ? "none" : additional}.
|
|
889
|
+
${formatEmptyRequestVerdict(plan.empty_request)}
|
|
890
|
+
Route byte-capacity order: ${routeTable(this.routes).map((route) => `${route.qualified_id}=${String(route.byte_capacity_utf8_bytes)}B`).join(", ")}.
|
|
891
|
+
Remediation: ${remediation.join(" ")}`;
|
|
892
|
+
const details = {
|
|
893
|
+
code: budgetErrorCode(checkKind),
|
|
894
|
+
childCreated: false,
|
|
895
|
+
budget,
|
|
896
|
+
stage: stageFromBudgetStage(primary.budget_stage)
|
|
897
|
+
};
|
|
898
|
+
if (primary.slot !== void 0) return new FusionError(message, { ...details, slot: primary.slot });
|
|
899
|
+
return new FusionError(message, details);
|
|
900
|
+
}
|
|
901
|
+
planMetadata(input) {
|
|
902
|
+
return {
|
|
903
|
+
workflow: this.profile.id,
|
|
904
|
+
context: {
|
|
905
|
+
kind: this.profile.contextKind,
|
|
906
|
+
policy_id: input?.context?.policy_id ?? this.contextPolicyId
|
|
907
|
+
},
|
|
908
|
+
fixed_candidate_policy: {
|
|
909
|
+
capability: this.candidateCapability,
|
|
910
|
+
tools: this.profile.candidateTools
|
|
911
|
+
},
|
|
912
|
+
tool_policy: {
|
|
913
|
+
candidate_tools: this.profile.candidateTools,
|
|
914
|
+
evaluation_tools: [],
|
|
915
|
+
merge_tools: []
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
plan(input) {
|
|
920
|
+
const stages = this.entries(input);
|
|
921
|
+
const blockers = selectBlockers(stages);
|
|
922
|
+
const primary = selectPrimaryBlocker(blockers);
|
|
923
|
+
const emptyRequest = this.emptyRequestVerdict(input, stages);
|
|
924
|
+
const base = {
|
|
925
|
+
schema_version: FUSION_BUDGET_PLAN_SCHEMA_VERSION,
|
|
926
|
+
...this.planMetadata(input),
|
|
927
|
+
policy: FUSION_BUDGET_POLICY,
|
|
928
|
+
routes: this.routes,
|
|
929
|
+
stages,
|
|
930
|
+
blockers,
|
|
931
|
+
empty_request: emptyRequest,
|
|
932
|
+
warnings: warningsFor(stages)
|
|
933
|
+
};
|
|
934
|
+
if (primary === void 0) return base;
|
|
935
|
+
return {
|
|
936
|
+
...base,
|
|
937
|
+
primary_blocker: primary,
|
|
938
|
+
primary_blocker_composition: this.composition(input, primary)
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
assertPlanFits(plan, artifactDir) {
|
|
942
|
+
if (plan.primary_blocker !== void 0) {
|
|
943
|
+
throw this.failure(
|
|
944
|
+
plan.primary_blocker,
|
|
945
|
+
plan,
|
|
946
|
+
artifactDir,
|
|
947
|
+
"stage_forecast",
|
|
948
|
+
"input_only_preflight"
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
assertStagePrompt(stage, systemPrompt, userPrompt, slot) {
|
|
953
|
+
const route = this.routeForStage(stage, slot);
|
|
954
|
+
const inputSegments = [knownTextSegment(systemPrompt), knownTextSegment(userPrompt)];
|
|
955
|
+
const inputBytes = inputSegments.reduce((sum, segment) => sum + segment.bytes, 0);
|
|
956
|
+
const estimate = estimateRouteInput(route, inputSegments);
|
|
957
|
+
if (estimate.tokens <= route.allowed_input_tokens) return;
|
|
958
|
+
const entry = {
|
|
959
|
+
budget_stage: stage,
|
|
960
|
+
route,
|
|
961
|
+
conditional: stage === "evaluation_repair",
|
|
962
|
+
check_kind: "input_only_preflight",
|
|
963
|
+
input_utf8_bytes: inputBytes,
|
|
964
|
+
upstream_output_contract_bytes: 0,
|
|
965
|
+
forecast_utf8_bytes: inputBytes,
|
|
966
|
+
input_only_input_tokens_upper_bound: estimate.tokens,
|
|
967
|
+
forecast_input_tokens_upper_bound: estimate.tokens,
|
|
968
|
+
allowed_input_tokens: route.allowed_input_tokens,
|
|
969
|
+
input_only_signed_headroom_tokens: route.allowed_input_tokens - estimate.tokens,
|
|
970
|
+
signed_headroom_tokens: route.allowed_input_tokens - estimate.tokens,
|
|
971
|
+
input_only_utilization_basis_points: utilizationBasisPoints(estimate.tokens, route.allowed_input_tokens),
|
|
972
|
+
utilization_basis_points: utilizationBasisPoints(estimate.tokens, route.allowed_input_tokens),
|
|
973
|
+
input_only_estimate: estimate,
|
|
974
|
+
reservation_estimate: estimate,
|
|
975
|
+
fits: false,
|
|
976
|
+
reservation_fits: false
|
|
977
|
+
};
|
|
978
|
+
if (slot !== void 0) entry.slot = slot;
|
|
979
|
+
const blocker = blockerFromEntry(entry);
|
|
980
|
+
const plan = {
|
|
981
|
+
schema_version: FUSION_BUDGET_PLAN_SCHEMA_VERSION,
|
|
982
|
+
...this.planMetadata(),
|
|
983
|
+
policy: FUSION_BUDGET_POLICY,
|
|
984
|
+
routes: this.routes,
|
|
985
|
+
stages: [entry],
|
|
986
|
+
blockers: [blocker],
|
|
987
|
+
primary_blocker: blocker,
|
|
988
|
+
empty_request: {
|
|
989
|
+
request_utf8_bytes: 0,
|
|
990
|
+
still_fails_with_empty_request: true,
|
|
991
|
+
shortening_request_can_help: false,
|
|
992
|
+
minimum_request_byte_reduction: blocker.bytes_over,
|
|
993
|
+
maximum_safe_request_utf8_bytes: 0,
|
|
994
|
+
blockers_with_empty_request: [blocker]
|
|
995
|
+
},
|
|
996
|
+
warnings: []
|
|
997
|
+
};
|
|
998
|
+
throw this.failure(blocker, plan, "stage prompt re-measurement", "rendered_prompt", "rendered_prompt");
|
|
999
|
+
}
|
|
1000
|
+
calibrationViolationForCompletedChild(stage, systemPrompt, userPrompt, result, slot) {
|
|
1001
|
+
const route = this.routeForStage(stage, slot);
|
|
1002
|
+
const inputSegments = [knownTextSegment(systemPrompt), knownTextSegment(userPrompt)];
|
|
1003
|
+
const promptUtf8Bytes = inputSegments.reduce((sum, segment) => sum + segment.bytes, 0);
|
|
1004
|
+
const estimate = estimateRouteInput(route, inputSegments);
|
|
1005
|
+
const observedUsage = result.firstRequestUsage;
|
|
1006
|
+
if (observedUsage === void 0) return void 0;
|
|
1007
|
+
const billedInput = observedUsage.input + observedUsage.cacheRead + observedUsage.cacheWrite;
|
|
1008
|
+
if (billedInput <= estimate.tokens) return void 0;
|
|
1009
|
+
const violation = {
|
|
1010
|
+
schema_version: FUSION_CALIBRATION_VIOLATION_SCHEMA_VERSION,
|
|
1011
|
+
stage,
|
|
1012
|
+
attempt: result.attempt,
|
|
1013
|
+
route: {
|
|
1014
|
+
provider: result.provider,
|
|
1015
|
+
model: result.model,
|
|
1016
|
+
qualified_id: result.qualifiedId
|
|
1017
|
+
},
|
|
1018
|
+
family: route.family,
|
|
1019
|
+
rate_source: estimate.rateSource,
|
|
1020
|
+
prompt_utf8_bytes: promptUtf8Bytes,
|
|
1021
|
+
prompt_sha256: sha256Hex(`${systemPrompt}\0${userPrompt}`),
|
|
1022
|
+
observation_scope: "first_provider_request",
|
|
1023
|
+
provider_request_count: result.providerRequestCount ?? 1,
|
|
1024
|
+
forecast_input_tokens: estimate.tokens,
|
|
1025
|
+
billed_input_tokens: billedInput,
|
|
1026
|
+
billed_input_breakdown: {
|
|
1027
|
+
input: observedUsage.input,
|
|
1028
|
+
cache_read: observedUsage.cacheRead,
|
|
1029
|
+
cache_write: observedUsage.cacheWrite
|
|
1030
|
+
},
|
|
1031
|
+
under_forecast_tokens: billedInput - estimate.tokens,
|
|
1032
|
+
byte_class_breakdown: estimate.byte_class_breakdown,
|
|
1033
|
+
dominant_byte_class: estimate.rateSource.dominant_byte_class
|
|
1034
|
+
};
|
|
1035
|
+
if (slot !== void 0) violation.slot = slot;
|
|
1036
|
+
return violation;
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
|
|
1040
|
+
// node_modules/pi-background-tasks/src/core/fusion/evaluation.ts?ext=pi-background-tasks
|
|
1041
|
+
var MAX_REPAIR_ERROR_CHARS = 500;
|
|
1042
|
+
var MAX_REPAIR_ERROR_COUNT = 24;
|
|
1043
|
+
var MAX_REPAIR_ERROR_TOTAL_CHARS = 4e3;
|
|
1044
|
+
function isRecord(value) {
|
|
1045
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1046
|
+
}
|
|
1047
|
+
__name(isRecord, "isRecord");
|
|
1048
|
+
function errorText(error) {
|
|
1049
|
+
return error instanceof Error ? error.message : String(error);
|
|
1050
|
+
}
|
|
1051
|
+
__name(errorText, "errorText");
|
|
1052
|
+
function closed(record, keys, label, errors) {
|
|
1053
|
+
const expected = new Set(keys);
|
|
1054
|
+
for (const key of Object.keys(record)) {
|
|
1055
|
+
if (!expected.has(key)) errors.push(`${label} contains unknown key ${key}`);
|
|
1056
|
+
}
|
|
1057
|
+
for (const key of keys) {
|
|
1058
|
+
if (!Object.prototype.hasOwnProperty.call(record, key))
|
|
1059
|
+
errors.push(`${label} is missing key ${key}`);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
__name(closed, "closed");
|
|
1063
|
+
function nonBlankString(value, label, errors) {
|
|
1064
|
+
if (typeof value !== "string") {
|
|
1065
|
+
errors.push(`${label} must be a string`);
|
|
1066
|
+
return void 0;
|
|
1067
|
+
}
|
|
1068
|
+
if (value.trim().length === 0) {
|
|
1069
|
+
errors.push(`${label} must be non-blank`);
|
|
1070
|
+
return void 0;
|
|
1071
|
+
}
|
|
1072
|
+
return value;
|
|
1073
|
+
}
|
|
1074
|
+
__name(nonBlankString, "nonBlankString");
|
|
1075
|
+
function stringList(value, label, errors) {
|
|
1076
|
+
if (!Array.isArray(value)) {
|
|
1077
|
+
errors.push(`${label} must be an array`);
|
|
1078
|
+
return void 0;
|
|
1079
|
+
}
|
|
1080
|
+
const out = [];
|
|
1081
|
+
for (const [index, item] of value.entries()) {
|
|
1082
|
+
const parsed = nonBlankString(item, `${label}[${String(index)}]`, errors);
|
|
1083
|
+
if (parsed !== void 0) out.push(parsed);
|
|
1084
|
+
}
|
|
1085
|
+
return out;
|
|
1086
|
+
}
|
|
1087
|
+
__name(stringList, "stringList");
|
|
1088
|
+
function candidateId(value, label, errors) {
|
|
1089
|
+
if (value === "A" || value === "B" || value === "C") return value;
|
|
1090
|
+
errors.push(`${label} must be A, B, or C`);
|
|
1091
|
+
return void 0;
|
|
1092
|
+
}
|
|
1093
|
+
__name(candidateId, "candidateId");
|
|
1094
|
+
function tuple3(items, label, errors) {
|
|
1095
|
+
if (items.length !== 3) {
|
|
1096
|
+
errors.push(`${label} must contain exactly three entries`);
|
|
1097
|
+
return void 0;
|
|
1098
|
+
}
|
|
1099
|
+
const first = items[0];
|
|
1100
|
+
const second = items[1];
|
|
1101
|
+
const third = items[2];
|
|
1102
|
+
if (first === void 0 || second === void 0 || third === void 0) {
|
|
1103
|
+
errors.push(`${label} must not contain empty positions`);
|
|
1104
|
+
return void 0;
|
|
1105
|
+
}
|
|
1106
|
+
return [first, second, third];
|
|
1107
|
+
}
|
|
1108
|
+
__name(tuple3, "tuple3");
|
|
1109
|
+
function parseAssessment(value, label, errors) {
|
|
1110
|
+
if (!isRecord(value)) {
|
|
1111
|
+
errors.push(`${label} must be an object`);
|
|
1112
|
+
return void 0;
|
|
1113
|
+
}
|
|
1114
|
+
closed(
|
|
1115
|
+
value,
|
|
1116
|
+
["candidate_id", "summary", "strengths", "limitations", "useful_contributions", "risks"],
|
|
1117
|
+
label,
|
|
1118
|
+
errors
|
|
1119
|
+
);
|
|
1120
|
+
const id = candidateId(value["candidate_id"], `${label}.candidate_id`, errors);
|
|
1121
|
+
const summary = nonBlankString(value["summary"], `${label}.summary`, errors);
|
|
1122
|
+
const strengths = stringList(value["strengths"], `${label}.strengths`, errors);
|
|
1123
|
+
const limitations = stringList(value["limitations"], `${label}.limitations`, errors);
|
|
1124
|
+
const useful = stringList(value["useful_contributions"], `${label}.useful_contributions`, errors);
|
|
1125
|
+
const risks = stringList(value["risks"], `${label}.risks`, errors);
|
|
1126
|
+
if (id === void 0 || summary === void 0 || strengths === void 0 || limitations === void 0 || useful === void 0 || risks === void 0) {
|
|
1127
|
+
return void 0;
|
|
1128
|
+
}
|
|
1129
|
+
return { candidate_id: id, summary, strengths, limitations, useful_contributions: useful, risks };
|
|
1130
|
+
}
|
|
1131
|
+
__name(parseAssessment, "parseAssessment");
|
|
1132
|
+
function parsePosition(value, label, errors) {
|
|
1133
|
+
if (!isRecord(value)) {
|
|
1134
|
+
errors.push(`${label} must be an object`);
|
|
1135
|
+
return void 0;
|
|
1136
|
+
}
|
|
1137
|
+
closed(value, ["candidate_id", "position"], label, errors);
|
|
1138
|
+
const id = candidateId(value["candidate_id"], `${label}.candidate_id`, errors);
|
|
1139
|
+
const position = nonBlankString(value["position"], `${label}.position`, errors);
|
|
1140
|
+
if (id === void 0 || position === void 0) return void 0;
|
|
1141
|
+
return { candidate_id: id, position };
|
|
1142
|
+
}
|
|
1143
|
+
__name(parsePosition, "parsePosition");
|
|
1144
|
+
function parseConflict(value, label, errors) {
|
|
1145
|
+
if (!isRecord(value)) {
|
|
1146
|
+
errors.push(`${label} must be an object`);
|
|
1147
|
+
return void 0;
|
|
1148
|
+
}
|
|
1149
|
+
closed(value, ["topic", "positions", "resolution"], label, errors);
|
|
1150
|
+
const topic = nonBlankString(value["topic"], `${label}.topic`, errors);
|
|
1151
|
+
const positionsRaw = value["positions"];
|
|
1152
|
+
const positions = [];
|
|
1153
|
+
if (!Array.isArray(positionsRaw)) {
|
|
1154
|
+
errors.push(`${label}.positions must be an array`);
|
|
1155
|
+
} else {
|
|
1156
|
+
for (const [index, item] of positionsRaw.entries()) {
|
|
1157
|
+
const parsed = parsePosition(item, `${label}.positions[${String(index)}]`, errors);
|
|
1158
|
+
if (parsed !== void 0) positions.push(parsed);
|
|
1159
|
+
}
|
|
1160
|
+
const distinctIds = new Set(positions.map((position) => position.candidate_id));
|
|
1161
|
+
if (distinctIds.size < 2)
|
|
1162
|
+
errors.push(`${label}.positions must include at least two distinct candidates`);
|
|
1163
|
+
if (distinctIds.size !== positions.length)
|
|
1164
|
+
errors.push(`${label}.positions candidate_id values must be unique`);
|
|
1165
|
+
}
|
|
1166
|
+
const resolution = nonBlankString(value["resolution"], `${label}.resolution`, errors);
|
|
1167
|
+
if (topic === void 0 || resolution === void 0 || !Array.isArray(positionsRaw))
|
|
1168
|
+
return void 0;
|
|
1169
|
+
return { topic, positions, resolution };
|
|
1170
|
+
}
|
|
1171
|
+
__name(parseConflict, "parseConflict");
|
|
1172
|
+
function parseContribution(value, label, errors) {
|
|
1173
|
+
if (!isRecord(value)) {
|
|
1174
|
+
errors.push(`${label} must be an object`);
|
|
1175
|
+
return void 0;
|
|
1176
|
+
}
|
|
1177
|
+
closed(value, ["candidate_id", "contribution"], label, errors);
|
|
1178
|
+
const id = candidateId(value["candidate_id"], `${label}.candidate_id`, errors);
|
|
1179
|
+
const contribution = nonBlankString(value["contribution"], `${label}.contribution`, errors);
|
|
1180
|
+
if (id === void 0 || contribution === void 0) return void 0;
|
|
1181
|
+
return { candidate_id: id, contribution };
|
|
1182
|
+
}
|
|
1183
|
+
__name(parseContribution, "parseContribution");
|
|
1184
|
+
function parseContributionList(value, label, errors) {
|
|
1185
|
+
if (!Array.isArray(value)) {
|
|
1186
|
+
errors.push(`${label} must be an array`);
|
|
1187
|
+
return void 0;
|
|
1188
|
+
}
|
|
1189
|
+
const out = [];
|
|
1190
|
+
for (const [index, item] of value.entries()) {
|
|
1191
|
+
const parsed = parseContribution(item, `${label}[${String(index)}]`, errors);
|
|
1192
|
+
if (parsed !== void 0) out.push(parsed);
|
|
1193
|
+
}
|
|
1194
|
+
return out;
|
|
1195
|
+
}
|
|
1196
|
+
__name(parseContributionList, "parseContributionList");
|
|
1197
|
+
function parseSynthesisPlan(value, label, errors) {
|
|
1198
|
+
if (!isRecord(value)) {
|
|
1199
|
+
errors.push(`${label} must be an object`);
|
|
1200
|
+
return void 0;
|
|
1201
|
+
}
|
|
1202
|
+
closed(value, ["must_include", "must_resolve", "must_avoid"], label, errors);
|
|
1203
|
+
const include = parseContributionList(value["must_include"], `${label}.must_include`, errors);
|
|
1204
|
+
const resolve2 = stringList(value["must_resolve"], `${label}.must_resolve`, errors);
|
|
1205
|
+
const avoid = stringList(value["must_avoid"], `${label}.must_avoid`, errors);
|
|
1206
|
+
if (include === void 0 || resolve2 === void 0 || avoid === void 0) return void 0;
|
|
1207
|
+
return { must_include: include, must_resolve: resolve2, must_avoid: avoid };
|
|
1208
|
+
}
|
|
1209
|
+
__name(parseSynthesisPlan, "parseSynthesisPlan");
|
|
1210
|
+
function parseAssessmentList(value, label, errors) {
|
|
1211
|
+
if (!Array.isArray(value)) {
|
|
1212
|
+
errors.push(`${label} must be an array`);
|
|
1213
|
+
return void 0;
|
|
1214
|
+
}
|
|
1215
|
+
const parsed = [];
|
|
1216
|
+
for (const [index, item] of value.entries()) {
|
|
1217
|
+
const assessment = parseAssessment(item, `${label}[${String(index)}]`, errors);
|
|
1218
|
+
if (assessment !== void 0) parsed.push(assessment);
|
|
1219
|
+
}
|
|
1220
|
+
const ids = new Set(parsed.map((assessment) => assessment.candidate_id));
|
|
1221
|
+
for (const id of FUSION_CANDIDATE_IDS) {
|
|
1222
|
+
if (!ids.has(id)) errors.push(`${label} must contain candidate ${id}`);
|
|
1223
|
+
}
|
|
1224
|
+
if (ids.size !== parsed.length) errors.push(`${label} candidate_id values must be unique`);
|
|
1225
|
+
return tuple3(parsed, label, errors);
|
|
1226
|
+
}
|
|
1227
|
+
__name(parseAssessmentList, "parseAssessmentList");
|
|
1228
|
+
function parseConflictList(value, label, errors) {
|
|
1229
|
+
if (!Array.isArray(value)) {
|
|
1230
|
+
errors.push(`${label} must be an array`);
|
|
1231
|
+
return void 0;
|
|
1232
|
+
}
|
|
1233
|
+
const out = [];
|
|
1234
|
+
for (const [index, item] of value.entries()) {
|
|
1235
|
+
const parsed = parseConflict(item, `${label}[${String(index)}]`, errors);
|
|
1236
|
+
if (parsed !== void 0) out.push(parsed);
|
|
1237
|
+
}
|
|
1238
|
+
return out;
|
|
1239
|
+
}
|
|
1240
|
+
__name(parseConflictList, "parseConflictList");
|
|
1241
|
+
function parseValidationGroups(value, label, errors) {
|
|
1242
|
+
if (!Array.isArray(value)) {
|
|
1243
|
+
errors.push(`${label} must be an array`);
|
|
1244
|
+
return [];
|
|
1245
|
+
}
|
|
1246
|
+
const groups = [];
|
|
1247
|
+
for (const [index, item] of value.entries()) {
|
|
1248
|
+
const itemLabel = `${label}[${String(index)}]`;
|
|
1249
|
+
if (!isRecord(item)) {
|
|
1250
|
+
errors.push(`${itemLabel} must be an object`);
|
|
1251
|
+
continue;
|
|
1252
|
+
}
|
|
1253
|
+
closed(
|
|
1254
|
+
item,
|
|
1255
|
+
["group_id", "source_ids", "severity", "location", "evidence", "impact", "summary", "rationale"],
|
|
1256
|
+
itemLabel,
|
|
1257
|
+
errors
|
|
1258
|
+
);
|
|
1259
|
+
const groupId = nonBlankString(item["group_id"], `${itemLabel}.group_id`, errors);
|
|
1260
|
+
const sourceIds = stringList(item["source_ids"], `${itemLabel}.source_ids`, errors);
|
|
1261
|
+
const severity = nonBlankString(item["severity"], `${itemLabel}.severity`, errors);
|
|
1262
|
+
const location = nonBlankString(item["location"], `${itemLabel}.location`, errors);
|
|
1263
|
+
const evidence = nonBlankString(item["evidence"], `${itemLabel}.evidence`, errors);
|
|
1264
|
+
const impact = nonBlankString(item["impact"], `${itemLabel}.impact`, errors);
|
|
1265
|
+
const summary = nonBlankString(item["summary"], `${itemLabel}.summary`, errors);
|
|
1266
|
+
const rationale = nonBlankString(item["rationale"], `${itemLabel}.rationale`, errors);
|
|
1267
|
+
if (sourceIds !== void 0 && sourceIds.length === 0) {
|
|
1268
|
+
errors.push(`${itemLabel}.source_ids must not be empty`);
|
|
1269
|
+
}
|
|
1270
|
+
if (severity !== void 0 && !["critical", "high", "minor"].includes(severity)) {
|
|
1271
|
+
errors.push(`${itemLabel}.severity invalid`);
|
|
1272
|
+
}
|
|
1273
|
+
if (groupId !== void 0 && sourceIds !== void 0 && sourceIds.length > 0 && severity !== void 0 && location !== void 0 && evidence !== void 0 && impact !== void 0 && summary !== void 0 && rationale !== void 0) {
|
|
1274
|
+
groups.push({
|
|
1275
|
+
group_id: groupId,
|
|
1276
|
+
source_ids: sourceIds,
|
|
1277
|
+
severity,
|
|
1278
|
+
location,
|
|
1279
|
+
evidence,
|
|
1280
|
+
impact,
|
|
1281
|
+
summary,
|
|
1282
|
+
rationale
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
return groups;
|
|
1287
|
+
}
|
|
1288
|
+
__name(parseValidationGroups, "parseValidationGroups");
|
|
1289
|
+
function parseValidationAccounting(value, label, errors) {
|
|
1290
|
+
if (!isRecord(value)) {
|
|
1291
|
+
errors.push(`${label} must be an object`);
|
|
1292
|
+
return void 0;
|
|
1293
|
+
}
|
|
1294
|
+
closed(value, ["findings", "decisions", "groups"], label, errors);
|
|
1295
|
+
const findingsRaw = value["findings"];
|
|
1296
|
+
const decisionsRaw = value["decisions"];
|
|
1297
|
+
const groups = parseValidationGroups(value["groups"], `${label}.groups`, errors);
|
|
1298
|
+
const findings = [];
|
|
1299
|
+
if (!Array.isArray(findingsRaw)) errors.push(`${label}.findings must be an array`);
|
|
1300
|
+
else {
|
|
1301
|
+
for (const [index, item] of findingsRaw.entries()) {
|
|
1302
|
+
const itemLabel = `${label}.findings[${String(index)}]`;
|
|
1303
|
+
if (!isRecord(item)) {
|
|
1304
|
+
errors.push(`${itemLabel} must be an object`);
|
|
1305
|
+
continue;
|
|
1306
|
+
}
|
|
1307
|
+
closed(item, ["id", "candidate_id", "severity", "location", "evidence", "impact", "summary"], itemLabel, errors);
|
|
1308
|
+
const id = nonBlankString(item["id"], `${itemLabel}.id`, errors);
|
|
1309
|
+
const candidate = candidateId(item["candidate_id"], `${itemLabel}.candidate_id`, errors);
|
|
1310
|
+
const severity = nonBlankString(item["severity"], `${itemLabel}.severity`, errors);
|
|
1311
|
+
const location = nonBlankString(item["location"], `${itemLabel}.location`, errors);
|
|
1312
|
+
const evidence = nonBlankString(item["evidence"], `${itemLabel}.evidence`, errors);
|
|
1313
|
+
const impact = nonBlankString(item["impact"], `${itemLabel}.impact`, errors);
|
|
1314
|
+
const summary = nonBlankString(item["summary"], `${itemLabel}.summary`, errors);
|
|
1315
|
+
if (severity !== void 0 && !["critical", "high", "minor"].includes(severity)) errors.push(`${itemLabel}.severity invalid`);
|
|
1316
|
+
if (id !== void 0 && candidate !== void 0 && severity !== void 0 && location !== void 0 && evidence !== void 0 && impact !== void 0 && summary !== void 0) {
|
|
1317
|
+
findings.push({ id, candidate_id: candidate, severity, location, evidence, impact, summary });
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
const decisions = [];
|
|
1322
|
+
if (!Array.isArray(decisionsRaw)) errors.push(`${label}.decisions must be an array`);
|
|
1323
|
+
else {
|
|
1324
|
+
for (const [index, item] of decisionsRaw.entries()) {
|
|
1325
|
+
const itemLabel = `${label}.decisions[${String(index)}]`;
|
|
1326
|
+
if (!isRecord(item)) {
|
|
1327
|
+
errors.push(`${itemLabel} must be an object`);
|
|
1328
|
+
continue;
|
|
1329
|
+
}
|
|
1330
|
+
const allowedDecisionKeys = /* @__PURE__ */ new Set(["source_id", "disposition", "rationale", "group_id"]);
|
|
1331
|
+
for (const key of Object.keys(item)) {
|
|
1332
|
+
if (!allowedDecisionKeys.has(key)) errors.push(`${itemLabel} contains unknown key ${key}`);
|
|
1333
|
+
}
|
|
1334
|
+
for (const key of ["source_id", "disposition", "rationale"]) {
|
|
1335
|
+
if (!Object.prototype.hasOwnProperty.call(item, key)) errors.push(`${itemLabel} is missing key ${key}`);
|
|
1336
|
+
}
|
|
1337
|
+
const sourceId = nonBlankString(item["source_id"], `${itemLabel}.source_id`, errors);
|
|
1338
|
+
const disposition = nonBlankString(item["disposition"], `${itemLabel}.disposition`, errors);
|
|
1339
|
+
const rationale = nonBlankString(item["rationale"], `${itemLabel}.rationale`, errors);
|
|
1340
|
+
const group = item["group_id"] === void 0 ? void 0 : nonBlankString(item["group_id"], `${itemLabel}.group_id`, errors);
|
|
1341
|
+
if (disposition !== void 0 && disposition !== "include" && disposition !== "exclude") errors.push(`${itemLabel}.disposition invalid`);
|
|
1342
|
+
if (sourceId !== void 0 && (disposition === "include" || disposition === "exclude") && rationale !== void 0) {
|
|
1343
|
+
const decision = { source_id: sourceId, disposition, rationale };
|
|
1344
|
+
if (group !== void 0) decision.group_id = group;
|
|
1345
|
+
decisions.push(decision);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
const accounting = { findings, decisions, groups };
|
|
1350
|
+
errors.push(...validateFusionFindingAccounting(accounting));
|
|
1351
|
+
return accounting;
|
|
1352
|
+
}
|
|
1353
|
+
__name(parseValidationAccounting, "parseValidationAccounting");
|
|
1354
|
+
function validateFusionEvaluation(value) {
|
|
1355
|
+
const errors = [];
|
|
1356
|
+
if (!isRecord(value)) return { ok: false, errors: ["evaluation must be a JSON object"] };
|
|
1357
|
+
const evaluationAllowed = /* @__PURE__ */ new Set(["schema_version", "candidate_assessments", "agreements", "conflicts", "synthesis_plan", "validation_accounting"]);
|
|
1358
|
+
for (const key of Object.keys(value)) {
|
|
1359
|
+
if (!evaluationAllowed.has(key)) errors.push(`evaluation contains unknown key ${key}`);
|
|
1360
|
+
}
|
|
1361
|
+
for (const key of ["schema_version", "candidate_assessments", "agreements", "conflicts", "synthesis_plan"]) {
|
|
1362
|
+
if (!Object.prototype.hasOwnProperty.call(value, key)) errors.push(`evaluation is missing key ${key}`);
|
|
1363
|
+
}
|
|
1364
|
+
if (value["schema_version"] !== FUSION_EVALUATION_SCHEMA_VERSION) {
|
|
1365
|
+
errors.push("evaluation.schema_version mismatch");
|
|
1366
|
+
}
|
|
1367
|
+
const assessments = parseAssessmentList(
|
|
1368
|
+
value["candidate_assessments"],
|
|
1369
|
+
"evaluation.candidate_assessments",
|
|
1370
|
+
errors
|
|
1371
|
+
);
|
|
1372
|
+
const agreements = stringList(value["agreements"], "evaluation.agreements", errors);
|
|
1373
|
+
const conflicts = parseConflictList(value["conflicts"], "evaluation.conflicts", errors);
|
|
1374
|
+
const plan = parseSynthesisPlan(value["synthesis_plan"], "evaluation.synthesis_plan", errors);
|
|
1375
|
+
const validationAccounting = Object.prototype.hasOwnProperty.call(value, "validation_accounting") ? parseValidationAccounting(value["validation_accounting"], "evaluation.validation_accounting", errors) : void 0;
|
|
1376
|
+
if (errors.length > 0 || assessments === void 0 || agreements === void 0 || conflicts === void 0 || plan === void 0) {
|
|
1377
|
+
return { ok: false, errors };
|
|
1378
|
+
}
|
|
1379
|
+
const parsedValue = {
|
|
1380
|
+
schema_version: FUSION_EVALUATION_SCHEMA_VERSION,
|
|
1381
|
+
candidate_assessments: assessments,
|
|
1382
|
+
agreements,
|
|
1383
|
+
conflicts,
|
|
1384
|
+
synthesis_plan: plan
|
|
1385
|
+
};
|
|
1386
|
+
if (validationAccounting !== void 0) parsedValue.validation_accounting = validationAccounting;
|
|
1387
|
+
return { ok: true, value: parsedValue };
|
|
1388
|
+
}
|
|
1389
|
+
__name(validateFusionEvaluation, "validateFusionEvaluation");
|
|
1390
|
+
function boundedEvaluationErrors(errors) {
|
|
1391
|
+
const bounded = [];
|
|
1392
|
+
let total = 0;
|
|
1393
|
+
for (const error of errors) {
|
|
1394
|
+
if (bounded.length >= MAX_REPAIR_ERROR_COUNT) break;
|
|
1395
|
+
const perError = error.length <= MAX_REPAIR_ERROR_CHARS ? error : `${error.slice(0, MAX_REPAIR_ERROR_CHARS - 1)}\u2026`;
|
|
1396
|
+
const remaining = MAX_REPAIR_ERROR_TOTAL_CHARS - total;
|
|
1397
|
+
if (remaining <= 0) break;
|
|
1398
|
+
const next = perError.length <= remaining ? perError : `${perError.slice(0, Math.max(0, remaining - 1))}\u2026`;
|
|
1399
|
+
bounded.push(next);
|
|
1400
|
+
total += next.length;
|
|
1401
|
+
}
|
|
1402
|
+
if (errors.length > bounded.length)
|
|
1403
|
+
bounded.push(`\u2026 ${String(errors.length - bounded.length)} more validation errors omitted`);
|
|
1404
|
+
return bounded;
|
|
1405
|
+
}
|
|
1406
|
+
__name(boundedEvaluationErrors, "boundedEvaluationErrors");
|
|
1407
|
+
function formatEvaluationErrors(errors) {
|
|
1408
|
+
return boundedEvaluationErrors(errors).join("; ");
|
|
1409
|
+
}
|
|
1410
|
+
__name(formatEvaluationErrors, "formatEvaluationErrors");
|
|
1411
|
+
function parseValidationCandidateFinding(value, label, errors) {
|
|
1412
|
+
if (!isRecord(value)) {
|
|
1413
|
+
errors.push(`${label} must be an object`);
|
|
1414
|
+
return void 0;
|
|
1415
|
+
}
|
|
1416
|
+
closed(value, ["severity", "location", "evidence", "impact", "summary"], label, errors);
|
|
1417
|
+
const severity = nonBlankString(value["severity"], `${label}.severity`, errors);
|
|
1418
|
+
const location = nonBlankString(value["location"], `${label}.location`, errors);
|
|
1419
|
+
const evidence = nonBlankString(value["evidence"], `${label}.evidence`, errors);
|
|
1420
|
+
const impact = nonBlankString(value["impact"], `${label}.impact`, errors);
|
|
1421
|
+
const summary = nonBlankString(value["summary"], `${label}.summary`, errors);
|
|
1422
|
+
if (severity !== void 0 && !["critical", "high", "minor"].includes(severity)) errors.push(`${label}.severity invalid`);
|
|
1423
|
+
if (severity === void 0 || location === void 0 || evidence === void 0 || impact === void 0 || summary === void 0) return void 0;
|
|
1424
|
+
return { severity, location, evidence, impact, summary };
|
|
1425
|
+
}
|
|
1426
|
+
__name(parseValidationCandidateFinding, "parseValidationCandidateFinding");
|
|
1427
|
+
function fencedValidationCandidateJson(text) {
|
|
1428
|
+
const trimmed = text.trim();
|
|
1429
|
+
const openingPattern = /```json[ \t]*\r?\n/giu;
|
|
1430
|
+
const openings = [...trimmed.matchAll(openingPattern)];
|
|
1431
|
+
if (openings.length !== 1) return void 0;
|
|
1432
|
+
const opening = openings[0];
|
|
1433
|
+
if (opening === void 0) return void 0;
|
|
1434
|
+
const headerEnd = opening.index + opening[0].length;
|
|
1435
|
+
const closing = trimmed.indexOf("```", headerEnd);
|
|
1436
|
+
if (closing < 0 || trimmed.slice(closing + 3).includes("```")) return void 0;
|
|
1437
|
+
if (trimmed.slice(closing + 3).trim().length > 0) return void 0;
|
|
1438
|
+
const preamble = trimmed.slice(0, opening.index).trim();
|
|
1439
|
+
if (Buffer.byteLength(preamble, "utf8") > 2e3 || preamble.includes("```")) return void 0;
|
|
1440
|
+
const payload = trimmed.slice(headerEnd, closing).trim();
|
|
1441
|
+
if (payload.length === 0 || payload.includes("```")) return void 0;
|
|
1442
|
+
return {
|
|
1443
|
+
payload,
|
|
1444
|
+
normalization: preamble.length === 0 ? "markdown_json_fence" : "prose_then_markdown_json_fence"
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
__name(fencedValidationCandidateJson, "fencedValidationCandidateJson");
|
|
1448
|
+
function recoverFencedFusionValidationCandidateReport(text, candidateId2) {
|
|
1449
|
+
const recovered = fencedValidationCandidateJson(text);
|
|
1450
|
+
if (recovered === void 0) return void 0;
|
|
1451
|
+
return {
|
|
1452
|
+
report: parseFusionValidationCandidateReport(recovered.payload, candidateId2),
|
|
1453
|
+
response: recovered.payload,
|
|
1454
|
+
normalization: recovered.normalization
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
__name(recoverFencedFusionValidationCandidateReport, "recoverFencedFusionValidationCandidateReport");
|
|
1458
|
+
function parseFusionValidationCandidateReport(text, candidateId2) {
|
|
1459
|
+
let parsed;
|
|
1460
|
+
try {
|
|
1461
|
+
parsed = parseJsonText(text);
|
|
1462
|
+
} catch (error) {
|
|
1463
|
+
throw new FusionError(
|
|
1464
|
+
`validation candidate ${candidateId2} output must be structured JSON only: ${errorText(error)}`,
|
|
1465
|
+
{ code: "evaluation_invalid", stage: "candidate" }
|
|
1466
|
+
);
|
|
1467
|
+
}
|
|
1468
|
+
const errors = [];
|
|
1469
|
+
if (!isRecord(parsed)) {
|
|
1470
|
+
errors.push("validation candidate report must be an object");
|
|
1471
|
+
} else {
|
|
1472
|
+
closed(parsed, ["schema_version", "findings", "verified", "limitations"], "validation candidate report", errors);
|
|
1473
|
+
}
|
|
1474
|
+
if (!isRecord(parsed)) {
|
|
1475
|
+
throw new FusionError(`validation candidate ${candidateId2} output failed schema validation: ${formatEvaluationErrors(errors)}`, {
|
|
1476
|
+
code: "evaluation_invalid",
|
|
1477
|
+
stage: "candidate"
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1480
|
+
if (parsed["schema_version"] !== FUSION_VALIDATE_CANDIDATE_SCHEMA_VERSION) errors.push("validation candidate report.schema_version mismatch");
|
|
1481
|
+
const rawFindings = parsed["findings"];
|
|
1482
|
+
const findings = [];
|
|
1483
|
+
if (!Array.isArray(rawFindings)) errors.push("validation candidate report.findings must be an array");
|
|
1484
|
+
else {
|
|
1485
|
+
for (const [index, item] of rawFindings.entries()) {
|
|
1486
|
+
const finding = parseValidationCandidateFinding(item, `validation candidate report.findings[${String(index)}]`, errors);
|
|
1487
|
+
if (finding !== void 0) findings.push(finding);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
const verified = stringList(parsed["verified"], "validation candidate report.verified", errors);
|
|
1491
|
+
const limitations = stringList(parsed["limitations"], "validation candidate report.limitations", errors);
|
|
1492
|
+
if (errors.length > 0) {
|
|
1493
|
+
throw new FusionError(`validation candidate ${candidateId2} output failed schema validation: ${formatEvaluationErrors(errors)}`, {
|
|
1494
|
+
code: "evaluation_invalid",
|
|
1495
|
+
stage: "candidate"
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1498
|
+
return {
|
|
1499
|
+
findings: findings.map((finding, index) => ({
|
|
1500
|
+
id: stableFusionFindingId(candidateId2, index + 1),
|
|
1501
|
+
candidate_id: candidateId2,
|
|
1502
|
+
...finding
|
|
1503
|
+
})),
|
|
1504
|
+
verified: verified ?? [],
|
|
1505
|
+
limitations: limitations ?? []
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
__name(parseFusionValidationCandidateReport, "parseFusionValidationCandidateReport");
|
|
1509
|
+
function stableFusionFindingId(candidateId2, ordinal) {
|
|
1510
|
+
if (!Number.isSafeInteger(ordinal) || ordinal <= 0) {
|
|
1511
|
+
throw new FusionError("validation finding ordinal must be a positive integer", {
|
|
1512
|
+
code: "evaluation_invalid",
|
|
1513
|
+
stage: "evaluation"
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
return `${candidateId2}-F${String(ordinal).padStart(3, "0")}`;
|
|
1517
|
+
}
|
|
1518
|
+
__name(stableFusionFindingId, "stableFusionFindingId");
|
|
1519
|
+
function validateFusionFindingAccounting(accounting) {
|
|
1520
|
+
const errors = [];
|
|
1521
|
+
const sourceIds = /* @__PURE__ */ new Set();
|
|
1522
|
+
const perCandidateOrdinal = { A: 0, B: 0, C: 0 };
|
|
1523
|
+
for (const [index, finding] of accounting.findings.entries()) {
|
|
1524
|
+
const label = `finding[${String(index)}]`;
|
|
1525
|
+
perCandidateOrdinal[finding.candidate_id] += 1;
|
|
1526
|
+
if (finding.id !== stableFusionFindingId(finding.candidate_id, perCandidateOrdinal[finding.candidate_id])) {
|
|
1527
|
+
errors.push(`${label}.id must be the stable host id for its candidate and ordinal`);
|
|
1528
|
+
}
|
|
1529
|
+
if (!["critical", "high", "minor"].includes(finding.severity)) errors.push(`${label}.severity invalid`);
|
|
1530
|
+
for (const key of ["location", "evidence", "impact", "summary"]) {
|
|
1531
|
+
if (finding[key].trim().length === 0) errors.push(`${label}.${key} must be non-blank`);
|
|
1532
|
+
}
|
|
1533
|
+
if (sourceIds.has(finding.id)) errors.push(`${label}.id duplicate`);
|
|
1534
|
+
sourceIds.add(finding.id);
|
|
1535
|
+
}
|
|
1536
|
+
const accounted = /* @__PURE__ */ new Set();
|
|
1537
|
+
for (const [index, decision] of accounting.decisions.entries()) {
|
|
1538
|
+
const label = `decision[${String(index)}]`;
|
|
1539
|
+
if (!sourceIds.has(decision.source_id)) errors.push(`${label}.source_id does not name a candidate finding`);
|
|
1540
|
+
if (accounted.has(decision.source_id)) errors.push(`${label}.source_id accounted more than once`);
|
|
1541
|
+
accounted.add(decision.source_id);
|
|
1542
|
+
if (decision.disposition !== "include" && decision.disposition !== "exclude") errors.push(`${label}.disposition invalid`);
|
|
1543
|
+
if (decision.rationale.trim().length === 0) errors.push(`${label}.rationale must be non-blank`);
|
|
1544
|
+
if (decision.disposition === "include" && (decision.group_id === void 0 || decision.group_id.trim().length === 0)) {
|
|
1545
|
+
errors.push(`${label}.group_id required for included findings`);
|
|
1546
|
+
}
|
|
1547
|
+
if (decision.disposition === "exclude" && decision.group_id !== void 0) {
|
|
1548
|
+
errors.push(`${label}.group_id must be omitted for excluded findings`);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
for (const id of sourceIds) {
|
|
1552
|
+
if (!accounted.has(id)) errors.push(`source finding ${id} was not accounted exactly once`);
|
|
1553
|
+
}
|
|
1554
|
+
const groupsById = /* @__PURE__ */ new Map();
|
|
1555
|
+
for (const [index, group] of accounting.groups.entries()) {
|
|
1556
|
+
const label = `group[${String(index)}]`;
|
|
1557
|
+
if (groupsById.has(group.group_id)) errors.push(`${label}.group_id duplicate`);
|
|
1558
|
+
groupsById.set(group.group_id, group);
|
|
1559
|
+
if (!["critical", "high", "minor"].includes(group.severity)) errors.push(`${label}.severity invalid`);
|
|
1560
|
+
for (const key of ["location", "evidence", "impact", "summary", "rationale"]) {
|
|
1561
|
+
if (group[key].trim().length === 0) errors.push(`${label}.${key} must be non-blank`);
|
|
1562
|
+
}
|
|
1563
|
+
if (group.source_ids.length === 0) errors.push(`${label}.source_ids must not be empty`);
|
|
1564
|
+
const groupSourceIds = /* @__PURE__ */ new Set();
|
|
1565
|
+
for (const sourceId of group.source_ids) {
|
|
1566
|
+
if (!sourceIds.has(sourceId)) errors.push(`${label}.source_ids contains unknown finding ${sourceId}`);
|
|
1567
|
+
if (groupSourceIds.has(sourceId)) errors.push(`${label}.source_ids contains duplicate ${sourceId}`);
|
|
1568
|
+
groupSourceIds.add(sourceId);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
const includedByGroup = /* @__PURE__ */ new Map();
|
|
1572
|
+
for (const decision of accounting.decisions) {
|
|
1573
|
+
if (decision.disposition !== "include" || decision.group_id === void 0) continue;
|
|
1574
|
+
const group = groupsById.get(decision.group_id);
|
|
1575
|
+
if (group === void 0) {
|
|
1576
|
+
errors.push(`included source finding ${decision.source_id} references unknown group ${decision.group_id}`);
|
|
1577
|
+
continue;
|
|
1578
|
+
}
|
|
1579
|
+
const members = includedByGroup.get(decision.group_id) ?? /* @__PURE__ */ new Set();
|
|
1580
|
+
members.add(decision.source_id);
|
|
1581
|
+
includedByGroup.set(decision.group_id, members);
|
|
1582
|
+
}
|
|
1583
|
+
for (const group of accounting.groups) {
|
|
1584
|
+
const expected = [...group.source_ids].sort();
|
|
1585
|
+
const actual = [...includedByGroup.get(group.group_id) ?? /* @__PURE__ */ new Set()].sort();
|
|
1586
|
+
if (expected.length !== actual.length || expected.some((id, index) => id !== actual[index])) {
|
|
1587
|
+
errors.push(`group ${group.group_id} source_ids must exactly match included decisions assigned to that group`);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
return errors;
|
|
1591
|
+
}
|
|
1592
|
+
__name(validateFusionFindingAccounting, "validateFusionFindingAccounting");
|
|
1593
|
+
function sanitizeValidationRationale(value) {
|
|
1594
|
+
return value.replace(/\b[ABC]-F\d{3}\b/gu, "source finding").replace(/\bcandidate [ABC]\b/giu, "one reviewer").replace(/\b[ABC]:\s*/gu, "");
|
|
1595
|
+
}
|
|
1596
|
+
__name(sanitizeValidationRationale, "sanitizeValidationRationale");
|
|
1597
|
+
function renderValidatedFusionValidationReport(accounting, coverage) {
|
|
1598
|
+
const errors = validateFusionFindingAccounting(accounting);
|
|
1599
|
+
if (errors.length > 0) {
|
|
1600
|
+
throw new FusionError(`validation accounting invalid before render: ${formatEvaluationErrors(errors)}`, {
|
|
1601
|
+
code: "evaluation_invalid",
|
|
1602
|
+
stage: "merge"
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
const severityOrder = { critical: 0, high: 1, minor: 2 };
|
|
1606
|
+
const renderedFindings = [...accounting.groups].sort(
|
|
1607
|
+
(left, right) => severityOrder[left.severity] - severityOrder[right.severity] || left.location.localeCompare(right.location) || left.group_id.localeCompare(right.group_id)
|
|
1608
|
+
);
|
|
1609
|
+
const lines = ["# Validation report", ""];
|
|
1610
|
+
if (renderedFindings.length === 0) {
|
|
1611
|
+
lines.push("No included findings were identified by the validated accounting.", "");
|
|
1612
|
+
} else {
|
|
1613
|
+
lines.push("## Findings", "");
|
|
1614
|
+
for (const finding of renderedFindings) {
|
|
1615
|
+
lines.push(`### ${finding.severity}: ${finding.summary}`, "");
|
|
1616
|
+
lines.push(`- Location: ${finding.location}`);
|
|
1617
|
+
lines.push(`- Evidence: ${finding.evidence}`);
|
|
1618
|
+
lines.push(`- Impact: ${finding.impact}`);
|
|
1619
|
+
lines.push(`- Inclusion rationale: ${sanitizeValidationRationale(finding.rationale)}`, "");
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
const exclusions = accounting.decisions.filter((decision) => decision.disposition === "exclude").sort((left, right) => left.source_id.localeCompare(right.source_id));
|
|
1623
|
+
if (exclusions.length > 0) {
|
|
1624
|
+
lines.push("## Excluded source findings", "");
|
|
1625
|
+
for (const decision of exclusions) lines.push(`- ${sanitizeValidationRationale(decision.rationale)}`);
|
|
1626
|
+
lines.push("");
|
|
1627
|
+
}
|
|
1628
|
+
if (coverage !== void 0) {
|
|
1629
|
+
lines.push("## Verified", "");
|
|
1630
|
+
if (coverage.verified.length === 0) lines.push("- No verification statements were provided.");
|
|
1631
|
+
else for (const item of coverage.verified) lines.push(`- ${item}`);
|
|
1632
|
+
lines.push("", "## Limitations", "");
|
|
1633
|
+
if (coverage.limitations.length === 0) lines.push("- No limitations were provided.");
|
|
1634
|
+
else for (const item of coverage.limitations) lines.push(`- ${item}`);
|
|
1635
|
+
lines.push("");
|
|
1636
|
+
}
|
|
1637
|
+
return lines.join("\n").trimEnd();
|
|
1638
|
+
}
|
|
1639
|
+
__name(renderValidatedFusionValidationReport, "renderValidatedFusionValidationReport");
|
|
1640
|
+
|
|
1641
|
+
// node_modules/pi-background-tasks/src/core/fusion/pi-child.ts?ext=pi-background-tasks
|
|
1642
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
1643
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
1644
|
+
import { constants, existsSync } from "node:fs";
|
|
1645
|
+
import { open } from "node:fs/promises";
|
|
1646
|
+
import { dirname, resolve } from "node:path";
|
|
1647
|
+
import { fileURLToPath } from "node:url";
|
|
1648
|
+
|
|
1649
|
+
// node_modules/pi-background-tasks/src/core/fusion/child-protocol.ts?ext=pi-background-tasks
|
|
1650
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1651
|
+
var FUSION_CHILD_RESULT_SCHEMA_VERSION = "pi-background-tasks.fusion-child-result.v4";
|
|
1652
|
+
var FUSION_CHILD_RESULT_PREFIX = "PI_FUSION_CHILD_RESULT ";
|
|
1653
|
+
var FUSION_CHILD_SETTLEMENT_SCHEMA_VERSION = "pi-background-tasks.fusion-child-settlement.v3";
|
|
1654
|
+
var FUSION_CHILD_SETTLEMENT_PREFIX = "PI_FUSION_CHILD_SETTLEMENT ";
|
|
1655
|
+
var FUSION_TOOL_CALL_LOG_PATH_ENV = "PI_FUSION_TOOL_CALL_LOG_PATH";
|
|
1656
|
+
var FUSION_CANDIDATE_OUTPUT_RECOVERY_PATH_ENV = "PI_FUSION_CANDIDATE_OUTPUT_RECOVERY_PATH";
|
|
1657
|
+
var FUSION_RESEARCH_ENABLED_ENV = "PI_FUSION_RESEARCH_ENABLED";
|
|
1658
|
+
var FUSION_SOURCE_POLICY_PATH_ENV = "PI_FUSION_SOURCE_POLICY_PATH";
|
|
1659
|
+
var FUSION_SOURCE_POLICY_SHA256_ENV = "PI_FUSION_SOURCE_POLICY_SHA256";
|
|
1660
|
+
var FUSION_TOOL_CALL_SEAL_SCHEMA_VERSION = "pi-background-tasks.fusion-tool-call-seal.v1";
|
|
1661
|
+
var FUSION_TOOL_CALL_SEAL_SUFFIX = ".seal.json";
|
|
1662
|
+
var FUSION_RUNTIME_GUARD_SCHEMA_VERSION = "pi-background-tasks.fusion-runtime-guard.v2";
|
|
1663
|
+
var FUSION_RUNTIME_GUARD_PREFIX = "PI_FUSION_RUNTIME_GUARD ";
|
|
1664
|
+
var FUSION_CHILD_MAX_PROVIDER_REQUESTS = 550;
|
|
1665
|
+
var FUSION_CHILD_MAX_TOOL_CALLS = 600;
|
|
1666
|
+
var FUSION_CHILD_MAX_TOTAL_TOOL_RESULT_BYTES = 32 * 1024 * 1024;
|
|
1667
|
+
function protocolSha256(value) {
|
|
1668
|
+
return createHash2("sha256").update(value).digest("hex");
|
|
1669
|
+
}
|
|
1670
|
+
__name(protocolSha256, "protocolSha256");
|
|
1671
|
+
function serializeFusionChildResultRecords(records) {
|
|
1672
|
+
return Buffer.from(
|
|
1673
|
+
records.length === 0 ? "" : `${records.map((record) => JSON.stringify(record)).join("\n")}
|
|
1674
|
+
`,
|
|
1675
|
+
"utf8"
|
|
1676
|
+
);
|
|
1677
|
+
}
|
|
1678
|
+
__name(serializeFusionChildResultRecords, "serializeFusionChildResultRecords");
|
|
1679
|
+
function hasZeroUsage(record) {
|
|
1680
|
+
const usage = record.usage;
|
|
1681
|
+
return usage.input === 0 && usage.output === 0 && usage.cacheRead === 0 && usage.cacheWrite === 0 && usage.totalTokens === 0 && usage.cost.input === 0 && usage.cost.output === 0 && usage.cost.cacheRead === 0 && usage.cost.cacheWrite === 0 && usage.cost.total === 0;
|
|
1682
|
+
}
|
|
1683
|
+
__name(hasZeroUsage, "hasZeroUsage");
|
|
1684
|
+
function isRecoverableFusionChildErrorRecord(record) {
|
|
1685
|
+
return record.stop_reason === "error" && record.text_blocks.length === 0 && record.text_sha256 === protocolSha256(Buffer.alloc(0)) && hasZeroUsage(record) && record.output_contract.recovery_role === "none";
|
|
1686
|
+
}
|
|
1687
|
+
__name(isRecoverableFusionChildErrorRecord, "isRecoverableFusionChildErrorRecord");
|
|
1688
|
+
function isOversizedOriginal(record) {
|
|
1689
|
+
const output = record.output_contract;
|
|
1690
|
+
return output.recovery_role === "oversized_original" && output.candidate_limit_bytes === FUSION_CANDIDATE_MAX_OUTPUT_BYTES && output.json_rendered_bytes > FUSION_CANDIDATE_MAX_OUTPUT_BYTES && record.stop_reason === "stop";
|
|
1691
|
+
}
|
|
1692
|
+
__name(isOversizedOriginal, "isOversizedOriginal");
|
|
1693
|
+
function outputRecoveryProtocolInvalid(records) {
|
|
1694
|
+
const originals = records.flatMap(
|
|
1695
|
+
(record, ordinal) => record.output_contract.recovery_role === "oversized_original" ? [ordinal] : []
|
|
1696
|
+
);
|
|
1697
|
+
const replacements = records.flatMap(
|
|
1698
|
+
(record, ordinal) => record.output_contract.recovery_role === "replacement" ? [ordinal] : []
|
|
1699
|
+
);
|
|
1700
|
+
for (const record of records) {
|
|
1701
|
+
const output = record.output_contract;
|
|
1702
|
+
if (output.candidate_limit_bytes !== null && output.candidate_limit_bytes !== FUSION_CANDIDATE_MAX_OUTPUT_BYTES) {
|
|
1703
|
+
return true;
|
|
1704
|
+
}
|
|
1705
|
+
if (output.recovery_role !== "none" && output.candidate_limit_bytes === null) return true;
|
|
1706
|
+
if (output.recovery_role === "oversized_original" && !isOversizedOriginal(record)) return true;
|
|
1707
|
+
}
|
|
1708
|
+
if (originals.length === 0 && replacements.length === 0) return false;
|
|
1709
|
+
if (originals.length !== 1 || replacements.length !== 1) return true;
|
|
1710
|
+
const original = originals[0];
|
|
1711
|
+
const replacement = replacements[0];
|
|
1712
|
+
return original === void 0 || replacement === void 0 || original !== records.length - 2 || replacement !== records.length - 1;
|
|
1713
|
+
}
|
|
1714
|
+
__name(outputRecoveryProtocolInvalid, "outputRecoveryProtocolInvalid");
|
|
1715
|
+
function finalCandidateOutputExceedsContract(records) {
|
|
1716
|
+
const final = records.at(-1);
|
|
1717
|
+
if (final === void 0) return false;
|
|
1718
|
+
const output = final.output_contract;
|
|
1719
|
+
return output.candidate_limit_bytes === FUSION_CANDIDATE_MAX_OUTPUT_BYTES && output.json_rendered_bytes > FUSION_CANDIDATE_MAX_OUTPUT_BYTES;
|
|
1720
|
+
}
|
|
1721
|
+
__name(finalCandidateOutputExceedsContract, "finalCandidateOutputExceedsContract");
|
|
1722
|
+
function buildFusionChildSettlement(records, runtimeGuardFailed = false, cacheObservationFailed = false, outputRecoveryFailed = false) {
|
|
1723
|
+
const finalRecordIndex = records.length === 0 ? null : records.length - 1;
|
|
1724
|
+
const final = records.at(-1);
|
|
1725
|
+
const recoveredErrorOrdinals = records.flatMap(
|
|
1726
|
+
(record, ordinal) => ordinal < records.length - 1 && isRecoverableFusionChildErrorRecord(record) ? [ordinal] : []
|
|
1727
|
+
);
|
|
1728
|
+
const recoveredOutputCapOrdinals = records.flatMap(
|
|
1729
|
+
(record, ordinal) => ordinal < records.length - 1 && isOversizedOriginal(record) ? [ordinal] : []
|
|
1730
|
+
);
|
|
1731
|
+
const invalidRecovery = outputRecoveryProtocolInvalid(records);
|
|
1732
|
+
const invalidNonFinal = records.some(
|
|
1733
|
+
(record, ordinal) => ordinal < records.length - 1 && record.stop_reason !== "toolUse" && !isRecoverableFusionChildErrorRecord(record) && !isOversizedOriginal(record)
|
|
1734
|
+
);
|
|
1735
|
+
let failureReason = null;
|
|
1736
|
+
if (runtimeGuardFailed) failureReason = "runtime_guard";
|
|
1737
|
+
else if (cacheObservationFailed) failureReason = "cache_observation";
|
|
1738
|
+
else if (final === void 0) failureReason = "no_records";
|
|
1739
|
+
else if (final.stop_reason !== "stop") failureReason = "final_not_stop";
|
|
1740
|
+
else if (outputRecoveryFailed || invalidRecovery || finalCandidateOutputExceedsContract(records)) {
|
|
1741
|
+
failureReason = "output_recovery";
|
|
1742
|
+
} else if (invalidNonFinal) failureReason = "invalid_non_final";
|
|
1743
|
+
return {
|
|
1744
|
+
schema_version: FUSION_CHILD_SETTLEMENT_SCHEMA_VERSION,
|
|
1745
|
+
status: failureReason === null ? "complete" : "failed",
|
|
1746
|
+
record_count: records.length,
|
|
1747
|
+
records_sha256: protocolSha256(serializeFusionChildResultRecords(records)),
|
|
1748
|
+
final_record_index: finalRecordIndex,
|
|
1749
|
+
final_text_sha256: final?.text_sha256 ?? null,
|
|
1750
|
+
recovered_error_ordinals: recoveredErrorOrdinals,
|
|
1751
|
+
recovered_output_cap_ordinals: recoveredOutputCapOrdinals,
|
|
1752
|
+
failure_reason: failureReason
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
__name(buildFusionChildSettlement, "buildFusionChildSettlement");
|
|
1756
|
+
|
|
1757
|
+
// node_modules/pi-background-tasks/src/core/fusion/claude-cache.ts?ext=pi-background-tasks
|
|
1758
|
+
var FUSION_CLAUDE_CACHE_OBSERVATION_SCHEMA_VERSION = "pi-background-tasks.fusion-claude-cache-observation.v1";
|
|
1759
|
+
var FUSION_CLAUDE_CACHE_RETENTION_ENV = "PI_CACHE_RETENTION";
|
|
1760
|
+
var FUSION_CLAUDE_CACHE_BREAKPOINT_LIMIT = 4;
|
|
1761
|
+
|
|
1762
|
+
// node_modules/pi-background-tasks/src/core/fusion/pi-child.ts?ext=pi-background-tasks
|
|
1763
|
+
var __pvMetaUrl = __pvUrl("../node_modules/pi-background-tasks/src/core/fusion/pi-child.ts");
|
|
1764
|
+
var FUSION_CHILD_STDOUT_LIMIT_BYTES = 32 * 1024 * 1024;
|
|
1765
|
+
var FUSION_CHILD_STDERR_LIMIT_BYTES = 16 * 1024 * 1024;
|
|
1766
|
+
var FUSION_CHILD_TIMEOUT_MS = 50 * 60 * 1e3;
|
|
1767
|
+
var FUSION_CHILD_IDLE_TIMEOUT_MS = 35 * 60 * 1e3;
|
|
1768
|
+
var FUSION_CHILD_KILL_GRACE_MS = 3e3;
|
|
1769
|
+
var FUSION_CHILD_SIGKILL_WAIT_MS = 5e3;
|
|
1770
|
+
var FUSION_PI_CHILD_O_NOFOLLOW = typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0;
|
|
1771
|
+
var FUSION_CHILD_REMOVED_ENV_KEYS = [
|
|
1772
|
+
"PI_SESSION_ID",
|
|
1773
|
+
"PI_SESSION_FILE",
|
|
1774
|
+
"PI_PROVIDER",
|
|
1775
|
+
"PI_MODEL",
|
|
1776
|
+
"PI_REASONING_LEVEL",
|
|
1777
|
+
"OPENROUTER_API_KEY",
|
|
1778
|
+
"OPENROUTER_BASE_URL",
|
|
1779
|
+
"OPENAI_API_KEY",
|
|
1780
|
+
"OPENAI_BASE_URL",
|
|
1781
|
+
"ANTHROPIC_API_KEY",
|
|
1782
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
1783
|
+
"ANTHROPIC_BASE_URL",
|
|
1784
|
+
"AZURE_OPENAI_API_KEY",
|
|
1785
|
+
"AZURE_OPENAI_BASE_URL",
|
|
1786
|
+
"AZURE_OPENAI_ENDPOINT",
|
|
1787
|
+
"AZURE_OPENAI_RESOURCE_NAME",
|
|
1788
|
+
"AZURE_OPENAI_API_VERSION",
|
|
1789
|
+
"AZURE_OPENAI_DEPLOYMENT_NAME_MAP",
|
|
1790
|
+
"AZURE_OPENAI_AD_TOKEN",
|
|
1791
|
+
"PI_API_KEY",
|
|
1792
|
+
"PI_API_BASE_URL",
|
|
1793
|
+
"PI_AUTH_FILE",
|
|
1794
|
+
FUSION_TOOL_CALL_LOG_PATH_ENV,
|
|
1795
|
+
FUSION_CANDIDATE_OUTPUT_RECOVERY_PATH_ENV,
|
|
1796
|
+
FUSION_RESEARCH_ENABLED_ENV,
|
|
1797
|
+
FUSION_SOURCE_POLICY_PATH_ENV,
|
|
1798
|
+
FUSION_SOURCE_POLICY_SHA256_ENV
|
|
1799
|
+
];
|
|
1800
|
+
var FusionChildRunError = class extends FusionError {
|
|
1801
|
+
static {
|
|
1802
|
+
__name(this, "FusionChildRunError");
|
|
1803
|
+
}
|
|
1804
|
+
events;
|
|
1805
|
+
response;
|
|
1806
|
+
stderr;
|
|
1807
|
+
exitCode;
|
|
1808
|
+
signalName;
|
|
1809
|
+
usage;
|
|
1810
|
+
provider;
|
|
1811
|
+
modelName;
|
|
1812
|
+
qualifiedId;
|
|
1813
|
+
outputRecovery;
|
|
1814
|
+
constructor(error, events, response, stderr, close, observed, outputRecovery) {
|
|
1815
|
+
const details = {
|
|
1816
|
+
code: error.code,
|
|
1817
|
+
transient: error.transient,
|
|
1818
|
+
childCreated: error.childCreated
|
|
1819
|
+
};
|
|
1820
|
+
if (error.stage !== void 0) details.stage = error.stage;
|
|
1821
|
+
if (error.slot !== void 0) details.slot = error.slot;
|
|
1822
|
+
if (error.attempt !== void 0) details.attempt = error.attempt;
|
|
1823
|
+
if (error.artifactDir !== void 0) details.artifactDir = error.artifactDir;
|
|
1824
|
+
super(error.message, details);
|
|
1825
|
+
this.name = "FusionChildRunError";
|
|
1826
|
+
this.events = events;
|
|
1827
|
+
this.response = response;
|
|
1828
|
+
this.stderr = stderr;
|
|
1829
|
+
this.exitCode = close.code;
|
|
1830
|
+
this.signalName = close.signal;
|
|
1831
|
+
this.usage = cloneFusionUsage(observed.usage);
|
|
1832
|
+
this.provider = observed.provider;
|
|
1833
|
+
this.modelName = observed.model;
|
|
1834
|
+
this.qualifiedId = observed.qualifiedId;
|
|
1835
|
+
this.outputRecovery = outputRecovery;
|
|
1836
|
+
}
|
|
1837
|
+
};
|
|
1838
|
+
function fusionPiChildEnv(env = process.env, provider) {
|
|
1839
|
+
const out = { ...env };
|
|
1840
|
+
const removed = new Set(FUSION_CHILD_REMOVED_ENV_KEYS);
|
|
1841
|
+
for (const inheritedKey of Object.keys(out)) {
|
|
1842
|
+
if (removed.has(inheritedKey.toUpperCase())) Reflect.deleteProperty(out, inheritedKey);
|
|
1843
|
+
}
|
|
1844
|
+
out["PI_SKIP_VERSION_CHECK"] = "1";
|
|
1845
|
+
if (provider === "anthropic" && out[FUSION_CLAUDE_CACHE_RETENTION_ENV] === void 0) {
|
|
1846
|
+
out[FUSION_CLAUDE_CACHE_RETENTION_ENV] = "long";
|
|
1847
|
+
}
|
|
1848
|
+
return out;
|
|
1849
|
+
}
|
|
1850
|
+
__name(fusionPiChildEnv, "fusionPiChildEnv");
|
|
1851
|
+
function resolveFusionChildExtensionPath(moduleUrl = __pvMetaUrl, pathExists = existsSync) {
|
|
1852
|
+
const modulePath = fileURLToPath(moduleUrl);
|
|
1853
|
+
const extension = modulePath.endsWith(".ts") ? "fusion-child.ts" : "fusion-child.js";
|
|
1854
|
+
const candidate = resolve(dirname(modulePath), "../../../extensions", extension);
|
|
1855
|
+
if (!pathExists(candidate)) {
|
|
1856
|
+
throw new Error(`Fusion child metadata extension is missing: ${candidate}`);
|
|
1857
|
+
}
|
|
1858
|
+
return candidate;
|
|
1859
|
+
}
|
|
1860
|
+
__name(resolveFusionChildExtensionPath, "resolveFusionChildExtensionPath");
|
|
1861
|
+
var FUSION_SANITIZED_PROVIDER = "anthropic";
|
|
1862
|
+
function assertFusionToolPolicyDisjoint(allowlist = FUSION_INSPECT_TOOLS, denylist = FUSION_FORBIDDEN_TOOLS) {
|
|
1863
|
+
for (const forbidden of denylist) {
|
|
1864
|
+
if (allowlist.includes(forbidden)) {
|
|
1865
|
+
throw new FusionError(
|
|
1866
|
+
`fusion inspect capability would enable the forbidden tool ${forbidden}`,
|
|
1867
|
+
{ code: "orchestration_failed", childCreated: false }
|
|
1868
|
+
);
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
__name(assertFusionToolPolicyDisjoint, "assertFusionToolPolicyDisjoint");
|
|
1873
|
+
function researchToolAllowlist() {
|
|
1874
|
+
return FUSION_RESEARCH_TOOLS;
|
|
1875
|
+
}
|
|
1876
|
+
__name(researchToolAllowlist, "researchToolAllowlist");
|
|
1877
|
+
function fusionToolArgv(capability) {
|
|
1878
|
+
if (capability === "reason") return ["--no-tools"];
|
|
1879
|
+
if (capability === "inspect") {
|
|
1880
|
+
assertFusionToolPolicyDisjoint(FUSION_INSPECT_TOOLS);
|
|
1881
|
+
return [
|
|
1882
|
+
"--no-builtin-tools",
|
|
1883
|
+
"--tools",
|
|
1884
|
+
FUSION_INSPECT_TOOLS.join(","),
|
|
1885
|
+
"--exclude-tools",
|
|
1886
|
+
FUSION_FORBIDDEN_TOOLS.join(",")
|
|
1887
|
+
];
|
|
1888
|
+
}
|
|
1889
|
+
if (capability === "research") {
|
|
1890
|
+
const allowlist = researchToolAllowlist();
|
|
1891
|
+
assertFusionToolPolicyDisjoint(allowlist);
|
|
1892
|
+
return [
|
|
1893
|
+
"--no-builtin-tools",
|
|
1894
|
+
"--tools",
|
|
1895
|
+
allowlist.join(","),
|
|
1896
|
+
"--exclude-tools",
|
|
1897
|
+
FUSION_FORBIDDEN_TOOLS.join(",")
|
|
1898
|
+
];
|
|
1899
|
+
}
|
|
1900
|
+
throw new FusionError(`fusion capability ${String(capability)} is not supported`, {
|
|
1901
|
+
code: "orchestration_failed",
|
|
1902
|
+
childCreated: false
|
|
1903
|
+
});
|
|
1904
|
+
}
|
|
1905
|
+
__name(fusionToolArgv, "fusionToolArgv");
|
|
1906
|
+
function fusionChildExtensionPaths(model, childExtensionPath, resolveAttribution = resolveAnthropicAttributionExtensionPath) {
|
|
1907
|
+
if (model.provider !== FUSION_SANITIZED_PROVIDER) return [childExtensionPath];
|
|
1908
|
+
return [resolveAttribution(), childExtensionPath];
|
|
1909
|
+
}
|
|
1910
|
+
__name(fusionChildExtensionPaths, "fusionChildExtensionPaths");
|
|
1911
|
+
function buildFusionPiChildArgv(model, systemPrompt, childExtensionPath = resolveFusionChildExtensionPath(), capability = FUSION_NO_TOOLS_CAPABILITY, resolveAttribution = resolveAnthropicAttributionExtensionPath) {
|
|
1912
|
+
const extensionArgs = fusionChildExtensionPaths(
|
|
1913
|
+
model,
|
|
1914
|
+
childExtensionPath,
|
|
1915
|
+
resolveAttribution
|
|
1916
|
+
).flatMap((path) => ["--extension", path]);
|
|
1917
|
+
return [
|
|
1918
|
+
"--mode",
|
|
1919
|
+
"text",
|
|
1920
|
+
"--no-session",
|
|
1921
|
+
...fusionToolArgv(capability),
|
|
1922
|
+
"--no-extensions",
|
|
1923
|
+
"--no-skills",
|
|
1924
|
+
"--no-prompt-templates",
|
|
1925
|
+
"--no-themes",
|
|
1926
|
+
"--no-context-files",
|
|
1927
|
+
...extensionArgs,
|
|
1928
|
+
"--provider",
|
|
1929
|
+
model.provider,
|
|
1930
|
+
"--model",
|
|
1931
|
+
model.model,
|
|
1932
|
+
"--thinking",
|
|
1933
|
+
model.thinkingLevel,
|
|
1934
|
+
"--system-prompt",
|
|
1935
|
+
systemPrompt
|
|
1936
|
+
];
|
|
1937
|
+
}
|
|
1938
|
+
__name(buildFusionPiChildArgv, "buildFusionPiChildArgv");
|
|
1939
|
+
var SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;
|
|
1940
|
+
var FUSION_CHILD_RESULT_PREFIX_BYTES = Buffer.from(FUSION_CHILD_RESULT_PREFIX, "utf8");
|
|
1941
|
+
var FUSION_CHILD_SETTLEMENT_PREFIX_BYTES = Buffer.from(FUSION_CHILD_SETTLEMENT_PREFIX, "utf8");
|
|
1942
|
+
var FUSION_RUNTIME_GUARD_PREFIX_BYTES = Buffer.from(FUSION_RUNTIME_GUARD_PREFIX, "utf8");
|
|
1943
|
+
var PI_EXTENSION_ERROR_PREFIX_BYTES = Buffer.from("Extension error (", "utf8");
|
|
1944
|
+
function assertClosedRecord(value, keys, label) {
|
|
1945
|
+
if (!isJsonObject(value) || Array.isArray(value)) throw new Error(`${label} must be an object`);
|
|
1946
|
+
const actual = Object.keys(value).sort();
|
|
1947
|
+
const expected = [...keys].sort();
|
|
1948
|
+
if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) {
|
|
1949
|
+
throw new Error(`${label} keys mismatch: expected ${expected.join(", ")}`);
|
|
1950
|
+
}
|
|
1951
|
+
return value;
|
|
1952
|
+
}
|
|
1953
|
+
__name(assertClosedRecord, "assertClosedRecord");
|
|
1954
|
+
function assertClosedRecordWithOptional(value, requiredKeys, optionalKeys, label) {
|
|
1955
|
+
if (!isJsonObject(value) || Array.isArray(value)) throw new Error(`${label} must be an object`);
|
|
1956
|
+
const allowed = /* @__PURE__ */ new Set([...requiredKeys, ...optionalKeys]);
|
|
1957
|
+
const actual = Object.keys(value).sort();
|
|
1958
|
+
const missing = requiredKeys.filter((key) => !Object.hasOwn(value, key));
|
|
1959
|
+
const unknownKeys = actual.filter((key) => !allowed.has(key));
|
|
1960
|
+
if (missing.length > 0 || unknownKeys.length > 0) {
|
|
1961
|
+
throw new Error(
|
|
1962
|
+
`${label} keys mismatch: required ${[...requiredKeys].sort().join(", ")}; optional ${[
|
|
1963
|
+
...optionalKeys
|
|
1964
|
+
].sort().join(", ")}`
|
|
1965
|
+
);
|
|
1966
|
+
}
|
|
1967
|
+
return value;
|
|
1968
|
+
}
|
|
1969
|
+
__name(assertClosedRecordWithOptional, "assertClosedRecordWithOptional");
|
|
1970
|
+
function requireNonBlankString(record, key, label) {
|
|
1971
|
+
const value = record[key];
|
|
1972
|
+
if (typeof value !== "string" || value.trim().length === 0)
|
|
1973
|
+
throw new Error(`${label}.${key} must be a non-blank string`);
|
|
1974
|
+
return value;
|
|
1975
|
+
}
|
|
1976
|
+
__name(requireNonBlankString, "requireNonBlankString");
|
|
1977
|
+
function requireSha256(record, key, label) {
|
|
1978
|
+
const value = record[key];
|
|
1979
|
+
if (typeof value !== "string" || !SHA256_HEX_PATTERN.test(value))
|
|
1980
|
+
throw new Error(`${label}.${key} must be a lowercase SHA-256 hex digest`);
|
|
1981
|
+
return value;
|
|
1982
|
+
}
|
|
1983
|
+
__name(requireSha256, "requireSha256");
|
|
1984
|
+
function requireUsageInteger(record, key, label) {
|
|
1985
|
+
const value = record[key];
|
|
1986
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
|
|
1987
|
+
throw new Error(`${label}.${key} must be a non-negative safe integer`);
|
|
1988
|
+
return value;
|
|
1989
|
+
}
|
|
1990
|
+
__name(requireUsageInteger, "requireUsageInteger");
|
|
1991
|
+
function requirePositiveSafeInteger(record, key, label) {
|
|
1992
|
+
const value = requireUsageInteger(record, key, label);
|
|
1993
|
+
if (value === 0) throw new Error(`${label}.${key} must be a positive safe integer`);
|
|
1994
|
+
return value;
|
|
1995
|
+
}
|
|
1996
|
+
__name(requirePositiveSafeInteger, "requirePositiveSafeInteger");
|
|
1997
|
+
function requireCostNumber(record, key, label) {
|
|
1998
|
+
const value = record[key];
|
|
1999
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
|
|
2000
|
+
throw new Error(`${label}.${key} must be a non-negative finite number`);
|
|
2001
|
+
return value;
|
|
2002
|
+
}
|
|
2003
|
+
__name(requireCostNumber, "requireCostNumber");
|
|
2004
|
+
function parseCompactUsage(value) {
|
|
2005
|
+
const record = assertClosedRecordWithOptional(
|
|
2006
|
+
value,
|
|
2007
|
+
["input", "output", "cacheRead", "cacheWrite", "totalTokens", "cost"],
|
|
2008
|
+
["cacheWrite1h", "reasoning"],
|
|
2009
|
+
"fusion child usage"
|
|
2010
|
+
);
|
|
2011
|
+
const cost = assertClosedRecord(
|
|
2012
|
+
record["cost"],
|
|
2013
|
+
["input", "output", "cacheRead", "cacheWrite", "total"],
|
|
2014
|
+
"fusion child usage.cost"
|
|
2015
|
+
);
|
|
2016
|
+
const output = requireUsageInteger(record, "output", "fusion child usage");
|
|
2017
|
+
const cacheWrite = requireUsageInteger(record, "cacheWrite", "fusion child usage");
|
|
2018
|
+
const cacheWrite1h = record["cacheWrite1h"] === void 0 ? void 0 : requireUsageInteger(record, "cacheWrite1h", "fusion child usage");
|
|
2019
|
+
const reasoning = record["reasoning"] === void 0 ? void 0 : requireUsageInteger(record, "reasoning", "fusion child usage");
|
|
2020
|
+
if (cacheWrite1h !== void 0 && cacheWrite1h > cacheWrite) {
|
|
2021
|
+
throw new Error("fusion child usage.cacheWrite1h must not exceed cacheWrite");
|
|
2022
|
+
}
|
|
2023
|
+
if (reasoning !== void 0 && reasoning > output) {
|
|
2024
|
+
throw new Error("fusion child usage.reasoning must not exceed output");
|
|
2025
|
+
}
|
|
2026
|
+
return {
|
|
2027
|
+
input: requireUsageInteger(record, "input", "fusion child usage"),
|
|
2028
|
+
output,
|
|
2029
|
+
cacheRead: requireUsageInteger(record, "cacheRead", "fusion child usage"),
|
|
2030
|
+
cacheWrite,
|
|
2031
|
+
...cacheWrite1h === void 0 ? {} : { cacheWrite1h },
|
|
2032
|
+
...reasoning === void 0 ? {} : { reasoning },
|
|
2033
|
+
totalTokens: requireUsageInteger(record, "totalTokens", "fusion child usage"),
|
|
2034
|
+
cost: {
|
|
2035
|
+
input: requireCostNumber(cost, "input", "fusion child usage.cost"),
|
|
2036
|
+
output: requireCostNumber(cost, "output", "fusion child usage.cost"),
|
|
2037
|
+
cacheRead: requireCostNumber(cost, "cacheRead", "fusion child usage.cost"),
|
|
2038
|
+
cacheWrite: requireCostNumber(cost, "cacheWrite", "fusion child usage.cost"),
|
|
2039
|
+
total: requireCostNumber(cost, "total", "fusion child usage.cost")
|
|
2040
|
+
}
|
|
2041
|
+
};
|
|
2042
|
+
}
|
|
2043
|
+
__name(parseCompactUsage, "parseCompactUsage");
|
|
2044
|
+
var FUSION_CLAUDE_CACHE_RETENTIONS = /* @__PURE__ */ new Set([
|
|
2045
|
+
"none",
|
|
2046
|
+
"short",
|
|
2047
|
+
"long"
|
|
2048
|
+
]);
|
|
2049
|
+
function parseFusionClaudeCacheObservation(value, provider) {
|
|
2050
|
+
const record = assertClosedRecord(
|
|
2051
|
+
value,
|
|
2052
|
+
[
|
|
2053
|
+
"schema_version",
|
|
2054
|
+
"applicability",
|
|
2055
|
+
"source",
|
|
2056
|
+
"requested_retention",
|
|
2057
|
+
"effective_retention",
|
|
2058
|
+
"breakpoint_count",
|
|
2059
|
+
"request_ordinal"
|
|
2060
|
+
],
|
|
2061
|
+
"fusion child cache observation"
|
|
2062
|
+
);
|
|
2063
|
+
if (record["schema_version"] !== FUSION_CLAUDE_CACHE_OBSERVATION_SCHEMA_VERSION) {
|
|
2064
|
+
throw new Error("fusion child cache observation schema_version mismatch");
|
|
2065
|
+
}
|
|
2066
|
+
const applicability = record["applicability"];
|
|
2067
|
+
const source = record["source"];
|
|
2068
|
+
const requested = record["requested_retention"];
|
|
2069
|
+
const effective = record["effective_retention"];
|
|
2070
|
+
const breakpointCount = requireUsageInteger(
|
|
2071
|
+
record,
|
|
2072
|
+
"breakpoint_count",
|
|
2073
|
+
"fusion child cache observation"
|
|
2074
|
+
);
|
|
2075
|
+
const requestOrdinal = requirePositiveSafeInteger(
|
|
2076
|
+
record,
|
|
2077
|
+
"request_ordinal",
|
|
2078
|
+
"fusion child cache observation"
|
|
2079
|
+
);
|
|
2080
|
+
if (requestOrdinal > FUSION_CHILD_MAX_PROVIDER_REQUESTS) {
|
|
2081
|
+
throw new Error("fusion child cache observation exceeds the provider request limit");
|
|
2082
|
+
}
|
|
2083
|
+
if (breakpointCount > FUSION_CLAUDE_CACHE_BREAKPOINT_LIMIT) {
|
|
2084
|
+
throw new Error("fusion child cache observation exceeds the Anthropic breakpoint limit");
|
|
2085
|
+
}
|
|
2086
|
+
if (provider !== "anthropic") {
|
|
2087
|
+
if (applicability !== "not_applicable" || source !== "not_applicable" || requested !== null || effective !== null || breakpointCount !== 0) {
|
|
2088
|
+
throw new Error("non-Anthropic fusion child has contradictory cache observation");
|
|
2089
|
+
}
|
|
2090
|
+
return {
|
|
2091
|
+
schema_version: FUSION_CLAUDE_CACHE_OBSERVATION_SCHEMA_VERSION,
|
|
2092
|
+
applicability: "not_applicable",
|
|
2093
|
+
source: "not_applicable",
|
|
2094
|
+
requested_retention: null,
|
|
2095
|
+
effective_retention: null,
|
|
2096
|
+
breakpoint_count: 0,
|
|
2097
|
+
request_ordinal: requestOrdinal
|
|
2098
|
+
};
|
|
2099
|
+
}
|
|
2100
|
+
if (applicability !== "anthropic") {
|
|
2101
|
+
throw new Error("Anthropic fusion child cache observation is not applicable");
|
|
2102
|
+
}
|
|
2103
|
+
if (source !== "default" && source !== FUSION_CLAUDE_CACHE_RETENTION_ENV) {
|
|
2104
|
+
throw new Error("Anthropic fusion child cache observation source is invalid");
|
|
2105
|
+
}
|
|
2106
|
+
if (typeof requested !== "string" || !FUSION_CLAUDE_CACHE_RETENTIONS.has(requested) || typeof effective !== "string" || !FUSION_CLAUDE_CACHE_RETENTIONS.has(effective)) {
|
|
2107
|
+
throw new Error("Anthropic fusion child cache retention is invalid");
|
|
2108
|
+
}
|
|
2109
|
+
const requestedRetention = requested;
|
|
2110
|
+
const effectiveRetention = effective;
|
|
2111
|
+
if (source === "default" && requestedRetention !== "long") {
|
|
2112
|
+
throw new Error("default Anthropic cache policy did not request long retention");
|
|
2113
|
+
}
|
|
2114
|
+
if (effectiveRetention === "none" !== (breakpointCount === 0)) {
|
|
2115
|
+
throw new Error("Anthropic fusion child cache retention/breakpoint evidence mismatch");
|
|
2116
|
+
}
|
|
2117
|
+
if (requestedRetention === "none" && effectiveRetention !== "none") {
|
|
2118
|
+
throw new Error("disabled Anthropic cache policy reported active breakpoints");
|
|
2119
|
+
}
|
|
2120
|
+
if (effectiveRetention === "long" && requestedRetention !== "long") {
|
|
2121
|
+
throw new Error("Anthropic long cache retention was not requested");
|
|
2122
|
+
}
|
|
2123
|
+
return {
|
|
2124
|
+
schema_version: FUSION_CLAUDE_CACHE_OBSERVATION_SCHEMA_VERSION,
|
|
2125
|
+
applicability: "anthropic",
|
|
2126
|
+
source,
|
|
2127
|
+
requested_retention: requestedRetention,
|
|
2128
|
+
effective_retention: effectiveRetention,
|
|
2129
|
+
breakpoint_count: breakpointCount,
|
|
2130
|
+
request_ordinal: requestOrdinal
|
|
2131
|
+
};
|
|
2132
|
+
}
|
|
2133
|
+
__name(parseFusionClaudeCacheObservation, "parseFusionClaudeCacheObservation");
|
|
2134
|
+
var FUSION_OUTPUT_RECOVERY_ROLES = /* @__PURE__ */ new Set([
|
|
2135
|
+
"none",
|
|
2136
|
+
"oversized_original",
|
|
2137
|
+
"replacement"
|
|
2138
|
+
]);
|
|
2139
|
+
function parseChildOutputContract(value) {
|
|
2140
|
+
const record = assertClosedRecord(
|
|
2141
|
+
value,
|
|
2142
|
+
["json_rendered_bytes", "candidate_limit_bytes", "recovery_role"],
|
|
2143
|
+
"fusion child result.output_contract"
|
|
2144
|
+
);
|
|
2145
|
+
const candidateLimitValue = record["candidate_limit_bytes"];
|
|
2146
|
+
const candidateLimit = candidateLimitValue === null ? null : requirePositiveSafeInteger(
|
|
2147
|
+
record,
|
|
2148
|
+
"candidate_limit_bytes",
|
|
2149
|
+
"fusion child result.output_contract"
|
|
2150
|
+
);
|
|
2151
|
+
if (candidateLimit !== null && candidateLimit !== FUSION_CANDIDATE_MAX_OUTPUT_BYTES) {
|
|
2152
|
+
throw new Error("fusion child result candidate output limit mismatches the shared contract");
|
|
2153
|
+
}
|
|
2154
|
+
const recoveryRole = record["recovery_role"];
|
|
2155
|
+
if (typeof recoveryRole !== "string" || !FUSION_OUTPUT_RECOVERY_ROLES.has(recoveryRole)) {
|
|
2156
|
+
throw new Error("fusion child result.output_contract.recovery_role is invalid");
|
|
2157
|
+
}
|
|
2158
|
+
if (recoveryRole !== "none" && candidateLimit === null) {
|
|
2159
|
+
throw new Error("fusion child result output recovery has no candidate contract");
|
|
2160
|
+
}
|
|
2161
|
+
return {
|
|
2162
|
+
json_rendered_bytes: requireUsageInteger(
|
|
2163
|
+
record,
|
|
2164
|
+
"json_rendered_bytes",
|
|
2165
|
+
"fusion child result.output_contract"
|
|
2166
|
+
),
|
|
2167
|
+
candidate_limit_bytes: candidateLimit,
|
|
2168
|
+
recovery_role: recoveryRole
|
|
2169
|
+
};
|
|
2170
|
+
}
|
|
2171
|
+
__name(parseChildOutputContract, "parseChildOutputContract");
|
|
2172
|
+
function parseChildResultMetadata(value) {
|
|
2173
|
+
const record = assertClosedRecord(
|
|
2174
|
+
value,
|
|
2175
|
+
[
|
|
2176
|
+
"schema_version",
|
|
2177
|
+
"provider",
|
|
2178
|
+
"model",
|
|
2179
|
+
"stop_reason",
|
|
2180
|
+
"text_blocks",
|
|
2181
|
+
"text_sha256",
|
|
2182
|
+
"usage",
|
|
2183
|
+
"cache_observation",
|
|
2184
|
+
"output_contract"
|
|
2185
|
+
],
|
|
2186
|
+
"fusion child result"
|
|
2187
|
+
);
|
|
2188
|
+
if (record["schema_version"] !== FUSION_CHILD_RESULT_SCHEMA_VERSION)
|
|
2189
|
+
throw new Error("fusion child result schema_version mismatch");
|
|
2190
|
+
const textBlocksValue = record["text_blocks"];
|
|
2191
|
+
if (!Array.isArray(textBlocksValue))
|
|
2192
|
+
throw new Error("fusion child result.text_blocks must be an array");
|
|
2193
|
+
const textBlocks = textBlocksValue.map((value2, index) => {
|
|
2194
|
+
const label = `fusion child result.text_blocks[${String(index)}]`;
|
|
2195
|
+
const block = assertClosedRecord(value2, ["utf8_bytes", "sha256"], label);
|
|
2196
|
+
return {
|
|
2197
|
+
utf8_bytes: requireUsageInteger(block, "utf8_bytes", label),
|
|
2198
|
+
sha256: requireSha256(block, "sha256", label)
|
|
2199
|
+
};
|
|
2200
|
+
});
|
|
2201
|
+
const usage = parseCompactUsage(record["usage"]);
|
|
2202
|
+
const provider = requireNonBlankString(record, "provider", "fusion child result");
|
|
2203
|
+
return {
|
|
2204
|
+
schema_version: FUSION_CHILD_RESULT_SCHEMA_VERSION,
|
|
2205
|
+
provider,
|
|
2206
|
+
model: requireNonBlankString(record, "model", "fusion child result"),
|
|
2207
|
+
stop_reason: requireNonBlankString(record, "stop_reason", "fusion child result"),
|
|
2208
|
+
text_blocks: textBlocks,
|
|
2209
|
+
text_sha256: requireSha256(record, "text_sha256", "fusion child result"),
|
|
2210
|
+
usage,
|
|
2211
|
+
cache_observation: parseFusionClaudeCacheObservation(record["cache_observation"], provider),
|
|
2212
|
+
output_contract: parseChildOutputContract(record["output_contract"])
|
|
2213
|
+
};
|
|
2214
|
+
}
|
|
2215
|
+
__name(parseChildResultMetadata, "parseChildResultMetadata");
|
|
2216
|
+
var FUSION_RUNTIME_GUARD_CODES = /* @__PURE__ */ new Set([
|
|
2217
|
+
"provider_request_limit",
|
|
2218
|
+
"provider_payload_invalid",
|
|
2219
|
+
"claude_cache_policy",
|
|
2220
|
+
"tool_call_limit"
|
|
2221
|
+
]);
|
|
2222
|
+
function parseFusionRuntimeGuard(stderr) {
|
|
2223
|
+
const frames = [];
|
|
2224
|
+
let cursor = 0;
|
|
2225
|
+
for (; ; ) {
|
|
2226
|
+
const frameStart = stderr.indexOf(FUSION_RUNTIME_GUARD_PREFIX_BYTES, cursor);
|
|
2227
|
+
if (frameStart < 0) break;
|
|
2228
|
+
const payloadStart = frameStart + FUSION_RUNTIME_GUARD_PREFIX_BYTES.length;
|
|
2229
|
+
const newline = stderr.indexOf(10, payloadStart);
|
|
2230
|
+
if (newline < 0) throw new Error("fusion runtime guard frame is not newline-terminated");
|
|
2231
|
+
const bytes = stderr.subarray(payloadStart, newline);
|
|
2232
|
+
const text = bytes.toString("utf8");
|
|
2233
|
+
if (!Buffer.from(text, "utf8").equals(bytes)) {
|
|
2234
|
+
throw new Error("fusion runtime guard frame is not valid UTF-8");
|
|
2235
|
+
}
|
|
2236
|
+
const record = assertClosedRecord(
|
|
2237
|
+
parseJsonText(text),
|
|
2238
|
+
[
|
|
2239
|
+
"schema_version",
|
|
2240
|
+
"code",
|
|
2241
|
+
"provider",
|
|
2242
|
+
"model",
|
|
2243
|
+
"request_ordinal",
|
|
2244
|
+
"tool_call_count",
|
|
2245
|
+
"payload_bytes",
|
|
2246
|
+
"payload_sha256",
|
|
2247
|
+
"message"
|
|
2248
|
+
],
|
|
2249
|
+
"fusion runtime guard"
|
|
2250
|
+
);
|
|
2251
|
+
if (record["schema_version"] !== FUSION_RUNTIME_GUARD_SCHEMA_VERSION) {
|
|
2252
|
+
throw new Error("fusion runtime guard schema_version mismatch");
|
|
2253
|
+
}
|
|
2254
|
+
const code = requireNonBlankString(record, "code", "fusion runtime guard");
|
|
2255
|
+
if (!FUSION_RUNTIME_GUARD_CODES.has(code)) {
|
|
2256
|
+
throw new Error(`fusion runtime guard code is unsupported: ${code}`);
|
|
2257
|
+
}
|
|
2258
|
+
const frame = {
|
|
2259
|
+
schema_version: FUSION_RUNTIME_GUARD_SCHEMA_VERSION,
|
|
2260
|
+
code,
|
|
2261
|
+
provider: requireNonBlankString(record, "provider", "fusion runtime guard"),
|
|
2262
|
+
model: requireNonBlankString(record, "model", "fusion runtime guard"),
|
|
2263
|
+
request_ordinal: requirePositiveSafeInteger(
|
|
2264
|
+
record,
|
|
2265
|
+
"request_ordinal",
|
|
2266
|
+
"fusion runtime guard"
|
|
2267
|
+
),
|
|
2268
|
+
tool_call_count: requireUsageInteger(record, "tool_call_count", "fusion runtime guard"),
|
|
2269
|
+
payload_bytes: requireUsageInteger(record, "payload_bytes", "fusion runtime guard"),
|
|
2270
|
+
payload_sha256: requireSha256(record, "payload_sha256", "fusion runtime guard"),
|
|
2271
|
+
message: requireNonBlankString(record, "message", "fusion runtime guard")
|
|
2272
|
+
};
|
|
2273
|
+
const emptyPayloadHash = createHash3("sha256").update(Buffer.alloc(0)).digest("hex");
|
|
2274
|
+
if (frame.code === "claude_cache_policy" || frame.code === "provider_payload_invalid") {
|
|
2275
|
+
if (frame.payload_bytes !== 0 || frame.payload_sha256 !== emptyPayloadHash) {
|
|
2276
|
+
throw new Error("fusion runtime guard invalid-payload evidence mismatch");
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
if (frame.code === "provider_request_limit" && frame.request_ordinal <= FUSION_CHILD_MAX_PROVIDER_REQUESTS) {
|
|
2280
|
+
throw new Error("fusion runtime guard provider request limit was not exceeded");
|
|
2281
|
+
}
|
|
2282
|
+
if (frame.code === "tool_call_limit") {
|
|
2283
|
+
if (frame.tool_call_count <= FUSION_CHILD_MAX_TOOL_CALLS) {
|
|
2284
|
+
throw new Error("fusion runtime guard tool call limit was not exceeded");
|
|
2285
|
+
}
|
|
2286
|
+
if (frame.payload_bytes !== 0 || frame.payload_sha256 !== emptyPayloadHash) {
|
|
2287
|
+
throw new Error("fusion runtime guard tool call limit payload evidence mismatch");
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
frames.push(frame);
|
|
2291
|
+
cursor = newline + 1;
|
|
2292
|
+
}
|
|
2293
|
+
if (frames.length > 1) throw new Error("fusion child emitted multiple runtime guard frames");
|
|
2294
|
+
return frames[0];
|
|
2295
|
+
}
|
|
2296
|
+
__name(parseFusionRuntimeGuard, "parseFusionRuntimeGuard");
|
|
2297
|
+
var FUSION_CHILD_SETTLEMENT_FAILURE_REASONS = /* @__PURE__ */ new Set([
|
|
2298
|
+
"no_records",
|
|
2299
|
+
"final_not_stop",
|
|
2300
|
+
"invalid_non_final",
|
|
2301
|
+
"runtime_guard",
|
|
2302
|
+
"cache_observation",
|
|
2303
|
+
"output_recovery"
|
|
2304
|
+
]);
|
|
2305
|
+
function parseFusionChildSettlement(stderr) {
|
|
2306
|
+
const frames = [];
|
|
2307
|
+
let cursor = 0;
|
|
2308
|
+
for (; ; ) {
|
|
2309
|
+
const frameStart = stderr.indexOf(FUSION_CHILD_SETTLEMENT_PREFIX_BYTES, cursor);
|
|
2310
|
+
if (frameStart < 0) break;
|
|
2311
|
+
const payloadStart = frameStart + FUSION_CHILD_SETTLEMENT_PREFIX_BYTES.length;
|
|
2312
|
+
const newline = stderr.indexOf(10, payloadStart);
|
|
2313
|
+
if (newline < 0) throw new Error("fusion child settlement frame is not newline-terminated");
|
|
2314
|
+
const bytes = stderr.subarray(payloadStart, newline);
|
|
2315
|
+
const text = bytes.toString("utf8");
|
|
2316
|
+
if (!Buffer.from(text, "utf8").equals(bytes)) {
|
|
2317
|
+
throw new Error("fusion child settlement frame is not valid UTF-8");
|
|
2318
|
+
}
|
|
2319
|
+
const record = assertClosedRecord(
|
|
2320
|
+
parseJsonText(text),
|
|
2321
|
+
[
|
|
2322
|
+
"schema_version",
|
|
2323
|
+
"status",
|
|
2324
|
+
"record_count",
|
|
2325
|
+
"records_sha256",
|
|
2326
|
+
"final_record_index",
|
|
2327
|
+
"final_text_sha256",
|
|
2328
|
+
"recovered_error_ordinals",
|
|
2329
|
+
"recovered_output_cap_ordinals",
|
|
2330
|
+
"failure_reason"
|
|
2331
|
+
],
|
|
2332
|
+
"fusion child settlement"
|
|
2333
|
+
);
|
|
2334
|
+
if (record["schema_version"] !== FUSION_CHILD_SETTLEMENT_SCHEMA_VERSION) {
|
|
2335
|
+
throw new Error("fusion child settlement schema_version mismatch");
|
|
2336
|
+
}
|
|
2337
|
+
const status = record["status"];
|
|
2338
|
+
if (status !== "complete" && status !== "failed") {
|
|
2339
|
+
throw new Error("fusion child settlement.status is invalid");
|
|
2340
|
+
}
|
|
2341
|
+
const recordCount = requireUsageInteger(record, "record_count", "fusion child settlement");
|
|
2342
|
+
const finalIndexValue = record["final_record_index"];
|
|
2343
|
+
const finalRecordIndex = finalIndexValue === null ? null : requireUsageInteger(record, "final_record_index", "fusion child settlement");
|
|
2344
|
+
const finalHashValue = record["final_text_sha256"];
|
|
2345
|
+
const finalTextSha256 = finalHashValue === null ? null : requireSha256(record, "final_text_sha256", "fusion child settlement");
|
|
2346
|
+
const recoveredValue = record["recovered_error_ordinals"];
|
|
2347
|
+
if (!Array.isArray(recoveredValue)) {
|
|
2348
|
+
throw new Error("fusion child settlement.recovered_error_ordinals must be an array");
|
|
2349
|
+
}
|
|
2350
|
+
const recoveredErrorOrdinals = recoveredValue.map((value, index) => {
|
|
2351
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
2352
|
+
throw new Error(
|
|
2353
|
+
`fusion child settlement.recovered_error_ordinals[${String(index)}] must be a non-negative safe integer`
|
|
2354
|
+
);
|
|
2355
|
+
}
|
|
2356
|
+
return value;
|
|
2357
|
+
});
|
|
2358
|
+
for (let index = 0; index < recoveredErrorOrdinals.length; index += 1) {
|
|
2359
|
+
const ordinal = recoveredErrorOrdinals[index];
|
|
2360
|
+
if (ordinal === void 0 || ordinal >= recordCount - 1 || index > 0 && ordinal <= (recoveredErrorOrdinals[index - 1] ?? -1)) {
|
|
2361
|
+
throw new Error("fusion child settlement recovered-error ordinals are not canonical");
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
const recoveredOutputValue = record["recovered_output_cap_ordinals"];
|
|
2365
|
+
if (!Array.isArray(recoveredOutputValue)) {
|
|
2366
|
+
throw new Error("fusion child settlement.recovered_output_cap_ordinals must be an array");
|
|
2367
|
+
}
|
|
2368
|
+
const recoveredOutputCapOrdinals = recoveredOutputValue.map((value, index) => {
|
|
2369
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
2370
|
+
throw new Error(
|
|
2371
|
+
`fusion child settlement.recovered_output_cap_ordinals[${String(index)}] must be a non-negative safe integer`
|
|
2372
|
+
);
|
|
2373
|
+
}
|
|
2374
|
+
return value;
|
|
2375
|
+
});
|
|
2376
|
+
for (let index = 0; index < recoveredOutputCapOrdinals.length; index += 1) {
|
|
2377
|
+
const ordinal = recoveredOutputCapOrdinals[index];
|
|
2378
|
+
if (ordinal === void 0 || ordinal >= recordCount - 1 || index > 0 && ordinal <= (recoveredOutputCapOrdinals[index - 1] ?? -1)) {
|
|
2379
|
+
throw new Error("fusion child settlement recovered-output ordinals are not canonical");
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
const failureValue = record["failure_reason"];
|
|
2383
|
+
let failureReason;
|
|
2384
|
+
if (failureValue === null) failureReason = null;
|
|
2385
|
+
else if (typeof failureValue === "string" && FUSION_CHILD_SETTLEMENT_FAILURE_REASONS.has(
|
|
2386
|
+
failureValue
|
|
2387
|
+
)) {
|
|
2388
|
+
failureReason = failureValue;
|
|
2389
|
+
} else {
|
|
2390
|
+
throw new Error("fusion child settlement.failure_reason is invalid");
|
|
2391
|
+
}
|
|
2392
|
+
if (status === "complete" !== (failureReason === null)) {
|
|
2393
|
+
throw new Error("fusion child settlement status/failure_reason mismatch");
|
|
2394
|
+
}
|
|
2395
|
+
if (recordCount === 0) {
|
|
2396
|
+
if (finalRecordIndex !== null || finalTextSha256 !== null) {
|
|
2397
|
+
throw new Error("fusion child settlement empty stream has final-record evidence");
|
|
2398
|
+
}
|
|
2399
|
+
} else if (finalRecordIndex !== recordCount - 1 || finalTextSha256 === null) {
|
|
2400
|
+
throw new Error("fusion child settlement final-record evidence mismatch");
|
|
2401
|
+
}
|
|
2402
|
+
frames.push({
|
|
2403
|
+
schema_version: FUSION_CHILD_SETTLEMENT_SCHEMA_VERSION,
|
|
2404
|
+
status,
|
|
2405
|
+
record_count: recordCount,
|
|
2406
|
+
records_sha256: requireSha256(record, "records_sha256", "fusion child settlement"),
|
|
2407
|
+
final_record_index: finalRecordIndex,
|
|
2408
|
+
final_text_sha256: finalTextSha256,
|
|
2409
|
+
recovered_error_ordinals: recoveredErrorOrdinals,
|
|
2410
|
+
recovered_output_cap_ordinals: recoveredOutputCapOrdinals,
|
|
2411
|
+
failure_reason: failureReason
|
|
2412
|
+
});
|
|
2413
|
+
cursor = newline + 1;
|
|
2414
|
+
}
|
|
2415
|
+
if (frames.length > 1) throw new Error("fusion child emitted multiple settlement frames");
|
|
2416
|
+
return frames[0];
|
|
2417
|
+
}
|
|
2418
|
+
__name(parseFusionChildSettlement, "parseFusionChildSettlement");
|
|
2419
|
+
function assertFusionChildSettlementOrdering(stderr) {
|
|
2420
|
+
const settlementStart = stderr.indexOf(FUSION_CHILD_SETTLEMENT_PREFIX_BYTES);
|
|
2421
|
+
if (settlementStart < 0) return;
|
|
2422
|
+
if (stderr.indexOf(FUSION_CHILD_RESULT_PREFIX_BYTES, settlementStart) >= 0) {
|
|
2423
|
+
throw new Error("fusion child emitted result metadata after terminal settlement");
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
__name(assertFusionChildSettlementOrdering, "assertFusionChildSettlementOrdering");
|
|
2427
|
+
function stripChildControlFrames(stderr) {
|
|
2428
|
+
const prefixes = [FUSION_CHILD_SETTLEMENT_PREFIX_BYTES, FUSION_RUNTIME_GUARD_PREFIX_BYTES];
|
|
2429
|
+
const diagnostics = [];
|
|
2430
|
+
let cursor = 0;
|
|
2431
|
+
for (; ; ) {
|
|
2432
|
+
let next = -1;
|
|
2433
|
+
let prefix;
|
|
2434
|
+
for (const candidate of prefixes) {
|
|
2435
|
+
const found = stderr.indexOf(candidate, cursor);
|
|
2436
|
+
if (found >= 0 && (next < 0 || found < next)) {
|
|
2437
|
+
next = found;
|
|
2438
|
+
prefix = candidate;
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
if (next < 0 || prefix === void 0) {
|
|
2442
|
+
if (cursor < stderr.length) diagnostics.push(stderr.subarray(cursor));
|
|
2443
|
+
break;
|
|
2444
|
+
}
|
|
2445
|
+
if (next > cursor) diagnostics.push(stderr.subarray(cursor, next));
|
|
2446
|
+
const newline = stderr.indexOf(10, next + prefix.length);
|
|
2447
|
+
if (newline < 0) throw new Error("fusion child control frame is not newline-terminated");
|
|
2448
|
+
cursor = newline + 1;
|
|
2449
|
+
}
|
|
2450
|
+
return Buffer.concat(diagnostics);
|
|
2451
|
+
}
|
|
2452
|
+
__name(stripChildControlFrames, "stripChildControlFrames");
|
|
2453
|
+
function assertFusionRuntimeGuardMatchesModel(guard, model) {
|
|
2454
|
+
const routeUnknown = guard.provider === "unknown" && guard.model === "unknown";
|
|
2455
|
+
if (!routeUnknown && (guard.provider !== model.provider || guard.model !== model.model)) {
|
|
2456
|
+
throw new Error(
|
|
2457
|
+
`fusion runtime guard route mismatch: expected ${model.qualifiedId}, observed ${guard.provider}/${guard.model}`
|
|
2458
|
+
);
|
|
2459
|
+
}
|
|
2460
|
+
if (routeUnknown && guard.code !== "provider_payload_invalid") {
|
|
2461
|
+
throw new Error("fusion runtime guard omitted the route for a route-bound refusal");
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
__name(assertFusionRuntimeGuardMatchesModel, "assertFusionRuntimeGuardMatchesModel");
|
|
2465
|
+
function parseFusionChildStderr(stderr) {
|
|
2466
|
+
parseFusionRuntimeGuard(stderr);
|
|
2467
|
+
const settlement = parseFusionChildSettlement(stderr);
|
|
2468
|
+
assertFusionChildSettlementOrdering(stderr);
|
|
2469
|
+
const records = [];
|
|
2470
|
+
const diagnostics = [];
|
|
2471
|
+
let cursor = 0;
|
|
2472
|
+
for (; ; ) {
|
|
2473
|
+
const frameStart = stderr.indexOf(FUSION_CHILD_RESULT_PREFIX_BYTES, cursor);
|
|
2474
|
+
if (frameStart < 0) {
|
|
2475
|
+
if (cursor < stderr.length) diagnostics.push(stderr.subarray(cursor));
|
|
2476
|
+
break;
|
|
2477
|
+
}
|
|
2478
|
+
if (frameStart > cursor) diagnostics.push(stderr.subarray(cursor, frameStart));
|
|
2479
|
+
const payloadStart = frameStart + FUSION_CHILD_RESULT_PREFIX_BYTES.length;
|
|
2480
|
+
const newline = stderr.indexOf(10, payloadStart);
|
|
2481
|
+
if (newline < 0) throw new Error("fusion child metadata frame is not newline-terminated");
|
|
2482
|
+
const payloadBytes = stderr.subarray(payloadStart, newline);
|
|
2483
|
+
const payloadText = payloadBytes.toString("utf8");
|
|
2484
|
+
if (!Buffer.from(payloadText, "utf8").equals(payloadBytes))
|
|
2485
|
+
throw new Error("fusion child metadata frame is not valid UTF-8");
|
|
2486
|
+
let parsed;
|
|
2487
|
+
try {
|
|
2488
|
+
parsed = parseJsonText(payloadText);
|
|
2489
|
+
} catch (error) {
|
|
2490
|
+
throw new Error(
|
|
2491
|
+
`fusion child metadata frame is invalid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
2492
|
+
);
|
|
2493
|
+
}
|
|
2494
|
+
records.push(parseChildResultMetadata(parsed));
|
|
2495
|
+
cursor = newline + 1;
|
|
2496
|
+
}
|
|
2497
|
+
const resultEvents = serializeFusionChildResultRecords(records);
|
|
2498
|
+
const events = settlement === void 0 ? resultEvents : Buffer.concat([resultEvents, Buffer.from(`${JSON.stringify(settlement)}
|
|
2499
|
+
`, "utf8")]);
|
|
2500
|
+
return {
|
|
2501
|
+
records,
|
|
2502
|
+
events,
|
|
2503
|
+
diagnostics: stripChildControlFrames(Buffer.concat(diagnostics))
|
|
2504
|
+
};
|
|
2505
|
+
}
|
|
2506
|
+
__name(parseFusionChildStderr, "parseFusionChildStderr");
|
|
2507
|
+
function parseToolCallLogRecord(value, label) {
|
|
2508
|
+
const record = assertClosedRecordWithOptional(
|
|
2509
|
+
value,
|
|
2510
|
+
[
|
|
2511
|
+
"schema_version",
|
|
2512
|
+
"ordinal",
|
|
2513
|
+
"tool_name",
|
|
2514
|
+
"arguments_sha256",
|
|
2515
|
+
"arguments_bytes",
|
|
2516
|
+
"result_bytes",
|
|
2517
|
+
"result_sha256",
|
|
2518
|
+
"status",
|
|
2519
|
+
"duration_ms"
|
|
2520
|
+
],
|
|
2521
|
+
["url", "rejected_url_sha256", "final_url", "http_status", "response_bytes", "content_sha256"],
|
|
2522
|
+
label
|
|
2523
|
+
);
|
|
2524
|
+
if (record["schema_version"] !== FUSION_TOOL_CALL_LOG_SCHEMA_VERSION) {
|
|
2525
|
+
throw new Error(`${label}.schema_version mismatch`);
|
|
2526
|
+
}
|
|
2527
|
+
const status = record["status"];
|
|
2528
|
+
if (status !== "ok" && status !== "error") throw new Error(`${label}.status is invalid`);
|
|
2529
|
+
const parsedRecord = {
|
|
2530
|
+
schema_version: FUSION_TOOL_CALL_LOG_SCHEMA_VERSION,
|
|
2531
|
+
ordinal: requireUsageInteger(record, "ordinal", label),
|
|
2532
|
+
tool_name: requireNonBlankString(record, "tool_name", label),
|
|
2533
|
+
arguments_sha256: requireSha256(record, "arguments_sha256", label),
|
|
2534
|
+
arguments_bytes: requireUsageInteger(record, "arguments_bytes", label),
|
|
2535
|
+
result_bytes: requireUsageInteger(record, "result_bytes", label),
|
|
2536
|
+
result_sha256: requireSha256(record, "result_sha256", label),
|
|
2537
|
+
status,
|
|
2538
|
+
duration_ms: requireUsageInteger(record, "duration_ms", label)
|
|
2539
|
+
};
|
|
2540
|
+
if (record["url"] !== void 0) parsedRecord.url = requireNonBlankString(record, "url", label);
|
|
2541
|
+
if (record["rejected_url_sha256"] !== void 0)
|
|
2542
|
+
parsedRecord.rejected_url_sha256 = requireSha256(record, "rejected_url_sha256", label);
|
|
2543
|
+
if (record["final_url"] !== void 0)
|
|
2544
|
+
parsedRecord.final_url = requireNonBlankString(record, "final_url", label);
|
|
2545
|
+
if (record["http_status"] !== void 0)
|
|
2546
|
+
parsedRecord.http_status = requireUsageInteger(record, "http_status", label);
|
|
2547
|
+
if (record["response_bytes"] !== void 0)
|
|
2548
|
+
parsedRecord.response_bytes = requireUsageInteger(record, "response_bytes", label);
|
|
2549
|
+
if (record["content_sha256"] !== void 0)
|
|
2550
|
+
parsedRecord.content_sha256 = requireSha256(record, "content_sha256", label);
|
|
2551
|
+
return parsedRecord;
|
|
2552
|
+
}
|
|
2553
|
+
__name(parseToolCallLogRecord, "parseToolCallLogRecord");
|
|
2554
|
+
function parseFusionToolCallLog(bytes) {
|
|
2555
|
+
if (bytes.length === 0) {
|
|
2556
|
+
return {
|
|
2557
|
+
bytes,
|
|
2558
|
+
records: [],
|
|
2559
|
+
summary: { count: 0, total_result_bytes: 0, trace_complete: true }
|
|
2560
|
+
};
|
|
2561
|
+
}
|
|
2562
|
+
if (bytes.at(-1) !== 10) {
|
|
2563
|
+
throw new Error("fusion tool-call log has trailing partial line");
|
|
2564
|
+
}
|
|
2565
|
+
const text = bytes.toString("utf8");
|
|
2566
|
+
if (!Buffer.from(text, "utf8").equals(bytes)) {
|
|
2567
|
+
throw new Error("fusion tool-call log is not valid UTF-8");
|
|
2568
|
+
}
|
|
2569
|
+
const lines = text.split("\n");
|
|
2570
|
+
lines.pop();
|
|
2571
|
+
const records = lines.map((line, index) => {
|
|
2572
|
+
let parsed;
|
|
2573
|
+
try {
|
|
2574
|
+
parsed = parseJsonText(line);
|
|
2575
|
+
} catch (error) {
|
|
2576
|
+
throw new Error(
|
|
2577
|
+
`fusion tool-call log line ${String(index)} is invalid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
2578
|
+
);
|
|
2579
|
+
}
|
|
2580
|
+
return parseToolCallLogRecord(parsed, `fusion tool-call log line ${String(index)}`);
|
|
2581
|
+
});
|
|
2582
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2583
|
+
for (const [index, record] of records.entries()) {
|
|
2584
|
+
if (seen.has(record.ordinal)) {
|
|
2585
|
+
throw new Error(`fusion tool-call log duplicate ordinal ${String(record.ordinal)}`);
|
|
2586
|
+
}
|
|
2587
|
+
seen.add(record.ordinal);
|
|
2588
|
+
if (record.ordinal !== index) {
|
|
2589
|
+
throw new Error(
|
|
2590
|
+
`fusion tool-call log ordinal gap: expected ${String(index)}, observed ${String(record.ordinal)}`
|
|
2591
|
+
);
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
return {
|
|
2595
|
+
bytes,
|
|
2596
|
+
records,
|
|
2597
|
+
summary: {
|
|
2598
|
+
count: records.length,
|
|
2599
|
+
total_result_bytes: records.reduce((sum, record) => sum + record.result_bytes, 0),
|
|
2600
|
+
trace_complete: true
|
|
2601
|
+
}
|
|
2602
|
+
};
|
|
2603
|
+
}
|
|
2604
|
+
__name(parseFusionToolCallLog, "parseFusionToolCallLog");
|
|
2605
|
+
async function assertCompletedToolPolicy(trace, capability, sourcePolicy) {
|
|
2606
|
+
const allowed = capability === "inspect" ? FUSION_INSPECT_TOOLS : capability === "research" ? FUSION_RESEARCH_TOOLS : [];
|
|
2607
|
+
const allowedSet = new Set(allowed);
|
|
2608
|
+
const declared = capability === "research" && sourcePolicy !== void 0 ? new Set(
|
|
2609
|
+
(await readFusionSourcePolicyFile(sourcePolicy.path, sourcePolicy.sha256)).sources.map(
|
|
2610
|
+
(source) => source.canonical_url
|
|
2611
|
+
)
|
|
2612
|
+
) : void 0;
|
|
2613
|
+
for (const record of trace.records) {
|
|
2614
|
+
if (!allowedSet.has(record.tool_name)) {
|
|
2615
|
+
throw new Error(`fusion child used non-allowlisted tool ${record.tool_name}`);
|
|
2616
|
+
}
|
|
2617
|
+
if (capability === "research" && record.tool_name === FUSION_WEB_FETCH_TOOL_NAME) {
|
|
2618
|
+
if (sourcePolicy === void 0 || declared === void 0)
|
|
2619
|
+
throw new Error("fusion research source policy missing during audit");
|
|
2620
|
+
if (record.status === "ok") {
|
|
2621
|
+
if (record.url === void 0) throw new Error("fusion research fetch audit is missing url");
|
|
2622
|
+
const canonicalUrl = canonicalizeFusionPublicUrl(record.url);
|
|
2623
|
+
if (record.url !== canonicalUrl)
|
|
2624
|
+
throw new Error("fusion research fetch audit URL was not canonical");
|
|
2625
|
+
if (!declared.has(canonicalUrl))
|
|
2626
|
+
throw new Error("fusion research fetch audit URL was not declared");
|
|
2627
|
+
if (record.rejected_url_sha256 !== void 0) {
|
|
2628
|
+
throw new Error(
|
|
2629
|
+
"fusion research successful fetch audit must not include rejected_url_sha256"
|
|
2630
|
+
);
|
|
2631
|
+
}
|
|
2632
|
+
if (record.final_url === void 0)
|
|
2633
|
+
throw new Error("fusion research fetch audit is missing final_url");
|
|
2634
|
+
if (record.http_status === void 0)
|
|
2635
|
+
throw new Error("fusion research fetch audit is missing http_status");
|
|
2636
|
+
if (record.response_bytes === void 0)
|
|
2637
|
+
throw new Error("fusion research fetch audit is missing response_bytes");
|
|
2638
|
+
if (record.content_sha256 === void 0)
|
|
2639
|
+
throw new Error("fusion research fetch audit is missing content_sha256");
|
|
2640
|
+
} else {
|
|
2641
|
+
if (record.url !== void 0 || record.final_url !== void 0) {
|
|
2642
|
+
throw new Error("fusion research rejected fetch audit must not persist raw URL");
|
|
2643
|
+
}
|
|
2644
|
+
if (record.rejected_url_sha256 === void 0) {
|
|
2645
|
+
throw new Error("fusion research rejected fetch audit is missing rejected_url_sha256");
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
__name(assertCompletedToolPolicy, "assertCompletedToolPolicy");
|
|
2652
|
+
function isNotFound(error) {
|
|
2653
|
+
return isJsonObject(error) && error["code"] === "ENOENT";
|
|
2654
|
+
}
|
|
2655
|
+
__name(isNotFound, "isNotFound");
|
|
2656
|
+
async function readFusionToolCallLog(path) {
|
|
2657
|
+
let handle;
|
|
2658
|
+
try {
|
|
2659
|
+
handle = await open(path, constants.O_RDONLY | FUSION_PI_CHILD_O_NOFOLLOW);
|
|
2660
|
+
} catch (error) {
|
|
2661
|
+
if (isNotFound(error)) {
|
|
2662
|
+
throw new Error(
|
|
2663
|
+
`fusion tool-call log is missing at ${path}; the inspect child never initialized its audit trail`
|
|
2664
|
+
);
|
|
2665
|
+
}
|
|
2666
|
+
if (isJsonObject(error) && error["code"] === "ELOOP") {
|
|
2667
|
+
throw new Error(
|
|
2668
|
+
`fusion tool-call log at ${path} is a symlink; refusing to trust a redirected audit trail`
|
|
2669
|
+
);
|
|
2670
|
+
}
|
|
2671
|
+
throw error;
|
|
2672
|
+
}
|
|
2673
|
+
try {
|
|
2674
|
+
const stats = await handle.stat();
|
|
2675
|
+
if (!stats.isFile()) {
|
|
2676
|
+
throw new Error(
|
|
2677
|
+
`fusion tool-call log at ${path} is not a regular file; refusing to trust a redirected audit trail`
|
|
2678
|
+
);
|
|
2679
|
+
}
|
|
2680
|
+
return parseFusionToolCallLog(await handle.readFile());
|
|
2681
|
+
} finally {
|
|
2682
|
+
await handle.close();
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
__name(readFusionToolCallLog, "readFusionToolCallLog");
|
|
2686
|
+
async function assertFusionToolCallLogSeal(path, trace) {
|
|
2687
|
+
const sealPath = `${path}${FUSION_TOOL_CALL_SEAL_SUFFIX}`;
|
|
2688
|
+
let handle;
|
|
2689
|
+
try {
|
|
2690
|
+
handle = await open(sealPath, constants.O_RDONLY | FUSION_PI_CHILD_O_NOFOLLOW);
|
|
2691
|
+
} catch (error) {
|
|
2692
|
+
if (isNotFound(error)) throw new Error("fusion tool-call audit completion seal is missing");
|
|
2693
|
+
if (isJsonObject(error) && error["code"] === "ELOOP") {
|
|
2694
|
+
throw new Error("fusion tool-call audit completion seal is a symlink");
|
|
2695
|
+
}
|
|
2696
|
+
throw error;
|
|
2697
|
+
}
|
|
2698
|
+
try {
|
|
2699
|
+
const stats = await handle.stat();
|
|
2700
|
+
if (!stats.isFile())
|
|
2701
|
+
throw new Error("fusion tool-call audit completion seal is not a regular file");
|
|
2702
|
+
if (stats.size > 4096) throw new Error("fusion tool-call audit completion seal is oversized");
|
|
2703
|
+
const bytes = await handle.readFile();
|
|
2704
|
+
if (bytes.at(-1) !== 10) throw new Error("fusion tool-call audit completion seal is partial");
|
|
2705
|
+
const text = bytes.toString("utf8");
|
|
2706
|
+
if (!Buffer.from(text, "utf8").equals(bytes)) {
|
|
2707
|
+
throw new Error("fusion tool-call audit completion seal is not UTF-8");
|
|
2708
|
+
}
|
|
2709
|
+
const parsed = parseJsonText(text);
|
|
2710
|
+
if (!isJsonObject(parsed) || Array.isArray(parsed)) {
|
|
2711
|
+
throw new Error("fusion tool-call audit completion seal must be an object");
|
|
2712
|
+
}
|
|
2713
|
+
const keys = Object.keys(parsed).sort();
|
|
2714
|
+
const expected = [
|
|
2715
|
+
"log_sha256",
|
|
2716
|
+
"record_count",
|
|
2717
|
+
"schema_version",
|
|
2718
|
+
"status",
|
|
2719
|
+
"total_result_bytes"
|
|
2720
|
+
];
|
|
2721
|
+
if (keys.join("\0") !== expected.join("\0")) {
|
|
2722
|
+
throw new Error("fusion tool-call audit completion seal keys mismatch");
|
|
2723
|
+
}
|
|
2724
|
+
if (parsed["schema_version"] !== FUSION_TOOL_CALL_SEAL_SCHEMA_VERSION) {
|
|
2725
|
+
throw new Error("fusion tool-call audit completion seal schema mismatch");
|
|
2726
|
+
}
|
|
2727
|
+
if (parsed["status"] !== "complete") {
|
|
2728
|
+
throw new Error("fusion tool-call audit completion seal reports a failed audit");
|
|
2729
|
+
}
|
|
2730
|
+
const recordCount = requireUsageInteger(parsed, "record_count", "fusion tool-call audit seal");
|
|
2731
|
+
const totalResultBytes = requireUsageInteger(
|
|
2732
|
+
parsed,
|
|
2733
|
+
"total_result_bytes",
|
|
2734
|
+
"fusion tool-call audit seal"
|
|
2735
|
+
);
|
|
2736
|
+
const logSha256 = requireSha256(parsed, "log_sha256", "fusion tool-call audit seal");
|
|
2737
|
+
if (recordCount !== trace.summary.count) {
|
|
2738
|
+
throw new Error("fusion tool-call audit completion seal record count mismatch");
|
|
2739
|
+
}
|
|
2740
|
+
if (recordCount > FUSION_CHILD_MAX_TOOL_CALLS) {
|
|
2741
|
+
throw new Error(
|
|
2742
|
+
`fusion tool-call audit exceeds tool-call limit ${String(FUSION_CHILD_MAX_TOOL_CALLS)}`
|
|
2743
|
+
);
|
|
2744
|
+
}
|
|
2745
|
+
if (totalResultBytes !== trace.summary.total_result_bytes) {
|
|
2746
|
+
throw new Error("fusion tool-call audit completion seal result-byte total mismatch");
|
|
2747
|
+
}
|
|
2748
|
+
if (logSha256 !== sha256Buffer(trace.bytes)) {
|
|
2749
|
+
throw new Error("fusion tool-call audit completion seal log hash mismatch");
|
|
2750
|
+
}
|
|
2751
|
+
if (totalResultBytes > FUSION_CHILD_MAX_TOTAL_TOOL_RESULT_BYTES) {
|
|
2752
|
+
throw new Error(
|
|
2753
|
+
`fusion tool-call audit exceeds aggregate result-byte limit ${String(FUSION_CHILD_MAX_TOTAL_TOOL_RESULT_BYTES)}`
|
|
2754
|
+
);
|
|
2755
|
+
}
|
|
2756
|
+
} finally {
|
|
2757
|
+
await handle.close();
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
__name(assertFusionToolCallLogSeal, "assertFusionToolCallLogSeal");
|
|
2761
|
+
function sha256Buffer(bytes) {
|
|
2762
|
+
return createHash3("sha256").update(bytes).digest("hex");
|
|
2763
|
+
}
|
|
2764
|
+
__name(sha256Buffer, "sha256Buffer");
|
|
2765
|
+
function reconstructFinalText(response, record) {
|
|
2766
|
+
const blocks = [];
|
|
2767
|
+
let cursor = 0;
|
|
2768
|
+
for (const [index, block] of record.text_blocks.entries()) {
|
|
2769
|
+
const end = cursor + block.utf8_bytes;
|
|
2770
|
+
if (end > response.length)
|
|
2771
|
+
throw new Error(`Pi final text block ${String(index)} is shorter than its metadata length`);
|
|
2772
|
+
const bytes = response.subarray(cursor, end);
|
|
2773
|
+
if (sha256Buffer(bytes) !== block.sha256)
|
|
2774
|
+
throw new Error(`Pi final text block ${String(index)} hash mismatch`);
|
|
2775
|
+
blocks.push(bytes);
|
|
2776
|
+
if (response.at(end) !== 10)
|
|
2777
|
+
throw new Error(`Pi final text block ${String(index)} lacks its print-mode newline`);
|
|
2778
|
+
cursor = end + 1;
|
|
2779
|
+
}
|
|
2780
|
+
if (cursor !== response.length)
|
|
2781
|
+
throw new Error("Pi final text stdout contains bytes outside declared text blocks");
|
|
2782
|
+
const joined = Buffer.concat(blocks);
|
|
2783
|
+
if (sha256Buffer(joined) !== record.text_sha256)
|
|
2784
|
+
throw new Error("Pi final text aggregate hash mismatch");
|
|
2785
|
+
const text = joined.toString("utf8");
|
|
2786
|
+
if (!Buffer.from(text, "utf8").equals(joined))
|
|
2787
|
+
throw new Error("Pi final text is not valid UTF-8");
|
|
2788
|
+
if (fusionJsonRenderedTextBytes(text) !== record.output_contract.json_rendered_bytes) {
|
|
2789
|
+
throw new Error("Pi final text JSON-rendered byte count mismatch");
|
|
2790
|
+
}
|
|
2791
|
+
if (text.trim().length === 0) throw new Error("Pi assistant response is empty");
|
|
2792
|
+
return text;
|
|
2793
|
+
}
|
|
2794
|
+
__name(reconstructFinalText, "reconstructFinalText");
|
|
2795
|
+
async function readCandidateOutputRecovery(path, evidence) {
|
|
2796
|
+
let handle;
|
|
2797
|
+
try {
|
|
2798
|
+
handle = await open(path, constants.O_RDONLY | FUSION_PI_CHILD_O_NOFOLLOW);
|
|
2799
|
+
} catch (error) {
|
|
2800
|
+
if (isNotFound(error))
|
|
2801
|
+
throw new Error("fusion oversized candidate response artifact is missing");
|
|
2802
|
+
if (isJsonObject(error) && error["code"] === "ELOOP") {
|
|
2803
|
+
throw new Error("fusion oversized candidate response artifact is a symlink");
|
|
2804
|
+
}
|
|
2805
|
+
throw error;
|
|
2806
|
+
}
|
|
2807
|
+
try {
|
|
2808
|
+
const stats = await handle.stat();
|
|
2809
|
+
if (!stats.isFile()) {
|
|
2810
|
+
throw new Error("fusion oversized candidate response artifact is not a regular file");
|
|
2811
|
+
}
|
|
2812
|
+
if (stats.size > FUSION_CHILD_STDOUT_LIMIT_BYTES) {
|
|
2813
|
+
throw new Error(
|
|
2814
|
+
`fusion oversized candidate response artifact exceeds ${String(FUSION_CHILD_STDOUT_LIMIT_BYTES)} bytes`
|
|
2815
|
+
);
|
|
2816
|
+
}
|
|
2817
|
+
const bytes = await handle.readFile();
|
|
2818
|
+
if (sha256Buffer(bytes) !== evidence.original_text_sha256) {
|
|
2819
|
+
throw new Error("fusion oversized candidate response artifact hash mismatch");
|
|
2820
|
+
}
|
|
2821
|
+
const text = bytes.toString("utf8");
|
|
2822
|
+
if (!Buffer.from(text, "utf8").equals(bytes)) {
|
|
2823
|
+
throw new Error("fusion oversized candidate response artifact is not valid UTF-8");
|
|
2824
|
+
}
|
|
2825
|
+
if (fusionJsonRenderedTextBytes(text) !== evidence.original_json_rendered_bytes) {
|
|
2826
|
+
throw new Error("fusion oversized candidate response JSON-rendered byte count mismatch");
|
|
2827
|
+
}
|
|
2828
|
+
if (evidence.original_json_rendered_bytes <= evidence.limit_bytes) {
|
|
2829
|
+
throw new Error("fusion output recovery original did not exceed the candidate contract");
|
|
2830
|
+
}
|
|
2831
|
+
return { ...evidence, original_text: text };
|
|
2832
|
+
} finally {
|
|
2833
|
+
await handle.close();
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
__name(readCandidateOutputRecovery, "readCandidateOutputRecovery");
|
|
2837
|
+
function parsedCandidateOutputRecovery(records, status) {
|
|
2838
|
+
const originalRecordIndex = records.findIndex(
|
|
2839
|
+
(record) => record.output_contract.recovery_role === "oversized_original"
|
|
2840
|
+
);
|
|
2841
|
+
if (originalRecordIndex < 0) return void 0;
|
|
2842
|
+
const original = records[originalRecordIndex];
|
|
2843
|
+
if (original === void 0) throw new Error("Pi output recovery original record disappeared");
|
|
2844
|
+
const replacementRecordIndex = records.findIndex(
|
|
2845
|
+
(record) => record.output_contract.recovery_role === "replacement"
|
|
2846
|
+
);
|
|
2847
|
+
const replacement = replacementRecordIndex < 0 ? void 0 : records[replacementRecordIndex];
|
|
2848
|
+
return {
|
|
2849
|
+
kind: "same_session_compression",
|
|
2850
|
+
limit_bytes: FUSION_CANDIDATE_MAX_OUTPUT_BYTES,
|
|
2851
|
+
original_record_index: originalRecordIndex,
|
|
2852
|
+
replacement_record_index: replacementRecordIndex < 0 ? null : replacementRecordIndex,
|
|
2853
|
+
original_json_rendered_bytes: original.output_contract.json_rendered_bytes,
|
|
2854
|
+
replacement_json_rendered_bytes: replacement?.output_contract.json_rendered_bytes ?? null,
|
|
2855
|
+
original_text_sha256: original.text_sha256,
|
|
2856
|
+
status
|
|
2857
|
+
};
|
|
2858
|
+
}
|
|
2859
|
+
__name(parsedCandidateOutputRecovery, "parsedCandidateOutputRecovery");
|
|
2860
|
+
var FusionPiCompactResultParser = class {
|
|
2861
|
+
static {
|
|
2862
|
+
__name(this, "FusionPiCompactResultParser");
|
|
2863
|
+
}
|
|
2864
|
+
expectedProvider;
|
|
2865
|
+
expectedModel;
|
|
2866
|
+
constructor(expectedProvider, expectedModel) {
|
|
2867
|
+
this.expectedProvider = expectedProvider;
|
|
2868
|
+
this.expectedModel = expectedModel;
|
|
2869
|
+
}
|
|
2870
|
+
snapshot(stderr) {
|
|
2871
|
+
try {
|
|
2872
|
+
const parsed = parseFusionChildStderr(stderr);
|
|
2873
|
+
return this.observedFromRecords(parsed.records);
|
|
2874
|
+
} catch {
|
|
2875
|
+
return { usage: createEmptyFusionUsage() };
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
finishOutputRecoveryFailure(response, stderr) {
|
|
2879
|
+
const parsed = parseFusionChildStderr(stderr);
|
|
2880
|
+
const settlement = parseFusionChildSettlement(stderr);
|
|
2881
|
+
if (settlement === void 0) throw new Error("Pi child emitted no terminal result settlement");
|
|
2882
|
+
const expectedSettlement = buildFusionChildSettlement(parsed.records);
|
|
2883
|
+
if (JSON.stringify(settlement) !== JSON.stringify(expectedSettlement)) {
|
|
2884
|
+
throw new Error("Pi child terminal result settlement does not match the metadata stream");
|
|
2885
|
+
}
|
|
2886
|
+
if (settlement.status !== "failed" || settlement.failure_reason !== "output_recovery") {
|
|
2887
|
+
throw new Error("Pi child did not report a failed candidate output recovery");
|
|
2888
|
+
}
|
|
2889
|
+
if (parsed.diagnostics.includes(PI_EXTENSION_ERROR_PREFIX_BYTES)) {
|
|
2890
|
+
throw new Error("Pi child reported an extension error diagnostic");
|
|
2891
|
+
}
|
|
2892
|
+
const final = parsed.records.at(-1);
|
|
2893
|
+
if (final === void 0) throw new Error("Pi child emitted no compact result metadata");
|
|
2894
|
+
for (const record of parsed.records) this.assertModel(record);
|
|
2895
|
+
this.assertCacheObservationOrdinals(parsed.records);
|
|
2896
|
+
this.assertTranscriptStopReasons(parsed.records);
|
|
2897
|
+
reconstructFinalText(response, final);
|
|
2898
|
+
const outputRecovery = parsedCandidateOutputRecovery(parsed.records, "failed");
|
|
2899
|
+
if (outputRecovery === void 0) {
|
|
2900
|
+
throw new Error("Pi child output-recovery failure omitted the oversized original");
|
|
2901
|
+
}
|
|
2902
|
+
const observed = this.observedFromRecords(parsed.records);
|
|
2903
|
+
return {
|
|
2904
|
+
usage: observed.usage,
|
|
2905
|
+
provider: final.provider,
|
|
2906
|
+
model: final.model,
|
|
2907
|
+
qualifiedId: `${final.provider}/${final.model}`,
|
|
2908
|
+
events: parsed.events,
|
|
2909
|
+
diagnostics: parsed.diagnostics,
|
|
2910
|
+
outputRecovery
|
|
2911
|
+
};
|
|
2912
|
+
}
|
|
2913
|
+
finish(response, stderr) {
|
|
2914
|
+
const parsed = parseFusionChildStderr(stderr);
|
|
2915
|
+
const runtimeGuard = parseFusionRuntimeGuard(stderr);
|
|
2916
|
+
if (runtimeGuard !== void 0) {
|
|
2917
|
+
throw new Error(`Pi child runtime guard refused the run: ${runtimeGuard.message}`);
|
|
2918
|
+
}
|
|
2919
|
+
const settlement = parseFusionChildSettlement(stderr);
|
|
2920
|
+
if (settlement === void 0) throw new Error("Pi child emitted no terminal result settlement");
|
|
2921
|
+
const expectedSettlement = buildFusionChildSettlement(parsed.records);
|
|
2922
|
+
if (JSON.stringify(settlement) !== JSON.stringify(expectedSettlement)) {
|
|
2923
|
+
throw new Error("Pi child terminal result settlement does not match the metadata stream");
|
|
2924
|
+
}
|
|
2925
|
+
if (parsed.diagnostics.includes(PI_EXTENSION_ERROR_PREFIX_BYTES)) {
|
|
2926
|
+
throw new Error("Pi child reported an extension error diagnostic");
|
|
2927
|
+
}
|
|
2928
|
+
const final = parsed.records.at(-1);
|
|
2929
|
+
if (final === void 0) throw new Error("Pi child emitted no compact result metadata");
|
|
2930
|
+
for (const record of parsed.records) this.assertModel(record);
|
|
2931
|
+
this.assertCacheObservationOrdinals(parsed.records);
|
|
2932
|
+
this.assertTranscriptStopReasons(parsed.records);
|
|
2933
|
+
if (settlement.status !== "complete") {
|
|
2934
|
+
throw new Error(
|
|
2935
|
+
`Pi child terminal result settlement failed: ${settlement.failure_reason ?? "unknown"}`
|
|
2936
|
+
);
|
|
2937
|
+
}
|
|
2938
|
+
const observed = this.observedFromRecords(parsed.records);
|
|
2939
|
+
const text = reconstructFinalText(response, final);
|
|
2940
|
+
return {
|
|
2941
|
+
text,
|
|
2942
|
+
usage: observed.usage,
|
|
2943
|
+
firstRequestUsage: cloneFusionUsage(parsed.records[0]?.usage ?? createEmptyFusionUsage()),
|
|
2944
|
+
providerRequestCount: parsed.records.length,
|
|
2945
|
+
provider: final.provider,
|
|
2946
|
+
model: final.model,
|
|
2947
|
+
qualifiedId: `${final.provider}/${final.model}`,
|
|
2948
|
+
events: parsed.events,
|
|
2949
|
+
diagnostics: parsed.diagnostics,
|
|
2950
|
+
outputRecovery: parsedCandidateOutputRecovery(parsed.records, "completed")
|
|
2951
|
+
};
|
|
2952
|
+
}
|
|
2953
|
+
assertModel(record) {
|
|
2954
|
+
if (record.provider !== this.expectedProvider || record.model !== this.expectedModel) {
|
|
2955
|
+
throw new Error(
|
|
2956
|
+
`Pi assistant model mismatch: expected ${this.expectedProvider}/${this.expectedModel}, observed ${record.provider}/${record.model}`
|
|
2957
|
+
);
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
assertCacheObservationOrdinals(records) {
|
|
2961
|
+
let priorOrdinal = 0;
|
|
2962
|
+
for (const [index, record] of records.entries()) {
|
|
2963
|
+
const ordinal = record.cache_observation.request_ordinal;
|
|
2964
|
+
if (ordinal <= priorOrdinal) {
|
|
2965
|
+
throw new Error(
|
|
2966
|
+
`Pi child cache observation ordinal is not increasing at result ${String(index)}: previous ${String(priorOrdinal)}, observed ${String(ordinal)}`
|
|
2967
|
+
);
|
|
2968
|
+
}
|
|
2969
|
+
priorOrdinal = ordinal;
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
assertTranscriptStopReasons(records) {
|
|
2973
|
+
for (const [index, record] of records.entries()) {
|
|
2974
|
+
const isFinal = index === records.length - 1;
|
|
2975
|
+
if (isFinal) {
|
|
2976
|
+
if (record.stop_reason !== "stop") {
|
|
2977
|
+
throw new Error(this.stopReasonError("final", "stop", record.stop_reason, true));
|
|
2978
|
+
}
|
|
2979
|
+
} else if (record.stop_reason !== "toolUse" && !isRecoverableFusionChildErrorRecord(record) && record.output_contract.recovery_role !== "oversized_original") {
|
|
2980
|
+
throw new Error(
|
|
2981
|
+
this.stopReasonError(
|
|
2982
|
+
`non-final record ${index}`,
|
|
2983
|
+
"toolUse, a settled zero-usage retry marker, or one oversized original",
|
|
2984
|
+
record.stop_reason,
|
|
2985
|
+
true
|
|
2986
|
+
)
|
|
2987
|
+
);
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
stopReasonError(position, expected, observed, includeStopDetail) {
|
|
2992
|
+
const prefix = `Pi ${position} stop reason is not ${expected}: ${observed}`;
|
|
2993
|
+
if (!includeStopDetail) return prefix;
|
|
2994
|
+
switch (observed) {
|
|
2995
|
+
case "length":
|
|
2996
|
+
return `${prefix} (model output was truncated)`;
|
|
2997
|
+
case "error":
|
|
2998
|
+
return `${prefix} (Pi reported an error stop)`;
|
|
2999
|
+
case "aborted":
|
|
3000
|
+
return `${prefix} (Pi reported an aborted stop)`;
|
|
3001
|
+
case "pending":
|
|
3002
|
+
return `${prefix} (Pi reported a pending stop)`;
|
|
3003
|
+
default:
|
|
3004
|
+
return prefix;
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
observedFromRecords(records) {
|
|
3008
|
+
const usage = createEmptyFusionUsage();
|
|
3009
|
+
for (const record of records) addFusionUsage(usage, record.usage);
|
|
3010
|
+
const final = records.at(-1);
|
|
3011
|
+
if (final === void 0) return { usage };
|
|
3012
|
+
return {
|
|
3013
|
+
usage,
|
|
3014
|
+
provider: final.provider,
|
|
3015
|
+
model: final.model,
|
|
3016
|
+
qualifiedId: `${final.provider}/${final.model}`
|
|
3017
|
+
};
|
|
3018
|
+
}
|
|
3019
|
+
};
|
|
3020
|
+
function appendCapped(chunks, currentBytes, chunk, limit) {
|
|
3021
|
+
if (currentBytes >= limit)
|
|
3022
|
+
return { bytes: currentBytes, accepted: Buffer.alloc(0), exceeded: true };
|
|
3023
|
+
const remaining = limit - currentBytes;
|
|
3024
|
+
if (chunk.length <= remaining) {
|
|
3025
|
+
chunks.push(chunk);
|
|
3026
|
+
return { bytes: currentBytes + chunk.length, accepted: chunk, exceeded: false };
|
|
3027
|
+
}
|
|
3028
|
+
const accepted = chunk.subarray(0, remaining);
|
|
3029
|
+
if (accepted.length > 0) chunks.push(accepted);
|
|
3030
|
+
return { bytes: limit, accepted, exceeded: true };
|
|
3031
|
+
}
|
|
3032
|
+
__name(appendCapped, "appendCapped");
|
|
3033
|
+
function codeOf(error) {
|
|
3034
|
+
return isJsonObject(error) && typeof error["code"] === "string" ? error["code"] : void 0;
|
|
3035
|
+
}
|
|
3036
|
+
__name(codeOf, "codeOf");
|
|
3037
|
+
function isTransientSpawnCode(code) {
|
|
3038
|
+
return code === "EAGAIN" || code === "EMFILE" || code === "ENFILE";
|
|
3039
|
+
}
|
|
3040
|
+
__name(isTransientSpawnCode, "isTransientSpawnCode");
|
|
3041
|
+
function childError(message, code, input, transient = false, childCreated = true) {
|
|
3042
|
+
const details = {
|
|
3043
|
+
code,
|
|
3044
|
+
stage: input.stage,
|
|
3045
|
+
attempt: input.attempt,
|
|
3046
|
+
transient,
|
|
3047
|
+
childCreated
|
|
3048
|
+
};
|
|
3049
|
+
if (input.slot !== void 0) details.slot = input.slot;
|
|
3050
|
+
return new FusionError(message, details);
|
|
3051
|
+
}
|
|
3052
|
+
__name(childError, "childError");
|
|
3053
|
+
function withCleanupErrors(error, cleanupErrors) {
|
|
3054
|
+
if (cleanupErrors.length === 0) return error;
|
|
3055
|
+
const details = {
|
|
3056
|
+
code: error.code,
|
|
3057
|
+
transient: error.transient,
|
|
3058
|
+
childCreated: error.childCreated
|
|
3059
|
+
};
|
|
3060
|
+
if (error.stage !== void 0) details.stage = error.stage;
|
|
3061
|
+
if (error.slot !== void 0) details.slot = error.slot;
|
|
3062
|
+
if (error.attempt !== void 0) details.attempt = error.attempt;
|
|
3063
|
+
if (error.artifactDir !== void 0) details.artifactDir = error.artifactDir;
|
|
3064
|
+
return new FusionError(
|
|
3065
|
+
`${error.message}; process cleanup issues: ${cleanupErrors.join("; ")}`,
|
|
3066
|
+
details
|
|
3067
|
+
);
|
|
3068
|
+
}
|
|
3069
|
+
__name(withCleanupErrors, "withCleanupErrors");
|
|
3070
|
+
function defaultSpawn(command, args, options) {
|
|
3071
|
+
return nodeSpawn(command, args, options);
|
|
3072
|
+
}
|
|
3073
|
+
__name(defaultSpawn, "defaultSpawn");
|
|
3074
|
+
function trackTimer(timer) {
|
|
3075
|
+
return timer;
|
|
3076
|
+
}
|
|
3077
|
+
__name(trackTimer, "trackTimer");
|
|
3078
|
+
function rememberCleanupErrors(state, signal, errors) {
|
|
3079
|
+
for (const error of errors) state.cleanupErrors.push(`${signal}: ${error}`);
|
|
3080
|
+
}
|
|
3081
|
+
__name(rememberCleanupErrors, "rememberCleanupErrors");
|
|
3082
|
+
function terminateChild(child, state, platform, killProcess, killGraceMs, sigkillWaitMs, settleSyntheticClose) {
|
|
3083
|
+
if (state.settled || state.terminationStarted) return;
|
|
3084
|
+
state.terminationStarted = true;
|
|
3085
|
+
const termResult = sendSignal(child, platform, killProcess, "SIGTERM");
|
|
3086
|
+
rememberCleanupErrors(state, "SIGTERM", termResult.errors);
|
|
3087
|
+
if (!termResult.sent && state.primaryError === void 0) {
|
|
3088
|
+
state.primaryError = new FusionError(
|
|
3089
|
+
`Pi child SIGTERM failed: ${termResult.errors.join("; ")}`,
|
|
3090
|
+
{
|
|
3091
|
+
code: "child_exit_failed",
|
|
3092
|
+
childCreated: true
|
|
3093
|
+
}
|
|
3094
|
+
);
|
|
3095
|
+
}
|
|
3096
|
+
state.termTimer = trackTimer(
|
|
3097
|
+
setTimeout(() => {
|
|
3098
|
+
if (state.settled) return;
|
|
3099
|
+
const killResult = sendSignal(child, platform, killProcess, "SIGKILL");
|
|
3100
|
+
rememberCleanupErrors(state, "SIGKILL", killResult.errors);
|
|
3101
|
+
if (!killResult.sent && state.primaryError === void 0) {
|
|
3102
|
+
state.primaryError = new FusionError(
|
|
3103
|
+
`Pi child SIGKILL failed: ${killResult.errors.join("; ")}`,
|
|
3104
|
+
{
|
|
3105
|
+
code: "child_exit_failed",
|
|
3106
|
+
childCreated: true
|
|
3107
|
+
}
|
|
3108
|
+
);
|
|
3109
|
+
}
|
|
3110
|
+
}, killGraceMs)
|
|
3111
|
+
);
|
|
3112
|
+
state.waitTimer = trackTimer(
|
|
3113
|
+
setTimeout(() => {
|
|
3114
|
+
if (state.settled) return;
|
|
3115
|
+
const message = "Pi child did not emit close after SIGKILL wait";
|
|
3116
|
+
state.cleanupErrors.push(message);
|
|
3117
|
+
if (state.primaryError === void 0) {
|
|
3118
|
+
state.primaryError = new FusionError(message, {
|
|
3119
|
+
code: "child_exit_failed",
|
|
3120
|
+
childCreated: true
|
|
3121
|
+
});
|
|
3122
|
+
}
|
|
3123
|
+
settleSyntheticClose({ code: null, signal: "SIGKILL" });
|
|
3124
|
+
}, killGraceMs + sigkillWaitMs)
|
|
3125
|
+
);
|
|
3126
|
+
}
|
|
3127
|
+
__name(terminateChild, "terminateChild");
|
|
3128
|
+
function sendSignal(child, platform, killProcess, signal) {
|
|
3129
|
+
const errors = [];
|
|
3130
|
+
const pid = child.pid;
|
|
3131
|
+
if (platform !== "win32" && pid !== void 0) {
|
|
3132
|
+
try {
|
|
3133
|
+
if (killProcess(-pid, signal)) return { sent: true, errors };
|
|
3134
|
+
errors.push("process group kill returned false");
|
|
3135
|
+
} catch (error) {
|
|
3136
|
+
errors.push(
|
|
3137
|
+
`process group kill failed: ${error instanceof Error ? error.message : String(error)}`
|
|
3138
|
+
);
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
try {
|
|
3142
|
+
if (child.kill(signal)) return { sent: true, errors };
|
|
3143
|
+
errors.push("child kill returned false");
|
|
3144
|
+
} catch (error) {
|
|
3145
|
+
errors.push(`child kill failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
3146
|
+
}
|
|
3147
|
+
return { sent: false, errors };
|
|
3148
|
+
}
|
|
3149
|
+
__name(sendSignal, "sendSignal");
|
|
3150
|
+
function cleanupTimers(state) {
|
|
3151
|
+
if (state.termTimer !== void 0) clearTimeout(state.termTimer);
|
|
3152
|
+
if (state.waitTimer !== void 0) clearTimeout(state.waitTimer);
|
|
3153
|
+
if (state.timeoutTimer !== void 0) clearTimeout(state.timeoutTimer);
|
|
3154
|
+
if (state.idleTimer !== void 0) clearTimeout(state.idleTimer);
|
|
3155
|
+
state.termTimer = void 0;
|
|
3156
|
+
state.waitTimer = void 0;
|
|
3157
|
+
state.timeoutTimer = void 0;
|
|
3158
|
+
state.idleTimer = void 0;
|
|
3159
|
+
}
|
|
3160
|
+
__name(cleanupTimers, "cleanupTimers");
|
|
3161
|
+
async function writePromptToStdin(child, prompt) {
|
|
3162
|
+
const stdin = child.stdin;
|
|
3163
|
+
if (stdin === void 0 || stdin === null) throw new Error("Pi child stdin pipe is unavailable");
|
|
3164
|
+
await new Promise((resolve2, reject) => {
|
|
3165
|
+
let settled = false;
|
|
3166
|
+
const fail = /* @__PURE__ */ __name((error) => {
|
|
3167
|
+
if (settled) return;
|
|
3168
|
+
settled = true;
|
|
3169
|
+
stdin.off("error", fail);
|
|
3170
|
+
reject(error);
|
|
3171
|
+
}, "fail");
|
|
3172
|
+
const finish = /* @__PURE__ */ __name(() => {
|
|
3173
|
+
if (settled) return;
|
|
3174
|
+
settled = true;
|
|
3175
|
+
stdin.off("error", fail);
|
|
3176
|
+
resolve2();
|
|
3177
|
+
}, "finish");
|
|
3178
|
+
stdin.once("error", fail);
|
|
3179
|
+
stdin.write(Buffer.from(prompt, "utf8"), (error) => {
|
|
3180
|
+
if (error !== void 0 && error !== null) {
|
|
3181
|
+
fail(error);
|
|
3182
|
+
return;
|
|
3183
|
+
}
|
|
3184
|
+
stdin.end(finish);
|
|
3185
|
+
});
|
|
3186
|
+
});
|
|
3187
|
+
}
|
|
3188
|
+
__name(writePromptToStdin, "writePromptToStdin");
|
|
3189
|
+
async function runPiChild(options) {
|
|
3190
|
+
if (options.signal?.aborted) {
|
|
3191
|
+
throw childError(
|
|
3192
|
+
"Pi child launch cancelled before spawn",
|
|
3193
|
+
"child_cancelled",
|
|
3194
|
+
options,
|
|
3195
|
+
false,
|
|
3196
|
+
false
|
|
3197
|
+
);
|
|
3198
|
+
}
|
|
3199
|
+
const spawnImpl = options.spawn ?? defaultSpawn;
|
|
3200
|
+
const killProcess = options.killProcess ?? process.kill.bind(process);
|
|
3201
|
+
const platform = options.platform ?? process.platform;
|
|
3202
|
+
const capability = options.capability ?? FUSION_NO_TOOLS_CAPABILITY;
|
|
3203
|
+
const env = fusionPiChildEnv(options.env ?? process.env, options.model.provider);
|
|
3204
|
+
if (options.candidateOutputRecoveryPath !== void 0) {
|
|
3205
|
+
if (options.stage !== "candidate") {
|
|
3206
|
+
throw childError(
|
|
3207
|
+
"candidate output recovery may be enabled only for candidate children",
|
|
3208
|
+
"orchestration_failed",
|
|
3209
|
+
options,
|
|
3210
|
+
false,
|
|
3211
|
+
false
|
|
3212
|
+
);
|
|
3213
|
+
}
|
|
3214
|
+
env[FUSION_CANDIDATE_OUTPUT_RECOVERY_PATH_ENV] = options.candidateOutputRecoveryPath;
|
|
3215
|
+
}
|
|
3216
|
+
if (capability !== "reason") {
|
|
3217
|
+
if (options.toolCallLogPath === void 0) {
|
|
3218
|
+
throw childError(
|
|
3219
|
+
`fusion ${capability} child requires a tool-call log path`,
|
|
3220
|
+
"orchestration_failed",
|
|
3221
|
+
options,
|
|
3222
|
+
false,
|
|
3223
|
+
false
|
|
3224
|
+
);
|
|
3225
|
+
}
|
|
3226
|
+
env[FUSION_TOOL_CALL_LOG_PATH_ENV] = options.toolCallLogPath;
|
|
3227
|
+
if (capability === "research") {
|
|
3228
|
+
if (options.sourcePolicy === void 0) {
|
|
3229
|
+
throw childError(
|
|
3230
|
+
"fusion research child requires a source-policy path and hash",
|
|
3231
|
+
"orchestration_failed",
|
|
3232
|
+
options,
|
|
3233
|
+
false,
|
|
3234
|
+
false
|
|
3235
|
+
);
|
|
3236
|
+
}
|
|
3237
|
+
env[FUSION_RESEARCH_ENABLED_ENV] = "1";
|
|
3238
|
+
env[FUSION_SOURCE_POLICY_PATH_ENV] = options.sourcePolicy.path;
|
|
3239
|
+
env[FUSION_SOURCE_POLICY_SHA256_ENV] = options.sourcePolicy.sha256;
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
const stdoutLimit = options.stdoutLimitBytes ?? FUSION_CHILD_STDOUT_LIMIT_BYTES;
|
|
3243
|
+
const stderrLimit = options.stderrLimitBytes ?? FUSION_CHILD_STDERR_LIMIT_BYTES;
|
|
3244
|
+
const timeoutMs = options.timeoutMs ?? FUSION_CHILD_TIMEOUT_MS;
|
|
3245
|
+
const idleTimeoutMs = options.idleTimeoutMs ?? FUSION_CHILD_IDLE_TIMEOUT_MS;
|
|
3246
|
+
const killGraceMs = options.killGraceMs ?? FUSION_CHILD_KILL_GRACE_MS;
|
|
3247
|
+
const sigkillWaitMs = options.sigkillWaitMs ?? FUSION_CHILD_SIGKILL_WAIT_MS;
|
|
3248
|
+
const argv = buildFusionPiChildArgv(
|
|
3249
|
+
options.model,
|
|
3250
|
+
options.systemPrompt,
|
|
3251
|
+
options.childExtensionPath ?? resolveFusionChildExtensionPath(),
|
|
3252
|
+
capability
|
|
3253
|
+
);
|
|
3254
|
+
const parser = new FusionPiCompactResultParser(options.model.provider, options.model.model);
|
|
3255
|
+
const stdoutChunks = [];
|
|
3256
|
+
const stderrChunks = [];
|
|
3257
|
+
let stdoutBytes = 0;
|
|
3258
|
+
let stderrBytes = 0;
|
|
3259
|
+
const state = {
|
|
3260
|
+
primaryError: void 0,
|
|
3261
|
+
cleanupErrors: [],
|
|
3262
|
+
terminationStarted: false,
|
|
3263
|
+
termTimer: void 0,
|
|
3264
|
+
waitTimer: void 0,
|
|
3265
|
+
timeoutTimer: void 0,
|
|
3266
|
+
idleTimer: void 0,
|
|
3267
|
+
settled: false
|
|
3268
|
+
};
|
|
3269
|
+
let child;
|
|
3270
|
+
try {
|
|
3271
|
+
const launchDeps = options.piLaunchDependencies === void 0 ? { platform } : { ...options.piLaunchDependencies, platform };
|
|
3272
|
+
const launch = resolvePiLaunch(launchDeps);
|
|
3273
|
+
assertWindowsCommandLineWithinLimit(launch, argv, platform, `fusion-${options.stage}`);
|
|
3274
|
+
child = spawnImpl(launch.executable, piLaunchArgv(launch, argv), {
|
|
3275
|
+
cwd: options.cwd,
|
|
3276
|
+
detached: platform !== "win32",
|
|
3277
|
+
shell: false,
|
|
3278
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
3279
|
+
env,
|
|
3280
|
+
windowsHide: true
|
|
3281
|
+
});
|
|
3282
|
+
} catch (error) {
|
|
3283
|
+
const code = codeOf(error);
|
|
3284
|
+
throw childError(
|
|
3285
|
+
`Pi child spawn failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3286
|
+
"child_spawn_failed",
|
|
3287
|
+
options,
|
|
3288
|
+
isTransientSpawnCode(code),
|
|
3289
|
+
false
|
|
3290
|
+
);
|
|
3291
|
+
}
|
|
3292
|
+
let settleClose = /* @__PURE__ */ __name(() => void 0, "settleClose");
|
|
3293
|
+
const closePromise = new Promise((resolve2) => {
|
|
3294
|
+
settleClose = /* @__PURE__ */ __name((close) => {
|
|
3295
|
+
if (state.settled) return;
|
|
3296
|
+
state.settled = true;
|
|
3297
|
+
resolve2(close);
|
|
3298
|
+
}, "settleClose");
|
|
3299
|
+
});
|
|
3300
|
+
const resetIdleTimer = /* @__PURE__ */ __name(() => {
|
|
3301
|
+
if (state.settled) return;
|
|
3302
|
+
if (state.idleTimer !== void 0) clearTimeout(state.idleTimer);
|
|
3303
|
+
state.idleTimer = trackTimer(
|
|
3304
|
+
setTimeout(() => {
|
|
3305
|
+
if (state.settled) return;
|
|
3306
|
+
if (state.primaryError === void 0) {
|
|
3307
|
+
state.primaryError = childError(
|
|
3308
|
+
`Pi child produced no output for ${String(idleTimeoutMs)}ms (stalled)`,
|
|
3309
|
+
"child_timeout",
|
|
3310
|
+
options
|
|
3311
|
+
);
|
|
3312
|
+
}
|
|
3313
|
+
terminateChild(
|
|
3314
|
+
child,
|
|
3315
|
+
state,
|
|
3316
|
+
platform,
|
|
3317
|
+
killProcess,
|
|
3318
|
+
killGraceMs,
|
|
3319
|
+
sigkillWaitMs,
|
|
3320
|
+
settleClose
|
|
3321
|
+
);
|
|
3322
|
+
}, idleTimeoutMs)
|
|
3323
|
+
);
|
|
3324
|
+
}, "resetIdleTimer");
|
|
3325
|
+
const abortListener = /* @__PURE__ */ __name(() => {
|
|
3326
|
+
if (state.settled) return;
|
|
3327
|
+
if (state.primaryError === void 0) {
|
|
3328
|
+
state.primaryError = childError("Pi child cancelled", "child_cancelled", options);
|
|
3329
|
+
}
|
|
3330
|
+
terminateChild(child, state, platform, killProcess, killGraceMs, sigkillWaitMs, settleClose);
|
|
3331
|
+
}, "abortListener");
|
|
3332
|
+
const stdoutListener = /* @__PURE__ */ __name((data) => {
|
|
3333
|
+
resetIdleTimer();
|
|
3334
|
+
const chunk = Buffer.isBuffer(data) ? data : Buffer.from(data, "utf8");
|
|
3335
|
+
const appended = appendCapped(stdoutChunks, stdoutBytes, chunk, stdoutLimit);
|
|
3336
|
+
stdoutBytes = appended.bytes;
|
|
3337
|
+
if (appended.exceeded && state.primaryError === void 0) {
|
|
3338
|
+
state.primaryError = childError(
|
|
3339
|
+
`Pi child final response exceeded ${String(stdoutLimit)} bytes`,
|
|
3340
|
+
"child_output_cap",
|
|
3341
|
+
options
|
|
3342
|
+
);
|
|
3343
|
+
terminateChild(child, state, platform, killProcess, killGraceMs, sigkillWaitMs, settleClose);
|
|
3344
|
+
}
|
|
3345
|
+
}, "stdoutListener");
|
|
3346
|
+
const stderrListener = /* @__PURE__ */ __name((data) => {
|
|
3347
|
+
resetIdleTimer();
|
|
3348
|
+
const chunk = Buffer.isBuffer(data) ? data : Buffer.from(data, "utf8");
|
|
3349
|
+
const appended = appendCapped(stderrChunks, stderrBytes, chunk, stderrLimit);
|
|
3350
|
+
stderrBytes = appended.bytes;
|
|
3351
|
+
if (appended.exceeded && state.primaryError === void 0) {
|
|
3352
|
+
state.primaryError = childError(
|
|
3353
|
+
`Pi child stderr exceeded ${String(stderrLimit)} bytes`,
|
|
3354
|
+
"child_output_cap",
|
|
3355
|
+
options
|
|
3356
|
+
);
|
|
3357
|
+
terminateChild(child, state, platform, killProcess, killGraceMs, sigkillWaitMs, settleClose);
|
|
3358
|
+
}
|
|
3359
|
+
}, "stderrListener");
|
|
3360
|
+
const errorListener = /* @__PURE__ */ __name((error) => {
|
|
3361
|
+
if (state.settled) return;
|
|
3362
|
+
if (state.primaryError === void 0) {
|
|
3363
|
+
const code = codeOf(error);
|
|
3364
|
+
const childCreated = child.pid !== void 0;
|
|
3365
|
+
state.primaryError = childError(
|
|
3366
|
+
`Pi child process error: ${error.message}`,
|
|
3367
|
+
"child_spawn_failed",
|
|
3368
|
+
options,
|
|
3369
|
+
isTransientSpawnCode(code),
|
|
3370
|
+
childCreated
|
|
3371
|
+
);
|
|
3372
|
+
}
|
|
3373
|
+
if (child.pid === void 0) settleClose({ code: null, signal: null });
|
|
3374
|
+
}, "errorListener");
|
|
3375
|
+
const closeListener = /* @__PURE__ */ __name((code, signal) => {
|
|
3376
|
+
settleClose({ code, signal });
|
|
3377
|
+
}, "closeListener");
|
|
3378
|
+
child.stdout?.on("data", stdoutListener);
|
|
3379
|
+
child.stderr?.on("data", stderrListener);
|
|
3380
|
+
child.once("error", errorListener);
|
|
3381
|
+
child.once("close", closeListener);
|
|
3382
|
+
options.signal?.addEventListener("abort", abortListener, { once: true });
|
|
3383
|
+
if (options.signal?.aborted) abortListener();
|
|
3384
|
+
resetIdleTimer();
|
|
3385
|
+
state.timeoutTimer = trackTimer(
|
|
3386
|
+
setTimeout(() => {
|
|
3387
|
+
if (state.primaryError === void 0) {
|
|
3388
|
+
state.primaryError = childError(
|
|
3389
|
+
`Pi child timed out after ${String(timeoutMs)}ms`,
|
|
3390
|
+
"child_timeout",
|
|
3391
|
+
options
|
|
3392
|
+
);
|
|
3393
|
+
}
|
|
3394
|
+
terminateChild(child, state, platform, killProcess, killGraceMs, sigkillWaitMs, settleClose);
|
|
3395
|
+
}, timeoutMs)
|
|
3396
|
+
);
|
|
3397
|
+
try {
|
|
3398
|
+
try {
|
|
3399
|
+
if (state.primaryError === void 0) await writePromptToStdin(child, options.userPrompt);
|
|
3400
|
+
} catch (error) {
|
|
3401
|
+
if (state.primaryError === void 0) {
|
|
3402
|
+
state.primaryError = childError(
|
|
3403
|
+
`Pi child stdin write failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3404
|
+
"child_stdin_failed",
|
|
3405
|
+
options
|
|
3406
|
+
);
|
|
3407
|
+
}
|
|
3408
|
+
terminateChild(child, state, platform, killProcess, killGraceMs, sigkillWaitMs, settleClose);
|
|
3409
|
+
}
|
|
3410
|
+
const close = await closePromise;
|
|
3411
|
+
const response = Buffer.concat(stdoutChunks);
|
|
3412
|
+
const rawStderr = Buffer.concat(stderrChunks);
|
|
3413
|
+
const observed = parser.snapshot(rawStderr);
|
|
3414
|
+
let compactEvents = Buffer.alloc(0);
|
|
3415
|
+
let diagnostics = rawStderr;
|
|
3416
|
+
try {
|
|
3417
|
+
const decoded = parseFusionChildStderr(rawStderr);
|
|
3418
|
+
compactEvents = decoded.events;
|
|
3419
|
+
diagnostics = decoded.diagnostics;
|
|
3420
|
+
} catch {
|
|
3421
|
+
}
|
|
3422
|
+
const readPartialOutputRecovery = /* @__PURE__ */ __name(async () => {
|
|
3423
|
+
let parsedRecords;
|
|
3424
|
+
try {
|
|
3425
|
+
parsedRecords = parseFusionChildStderr(rawStderr).records;
|
|
3426
|
+
} catch {
|
|
3427
|
+
return void 0;
|
|
3428
|
+
}
|
|
3429
|
+
const evidence = parsedCandidateOutputRecovery(parsedRecords, "failed");
|
|
3430
|
+
if (evidence === void 0) return void 0;
|
|
3431
|
+
if (options.candidateOutputRecoveryPath === void 0) {
|
|
3432
|
+
throw new Error("fusion child emitted output-recovery evidence without an artifact path");
|
|
3433
|
+
}
|
|
3434
|
+
return readCandidateOutputRecovery(options.candidateOutputRecoveryPath, evidence);
|
|
3435
|
+
}, "readPartialOutputRecovery");
|
|
3436
|
+
const primary = state.primaryError;
|
|
3437
|
+
if (primary !== void 0) {
|
|
3438
|
+
let outputRecovery2;
|
|
3439
|
+
try {
|
|
3440
|
+
outputRecovery2 = await readPartialOutputRecovery();
|
|
3441
|
+
} catch (error) {
|
|
3442
|
+
state.cleanupErrors.push(
|
|
3443
|
+
`output recovery evidence invalid: ${error instanceof Error ? error.message : String(error)}`
|
|
3444
|
+
);
|
|
3445
|
+
}
|
|
3446
|
+
throw new FusionChildRunError(
|
|
3447
|
+
withCleanupErrors(primary, state.cleanupErrors),
|
|
3448
|
+
compactEvents,
|
|
3449
|
+
response,
|
|
3450
|
+
diagnostics,
|
|
3451
|
+
close,
|
|
3452
|
+
observed,
|
|
3453
|
+
outputRecovery2
|
|
3454
|
+
);
|
|
3455
|
+
}
|
|
3456
|
+
let terminalSettlement;
|
|
3457
|
+
try {
|
|
3458
|
+
terminalSettlement = parseFusionChildSettlement(rawStderr);
|
|
3459
|
+
} catch (error) {
|
|
3460
|
+
throw new FusionChildRunError(
|
|
3461
|
+
withCleanupErrors(
|
|
3462
|
+
childError(
|
|
3463
|
+
`Pi child terminal settlement invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
3464
|
+
"child_event_invalid",
|
|
3465
|
+
options
|
|
3466
|
+
),
|
|
3467
|
+
state.cleanupErrors
|
|
3468
|
+
),
|
|
3469
|
+
compactEvents,
|
|
3470
|
+
response,
|
|
3471
|
+
diagnostics,
|
|
3472
|
+
close,
|
|
3473
|
+
observed
|
|
3474
|
+
);
|
|
3475
|
+
}
|
|
3476
|
+
if (terminalSettlement?.failure_reason === "output_recovery") {
|
|
3477
|
+
try {
|
|
3478
|
+
const failure = parser.finishOutputRecoveryFailure(response, rawStderr);
|
|
3479
|
+
if (options.candidateOutputRecoveryPath === void 0) {
|
|
3480
|
+
throw new Error(
|
|
3481
|
+
"fusion child output-recovery failure omitted the configured artifact path"
|
|
3482
|
+
);
|
|
3483
|
+
}
|
|
3484
|
+
const outputRecovery2 = await readCandidateOutputRecovery(
|
|
3485
|
+
options.candidateOutputRecoveryPath,
|
|
3486
|
+
failure.outputRecovery
|
|
3487
|
+
);
|
|
3488
|
+
const recoveryMessage = outputRecovery2.replacement_json_rendered_bytes === null ? `Pi child could not complete its one allowed same-session candidate compression continuation; the ${String(outputRecovery2.original_json_rendered_bytes)}-byte original is preserved and nothing was truncated` : `Pi child compressed candidate response is still ${String(outputRecovery2.replacement_json_rendered_bytes)} JSON-rendered bytes, exceeding the ${String(outputRecovery2.limit_bytes)}-byte output contract; both responses are preserved and nothing was truncated`;
|
|
3489
|
+
throw new FusionChildRunError(
|
|
3490
|
+
withCleanupErrors(
|
|
3491
|
+
childError(recoveryMessage, "child_output_cap", options),
|
|
3492
|
+
state.cleanupErrors
|
|
3493
|
+
),
|
|
3494
|
+
failure.events,
|
|
3495
|
+
response,
|
|
3496
|
+
failure.diagnostics,
|
|
3497
|
+
close,
|
|
3498
|
+
{
|
|
3499
|
+
usage: failure.usage,
|
|
3500
|
+
provider: failure.provider,
|
|
3501
|
+
model: failure.model,
|
|
3502
|
+
qualifiedId: failure.qualifiedId
|
|
3503
|
+
},
|
|
3504
|
+
outputRecovery2
|
|
3505
|
+
);
|
|
3506
|
+
} catch (error) {
|
|
3507
|
+
if (error instanceof FusionChildRunError) throw error;
|
|
3508
|
+
throw new FusionChildRunError(
|
|
3509
|
+
withCleanupErrors(
|
|
3510
|
+
childError(
|
|
3511
|
+
`Pi child output-recovery evidence invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
3512
|
+
"child_event_invalid",
|
|
3513
|
+
options
|
|
3514
|
+
),
|
|
3515
|
+
state.cleanupErrors
|
|
3516
|
+
),
|
|
3517
|
+
compactEvents,
|
|
3518
|
+
response,
|
|
3519
|
+
diagnostics,
|
|
3520
|
+
close,
|
|
3521
|
+
observed
|
|
3522
|
+
);
|
|
3523
|
+
}
|
|
3524
|
+
}
|
|
3525
|
+
if (close.code !== 0 || close.signal !== null) {
|
|
3526
|
+
let runtimeGuard;
|
|
3527
|
+
try {
|
|
3528
|
+
runtimeGuard = parseFusionRuntimeGuard(rawStderr);
|
|
3529
|
+
if (runtimeGuard !== void 0) {
|
|
3530
|
+
assertFusionRuntimeGuardMatchesModel(runtimeGuard, options.model);
|
|
3531
|
+
}
|
|
3532
|
+
} catch (error) {
|
|
3533
|
+
throw new FusionChildRunError(
|
|
3534
|
+
withCleanupErrors(
|
|
3535
|
+
childError(
|
|
3536
|
+
`Pi child runtime guard evidence invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
3537
|
+
"child_event_invalid",
|
|
3538
|
+
options
|
|
3539
|
+
),
|
|
3540
|
+
state.cleanupErrors
|
|
3541
|
+
),
|
|
3542
|
+
compactEvents,
|
|
3543
|
+
response,
|
|
3544
|
+
diagnostics,
|
|
3545
|
+
close,
|
|
3546
|
+
observed
|
|
3547
|
+
);
|
|
3548
|
+
}
|
|
3549
|
+
throw new FusionChildRunError(
|
|
3550
|
+
withCleanupErrors(
|
|
3551
|
+
childError(
|
|
3552
|
+
runtimeGuard?.message ?? `Pi child exited with code ${close.code === null ? "null" : String(close.code)}${close.signal === null ? "" : ` (${close.signal})`}`,
|
|
3553
|
+
runtimeGuard === void 0 ? "child_exit_failed" : runtimeGuard.code === "claude_cache_policy" ? "child_cache_policy_invalid" : runtimeGuard.code === "provider_payload_invalid" ? "child_runtime_payload_invalid" : "child_runtime_limit_exceeded",
|
|
3554
|
+
options
|
|
3555
|
+
),
|
|
3556
|
+
state.cleanupErrors
|
|
3557
|
+
),
|
|
3558
|
+
compactEvents,
|
|
3559
|
+
response,
|
|
3560
|
+
diagnostics,
|
|
3561
|
+
close,
|
|
3562
|
+
observed
|
|
3563
|
+
);
|
|
3564
|
+
}
|
|
3565
|
+
let parsed;
|
|
3566
|
+
try {
|
|
3567
|
+
parsed = parser.finish(response, rawStderr);
|
|
3568
|
+
} catch (error) {
|
|
3569
|
+
throw new FusionChildRunError(
|
|
3570
|
+
withCleanupErrors(
|
|
3571
|
+
childError(
|
|
3572
|
+
`Pi child compact result invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
3573
|
+
"child_event_invalid",
|
|
3574
|
+
options
|
|
3575
|
+
),
|
|
3576
|
+
state.cleanupErrors
|
|
3577
|
+
),
|
|
3578
|
+
compactEvents,
|
|
3579
|
+
response,
|
|
3580
|
+
diagnostics,
|
|
3581
|
+
close,
|
|
3582
|
+
observed
|
|
3583
|
+
);
|
|
3584
|
+
}
|
|
3585
|
+
let outputRecovery;
|
|
3586
|
+
if (parsed.outputRecovery !== void 0) {
|
|
3587
|
+
if (options.candidateOutputRecoveryPath === void 0) {
|
|
3588
|
+
throw new FusionChildRunError(
|
|
3589
|
+
childError(
|
|
3590
|
+
"Pi child output-recovery evidence has no configured artifact path",
|
|
3591
|
+
"child_event_invalid",
|
|
3592
|
+
options
|
|
3593
|
+
),
|
|
3594
|
+
parsed.events,
|
|
3595
|
+
response,
|
|
3596
|
+
parsed.diagnostics,
|
|
3597
|
+
close,
|
|
3598
|
+
observed
|
|
3599
|
+
);
|
|
3600
|
+
}
|
|
3601
|
+
try {
|
|
3602
|
+
outputRecovery = await readCandidateOutputRecovery(
|
|
3603
|
+
options.candidateOutputRecoveryPath,
|
|
3604
|
+
parsed.outputRecovery
|
|
3605
|
+
);
|
|
3606
|
+
} catch (error) {
|
|
3607
|
+
throw new FusionChildRunError(
|
|
3608
|
+
childError(
|
|
3609
|
+
`Pi child output-recovery artifact invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
3610
|
+
"child_event_invalid",
|
|
3611
|
+
options
|
|
3612
|
+
),
|
|
3613
|
+
parsed.events,
|
|
3614
|
+
response,
|
|
3615
|
+
parsed.diagnostics,
|
|
3616
|
+
close,
|
|
3617
|
+
observed
|
|
3618
|
+
);
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
let toolCallTrace;
|
|
3622
|
+
if (capability !== "reason") {
|
|
3623
|
+
const logPath = options.toolCallLogPath;
|
|
3624
|
+
if (logPath === void 0) {
|
|
3625
|
+
throw childError(
|
|
3626
|
+
`fusion ${capability} child completed without a tool-call log path`,
|
|
3627
|
+
"orchestration_failed",
|
|
3628
|
+
options
|
|
3629
|
+
);
|
|
3630
|
+
}
|
|
3631
|
+
try {
|
|
3632
|
+
toolCallTrace = await readFusionToolCallLog(logPath);
|
|
3633
|
+
await assertFusionToolCallLogSeal(logPath, toolCallTrace);
|
|
3634
|
+
await assertCompletedToolPolicy(toolCallTrace, capability, options.sourcePolicy);
|
|
3635
|
+
} catch (error) {
|
|
3636
|
+
throw new FusionChildRunError(
|
|
3637
|
+
withCleanupErrors(
|
|
3638
|
+
childError(
|
|
3639
|
+
`Pi child tool-call log invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
3640
|
+
"child_event_invalid",
|
|
3641
|
+
options
|
|
3642
|
+
),
|
|
3643
|
+
state.cleanupErrors
|
|
3644
|
+
),
|
|
3645
|
+
compactEvents,
|
|
3646
|
+
response,
|
|
3647
|
+
diagnostics,
|
|
3648
|
+
close,
|
|
3649
|
+
observed,
|
|
3650
|
+
outputRecovery
|
|
3651
|
+
);
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
const result = {
|
|
3655
|
+
stage: options.stage,
|
|
3656
|
+
attempt: options.attempt,
|
|
3657
|
+
provider: parsed.provider,
|
|
3658
|
+
model: parsed.model,
|
|
3659
|
+
qualifiedId: parsed.qualifiedId,
|
|
3660
|
+
text: parsed.text,
|
|
3661
|
+
usage: parsed.usage,
|
|
3662
|
+
firstRequestUsage: parsed.firstRequestUsage,
|
|
3663
|
+
providerRequestCount: parsed.providerRequestCount,
|
|
3664
|
+
events: parsed.events,
|
|
3665
|
+
stderr: parsed.diagnostics,
|
|
3666
|
+
exitCode: close.code,
|
|
3667
|
+
signal: close.signal
|
|
3668
|
+
};
|
|
3669
|
+
if (options.slot !== void 0) result.slot = options.slot;
|
|
3670
|
+
if (outputRecovery !== void 0) result.outputRecovery = outputRecovery;
|
|
3671
|
+
if (toolCallTrace !== void 0) result.toolCallTrace = toolCallTrace;
|
|
3672
|
+
return result;
|
|
3673
|
+
} finally {
|
|
3674
|
+
cleanupTimers(state);
|
|
3675
|
+
options.signal?.removeEventListener("abort", abortListener);
|
|
3676
|
+
child.stdout?.off("data", stdoutListener);
|
|
3677
|
+
child.stderr?.off("data", stderrListener);
|
|
3678
|
+
child.off("error", errorListener);
|
|
3679
|
+
child.off("close", closeListener);
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
__name(runPiChild, "runPiChild");
|
|
3683
|
+
|
|
3684
|
+
// node_modules/pi-background-tasks/src/core/fusion/orchestrator.ts?ext=pi-background-tasks
|
|
3685
|
+
function addFailedChildUsage(target, error) {
|
|
3686
|
+
if (error instanceof FusionChildRunError) addFusionUsage(target, error.usage);
|
|
3687
|
+
}
|
|
3688
|
+
__name(addFailedChildUsage, "addFailedChildUsage");
|
|
3689
|
+
function errorText2(error) {
|
|
3690
|
+
return error instanceof Error ? error.message : String(error);
|
|
3691
|
+
}
|
|
3692
|
+
__name(errorText2, "errorText");
|
|
3693
|
+
function isRecord2(value) {
|
|
3694
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3695
|
+
}
|
|
3696
|
+
__name(isRecord2, "isRecord");
|
|
3697
|
+
function hasOnlyKeys(value, allowed) {
|
|
3698
|
+
const allowedSet = new Set(allowed);
|
|
3699
|
+
return Object.keys(value).every((key) => allowedSet.has(key));
|
|
3700
|
+
}
|
|
3701
|
+
__name(hasOnlyKeys, "hasOnlyKeys");
|
|
3702
|
+
function isStrictCleanCanonicalInput(value) {
|
|
3703
|
+
if (!isRecord2(value)) return false;
|
|
3704
|
+
if (!hasOnlyKeys(value, ["schema_version", "workflow", "cwd", "request", "context"]))
|
|
3705
|
+
return false;
|
|
3706
|
+
const request = value["request"];
|
|
3707
|
+
if (!isRecord2(request)) return false;
|
|
3708
|
+
if (!hasOnlyKeys(request, ["source", "authority", "text", "sha256"])) return false;
|
|
3709
|
+
const context = value["context"];
|
|
3710
|
+
if (!isRecord2(context)) return false;
|
|
3711
|
+
if (!hasOnlyKeys(context, ["kind", "policy_id", "declared_sources"])) return false;
|
|
3712
|
+
if (context["kind"] !== "clean_task") return false;
|
|
3713
|
+
const declaredSources = context["declared_sources"];
|
|
3714
|
+
if (!Array.isArray(declaredSources)) return false;
|
|
3715
|
+
for (const source of declaredSources) {
|
|
3716
|
+
if (!isRecord2(source) || !hasOnlyKeys(source, ["url", "canonical_url", "purpose", "sha256"]))
|
|
3717
|
+
return false;
|
|
3718
|
+
}
|
|
3719
|
+
return true;
|
|
3720
|
+
}
|
|
3721
|
+
__name(isStrictCleanCanonicalInput, "isStrictCleanCanonicalInput");
|
|
3722
|
+
function asFusionError(error, artifactDir, messageOverride) {
|
|
3723
|
+
if (error instanceof FusionError) {
|
|
3724
|
+
const details = {
|
|
3725
|
+
code: error.code,
|
|
3726
|
+
artifactDir,
|
|
3727
|
+
transient: error.transient,
|
|
3728
|
+
childCreated: error.childCreated
|
|
3729
|
+
};
|
|
3730
|
+
if (error.stage !== void 0) details.stage = error.stage;
|
|
3731
|
+
if (error.slot !== void 0) details.slot = error.slot;
|
|
3732
|
+
if (error.attempt !== void 0) details.attempt = error.attempt;
|
|
3733
|
+
if (error.budget !== void 0) details.budget = error.budget;
|
|
3734
|
+
if (error.runProgress !== void 0) details.runProgress = error.runProgress;
|
|
3735
|
+
return new FusionError(messageOverride ?? error.message, details);
|
|
3736
|
+
}
|
|
3737
|
+
return new FusionError(messageOverride ?? errorText2(error), {
|
|
3738
|
+
code: "orchestration_failed",
|
|
3739
|
+
artifactDir,
|
|
3740
|
+
childCreated: false
|
|
3741
|
+
});
|
|
3742
|
+
}
|
|
3743
|
+
__name(asFusionError, "asFusionError");
|
|
3744
|
+
function formatFusionRunStage(name, stage) {
|
|
3745
|
+
const notStarted = stage.not_started_slots === void 0 ? "" : `, ${String(stage.not_started_slots)} slot(s) not started`;
|
|
3746
|
+
return `${name}=${stage.status} (${String(stage.children_created)} created, ${String(stage.children_completed)} completed, ${String(stage.children_failed)} failed, ${String(stage.children_cancelled)} cancelled${notStarted})`;
|
|
3747
|
+
}
|
|
3748
|
+
__name(formatFusionRunStage, "formatFusionRunStage");
|
|
3749
|
+
function summaryUnavailableNote(error) {
|
|
3750
|
+
const detail = errorText2(error);
|
|
3751
|
+
const detailBytes = Buffer.from(detail, "utf8");
|
|
3752
|
+
if (detailBytes.length > 1024) {
|
|
3753
|
+
return "failure-summary.json unavailable after terminal publication; write failure detail omitted because it exceeds the 1024-byte diagnostic cap.";
|
|
3754
|
+
}
|
|
3755
|
+
return `failure-summary.json unavailable after terminal publication: ${detail}`;
|
|
3756
|
+
}
|
|
3757
|
+
__name(summaryUnavailableNote, "summaryUnavailableNote");
|
|
3758
|
+
function withSummaryUnavailableNote(error, summaryError) {
|
|
3759
|
+
const details = {
|
|
3760
|
+
code: error.code,
|
|
3761
|
+
transient: error.transient,
|
|
3762
|
+
childCreated: error.childCreated
|
|
3763
|
+
};
|
|
3764
|
+
if (error.artifactDir !== void 0) details.artifactDir = error.artifactDir;
|
|
3765
|
+
if (error.stage !== void 0) details.stage = error.stage;
|
|
3766
|
+
if (error.slot !== void 0) details.slot = error.slot;
|
|
3767
|
+
if (error.attempt !== void 0) details.attempt = error.attempt;
|
|
3768
|
+
if (error.budget !== void 0) details.budget = error.budget;
|
|
3769
|
+
if (error.runProgress !== void 0) details.runProgress = error.runProgress;
|
|
3770
|
+
return new FusionError(`${error.message}
|
|
3771
|
+
${summaryUnavailableNote(summaryError)}`, details);
|
|
3772
|
+
}
|
|
3773
|
+
__name(withSummaryUnavailableNote, "withSummaryUnavailableNote");
|
|
3774
|
+
function formatFusionRunProgress(progress) {
|
|
3775
|
+
const usage = progress.usage_so_far;
|
|
3776
|
+
const optionalUsage = [
|
|
3777
|
+
usage.cacheWrite1h === void 0 ? void 0 : `cacheWrite1h=${String(usage.cacheWrite1h)}`,
|
|
3778
|
+
usage.reasoning === void 0 ? void 0 : `reasoning=${String(usage.reasoning)}`
|
|
3779
|
+
].filter((value) => value !== void 0);
|
|
3780
|
+
const optionalText = optionalUsage.length === 0 ? "" : `, ${optionalUsage.join(", ")}`;
|
|
3781
|
+
return `Run progress from durable attempts: ${formatFusionRunStage("candidates", progress.candidates)}; ${formatFusionRunStage("evaluation", progress.evaluation)}; ${formatFusionRunStage("merge", progress.merge)}. Usage so far: input=${String(usage.input)}, output=${String(usage.output)}, cacheRead=${String(usage.cacheRead)}, cacheWrite=${String(usage.cacheWrite)}${optionalText}, totalTokens=${String(usage.totalTokens)}, cost.input=${String(usage.cost.input)}, cost.output=${String(usage.cost.output)}, cost.cacheRead=${String(usage.cost.cacheRead)}, cost.cacheWrite=${String(usage.cost.cacheWrite)}, cost.total=${String(usage.cost.total)}.`;
|
|
3782
|
+
}
|
|
3783
|
+
__name(formatFusionRunProgress, "formatFusionRunProgress");
|
|
3784
|
+
function withRunProgress(error, artifactDir, progress) {
|
|
3785
|
+
const base = asFusionError(error, artifactDir);
|
|
3786
|
+
const details = {
|
|
3787
|
+
code: base.code,
|
|
3788
|
+
artifactDir,
|
|
3789
|
+
transient: base.transient,
|
|
3790
|
+
childCreated: base.childCreated,
|
|
3791
|
+
runProgress: progress
|
|
3792
|
+
};
|
|
3793
|
+
if (base.stage !== void 0) details.stage = base.stage;
|
|
3794
|
+
if (base.slot !== void 0) details.slot = base.slot;
|
|
3795
|
+
if (base.attempt !== void 0) details.attempt = base.attempt;
|
|
3796
|
+
if (base.budget !== void 0) details.budget = base.budget;
|
|
3797
|
+
return new FusionError(`${base.message}
|
|
3798
|
+
${formatFusionRunProgress(progress)}`, details);
|
|
3799
|
+
}
|
|
3800
|
+
__name(withRunProgress, "withRunProgress");
|
|
3801
|
+
function withTerminalArtifactFailure(error, artifactDir, artifactError) {
|
|
3802
|
+
const message = `${errorText2(error)}; additionally failed to write terminal fusion artifacts: ${errorText2(artifactError)}`;
|
|
3803
|
+
return asFusionError(error, artifactDir, message);
|
|
3804
|
+
}
|
|
3805
|
+
__name(withTerminalArtifactFailure, "withTerminalArtifactFailure");
|
|
3806
|
+
function recordFailureInput(error, stage, slot, attempt, systemPrompt, prompt, responseKind) {
|
|
3807
|
+
if (error instanceof FusionChildRunError) {
|
|
3808
|
+
const base2 = {
|
|
3809
|
+
stage,
|
|
3810
|
+
attempt,
|
|
3811
|
+
systemPrompt,
|
|
3812
|
+
prompt,
|
|
3813
|
+
events: error.events,
|
|
3814
|
+
partialResponse: error.response,
|
|
3815
|
+
stderr: error.stderr,
|
|
3816
|
+
error: error.message,
|
|
3817
|
+
status: error.code === "child_cancelled" ? "cancelled" : "failed",
|
|
3818
|
+
responseKind,
|
|
3819
|
+
childCreated: error.childCreated,
|
|
3820
|
+
usage: error.usage,
|
|
3821
|
+
...error.outputRecovery === void 0 ? {} : { outputRecovery: error.outputRecovery }
|
|
3822
|
+
};
|
|
3823
|
+
if (slot !== void 0) base2.slot = slot;
|
|
3824
|
+
if (error.provider !== void 0) base2.provider = error.provider;
|
|
3825
|
+
if (error.modelName !== void 0) base2.model = error.modelName;
|
|
3826
|
+
if (error.qualifiedId !== void 0) base2.qualifiedId = error.qualifiedId;
|
|
3827
|
+
return base2;
|
|
3828
|
+
}
|
|
3829
|
+
const base = {
|
|
3830
|
+
stage,
|
|
3831
|
+
attempt,
|
|
3832
|
+
systemPrompt,
|
|
3833
|
+
prompt,
|
|
3834
|
+
events: Buffer.alloc(0),
|
|
3835
|
+
partialResponse: Buffer.alloc(0),
|
|
3836
|
+
stderr: Buffer.alloc(0),
|
|
3837
|
+
error: errorText2(error),
|
|
3838
|
+
status: error instanceof FusionError && error.code === "child_cancelled" ? "cancelled" : "failed",
|
|
3839
|
+
responseKind,
|
|
3840
|
+
childCreated: error instanceof FusionError ? error.childCreated : false
|
|
3841
|
+
};
|
|
3842
|
+
if (slot !== void 0) base.slot = slot;
|
|
3843
|
+
return base;
|
|
3844
|
+
}
|
|
3845
|
+
__name(recordFailureInput, "recordFailureInput");
|
|
3846
|
+
function retryableSpawn(error, attempt) {
|
|
3847
|
+
if (!(error instanceof FusionError)) return false;
|
|
3848
|
+
return attempt === 1 && error.code === "child_spawn_failed" && error.transient && !error.childCreated;
|
|
3849
|
+
}
|
|
3850
|
+
__name(retryableSpawn, "retryableSpawn");
|
|
3851
|
+
function childOptions(input, model, stage, attempt, capability, systemPrompt, userPrompt, signal, slot, toolCallLogPath, sourcePolicy, candidateOutputRecoveryPath) {
|
|
3852
|
+
const out = {
|
|
3853
|
+
stage,
|
|
3854
|
+
attempt,
|
|
3855
|
+
cwd: input.cwd,
|
|
3856
|
+
model,
|
|
3857
|
+
capability,
|
|
3858
|
+
systemPrompt,
|
|
3859
|
+
userPrompt,
|
|
3860
|
+
signal
|
|
3861
|
+
};
|
|
3862
|
+
if (slot !== void 0) out.slot = slot;
|
|
3863
|
+
if (toolCallLogPath !== void 0) out.toolCallLogPath = toolCallLogPath;
|
|
3864
|
+
if (sourcePolicy !== void 0) out.sourcePolicy = sourcePolicy;
|
|
3865
|
+
if (candidateOutputRecoveryPath !== void 0)
|
|
3866
|
+
out.candidateOutputRecoveryPath = candidateOutputRecoveryPath;
|
|
3867
|
+
return out;
|
|
3868
|
+
}
|
|
3869
|
+
__name(childOptions, "childOptions");
|
|
3870
|
+
function parseEvaluationAttempt(text, expectedValidationFindings) {
|
|
3871
|
+
let parsed;
|
|
3872
|
+
try {
|
|
3873
|
+
parsed = parseJsonText(text);
|
|
3874
|
+
} catch (error) {
|
|
3875
|
+
return {
|
|
3876
|
+
evaluation: void 0,
|
|
3877
|
+
errors: [`evaluation output must be JSON only: ${errorText2(error)}`]
|
|
3878
|
+
};
|
|
3879
|
+
}
|
|
3880
|
+
const result = validateFusionEvaluation(parsed);
|
|
3881
|
+
if (!result.ok) return { evaluation: void 0, errors: result.errors };
|
|
3882
|
+
if (expectedValidationFindings === void 0 && result.value.validation_accounting !== void 0) {
|
|
3883
|
+
return {
|
|
3884
|
+
evaluation: void 0,
|
|
3885
|
+
errors: ["evaluation.validation_accounting is permitted only for fusion_validate"]
|
|
3886
|
+
};
|
|
3887
|
+
}
|
|
3888
|
+
if (expectedValidationFindings !== void 0) {
|
|
3889
|
+
const accountingErrors = validateEvaluationAccountsForSourceFindings(
|
|
3890
|
+
result.value,
|
|
3891
|
+
expectedValidationFindings
|
|
3892
|
+
);
|
|
3893
|
+
if (accountingErrors.length > 0) return { evaluation: void 0, errors: accountingErrors };
|
|
3894
|
+
}
|
|
3895
|
+
return { evaluation: result.value, errors: [] };
|
|
3896
|
+
}
|
|
3897
|
+
__name(parseEvaluationAttempt, "parseEvaluationAttempt");
|
|
3898
|
+
function randomIndex(limit, randomBytes) {
|
|
3899
|
+
if (!Number.isInteger(limit) || limit <= 0 || limit > 4294967295) {
|
|
3900
|
+
throw new FusionError(`invalid random limit ${String(limit)}`, {
|
|
3901
|
+
code: "orchestration_failed",
|
|
3902
|
+
childCreated: false
|
|
3903
|
+
});
|
|
3904
|
+
}
|
|
3905
|
+
const range = 4294967296;
|
|
3906
|
+
const ceiling = range - range % limit;
|
|
3907
|
+
for (; ; ) {
|
|
3908
|
+
const bytes = randomBytes(4);
|
|
3909
|
+
if (bytes.length < 4) {
|
|
3910
|
+
throw new FusionError("random byte source returned too few bytes", {
|
|
3911
|
+
code: "orchestration_failed",
|
|
3912
|
+
childCreated: false
|
|
3913
|
+
});
|
|
3914
|
+
}
|
|
3915
|
+
const value = bytes.readUInt32BE(0);
|
|
3916
|
+
if (value < ceiling) return value % limit;
|
|
3917
|
+
}
|
|
3918
|
+
}
|
|
3919
|
+
__name(randomIndex, "randomIndex");
|
|
3920
|
+
function shuffledSlots(randomBytes) {
|
|
3921
|
+
const slots = [1, 2, 3];
|
|
3922
|
+
for (let i = slots.length - 1; i > 0; i--) {
|
|
3923
|
+
const j = randomIndex(i + 1, randomBytes);
|
|
3924
|
+
const left = slots[i];
|
|
3925
|
+
const right = slots[j];
|
|
3926
|
+
if (left === void 0 || right === void 0) {
|
|
3927
|
+
throw new FusionError("random slot shuffle failed", {
|
|
3928
|
+
code: "orchestration_failed",
|
|
3929
|
+
childCreated: false
|
|
3930
|
+
});
|
|
3931
|
+
}
|
|
3932
|
+
slots[i] = right;
|
|
3933
|
+
slots[j] = left;
|
|
3934
|
+
}
|
|
3935
|
+
return slots;
|
|
3936
|
+
}
|
|
3937
|
+
__name(shuffledSlots, "shuffledSlots");
|
|
3938
|
+
function candidateBySlot(results, slot) {
|
|
3939
|
+
const found = results.find((candidate) => candidate.slot === slot);
|
|
3940
|
+
if (found === void 0) {
|
|
3941
|
+
throw new FusionError(`candidate slot ${String(slot)} is missing`, {
|
|
3942
|
+
code: "orchestration_failed",
|
|
3943
|
+
childCreated: false
|
|
3944
|
+
});
|
|
3945
|
+
}
|
|
3946
|
+
return found.result;
|
|
3947
|
+
}
|
|
3948
|
+
__name(candidateBySlot, "candidateBySlot");
|
|
3949
|
+
function candidateModel(models, slot) {
|
|
3950
|
+
if (slot === 1) return models.candidates[0];
|
|
3951
|
+
if (slot === 2) return models.candidates[1];
|
|
3952
|
+
return models.candidates[2];
|
|
3953
|
+
}
|
|
3954
|
+
__name(candidateModel, "candidateModel");
|
|
3955
|
+
function anonymousCandidates(results, slots) {
|
|
3956
|
+
const firstSlot = slots[0];
|
|
3957
|
+
const secondSlot = slots[1];
|
|
3958
|
+
const thirdSlot = slots[2];
|
|
3959
|
+
if (firstSlot === void 0 || secondSlot === void 0 || thirdSlot === void 0) {
|
|
3960
|
+
throw new FusionError("anonymous candidate shuffle produced too few slots", {
|
|
3961
|
+
code: "orchestration_failed",
|
|
3962
|
+
childCreated: false
|
|
3963
|
+
});
|
|
3964
|
+
}
|
|
3965
|
+
const first = candidateBySlot(results, firstSlot);
|
|
3966
|
+
const second = candidateBySlot(results, secondSlot);
|
|
3967
|
+
const third = candidateBySlot(results, thirdSlot);
|
|
3968
|
+
return {
|
|
3969
|
+
map: { A: firstSlot, B: secondSlot, C: thirdSlot },
|
|
3970
|
+
candidates: [
|
|
3971
|
+
{ candidate_id: "A", response: first.text },
|
|
3972
|
+
{ candidate_id: "B", response: second.text },
|
|
3973
|
+
{ candidate_id: "C", response: third.text }
|
|
3974
|
+
]
|
|
3975
|
+
};
|
|
3976
|
+
}
|
|
3977
|
+
__name(anonymousCandidates, "anonymousCandidates");
|
|
3978
|
+
function sha256Text(value) {
|
|
3979
|
+
return createHash4("sha256").update(value, "utf8").digest("hex");
|
|
3980
|
+
}
|
|
3981
|
+
__name(sha256Text, "sha256Text");
|
|
3982
|
+
function boundedContractError(error) {
|
|
3983
|
+
const value = errorText2(error);
|
|
3984
|
+
return value.length <= 1e3 ? value : `${value.slice(0, 999)}\u2026`;
|
|
3985
|
+
}
|
|
3986
|
+
__name(boundedContractError, "boundedContractError");
|
|
3987
|
+
async function prepareValidationSourceData(candidates, anonymousMap, store) {
|
|
3988
|
+
const prepared = candidates.map((candidate) => ({ ...candidate }));
|
|
3989
|
+
const findings = [];
|
|
3990
|
+
const verified = [];
|
|
3991
|
+
const limitations = [];
|
|
3992
|
+
let normalizationCount = 0;
|
|
3993
|
+
const failures = [];
|
|
3994
|
+
for (const candidate of prepared) {
|
|
3995
|
+
try {
|
|
3996
|
+
const report = parseFusionValidationCandidateReport(
|
|
3997
|
+
candidate.response,
|
|
3998
|
+
candidate.candidate_id
|
|
3999
|
+
);
|
|
4000
|
+
findings.push(...report.findings);
|
|
4001
|
+
verified.push(...report.verified);
|
|
4002
|
+
limitations.push(...report.limitations);
|
|
4003
|
+
continue;
|
|
4004
|
+
} catch (strictError) {
|
|
4005
|
+
try {
|
|
4006
|
+
const recovered = recoverFencedFusionValidationCandidateReport(
|
|
4007
|
+
candidate.response,
|
|
4008
|
+
candidate.candidate_id
|
|
4009
|
+
);
|
|
4010
|
+
if (recovered === void 0) throw strictError;
|
|
4011
|
+
await store.recordValidationCandidateContractEvent({
|
|
4012
|
+
candidateId: candidate.candidate_id,
|
|
4013
|
+
slot: anonymousMap[candidate.candidate_id],
|
|
4014
|
+
status: "normalized",
|
|
4015
|
+
detail: {
|
|
4016
|
+
normalization: recovered.normalization,
|
|
4017
|
+
original_sha256: sha256Text(candidate.response),
|
|
4018
|
+
forwarded_sha256: sha256Text(recovered.response),
|
|
4019
|
+
warning: "Candidate output violated the bare-JSON contract; a single complete JSON fence was removed and recorded."
|
|
4020
|
+
}
|
|
4021
|
+
});
|
|
4022
|
+
candidate.response = recovered.response;
|
|
4023
|
+
findings.push(...recovered.report.findings);
|
|
4024
|
+
verified.push(...recovered.report.verified);
|
|
4025
|
+
limitations.push(...recovered.report.limitations);
|
|
4026
|
+
normalizationCount += 1;
|
|
4027
|
+
continue;
|
|
4028
|
+
} catch (recoveryError) {
|
|
4029
|
+
failures.push({
|
|
4030
|
+
candidate,
|
|
4031
|
+
error: boundedContractError(recoveryError === strictError ? strictError : recoveryError)
|
|
4032
|
+
});
|
|
4033
|
+
}
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
4036
|
+
if (normalizationCount > 0) {
|
|
4037
|
+
limitations.push(
|
|
4038
|
+
`${String(normalizationCount)} validation report${normalizationCount === 1 ? "" : "s"} required audited removal of a Markdown JSON wrapper; JSON content was unchanged.`
|
|
4039
|
+
);
|
|
4040
|
+
}
|
|
4041
|
+
for (const failure2 of failures) {
|
|
4042
|
+
await store.recordValidationCandidateContractEvent({
|
|
4043
|
+
candidateId: failure2.candidate.candidate_id,
|
|
4044
|
+
slot: anonymousMap[failure2.candidate.candidate_id],
|
|
4045
|
+
status: "dropped",
|
|
4046
|
+
detail: {
|
|
4047
|
+
response_sha256: sha256Text(failure2.candidate.response),
|
|
4048
|
+
error: failure2.error,
|
|
4049
|
+
warning: "Candidate output could not be parsed under the strict or fenced-JSON contract."
|
|
4050
|
+
}
|
|
4051
|
+
});
|
|
4052
|
+
}
|
|
4053
|
+
if (failures.length > 1) {
|
|
4054
|
+
throw new FusionError(
|
|
4055
|
+
`fusion_validate cannot continue: ${String(failures.length)} of 3 candidate reports violated the structured-output contract`,
|
|
4056
|
+
{ code: "evaluation_invalid", stage: "candidate" }
|
|
4057
|
+
);
|
|
4058
|
+
}
|
|
4059
|
+
const failure = failures[0];
|
|
4060
|
+
if (failure !== void 0) {
|
|
4061
|
+
const synthetic = canonicalJson({
|
|
4062
|
+
schema_version: FUSION_VALIDATE_CANDIDATE_SCHEMA_VERSION,
|
|
4063
|
+
findings: [],
|
|
4064
|
+
verified: [],
|
|
4065
|
+
limitations: [
|
|
4066
|
+
"This validation report could not be parsed after strict contract checks; no findings or verification claims from it were included."
|
|
4067
|
+
]
|
|
4068
|
+
});
|
|
4069
|
+
failure.candidate.response = synthetic;
|
|
4070
|
+
const report = parseFusionValidationCandidateReport(synthetic, failure.candidate.candidate_id);
|
|
4071
|
+
limitations.push(...report.limitations);
|
|
4072
|
+
}
|
|
4073
|
+
return { candidates: prepared, findings, verified, limitations };
|
|
4074
|
+
}
|
|
4075
|
+
__name(prepareValidationSourceData, "prepareValidationSourceData");
|
|
4076
|
+
function validateEvaluationAccountsForSourceFindings(evaluation, sourceFindings) {
|
|
4077
|
+
const errors = [];
|
|
4078
|
+
const accounting = evaluation.validation_accounting;
|
|
4079
|
+
if (accounting === void 0) {
|
|
4080
|
+
return ["validation evaluator output must include validation_accounting"];
|
|
4081
|
+
}
|
|
4082
|
+
const expected = sourceFindings.map((finding) => canonicalJson(finding)).sort();
|
|
4083
|
+
const actual = accounting.findings.map((finding) => canonicalJson(finding)).sort();
|
|
4084
|
+
if (expected.length !== actual.length || expected.some((value, index) => value !== actual[index])) {
|
|
4085
|
+
errors.push(
|
|
4086
|
+
"validation evaluator validation_accounting.findings must exactly equal host-assigned source findings"
|
|
4087
|
+
);
|
|
4088
|
+
}
|
|
4089
|
+
errors.push(...validateFusionFindingAccounting(accounting));
|
|
4090
|
+
return errors;
|
|
4091
|
+
}
|
|
4092
|
+
__name(validateEvaluationAccountsForSourceFindings, "validateEvaluationAccountsForSourceFindings");
|
|
4093
|
+
function resolveRunProfile(input) {
|
|
4094
|
+
if (input.profile !== void 0) return fusionWorkflowProfile(input.profile.id);
|
|
4095
|
+
const workflow = input.canonicalInput.workflow;
|
|
4096
|
+
const contextKind = input.canonicalInput.context?.kind;
|
|
4097
|
+
if (workflow !== void 0 && workflow !== "reason") {
|
|
4098
|
+
throw new FusionError(`fusion workflow profile is required for ${workflow} runs`, {
|
|
4099
|
+
code: "orchestration_failed",
|
|
4100
|
+
childCreated: false
|
|
4101
|
+
});
|
|
4102
|
+
}
|
|
4103
|
+
if (contextKind === "clean_task") {
|
|
4104
|
+
throw new FusionError("fusion workflow profile is required for clean-task runs", {
|
|
4105
|
+
code: "orchestration_failed",
|
|
4106
|
+
childCreated: false
|
|
4107
|
+
});
|
|
4108
|
+
}
|
|
4109
|
+
return fusionWorkflowProfile("reason");
|
|
4110
|
+
}
|
|
4111
|
+
__name(resolveRunProfile, "resolveRunProfile");
|
|
4112
|
+
var FusionOrchestrator = class {
|
|
4113
|
+
static {
|
|
4114
|
+
__name(this, "FusionOrchestrator");
|
|
4115
|
+
}
|
|
4116
|
+
childRunner;
|
|
4117
|
+
randomBytes;
|
|
4118
|
+
now;
|
|
4119
|
+
createArtifactStore;
|
|
4120
|
+
constructor(options = {}) {
|
|
4121
|
+
this.childRunner = options.childRunner ?? runPiChild;
|
|
4122
|
+
this.randomBytes = options.randomBytes ?? nodeRandomBytes;
|
|
4123
|
+
this.now = options.now;
|
|
4124
|
+
this.createArtifactStore = options.createArtifactStore ?? FusionArtifactStore.create;
|
|
4125
|
+
}
|
|
4126
|
+
async run(input) {
|
|
4127
|
+
if (input.canonicalInput.schema_version !== FUSION_INPUT_SCHEMA_VERSION) {
|
|
4128
|
+
throw new FusionError("fusion orchestrator accepts only v5 canonical input", {
|
|
4129
|
+
code: "orchestration_failed",
|
|
4130
|
+
childCreated: false
|
|
4131
|
+
});
|
|
4132
|
+
}
|
|
4133
|
+
const profile = resolveRunProfile(input);
|
|
4134
|
+
const inputWorkflow = input.canonicalInput.workflow ?? profile.id;
|
|
4135
|
+
const inputContextKind = input.canonicalInput.context?.kind ?? "session_projection";
|
|
4136
|
+
if (inputWorkflow !== profile.id || inputContextKind !== profile.contextKind) {
|
|
4137
|
+
throw new FusionError(
|
|
4138
|
+
`fusion workflow profile ${profile.id} is incompatible with canonical input workflow=${String(inputWorkflow)} context=${String(inputContextKind)}`,
|
|
4139
|
+
{ code: "orchestration_failed", childCreated: false }
|
|
4140
|
+
);
|
|
4141
|
+
}
|
|
4142
|
+
if (profile.contextKind === "clean_task" && !isStrictCleanCanonicalInput(input.canonicalInput)) {
|
|
4143
|
+
throw new FusionError(
|
|
4144
|
+
"clean-task fusion input must not carry parent context fields and must match the strict clean canonical shape",
|
|
4145
|
+
{
|
|
4146
|
+
code: "orchestration_failed",
|
|
4147
|
+
childCreated: false
|
|
4148
|
+
}
|
|
4149
|
+
);
|
|
4150
|
+
}
|
|
4151
|
+
const candidateCapability = assertWorkflowCapability(profile, input.candidateCapability);
|
|
4152
|
+
const storeOptions = {
|
|
4153
|
+
cwd: input.cwd,
|
|
4154
|
+
profile,
|
|
4155
|
+
source: input.source,
|
|
4156
|
+
config: input.config,
|
|
4157
|
+
models: input.models,
|
|
4158
|
+
capabilities: {
|
|
4159
|
+
candidate: candidateCapability,
|
|
4160
|
+
evaluation: FUSION_NO_TOOLS_CAPABILITY,
|
|
4161
|
+
merge: FUSION_NO_TOOLS_CAPABILITY
|
|
4162
|
+
}
|
|
4163
|
+
};
|
|
4164
|
+
if (input.sessionId !== void 0) storeOptions.sessionId = input.sessionId;
|
|
4165
|
+
if (this.now !== void 0) storeOptions.now = this.now;
|
|
4166
|
+
let serializedParsed;
|
|
4167
|
+
try {
|
|
4168
|
+
serializedParsed = parseJsonText(input.canonicalInputSerialized);
|
|
4169
|
+
} catch (error) {
|
|
4170
|
+
throw new FusionError(
|
|
4171
|
+
`fusion canonical input artifact is not valid JSON: ${errorText2(error)}`,
|
|
4172
|
+
{
|
|
4173
|
+
code: "orchestration_failed",
|
|
4174
|
+
childCreated: false
|
|
4175
|
+
}
|
|
4176
|
+
);
|
|
4177
|
+
}
|
|
4178
|
+
if (canonicalJson(serializedParsed) !== canonicalJson(input.canonicalInput)) {
|
|
4179
|
+
throw new FusionError(
|
|
4180
|
+
"fusion canonical input serialized bytes do not match canonical input object",
|
|
4181
|
+
{
|
|
4182
|
+
code: "orchestration_failed",
|
|
4183
|
+
childCreated: false
|
|
4184
|
+
}
|
|
4185
|
+
);
|
|
4186
|
+
}
|
|
4187
|
+
const store = await this.createArtifactStore(storeOptions);
|
|
4188
|
+
input.onProgress?.({ type: "state", state: "initializing" });
|
|
4189
|
+
const usage = createEmptyFusionUsage();
|
|
4190
|
+
const calibrationWarnings = [];
|
|
4191
|
+
try {
|
|
4192
|
+
await store.writeCanonicalInput(input.canonicalInputSerialized);
|
|
4193
|
+
if (inputContextKind === "session_projection") {
|
|
4194
|
+
if (input.contextLedger === void 0) {
|
|
4195
|
+
throw new FusionError(
|
|
4196
|
+
"session-projection fusion input requires an omission ledger artifact",
|
|
4197
|
+
{
|
|
4198
|
+
code: "orchestration_failed",
|
|
4199
|
+
childCreated: false
|
|
4200
|
+
}
|
|
4201
|
+
);
|
|
4202
|
+
}
|
|
4203
|
+
await store.writeContextLedger(input.contextLedger);
|
|
4204
|
+
} else if (input.contextLedger !== void 0) {
|
|
4205
|
+
throw new FusionError("clean-task fusion input must not carry a parent omission ledger", {
|
|
4206
|
+
code: "orchestration_failed",
|
|
4207
|
+
childCreated: false
|
|
4208
|
+
});
|
|
4209
|
+
}
|
|
4210
|
+
if (profile.id === "research") {
|
|
4211
|
+
const cleanContext = input.canonicalInput.context;
|
|
4212
|
+
if (cleanContext?.kind !== "clean_task") {
|
|
4213
|
+
throw new FusionError("research workflow requires a clean-task canonical input", {
|
|
4214
|
+
code: "orchestration_failed",
|
|
4215
|
+
childCreated: false
|
|
4216
|
+
});
|
|
4217
|
+
}
|
|
4218
|
+
const policy = buildFusionSourcePolicy(input.cwd, cleanContext.declared_sources);
|
|
4219
|
+
await store.writeSourcePolicy(sourcePolicyCanonicalBytes(policy));
|
|
4220
|
+
}
|
|
4221
|
+
const budget = new FusionBudget(
|
|
4222
|
+
input.models,
|
|
4223
|
+
input.canonicalInput.context?.policy_id ?? "fusion-session-projection-v1",
|
|
4224
|
+
candidateCapability,
|
|
4225
|
+
profile
|
|
4226
|
+
);
|
|
4227
|
+
const budgetPlan = budget.plan(input.canonicalInput);
|
|
4228
|
+
await store.writeBudgetPlan(budgetPlan);
|
|
4229
|
+
budget.assertPlanFits(budgetPlan, store.artifactDir);
|
|
4230
|
+
if (budgetPlan.warnings.length > 0) {
|
|
4231
|
+
input.onProgress?.({
|
|
4232
|
+
type: "budget_warning",
|
|
4233
|
+
warnings: budgetPlan.warnings,
|
|
4234
|
+
error: "fusion budget utilization warning"
|
|
4235
|
+
});
|
|
4236
|
+
}
|
|
4237
|
+
await input.onReady?.({
|
|
4238
|
+
runId: store.runId,
|
|
4239
|
+
artifactDir: store.artifactDir,
|
|
4240
|
+
artifactDirAbs: store.artifactDirAbs
|
|
4241
|
+
});
|
|
4242
|
+
if (input.signal?.aborted === true) {
|
|
4243
|
+
throw new FusionError("fusion run cancelled before launch", {
|
|
4244
|
+
code: "child_cancelled",
|
|
4245
|
+
childCreated: false
|
|
4246
|
+
});
|
|
4247
|
+
}
|
|
4248
|
+
await store.transition("candidates_running");
|
|
4249
|
+
input.onProgress?.({ type: "state", state: "candidates_running" });
|
|
4250
|
+
const candidateResults = await this.runCandidates(
|
|
4251
|
+
input,
|
|
4252
|
+
store,
|
|
4253
|
+
usage,
|
|
4254
|
+
budget,
|
|
4255
|
+
calibrationWarnings,
|
|
4256
|
+
profile,
|
|
4257
|
+
candidateCapability
|
|
4258
|
+
);
|
|
4259
|
+
await store.transition("candidates_complete");
|
|
4260
|
+
input.onProgress?.({ type: "state", state: "candidates_complete" });
|
|
4261
|
+
const shuffled = anonymousCandidates(candidateResults, shuffledSlots(this.randomBytes));
|
|
4262
|
+
await store.setAnonymousMap(shuffled.map);
|
|
4263
|
+
const validationData = profile.id === "validate" ? await prepareValidationSourceData(shuffled.candidates, shuffled.map, store) : void 0;
|
|
4264
|
+
const evaluationCandidates = validationData?.candidates ?? shuffled.candidates;
|
|
4265
|
+
const blindInput = buildBlindEvaluationInput(
|
|
4266
|
+
input.canonicalInput,
|
|
4267
|
+
evaluationCandidates,
|
|
4268
|
+
validationData?.findings
|
|
4269
|
+
);
|
|
4270
|
+
await store.writeBlindCandidates(buildEvaluationPrompt(blindInput));
|
|
4271
|
+
await store.transition("evaluating");
|
|
4272
|
+
input.onProgress?.({ type: "state", state: "evaluating" });
|
|
4273
|
+
const evaluation = await this.runEvaluation(
|
|
4274
|
+
input,
|
|
4275
|
+
store,
|
|
4276
|
+
usage,
|
|
4277
|
+
blindInput,
|
|
4278
|
+
budget,
|
|
4279
|
+
calibrationWarnings,
|
|
4280
|
+
profile,
|
|
4281
|
+
validationData?.findings
|
|
4282
|
+
);
|
|
4283
|
+
await store.writeEvaluationJson(evaluation);
|
|
4284
|
+
await store.transition("evaluation_complete");
|
|
4285
|
+
input.onProgress?.({ type: "state", state: "evaluation_complete" });
|
|
4286
|
+
await store.transition("merging");
|
|
4287
|
+
input.onProgress?.({ type: "state", state: "merging" });
|
|
4288
|
+
const mergeInput = buildMergeInput(input.canonicalInput, evaluationCandidates, evaluation);
|
|
4289
|
+
const mergePrompt = buildMergePrompt(mergeInput);
|
|
4290
|
+
budget.assertStagePrompt("merge", profile.mergerSystemPrompt, mergePrompt);
|
|
4291
|
+
input.onProgress?.({ type: "merge_started" });
|
|
4292
|
+
const merged = await this.runChildWithRetry(
|
|
4293
|
+
input,
|
|
4294
|
+
store,
|
|
4295
|
+
usage,
|
|
4296
|
+
input.models.merger,
|
|
4297
|
+
"merge",
|
|
4298
|
+
profile.mergerSystemPrompt,
|
|
4299
|
+
mergePrompt,
|
|
4300
|
+
input.signal ?? new AbortController().signal,
|
|
4301
|
+
FUSION_NO_TOOLS_CAPABILITY,
|
|
4302
|
+
void 0,
|
|
4303
|
+
"md"
|
|
4304
|
+
);
|
|
4305
|
+
addFusionUsage(usage, merged.usage);
|
|
4306
|
+
await store.recordChildAttempt({
|
|
4307
|
+
result: merged,
|
|
4308
|
+
systemPrompt: profile.mergerSystemPrompt,
|
|
4309
|
+
prompt: mergePrompt,
|
|
4310
|
+
responseKind: "md"
|
|
4311
|
+
});
|
|
4312
|
+
await this.recordCalibrationObservation(
|
|
4313
|
+
input,
|
|
4314
|
+
store,
|
|
4315
|
+
budget,
|
|
4316
|
+
calibrationWarnings,
|
|
4317
|
+
"merge",
|
|
4318
|
+
profile.mergerSystemPrompt,
|
|
4319
|
+
mergePrompt,
|
|
4320
|
+
merged
|
|
4321
|
+
);
|
|
4322
|
+
assertChildOutputWithinContract("merge", merged.text);
|
|
4323
|
+
let finalMergedText = merged.text;
|
|
4324
|
+
if (profile.id === "validate") {
|
|
4325
|
+
const accounting = evaluation.validation_accounting;
|
|
4326
|
+
if (accounting === void 0) {
|
|
4327
|
+
throw new FusionError(
|
|
4328
|
+
"fusion_validate evaluation completed without validation accounting",
|
|
4329
|
+
{
|
|
4330
|
+
code: "evaluation_invalid",
|
|
4331
|
+
stage: "merge"
|
|
4332
|
+
}
|
|
4333
|
+
);
|
|
4334
|
+
}
|
|
4335
|
+
finalMergedText = renderValidatedFusionValidationReport(accounting, validationData);
|
|
4336
|
+
}
|
|
4337
|
+
if (finalMergedText !== merged.text)
|
|
4338
|
+
assertChildOutputWithinContract("merge", finalMergedText);
|
|
4339
|
+
const mergedRef = await store.writeMerged(finalMergedText);
|
|
4340
|
+
await store.setUsage(usage);
|
|
4341
|
+
const details = {
|
|
4342
|
+
schema_version: FUSION_RESULT_SCHEMA_VERSION,
|
|
4343
|
+
run_id: store.runId,
|
|
4344
|
+
workflow: profile.id,
|
|
4345
|
+
source: input.source,
|
|
4346
|
+
status: "completed",
|
|
4347
|
+
artifact_dir: store.artifactDir,
|
|
4348
|
+
context: {
|
|
4349
|
+
kind: inputContextKind,
|
|
4350
|
+
policy_id: input.canonicalInput.context?.policy_id ?? "fusion-session-projection-v1"
|
|
4351
|
+
},
|
|
4352
|
+
tool_policy: {
|
|
4353
|
+
candidate_tools: profile.candidateTools,
|
|
4354
|
+
evaluation_tools: [],
|
|
4355
|
+
merge_tools: []
|
|
4356
|
+
},
|
|
4357
|
+
models: store.snapshot().models,
|
|
4358
|
+
evaluator_attempts: store.snapshot().attempts.filter((attempt) => attempt.stage === "evaluation").length,
|
|
4359
|
+
usage,
|
|
4360
|
+
budget: {
|
|
4361
|
+
policy_id: FUSION_BUDGET_POLICY.id,
|
|
4362
|
+
calibration_version: budgetPlan.policy.calibration_version,
|
|
4363
|
+
route_table: budget.routes,
|
|
4364
|
+
rate_sources: budget.resultRateSources,
|
|
4365
|
+
unknown_provider_warnings: budget.unknownProviderWarnings,
|
|
4366
|
+
calibration_warnings: calibrationWarnings
|
|
4367
|
+
}
|
|
4368
|
+
};
|
|
4369
|
+
await store.writeCommittedResult(mergedRef, details);
|
|
4370
|
+
await store.transition("completed");
|
|
4371
|
+
input.onProgress?.({ type: "completed", runId: store.runId, artifactDir: store.artifactDir });
|
|
4372
|
+
return { mergedText: finalMergedText, details };
|
|
4373
|
+
} catch (error) {
|
|
4374
|
+
const cancelled = input.signal?.aborted === true || error instanceof FusionError && error.code === "child_cancelled";
|
|
4375
|
+
let terminalError;
|
|
4376
|
+
try {
|
|
4377
|
+
await store.setUsage(usage);
|
|
4378
|
+
terminalError = withRunProgress(
|
|
4379
|
+
error,
|
|
4380
|
+
store.artifactDir,
|
|
4381
|
+
buildFusionRunProgress(store.snapshot())
|
|
4382
|
+
);
|
|
4383
|
+
const terminalState = cancelled ? "cancelled" : "failed";
|
|
4384
|
+
await store.writeError(terminalState, terminalError.message);
|
|
4385
|
+
try {
|
|
4386
|
+
const terminalManifest = store.snapshot();
|
|
4387
|
+
await store.writeFailureSummary(
|
|
4388
|
+
buildFusionFailureSummary({
|
|
4389
|
+
manifest: terminalManifest,
|
|
4390
|
+
terminalError,
|
|
4391
|
+
progress: buildFusionRunProgress(terminalManifest),
|
|
4392
|
+
terminalState,
|
|
4393
|
+
createdAt: terminalManifest.updated_at
|
|
4394
|
+
})
|
|
4395
|
+
);
|
|
4396
|
+
} catch (summaryError) {
|
|
4397
|
+
terminalError = withSummaryUnavailableNote(terminalError, summaryError);
|
|
4398
|
+
}
|
|
4399
|
+
} catch (artifactError) {
|
|
4400
|
+
throw withTerminalArtifactFailure(error, store.artifactDir, artifactError);
|
|
4401
|
+
}
|
|
4402
|
+
if (cancelled) {
|
|
4403
|
+
input.onProgress?.({
|
|
4404
|
+
type: "cancelled",
|
|
4405
|
+
runId: store.runId,
|
|
4406
|
+
artifactDir: store.artifactDir,
|
|
4407
|
+
reason: terminalError.message
|
|
4408
|
+
});
|
|
4409
|
+
} else {
|
|
4410
|
+
input.onProgress?.({
|
|
4411
|
+
type: "failed",
|
|
4412
|
+
runId: store.runId,
|
|
4413
|
+
artifactDir: store.artifactDir,
|
|
4414
|
+
error: terminalError.message
|
|
4415
|
+
});
|
|
4416
|
+
}
|
|
4417
|
+
throw terminalError;
|
|
4418
|
+
}
|
|
4419
|
+
}
|
|
4420
|
+
async runCandidates(input, store, usage, budget, calibrationWarnings, profile, candidateCapability) {
|
|
4421
|
+
const controller = new AbortController();
|
|
4422
|
+
const abortListener = /* @__PURE__ */ __name(() => controller.abort(), "abortListener");
|
|
4423
|
+
input.signal?.addEventListener("abort", abortListener, { once: true });
|
|
4424
|
+
if (input.signal?.aborted) controller.abort();
|
|
4425
|
+
const systemPrompt = profile.candidateSystemPrompt(candidateCapability);
|
|
4426
|
+
const prompt = buildCandidatePrompt(input.canonicalInput);
|
|
4427
|
+
for (const slot of [1, 2, 3]) {
|
|
4428
|
+
budget.assertStagePrompt("candidate", systemPrompt, prompt, slot);
|
|
4429
|
+
}
|
|
4430
|
+
let primaryError;
|
|
4431
|
+
let completed = 0;
|
|
4432
|
+
try {
|
|
4433
|
+
if (controller.signal.aborted) {
|
|
4434
|
+
throw new FusionError("fusion candidate wave cancelled before launch", {
|
|
4435
|
+
code: "child_cancelled",
|
|
4436
|
+
stage: "candidate",
|
|
4437
|
+
childCreated: false
|
|
4438
|
+
});
|
|
4439
|
+
}
|
|
4440
|
+
const tasks = [1, 2, 3].map((slot) => {
|
|
4441
|
+
const model = candidateModel(input.models, slot);
|
|
4442
|
+
const task = this.runChildWithRetry(
|
|
4443
|
+
input,
|
|
4444
|
+
store,
|
|
4445
|
+
usage,
|
|
4446
|
+
model,
|
|
4447
|
+
"candidate",
|
|
4448
|
+
systemPrompt,
|
|
4449
|
+
prompt,
|
|
4450
|
+
controller.signal,
|
|
4451
|
+
candidateCapability,
|
|
4452
|
+
slot,
|
|
4453
|
+
profile.id === "validate" ? "txt" : "md"
|
|
4454
|
+
).then(async (result) => {
|
|
4455
|
+
await store.recordChildAttempt({
|
|
4456
|
+
result,
|
|
4457
|
+
systemPrompt,
|
|
4458
|
+
prompt,
|
|
4459
|
+
responseKind: profile.id === "validate" ? "txt" : "md"
|
|
4460
|
+
});
|
|
4461
|
+
await this.recordCalibrationObservation(
|
|
4462
|
+
input,
|
|
4463
|
+
store,
|
|
4464
|
+
budget,
|
|
4465
|
+
calibrationWarnings,
|
|
4466
|
+
"candidate",
|
|
4467
|
+
systemPrompt,
|
|
4468
|
+
prompt,
|
|
4469
|
+
result,
|
|
4470
|
+
slot
|
|
4471
|
+
);
|
|
4472
|
+
addFusionUsage(usage, result.usage);
|
|
4473
|
+
await store.setUsage(usage);
|
|
4474
|
+
assertChildOutputWithinContract("candidate", result.text);
|
|
4475
|
+
completed += 1;
|
|
4476
|
+
input.onProgress?.({ type: "candidate_completed", slot, completed, total: 3 });
|
|
4477
|
+
return { slot, result };
|
|
4478
|
+
});
|
|
4479
|
+
return task.catch((error) => {
|
|
4480
|
+
if (primaryError === void 0) {
|
|
4481
|
+
primaryError = error;
|
|
4482
|
+
controller.abort();
|
|
4483
|
+
}
|
|
4484
|
+
throw error;
|
|
4485
|
+
});
|
|
4486
|
+
});
|
|
4487
|
+
const settled = await Promise.allSettled(tasks);
|
|
4488
|
+
if (primaryError !== void 0) throw primaryError;
|
|
4489
|
+
const results = [];
|
|
4490
|
+
for (const item of settled) {
|
|
4491
|
+
if (item.status === "fulfilled") results.push(item.value);
|
|
4492
|
+
else throw item.reason;
|
|
4493
|
+
}
|
|
4494
|
+
return results.sort((left, right) => left.slot - right.slot);
|
|
4495
|
+
} finally {
|
|
4496
|
+
input.signal?.removeEventListener("abort", abortListener);
|
|
4497
|
+
}
|
|
4498
|
+
}
|
|
4499
|
+
async runEvaluation(input, store, usage, blindInput, budget, calibrationWarnings, profile, expectedValidationFindings) {
|
|
4500
|
+
const firstPrompt = buildEvaluationPrompt(blindInput);
|
|
4501
|
+
budget.assertStagePrompt("evaluation", profile.evaluatorSystemPrompt, firstPrompt);
|
|
4502
|
+
const first = await this.runEvaluationAttempt(
|
|
4503
|
+
input,
|
|
4504
|
+
store,
|
|
4505
|
+
usage,
|
|
4506
|
+
budget,
|
|
4507
|
+
calibrationWarnings,
|
|
4508
|
+
firstPrompt,
|
|
4509
|
+
1,
|
|
4510
|
+
false,
|
|
4511
|
+
profile,
|
|
4512
|
+
expectedValidationFindings
|
|
4513
|
+
);
|
|
4514
|
+
if (first.evaluation !== void 0) return first.evaluation;
|
|
4515
|
+
const errors = boundedEvaluationErrors(first.errors);
|
|
4516
|
+
input.onProgress?.({ type: "evaluation_retry", errors });
|
|
4517
|
+
const repairPrompt = buildEvaluationRepairPrompt({
|
|
4518
|
+
schema_version: "pi-background-tasks.fusion-evaluation-repair-input.v1",
|
|
4519
|
+
original_blind_input: blindInput,
|
|
4520
|
+
invalid_output: first.result.text,
|
|
4521
|
+
validation_errors: errors
|
|
4522
|
+
});
|
|
4523
|
+
budget.assertStagePrompt(
|
|
4524
|
+
"evaluation_repair",
|
|
4525
|
+
profile.evaluationRepairSystemPrompt,
|
|
4526
|
+
repairPrompt
|
|
4527
|
+
);
|
|
4528
|
+
const second = await this.runEvaluationAttempt(
|
|
4529
|
+
input,
|
|
4530
|
+
store,
|
|
4531
|
+
usage,
|
|
4532
|
+
budget,
|
|
4533
|
+
calibrationWarnings,
|
|
4534
|
+
repairPrompt,
|
|
4535
|
+
2,
|
|
4536
|
+
true,
|
|
4537
|
+
profile,
|
|
4538
|
+
expectedValidationFindings
|
|
4539
|
+
);
|
|
4540
|
+
if (second.evaluation !== void 0) return second.evaluation;
|
|
4541
|
+
throw new FusionError(
|
|
4542
|
+
`evaluation schema repair failed: ${formatEvaluationErrors(second.errors)}`,
|
|
4543
|
+
{
|
|
4544
|
+
code: "evaluation_invalid",
|
|
4545
|
+
stage: "evaluation",
|
|
4546
|
+
attempt: 2
|
|
4547
|
+
}
|
|
4548
|
+
);
|
|
4549
|
+
}
|
|
4550
|
+
async runEvaluationAttempt(input, store, usage, budget, calibrationWarnings, prompt, attempt, repair, profile, expectedValidationFindings) {
|
|
4551
|
+
input.onProgress?.({ type: "evaluation_started", attempt, repair });
|
|
4552
|
+
const systemPrompt = repair ? profile.evaluationRepairSystemPrompt : profile.evaluatorSystemPrompt;
|
|
4553
|
+
const result = await this.runChildWithRetry(
|
|
4554
|
+
input,
|
|
4555
|
+
store,
|
|
4556
|
+
usage,
|
|
4557
|
+
input.models.evaluator,
|
|
4558
|
+
"evaluation",
|
|
4559
|
+
systemPrompt,
|
|
4560
|
+
prompt,
|
|
4561
|
+
input.signal ?? new AbortController().signal,
|
|
4562
|
+
FUSION_NO_TOOLS_CAPABILITY,
|
|
4563
|
+
void 0,
|
|
4564
|
+
"txt",
|
|
4565
|
+
attempt
|
|
4566
|
+
);
|
|
4567
|
+
addFusionUsage(usage, result.usage);
|
|
4568
|
+
await store.recordChildAttempt({ result, systemPrompt, prompt, responseKind: "txt" });
|
|
4569
|
+
await this.recordCalibrationObservation(
|
|
4570
|
+
input,
|
|
4571
|
+
store,
|
|
4572
|
+
budget,
|
|
4573
|
+
calibrationWarnings,
|
|
4574
|
+
"evaluation",
|
|
4575
|
+
systemPrompt,
|
|
4576
|
+
prompt,
|
|
4577
|
+
result
|
|
4578
|
+
);
|
|
4579
|
+
await store.setUsage(usage);
|
|
4580
|
+
assertChildOutputWithinContract("evaluation", result.text);
|
|
4581
|
+
const parsed = parseEvaluationAttempt(result.text, expectedValidationFindings);
|
|
4582
|
+
return { result, evaluation: parsed.evaluation, errors: parsed.errors };
|
|
4583
|
+
}
|
|
4584
|
+
async recordCalibrationObservation(input, store, budget, calibrationWarnings, stage, systemPrompt, userPrompt, result, slot) {
|
|
4585
|
+
const violation = budget.calibrationViolationForCompletedChild(
|
|
4586
|
+
stage,
|
|
4587
|
+
systemPrompt,
|
|
4588
|
+
userPrompt,
|
|
4589
|
+
result,
|
|
4590
|
+
slot
|
|
4591
|
+
);
|
|
4592
|
+
if (violation === void 0) return;
|
|
4593
|
+
calibrationWarnings.push(violation);
|
|
4594
|
+
let artifact = "calibration-violation artifact was not written";
|
|
4595
|
+
try {
|
|
4596
|
+
const ref = await store.recordCalibrationViolation({
|
|
4597
|
+
stage,
|
|
4598
|
+
attempt: result.attempt,
|
|
4599
|
+
violation,
|
|
4600
|
+
...slot === void 0 ? {} : { slot }
|
|
4601
|
+
});
|
|
4602
|
+
artifact = ref.path;
|
|
4603
|
+
} catch (error) {
|
|
4604
|
+
artifact = `calibration-violation artifact write failed: ${errorText2(error)}`;
|
|
4605
|
+
}
|
|
4606
|
+
input.onProgress?.({ type: "calibration_warning", warning: violation, artifact });
|
|
4607
|
+
}
|
|
4608
|
+
async runChildWithRetry(input, store, usage, model, stage, systemPrompt, userPrompt, signal, capability, slot, responseKind, fixedAttempt) {
|
|
4609
|
+
const logicalAttempt = fixedAttempt ?? 1;
|
|
4610
|
+
for (let launchTry = 1; launchTry <= 2; launchTry++) {
|
|
4611
|
+
if (stage === "candidate" && slot !== void 0) {
|
|
4612
|
+
input.onProgress?.({ type: "candidate_started", slot, attempt: logicalAttempt });
|
|
4613
|
+
}
|
|
4614
|
+
const toolCallLogPath = capability !== "reason" ? store.childToolCallLogPath(stage, slot, logicalAttempt) : void 0;
|
|
4615
|
+
const sourcePolicy = capability === "research" ? store.sourcePolicyLaunchReference() : void 0;
|
|
4616
|
+
const candidateOutputRecoveryPath = stage === "candidate" && slot !== void 0 ? store.childOutputRecoveryPath(slot, logicalAttempt, responseKind) : void 0;
|
|
4617
|
+
try {
|
|
4618
|
+
return await this.childRunner(
|
|
4619
|
+
childOptions(
|
|
4620
|
+
input,
|
|
4621
|
+
model,
|
|
4622
|
+
stage,
|
|
4623
|
+
logicalAttempt,
|
|
4624
|
+
capability,
|
|
4625
|
+
systemPrompt,
|
|
4626
|
+
userPrompt,
|
|
4627
|
+
signal,
|
|
4628
|
+
slot,
|
|
4629
|
+
toolCallLogPath,
|
|
4630
|
+
sourcePolicy,
|
|
4631
|
+
candidateOutputRecoveryPath
|
|
4632
|
+
)
|
|
4633
|
+
);
|
|
4634
|
+
} catch (error) {
|
|
4635
|
+
if (!signal.aborted && retryableSpawn(error, launchTry) && launchTry === 1) continue;
|
|
4636
|
+
addFailedChildUsage(usage, error);
|
|
4637
|
+
await store.recordFailedAttempt(
|
|
4638
|
+
recordFailureInput(
|
|
4639
|
+
error,
|
|
4640
|
+
stage,
|
|
4641
|
+
slot,
|
|
4642
|
+
logicalAttempt,
|
|
4643
|
+
systemPrompt,
|
|
4644
|
+
userPrompt,
|
|
4645
|
+
responseKind
|
|
4646
|
+
)
|
|
4647
|
+
);
|
|
4648
|
+
await store.setUsage(usage);
|
|
4649
|
+
throw error;
|
|
4650
|
+
}
|
|
4651
|
+
}
|
|
4652
|
+
const details = {
|
|
4653
|
+
code: "orchestration_failed",
|
|
4654
|
+
stage,
|
|
4655
|
+
childCreated: false
|
|
4656
|
+
};
|
|
4657
|
+
if (slot !== void 0) details.slot = slot;
|
|
4658
|
+
throw new FusionError(`${stage} child did not produce a result`, details);
|
|
4659
|
+
}
|
|
4660
|
+
};
|
|
4661
|
+
export {
|
|
4662
|
+
FusionOrchestrator,
|
|
4663
|
+
buildFusionRunProgress,
|
|
4664
|
+
summaryUnavailableNote
|
|
4665
|
+
};
|