@iowarp/clio-coder 0.3.8 → 0.3.9
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/CHANGELOG.md +49 -0
- package/README.md +7 -3
- package/dist/{acp-U67UHUK2.js → acp-7LOELQFP.js} +6 -6
- package/dist/{agents-YU6SGALZ.js → agents-FIBG2SHA.js} +27 -25
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-5ZPJOIVG.js → auth-OI4LIH2I.js} +11 -12
- package/dist/{builtins-C6JMZVV6.js → builtins-AD25UL3C.js} +5 -5
- package/dist/{chunk-5FR74PWO.js → chunk-2JDWVJND.js} +2 -2
- package/dist/chunk-3DPEIQKN.js +113 -0
- package/dist/{chunk-4SPRNWDE.js → chunk-3DUR4WUA.js} +15 -15
- package/dist/{chunk-VHN4MY6O.js → chunk-3MRC2YSQ.js} +2 -2
- package/dist/{chunk-5DHKRSMQ.js → chunk-3UUY7R3Z.js} +11 -7
- package/dist/{chunk-IGWKHNIQ.js → chunk-3V5AYSEQ.js} +8 -8
- package/dist/{chunk-FHJEP5SW.js → chunk-465CC7FK.js} +8 -5
- package/dist/{chunk-5WIGXA4T.js → chunk-47CMYGET.js} +111 -4
- package/dist/{chunk-A3WNZD3P.js → chunk-4H6ULJ3H.js} +67 -21
- package/dist/{chunk-TB5666IT.js → chunk-4LJX2PUC.js} +3 -3
- package/dist/{chunk-XWSF374K.js → chunk-56KB5IJP.js} +2 -2
- package/dist/{chunk-SPULKLCF.js → chunk-5DQRIYDZ.js} +2 -2
- package/dist/{chunk-2HEJ2F35.js → chunk-5HFBWUMU.js} +20 -8
- package/dist/{chunk-WNIJTQQK.js → chunk-5PVQ4SRS.js} +78 -6
- package/dist/{chunk-DYIM5TJT.js → chunk-5QKCQQ3E.js} +262 -6
- package/dist/{chunk-TYPGUK6W.js → chunk-5T7RBWN2.js} +111 -5
- package/dist/{chunk-IIZWH4XA.js → chunk-774ILSRL.js} +2 -2
- package/dist/chunk-7C6RYZGQ.js +391 -0
- package/dist/{chunk-TANS5ZJS.js → chunk-AD7Y7STJ.js} +3 -3
- package/dist/{chunk-RWSI4YD7.js → chunk-AEYBF3TB.js} +33 -12
- package/dist/{chunk-DGSYXYMX.js → chunk-AMKHQW3C.js} +2 -2
- package/dist/{chunk-VWZOAB7K.js → chunk-B5XRQOLB.js} +7 -7
- package/dist/{chunk-WXY7KU3G.js → chunk-BVDVID7E.js} +2 -2
- package/dist/{chunk-PMDBGQSJ.js → chunk-CA42X6KT.js} +2 -2
- package/dist/{chunk-HLE42MG7.js → chunk-D73KXYPF.js} +3 -3
- package/dist/{chunk-5Q2VVUKB.js → chunk-DG4M6ZUE.js} +3 -3
- package/dist/{chunk-ME6CCNFO.js → chunk-EBOC7MT3.js} +6 -6
- package/dist/{chunk-MXKJU4JB.js → chunk-ECUO3KDP.js} +48 -7
- package/dist/{chunk-26LEYJZH.js → chunk-FALJGAWU.js} +2 -2
- package/dist/{chunk-GWS3VEIW.js → chunk-FWDFM5ZU.js} +24 -3
- package/dist/{chunk-7RGZWPB6.js → chunk-GAYUJ7LE.js} +67 -13
- package/dist/{chunk-VCBR6CU7.js → chunk-HAY4ZE2P.js} +2 -2
- package/dist/{chunk-FBVTI2TJ.js → chunk-HCBCAYZU.js} +11 -130
- package/dist/{chunk-WSB3FPX7.js → chunk-HJB5IUKP.js} +32 -136
- package/dist/{chunk-J3YUBZWY.js → chunk-HKO36JWF.js} +33 -5
- package/dist/{chunk-E77JEWSD.js → chunk-HPCTNZM2.js} +6 -36
- package/dist/{chunk-FJ3H4MN5.js → chunk-IHKBWSXF.js} +2 -2
- package/dist/{chunk-NMPKI6XL.js → chunk-JEQQR47K.js} +37 -18
- package/dist/{chunk-3BINW3FP.js → chunk-KV2AOLDF.js} +24 -4
- package/dist/{chunk-YS5VLNH5.js → chunk-LXPJXFM5.js} +7 -7
- package/dist/{chunk-7RFXX52T.js → chunk-MIX5N5AC.js} +271 -41
- package/dist/{chunk-K4XHGFR5.js → chunk-MLOK6ZOS.js} +1297 -218
- package/dist/{chunk-ZNLWCMVZ.js → chunk-MV2VUEJC.js} +2 -2
- package/dist/{chunk-MVVUPGPW.js → chunk-MXHC5QYU.js} +6 -6
- package/dist/{chunk-5H3GB5BO.js → chunk-N3PBVRTZ.js} +4 -382
- package/dist/{chunk-2HFZQUHL.js → chunk-N5XKWMDW.js} +17 -7
- package/dist/{chunk-5C3AQNDW.js → chunk-NNNWO6F2.js} +124 -36
- package/dist/{chunk-GU2UIAFZ.js → chunk-NQ6UCCOD.js} +3 -3
- package/dist/{chunk-N22QMJKY.js → chunk-NZU6YDNV.js} +4 -4
- package/dist/{chunk-ZVJ5BLO2.js → chunk-O6I4CIEU.js} +151 -13
- package/dist/{chunk-XN3L4EYL.js → chunk-OEDBCISO.js} +2 -2
- package/dist/{chunk-VAWNZU7Z.js → chunk-P3JGPQFL.js} +2 -2
- package/dist/{chunk-IJ7RPIYJ.js → chunk-PNY46YEY.js} +20 -3
- package/dist/{chunk-U6MBIEMB.js → chunk-PZ4I4JE2.js} +56 -35
- package/dist/{chunk-GPIEI3LY.js → chunk-QQ7EKM72.js} +2 -2
- package/dist/{chunk-WLFILSD5.js → chunk-R7LNVMCS.js} +66 -28
- package/dist/{chunk-GOXNB3AO.js → chunk-RAPCMZL4.js} +75 -4
- package/dist/chunk-RKKLTLYB.js +45 -0
- package/dist/{chunk-TT36MB5S.js → chunk-RKRLDWD3.js} +3 -1
- package/dist/{chunk-TTHACPOM.js → chunk-S4COXYBG.js} +456 -18
- package/dist/{chunk-WWCZ5F23.js → chunk-T3Z6VAAF.js} +69 -10
- package/dist/{chunk-GN57SG4G.js → chunk-TD7UE2L5.js} +9 -7
- package/dist/{chunk-TLQJPP24.js → chunk-TEO2TLVN.js} +523 -322
- package/dist/{chunk-FCSXB6T2.js → chunk-UOSL25KY.js} +14 -2
- package/dist/{chunk-KTYTFRMB.js → chunk-VKBMFOYV.js} +17 -15
- package/dist/{chunk-PT7HYKEM.js → chunk-VO2LKSTM.js} +2 -2
- package/dist/{chunk-P43ETTHK.js → chunk-VPTUJU4P.js} +2 -2
- package/dist/{chunk-WEH5XRJQ.js → chunk-WIE7ZOSW.js} +2 -2
- package/dist/{chunk-EMYUUSFG.js → chunk-WXCJ7VME.js} +5 -5
- package/dist/{chunk-4DGYLA73.js → chunk-XDOQXGFO.js} +22 -7
- package/dist/{chunk-JOZYP4GM.js → chunk-YKOFT37S.js} +5 -5
- package/dist/chunk-YSEHGPCT.js +127 -0
- package/dist/{chunk-HFSBBKSQ.js → chunk-YW7UVM5V.js} +138 -3
- package/dist/cli/index.js +27 -27
- package/dist/{clio-QVTYJ57A.js → clio-LT5V7SSZ.js} +6 -6
- package/dist/{code-nav-FGGFIE7L.js → code-nav-LMW275PA.js} +4 -4
- package/dist/{config-LW5IJFQN.js → config-RXS5T3JT.js} +71 -43
- package/dist/{configure-7XIZCOU4.js → configure-2WYWSCSD.js} +14 -15
- package/dist/{context-Y6Y7QPR6.js → context-I3BTOTCS.js} +12 -12
- package/dist/{context-L3WL3X7K.js → context-MVOORGMF.js} +34 -33
- package/dist/{context-N52ZA626.js → context-PALKKQYL.js} +20 -20
- package/dist/{context-clear-MBQRLSDQ.js → context-clear-N2WOYZ2K.js} +34 -33
- package/dist/{context-index-HVMFQHK3.js → context-index-HNG3MOME.js} +2 -2
- package/dist/{context-working-set-GS6DSO7F.js → context-working-set-MIEVECVZ.js} +10 -11
- package/dist/{dispatch-runner-22ZCNOM3.js → dispatch-runner-VVA4SRRH.js} +34 -33
- package/dist/doctor-TWBWFK5V.js +165 -0
- package/dist/{eval-BEC2WHDA.js → eval-IJ5VEZDJ.js} +2016 -142
- package/dist/{evidence-REJUMSKM.js → evidence-L5APPXNV.js} +29 -28
- package/dist/{evolve-PY5ZBA5K.js → evolve-RGNKFJ52.js} +29 -28
- package/dist/{extensions-HVKU65YU.js → extensions-7WYWUX5A.js} +9 -3
- package/dist/{fleet-7WZEWRFA.js → fleet-6CNVBZZP.js} +87 -55
- package/dist/{fleet-commands-UVHWM76J.js → fleet-commands-L2SXSYEI.js} +6 -6
- package/dist/{fleet-graph-6ULH7PES.js → fleet-graph-2J3OOIPO.js} +14 -12
- package/dist/{fleet-preflight-J53T6CCE.js → fleet-preflight-CZRJ4JP5.js} +3 -4
- package/dist/{fleet-validate-72PC4SLA.js → fleet-validate-C5RI6DP7.js} +16 -15
- package/dist/{init-OG3TPGQG.js → init-VBN2ACVA.js} +50 -48
- package/dist/{library-CNTMPLRF.js → library-JHGUMLY2.js} +13 -11
- package/dist/{memory-6IS7F275.js → memory-K4OQIYWG.js} +31 -30
- package/dist/{models-ENRJDA5W.js → models-2NCZUWDD.js} +23 -22
- package/dist/{monitor-XLDVO7TN.js → monitor-MMVTJABD.js} +35 -34
- package/dist/{orchestrator-6KSPYRHA.js → orchestrator-ZKBPCHW6.js} +1627 -294
- package/dist/{reset-RZ4ER727.js → reset-DD5JGOY3.js} +3 -3
- package/dist/{run-Y2CNK5RU.js → run-QEGNX7FL.js} +56 -55
- package/dist/{share-A55GYP6Z.js → share-JKD3BQMW.js} +13 -11
- package/dist/{skills-ALC5J6AT.js → skills-LMQIKDOZ.js} +14 -12
- package/dist/{skills-eval-JPBEBYQU.js → skills-eval-I7X2774U.js} +34 -32
- package/dist/{steer-GGWFUJUD.js → steer-CF5TDANS.js} +3 -3
- package/dist/{support-MIETYA5E.js → support-I7LOJLIF.js} +4 -4
- package/dist/{targets-VGNXIR3S.js → targets-RUSR6B5Z.js} +58 -32
- package/dist/{terminal-lease-WOBR64YA.js → terminal-lease-QYVORFR4.js} +6 -4
- package/dist/{trace-PNCASAXC.js → trace-ODOQIVIW.js} +61 -6
- package/dist/{upgrade-FUSUAGHR.js → upgrade-XANW3FXB.js} +17 -16
- package/dist/{usage-N4MKVHKD.js → usage-4H7ZRXQT.js} +88 -44
- package/dist/{verifiers-YAWOJ3H2.js → verifiers-UZXNBZEB.js} +6 -6
- package/dist/{verify-LTDHYBGY.js → verify-BVKWTNDL.js} +5 -5
- package/dist/{wiki-generate-6M7GHTBJ.js → wiki-generate-MY7WV2QI.js} +49 -47
- package/dist/worker/entry.js +29 -33
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +1 -1
- package/docs/artifact-versions.md +6 -1
- package/docs/built-in-agents.md +1 -1
- package/docs/capacity-and-scheduling.md +23 -2
- package/docs/commands-and-modes.md +1 -1
- package/docs/configuration-and-targets.md +30 -3
- package/docs/context-engine.md +63 -4
- package/docs/documentation-coverage.md +3 -3
- package/docs/documentation-guide.md +1 -1
- package/docs/environment-variables.md +2 -0
- package/docs/eval-runner.md +262 -11
- package/docs/evals-internal.md +72 -2
- package/docs/evidence-and-memory.md +11 -10
- package/docs/evolution.md +1 -1
- package/docs/extensions-and-sharing.md +3 -1
- package/docs/fleet-dispatch.md +4 -4
- package/docs/installation-and-lifecycle.md +1 -1
- package/docs/middleware-and-components.md +1 -1
- package/docs/model-catalog.md +1 -1
- package/docs/observability.md +53 -2
- package/docs/proactive-memory.md +127 -14
- package/docs/prompt-envelope-and-tools.md +19 -1
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +19 -3
- package/docs/safety-model.md +1 -1
- package/docs/scientific-validation.md +1 -1
- package/docs/skills-marketplace.md +1 -1
- package/docs/tool-usage.md +1 -1
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +87 -0
- package/docs/tui-design.md +1 -1
- package/docs/worker-dispatch-mechanics.md +1 -1
- package/package.json +2 -1
- package/src/cli/agents.ts +1 -1
- package/src/cli/config-inspect.ts +33 -6
- package/src/cli/config.ts +1 -1
- package/src/cli/doctor-state-size.ts +82 -0
- package/src/cli/doctor.ts +3 -1
- package/src/cli/eval.ts +80 -16
- package/src/cli/extensions.ts +5 -1
- package/src/cli/fleet.ts +32 -3
- package/src/cli/targets.ts +44 -13
- package/src/cli/trace.ts +63 -4
- package/src/cli/usage.ts +63 -14
- package/src/core/bus-events.ts +29 -1
- package/src/core/cache-telemetry.ts +42 -0
- package/src/core/config.ts +18 -0
- package/src/core/defaults.ts +36 -6
- package/src/core/endpoint-key.ts +27 -0
- package/src/core/residency-target-key.ts +25 -0
- package/src/core/response-schema.ts +36 -2
- package/src/domains/config/classify.ts +3 -0
- package/src/domains/context/codewiki/coordinator.ts +12 -4
- package/src/domains/dispatch/admission.ts +40 -3
- package/src/domains/dispatch/capacity-lease.ts +98 -9
- package/src/domains/dispatch/contract.ts +11 -0
- package/src/domains/dispatch/execution-plan.ts +44 -4
- package/src/domains/dispatch/extension.ts +166 -42
- package/src/domains/dispatch/fleet-run.ts +23 -3
- package/src/domains/dispatch/heartbeat.ts +32 -8
- package/src/domains/dispatch/index.ts +3 -0
- package/src/domains/dispatch/orphan-recovery.ts +5 -0
- package/src/domains/dispatch/reservation-store.ts +116 -8
- package/src/domains/dispatch/state.ts +4 -0
- package/src/domains/dispatch/worker-spawn.ts +25 -11
- package/src/domains/dispatch/write-boundary-enforcer.ts +20 -3
- package/src/domains/dispatch/write-boundary.ts +62 -1
- package/src/domains/eval/artifacts/store.ts +62 -0
- package/src/domains/eval/compare/behavioral.ts +224 -0
- package/src/domains/eval/compare/compare.ts +355 -2
- package/src/domains/eval/compare/envelope.ts +128 -0
- package/src/domains/eval/compare/gates.ts +24 -6
- package/src/domains/eval/compare/thresholds.ts +30 -3
- package/src/domains/eval/execution-provenance.ts +240 -0
- package/src/domains/eval/metrics/aggregate.ts +136 -0
- package/src/domains/eval/metrics/call-ledger-stream.ts +112 -0
- package/src/domains/eval/metrics/tracked.ts +413 -0
- package/src/domains/eval/provenance.ts +117 -0
- package/src/domains/eval/reports/comparison.ts +128 -0
- package/src/domains/eval/reports/junit.ts +17 -3
- package/src/domains/eval/reports/markdown.ts +3 -3
- package/src/domains/eval/reports/text.ts +14 -0
- package/src/domains/eval/run-compare.ts +20 -0
- package/src/domains/eval/runners/clio-run.ts +127 -0
- package/src/domains/eval/runners/external-command.ts +28 -3
- package/src/domains/eval/schema/adapter.ts +111 -0
- package/src/domains/eval/schema/artifact.ts +20 -0
- package/src/domains/eval/schema/behavioral-metrics.ts +204 -0
- package/src/domains/eval/schema/behavioral.ts +520 -0
- package/src/domains/eval/schema/execution-envelope.ts +194 -0
- package/src/domains/eval/schema/serving.ts +74 -0
- package/src/domains/eval/schema/suite.ts +38 -8
- package/src/domains/eval/schema/validate.ts +58 -3
- package/src/domains/eval/schema/verdict.ts +237 -0
- package/src/domains/eval/suites/resolve.ts +2 -0
- package/src/domains/eval/suites/run.ts +264 -33
- package/src/domains/eval/verifiers/command.ts +2 -1
- package/src/domains/eval/workspaces/temp-copy.ts +145 -13
- package/src/domains/evidence/build.ts +2 -13
- package/src/domains/evidence/eval.ts +2 -12
- package/src/domains/evidence/findings-markdown.ts +33 -0
- package/src/domains/evidence/run-trust.ts +7 -113
- package/src/domains/evidence/trust-projection.ts +2 -2
- package/src/domains/extensions/compatibility.ts +285 -0
- package/src/domains/extensions/discovery.ts +38 -3
- package/src/domains/extensions/resources.ts +1 -1
- package/src/domains/extensions/state.ts +12 -3
- package/src/domains/extensions/types.ts +2 -0
- package/src/domains/lifecycle/doctor.ts +69 -1
- package/src/domains/memory/index.ts +14 -0
- package/src/domains/memory/task-bank-promotion.ts +64 -0
- package/src/domains/memory/task-memory-policy.ts +77 -8
- package/src/domains/memory/task-memory-spend.ts +131 -0
- package/src/domains/memory/task-memory-status.ts +7 -0
- package/src/domains/memory/task-memory-telemetry.ts +2 -0
- package/src/domains/middleware/index.ts +1 -0
- package/src/domains/middleware/memory-intervention.ts +69 -5
- package/src/domains/middleware/memory-step-endpoint.ts +71 -0
- package/src/domains/observability/background-memory-usage.ts +140 -0
- package/src/domains/observability/cost.ts +1 -1
- package/src/domains/observability/index.ts +7 -0
- package/src/domains/observability/out-of-turn-usage.ts +51 -2
- package/src/domains/observability/trace-store.ts +192 -2
- package/src/domains/prompts/compiler.ts +100 -13
- package/src/domains/providers/endpoint-capacity.ts +96 -0
- package/src/domains/providers/index.ts +10 -0
- package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +243 -1
- package/src/domains/providers/runtime-resolution.ts +8 -1
- package/src/domains/providers/runtimes/common/probe-helpers.ts +31 -9
- package/src/domains/providers/runtimes/local-native/llamacpp-anthropic.ts +1 -1
- package/src/domains/providers/runtimes/local-native/llamacpp-completion.ts +1 -1
- package/src/domains/providers/runtimes/local-native/llamacpp-embed.ts +1 -1
- package/src/domains/providers/runtimes/local-native/llamacpp-rerank.ts +1 -1
- package/src/domains/providers/runtimes/local-native/llamacpp.ts +4 -1
- package/src/domains/providers/runtimes/local-native/lmstudio.ts +4 -1
- package/src/domains/providers/runtimes/local-native/ollama-native.ts +6 -1
- package/src/domains/providers/types/capability-flags.ts +2 -0
- package/src/domains/providers/types/target-descriptor.ts +2 -0
- package/src/domains/resources/prompts/loader.ts +95 -33
- package/src/domains/safety/call-target.ts +52 -0
- package/src/domains/safety/run-effects.ts +35 -4
- package/src/domains/session/context-accounting.ts +52 -1
- package/src/domains/session/context-ledger.ts +37 -13
- package/src/domains/session/index.ts +6 -0
- package/src/domains/session/prompt-cache.ts +140 -0
- package/src/domains/session/prompt-manifest.ts +42 -0
- package/src/engine/acp/adapter.ts +18 -3
- package/src/engine/ai.ts +35 -0
- package/src/engine/apis/llamacpp-residency.ts +55 -3
- package/src/engine/apis/lmstudio.ts +25 -5
- package/src/engine/apis/ollama-native.ts +2 -1
- package/src/engine/apis/openai-completions.ts +80 -17
- package/src/engine/apis/residency-lock.ts +3 -1
- package/src/engine/apis/residency.ts +34 -1
- package/src/engine/provider-payload.ts +29 -1
- package/src/entry/orchestrator.ts +176 -30
- package/src/interactive/chat-loop-messages.ts +26 -7
- package/src/interactive/chat-loop.ts +318 -41
- package/src/interactive/chat-panel.ts +62 -8
- package/src/interactive/clio-editor.ts +45 -8
- package/src/interactive/context-activity.ts +5 -1
- package/src/interactive/context-meter.ts +1 -1
- package/src/interactive/context-overlay.ts +40 -10
- package/src/interactive/cost-overlay.ts +64 -6
- package/src/interactive/dispatch-board.ts +84 -12
- package/src/interactive/fleet-run-preview.ts +41 -15
- package/src/interactive/handoff-round.ts +41 -2
- package/src/interactive/interactive-application.ts +24 -1
- package/src/interactive/interactive-input-runtime.ts +8 -0
- package/src/interactive/interactive-presentation.ts +4 -0
- package/src/interactive/interactive-shell.ts +20 -17
- package/src/interactive/interactive-slash-runtime.ts +27 -4
- package/src/interactive/memory-overlay.ts +8 -0
- package/src/interactive/mutation-preview.ts +295 -0
- package/src/interactive/overlay-general-openers.ts +16 -0
- package/src/interactive/overlay-key-routing.ts +38 -0
- package/src/interactive/overlay-lifecycle.ts +38 -5
- package/src/interactive/overlay-permission-lifecycle.ts +22 -2
- package/src/interactive/overlay-session-lifecycle.ts +73 -9
- package/src/interactive/overlays/ask-user.ts +91 -19
- package/src/interactive/overlays/help-reference.ts +4 -0
- package/src/interactive/overlays/prompts.ts +11 -1
- package/src/interactive/overlays/settings.ts +35 -1
- package/src/interactive/permission-hint.ts +34 -2
- package/src/interactive/permission-overlay.ts +159 -9
- package/src/interactive/prewarm.ts +197 -0
- package/src/interactive/render-trace.ts +162 -15
- package/src/interactive/renderers/tool-execution.ts +4 -0
- package/src/interactive/side-question.ts +58 -1
- package/src/interactive/status/controller.ts +11 -0
- package/src/interactive/status/state-machine.ts +54 -2
- package/src/interactive/status/types.ts +7 -0
- package/src/interactive/terminal-lease.ts +2 -0
- package/src/interactive/turn-context.ts +299 -31
- package/src/interactive/turn-persistence.ts +14 -4
- package/src/interactive/turn-prewarm.ts +364 -0
- package/src/interactive/turn-queues.ts +7 -4
- package/src/interactive/turn-runtime.ts +8 -1
- package/src/interactive/turn-state.ts +23 -0
- package/src/interactive/view/view-overlay.ts +28 -3
- package/src/tools/ask-user.ts +43 -2
- package/src/tools/dispatch-plan.ts +17 -9
- package/src/tools/dispatch-scout.ts +1 -1
- package/src/tools/registry.ts +16 -0
- package/dist/chunk-AOCYTWAV.js +0 -449
- package/dist/chunk-HWUFFB6L.js +0 -83
- package/dist/chunk-R346GLFC.js +0 -31
- package/dist/doctor-M7YEDGAE.js +0 -91
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { performance } from "node:perf_hooks";
|
|
2
|
+
import type { SessionEntry } from "../../session/entries.js";
|
|
3
|
+
|
|
4
|
+
export interface EvalCallLedgerFold {
|
|
5
|
+
push(chunk: string): void;
|
|
6
|
+
entries(): SessionEntry[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Fold structured assistant completions into the eval's per-call ledger. */
|
|
10
|
+
export function createEvalCallLedgerFold(now: () => number = () => performance.now()): EvalCallLedgerFold {
|
|
11
|
+
const entries: SessionEntry[] = [];
|
|
12
|
+
let pending = "";
|
|
13
|
+
let activeStartedAt: number | null = null;
|
|
14
|
+
let activeFirstOutputAt: number | null = null;
|
|
15
|
+
|
|
16
|
+
const consume = (line: string): void => {
|
|
17
|
+
const event = parseRecord(line);
|
|
18
|
+
if (event === null) return;
|
|
19
|
+
if (event.type === "message_start" && isAssistantMessage(event.message)) {
|
|
20
|
+
activeStartedAt = now();
|
|
21
|
+
activeFirstOutputAt = null;
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (event.type === "message_update" && activeStartedAt !== null && activeFirstOutputAt === null) {
|
|
25
|
+
activeFirstOutputAt = now();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (event.type !== "message_end" || !isAssistantMessage(event.message)) return;
|
|
29
|
+
const message = event.message;
|
|
30
|
+
const usage = isRecord(message.usage) ? message.usage : null;
|
|
31
|
+
if (usage === null) {
|
|
32
|
+
activeStartedAt = null;
|
|
33
|
+
activeFirstOutputAt = null;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const endedAt = now();
|
|
37
|
+
const promptCache: Record<string, unknown> = {
|
|
38
|
+
input: nonNegativeNumber(usage.input) ?? 0,
|
|
39
|
+
cacheRead: nonNegativeNumber(usage.cacheRead) ?? 0,
|
|
40
|
+
cacheWrite: nonNegativeNumber(usage.cacheWrite) ?? 0,
|
|
41
|
+
backendVerdict: "unknown",
|
|
42
|
+
};
|
|
43
|
+
if (isRecord(message.backendTimings)) promptCache.backend = structuredClone(message.backendTimings);
|
|
44
|
+
const previous = entries.at(-1);
|
|
45
|
+
entries.push({
|
|
46
|
+
kind: "message",
|
|
47
|
+
role: "assistant",
|
|
48
|
+
turnId: `eval-call-${entries.length + 1}`,
|
|
49
|
+
parentTurnId: previous?.turnId ?? null,
|
|
50
|
+
timestamp: messageTimestamp(message.timestamp),
|
|
51
|
+
payload: {
|
|
52
|
+
promptCache,
|
|
53
|
+
timing: {
|
|
54
|
+
ttftMs:
|
|
55
|
+
activeStartedAt === null ? null : Math.round(Math.max(0, (activeFirstOutputAt ?? endedAt) - activeStartedAt)),
|
|
56
|
+
apiMs: activeStartedAt === null ? 0 : Math.round(Math.max(0, endedAt - activeStartedAt)),
|
|
57
|
+
},
|
|
58
|
+
usage: structuredClone(usage),
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
activeStartedAt = null;
|
|
62
|
+
activeFirstOutputAt = null;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
push(chunk: string): void {
|
|
67
|
+
pending += chunk;
|
|
68
|
+
for (;;) {
|
|
69
|
+
const newline = pending.indexOf("\n");
|
|
70
|
+
if (newline === -1) break;
|
|
71
|
+
consume(pending.slice(0, newline).replace(/\r$/u, ""));
|
|
72
|
+
pending = pending.slice(newline + 1);
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
entries(): SessionEntry[] {
|
|
76
|
+
if (pending.length > 0) {
|
|
77
|
+
consume(pending.replace(/\r$/u, ""));
|
|
78
|
+
pending = "";
|
|
79
|
+
}
|
|
80
|
+
return structuredClone(entries);
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isAssistantMessage(value: unknown): value is Record<string, unknown> & { role: "assistant" } {
|
|
86
|
+
return isRecord(value) && value.role === "assistant";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function messageTimestamp(value: unknown): string {
|
|
90
|
+
const milliseconds = nonNegativeNumber(value);
|
|
91
|
+
if (milliseconds === null) return new Date(0).toISOString();
|
|
92
|
+
const date = new Date(milliseconds);
|
|
93
|
+
return Number.isFinite(date.getTime()) ? date.toISOString() : new Date(0).toISOString();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function nonNegativeNumber(value: unknown): number | null {
|
|
97
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function parseRecord(line: string): Record<string, unknown> | null {
|
|
101
|
+
if (line.trim().length === 0) return null;
|
|
102
|
+
try {
|
|
103
|
+
const value: unknown = JSON.parse(line);
|
|
104
|
+
return isRecord(value) ? value : null;
|
|
105
|
+
} catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
111
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
112
|
+
}
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import type { RunReceipt } from "../../dispatch/types.js";
|
|
4
|
+
import { listSessionLedgerRefs, parseSessionEntries } from "../../session/archive-readers.js";
|
|
5
|
+
import { extractReasoningTokens } from "../../session/context-accounting.js";
|
|
6
|
+
import type { SessionEntry } from "../../session/entries.js";
|
|
7
|
+
import { evalHarnessMetricsFromReceipt } from "../harness-metrics.js";
|
|
8
|
+
import type {
|
|
9
|
+
EvalMetricSource,
|
|
10
|
+
EvalSourcedNullableNumber,
|
|
11
|
+
EvalSourcedNumber,
|
|
12
|
+
EvalTrackedMetricsV1,
|
|
13
|
+
} from "../schema/verdict.js";
|
|
14
|
+
|
|
15
|
+
export interface EvalLedgerSnapshot {
|
|
16
|
+
entries: SessionEntry[];
|
|
17
|
+
compiledPromptHashes: string[];
|
|
18
|
+
promptManifests: EvalPromptManifestObservation[];
|
|
19
|
+
contextSnapshots: EvalContextSnapshotObservation[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface EvalPromptManifestObservation {
|
|
23
|
+
systemPromptHash: string;
|
|
24
|
+
thinkingLevel: string | null;
|
|
25
|
+
projectPreload: {
|
|
26
|
+
mode: "full" | "synopsis" | "none";
|
|
27
|
+
chars: number;
|
|
28
|
+
lines: number;
|
|
29
|
+
reason: string | null;
|
|
30
|
+
nearLimit: boolean;
|
|
31
|
+
label: string;
|
|
32
|
+
} | null;
|
|
33
|
+
fragments: Array<{ id: string; contentHash: string }>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface EvalContextSnapshotObservation {
|
|
37
|
+
runtimeId: string | null;
|
|
38
|
+
modelId: string | null;
|
|
39
|
+
promptHash: string | null;
|
|
40
|
+
toolSignature: string | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface BuildEvalTrackedMetricsInput {
|
|
44
|
+
ledgerEntries: ReadonlyArray<SessionEntry>;
|
|
45
|
+
receipt: RunReceipt | null;
|
|
46
|
+
fallbackWallClockMs: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface AssistantCall {
|
|
50
|
+
payload: Record<string, unknown>;
|
|
51
|
+
promptCache: Record<string, unknown> | null;
|
|
52
|
+
backend: Record<string, unknown> | null;
|
|
53
|
+
timing: Record<string, unknown> | null;
|
|
54
|
+
usage: Record<string, unknown> | null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface NumericReading {
|
|
58
|
+
value: number;
|
|
59
|
+
source: EvalMetricSource;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Read every isolated session ledger and its prompt manifest before cleanup. */
|
|
63
|
+
export async function readEvalLedgerSnapshot(stateDir: string): Promise<EvalLedgerSnapshot> {
|
|
64
|
+
const refs = await listSessionLedgerRefs(stateDir);
|
|
65
|
+
const entries: SessionEntry[] = [];
|
|
66
|
+
const compiledPromptHashes: string[] = [];
|
|
67
|
+
const promptManifests: EvalPromptManifestObservation[] = [];
|
|
68
|
+
const contextSnapshots: EvalContextSnapshotObservation[] = [];
|
|
69
|
+
for (const ref of refs) {
|
|
70
|
+
try {
|
|
71
|
+
const raw = await readFile(ref.path, "utf8");
|
|
72
|
+
entries.push(...parseSessionEntries(raw, ref.path).entries);
|
|
73
|
+
} catch {
|
|
74
|
+
// An unreadable ledger contributes no invented measurements.
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const manifest = await readFile(join(dirname(ref.path), "prompt-manifest.jsonl"), "utf8");
|
|
78
|
+
for (const line of manifest.split(/\r?\n/u)) {
|
|
79
|
+
const record = parseJsonRecord(line);
|
|
80
|
+
if (record === null) continue;
|
|
81
|
+
const hash = record?.systemPromptHash;
|
|
82
|
+
if (typeof hash !== "string" || !/^[a-f0-9]{64}$/u.test(hash)) continue;
|
|
83
|
+
compiledPromptHashes.push(hash);
|
|
84
|
+
const observation = promptManifestObservation(record);
|
|
85
|
+
if (observation !== null) promptManifests.push(observation);
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
// A missing manifest leaves compiled prompt provenance unresolved.
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const snapshots = await readFile(join(dirname(ref.path), "context-snapshots.jsonl"), "utf8");
|
|
92
|
+
for (const line of snapshots.split(/\r?\n/u)) {
|
|
93
|
+
const record = parseJsonRecord(line);
|
|
94
|
+
if (record === null) continue;
|
|
95
|
+
contextSnapshots.push({
|
|
96
|
+
runtimeId: nullableString(record.runtimeId),
|
|
97
|
+
modelId: nullableString(record.modelId),
|
|
98
|
+
promptHash: nullableDigest(record.promptHash),
|
|
99
|
+
toolSignature: nullableDigest(record.toolSignature),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
// A missing context snapshot leaves runtime and tool-surface provenance unresolved.
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return { entries, compiledPromptHashes: [...new Set(compiledPromptHashes)], promptManifests, contextSnapshots };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Fold structured ledger calls and a sealed receipt into verdict metrics. */
|
|
110
|
+
export function buildEvalTrackedMetrics(input: BuildEvalTrackedMetricsInput): EvalTrackedMetricsV1 {
|
|
111
|
+
const calls = assistantCalls(input.ledgerEntries);
|
|
112
|
+
const compactionEntries = input.ledgerEntries.filter((entry) => entry.kind === "compactionSummary");
|
|
113
|
+
const compactionUsage = compactionEntries.flatMap((entry) => {
|
|
114
|
+
if (entry.kind !== "compactionSummary" || !isRecord(entry.usage)) return [];
|
|
115
|
+
return [entry.usage];
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const modelCallReadings: NumericReading[] = calls.map(() => ledgerReading(1));
|
|
119
|
+
for (const entry of compactionEntries) {
|
|
120
|
+
if (entry.kind !== "compactionSummary") continue;
|
|
121
|
+
const apiCalls = isRecord(entry.usage) ? nonNegativeNumber(entry.usage.apiCalls) : null;
|
|
122
|
+
modelCallReadings.push(apiCalls === null ? estimatedReading(1) : ledgerReading(apiCalls));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const uncachedReadings = calls.map(uncachedPrefillForCall);
|
|
126
|
+
const cacheReadings = calls.map(cacheReadForCall);
|
|
127
|
+
const generatedReadings = calls.map(generatedForCall);
|
|
128
|
+
for (const usage of compactionUsage) {
|
|
129
|
+
uncachedReadings.push(readingFromUsage(usage, "input"));
|
|
130
|
+
cacheReadings.push(readingFromUsage(usage, "cacheRead"));
|
|
131
|
+
generatedReadings.push(readingFromUsage(usage, "output"));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const reasoning = reasoningMetric(input.receipt, calls, compactionUsage);
|
|
135
|
+
const receiptToolMetrics = input.receipt === null ? null : evalHarnessMetricsFromReceipt(input.receipt);
|
|
136
|
+
const ledgerToolCalls = input.ledgerEntries.filter(
|
|
137
|
+
(entry) => entry.kind === "message" && entry.role === "tool_call",
|
|
138
|
+
).length;
|
|
139
|
+
const ledgerToolErrors = input.ledgerEntries.filter((entry) => {
|
|
140
|
+
if (entry.kind !== "message" || entry.role !== "tool_result" || !isRecord(entry.payload)) return false;
|
|
141
|
+
return entry.payload.isError === true || entry.payload.outcome === "error";
|
|
142
|
+
}).length;
|
|
143
|
+
const receiptToolErrors = input.receipt?.toolStats.reduce((sum, stat) => sum + finiteNonNegative(stat.errors), 0);
|
|
144
|
+
const expectedColdReasons = expectedColdReasonMetrics(calls);
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
modelCalls: sumReadings(modelCallReadings, "ledger"),
|
|
148
|
+
uncachedPrefillTokens: sumReadings(uncachedReadings, "estimated"),
|
|
149
|
+
cacheReadTokens: sumReadings(cacheReadings, "estimated"),
|
|
150
|
+
generatedTokens: sumReadings(generatedReadings, "estimated"),
|
|
151
|
+
reasoningTokens: reasoning,
|
|
152
|
+
toolCalls:
|
|
153
|
+
receiptToolMetrics === null
|
|
154
|
+
? { value: ledgerToolCalls, source: "ledger" }
|
|
155
|
+
: { value: receiptToolMetrics.toolCalls, source: "receipt" },
|
|
156
|
+
toolErrors:
|
|
157
|
+
receiptToolErrors === undefined
|
|
158
|
+
? { value: ledgerToolErrors, source: "ledger" }
|
|
159
|
+
: { value: receiptToolErrors, source: "receipt" },
|
|
160
|
+
ttftMsFirstCall: firstCallTtft(calls),
|
|
161
|
+
wallClockMs: wallClockMetric(input.receipt, input.fallbackWallClockMs),
|
|
162
|
+
contextTokensAtEnd: contextTokensAtEnd(calls, compactionEntries),
|
|
163
|
+
compactions: { value: compactionEntries.length, source: "ledger" },
|
|
164
|
+
expectedColdReasons,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function emptyEvalTrackedMetrics(source: EvalMetricSource = "estimated"): EvalTrackedMetricsV1 {
|
|
169
|
+
const zero = (): EvalSourcedNumber => ({ value: 0, source });
|
|
170
|
+
return {
|
|
171
|
+
modelCalls: zero(),
|
|
172
|
+
uncachedPrefillTokens: zero(),
|
|
173
|
+
cacheReadTokens: zero(),
|
|
174
|
+
generatedTokens: zero(),
|
|
175
|
+
reasoningTokens: { value: null, source },
|
|
176
|
+
toolCalls: zero(),
|
|
177
|
+
toolErrors: zero(),
|
|
178
|
+
ttftMsFirstCall: zero(),
|
|
179
|
+
wallClockMs: zero(),
|
|
180
|
+
contextTokensAtEnd: zero(),
|
|
181
|
+
compactions: zero(),
|
|
182
|
+
expectedColdReasons: {},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function assistantCalls(entries: ReadonlyArray<SessionEntry>): AssistantCall[] {
|
|
187
|
+
return entries.flatMap((entry) => {
|
|
188
|
+
if (entry.kind !== "message" || entry.role !== "assistant" || !isRecord(entry.payload)) return [];
|
|
189
|
+
const promptCache = recordField(entry.payload, "promptCache");
|
|
190
|
+
const timing = recordField(entry.payload, "timing");
|
|
191
|
+
const usage = recordField(entry.payload, "usage");
|
|
192
|
+
if (promptCache === null && timing === null && usage === null) return [];
|
|
193
|
+
return [
|
|
194
|
+
{
|
|
195
|
+
payload: entry.payload,
|
|
196
|
+
promptCache,
|
|
197
|
+
backend: promptCache === null ? null : recordField(promptCache, "backend"),
|
|
198
|
+
timing,
|
|
199
|
+
usage,
|
|
200
|
+
},
|
|
201
|
+
];
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function uncachedPrefillForCall(call: AssistantCall): NumericReading {
|
|
206
|
+
const promptTokens = call.backend === null ? null : nonNegativeNumber(call.backend.promptTokens);
|
|
207
|
+
const cachedTokens = call.backend === null ? null : nonNegativeNumber(call.backend.cachedTokens);
|
|
208
|
+
if (promptTokens !== null && cachedTokens !== null && cachedTokens <= promptTokens) {
|
|
209
|
+
return ledgerReading(promptTokens - cachedTokens);
|
|
210
|
+
}
|
|
211
|
+
const piInput = call.promptCache === null ? null : nonNegativeNumber(call.promptCache.input);
|
|
212
|
+
if (piInput !== null) return ledgerReading(piInput);
|
|
213
|
+
const legacyInput = call.usage === null ? null : nonNegativeNumber(call.usage.input);
|
|
214
|
+
return legacyInput === null ? estimatedReading(0) : estimatedReading(legacyInput);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function cacheReadForCall(call: AssistantCall): NumericReading {
|
|
218
|
+
const cachedTokens = call.backend === null ? null : nonNegativeNumber(call.backend.cachedTokens);
|
|
219
|
+
if (cachedTokens !== null) return ledgerReading(cachedTokens);
|
|
220
|
+
const piCacheRead = call.promptCache === null ? null : nonNegativeNumber(call.promptCache.cacheRead);
|
|
221
|
+
if (piCacheRead !== null) return ledgerReading(piCacheRead);
|
|
222
|
+
const legacyCacheRead = call.usage === null ? null : nonNegativeNumber(call.usage.cacheRead);
|
|
223
|
+
return legacyCacheRead === null ? estimatedReading(0) : estimatedReading(legacyCacheRead);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function generatedForCall(call: AssistantCall): NumericReading {
|
|
227
|
+
const predictedTokens = call.backend === null ? null : nonNegativeNumber(call.backend.predictedTokens);
|
|
228
|
+
if (predictedTokens !== null) return ledgerReading(predictedTokens);
|
|
229
|
+
const output = call.usage === null ? null : nonNegativeNumber(call.usage.output);
|
|
230
|
+
return output === null ? estimatedReading(0) : ledgerReading(output);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function readingFromUsage(usage: Record<string, unknown>, field: string): NumericReading {
|
|
234
|
+
const value = nonNegativeNumber(usage[field]);
|
|
235
|
+
return value === null ? estimatedReading(0) : ledgerReading(value);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function reasoningMetric(
|
|
239
|
+
receipt: RunReceipt | null,
|
|
240
|
+
calls: ReadonlyArray<AssistantCall>,
|
|
241
|
+
compactionUsage: ReadonlyArray<Record<string, unknown>>,
|
|
242
|
+
): EvalSourcedNullableNumber {
|
|
243
|
+
if (receipt !== null && typeof receipt.reasoningTokenCount === "number") {
|
|
244
|
+
return { value: finiteNonNegative(receipt.reasoningTokenCount), source: "receipt" };
|
|
245
|
+
}
|
|
246
|
+
let total = 0;
|
|
247
|
+
let measured = false;
|
|
248
|
+
for (const call of calls) {
|
|
249
|
+
const value = extractReasoningTokens(call.usage);
|
|
250
|
+
if (value === null) continue;
|
|
251
|
+
measured = true;
|
|
252
|
+
total += finiteNonNegative(value);
|
|
253
|
+
}
|
|
254
|
+
for (const usage of compactionUsage) {
|
|
255
|
+
const value = nonNegativeNumber(usage.reasoning);
|
|
256
|
+
if (value === null) continue;
|
|
257
|
+
measured = true;
|
|
258
|
+
total += value;
|
|
259
|
+
}
|
|
260
|
+
return measured ? { value: total, source: "ledger" } : { value: null, source: "estimated" };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function firstCallTtft(calls: ReadonlyArray<AssistantCall>): EvalSourcedNumber {
|
|
264
|
+
const first = calls[0];
|
|
265
|
+
const value = first?.timing === null || first?.timing === undefined ? null : nonNegativeNumber(first.timing.ttftMs);
|
|
266
|
+
return value === null ? { value: 0, source: "estimated" } : { value, source: "ledger" };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function wallClockMetric(receipt: RunReceipt | null, fallback: number): EvalSourcedNumber {
|
|
270
|
+
if (receipt !== null) {
|
|
271
|
+
const started = Date.parse(receipt.startedAt);
|
|
272
|
+
const ended = Date.parse(receipt.endedAt);
|
|
273
|
+
if (Number.isFinite(started) && Number.isFinite(ended) && ended >= started) {
|
|
274
|
+
return { value: ended - started, source: "receipt" };
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return { value: finiteNonNegative(fallback), source: "estimated" };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function contextTokensAtEnd(
|
|
281
|
+
calls: ReadonlyArray<AssistantCall>,
|
|
282
|
+
compactions: ReadonlyArray<SessionEntry>,
|
|
283
|
+
): EvalSourcedNumber {
|
|
284
|
+
const lastCall = calls.at(-1);
|
|
285
|
+
if (lastCall !== undefined) {
|
|
286
|
+
const lastReading = contextForCall(lastCall);
|
|
287
|
+
if (lastReading !== null && lastReading > 0) return { value: lastReading, source: "ledger" };
|
|
288
|
+
if (lastReading === 0) {
|
|
289
|
+
for (const call of [...calls.slice(0, -1)].reverse()) {
|
|
290
|
+
const reading = contextForCall(call);
|
|
291
|
+
if (reading !== null && reading > 0) return { value: reading, source: "ledger" };
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const lastCompaction = compactions.at(-1);
|
|
296
|
+
if (lastCompaction?.kind === "compactionSummary") {
|
|
297
|
+
const tokensAfter = nonNegativeNumber(lastCompaction.tokensAfter);
|
|
298
|
+
if (tokensAfter !== null) return { value: tokensAfter, source: "ledger" };
|
|
299
|
+
}
|
|
300
|
+
return { value: 0, source: "estimated" };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function contextForCall(call: AssistantCall): number | null {
|
|
304
|
+
const promptTokens = call.backend === null ? null : nonNegativeNumber(call.backend.promptTokens);
|
|
305
|
+
const predictedTokens = call.backend === null ? null : nonNegativeNumber(call.backend.predictedTokens);
|
|
306
|
+
if (promptTokens !== null && predictedTokens !== null) return promptTokens + predictedTokens;
|
|
307
|
+
const input = call.promptCache === null ? null : nonNegativeNumber(call.promptCache.input);
|
|
308
|
+
const cacheRead = call.promptCache === null ? null : nonNegativeNumber(call.promptCache.cacheRead);
|
|
309
|
+
const output = call.usage === null ? null : nonNegativeNumber(call.usage.output);
|
|
310
|
+
return input === null || cacheRead === null || output === null ? null : input + cacheRead + output;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function expectedColdReasonMetrics(calls: ReadonlyArray<AssistantCall>): Record<string, EvalSourcedNumber> {
|
|
314
|
+
const counts = new Map<string, number>();
|
|
315
|
+
for (const call of calls) {
|
|
316
|
+
const reasons = call.promptCache?.expectedColdReasons;
|
|
317
|
+
if (!Array.isArray(reasons)) continue;
|
|
318
|
+
const unique = new Set(reasons.filter((reason): reason is string => typeof reason === "string" && reason.length > 0));
|
|
319
|
+
for (const reason of unique) counts.set(reason, (counts.get(reason) ?? 0) + 1);
|
|
320
|
+
}
|
|
321
|
+
return Object.fromEntries(
|
|
322
|
+
[...counts.entries()]
|
|
323
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
324
|
+
.map(([reason, value]) => [reason, { value, source: "ledger" as const }]),
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function sumReadings(readings: ReadonlyArray<NumericReading>, emptySource: EvalMetricSource): EvalSourcedNumber {
|
|
329
|
+
if (readings.length === 0) return { value: 0, source: emptySource };
|
|
330
|
+
return {
|
|
331
|
+
value: readings.reduce((sum, reading) => sum + reading.value, 0),
|
|
332
|
+
source: readings.some((reading) => reading.source === "estimated") ? "estimated" : "ledger",
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function ledgerReading(value: number): NumericReading {
|
|
337
|
+
return { value: finiteNonNegative(value), source: "ledger" };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function estimatedReading(value: number): NumericReading {
|
|
341
|
+
return { value: finiteNonNegative(value), source: "estimated" };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function finiteNonNegative(value: number): number {
|
|
345
|
+
return Number.isFinite(value) && value >= 0 ? value : 0;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function nonNegativeNumber(value: unknown): number | null {
|
|
349
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function recordField(record: Record<string, unknown>, field: string): Record<string, unknown> | null {
|
|
353
|
+
return isRecord(record[field]) ? record[field] : null;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function parseJsonRecord(line: string): Record<string, unknown> | null {
|
|
357
|
+
if (line.trim().length === 0) return null;
|
|
358
|
+
try {
|
|
359
|
+
const parsed: unknown = JSON.parse(line);
|
|
360
|
+
return isRecord(parsed) ? parsed : null;
|
|
361
|
+
} catch {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function promptManifestObservation(record: Record<string, unknown>): EvalPromptManifestObservation | null {
|
|
367
|
+
const systemPromptHash = nullableDigest(record.systemPromptHash);
|
|
368
|
+
if (systemPromptHash === null || !Array.isArray(record.fragments)) return null;
|
|
369
|
+
const fragments = record.fragments.flatMap((entry) => {
|
|
370
|
+
if (!isRecord(entry) || typeof entry.id !== "string") return [];
|
|
371
|
+
const contentHash = nullableDigest(entry.contentHash);
|
|
372
|
+
return contentHash === null ? [] : [{ id: entry.id, contentHash }];
|
|
373
|
+
});
|
|
374
|
+
const preload = record.projectPreload;
|
|
375
|
+
const projectPreload: EvalPromptManifestObservation["projectPreload"] =
|
|
376
|
+
preload === null
|
|
377
|
+
? null
|
|
378
|
+
: isRecord(preload) &&
|
|
379
|
+
(preload.mode === "full" || preload.mode === "synopsis" || preload.mode === "none") &&
|
|
380
|
+
typeof preload.chars === "number" &&
|
|
381
|
+
Number.isInteger(preload.chars) &&
|
|
382
|
+
typeof preload.lines === "number" &&
|
|
383
|
+
Number.isInteger(preload.lines) &&
|
|
384
|
+
typeof preload.nearLimit === "boolean" &&
|
|
385
|
+
typeof preload.label === "string"
|
|
386
|
+
? {
|
|
387
|
+
mode: preload.mode as "full" | "synopsis" | "none",
|
|
388
|
+
chars: preload.chars,
|
|
389
|
+
lines: preload.lines,
|
|
390
|
+
reason: nullableString(preload.reason),
|
|
391
|
+
nearLimit: preload.nearLimit,
|
|
392
|
+
label: preload.label,
|
|
393
|
+
}
|
|
394
|
+
: null;
|
|
395
|
+
return {
|
|
396
|
+
systemPromptHash,
|
|
397
|
+
thinkingLevel: nullableString(record.thinkingLevel),
|
|
398
|
+
projectPreload,
|
|
399
|
+
fragments,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function nullableString(value: unknown): string | null {
|
|
404
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function nullableDigest(value: unknown): string | null {
|
|
408
|
+
return typeof value === "string" && /^[a-f0-9]{64}$/u.test(value) ? value : null;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
412
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
413
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { readSettings } from "../../core/config.js";
|
|
2
3
|
import { readClioVersion, resolvePackageRoot } from "../../core/package-root.js";
|
|
4
|
+
import type { RunReceipt } from "../dispatch/types.js";
|
|
5
|
+
import type { EvalServingConfigurationV1 } from "./schema/serving.js";
|
|
6
|
+
import type { EvalSuiteTargetV2 } from "./schema/suite.js";
|
|
3
7
|
import type { EvalClioProvenance, EvalEnvironmentProvenance } from "./types.js";
|
|
4
8
|
|
|
5
9
|
export interface EvalProvenanceOptions {
|
|
@@ -7,6 +11,14 @@ export interface EvalProvenanceOptions {
|
|
|
7
11
|
commit?: string | null;
|
|
8
12
|
}
|
|
9
13
|
|
|
14
|
+
export interface EvalServingObservation {
|
|
15
|
+
targetId: string;
|
|
16
|
+
runtimeId: string | null;
|
|
17
|
+
modelId: string | null;
|
|
18
|
+
thinkingLevel: string | null;
|
|
19
|
+
compiledPromptHash: string | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
10
22
|
export function evalClioProvenance(options: EvalProvenanceOptions = {}): EvalClioProvenance {
|
|
11
23
|
return {
|
|
12
24
|
version: readClioVersion(),
|
|
@@ -22,6 +34,45 @@ export function evalEnvironmentProvenance(): EvalEnvironmentProvenance {
|
|
|
22
34
|
};
|
|
23
35
|
}
|
|
24
36
|
|
|
37
|
+
/** Capture serving identity after the matrix has run while a local server is awake. */
|
|
38
|
+
export async function evalServingConfiguration(
|
|
39
|
+
targets: ReadonlyArray<EvalSuiteTargetV2>,
|
|
40
|
+
observations: ReadonlyArray<EvalServingObservation>,
|
|
41
|
+
): Promise<EvalServingConfigurationV1> {
|
|
42
|
+
const targetId = targets.length === 1 ? (targets[0]?.id ?? "unknown") : "multiple";
|
|
43
|
+
const configured = configuredTarget(targetId);
|
|
44
|
+
const target = targets.length === 1 ? targets[0] : undefined;
|
|
45
|
+
const runtimeId = consensus(observations.map((entry) => entry.runtimeId)) ?? configured?.runtime ?? null;
|
|
46
|
+
const modelId =
|
|
47
|
+
consensus(observations.map((entry) => entry.modelId)) ?? target?.model ?? configured?.defaultModel ?? null;
|
|
48
|
+
const props = configured?.url === undefined ? null : await readServingProps(configured.url, modelId);
|
|
49
|
+
return {
|
|
50
|
+
targetId,
|
|
51
|
+
runtimeId,
|
|
52
|
+
modelId,
|
|
53
|
+
serverBuild: props?.serverBuild ?? null,
|
|
54
|
+
total_slots: props?.totalSlots ?? null,
|
|
55
|
+
thinkingLevel: consensus(observations.map((entry) => entry.thinkingLevel)) ?? target?.thinking ?? null,
|
|
56
|
+
compiledPromptHash: consensus(observations.map((entry) => entry.compiledPromptHash)),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function evalServingObservationFrom(
|
|
61
|
+
target: EvalSuiteTargetV2,
|
|
62
|
+
receipt: RunReceipt | null,
|
|
63
|
+
compiledPromptHashes: ReadonlyArray<string>,
|
|
64
|
+
): EvalServingObservation {
|
|
65
|
+
const compiledPromptHash =
|
|
66
|
+
receipt?.staticCompositionHash ?? receipt?.compiledPromptHash ?? consensus(compiledPromptHashes);
|
|
67
|
+
return {
|
|
68
|
+
targetId: receipt?.targetId ?? target.id,
|
|
69
|
+
runtimeId: receipt?.runtimeId ?? null,
|
|
70
|
+
modelId: receipt?.wireModelId ?? target.model ?? null,
|
|
71
|
+
thinkingLevel: receipt?.runtimeResolution?.effectiveThinkingLevel ?? target.thinking ?? null,
|
|
72
|
+
compiledPromptHash,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
25
76
|
function currentClioCommit(): string | null {
|
|
26
77
|
const result = spawnSync("git", ["rev-parse", "HEAD"], {
|
|
27
78
|
cwd: resolvePackageRoot(),
|
|
@@ -33,3 +84,69 @@ function currentClioCommit(): string | null {
|
|
|
33
84
|
const value = result.stdout.trim();
|
|
34
85
|
return value.length > 0 ? value : null;
|
|
35
86
|
}
|
|
87
|
+
|
|
88
|
+
interface ConfiguredServingTarget {
|
|
89
|
+
runtime: string;
|
|
90
|
+
url?: string;
|
|
91
|
+
defaultModel?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function configuredTarget(targetId: string): ConfiguredServingTarget | null {
|
|
95
|
+
try {
|
|
96
|
+
const target = readSettings().targets.find((entry) => entry.id === targetId);
|
|
97
|
+
if (target === undefined) return null;
|
|
98
|
+
return {
|
|
99
|
+
runtime: target.runtime,
|
|
100
|
+
...(target.url === undefined ? {} : { url: target.url }),
|
|
101
|
+
...(target.defaultModel === undefined ? {} : { defaultModel: target.defaultModel }),
|
|
102
|
+
};
|
|
103
|
+
} catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function readServingProps(
|
|
109
|
+
targetUrl: string,
|
|
110
|
+
modelId: string | null,
|
|
111
|
+
): Promise<{ serverBuild: string | null; totalSlots: number | null } | null> {
|
|
112
|
+
const root = targetUrl.replace(/\/+$/u, "").replace(/\/v1$/u, "");
|
|
113
|
+
try {
|
|
114
|
+
const response = await fetch(`${root}/props`, { signal: AbortSignal.timeout(5_000) });
|
|
115
|
+
if (!response.ok) return null;
|
|
116
|
+
const value: unknown = await response.json();
|
|
117
|
+
if (!isRecord(value)) return null;
|
|
118
|
+
const totalSlots = positiveInteger(value.total_slots) ?? (await readServingSlots(root, modelId));
|
|
119
|
+
return {
|
|
120
|
+
serverBuild: typeof value.build_info === "string" && value.build_info.length > 0 ? value.build_info : null,
|
|
121
|
+
totalSlots,
|
|
122
|
+
};
|
|
123
|
+
} catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function readServingSlots(root: string, modelId: string | null): Promise<number | null> {
|
|
129
|
+
if (modelId === null) return null;
|
|
130
|
+
try {
|
|
131
|
+
const query = new URLSearchParams({ model: modelId });
|
|
132
|
+
const response = await fetch(`${root}/slots?${query}`, { signal: AbortSignal.timeout(5_000) });
|
|
133
|
+
if (!response.ok) return null;
|
|
134
|
+
const value: unknown = await response.json();
|
|
135
|
+
return Array.isArray(value) && value.length > 0 ? value.length : null;
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function consensus(values: ReadonlyArray<string | null>): string | null {
|
|
142
|
+
const known = [...new Set(values.filter((value): value is string => typeof value === "string" && value.length > 0))];
|
|
143
|
+
return known.length === 1 ? (known[0] ?? null) : null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function positiveInteger(value: unknown): number | null {
|
|
147
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
151
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
152
|
+
}
|