@remnic/core 9.3.612 → 9.3.614

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.
Files changed (374) hide show
  1. package/dist/access-cli.js +58 -57
  2. package/dist/access-cli.js.map +1 -1
  3. package/dist/access-http.d.ts +4 -2
  4. package/dist/access-http.js +22 -22
  5. package/dist/access-mcp.d.ts +9 -2
  6. package/dist/access-mcp.js +19 -19
  7. package/dist/access-schema.d.ts +12 -12
  8. package/dist/access-schema.js +3 -3
  9. package/dist/{access-service-D2J9dh_9.d.ts → access-service-DGG_2xPK.d.ts} +1 -1
  10. package/dist/access-service.d.ts +2 -2
  11. package/dist/access-service.js +16 -16
  12. package/dist/active-recall.js +20 -3
  13. package/dist/active-recall.js.map +1 -1
  14. package/dist/adapters/index.js +4 -4
  15. package/dist/adapters/registry.js +2 -2
  16. package/dist/behavior-learner.js +2 -3
  17. package/dist/behavior-learner.js.map +1 -1
  18. package/dist/bootstrap.d.ts +1 -1
  19. package/dist/briefing.js +3 -3
  20. package/dist/buffer.d.ts +1 -1
  21. package/dist/buffer.js +1 -1
  22. package/dist/calibration.d.ts +5 -2
  23. package/dist/calibration.js +7 -5
  24. package/dist/calibration.js.map +1 -1
  25. package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-YO5QJ6L3.js} +2 -2
  26. package/dist/causal-consolidation.d.ts +8 -2
  27. package/dist/causal-consolidation.js +13 -11
  28. package/dist/causal-consolidation.js.map +1 -1
  29. package/dist/{chunk-3BP57I6J.js → chunk-2F6NP3NT.js} +2 -1
  30. package/dist/{chunk-3BP57I6J.js.map → chunk-2F6NP3NT.js.map} +1 -1
  31. package/dist/{chunk-AU7Q3LSC.js → chunk-2QSZNTDO.js} +4 -4
  32. package/dist/{chunk-HSVJGWYS.js → chunk-2ROPI5OE.js} +2 -2
  33. package/dist/{chunk-C4SQJZAF.js → chunk-2SGJY2UY.js} +6 -3
  34. package/dist/chunk-2SGJY2UY.js.map +1 -0
  35. package/dist/{chunk-ZDTVJXIP.js → chunk-3MAONBX3.js} +13 -5
  36. package/dist/chunk-3MAONBX3.js.map +1 -0
  37. package/dist/{chunk-G3Z3QEF5.js → chunk-3PY7VHV7.js} +2 -2
  38. package/dist/chunk-3PY7VHV7.js.map +1 -0
  39. package/dist/{chunk-CF3ZF2YU.js → chunk-3QSU4NFF.js} +3 -3
  40. package/dist/{chunk-AJA46VX5.js → chunk-3T74IZB3.js} +11 -2
  41. package/dist/chunk-3T74IZB3.js.map +1 -0
  42. package/dist/{chunk-KVEVLBKC.js → chunk-4HFJQCJZ.js} +13 -8
  43. package/dist/chunk-4HFJQCJZ.js.map +1 -0
  44. package/dist/{chunk-KGK2QKWL.js → chunk-4R4KTDIE.js} +1 -1
  45. package/dist/chunk-4R4KTDIE.js.map +1 -0
  46. package/dist/{chunk-OI27U2HT.js → chunk-5BTCT236.js} +2 -2
  47. package/dist/{chunk-CO7ZO4TU.js → chunk-5VDJMYTF.js} +2 -2
  48. package/dist/{chunk-BFBF3XEF.js → chunk-6BDVBBBY.js} +33 -25
  49. package/dist/{chunk-BFBF3XEF.js.map → chunk-6BDVBBBY.js.map} +1 -1
  50. package/dist/{chunk-EAZGEEG2.js → chunk-6L46YAEZ.js} +45 -9
  51. package/dist/chunk-6L46YAEZ.js.map +1 -0
  52. package/dist/{chunk-YFS5OEKO.js → chunk-7MLB4NCL.js} +2 -2
  53. package/dist/{chunk-IOTENEVL.js → chunk-7YQFWOF7.js} +57 -50
  54. package/dist/chunk-7YQFWOF7.js.map +1 -0
  55. package/dist/{chunk-2QANQKSQ.js → chunk-ADNZVFXG.js} +15 -15
  56. package/dist/{chunk-LZ3VEOU5.js → chunk-AL4RAJL5.js} +22 -5
  57. package/dist/chunk-AL4RAJL5.js.map +1 -0
  58. package/dist/{chunk-557IAFPD.js → chunk-APRRL26Q.js} +2 -2
  59. package/dist/{chunk-QDDHYAKV.js → chunk-AZDOWD2L.js} +2 -2
  60. package/dist/{chunk-TH67Q46T.js → chunk-B6FDZPCF.js} +17 -9
  61. package/dist/chunk-B6FDZPCF.js.map +1 -0
  62. package/dist/{chunk-MLT75J5S.js → chunk-B6SU7YSE.js} +3 -3
  63. package/dist/{chunk-FXKPZ3H6.js → chunk-BPSGLMQ4.js} +2 -2
  64. package/dist/{chunk-2NLLXCJG.js → chunk-BXLOS5AJ.js} +2 -2
  65. package/dist/{chunk-NOMEVTUD.js → chunk-C6C7XVKG.js} +5 -4
  66. package/dist/chunk-C6C7XVKG.js.map +1 -0
  67. package/dist/{chunk-XKIQZXUB.js → chunk-CI7RKSRE.js} +7 -1
  68. package/dist/chunk-CI7RKSRE.js.map +1 -0
  69. package/dist/{chunk-IK34DVAC.js → chunk-CIOMS6DI.js} +2 -2
  70. package/dist/{chunk-2I5JGH3M.js → chunk-CYEPCZN5.js} +2 -2
  71. package/dist/{chunk-2I5JGH3M.js.map → chunk-CYEPCZN5.js.map} +1 -1
  72. package/dist/{chunk-JHMFYY7L.js → chunk-DCGT4FPP.js} +13 -5
  73. package/dist/chunk-DCGT4FPP.js.map +1 -0
  74. package/dist/{chunk-7DZRO2DC.js → chunk-DEPRLVLK.js} +2 -2
  75. package/dist/{chunk-CSKLPDN6.js → chunk-DEVUWMME.js} +52 -19
  76. package/dist/chunk-DEVUWMME.js.map +1 -0
  77. package/dist/{chunk-DHGSZ3UD.js → chunk-DGNQRNLL.js} +2 -2
  78. package/dist/{chunk-X7Y7WX73.js → chunk-DQEMWVMT.js} +1 -1
  79. package/dist/chunk-FAV25DUZ.js +12 -0
  80. package/dist/chunk-FAV25DUZ.js.map +1 -0
  81. package/dist/{chunk-ETUPBUHB.js → chunk-GDASG7NC.js} +2 -2
  82. package/dist/{chunk-L227SKTB.js → chunk-GDB4J2H3.js} +17 -1
  83. package/dist/chunk-GDB4J2H3.js.map +1 -0
  84. package/dist/{chunk-IP73YCZP.js → chunk-GLPBYIXN.js} +4 -2
  85. package/dist/chunk-GLPBYIXN.js.map +1 -0
  86. package/dist/{chunk-4HP7HIE3.js → chunk-HP5FMB6L.js} +2 -2
  87. package/dist/{chunk-EVZFIAPG.js → chunk-IBTZEBUD.js} +23 -10
  88. package/dist/chunk-IBTZEBUD.js.map +1 -0
  89. package/dist/{chunk-DOX2CG6Y.js → chunk-IEUU7O4F.js} +2 -2
  90. package/dist/{chunk-JNANKJLN.js → chunk-JOASJWQR.js} +2 -2
  91. package/dist/chunk-JOASJWQR.js.map +1 -0
  92. package/dist/{chunk-WSGF57U2.js → chunk-JQDZQ4TB.js} +2 -2
  93. package/dist/{chunk-HINSGUA7.js → chunk-KBL3JJR6.js} +9 -13
  94. package/dist/chunk-KBL3JJR6.js.map +1 -0
  95. package/dist/{chunk-W7L6HXUC.js → chunk-LXOM6IQU.js} +2 -2
  96. package/dist/{chunk-G6R5UD3Q.js → chunk-MGN7VHWQ.js} +42 -1
  97. package/dist/{chunk-G6R5UD3Q.js.map → chunk-MGN7VHWQ.js.map} +1 -1
  98. package/dist/{chunk-DLJ4IR6M.js → chunk-MHQC2WU2.js} +2 -2
  99. package/dist/chunk-MHQC2WU2.js.map +1 -0
  100. package/dist/{chunk-6JGNHWCI.js → chunk-OBIRVF36.js} +3 -3
  101. package/dist/{chunk-CHCA44C3.js → chunk-ODPLEWB6.js} +3 -3
  102. package/dist/chunk-ODPLEWB6.js.map +1 -0
  103. package/dist/{chunk-HENLZHIT.js → chunk-OIF36KGD.js} +7 -4
  104. package/dist/chunk-OIF36KGD.js.map +1 -0
  105. package/dist/{chunk-GUPISBV2.js → chunk-PP2JH3GP.js} +2 -2
  106. package/dist/{chunk-OXJBNGBK.js → chunk-PSUB67YB.js} +2 -2
  107. package/dist/{chunk-UWY7GIVS.js → chunk-PYIFUBRK.js} +45 -13
  108. package/dist/chunk-PYIFUBRK.js.map +1 -0
  109. package/dist/{chunk-KIB7SDIJ.js → chunk-Q6YIJGXJ.js} +2 -2
  110. package/dist/{chunk-PPPZY2EU.js → chunk-QEMCQFDW.js} +2 -2
  111. package/dist/{chunk-ZT3EGNLR.js → chunk-QPD426WT.js} +2 -2
  112. package/dist/{chunk-RLV3PQGH.js → chunk-QVO4YOB7.js} +6 -6
  113. package/dist/{chunk-GMAG2HS4.js → chunk-RG3LBSGH.js} +46 -9
  114. package/dist/chunk-RG3LBSGH.js.map +1 -0
  115. package/dist/{chunk-XSWKORGM.js → chunk-S53OYO3F.js} +3 -1
  116. package/dist/chunk-S53OYO3F.js.map +1 -0
  117. package/dist/{chunk-YCN4BVDK.js → chunk-SCPFRKIT.js} +4 -2
  118. package/dist/chunk-SCPFRKIT.js.map +1 -0
  119. package/dist/{chunk-HJNQQICM.js → chunk-T5XWMMU2.js} +107 -50
  120. package/dist/chunk-T5XWMMU2.js.map +1 -0
  121. package/dist/{chunk-NZPF2SYV.js → chunk-T7N6KQGS.js} +138 -5
  122. package/dist/chunk-T7N6KQGS.js.map +1 -0
  123. package/dist/{chunk-VJXSUAO7.js → chunk-TNOWU6RP.js} +13 -10
  124. package/dist/chunk-TNOWU6RP.js.map +1 -0
  125. package/dist/{chunk-PCI747N2.js → chunk-TZVQQTG4.js} +48 -19
  126. package/dist/chunk-TZVQQTG4.js.map +1 -0
  127. package/dist/{chunk-KQAFEZQX.js → chunk-VDX2J7OX.js} +2 -2
  128. package/dist/{chunk-IK7DCC5H.js → chunk-VMGLYN42.js} +2 -2
  129. package/dist/{chunk-5RPTH6AU.js → chunk-VPGUMLBA.js} +8 -7
  130. package/dist/chunk-VPGUMLBA.js.map +1 -0
  131. package/dist/{chunk-KM2A35EO.js → chunk-WB3LYXC5.js} +11 -7
  132. package/dist/chunk-WB3LYXC5.js.map +1 -0
  133. package/dist/{chunk-NSKYFGDL.js → chunk-X4QQB7O6.js} +2 -2
  134. package/dist/{chunk-HPWVAEET.js → chunk-X6IRLNOO.js} +3 -7
  135. package/dist/chunk-X6IRLNOO.js.map +1 -0
  136. package/dist/{chunk-46GJIW5M.js → chunk-XAZOWLW4.js} +5 -5
  137. package/dist/{chunk-46GJIW5M.js.map → chunk-XAZOWLW4.js.map} +1 -1
  138. package/dist/{chunk-XPSVGJYA.js → chunk-YRMKDTKF.js} +12 -9
  139. package/dist/chunk-YRMKDTKF.js.map +1 -0
  140. package/dist/{chunk-6ZZP4EJF.js → chunk-ZJR7VG5L.js} +3 -3
  141. package/dist/{chunk-6ZZP4EJF.js.map → chunk-ZJR7VG5L.js.map} +1 -1
  142. package/dist/{cli-OrfKXNU4.d.ts → cli-DWeu7eTY.d.ts} +6 -2
  143. package/dist/cli.d.ts +3 -3
  144. package/dist/cli.js +60 -59
  145. package/dist/compounding/engine.js +3 -3
  146. package/dist/compounding/preference-consolidator.js +39 -11
  147. package/dist/compounding/preference-consolidator.js.map +1 -1
  148. package/dist/config.js +1 -1
  149. package/dist/connectors/codex-materialize-runner.js +3 -3
  150. package/dist/connectors/index.js +3 -3
  151. package/dist/consolidation-provenance-check.js +1 -1
  152. package/dist/contradiction/index.js +4 -4
  153. package/dist/conversation-index/backend.js +2 -2
  154. package/dist/conversation-index/indexer.js +1 -1
  155. package/dist/cross-namespace-budget.js +1 -1
  156. package/dist/enrichment/index.js +1 -1
  157. package/dist/entity-retrieval.js +3 -3
  158. package/dist/evals.js +1 -1
  159. package/dist/explicit-capture.d.ts +1 -1
  160. package/dist/extraction-judge.js +8 -1
  161. package/dist/extraction.js +2 -2
  162. package/dist/fallback-llm.d.ts +23 -6
  163. package/dist/fallback-llm.js +5 -3
  164. package/dist/{first-start-migration-GYJWIH36.js → first-start-migration-FF7YFGRP.js} +6 -6
  165. package/dist/index.d.ts +3 -3
  166. package/dist/index.js +94 -93
  167. package/dist/index.js.map +1 -1
  168. package/dist/lcm/archive.js +2 -2
  169. package/dist/lcm/engine.js +5 -5
  170. package/dist/lcm/index.js +7 -7
  171. package/dist/lcm/summarizer.js +3 -3
  172. package/dist/maintenance/memory-governance-cron.d.ts +6 -4
  173. package/dist/maintenance/memory-governance-cron.js +1 -1
  174. package/dist/maintenance/memory-governance.js +3 -3
  175. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  176. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  177. package/dist/mcp-memory-inspector-app.d.ts +2 -2
  178. package/dist/mcp-memory-inspector-app.js +1 -1
  179. package/dist/migrate/from-engram.js +1 -1
  180. package/dist/namespaces/migrate.js +16 -15
  181. package/dist/namespaces/search.js +12 -11
  182. package/dist/namespaces/storage.js +3 -3
  183. package/dist/network/webdav.d.ts +2 -0
  184. package/dist/network/webdav.js +1 -1
  185. package/dist/objective-state-writers.js +2 -2
  186. package/dist/operator-toolkit.d.ts +3 -1
  187. package/dist/operator-toolkit.js +21 -20
  188. package/dist/{orchestrator-DTRQG75J.d.ts → orchestrator-CqWOjfgl.d.ts} +46 -3
  189. package/dist/orchestrator.d.ts +1 -1
  190. package/dist/orchestrator.js +47 -44
  191. package/dist/patterns-cli.js +1 -1
  192. package/dist/qmd-recall-cache.d.ts +2 -0
  193. package/dist/qmd-recall-cache.js +1 -1
  194. package/dist/qmd.d.ts +37 -2
  195. package/dist/qmd.js +4 -1
  196. package/dist/recall-explain-renderer.js +3 -3
  197. package/dist/recall-planner-llm.d.ts +57 -0
  198. package/dist/recall-planner-llm.js +167 -0
  199. package/dist/recall-planner-llm.js.map +1 -0
  200. package/dist/recall-xray-cli.js +4 -4
  201. package/dist/recall-xray-renderer.js +3 -3
  202. package/dist/recall-xray.js +2 -2
  203. package/dist/resume-bundles.js +2 -2
  204. package/dist/retrieval-agents.js +2 -2
  205. package/dist/routing/store.js +1 -1
  206. package/dist/search/factory.js +11 -10
  207. package/dist/search/index.js +11 -10
  208. package/dist/search/lancedb-backend.d.ts +1 -1
  209. package/dist/search/lancedb-backend.js +3 -2
  210. package/dist/search/meilisearch-backend.d.ts +1 -1
  211. package/dist/search/meilisearch-backend.js +3 -2
  212. package/dist/search/noop-backend.d.ts +1 -1
  213. package/dist/search/noop-backend.js +1 -1
  214. package/dist/search/orama-backend.d.ts +1 -1
  215. package/dist/search/orama-backend.js +3 -2
  216. package/dist/search/port.d.ts +6 -1
  217. package/dist/search/port.js +7 -0
  218. package/dist/search/remote-backend.d.ts +1 -1
  219. package/dist/search/remote-backend.js +1 -1
  220. package/dist/semantic-consolidation.js +4 -4
  221. package/dist/semantic-rule-promotion.js +3 -3
  222. package/dist/semantic-rule-verifier.js +3 -3
  223. package/dist/session-observer-state.js +1 -1
  224. package/dist/storage.js +2 -2
  225. package/dist/summarizer.js +2 -2
  226. package/dist/temporal-index.js +1 -1
  227. package/dist/{tier-stats-SKML2OSF.js → tier-stats-3LYQ3VV5.js} +3 -3
  228. package/dist/transfer/backup.js +2 -2
  229. package/dist/transfer/capsule-export.js +2 -2
  230. package/dist/transfer/capsule-import.js +2 -2
  231. package/dist/transfer/export-sqlite.js +1 -1
  232. package/dist/types.d.ts +32 -0
  233. package/dist/types.js +1 -1
  234. package/dist/utility-learner.js +1 -1
  235. package/dist/utility-runtime.js +2 -2
  236. package/dist/verified-recall.js +3 -3
  237. package/dist/work/board.js +2 -2
  238. package/dist/work/storage.d.ts +2 -0
  239. package/dist/work/storage.js +1 -1
  240. package/package.json +1 -1
  241. package/src/access-http.ts +3 -0
  242. package/src/access-mcp.test.ts +51 -0
  243. package/src/access-mcp.ts +26 -5
  244. package/src/active-recall.test.ts +40 -0
  245. package/src/active-recall.ts +19 -2
  246. package/src/behavior-learner.ts +5 -3
  247. package/src/buffer-session.test.ts +58 -0
  248. package/src/buffer-surprise-trigger.test.ts +4 -18
  249. package/src/buffer.ts +39 -11
  250. package/src/calibration.ts +10 -4
  251. package/src/causal-consolidation.test.ts +47 -2
  252. package/src/causal-consolidation.ts +13 -9
  253. package/src/cli.ts +19 -4
  254. package/src/compounding/engine.ts +2 -0
  255. package/src/compounding/preference-consolidator.test.ts +292 -0
  256. package/src/compounding/preference-consolidator.ts +55 -19
  257. package/src/config.test.ts +213 -0
  258. package/src/config.ts +175 -4
  259. package/src/connectors/codex-materialize-runner.ts +7 -4
  260. package/src/consolidation-provenance-check.ts +24 -5
  261. package/src/conversation-index/indexer.test.ts +22 -0
  262. package/src/conversation-index/indexer.ts +7 -3
  263. package/src/cross-namespace-budget.test.ts +44 -21
  264. package/src/cross-namespace-budget.ts +2 -2
  265. package/src/enrichment/pipeline.ts +11 -16
  266. package/src/evals.ts +1 -1
  267. package/src/extraction-judge-chain.test.ts +55 -0
  268. package/src/extraction-judge.ts +7 -9
  269. package/src/extraction.ts +16 -5
  270. package/src/fallback-llm.test.ts +600 -1
  271. package/src/fallback-llm.ts +91 -22
  272. package/src/maintenance/memory-governance-cron.ts +39 -29
  273. package/src/mcp-memory-inspector-app.ts +54 -12
  274. package/src/message-parts/index.ts +6 -0
  275. package/src/message-parts/message-parts.test.ts +30 -0
  276. package/src/migrate/from-engram.ts +19 -5
  277. package/src/namespaces/search.test.ts +15 -2
  278. package/src/namespaces/search.ts +1 -1
  279. package/src/network/webdav.ts +61 -21
  280. package/src/operator-toolkit.ts +6 -2
  281. package/src/orchestrator.ts +173 -20
  282. package/src/qmd-client.test.ts +85 -0
  283. package/src/qmd-recall-cache.test.ts +16 -0
  284. package/src/qmd-recall-cache.ts +7 -0
  285. package/src/qmd.test.ts +54 -0
  286. package/src/qmd.ts +119 -19
  287. package/src/recall-planner-llm.test.ts +224 -0
  288. package/src/recall-planner-llm.ts +289 -0
  289. package/src/routing/store.ts +4 -8
  290. package/src/search/factory.ts +3 -0
  291. package/src/search/lancedb-backend.ts +15 -3
  292. package/src/search/meilisearch-backend.ts +70 -7
  293. package/src/search/noop-backend.ts +5 -1
  294. package/src/search/orama-backend.ts +15 -3
  295. package/src/search/port.ts +15 -0
  296. package/src/search/remote-backend.ts +5 -1
  297. package/src/session-observer-state.ts +1 -1
  298. package/src/summarizer.ts +3 -3
  299. package/src/temporal-index.test.ts +18 -0
  300. package/src/temporal-index.ts +45 -0
  301. package/src/training-export/cli-date-validation.test.ts +36 -0
  302. package/src/training-export/date-parse.ts +21 -2
  303. package/src/transfer/export-sqlite.ts +3 -0
  304. package/src/types.ts +35 -0
  305. package/src/utility-learner.ts +1 -0
  306. package/src/work/storage.ts +23 -0
  307. package/dist/chunk-5RPTH6AU.js.map +0 -1
  308. package/dist/chunk-AJA46VX5.js.map +0 -1
  309. package/dist/chunk-C4SQJZAF.js.map +0 -1
  310. package/dist/chunk-CHCA44C3.js.map +0 -1
  311. package/dist/chunk-CSKLPDN6.js.map +0 -1
  312. package/dist/chunk-DLJ4IR6M.js.map +0 -1
  313. package/dist/chunk-EAZGEEG2.js.map +0 -1
  314. package/dist/chunk-EVZFIAPG.js.map +0 -1
  315. package/dist/chunk-G3Z3QEF5.js.map +0 -1
  316. package/dist/chunk-GMAG2HS4.js.map +0 -1
  317. package/dist/chunk-HENLZHIT.js.map +0 -1
  318. package/dist/chunk-HINSGUA7.js.map +0 -1
  319. package/dist/chunk-HJNQQICM.js.map +0 -1
  320. package/dist/chunk-HPWVAEET.js.map +0 -1
  321. package/dist/chunk-IOTENEVL.js.map +0 -1
  322. package/dist/chunk-IP73YCZP.js.map +0 -1
  323. package/dist/chunk-JHMFYY7L.js.map +0 -1
  324. package/dist/chunk-JNANKJLN.js.map +0 -1
  325. package/dist/chunk-KGK2QKWL.js.map +0 -1
  326. package/dist/chunk-KM2A35EO.js.map +0 -1
  327. package/dist/chunk-KVEVLBKC.js.map +0 -1
  328. package/dist/chunk-L227SKTB.js.map +0 -1
  329. package/dist/chunk-LZ3VEOU5.js.map +0 -1
  330. package/dist/chunk-NOMEVTUD.js.map +0 -1
  331. package/dist/chunk-NZPF2SYV.js.map +0 -1
  332. package/dist/chunk-PCI747N2.js.map +0 -1
  333. package/dist/chunk-TH67Q46T.js.map +0 -1
  334. package/dist/chunk-UWY7GIVS.js.map +0 -1
  335. package/dist/chunk-VJXSUAO7.js.map +0 -1
  336. package/dist/chunk-XKIQZXUB.js.map +0 -1
  337. package/dist/chunk-XPSVGJYA.js.map +0 -1
  338. package/dist/chunk-XSWKORGM.js.map +0 -1
  339. package/dist/chunk-YCN4BVDK.js.map +0 -1
  340. package/dist/chunk-ZDTVJXIP.js.map +0 -1
  341. /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-YO5QJ6L3.js.map} +0 -0
  342. /package/dist/{chunk-AU7Q3LSC.js.map → chunk-2QSZNTDO.js.map} +0 -0
  343. /package/dist/{chunk-HSVJGWYS.js.map → chunk-2ROPI5OE.js.map} +0 -0
  344. /package/dist/{chunk-CF3ZF2YU.js.map → chunk-3QSU4NFF.js.map} +0 -0
  345. /package/dist/{chunk-OI27U2HT.js.map → chunk-5BTCT236.js.map} +0 -0
  346. /package/dist/{chunk-CO7ZO4TU.js.map → chunk-5VDJMYTF.js.map} +0 -0
  347. /package/dist/{chunk-YFS5OEKO.js.map → chunk-7MLB4NCL.js.map} +0 -0
  348. /package/dist/{chunk-2QANQKSQ.js.map → chunk-ADNZVFXG.js.map} +0 -0
  349. /package/dist/{chunk-557IAFPD.js.map → chunk-APRRL26Q.js.map} +0 -0
  350. /package/dist/{chunk-QDDHYAKV.js.map → chunk-AZDOWD2L.js.map} +0 -0
  351. /package/dist/{chunk-MLT75J5S.js.map → chunk-B6SU7YSE.js.map} +0 -0
  352. /package/dist/{chunk-FXKPZ3H6.js.map → chunk-BPSGLMQ4.js.map} +0 -0
  353. /package/dist/{chunk-2NLLXCJG.js.map → chunk-BXLOS5AJ.js.map} +0 -0
  354. /package/dist/{chunk-IK34DVAC.js.map → chunk-CIOMS6DI.js.map} +0 -0
  355. /package/dist/{chunk-7DZRO2DC.js.map → chunk-DEPRLVLK.js.map} +0 -0
  356. /package/dist/{chunk-DHGSZ3UD.js.map → chunk-DGNQRNLL.js.map} +0 -0
  357. /package/dist/{chunk-X7Y7WX73.js.map → chunk-DQEMWVMT.js.map} +0 -0
  358. /package/dist/{chunk-ETUPBUHB.js.map → chunk-GDASG7NC.js.map} +0 -0
  359. /package/dist/{chunk-4HP7HIE3.js.map → chunk-HP5FMB6L.js.map} +0 -0
  360. /package/dist/{chunk-DOX2CG6Y.js.map → chunk-IEUU7O4F.js.map} +0 -0
  361. /package/dist/{chunk-WSGF57U2.js.map → chunk-JQDZQ4TB.js.map} +0 -0
  362. /package/dist/{chunk-W7L6HXUC.js.map → chunk-LXOM6IQU.js.map} +0 -0
  363. /package/dist/{chunk-6JGNHWCI.js.map → chunk-OBIRVF36.js.map} +0 -0
  364. /package/dist/{chunk-GUPISBV2.js.map → chunk-PP2JH3GP.js.map} +0 -0
  365. /package/dist/{chunk-OXJBNGBK.js.map → chunk-PSUB67YB.js.map} +0 -0
  366. /package/dist/{chunk-KIB7SDIJ.js.map → chunk-Q6YIJGXJ.js.map} +0 -0
  367. /package/dist/{chunk-PPPZY2EU.js.map → chunk-QEMCQFDW.js.map} +0 -0
  368. /package/dist/{chunk-ZT3EGNLR.js.map → chunk-QPD426WT.js.map} +0 -0
  369. /package/dist/{chunk-RLV3PQGH.js.map → chunk-QVO4YOB7.js.map} +0 -0
  370. /package/dist/{chunk-KQAFEZQX.js.map → chunk-VDX2J7OX.js.map} +0 -0
  371. /package/dist/{chunk-IK7DCC5H.js.map → chunk-VMGLYN42.js.map} +0 -0
  372. /package/dist/{chunk-NSKYFGDL.js.map → chunk-X4QQB7O6.js.map} +0 -0
  373. /package/dist/{first-start-migration-GYJWIH36.js.map → first-start-migration-FF7YFGRP.js.map} +0 -0
  374. /package/dist/{tier-stats-SKML2OSF.js.map → tier-stats-3LYQ3VV5.js.map} +0 -0
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
2
2
  import path from "node:path";
