@ashx-j/lunr-dev 0.2.13-dev.5.1 → 0.2.13-dev.8.1

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 (246) hide show
  1. package/dist/builtin-extensions/ashxj-tui.d.ts.map +1 -1
  2. package/dist/builtin-extensions/ashxj-tui.js +4 -4
  3. package/dist/builtin-extensions/ashxj-tui.js.map +1 -1
  4. package/dist/builtin-extensions/index.d.ts.map +1 -1
  5. package/dist/builtin-extensions/index.js +2 -0
  6. package/dist/builtin-extensions/index.js.map +1 -1
  7. package/dist/builtin-extensions/lunr-settings-tools.d.ts +4 -0
  8. package/dist/builtin-extensions/lunr-settings-tools.d.ts.map +1 -0
  9. package/dist/builtin-extensions/lunr-settings-tools.js +120 -0
  10. package/dist/builtin-extensions/lunr-settings-tools.js.map +1 -0
  11. package/dist/builtin-extensions/pi-prompt-template-model/subagent-runtime.d.ts +2 -2
  12. package/dist/builtin-extensions/pi-prompt-template-model/subagent-runtime.d.ts.map +1 -1
  13. package/dist/builtin-extensions/pi-prompt-template-model/subagent-runtime.js.map +1 -1
  14. package/dist/builtin-extensions/pi-prompt-template-model/subagent-step.d.ts.map +1 -1
  15. package/dist/builtin-extensions/pi-prompt-template-model/subagent-step.js +3 -3
  16. package/dist/builtin-extensions/pi-prompt-template-model/subagent-step.js.map +1 -1
  17. package/dist/builtin-extensions/pi-subagents/src/api/delegation.d.ts +2 -2
  18. package/dist/builtin-extensions/pi-subagents/src/api/delegation.d.ts.map +1 -1
  19. package/dist/builtin-extensions/pi-subagents/src/api/delegation.js +1 -1
  20. package/dist/builtin-extensions/pi-subagents/src/api/delegation.js.map +1 -1
  21. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
  22. package/dist/builtin-extensions/pi-subagents/src/extension/index.js +2 -1
  23. package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
  24. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts +5 -11
  25. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
  26. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +12 -15
  27. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
  28. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +3 -3
  29. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
  30. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +14 -27
  31. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
  32. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts.map +1 -1
  33. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js +6 -4
  34. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js.map +1 -1
  35. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.d.ts.map +1 -1
  36. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.js +2 -1
  37. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.js.map +1 -1
  38. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.d.ts.map +1 -1
  39. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js +6 -4
  40. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js.map +1 -1
  41. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-status.d.ts.map +1 -1
  42. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-status.js +4 -2
  43. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-status.js.map +1 -1
  44. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-clarify.d.ts.map +1 -1
  45. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-clarify.js +3 -35
  46. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-clarify.js.map +1 -1
  47. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts.map +1 -1
  48. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js +5 -12
  49. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js.map +1 -1
  50. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  51. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +14 -31
  52. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
  53. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts +2 -0
  54. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts.map +1 -1
  55. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js +28 -22
  56. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js.map +1 -1
  57. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts +1 -0
  58. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts.map +1 -1
  59. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.js.map +1 -1
  60. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts.map +1 -1
  61. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js +9 -4
  62. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js.map +1 -1
  63. package/dist/builtin-extensions/pi-subagents/src/shared/formatters.d.ts +1 -1
  64. package/dist/builtin-extensions/pi-subagents/src/shared/formatters.d.ts.map +1 -1
  65. package/dist/builtin-extensions/pi-subagents/src/shared/formatters.js +2 -2
  66. package/dist/builtin-extensions/pi-subagents/src/shared/formatters.js.map +1 -1
  67. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +4 -7
  68. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
  69. package/dist/builtin-extensions/pi-subagents/src/shared/types.js +1 -1
  70. package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
  71. package/dist/builtin-extensions/pi-subagents/src/slash/delegation-adapters.d.ts +3 -3
  72. package/dist/builtin-extensions/pi-subagents/src/slash/delegation-adapters.d.ts.map +1 -1
  73. package/dist/builtin-extensions/pi-subagents/src/slash/delegation-adapters.js +7 -6
  74. package/dist/builtin-extensions/pi-subagents/src/slash/delegation-adapters.js.map +1 -1
  75. package/dist/builtin-extensions/pi-subagents/src/slash/delegation-request.d.ts.map +1 -1
  76. package/dist/builtin-extensions/pi-subagents/src/slash/delegation-request.js +3 -3
  77. package/dist/builtin-extensions/pi-subagents/src/slash/delegation-request.js.map +1 -1
  78. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts +1 -1
  79. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts.map +1 -1
  80. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js +6 -4
  81. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js.map +1 -1
  82. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts +1 -1
  83. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts.map +1 -1
  84. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js +15 -10
  85. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js.map +1 -1
  86. package/dist/builtin-extensions/pi-subagents/src/tui/fleet.d.ts.map +1 -1
  87. package/dist/builtin-extensions/pi-subagents/src/tui/fleet.js +3 -2
  88. package/dist/builtin-extensions/pi-subagents/src/tui/fleet.js.map +1 -1
  89. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +2 -2
  90. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
  91. package/dist/builtin-extensions/pi-subagents/src/tui/render.js +75 -43
  92. package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
  93. package/dist/catalog/models.json +1 -1
  94. package/dist/catalog/providers/amazon-bedrock.json +1 -1
  95. package/dist/catalog/providers/anthropic.json +1 -1
  96. package/dist/catalog/providers/azure-openai-responses.json +1 -1
  97. package/dist/catalog/providers/cloudflare-ai-gateway.json +1 -1
  98. package/dist/catalog/providers/fireworks.json +1 -1
  99. package/dist/catalog/providers/github-copilot.json +1 -1
  100. package/dist/catalog/providers/google-vertex.json +1 -1
  101. package/dist/catalog/providers/google.json +1 -1
  102. package/dist/catalog/providers/huggingface.json +1 -1
  103. package/dist/catalog/providers/nvidia.json +1 -1
  104. package/dist/catalog/providers/openai-codex.json +1 -1
  105. package/dist/catalog/providers/openai.json +1 -1
  106. package/dist/catalog/providers/opencode-go.json +1 -1
  107. package/dist/catalog/providers/opencode.json +1 -1
  108. package/dist/catalog/providers/openrouter.json +1 -1
  109. package/dist/catalog/providers/vercel-ai-gateway.json +1 -1
  110. package/dist/catalog/publication.json +131 -3
  111. package/dist/cli/list-models.d.ts.map +1 -1
  112. package/dist/cli/list-models.js +1 -0
  113. package/dist/cli/list-models.js.map +1 -1
  114. package/dist/core/agent-session-runtime.d.ts +5 -1
  115. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  116. package/dist/core/agent-session-runtime.js +7 -2
  117. package/dist/core/agent-session-runtime.js.map +1 -1
  118. package/dist/core/agent-session.d.ts +2 -0
  119. package/dist/core/agent-session.d.ts.map +1 -1
  120. package/dist/core/agent-session.js +20 -5
  121. package/dist/core/agent-session.js.map +1 -1
  122. package/dist/core/catalog-cache-scope.d.ts +3 -0
  123. package/dist/core/catalog-cache-scope.d.ts.map +1 -0
  124. package/dist/core/catalog-cache-scope.js +12 -0
  125. package/dist/core/catalog-cache-scope.js.map +1 -0
  126. package/dist/core/catalog-merge.d.ts +2 -0
  127. package/dist/core/catalog-merge.d.ts.map +1 -1
  128. package/dist/core/catalog-merge.js +29 -2
  129. package/dist/core/catalog-merge.js.map +1 -1
  130. package/dist/core/catalog-refresh-polling.d.ts +8 -0
  131. package/dist/core/catalog-refresh-polling.d.ts.map +1 -0
  132. package/dist/core/catalog-refresh-polling.js +30 -0
  133. package/dist/core/catalog-refresh-polling.js.map +1 -0
  134. package/dist/core/codex-catalog.d.ts +3 -0
  135. package/dist/core/codex-catalog.d.ts.map +1 -0
  136. package/dist/core/codex-catalog.js +15 -0
  137. package/dist/core/codex-catalog.js.map +1 -0
  138. package/dist/core/context-breakdown.d.ts +2 -0
  139. package/dist/core/context-breakdown.d.ts.map +1 -1
  140. package/dist/core/context-breakdown.js +6 -2
  141. package/dist/core/context-breakdown.js.map +1 -1
  142. package/dist/core/injected-prompt.d.ts +3 -3
  143. package/dist/core/injected-prompt.d.ts.map +1 -1
  144. package/dist/core/injected-prompt.js +2 -7
  145. package/dist/core/injected-prompt.js.map +1 -1
  146. package/dist/core/large-subagent-launch.d.ts +13 -0
  147. package/dist/core/large-subagent-launch.d.ts.map +1 -0
  148. package/dist/core/large-subagent-launch.js +54 -0
  149. package/dist/core/large-subagent-launch.js.map +1 -0
  150. package/dist/core/live-catalog.d.ts +5 -2
  151. package/dist/core/live-catalog.d.ts.map +1 -1
  152. package/dist/core/live-catalog.js +108 -27
  153. package/dist/core/live-catalog.js.map +1 -1
  154. package/dist/core/model-instructions.d.ts +24 -0
  155. package/dist/core/model-instructions.d.ts.map +1 -0
  156. package/dist/core/model-instructions.js +123 -0
  157. package/dist/core/model-instructions.js.map +1 -0
  158. package/dist/core/model-runtime.d.ts +8 -0
  159. package/dist/core/model-runtime.d.ts.map +1 -1
  160. package/dist/core/model-runtime.js +64 -12
  161. package/dist/core/model-runtime.js.map +1 -1
  162. package/dist/core/official-catalog.d.ts +4 -0
  163. package/dist/core/official-catalog.d.ts.map +1 -1
  164. package/dist/core/official-catalog.js +86 -21
  165. package/dist/core/official-catalog.js.map +1 -1
  166. package/dist/core/permissions.d.ts +11 -10
  167. package/dist/core/permissions.d.ts.map +1 -1
  168. package/dist/core/permissions.js +61 -45
  169. package/dist/core/permissions.js.map +1 -1
  170. package/dist/core/plan-mode.d.ts.map +1 -1
  171. package/dist/core/plan-mode.js +10 -1
  172. package/dist/core/plan-mode.js.map +1 -1
  173. package/dist/core/resource-loader.d.ts +3 -0
  174. package/dist/core/resource-loader.d.ts.map +1 -1
  175. package/dist/core/resource-loader.js +13 -4
  176. package/dist/core/resource-loader.js.map +1 -1
  177. package/dist/core/runtime-bridges.d.ts +4 -0
  178. package/dist/core/runtime-bridges.d.ts.map +1 -0
  179. package/dist/core/runtime-bridges.js +16 -0
  180. package/dist/core/runtime-bridges.js.map +1 -0
  181. package/dist/core/sdk.d.ts.map +1 -1
  182. package/dist/core/sdk.js +1 -0
  183. package/dist/core/sdk.js.map +1 -1
  184. package/dist/core/session-manager.d.ts.map +1 -1
  185. package/dist/core/session-manager.js +17 -16
  186. package/dist/core/session-manager.js.map +1 -1
  187. package/dist/core/settings-manager.d.ts +13 -0
  188. package/dist/core/settings-manager.d.ts.map +1 -1
  189. package/dist/core/settings-manager.js +32 -0
  190. package/dist/core/settings-manager.js.map +1 -1
  191. package/dist/core/settings-tools-bridge.d.ts +35 -0
  192. package/dist/core/settings-tools-bridge.d.ts.map +1 -0
  193. package/dist/core/settings-tools-bridge.js +56 -0
  194. package/dist/core/settings-tools-bridge.js.map +1 -0
  195. package/dist/core/swarm.d.ts +0 -31
  196. package/dist/core/swarm.d.ts.map +1 -1
  197. package/dist/core/swarm.js +2 -76
  198. package/dist/core/swarm.js.map +1 -1
  199. package/dist/core/system-prompt.d.ts.map +1 -1
  200. package/dist/core/system-prompt.js +1 -1
  201. package/dist/core/system-prompt.js.map +1 -1
  202. package/dist/gateway/agent-bridge.d.ts.map +1 -1
  203. package/dist/gateway/agent-bridge.js +3 -2
  204. package/dist/gateway/agent-bridge.js.map +1 -1
  205. package/dist/gateway/approval.d.ts.map +1 -1
  206. package/dist/gateway/approval.js +4 -3
  207. package/dist/gateway/approval.js.map +1 -1
  208. package/dist/gateway/cron.d.ts.map +1 -1
  209. package/dist/gateway/cron.js +3 -2
  210. package/dist/gateway/cron.js.map +1 -1
  211. package/dist/gateway/router.d.ts +1 -1
  212. package/dist/gateway/router.d.ts.map +1 -1
  213. package/dist/gateway/router.js +1 -1
  214. package/dist/gateway/router.js.map +1 -1
  215. package/dist/main.d.ts.map +1 -1
  216. package/dist/main.js +12 -9
  217. package/dist/main.js.map +1 -1
  218. package/dist/migrations.d.ts.map +1 -1
  219. package/dist/migrations.js +4 -0
  220. package/dist/migrations.js.map +1 -1
  221. package/dist/modes/interactive/components/injected-prompt-message.d.ts +1 -1
  222. package/dist/modes/interactive/components/injected-prompt-message.d.ts.map +1 -1
  223. package/dist/modes/interactive/components/injected-prompt-message.js +1 -1
  224. package/dist/modes/interactive/components/injected-prompt-message.js.map +1 -1
  225. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  226. package/dist/modes/interactive/components/model-selector.js +4 -1
  227. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  228. package/dist/modes/interactive/components/settings-selector.d.ts +8 -1
  229. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  230. package/dist/modes/interactive/components/settings-selector.js +61 -1
  231. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  232. package/dist/modes/interactive/interactive-mode.d.ts +2 -2
  233. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  234. package/dist/modes/interactive/interactive-mode.js +65 -99
  235. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  236. package/dist/startup/interactive-shell.d.ts.map +1 -1
  237. package/dist/startup/interactive-shell.js +2 -1
  238. package/dist/startup/interactive-shell.js.map +1 -1
  239. package/docs/features.md +7 -6
  240. package/docs/model-catalog.md +29 -0
  241. package/docs/quickstart.md +3 -2
  242. package/docs/settings.md +3 -1
  243. package/docs/termux.md +1 -1
  244. package/docs/usage.md +2 -1
  245. package/examples/extensions/subagent/README.md +1 -1
  246. package/package.json +4 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-instructions.d.ts","sourceRoot":"","sources":["../../src/core/model-instructions.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAEvE,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,YAAY,CAAC;AAE1D,MAAM,WAAW,gCAAgC;IAChD,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,CAAC;IACtD,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAElE;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAWlE;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAElF;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGlF;AAED,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,MAAM,GAAG,gCAAgC,CAoBlG;AASD,wBAAgB,0BAA0B,CACzC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,cAAc,GAAG,eAAe,GAAG,YAAY,CAAC,EACzE,YAAY,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,GACnC,MAAM,EAAE,CAwBV;AAWD,wBAAgB,4BAA4B,CAAC,OAAO,EAAE;IACrD,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,eAAe,CAAC;IACjC,KAAK,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;CAC/B,GAAG,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAc3C;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAO3F","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, renameSync, statSync } from \"node:fs\";\nimport { basename, isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport type { Model } from \"@ashx-j/lunr-ai\";\nimport type { Settings, SettingsManager } from \"./settings-manager.ts\";\n\nexport type ModelInstructionsMode = \"both\" | \"model-only\";\n\nexport interface ModelInstructionsMigrationResult {\n\tstatus: \"absent\" | \"migrated\" | \"conflict\" | \"failed\";\n\tdiagnostic?: string;\n}\n\nexport function getInstructionsRoot(agentDir: string): string {\n\treturn join(resolve(agentDir), \"agents\");\n}\n\nexport function getGlobalInstructionsPath(agentDir: string): string {\n\treturn join(getInstructionsRoot(agentDir), \"AGENTS.md\");\n}\n\nexport function modelInstructionFolderName(modelId: string): string {\n\tconst finalSegment = modelId.trim().split(/[\\\\/]/).filter(Boolean).at(-1) ?? \"model\";\n\tconst withoutThinking = finalSegment.replace(/:(?:off|minimal|low|medium|high|xhigh|max)$/i, \"\");\n\tlet safe = withoutThinking.replace(/[<>:\"/\\\\|?*\\u0000-\\u001f]/g, \"_\").replace(/[. ]+$/g, \"\");\n\tif (!safe || safe === \".\" || safe === \"..\") safe = \"model\";\n\tif (/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\\.|$)/i.test(safe)) safe = `_${safe}`;\n\tif (safe.length > 100 || safe.toLowerCase() !== withoutThinking.toLowerCase()) {\n\t\tconst digest = createHash(\"sha256\").update(withoutThinking.toLowerCase()).digest(\"hex\").slice(0, 8);\n\t\tsafe = `${safe.slice(0, 90)}-${digest}`;\n\t}\n\treturn safe.toLowerCase();\n}\n\nexport function getModelInstructionsPath(agentDir: string, modelId: string): string {\n\treturn join(getInstructionsRoot(agentDir), modelInstructionFolderName(modelId), \"AGENTS.md\");\n}\n\nexport function isUserInstructionsPath(filePath: string, agentDir: string): boolean {\n\tconst rel = relative(resolve(getInstructionsRoot(agentDir)), resolve(filePath));\n\treturn rel === \"\" || (!isAbsolute(rel) && rel !== \"..\" && !rel.startsWith(`..${sep}`));\n}\n\nexport function migrateLegacyGlobalInstructions(agentDir: string): ModelInstructionsMigrationResult {\n\tconst legacyPath = join(resolve(agentDir), \"AGENTS.md\");\n\tconst destinationPath = getGlobalInstructionsPath(agentDir);\n\tif (!existsSync(legacyPath)) return { status: \"absent\" };\n\tif (existsSync(destinationPath)) {\n\t\treturn {\n\t\t\tstatus: \"conflict\",\n\t\t\tdiagnostic: `Both legacy '${legacyPath}' and current '${destinationPath}' exist. Kept both and will use the current file.`,\n\t\t};\n\t}\n\ttry {\n\t\tmkdirSync(getInstructionsRoot(agentDir), { recursive: true });\n\t\trenameSync(legacyPath, destinationPath);\n\t\treturn { status: \"migrated\" };\n\t} catch (error) {\n\t\treturn {\n\t\t\tstatus: \"failed\",\n\t\t\tdiagnostic: `Could not migrate '${legacyPath}' to '${destinationPath}': ${error instanceof Error ? error.message : String(error)}`,\n\t\t};\n\t}\n}\n\nfunction concreteModelId(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") return undefined;\n\tconst trimmed = value.trim();\n\tif (!trimmed || /[*?{}]/.test(trimmed)) return undefined;\n\treturn trimmed.replace(/:(?:off|minimal|low|medium|high|xhigh|max)$/i, \"\");\n}\n\nexport function ensureModelInstructionDirs(\n\tagentDir: string,\n\tsettings: Pick<Settings, \"defaultModel\" | \"enabledModels\" | \"modelTiers\">,\n\tcurrentModel?: Pick<Model<any>, \"id\">,\n): string[] {\n\tconst modelIds = new Set<string>();\n\tconst add = (value: unknown) => {\n\t\tconst id = concreteModelId(value);\n\t\tif (id) modelIds.add(id);\n\t};\n\tadd(currentModel?.id);\n\tadd(settings.defaultModel);\n\tfor (const value of settings.enabledModels ?? []) add(value);\n\tadd(settings.modelTiers?.light);\n\tadd(settings.modelTiers?.standard);\n\tadd(settings.modelTiers?.heavy);\n\n\tconst diagnostics: string[] = [];\n\tfor (const id of modelIds) {\n\t\ttry {\n\t\t\tmkdirSync(join(getInstructionsRoot(agentDir), modelInstructionFolderName(id)), { recursive: true });\n\t\t} catch (error) {\n\t\t\tdiagnostics.push(\n\t\t\t\t`Could not prepare model-instruction folder for '${id}': ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t}\n\t}\n\treturn diagnostics;\n}\n\nfunction safeReadInstruction(path: string): { path: string; content: string } | undefined {\n\ttry {\n\t\tif (!existsSync(path) || !statSync(path).isFile()) return undefined;\n\t\treturn { path, content: readFileSync(path, \"utf8\") };\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport function loadSelectedUserInstructions(options: {\n\tagentDir: string;\n\tsettingsManager: SettingsManager;\n\tmodel?: Pick<Model<any>, \"id\">;\n}): Array<{ path: string; content: string }> {\n\tensureModelInstructionDirs(options.agentDir, options.settingsManager.getGlobalSettings(), options.model);\n\tconst enabled = options.settingsManager.getModelInstructionsEnabled();\n\tconst mode = options.settingsManager.getModelInstructionsMode();\n\tconst files: Array<{ path: string; content: string }> = [];\n\tif (!enabled || mode === \"both\") {\n\t\tconst globalFile = safeReadInstruction(getGlobalInstructionsPath(options.agentDir));\n\t\tif (globalFile) files.push(globalFile);\n\t}\n\tif (enabled && options.model) {\n\t\tconst modelFile = safeReadInstruction(getModelInstructionsPath(options.agentDir, options.model.id));\n\t\tif (modelFile) files.push(modelFile);\n\t}\n\treturn files;\n}\n\nexport function instructionPathLabel(filePath: string, agentDir: string): string | undefined {\n\tconst resolved = resolve(filePath);\n\tif (resolved.toLowerCase() === resolve(getGlobalInstructionsPath(agentDir)).toLowerCase()) {\n\t\treturn \"Global AGENTS.md\";\n\t}\n\tif (!isUserInstructionsPath(resolved, agentDir)) return undefined;\n\treturn `${basename(resolve(resolved, \"..\"))} AGENTS.md`;\n}\n"]}
