@remnic/core 1.0.0
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/abstraction-nodes.d.ts +52 -0
- package/dist/abstraction-nodes.js +15 -0
- package/dist/abstraction-nodes.js.map +1 -0
- package/dist/access-cli.d.ts +5 -0
- package/dist/access-cli.js +308 -0
- package/dist/access-cli.js.map +1 -0
- package/dist/access-http.d.ts +158 -0
- package/dist/access-http.js +32 -0
- package/dist/access-http.js.map +1 -0
- package/dist/access-idempotency.d.ts +31 -0
- package/dist/access-idempotency.js +11 -0
- package/dist/access-idempotency.js.map +1 -0
- package/dist/access-mcp.d.ts +76 -0
- package/dist/access-mcp.js +8 -0
- package/dist/access-mcp.js.map +1 -0
- package/dist/access-schema.d.ts +266 -0
- package/dist/access-schema.js +29 -0
- package/dist/access-schema.js.map +1 -0
- package/dist/access-service.d.ts +614 -0
- package/dist/access-service.js +32 -0
- package/dist/access-service.js.map +1 -0
- package/dist/behavior-learner.d.ts +16 -0
- package/dist/behavior-learner.js +124 -0
- package/dist/behavior-learner.js.map +1 -0
- package/dist/behavior-signals.d.ts +15 -0
- package/dist/behavior-signals.js +11 -0
- package/dist/behavior-signals.js.map +1 -0
- package/dist/bootstrap.d.ts +46 -0
- package/dist/bootstrap.js +9 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/boxes.d.ts +93 -0
- package/dist/boxes.js +14 -0
- package/dist/boxes.js.map +1 -0
- package/dist/buffer.d.ts +22 -0
- package/dist/buffer.js +9 -0
- package/dist/buffer.js.map +1 -0
- package/dist/calibration.d.ts +81 -0
- package/dist/calibration.js +239 -0
- package/dist/calibration.js.map +1 -0
- package/dist/causal-behavior.d.ts +79 -0
- package/dist/causal-behavior.js +190 -0
- package/dist/causal-behavior.js.map +1 -0
- package/dist/causal-chain.d.ts +61 -0
- package/dist/causal-chain.js +24 -0
- package/dist/causal-chain.js.map +1 -0
- package/dist/causal-consolidation.d.ts +71 -0
- package/dist/causal-consolidation.js +211 -0
- package/dist/causal-consolidation.js.map +1 -0
- package/dist/causal-retrieval.d.ts +44 -0
- package/dist/causal-retrieval.js +184 -0
- package/dist/causal-retrieval.js.map +1 -0
- package/dist/causal-trajectory-graph.d.ts +13 -0
- package/dist/causal-trajectory-graph.js +59 -0
- package/dist/causal-trajectory-graph.js.map +1 -0
- package/dist/causal-trajectory.d.ts +68 -0
- package/dist/causal-trajectory.js +18 -0
- package/dist/causal-trajectory.js.map +1 -0
- package/dist/chunk-2CJCWDMR.js +87 -0
- package/dist/chunk-2CJCWDMR.js.map +1 -0
- package/dist/chunk-2NMMFZ5T.js +216 -0
- package/dist/chunk-2NMMFZ5T.js.map +1 -0
- package/dist/chunk-2PO5ZRKV.js +103 -0
- package/dist/chunk-2PO5ZRKV.js.map +1 -0
- package/dist/chunk-3QKK7QOS.js +154 -0
- package/dist/chunk-3QKK7QOS.js.map +1 -0
- package/dist/chunk-3SLRNYNG.js +26 -0
- package/dist/chunk-3SLRNYNG.js.map +1 -0
- package/dist/chunk-4A24LIM2.js +68 -0
- package/dist/chunk-4A24LIM2.js.map +1 -0
- package/dist/chunk-6HZ6AO2P.js +164 -0
- package/dist/chunk-6HZ6AO2P.js.map +1 -0
- package/dist/chunk-763GUIOU.js +302 -0
- package/dist/chunk-763GUIOU.js.map +1 -0
- package/dist/chunk-AAI7JARD.js +173 -0
- package/dist/chunk-AAI7JARD.js.map +1 -0
- package/dist/chunk-B7LOFDVE.js +112 -0
- package/dist/chunk-B7LOFDVE.js.map +1 -0
- package/dist/chunk-BDFZXRSO.js +318 -0
- package/dist/chunk-BDFZXRSO.js.map +1 -0
- package/dist/chunk-BOUYNNYD.js +707 -0
- package/dist/chunk-BOUYNNYD.js.map +1 -0
- package/dist/chunk-BRK4ODMI.js +60 -0
- package/dist/chunk-BRK4ODMI.js.map +1 -0
- package/dist/chunk-C6QPK5GG.js +111 -0
- package/dist/chunk-C6QPK5GG.js.map +1 -0
- package/dist/chunk-C7VW7C3F.js +117 -0
- package/dist/chunk-C7VW7C3F.js.map +1 -0
- package/dist/chunk-CDW777AI.js +621 -0
- package/dist/chunk-CDW777AI.js.map +1 -0
- package/dist/chunk-CULXMQJH.js +185 -0
- package/dist/chunk-CULXMQJH.js.map +1 -0
- package/dist/chunk-CXWFUJR2.js +1203 -0
- package/dist/chunk-CXWFUJR2.js.map +1 -0
- package/dist/chunk-DGXUHMOV.js +61 -0
- package/dist/chunk-DGXUHMOV.js.map +1 -0
- package/dist/chunk-DM2T26WE.js +61 -0
- package/dist/chunk-DM2T26WE.js.map +1 -0
- package/dist/chunk-DORBM6OB.js +81 -0
- package/dist/chunk-DORBM6OB.js.map +1 -0
- package/dist/chunk-DT5TVLJE.js +32 -0
- package/dist/chunk-DT5TVLJE.js.map +1 -0
- package/dist/chunk-EEQLFRUM.js +89 -0
- package/dist/chunk-EEQLFRUM.js.map +1 -0
- package/dist/chunk-EQINRHYR.js +672 -0
- package/dist/chunk-EQINRHYR.js.map +1 -0
- package/dist/chunk-ESSMF2FR.js +146 -0
- package/dist/chunk-ESSMF2FR.js.map +1 -0
- package/dist/chunk-ETOW6ACV.js +158 -0
- package/dist/chunk-ETOW6ACV.js.map +1 -0
- package/dist/chunk-FYIYMQ5N.js +221 -0
- package/dist/chunk-FYIYMQ5N.js.map +1 -0
- package/dist/chunk-G3AG3KZN.js +78 -0
- package/dist/chunk-G3AG3KZN.js.map +1 -0
- package/dist/chunk-GJR6D6KC.js +61 -0
- package/dist/chunk-GJR6D6KC.js.map +1 -0
- package/dist/chunk-GPGBSNKM.js +380 -0
- package/dist/chunk-GPGBSNKM.js.map +1 -0
- package/dist/chunk-H63EDPFJ.js +57 -0
- package/dist/chunk-H63EDPFJ.js.map +1 -0
- package/dist/chunk-HG2NKWR2.js +185 -0
- package/dist/chunk-HG2NKWR2.js.map +1 -0
- package/dist/chunk-HL4DB7TO.js +13 -0
- package/dist/chunk-HL4DB7TO.js.map +1 -0
- package/dist/chunk-HLBYLYRD.js +346 -0
- package/dist/chunk-HLBYLYRD.js.map +1 -0
- package/dist/chunk-HLXVTBF3.js +109 -0
- package/dist/chunk-HLXVTBF3.js.map +1 -0
- package/dist/chunk-IFFFR3MR.js +68 -0
- package/dist/chunk-IFFFR3MR.js.map +1 -0
- package/dist/chunk-ISY75RLM.js +1027 -0
- package/dist/chunk-ISY75RLM.js.map +1 -0
- package/dist/chunk-IZME7KW2.js +1886 -0
- package/dist/chunk-IZME7KW2.js.map +1 -0
- package/dist/chunk-J3BT33K7.js +720 -0
- package/dist/chunk-J3BT33K7.js.map +1 -0
- package/dist/chunk-J47FNDR7.js +113 -0
- package/dist/chunk-J47FNDR7.js.map +1 -0
- package/dist/chunk-JWPLJLDU.js +63 -0
- package/dist/chunk-JWPLJLDU.js.map +1 -0
- package/dist/chunk-K6WK37A6.js +865 -0
- package/dist/chunk-K6WK37A6.js.map +1 -0
- package/dist/chunk-KL4CP4SB.js +130 -0
- package/dist/chunk-KL4CP4SB.js.map +1 -0
- package/dist/chunk-KT4NEUNF.js +315 -0
- package/dist/chunk-KT4NEUNF.js.map +1 -0
- package/dist/chunk-KWBU5S5U.js +42 -0
- package/dist/chunk-KWBU5S5U.js.map +1 -0
- package/dist/chunk-L5RPWGFK.js +59 -0
- package/dist/chunk-L5RPWGFK.js.map +1 -0
- package/dist/chunk-L7WO3MZ4.js +128 -0
- package/dist/chunk-L7WO3MZ4.js.map +1 -0
- package/dist/chunk-LIRZNNUP.js +74 -0
- package/dist/chunk-LIRZNNUP.js.map +1 -0
- package/dist/chunk-LK6SGL53.js +22 -0
- package/dist/chunk-LK6SGL53.js.map +1 -0
- package/dist/chunk-LOBRX7VD.js +200 -0
- package/dist/chunk-LOBRX7VD.js.map +1 -0
- package/dist/chunk-LPSF4OQH.js +47 -0
- package/dist/chunk-LPSF4OQH.js.map +1 -0
- package/dist/chunk-LU3GQNDQ.js +152 -0
- package/dist/chunk-LU3GQNDQ.js.map +1 -0
- package/dist/chunk-M5KEYE5E.js +350 -0
- package/dist/chunk-M5KEYE5E.js.map +1 -0
- package/dist/chunk-M62O4P4T.js +41 -0
- package/dist/chunk-M62O4P4T.js.map +1 -0
- package/dist/chunk-MARWOCVP.js +48 -0
- package/dist/chunk-MARWOCVP.js.map +1 -0
- package/dist/chunk-MDDAA2AO.js +925 -0
- package/dist/chunk-MDDAA2AO.js.map +1 -0
- package/dist/chunk-MWGVGUIS.js +198 -0
- package/dist/chunk-MWGVGUIS.js.map +1 -0
- package/dist/chunk-N5AKDXAI.js +74 -0
- package/dist/chunk-N5AKDXAI.js.map +1 -0
- package/dist/chunk-NGAVDO7E.js +115 -0
- package/dist/chunk-NGAVDO7E.js.map +1 -0
- package/dist/chunk-NTTLPF7F.js +283 -0
- package/dist/chunk-NTTLPF7F.js.map +1 -0
- package/dist/chunk-ONRU4L2N.js +240 -0
- package/dist/chunk-ONRU4L2N.js.map +1 -0
- package/dist/chunk-ORZMT74A.js +209 -0
- package/dist/chunk-ORZMT74A.js.map +1 -0
- package/dist/chunk-OTAVQCSF.js +268 -0
- package/dist/chunk-OTAVQCSF.js.map +1 -0
- package/dist/chunk-PGK3VUHN.js +160 -0
- package/dist/chunk-PGK3VUHN.js.map +1 -0
- package/dist/chunk-Q6FETXJA.js +1362 -0
- package/dist/chunk-Q6FETXJA.js.map +1 -0
- package/dist/chunk-QANCTXQF.js +271 -0
- package/dist/chunk-QANCTXQF.js.map +1 -0
- package/dist/chunk-QCCCQT3O.js +189 -0
- package/dist/chunk-QCCCQT3O.js.map +1 -0
- package/dist/chunk-QDOSNLB4.js +1048 -0
- package/dist/chunk-QDOSNLB4.js.map +1 -0
- package/dist/chunk-QFQVZOGA.js +2168 -0
- package/dist/chunk-QFQVZOGA.js.map +1 -0
- package/dist/chunk-QPKFPHOO.js +178 -0
- package/dist/chunk-QPKFPHOO.js.map +1 -0
- package/dist/chunk-QSVPYQPG.js +268 -0
- package/dist/chunk-QSVPYQPG.js.map +1 -0
- package/dist/chunk-QWUUMMIK.js +3045 -0
- package/dist/chunk-QWUUMMIK.js.map +1 -0
- package/dist/chunk-QY2BHY5O.js +2378 -0
- package/dist/chunk-QY2BHY5O.js.map +1 -0
- package/dist/chunk-SCHEKPYH.js +349 -0
- package/dist/chunk-SCHEKPYH.js.map +1 -0
- package/dist/chunk-SCU65EZI.js +15 -0
- package/dist/chunk-SCU65EZI.js.map +1 -0
- package/dist/chunk-T4WRIV2C.js +170 -0
- package/dist/chunk-T4WRIV2C.js.map +1 -0
- package/dist/chunk-TKO4HZCK.js +1852 -0
- package/dist/chunk-TKO4HZCK.js.map +1 -0
- package/dist/chunk-TP4FZJIZ.js +93 -0
- package/dist/chunk-TP4FZJIZ.js.map +1 -0
- package/dist/chunk-TPB3I2AC.js +403 -0
- package/dist/chunk-TPB3I2AC.js.map +1 -0
- package/dist/chunk-TVVVQQAK.js +1431 -0
- package/dist/chunk-TVVVQQAK.js.map +1 -0
- package/dist/chunk-U4PV25RD.js +14 -0
- package/dist/chunk-U4PV25RD.js.map +1 -0
- package/dist/chunk-UCYSTFZR.js +284 -0
- package/dist/chunk-UCYSTFZR.js.map +1 -0
- package/dist/chunk-UHGBNIOS.js +205 -0
- package/dist/chunk-UHGBNIOS.js.map +1 -0
- package/dist/chunk-UIYZ5T3I.js +108 -0
- package/dist/chunk-UIYZ5T3I.js.map +1 -0
- package/dist/chunk-UV2FO7J4.js +747 -0
- package/dist/chunk-UV2FO7J4.js.map +1 -0
- package/dist/chunk-UZB5KHKX.js +63 -0
- package/dist/chunk-UZB5KHKX.js.map +1 -0
- package/dist/chunk-V3RXWQIE.js +626 -0
- package/dist/chunk-V3RXWQIE.js.map +1 -0
- package/dist/chunk-V4YC4LUK.js +444 -0
- package/dist/chunk-V4YC4LUK.js.map +1 -0
- package/dist/chunk-VEWZZM3H.js +133 -0
- package/dist/chunk-VEWZZM3H.js.map +1 -0
- package/dist/chunk-WWIQTB2Y.js +98 -0
- package/dist/chunk-WWIQTB2Y.js.map +1 -0
- package/dist/chunk-X7XN6YU4.js +24 -0
- package/dist/chunk-X7XN6YU4.js.map +1 -0
- package/dist/chunk-XKECPATV.js +202 -0
- package/dist/chunk-XKECPATV.js.map +1 -0
- package/dist/chunk-XYIK4LF6.js +75 -0
- package/dist/chunk-XYIK4LF6.js.map +1 -0
- package/dist/chunk-Y27UJK6V.js +39 -0
- package/dist/chunk-Y27UJK6V.js.map +1 -0
- package/dist/chunk-Y4Z4I6WK.js +9 -0
- package/dist/chunk-Y4Z4I6WK.js.map +1 -0
- package/dist/chunk-YAPUAHAY.js +10761 -0
- package/dist/chunk-YAPUAHAY.js.map +1 -0
- package/dist/chunk-YAZNBMNF.js +92 -0
- package/dist/chunk-YAZNBMNF.js.map +1 -0
- package/dist/chunk-YCN4BVDK.js +66 -0
- package/dist/chunk-YCN4BVDK.js.map +1 -0
- package/dist/chunk-YNCQ7E4M.js +388 -0
- package/dist/chunk-YNCQ7E4M.js.map +1 -0
- package/dist/chunk-YNI4S5WT.js +143 -0
- package/dist/chunk-YNI4S5WT.js.map +1 -0
- package/dist/chunk-YRMVARQP.js +406 -0
- package/dist/chunk-YRMVARQP.js.map +1 -0
- package/dist/chunk-Z5AAYHUC.js +79 -0
- package/dist/chunk-Z5AAYHUC.js.map +1 -0
- package/dist/chunk-Z5LAYHGJ.js +15 -0
- package/dist/chunk-Z5LAYHGJ.js.map +1 -0
- package/dist/chunk-ZJLY4QSU.js +823 -0
- package/dist/chunk-ZJLY4QSU.js.map +1 -0
- package/dist/chunk-ZKYI7UVO.js +276 -0
- package/dist/chunk-ZKYI7UVO.js.map +1 -0
- package/dist/chunk-ZPKBYX2F.js +297 -0
- package/dist/chunk-ZPKBYX2F.js.map +1 -0
- package/dist/chunking.d.ts +48 -0
- package/dist/chunking.js +11 -0
- package/dist/chunking.js.map +1 -0
- package/dist/cli.d.ts +1162 -0
- package/dist/cli.js +7187 -0
- package/dist/cli.js.map +1 -0
- package/dist/commitment-ledger.d.ts +83 -0
- package/dist/commitment-ledger.js +19 -0
- package/dist/commitment-ledger.js.map +1 -0
- package/dist/compression-optimizer.d.ts +37 -0
- package/dist/compression-optimizer.js +13 -0
- package/dist/compression-optimizer.js.map +1 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.js +12 -0
- package/dist/config.js.map +1 -0
- package/dist/cue-anchors.d.ts +50 -0
- package/dist/cue-anchors.js +15 -0
- package/dist/cue-anchors.js.map +1 -0
- package/dist/dashboard-runtime.d.ts +46 -0
- package/dist/dashboard-runtime.js +10 -0
- package/dist/dashboard-runtime.js.map +1 -0
- package/dist/day-summary.d.ts +6 -0
- package/dist/day-summary.js +10 -0
- package/dist/day-summary.js.map +1 -0
- package/dist/delinearize.d.ts +34 -0
- package/dist/delinearize.js +11 -0
- package/dist/delinearize.js.map +1 -0
- package/dist/embedding-fallback.d.ts +22 -0
- package/dist/embedding-fallback.js +8 -0
- package/dist/embedding-fallback.js.map +1 -0
- package/dist/engine-P26JFSVY.js +19 -0
- package/dist/engine-P26JFSVY.js.map +1 -0
- package/dist/entity-retrieval.d.ts +23 -0
- package/dist/entity-retrieval.js +24 -0
- package/dist/entity-retrieval.js.map +1 -0
- package/dist/evals.d.ts +282 -0
- package/dist/evals.js +32 -0
- package/dist/evals.js.map +1 -0
- package/dist/explicit-capture.d.ts +60 -0
- package/dist/explicit-capture.js +23 -0
- package/dist/explicit-capture.js.map +1 -0
- package/dist/extraction.d.ts +141 -0
- package/dist/extraction.js +22 -0
- package/dist/extraction.js.map +1 -0
- package/dist/fallback-llm.d.ts +95 -0
- package/dist/fallback-llm.js +12 -0
- package/dist/fallback-llm.js.map +1 -0
- package/dist/graph-dashboard-diff.d.ts +12 -0
- package/dist/graph-dashboard-diff.js +8 -0
- package/dist/graph-dashboard-diff.js.map +1 -0
- package/dist/graph-dashboard-key.d.ts +5 -0
- package/dist/graph-dashboard-key.js +7 -0
- package/dist/graph-dashboard-key.js.map +1 -0
- package/dist/graph-dashboard-parser.d.ts +20 -0
- package/dist/graph-dashboard-parser.js +8 -0
- package/dist/graph-dashboard-parser.js.map +1 -0
- package/dist/graph.d.ts +157 -0
- package/dist/graph.js +27 -0
- package/dist/graph.js.map +1 -0
- package/dist/harmonic-retrieval.d.ts +27 -0
- package/dist/harmonic-retrieval.js +12 -0
- package/dist/harmonic-retrieval.js.map +1 -0
- package/dist/himem.d.ts +23 -0
- package/dist/himem.js +7 -0
- package/dist/himem.js.map +1 -0
- package/dist/hygiene.d.ts +24 -0
- package/dist/hygiene.js +9 -0
- package/dist/hygiene.js.map +1 -0
- package/dist/identity-continuity.d.ts +17 -0
- package/dist/identity-continuity.js +19 -0
- package/dist/identity-continuity.js.map +1 -0
- package/dist/importance.d.ts +25 -0
- package/dist/importance.js +11 -0
- package/dist/importance.js.map +1 -0
- package/dist/index.d.ts +923 -0
- package/dist/index.js +2512 -0
- package/dist/index.js.map +1 -0
- package/dist/intent.d.ts +8 -0
- package/dist/intent.js +13 -0
- package/dist/intent.js.map +1 -0
- package/dist/json-extract.d.ts +14 -0
- package/dist/json-extract.js +9 -0
- package/dist/json-extract.js.map +1 -0
- package/dist/json-store.d.ts +5 -0
- package/dist/json-store.js +11 -0
- package/dist/json-store.js.map +1 -0
- package/dist/legacy-hook-compat.d.ts +3 -0
- package/dist/legacy-hook-compat.js +35 -0
- package/dist/legacy-hook-compat.js.map +1 -0
- package/dist/lifecycle.d.ts +52 -0
- package/dist/lifecycle.js +21 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/local-llm.d.ts +154 -0
- package/dist/local-llm.js +10 -0
- package/dist/local-llm.js.map +1 -0
- package/dist/logger.d.ts +15 -0
- package/dist/logger.js +9 -0
- package/dist/logger.js.map +1 -0
- package/dist/memory-action-policy.d.ts +13 -0
- package/dist/memory-action-policy.js +7 -0
- package/dist/memory-action-policy.js.map +1 -0
- package/dist/memory-cache.d.ts +35 -0
- package/dist/memory-cache.js +37 -0
- package/dist/memory-cache.js.map +1 -0
- package/dist/memory-lifecycle-ledger-utils.d.ts +13 -0
- package/dist/memory-lifecycle-ledger-utils.js +23 -0
- package/dist/memory-lifecycle-ledger-utils.js.map +1 -0
- package/dist/memory-projection-format.d.ts +4 -0
- package/dist/memory-projection-format.js +9 -0
- package/dist/memory-projection-format.js.map +1 -0
- package/dist/memory-projection-store-NxMkbocT.d.ts +221 -0
- package/dist/memory-projection-store.d.ts +3 -0
- package/dist/memory-projection-store.js +31 -0
- package/dist/memory-projection-store.js.map +1 -0
- package/dist/model-registry.d.ts +60 -0
- package/dist/model-registry.js +8 -0
- package/dist/model-registry.js.map +1 -0
- package/dist/native-knowledge.d.ts +94 -0
- package/dist/native-knowledge.js +26 -0
- package/dist/native-knowledge.js.map +1 -0
- package/dist/negative.d.ts +26 -0
- package/dist/negative.js +8 -0
- package/dist/negative.js.map +1 -0
- package/dist/objective-state-writers.d.ts +22 -0
- package/dist/objective-state-writers.js +313 -0
- package/dist/objective-state-writers.js.map +1 -0
- package/dist/objective-state.d.ts +75 -0
- package/dist/objective-state.js +17 -0
- package/dist/objective-state.js.map +1 -0
- package/dist/openai-chat-compat.d.ts +13 -0
- package/dist/openai-chat-compat.js +11 -0
- package/dist/openai-chat-compat.js.map +1 -0
- package/dist/operator-toolkit.d.ts +304 -0
- package/dist/operator-toolkit.js +41 -0
- package/dist/operator-toolkit.js.map +1 -0
- package/dist/opik-exporter.d.ts +72 -0
- package/dist/opik-exporter.js +361 -0
- package/dist/opik-exporter.js.map +1 -0
- package/dist/orchestrator-zTa-Qo-1.d.ts +1104 -0
- package/dist/orchestrator.d.ts +21 -0
- package/dist/orchestrator.js +145 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/policy-runtime.d.ts +37 -0
- package/dist/policy-runtime.js +13 -0
- package/dist/policy-runtime.js.map +1 -0
- package/dist/port-C1GZFv8h.d.ts +41 -0
- package/dist/profiling.d.ts +80 -0
- package/dist/profiling.js +10 -0
- package/dist/profiling.js.map +1 -0
- package/dist/qmd-recall-cache.d.ts +29 -0
- package/dist/qmd-recall-cache.js +13 -0
- package/dist/qmd-recall-cache.js.map +1 -0
- package/dist/qmd.d.ts +105 -0
- package/dist/qmd.js +13 -0
- package/dist/qmd.js.map +1 -0
- package/dist/recall-qos.d.ts +33 -0
- package/dist/recall-qos.js +10 -0
- package/dist/recall-qos.js.map +1 -0
- package/dist/recall-query-policy.d.ts +20 -0
- package/dist/recall-query-policy.js +11 -0
- package/dist/recall-query-policy.js.map +1 -0
- package/dist/recall-state.d.ts +113 -0
- package/dist/recall-state.js +12 -0
- package/dist/recall-state.js.map +1 -0
- package/dist/recall-tokenization.d.ts +4 -0
- package/dist/recall-tokenization.js +9 -0
- package/dist/recall-tokenization.js.map +1 -0
- package/dist/reconstruct.d.ts +16 -0
- package/dist/reconstruct.js +7 -0
- package/dist/reconstruct.js.map +1 -0
- package/dist/release-changelog.d.ts +7 -0
- package/dist/release-changelog.js +30 -0
- package/dist/release-changelog.js.map +1 -0
- package/dist/relevance.d.ts +18 -0
- package/dist/relevance.js +8 -0
- package/dist/relevance.js.map +1 -0
- package/dist/rerank.d.ts +57 -0
- package/dist/rerank.js +11 -0
- package/dist/rerank.js.map +1 -0
- package/dist/resolve-provider-secret.d.ts +16 -0
- package/dist/resolve-provider-secret.js +11 -0
- package/dist/resolve-provider-secret.js.map +1 -0
- package/dist/resume-bundles.d.ts +66 -0
- package/dist/resume-bundles.js +27 -0
- package/dist/resume-bundles.js.map +1 -0
- package/dist/retrieval-agents.d.ts +129 -0
- package/dist/retrieval-agents.js +23 -0
- package/dist/retrieval-agents.js.map +1 -0
- package/dist/retrieval.d.ts +19 -0
- package/dist/retrieval.js +10 -0
- package/dist/retrieval.js.map +1 -0
- package/dist/sanitize.d.ts +9 -0
- package/dist/sanitize.js +9 -0
- package/dist/sanitize.js.map +1 -0
- package/dist/schemas.d.ts +688 -0
- package/dist/schemas.js +51 -0
- package/dist/schemas.js.map +1 -0
- package/dist/sdk-compat.d.ts +21 -0
- package/dist/sdk-compat.js +28 -0
- package/dist/sdk-compat.js.map +1 -0
- package/dist/semantic-consolidation.d.ts +42 -0
- package/dist/semantic-consolidation.js +12 -0
- package/dist/semantic-consolidation.js.map +1 -0
- package/dist/semantic-rule-promotion.d.ts +28 -0
- package/dist/semantic-rule-promotion.js +17 -0
- package/dist/semantic-rule-promotion.js.map +1 -0
- package/dist/semantic-rule-verifier.d.ts +19 -0
- package/dist/semantic-rule-verifier.js +18 -0
- package/dist/semantic-rule-verifier.js.map +1 -0
- package/dist/session-integrity.d.ts +67 -0
- package/dist/session-integrity.js +11 -0
- package/dist/session-integrity.js.map +1 -0
- package/dist/session-observer-bands.d.ts +6 -0
- package/dist/session-observer-bands.js +9 -0
- package/dist/session-observer-bands.js.map +1 -0
- package/dist/session-observer-state.d.ts +40 -0
- package/dist/session-observer-state.js +11 -0
- package/dist/session-observer-state.js.map +1 -0
- package/dist/signal.d.ts +6 -0
- package/dist/signal.js +9 -0
- package/dist/signal.js.map +1 -0
- package/dist/storage.d.ts +453 -0
- package/dist/storage.js +24 -0
- package/dist/storage.js.map +1 -0
- package/dist/store-contract.d.ts +10 -0
- package/dist/store-contract.js +21 -0
- package/dist/store-contract.js.map +1 -0
- package/dist/summarizer.d.ts +35 -0
- package/dist/summarizer.js +17 -0
- package/dist/summarizer.js.map +1 -0
- package/dist/summary-snapshot.d.ts +8 -0
- package/dist/summary-snapshot.js +13 -0
- package/dist/summary-snapshot.js.map +1 -0
- package/dist/temporal-index.d.ts +139 -0
- package/dist/temporal-index.js +29 -0
- package/dist/temporal-index.js.map +1 -0
- package/dist/threading.d.ts +62 -0
- package/dist/threading.js +8 -0
- package/dist/threading.js.map +1 -0
- package/dist/tier-migration.d.ts +44 -0
- package/dist/tier-migration.js +7 -0
- package/dist/tier-migration.js.map +1 -0
- package/dist/tier-routing.d.ts +21 -0
- package/dist/tier-routing.js +10 -0
- package/dist/tier-routing.js.map +1 -0
- package/dist/tmt.d.ts +79 -0
- package/dist/tmt.js +29 -0
- package/dist/tmt.js.map +1 -0
- package/dist/tokens.d.ts +24 -0
- package/dist/tokens.js +21 -0
- package/dist/tokens.js.map +1 -0
- package/dist/topics.d.ts +29 -0
- package/dist/topics.js +9 -0
- package/dist/topics.js.map +1 -0
- package/dist/transcript.d.ts +171 -0
- package/dist/transcript.js +9 -0
- package/dist/transcript.js.map +1 -0
- package/dist/trust-zones.d.ts +170 -0
- package/dist/trust-zones.js +32 -0
- package/dist/trust-zones.js.map +1 -0
- package/dist/types.d.ts +1243 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/utility-learner.d.ts +59 -0
- package/dist/utility-learner.js +17 -0
- package/dist/utility-learner.js.map +1 -0
- package/dist/utility-runtime.d.ts +21 -0
- package/dist/utility-runtime.js +16 -0
- package/dist/utility-runtime.js.map +1 -0
- package/dist/utility-telemetry.d.ts +68 -0
- package/dist/utility-telemetry.js +17 -0
- package/dist/utility-telemetry.js.map +1 -0
- package/dist/verified-recall.d.ts +17 -0
- package/dist/verified-recall.js +19 -0
- package/dist/verified-recall.js.map +1 -0
- package/dist/version-utils.d.ts +4 -0
- package/dist/version-utils.js +7 -0
- package/dist/version-utils.js.map +1 -0
- package/dist/work-product-ledger.d.ts +65 -0
- package/dist/work-product-ledger.js +18 -0
- package/dist/work-product-ledger.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,925 @@
|
|
|
1
|
+
import {
|
|
2
|
+
launchProcessSync
|
|
3
|
+
} from "./chunk-LK6SGL53.js";
|
|
4
|
+
import {
|
|
5
|
+
mergeEnv,
|
|
6
|
+
readEnvVar
|
|
7
|
+
} from "./chunk-MARWOCVP.js";
|
|
8
|
+
import {
|
|
9
|
+
log
|
|
10
|
+
} from "./chunk-KWBU5S5U.js";
|
|
11
|
+
|
|
12
|
+
// src/local-llm.ts
|
|
13
|
+
import { existsSync, readFileSync } from "fs";
|
|
14
|
+
import os from "os";
|
|
15
|
+
function trimTrailingSlashes(s) {
|
|
16
|
+
let end = s.length;
|
|
17
|
+
while (end > 0 && s[end - 1] === "/") end--;
|
|
18
|
+
return s.substring(0, end);
|
|
19
|
+
}
|
|
20
|
+
var LOCAL_SERVERS = [
|
|
21
|
+
{
|
|
22
|
+
type: "ollama",
|
|
23
|
+
defaultPort: 11434,
|
|
24
|
+
healthEndpoint: "/",
|
|
25
|
+
modelsEndpoint: "/api/tags",
|
|
26
|
+
detectFn: (resp) => typeof resp === "string" && resp.includes("Ollama")
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
type: "mlx",
|
|
30
|
+
defaultPort: 8080,
|
|
31
|
+
healthEndpoint: "/v1/models",
|
|
32
|
+
modelsEndpoint: "/v1/models",
|
|
33
|
+
detectFn: (resp) => typeof resp === "object" && resp !== null && "data" in resp && Array.isArray(resp.data)
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
type: "lmstudio",
|
|
37
|
+
defaultPort: 1234,
|
|
38
|
+
healthEndpoint: "/v1/models",
|
|
39
|
+
modelsEndpoint: "/v1/models",
|
|
40
|
+
detectFn: (resp) => typeof resp === "object" && resp !== null && "data" in resp && Array.isArray(resp.data)
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: "vllm",
|
|
44
|
+
defaultPort: 8e3,
|
|
45
|
+
healthEndpoint: "/health",
|
|
46
|
+
modelsEndpoint: "/v1/models",
|
|
47
|
+
detectFn: (resp) => resp === "" || typeof resp === "object" && resp !== null
|
|
48
|
+
}
|
|
49
|
+
];
|
|
50
|
+
var LOCAL_LLM_GLOBAL_BACKEND_STATE = "__openclawEngramLocalLlmBackendState";
|
|
51
|
+
var LocalLlmClient = class _LocalLlmClient {
|
|
52
|
+
config;
|
|
53
|
+
isAvailable = null;
|
|
54
|
+
lastHealthCheck = 0;
|
|
55
|
+
detectedType = null;
|
|
56
|
+
cachedModelInfo = null;
|
|
57
|
+
cachedLmsContext = null;
|
|
58
|
+
lastLmsCheck = 0;
|
|
59
|
+
consecutive400s = 0;
|
|
60
|
+
cooldownUntilMs = 0;
|
|
61
|
+
modelRegistry;
|
|
62
|
+
_disableThinking = false;
|
|
63
|
+
requestQueues = {
|
|
64
|
+
"recall-critical": [],
|
|
65
|
+
background: []
|
|
66
|
+
};
|
|
67
|
+
queueProcessing = /* @__PURE__ */ new Set();
|
|
68
|
+
queueDrainScheduled = false;
|
|
69
|
+
static HEALTH_CHECK_INTERVAL_MS = 6e4;
|
|
70
|
+
// 1 minute
|
|
71
|
+
static LMS_CACHE_INTERVAL_MS = 3e4;
|
|
72
|
+
// 30 seconds
|
|
73
|
+
constructor(config, modelRegistry) {
|
|
74
|
+
this.config = config;
|
|
75
|
+
this.modelRegistry = modelRegistry;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Disable thinking/reasoning mode for models that support it (e.g. Qwen 3.5).
|
|
79
|
+
* When enabled, adds chat_template_kwargs to suppress chain-of-thought,
|
|
80
|
+
* reducing latency for fast-tier operations.
|
|
81
|
+
*/
|
|
82
|
+
set disableThinking(value) {
|
|
83
|
+
this._disableThinking = value;
|
|
84
|
+
}
|
|
85
|
+
resolveHomeDir() {
|
|
86
|
+
return this.config.localLlmHomeDir || readEnvVar("HOME") || os.homedir();
|
|
87
|
+
}
|
|
88
|
+
buildRequestHeaders(base = {}) {
|
|
89
|
+
const headers = {
|
|
90
|
+
...base,
|
|
91
|
+
...this.config.localLlmHeaders ?? {}
|
|
92
|
+
};
|
|
93
|
+
if (this.config.localLlmApiKey && this.config.localLlmAuthHeader !== false) {
|
|
94
|
+
headers.Authorization = `Bearer ${this.config.localLlmApiKey}`;
|
|
95
|
+
}
|
|
96
|
+
return headers;
|
|
97
|
+
}
|
|
98
|
+
isAbortError(err) {
|
|
99
|
+
if (!err || typeof err !== "object") return false;
|
|
100
|
+
const maybe = err;
|
|
101
|
+
return maybe.name === "AbortError" || maybe.message === "This operation was aborted" || maybe.message === "The operation was aborted";
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Set the ModelRegistry for caching detected capabilities
|
|
105
|
+
*/
|
|
106
|
+
setModelRegistry(registry) {
|
|
107
|
+
this.modelRegistry = registry;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get the detected server type (null if not detected)
|
|
111
|
+
*/
|
|
112
|
+
getDetectedType() {
|
|
113
|
+
return this.detectedType;
|
|
114
|
+
}
|
|
115
|
+
getBackendKey() {
|
|
116
|
+
return trimTrailingSlashes(
|
|
117
|
+
this.config.localLlmUrl.replace("localhost", "127.0.0.1")
|
|
118
|
+
).replace(/\/v1$/, "");
|
|
119
|
+
}
|
|
120
|
+
getGlobalBackendState() {
|
|
121
|
+
const globalAny = globalThis;
|
|
122
|
+
if (!globalAny[LOCAL_LLM_GLOBAL_BACKEND_STATE]) {
|
|
123
|
+
globalAny[LOCAL_LLM_GLOBAL_BACKEND_STATE] = /* @__PURE__ */ new Map();
|
|
124
|
+
}
|
|
125
|
+
return globalAny[LOCAL_LLM_GLOBAL_BACKEND_STATE];
|
|
126
|
+
}
|
|
127
|
+
getTrippedBackendState(now) {
|
|
128
|
+
const state = this.getGlobalBackendState().get(this.getBackendKey()) ?? null;
|
|
129
|
+
if (!state) return null;
|
|
130
|
+
if (state.untilMs <= now) {
|
|
131
|
+
this.getGlobalBackendState().delete(this.getBackendKey());
|
|
132
|
+
this.lastHealthCheck = 0;
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
return state;
|
|
136
|
+
}
|
|
137
|
+
markBackendUnavailable(reason, durationMs) {
|
|
138
|
+
const normalizedReason = this.normalizeBackendTripReason(reason);
|
|
139
|
+
if (durationMs > 0) {
|
|
140
|
+
const untilMs = Date.now() + durationMs;
|
|
141
|
+
this.getGlobalBackendState().set(this.getBackendKey(), { untilMs, reason: normalizedReason });
|
|
142
|
+
} else {
|
|
143
|
+
this.getGlobalBackendState().delete(this.getBackendKey());
|
|
144
|
+
}
|
|
145
|
+
this.isAvailable = false;
|
|
146
|
+
this.lastHealthCheck = 0;
|
|
147
|
+
log.warn(
|
|
148
|
+
`local LLM backend unavailable for ${durationMs}ms: model=${this.config.localLlmModel} reason=${normalizedReason}`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
extractNonRecoverableBackendReason(reason) {
|
|
152
|
+
const match = reason.match(
|
|
153
|
+
/Failed to load model|Library not loaded|different Team IDs|code signature|llm_engine_mlx_amphibian/i
|
|
154
|
+
);
|
|
155
|
+
return match?.[0] ?? null;
|
|
156
|
+
}
|
|
157
|
+
extractNonRecoverableBackendReasonFromErrorText(errorText) {
|
|
158
|
+
const directReason = this.extractNonRecoverableBackendReason(errorText);
|
|
159
|
+
if (directReason) return directReason;
|
|
160
|
+
try {
|
|
161
|
+
const parsed = JSON.parse(errorText);
|
|
162
|
+
return this.extractNonRecoverableBackendReason(parsed?.error?.message ?? "");
|
|
163
|
+
} catch {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
normalizeBackendTripReason(reason) {
|
|
168
|
+
const cleaned = reason.replace(/\s+/g, " ").replace(/^[-:–—\s]+/, "").trim();
|
|
169
|
+
if (!cleaned) return "unknown local backend failure";
|
|
170
|
+
return cleaned.length > 160 ? `${cleaned.slice(0, 157)}...` : cleaned;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Fetch with timeout for health checks
|
|
174
|
+
*/
|
|
175
|
+
async fetchWithTimeout(url, timeoutMs = 2e3, headers) {
|
|
176
|
+
const controller = new AbortController();
|
|
177
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
178
|
+
try {
|
|
179
|
+
const response = await fetch(url, {
|
|
180
|
+
signal: controller.signal,
|
|
181
|
+
headers: this.buildRequestHeaders({ Accept: "application/json", ...headers ?? {} })
|
|
182
|
+
});
|
|
183
|
+
clearTimeout(timeout);
|
|
184
|
+
if (!response.ok) {
|
|
185
|
+
return { ok: false, data: null, status: response.status };
|
|
186
|
+
}
|
|
187
|
+
const contentType = response.headers.get("content-type");
|
|
188
|
+
if (contentType?.includes("application/json")) {
|
|
189
|
+
return { ok: true, data: await response.json(), status: response.status };
|
|
190
|
+
} else {
|
|
191
|
+
return { ok: true, data: await response.text(), status: response.status };
|
|
192
|
+
}
|
|
193
|
+
} catch (err) {
|
|
194
|
+
clearTimeout(timeout);
|
|
195
|
+
return { ok: false, data: null, status: null };
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Check if local LLM is available
|
|
200
|
+
* Uses 127.0.0.1 instead of localhost to avoid DNS issues (consistent with tactician)
|
|
201
|
+
*/
|
|
202
|
+
async checkAvailability() {
|
|
203
|
+
const now = Date.now();
|
|
204
|
+
const trippedState = this.getTrippedBackendState(now);
|
|
205
|
+
if (trippedState) {
|
|
206
|
+
this.isAvailable = false;
|
|
207
|
+
this.lastHealthCheck = 0;
|
|
208
|
+
log.info(
|
|
209
|
+
`local LLM availability: backend circuit open for ${Math.max(0, trippedState.untilMs - now)}ms (${trippedState.reason})`
|
|
210
|
+
);
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
if (this.isAvailable !== null && now - this.lastHealthCheck < _LocalLlmClient.HEALTH_CHECK_INTERVAL_MS) {
|
|
214
|
+
return this.isAvailable;
|
|
215
|
+
}
|
|
216
|
+
const baseUrl = trimTrailingSlashes(
|
|
217
|
+
this.config.localLlmUrl.replace("localhost", "127.0.0.1")
|
|
218
|
+
);
|
|
219
|
+
let sawUnauthorizedProbe = false;
|
|
220
|
+
for (const serverConfig of LOCAL_SERVERS) {
|
|
221
|
+
const healthUrl = `${baseUrl}${serverConfig.healthEndpoint}`;
|
|
222
|
+
log.debug(`checking ${serverConfig.type} at ${healthUrl}`);
|
|
223
|
+
const result = await this.fetchWithTimeout(healthUrl);
|
|
224
|
+
if (result.ok && serverConfig.detectFn(result.data)) {
|
|
225
|
+
this.isAvailable = true;
|
|
226
|
+
this.detectedType = serverConfig.type;
|
|
227
|
+
this.lastHealthCheck = now;
|
|
228
|
+
log.info(`detected ${serverConfig.type} at ${baseUrl}`);
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
if (result.status === 401 || result.status === 403) {
|
|
232
|
+
sawUnauthorizedProbe = true;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
const modelsUrl = `${baseUrl}/v1/models`;
|
|
237
|
+
const result = await this.fetchWithTimeout(modelsUrl);
|
|
238
|
+
if (result.ok) {
|
|
239
|
+
this.isAvailable = true;
|
|
240
|
+
this.detectedType = "generic";
|
|
241
|
+
this.lastHealthCheck = now;
|
|
242
|
+
log.info(`detected generic OpenAI-compatible server at ${baseUrl}`);
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
if (result.status === 401 || result.status === 403) {
|
|
246
|
+
sawUnauthorizedProbe = true;
|
|
247
|
+
}
|
|
248
|
+
} catch {
|
|
249
|
+
}
|
|
250
|
+
this.isAvailable = false;
|
|
251
|
+
this.detectedType = null;
|
|
252
|
+
this.lastHealthCheck = now;
|
|
253
|
+
if (sawUnauthorizedProbe) {
|
|
254
|
+
log.warn(
|
|
255
|
+
`local LLM availability probe was unauthorized at ${baseUrl}; verify localLlmApiKey and localLlmAuthHeader settings`
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
log.debug("local LLM not available at", baseUrl);
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Try to get context window from LM Studio settings.json as fallback.
|
|
263
|
+
* This reads the defaultContextLength setting which is what LM Studio uses
|
|
264
|
+
* when loading models without explicit context configuration.
|
|
265
|
+
*/
|
|
266
|
+
getContextFromLmStudioSettings() {
|
|
267
|
+
try {
|
|
268
|
+
const homeDir = this.resolveHomeDir();
|
|
269
|
+
const settingsPath = `${homeDir}/.cache/lm-studio/settings.json`;
|
|
270
|
+
if (!existsSync(settingsPath)) {
|
|
271
|
+
log.debug(`LM Studio settings: file not found at ${settingsPath}`);
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
const content = readFileSync(settingsPath, "utf-8");
|
|
275
|
+
const settings = JSON.parse(content);
|
|
276
|
+
if (settings.defaultContextLength?.value) {
|
|
277
|
+
const contextWindow = settings.defaultContextLength.value;
|
|
278
|
+
log.debug(`LM Studio settings: found default context length: ${contextWindow}`);
|
|
279
|
+
return contextWindow;
|
|
280
|
+
}
|
|
281
|
+
return null;
|
|
282
|
+
} catch (err) {
|
|
283
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
284
|
+
log.debug(`LM Studio settings: failed to read - ${errorMsg}`);
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Try to get context window from LMS CLI (LM Studio specific).
|
|
290
|
+
* Uses --json flag for reliable parsing.
|
|
291
|
+
* Returns null if LMS CLI is not available or model not found.
|
|
292
|
+
*/
|
|
293
|
+
getContextFromLmsCli(modelId) {
|
|
294
|
+
try {
|
|
295
|
+
const homeDir = this.resolveHomeDir();
|
|
296
|
+
const lmsPaths = [
|
|
297
|
+
this.config.localLmsCliPath || "",
|
|
298
|
+
`${homeDir}/.cache/lm-studio/bin/lms`,
|
|
299
|
+
"/usr/local/bin/lms",
|
|
300
|
+
"/opt/homebrew/bin/lms"
|
|
301
|
+
];
|
|
302
|
+
const lmsPath = lmsPaths.find((p) => p.length > 0 && existsSync(p));
|
|
303
|
+
if (!lmsPath) {
|
|
304
|
+
log.debug(`LMS CLI: not found in standard locations (checked: ${lmsPaths.join(", ")})`);
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
log.debug(`LMS CLI: running: ${lmsPath} ps --json`);
|
|
308
|
+
const existingPath = readEnvVar("PATH") || "";
|
|
309
|
+
const result = launchProcessSync(lmsPath, ["ps", "--json"], {
|
|
310
|
+
encoding: "utf-8",
|
|
311
|
+
timeout: 5e3,
|
|
312
|
+
shell: false,
|
|
313
|
+
// Don't use shell for JSON output - more reliable
|
|
314
|
+
env: mergeEnv({
|
|
315
|
+
PATH: `${this.config.localLmsBinDir || `${homeDir}/.cache/lm-studio/bin`}:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:${existingPath}`,
|
|
316
|
+
HOME: homeDir
|
|
317
|
+
})
|
|
318
|
+
});
|
|
319
|
+
if (result.error) {
|
|
320
|
+
log.debug(`LMS CLI: spawn error - ${result.error.message}`);
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
if (result.stderr && result.stderr.trim()) {
|
|
324
|
+
log.debug(`LMS CLI: stderr - ${result.stderr.slice(0, 200)}`);
|
|
325
|
+
}
|
|
326
|
+
const output = result.stdout || "";
|
|
327
|
+
if (!output.trim()) {
|
|
328
|
+
log.debug("LMS CLI: empty output - LM Studio may not be running or no models loaded");
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
let models;
|
|
332
|
+
try {
|
|
333
|
+
models = JSON.parse(output);
|
|
334
|
+
} catch (parseErr) {
|
|
335
|
+
log.debug(`LMS CLI: JSON parse error - ${parseErr}`);
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
if (!Array.isArray(models) || models.length === 0) {
|
|
339
|
+
log.debug("LMS CLI: no models loaded");
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
const model = models.find(
|
|
343
|
+
(m) => m.identifier === modelId || m.modelKey === modelId || m.identifier?.includes(modelId.replace(/@\d+bit$/, ""))
|
|
344
|
+
);
|
|
345
|
+
if (!model) {
|
|
346
|
+
log.debug(`LMS CLI: model "${modelId}" not found in loaded models: ${models.map((m) => m.identifier).join(", ")}`);
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
const contextWindow = model.contextLength || model.maxContextLength;
|
|
350
|
+
if (contextWindow) {
|
|
351
|
+
log.info(`LMS CLI detected context window: ${contextWindow} for ${modelId} (max: ${model.maxContextLength})`);
|
|
352
|
+
return contextWindow;
|
|
353
|
+
}
|
|
354
|
+
return null;
|
|
355
|
+
} catch (err) {
|
|
356
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
357
|
+
log.debug(`LMS CLI: failed - ${errorMsg}`);
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Get full model info from LMS CLI including context length and max context length.
|
|
363
|
+
* Returns null if LMS CLI is unavailable or model not found.
|
|
364
|
+
*/
|
|
365
|
+
getLmsModelInfo(modelId) {
|
|
366
|
+
try {
|
|
367
|
+
const result = launchProcessSync("lms", ["ps", "--json"], {
|
|
368
|
+
encoding: "utf-8",
|
|
369
|
+
timeout: 5e3,
|
|
370
|
+
shell: false
|
|
371
|
+
});
|
|
372
|
+
if (result.error) {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
const output = result.stdout || "";
|
|
376
|
+
if (!output.trim()) {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
let models;
|
|
380
|
+
try {
|
|
381
|
+
models = JSON.parse(output);
|
|
382
|
+
} catch {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
if (!Array.isArray(models) || models.length === 0) {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
const model = models.find(
|
|
389
|
+
(m) => m.identifier === modelId || m.modelKey === modelId || m.identifier?.includes(modelId.replace(/@\d+bit$/, ""))
|
|
390
|
+
);
|
|
391
|
+
if (!model || !model.contextLength) {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
return {
|
|
395
|
+
contextLength: model.contextLength,
|
|
396
|
+
maxContextLength: model.maxContextLength || model.contextLength,
|
|
397
|
+
identifier: model.identifier || modelId
|
|
398
|
+
};
|
|
399
|
+
} catch {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Get context window for the configured model, using cache if available.
|
|
405
|
+
* This method caches the result to avoid repeated LMS CLI calls.
|
|
406
|
+
* Order: ModelRegistry (persistent) -> memory cache -> LMS CLI -> settings.json
|
|
407
|
+
*/
|
|
408
|
+
getCachedContextWindow(modelId) {
|
|
409
|
+
const now = Date.now();
|
|
410
|
+
if (this.modelRegistry) {
|
|
411
|
+
const caps = this.modelRegistry.getCapabilities(modelId);
|
|
412
|
+
if (caps.source === "lmstudio" && caps.contextWindow) {
|
|
413
|
+
log.debug(`ModelRegistry: using persisted LM Studio context: ${caps.contextWindow}`);
|
|
414
|
+
this.cachedLmsContext = caps.contextWindow;
|
|
415
|
+
this.lastLmsCheck = now;
|
|
416
|
+
return caps.contextWindow;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (this.cachedLmsContext && now - this.lastLmsCheck < _LocalLlmClient.LMS_CACHE_INTERVAL_MS) {
|
|
420
|
+
log.debug(`LMS CLI: returning in-memory cached context: ${this.cachedLmsContext}`);
|
|
421
|
+
return this.cachedLmsContext;
|
|
422
|
+
}
|
|
423
|
+
const lmsInfo = this.getLmsModelInfo(modelId);
|
|
424
|
+
if (lmsInfo?.contextLength) {
|
|
425
|
+
this.cachedLmsContext = lmsInfo.contextLength;
|
|
426
|
+
this.lastLmsCheck = now;
|
|
427
|
+
const calculatedOutputTokens = Math.min(Math.floor(lmsInfo.contextLength / 8), 16384);
|
|
428
|
+
const outputTokens = Math.max(calculatedOutputTokens, 4096);
|
|
429
|
+
if (this.modelRegistry) {
|
|
430
|
+
this.modelRegistry.setCapabilities(modelId, {
|
|
431
|
+
maxPositionEmbeddings: lmsInfo.maxContextLength || lmsInfo.contextLength,
|
|
432
|
+
contextWindow: lmsInfo.contextLength,
|
|
433
|
+
supportsExtendedContext: (lmsInfo.maxContextLength || lmsInfo.contextLength) > 65536,
|
|
434
|
+
typicalOutputTokens: outputTokens,
|
|
435
|
+
source: "lmstudio"
|
|
436
|
+
});
|
|
437
|
+
log.info(`LMS CLI: Stored capabilities for ${modelId}: ${lmsInfo.contextLength} context, ${outputTokens} output tokens`);
|
|
438
|
+
}
|
|
439
|
+
return lmsInfo.contextLength;
|
|
440
|
+
}
|
|
441
|
+
const legacyContext = this.getContextFromLmsCli(modelId);
|
|
442
|
+
if (legacyContext) {
|
|
443
|
+
this.cachedLmsContext = legacyContext;
|
|
444
|
+
this.lastLmsCheck = now;
|
|
445
|
+
if (this.modelRegistry) {
|
|
446
|
+
const calculatedOutputTokens = Math.min(Math.floor(legacyContext / 8), 16384);
|
|
447
|
+
const outputTokens = Math.max(calculatedOutputTokens, 4096);
|
|
448
|
+
this.modelRegistry.setCapabilities(modelId, {
|
|
449
|
+
maxPositionEmbeddings: legacyContext,
|
|
450
|
+
contextWindow: legacyContext,
|
|
451
|
+
supportsExtendedContext: false,
|
|
452
|
+
typicalOutputTokens: outputTokens,
|
|
453
|
+
source: "lmstudio"
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
return legacyContext;
|
|
457
|
+
}
|
|
458
|
+
const settingsContext = this.getContextFromLmStudioSettings();
|
|
459
|
+
if (settingsContext) {
|
|
460
|
+
log.info(`LM Studio settings: using default context: ${settingsContext}`);
|
|
461
|
+
this.cachedLmsContext = settingsContext;
|
|
462
|
+
this.lastLmsCheck = now;
|
|
463
|
+
return settingsContext;
|
|
464
|
+
}
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Clear the LMS context cache. Call this when the model changes.
|
|
469
|
+
*/
|
|
470
|
+
clearContextCache() {
|
|
471
|
+
this.cachedLmsContext = null;
|
|
472
|
+
this.lastLmsCheck = 0;
|
|
473
|
+
log.debug("LMS CLI: context cache cleared");
|
|
474
|
+
}
|
|
475
|
+
remainingCooldownMs(now = Date.now()) {
|
|
476
|
+
return Math.max(0, this.cooldownUntilMs - now);
|
|
477
|
+
}
|
|
478
|
+
scheduleQueueDrain() {
|
|
479
|
+
if (this.queueDrainScheduled) return;
|
|
480
|
+
this.queueDrainScheduled = true;
|
|
481
|
+
queueMicrotask(() => {
|
|
482
|
+
this.queueDrainScheduled = false;
|
|
483
|
+
this.startAvailableQueuedRequests();
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
hasQueuedRequests() {
|
|
487
|
+
return this.requestQueues["recall-critical"].length > 0 || this.requestQueues.background.length > 0;
|
|
488
|
+
}
|
|
489
|
+
dequeueQueuedRequest(priority) {
|
|
490
|
+
const next = this.requestQueues[priority].shift();
|
|
491
|
+
return next ?? null;
|
|
492
|
+
}
|
|
493
|
+
failOpenQueuedRequestsForCooldown() {
|
|
494
|
+
let dropped = 0;
|
|
495
|
+
for (const priority of ["recall-critical", "background"]) {
|
|
496
|
+
while (this.requestQueues[priority].length > 0) {
|
|
497
|
+
const queued = this.requestQueues[priority].shift();
|
|
498
|
+
queued?.resolve(null);
|
|
499
|
+
dropped += 1;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return dropped;
|
|
503
|
+
}
|
|
504
|
+
startAvailableQueuedRequests() {
|
|
505
|
+
if (!this.queueProcessing.has("recall-critical")) {
|
|
506
|
+
const nextCritical = this.dequeueQueuedRequest("recall-critical");
|
|
507
|
+
if (nextCritical) {
|
|
508
|
+
this.queueProcessing.add("recall-critical");
|
|
509
|
+
void this.runQueuedRequest(nextCritical);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if (!this.queueProcessing.has("background")) {
|
|
513
|
+
const nextBackground = this.dequeueQueuedRequest("background");
|
|
514
|
+
if (nextBackground) {
|
|
515
|
+
this.queueProcessing.add("background");
|
|
516
|
+
void this.runQueuedRequest(nextBackground);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
async runQueuedRequest(next) {
|
|
521
|
+
try {
|
|
522
|
+
const remainingCooldownMs = this.remainingCooldownMs();
|
|
523
|
+
if (remainingCooldownMs > 0) {
|
|
524
|
+
const additionalDropped = this.failOpenQueuedRequestsForCooldown();
|
|
525
|
+
log.warn(
|
|
526
|
+
`local LLM: cooldown active (${remainingCooldownMs}ms remaining), dropping ${additionalDropped + 1} queued request(s) fail-open`
|
|
527
|
+
);
|
|
528
|
+
next.resolve(null);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
let result = null;
|
|
532
|
+
try {
|
|
533
|
+
result = await this.runChatCompletionRequest(next.messages, next.options, {
|
|
534
|
+
priority: next.priority,
|
|
535
|
+
enqueuedAtMs: next.enqueuedAtMs
|
|
536
|
+
});
|
|
537
|
+
} catch (err) {
|
|
538
|
+
log.warn(`local LLM queue drain failed open: ${err instanceof Error ? err.message : String(err)}`);
|
|
539
|
+
}
|
|
540
|
+
next.resolve(result);
|
|
541
|
+
} finally {
|
|
542
|
+
this.queueProcessing.delete(next.priority);
|
|
543
|
+
if (this.hasQueuedRequests()) {
|
|
544
|
+
this.scheduleQueueDrain();
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
async runChatCompletionRequest(messages, options, queueMeta) {
|
|
549
|
+
log.debug(
|
|
550
|
+
`local LLM chatCompletion: localLlmEnabled=${this.config.localLlmEnabled}, model=${this.config.localLlmModel}`
|
|
551
|
+
);
|
|
552
|
+
const operation = options.operation ?? "unspecified";
|
|
553
|
+
const startedAtMs = Date.now();
|
|
554
|
+
if (queueMeta) {
|
|
555
|
+
log.debug(
|
|
556
|
+
`local LLM queue start: priority=${queueMeta.priority} waitMs=${startedAtMs - queueMeta.enqueuedAtMs} op=${operation}`
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
try {
|
|
560
|
+
const isAvailable = await this.checkAvailability();
|
|
561
|
+
if (!isAvailable) {
|
|
562
|
+
log.debug(
|
|
563
|
+
`local LLM: checkAvailability returned false for ${this.config.localLlmUrl}`
|
|
564
|
+
);
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
567
|
+
const promptChars = messages.reduce((sum, m) => sum + (m.content?.length ?? 0), 0);
|
|
568
|
+
const requestBody = {
|
|
569
|
+
model: this.config.localLlmModel,
|
|
570
|
+
messages,
|
|
571
|
+
temperature: options.temperature ?? 0.7,
|
|
572
|
+
// Use max_tokens consistent with cloud models
|
|
573
|
+
max_tokens: options.maxTokens ?? 4096
|
|
574
|
+
};
|
|
575
|
+
if (options.responseFormat?.type === "json_schema") {
|
|
576
|
+
requestBody.response_format = options.responseFormat;
|
|
577
|
+
}
|
|
578
|
+
if (this._disableThinking) {
|
|
579
|
+
requestBody.chat_template_kwargs = { enable_thinking: false };
|
|
580
|
+
}
|
|
581
|
+
const baseUrl = trimTrailingSlashes(
|
|
582
|
+
this.config.localLlmUrl.replace("localhost", "127.0.0.1")
|
|
583
|
+
);
|
|
584
|
+
const chatUrl = baseUrl.endsWith("/v1") ? `${baseUrl}/chat/completions` : `${baseUrl}/v1/chat/completions`;
|
|
585
|
+
const requestBodyJson = JSON.stringify(requestBody);
|
|
586
|
+
log.debug(
|
|
587
|
+
`local LLM: sending request to ${chatUrl} with model ${this.config.localLlmModel}`
|
|
588
|
+
);
|
|
589
|
+
log.debug(`local LLM: request body length=${requestBodyJson.length}`);
|
|
590
|
+
if (this.config.debug) {
|
|
591
|
+
try {
|
|
592
|
+
const { writeFileSync } = await import("fs");
|
|
593
|
+
writeFileSync("/tmp/engram-last-request.json", requestBodyJson);
|
|
594
|
+
} catch {
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
const effectiveTimeoutMs = typeof options.timeoutMs === "number" ? Math.min(this.config.localLlmTimeoutMs, options.timeoutMs) : this.config.localLlmTimeoutMs;
|
|
598
|
+
const maxAttempts = 1 + Math.max(0, this.config.localLlmRetry5xxCount);
|
|
599
|
+
let response = null;
|
|
600
|
+
let lastAbortError = null;
|
|
601
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
602
|
+
const attemptAbort = new AbortController();
|
|
603
|
+
const attemptTimeout = setTimeout(() => attemptAbort.abort(), effectiveTimeoutMs);
|
|
604
|
+
try {
|
|
605
|
+
response = await fetch(chatUrl, {
|
|
606
|
+
method: "POST",
|
|
607
|
+
headers: this.buildRequestHeaders({
|
|
608
|
+
"Content-Type": "application/json"
|
|
609
|
+
}),
|
|
610
|
+
body: JSON.stringify(requestBody),
|
|
611
|
+
signal: attemptAbort.signal
|
|
612
|
+
});
|
|
613
|
+
} catch (err) {
|
|
614
|
+
if (!this.isAbortError(err)) throw err;
|
|
615
|
+
lastAbortError = err instanceof Error ? err : new Error(String(err));
|
|
616
|
+
if (attempt < maxAttempts) {
|
|
617
|
+
const backoffMs2 = this.config.localLlmRetryBackoffMs * attempt;
|
|
618
|
+
log.warn(
|
|
619
|
+
`local LLM request aborted: op=${operation} attempt=${attempt}/${maxAttempts} timeoutMs=${effectiveTimeoutMs} model=${this.config.localLlmModel}; retrying after ${backoffMs2}ms`
|
|
620
|
+
);
|
|
621
|
+
await new Promise((resolve) => setTimeout(resolve, backoffMs2));
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
break;
|
|
625
|
+
} finally {
|
|
626
|
+
clearTimeout(attemptTimeout);
|
|
627
|
+
}
|
|
628
|
+
if (response.ok) break;
|
|
629
|
+
if (response.status >= 500 && attempt < maxAttempts) {
|
|
630
|
+
try {
|
|
631
|
+
const errorText = await response.clone().text();
|
|
632
|
+
const nonRecoverableReason = this.extractNonRecoverableBackendReasonFromErrorText(errorText);
|
|
633
|
+
if (nonRecoverableReason) {
|
|
634
|
+
this.markBackendUnavailable(
|
|
635
|
+
nonRecoverableReason,
|
|
636
|
+
this.config.localLlm400CooldownMs
|
|
637
|
+
);
|
|
638
|
+
this.consecutive400s = 0;
|
|
639
|
+
return null;
|
|
640
|
+
}
|
|
641
|
+
} catch (e) {
|
|
642
|
+
log.debug(`local LLM failed to inspect retryable error body: ${e}`);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (response.status < 500 || attempt >= maxAttempts) break;
|
|
646
|
+
const backoffMs = this.config.localLlmRetryBackoffMs * attempt;
|
|
647
|
+
log.warn(
|
|
648
|
+
`local LLM request got ${response.status}; retrying (attempt ${attempt + 1}/${maxAttempts}) after ${backoffMs}ms`
|
|
649
|
+
);
|
|
650
|
+
await new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
651
|
+
}
|
|
652
|
+
log.debug(
|
|
653
|
+
`local LLM: received response, status=${response?.status}, ok=${response?.ok}`
|
|
654
|
+
);
|
|
655
|
+
if (!response) {
|
|
656
|
+
if (lastAbortError) {
|
|
657
|
+
log.warn(
|
|
658
|
+
`local LLM request aborted after ${maxAttempts} attempt(s): op=${operation} timeoutMs=${effectiveTimeoutMs} model=${this.config.localLlmModel} promptChars=${promptChars} durationMs=${Date.now() - startedAtMs}`
|
|
659
|
+
);
|
|
660
|
+
} else {
|
|
661
|
+
log.warn(
|
|
662
|
+
`local LLM request failed: no response object (op=${operation} model=${this.config.localLlmModel} durationMs=${Date.now() - startedAtMs})`
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
if (!response.ok) {
|
|
668
|
+
let reason = "";
|
|
669
|
+
let errorText = "";
|
|
670
|
+
try {
|
|
671
|
+
errorText = await response.text();
|
|
672
|
+
try {
|
|
673
|
+
const parsed = JSON.parse(errorText);
|
|
674
|
+
reason = parsed?.error?.message ? ` \u2014 ${parsed.error.message}` : "";
|
|
675
|
+
} catch {
|
|
676
|
+
log.debug(`local LLM error body: ${errorText.slice(0, 500)}`);
|
|
677
|
+
}
|
|
678
|
+
} catch (e) {
|
|
679
|
+
log.debug(`local LLM failed to read error body: ${e}`);
|
|
680
|
+
}
|
|
681
|
+
log.warn(
|
|
682
|
+
`local LLM request failed: ${response.status} ${response.statusText}${reason} (op=${operation}, model=${this.config.localLlmModel}, url=${chatUrl}, promptChars=${promptChars}, maxTokens=${requestBody.max_tokens})`
|
|
683
|
+
);
|
|
684
|
+
const nonRecoverableReason = this.extractNonRecoverableBackendReason(reason) ?? this.extractNonRecoverableBackendReasonFromErrorText(errorText);
|
|
685
|
+
if (nonRecoverableReason) {
|
|
686
|
+
this.markBackendUnavailable(
|
|
687
|
+
nonRecoverableReason,
|
|
688
|
+
this.config.localLlm400CooldownMs
|
|
689
|
+
);
|
|
690
|
+
this.consecutive400s = 0;
|
|
691
|
+
return null;
|
|
692
|
+
}
|
|
693
|
+
if (response.status === 400) {
|
|
694
|
+
this.consecutive400s += 1;
|
|
695
|
+
if (this.consecutive400s >= this.config.localLlm400TripThreshold) {
|
|
696
|
+
this.cooldownUntilMs = Date.now() + this.config.localLlm400CooldownMs;
|
|
697
|
+
log.warn(
|
|
698
|
+
`local LLM: entering cooldown for ${this.config.localLlm400CooldownMs}ms after ${this.consecutive400s} consecutive 400 responses`
|
|
699
|
+
);
|
|
700
|
+
this.consecutive400s = 0;
|
|
701
|
+
}
|
|
702
|
+
} else {
|
|
703
|
+
this.consecutive400s = 0;
|
|
704
|
+
}
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
this.consecutive400s = 0;
|
|
708
|
+
const data = await response.json();
|
|
709
|
+
log.debug(
|
|
710
|
+
`local LLM response: choices=${data.choices?.length}, usage=${JSON.stringify(data.usage)}`
|
|
711
|
+
);
|
|
712
|
+
const msg = data.choices?.[0]?.message;
|
|
713
|
+
const content = msg?.content || msg?.reasoning_content || "";
|
|
714
|
+
if (!content) {
|
|
715
|
+
log.warn(`local LLM returned empty content. choices=${JSON.stringify(data.choices)?.slice(0, 200)}`);
|
|
716
|
+
return null;
|
|
717
|
+
}
|
|
718
|
+
const usage = data.usage ? {
|
|
719
|
+
promptTokens: data.usage.prompt_tokens ?? 0,
|
|
720
|
+
completionTokens: data.usage.completion_tokens ?? 0,
|
|
721
|
+
totalTokens: data.usage.total_tokens ?? 0
|
|
722
|
+
} : this.estimateTokens(messages, content);
|
|
723
|
+
const durationMs = Date.now() - startedAtMs;
|
|
724
|
+
if (this.config.slowLogEnabled && durationMs >= this.config.slowLogThresholdMs) {
|
|
725
|
+
const promptChars2 = messages.reduce((sum, m) => sum + (m.content?.length ?? 0), 0);
|
|
726
|
+
const op = options.operation ? ` op=${options.operation}` : "";
|
|
727
|
+
log.warn(
|
|
728
|
+
`SLOW local LLM:${op} durationMs=${durationMs} model=${this.config.localLlmModel} url=${chatUrl} promptChars=${promptChars2} outputTokens=${usage.completionTokens} totalTokens=${usage.totalTokens}`
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
log.debug("local LLM: request succeeded, tokens:", usage.totalTokens);
|
|
732
|
+
return { content, usage };
|
|
733
|
+
} catch (err) {
|
|
734
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
735
|
+
const durationMs = Date.now() - startedAtMs;
|
|
736
|
+
if (this.isAbortError(err)) {
|
|
737
|
+
log.warn(
|
|
738
|
+
`local LLM request aborted: op=${operation} timeoutMs=${options.timeoutMs ?? this.config.localLlmTimeoutMs} model=${this.config.localLlmModel} durationMs=${durationMs} error=${errMsg}`
|
|
739
|
+
);
|
|
740
|
+
return null;
|
|
741
|
+
}
|
|
742
|
+
log.warn(`local LLM request error: op=${operation} error=${errMsg}`);
|
|
743
|
+
this.isAvailable = false;
|
|
744
|
+
const nonRecoverableReason = this.extractNonRecoverableBackendReason(errMsg);
|
|
745
|
+
if (nonRecoverableReason) {
|
|
746
|
+
this.markBackendUnavailable(
|
|
747
|
+
nonRecoverableReason,
|
|
748
|
+
this.config.localLlm400CooldownMs
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
return null;
|
|
752
|
+
} finally {
|
|
753
|
+
if (queueMeta) {
|
|
754
|
+
const finishedAtMs = Date.now();
|
|
755
|
+
const waitMs = startedAtMs - queueMeta.enqueuedAtMs;
|
|
756
|
+
log.debug(
|
|
757
|
+
`local LLM queue finish: priority=${queueMeta.priority} waitMs=${waitMs} runMs=${finishedAtMs - startedAtMs} totalMs=${finishedAtMs - queueMeta.enqueuedAtMs} op=${operation}`
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Query the local LLM server for loaded model information.
|
|
764
|
+
* Returns null if unavailable or if the model is not found.
|
|
765
|
+
*/
|
|
766
|
+
async getLoadedModelInfo() {
|
|
767
|
+
const baseUrl = trimTrailingSlashes(
|
|
768
|
+
this.config.localLlmUrl.replace("localhost", "127.0.0.1")
|
|
769
|
+
);
|
|
770
|
+
const modelsUrl = baseUrl.endsWith("/v1") ? `${baseUrl}/models` : `${baseUrl}/v1/models`;
|
|
771
|
+
log.debug(`Fetching model info from ${modelsUrl}`);
|
|
772
|
+
try {
|
|
773
|
+
const result = await this.fetchWithTimeout(modelsUrl, 3e3);
|
|
774
|
+
if (!result.ok) {
|
|
775
|
+
if (result.status === 401 || result.status === 403) {
|
|
776
|
+
log.warn(
|
|
777
|
+
`Local LLM: unauthorized while fetching models from ${modelsUrl}; verify localLlmApiKey and localLlmAuthHeader settings`
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
log.warn(`Local LLM: Failed to fetch models from ${modelsUrl} - server returned error`);
|
|
781
|
+
return null;
|
|
782
|
+
}
|
|
783
|
+
if (!result.data) {
|
|
784
|
+
log.warn(`Local LLM: No data returned from ${modelsUrl}`);
|
|
785
|
+
return null;
|
|
786
|
+
}
|
|
787
|
+
const data = result.data;
|
|
788
|
+
if (!Array.isArray(data.data) || data.data.length === 0) {
|
|
789
|
+
log.warn("Local LLM returned no models");
|
|
790
|
+
return null;
|
|
791
|
+
}
|
|
792
|
+
const modelIds = data.data.map((m) => m.id).filter(Boolean);
|
|
793
|
+
log.debug(
|
|
794
|
+
`Local LLM: Found ${modelIds.length} model(s). First 10: ${modelIds.slice(0, 10).join(", ")}`
|
|
795
|
+
);
|
|
796
|
+
const configuredModel = this.config.localLlmModel;
|
|
797
|
+
let model = data.data.find((m) => m.id === configuredModel);
|
|
798
|
+
if (!model) {
|
|
799
|
+
model = data.data.find(
|
|
800
|
+
(m) => configuredModel.includes(m.id || "") || (m.id || "").includes(configuredModel.replace(/@\d+bit$/, ""))
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
if (!model) {
|
|
804
|
+
model = data.data[0];
|
|
805
|
+
const availablePreview = data.data.map((m) => m.id).filter(Boolean).slice(0, 10).join(", ");
|
|
806
|
+
log.warn(
|
|
807
|
+
`Configured model "${configuredModel}" not found in local LLM. Using "${model.id}" instead. Available (first 10): ${availablePreview}`
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
let contextWindow = model.max_context_length || model.max_tokens;
|
|
811
|
+
if (!contextWindow) {
|
|
812
|
+
log.info("Local LLM: API did not report context window, trying LMS CLI...");
|
|
813
|
+
const lmsContext = this.getCachedContextWindow(model.id || "");
|
|
814
|
+
if (lmsContext) {
|
|
815
|
+
contextWindow = lmsContext;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
this.cachedModelInfo = {
|
|
819
|
+
id: model.id || "unknown",
|
|
820
|
+
contextWindow,
|
|
821
|
+
maxTokens: model.max_tokens
|
|
822
|
+
};
|
|
823
|
+
log.info(
|
|
824
|
+
`Local LLM model detected: ${this.cachedModelInfo.id}, context window: ${contextWindow?.toLocaleString() || "unknown (may use default)"}`
|
|
825
|
+
);
|
|
826
|
+
return this.cachedModelInfo;
|
|
827
|
+
} catch (err) {
|
|
828
|
+
log.warn(`Failed to fetch model info: ${err}`);
|
|
829
|
+
return null;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Check if the configured model is available and get its actual context window.
|
|
834
|
+
* Warns if there's a mismatch between expected and actual context.
|
|
835
|
+
*/
|
|
836
|
+
async validateModelConfig(expectedContextWindow) {
|
|
837
|
+
const warnings = [];
|
|
838
|
+
const modelInfo = await this.getLoadedModelInfo();
|
|
839
|
+
if (!modelInfo) {
|
|
840
|
+
return { available: false, warnings: ["Could not query local LLM for model info"] };
|
|
841
|
+
}
|
|
842
|
+
if (expectedContextWindow && modelInfo.contextWindow) {
|
|
843
|
+
if (modelInfo.contextWindow < expectedContextWindow) {
|
|
844
|
+
warnings.push(
|
|
845
|
+
`Context window mismatch: Model ${modelInfo.id} supports ${modelInfo.contextWindow.toLocaleString()} tokens, but engram is configured for ${expectedContextWindow.toLocaleString()}. Set localLlmMaxContext: ${modelInfo.contextWindow} in config to avoid errors.`
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
if (!modelInfo.contextWindow) {
|
|
850
|
+
warnings.push(
|
|
851
|
+
`Local LLM server did not report context window for ${modelInfo.id}. If you get "context length exceeded" errors, set localLlmMaxContext in config.`
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
return {
|
|
855
|
+
available: true,
|
|
856
|
+
actualContextWindow: modelInfo.contextWindow,
|
|
857
|
+
warnings
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Make a chat completion request to local LLM
|
|
862
|
+
*/
|
|
863
|
+
async chatCompletion(messages, options = {}) {
|
|
864
|
+
if (!this.config.localLlmEnabled) {
|
|
865
|
+
log.debug("local LLM: disabled, returning null");
|
|
866
|
+
return null;
|
|
867
|
+
}
|
|
868
|
+
const remainingMs = this.remainingCooldownMs();
|
|
869
|
+
if (remainingMs > 0) {
|
|
870
|
+
log.debug(`local LLM: cooldown active (${remainingMs}ms remaining), skipping request`);
|
|
871
|
+
return null;
|
|
872
|
+
}
|
|
873
|
+
if (options.priority) {
|
|
874
|
+
const priority = options.priority;
|
|
875
|
+
return await new Promise((resolve) => {
|
|
876
|
+
this.requestQueues[priority].push({
|
|
877
|
+
messages,
|
|
878
|
+
options,
|
|
879
|
+
priority,
|
|
880
|
+
enqueuedAtMs: Date.now(),
|
|
881
|
+
resolve
|
|
882
|
+
});
|
|
883
|
+
this.scheduleQueueDrain();
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
return await this.runChatCompletionRequest(messages, options);
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Estimate tokens when local LLM doesn't return usage stats
|
|
890
|
+
* Rough estimate: 1 token ≈ 4 characters
|
|
891
|
+
*/
|
|
892
|
+
estimateTokens(messages, response) {
|
|
893
|
+
const promptChars = messages.reduce((sum, m) => sum + m.content.length, 0);
|
|
894
|
+
const promptTokens = Math.ceil(promptChars / 4);
|
|
895
|
+
const completionTokens = Math.ceil(response.length / 4);
|
|
896
|
+
return {
|
|
897
|
+
promptTokens,
|
|
898
|
+
completionTokens,
|
|
899
|
+
totalTokens: promptTokens + completionTokens
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Try local LLM first, fallback to cloud provider if configured
|
|
904
|
+
*/
|
|
905
|
+
async withFallback(localOperation, fallbackOperation, operationName) {
|
|
906
|
+
if (this.config.localLlmEnabled) {
|
|
907
|
+
const localResult = await localOperation();
|
|
908
|
+
if (localResult !== null) {
|
|
909
|
+
log.debug(`${operationName}: used local LLM`);
|
|
910
|
+
return localResult;
|
|
911
|
+
}
|
|
912
|
+
if (this.config.localLlmFallback) {
|
|
913
|
+
log.info(`${operationName}: local LLM unavailable, falling back to cloud`);
|
|
914
|
+
} else {
|
|
915
|
+
throw new Error(`${operationName}: local LLM unavailable and fallback disabled`);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
return fallbackOperation();
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
export {
|
|
923
|
+
LocalLlmClient
|
|
924
|
+
};
|
|
925
|
+
//# sourceMappingURL=chunk-MDDAA2AO.js.map
|