3
3
  import test from "node:test";
4
4
 
5
- import { FallbackLlmClient } from "./fallback-llm.js";
5
+ import { FallbackLlmClient, gatewayTaskChainOptions } from "./fallback-llm.js";
6
6
  import { __codexCliFallbackTestHooks } from "./codex-cli-fallback.js";
7
7
  import { clearModelsJsonCache, __setModelsJsonForTest } from "./models-json.js";
8
8
  import {
@@ -73,6 +73,232 @@ test("fallback llm prefers the active gateway provider config over models.json",
73
73
  }
74
74
  });
75
75
 
76
+ test("fallback llm uses an explicit model chain override instead of gateway defaults", { concurrency: false }, async () => {
77
+ clearModelsJsonCache();
78
+ clearSecretCache();
79
+
80
+ const llm = new FallbackLlmClient({
81
+ agents: {
82
+ defaults: {
83
+ model: {
84
+ primary: "openai/default-model",
85
+ fallbacks: ["openai/default-fallback"],
86
+ },
87
+ },
88
+ },
89
+ models: {
90
+ providers: {
91
+ openai: {
92
+ baseUrl: "https://openai.example/v1",
93
+ api: "openai-completions",
94
+ apiKey: "openai-key",
95
+ models: [],
96
+ },
97
+ },
98
+ },
99
+ });
100
+
101
+ const originalFetch = globalThis.fetch;
102
+ const attemptedModels: string[] = [];
103
+ globalThis.fetch = (async (_url, init) => {
104
+ const body = JSON.parse(String(init?.body ?? "{}")) as { model?: string };
105
+ attemptedModels.push(String(body.model ?? ""));
106
+ if (body.model === "cheap-primary") {
107
+ return new Response(JSON.stringify({ error: { message: "try fallback" } }), {
108
+ status: 429,
109
+ headers: { "Content-Type": "application/json" },
110
+ });
111
+ }
112
+ return new Response(
113
+ JSON.stringify({
114
+ choices: [{ message: { content: "fallback ok" } }],
115
+ }),
116
+ {
117
+ status: 200,
118
+ headers: { "Content-Type": "application/json" },
119
+ },
120
+ );
121
+ }) as typeof fetch;
122
+
123
+ try {
124
+ const response = await llm.chatCompletion(
125
+ [{ role: "user", content: "Extract this" }],
126
+ {
127
+ temperature: 0,
128
+ maxTokens: 16,
129
+ modelChain: {
130
+ primary: "openai/cheap-primary",
131
+ fallbacks: ["openai/cheap-primary", "openai/cheap-fallback"],
132
+ },
133
+ },
134
+ );
135
+
136
+ assert.equal(response?.content, "fallback ok");
137
+ assert.equal(response?.modelUsed, "openai/cheap-fallback");
138
+ assert.deepEqual(attemptedModels, ["cheap-primary", "cheap-fallback"]);
139
+ } finally {
140
+ globalThis.fetch = originalFetch;
141
+ clearModelsJsonCache();
142
+ clearSecretCache();
143
+ }
144
+ });
145
+
146
+ test("fallback llm tries an explicit model override before a model chain override", { concurrency: false }, async () => {
147
+ clearModelsJsonCache();
148
+ clearSecretCache();
149
+
150
+ const llm = new FallbackLlmClient({
151
+ agents: {
152
+ defaults: {
153
+ model: {
154
+ primary: "openai/default-model",
155
+ },
156
+ },
157
+ },
158
+ models: {
159
+ providers: {
160
+ openai: {
161
+ baseUrl: "https://openai.example/v1",
162
+ api: "openai-completions",
163
+ apiKey: "openai-key",
164
+ models: [],
165
+ },
166
+ },
167
+ },
168
+ });
169
+
170
+ const originalFetch = globalThis.fetch;
171
+ const attemptedModels: string[] = [];
172
+ globalThis.fetch = (async (_url, init) => {
173
+ const body = JSON.parse(String(init?.body ?? "{}")) as { model?: string };
174
+ attemptedModels.push(String(body.model ?? ""));
175
+ if (body.model === "judge-model") {
176
+ return new Response(JSON.stringify({ error: { message: "try task chain" } }), {
177
+ status: 429,
178
+ headers: { "Content-Type": "application/json" },
179
+ });
180
+ }
181
+ return new Response(
182
+ JSON.stringify({
183
+ choices: [{ message: { content: "task chain ok" } }],
184
+ }),
185
+ {
186
+ status: 200,
187
+ headers: { "Content-Type": "application/json" },
188
+ },
189
+ );
190
+ }) as typeof fetch;
191
+
192
+ try {
193
+ const response = await llm.chatCompletion(
194
+ [{ role: "user", content: "Judge this" }],
195
+ {
196
+ temperature: 0,
197
+ maxTokens: 16,
198
+ model: "openai/judge-model",
199
+ modelChain: { primary: "openai/task-primary" },
200
+ },
201
+ );
202
+
203
+ assert.equal(response?.content, "task chain ok");
204
+ assert.equal(response?.modelUsed, "openai/task-primary");
205
+ assert.deepEqual(attemptedModels, ["judge-model", "task-primary"]);
206
+ } finally {
207
+ globalThis.fetch = originalFetch;
208
+ clearModelsJsonCache();
209
+ clearSecretCache();
210
+ }
211
+ });
212
+
213
+ test("fallback llm availability checks an explicit model chain override", () => {
214
+ const llm = new FallbackLlmClient({
215
+ agents: {
216
+ defaults: {
217
+ model: {
218
+ primary: "openai/default-model",
219
+ },
220
+ },
221
+ },
222
+ models: {
223
+ providers: {
224
+ openai: {
225
+ baseUrl: "https://openai.example/v1",
226
+ api: "openai-completions",
227
+ apiKey: "openai-key",
228
+ models: [],
229
+ },
230
+ },
231
+ },
232
+ });
233
+
234
+ assert.equal(llm.isAvailable({ modelChain: { primary: "openai/task-primary" } }), true);
235
+ assert.equal(llm.isAvailable({ modelChain: {} }), true);
236
+ });
237
+
238
+ test("fallback llm deduplicates a model override that matches the model chain primary", { concurrency: false }, async () => {
239
+ clearModelsJsonCache();
240
+ clearSecretCache();
241
+
242
+ const llm = new FallbackLlmClient({
243
+ agents: {
244
+ defaults: {
245
+ model: {
246
+ primary: "openai/default-model",
247
+ },
248
+ },
249
+ },
250
+ models: {
251
+ providers: {
252
+ openai: {
253
+ baseUrl: "https://openai.example/v1",
254
+ api: "openai-completions",
255
+ apiKey: "openai-key",
256
+ models: [],
257
+ },
258
+ },
259
+ },
260
+ });
261
+
262
+ const originalFetch = globalThis.fetch;
263
+ const attemptedModels: string[] = [];
264
+ globalThis.fetch = (async (_url, init) => {
265
+ const body = JSON.parse(String(init?.body ?? "{}")) as { model?: string };
266
+ attemptedModels.push(String(body.model ?? ""));
267
+ return new Response(
268
+ JSON.stringify({
269
+ choices: [{ message: { content: "dedupe ok" } }],
270
+ }),
271
+ {
272
+ status: 200,
273
+ headers: { "Content-Type": "application/json" },
274
+ },
275
+ );
276
+ }) as typeof fetch;
277
+
278
+ try {
279
+ const response = await llm.chatCompletion(
280
+ [{ role: "user", content: "Judge this" }],
281
+ {
282
+ temperature: 0,
283
+ maxTokens: 16,
284
+ model: "openai/task-primary",
285
+ modelChain: {
286
+ primary: "openai/task-primary",
287
+ fallbacks: ["openai/task-fallback"],
288
+ },
289
+ },
290
+ );
291
+
292
+ assert.equal(response?.content, "dedupe ok");
293
+ assert.equal(response?.modelUsed, "openai/task-primary");
294
+ assert.deepEqual(attemptedModels, ["task-primary"]);
295
+ } finally {
296
+ globalThis.fetch = originalFetch;
297
+ clearModelsJsonCache();
298
+ clearSecretCache();
299
+ }
300
+ });
301
+
76
302
  test("fallback llm tries an explicit model override before the configured chain", { concurrency: false }, async () => {
77
303
  clearModelsJsonCache();
78
304
  clearSecretCache();
@@ -1055,6 +1281,379 @@ test("fallback llm normalizes anthropic-compatible base URLs that omit /v1", { c
1055
1281
  }
1056
1282
  });
1057
1283
 
1284
+ test("fallback llm appends gateway default model as implicit last resort when chain is exhausted", { concurrency: false }, async () => {
1285
+ clearModelsJsonCache();
1286
+ clearSecretCache();
1287
+
1288
+ const llm = new FallbackLlmClient({
1289
+ agents: {
1290
+ defaults: {
1291
+ model: {
1292
+ primary: "openai/default-model",
1293
+ },
1294
+ },
1295
+ },
1296
+ models: {
1297
+ providers: {
1298
+ openai: {
1299
+ baseUrl: "https://openai.example/v1",
1300
+ api: "openai-completions",
1301
+ apiKey: "openai-key",
1302
+ models: [],
1303
+ },
1304
+ },
1305
+ },
1306
+ });
1307
+
1308
+ const originalFetch = globalThis.fetch;
1309
+ const attemptedModels: string[] = [];
1310
+ globalThis.fetch = (async (_url, init) => {
1311
+ const body = JSON.parse(String(init?.body ?? "{}")) as { model?: string };
1312
+ attemptedModels.push(String(body.model ?? ""));
1313
+ if (body.model === "stale-primary" || body.model === "stale-fallback") {
1314
+ return new Response(JSON.stringify({ error: { message: "provider gone" } }), {
1315
+ status: 401,
1316
+ headers: { "Content-Type": "application/json" },
1317
+ });
1318
+ }
1319
+ return new Response(
1320
+ JSON.stringify({
1321
+ choices: [{ message: { content: "default-model ok" } }],
1322
+ }),
1323
+ {
1324
+ status: 200,
1325
+ headers: { "Content-Type": "application/json" },
1326
+ },
1327
+ );
1328
+ }) as typeof fetch;
1329
+
1330
+ try {
1331
+ const response = await llm.chatCompletion(
1332
+ [{ role: "user", content: "Extract this" }],
1333
+ {
1334
+ temperature: 0,
1335
+ maxTokens: 16,
1336
+ modelChain: {
1337
+ primary: "openai/stale-primary",
1338
+ fallbacks: ["openai/stale-fallback"],
1339
+ },
1340
+ },
1341
+ );
1342
+
1343
+ // All chain models failed, but gateway default model was appended and succeeds
1344
+ assert.equal(response?.content, "default-model ok");
1345
+ assert.equal(response?.modelUsed, "openai/default-model");
1346
+ assert.deepEqual(attemptedModels, ["stale-primary", "stale-fallback", "default-model"]);
1347
+ } finally {
1348
+ globalThis.fetch = originalFetch;
1349
+ clearModelsJsonCache();
1350
+ clearSecretCache();
1351
+ }
1352
+ });
1353
+
1354
+ test("fallback llm does not duplicate gateway default model when it is already in chain", { concurrency: false }, async () => {
1355
+ clearModelsJsonCache();
1356
+ clearSecretCache();
1357
+
1358
+ const llm = new FallbackLlmClient({
1359
+ agents: {
1360
+ defaults: {
1361
+ model: {
1362
+ primary: "openai/default-model",
1363
+ },
1364
+ },
1365
+ },
1366
+ models: {
1367
+ providers: {
1368
+ openai: {
1369
+ baseUrl: "https://openai.example/v1",
1370
+ api: "openai-completions",
1371
+ apiKey: "openai-key",
1372
+ models: [],
1373
+ },
1374
+ },
1375
+ },
1376
+ });
1377
+
1378
+ const originalFetch = globalThis.fetch;
1379
+ const attemptedModels: string[] = [];
1380
+ globalThis.fetch = (async (_url, init) => {
1381
+ const body = JSON.parse(String(init?.body ?? "{}")) as { model?: string };
1382
+ attemptedModels.push(String(body.model ?? ""));
1383
+ return new Response(
1384
+ JSON.stringify({
1385
+ choices: [{ message: { content: "ok" } }],
1386
+ }),
1387
+ {
1388
+ status: 200,
1389
+ headers: { "Content-Type": "application/json" },
1390
+ },
1391
+ );
1392
+ }) as typeof fetch;
1393
+
1394
+ try {
1395
+ const response = await llm.chatCompletion(
1396
+ [{ role: "user", content: "Extract this" }],
1397
+ {
1398
+ temperature: 0,
1399
+ maxTokens: 16,
1400
+ modelChain: {
1401
+ primary: "openai/default-model",
1402
+ },
1403
+ },
1404
+ );
1405
+
1406
+ // The default model is already primary in the chain — should not be appended again
1407
+ assert.equal(response?.content, "ok");
1408
+ assert.equal(response?.modelUsed, "openai/default-model");
1409
+ assert.deepEqual(attemptedModels, ["default-model"]);
1410
+ } finally {
1411
+ globalThis.fetch = originalFetch;
1412
+ clearModelsJsonCache();
1413
+ clearSecretCache();
1414
+ }
1415
+ });
1416
+
1417
+ test("fallback llm does not append gateway default model when no chain is provided", { concurrency: false }, async () => {
1418
+ clearModelsJsonCache();
1419
+ clearSecretCache();
1420
+
1421
+ const llm = new FallbackLlmClient({
1422
+ agents: {
1423
+ defaults: {
1424
+ model: {
1425
+ primary: "openai/default-model",
1426
+ },
1427
+ },
1428
+ },
1429
+ models: {
1430
+ providers: {
1431
+ openai: {
1432
+ baseUrl: "https://openai.example/v1",
1433
+ api: "openai-completions",
1434
+ apiKey: "openai-key",
1435
+ models: [],
1436
+ },
1437
+ },
1438
+ },
1439
+ });
1440
+
1441
+ const originalFetch = globalThis.fetch;
1442
+ const attemptedModels: string[] = [];
1443
+ globalThis.fetch = (async (_url, init) => {
1444
+ const body = JSON.parse(String(init?.body ?? "{}")) as { model?: string };
1445
+ attemptedModels.push(String(body.model ?? ""));
1446
+ return new Response(
1447
+ JSON.stringify({
1448
+ choices: [{ message: { content: "ok" } }],
1449
+ }),
1450
+ {
1451
+ status: 200,
1452
+ headers: { "Content-Type": "application/json" },
1453
+ },
1454
+ );
1455
+ }) as typeof fetch;
1456
+
1457
+ try {
1458
+ const response = await llm.chatCompletion(
1459
+ [{ role: "user", content: "Extract this" }],
1460
+ { temperature: 0, maxTokens: 16 },
1461
+ );
1462
+
1463
+ // Without modelChainOverride, the chain is built from agents.defaults.model;
1464
+ // the default model is already the primary, so no duplication
1465
+ assert.equal(response?.content, "ok");
1466
+ assert.equal(response?.modelUsed, "openai/default-model");
1467
+ assert.deepEqual(attemptedModels, ["default-model"]);
1468
+ } finally {
1469
+ globalThis.fetch = originalFetch;
1470
+ clearModelsJsonCache();
1471
+ clearSecretCache();
1472
+ }
1473
+ });
1474
+
1475
+ test("gatewayTaskChainOptions resolves the single source of truth for task routing", () => {
1476
+ // taskModelChain wins over gatewayAgentId in gateway mode.
1477
+ assert.deepEqual(
1478
+ gatewayTaskChainOptions({ modelSource: "gateway", gatewayAgentId: "persona", taskModelChain: { primary: "zai/glm-4.7-flash" } }),
1479
+ { modelChain: { primary: "zai/glm-4.7-flash" } },
1480
+ );
1481
+ // Falls back to gatewayAgentId when no taskModelChain.
1482
+ assert.deepEqual(
1483
+ gatewayTaskChainOptions({ modelSource: "gateway", gatewayAgentId: "persona", taskModelChain: undefined }),
1484
+ { agentId: "persona" },
1485
+ );
1486
+ // Empty when gateway mode but neither configured.
1487
+ assert.deepEqual(
1488
+ gatewayTaskChainOptions({ modelSource: "gateway", gatewayAgentId: "", taskModelChain: undefined }),
1489
+ {},
1490
+ );
1491
+ // Plugin mode never routes through the chain, even if taskModelChain is set.
1492
+ assert.deepEqual(
1493
+ gatewayTaskChainOptions({ modelSource: "plugin", gatewayAgentId: "persona", taskModelChain: { primary: "zai/glm-4.7-flash" } }),
1494
+ {},
1495
+ );
1496
+ });
1497
+
1498
+ test("fallback llm last-resort appends the full gateway default chain (primary + fallbacks)", { concurrency: false }, async () => {
1499
+ clearModelsJsonCache();
1500
+ clearSecretCache();
1501
+
1502
+ // taskModelChain exhausted AND default primary unreachable — a listed default
1503
+ // fallback must still be tried (cursor review #1425).
1504
+ const llm = new FallbackLlmClient({
1505
+ agents: {
1506
+ defaults: { model: { primary: "openai/default-primary", fallbacks: ["openai/default-fallback"] } },
1507
+ },
1508
+ models: {
1509
+ providers: {
1510
+ openai: {
1511
+ baseUrl: "https://openai.example/v1",
1512
+ api: "openai-completions",
1513
+ apiKey: "openai-key",
1514
+ models: [],
1515
+ },
1516
+ },
1517
+ },
1518
+ });
1519
+
1520
+ const originalFetch = globalThis.fetch;
1521
+ const attempted: string[] = [];
1522
+ globalThis.fetch = (async (_url, init) => {
1523
+ const body = JSON.parse(String(init?.body ?? "{}")) as { model?: string };
1524
+ attempted.push(String(body.model ?? ""));
1525
+ if (body.model === "default-fallback") {
1526
+ return new Response(JSON.stringify({ choices: [{ message: { content: "default-fallback ok" } }] }), {
1527
+ status: 200,
1528
+ headers: { "Content-Type": "application/json" },
1529
+ });
1530
+ }
1531
+ return new Response(JSON.stringify({ error: { message: "gone" } }), {
1532
+ status: 401,
1533
+ headers: { "Content-Type": "application/json" },
1534
+ });
1535
+ }) as typeof fetch;
1536
+
1537
+ try {
1538
+ const response = await llm.chatCompletion(
1539
+ [{ role: "user", content: "Extract this" }],
1540
+ {
1541
+ temperature: 0,
1542
+ maxTokens: 16,
1543
+ modelChain: { primary: "openai/stale-primary", fallbacks: ["openai/stale-fallback"] },
1544
+ },
1545
+ );
1546
+
1547
+ assert.equal(response?.modelUsed, "openai/default-fallback");
1548
+ assert.deepEqual(attempted, ["stale-primary", "stale-fallback", "default-primary", "default-fallback"]);
1549
+ } finally {
1550
+ globalThis.fetch = originalFetch;
1551
+ clearModelsJsonCache();
1552
+ clearSecretCache();
1553
+ }
1554
+ });
1555
+
1556
+ test("fallback llm does NOT append gateway default to a persona chain (last-resort is scoped to task chains)", { concurrency: false }, async () => {
1557
+ clearModelsJsonCache();
1558
+ clearSecretCache();
1559
+
1560
+ // A persona chain that deliberately excludes the gateway default. Without a
1561
+ // modelChain override, the implicit last-resort must NOT augment it, so the
1562
+ // main agent's persona fallback behavior is preserved (gotcha #39).
1563
+ const llm = new FallbackLlmClient({
1564
+ agents: {
1565
+ defaults: { model: { primary: "openai/default-model" } },
1566
+ list: [{ id: "main-agent", model: { primary: "openai/persona-model" } }],
1567
+ },
1568
+ models: {
1569
+ providers: {
1570
+ openai: {
1571
+ baseUrl: "https://openai.example/v1",
1572
+ api: "openai-completions",
1573
+ apiKey: "openai-key",
1574
+ models: [],
1575
+ },
1576
+ },
1577
+ },
1578
+ });
1579
+
1580
+ const originalFetch = globalThis.fetch;
1581
+ const attemptedModels: string[] = [];
1582
+ globalThis.fetch = (async (_url, init) => {
1583
+ const body = JSON.parse(String(init?.body ?? "{}")) as { model?: string };
1584
+ attemptedModels.push(String(body.model ?? ""));
1585
+ return new Response(JSON.stringify({ choices: [{ message: { content: "ok" } }] }), {
1586
+ status: 200,
1587
+ headers: { "Content-Type": "application/json" },
1588
+ });
1589
+ }) as typeof fetch;
1590
+
1591
+ try {
1592
+ const response = await llm.chatCompletion(
1593
+ [{ role: "user", content: "Extract this" }],
1594
+ { temperature: 0, maxTokens: 16, agentId: "main-agent" },
1595
+ );
1596
+
1597
+ assert.equal(response?.modelUsed, "openai/persona-model");
1598
+ // The gateway default must not be appended to the persona chain.
1599
+ assert.deepEqual(attemptedModels, ["persona-model"]);
1600
+ } finally {
1601
+ globalThis.fetch = originalFetch;
1602
+ clearModelsJsonCache();
1603
+ clearSecretCache();
1604
+ }
1605
+ });
1606
+
1607
+ test("fallback llm does NOT append default for a primary-less override that falls through to a persona chain (cursor #1425)", { concurrency: false }, async () => {
1608
+ clearModelsJsonCache();
1609
+ clearSecretCache();
1610
+
1611
+ // A primary-less override ({}) is inactive — chain resolution uses the persona
1612
+ // chain. The implicit last-resort must key on the SAME activation condition
1613
+ // (override.primary), so the persona chain is not augmented with the default.
1614
+ const llm = new FallbackLlmClient({
1615
+ agents: {
1616
+ defaults: { model: { primary: "openai/default-model" } },
1617
+ list: [{ id: "main-agent", model: { primary: "openai/persona-model" } }],
1618
+ },
1619
+ models: {
1620
+ providers: {
1621
+ openai: {
1622
+ baseUrl: "https://openai.example/v1",
1623
+ api: "openai-completions",
1624
+ apiKey: "openai-key",
1625
+ models: [],
1626
+ },
1627
+ },
1628
+ },
1629
+ });
1630
+
1631
+ const originalFetch = globalThis.fetch;
1632
+ const attemptedModels: string[] = [];
1633
+ globalThis.fetch = (async (_url, init) => {
1634
+ const body = JSON.parse(String(init?.body ?? "{}")) as { model?: string };
1635
+ attemptedModels.push(String(body.model ?? ""));
1636
+ return new Response(JSON.stringify({ choices: [{ message: { content: "ok" } }] }), {
1637
+ status: 200,
1638
+ headers: { "Content-Type": "application/json" },
1639
+ });
1640
+ }) as typeof fetch;
1641
+
1642
+ try {
1643
+ const response = await llm.chatCompletion(
1644
+ [{ role: "user", content: "Extract this" }],
1645
+ { temperature: 0, maxTokens: 16, agentId: "main-agent", modelChain: {} },
1646
+ );
1647
+
1648
+ assert.equal(response?.modelUsed, "openai/persona-model");
1649
+ assert.deepEqual(attemptedModels, ["persona-model"]);
1650
+ } finally {
1651
+ globalThis.fetch = originalFetch;
1652
+ clearModelsJsonCache();
1653
+ clearSecretCache();
1654
+ }
1655
+ });
1656
+
1058
1657
  function disableGatewaySecretResolverForTest(): void {
1059
1658
  __setGatewayResolverForTest(async () => null);
1060
1659
  }