@remnic/core 10.0.37 → 10.0.38
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/dist/access-admin-ops-surface.js +24 -24
- package/dist/access-authorization-probe.js +25 -25
- package/dist/access-boundary.js +24 -24
- package/dist/access-cli.js +71 -71
- package/dist/access-extraction-force-flush.js +24 -24
- package/dist/access-http-briefing.js +25 -25
- package/dist/access-http-query.js +26 -26
- package/dist/access-http.js +41 -41
- package/dist/access-identity-continuity-surface.js +24 -24
- package/dist/access-lcm-surface.js +24 -24
- package/dist/access-mcp.js +34 -34
- package/dist/access-namespace-preflight.js +24 -24
- package/dist/access-observe-write-surface.js +24 -24
- package/dist/access-operations-batch.js +25 -25
- package/dist/access-operations.js +32 -32
- package/dist/access-recall-concurrency.js +24 -24
- package/dist/access-recall-diagnostics.js +2 -2
- package/dist/access-recall-response.js +24 -24
- package/dist/access-recall-surface.js +24 -24
- package/dist/access-service.js +24 -24
- package/dist/access-surface-catalog.js +26 -26
- package/dist/active-recall.js +5 -5
- package/dist/{auto-sync-R3XGJL5I.js → auto-sync-432USQDH.js} +4 -4
- package/dist/briefing.js +5 -5
- package/dist/buffer.js +3 -3
- package/dist/calibration.js +1 -1
- package/dist/causal-consolidation.js +6 -6
- package/dist/{chunk-FL6SLVAJ.js → chunk-2AKIVSHP.js} +2 -2
- package/dist/{chunk-G6PPA4TH.js → chunk-3AODCBKX.js} +2 -2
- package/dist/{chunk-MGXFJOUR.js → chunk-3K5IEJTK.js} +2 -2
- package/dist/{chunk-YGYT7S3G.js → chunk-3PLAL2KP.js} +15 -1
- package/dist/chunk-3PLAL2KP.js.map +1 -0
- package/dist/{chunk-WADPRAZE.js → chunk-3XVBGGF3.js} +2 -2
- package/dist/{chunk-BPBUUGAO.js → chunk-4B37KNMP.js} +2 -2
- package/dist/{chunk-2RJO7VS4.js → chunk-4IZ6AC2U.js} +2 -2
- package/dist/{chunk-RBC4GIZU.js → chunk-53BLBDV6.js} +2 -2
- package/dist/{chunk-5UWP5X3T.js → chunk-5ANUG4JZ.js} +2 -2
- package/dist/{chunk-Y6Y3TRBN.js → chunk-5DCS4C4S.js} +2 -2
- package/dist/{chunk-MH3LC7KB.js → chunk-5K23NQRI.js} +2 -2
- package/dist/{chunk-UMFBHYPW.js → chunk-5UPOKIFP.js} +8 -8
- package/dist/{chunk-U4JXQCJ3.js → chunk-63YPKHAU.js} +2 -2
- package/dist/{chunk-S53WZWGL.js → chunk-6DTIYAAN.js} +2 -2
- package/dist/{chunk-RWWYBE7E.js → chunk-6F5BGIAF.js} +2 -2
- package/dist/{chunk-Z5AQZRHF.js → chunk-6JEEY42N.js} +2 -2
- package/dist/{chunk-HKEK27M4.js → chunk-6SYS5YX3.js} +2 -2
- package/dist/{chunk-CCYXCT4B.js → chunk-7HXZOWTR.js} +2 -2
- package/dist/{chunk-DGBWPSM6.js → chunk-7VFNGQ4V.js} +2 -2
- package/dist/{chunk-ARFT6ZBE.js → chunk-7ZRRJ6RG.js} +2 -2
- package/dist/{chunk-GMMZ45C4.js → chunk-AVVCPA7U.js} +6 -8
- package/dist/chunk-AVVCPA7U.js.map +1 -0
- package/dist/{chunk-LSSCMZQ2.js → chunk-AYBAC2N4.js} +3 -3
- package/dist/{chunk-EVVCE7S2.js → chunk-C2KOW2UQ.js} +2 -2
- package/dist/{chunk-GCVUBVEN.js → chunk-CVMAOPAH.js} +8 -8
- package/dist/{chunk-D2AKUKAQ.js → chunk-DG4H56SK.js} +1 -1
- package/dist/{chunk-BQ6DGL6A.js → chunk-DKHX2FHI.js} +2 -2
- package/dist/{chunk-HRT2SJPC.js → chunk-E3NBMTTX.js} +2 -2
- package/dist/{chunk-V5RJCXLG.js → chunk-EGNONTVT.js} +5 -5
- package/dist/{chunk-7PG7QBWD.js → chunk-FGEY2AGV.js} +2 -2
- package/dist/{chunk-O6YJI5VX.js → chunk-FKTMWDN2.js} +2 -2
- package/dist/{chunk-QIC7YZ3D.js → chunk-FQFMLOAC.js} +12 -12
- package/dist/{chunk-ID2PLENW.js → chunk-GGI6FLO6.js} +2 -2
- package/dist/{chunk-KC5SVCUJ.js → chunk-I75BWXRZ.js} +2 -2
- package/dist/{chunk-ULUX6Q7R.js → chunk-J7G656MY.js} +3 -3
- package/dist/{chunk-VKTP3TZB.js → chunk-JF3MEIHO.js} +2 -2
- package/dist/{chunk-A23DPFFI.js → chunk-JIA435P3.js} +2 -2
- package/dist/{chunk-USOXPN5H.js → chunk-KOAVX2IT.js} +2 -2
- package/dist/{chunk-Z5QWUR3Y.js → chunk-L7PGPH4E.js} +2 -2
- package/dist/{chunk-5U3NUHMA.js → chunk-LOMSBYNS.js} +33 -33
- package/dist/{chunk-FO5AFCRI.js → chunk-MCPSMWUC.js} +4 -4
- package/dist/{chunk-64IO6ZEE.js → chunk-MZWHIAOL.js} +2 -2
- package/dist/{chunk-LDXS746V.js → chunk-N7DVBHU3.js} +3 -3
- package/dist/{chunk-WSLNC5AL.js → chunk-NH6GZEQN.js} +3 -3
- package/dist/{chunk-3L5WH6CV.js → chunk-OKKBOTRA.js} +2 -2
- package/dist/{chunk-MBMT5FUQ.js → chunk-OLH4XCLU.js} +2 -2
- package/dist/{chunk-UXXVOA4D.js → chunk-OLHN4ZNT.js} +2 -2
- package/dist/{chunk-LRJKHXU4.js → chunk-POGPI5GR.js} +2 -2
- package/dist/{chunk-RVYXSS3Q.js → chunk-QO5KDQGG.js} +3 -3
- package/dist/{chunk-7ZBANI3M.js → chunk-QOUKZ2N3.js} +2 -2
- package/dist/{chunk-HIMXDRSQ.js → chunk-RGKXF4W3.js} +2 -2
- package/dist/{chunk-CNQQNYJ2.js → chunk-RWRHGE75.js} +2 -2
- package/dist/{chunk-6R6CU3MR.js → chunk-SWZOAQUM.js} +22 -22
- package/dist/{chunk-FBAHBFT5.js → chunk-SZPPCEYT.js} +2 -2
- package/dist/{chunk-QEGE5QCL.js → chunk-TAEJ2MRR.js} +19 -19
- package/dist/{chunk-BBDASWNT.js → chunk-TJ5YGCPV.js} +81 -76
- package/dist/chunk-TJ5YGCPV.js.map +1 -0
- package/dist/{chunk-G2SRB62M.js → chunk-TXP7Q7T5.js} +2 -2
- package/dist/{chunk-NSQSOLCE.js → chunk-TYLNWE4D.js} +2 -2
- package/dist/{chunk-A6PKEU7T.js → chunk-UFM3PJDT.js} +1 -1
- package/dist/chunk-UFM3PJDT.js.map +1 -0
- package/dist/{chunk-MNDHKZZQ.js → chunk-VC3X7XP3.js} +2 -2
- package/dist/{chunk-PTRPOJ7U.js → chunk-VGRM43X6.js} +2 -2
- package/dist/{chunk-DK5VMJL3.js → chunk-VSSQIBVU.js} +2 -2
- package/dist/{chunk-R6TCM47S.js → chunk-WCBLPKG3.js} +3 -3
- package/dist/{chunk-UQWA5QDT.js → chunk-WCIDCJOQ.js} +2 -2
- package/dist/{chunk-UFBIOF7C.js → chunk-WEK3OUGO.js} +2 -2
- package/dist/{chunk-5EILJ6W2.js → chunk-WXPBDEMW.js} +2 -2
- package/dist/{chunk-CU7WDDPV.js → chunk-X247CYL6.js} +3 -3
- package/dist/{chunk-JWTUJVIF.js → chunk-XHVO3CGX.js} +2 -2
- package/dist/{chunk-3BPMYMPX.js → chunk-XHX67LX6.js} +2 -2
- package/dist/{chunk-LAQS5HYU.js → chunk-Y5ZHZKZD.js} +2 -2
- package/dist/{chunk-L46Y5FPE.js → chunk-YYGZ4EIY.js} +2 -2
- package/dist/{chunk-NQOM6PHO.js → chunk-ZAP7BKTZ.js} +3 -3
- package/dist/cli.js +54 -54
- package/dist/coding/export-okf-codegraph.js +4 -4
- package/dist/compounding/engine.js +6 -6
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -5
- package/dist/connectors/codex-materialize-runner.js +5 -5
- package/dist/connectors/index.js +5 -5
- package/dist/convergence-refresh.js +7 -7
- package/dist/corpus-watermark.js +7 -7
- package/dist/deep-recall-policy-llm.js +2 -2
- package/dist/entity-retrieval.js +5 -5
- package/dist/external-wiki-access.js +25 -25
- package/dist/extraction-faithfulness.js +2 -2
- package/dist/extraction-judge.d.ts +42 -2
- package/dist/extraction-judge.d.ts.map +1 -1
- package/dist/extraction-judge.js +12 -2
- package/dist/extraction.js +9 -9
- package/dist/fallback-llm.d.ts +1 -0
- package/dist/fallback-llm.d.ts.map +1 -1
- package/dist/fallback-llm.js +1 -1
- package/dist/harmonic-retrieval.js +5 -5
- package/dist/index.js +71 -71
- package/dist/location/index.js +7 -7
- package/dist/maintenance/memory-governance.js +5 -5
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +5 -5
- package/dist/maintenance/rebuild-memory-projection-runner.js +7 -7
- package/dist/maintenance/rebuild-memory-projection.js +6 -6
- package/dist/memory-browse-operations.js +25 -25
- package/dist/memory-browse.js +3 -3
- package/dist/namespaces/migrate.js +6 -6
- package/dist/namespaces/storage.js +5 -5
- package/dist/operator-doctor-corpus.js +8 -8
- package/dist/operator-toolkit.js +14 -14
- package/dist/orchestration/maintenance.js +15 -15
- package/dist/orchestrator.js +71 -71
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-navigate-access.js +2 -2
- package/dist/recall-navigate-disclosure.js +2 -2
- package/dist/recall-navigate.js +2 -2
- package/dist/recall-navigation-operations.js +25 -25
- package/dist/recall-navigation.js +24 -24
- package/dist/recall-planner-llm.js +1 -1
- package/dist/recall-state-view-admission.js +3 -3
- package/dist/recall-why-service.js +3 -3
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.js +2 -2
- package/dist/resume-bundles.js +6 -6
- package/dist/semantic-consolidation.js +6 -6
- package/dist/semantic-rule-promotion.js +5 -5
- package/dist/semantic-rule-verifier.js +5 -5
- package/dist/source-agent-qualifier.js +8 -8
- package/dist/standup/build.js +5 -5
- package/dist/storage.js +4 -4
- package/dist/summarizer.js +2 -2
- package/dist/support-passport/index.js +27 -27
- package/dist/transfer/export-okf.js +5 -5
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/verified-recall.js +5 -5
- package/dist/who-knows-cli.js +6 -6
- package/dist/who-knows.js +5 -5
- package/package.json +2 -2
- package/src/config.test.ts +16 -0
- package/src/config.ts +14 -0
- package/src/extraction-judge-chain.test.ts +298 -2
- package/src/extraction-judge.ts +147 -82
- package/src/fallback-llm.test.ts +94 -0
- package/src/fallback-llm.ts +7 -7
- package/src/types.ts +12 -0
- package/dist/chunk-A6PKEU7T.js.map +0 -1
- package/dist/chunk-BBDASWNT.js.map +0 -1
- package/dist/chunk-GMMZ45C4.js.map +0 -1
- package/dist/chunk-YGYT7S3G.js.map +0 -1
- /package/dist/{auto-sync-R3XGJL5I.js.map → auto-sync-432USQDH.js.map} +0 -0
- /package/dist/{chunk-FL6SLVAJ.js.map → chunk-2AKIVSHP.js.map} +0 -0
- /package/dist/{chunk-G6PPA4TH.js.map → chunk-3AODCBKX.js.map} +0 -0
- /package/dist/{chunk-MGXFJOUR.js.map → chunk-3K5IEJTK.js.map} +0 -0
- /package/dist/{chunk-WADPRAZE.js.map → chunk-3XVBGGF3.js.map} +0 -0
- /package/dist/{chunk-BPBUUGAO.js.map → chunk-4B37KNMP.js.map} +0 -0
- /package/dist/{chunk-2RJO7VS4.js.map → chunk-4IZ6AC2U.js.map} +0 -0
- /package/dist/{chunk-RBC4GIZU.js.map → chunk-53BLBDV6.js.map} +0 -0
- /package/dist/{chunk-5UWP5X3T.js.map → chunk-5ANUG4JZ.js.map} +0 -0
- /package/dist/{chunk-Y6Y3TRBN.js.map → chunk-5DCS4C4S.js.map} +0 -0
- /package/dist/{chunk-MH3LC7KB.js.map → chunk-5K23NQRI.js.map} +0 -0
- /package/dist/{chunk-UMFBHYPW.js.map → chunk-5UPOKIFP.js.map} +0 -0
- /package/dist/{chunk-U4JXQCJ3.js.map → chunk-63YPKHAU.js.map} +0 -0
- /package/dist/{chunk-S53WZWGL.js.map → chunk-6DTIYAAN.js.map} +0 -0
- /package/dist/{chunk-RWWYBE7E.js.map → chunk-6F5BGIAF.js.map} +0 -0
- /package/dist/{chunk-Z5AQZRHF.js.map → chunk-6JEEY42N.js.map} +0 -0
- /package/dist/{chunk-HKEK27M4.js.map → chunk-6SYS5YX3.js.map} +0 -0
- /package/dist/{chunk-CCYXCT4B.js.map → chunk-7HXZOWTR.js.map} +0 -0
- /package/dist/{chunk-DGBWPSM6.js.map → chunk-7VFNGQ4V.js.map} +0 -0
- /package/dist/{chunk-ARFT6ZBE.js.map → chunk-7ZRRJ6RG.js.map} +0 -0
- /package/dist/{chunk-LSSCMZQ2.js.map → chunk-AYBAC2N4.js.map} +0 -0
- /package/dist/{chunk-EVVCE7S2.js.map → chunk-C2KOW2UQ.js.map} +0 -0
- /package/dist/{chunk-GCVUBVEN.js.map → chunk-CVMAOPAH.js.map} +0 -0
- /package/dist/{chunk-D2AKUKAQ.js.map → chunk-DG4H56SK.js.map} +0 -0
- /package/dist/{chunk-BQ6DGL6A.js.map → chunk-DKHX2FHI.js.map} +0 -0
- /package/dist/{chunk-HRT2SJPC.js.map → chunk-E3NBMTTX.js.map} +0 -0
- /package/dist/{chunk-V5RJCXLG.js.map → chunk-EGNONTVT.js.map} +0 -0
- /package/dist/{chunk-7PG7QBWD.js.map → chunk-FGEY2AGV.js.map} +0 -0
- /package/dist/{chunk-O6YJI5VX.js.map → chunk-FKTMWDN2.js.map} +0 -0
- /package/dist/{chunk-QIC7YZ3D.js.map → chunk-FQFMLOAC.js.map} +0 -0
- /package/dist/{chunk-ID2PLENW.js.map → chunk-GGI6FLO6.js.map} +0 -0
- /package/dist/{chunk-KC5SVCUJ.js.map → chunk-I75BWXRZ.js.map} +0 -0
- /package/dist/{chunk-ULUX6Q7R.js.map → chunk-J7G656MY.js.map} +0 -0
- /package/dist/{chunk-VKTP3TZB.js.map → chunk-JF3MEIHO.js.map} +0 -0
- /package/dist/{chunk-A23DPFFI.js.map → chunk-JIA435P3.js.map} +0 -0
- /package/dist/{chunk-USOXPN5H.js.map → chunk-KOAVX2IT.js.map} +0 -0
- /package/dist/{chunk-Z5QWUR3Y.js.map → chunk-L7PGPH4E.js.map} +0 -0
- /package/dist/{chunk-5U3NUHMA.js.map → chunk-LOMSBYNS.js.map} +0 -0
- /package/dist/{chunk-FO5AFCRI.js.map → chunk-MCPSMWUC.js.map} +0 -0
- /package/dist/{chunk-64IO6ZEE.js.map → chunk-MZWHIAOL.js.map} +0 -0
- /package/dist/{chunk-LDXS746V.js.map → chunk-N7DVBHU3.js.map} +0 -0
- /package/dist/{chunk-WSLNC5AL.js.map → chunk-NH6GZEQN.js.map} +0 -0
- /package/dist/{chunk-3L5WH6CV.js.map → chunk-OKKBOTRA.js.map} +0 -0
- /package/dist/{chunk-MBMT5FUQ.js.map → chunk-OLH4XCLU.js.map} +0 -0
- /package/dist/{chunk-UXXVOA4D.js.map → chunk-OLHN4ZNT.js.map} +0 -0
- /package/dist/{chunk-LRJKHXU4.js.map → chunk-POGPI5GR.js.map} +0 -0
- /package/dist/{chunk-RVYXSS3Q.js.map → chunk-QO5KDQGG.js.map} +0 -0
- /package/dist/{chunk-7ZBANI3M.js.map → chunk-QOUKZ2N3.js.map} +0 -0
- /package/dist/{chunk-HIMXDRSQ.js.map → chunk-RGKXF4W3.js.map} +0 -0
- /package/dist/{chunk-CNQQNYJ2.js.map → chunk-RWRHGE75.js.map} +0 -0
- /package/dist/{chunk-6R6CU3MR.js.map → chunk-SWZOAQUM.js.map} +0 -0
- /package/dist/{chunk-FBAHBFT5.js.map → chunk-SZPPCEYT.js.map} +0 -0
- /package/dist/{chunk-QEGE5QCL.js.map → chunk-TAEJ2MRR.js.map} +0 -0
- /package/dist/{chunk-G2SRB62M.js.map → chunk-TXP7Q7T5.js.map} +0 -0
- /package/dist/{chunk-NSQSOLCE.js.map → chunk-TYLNWE4D.js.map} +0 -0
- /package/dist/{chunk-MNDHKZZQ.js.map → chunk-VC3X7XP3.js.map} +0 -0
- /package/dist/{chunk-PTRPOJ7U.js.map → chunk-VGRM43X6.js.map} +0 -0
- /package/dist/{chunk-DK5VMJL3.js.map → chunk-VSSQIBVU.js.map} +0 -0
- /package/dist/{chunk-R6TCM47S.js.map → chunk-WCBLPKG3.js.map} +0 -0
- /package/dist/{chunk-UQWA5QDT.js.map → chunk-WCIDCJOQ.js.map} +0 -0
- /package/dist/{chunk-UFBIOF7C.js.map → chunk-WEK3OUGO.js.map} +0 -0
- /package/dist/{chunk-5EILJ6W2.js.map → chunk-WXPBDEMW.js.map} +0 -0
- /package/dist/{chunk-CU7WDDPV.js.map → chunk-X247CYL6.js.map} +0 -0
- /package/dist/{chunk-JWTUJVIF.js.map → chunk-XHVO3CGX.js.map} +0 -0
- /package/dist/{chunk-3BPMYMPX.js.map → chunk-XHX67LX6.js.map} +0 -0
- /package/dist/{chunk-LAQS5HYU.js.map → chunk-Y5ZHZKZD.js.map} +0 -0
- /package/dist/{chunk-L46Y5FPE.js.map → chunk-YYGZ4EIY.js.map} +0 -0
- /package/dist/{chunk-NQOM6PHO.js.map → chunk-ZAP7BKTZ.js.map} +0 -0
package/src/extraction-judge.ts
CHANGED
|
@@ -10,7 +10,11 @@
|
|
|
10
10
|
* - Critical-importance facts are auto-approved.
|
|
11
11
|
* - Batches respect extractionJudgeBatchSize.
|
|
12
12
|
* - Content-hash caching avoids redundant LLM calls.
|
|
13
|
-
* -
|
|
13
|
+
* - LLM calls use extractionJudgeTimeoutMs (default 20s: measured 2.1-2.5 s
|
|
14
|
+
* per compact verdict request with a batch-scaled completion budget on a
|
|
15
|
+
* small fast judge model; 13-22 s per compact call, 41 s verbose, on the
|
|
16
|
+
* big shared core model — raise it there; the old 150s default only
|
|
17
|
+
* papered over 59-305s verbose replies).
|
|
14
18
|
*/
|
|
15
19
|
|
|
16
20
|
import { createHash } from "node:crypto";
|
|
@@ -215,48 +219,79 @@ export const JUDGE_SYSTEM_PROMPT = `You are a memory curator evaluating whether
|
|
|
215
219
|
|
|
216
220
|
A fact is **durable** if it will still be useful 30+ days from now and is relevant across multiple sessions, not just the current task.
|
|
217
221
|
|
|
218
|
-
|
|
222
|
+
The user message is a JSON array of candidates: [{"index": 0, "text": "...", "category": "...", "confidence": 0.5}, ...].
|
|
219
223
|
|
|
220
|
-
|
|
221
|
-
- Personal preferences, identities, or
|
|
222
|
-
-
|
|
223
|
-
-
|
|
224
|
-
- Principles, rules, or constraints the user wants respected
|
|
225
|
-
- Stable facts about projects, tools, or workflows
|
|
226
|
-
- Commitments, deadlines, or obligations
|
|
224
|
+
Verdicts:
|
|
225
|
+
- "accept" — durable. Personal preferences, identities, relationships; decisions with rationale that affect future work; corrections to previously held beliefs; principles, rules, or constraints the user wants respected; stable facts about projects, tools, or workflows; commitments, deadlines, or obligations.
|
|
226
|
+
- "reject" — not durable. Transient task details ("currently debugging line 42"); ephemeral state ("the build is running now"); routine operations ("ran npm install"); conversational filler; anything stale within hours; step-by-step instructions for a one-time task.
|
|
227
|
+
- "defer" — MIGHT be durable but the candidate alone cannot say: ambiguous referents ("he said they'd follow up"), in-progress statements that may become durable once completed, future commitments with unclear subject or timeline. Defer is NOT a soft reject: use it only when another turn of context would genuinely change the verdict; when torn between accept and reject, lean accept — losing a useful fact is worse than keeping a marginal one.
|
|
227
228
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
- Ephemeral state ("the build is running now")
|
|
231
|
-
- Routine operations ("ran npm install")
|
|
232
|
-
- Conversational filler or acknowledgements
|
|
233
|
-
- Information that will be stale within hours
|
|
234
|
-
- Step-by-step instructions for a one-time task
|
|
229
|
+
Reply with a JSON object with a single "verdicts" key, exactly one object per input candidate:
|
|
230
|
+
{"verdicts": [{"index": <candidate index>, "kind": "accept" | "reject" | "defer"}]}
|
|
235
231
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
232
|
+
Rules:
|
|
233
|
+
1. The top level must be one object {"verdicts": [...]} — JSON mode rejects a bare top-level array.
|
|
234
|
+
2. Copy each object's "index" from the input candidate. One object per candidate, exactly.
|
|
235
|
+
3. No other fields — no "reason", no "durable", no commentary.
|
|
236
|
+
4. Output valid JSON only. No markdown fences.
|
|
237
|
+
5. ${OUTPUT_LANGUAGE_POLICY}
|
|
241
238
|
|
|
242
|
-
|
|
239
|
+
Example output:
|
|
240
|
+
{"verdicts": [{"index": 0, "kind": "accept"}, {"index": 1, "kind": "reject"}, {"index": 2, "kind": "defer"}]}`;
|
|
243
241
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
- reason: string (brief explanation, under 80 characters)
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
// Judge request assembly (PR 3264 follow-up)
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
248
245
|
|
|
249
|
-
|
|
246
|
+
/** Completion tokens reserved per candidate verdict (measured 13 on a small judge model). */
|
|
247
|
+
const JUDGE_TOKENS_PER_CANDIDATE = 24;
|
|
248
|
+
/** Ceiling on one judge reply: the old flat budget. */
|
|
249
|
+
export const MAX_JUDGE_TOKENS = 2048;
|
|
250
|
+
/**
|
|
251
|
+
* Extra completion tokens for a judge call routed through the Responses API. Reasoning tokens count against
|
|
252
|
+
* `max_output_tokens`, so the compact verdict budget alone can be spent before a reasoning-enabled model answers; the
|
|
253
|
+
* unparsed batch would then fail open as durable. A cap, not a target: a non-reasoning model still writes a short reply.
|
|
254
|
+
*/
|
|
255
|
+
export const JUDGE_REASONING_HEADROOM_TOKENS = 1024;
|
|
256
|
+
/** Largest batch whose verdict array fits MAX_JUDGE_TOKENS; a larger configured batch is split. */
|
|
257
|
+
export const MAX_JUDGE_BATCH = Math.floor(MAX_JUDGE_TOKENS / JUDGE_TOKENS_PER_CANDIDATE);
|
|
250
258
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
259
|
+
/**
|
|
260
|
+
* Compact completion budget for one judge call. A verdict is
|
|
261
|
+
* `{"index":12,"kind":"reject"}` (~14 tokens), so the old flat 2048 only gave
|
|
262
|
+
* a slow model room to produce 59-305s replies that hid inside the timeout.
|
|
263
|
+
* Scales with the batch size (about 24 tokens per candidate verdict, measured
|
|
264
|
+
* 13 per verdict on a small judge model) so a large batch is never truncated;
|
|
265
|
+
* capped at 2048, the old flat budget.
|
|
266
|
+
*/
|
|
267
|
+
export function judgeMaxTokens(batchSize: number): number {
|
|
268
|
+
return Math.min(MAX_JUDGE_TOKENS, Math.max(64, batchSize * JUDGE_TOKENS_PER_CANDIDATE));
|
|
269
|
+
}
|
|
257
270
|
|
|
258
|
-
|
|
259
|
-
|
|
271
|
+
/** One indexed candidate entry in a judge request payload. */
|
|
272
|
+
export interface JudgeRequestEntry {
|
|
273
|
+
index: number;
|
|
274
|
+
text: string;
|
|
275
|
+
category: string;
|
|
276
|
+
confidence: number;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Full judge LLM request: messages plus the completion budget. */
|
|
280
|
+
export interface JudgeRequest {
|
|
281
|
+
messages: Array<{ role: "system" | "user"; content: string }>;
|
|
282
|
+
maxTokens: number;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Build a full judge request body: messages plus the completion budget. */
|
|
286
|
+
export function buildJudgeRequest(entries: readonly JudgeRequestEntry[]): JudgeRequest {
|
|
287
|
+
return {
|
|
288
|
+
messages: [
|
|
289
|
+
{ role: "system", content: JUDGE_SYSTEM_PROMPT },
|
|
290
|
+
{ role: "user", content: JSON.stringify(entries) },
|
|
291
|
+
],
|
|
292
|
+
maxTokens: judgeMaxTokens(entries.length),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
260
295
|
|
|
261
296
|
// ---------------------------------------------------------------------------
|
|
262
297
|
// Content-hash cache (in-memory, per-process fallback)
|
|
@@ -492,40 +527,39 @@ export async function judgeFactDurability(
|
|
|
492
527
|
}
|
|
493
528
|
|
|
494
529
|
// Batch the pending candidates up to batchSize
|
|
495
|
-
const batchSize = config.extractionJudgeBatchSize;
|
|
530
|
+
const batchSize = Math.min(config.extractionJudgeBatchSize, MAX_JUDGE_BATCH);
|
|
531
|
+
// Defer bookkeeping spans the WHOLE call, not one batch: identical candidates can land in different batches, and each key
|
|
532
|
+
// must advance the defer cap once and decide against the same prior count (the snapshot taken at its first sighting).
|
|
533
|
+
const deferredThisCall = new Set<string>();
|
|
534
|
+
const priorDefersSeen = new Map<string, number>();
|
|
496
535
|
for (let batchStart = 0; batchStart < pendingIndices.length; batchStart += batchSize) {
|
|
497
536
|
const batchIndices = pendingIndices.slice(batchStart, batchStart + batchSize);
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
537
|
+
const request = buildJudgeRequest(
|
|
538
|
+
batchIndices.map((idx) => ({
|
|
539
|
+
index: idx,
|
|
540
|
+
text: candidates[idx].text,
|
|
541
|
+
category: candidates[idx].category,
|
|
542
|
+
confidence: candidates[idx].confidence,
|
|
543
|
+
})),
|
|
544
|
+
);
|
|
506
545
|
|
|
507
546
|
try {
|
|
508
|
-
const llmResponse = await callJudgeLlm(
|
|
509
|
-
userPrompt,
|
|
510
|
-
config,
|
|
511
|
-
localLlm,
|
|
512
|
-
fallbackLlm,
|
|
513
|
-
);
|
|
547
|
+
const llmResponse = await callJudgeLlm(request, config, localLlm, fallbackLlm);
|
|
514
548
|
|
|
515
549
|
if (llmResponse) {
|
|
516
550
|
const parsed = parseJudgeResponse(llmResponse, batchIndices);
|
|
517
|
-
// Per-
|
|
551
|
+
// Per-call defer-increment dedupe (codex P2): a single extraction
|
|
518
552
|
// pass must not advance the cap more than once for identical
|
|
519
|
-
// candidate content, even if the same text appears
|
|
520
|
-
//
|
|
521
|
-
//
|
|
522
|
-
const deferredThisBatch = new Set<string>();
|
|
553
|
+
// candidate content, even if the same text appears several times in
|
|
554
|
+
// one LLM response or in different batches of one call. Duplicate
|
|
555
|
+
// hits share the first sighting's `priorDeferrals` snapshot.
|
|
523
556
|
for (const [idx, rawVerdict] of parsed.entries()) {
|
|
524
557
|
const c = candidates[idx];
|
|
525
558
|
const key = cacheKey(c.text, c.category);
|
|
526
559
|
let verdict = rawVerdict;
|
|
527
560
|
let source: JudgeVerdictObservation["source"] = "llm";
|
|
528
|
-
|
|
561
|
+
if (!priorDefersSeen.has(key)) priorDefersSeen.set(key, deferCountMap.get(key) ?? 0);
|
|
562
|
+
const priorDefers = priorDefersSeen.get(key) ?? 0;
|
|
529
563
|
|
|
530
564
|
// Defer cap (issue #562, PR 2). A candidate that has already been
|
|
531
565
|
// deferred `deferCap` times is forcibly rejected so a pathological
|
|
@@ -541,15 +575,15 @@ export async function judgeFactDurability(
|
|
|
541
575
|
// Only clear + count the cap conversion once per batch for
|
|
542
576
|
// this key — duplicates in the same response all resolve to
|
|
543
577
|
// reject but should not inflate the cap-rejection counter.
|
|
544
|
-
if (!
|
|
578
|
+
if (!deferredThisCall.has(key)) {
|
|
545
579
|
deferCountMap.delete(key);
|
|
546
580
|
deferredCappedToReject++;
|
|
547
|
-
|
|
581
|
+
deferredThisCall.add(key);
|
|
548
582
|
}
|
|
549
|
-
} else if (!
|
|
583
|
+
} else if (!deferredThisCall.has(key)) {
|
|
550
584
|
deferCountMap.set(key, priorDefers + 1);
|
|
551
585
|
deferred++;
|
|
552
|
-
|
|
586
|
+
deferredThisCall.add(key);
|
|
553
587
|
// Bound the per-process defer-counter map.
|
|
554
588
|
if (deferCountMap.size > DEFER_COUNT_MAX_SIZE) {
|
|
555
589
|
const drop = Math.floor(deferCountMap.size / 2);
|
|
@@ -561,12 +595,14 @@ export async function judgeFactDurability(
|
|
|
561
595
|
}
|
|
562
596
|
}
|
|
563
597
|
}
|
|
564
|
-
// else: duplicate defer for a key already counted this
|
|
598
|
+
// else: duplicate defer for a key already counted this call —
|
|
565
599
|
// no additional counter increment, verdict still marked defer.
|
|
566
600
|
} else {
|
|
567
601
|
// On accept/reject, clear any outstanding defer counter so a
|
|
568
602
|
// future reappearance of the same text starts fresh.
|
|
569
603
|
deferCountMap.delete(key);
|
|
604
|
+
priorDefersSeen.set(key, 0);
|
|
605
|
+
deferredThisCall.delete(key);
|
|
570
606
|
}
|
|
571
607
|
|
|
572
608
|
verdicts.set(idx, verdict);
|
|
@@ -607,6 +643,8 @@ export async function judgeFactDurability(
|
|
|
607
643
|
// telemetry event reflects the true state rather than always 0.
|
|
608
644
|
const priorDefers = deferCountMap.get(hash) ?? 0;
|
|
609
645
|
deferCountMap.delete(hash);
|
|
646
|
+
priorDefersSeen.set(hash, 0);
|
|
647
|
+
deferredThisCall.delete(hash);
|
|
610
648
|
const v: JudgeVerdict = {
|
|
611
649
|
durable: true,
|
|
612
650
|
reason: "Approved by default (judge unavailable or parse error)",
|
|
@@ -641,15 +679,12 @@ export async function judgeFactDurability(
|
|
|
641
679
|
// ---------------------------------------------------------------------------
|
|
642
680
|
|
|
643
681
|
async function callJudgeLlm(
|
|
644
|
-
|
|
682
|
+
request: JudgeRequest,
|
|
645
683
|
config: PluginConfig,
|
|
646
684
|
localLlm: LocalLlmClient | null,
|
|
647
685
|
fallbackLlm: FallbackLlmClient | null,
|
|
648
686
|
): Promise<string | null> {
|
|
649
|
-
const
|
|
650
|
-
{ role: "system", content: JUDGE_SYSTEM_PROMPT },
|
|
651
|
-
{ role: "user", content: userPrompt },
|
|
652
|
-
];
|
|
687
|
+
const { messages, maxTokens } = request;
|
|
653
688
|
|
|
654
689
|
const modelOverride = config.extractionJudgeModel || undefined;
|
|
655
690
|
|
|
@@ -669,9 +704,14 @@ async function callJudgeLlm(
|
|
|
669
704
|
try {
|
|
670
705
|
const result = await (localLlm as any).chatCompletion(messages, {
|
|
671
706
|
temperature: 0.1,
|
|
672
|
-
maxTokens
|
|
707
|
+
maxTokens,
|
|
708
|
+
// A verdict needs no reasoning: keep a thinking-capable model from spending the compact budget on it.
|
|
709
|
+
disableThinking: true,
|
|
673
710
|
responseFormat: { type: "json_object" },
|
|
674
|
-
timeoutMs:
|
|
711
|
+
timeoutMs: config.extractionJudgeTimeoutMs,
|
|
712
|
+
// The client applies timeoutMs per attempt and retries an abort once:
|
|
713
|
+
// one deadline signal bounds the whole call to the configured time.
|
|
714
|
+
signal: AbortSignal.timeout(config.extractionJudgeTimeoutMs),
|
|
675
715
|
operation: "extraction-judge",
|
|
676
716
|
...(modelOverride ? { model: modelOverride } : {}),
|
|
677
717
|
});
|
|
@@ -692,8 +732,9 @@ async function callJudgeLlm(
|
|
|
692
732
|
messages as Array<{ role: "system" | "user" | "assistant"; content: string }>,
|
|
693
733
|
{
|
|
694
734
|
temperature: 0.1,
|
|
695
|
-
maxTokens
|
|
696
|
-
|
|
735
|
+
maxTokens,
|
|
736
|
+
reasoningHeadroomTokens: JUDGE_REASONING_HEADROOM_TOKENS,
|
|
737
|
+
timeoutMs: config.extractionJudgeTimeoutMs,
|
|
697
738
|
...(modelOverride ? { model: modelOverride } : {}),
|
|
698
739
|
...gatewayChain,
|
|
699
740
|
},
|
|
@@ -711,6 +752,38 @@ async function callJudgeLlm(
|
|
|
711
752
|
return null;
|
|
712
753
|
}
|
|
713
754
|
|
|
755
|
+
interface JudgeVerdictItem {
|
|
756
|
+
index?: number;
|
|
757
|
+
kind?: string;
|
|
758
|
+
action?: string;
|
|
759
|
+
durable?: boolean;
|
|
760
|
+
reason?: string;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/** What a judge model may return: a bare array, or one object wrapping verdicts. */
|
|
764
|
+
interface JudgeEnvelope {
|
|
765
|
+
verdicts?: JudgeVerdictItem[];
|
|
766
|
+
index?: number;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
type ParsedJudgeJson = JudgeVerdictItem[] | JudgeEnvelope | null | undefined;
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Normalise the object shapes a judge model returns to a verdict array.
|
|
773
|
+
* Requested shape {"verdicts": [...]} (response_format json_object forces a
|
|
774
|
+
* top-level object); a single verdict object counts as one verdict at its
|
|
775
|
+
* index, never the whole batch; legacy: any array-valued key.
|
|
776
|
+
*/
|
|
777
|
+
function unwrapJudgeEnvelope(parsed: ParsedJudgeJson): JudgeVerdictItem[] | null {
|
|
778
|
+
if (!parsed || Array.isArray(parsed)) return null;
|
|
779
|
+
if (Array.isArray(parsed.verdicts)) return parsed.verdicts;
|
|
780
|
+
if (Number.isInteger(parsed.index)) return [parsed];
|
|
781
|
+
for (const v of Object.values(parsed)) {
|
|
782
|
+
if (Array.isArray(v)) return v;
|
|
783
|
+
}
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
786
|
+
|
|
714
787
|
// ---------------------------------------------------------------------------
|
|
715
788
|
// Response parsing
|
|
716
789
|
// ---------------------------------------------------------------------------
|
|
@@ -724,7 +797,7 @@ function parseJudgeResponse(
|
|
|
724
797
|
|
|
725
798
|
try {
|
|
726
799
|
// Try direct parse first, then fall back to JSON extraction
|
|
727
|
-
let parsed:
|
|
800
|
+
let parsed: ParsedJudgeJson;
|
|
728
801
|
try {
|
|
729
802
|
parsed = JSON.parse(raw);
|
|
730
803
|
} catch {
|
|
@@ -735,20 +808,12 @@ function parseJudgeResponse(
|
|
|
735
808
|
}
|
|
736
809
|
|
|
737
810
|
if (!Array.isArray(parsed)) {
|
|
738
|
-
|
|
739
|
-
if (
|
|
740
|
-
const values = Object.values(parsed as Record<string, unknown>);
|
|
741
|
-
for (const v of values) {
|
|
742
|
-
if (Array.isArray(v)) {
|
|
743
|
-
parsed = v;
|
|
744
|
-
break;
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
if (!Array.isArray(parsed)) {
|
|
811
|
+
const unwrapped = unwrapJudgeEnvelope(parsed);
|
|
812
|
+
if (unwrapped === null) {
|
|
749
813
|
log.debug("extraction-judge: response is not an array, cannot parse");
|
|
750
814
|
return result;
|
|
751
815
|
}
|
|
816
|
+
parsed = unwrapped;
|
|
752
817
|
}
|
|
753
818
|
|
|
754
819
|
for (const item of parsed) {
|
package/src/fallback-llm.test.ts
CHANGED
|
@@ -1453,6 +1453,57 @@ test("fallback llm uses the Responses API for openai-responses transports", { co
|
|
|
1453
1453
|
}
|
|
1454
1454
|
});
|
|
1455
1455
|
|
|
1456
|
+
test("fallback llm adds reasoningHeadroomTokens to max_output_tokens on the Responses API", { concurrency: false }, async () => {
|
|
1457
|
+
clearModelsJsonCache();
|
|
1458
|
+
clearSecretCache();
|
|
1459
|
+
|
|
1460
|
+
const llm = new FallbackLlmClient({
|
|
1461
|
+
agents: { defaults: { model: { primary: "responses-provider/demo-model" } } },
|
|
1462
|
+
models: {
|
|
1463
|
+
providers: {
|
|
1464
|
+
"responses-provider": {
|
|
1465
|
+
baseUrl: "https://responses.example/v1",
|
|
1466
|
+
api: "openai-responses",
|
|
1467
|
+
apiKey: "responses-key",
|
|
1468
|
+
models: [],
|
|
1469
|
+
},
|
|
1470
|
+
},
|
|
1471
|
+
},
|
|
1472
|
+
});
|
|
1473
|
+
|
|
1474
|
+
const originalFetch = globalThis.fetch;
|
|
1475
|
+
const bodies: Array<{ max_output_tokens?: number }> = [];
|
|
1476
|
+
// SAFETY: the stub returns the Response shape the client reads and nothing else.
|
|
1477
|
+
globalThis.fetch = (async (_url, init) => {
|
|
1478
|
+
bodies.push(JSON.parse(String(init?.body ?? "{}")));
|
|
1479
|
+
return new Response(JSON.stringify({ output_text: "ok" }), {
|
|
1480
|
+
status: 200,
|
|
1481
|
+
headers: { "Content-Type": "application/json" },
|
|
1482
|
+
});
|
|
1483
|
+
}) as typeof fetch;
|
|
1484
|
+
|
|
1485
|
+
try {
|
|
1486
|
+
const messages = [{ role: "user" as const, content: "Say OK" }];
|
|
1487
|
+
// Non-finite headroom is ignored, like a negative one.
|
|
1488
|
+
await llm.chatCompletion(messages, { temperature: 0, maxTokens: 8, reasoningHeadroomTokens: Number.NaN });
|
|
1489
|
+
await llm.chatCompletion(messages, { temperature: 0, maxTokens: 64, reasoningHeadroomTokens: 1024 });
|
|
1490
|
+
await llm.chatCompletion(messages, { temperature: 0, maxTokens: 64 });
|
|
1491
|
+
await llm.chatCompletion(messages, { temperature: 0, maxTokens: 64, reasoningHeadroomTokens: -5 });
|
|
1492
|
+
await llm.chatCompletion(messages, { temperature: 0, reasoningHeadroomTokens: 1024 });
|
|
1493
|
+
await llm.chatCompletion(messages, { temperature: 0, maxTokens: 64, reasoningHeadroomTokens: Number.POSITIVE_INFINITY });
|
|
1494
|
+
assert.equal(bodies[5].max_output_tokens, 64, "an infinite headroom adds nothing (it would serialize as null)");
|
|
1495
|
+
assert.equal(bodies[0].max_output_tokens, 8, "a non-finite headroom adds nothing");
|
|
1496
|
+
assert.equal(bodies[1].max_output_tokens, 64 + 1024, "headroom is added on top of the compact budget");
|
|
1497
|
+
assert.equal(bodies[2].max_output_tokens, 64, "no headroom option: exactly maxTokens, as before");
|
|
1498
|
+
assert.equal(bodies[3].max_output_tokens, 64, "a negative headroom adds nothing");
|
|
1499
|
+
assert.equal(bodies[4].max_output_tokens, 4096, "no maxTokens: the default budget is not inflated by headroom");
|
|
1500
|
+
} finally {
|
|
1501
|
+
globalThis.fetch = originalFetch;
|
|
1502
|
+
clearModelsJsonCache();
|
|
1503
|
+
clearSecretCache();
|
|
1504
|
+
}
|
|
1505
|
+
});
|
|
1506
|
+
|
|
1456
1507
|
test("fallback llm ignores echoed input_text blocks in responses output extraction", { concurrency: false }, async () => {
|
|
1457
1508
|
clearModelsJsonCache();
|
|
1458
1509
|
clearSecretCache();
|
|
@@ -2122,3 +2173,46 @@ test("timeout records TimeoutError on failureDiag (issue #2891)", { concurrency:
|
|
|
2122
2173
|
clearSecretCache();
|
|
2123
2174
|
}
|
|
2124
2175
|
});
|
|
2176
|
+
|
|
2177
|
+
test("fallback llm adds reasoningHeadroomTokens to the Chat Completions token limit too", { concurrency: false }, async () => {
|
|
2178
|
+
clearModelsJsonCache();
|
|
2179
|
+
clearSecretCache();
|
|
2180
|
+
|
|
2181
|
+
const llm = new FallbackLlmClient({
|
|
2182
|
+
agents: { defaults: { model: { primary: "chat-provider/demo-model" } } },
|
|
2183
|
+
models: {
|
|
2184
|
+
providers: {
|
|
2185
|
+
"chat-provider": {
|
|
2186
|
+
baseUrl: "https://chat.example/v1",
|
|
2187
|
+
api: "openai-completions",
|
|
2188
|
+
apiKey: "chat-key",
|
|
2189
|
+
models: [],
|
|
2190
|
+
},
|
|
2191
|
+
},
|
|
2192
|
+
},
|
|
2193
|
+
});
|
|
2194
|
+
|
|
2195
|
+
const originalFetch = globalThis.fetch;
|
|
2196
|
+
const bodies: Array<{ max_tokens?: number; max_completion_tokens?: number }> = [];
|
|
2197
|
+
// SAFETY: the stub returns the Chat Completions shape the client reads and nothing else.
|
|
2198
|
+
globalThis.fetch = (async (_url, init) => {
|
|
2199
|
+
bodies.push(JSON.parse(String(init?.body ?? "{}")));
|
|
2200
|
+
return new Response(JSON.stringify({ choices: [{ message: { content: "ok" } }] }), {
|
|
2201
|
+
status: 200,
|
|
2202
|
+
headers: { "Content-Type": "application/json" },
|
|
2203
|
+
});
|
|
2204
|
+
}) as typeof fetch;
|
|
2205
|
+
|
|
2206
|
+
try {
|
|
2207
|
+
const messages = [{ role: "user" as const, content: "Say OK" }];
|
|
2208
|
+
await llm.chatCompletion(messages, { temperature: 0, maxTokens: 64, reasoningHeadroomTokens: 1024 });
|
|
2209
|
+
await llm.chatCompletion(messages, { temperature: 0, maxTokens: 64 });
|
|
2210
|
+
const limit = (b: (typeof bodies)[number]) => b.max_completion_tokens ?? b.max_tokens;
|
|
2211
|
+
assert.equal(limit(bodies[0]), 64 + 1024, "headroom is added on top of the compact budget");
|
|
2212
|
+
assert.equal(limit(bodies[1]), 64, "no headroom option: exactly maxTokens, as before");
|
|
2213
|
+
} finally {
|
|
2214
|
+
globalThis.fetch = originalFetch;
|
|
2215
|
+
clearModelsJsonCache();
|
|
2216
|
+
clearSecretCache();
|
|
2217
|
+
}
|
|
2218
|
+
});
|
package/src/fallback-llm.ts
CHANGED
|
@@ -46,6 +46,7 @@ export {
|
|
|
46
46
|
export interface FallbackLlmOptions {
|
|
47
47
|
temperature?: number;
|
|
48
48
|
maxTokens?: number;
|
|
49
|
+
reasoningHeadroomTokens?: number; // Responses API only: extra max_output_tokens for reasoning (they count against it); unset adds none
|
|
49
50
|
timeoutMs?: number;
|
|
50
51
|
signal?: AbortSignal;
|
|
51
52
|
/** Set the Responses API storage policy. Other transports ignore this value. */
|
|
@@ -764,17 +765,16 @@ export class FallbackLlmClient {
|
|
|
764
765
|
return await this.callOllamaChat(effectiveConfig, model.modelId, messages, options);
|
|
765
766
|
}
|
|
766
767
|
|
|
768
|
+
const wanted = Math.floor(options.reasoningHeadroomTokens ?? 0);
|
|
769
|
+
const headroom = Number.isFinite(wanted) ? Math.max(0, wanted) : 0; // NaN and Infinity add nothing
|
|
770
|
+
const capped = options.maxTokens === undefined ? options : { ...options, maxTokens: options.maxTokens + headroom };
|
|
771
|
+
|
|
767
772
|
if (
|
|
768
773
|
model.providerConfig.api === "openai-responses" ||
|
|
769
774
|
model.providerConfig.api === "openai-codex-responses" ||
|
|
770
775
|
model.providerConfig.api === "azure-openai-responses"
|
|
771
776
|
) {
|
|
772
|
-
return await this.callOpenAIResponses(
|
|
773
|
-
effectiveConfig,
|
|
774
|
-
model.modelId,
|
|
775
|
-
messages,
|
|
776
|
-
options,
|
|
777
|
-
);
|
|
777
|
+
return await this.callOpenAIResponses(effectiveConfig, model.modelId, messages, capped);
|
|
778
778
|
}
|
|
779
779
|
|
|
780
780
|
// For OpenAI-compatible chat-completions APIs (openai-completions,
|
|
@@ -785,7 +785,7 @@ export class FallbackLlmClient {
|
|
|
785
785
|
effectiveConfig,
|
|
786
786
|
model.modelId,
|
|
787
787
|
messages,
|
|
788
|
-
|
|
788
|
+
capped,
|
|
789
789
|
shouldAssumeOpenAiChatCompletions(effectiveConfig.baseUrl),
|
|
790
790
|
);
|
|
791
791
|
}
|
package/src/types.ts
CHANGED
|
@@ -1299,6 +1299,18 @@ export interface PluginConfig
|
|
|
1299
1299
|
extractionJudgeModel: string;
|
|
1300
1300
|
/** Maximum number of candidate facts per judge LLM batch call. */
|
|
1301
1301
|
extractionJudgeBatchSize: number;
|
|
1302
|
+
/**
|
|
1303
|
+
* Per-call timeout for the judge LLM (ms). Default 20000: measured 2.1-2.5 s
|
|
1304
|
+
* per compact verdict call (completion budget 24 tokens per candidate, up
|
|
1305
|
+
* to 2048) on a small fast judge model; the big shared core model measured
|
|
1306
|
+
* 13-22 s per compact call, 41 s verbose — raise it there. The earlier 150s
|
|
1307
|
+
* default only papered over 59-305s verbose replies; sub-second budgets
|
|
1308
|
+
* abort every call. On the local LLM path each attempt lasts
|
|
1309
|
+
* min(localLlmTimeoutMs, extractionJudgeTimeoutMs) (localLlmTimeoutMs
|
|
1310
|
+
* defaults to 180000), so a value above localLlmTimeoutMs does not lengthen
|
|
1311
|
+
* an attempt: raise both settings for a slower judge model.
|
|
1312
|
+
*/
|
|
1313
|
+
extractionJudgeTimeoutMs: number;
|
|
1302
1314
|
/** Shadow mode: log judge verdicts but do not filter facts. Default false. */
|
|
1303
1315
|
extractionJudgeShadow: boolean;
|
|
1304
1316
|
/**
|