@remnic/core 10.0.26 → 10.0.28
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 +28 -28
- package/dist/access-authorization-probe.js +29 -29
- package/dist/access-boundary.js +28 -28
- package/dist/access-cli.js +74 -74
- package/dist/access-extraction-force-flush.js +28 -28
- package/dist/access-http-briefing.js +29 -29
- package/dist/access-http-query.js +30 -30
- package/dist/access-http.js +45 -45
- package/dist/access-identity-continuity-surface.js +28 -28
- package/dist/access-lcm-surface.js +28 -28
- package/dist/access-mcp.js +38 -38
- package/dist/access-namespace-preflight.js +28 -28
- package/dist/access-observe-write-surface.js +28 -28
- package/dist/access-operations-batch.js +29 -29
- package/dist/access-operations.js +36 -36
- package/dist/access-recall-concurrency.js +28 -28
- package/dist/access-recall-diagnostics.js +2 -2
- package/dist/access-recall-response.js +28 -28
- package/dist/access-recall-surface.js +28 -28
- package/dist/access-service.js +28 -28
- package/dist/access-surface-catalog.js +30 -30
- package/dist/active-recall.js +5 -5
- package/dist/{auto-sync-7WWAKW6F.js → auto-sync-UQESPLCB.js} +5 -5
- package/dist/briefing.js +6 -6
- package/dist/buffer.js +3 -3
- package/dist/calibration.js +5 -5
- package/dist/causal-consolidation.js +7 -7
- package/dist/{chunk-AAQA6ZUP.js → chunk-23ZLN5X6.js} +13 -2
- package/dist/{chunk-AAQA6ZUP.js.map → chunk-23ZLN5X6.js.map} +1 -1
- package/dist/{chunk-2NQD4N42.js → chunk-2TGDT37F.js} +10 -10
- package/dist/{chunk-QNKEMFZY.js → chunk-2WDMUQOH.js} +2 -2
- package/dist/{chunk-B2BAZPVQ.js → chunk-364M2T3D.js} +2 -2
- package/dist/{chunk-D3I4P2SX.js → chunk-3EE7EHFX.js} +2 -2
- package/dist/{chunk-XSEVBFMD.js → chunk-3EJQXF3B.js} +22 -7
- package/dist/chunk-3EJQXF3B.js.map +1 -0
- package/dist/{chunk-E2I5FVEU.js → chunk-3LRBJQZE.js} +2 -2
- package/dist/{chunk-MRRLXXWA.js → chunk-3Z5LSDKR.js} +2 -2
- package/dist/{chunk-5ALVWXQG.js → chunk-4DQ4VEIQ.js} +2 -2
- package/dist/{chunk-DIHJ2QWP.js → chunk-4GGTC2AX.js} +2 -2
- package/dist/{chunk-HGSERGFX.js → chunk-4LJSCW24.js} +2 -2
- package/dist/{chunk-7RTNJ6M2.js → chunk-4QPW6JVI.js} +2 -2
- package/dist/{chunk-BO3F4X5U.js → chunk-67F6SBCJ.js} +2 -2
- package/dist/{chunk-LO76EF3Y.js → chunk-7EJCAFMS.js} +2 -2
- package/dist/{chunk-YUZRI3QU.js → chunk-AN3G5DZH.js} +2 -2
- package/dist/{chunk-ZXVLOKN4.js → chunk-AUPL775F.js} +2 -2
- package/dist/{chunk-XE5QLP3P.js → chunk-B6VPA3PF.js} +5 -5
- package/dist/{chunk-FT4UQYOV.js → chunk-BDLOEODO.js} +2 -2
- package/dist/{chunk-MKP4W52S.js → chunk-C2J4LBNB.js} +3 -3
- package/dist/{chunk-HNZTKT4J.js → chunk-C5JXYJHN.js} +3 -3
- package/dist/{chunk-JT7GSF3T.js → chunk-CQBNFDSG.js} +2 -2
- package/dist/{chunk-EQAIXC4Q.js → chunk-CRVL6SLC.js} +2 -2
- package/dist/{chunk-EP6BA7VV.js → chunk-CU5TILGD.js} +2 -5
- package/dist/chunk-CU5TILGD.js.map +1 -0
- package/dist/{chunk-2DT7PIDZ.js → chunk-EPOYPPOW.js} +2 -2
- package/dist/{chunk-NJAFC244.js → chunk-FDPE6OIK.js} +2 -2
- package/dist/{chunk-JNLEJE4A.js → chunk-GEHIRZXF.js} +2 -2
- package/dist/{chunk-V7KGDLBC.js → chunk-GQ7VWY6N.js} +2 -2
- package/dist/{chunk-IXUEFTK4.js → chunk-GV2V4CX6.js} +2 -2
- package/dist/{chunk-3VRMUNWL.js → chunk-HJ444KNS.js} +15 -7
- package/dist/chunk-HJ444KNS.js.map +1 -0
- package/dist/{chunk-I75TTE7D.js → chunk-HZKDYR3Z.js} +2 -2
- package/dist/{chunk-CT5YXLVB.js → chunk-I4MYLDRY.js} +2 -2
- package/dist/{chunk-ECBAENW7.js → chunk-IP5QGSNH.js} +12 -12
- package/dist/{chunk-G7QUQILS.js → chunk-J4PILQJG.js} +2 -2
- package/dist/{chunk-OL7LAGBG.js → chunk-JCPR47NY.js} +2 -2
- package/dist/{chunk-Z2XUE3VT.js → chunk-JSFU7AFZ.js} +2 -2
- package/dist/{chunk-B45WUQUU.js → chunk-JTLXVXYH.js} +2 -2
- package/dist/{chunk-HWMK3QHK.js → chunk-KNWC2EPP.js} +4 -4
- package/dist/{chunk-42KSEX6C.js → chunk-LDXNKEYB.js} +1 -1
- package/dist/chunk-LDXNKEYB.js.map +1 -0
- package/dist/{chunk-PIN5OPBA.js → chunk-LVU7FP27.js} +1 -1
- package/dist/{chunk-BGG5BP3M.js → chunk-LW6UHNWA.js} +2 -2
- package/dist/{chunk-BWOLINYY.js → chunk-MIIZAEJD.js} +2 -2
- package/dist/{chunk-QM2SPLKQ.js → chunk-MLED254J.js} +1 -1
- package/dist/chunk-MLED254J.js.map +1 -0
- package/dist/{chunk-HJQXHLQQ.js → chunk-MROA6OFU.js} +2 -2
- package/dist/{chunk-DFL3MBL5.js → chunk-NDZ7APNY.js} +3 -3
- package/dist/{chunk-DF2G4IXN.js → chunk-NPO4ZQFY.js} +3 -3
- package/dist/{chunk-X3ZLVGGX.js → chunk-NRG67KMO.js} +3 -3
- package/dist/{chunk-VBINQDWP.js → chunk-NRLWHZVS.js} +4 -4
- package/dist/{chunk-Q35L7TYB.js → chunk-NYPQHFC3.js} +2 -2
- package/dist/{chunk-G2OYFTUD.js → chunk-O6S5FLZB.js} +2 -2
- package/dist/{chunk-4B7JO3LG.js → chunk-OGOADX26.js} +2 -2
- package/dist/{chunk-US4WUDQR.js → chunk-OKNB2II7.js} +2 -2
- package/dist/{chunk-5BNDPLGI.js → chunk-P3NXZAV7.js} +2 -2
- package/dist/{chunk-KD73Q7EN.js → chunk-PCSPXMPD.js} +2 -2
- package/dist/{chunk-NKGI7JWP.js → chunk-PDN6MRWF.js} +2 -2
- package/dist/{chunk-YBO2WGCV.js → chunk-PM72NSEW.js} +2 -2
- package/dist/{chunk-KDMZZSL4.js → chunk-PRUXE3RC.js} +2 -2
- package/dist/{chunk-DUPHIZBY.js → chunk-PUDYANAX.js} +2 -2
- package/dist/{chunk-SA7NPDWJ.js → chunk-QLI74KWK.js} +9 -7
- package/dist/chunk-QLI74KWK.js.map +1 -0
- package/dist/{chunk-QP5GYLMS.js → chunk-QMYB3N7X.js} +20 -20
- package/dist/{chunk-FI6CUYMJ.js → chunk-RKB4JZEM.js} +34 -34
- package/dist/{chunk-3F3HA3R2.js → chunk-RN7HPFYS.js} +2 -2
- package/dist/{chunk-H53F22SJ.js → chunk-RQUF3FXK.js} +3 -3
- package/dist/{chunk-H3AW6QZF.js → chunk-RRJ462HP.js} +2 -2
- package/dist/{chunk-CZURNXGO.js → chunk-SSFO7B7H.js} +2 -2
- package/dist/{chunk-TRG3A2AW.js → chunk-SUZG7UUK.js} +2 -2
- package/dist/{chunk-ROF3JORU.js → chunk-TZZFGR4E.js} +2 -2
- package/dist/{chunk-TKC5LZ43.js → chunk-UIDWASOS.js} +2 -2
- package/dist/{chunk-M47LM554.js → chunk-UTGHW2Y5.js} +2 -2
- package/dist/{chunk-7BFE2UD4.js → chunk-XAJH5H3Z.js} +2 -2
- package/dist/{chunk-5A4QMATS.js → chunk-XALR4DOQ.js} +3 -3
- package/dist/{chunk-UFUWKKCV.js → chunk-XN6ABQGM.js} +21 -21
- package/dist/{chunk-CJRENHVZ.js → chunk-XNAOCY67.js} +2 -2
- package/dist/{chunk-DVE7B2IR.js → chunk-XOSCGZEV.js} +2 -2
- package/dist/{chunk-CA35LZAE.js → chunk-XOWR3I2W.js} +7 -7
- package/dist/cli.js +56 -56
- package/dist/coding/export-okf-codegraph.js +5 -5
- package/dist/compounding/engine.js +7 -7
- package/dist/config.js +5 -5
- package/dist/connectors/codex-materialize-runner.js +6 -6
- package/dist/connectors/index.js +6 -6
- package/dist/convergence-refresh.js +8 -8
- package/dist/corpus-watermark.js +8 -8
- package/dist/deep-recall-policy-llm.js +3 -3
- package/dist/entity-retrieval.js +6 -6
- package/dist/external-wiki-access.js +29 -29
- package/dist/extraction-faithfulness.js +3 -3
- package/dist/extraction-judge.js +3 -3
- package/dist/extraction-span-wire.d.ts +8 -0
- package/dist/extraction-span-wire.d.ts.map +1 -1
- package/dist/extraction-span-wire.js +5 -3
- package/dist/extraction.js +14 -14
- package/dist/fallback-llm-codex-subscription.d.ts.map +1 -1
- package/dist/fallback-llm-codex-subscription.js +1 -1
- package/dist/fallback-llm.js +2 -2
- package/dist/harmonic-retrieval.js +6 -6
- package/dist/index.js +74 -74
- package/dist/local-llm-helpers.d.ts +2 -1
- package/dist/local-llm-helpers.d.ts.map +1 -1
- package/dist/local-llm-helpers.js +1 -1
- package/dist/local-llm-rate-limit.d.ts +3 -1
- package/dist/local-llm-rate-limit.d.ts.map +1 -1
- package/dist/local-llm-rate-limit.js +1 -1
- package/dist/local-llm.d.ts.map +1 -1
- package/dist/local-llm.js +7 -3
- package/dist/location/index.js +8 -8
- package/dist/maintenance/memory-governance.js +6 -6
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +6 -6
- package/dist/maintenance/rebuild-memory-projection-runner.js +8 -8
- package/dist/maintenance/rebuild-memory-projection.js +7 -7
- package/dist/memory-browse-operations.js +29 -29
- package/dist/memory-browse.js +3 -3
- package/dist/namespaces/migrate.js +7 -7
- package/dist/namespaces/storage.js +6 -6
- package/dist/operator-doctor-corpus.js +9 -9
- package/dist/operator-toolkit.js +14 -14
- package/dist/orchestration/maintenance.js +19 -19
- package/dist/orchestrator.js +74 -74
- 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 +29 -29
- package/dist/recall-navigation.js +28 -28
- package/dist/recall-planner-llm.js +2 -2
- 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 +5 -5
- package/dist/semantic-consolidation.js +7 -7
- package/dist/semantic-rule-promotion.js +6 -6
- package/dist/semantic-rule-verifier.js +6 -6
- package/dist/source-agent-qualifier.js +9 -9
- package/dist/standup/build.js +6 -6
- package/dist/storage.js +5 -5
- package/dist/summarizer.js +6 -6
- package/dist/support-passport/index.js +31 -31
- package/dist/transfer/export-okf.js +6 -6
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/verified-recall.js +6 -6
- package/dist/who-knows-cli.js +7 -7
- package/dist/who-knows.js +6 -6
- package/package.json +2 -2
- package/src/ambient-provenance.test.ts +21 -3
- package/src/extraction-span-wire.ts +11 -0
- package/src/extraction.ts +2 -2
- package/src/fallback-llm-codex-subscription.ts +6 -9
- package/src/local-llm-helpers.ts +2 -1
- package/src/local-llm-json-schema-fallback.test.ts +278 -0
- package/src/local-llm-rate-limit.test.ts +59 -1
- package/src/local-llm-rate-limit.ts +22 -7
- package/src/local-llm.ts +22 -4
- package/src/types.ts +1 -1
- package/dist/chunk-3VRMUNWL.js.map +0 -1
- package/dist/chunk-42KSEX6C.js.map +0 -1
- package/dist/chunk-EP6BA7VV.js.map +0 -1
- package/dist/chunk-QM2SPLKQ.js.map +0 -1
- package/dist/chunk-SA7NPDWJ.js.map +0 -1
- package/dist/chunk-XSEVBFMD.js.map +0 -1
- /package/dist/{auto-sync-7WWAKW6F.js.map → auto-sync-UQESPLCB.js.map} +0 -0
- /package/dist/{chunk-2NQD4N42.js.map → chunk-2TGDT37F.js.map} +0 -0
- /package/dist/{chunk-QNKEMFZY.js.map → chunk-2WDMUQOH.js.map} +0 -0
- /package/dist/{chunk-B2BAZPVQ.js.map → chunk-364M2T3D.js.map} +0 -0
- /package/dist/{chunk-D3I4P2SX.js.map → chunk-3EE7EHFX.js.map} +0 -0
- /package/dist/{chunk-E2I5FVEU.js.map → chunk-3LRBJQZE.js.map} +0 -0
- /package/dist/{chunk-MRRLXXWA.js.map → chunk-3Z5LSDKR.js.map} +0 -0
- /package/dist/{chunk-5ALVWXQG.js.map → chunk-4DQ4VEIQ.js.map} +0 -0
- /package/dist/{chunk-DIHJ2QWP.js.map → chunk-4GGTC2AX.js.map} +0 -0
- /package/dist/{chunk-HGSERGFX.js.map → chunk-4LJSCW24.js.map} +0 -0
- /package/dist/{chunk-7RTNJ6M2.js.map → chunk-4QPW6JVI.js.map} +0 -0
- /package/dist/{chunk-BO3F4X5U.js.map → chunk-67F6SBCJ.js.map} +0 -0
- /package/dist/{chunk-LO76EF3Y.js.map → chunk-7EJCAFMS.js.map} +0 -0
- /package/dist/{chunk-YUZRI3QU.js.map → chunk-AN3G5DZH.js.map} +0 -0
- /package/dist/{chunk-ZXVLOKN4.js.map → chunk-AUPL775F.js.map} +0 -0
- /package/dist/{chunk-XE5QLP3P.js.map → chunk-B6VPA3PF.js.map} +0 -0
- /package/dist/{chunk-FT4UQYOV.js.map → chunk-BDLOEODO.js.map} +0 -0
- /package/dist/{chunk-MKP4W52S.js.map → chunk-C2J4LBNB.js.map} +0 -0
- /package/dist/{chunk-HNZTKT4J.js.map → chunk-C5JXYJHN.js.map} +0 -0
- /package/dist/{chunk-JT7GSF3T.js.map → chunk-CQBNFDSG.js.map} +0 -0
- /package/dist/{chunk-EQAIXC4Q.js.map → chunk-CRVL6SLC.js.map} +0 -0
- /package/dist/{chunk-2DT7PIDZ.js.map → chunk-EPOYPPOW.js.map} +0 -0
- /package/dist/{chunk-NJAFC244.js.map → chunk-FDPE6OIK.js.map} +0 -0
- /package/dist/{chunk-JNLEJE4A.js.map → chunk-GEHIRZXF.js.map} +0 -0
- /package/dist/{chunk-V7KGDLBC.js.map → chunk-GQ7VWY6N.js.map} +0 -0
- /package/dist/{chunk-IXUEFTK4.js.map → chunk-GV2V4CX6.js.map} +0 -0
- /package/dist/{chunk-I75TTE7D.js.map → chunk-HZKDYR3Z.js.map} +0 -0
- /package/dist/{chunk-CT5YXLVB.js.map → chunk-I4MYLDRY.js.map} +0 -0
- /package/dist/{chunk-ECBAENW7.js.map → chunk-IP5QGSNH.js.map} +0 -0
- /package/dist/{chunk-G7QUQILS.js.map → chunk-J4PILQJG.js.map} +0 -0
- /package/dist/{chunk-OL7LAGBG.js.map → chunk-JCPR47NY.js.map} +0 -0
- /package/dist/{chunk-Z2XUE3VT.js.map → chunk-JSFU7AFZ.js.map} +0 -0
- /package/dist/{chunk-B45WUQUU.js.map → chunk-JTLXVXYH.js.map} +0 -0
- /package/dist/{chunk-HWMK3QHK.js.map → chunk-KNWC2EPP.js.map} +0 -0
- /package/dist/{chunk-PIN5OPBA.js.map → chunk-LVU7FP27.js.map} +0 -0
- /package/dist/{chunk-BGG5BP3M.js.map → chunk-LW6UHNWA.js.map} +0 -0
- /package/dist/{chunk-BWOLINYY.js.map → chunk-MIIZAEJD.js.map} +0 -0
- /package/dist/{chunk-HJQXHLQQ.js.map → chunk-MROA6OFU.js.map} +0 -0
- /package/dist/{chunk-DFL3MBL5.js.map → chunk-NDZ7APNY.js.map} +0 -0
- /package/dist/{chunk-DF2G4IXN.js.map → chunk-NPO4ZQFY.js.map} +0 -0
- /package/dist/{chunk-X3ZLVGGX.js.map → chunk-NRG67KMO.js.map} +0 -0
- /package/dist/{chunk-VBINQDWP.js.map → chunk-NRLWHZVS.js.map} +0 -0
- /package/dist/{chunk-Q35L7TYB.js.map → chunk-NYPQHFC3.js.map} +0 -0
- /package/dist/{chunk-G2OYFTUD.js.map → chunk-O6S5FLZB.js.map} +0 -0
- /package/dist/{chunk-4B7JO3LG.js.map → chunk-OGOADX26.js.map} +0 -0
- /package/dist/{chunk-US4WUDQR.js.map → chunk-OKNB2II7.js.map} +0 -0
- /package/dist/{chunk-5BNDPLGI.js.map → chunk-P3NXZAV7.js.map} +0 -0
- /package/dist/{chunk-KD73Q7EN.js.map → chunk-PCSPXMPD.js.map} +0 -0
- /package/dist/{chunk-NKGI7JWP.js.map → chunk-PDN6MRWF.js.map} +0 -0
- /package/dist/{chunk-YBO2WGCV.js.map → chunk-PM72NSEW.js.map} +0 -0
- /package/dist/{chunk-KDMZZSL4.js.map → chunk-PRUXE3RC.js.map} +0 -0
- /package/dist/{chunk-DUPHIZBY.js.map → chunk-PUDYANAX.js.map} +0 -0
- /package/dist/{chunk-QP5GYLMS.js.map → chunk-QMYB3N7X.js.map} +0 -0
- /package/dist/{chunk-FI6CUYMJ.js.map → chunk-RKB4JZEM.js.map} +0 -0
- /package/dist/{chunk-3F3HA3R2.js.map → chunk-RN7HPFYS.js.map} +0 -0
- /package/dist/{chunk-H53F22SJ.js.map → chunk-RQUF3FXK.js.map} +0 -0
- /package/dist/{chunk-H3AW6QZF.js.map → chunk-RRJ462HP.js.map} +0 -0
- /package/dist/{chunk-CZURNXGO.js.map → chunk-SSFO7B7H.js.map} +0 -0
- /package/dist/{chunk-TRG3A2AW.js.map → chunk-SUZG7UUK.js.map} +0 -0
- /package/dist/{chunk-ROF3JORU.js.map → chunk-TZZFGR4E.js.map} +0 -0
- /package/dist/{chunk-TKC5LZ43.js.map → chunk-UIDWASOS.js.map} +0 -0
- /package/dist/{chunk-M47LM554.js.map → chunk-UTGHW2Y5.js.map} +0 -0
- /package/dist/{chunk-7BFE2UD4.js.map → chunk-XAJH5H3Z.js.map} +0 -0
- /package/dist/{chunk-5A4QMATS.js.map → chunk-XALR4DOQ.js.map} +0 -0
- /package/dist/{chunk-UFUWKKCV.js.map → chunk-XN6ABQGM.js.map} +0 -0
- /package/dist/{chunk-CJRENHVZ.js.map → chunk-XNAOCY67.js.map} +0 -0
- /package/dist/{chunk-DVE7B2IR.js.map → chunk-XOSCGZEV.js.map} +0 -0
- /package/dist/{chunk-CA35LZAE.js.map → chunk-XOWR3I2W.js.map} +0 -0
package/dist/who-knows-cli.js
CHANGED
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
renderWhoKnows,
|
|
5
5
|
renderWhoKnowsJson,
|
|
6
6
|
renderWhoKnowsText
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-4LJSCW24.js";
|
|
8
|
+
import "./chunk-OGOADX26.js";
|
|
9
|
+
import "./chunk-XALR4DOQ.js";
|
|
10
10
|
import "./chunk-UHVXE24Y.js";
|
|
11
11
|
import "./chunk-TRMJ6BYC.js";
|
|
12
12
|
import "./chunk-V4JZCTMK.js";
|
|
@@ -31,7 +31,7 @@ import "./chunk-S2HLHAZ4.js";
|
|
|
31
31
|
import "./chunk-MRPOZBJC.js";
|
|
32
32
|
import "./chunk-DM2T26WE.js";
|
|
33
33
|
import "./chunk-LDXUBPMO.js";
|
|
34
|
-
import "./chunk-
|
|
34
|
+
import "./chunk-EPOYPPOW.js";
|
|
35
35
|
import "./chunk-AAD2ZS6P.js";
|
|
36
36
|
import "./chunk-FF6N7BXC.js";
|
|
37
37
|
import "./chunk-JNIVZNIB.js";
|
|
@@ -60,11 +60,11 @@ import "./chunk-IUM3VSHI.js";
|
|
|
60
60
|
import "./chunk-C64XATK7.js";
|
|
61
61
|
import "./chunk-42JKGUFJ.js";
|
|
62
62
|
import "./chunk-23G5P7TA.js";
|
|
63
|
-
import "./chunk-
|
|
63
|
+
import "./chunk-UIDWASOS.js";
|
|
64
64
|
import "./chunk-I55KAVET.js";
|
|
65
65
|
import "./chunk-L2EXJQJP.js";
|
|
66
66
|
import "./chunk-45GF7Y3V.js";
|
|
67
|
-
import "./chunk-
|
|
67
|
+
import "./chunk-CU5TILGD.js";
|
|
68
68
|
import "./chunk-E2AWJ7KQ.js";
|
|
69
69
|
import "./chunk-Z7L72ZHL.js";
|
|
70
70
|
import "./chunk-4O4TKIXQ.js";
|
|
@@ -72,7 +72,7 @@ import "./chunk-YCVB7X56.js";
|
|
|
72
72
|
import "./chunk-PQVCH2AE.js";
|
|
73
73
|
import "./chunk-PJKTFXE2.js";
|
|
74
74
|
import "./chunk-W5BIQOAJ.js";
|
|
75
|
-
import "./chunk-
|
|
75
|
+
import "./chunk-LDXNKEYB.js";
|
|
76
76
|
import "./chunk-YBA52OKG.js";
|
|
77
77
|
import "./chunk-5LRV557D.js";
|
|
78
78
|
import "./chunk-BT7NVCML.js";
|
package/dist/who-knows.js
CHANGED
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
loadWhoKnowsEntities,
|
|
10
10
|
tokenizeTopic,
|
|
11
11
|
validateWhoKnowsInput
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-OGOADX26.js";
|
|
13
|
+
import "./chunk-XALR4DOQ.js";
|
|
14
14
|
import "./chunk-UHVXE24Y.js";
|
|
15
15
|
import "./chunk-TRMJ6BYC.js";
|
|
16
16
|
import "./chunk-V4JZCTMK.js";
|
|
@@ -35,7 +35,7 @@ import "./chunk-S2HLHAZ4.js";
|
|
|
35
35
|
import "./chunk-MRPOZBJC.js";
|
|
36
36
|
import "./chunk-DM2T26WE.js";
|
|
37
37
|
import "./chunk-LDXUBPMO.js";
|
|
38
|
-
import "./chunk-
|
|
38
|
+
import "./chunk-EPOYPPOW.js";
|
|
39
39
|
import "./chunk-AAD2ZS6P.js";
|
|
40
40
|
import "./chunk-FF6N7BXC.js";
|
|
41
41
|
import "./chunk-JNIVZNIB.js";
|
|
@@ -64,11 +64,11 @@ import "./chunk-IUM3VSHI.js";
|
|
|
64
64
|
import "./chunk-C64XATK7.js";
|
|
65
65
|
import "./chunk-42JKGUFJ.js";
|
|
66
66
|
import "./chunk-23G5P7TA.js";
|
|
67
|
-
import "./chunk-
|
|
67
|
+
import "./chunk-UIDWASOS.js";
|
|
68
68
|
import "./chunk-I55KAVET.js";
|
|
69
69
|
import "./chunk-L2EXJQJP.js";
|
|
70
70
|
import "./chunk-45GF7Y3V.js";
|
|
71
|
-
import "./chunk-
|
|
71
|
+
import "./chunk-CU5TILGD.js";
|
|
72
72
|
import "./chunk-E2AWJ7KQ.js";
|
|
73
73
|
import "./chunk-Z7L72ZHL.js";
|
|
74
74
|
import "./chunk-4O4TKIXQ.js";
|
|
@@ -76,7 +76,7 @@ import "./chunk-YCVB7X56.js";
|
|
|
76
76
|
import "./chunk-PQVCH2AE.js";
|
|
77
77
|
import "./chunk-PJKTFXE2.js";
|
|
78
78
|
import "./chunk-W5BIQOAJ.js";
|
|
79
|
-
import "./chunk-
|
|
79
|
+
import "./chunk-LDXNKEYB.js";
|
|
80
80
|
import "./chunk-YBA52OKG.js";
|
|
81
81
|
import "./chunk-5LRV557D.js";
|
|
82
82
|
import "./chunk-BT7NVCML.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/core",
|
|
3
|
-
"version": "10.0.
|
|
3
|
+
"version": "10.0.28",
|
|
4
4
|
"description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -3347,7 +3347,7 @@
|
|
|
3347
3347
|
"core"
|
|
3348
3348
|
],
|
|
3349
3349
|
"peerDependencies": {
|
|
3350
|
-
"@remnic/coding-graph": "^10.0.
|
|
3350
|
+
"@remnic/coding-graph": "^10.0.28"
|
|
3351
3351
|
},
|
|
3352
3352
|
"peerDependenciesMeta": {
|
|
3353
3353
|
"@remnic/coding-graph": {
|
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
import { parseConfig } from "./config.js";
|
|
12
12
|
import { bufferTurnsEqual, copyBufferTurn } from "./buffer-turn-helpers.js";
|
|
13
13
|
import { ExtractionEngine } from "./extraction.js";
|
|
14
|
+
import { extractionProviderJsonSchema } from "./extraction-span-wire.js";
|
|
15
|
+
import type { LocalLlmChatCompletionOptions } from "./local-llm-helpers.js";
|
|
14
16
|
import { SUMMARY_SYSTEM_PROMPT } from "./meetings/summary-extractor.js";
|
|
15
17
|
import { emptySpeakerRegistry } from "./wearables/speakers.js";
|
|
16
18
|
import { buildExtractionTurns } from "./wearables/memory-gen.js";
|
|
@@ -77,20 +79,26 @@ const MODEL_REGISTRY_FIXTURE = {
|
|
|
77
79
|
};
|
|
78
80
|
|
|
79
81
|
/** Run the local-LLM path, returning the prompt the model saw + the result. */
|
|
80
|
-
async function runLocal(turns: BufferTurn[]): Promise<{
|
|
82
|
+
async function runLocal(turns: BufferTurn[]): Promise<{
|
|
83
|
+
prompt: string;
|
|
84
|
+
result: ExtractionResult;
|
|
85
|
+
options: LocalLlmChatCompletionOptions;
|
|
86
|
+
}> {
|
|
81
87
|
const engine = new ExtractionEngine(
|
|
82
88
|
parseConfig({ localLlmEnabled: true, localLlmModel: "fixture-local", localLlmFallback: false }),
|
|
83
89
|
);
|
|
84
90
|
let prompt = "";
|
|
91
|
+
let options: LocalLlmChatCompletionOptions = {};
|
|
85
92
|
const localLlm = {
|
|
86
|
-
async chatCompletion(messages: ChatMessage[],
|
|
93
|
+
async chatCompletion(messages: ChatMessage[], seen: LocalLlmChatCompletionOptions = {}) {
|
|
87
94
|
prompt = messages[1]?.content ?? "";
|
|
95
|
+
options = seen;
|
|
88
96
|
return { content: JSON.stringify(HIGH_IMPACT_EXTRACTION) };
|
|
89
97
|
},
|
|
90
98
|
};
|
|
91
99
|
assert.equal(Reflect.set(engine, "localLlm", localLlm), true);
|
|
92
100
|
assert.equal(Reflect.set(engine, "modelRegistry", MODEL_REGISTRY_FIXTURE), true);
|
|
93
|
-
return { result: await engine.extract(turns), prompt };
|
|
101
|
+
return { result: await engine.extract(turns), prompt, options };
|
|
94
102
|
}
|
|
95
103
|
|
|
96
104
|
/** Run the gateway-fallback path. */
|
|
@@ -169,6 +177,16 @@ for (const [name, run] of PATHS) {
|
|
|
169
177
|
});
|
|
170
178
|
}
|
|
171
179
|
|
|
180
|
+
test("local extraction supplies the provider JSON schema as responseFormat with an object root", async () => {
|
|
181
|
+
const { options } = await runLocal([TYPED_TURN]);
|
|
182
|
+
const schema = extractionProviderJsonSchema("off");
|
|
183
|
+
assert.deepEqual(options.responseFormat, {
|
|
184
|
+
type: "json_schema",
|
|
185
|
+
json_schema: { name: "extraction_result", strict: false, schema },
|
|
186
|
+
});
|
|
187
|
+
assert.equal(schema.type, "object");
|
|
188
|
+
});
|
|
189
|
+
|
|
172
190
|
test("an ambient turn marked context-only does not trigger the ambient prompt", async () => {
|
|
173
191
|
const { prompt } = await runGateway([
|
|
174
192
|
{ ...AMBIENT_TURN, extractionContextOnly: true },
|
|
@@ -58,3 +58,14 @@ export function extractionProviderJsonSchema(
|
|
|
58
58
|
? EXTRACTION_PROVIDER_JSON_SCHEMA_WITHOUT_SPAN
|
|
59
59
|
: EXTRACTION_PROVIDER_JSON_SCHEMA_WITH_SPAN;
|
|
60
60
|
}
|
|
61
|
+
|
|
62
|
+
export function extractionProviderResponseFormat(spanMode: ExtractionSpanConfig["spanMode"] = "off") {
|
|
63
|
+
return {
|
|
64
|
+
type: "json_schema" as const,
|
|
65
|
+
json_schema: {
|
|
66
|
+
name: "extraction_result",
|
|
67
|
+
schema: extractionProviderJsonSchema(spanMode),
|
|
68
|
+
strict: false,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
package/src/extraction.ts
CHANGED
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
} from "./extraction-prompt.js";
|
|
55
55
|
import { containsExtractionPlaceholder, extractionAttributes, extractionCueAnchors, extractionText, isPlainRecord } from "./extraction-normalization.js";
|
|
56
56
|
import { applyExtractionSpanMaterialization, bindLocalExtractionPrompt, buildSpanMaterializeTurns, stripUntrustedFactSpans, truncateLocalExtractionConversation, type SpanMaterializeTurn } from "./extraction-span-materialize.js";
|
|
57
|
-
import { extractionProviderJsonSchema } from "./extraction-span-wire.js";
|
|
57
|
+
import { extractionProviderJsonSchema, extractionProviderResponseFormat } from "./extraction-span-wire.js";
|
|
58
58
|
import { resolveLocalLlmCapabilities, resolveMemoryLifecycleCapabilities, resolvePipelineProcessingCapabilities, resolveRecallAuxiliaryCapabilities } from "./capabilities.js";
|
|
59
59
|
type ExtractionQuestion = ExtractionResult["questions"][number];
|
|
60
60
|
type ExtractedFactResult = ExtractionResult["facts"][number];
|
|
@@ -1506,6 +1506,7 @@ ${truncatedConversation}`;
|
|
|
1506
1506
|
temperature: 0.1,
|
|
1507
1507
|
maxTokens: contextSizes.maxOutputTokens,
|
|
1508
1508
|
operation: "extraction",
|
|
1509
|
+
responseFormat: extractionProviderResponseFormat(this.config.extraction?.spanMode ?? "off"),
|
|
1509
1510
|
disableThinking: shouldEnableLocalExtractionThinking(this.config, conversation.length)
|
|
1510
1511
|
? false
|
|
1511
1512
|
: undefined,
|
|
@@ -1513,7 +1514,6 @@ ${truncatedConversation}`;
|
|
|
1513
1514
|
signal,
|
|
1514
1515
|
},
|
|
1515
1516
|
);
|
|
1516
|
-
|
|
1517
1517
|
if (!response?.content) {
|
|
1518
1518
|
log.debug("extractWithLocalLlm: chatCompletion returned null or empty content");
|
|
1519
1519
|
return null;
|
|
@@ -194,18 +194,15 @@ export function withCodexRuntimeShutdown<T extends { signal?: AbortSignal }>(
|
|
|
194
194
|
|
|
195
195
|
export function isUnsupportedJsonSchemaError(error: unknown): boolean {
|
|
196
196
|
const message = error instanceof Error ? error.message : String(error);
|
|
197
|
-
//
|
|
198
|
-
// "unsupported" (e.g. "'temperature' is unsupported with this model") must
|
|
199
|
-
// NOT trigger the schema-stripping retry, or we pay a duplicate request
|
|
200
|
-
// for unrelated provider errors.
|
|
197
|
+
// An echoed request format is not a capability rejection.
|
|
201
198
|
if (!/\b(?:400|404|422)\b/.test(message)) {
|
|
202
199
|
return false;
|
|
203
200
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
201
|
+
return (
|
|
202
|
+
/(?:response[_ ]?format|json[_ ]?schema|structured[_ ]?output)\b[^;,\n]{0,60}\b(?:unsupported|not supported|not implemented|not available)\b/i.test(message) ||
|
|
203
|
+
/\b(?:unsupported|unknown|unrecognized)(?:[\s:"'=]|parameter|argument|request|supplied|field|type|value){1,40}\b(?:response[_ ]?format|json[_ ]?schema|structured[_ ]?output|schema|format)\b/i.test(message) ||
|
|
204
|
+
/\b(?:does not|doesn't|cannot|can't) support(?:[\s:"'=]|the|native|type){1,20}\b(?:response[_ ]?format|json[_ ]?schema|structured[_ ]?output|schema|format)\b/i.test(message)
|
|
205
|
+
);
|
|
209
206
|
}
|
|
210
207
|
|
|
211
208
|
export function extractResponsesOutputText(data: {
|
package/src/local-llm-helpers.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import type { ResponseFormatJSONSchema } from "openai/resources/shared";
|
|
1
2
|
import { log } from "./logger.js";
|
|
2
3
|
import type { LocalLlmRequestPriority } from "./local-llm.js";
|
|
3
4
|
|
|
4
5
|
export interface LocalLlmChatCompletionOptions {
|
|
5
6
|
temperature?: number;
|
|
6
7
|
maxTokens?: number;
|
|
7
|
-
responseFormat?: { type: string };
|
|
8
|
+
responseFormat?: { type: string } | ResponseFormatJSONSchema;
|
|
8
9
|
timeoutMs?: number;
|
|
9
10
|
operation?: string;
|
|
10
11
|
forceDisableThinking?: boolean;
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import http from "node:http";
|
|
3
|
+
import type { AddressInfo } from "node:net";
|
|
4
|
+
import test, { mock } from "node:test";
|
|
5
|
+
|
|
6
|
+
import { LocalLlmClient } from "./local-llm.js";
|
|
7
|
+
import { ChatTransport } from "./local-llm-transport.js";
|
|
8
|
+
import { parseConfig } from "./config.js";
|
|
9
|
+
import type { LocalLlmChatCompletionOptions } from "./local-llm-helpers.js";
|
|
10
|
+
import type { PluginConfig } from "./types.js";
|
|
11
|
+
|
|
12
|
+
function createConfig(overrides: Partial<PluginConfig> = {}): PluginConfig {
|
|
13
|
+
return parseConfig({
|
|
14
|
+
localLlmEnabled: true,
|
|
15
|
+
localLlmModel: "test-local-model",
|
|
16
|
+
localLlmUrl: "http://127.0.0.1:1234",
|
|
17
|
+
localLlmTimeoutMs: 5_000,
|
|
18
|
+
localLlmRetry5xxCount: 0,
|
|
19
|
+
localLlmRateLimitRetryCount: 0,
|
|
20
|
+
localLlmRetryBackoffMs: 1,
|
|
21
|
+
localLlmHeaders: { "X-Test": "yes" },
|
|
22
|
+
localLlmApiKey: undefined,
|
|
23
|
+
localLlmAuthHeader: false,
|
|
24
|
+
localLlm400TripThreshold: 50,
|
|
25
|
+
localLlm400CooldownMs: 60_000,
|
|
26
|
+
debug: false,
|
|
27
|
+
localLlmReasoningEffort: "none",
|
|
28
|
+
slowLogEnabled: false,
|
|
29
|
+
slowLogThresholdMs: 60_000,
|
|
30
|
+
...overrides,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function primeClient(client: LocalLlmClient): void {
|
|
35
|
+
mock.method(client, "checkAvailability", async () => true);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface RecordedRequest {
|
|
39
|
+
headers: http.IncomingHttpHeaders;
|
|
40
|
+
body: { model: string; response_format?: LocalLlmChatCompletionOptions["responseFormat"] };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function startRecordingServer(
|
|
44
|
+
respond: (req: http.IncomingMessage, requestCount: number) => {
|
|
45
|
+
status: number;
|
|
46
|
+
body: unknown;
|
|
47
|
+
},
|
|
48
|
+
): Promise<{
|
|
49
|
+
url: string;
|
|
50
|
+
requests: RecordedRequest[];
|
|
51
|
+
close: () => Promise<void>;
|
|
52
|
+
}> {
|
|
53
|
+
const requests: RecordedRequest[] = [];
|
|
54
|
+
const server = http.createServer((req, res) => {
|
|
55
|
+
let raw = "";
|
|
56
|
+
req.setEncoding("utf8");
|
|
57
|
+
req.on("data", (chunk: string) => {
|
|
58
|
+
raw += chunk;
|
|
59
|
+
});
|
|
60
|
+
req.on("end", () => {
|
|
61
|
+
requests.push({ headers: req.headers, body: JSON.parse(raw) });
|
|
62
|
+
const { status, body } = respond(req, requests.length);
|
|
63
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
64
|
+
res.end(JSON.stringify(body));
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
68
|
+
// SAFETY: listen(0, "127.0.0.1") above binds TCP, so its address is AddressInfo.
|
|
69
|
+
const { port } = server.address() as AddressInfo;
|
|
70
|
+
return {
|
|
71
|
+
url: `http://127.0.0.1:${port}/v1`,
|
|
72
|
+
requests,
|
|
73
|
+
close: async () => {
|
|
74
|
+
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const okCompletion = {
|
|
80
|
+
choices: [{ message: { content: "done" } }],
|
|
81
|
+
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const responseFormat = {
|
|
85
|
+
type: "json_schema",
|
|
86
|
+
json_schema: {
|
|
87
|
+
name: "result",
|
|
88
|
+
schema: { type: "object", properties: { ok: { type: "boolean" } } },
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
test("forwards json_schema response_format when the backend accepts it", async () => {
|
|
93
|
+
const server = await startRecordingServer(() => ({ status: 200, body: okCompletion }));
|
|
94
|
+
try {
|
|
95
|
+
const client = new LocalLlmClient(createConfig({ localLlmUrl: server.url }));
|
|
96
|
+
primeClient(client);
|
|
97
|
+
const result = await client.chatCompletion([{ role: "user", content: "hello" }], {
|
|
98
|
+
responseFormat,
|
|
99
|
+
});
|
|
100
|
+
assert.equal(result?.content, "done");
|
|
101
|
+
assert.equal(server.requests.length, 1);
|
|
102
|
+
assert.deepEqual(server.requests[0].body.response_format, responseFormat);
|
|
103
|
+
} finally {
|
|
104
|
+
await server.close();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("400 rejecting json_schema triggers exactly one retry without the format", async (t) => {
|
|
109
|
+
const server = await startRecordingServer((_req, count) =>
|
|
110
|
+
count === 1
|
|
111
|
+
? {
|
|
112
|
+
status: 400,
|
|
113
|
+
body: {
|
|
114
|
+
error: { message: "response_format type json_schema is not supported" },
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
: { status: 200, body: okCompletion },
|
|
118
|
+
);
|
|
119
|
+
try {
|
|
120
|
+
const client = new LocalLlmClient(createConfig({ localLlmUrl: server.url }));
|
|
121
|
+
primeClient(client);
|
|
122
|
+
const post = ChatTransport.prototype.post;
|
|
123
|
+
const attempts: Array<Parameters<ChatTransport["post"]>[0]> = [];
|
|
124
|
+
t.mock.method(ChatTransport.prototype, "post", async function (this: ChatTransport, request: Parameters<ChatTransport["post"]>[0]) {
|
|
125
|
+
attempts.push(request);
|
|
126
|
+
return post.call(this, request);
|
|
127
|
+
});
|
|
128
|
+
const result = await client.chatCompletion([{ role: "user", content: "hello" }], {
|
|
129
|
+
responseFormat,
|
|
130
|
+
});
|
|
131
|
+
assert.equal(result?.content, "done");
|
|
132
|
+
assert.equal(server.requests.length, 2, "exactly one strip-and-retry");
|
|
133
|
+
assert.deepEqual(server.requests[0].body.response_format, responseFormat);
|
|
134
|
+
assert.equal(server.requests[1].body.response_format, undefined);
|
|
135
|
+
// headers/model/deadline preserved on the retry
|
|
136
|
+
assert.equal(server.requests[1].headers["x-test"], "yes");
|
|
137
|
+
assert.equal(server.requests[1].body.model, "test-local-model");
|
|
138
|
+
assert.equal(attempts[0].signal, attempts[1].signal, "retry shares the attempt deadline and caller abort");
|
|
139
|
+
assert.equal(attempts[0].budgetMs, attempts[1].budgetMs);
|
|
140
|
+
assert.deepEqual(attempts[0].headers, attempts[1].headers);
|
|
141
|
+
} finally {
|
|
142
|
+
await server.close();
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("timeout before strip-retry headers uses the configured transport retry", async (t) => {
|
|
147
|
+
const bodies: RecordedRequest["body"][] = [];
|
|
148
|
+
t.mock.method(ChatTransport.prototype, "post", async (request: Parameters<ChatTransport["post"]>[0]) => {
|
|
149
|
+
bodies.push(JSON.parse(request.body));
|
|
150
|
+
if (bodies.length === 1) return new Response("json_schema is not supported", { status: 400 });
|
|
151
|
+
if (bodies.length === 2) throw new DOMException("Timed out before headers", "AbortError");
|
|
152
|
+
return new Response(JSON.stringify(okCompletion), { status: 200 });
|
|
153
|
+
});
|
|
154
|
+
const client = new LocalLlmClient(createConfig({ localLlmRetry5xxCount: 1, localLlm400TripThreshold: 1 }));
|
|
155
|
+
primeClient(client);
|
|
156
|
+
const result = await client.chatCompletion([{ role: "user", content: "hello" }], { responseFormat });
|
|
157
|
+
assert.equal(result?.content, "done");
|
|
158
|
+
assert.equal(bodies.length, 3);
|
|
159
|
+
assert.deepEqual(bodies[0].response_format, responseFormat);
|
|
160
|
+
assert.equal(bodies[1].response_format, undefined);
|
|
161
|
+
assert.equal(bodies[2].response_format, undefined);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("a diagnostic echo of the format is not a schema capability rejection", async () => {
|
|
165
|
+
for (const message of [
|
|
166
|
+
"Context length exceeded; request included response_format json_schema",
|
|
167
|
+
"Unsupported parameter temperature; request included response_format json_schema",
|
|
168
|
+
]) {
|
|
169
|
+
const server = await startRecordingServer(() => ({ status: 400, body: { error: { message } } }));
|
|
170
|
+
try {
|
|
171
|
+
const client = new LocalLlmClient(createConfig({ localLlmUrl: server.url }));
|
|
172
|
+
primeClient(client);
|
|
173
|
+
assert.equal(await client.chatCompletion([{ role: "user", content: "hello" }], { responseFormat }), null);
|
|
174
|
+
assert.equal(server.requests.length, 1, "unrelated provider errors must not strip the format");
|
|
175
|
+
} finally {
|
|
176
|
+
await server.close();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("an explicit unknown response_format field retries without the format", async () => {
|
|
182
|
+
const server = await startRecordingServer((_req, count) => count === 1
|
|
183
|
+
? { status: 400, body: { error: { message: "Unknown field response_format" } } }
|
|
184
|
+
: { status: 200, body: okCompletion });
|
|
185
|
+
try {
|
|
186
|
+
const client = new LocalLlmClient(createConfig({ localLlmUrl: server.url }));
|
|
187
|
+
primeClient(client);
|
|
188
|
+
assert.equal((await client.chatCompletion([{ role: "user", content: "hello" }], { responseFormat }))?.content, "done");
|
|
189
|
+
assert.equal(server.requests.length, 2);
|
|
190
|
+
assert.deepEqual(server.requests[0].body.response_format, responseFormat);
|
|
191
|
+
assert.equal(server.requests[1].body.response_format, undefined);
|
|
192
|
+
} finally {
|
|
193
|
+
await server.close();
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("format removal persists for later 5xx attempts", async () => {
|
|
198
|
+
const server = await startRecordingServer((_req, count) =>
|
|
199
|
+
count === 1
|
|
200
|
+
? { status: 400, body: { error: { message: "json_schema unsupported by backend" } } }
|
|
201
|
+
: count === 2
|
|
202
|
+
? { status: 500, body: { error: { message: "boom" } } }
|
|
203
|
+
: { status: 200, body: okCompletion },
|
|
204
|
+
);
|
|
205
|
+
try {
|
|
206
|
+
const client = new LocalLlmClient(
|
|
207
|
+
createConfig({ localLlmUrl: server.url, localLlmRetry5xxCount: 2 }),
|
|
208
|
+
);
|
|
209
|
+
primeClient(client);
|
|
210
|
+
const result = await client.chatCompletion([{ role: "user", content: "hello" }], {
|
|
211
|
+
responseFormat,
|
|
212
|
+
});
|
|
213
|
+
assert.equal(result?.content, "done");
|
|
214
|
+
assert.equal(server.requests.length, 3);
|
|
215
|
+
assert.equal(server.requests[2].body.response_format, undefined);
|
|
216
|
+
} finally {
|
|
217
|
+
await server.close();
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("unrelated 400 and 429 are not retried with a stripped format", async () => {
|
|
222
|
+
for (const status of [400, 429]) {
|
|
223
|
+
const server = await startRecordingServer(() => ({
|
|
224
|
+
status,
|
|
225
|
+
body: { error: { message: "some other problem" } },
|
|
226
|
+
}));
|
|
227
|
+
try {
|
|
228
|
+
const client = new LocalLlmClient(createConfig({ localLlmUrl: server.url }));
|
|
229
|
+
primeClient(client);
|
|
230
|
+
const result = await client.chatCompletion([{ role: "user", content: "hello" }], {
|
|
231
|
+
responseFormat,
|
|
232
|
+
});
|
|
233
|
+
assert.equal(result, null);
|
|
234
|
+
assert.equal(server.requests.length, 1);
|
|
235
|
+
assert.ok(server.requests[0].body.response_format, "format still present");
|
|
236
|
+
} finally {
|
|
237
|
+
await server.close();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("caller abort still fails closed during the strip retry", async () => {
|
|
243
|
+
const controller = new AbortController();
|
|
244
|
+
const server = await startRecordingServer((_req, count) => {
|
|
245
|
+
if (count === 2) controller.abort();
|
|
246
|
+
return count === 1
|
|
247
|
+
? { status: 400, body: { error: { message: "response_format json_schema is not supported" } } }
|
|
248
|
+
: { status: 200, body: okCompletion };
|
|
249
|
+
});
|
|
250
|
+
try {
|
|
251
|
+
const client = new LocalLlmClient(createConfig({ localLlmUrl: server.url }));
|
|
252
|
+
primeClient(client);
|
|
253
|
+
const pending = client.chatCompletion([{ role: "user", content: "hello" }], {
|
|
254
|
+
responseFormat,
|
|
255
|
+
signal: controller.signal,
|
|
256
|
+
});
|
|
257
|
+
const result = await pending;
|
|
258
|
+
assert.equal(result, null);
|
|
259
|
+
assert.equal(server.requests.length, 2, "caller abort happens during the strip retry, not before the first request");
|
|
260
|
+
} finally {
|
|
261
|
+
await server.close();
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("request without response_format is unchanged", async () => {
|
|
266
|
+
const server = await startRecordingServer(() => ({ status: 200, body: okCompletion }));
|
|
267
|
+
try {
|
|
268
|
+
const client = new LocalLlmClient(createConfig({ localLlmUrl: server.url }));
|
|
269
|
+
primeClient(client);
|
|
270
|
+
const result = await client.chatCompletion([{ role: "user", content: "hello" }]);
|
|
271
|
+
assert.equal(result?.content, "done");
|
|
272
|
+
assert.equal(server.requests.length, 1);
|
|
273
|
+
assert.equal(server.requests[0].body.response_format, undefined);
|
|
274
|
+
} finally {
|
|
275
|
+
await server.close();
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
@@ -339,7 +339,10 @@ test("process-wide concurrency cap bounds in-flight requests across clients", as
|
|
|
339
339
|
const config = createConfig({ localLlmUrl: "http://127.0.0.1:1007", localLlmMaxConcurrency: 2 });
|
|
340
340
|
const clients = [new LocalLlmClient(config), new LocalLlmClient(config), new LocalLlmClient(config)];
|
|
341
341
|
const results = await Promise.all(
|
|
342
|
-
Array.from({ length: 7 }, (_, i) => clients[i % 3].chatCompletion(
|
|
342
|
+
Array.from({ length: 7 }, (_, i) => clients[i % 3].chatCompletion(
|
|
343
|
+
[{ role: "user", content: `q${i}` }],
|
|
344
|
+
{ priority: i % 2 === 0 ? "recall-critical" : "background" }
|
|
345
|
+
))
|
|
343
346
|
);
|
|
344
347
|
assert.equal(results.filter((r) => r?.content === "ok").length, 7);
|
|
345
348
|
assert.equal(peak, 2);
|
|
@@ -404,3 +407,58 @@ test("waiting for a concurrency slot is bounded by the wait budget", async () =>
|
|
|
404
407
|
}
|
|
405
408
|
);
|
|
406
409
|
});
|
|
410
|
+
|
|
411
|
+
test("limiter reserves one finite permit for recall-critical work", async () => {
|
|
412
|
+
const limiter = new LocalLlmConcurrencyLimiter(2);
|
|
413
|
+
const background = await limiter.acquire();
|
|
414
|
+
const secondBackground = await limiter.acquire({ timeoutMs: 0 });
|
|
415
|
+
try {
|
|
416
|
+
assert.ok(background);
|
|
417
|
+
assert.equal(secondBackground, null, "background must not consume the recall permit");
|
|
418
|
+
const foreground = await limiter.acquire({ highPriority: true, timeoutMs: 0 });
|
|
419
|
+
assert.ok(foreground, "recall must start without waiting for background completion");
|
|
420
|
+
foreground();
|
|
421
|
+
foreground();
|
|
422
|
+
assert.equal(limiter.inFlight, 1, "release remains idempotent");
|
|
423
|
+
} finally {
|
|
424
|
+
secondBackground?.();
|
|
425
|
+
background?.();
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test("reserved background queue survives aborts and responds to resizing", async () => {
|
|
430
|
+
const limiter = new LocalLlmConcurrencyLimiter(2);
|
|
431
|
+
const background = await limiter.acquire();
|
|
432
|
+
assert.ok(background);
|
|
433
|
+
const controller = new AbortController();
|
|
434
|
+
const aborted = limiter.acquire({ signal: controller.signal });
|
|
435
|
+
controller.abort();
|
|
436
|
+
const abortedRelease = await aborted;
|
|
437
|
+
abortedRelease?.();
|
|
438
|
+
assert.equal(abortedRelease, null);
|
|
439
|
+
const queued = limiter.acquire();
|
|
440
|
+
const foreground = await limiter.acquire({ highPriority: true, timeoutMs: 0 });
|
|
441
|
+
assert.ok(foreground);
|
|
442
|
+
assert.equal(limiter.queued, 1);
|
|
443
|
+
limiter.setMax(3);
|
|
444
|
+
const nextBackground = await queued;
|
|
445
|
+
assert.ok(nextBackground);
|
|
446
|
+
assert.equal(limiter.inFlight, 3);
|
|
447
|
+
nextBackground();
|
|
448
|
+
background();
|
|
449
|
+
foreground();
|
|
450
|
+
assert.equal(limiter.inFlight, 0);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
test("one-permit and unlimited limits still admit background work", async () => {
|
|
454
|
+
for (const max of [1, 0]) {
|
|
455
|
+
const limiter = new LocalLlmConcurrencyLimiter(max);
|
|
456
|
+
const first = await limiter.acquire({ timeoutMs: 0 });
|
|
457
|
+
assert.ok(first);
|
|
458
|
+
const second = await limiter.acquire({ timeoutMs: 0 });
|
|
459
|
+
assert.equal(second === null, max === 1);
|
|
460
|
+
second?.();
|
|
461
|
+
first();
|
|
462
|
+
assert.equal(limiter.inFlight, 0);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
@@ -151,9 +151,10 @@ export function computeRateLimitDelayMs(
|
|
|
151
151
|
|
|
152
152
|
type Waiter = { highPriority: boolean; grant: () => void };
|
|
153
153
|
|
|
154
|
-
/** FIFO
|
|
154
|
+
/** FIFO semaphore; finite caps above one reserve a permit for recall-critical work. */
|
|
155
155
|
export class LocalLlmConcurrencyLimiter {
|
|
156
156
|
private active = 0;
|
|
157
|
+
private activeBackground = 0;
|
|
157
158
|
private max: number;
|
|
158
159
|
private readonly waiters: Waiter[] = [];
|
|
159
160
|
|
|
@@ -182,20 +183,25 @@ export class LocalLlmConcurrencyLimiter {
|
|
|
182
183
|
options: { highPriority?: boolean; signal?: AbortSignal; timeoutMs?: number } = {}
|
|
183
184
|
): Promise<(() => void) | null> {
|
|
184
185
|
const { signal, timeoutMs } = options;
|
|
186
|
+
const highPriority = options.highPriority === true;
|
|
185
187
|
if (signal?.aborted) return Promise.resolve(null);
|
|
186
|
-
if (
|
|
188
|
+
if (
|
|
189
|
+
this.canAcquire(highPriority) &&
|
|
190
|
+
(this.waiters.length === 0 || (highPriority && !this.waiters[0].highPriority))
|
|
191
|
+
) {
|
|
187
192
|
this.active += 1;
|
|
188
|
-
|
|
193
|
+
if (!highPriority) this.activeBackground += 1;
|
|
194
|
+
return Promise.resolve(this.releaser(highPriority));
|
|
189
195
|
}
|
|
190
196
|
if (timeoutMs !== undefined && timeoutMs <= 0) return Promise.resolve(null);
|
|
191
197
|
return new Promise((resolve) => {
|
|
192
198
|
let timer: NodeJS.Timeout | undefined;
|
|
193
199
|
const waiter: Waiter = {
|
|
194
|
-
highPriority
|
|
200
|
+
highPriority,
|
|
195
201
|
grant: () => {
|
|
196
202
|
signal?.removeEventListener("abort", onAbort);
|
|
197
203
|
if (timer) clearTimeout(timer);
|
|
198
|
-
resolve(this.releaser());
|
|
204
|
+
resolve(this.releaser(highPriority));
|
|
199
205
|
},
|
|
200
206
|
};
|
|
201
207
|
const onAbort = (): void => {
|
|
@@ -217,21 +223,30 @@ export class LocalLlmConcurrencyLimiter {
|
|
|
217
223
|
});
|
|
218
224
|
}
|
|
219
225
|
|
|
220
|
-
private
|
|
226
|
+
private canAcquire(highPriority: boolean): boolean {
|
|
227
|
+
return this.max <= 0 || (
|
|
228
|
+
this.active < this.max &&
|
|
229
|
+
(highPriority || this.max === 1 || this.activeBackground < this.max - 1)
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private releaser(highPriority: boolean): () => void {
|
|
221
234
|
let released = false;
|
|
222
235
|
return () => {
|
|
223
236
|
if (released) return;
|
|
224
237
|
released = true;
|
|
225
238
|
this.active -= 1;
|
|
239
|
+
if (!highPriority) this.activeBackground -= 1;
|
|
226
240
|
this.pump();
|
|
227
241
|
};
|
|
228
242
|
}
|
|
229
243
|
|
|
230
244
|
private pump(): void {
|
|
231
|
-
while (this.waiters.length > 0 && (this.
|
|
245
|
+
while (this.waiters.length > 0 && this.canAcquire(this.waiters[0].highPriority)) {
|
|
232
246
|
const next = this.waiters.shift();
|
|
233
247
|
if (!next) break;
|
|
234
248
|
this.active += 1;
|
|
249
|
+
if (!next.highPriority) this.activeBackground += 1;
|
|
235
250
|
next.grant();
|
|
236
251
|
}
|
|
237
252
|
}
|