@@ -0,0 +1,123 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, statSync } from "node:fs";
3
+ import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
4
+ export function getInstructionsRoot(agentDir) {
5
+ return join(resolve(agentDir), "agents");
6
+ }
7
+ export function getGlobalInstructionsPath(agentDir) {
8
+ return join(getInstructionsRoot(agentDir), "AGENTS.md");
9
+ }
10
+ export function modelInstructionFolderName(modelId) {
11
+ const finalSegment = modelId.trim().split(/[\\/]/).filter(Boolean).at(-1) ?? "model";
12
+ const withoutThinking = finalSegment.replace(/:(?:off|minimal|low|medium|high|xhigh|max)$/i, "");
13
+ let safe = withoutThinking.replace(/[<>:"/\\|?*\u0000-\u001f]/g, "_").replace(/[. ]+$/g, "");
14
+ if (!safe || safe === "." || safe === "..")
15
+ safe = "model";
16
+ if (/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(safe))
17
+ safe = `_${safe}`;
18
+ if (safe.length > 100 || safe.toLowerCase() !== withoutThinking.toLowerCase()) {
19
+ const digest = createHash("sha256").update(withoutThinking.toLowerCase()).digest("hex").slice(0, 8);
20
+ safe = `${safe.slice(0, 90)}-${digest}`;
21
+ }
22
+ return safe.toLowerCase();
23
+ }
24
+ export function getModelInstructionsPath(agentDir, modelId) {
25
+ return join(getInstructionsRoot(agentDir), modelInstructionFolderName(modelId), "AGENTS.md");
26
+ }
27
+ export function isUserInstructionsPath(filePath, agentDir) {
28
+ const rel = relative(resolve(getInstructionsRoot(agentDir)), resolve(filePath));
29
+ return rel === "" || (!isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${sep}`));
30
+ }
31
+ export function migrateLegacyGlobalInstructions(agentDir) {
32
+ const legacyPath = join(resolve(agentDir), "AGENTS.md");
33
+ const destinationPath = getGlobalInstructionsPath(agentDir);
34
+ if (!existsSync(legacyPath))
35
+ return { status: "absent" };
36
+ if (existsSync(destinationPath)) {
37
+ return {
38
+ status: "conflict",
39
+ diagnostic: `Both legacy '${legacyPath}' and current '${destinationPath}' exist. Kept both and will use the current file.`,
40
+ };
41
+ }
42
+ try {
43
+ mkdirSync(getInstructionsRoot(agentDir), { recursive: true });
44
+ renameSync(legacyPath, destinationPath);
45
+ return { status: "migrated" };
46
+ }
47
+ catch (error) {
48
+ return {
49
+ status: "failed",
50
+ diagnostic: `Could not migrate '${legacyPath}' to '${destinationPath}': ${error instanceof Error ? error.message : String(error)}`,
51
+ };
52
+ }
53
+ }
54
+ function concreteModelId(value) {
55
+ if (typeof value !== "string")
56
+ return undefined;
57
+ const trimmed = value.trim();
58
+ if (!trimmed || /[*?{}]/.test(trimmed))
59
+ return undefined;
60
+ return trimmed.replace(/:(?:off|minimal|low|medium|high|xhigh|max)$/i, "");
61
+ }
62
+ export function ensureModelInstructionDirs(agentDir, settings, currentModel) {
63
+ const modelIds = new Set();
64
+ const add = (value) => {
65
+ const id = concreteModelId(value);
66
+ if (id)
67
+ modelIds.add(id);
68
+ };
69
+ add(currentModel?.id);
70
+ add(settings.defaultModel);
71
+ for (const value of settings.enabledModels ?? [])
72
+ add(value);
73
+ add(settings.modelTiers?.light);
74
+ add(settings.modelTiers?.standard);
75
+ add(settings.modelTiers?.heavy);
76
+ const diagnostics = [];
77
+ for (const id of modelIds) {
78
+ try {
79
+ mkdirSync(join(getInstructionsRoot(agentDir), modelInstructionFolderName(id)), { recursive: true });
80
+ }
81
+ catch (error) {
82
+ diagnostics.push(`Could not prepare model-instruction folder for '${id}': ${error instanceof Error ? error.message : String(error)}`);
83
+ }
84
+ }
85
+ return diagnostics;
86
+ }
87
+ function safeReadInstruction(path) {
88
+ try {
89
+ if (!existsSync(path) || !statSync(path).isFile())
90
+ return undefined;
91
+ return { path, content: readFileSync(path, "utf8") };
92
+ }
93
+ catch {
94
+ return undefined;
95
+ }
96
+ }
97
+ export function loadSelectedUserInstructions(options) {
98
+ ensureModelInstructionDirs(options.agentDir, options.settingsManager.getGlobalSettings(), options.model);
99
+ const enabled = options.settingsManager.getModelInstructionsEnabled();
100
+ const mode = options.settingsManager.getModelInstructionsMode();
101
+ const files = [];
102
+ if (!enabled || mode === "both") {
103
+ const globalFile = safeReadInstruction(getGlobalInstructionsPath(options.agentDir));
104
+ if (globalFile)
105
+ files.push(globalFile);
106
+ }
107
+ if (enabled && options.model) {
108
+ const modelFile = safeReadInstruction(getModelInstructionsPath(options.agentDir, options.model.id));
109
+ if (modelFile)
110
+ files.push(modelFile);
111
+ }
112
+ return files;
113
+ }
114
+ export function instructionPathLabel(filePath, agentDir) {
115
+ const resolved = resolve(filePath);
116
+ if (resolved.toLowerCase() === resolve(getGlobalInstructionsPath(agentDir)).toLowerCase()) {
117
+ return "Global AGENTS.md";
118
+ }
119
+ if (!isUserInstructionsPath(resolved, agentDir))
120
+ return undefined;
121
+ return `${basename(resolve(resolved, ".."))} AGENTS.md`;
122
+ }
123
+ //# sourceMappingURL=model-instructions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-instructions.js","sourceRoot":"","sources":["../../src/core/model-instructions.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, renameSync, statSync } from \"node:fs\";\nimport { basename, isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport type { Model } from \"@ashx-j/lunr-ai\";\nimport type { Settings, SettingsManager } from \"./settings-manager.ts\";\n\nexport type ModelInstructionsMode = \"both\" | \"model-only\";\n\nexport interface ModelInstructionsMigrationResult {\n\tstatus: \"absent\" | \"migrated\" | \"conflict\" | \"failed\";\n\tdiagnostic?: string;\n}\n\nexport function getInstructionsRoot(agentDir: string): string {\n\treturn join(resolve(agentDir), \"agents\");\n}\n\nexport function getGlobalInstructionsPath(agentDir: string): string {\n\treturn join(getInstructionsRoot(agentDir), \"AGENTS.md\");\n}\n\nexport function modelInstructionFolderName(modelId: string): string {\n\tconst finalSegment = modelId.trim().split(/[\\\\/]/).filter(Boolean).at(-1) ?? \"model\";\n\tconst withoutThinking = finalSegment.replace(/:(?:off|minimal|low|medium|high|xhigh|max)$/i, \"\");\n\tlet safe = withoutThinking.replace(/[<>:\"/\\\\|?*\\u0000-\\u001f]/g, \"_\").replace(/[. ]+$/g, \"\");\n\tif (!safe || safe === \".\" || safe === \"..\") safe = \"model\";\n\tif (/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\\.|$)/i.test(safe)) safe = `_${safe}`;\n\tif (safe.length > 100 || safe.toLowerCase() !== withoutThinking.toLowerCase()) {\n\t\tconst digest = createHash(\"sha256\").update(withoutThinking.toLowerCase()).digest(\"hex\").slice(0, 8);\n\t\tsafe = `${safe.slice(0, 90)}-${digest}`;\n\t}\n\treturn safe.toLowerCase();\n}\n\nexport function getModelInstructionsPath(agentDir: string, modelId: string): string {\n\treturn join(getInstructionsRoot(agentDir), modelInstructionFolderName(modelId), \"AGENTS.md\");\n}\n\nexport function isUserInstructionsPath(filePath: string, agentDir: string): boolean {\n\tconst rel = relative(resolve(getInstructionsRoot(agentDir)), resolve(filePath));\n\treturn rel === \"\" || (!isAbsolute(rel) && rel !== \"..\" && !rel.startsWith(`..${sep}`));\n}\n\nexport function migrateLegacyGlobalInstructions(agentDir: string): ModelInstructionsMigrationResult {\n\tconst legacyPath = join(resolve(agentDir), \"AGENTS.md\");\n\tconst destinationPath = getGlobalInstructionsPath(agentDir);\n\tif (!existsSync(legacyPath)) return { status: \"absent\" };\n\tif (existsSync(destinationPath)) {\n\t\treturn {\n\t\t\tstatus: \"conflict\",\n\t\t\tdiagnostic: `Both legacy '${legacyPath}' and current '${destinationPath}' exist. Kept both and will use the current file.`,\n\t\t};\n\t}\n\ttry {\n\t\tmkdirSync(getInstructionsRoot(agentDir), { recursive: true });\n\t\trenameSync(legacyPath, destinationPath);\n\t\treturn { status: \"migrated\" };\n\t} catch (error) {\n\t\treturn {\n\t\t\tstatus: \"failed\",\n\t\t\tdiagnostic: `Could not migrate '${legacyPath}' to '${destinationPath}': ${error instanceof Error ? error.message : String(error)}`,\n\t\t};\n\t}\n}\n\nfunction concreteModelId(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") return undefined;\n\tconst trimmed = value.trim();\n\tif (!trimmed || /[*?{}]/.test(trimmed)) return undefined;\n\treturn trimmed.replace(/:(?:off|minimal|low|medium|high|xhigh|max)$/i, \"\");\n}\n\nexport function ensureModelInstructionDirs(\n\tagentDir: string,\n\tsettings: Pick<Settings, \"defaultModel\" | \"enabledModels\" | \"modelTiers\">,\n\tcurrentModel?: Pick<Model<any>, \"id\">,\n): string[] {\n\tconst modelIds = new Set<string>();\n\tconst add = (value: unknown) => {\n\t\tconst id = concreteModelId(value);\n\t\tif (id) modelIds.add(id);\n\t};\n\tadd(currentModel?.id);\n\tadd(settings.defaultModel);\n\tfor (const value of settings.enabledModels ?? []) add(value);\n\tadd(settings.modelTiers?.light);\n\tadd(settings.modelTiers?.standard);\n\tadd(settings.modelTiers?.heavy);\n\n\tconst diagnostics: string[] = [];\n\tfor (const id of modelIds) {\n\t\ttry {\n\t\t\tmkdirSync(join(getInstructionsRoot(agentDir), modelInstructionFolderName(id)), { recursive: true });\n\t\t} catch (error) {\n\t\t\tdiagnostics.push(\n\t\t\t\t`Could not prepare model-instruction folder for '${id}': ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t}\n\t}\n\treturn diagnostics;\n}\n\nfunction safeReadInstruction(path: string): { path: string; content: string } | undefined {\n\ttry {\n\t\tif (!existsSync(path) || !statSync(path).isFile()) return undefined;\n\t\treturn { path, content: readFileSync(path, \"utf8\") };\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport function loadSelectedUserInstructions(options: {\n\tagentDir: string;\n\tsettingsManager: SettingsManager;\n\tmodel?: Pick<Model<any>, \"id\">;\n}): Array<{ path: string; content: string }> {\n\tensureModelInstructionDirs(options.agentDir, options.settingsManager.getGlobalSettings(), options.model);\n\tconst enabled = options.settingsManager.getModelInstructionsEnabled();\n\tconst mode = options.settingsManager.getModelInstructionsMode();\n\tconst files: Array<{ path: string; content: string }> = [];\n\tif (!enabled || mode === \"both\") {\n\t\tconst globalFile = safeReadInstruction(getGlobalInstructionsPath(options.agentDir));\n\t\tif (globalFile) files.push(globalFile);\n\t}\n\tif (enabled && options.model) {\n\t\tconst modelFile = safeReadInstruction(getModelInstructionsPath(options.agentDir, options.model.id));\n\t\tif (modelFile) files.push(modelFile);\n\t}\n\treturn files;\n}\n\nexport function instructionPathLabel(filePath: string, agentDir: string): string | undefined {\n\tconst resolved = resolve(filePath);\n\tif (resolved.toLowerCase() === resolve(getGlobalInstructionsPath(agentDir)).toLowerCase()) {\n\t\treturn \"Global AGENTS.md\";\n\t}\n\tif (!isUserInstructionsPath(resolved, agentDir)) return undefined;\n\treturn `${basename(resolve(resolved, \"..\"))} AGENTS.md`;\n}\n"]}
@@ -60,6 +60,8 @@ export declare class ModelRuntime implements Models {
60
60
  private lastOfficial;
61
61
  private catalogRefresh;
62
62
  private catalogRefreshAllowsNetwork;
63
+ private nextAutomaticRefresh;
64
+ private automaticRefreshIdentity;
63
65
  private readonly grokHome;
64
66
  private constructor();
65
67
  static create(options?: CreateModelRuntimeOptions): Promise<ModelRuntime>;
@@ -73,6 +75,7 @@ export declare class ModelRuntime implements Models {
73
75
  private bakedInModels;
74
76
  private rebuildProviders;
75
77
  private updateModelSnapshot;
78
+ private isCatalogModelAvailable;
76
79
  private listStoredProviderIds;
77
80
  /** `/model` lists stored creds + models.json/extension keys, never ambient envApiKeyAuth. */
78
81
  private configuredProviderIds;
@@ -122,6 +125,11 @@ export declare class ModelRuntime implements Models {
122
125
  refresh(options?: ModelsRefreshOptions): Promise<CatalogRefreshResult>;
123
126
  private executeCatalogRefresh;
124
127
  getLastCatalogRefresh(): CatalogRefreshResult;
128
+ /** Called after first paint and by the picker; explicit /refresh always bypasses this gate. */
129
+ refreshIfStale(options?: {
130
+ signal?: AbortSignal;
131
+ }): Promise<CatalogRefreshResult>;
132
+ private scopeFor;
125
133
  persistUserModels(rows: readonly UserModelEntry[]): void;
126
134
  private refreshOfficialCatalog;
127
135
  private restoreLiveCatalogs;
@@ -1 +1 @@
1
- {"version":3,"file":"model-runtime.d.ts","sourceRoot":"","sources":["../../src/core/model-runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import { dirname, join } from \"node:path\";\nimport {\n\ttype Api,\n\ttype ApiStreamOptions,\n\ttype AssistantMessage,\n\ttype AssistantMessageEventStream,\n\ttype AuthCheck,\n\ttype AuthInteraction,\n\ttype AuthResult,\n\ttype AuthType,\n\ttype Context,\n\ttype Credential,\n\ttype CredentialInfo,\n\ttype CredentialStore,\n\tcreateModels,\n\tlazyStream,\n\ttype Model,\n\ttype Models,\n\ttype ModelsApiStreamOptions,\n\tModelsError,\n\ttype ModelsRefreshOptions,\n\ttype ModelsRefreshResult,\n\ttype ModelsSimpleStreamOptions,\n\ttype ModelsStore,\n\ttype ModelsStreamTransforms,\n\ttype MutableModels,\n\ttype Provider,\n\ttype ProviderHeaders,\n\ttype SimpleStreamOptions,\n\ttype StreamOptions,\n} from \"@ashx-j/lunr-ai\";\nimport * as builtinProviderCatalog from \"@ashx-j/lunr-ai/providers/all\";\nimport { getAgentDir } from \"../config.ts\";\nimport { AuthStorage as DefaultAuthStorage } from \"./auth-storage.ts\";\nimport { CatalogOverlaySource, knownModelKeys, withCatalogOverlay } from \"./catalog-merge.ts\";\nimport { readGrokCliXaiOAuth, wrapXaiGrokCliCredentials } from \"./grok-cli-auth.ts\";\nimport {\n\tbuildLiveOverlay,\n\tfetchLiveProviderModels,\n\ttype IncompleteLiveModel,\n\tLIVE_LIST_PROVIDER_IDS,\n\ttype LiveCatalogResult,\n} from \"./live-catalog.ts\";\nimport { ModelConfig } from \"./model-config.ts\";\nimport { FileModelsStore, InMemoryCodingAgentModelsStore } from \"./models-store.ts\";\nimport {\n\tloadOfficialCatalog,\n\ttype OfficialCatalogLoadResult,\n\tofficialCatalogCachePath,\n\tofficialEntryToModel,\n\tofficialModelKeys,\n\tpreferOfficialCatalog,\n} from \"./official-catalog.ts\";\nimport {\n\ttype AuthStatus,\n\ttype CompatibilityRequestConfig,\n\tcomposeModelProvider,\n\tconfiguredRequestAuthStatus,\n\ttype ProviderConfigInput,\n\tresolveCompatibilityRequestConfig,\n\tresolveConfiguredModelHeaders,\n\tvalidateExtensionProvider,\n} from \"./provider-composer.ts\";\nimport { RuntimeCredentials } from \"./runtime-credentials.ts\";\nimport { SubscriptionManager } from \"./subscriptions.ts\";\nimport { clearPlanUsageCache } from \"./usage-service.ts\";\nimport { type UserModelEntry, UserModelsStore, userEntryToModel, userModelsPath } from \"./user-models.ts\";\n\ninterface ModelRuntimeSnapshot {\n\tall: readonly Model<Api>[];\n\tavailable: readonly Model<Api>[];\n\tconfiguredProviders: ReadonlySet<string>;\n\tstoredProviders: ReadonlySet<string>;\n\tauth: ReadonlyMap<string, AuthCheck | undefined>;\n}\n\nexport interface CreateModelRuntimeOptions {\n\t/** Credential storage. Defaults to the file at authPath. */\n\tcredentials?: CredentialStore;\n\tauthPath?: string;\n\tmodelsPath?: string | null;\n\tmodelsStore?: ModelsStore;\n\tmodelsStorePath?: string;\n\tallowModelNetwork?: boolean;\n\t/** Applied by callers of `refresh()`, not by `create()` (startup is cache-only). */\n\tmodelRefreshTimeoutMs?: number;\n\t/** Unused. Kept so older callers compiling against this options bag still typecheck. */\n\tcatalogBaseUrl?: string;\n\t/** lunr: subscription-key pool. Defaults to subscriptions.json next to authPath (in-memory for custom stores). */\n\tsubscriptions?: SubscriptionManager;\n\t/**\n\t * Grok CLI home (`~/.grok`) used as a SuperGrok OAuth fallback.\n\t * `null` disables the fallback. Custom `credentials` default to disabled\n\t * unless this is an explicit path.\n\t */\n\tgrokHome?: string | null;\n}\n\nexport interface CatalogRefreshResult extends ModelsRefreshResult {\n\tofficial?: OfficialCatalogLoadResult & { evictedUserRows: number };\n\tlive: LiveCatalogResult[];\n\tincomplete: IncompleteLiveModel[];\n}\n\nexport interface ModelRuntimeAuthOverrides {\n\tapiKey?: string;\n\tenv?: Record<string, string>;\n}\n\nfunction mergeHeaders(\n\tbase: ProviderHeaders | undefined,\n\toverride: ProviderHeaders | undefined,\n): ProviderHeaders | undefined {\n\tif (!base && !override) return undefined;\n\tconst merged = { ...base };\n\tfor (const [name, value] of Object.entries(override ?? {})) {\n\t\tconst lowerName = name.toLowerCase();\n\t\tfor (const existingName of Object.keys(merged)) {\n\t\t\tif (existingName.toLowerCase() === lowerName) delete merged[existingName];\n\t\t}\n\t\tmerged[name] = value;\n\t}\n\treturn merged;\n}\n\n/** Configured pi-ai Models collection used by coding-agent and SDK consumers. */\nexport class ModelRuntime implements Models {\n\tprivate readonly models: MutableModels;\n\tprivate readonly credentials: RuntimeCredentials;\n\t/** lunr: per-provider subscription-key pools; rotation mirrors the active key into the credential store. */\n\treadonly subscriptionManager: SubscriptionManager;\n\tprivate readonly defaultBuiltins: ReadonlyMap<string, Provider>;\n\tprivate readonly builtins = new Map<string, Provider>();\n\tprivate readonly extensionProviders = new Map<string, ProviderConfigInput>();\n\tprivate readonly compositionErrors = new Map<string, string>();\n\tprivate readonly modelsPath: string | undefined;\n\tprivate readonly allowModelNetwork: boolean;\n\tprivate config: ModelConfig;\n\tprivate snapshot: ModelRuntimeSnapshot = {\n\t\tall: [],\n\t\tavailable: [],\n\t\tconfiguredProviders: new Set(),\n\t\tstoredProviders: new Set(),\n\t\tauth: new Map(),\n\t};\n\tprivate availabilityRefresh: Promise<void> | undefined;\n\tprivate availabilityError: string | undefined;\n\tprivate readonly modelsStore: ModelsStore;\n\tprivate readonly overlay = new CatalogOverlaySource();\n\tprivate readonly userModels: UserModelsStore;\n\tprivate readonly officialCachePath: string | undefined;\n\tprivate lastCatalogRefresh: CatalogRefreshResult = { aborted: false, errors: new Map(), live: [], incomplete: [] };\n\tprivate lastOfficial: OfficialCatalogLoadResult | undefined;\n\tprivate catalogRefresh: Promise<CatalogRefreshResult> | undefined;\n\tprivate catalogRefreshAllowsNetwork = false;\n\tprivate readonly grokHome: string | undefined;\n\n\tprivate constructor(\n\t\tcredentials: RuntimeCredentials,\n\t\tconfig: ModelConfig,\n\t\tmodelsPath: string | undefined,\n\t\tmodelsStore: ModelsStore,\n\t\tproviders: readonly Provider[],\n\t\tallowModelNetwork: boolean,\n\t\tsubscriptionManager: SubscriptionManager,\n\t\tcatalogDir: string | undefined,\n\t\tgrokHome: string | undefined,\n\t) {\n\t\tthis.credentials = credentials;\n\t\tthis.config = config;\n\t\tthis.modelsPath = modelsPath;\n\t\tthis.modelsStore = modelsStore;\n\t\tthis.allowModelNetwork = allowModelNetwork;\n\t\tthis.subscriptionManager = subscriptionManager;\n\t\tthis.userModels = new UserModelsStore(catalogDir ? userModelsPath(catalogDir) : undefined);\n\t\tthis.officialCachePath = catalogDir ? officialCatalogCachePath(catalogDir) : undefined;\n\t\tthis.grokHome = grokHome;\n\t\tthis.defaultBuiltins = new Map(providers.map((provider) => [provider.id, provider]));\n\t\tfor (const [providerId, provider] of this.defaultBuiltins) this.builtins.set(providerId, provider);\n\t\tthis.models = createModels({ credentials, modelsStore });\n\t\tthis.rebuildProviders();\n\t}\n\n\tstatic async create(options: CreateModelRuntimeOptions = {}): Promise<ModelRuntime> {\n\t\treturn ModelRuntime.createInternal(options, true);\n\t}\n\n\t/** @internal Agent-session hydration registers extension providers before the first refresh. */\n\tstatic async createForAgentSession(options: CreateModelRuntimeOptions = {}): Promise<ModelRuntime> {\n\t\treturn ModelRuntime.createInternal(options, false);\n\t}\n\n\tprivate static async createInternal(\n\t\toptions: CreateModelRuntimeOptions,\n\t\trefreshOnCreate: boolean,\n\t): Promise<ModelRuntime> {\n\t\t// lunr: keep the underlying store — the SubscriptionManager mirrors rotated\n\t\t// keys into it directly, NOT through the RuntimeCredentials overlay (a runtime\n\t\t// --api-key override would shadow the mirror there).\n\t\tconst rawStore = options.credentials ?? DefaultAuthStorage.create(options.authPath);\n\t\tconst grokHomeDisabled =\n\t\t\toptions.grokHome === null || (options.grokHome === undefined && options.credentials !== undefined);\n\t\tconst grokHome = grokHomeDisabled ? undefined : (options.grokHome ?? undefined);\n\t\tconst baseStore = grokHomeDisabled ? rawStore : wrapXaiGrokCliCredentials(rawStore, { grokHome });\n\t\tconst credentials = new RuntimeCredentials(baseStore);\n\t\t// Pool persistence: subscriptions.json next to auth.json. A custom credential\n\t\t// store without an authPath (tests, SDK embeddings) gets a process-local pool\n\t\t// so nothing leaks into the real agent dir.\n\t\tconst subscriptionManager =\n\t\t\toptions.subscriptions ??\n\t\t\t(options.credentials && !options.authPath\n\t\t\t\t? SubscriptionManager.inMemory(baseStore)\n\t\t\t\t: SubscriptionManager.create(\n\t\t\t\t\t\tbaseStore,\n\t\t\t\t\t\toptions.authPath ? join(dirname(options.authPath), \"subscriptions.json\") : undefined,\n\t\t\t\t\t));\n\t\tconst modelsPath =\n\t\t\toptions.modelsPath === null ? undefined : (options.modelsPath ?? join(getAgentDir(), \"models.json\"));\n\t\tconst config = await ModelConfig.load(modelsPath);\n\t\tconst modelsStore =\n\t\t\toptions.modelsStore ??\n\t\t\t(modelsPath\n\t\t\t\t? new FileModelsStore(options.modelsStorePath ?? join(dirname(modelsPath), \"models-store.json\"))\n\t\t\t\t: new InMemoryCodingAgentModelsStore());\n\t\tconst providers = builtinProviderCatalog.builtinProviders();\n\t\tconst catalogDir = modelsPath ? dirname(modelsPath) : options.authPath ? dirname(options.authPath) : undefined;\n\t\tconst runtime = new ModelRuntime(\n\t\t\tcredentials,\n\t\t\tconfig,\n\t\t\tmodelsPath,\n\t\t\tmodelsStore,\n\t\t\tproviders,\n\t\t\toptions.allowModelNetwork ?? process.env.PI_OFFLINE === undefined,\n\t\t\tsubscriptionManager,\n\t\t\tcatalogDir,\n\t\t\tgrokHome,\n\t\t);\n\t\truntime.configureRadiusProviders();\n\t\truntime.rebuildProviders();\n\t\t// lunr: create() must not wait on GitHub / provider /v1/models / a dead NIC.\n\t\t// Disk cache + baked-in + bundled official overlay are enough to start;\n\t\t// /refresh and the model picker still do a live refresh.\n\t\tif (refreshOnCreate) {\n\t\t\tawait runtime.refresh({ allowNetwork: false });\n\t\t}\n\t\treturn runtime;\n\t}\n\n\tprivate configureRadiusProviders(): void {\n\t\tthis.builtins.clear();\n\t\tfor (const [providerId, provider] of this.defaultBuiltins) this.builtins.set(providerId, provider);\n\t\tfor (const providerId of this.config.getProviderIds()) {\n\t\t\tconst config = this.config.getProvider(providerId);\n\t\t\tif (config?.oauth !== \"radius\" || !config.baseUrl) continue;\n\t\t\tthis.builtins.set(\n\t\t\t\tproviderId,\n\t\t\t\tbuiltinProviderCatalog.radiusProvider({\n\t\t\t\t\tid: providerId,\n\t\t\t\t\tname: config.name ?? providerId,\n\t\t\t\t\tgateway: config.baseUrl.replace(/\\/v1\\/?$/u, \"\"),\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate providerIds(): Set<string> {\n\t\treturn new Set([...this.builtins.keys(), ...this.config.getProviderIds(), ...this.extensionProviders.keys()]);\n\t}\n\n\tprivate recomposeProvider(providerId: string): void {\n\t\tconst base = this.builtins.get(providerId);\n\t\tconst extension = this.extensionProviders.get(providerId);\n\t\tif (!base && !this.config.getProvider(providerId) && !extension) {\n\t\t\tthis.models.deleteProvider(providerId);\n\t\t\tthis.compositionErrors.delete(providerId);\n\t\t\treturn;\n\t\t}\n\t\tif (base && !this.config.getProvider(providerId) && !extension) {\n\t\t\t// No models.json/extension overlays: keep builtin auth/stream exact, then apply catalog layers.\n\t\t\tthis.models.setProvider(this.withLocalCatalog(base));\n\t\t\tthis.compositionErrors.delete(providerId);\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tthis.models.setProvider(this.withLocalCatalog(composeModelProvider(providerId, base, this.config, extension)));\n\t\t\tthis.compositionErrors.delete(providerId);\n\t\t} catch (error) {\n\t\t\tthis.compositionErrors.set(providerId, error instanceof Error ? error.message : String(error));\n\t\t\tif (base) this.models.setProvider(this.withLocalCatalog(base));\n\t\t\telse this.models.deleteProvider(providerId);\n\t\t}\n\t}\n\n\tprivate withLocalCatalog(provider: Provider): Provider {\n\t\treturn withCatalogOverlay(provider, this.overlay);\n\t}\n\n\tprivate bakedInModels(providerId: string): Model<Api>[] {\n\t\treturn [...(this.defaultBuiltins.get(providerId)?.getModels() ?? [])];\n\t}\n\n\tprivate rebuildProviders(): void {\n\t\tthis.models.clearProviders();\n\t\tthis.compositionErrors.clear();\n\t\tfor (const providerId of this.providerIds()) this.recomposeProvider(providerId);\n\t\tthis.updateModelSnapshot();\n\t}\n\n\tprivate updateModelSnapshot(): void {\n\t\tconst all = [...this.models.getModels()];\n\t\tthis.snapshot = {\n\t\t\t...this.snapshot,\n\t\t\tall,\n\t\t\tavailable: all.filter((model) => this.snapshot.configuredProviders.has(model.provider)),\n\t\t};\n\t}\n\n\tprivate async listStoredProviderIds(): Promise<Set<string>> {\n\t\tconst credentials = await this.credentials.list();\n\t\treturn new Set(credentials.map((entry) => entry.providerId));\n\t}\n\n\t/** `/model` lists stored creds + models.json/extension keys, never ambient envApiKeyAuth. */\n\tprivate configuredProviderIds(storedProviders: ReadonlySet<string>): Set<string> {\n\t\tconst configured = new Set(storedProviders);\n\t\tfor (const provider of this.models.getProviders()) {\n\t\t\tif (\n\t\t\t\tconfiguredRequestAuthStatus(this.config.getProvider(provider.id), this.extensionProviders.get(provider.id))\n\t\t\t\t\t?.configured\n\t\t\t) {\n\t\t\t\tconfigured.add(provider.id);\n\t\t\t}\n\t\t}\n\t\treturn configured;\n\t}\n\n\tprivate userModelsForStoredProviders(storedProviders: ReadonlySet<string>): Model<Api>[] {\n\t\treturn this.userModels\n\t\t\t.list()\n\t\t\t.filter((row) => storedProviders.has(row.provider))\n\t\t\t.map(userEntryToModel);\n\t}\n\n\tprivate async runAvailabilityRefresh(): Promise<void> {\n\t\tconst providers = this.models.getProviders();\n\t\tconst [available, checks, storedProviders] = await Promise.all([\n\t\t\tthis.models.getAvailable(),\n\t\t\tPromise.all(\n\t\t\t\tproviders.map(\n\t\t\t\t\tasync (provider): Promise<[string, AuthCheck | undefined]> => [\n\t\t\t\t\t\tprovider.id,\n\t\t\t\t\t\tawait this.models.checkAuth(provider.id),\n\t\t\t\t\t],\n\t\t\t\t),\n\t\t\t),\n\t\t\tthis.listStoredProviderIds(),\n\t\t]);\n\t\tconst auth = new Map(checks);\n\t\tconst configuredProviders = this.configuredProviderIds(storedProviders);\n\t\tthis.snapshot = {\n\t\t\tall: [...this.models.getModels()],\n\t\t\tavailable: available.filter((model) => configuredProviders.has(model.provider)),\n\t\t\tconfiguredProviders,\n\t\t\tstoredProviders,\n\t\t\tauth,\n\t\t};\n\t\tthis.availabilityError = undefined;\n\t}\n\n\tprivate queueAvailabilityRefresh(after: Promise<void> | undefined): Promise<void> {\n\t\tconst refresh = (after ?? Promise.resolve()).catch(() => {}).then(() => this.runAvailabilityRefresh());\n\t\tconst recorded = refresh.catch((error) => {\n\t\t\tthis.availabilityError = error instanceof Error ? error.message : String(error);\n\t\t\tthrow error;\n\t\t});\n\t\tconst tracked = recorded.finally(() => {\n\t\t\tif (this.availabilityRefresh === tracked) this.availabilityRefresh = undefined;\n\t\t});\n\t\tthis.availabilityRefresh = tracked;\n\t\treturn tracked;\n\t}\n\n\t/** Coalesce concurrent readers onto the pending refresh. */\n\tprivate refreshAvailability(): Promise<void> {\n\t\treturn this.availabilityRefresh ?? this.queueAvailabilityRefresh(undefined);\n\t}\n\n\t/** Mutations must not observe an in-flight refresh started before them. */\n\tprivate forceRefreshAvailability(): Promise<void> {\n\t\treturn this.queueAvailabilityRefresh(this.availabilityRefresh);\n\t}\n\n\tgetProviders(): readonly Provider[] {\n\t\treturn this.models.getProviders();\n\t}\n\n\tgetProvider(providerId: string): Provider | undefined {\n\t\treturn this.models.getProvider(providerId);\n\t}\n\n\tgetModels(providerId?: string): readonly Model<Api>[] {\n\t\treturn this.models.getModels(providerId);\n\t}\n\n\tgetModel(providerId: string, modelId: string): Model<Api> | undefined {\n\t\treturn this.models.getModel(providerId, modelId);\n\t}\n\n\tasync checkAuth(providerId: string): Promise<AuthCheck | undefined> {\n\t\treturn this.models.checkAuth(providerId);\n\t}\n\n\tasync getAvailable(providerId?: string): Promise<readonly Model<Api>[]> {\n\t\tif (providerId) {\n\t\t\tif (this.availabilityRefresh) {\n\t\t\t\tawait this.availabilityRefresh;\n\t\t\t\treturn this.snapshot.available.filter((model) => model.provider === providerId);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn await this.models.getAvailable(providerId);\n\t\t\t} catch (error) {\n\t\t\t\tthis.availabilityError = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t\tawait this.refreshAvailability();\n\t\treturn this.snapshot.available;\n\t}\n\n\tgetAvailableSnapshot(): readonly Model<Api>[] {\n\t\treturn this.snapshot.available;\n\t}\n\n\tgetError(): string | undefined {\n\t\tconst errors: string[] = [];\n\t\tconst configError = this.config.getError();\n\t\tif (configError) errors.push(configError);\n\t\tfor (const [providerId, error] of this.compositionErrors) {\n\t\t\terrors.push(`Provider \"${providerId}\": ${error}`);\n\t\t}\n\t\tif (this.availabilityError) errors.push(`Availability refresh: ${this.availabilityError}`);\n\t\treturn errors.length > 0 ? errors.join(\"\\n\\n\") : undefined;\n\t}\n\n\tgetRegisteredProviderConfig(providerId: string): ProviderConfigInput | undefined {\n\t\treturn this.extensionProviders.get(providerId);\n\t}\n\n\tgetRegisteredProviderIds(): readonly string[] {\n\t\treturn [...this.extensionProviders.keys()];\n\t}\n\n\t/** @internal Compatibility fallback for ModelRegistry when provider auth is unconfigured. */\n\tgetCompatibilityRequestConfig(model: Model<Api>): CompatibilityRequestConfig {\n\t\treturn resolveCompatibilityRequestConfig(\n\t\t\tmodel,\n\t\t\tthis.config.getProvider(model.provider),\n\t\t\tthis.extensionProviders.get(model.provider),\n\t\t);\n\t}\n\n\tisUsingOAuth(providerId: string): boolean {\n\t\treturn this.snapshot.auth.get(providerId)?.type === \"oauth\";\n\t}\n\n\thasConfiguredAuth(providerId: string): boolean {\n\t\treturn this.snapshot.configuredProviders.has(providerId);\n\t}\n\n\tgetAuth(providerId: string, overrides?: ModelRuntimeAuthOverrides): Promise<AuthResult | undefined>;\n\tgetAuth(model: Model<Api>, overrides?: ModelRuntimeAuthOverrides): Promise<AuthResult | undefined>;\n\tasync getAuth(\n\t\tproviderOrModel: string | Model<Api>,\n\t\toverrides: ModelRuntimeAuthOverrides = {},\n\t): Promise<AuthResult | undefined> {\n\t\tif (typeof providerOrModel === \"string\") return this.models.getAuth(providerOrModel, overrides);\n\t\tconst resolution = await this.models.getAuth(providerOrModel, overrides);\n\t\tif (!resolution) return undefined;\n\t\tconst configuredHeaders = resolveConfiguredModelHeaders(\n\t\t\tproviderOrModel,\n\t\t\tthis.config.getProvider(providerOrModel.provider),\n\t\t\tthis.extensionProviders.get(providerOrModel.provider),\n\t\t\t{ ...(resolution.env ?? {}), ...(overrides.env ?? {}) },\n\t\t);\n\t\treturn {\n\t\t\t...resolution,\n\t\t\tauth: {\n\t\t\t\t...resolution.auth,\n\t\t\t\theaders: mergeHeaders(resolution.auth.headers, configuredHeaders),\n\t\t\t},\n\t\t};\n\t}\n\n\t/** lunr: whether a runtime --api-key override is active for the provider (it shadows auth.json). */\n\thasRuntimeApiKey(providerId: string): boolean {\n\t\treturn this.credentials.hasRuntimeApiKey(providerId);\n\t}\n\n\tasync setRuntimeApiKey(providerId: string, apiKey: string, options?: { allowNetwork?: boolean }): Promise<void> {\n\t\tthis.credentials.setRuntimeApiKey(providerId, apiKey);\n\t\tconst auth = new Map(this.snapshot.auth).set(providerId, { type: \"api_key\", source: \"runtime API key\" });\n\t\tconst configuredProviders = new Set(this.snapshot.configuredProviders).add(providerId);\n\t\tconst storedProviders = new Set(this.snapshot.storedProviders).add(providerId);\n\t\tthis.snapshot = {\n\t\t\t...this.snapshot,\n\t\t\tauth,\n\t\t\tconfiguredProviders,\n\t\t\tstoredProviders,\n\t\t\tavailable: this.snapshot.all.filter((model) => configuredProviders.has(model.provider)),\n\t\t};\n\t\tawait this.refresh({ allowNetwork: options?.allowNetwork ?? this.allowModelNetwork });\n\t}\n\n\tasync removeRuntimeApiKey(providerId: string): Promise<void> {\n\t\tthis.credentials.removeRuntimeApiKey(providerId);\n\t\tawait this.refresh({ allowNetwork: this.allowModelNetwork });\n\t}\n\n\t/** lunr: whether network model-catalog fetches are allowed (offline mode disables them). */\n\tisNetworkAllowed(): boolean {\n\t\treturn this.allowModelNetwork;\n\t}\n\n\tlistCredentials(): Promise<readonly CredentialInfo[]> {\n\t\treturn this.credentials.list();\n\t}\n\n\tgetProviderAuthStatus(providerId: string): AuthStatus {\n\t\tif (this.credentials.hasRuntimeApiKey(providerId)) return { configured: true, source: \"runtime\" };\n\t\tif (this.snapshot.storedProviders.has(providerId)) return { configured: true, source: \"stored\" };\n\t\tconst configured = configuredRequestAuthStatus(\n\t\t\tthis.config.getProvider(providerId),\n\t\t\tthis.extensionProviders.get(providerId),\n\t\t);\n\t\tif (configured) return configured;\n\t\tconst check = this.snapshot.auth.get(providerId);\n\t\treturn check ? { configured: true, source: \"environment\", label: check.source } : { configured: false };\n\t}\n\n\tprivate async prepareRequest(\n\t\tmodel: Model<Api>,\n\t\toptions: (StreamOptions & ModelsStreamTransforms) | undefined,\n\t): Promise<{ provider: Provider; model: Model<Api>; options: StreamOptions }> {\n\t\tconst provider = this.models.getProvider(model.provider);\n\t\tif (!provider) throw new ModelsError(\"provider\", `Unknown provider: ${model.provider}`);\n\t\tconst resolution = await this.getAuth(model, { apiKey: options?.apiKey, env: options?.env });\n\t\tif (!resolution) throw new ModelsError(\"auth\", `Provider is not configured: ${model.provider}`);\n\n\t\tconst { transformHeaders, ...providerOptions } = options ?? {};\n\t\tlet headers = mergeHeaders(resolution.auth.headers, providerOptions.headers);\n\t\tif (transformHeaders) headers = await transformHeaders(headers ?? {});\n\t\tconst env =\n\t\t\tresolution.env || providerOptions.env\n\t\t\t\t? { ...(resolution.env ?? {}), ...(providerOptions.env ?? {}) }\n\t\t\t\t: undefined;\n\t\treturn {\n\t\t\tprovider,\n\t\t\tmodel: resolution.auth.baseUrl ? { ...model, baseUrl: resolution.auth.baseUrl } : model,\n\t\t\toptions: {\n\t\t\t\t...providerOptions,\n\t\t\t\tapiKey: providerOptions.apiKey ?? resolution.auth.apiKey,\n\t\t\t\theaders,\n\t\t\t\tenv,\n\t\t\t},\n\t\t};\n\t}\n\n\tstream<TApi extends Api>(\n\t\tmodel: Model<TApi>,\n\t\tcontext: Context,\n\t\toptions?: ModelsApiStreamOptions<TApi>,\n\t): AssistantMessageEventStream {\n\t\treturn lazyStream(model, async () => {\n\t\t\tconst prepared = await this.prepareRequest(\n\t\t\t\tmodel,\n\t\t\t\toptions as (StreamOptions & ModelsStreamTransforms) | undefined,\n\t\t\t);\n\t\t\treturn prepared.provider.stream(\n\t\t\t\tprepared.model as Model<TApi>,\n\t\t\t\tcontext,\n\t\t\t\tprepared.options as ApiStreamOptions<TApi>,\n\t\t\t);\n\t\t});\n\t}\n\n\tcomplete<TApi extends Api>(\n\t\tmodel: Model<TApi>,\n\t\tcontext: Context,\n\t\toptions?: ModelsApiStreamOptions<TApi>,\n\t): Promise<AssistantMessage> {\n\t\treturn this.stream(model, context, options).result();\n\t}\n\n\tstreamSimple(model: Model<Api>, context: Context, options?: ModelsSimpleStreamOptions): AssistantMessageEventStream {\n\t\treturn lazyStream(model, async () => {\n\t\t\tconst prepared = await this.prepareRequest(model, options);\n\t\t\treturn prepared.provider.streamSimple(prepared.model, context, prepared.options as SimpleStreamOptions);\n\t\t});\n\t}\n\n\tcompleteSimple(model: Model<Api>, context: Context, options?: ModelsSimpleStreamOptions): Promise<AssistantMessage> {\n\t\treturn this.streamSimple(model, context, options).result();\n\t}\n\n\tasync login(providerId: string, type: AuthType, interaction: AuthInteraction): Promise<Credential> {\n\t\tconst credential = await this.models.login(providerId, type, interaction);\n\t\tclearPlanUsageCache();\n\t\tawait this.refresh({ allowNetwork: this.allowModelNetwork });\n\t\treturn credential;\n\t}\n\n\tasync logout(providerId: string): Promise<void> {\n\t\tawait this.models.logout(providerId);\n\t\tawait this.modelsStore.delete(providerId);\n\t\tthis.userModels.evictProvider(providerId);\n\t\tthis.overlay.setLive(providerId, []);\n\t\t// Reset credential-dependent compatibility projections before the unconfigured provider is skipped by refresh.\n\t\tthis.recomposeProvider(providerId);\n\t\tclearPlanUsageCache();\n\t\tawait this.refresh({ allowNetwork: this.allowModelNetwork });\n\t}\n\n\t/** Copy a live Grok CLI SuperGrok session into lunR `auth.json`. */\n\tasync importGrokCliXaiAuth(): Promise<Credential | undefined> {\n\t\tconst grok = readGrokCliXaiOAuth({ grokHome: this.grokHome });\n\t\tif (!grok) return undefined;\n\t\tconst stored = await this.credentials.modify(\"xai\", async () => grok);\n\t\tclearPlanUsageCache();\n\t\tawait this.refresh({ allowNetwork: this.allowModelNetwork });\n\t\treturn stored;\n\t}\n\n\tasync reloadConfig(): Promise<void> {\n\t\tthis.config = await ModelConfig.load(this.modelsPath);\n\t\tthis.configureRadiusProviders();\n\t\tthis.rebuildProviders();\n\t\tawait this.refresh({ allowNetwork: this.allowModelNetwork });\n\t}\n\n\tasync refresh(options: ModelsRefreshOptions = {}): Promise<CatalogRefreshResult> {\n\t\tconst allowNetwork = options.allowNetwork ?? this.allowModelNetwork;\n\t\tif (this.catalogRefresh) {\n\t\t\t// Join an in-flight refresh that is at least as capable. A cache-only\n\t\t\t// flight must not swallow a later network refresh.\n\t\t\tif (this.catalogRefreshAllowsNetwork || !allowNetwork) {\n\t\t\t\treturn this.catalogRefresh;\n\t\t\t}\n\t\t\tawait this.catalogRefresh.catch(() => {});\n\t\t\treturn this.refresh({ ...options, allowNetwork });\n\t\t}\n\n\t\tconst run = this.executeCatalogRefresh({ ...options, allowNetwork }).finally(() => {\n\t\t\tif (this.catalogRefresh === run) {\n\t\t\t\tthis.catalogRefresh = undefined;\n\t\t\t\tthis.catalogRefreshAllowsNetwork = false;\n\t\t\t}\n\t\t});\n\t\tthis.catalogRefresh = run;\n\t\tthis.catalogRefreshAllowsNetwork = allowNetwork;\n\t\treturn run;\n\t}\n\n\tprivate async executeCatalogRefresh(\n\t\trefreshOptions: ModelsRefreshOptions & { allowNetwork: boolean },\n\t): Promise<CatalogRefreshResult> {\n\t\tconst official = await this.refreshOfficialCatalog(refreshOptions.allowNetwork, refreshOptions.signal);\n\t\tconst live = refreshOptions.allowNetwork\n\t\t\t? await this.refreshLiveCatalogs(\n\t\t\t\t\tofficial ? officialModelKeys(official.catalog) : new Set(),\n\t\t\t\t\trefreshOptions.signal,\n\t\t\t\t)\n\t\t\t: await this.restoreLiveCatalogs();\n\n\t\t// Radius / local-provider / extension refreshModels hooks. Live-list\n\t\t// providers have no refreshModels, so this does not hit pi.dev.\n\t\tconst result = ((await this.models.refresh(refreshOptions)) as ModelsRefreshResult | undefined) ?? {\n\t\t\taborted: refreshOptions.signal?.aborted ?? false,\n\t\t\terrors: new Map(),\n\t\t};\n\t\tconst errors = new Map(result.errors);\n\t\tfor (const entry of live) {\n\t\t\tif (entry.status === \"error\" || entry.status === \"timeout\") {\n\t\t\t\terrors.set(entry.providerId, new Error(entry.error ?? entry.status));\n\t\t\t}\n\t\t}\n\t\tthis.updateModelSnapshot();\n\t\ttry {\n\t\t\tawait this.forceRefreshAvailability();\n\t\t} catch {\n\t\t\t// Availability errors are recorded by forceRefreshAvailability; refreshed models remain usable.\n\t\t}\n\t\tconst catalogResult: CatalogRefreshResult = {\n\t\t\taborted: result.aborted,\n\t\t\terrors,\n\t\t\tofficial,\n\t\t\tlive,\n\t\t\tincomplete: live.flatMap((entry) => entry.incomplete),\n\t\t};\n\t\tthis.lastCatalogRefresh = catalogResult;\n\t\treturn catalogResult;\n\t}\n\n\tgetLastCatalogRefresh(): CatalogRefreshResult {\n\t\treturn this.lastCatalogRefresh;\n\t}\n\n\tpersistUserModels(rows: readonly UserModelEntry[]): void {\n\t\tthis.userModels.upsert(rows);\n\t\tthis.overlay.setUser(this.userModelsForStoredProviders(this.snapshot.storedProviders));\n\t\tthis.updateModelSnapshot();\n\t}\n\n\tprivate async refreshOfficialCatalog(\n\t\tallowNetwork: boolean,\n\t\tsignal?: AbortSignal,\n\t): Promise<(OfficialCatalogLoadResult & { evictedUserRows: number }) | undefined> {\n\t\tconst storedProviders = await this.listStoredProviderIds();\n\t\tconst loaded = preferOfficialCatalog(\n\t\t\tthis.lastOfficial,\n\t\t\tawait loadOfficialCatalog({\n\t\t\t\tallowNetwork,\n\t\t\t\tcachePath: this.officialCachePath,\n\t\t\t\tsignal,\n\t\t\t\tproviderIds: [...storedProviders],\n\t\t\t}),\n\t\t);\n\t\tthis.lastOfficial = loaded;\n\t\tconst evicted = this.userModels.evictOfficial(officialModelKeys(loaded.catalog));\n\t\tconst templateByProvider = new Map<string, Model<Api> | undefined>();\n\t\tconst officialModels = loaded.catalog.models\n\t\t\t.filter((entry) => storedProviders.has(entry.provider))\n\t\t\t.map((entry) => {\n\t\t\t\tif (!templateByProvider.has(entry.provider)) {\n\t\t\t\t\ttemplateByProvider.set(entry.provider, this.bakedInModels(entry.provider)[0]);\n\t\t\t\t}\n\t\t\t\treturn officialEntryToModel(entry, templateByProvider.get(entry.provider));\n\t\t\t});\n\t\tthis.overlay.setOfficial(officialModels);\n\t\tthis.overlay.setUser(this.userModelsForStoredProviders(storedProviders));\n\t\treturn { ...loaded, evictedUserRows: evicted.length };\n\t}\n\n\tprivate async restoreLiveCatalogs(): Promise<LiveCatalogResult[]> {\n\t\tconst storedProviders = await this.listStoredProviderIds();\n\t\tconst results: LiveCatalogResult[] = [];\n\t\tfor (const providerId of LIVE_LIST_PROVIDER_IDS) {\n\t\t\tconst stored = storedProviders.has(providerId) ? await this.modelsStore.read(providerId) : undefined;\n\t\t\tif (stored?.models) this.overlay.setLive(providerId, stored.models);\n\t\t\telse this.overlay.setLive(providerId, []);\n\t\t\tresults.push({\n\t\t\t\tproviderId,\n\t\t\t\tstatus: \"skipped\",\n\t\t\t\tdiscoveries: [],\n\t\t\t\tadded: 0,\n\t\t\t\ttotal: stored?.models.length ?? 0,\n\t\t\t\tincomplete: [],\n\t\t\t});\n\t\t}\n\t\treturn results;\n\t}\n\n\tprivate async refreshLiveCatalogs(officialKeys: Set<string>, signal?: AbortSignal): Promise<LiveCatalogResult[]> {\n\t\tconst storedProviders = await this.listStoredProviderIds();\n\t\tconst known = knownModelKeys(\n\t\t\tLIVE_LIST_PROVIDER_IDS.flatMap((id) => this.bakedInModels(id)),\n\t\t\tofficialKeys,\n\t\t\tthis.userModels.keys(),\n\t\t);\n\n\t\tconst results = await Promise.all(\n\t\t\tLIVE_LIST_PROVIDER_IDS.map(async (providerId) => {\n\t\t\t\tif (!storedProviders.has(providerId)) {\n\t\t\t\t\tthis.overlay.setLive(providerId, []);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tproviderId,\n\t\t\t\t\t\tstatus: \"skipped\" as const,\n\t\t\t\t\t\tdiscoveries: [],\n\t\t\t\t\t\tadded: 0,\n\t\t\t\t\t\ttotal: 0,\n\t\t\t\t\t\tincomplete: [],\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst stored = await this.modelsStore.read(providerId);\n\t\t\t\tif (stored?.models) this.overlay.setLive(providerId, stored.models);\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tproviderId,\n\t\t\t\t\t\tstatus: \"skipped\" as const,\n\t\t\t\t\t\tdiscoveries: [],\n\t\t\t\t\t\tadded: 0,\n\t\t\t\t\t\ttotal: stored?.models.length ?? 0,\n\t\t\t\t\t\tincomplete: [],\n\t\t\t\t\t\terror: \"aborted\",\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst bakedIn = this.bakedInModels(providerId);\n\t\t\t\tconst provider = this.models.getProvider(providerId) ?? this.defaultBuiltins.get(providerId);\n\t\t\t\tconst baseUrl = provider?.baseUrl ?? bakedIn[0]?.baseUrl;\n\t\t\t\tif (!baseUrl) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tproviderId,\n\t\t\t\t\t\tstatus: \"skipped\" as const,\n\t\t\t\t\t\tdiscoveries: [],\n\t\t\t\t\t\tadded: 0,\n\t\t\t\t\t\ttotal: 0,\n\t\t\t\t\t\tincomplete: [],\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tlet credential: Credential | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tcredential = await this.refreshCredentialFor(providerId);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tproviderId,\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\tdiscoveries: [],\n\t\t\t\t\t\tadded: 0,\n\t\t\t\t\t\ttotal: stored?.models.length ?? 0,\n\t\t\t\t\t\tincomplete: [],\n\t\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (!credential) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tproviderId,\n\t\t\t\t\t\tstatus: \"skipped\" as const,\n\t\t\t\t\t\tdiscoveries: [],\n\t\t\t\t\t\tadded: 0,\n\t\t\t\t\t\ttotal: stored?.models.length ?? 0,\n\t\t\t\t\t\tincomplete: [],\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst result = await fetchLiveProviderModels({\n\t\t\t\t\tproviderId,\n\t\t\t\t\tbaseUrl,\n\t\t\t\t\tbakedIn,\n\t\t\t\t\tcredential,\n\t\t\t\t\tknownKeys: known,\n\t\t\t\t\tsignal,\n\t\t\t\t});\n\t\t\t\tif (result.status === \"ok\") {\n\t\t\t\t\tconst overlayModels = buildLiveOverlay(bakedIn, result.discoveries);\n\t\t\t\t\tthis.overlay.setLive(providerId, overlayModels);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.modelsStore.write(providerId, { models: overlayModels, checkedAt: Date.now() });\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Store write is best-effort.\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t}),\n\t\t);\n\t\treturn results;\n\t}\n\n\tprivate async refreshCredentialFor(providerId: string): Promise<Credential | undefined> {\n\t\tconst stored = await this.credentials.read(providerId);\n\t\t// Same lock + persist path as chat. Returning stored OAuth here skips\n\t\t// refresh and sends expired SuperGrok tokens to GET /models (HTTP 403).\n\t\tconst resolved = await this.getAuth(providerId);\n\t\tif (resolved?.auth.apiKey) return { type: \"api_key\", key: resolved.auth.apiKey, env: resolved.env };\n\t\tif (stored?.type === \"oauth\" && stored.access) return stored;\n\t\tif (stored?.type === \"api_key\" && stored.key) return stored;\n\t\treturn undefined;\n\t}\n\n\tregisterProvider(providerId: string, config: ProviderConfigInput): void {\n\t\t// Validate the incoming registration on its own, like the legacy registry:\n\t\t// a broken re-registration must throw without touching the stored config.\n\t\tvalidateExtensionProvider(providerId, this.builtins.get(providerId), this.config.getProvider(providerId), config);\n\t\t// Re-registration merges defined values over the previous registration and\n\t\t// preserves undefined ones, matching the legacy ModelRegistry contract.\n\t\tconst previous = this.extensionProviders.get(providerId);\n\t\tconst effective: ProviderConfigInput = { ...previous };\n\t\tfor (const [key, value] of Object.entries(config)) {\n\t\t\tif (value !== undefined) (effective as Record<string, unknown>)[key] = value;\n\t\t}\n\t\tthis.extensionProviders.set(providerId, effective);\n\t\tthis.recomposeProvider(providerId);\n\t\tthis.updateModelSnapshot();\n\t\tif (\n\t\t\tthis.snapshot.storedProviders.has(providerId) ||\n\t\t\tconfiguredRequestAuthStatus(this.config.getProvider(providerId), effective)?.configured\n\t\t) {\n\t\t\tconst configuredProviders = new Set(this.snapshot.configuredProviders).add(providerId);\n\t\t\tconst auth = new Map(this.snapshot.auth);\n\t\t\t// Provisional entry until the async refresh lands; never clobber a real check result.\n\t\t\tif (!auth.get(providerId)) {\n\t\t\t\tauth.set(providerId, {\n\t\t\t\t\ttype: effective.oauth && !effective.apiKey ? \"oauth\" : \"api_key\",\n\t\t\t\t\tsource: \"configured provider\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tthis.snapshot = {\n\t\t\t\t...this.snapshot,\n\t\t\t\tauth,\n\t\t\t\tconfiguredProviders,\n\t\t\t\tavailable: this.snapshot.all.filter((model) => configuredProviders.has(model.provider)),\n\t\t\t};\n\t\t}\n\t\tvoid this.refresh({ allowNetwork: false });\n\t}\n\n\tunregisterProvider(providerId: string): void {\n\t\tthis.extensionProviders.delete(providerId);\n\t\tthis.recomposeProvider(providerId);\n\t\tthis.updateModelSnapshot();\n\t\tvoid this.refresh({ allowNetwork: false });\n\t}\n}\n\n/** TUI init / footer / rebind: cache-only. Live refresh is `/refresh` and the model picker. */\nexport async function refreshModelCandidatesForInit(\n\truntime: Pick<ModelRuntime, \"refresh\" | \"getAvailableSnapshot\">,\n): Promise<readonly Model<Api>[]> {\n\tawait runtime.refresh({ allowNetwork: false });\n\treturn runtime.getAvailableSnapshot();\n}\n"]}
1
+ {"version":3,"file":"model-runtime.d.ts","sourceRoot":"","sources":["../../src/core/model-runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import { dirname, join } from \"node:path\";\nimport {\n\ttype Api,\n\ttype ApiStreamOptions,\n\ttype AssistantMessage,\n\ttype AssistantMessageEventStream,\n\ttype AuthCheck,\n\ttype AuthInteraction,\n\ttype AuthResult,\n\ttype AuthType,\n\ttype Context,\n\ttype Credential,\n\ttype CredentialInfo,\n\ttype CredentialStore,\n\tcreateModels,\n\tlazyStream,\n\ttype Model,\n\ttype Models,\n\ttype ModelsApiStreamOptions,\n\tModelsError,\n\ttype ModelsRefreshOptions,\n\ttype ModelsRefreshResult,\n\ttype ModelsSimpleStreamOptions,\n\ttype ModelsStore,\n\ttype ModelsStreamTransforms,\n\ttype MutableModels,\n\ttype Provider,\n\ttype ProviderHeaders,\n\ttype SimpleStreamOptions,\n\ttype StreamOptions,\n} from \"@ashx-j/lunr-ai\";\nimport * as builtinProviderCatalog from \"@ashx-j/lunr-ai/providers/all\";\nimport { getAgentDir } from \"../config.ts\";\nimport { AuthStorage as DefaultAuthStorage } from \"./auth-storage.ts\";\nimport { catalogCacheScope } from \"./catalog-cache-scope.ts\";\nimport { CatalogOverlaySource, knownModelKeys, withCatalogOverlay } from \"./catalog-merge.ts\";\nimport { readGrokCliXaiOAuth, wrapXaiGrokCliCredentials } from \"./grok-cli-auth.ts\";\nimport {\n\tbuildLiveOverlay,\n\tfetchLiveProviderModels,\n\ttype IncompleteLiveModel,\n\tLIVE_LIST_PROVIDER_IDS,\n\ttype LiveCatalogResult,\n} from \"./live-catalog.ts\";\nimport { ModelConfig } from \"./model-config.ts\";\nimport { FileModelsStore, InMemoryCodingAgentModelsStore } from \"./models-store.ts\";\nimport {\n\tloadOfficialCatalog,\n\ttype OfficialCatalogLoadResult,\n\tofficialCatalogCachePath,\n\tofficialEntryToModel,\n\tofficialModelKeys,\n\tpreferOfficialCatalog,\n} from \"./official-catalog.ts\";\nimport {\n\ttype AuthStatus,\n\ttype CompatibilityRequestConfig,\n\tcomposeModelProvider,\n\tconfiguredRequestAuthStatus,\n\ttype ProviderConfigInput,\n\tresolveCompatibilityRequestConfig,\n\tresolveConfiguredModelHeaders,\n\tvalidateExtensionProvider,\n} from \"./provider-composer.ts\";\nimport { RuntimeCredentials } from \"./runtime-credentials.ts\";\nimport { SubscriptionManager } from \"./subscriptions.ts\";\nimport { clearPlanUsageCache } from \"./usage-service.ts\";\nimport { type UserModelEntry, UserModelsStore, userEntryToModel, userModelsPath } from \"./user-models.ts\";\n\ninterface ModelRuntimeSnapshot {\n\tall: readonly Model<Api>[];\n\tavailable: readonly Model<Api>[];\n\tconfiguredProviders: ReadonlySet<string>;\n\tstoredProviders: ReadonlySet<string>;\n\tauth: ReadonlyMap<string, AuthCheck | undefined>;\n}\n\nexport interface CreateModelRuntimeOptions {\n\t/** Credential storage. Defaults to the file at authPath. */\n\tcredentials?: CredentialStore;\n\tauthPath?: string;\n\tmodelsPath?: string | null;\n\tmodelsStore?: ModelsStore;\n\tmodelsStorePath?: string;\n\tallowModelNetwork?: boolean;\n\t/** Applied by callers of `refresh()`, not by `create()` (startup is cache-only). */\n\tmodelRefreshTimeoutMs?: number;\n\t/** Unused. Kept so older callers compiling against this options bag still typecheck. */\n\tcatalogBaseUrl?: string;\n\t/** lunr: subscription-key pool. Defaults to subscriptions.json next to authPath (in-memory for custom stores). */\n\tsubscriptions?: SubscriptionManager;\n\t/**\n\t * Grok CLI home (`~/.grok`) used as a SuperGrok OAuth fallback.\n\t * `null` disables the fallback. Custom `credentials` default to disabled\n\t * unless this is an explicit path.\n\t */\n\tgrokHome?: string | null;\n}\n\nexport interface CatalogRefreshResult extends ModelsRefreshResult {\n\tofficial?: OfficialCatalogLoadResult & { evictedUserRows: number };\n\tlive: LiveCatalogResult[];\n\tincomplete: IncompleteLiveModel[];\n}\n\nexport interface ModelRuntimeAuthOverrides {\n\tapiKey?: string;\n\tenv?: Record<string, string>;\n}\n\nfunction mergeHeaders(\n\tbase: ProviderHeaders | undefined,\n\toverride: ProviderHeaders | undefined,\n): ProviderHeaders | undefined {\n\tif (!base && !override) return undefined;\n\tconst merged = { ...base };\n\tfor (const [name, value] of Object.entries(override ?? {})) {\n\t\tconst lowerName = name.toLowerCase();\n\t\tfor (const existingName of Object.keys(merged)) {\n\t\t\tif (existingName.toLowerCase() === lowerName) delete merged[existingName];\n\t\t}\n\t\tmerged[name] = value;\n\t}\n\treturn merged;\n}\n\n/** Configured pi-ai Models collection used by coding-agent and SDK consumers. */\nexport class ModelRuntime implements Models {\n\tprivate readonly models: MutableModels;\n\tprivate readonly credentials: RuntimeCredentials;\n\t/** lunr: per-provider subscription-key pools; rotation mirrors the active key into the credential store. */\n\treadonly subscriptionManager: SubscriptionManager;\n\tprivate readonly defaultBuiltins: ReadonlyMap<string, Provider>;\n\tprivate readonly builtins = new Map<string, Provider>();\n\tprivate readonly extensionProviders = new Map<string, ProviderConfigInput>();\n\tprivate readonly compositionErrors = new Map<string, string>();\n\tprivate readonly modelsPath: string | undefined;\n\tprivate readonly allowModelNetwork: boolean;\n\tprivate config: ModelConfig;\n\tprivate snapshot: ModelRuntimeSnapshot = {\n\t\tall: [],\n\t\tavailable: [],\n\t\tconfiguredProviders: new Set(),\n\t\tstoredProviders: new Set(),\n\t\tauth: new Map(),\n\t};\n\tprivate availabilityRefresh: Promise<void> | undefined;\n\tprivate availabilityError: string | undefined;\n\tprivate readonly modelsStore: ModelsStore;\n\tprivate readonly overlay = new CatalogOverlaySource();\n\tprivate readonly userModels: UserModelsStore;\n\tprivate readonly officialCachePath: string | undefined;\n\tprivate lastCatalogRefresh: CatalogRefreshResult = { aborted: false, errors: new Map(), live: [], incomplete: [] };\n\tprivate lastOfficial: OfficialCatalogLoadResult | undefined;\n\tprivate catalogRefresh: Promise<CatalogRefreshResult> | undefined;\n\tprivate catalogRefreshAllowsNetwork = false;\n\tprivate nextAutomaticRefresh = 0;\n\tprivate automaticRefreshIdentity = \"\";\n\tprivate readonly grokHome: string | undefined;\n\n\tprivate constructor(\n\t\tcredentials: RuntimeCredentials,\n\t\tconfig: ModelConfig,\n\t\tmodelsPath: string | undefined,\n\t\tmodelsStore: ModelsStore,\n\t\tproviders: readonly Provider[],\n\t\tallowModelNetwork: boolean,\n\t\tsubscriptionManager: SubscriptionManager,\n\t\tcatalogDir: string | undefined,\n\t\tgrokHome: string | undefined,\n\t) {\n\t\tthis.credentials = credentials;\n\t\tthis.config = config;\n\t\tthis.modelsPath = modelsPath;\n\t\tthis.modelsStore = modelsStore;\n\t\tthis.allowModelNetwork = allowModelNetwork;\n\t\tthis.subscriptionManager = subscriptionManager;\n\t\tthis.userModels = new UserModelsStore(catalogDir ? userModelsPath(catalogDir) : undefined);\n\t\tthis.officialCachePath = catalogDir ? officialCatalogCachePath(catalogDir) : undefined;\n\t\tthis.grokHome = grokHome;\n\t\tthis.defaultBuiltins = new Map(providers.map((provider) => [provider.id, provider]));\n\t\tfor (const [providerId, provider] of this.defaultBuiltins) this.builtins.set(providerId, provider);\n\t\tthis.models = createModels({ credentials, modelsStore });\n\t\tthis.rebuildProviders();\n\t}\n\n\tstatic async create(options: CreateModelRuntimeOptions = {}): Promise<ModelRuntime> {\n\t\treturn ModelRuntime.createInternal(options, true);\n\t}\n\n\t/** @internal Agent-session hydration registers extension providers before the first refresh. */\n\tstatic async createForAgentSession(options: CreateModelRuntimeOptions = {}): Promise<ModelRuntime> {\n\t\treturn ModelRuntime.createInternal(options, false);\n\t}\n\n\tprivate static async createInternal(\n\t\toptions: CreateModelRuntimeOptions,\n\t\trefreshOnCreate: boolean,\n\t): Promise<ModelRuntime> {\n\t\t// lunr: keep the underlying store — the SubscriptionManager mirrors rotated\n\t\t// keys into it directly, NOT through the RuntimeCredentials overlay (a runtime\n\t\t// --api-key override would shadow the mirror there).\n\t\tconst rawStore = options.credentials ?? DefaultAuthStorage.create(options.authPath);\n\t\tconst grokHomeDisabled =\n\t\t\toptions.grokHome === null || (options.grokHome === undefined && options.credentials !== undefined);\n\t\tconst grokHome = grokHomeDisabled ? undefined : (options.grokHome ?? undefined);\n\t\tconst baseStore = grokHomeDisabled ? rawStore : wrapXaiGrokCliCredentials(rawStore, { grokHome });\n\t\tconst credentials = new RuntimeCredentials(baseStore);\n\t\t// Pool persistence: subscriptions.json next to auth.json. A custom credential\n\t\t// store without an authPath (tests, SDK embeddings) gets a process-local pool\n\t\t// so nothing leaks into the real agent dir.\n\t\tconst subscriptionManager =\n\t\t\toptions.subscriptions ??\n\t\t\t(options.credentials && !options.authPath\n\t\t\t\t? SubscriptionManager.inMemory(baseStore)\n\t\t\t\t: SubscriptionManager.create(\n\t\t\t\t\t\tbaseStore,\n\t\t\t\t\t\toptions.authPath ? join(dirname(options.authPath), \"subscriptions.json\") : undefined,\n\t\t\t\t\t));\n\t\tconst modelsPath =\n\t\t\toptions.modelsPath === null ? undefined : (options.modelsPath ?? join(getAgentDir(), \"models.json\"));\n\t\tconst config = await ModelConfig.load(modelsPath);\n\t\tconst modelsStore =\n\t\t\toptions.modelsStore ??\n\t\t\t(modelsPath\n\t\t\t\t? new FileModelsStore(options.modelsStorePath ?? join(dirname(modelsPath), \"models-store.json\"))\n\t\t\t\t: new InMemoryCodingAgentModelsStore());\n\t\tconst providers = builtinProviderCatalog.builtinProviders();\n\t\tconst catalogDir = modelsPath ? dirname(modelsPath) : options.authPath ? dirname(options.authPath) : undefined;\n\t\tconst runtime = new ModelRuntime(\n\t\t\tcredentials,\n\t\t\tconfig,\n\t\t\tmodelsPath,\n\t\t\tmodelsStore,\n\t\t\tproviders,\n\t\t\toptions.allowModelNetwork ?? process.env.PI_OFFLINE === undefined,\n\t\t\tsubscriptionManager,\n\t\t\tcatalogDir,\n\t\t\tgrokHome,\n\t\t);\n\t\truntime.configureRadiusProviders();\n\t\truntime.rebuildProviders();\n\t\t// lunr: create() must not wait on GitHub / provider /v1/models / a dead NIC.\n\t\t// Disk cache + baked-in + bundled official overlay are enough to start;\n\t\t// /refresh and the model picker still do a live refresh.\n\t\tif (refreshOnCreate) {\n\t\t\tawait runtime.refresh({ allowNetwork: false });\n\t\t}\n\t\treturn runtime;\n\t}\n\n\tprivate configureRadiusProviders(): void {\n\t\tthis.builtins.clear();\n\t\tfor (const [providerId, provider] of this.defaultBuiltins) this.builtins.set(providerId, provider);\n\t\tfor (const providerId of this.config.getProviderIds()) {\n\t\t\tconst config = this.config.getProvider(providerId);\n\t\t\tif (config?.oauth !== \"radius\" || !config.baseUrl) continue;\n\t\t\tthis.builtins.set(\n\t\t\t\tproviderId,\n\t\t\t\tbuiltinProviderCatalog.radiusProvider({\n\t\t\t\t\tid: providerId,\n\t\t\t\t\tname: config.name ?? providerId,\n\t\t\t\t\tgateway: config.baseUrl.replace(/\\/v1\\/?$/u, \"\"),\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate providerIds(): Set<string> {\n\t\treturn new Set([...this.builtins.keys(), ...this.config.getProviderIds(), ...this.extensionProviders.keys()]);\n\t}\n\n\tprivate recomposeProvider(providerId: string): void {\n\t\tconst base = this.builtins.get(providerId);\n\t\tconst extension = this.extensionProviders.get(providerId);\n\t\tif (!base && !this.config.getProvider(providerId) && !extension) {\n\t\t\tthis.models.deleteProvider(providerId);\n\t\t\tthis.compositionErrors.delete(providerId);\n\t\t\treturn;\n\t\t}\n\t\tif (base && !this.config.getProvider(providerId) && !extension) {\n\t\t\t// No models.json/extension overlays: keep builtin auth/stream exact, then apply catalog layers.\n\t\t\tthis.models.setProvider(this.withLocalCatalog(base));\n\t\t\tthis.compositionErrors.delete(providerId);\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\t// Discovery is metadata beneath explicit user configuration and extension model definitions.\n\t\t\tthis.models.setProvider(\n\t\t\t\tcomposeModelProvider(providerId, base ? this.withLocalCatalog(base) : undefined, this.config, extension),\n\t\t\t);\n\t\t\tthis.compositionErrors.delete(providerId);\n\t\t} catch (error) {\n\t\t\tthis.compositionErrors.set(providerId, error instanceof Error ? error.message : String(error));\n\t\t\tif (base) this.models.setProvider(this.withLocalCatalog(base));\n\t\t\telse this.models.deleteProvider(providerId);\n\t\t}\n\t}\n\n\tprivate withLocalCatalog(provider: Provider): Provider {\n\t\treturn withCatalogOverlay(provider, this.overlay);\n\t}\n\n\tprivate bakedInModels(providerId: string): Model<Api>[] {\n\t\treturn [...(this.defaultBuiltins.get(providerId)?.getModels() ?? [])];\n\t}\n\n\tprivate rebuildProviders(): void {\n\t\tthis.models.clearProviders();\n\t\tthis.compositionErrors.clear();\n\t\tfor (const providerId of this.providerIds()) this.recomposeProvider(providerId);\n\t\tthis.updateModelSnapshot();\n\t}\n\n\tprivate updateModelSnapshot(): void {\n\t\tconst all = [...this.models.getModels()];\n\t\tthis.snapshot = {\n\t\t\t...this.snapshot,\n\t\t\tall,\n\t\t\tavailable: all.filter(\n\t\t\t\t(model) => this.snapshot.configuredProviders.has(model.provider) && this.isCatalogModelAvailable(model),\n\t\t\t),\n\t\t};\n\t}\n\n\tprivate isCatalogModelAvailable(model: Model<Api>): boolean {\n\t\tconst config = this.config.getProvider(model.provider);\n\t\treturn (\n\t\t\t!!config?.models?.some((row) => row.id === model.id) ||\n\t\t\t!!this.extensionProviders.get(model.provider)?.models?.some((row) => row.id === model.id) ||\n\t\t\tthis.overlay.isAvailable(model)\n\t\t);\n\t}\n\n\tprivate async listStoredProviderIds(): Promise<Set<string>> {\n\t\tconst credentials = await this.credentials.list();\n\t\treturn new Set(credentials.map((entry) => entry.providerId));\n\t}\n\n\t/** `/model` lists stored creds + models.json/extension keys, never ambient envApiKeyAuth. */\n\tprivate configuredProviderIds(storedProviders: ReadonlySet<string>): Set<string> {\n\t\tconst configured = new Set(storedProviders);\n\t\tfor (const provider of this.models.getProviders()) {\n\t\t\tif (\n\t\t\t\tconfiguredRequestAuthStatus(this.config.getProvider(provider.id), this.extensionProviders.get(provider.id))\n\t\t\t\t\t?.configured\n\t\t\t) {\n\t\t\t\tconfigured.add(provider.id);\n\t\t\t}\n\t\t}\n\t\treturn configured;\n\t}\n\n\tprivate userModelsForStoredProviders(storedProviders: ReadonlySet<string>): Model<Api>[] {\n\t\treturn this.userModels\n\t\t\t.list()\n\t\t\t.filter((row) => storedProviders.has(row.provider))\n\t\t\t.map(userEntryToModel);\n\t}\n\n\tprivate async runAvailabilityRefresh(): Promise<void> {\n\t\tconst providers = this.models.getProviders();\n\t\tconst [available, checks, storedProviders] = await Promise.all([\n\t\t\tthis.models.getAvailable(),\n\t\t\tPromise.all(\n\t\t\t\tproviders.map(\n\t\t\t\t\tasync (provider): Promise<[string, AuthCheck | undefined]> => [\n\t\t\t\t\t\tprovider.id,\n\t\t\t\t\t\tawait this.models.checkAuth(provider.id),\n\t\t\t\t\t],\n\t\t\t\t),\n\t\t\t),\n\t\t\tthis.listStoredProviderIds(),\n\t\t]);\n\t\tconst auth = new Map(checks);\n\t\tconst configuredProviders = this.configuredProviderIds(storedProviders);\n\t\tthis.snapshot = {\n\t\t\tall: [...this.models.getModels()],\n\t\t\tavailable: available.filter(\n\t\t\t\t(model) => configuredProviders.has(model.provider) && this.isCatalogModelAvailable(model),\n\t\t\t),\n\t\t\tconfiguredProviders,\n\t\t\tstoredProviders,\n\t\t\tauth,\n\t\t};\n\t\tthis.availabilityError = undefined;\n\t}\n\n\tprivate queueAvailabilityRefresh(after: Promise<void> | undefined): Promise<void> {\n\t\tconst refresh = (after ?? Promise.resolve()).catch(() => {}).then(() => this.runAvailabilityRefresh());\n\t\tconst recorded = refresh.catch((error) => {\n\t\t\tthis.availabilityError = error instanceof Error ? error.message : String(error);\n\t\t\tthrow error;\n\t\t});\n\t\tconst tracked = recorded.finally(() => {\n\t\t\tif (this.availabilityRefresh === tracked) this.availabilityRefresh = undefined;\n\t\t});\n\t\tthis.availabilityRefresh = tracked;\n\t\treturn tracked;\n\t}\n\n\t/** Coalesce concurrent readers onto the pending refresh. */\n\tprivate refreshAvailability(): Promise<void> {\n\t\treturn this.availabilityRefresh ?? this.queueAvailabilityRefresh(undefined);\n\t}\n\n\t/** Mutations must not observe an in-flight refresh started before them. */\n\tprivate forceRefreshAvailability(): Promise<void> {\n\t\treturn this.queueAvailabilityRefresh(this.availabilityRefresh);\n\t}\n\n\tgetProviders(): readonly Provider[] {\n\t\treturn this.models.getProviders();\n\t}\n\n\tgetProvider(providerId: string): Provider | undefined {\n\t\treturn this.models.getProvider(providerId);\n\t}\n\n\tgetModels(providerId?: string): readonly Model<Api>[] {\n\t\treturn this.models.getModels(providerId);\n\t}\n\n\tgetModel(providerId: string, modelId: string): Model<Api> | undefined {\n\t\treturn this.models.getModel(providerId, modelId);\n\t}\n\n\tasync checkAuth(providerId: string): Promise<AuthCheck | undefined> {\n\t\treturn this.models.checkAuth(providerId);\n\t}\n\n\tasync getAvailable(providerId?: string): Promise<readonly Model<Api>[]> {\n\t\tif (providerId) {\n\t\t\tif (this.availabilityRefresh) {\n\t\t\t\tawait this.availabilityRefresh;\n\t\t\t\treturn this.snapshot.available.filter((model) => model.provider === providerId);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn (await this.models.getAvailable(providerId)).filter((model) => this.isCatalogModelAvailable(model));\n\t\t\t} catch (error) {\n\t\t\t\tthis.availabilityError = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t\tawait this.refreshAvailability();\n\t\treturn this.snapshot.available;\n\t}\n\n\tgetAvailableSnapshot(): readonly Model<Api>[] {\n\t\treturn this.snapshot.available;\n\t}\n\n\tgetError(): string | undefined {\n\t\tconst errors: string[] = [];\n\t\tconst configError = this.config.getError();\n\t\tif (configError) errors.push(configError);\n\t\tfor (const [providerId, error] of this.compositionErrors) {\n\t\t\terrors.push(`Provider \"${providerId}\": ${error}`);\n\t\t}\n\t\tif (this.availabilityError) errors.push(`Availability refresh: ${this.availabilityError}`);\n\t\treturn errors.length > 0 ? errors.join(\"\\n\\n\") : undefined;\n\t}\n\n\tgetRegisteredProviderConfig(providerId: string): ProviderConfigInput | undefined {\n\t\treturn this.extensionProviders.get(providerId);\n\t}\n\n\tgetRegisteredProviderIds(): readonly string[] {\n\t\treturn [...this.extensionProviders.keys()];\n\t}\n\n\t/** @internal Compatibility fallback for ModelRegistry when provider auth is unconfigured. */\n\tgetCompatibilityRequestConfig(model: Model<Api>): CompatibilityRequestConfig {\n\t\treturn resolveCompatibilityRequestConfig(\n\t\t\tmodel,\n\t\t\tthis.config.getProvider(model.provider),\n\t\t\tthis.extensionProviders.get(model.provider),\n\t\t);\n\t}\n\n\tisUsingOAuth(providerId: string): boolean {\n\t\treturn this.snapshot.auth.get(providerId)?.type === \"oauth\";\n\t}\n\n\thasConfiguredAuth(providerId: string): boolean {\n\t\treturn this.snapshot.configuredProviders.has(providerId);\n\t}\n\n\tgetAuth(providerId: string, overrides?: ModelRuntimeAuthOverrides): Promise<AuthResult | undefined>;\n\tgetAuth(model: Model<Api>, overrides?: ModelRuntimeAuthOverrides): Promise<AuthResult | undefined>;\n\tasync getAuth(\n\t\tproviderOrModel: string | Model<Api>,\n\t\toverrides: ModelRuntimeAuthOverrides = {},\n\t): Promise<AuthResult | undefined> {\n\t\tif (typeof providerOrModel === \"string\") return this.models.getAuth(providerOrModel, overrides);\n\t\tconst resolution = await this.models.getAuth(providerOrModel, overrides);\n\t\tif (!resolution) return undefined;\n\t\tconst configuredHeaders = resolveConfiguredModelHeaders(\n\t\t\tproviderOrModel,\n\t\t\tthis.config.getProvider(providerOrModel.provider),\n\t\t\tthis.extensionProviders.get(providerOrModel.provider),\n\t\t\t{ ...(resolution.env ?? {}), ...(overrides.env ?? {}) },\n\t\t);\n\t\treturn {\n\t\t\t...resolution,\n\t\t\tauth: {\n\t\t\t\t...resolution.auth,\n\t\t\t\theaders: mergeHeaders(resolution.auth.headers, configuredHeaders),\n\t\t\t},\n\t\t};\n\t}\n\n\t/** lunr: whether a runtime --api-key override is active for the provider (it shadows auth.json). */\n\thasRuntimeApiKey(providerId: string): boolean {\n\t\treturn this.credentials.hasRuntimeApiKey(providerId);\n\t}\n\n\tasync setRuntimeApiKey(providerId: string, apiKey: string, options?: { allowNetwork?: boolean }): Promise<void> {\n\t\tthis.credentials.setRuntimeApiKey(providerId, apiKey);\n\t\tconst auth = new Map(this.snapshot.auth).set(providerId, { type: \"api_key\", source: \"runtime API key\" });\n\t\tconst configuredProviders = new Set(this.snapshot.configuredProviders).add(providerId);\n\t\tconst storedProviders = new Set(this.snapshot.storedProviders).add(providerId);\n\t\tthis.snapshot = {\n\t\t\t...this.snapshot,\n\t\t\tauth,\n\t\t\tconfiguredProviders,\n\t\t\tstoredProviders,\n\t\t\tavailable: this.snapshot.all.filter(\n\t\t\t\t(model) => configuredProviders.has(model.provider) && this.isCatalogModelAvailable(model),\n\t\t\t),\n\t\t};\n\t\tawait this.refresh({ allowNetwork: options?.allowNetwork ?? this.allowModelNetwork });\n\t}\n\n\tasync removeRuntimeApiKey(providerId: string): Promise<void> {\n\t\tthis.credentials.removeRuntimeApiKey(providerId);\n\t\tawait this.refresh({ allowNetwork: this.allowModelNetwork });\n\t}\n\n\t/** lunr: whether network model-catalog fetches are allowed (offline mode disables them). */\n\tisNetworkAllowed(): boolean {\n\t\treturn this.allowModelNetwork;\n\t}\n\n\tlistCredentials(): Promise<readonly CredentialInfo[]> {\n\t\treturn this.credentials.list();\n\t}\n\n\tgetProviderAuthStatus(providerId: string): AuthStatus {\n\t\tif (this.credentials.hasRuntimeApiKey(providerId)) return { configured: true, source: \"runtime\" };\n\t\tif (this.snapshot.storedProviders.has(providerId)) return { configured: true, source: \"stored\" };\n\t\tconst configured = configuredRequestAuthStatus(\n\t\t\tthis.config.getProvider(providerId),\n\t\t\tthis.extensionProviders.get(providerId),\n\t\t);\n\t\tif (configured) return configured;\n\t\tconst check = this.snapshot.auth.get(providerId);\n\t\treturn check ? { configured: true, source: \"environment\", label: check.source } : { configured: false };\n\t}\n\n\tprivate async prepareRequest(\n\t\tmodel: Model<Api>,\n\t\toptions: (StreamOptions & ModelsStreamTransforms) | undefined,\n\t): Promise<{ provider: Provider; model: Model<Api>; options: StreamOptions }> {\n\t\tconst provider = this.models.getProvider(model.provider);\n\t\tif (!provider) throw new ModelsError(\"provider\", `Unknown provider: ${model.provider}`);\n\t\tconst resolution = await this.getAuth(model, { apiKey: options?.apiKey, env: options?.env });\n\t\tif (!resolution) throw new ModelsError(\"auth\", `Provider is not configured: ${model.provider}`);\n\n\t\tconst { transformHeaders, ...providerOptions } = options ?? {};\n\t\tlet headers = mergeHeaders(resolution.auth.headers, providerOptions.headers);\n\t\tif (transformHeaders) headers = await transformHeaders(headers ?? {});\n\t\tconst env =\n\t\t\tresolution.env || providerOptions.env\n\t\t\t\t? { ...(resolution.env ?? {}), ...(providerOptions.env ?? {}) }\n\t\t\t\t: undefined;\n\t\treturn {\n\t\t\tprovider,\n\t\t\tmodel: resolution.auth.baseUrl ? { ...model, baseUrl: resolution.auth.baseUrl } : model,\n\t\t\toptions: {\n\t\t\t\t...providerOptions,\n\t\t\t\tapiKey: providerOptions.apiKey ?? resolution.auth.apiKey,\n\t\t\t\theaders,\n\t\t\t\tenv,\n\t\t\t},\n\t\t};\n\t}\n\n\tstream<TApi extends Api>(\n\t\tmodel: Model<TApi>,\n\t\tcontext: Context,\n\t\toptions?: ModelsApiStreamOptions<TApi>,\n\t): AssistantMessageEventStream {\n\t\treturn lazyStream(model, async () => {\n\t\t\tconst prepared = await this.prepareRequest(\n\t\t\t\tmodel,\n\t\t\t\toptions as (StreamOptions & ModelsStreamTransforms) | undefined,\n\t\t\t);\n\t\t\treturn prepared.provider.stream(\n\t\t\t\tprepared.model as Model<TApi>,\n\t\t\t\tcontext,\n\t\t\t\tprepared.options as ApiStreamOptions<TApi>,\n\t\t\t);\n\t\t});\n\t}\n\n\tcomplete<TApi extends Api>(\n\t\tmodel: Model<TApi>,\n\t\tcontext: Context,\n\t\toptions?: ModelsApiStreamOptions<TApi>,\n\t): Promise<AssistantMessage> {\n\t\treturn this.stream(model, context, options).result();\n\t}\n\n\tstreamSimple(model: Model<Api>, context: Context, options?: ModelsSimpleStreamOptions): AssistantMessageEventStream {\n\t\treturn lazyStream(model, async () => {\n\t\t\tconst prepared = await this.prepareRequest(model, options);\n\t\t\treturn prepared.provider.streamSimple(prepared.model, context, prepared.options as SimpleStreamOptions);\n\t\t});\n\t}\n\n\tcompleteSimple(model: Model<Api>, context: Context, options?: ModelsSimpleStreamOptions): Promise<AssistantMessage> {\n\t\treturn this.streamSimple(model, context, options).result();\n\t}\n\n\tasync login(providerId: string, type: AuthType, interaction: AuthInteraction): Promise<Credential> {\n\t\tconst credential = await this.models.login(providerId, type, interaction);\n\t\tclearPlanUsageCache();\n\t\tawait this.refresh({ allowNetwork: this.allowModelNetwork });\n\t\treturn credential;\n\t}\n\n\tasync logout(providerId: string): Promise<void> {\n\t\tawait this.models.logout(providerId);\n\t\tawait this.modelsStore.delete(providerId);\n\t\tthis.userModels.evictProvider(providerId);\n\t\tthis.overlay.setLive(providerId, []);\n\t\t// Reset credential-dependent compatibility projections before the unconfigured provider is skipped by refresh.\n\t\tthis.recomposeProvider(providerId);\n\t\tclearPlanUsageCache();\n\t\tawait this.refresh({ allowNetwork: this.allowModelNetwork });\n\t}\n\n\t/** Copy a live Grok CLI SuperGrok session into lunR `auth.json`. */\n\tasync importGrokCliXaiAuth(): Promise<Credential | undefined> {\n\t\tconst grok = readGrokCliXaiOAuth({ grokHome: this.grokHome });\n\t\tif (!grok) return undefined;\n\t\tconst stored = await this.credentials.modify(\"xai\", async () => grok);\n\t\tclearPlanUsageCache();\n\t\tawait this.refresh({ allowNetwork: this.allowModelNetwork });\n\t\treturn stored;\n\t}\n\n\tasync reloadConfig(): Promise<void> {\n\t\tthis.config = await ModelConfig.load(this.modelsPath);\n\t\tthis.configureRadiusProviders();\n\t\tthis.rebuildProviders();\n\t\tawait this.refresh({ allowNetwork: this.allowModelNetwork });\n\t}\n\n\tasync refresh(options: ModelsRefreshOptions = {}): Promise<CatalogRefreshResult> {\n\t\tconst allowNetwork = options.allowNetwork ?? this.allowModelNetwork;\n\t\tif (this.catalogRefresh) {\n\t\t\t// Join an in-flight refresh that is at least as capable. A cache-only\n\t\t\t// flight must not swallow a later network refresh.\n\t\t\tif (this.catalogRefreshAllowsNetwork || !allowNetwork) {\n\t\t\t\treturn this.catalogRefresh;\n\t\t\t}\n\t\t\tawait this.catalogRefresh.catch(() => {});\n\t\t\treturn this.refresh({ ...options, allowNetwork });\n\t\t}\n\n\t\tconst run = this.executeCatalogRefresh({ ...options, allowNetwork }).finally(() => {\n\t\t\tif (this.catalogRefresh === run) {\n\t\t\t\tthis.catalogRefresh = undefined;\n\t\t\t\tthis.catalogRefreshAllowsNetwork = false;\n\t\t\t}\n\t\t});\n\t\tthis.catalogRefresh = run;\n\t\tthis.catalogRefreshAllowsNetwork = allowNetwork;\n\t\treturn run;\n\t}\n\n\tprivate async executeCatalogRefresh(\n\t\trefreshOptions: ModelsRefreshOptions & { allowNetwork: boolean },\n\t): Promise<CatalogRefreshResult> {\n\t\tconst official = await this.refreshOfficialCatalog(refreshOptions.allowNetwork, refreshOptions.signal);\n\t\tconst live = refreshOptions.allowNetwork\n\t\t\t? await this.refreshLiveCatalogs(\n\t\t\t\t\tofficial ? officialModelKeys(official.catalog) : new Set(),\n\t\t\t\t\trefreshOptions.signal,\n\t\t\t\t)\n\t\t\t: await this.restoreLiveCatalogs();\n\n\t\t// Radius / local-provider / extension refreshModels hooks. Live-list\n\t\t// providers have no refreshModels, so this does not hit pi.dev.\n\t\tconst result = ((await this.models.refresh(refreshOptions)) as ModelsRefreshResult | undefined) ?? {\n\t\t\taborted: refreshOptions.signal?.aborted ?? false,\n\t\t\terrors: new Map(),\n\t\t};\n\t\tconst errors = new Map(result.errors);\n\t\tfor (const [id, error] of Object.entries(official?.errors ?? {})) errors.set(id, new Error(error));\n\t\tfor (const entry of live) {\n\t\t\tif (entry.status === \"error\" || entry.status === \"timeout\") {\n\t\t\t\terrors.set(entry.providerId, new Error(entry.error ?? entry.status));\n\t\t\t}\n\t\t}\n\t\tthis.updateModelSnapshot();\n\t\ttry {\n\t\t\tawait this.forceRefreshAvailability();\n\t\t} catch {\n\t\t\t// Availability errors are recorded by forceRefreshAvailability; refreshed models remain usable.\n\t\t}\n\t\tconst catalogResult: CatalogRefreshResult = {\n\t\t\taborted: result.aborted,\n\t\t\terrors,\n\t\t\tofficial,\n\t\t\tlive,\n\t\t\tincomplete: live.flatMap((entry) => entry.incomplete),\n\t\t};\n\t\tthis.lastCatalogRefresh = catalogResult;\n\t\treturn catalogResult;\n\t}\n\n\tgetLastCatalogRefresh(): CatalogRefreshResult {\n\t\treturn this.lastCatalogRefresh;\n\t}\n\n\t/** Called after first paint and by the picker; explicit /refresh always bypasses this gate. */\n\tasync refreshIfStale(options: { signal?: AbortSignal } = {}): Promise<CatalogRefreshResult> {\n\t\tif (!this.allowModelNetwork || options.signal?.aborted) return this.lastCatalogRefresh;\n\t\tif (this.catalogRefresh) return this.refresh({ allowNetwork: true, signal: options.signal });\n\t\tconst providers = [...(await this.listStoredProviderIds())].sort();\n\t\tconst identity = (await Promise.all(providers.map(async (id) => this.scopeFor(id)))).join(\":\");\n\t\tif (identity === this.automaticRefreshIdentity && Date.now() < this.nextAutomaticRefresh)\n\t\t\treturn this.lastCatalogRefresh;\n\t\tthis.automaticRefreshIdentity = identity;\n\t\t// Bound retries on a broken source; a successful refresh extends the deadline to one hour.\n\t\tthis.nextAutomaticRefresh = Date.now() + 60_000;\n\t\tconst result = await this.refresh({ allowNetwork: true, signal: options.signal });\n\t\tif (!result.aborted && !result.errors.size) this.nextAutomaticRefresh = Date.now() + 3_600_000;\n\t\treturn result;\n\t}\n\n\tprivate async scopeFor(providerId: string): Promise<string> {\n\t\tconst provider = this.models.getProvider(providerId) ?? this.defaultBuiltins.get(providerId);\n\t\treturn catalogCacheScope(providerId, provider?.baseUrl ?? \"\", await this.credentials.read(providerId));\n\t}\n\n\tpersistUserModels(rows: readonly UserModelEntry[]): void {\n\t\tthis.userModels.upsert(rows);\n\t\tthis.overlay.setUser(this.userModelsForStoredProviders(this.snapshot.storedProviders));\n\t\tthis.updateModelSnapshot();\n\t}\n\n\tprivate async refreshOfficialCatalog(\n\t\tallowNetwork: boolean,\n\t\tsignal?: AbortSignal,\n\t): Promise<(OfficialCatalogLoadResult & { evictedUserRows: number }) | undefined> {\n\t\tconst storedProviders = await this.listStoredProviderIds();\n\t\tconst loaded = preferOfficialCatalog(\n\t\t\tthis.lastOfficial,\n\t\t\tawait loadOfficialCatalog({\n\t\t\t\tallowNetwork,\n\t\t\t\tcachePath: this.officialCachePath,\n\t\t\t\tsignal,\n\t\t\t\tproviderIds: [...storedProviders],\n\t\t\t}),\n\t\t);\n\t\tthis.lastOfficial = loaded;\n\t\tconst evicted = this.userModels.evictOfficial(officialModelKeys(loaded.catalog));\n\t\tconst templateByProvider = new Map<string, Model<Api> | undefined>();\n\t\tconst officialModels = loaded.catalog.models\n\t\t\t.filter((entry) => storedProviders.has(entry.provider))\n\t\t\t.map((entry) => {\n\t\t\t\tif (!templateByProvider.has(entry.provider)) {\n\t\t\t\t\ttemplateByProvider.set(entry.provider, this.bakedInModels(entry.provider)[0]);\n\t\t\t\t}\n\t\t\t\treturn officialEntryToModel(entry, templateByProvider.get(entry.provider));\n\t\t\t});\n\t\tthis.overlay.setOfficial(officialModels);\n\t\tthis.overlay.setUser(this.userModelsForStoredProviders(storedProviders));\n\t\treturn { ...loaded, evictedUserRows: evicted.length };\n\t}\n\n\tprivate async restoreLiveCatalogs(): Promise<LiveCatalogResult[]> {\n\t\tconst storedProviders = await this.listStoredProviderIds();\n\t\tconst results: LiveCatalogResult[] = [];\n\t\tfor (const providerId of LIVE_LIST_PROVIDER_IDS) {\n\t\t\tconst stored = storedProviders.has(providerId) ? await this.modelsStore.read(providerId) : undefined;\n\t\t\tconst valid =\n\t\t\t\t(!stored?.scope && providerId !== \"openai-codex\") || stored?.scope === (await this.scopeFor(providerId));\n\t\t\tif (valid && stored?.models) this.overlay.setLive(providerId, stored.models);\n\t\t\telse this.overlay.setLive(providerId, []);\n\t\t\tresults.push({\n\t\t\t\tproviderId,\n\t\t\t\tstatus: \"skipped\",\n\t\t\t\tdiscoveries: [],\n\t\t\t\tadded: 0,\n\t\t\t\ttotal: stored?.models.length ?? 0,\n\t\t\t\tincomplete: [],\n\t\t\t});\n\t\t}\n\t\treturn results;\n\t}\n\n\tprivate async refreshLiveCatalogs(officialKeys: Set<string>, signal?: AbortSignal): Promise<LiveCatalogResult[]> {\n\t\tconst storedProviders = await this.listStoredProviderIds();\n\t\tconst known = knownModelKeys(\n\t\t\tLIVE_LIST_PROVIDER_IDS.flatMap((id) => this.bakedInModels(id)),\n\t\t\tofficialKeys,\n\t\t\tthis.userModels.keys(),\n\t\t);\n\n\t\tconst results = await Promise.all(\n\t\t\tLIVE_LIST_PROVIDER_IDS.map(async (providerId) => {\n\t\t\t\tif (!storedProviders.has(providerId)) {\n\t\t\t\t\tthis.overlay.setLive(providerId, []);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tproviderId,\n\t\t\t\t\t\tstatus: \"skipped\" as const,\n\t\t\t\t\t\tdiscoveries: [],\n\t\t\t\t\t\tadded: 0,\n\t\t\t\t\t\ttotal: 0,\n\t\t\t\t\t\tincomplete: [],\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst stored = await this.modelsStore.read(providerId);\n\t\t\t\tconst initialScope = await this.scopeFor(providerId);\n\t\t\t\tif (stored?.models && (stored.scope === initialScope || (!stored.scope && providerId !== \"openai-codex\")))\n\t\t\t\t\tthis.overlay.setLive(providerId, stored.models);\n\t\t\t\telse this.overlay.setLive(providerId, []);\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tproviderId,\n\t\t\t\t\t\tstatus: \"skipped\" as const,\n\t\t\t\t\t\tdiscoveries: [],\n\t\t\t\t\t\tadded: 0,\n\t\t\t\t\t\ttotal: stored?.models.length ?? 0,\n\t\t\t\t\t\tincomplete: [],\n\t\t\t\t\t\terror: \"aborted\",\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst bakedIn = this.bakedInModels(providerId);\n\t\t\t\tconst provider = this.models.getProvider(providerId) ?? this.defaultBuiltins.get(providerId);\n\t\t\t\tconst baseUrl = provider?.baseUrl ?? bakedIn[0]?.baseUrl;\n\t\t\t\tif (!baseUrl) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tproviderId,\n\t\t\t\t\t\tstatus: \"skipped\" as const,\n\t\t\t\t\t\tdiscoveries: [],\n\t\t\t\t\t\tadded: 0,\n\t\t\t\t\t\ttotal: 0,\n\t\t\t\t\t\tincomplete: [],\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tlet credential: Credential | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tcredential = await this.refreshCredentialFor(providerId);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tproviderId,\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\tdiscoveries: [],\n\t\t\t\t\t\tadded: 0,\n\t\t\t\t\t\ttotal: stored?.models.length ?? 0,\n\t\t\t\t\t\tincomplete: [],\n\t\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (!credential) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tproviderId,\n\t\t\t\t\t\tstatus: \"skipped\" as const,\n\t\t\t\t\t\tdiscoveries: [],\n\t\t\t\t\t\tadded: 0,\n\t\t\t\t\t\ttotal: stored?.models.length ?? 0,\n\t\t\t\t\t\tincomplete: [],\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst requestScope = catalogCacheScope(providerId, baseUrl, credential);\n\t\t\t\tconst result = await fetchLiveProviderModels({\n\t\t\t\t\tdiscoveryClientVersion: stored?.scope === requestScope ? stored.discoveryClientVersion : undefined,\n\t\t\t\t\tproviderId,\n\t\t\t\t\tbaseUrl,\n\t\t\t\t\tbakedIn,\n\t\t\t\t\tcredential,\n\t\t\t\t\tknownKeys: known,\n\t\t\t\t\tsignal,\n\t\t\t\t});\n\t\t\t\t// A login/logout during the request must not install the previous account's result.\n\t\t\t\tconst accountChanged = (await this.scopeFor(providerId)) !== requestScope;\n\t\t\t\tif (accountChanged) this.overlay.setLive(providerId, []);\n\t\t\t\tif (signal?.aborted || accountChanged) {\n\t\t\t\t\treturn { ...result, status: \"skipped\" as const, discoveries: [], incomplete: [] };\n\t\t\t\t}\n\t\t\t\tif (result.status === \"ok\") {\n\t\t\t\t\tconst overlayModels = buildLiveOverlay(bakedIn, result.discoveries);\n\t\t\t\t\tthis.overlay.setLive(providerId, overlayModels);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.modelsStore.write(providerId, {\n\t\t\t\t\t\t\tmodels: overlayModels,\n\t\t\t\t\t\t\tcheckedAt: Date.now(),\n\t\t\t\t\t\t\tscope: requestScope,\n\t\t\t\t\t\t\tdiscoveryClientVersion: result.discoveryClientVersion,\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Store write is best-effort.\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t}),\n\t\t);\n\t\treturn results;\n\t}\n\n\tprivate async refreshCredentialFor(providerId: string): Promise<Credential | undefined> {\n\t\t// Same lock + persist path as chat. Returning stored OAuth here skips\n\t\t// refresh and sends expired SuperGrok tokens to GET /models (HTTP 403).\n\t\tconst resolved = await this.getAuth(providerId);\n\t\tconst stored = await this.credentials.read(providerId);\n\t\tif (stored?.type === \"oauth\") return { ...stored, access: resolved?.auth.apiKey ?? stored.access };\n\t\tif (resolved?.auth.apiKey) return { type: \"api_key\", key: resolved.auth.apiKey, env: resolved.env };\n\t\tif (stored?.type === \"api_key\" && stored.key) return stored;\n\t\treturn undefined;\n\t}\n\n\tregisterProvider(providerId: string, config: ProviderConfigInput): void {\n\t\t// Validate the incoming registration on its own, like the legacy registry:\n\t\t// a broken re-registration must throw without touching the stored config.\n\t\tvalidateExtensionProvider(providerId, this.builtins.get(providerId), this.config.getProvider(providerId), config);\n\t\t// Re-registration merges defined values over the previous registration and\n\t\t// preserves undefined ones, matching the legacy ModelRegistry contract.\n\t\tconst previous = this.extensionProviders.get(providerId);\n\t\tconst effective: ProviderConfigInput = { ...previous };\n\t\tfor (const [key, value] of Object.entries(config)) {\n\t\t\tif (value !== undefined) (effective as Record<string, unknown>)[key] = value;\n\t\t}\n\t\tthis.extensionProviders.set(providerId, effective);\n\t\tthis.recomposeProvider(providerId);\n\t\tthis.updateModelSnapshot();\n\t\tif (\n\t\t\tthis.snapshot.storedProviders.has(providerId) ||\n\t\t\tconfiguredRequestAuthStatus(this.config.getProvider(providerId), effective)?.configured\n\t\t) {\n\t\t\tconst configuredProviders = new Set(this.snapshot.configuredProviders).add(providerId);\n\t\t\tconst auth = new Map(this.snapshot.auth);\n\t\t\t// Provisional entry until the async refresh lands; never clobber a real check result.\n\t\t\tif (!auth.get(providerId)) {\n\t\t\t\tauth.set(providerId, {\n\t\t\t\t\ttype: effective.oauth && !effective.apiKey ? \"oauth\" : \"api_key\",\n\t\t\t\t\tsource: \"configured provider\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tthis.snapshot = {\n\t\t\t\t...this.snapshot,\n\t\t\t\tauth,\n\t\t\t\tconfiguredProviders,\n\t\t\t\tavailable: this.snapshot.all.filter(\n\t\t\t\t\t(model) => configuredProviders.has(model.provider) && this.isCatalogModelAvailable(model),\n\t\t\t\t),\n\t\t\t};\n\t\t}\n\t\tvoid this.refresh({ allowNetwork: false });\n\t}\n\n\tunregisterProvider(providerId: string): void {\n\t\tthis.extensionProviders.delete(providerId);\n\t\tthis.recomposeProvider(providerId);\n\t\tthis.updateModelSnapshot();\n\t\tvoid this.refresh({ allowNetwork: false });\n\t}\n}\n\n/** TUI init / footer / rebind: cache-only. Live refresh is `/refresh` and the model picker. */\nexport async function refreshModelCandidatesForInit(\n\truntime: Pick<ModelRuntime, \"refresh\" | \"getAvailableSnapshot\">,\n): Promise<readonly Model<Api>[]> {\n\tawait runtime.refresh({ allowNetwork: false });\n\treturn runtime.getAvailableSnapshot();\n}\n"]}
@@ -3,6 +3,7 @@ import { createModels, lazyStream, ModelsError, } from "@ashx-j/lunr-ai";
3
3
  import * as builtinProviderCatalog from "@ashx-j/lunr-ai/providers/all";
4
4
  import { getAgentDir } from "../config.js";
5
5
  import { AuthStorage as DefaultAuthStorage } from "./auth-storage.js";
6
+ import { catalogCacheScope } from "./catalog-cache-scope.js";
6
7
  import { CatalogOverlaySource, knownModelKeys, withCatalogOverlay } from "./catalog-merge.js";
7
8
  import { readGrokCliXaiOAuth, wrapXaiGrokCliCredentials } from "./grok-cli-auth.js";
8
9
  import { buildLiveOverlay, fetchLiveProviderModels, LIVE_LIST_PROVIDER_IDS, } from "./live-catalog.js";
@@ -58,6 +59,8 @@ export class ModelRuntime {
58
59
  lastOfficial;
59
60
  catalogRefresh;
60
61
  catalogRefreshAllowsNetwork = false;
62
+ nextAutomaticRefresh = 0;
63
+ automaticRefreshIdentity = "";
61
64
  grokHome;
62
65
  constructor(credentials, config, modelsPath, modelsStore, providers, allowModelNetwork, subscriptionManager, catalogDir, grokHome) {
63
66
  this.credentials = credentials;
@@ -150,7 +153,8 @@ export class ModelRuntime {
150
153
  return;
151
154
  }
152
155
  try {
153
- this.models.setProvider(this.withLocalCatalog(composeModelProvider(providerId, base, this.config, extension)));
156
+ // Discovery is metadata beneath explicit user configuration and extension model definitions.
157
+ this.models.setProvider(composeModelProvider(providerId, base ? this.withLocalCatalog(base) : undefined, this.config, extension));
154
158
  this.compositionErrors.delete(providerId);
155
159
  }
156
160
  catch (error) {
@@ -179,9 +183,15 @@ export class ModelRuntime {
179
183
  this.snapshot = {
180
184
  ...this.snapshot,
181
185
  all,
182
- available: all.filter((model) => this.snapshot.configuredProviders.has(model.provider)),
186
+ available: all.filter((model) => this.snapshot.configuredProviders.has(model.provider) && this.isCatalogModelAvailable(model)),
183
187
  };
184
188
  }
189
+ isCatalogModelAvailable(model) {
190
+ const config = this.config.getProvider(model.provider);
191
+ return (!!config?.models?.some((row) => row.id === model.id) ||
192
+ !!this.extensionProviders.get(model.provider)?.models?.some((row) => row.id === model.id) ||
193
+ this.overlay.isAvailable(model));
194
+ }
185
195
  async listStoredProviderIds() {
186
196
  const credentials = await this.credentials.list();
187
197
  return new Set(credentials.map((entry) => entry.providerId));
@@ -217,7 +227,7 @@ export class ModelRuntime {
217
227
  const configuredProviders = this.configuredProviderIds(storedProviders);
218
228
  this.snapshot = {
219
229
  all: [...this.models.getModels()],
220
- available: available.filter((model) => configuredProviders.has(model.provider)),
230
+ available: available.filter((model) => configuredProviders.has(model.provider) && this.isCatalogModelAvailable(model)),
221
231
  configuredProviders,
222
232
  storedProviders,
223
233
  auth,
@@ -267,7 +277,7 @@ export class ModelRuntime {
267
277
  return this.snapshot.available.filter((model) => model.provider === providerId);
268
278
  }
269
279
  try {
270
- return await this.models.getAvailable(providerId);
280
+ return (await this.models.getAvailable(providerId)).filter((model) => this.isCatalogModelAvailable(model));
271
281
  }
272
282
  catch (error) {
273
283
  this.availabilityError = error instanceof Error ? error.message : String(error);
@@ -337,7 +347,7 @@ export class ModelRuntime {
337
347
  auth,
338
348
  configuredProviders,
339
349
  storedProviders,
340
- available: this.snapshot.all.filter((model) => configuredProviders.has(model.provider)),
350
+ available: this.snapshot.all.filter((model) => configuredProviders.has(model.provider) && this.isCatalogModelAvailable(model)),
341
351
  };
342
352
  await this.refresh({ allowNetwork: options?.allowNetwork ?? this.allowModelNetwork });
343
353
  }
@@ -471,6 +481,8 @@ export class ModelRuntime {
471
481
  errors: new Map(),
472
482
  };
473
483
  const errors = new Map(result.errors);
484
+ for (const [id, error] of Object.entries(official?.errors ?? {}))
485
+ errors.set(id, new Error(error));
474
486
  for (const entry of live) {
475
487
  if (entry.status === "error" || entry.status === "timeout") {
476
488
  errors.set(entry.providerId, new Error(entry.error ?? entry.status));
@@ -496,6 +508,28 @@ export class ModelRuntime {
496
508
  getLastCatalogRefresh() {
497
509
  return this.lastCatalogRefresh;
498
510
  }
511
+ /** Called after first paint and by the picker; explicit /refresh always bypasses this gate. */
512
+ async refreshIfStale(options = {}) {
513
+ if (!this.allowModelNetwork || options.signal?.aborted)
514
+ return this.lastCatalogRefresh;
515
+ if (this.catalogRefresh)
516
+ return this.refresh({ allowNetwork: true, signal: options.signal });
517
+ const providers = [...(await this.listStoredProviderIds())].sort();
518
+ const identity = (await Promise.all(providers.map(async (id) => this.scopeFor(id)))).join(":");
519
+ if (identity === this.automaticRefreshIdentity && Date.now() < this.nextAutomaticRefresh)
520
+ return this.lastCatalogRefresh;
521
+ this.automaticRefreshIdentity = identity;
522
+ // Bound retries on a broken source; a successful refresh extends the deadline to one hour.
523
+ this.nextAutomaticRefresh = Date.now() + 60_000;
524
+ const result = await this.refresh({ allowNetwork: true, signal: options.signal });
525
+ if (!result.aborted && !result.errors.size)
526
+ this.nextAutomaticRefresh = Date.now() + 3_600_000;
527
+ return result;
528
+ }
529
+ async scopeFor(providerId) {
530
+ const provider = this.models.getProvider(providerId) ?? this.defaultBuiltins.get(providerId);
531
+ return catalogCacheScope(providerId, provider?.baseUrl ?? "", await this.credentials.read(providerId));
532
+ }
499
533
  persistUserModels(rows) {
500
534
  this.userModels.upsert(rows);
501
535
  this.overlay.setUser(this.userModelsForStoredProviders(this.snapshot.storedProviders));
@@ -529,7 +563,8 @@ export class ModelRuntime {
529
563
  const results = [];
530
564
  for (const providerId of LIVE_LIST_PROVIDER_IDS) {
531
565
  const stored = storedProviders.has(providerId) ? await this.modelsStore.read(providerId) : undefined;
532
- if (stored?.models)
566
+ const valid = (!stored?.scope && providerId !== "openai-codex") || stored?.scope === (await this.scopeFor(providerId));
567
+ if (valid && stored?.models)
533
568
  this.overlay.setLive(providerId, stored.models);
534
569
  else
535
570
  this.overlay.setLive(providerId, []);
@@ -560,8 +595,11 @@ export class ModelRuntime {
560
595
  };
561
596
  }
562
597
  const stored = await this.modelsStore.read(providerId);
563
- if (stored?.models)
598
+ const initialScope = await this.scopeFor(providerId);
599
+ if (stored?.models && (stored.scope === initialScope || (!stored.scope && providerId !== "openai-codex")))
564
600
  this.overlay.setLive(providerId, stored.models);
601
+ else
602
+ this.overlay.setLive(providerId, []);
565
603
  if (signal?.aborted) {
566
604
  return {
567
605
  providerId,
@@ -611,7 +649,9 @@ export class ModelRuntime {
611
649
  incomplete: [],
612
650
  };
613
651
  }
652
+ const requestScope = catalogCacheScope(providerId, baseUrl, credential);
614
653
  const result = await fetchLiveProviderModels({
654
+ discoveryClientVersion: stored?.scope === requestScope ? stored.discoveryClientVersion : undefined,
615
655
  providerId,
616
656
  baseUrl,
617
657
  bakedIn,
@@ -619,11 +659,23 @@ export class ModelRuntime {
619
659
  knownKeys: known,
620
660
  signal,
621
661
  });
662
+ // A login/logout during the request must not install the previous account's result.
663
+ const accountChanged = (await this.scopeFor(providerId)) !== requestScope;
664
+ if (accountChanged)
665
+ this.overlay.setLive(providerId, []);
666
+ if (signal?.aborted || accountChanged) {
667
+ return { ...result, status: "skipped", discoveries: [], incomplete: [] };
668
+ }
622
669
  if (result.status === "ok") {
623
670
  const overlayModels = buildLiveOverlay(bakedIn, result.discoveries);
624
671
  this.overlay.setLive(providerId, overlayModels);
625
672
  try {
626
- await this.modelsStore.write(providerId, { models: overlayModels, checkedAt: Date.now() });
673
+ await this.modelsStore.write(providerId, {
674
+ models: overlayModels,
675
+ checkedAt: Date.now(),
676
+ scope: requestScope,
677
+ discoveryClientVersion: result.discoveryClientVersion,
678
+ });
627
679
  }
628
680
  catch {
629
681
  // Store write is best-effort.
@@ -634,14 +686,14 @@ export class ModelRuntime {
634
686
  return results;
635
687
  }
636
688
  async refreshCredentialFor(providerId) {
637
- const stored = await this.credentials.read(providerId);
638
689
  // Same lock + persist path as chat. Returning stored OAuth here skips
639
690
  // refresh and sends expired SuperGrok tokens to GET /models (HTTP 403).
640
691
  const resolved = await this.getAuth(providerId);
692
+ const stored = await this.credentials.read(providerId);
693
+ if (stored?.type === "oauth")
694
+ return { ...stored, access: resolved?.auth.apiKey ?? stored.access };
641
695
  if (resolved?.auth.apiKey)
642
696
  return { type: "api_key", key: resolved.auth.apiKey, env: resolved.env };
643
- if (stored?.type === "oauth" && stored.access)
644
- return stored;
645
697
  if (stored?.type === "api_key" && stored.key)
646
698
  return stored;
647
699
  return undefined;
@@ -676,7 +728,7 @@ export class ModelRuntime {
676
728
  ...this.snapshot,
677
729
  auth,
678
730
  configuredProviders,
679
- available: this.snapshot.all.filter((model) => configuredProviders.has(model.provider)),
731
+ available: this.snapshot.all.filter((model) => configuredProviders.has(model.provider) && this.isCatalogModelAvailable(model)),
680
732
  };
681
733
  }
682
734
  void this.refresh({ allowNetwork: false });