@remnic/core 10.0.74 → 10.0.75
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 +46 -45
- package/dist/access-authorization-probe.js +47 -46
- package/dist/access-boundary.js +46 -45
- package/dist/access-cli.js +102 -101
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.js +46 -45
- package/dist/access-http-briefing.js +47 -46
- package/dist/access-http-query.js +48 -47
- package/dist/access-http.js +65 -64
- package/dist/access-identity-continuity-surface.js +46 -45
- package/dist/access-lcm-surface.js +46 -45
- package/dist/access-mcp.js +55 -54
- package/dist/access-namespace-preflight.js +46 -45
- package/dist/access-observe-write-category.js +6 -6
- package/dist/access-observe-write-surface.js +46 -45
- package/dist/access-offline-manifest.js +5 -5
- package/dist/access-operations-batch.js +47 -46
- package/dist/access-operations.js +54 -53
- package/dist/access-recall-concurrency.js +46 -45
- package/dist/access-recall-response.js +46 -45
- package/dist/access-recall-surface.js +46 -45
- package/dist/access-schema.js +5 -5
- package/dist/access-service.js +46 -45
- package/dist/access-surface-catalog.js +48 -47
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/active-recall.js +16 -16
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/{auto-sync-ANOJDQD4.js → auto-sync-XHSS3XPL.js} +5 -5
- package/dist/briefing.js +13 -13
- package/dist/buffer.js +3 -3
- package/dist/calibration.js +5 -4
- package/dist/calibration.js.map +1 -1
- package/dist/causal-consolidation.js +14 -14
- package/dist/{chunk-NHTEEGE4.js → chunk-233S3MXC.js} +3 -3
- package/dist/{chunk-N7NAGECH.js → chunk-36B5VPRU.js} +2 -2
- package/dist/{chunk-LC55ZXLR.js → chunk-3GGCMRO7.js} +2 -2
- package/dist/{chunk-RZED7FAU.js → chunk-3M7YP42P.js} +2 -2
- package/dist/{chunk-KPCTZB3U.js → chunk-3MBSD53O.js} +2 -2
- package/dist/{chunk-VWHWWIFS.js → chunk-4DLZ2ITE.js} +2 -2
- package/dist/{chunk-WN2DASTS.js → chunk-5BE3FLKL.js} +4 -4
- package/dist/{chunk-45SFSQON.js → chunk-5IQL4N37.js} +9 -9
- package/dist/{chunk-CIL4GHMD.js → chunk-6Q6AQ36B.js} +2 -2
- package/dist/{chunk-XV7PJVQO.js → chunk-6RPPZL7W.js} +2 -2
- package/dist/{chunk-EQ6EMJRG.js → chunk-6Y5ANLDH.js} +2 -2
- package/dist/{chunk-V33ETCF6.js → chunk-7FFGWIBN.js} +2 -2
- package/dist/{chunk-5RW247F5.js → chunk-AY2UK5XK.js} +14 -9
- package/dist/chunk-AY2UK5XK.js.map +1 -0
- package/dist/{chunk-5QV6CE6K.js → chunk-B36ACJG7.js} +2 -2
- package/dist/{chunk-2WDFLORC.js → chunk-BZTA7DR4.js} +3 -3
- package/dist/{chunk-5XOCAJHC.js → chunk-C44DRDZM.js} +2 -2
- package/dist/{chunk-UIFWZMO7.js → chunk-CLVILSBR.js} +7 -7
- package/dist/{chunk-KH2HB3OW.js → chunk-COIQ5N4J.js} +2 -2
- package/dist/{chunk-CU5N4RDI.js → chunk-DANFOVQG.js} +2 -2
- package/dist/{chunk-FU2ZLXVN.js → chunk-DEGE32C3.js} +2 -2
- package/dist/{chunk-YIJECSIX.js → chunk-DJDJBMFX.js} +9 -2
- package/dist/chunk-DJDJBMFX.js.map +1 -0
- package/dist/{chunk-LUXVW5UU.js → chunk-E22CQZ3H.js} +14 -14
- package/dist/{chunk-DVDRKCZK.js → chunk-ELUG6NKQ.js} +2 -2
- package/dist/{chunk-DLLQMNYQ.js → chunk-F3FNXE7C.js} +3 -3
- package/dist/{chunk-YIXWY4OZ.js → chunk-FETPTCQZ.js} +2 -2
- package/dist/{chunk-WPPAXTW6.js → chunk-FGUWCE5I.js} +2 -2
- package/dist/{chunk-FDUCIUQ5.js → chunk-FJ2VJTGA.js} +2 -2
- package/dist/{chunk-ENPJX3BB.js → chunk-FMFCQRDM.js} +3 -3
- package/dist/{chunk-BNU5TQLR.js → chunk-FOQ7TW2T.js} +2 -2
- package/dist/{chunk-DHQLVS7J.js → chunk-FP2AT4WL.js} +2 -2
- package/dist/{chunk-DLZGSZVC.js → chunk-FYQUUIPI.js} +4 -4
- package/dist/{chunk-XTN54NML.js → chunk-G3T6YMBO.js} +4 -4
- package/dist/{chunk-MUH7RR6T.js → chunk-G5NH3SSX.js} +5 -5
- package/dist/{chunk-G7I2RQ64.js → chunk-GCJA5KKT.js} +2 -2
- package/dist/{chunk-A2BH2VE2.js → chunk-GWOFCHPN.js} +2 -2
- package/dist/{chunk-TJ5YGCPV.js → chunk-GXVK754R.js} +2 -2
- package/dist/{chunk-Y2ZGT4W7.js → chunk-HB5TNQMM.js} +2 -2
- package/dist/{chunk-QYLEE4UQ.js → chunk-HCFNZWFB.js} +2 -2
- package/dist/{chunk-F4ZHKDBW.js → chunk-HLWXCDUQ.js} +2 -2
- package/dist/{chunk-62O7YWBR.js → chunk-HTDXNIO6.js} +2 -2
- package/dist/{chunk-4RASUAN3.js → chunk-HXFIILGF.js} +4 -4
- package/dist/{chunk-6EKP7BJW.js → chunk-I4LJJ44E.js} +2 -2
- package/dist/{chunk-STDAR7M7.js → chunk-IBQ7LGUJ.js} +6 -6
- package/dist/{chunk-PUP7NTNB.js → chunk-IRFQKR4Y.js} +2 -2
- package/dist/{chunk-BEQU6QGW.js → chunk-IVXOYE43.js} +2 -2
- package/dist/{chunk-WXPBDEMW.js → chunk-JDQEQE2R.js} +2 -2
- package/dist/{chunk-CAVXVGYK.js → chunk-KAED7IBB.js} +2 -2
- package/dist/{chunk-OFOV6TTZ.js → chunk-KNZDGZSY.js} +2 -2
- package/dist/{chunk-KFZ3A2EA.js → chunk-L4QYW2HO.js} +2 -2
- package/dist/{chunk-IXBIL5WY.js → chunk-LJUQPT5G.js} +3 -3
- package/dist/{chunk-4SOISEZ3.js → chunk-MBENRPMY.js} +10 -10
- package/dist/chunk-MJFL75J6.js +42 -0
- package/dist/chunk-MJFL75J6.js.map +1 -0
- package/dist/{chunk-OKBGLAAI.js → chunk-N6GEGRCT.js} +2 -2
- package/dist/{chunk-U2A56LLF.js → chunk-OBQ2NZAR.js} +10 -10
- package/dist/{chunk-WGRAB3KB.js → chunk-ORVPS4ZW.js} +2 -2
- package/dist/{chunk-PFUXPTBT.js → chunk-P3AQ2YSX.js} +4 -4
- package/dist/{chunk-DE4U5PTV.js → chunk-P4TEQSIZ.js} +2 -2
- package/dist/{chunk-DQVT45LZ.js → chunk-P7HNMNN2.js} +7 -7
- package/dist/{chunk-ZG4GNPRE.js → chunk-PBE7NFB5.js} +5 -5
- package/dist/{chunk-T64HVBAP.js → chunk-PLGBPFEO.js} +2 -2
- package/dist/{chunk-5MA7ZLPD.js → chunk-QNUNAXBE.js} +35 -1
- package/dist/chunk-QNUNAXBE.js.map +1 -0
- package/dist/{chunk-GO2GCJ7S.js → chunk-R24RYSQX.js} +41 -41
- package/dist/{chunk-YJX3K5LK.js → chunk-RXGFITWS.js} +4 -4
- package/dist/{chunk-JEX2JL3U.js → chunk-SHVLNI4L.js} +2 -2
- package/dist/{chunk-LIGA5KHG.js → chunk-SY4AFWUZ.js} +31 -31
- package/dist/{chunk-GI47WZWA.js → chunk-SYDPJUFR.js} +2 -2
- package/dist/{chunk-55NKJDKX.js → chunk-SZBZOUHR.js} +2 -2
- package/dist/{chunk-UEY5TPEE.js → chunk-T6C3G67T.js} +2 -2
- package/dist/{chunk-E3NBMTTX.js → chunk-TLTBACYX.js} +2 -2
- package/dist/{chunk-AVVCPA7U.js → chunk-UCPH56YX.js} +2 -2
- package/dist/{chunk-HPCWJMMQ.js → chunk-URS56HGW.js} +5 -5
- package/dist/{chunk-GJOBCNST.js → chunk-VEQVXNWR.js} +4 -4
- package/dist/{chunk-2H3SITYD.js → chunk-VEWTHVX2.js} +2 -2
- package/dist/{chunk-K5Y42U2P.js → chunk-VPEMGBTI.js} +2 -2
- package/dist/{chunk-Q2QPHGEC.js → chunk-VYOAYFDV.js} +47 -47
- package/dist/{chunk-5JBVPJCT.js → chunk-WIAXVBQH.js} +2 -2
- package/dist/{chunk-KWUMXJN7.js → chunk-X53HJENJ.js} +2 -2
- package/dist/{chunk-KY3KJUNN.js → chunk-XC7OYOQC.js} +6 -6
- package/dist/{chunk-43FLJLT2.js → chunk-Y5HZFIQO.js} +1 -1
- package/dist/{chunk-DOJBV6WR.js → chunk-YK6SFDP2.js} +4 -4
- package/dist/{chunk-WBMT3C6A.js → chunk-Z3CKK5RR.js} +3 -3
- package/dist/{chunk-RQHIN3PY.js → chunk-ZG57BEUS.js} +2 -2
- package/dist/{chunk-MOJ4UDA3.js → chunk-ZZSFRWKD.js} +2 -2
- package/dist/cli.js +78 -77
- package/dist/coding/export-okf-codegraph.js +12 -12
- package/dist/compounding/engine.js +14 -14
- package/dist/config.js +16 -16
- package/dist/connectors/codex-materialize-runner.js +13 -13
- package/dist/connectors/index.js +13 -13
- package/dist/convergence-refresh.js +15 -15
- package/dist/corpus-watermark.js +15 -15
- package/dist/deep-recall-http-glue.js +6 -6
- package/dist/deep-recall-policy-llm.js +3 -3
- package/dist/deep-recall.js +2 -2
- package/dist/entity-retrieval.js +13 -13
- package/dist/external-wiki-access.js +47 -46
- package/dist/extraction-faithfulness.js +3 -3
- package/dist/extraction-judge.js +3 -3
- package/dist/extraction.js +21 -20
- 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/{first-start-migration-A6PHIBUQ.js → first-start-migration-QVD22WWS.js} +4 -4
- package/dist/harmonic-retrieval.js +13 -13
- package/dist/index.js +127 -126
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.js +5 -5
- package/dist/local-llm-native-schema.d.ts +50 -0
- package/dist/local-llm-native-schema.d.ts.map +1 -0
- package/dist/local-llm-native-schema.js +8 -0
- package/dist/local-llm-native-schema.js.map +1 -0
- package/dist/local-llm.d.ts +1 -0
- package/dist/local-llm.d.ts.map +1 -1
- package/dist/local-llm.js +4 -3
- package/dist/location/index.js +15 -15
- package/dist/maintenance/memory-governance.js +13 -13
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +13 -13
- package/dist/maintenance/rebuild-memory-projection-runner.js +15 -15
- package/dist/maintenance/rebuild-memory-projection.js +14 -14
- package/dist/memory-browse-operations.js +47 -46
- package/dist/memory-browse.js +3 -3
- package/dist/model-registry.d.ts +11 -0
- package/dist/model-registry.d.ts.map +1 -1
- package/dist/model-registry.js +1 -1
- package/dist/namespaces/migrate.js +14 -14
- package/dist/namespaces/search.js +7 -7
- package/dist/namespaces/storage.js +13 -13
- package/dist/offline-sync-changeset-generations.js +3 -3
- package/dist/offline-sync-embedding-generation.js +2 -2
- package/dist/offline-sync-snapshot-stream.js +5 -5
- package/dist/offline-sync.js +7 -7
- package/dist/operator-doctor-corpus.js +16 -16
- package/dist/operator-toolkit.js +25 -25
- package/dist/orchestration/backlog-scheduler.d.ts +8 -0
- package/dist/orchestration/backlog-scheduler.d.ts.map +1 -1
- package/dist/orchestration/backlog-scheduler.js +115 -103
- package/dist/orchestration/backlog-scheduler.js.map +1 -1
- package/dist/orchestration/buffer-drain-runner.js +103 -102
- package/dist/orchestration/maintenance.js +26 -25
- package/dist/orchestrator.js +102 -101
- package/dist/recall-navigation-operations.js +47 -46
- package/dist/recall-navigation.js +46 -45
- 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/resume-bundles.js +16 -16
- package/dist/search/factory.js +6 -6
- package/dist/search/index.js +11 -11
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/orama-backend.js +2 -2
- package/dist/semantic-consolidation.js +14 -14
- package/dist/semantic-rule-promotion.js +13 -13
- package/dist/semantic-rule-verifier.js +13 -13
- package/dist/source-agent-qualifier.js +16 -16
- package/dist/standup/build.js +13 -13
- package/dist/storage.js +12 -12
- package/dist/summarizer.js +7 -6
- package/dist/support-passport/index.js +49 -48
- package/dist/transfer/export-okf.js +13 -13
- package/dist/verified-recall.js +13 -13
- package/dist/who-knows-cli.js +14 -14
- package/dist/who-knows.js +13 -13
- package/package.json +2 -2
- package/src/backlog-scheduler.test.ts +104 -0
- package/src/fallback-llm-codex-subscription.ts +10 -1
- package/src/local-llm-json-schema-fallback.test.ts +315 -0
- package/src/local-llm-native-schema.ts +78 -0
- package/src/local-llm.ts +8 -9
- package/src/model-registry.ts +40 -0
- package/src/orchestration/backlog-scheduler.ts +32 -0
- package/dist/chunk-5MA7ZLPD.js.map +0 -1
- package/dist/chunk-5RW247F5.js.map +0 -1
- package/dist/chunk-YIJECSIX.js.map +0 -1
- /package/dist/{auto-sync-ANOJDQD4.js.map → auto-sync-XHSS3XPL.js.map} +0 -0
- /package/dist/{chunk-NHTEEGE4.js.map → chunk-233S3MXC.js.map} +0 -0
- /package/dist/{chunk-N7NAGECH.js.map → chunk-36B5VPRU.js.map} +0 -0
- /package/dist/{chunk-LC55ZXLR.js.map → chunk-3GGCMRO7.js.map} +0 -0
- /package/dist/{chunk-RZED7FAU.js.map → chunk-3M7YP42P.js.map} +0 -0
- /package/dist/{chunk-KPCTZB3U.js.map → chunk-3MBSD53O.js.map} +0 -0
- /package/dist/{chunk-VWHWWIFS.js.map → chunk-4DLZ2ITE.js.map} +0 -0
- /package/dist/{chunk-WN2DASTS.js.map → chunk-5BE3FLKL.js.map} +0 -0
- /package/dist/{chunk-45SFSQON.js.map → chunk-5IQL4N37.js.map} +0 -0
- /package/dist/{chunk-CIL4GHMD.js.map → chunk-6Q6AQ36B.js.map} +0 -0
- /package/dist/{chunk-XV7PJVQO.js.map → chunk-6RPPZL7W.js.map} +0 -0
- /package/dist/{chunk-EQ6EMJRG.js.map → chunk-6Y5ANLDH.js.map} +0 -0
- /package/dist/{chunk-V33ETCF6.js.map → chunk-7FFGWIBN.js.map} +0 -0
- /package/dist/{chunk-5QV6CE6K.js.map → chunk-B36ACJG7.js.map} +0 -0
- /package/dist/{chunk-2WDFLORC.js.map → chunk-BZTA7DR4.js.map} +0 -0
- /package/dist/{chunk-5XOCAJHC.js.map → chunk-C44DRDZM.js.map} +0 -0
- /package/dist/{chunk-UIFWZMO7.js.map → chunk-CLVILSBR.js.map} +0 -0
- /package/dist/{chunk-KH2HB3OW.js.map → chunk-COIQ5N4J.js.map} +0 -0
- /package/dist/{chunk-CU5N4RDI.js.map → chunk-DANFOVQG.js.map} +0 -0
- /package/dist/{chunk-FU2ZLXVN.js.map → chunk-DEGE32C3.js.map} +0 -0
- /package/dist/{chunk-LUXVW5UU.js.map → chunk-E22CQZ3H.js.map} +0 -0
- /package/dist/{chunk-DVDRKCZK.js.map → chunk-ELUG6NKQ.js.map} +0 -0
- /package/dist/{chunk-DLLQMNYQ.js.map → chunk-F3FNXE7C.js.map} +0 -0
- /package/dist/{chunk-YIXWY4OZ.js.map → chunk-FETPTCQZ.js.map} +0 -0
- /package/dist/{chunk-WPPAXTW6.js.map → chunk-FGUWCE5I.js.map} +0 -0
- /package/dist/{chunk-FDUCIUQ5.js.map → chunk-FJ2VJTGA.js.map} +0 -0
- /package/dist/{chunk-ENPJX3BB.js.map → chunk-FMFCQRDM.js.map} +0 -0
- /package/dist/{chunk-BNU5TQLR.js.map → chunk-FOQ7TW2T.js.map} +0 -0
- /package/dist/{chunk-DHQLVS7J.js.map → chunk-FP2AT4WL.js.map} +0 -0
- /package/dist/{chunk-DLZGSZVC.js.map → chunk-FYQUUIPI.js.map} +0 -0
- /package/dist/{chunk-XTN54NML.js.map → chunk-G3T6YMBO.js.map} +0 -0
- /package/dist/{chunk-MUH7RR6T.js.map → chunk-G5NH3SSX.js.map} +0 -0
- /package/dist/{chunk-G7I2RQ64.js.map → chunk-GCJA5KKT.js.map} +0 -0
- /package/dist/{chunk-A2BH2VE2.js.map → chunk-GWOFCHPN.js.map} +0 -0
- /package/dist/{chunk-TJ5YGCPV.js.map → chunk-GXVK754R.js.map} +0 -0
- /package/dist/{chunk-Y2ZGT4W7.js.map → chunk-HB5TNQMM.js.map} +0 -0
- /package/dist/{chunk-QYLEE4UQ.js.map → chunk-HCFNZWFB.js.map} +0 -0
- /package/dist/{chunk-F4ZHKDBW.js.map → chunk-HLWXCDUQ.js.map} +0 -0
- /package/dist/{chunk-62O7YWBR.js.map → chunk-HTDXNIO6.js.map} +0 -0
- /package/dist/{chunk-4RASUAN3.js.map → chunk-HXFIILGF.js.map} +0 -0
- /package/dist/{chunk-6EKP7BJW.js.map → chunk-I4LJJ44E.js.map} +0 -0
- /package/dist/{chunk-STDAR7M7.js.map → chunk-IBQ7LGUJ.js.map} +0 -0
- /package/dist/{chunk-PUP7NTNB.js.map → chunk-IRFQKR4Y.js.map} +0 -0
- /package/dist/{chunk-BEQU6QGW.js.map → chunk-IVXOYE43.js.map} +0 -0
- /package/dist/{chunk-WXPBDEMW.js.map → chunk-JDQEQE2R.js.map} +0 -0
- /package/dist/{chunk-CAVXVGYK.js.map → chunk-KAED7IBB.js.map} +0 -0
- /package/dist/{chunk-OFOV6TTZ.js.map → chunk-KNZDGZSY.js.map} +0 -0
- /package/dist/{chunk-KFZ3A2EA.js.map → chunk-L4QYW2HO.js.map} +0 -0
- /package/dist/{chunk-IXBIL5WY.js.map → chunk-LJUQPT5G.js.map} +0 -0
- /package/dist/{chunk-4SOISEZ3.js.map → chunk-MBENRPMY.js.map} +0 -0
- /package/dist/{chunk-OKBGLAAI.js.map → chunk-N6GEGRCT.js.map} +0 -0
- /package/dist/{chunk-U2A56LLF.js.map → chunk-OBQ2NZAR.js.map} +0 -0
- /package/dist/{chunk-WGRAB3KB.js.map → chunk-ORVPS4ZW.js.map} +0 -0
- /package/dist/{chunk-PFUXPTBT.js.map → chunk-P3AQ2YSX.js.map} +0 -0
- /package/dist/{chunk-DE4U5PTV.js.map → chunk-P4TEQSIZ.js.map} +0 -0
- /package/dist/{chunk-DQVT45LZ.js.map → chunk-P7HNMNN2.js.map} +0 -0
- /package/dist/{chunk-ZG4GNPRE.js.map → chunk-PBE7NFB5.js.map} +0 -0
- /package/dist/{chunk-T64HVBAP.js.map → chunk-PLGBPFEO.js.map} +0 -0
- /package/dist/{chunk-GO2GCJ7S.js.map → chunk-R24RYSQX.js.map} +0 -0
- /package/dist/{chunk-YJX3K5LK.js.map → chunk-RXGFITWS.js.map} +0 -0
- /package/dist/{chunk-JEX2JL3U.js.map → chunk-SHVLNI4L.js.map} +0 -0
- /package/dist/{chunk-LIGA5KHG.js.map → chunk-SY4AFWUZ.js.map} +0 -0
- /package/dist/{chunk-GI47WZWA.js.map → chunk-SYDPJUFR.js.map} +0 -0
- /package/dist/{chunk-55NKJDKX.js.map → chunk-SZBZOUHR.js.map} +0 -0
- /package/dist/{chunk-UEY5TPEE.js.map → chunk-T6C3G67T.js.map} +0 -0
- /package/dist/{chunk-E3NBMTTX.js.map → chunk-TLTBACYX.js.map} +0 -0
- /package/dist/{chunk-AVVCPA7U.js.map → chunk-UCPH56YX.js.map} +0 -0
- /package/dist/{chunk-HPCWJMMQ.js.map → chunk-URS56HGW.js.map} +0 -0
- /package/dist/{chunk-GJOBCNST.js.map → chunk-VEQVXNWR.js.map} +0 -0
- /package/dist/{chunk-2H3SITYD.js.map → chunk-VEWTHVX2.js.map} +0 -0
- /package/dist/{chunk-K5Y42U2P.js.map → chunk-VPEMGBTI.js.map} +0 -0
- /package/dist/{chunk-Q2QPHGEC.js.map → chunk-VYOAYFDV.js.map} +0 -0
- /package/dist/{chunk-5JBVPJCT.js.map → chunk-WIAXVBQH.js.map} +0 -0
- /package/dist/{chunk-KWUMXJN7.js.map → chunk-X53HJENJ.js.map} +0 -0
- /package/dist/{chunk-KY3KJUNN.js.map → chunk-XC7OYOQC.js.map} +0 -0
- /package/dist/{chunk-43FLJLT2.js.map → chunk-Y5HZFIQO.js.map} +0 -0
- /package/dist/{chunk-DOJBV6WR.js.map → chunk-YK6SFDP2.js.map} +0 -0
- /package/dist/{chunk-WBMT3C6A.js.map → chunk-Z3CKK5RR.js.map} +0 -0
- /package/dist/{chunk-RQHIN3PY.js.map → chunk-ZG57BEUS.js.map} +0 -0
- /package/dist/{chunk-MOJ4UDA3.js.map → chunk-ZZSFRWKD.js.map} +0 -0
- /package/dist/{first-start-migration-A6PHIBUQ.js.map → first-start-migration-QVD22WWS.js.map} +0 -0
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
+
import fs from "node:fs";
|
|
2
3
|
import http from "node:http";
|
|
4
|
+
import os from "node:os";
|
|
3
5
|
import type { AddressInfo } from "node:net";
|
|
6
|
+
import { join } from "node:path";
|
|
4
7
|
import test, { mock } from "node:test";
|
|
5
8
|
|
|
6
9
|
import { LocalLlmClient } from "./local-llm.js";
|
|
10
|
+
import { ModelRegistry } from "./model-registry.js";
|
|
11
|
+
import { NativeSchemaGate, type LocalLlmChatRequestBody } from "./local-llm-native-schema.js";
|
|
7
12
|
import { ChatTransport } from "./local-llm-transport.js";
|
|
8
13
|
import { parseConfig } from "./config.js";
|
|
9
14
|
import type { LocalLlmChatCompletionOptions } from "./local-llm-helpers.js";
|
|
@@ -276,3 +281,313 @@ test("request without response_format is unchanged", async () => {
|
|
|
276
281
|
}
|
|
277
282
|
});
|
|
278
283
|
|
|
284
|
+
// ── Schema-validation rejections (the provider parses our json_schema) ──────
|
|
285
|
+
//
|
|
286
|
+
// Production signature (2026-11-05, MiniMax-M3.1-Flash via local proxy): a
|
|
287
|
+
// 400 "invalid params — Mismatch type string with value array" quoting a
|
|
288
|
+
// schema fragment like `"episodeTitle": {"type": ["string","null"]}`. The
|
|
289
|
+
// provider accepts structured output in principle but its strict parser
|
|
290
|
+
// rejects our draft-7 nullable type unions. Unlike capability rejections
|
|
291
|
+
// these errors never name response_format, so nothing stripped the format,
|
|
292
|
+
// every drain attempt re-sent the identical payload, and the 400 breaker
|
|
293
|
+
// opened. The zod draft-7 schema legitimately contains `type` arrays
|
|
294
|
+
// (nullable fields), so the provider dialect, not our request, is the
|
|
295
|
+
// constraint: degrade to the pre-schema request shape and remember it.
|
|
296
|
+
|
|
297
|
+
const schemaValidationRejection = {
|
|
298
|
+
error: {
|
|
299
|
+
// Verbatim production body shape (2026-11-05): "Mismatch type" (no "ed"),
|
|
300
|
+
// JSON-escaped quotes around the schema fragment, and no mention of
|
|
301
|
+
// response_format anywhere.
|
|
302
|
+
message:
|
|
303
|
+
'invalid params, Mismatch type string with value array "at index 13779: \\"episodeTitle\\": {\\"type\\": [\\"string\\",\\"null\\"]}"',
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
test("a schema-validation 400 strips the format and retries without it", async () => {
|
|
308
|
+
const server = await startRecordingServer((_req, count) =>
|
|
309
|
+
count === 1
|
|
310
|
+
? { status: 400, body: schemaValidationRejection }
|
|
311
|
+
: { status: 200, body: okCompletion },
|
|
312
|
+
);
|
|
313
|
+
try {
|
|
314
|
+
const client = new LocalLlmClient(createConfig({ localLlmUrl: server.url }));
|
|
315
|
+
primeClient(client);
|
|
316
|
+
const result = await client.chatCompletion([{ role: "user", content: "hello" }], {
|
|
317
|
+
responseFormat,
|
|
318
|
+
});
|
|
319
|
+
assert.equal(result?.content, "done");
|
|
320
|
+
assert.equal(server.requests.length, 2, "exactly one strip-and-retry");
|
|
321
|
+
assert.ok(server.requests[0].body.response_format, "the schema-shaped request went out first");
|
|
322
|
+
assert.equal(server.requests[1].body.response_format, undefined);
|
|
323
|
+
} finally {
|
|
324
|
+
await server.close();
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("a rejected schema format is not re-sent on later requests", async () => {
|
|
329
|
+
const server = await startRecordingServer((_req, count) =>
|
|
330
|
+
count === 1
|
|
331
|
+
? { status: 400, body: schemaValidationRejection }
|
|
332
|
+
: { status: 200, body: okCompletion },
|
|
333
|
+
);
|
|
334
|
+
try {
|
|
335
|
+
const client = new LocalLlmClient(createConfig({ localLlmUrl: server.url }));
|
|
336
|
+
primeClient(client);
|
|
337
|
+
assert.equal(
|
|
338
|
+
(await client.chatCompletion([{ role: "user", content: "hello" }], { responseFormat }))?.content,
|
|
339
|
+
"done",
|
|
340
|
+
);
|
|
341
|
+
assert.equal(
|
|
342
|
+
(await client.chatCompletion([{ role: "user", content: "hello again" }], { responseFormat }))?.content,
|
|
343
|
+
"done",
|
|
344
|
+
);
|
|
345
|
+
assert.equal(server.requests.length, 3, "second call sends one request, not a new 400");
|
|
346
|
+
assert.equal(
|
|
347
|
+
server.requests[2].body.response_format,
|
|
348
|
+
undefined,
|
|
349
|
+
"the learned rejection skips the format entirely",
|
|
350
|
+
);
|
|
351
|
+
} finally {
|
|
352
|
+
await server.close();
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
test("a learned schema rejection persists in the model registry for fresh clients", async () => {
|
|
357
|
+
const memoryDir = fs.mkdtempSync(join(os.tmpdir(), "remnic-schema-cap-"));
|
|
358
|
+
const server = await startRecordingServer((_req, count) =>
|
|
359
|
+
count === 1
|
|
360
|
+
? { status: 400, body: schemaValidationRejection }
|
|
361
|
+
: { status: 200, body: okCompletion },
|
|
362
|
+
);
|
|
363
|
+
try {
|
|
364
|
+
const registry = new ModelRegistry(memoryDir);
|
|
365
|
+
const client = new LocalLlmClient(createConfig({ localLlmUrl: server.url }), registry);
|
|
366
|
+
primeClient(client);
|
|
367
|
+
assert.equal(
|
|
368
|
+
(await client.chatCompletion([{ role: "user", content: "hello" }], { responseFormat }))?.content,
|
|
369
|
+
"done",
|
|
370
|
+
);
|
|
371
|
+
// SAFETY: we wrote model-capabilities.json through ModelRegistry above; the
|
|
372
|
+
// document is an object keyed by normalized model id.
|
|
373
|
+
const persisted = JSON.parse(
|
|
374
|
+
fs.readFileSync(join(memoryDir, ".registry", "model-capabilities.json"), "utf8"),
|
|
375
|
+
) as { models: Record<string, { structuredOutput?: string }> };
|
|
376
|
+
assert.equal(persisted.models["test-local-model"]?.structuredOutput, "none");
|
|
377
|
+
|
|
378
|
+
const fresh = new LocalLlmClient(createConfig({ localLlmUrl: server.url }), new ModelRegistry(memoryDir));
|
|
379
|
+
primeClient(fresh);
|
|
380
|
+
assert.equal(
|
|
381
|
+
(await fresh.chatCompletion([{ role: "user", content: "hello" }], { responseFormat }))?.content,
|
|
382
|
+
"done",
|
|
383
|
+
);
|
|
384
|
+
const formatRequests = server.requests.filter((r) => r.body.response_format);
|
|
385
|
+
assert.equal(formatRequests.length, 1, "only the first-ever request carries the format");
|
|
386
|
+
} finally {
|
|
387
|
+
await server.close();
|
|
388
|
+
fs.rmSync(memoryDir, { recursive: true, force: true });
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test("a schema-carrying success records native and keeps sending the format", async () => {
|
|
393
|
+
const memoryDir = fs.mkdtempSync(join(os.tmpdir(), "remnic-schema-cap-"));
|
|
394
|
+
const server = await startRecordingServer(() => ({ status: 200, body: okCompletion }));
|
|
395
|
+
try {
|
|
396
|
+
const registry = new ModelRegistry(memoryDir);
|
|
397
|
+
const client = new LocalLlmClient(createConfig({ localLlmUrl: server.url }), registry);
|
|
398
|
+
primeClient(client);
|
|
399
|
+
await client.chatCompletion([{ role: "user", content: "hello" }], { responseFormat });
|
|
400
|
+
await client.chatCompletion([{ role: "user", content: "hello again" }], { responseFormat });
|
|
401
|
+
// SAFETY: we wrote model-capabilities.json through ModelRegistry above; the
|
|
402
|
+
// document is an object keyed by normalized model id.
|
|
403
|
+
const persisted = JSON.parse(
|
|
404
|
+
fs.readFileSync(join(memoryDir, ".registry", "model-capabilities.json"), "utf8"),
|
|
405
|
+
) as { models: Record<string, { structuredOutput?: string }> };
|
|
406
|
+
assert.equal(persisted.models["test-local-model"]?.structuredOutput, "native");
|
|
407
|
+
assert.equal(server.requests.length, 2);
|
|
408
|
+
assert.ok(server.requests[1].body.response_format, "structured output remains for supporting providers");
|
|
409
|
+
} finally {
|
|
410
|
+
await server.close();
|
|
411
|
+
fs.rmSync(memoryDir, { recursive: true, force: true });
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
test("an expired registry rejection is re-probed instead of honored forever", async () => {
|
|
416
|
+
const memoryDir = fs.mkdtempSync(join(os.tmpdir(), "remnic-schema-cap-"));
|
|
417
|
+
const server = await startRecordingServer(() => ({ status: 200, body: okCompletion }));
|
|
418
|
+
try {
|
|
419
|
+
const registry = new ModelRegistry(memoryDir);
|
|
420
|
+
registry.setStructuredOutput("test-local-model", "none");
|
|
421
|
+
// Age the entry past the 7-day registry TTL, as a long-lived deployment
|
|
422
|
+
// would: the learned rejection must not be honored forever.
|
|
423
|
+
const registryPath = join(memoryDir, ".registry", "model-capabilities.json");
|
|
424
|
+
// SAFETY: we wrote model-capabilities.json through ModelRegistry above; the
|
|
425
|
+
// document is an object keyed by normalized model id.
|
|
426
|
+
const persisted = JSON.parse(fs.readFileSync(registryPath, "utf8")) as {
|
|
427
|
+
models: Record<string, { fetchedAt: string }>;
|
|
428
|
+
};
|
|
429
|
+
persisted.models["test-local-model"].fetchedAt = new Date(
|
|
430
|
+
Date.now() - 8 * 24 * 60 * 60 * 1000,
|
|
431
|
+
).toISOString();
|
|
432
|
+
fs.writeFileSync(registryPath, JSON.stringify(persisted));
|
|
433
|
+
|
|
434
|
+
const client = new LocalLlmClient(createConfig({ localLlmUrl: server.url }), new ModelRegistry(memoryDir));
|
|
435
|
+
primeClient(client);
|
|
436
|
+
const result = await client.chatCompletion([{ role: "user", content: "hello" }], {
|
|
437
|
+
responseFormat,
|
|
438
|
+
});
|
|
439
|
+
assert.equal(result?.content, "done");
|
|
440
|
+
assert.ok(server.requests[0].body.response_format, "expired rejection is re-probed with the schema");
|
|
441
|
+
// The provider accepts it this time: the native record refreshes.
|
|
442
|
+
// SAFETY: the file above was written by ModelRegistry's own persistence;
|
|
443
|
+
// the document is an object keyed by normalized model id.
|
|
444
|
+
const refreshed = JSON.parse(fs.readFileSync(registryPath, "utf8")) as {
|
|
445
|
+
models: Record<string, { structuredOutput?: string }>;
|
|
446
|
+
};
|
|
447
|
+
assert.equal(refreshed.models["test-local-model"]?.structuredOutput, "native");
|
|
448
|
+
} finally {
|
|
449
|
+
await server.close();
|
|
450
|
+
fs.rmSync(memoryDir, { recursive: true, force: true });
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
test("a later capabilities write preserves the learned structuredOutput", async () => {
|
|
455
|
+
const memoryDir = fs.mkdtempSync(join(os.tmpdir(), "remnic-schema-cap-"));
|
|
456
|
+
try {
|
|
457
|
+
const registry = new ModelRegistry(memoryDir);
|
|
458
|
+
registry.setStructuredOutput("test-local-model", "none");
|
|
459
|
+
// Mirrors getCachedContextWindow's LM Studio detection replacing the record.
|
|
460
|
+
registry.setCapabilities("test-local-model", {
|
|
461
|
+
maxPositionEmbeddings: 131072,
|
|
462
|
+
contextWindow: 131072,
|
|
463
|
+
supportsExtendedContext: true,
|
|
464
|
+
typicalOutputTokens: 8192,
|
|
465
|
+
source: "lmstudio",
|
|
466
|
+
});
|
|
467
|
+
assert.equal(registry.getStructuredOutput("test-local-model"), "none");
|
|
468
|
+
} finally {
|
|
469
|
+
fs.rmSync(memoryDir, { recursive: true, force: true });
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test("an expired structuredOutput decision is not renewed by a capability refresh", async () => {
|
|
474
|
+
const memoryDir = fs.mkdtempSync(join(os.tmpdir(), "remnic-schema-cap-"));
|
|
475
|
+
try {
|
|
476
|
+
const registry = new ModelRegistry(memoryDir);
|
|
477
|
+
registry.setStructuredOutput("test-local-model", "none");
|
|
478
|
+
// Age the learned decision past the registry TTL exactly as a long-lived
|
|
479
|
+
// deployment would.
|
|
480
|
+
const registryPath = join(memoryDir, ".registry", "model-capabilities.json");
|
|
481
|
+
// SAFETY: we wrote model-capabilities.json through ModelRegistry above; the
|
|
482
|
+
// document is an object keyed by normalized model id.
|
|
483
|
+
const persisted = JSON.parse(fs.readFileSync(registryPath, "utf8")) as {
|
|
484
|
+
models: Record<string, { fetchedAt: string }>;
|
|
485
|
+
};
|
|
486
|
+
persisted.models["test-local-model"].fetchedAt = new Date(
|
|
487
|
+
Date.now() - 8 * 24 * 60 * 60 * 1000,
|
|
488
|
+
).toISOString();
|
|
489
|
+
fs.writeFileSync(registryPath, JSON.stringify(persisted));
|
|
490
|
+
// The refresh happens in a process that loaded the AGED state from disk
|
|
491
|
+
// (a restart or another runtime sharing the memory dir).
|
|
492
|
+
const refreshedRegistry = new ModelRegistry(memoryDir);
|
|
493
|
+
refreshedRegistry.setCapabilities("test-local-model", {
|
|
494
|
+
maxPositionEmbeddings: 131072,
|
|
495
|
+
contextWindow: 131072,
|
|
496
|
+
supportsExtendedContext: true,
|
|
497
|
+
typicalOutputTokens: 8192,
|
|
498
|
+
source: "lmstudio",
|
|
499
|
+
});
|
|
500
|
+
assert.equal(
|
|
501
|
+
refreshedRegistry.getStructuredOutput("test-local-model"),
|
|
502
|
+
undefined,
|
|
503
|
+
"an expired decision must not be renewed into a fresh record — the re-probe stays due",
|
|
504
|
+
);
|
|
505
|
+
} finally {
|
|
506
|
+
fs.rmSync(memoryDir, { recursive: true, force: true });
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
test("a stale in-flight schema success does not overwrite a current rejection", () => {
|
|
511
|
+
const memoryDir = fs.mkdtempSync(join(os.tmpdir(), "remnic-schema-cap-"));
|
|
512
|
+
try {
|
|
513
|
+
const registry = new ModelRegistry(memoryDir);
|
|
514
|
+
const gate = new NativeSchemaGate(() => registry);
|
|
515
|
+
// Two schema-bearing requests attach before either settles.
|
|
516
|
+
const body: LocalLlmChatRequestBody = {
|
|
517
|
+
model: "test-local-model",
|
|
518
|
+
messages: [{ role: "user", content: "hello" }],
|
|
519
|
+
temperature: 0.1,
|
|
520
|
+
max_tokens: 16,
|
|
521
|
+
stream: false,
|
|
522
|
+
};
|
|
523
|
+
gate.attach(body, { responseFormat }, "test-local-model");
|
|
524
|
+
// The rejection settles first: "none" is the newest evidence.
|
|
525
|
+
gate.rememberRejection("test-local-model");
|
|
526
|
+
// The older in-flight success settles last: it must not flip the
|
|
527
|
+
// decision back, or later requests re-send the rejected schema.
|
|
528
|
+
gate.recordSuccess(body, "test-local-model");
|
|
529
|
+
assert.equal(registry.getStructuredOutput("test-local-model"), "none");
|
|
530
|
+
// A fresh success with no rejection standing still records native.
|
|
531
|
+
const otherBody: LocalLlmChatRequestBody = { ...body, model: "other-model" };
|
|
532
|
+
const freshGate = new NativeSchemaGate(() => registry);
|
|
533
|
+
freshGate.attach(otherBody, { responseFormat }, "other-model");
|
|
534
|
+
freshGate.recordSuccess(otherBody, "other-model");
|
|
535
|
+
assert.equal(registry.getStructuredOutput("other-model"), "native");
|
|
536
|
+
} finally {
|
|
537
|
+
fs.rmSync(memoryDir, { recursive: true, force: true });
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
test("invalid params without schema evidence is not treated as a schema rejection", async () => {
|
|
542
|
+
const server = await startRecordingServer(() => ({
|
|
543
|
+
status: 400,
|
|
544
|
+
body: { error: { message: "invalid params: temperature must be between 0 and 2" } },
|
|
545
|
+
}));
|
|
546
|
+
try {
|
|
547
|
+
const client = new LocalLlmClient(createConfig({ localLlmUrl: server.url }));
|
|
548
|
+
primeClient(client);
|
|
549
|
+
assert.equal(await client.chatCompletion([{ role: "user", content: "hello" }], { responseFormat }), null);
|
|
550
|
+
assert.equal(server.requests.length, 1, "unrelated invalid-param errors must not strip the format");
|
|
551
|
+
} finally {
|
|
552
|
+
await server.close();
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
test("an echoed format name inside an unrelated invalid-param error does not strip", async () => {
|
|
557
|
+
const server = await startRecordingServer(() => ({
|
|
558
|
+
status: 400,
|
|
559
|
+
body: {
|
|
560
|
+
error: {
|
|
561
|
+
message: "invalid params: quota exceeded; request included response_format json_schema",
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
}));
|
|
565
|
+
try {
|
|
566
|
+
const client = new LocalLlmClient(createConfig({ localLlmUrl: server.url }));
|
|
567
|
+
primeClient(client);
|
|
568
|
+
assert.equal(await client.chatCompletion([{ role: "user", content: "hello" }], { responseFormat }), null);
|
|
569
|
+
assert.equal(server.requests.length, 1, "an echo of the format name is not schema-rejection evidence");
|
|
570
|
+
} finally {
|
|
571
|
+
await server.close();
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
test("a type mismatch on an unrelated parameter does not strip the schema", async () => {
|
|
576
|
+
const server = await startRecordingServer(() => ({
|
|
577
|
+
status: 400,
|
|
578
|
+
body: { error: { message: "invalid params: mismatch type for temperature, expected number" } },
|
|
579
|
+
}));
|
|
580
|
+
try {
|
|
581
|
+
const client = new LocalLlmClient(createConfig({ localLlmUrl: server.url }));
|
|
582
|
+
primeClient(client);
|
|
583
|
+
assert.equal(await client.chatCompletion([{ role: "user", content: "hello" }], { responseFormat }), null);
|
|
584
|
+
assert.equal(
|
|
585
|
+
server.requests.length,
|
|
586
|
+
1,
|
|
587
|
+
"no schema fragment quoted: the mismatch is not evidence our schema was rejected",
|
|
588
|
+
);
|
|
589
|
+
} finally {
|
|
590
|
+
await server.close();
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native json_schema capability shaping for the local LLM client.
|
|
3
|
+
*
|
|
4
|
+
* A provider that rejects the schema PAYLOAD itself (strict parsers that die
|
|
5
|
+
* on draft-7 nullable `type` unions, e.g. MiniMax-M3.1-Flash on 2026-11-05:
|
|
6
|
+
* "invalid params, Mismatch type string with value array") gets the pre-#3248
|
|
7
|
+
* shape: no response_format, the JSON contract stays in the prompt. The
|
|
8
|
+
* rejection is learned per model and persisted through the ModelRegistry so
|
|
9
|
+
* fresh runtimes shape requests correctly without re-proving it; a
|
|
10
|
+
* schema-carrying success persists `structuredOutput: "native"`.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { ResponseFormatJSONSchema } from "openai/resources/shared";
|
|
14
|
+
import type { LocalLlmChatCompletionOptions } from "./local-llm-helpers.js";
|
|
15
|
+
import type { ModelRegistry } from "./model-registry.js";
|
|
16
|
+
|
|
17
|
+
/** The chat-completions wire body; optional keys are capability-gated additions. */
|
|
18
|
+
export interface LocalLlmChatRequestBody {
|
|
19
|
+
model: string;
|
|
20
|
+
messages: Array<{ role: string; content: string }>;
|
|
21
|
+
temperature: number;
|
|
22
|
+
max_tokens: number;
|
|
23
|
+
stream: boolean;
|
|
24
|
+
response_format?: { type: string } | ResponseFormatJSONSchema;
|
|
25
|
+
chat_template_kwargs?: { enable_thinking: boolean };
|
|
26
|
+
reasoning_effort?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Learned structured-output shaping for one local endpoint. */
|
|
30
|
+
export class NativeSchemaGate {
|
|
31
|
+
/** Models rejected in this process — the fallback for registry-less clients. */
|
|
32
|
+
private readonly rejectedByProcess = new Set<string>();
|
|
33
|
+
|
|
34
|
+
constructor(private readonly registry: () => ModelRegistry | undefined) {}
|
|
35
|
+
|
|
36
|
+
/** False when the model is known to reject the native json_schema payload. */
|
|
37
|
+
usable(modelId: string): boolean {
|
|
38
|
+
const registry = this.registry();
|
|
39
|
+
// Registry-backed decisions honor the registry TTL: an expired entry is
|
|
40
|
+
// re-probed instead of being honored forever. The in-memory set only
|
|
41
|
+
// serves clients without a registry.
|
|
42
|
+
if (registry) return registry.getStructuredOutput(modelId) !== "none";
|
|
43
|
+
return !this.rejectedByProcess.has(modelId);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
rememberRejection(modelId: string): void {
|
|
47
|
+
const registry = this.registry();
|
|
48
|
+
if (registry) registry.setStructuredOutput(modelId, "none");
|
|
49
|
+
else this.rejectedByProcess.add(modelId);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Attach the schema format unless the gate suppresses it for this model. */
|
|
53
|
+
attach(
|
|
54
|
+
requestBody: LocalLlmChatRequestBody,
|
|
55
|
+
options: Pick<LocalLlmChatCompletionOptions, "responseFormat">,
|
|
56
|
+
modelId: string,
|
|
57
|
+
): void {
|
|
58
|
+
if (options.responseFormat?.type === "json_schema" && this.usable(modelId)) {
|
|
59
|
+
requestBody.response_format = options.responseFormat;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** A schema-carrying 200: persist the native capability once. A CURRENT
|
|
64
|
+
* "none" stands — that success is stale in-flight evidence from before the
|
|
65
|
+
* rejection, and overwriting it would make later requests re-send the
|
|
66
|
+
* rejected schema. */
|
|
67
|
+
recordSuccess(requestBody: LocalLlmChatRequestBody, modelId: string): void {
|
|
68
|
+
const registry = this.registry();
|
|
69
|
+
if (
|
|
70
|
+
!requestBody.response_format ||
|
|
71
|
+
!registry ||
|
|
72
|
+
registry.getStructuredOutput(modelId) !== undefined
|
|
73
|
+
) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
registry.setStructuredOutput(modelId, "native");
|
|
77
|
+
}
|
|
78
|
+
}
|
package/src/local-llm.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
type LocalLlmChatCompletionOptions,
|
|
25
25
|
type LocalLlmChatCompletionResult,
|
|
26
26
|
} from "./local-llm-helpers.js";
|
|
27
|
+
import { NativeSchemaGate, type LocalLlmChatRequestBody } from "./local-llm-native-schema.js";
|
|
27
28
|
import {
|
|
28
29
|
isObjectRecord,
|
|
29
30
|
orderedLocalServers,
|
|
@@ -189,6 +190,7 @@ export class LocalLlmClient {
|
|
|
189
190
|
private consecutive400s: number = 0;
|
|
190
191
|
private cooldownUntilMs: number = 0;
|
|
191
192
|
private modelRegistry?: ModelRegistry;
|
|
193
|
+
private readonly nativeSchemaGate = new NativeSchemaGate(() => this.modelRegistry);
|
|
192
194
|
private _disableThinking: boolean = false;
|
|
193
195
|
private readonly requestQueues: Record<LocalLlmRequestPriority, LocalLlmQueuedRequest[]> = {
|
|
194
196
|
"recall-critical": [],
|
|
@@ -887,21 +889,16 @@ export class LocalLlmClient {
|
|
|
887
889
|
}
|
|
888
890
|
|
|
889
891
|
const promptChars = messages.reduce((sum, m) => sum + (m.content?.length ?? 0), 0);
|
|
890
|
-
const
|
|
891
|
-
|
|
892
|
+
const requestModel = options.model ?? this.config.localLlmModel;
|
|
893
|
+
const requestBody: LocalLlmChatRequestBody = {
|
|
894
|
+
model: requestModel,
|
|
892
895
|
messages,
|
|
893
896
|
temperature: options.temperature ?? 0.7,
|
|
894
897
|
// Use max_tokens consistent with cloud models
|
|
895
898
|
max_tokens: options.maxTokens ?? 4096,
|
|
896
899
|
stream: false,
|
|
897
900
|
};
|
|
898
|
-
|
|
899
|
-
// Skip response_format for local LLMs - they don't support json_object type
|
|
900
|
-
// The prompts already instruct the model to output JSON
|
|
901
|
-
// Only send if it's json_schema type which some local LLMs support
|
|
902
|
-
if (options.responseFormat?.type === "json_schema") {
|
|
903
|
-
requestBody.response_format = options.responseFormat;
|
|
904
|
-
}
|
|
901
|
+
this.nativeSchemaGate.attach(requestBody, options, requestModel);
|
|
905
902
|
|
|
906
903
|
// Suppress thinking/reasoning for operations that benefit from terse,
|
|
907
904
|
// structured output. Thinking-capable models (Qwen 3.5, Gemma 4,
|
|
@@ -1016,6 +1013,7 @@ export class LocalLlmClient {
|
|
|
1016
1013
|
requestBody.response_format &&
|
|
1017
1014
|
isUnsupportedJsonSchemaError(`400 ${responseBody}`)
|
|
1018
1015
|
) {
|
|
1016
|
+
this.nativeSchemaGate.rememberRejection(requestModel);
|
|
1019
1017
|
// Match gateway degradation without extending this attempt's deadline.
|
|
1020
1018
|
delete requestBody.response_format;
|
|
1021
1019
|
requestBodyJson = JSON.stringify(requestBody);
|
|
@@ -1150,6 +1148,7 @@ export class LocalLlmClient {
|
|
|
1150
1148
|
return null;
|
|
1151
1149
|
}
|
|
1152
1150
|
this.consecutive400s = 0;
|
|
1151
|
+
this.nativeSchemaGate.recordSuccess(requestBody, requestModel);
|
|
1153
1152
|
|
|
1154
1153
|
const data = JSON.parse(responseBody) as {
|
|
1155
1154
|
choices?: Array<{
|
package/src/model-registry.ts
CHANGED
|
@@ -19,6 +19,8 @@ export interface ModelCapabilities {
|
|
|
19
19
|
};
|
|
20
20
|
typicalOutputTokens: number;
|
|
21
21
|
source: "huggingface" | "lmstudio" | "manual" | "default";
|
|
22
|
+
/** Learned structured-output shaping (see ModelRegistry.getStructuredOutput). */
|
|
23
|
+
structuredOutput?: "native" | "none";
|
|
22
24
|
fetchedAt: string;
|
|
23
25
|
}
|
|
24
26
|
|
|
@@ -193,6 +195,9 @@ export class ModelRegistry {
|
|
|
193
195
|
...capabilities,
|
|
194
196
|
modelId: normalizedId,
|
|
195
197
|
source: "default",
|
|
198
|
+
// Reachable only with a missing or expired cached record, so any
|
|
199
|
+
// learned structuredOutput is stale here — leave it unset and let
|
|
200
|
+
// getStructuredOutput's freshness rules drive the re-probe.
|
|
196
201
|
fetchedAt: new Date().toISOString(),
|
|
197
202
|
};
|
|
198
203
|
this.data.models[normalizedId] = caps;
|
|
@@ -214,8 +219,15 @@ export class ModelRegistry {
|
|
|
214
219
|
*/
|
|
215
220
|
setCapabilities(modelId: string, capabilities: Omit<ModelCapabilities, "modelId" | "fetchedAt">): void {
|
|
216
221
|
const normalizedId = this.normalizeModelId(modelId);
|
|
222
|
+
const previous = this.data.models[normalizedId];
|
|
217
223
|
const caps: ModelCapabilities = {
|
|
218
224
|
...capabilities,
|
|
225
|
+
// A replacement record (e.g. fresh LM Studio detection) must not erase a
|
|
226
|
+
// learned structuredOutput shaping — but an EXPIRED decision must not be
|
|
227
|
+
// renewed either: drop it so the re-probe stays due.
|
|
228
|
+
structuredOutput:
|
|
229
|
+
capabilities.structuredOutput ??
|
|
230
|
+
(previous && !this.isCacheExpired(previous) ? previous.structuredOutput : undefined),
|
|
219
231
|
modelId: normalizedId,
|
|
220
232
|
fetchedAt: new Date().toISOString(),
|
|
221
233
|
};
|
|
@@ -224,6 +236,34 @@ export class ModelRegistry {
|
|
|
224
236
|
log.info(`ModelRegistry: stored capabilities for ${modelId}`);
|
|
225
237
|
}
|
|
226
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Learned structured-output shaping for a model, persisted so fresh
|
|
241
|
+
* runtimes shape requests correctly without re-proving a rejection.
|
|
242
|
+
* Honors the cache TTL: a provider that later gains schema support is
|
|
243
|
+
* re-probed (one degraded request) after the window lapses.
|
|
244
|
+
*/
|
|
245
|
+
getStructuredOutput(modelId: string): "native" | "none" | undefined {
|
|
246
|
+
const cached = this.data.models[this.normalizeModelId(modelId)];
|
|
247
|
+
if (!cached || this.isCacheExpired(cached)) return undefined;
|
|
248
|
+
return cached.structuredOutput;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Merge-and-persist: preserves any previously learned capabilities. */
|
|
252
|
+
setStructuredOutput(modelId: string, mode: "native" | "none"): void {
|
|
253
|
+
const normalizedId = this.normalizeModelId(modelId);
|
|
254
|
+
const base = this.data.models[normalizedId] ?? {
|
|
255
|
+
...DEFAULT_CAPABILITIES,
|
|
256
|
+
modelId: normalizedId,
|
|
257
|
+
};
|
|
258
|
+
this.data.models[normalizedId] = {
|
|
259
|
+
...base,
|
|
260
|
+
structuredOutput: mode,
|
|
261
|
+
fetchedAt: new Date().toISOString(),
|
|
262
|
+
};
|
|
263
|
+
this.saveRegistry();
|
|
264
|
+
log.info(`ModelRegistry: stored structuredOutput=${mode} for ${modelId}`);
|
|
265
|
+
}
|
|
266
|
+
|
|
227
267
|
/**
|
|
228
268
|
* Calculate optimal input/output sizes for a model
|
|
229
269
|
* @param maxContextOverride - Optional override for max context (e.g., if LLM server defaults to smaller window)
|
|
@@ -32,6 +32,7 @@ import type { BufferDrainClaims, DrainLease } from "../storage/buffer-drain-clai
|
|
|
32
32
|
import { BufferDrainRunner } from "./buffer-drain-runner.js";
|
|
33
33
|
import type { DrainRunnerEvent, DrainSessionOutcome } from "./buffer-drain-runner.js";
|
|
34
34
|
import type { ExtractionQueueCoordinator } from "./extraction-queue-coordinator.js";
|
|
35
|
+
import type { ProviderBreakerSnapshot } from "./provider-breaker.js";
|
|
35
36
|
|
|
36
37
|
/** What the scheduler needs from its host (structurally satisfied by the
|
|
37
38
|
* orchestrator wiring); injectable so tests drive ticks deterministically. */
|
|
@@ -50,6 +51,13 @@ export interface BacklogSchedulerDeps {
|
|
|
50
51
|
key: string,
|
|
51
52
|
options: { ownerId: string; signal: AbortSignal; onEvent?: (event: DrainRunnerEvent) => void }
|
|
52
53
|
): Promise<DrainSessionOutcome>;
|
|
54
|
+
/** Shared provider circuit-breaker snapshot (the same instance the run
|
|
55
|
+
* coordinator gates attempts on). While OPEN inside its cooldown window
|
|
56
|
+
* the scheduler skips submission entirely — claims stay durable, and the
|
|
57
|
+
* breaker's single half-open probe (one attempt through once the window
|
|
58
|
+
* lapses) keeps recovery automatic. Optional: hosts without a breaker
|
|
59
|
+
* never skip. */
|
|
60
|
+
breakerSnapshot?(): ProviderBreakerSnapshot;
|
|
53
61
|
/** Test seam: monotonic-ish clock for backoff windows. */
|
|
54
62
|
now?(): number;
|
|
55
63
|
}
|
|
@@ -211,6 +219,18 @@ export class BacklogScheduler {
|
|
|
211
219
|
this.ticking = true;
|
|
212
220
|
const cursorBeforeTick = this.claimCursor;
|
|
213
221
|
try {
|
|
222
|
+
// Fast bail: a breaker already open must not pay discovery I/O
|
|
223
|
+
// (listPage reads and parses claim bodies on every poll). The
|
|
224
|
+
// submitCandidates guard below still catches a breaker that opens
|
|
225
|
+
// while discovery awaits.
|
|
226
|
+
const breakerBeforeDiscovery = this.deps.breakerSnapshot?.();
|
|
227
|
+
if (
|
|
228
|
+
breakerBeforeDiscovery &&
|
|
229
|
+
breakerBeforeDiscovery.state === "open" &&
|
|
230
|
+
this.now() < breakerBeforeDiscovery.openUntilMs
|
|
231
|
+
) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
214
234
|
// stop() may land while discovery's I/O is pending: re-check before
|
|
215
235
|
// submitting anything, and again per candidate.
|
|
216
236
|
// A discovery failure (EACCES/EIO on the claims dir) must not reject
|
|
@@ -269,6 +289,15 @@ export class BacklogScheduler {
|
|
|
269
289
|
|
|
270
290
|
/** Submit discovery candidates within the queue's concurrency bound. */
|
|
271
291
|
private submitCandidates(candidates: { key: string; needsClaim: boolean }[]): boolean {
|
|
292
|
+
// Breaker-open churn guard: a full attempt reads the claim, rebuilds the
|
|
293
|
+
// buffer/coordinator and re-renders chunks before the breaker fails it
|
|
294
|
+
// fast — pure heap pressure per poll during a provider stall. Skip while
|
|
295
|
+
// the window is open; once it lapses, submissions resume and the
|
|
296
|
+
// breaker's half-open probe runs as usual.
|
|
297
|
+
const breaker = this.deps.breakerSnapshot?.();
|
|
298
|
+
if (breaker && breaker.state === "open" && this.now() < breaker.openUntilMs) {
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
272
301
|
let dropped = false;
|
|
273
302
|
for (const candidate of candidates) {
|
|
274
303
|
if (this.stopped || this.inFlight.size >= this.submissionLimit()) {
|
|
@@ -475,6 +504,9 @@ export function wireBacklogScheduler(
|
|
|
475
504
|
};
|
|
476
505
|
},
|
|
477
506
|
drainKey: (key, drainOptions) => runner.drainSession(key, drainOptions),
|
|
507
|
+
// The orchestrator's one shared provider breaker (foreground and every
|
|
508
|
+
// claim-backed drain accumulate toward the same threshold).
|
|
509
|
+
breakerSnapshot: () => orchestrator.getExtractionResilienceStatus().breaker,
|
|
478
510
|
},
|
|
479
511
|
{ ...options, ownerId }
|
|
480
512
|
);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/model-registry.ts"],"sourcesContent":["/**\n * Model Registry - Stores and retrieves model capabilities\n * Avoids repeated external lookups by caching model info locally\n */\n\nimport { log } from \"./logger.js\";\nimport fs from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nexport interface ModelCapabilities {\n modelId: string;\n maxPositionEmbeddings: number;\n contextWindow: number;\n supportsExtendedContext: boolean;\n ropeScaling?: {\n type: string;\n factor: number;\n originalMaxPositionEmbeddings: number;\n };\n typicalOutputTokens: number;\n source: \"huggingface\" | \"lmstudio\" | \"manual\" | \"default\";\n fetchedAt: string;\n}\n\ninterface ModelRegistryData {\n models: Record<string, ModelCapabilities>;\n version: number;\n}\n\nconst DEFAULT_CAPABILITIES: ModelCapabilities = {\n modelId: \"default\",\n maxPositionEmbeddings: 32768,\n contextWindow: 32768,\n supportsExtendedContext: false,\n typicalOutputTokens: 8192,\n source: \"default\",\n fetchedAt: new Date().toISOString(),\n};\n\n// Known model capabilities (fallback when offline)\n// NOTE: Context windows here are the MODEL's theoretical maximum. Local LLM servers\n// (LM Studio, Ollama, etc.) often load models with smaller default context windows.\n// If you get \"context length exceeded\" errors, either:\n// 1. Increase the context window in your LLM server UI (recommended)\n// 2. Set localLlmMaxContext in openclaw.json to limit prompts (fallback)\nconst KNOWN_MODELS: Record<string, Partial<ModelCapabilities>> = {\n \"qwen3-30b-a3b-instruct\": {\n maxPositionEmbeddings: 40960,\n contextWindow: 131072, // 128K with YaRN - but LM Studio defaults to ~32K\n supportsExtendedContext: true,\n ropeScaling: {\n type: \"yarn\",\n factor: 4.0,\n originalMaxPositionEmbeddings: 32768,\n },\n typicalOutputTokens: 8192,\n },\n \"qwen3-coder-30b-a3b-instruct\": {\n maxPositionEmbeddings: 40960,\n contextWindow: 131072, // 128K with YaRN - but LM Studio defaults to ~32K\n supportsExtendedContext: true,\n ropeScaling: {\n type: \"yarn\",\n factor: 4.0,\n originalMaxPositionEmbeddings: 32768,\n },\n typicalOutputTokens: 8192,\n },\n \"qwen3-8b\": {\n maxPositionEmbeddings: 40960,\n contextWindow: 131072,\n supportsExtendedContext: true,\n typicalOutputTokens: 4096,\n },\n \"qwen3-14b\": {\n maxPositionEmbeddings: 40960,\n contextWindow: 131072,\n supportsExtendedContext: true,\n typicalOutputTokens: 4096,\n },\n \"qwen3-32b\": {\n maxPositionEmbeddings: 40960,\n contextWindow: 131072,\n supportsExtendedContext: true,\n typicalOutputTokens: 4096,\n },\n \"llama-3.1\": {\n maxPositionEmbeddings: 131072,\n contextWindow: 131072,\n supportsExtendedContext: false,\n typicalOutputTokens: 8192,\n },\n \"llama-3.2\": {\n maxPositionEmbeddings: 131072,\n contextWindow: 131072,\n supportsExtendedContext: false,\n typicalOutputTokens: 8192,\n },\n \"mistral-nemo\": {\n maxPositionEmbeddings: 131072,\n contextWindow: 131072,\n supportsExtendedContext: false,\n typicalOutputTokens: 8192,\n },\n \"gemma-2\": {\n maxPositionEmbeddings: 8192,\n contextWindow: 8192,\n supportsExtendedContext: false,\n typicalOutputTokens: 4096,\n },\n};\n\nexport class ModelRegistry {\n private registryPath: string;\n private data: ModelRegistryData;\n private readonly CACHE_TTL_DAYS = 7;\n\n constructor(memoryDir: string) {\n this.registryPath = join(memoryDir, \".registry\", \"model-capabilities.json\");\n this.data = this.loadRegistry();\n }\n\n private loadRegistry(): ModelRegistryData {\n try {\n if (fs.existsSync(this.registryPath)) {\n const content = fs.readFileSync(this.registryPath, \"utf-8\");\n const data = JSON.parse(content) as ModelRegistryData;\n log.info(`ModelRegistry: loaded ${Object.keys(data.models).length} cached models`);\n return data;\n }\n } catch (err) {\n log.warn(`ModelRegistry: failed to load registry: ${err}`);\n }\n return { models: {}, version: 1 };\n }\n\n private saveRegistry(): void {\n try {\n // Created on first save, never at construction: building a registry\n // happens on every plugin registration, including read-only discovery\n // passes, and must not touch the operator's memory dir.\n const registryDir = dirname(this.registryPath);\n if (!fs.existsSync(registryDir)) fs.mkdirSync(registryDir, { recursive: true });\n fs.writeFileSync(this.registryPath, JSON.stringify(this.data, null, 2));\n } catch (err) {\n log.warn(`ModelRegistry: failed to save registry: ${err}`);\n }\n }\n\n private isCacheExpired(model: ModelCapabilities): boolean {\n const fetched = new Date(model.fetchedAt);\n const now = new Date();\n const daysDiff = (now.getTime() - fetched.getTime()) / (1000 * 60 * 60 * 24);\n return daysDiff > this.CACHE_TTL_DAYS;\n }\n\n private normalizeModelId(modelId: string): string {\n // Remove common suffixes and prefixes for matching\n return modelId\n .toLowerCase()\n .replace(/@\\d+bit$/, \"\") // Remove @4bit, @8bit\n .replace(/-mlx$/, \"\")\n .replace(/-awq$/, \"\")\n .replace(/-gptq$/, \"\")\n .replace(/-gguf$/, \"\")\n .replace(/^mlx-community\\//, \"\")\n .replace(/^models\\//, \"\")\n .trim();\n }\n\n /**\n * Get capabilities for a model, using cache if available\n */\n getCapabilities(modelId: string): ModelCapabilities {\n const normalizedId = this.normalizeModelId(modelId);\n\n // Check cache first\n if (this.data.models[normalizedId]) {\n const cached = this.data.models[normalizedId];\n if (!this.isCacheExpired(cached)) {\n log.info(`ModelRegistry: using cached capabilities for ${modelId}`);\n return cached;\n }\n log.info(`ModelRegistry: cache expired for ${modelId}, will refresh`);\n }\n\n // Check known models\n for (const [knownId, capabilities] of Object.entries(KNOWN_MODELS)) {\n if (normalizedId.includes(knownId)) {\n log.info(`ModelRegistry: using known capabilities for ${modelId}`);\n const caps: ModelCapabilities = {\n ...DEFAULT_CAPABILITIES,\n ...capabilities,\n modelId: normalizedId,\n source: \"default\",\n fetchedAt: new Date().toISOString(),\n };\n this.data.models[normalizedId] = caps;\n this.saveRegistry();\n return caps;\n }\n }\n\n // Return defaults\n log.info(`ModelRegistry: using default capabilities for ${modelId}`);\n return {\n ...DEFAULT_CAPABILITIES,\n modelId: normalizedId,\n };\n }\n\n /**\n * Store capabilities for a model\n */\n setCapabilities(modelId: string, capabilities: Omit<ModelCapabilities, \"modelId\" | \"fetchedAt\">): void {\n const normalizedId = this.normalizeModelId(modelId);\n const caps: ModelCapabilities = {\n ...capabilities,\n modelId: normalizedId,\n fetchedAt: new Date().toISOString(),\n };\n this.data.models[normalizedId] = caps;\n this.saveRegistry();\n log.info(`ModelRegistry: stored capabilities for ${modelId}`);\n }\n\n /**\n * Calculate optimal input/output sizes for a model\n * @param maxContextOverride - Optional override for max context (e.g., if LLM server defaults to smaller window)\n */\n calculateContextSizes(modelId: string, maxContextOverride?: number): {\n maxInputChars: number;\n maxOutputTokens: number;\n description: string;\n } {\n const caps = this.getCapabilities(modelId);\n\n // Use override if provided (e.g., user knows their LLM server limits), otherwise use detected caps\n const sanitizedOverride =\n typeof maxContextOverride === \"number\" &&\n Number.isFinite(maxContextOverride) &&\n Number.isInteger(maxContextOverride) &&\n maxContextOverride >= 1024\n ? maxContextOverride\n : undefined;\n const effectiveContextWindow = sanitizedOverride ?? Math.max(1024, Math.floor(caps.contextWindow));\n\n // Guardrails: never allow output budget to exceed the model/server context window.\n // If we do, input budget goes negative and we end up generating huge, invalid prompts.\n const overheadTokens = Math.min(1000, Math.floor(effectiveContextWindow / 10)); // <=10% overhead, max 1k\n const minInputTokens = Math.min(512, Math.floor(effectiveContextWindow / 4)); // keep some room even on small contexts\n const minOutputTokens = Math.min(256, Math.max(1, effectiveContextWindow - overheadTokens - minInputTokens));\n\n // Base output budget: typical output, but scaled down for small contexts.\n let outputTokens = caps.typicalOutputTokens;\n\n // For very large contexts, default to ~12.5% output (capped), which tends to be plenty for JSON extraction.\n if (effectiveContextWindow > 65536) {\n outputTokens = Math.min(Math.floor(effectiveContextWindow / 8), 16384);\n }\n\n // Never let output exceed 25% of context.\n outputTokens = Math.min(outputTokens, Math.floor(effectiveContextWindow / 4));\n\n // Clamp output so we always have positive input headroom.\n const maxOutputTokens = Math.max(1, effectiveContextWindow - overheadTokens - minInputTokens);\n outputTokens = Math.min(maxOutputTokens, Math.max(minOutputTokens, outputTokens));\n\n const availableForInput = Math.max(\n 0,\n effectiveContextWindow - outputTokens - overheadTokens,\n );\n\n // Convert to characters (rough estimate: 1 token ≈ 4 chars)\n const maxInputChars = Math.max(0, Math.floor(availableForInput * 3.5)); // Conservative: 3.5 chars/token\n\n const source = sanitizedOverride ? \"user override\" : caps.source;\n return {\n maxInputChars,\n maxOutputTokens: outputTokens,\n description: `${caps.modelId}: ${effectiveContextWindow.toLocaleString()} context (${source}), using ${maxInputChars.toLocaleString()} chars input / ${outputTokens} tokens output`,\n };\n }\n\n /**\n * Fetch capabilities from Hugging Face (if available)\n * Returns true if successful\n */\n async fetchFromHuggingFace(modelId: string): Promise<boolean> {\n // This would be implemented to fetch from HF Hub API\n // For now, we rely on the known models and manual updates\n log.info(`ModelRegistry: fetchFromHuggingFace not yet implemented for ${modelId}`);\n return false;\n }\n\n /**\n * List all cached models\n */\n listCached(): string[] {\n return Object.keys(this.data.models);\n }\n\n /**\n * Clear expired cache entries\n */\n cleanExpired(): number {\n const before = Object.keys(this.data.models).length;\n this.data.models = Object.fromEntries(\n Object.entries(this.data.models).filter(([_, caps]) => !this.isCacheExpired(caps))\n );\n const after = Object.keys(this.data.models).length;\n const removed = before - after;\n if (removed > 0) {\n this.saveRegistry();\n log.info(`ModelRegistry: cleaned ${removed} expired entries`);\n }\n return removed;\n }\n}\n"],"mappings":"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","names":[]}
|