apex-code 0.0.1-alpha.7 → 0.0.1-alpha.8

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 (256) hide show
  1. package/CHANGELOG.md +146 -0
  2. package/README.md +5 -0
  3. package/dist/cli/args.d.ts +3 -1
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +15 -0
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/concurrent-session.d.ts +3 -0
  8. package/dist/cli/concurrent-session.d.ts.map +1 -0
  9. package/dist/cli/concurrent-session.js +13 -0
  10. package/dist/cli/concurrent-session.js.map +1 -0
  11. package/dist/cli.d.ts.map +1 -1
  12. package/dist/cli.js +1 -2
  13. package/dist/cli.js.map +1 -1
  14. package/dist/core/agent-session-services.d.ts.map +1 -1
  15. package/dist/core/agent-session-services.js +20 -0
  16. package/dist/core/agent-session-services.js.map +1 -1
  17. package/dist/core/agent-session.d.ts +15 -2
  18. package/dist/core/agent-session.d.ts.map +1 -1
  19. package/dist/core/agent-session.js +9 -6
  20. package/dist/core/agent-session.js.map +1 -1
  21. package/dist/core/auth-storage.d.ts +4 -1
  22. package/dist/core/auth-storage.d.ts.map +1 -1
  23. package/dist/core/auth-storage.js +6 -2
  24. package/dist/core/auth-storage.js.map +1 -1
  25. package/dist/core/bash-executor.d.ts.map +1 -1
  26. package/dist/core/bash-executor.js +10 -9
  27. package/dist/core/bash-executor.js.map +1 -1
  28. package/dist/core/experimental.d.ts +4 -0
  29. package/dist/core/experimental.d.ts.map +1 -1
  30. package/dist/core/experimental.js +4 -0
  31. package/dist/core/experimental.js.map +1 -1
  32. package/dist/core/extensions/types.d.ts +4 -0
  33. package/dist/core/extensions/types.d.ts.map +1 -1
  34. package/dist/core/extensions/types.js.map +1 -1
  35. package/dist/core/http-dispatcher.d.ts +1 -2
  36. package/dist/core/http-dispatcher.d.ts.map +1 -1
  37. package/dist/core/http-dispatcher.js +2 -17
  38. package/dist/core/http-dispatcher.js.map +1 -1
  39. package/dist/core/http-idle-timeout.d.ts +12 -0
  40. package/dist/core/http-idle-timeout.d.ts.map +1 -0
  41. package/dist/core/http-idle-timeout.js +27 -0
  42. package/dist/core/http-idle-timeout.js.map +1 -0
  43. package/dist/core/keybindings.d.ts +24 -0
  44. package/dist/core/keybindings.d.ts.map +1 -1
  45. package/dist/core/lsp/client.d.ts +2 -0
  46. package/dist/core/lsp/client.d.ts.map +1 -1
  47. package/dist/core/lsp/client.js +27 -2
  48. package/dist/core/lsp/client.js.map +1 -1
  49. package/dist/core/model-config.d.ts +5 -0
  50. package/dist/core/model-config.d.ts.map +1 -1
  51. package/dist/core/model-config.js +1 -0
  52. package/dist/core/model-config.js.map +1 -1
  53. package/dist/core/sandbox/child-entry.d.ts.map +1 -1
  54. package/dist/core/sandbox/child-entry.js +8 -1
  55. package/dist/core/sandbox/child-entry.js.map +1 -1
  56. package/dist/core/sandbox/cli-launch.d.ts +3 -0
  57. package/dist/core/sandbox/cli-launch.d.ts.map +1 -1
  58. package/dist/core/sandbox/cli-launch.js +25 -4
  59. package/dist/core/sandbox/cli-launch.js.map +1 -1
  60. package/dist/core/sandbox/cli-supervisor.d.ts.map +1 -1
  61. package/dist/core/sandbox/cli-supervisor.js +53 -13
  62. package/dist/core/sandbox/cli-supervisor.js.map +1 -1
  63. package/dist/core/sandbox/linux-backend.d.ts.map +1 -1
  64. package/dist/core/sandbox/linux-backend.js +15 -0
  65. package/dist/core/sandbox/linux-backend.js.map +1 -1
  66. package/dist/core/sandbox/macos-backend.d.ts.map +1 -1
  67. package/dist/core/sandbox/macos-backend.js +24 -1
  68. package/dist/core/sandbox/macos-backend.js.map +1 -1
  69. package/dist/core/sandbox/rpc/credential-client.d.ts +20 -0
  70. package/dist/core/sandbox/rpc/credential-client.d.ts.map +1 -0
  71. package/dist/core/sandbox/rpc/credential-client.js +170 -0
  72. package/dist/core/sandbox/rpc/credential-client.js.map +1 -0
  73. package/dist/core/sandbox/rpc/credential-proxy.d.ts +44 -0
  74. package/dist/core/sandbox/rpc/credential-proxy.d.ts.map +1 -0
  75. package/dist/core/sandbox/rpc/credential-proxy.js +381 -0
  76. package/dist/core/sandbox/rpc/credential-proxy.js.map +1 -0
  77. package/dist/core/sandbox/supervisor.d.ts +11 -0
  78. package/dist/core/sandbox/supervisor.d.ts.map +1 -1
  79. package/dist/core/sandbox/supervisor.js.map +1 -1
  80. package/dist/core/sandbox/terminal-size.d.ts +40 -0
  81. package/dist/core/sandbox/terminal-size.d.ts.map +1 -0
  82. package/dist/core/sandbox/terminal-size.js +105 -0
  83. package/dist/core/sandbox/terminal-size.js.map +1 -0
  84. package/dist/core/sdk.d.ts +6 -3
  85. package/dist/core/sdk.d.ts.map +1 -1
  86. package/dist/core/sdk.js +16 -4
  87. package/dist/core/sdk.js.map +1 -1
  88. package/dist/core/session-lease.d.ts +31 -0
  89. package/dist/core/session-lease.d.ts.map +1 -0
  90. package/dist/core/session-lease.js +138 -0
  91. package/dist/core/session-lease.js.map +1 -0
  92. package/dist/core/session-manager.d.ts +5 -0
  93. package/dist/core/session-manager.d.ts.map +1 -1
  94. package/dist/core/session-manager.js +3 -3
  95. package/dist/core/session-manager.js.map +1 -1
  96. package/dist/core/settings-manager.d.ts +26 -0
  97. package/dist/core/settings-manager.d.ts.map +1 -1
  98. package/dist/core/settings-manager.js +42 -3
  99. package/dist/core/settings-manager.js.map +1 -1
  100. package/dist/core/slash-commands.d.ts.map +1 -1
  101. package/dist/core/slash-commands.js +2 -0
  102. package/dist/core/slash-commands.js.map +1 -1
  103. package/dist/core/system-prompt.d.ts +2 -2
  104. package/dist/core/system-prompt.d.ts.map +1 -1
  105. package/dist/core/system-prompt.js +47 -62
  106. package/dist/core/system-prompt.js.map +1 -1
  107. package/dist/core/tools/bash.d.ts.map +1 -1
  108. package/dist/core/tools/bash.js +2 -0
  109. package/dist/core/tools/bash.js.map +1 -1
  110. package/dist/core/tools/delegate.d.ts.map +1 -1
  111. package/dist/core/tools/delegate.js +24 -0
  112. package/dist/core/tools/delegate.js.map +1 -1
  113. package/dist/core/tools/edit.d.ts.map +1 -1
  114. package/dist/core/tools/edit.js +2 -0
  115. package/dist/core/tools/edit.js.map +1 -1
  116. package/dist/core/tools/find.d.ts.map +1 -1
  117. package/dist/core/tools/find.js +1 -1
  118. package/dist/core/tools/find.js.map +1 -1
  119. package/dist/core/tools/grep.d.ts +2 -0
  120. package/dist/core/tools/grep.d.ts.map +1 -1
  121. package/dist/core/tools/grep.js +35 -10
  122. package/dist/core/tools/grep.js.map +1 -1
  123. package/dist/core/tools/ls.d.ts +2 -0
  124. package/dist/core/tools/ls.d.ts.map +1 -1
  125. package/dist/core/tools/ls.js +5 -1
  126. package/dist/core/tools/ls.js.map +1 -1
  127. package/dist/core/tools/read.d.ts.map +1 -1
  128. package/dist/core/tools/read.js +2 -0
  129. package/dist/core/tools/read.js.map +1 -1
  130. package/dist/core/tools/web-search-exa.d.ts +33 -0
  131. package/dist/core/tools/web-search-exa.d.ts.map +1 -0
  132. package/dist/core/tools/web-search-exa.js +128 -0
  133. package/dist/core/tools/web-search-exa.js.map +1 -0
  134. package/dist/core/tools/write.d.ts.map +1 -1
  135. package/dist/core/tools/write.js +2 -0
  136. package/dist/core/tools/write.js.map +1 -1
  137. package/dist/core/web-search-provider.d.ts +64 -0
  138. package/dist/core/web-search-provider.d.ts.map +1 -0
  139. package/dist/core/web-search-provider.js +149 -0
  140. package/dist/core/web-search-provider.js.map +1 -0
  141. package/dist/index.d.ts +1 -1
  142. package/dist/index.d.ts.map +1 -1
  143. package/dist/index.js.map +1 -1
  144. package/dist/main.d.ts +1 -0
  145. package/dist/main.d.ts.map +1 -1
  146. package/dist/main.js +20 -1
  147. package/dist/main.js.map +1 -1
  148. package/dist/modes/interactive/components/custom-editor.d.ts +23 -0
  149. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  150. package/dist/modes/interactive/components/custom-editor.js +105 -2
  151. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  152. package/dist/modes/interactive/components/dynamic-border.d.ts +7 -1
  153. package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
  154. package/dist/modes/interactive/components/dynamic-border.js +11 -2
  155. package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
  156. package/dist/modes/interactive/components/extension-selector.d.ts +4 -0
  157. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  158. package/dist/modes/interactive/components/extension-selector.js +24 -6
  159. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  160. package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
  161. package/dist/modes/interactive/components/first-time-setup.js +6 -3
  162. package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
  163. package/dist/modes/interactive/components/first-use-hints.d.ts +8 -0
  164. package/dist/modes/interactive/components/first-use-hints.d.ts.map +1 -0
  165. package/dist/modes/interactive/components/first-use-hints.js +22 -0
  166. package/dist/modes/interactive/components/first-use-hints.js.map +1 -0
  167. package/dist/modes/interactive/components/footer.d.ts +12 -0
  168. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  169. package/dist/modes/interactive/components/footer.js +128 -9
  170. package/dist/modes/interactive/components/footer.js.map +1 -1
  171. package/dist/modes/interactive/components/index.d.ts +1 -0
  172. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  173. package/dist/modes/interactive/components/index.js +1 -0
  174. package/dist/modes/interactive/components/index.js.map +1 -1
  175. package/dist/modes/interactive/components/model-selector.d.ts +2 -0
  176. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  177. package/dist/modes/interactive/components/model-selector.js +20 -5
  178. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  179. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  180. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  181. package/dist/modes/interactive/components/settings-selector.js +10 -0
  182. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  183. package/dist/modes/interactive/components/splash-header.d.ts +29 -4
  184. package/dist/modes/interactive/components/splash-header.d.ts.map +1 -1
  185. package/dist/modes/interactive/components/splash-header.js +58 -23
  186. package/dist/modes/interactive/components/splash-header.js.map +1 -1
  187. package/dist/modes/interactive/components/tool-execution.d.ts +14 -0
  188. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  189. package/dist/modes/interactive/components/tool-execution.js +113 -35
  190. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  191. package/dist/modes/interactive/components/tool-panel.d.ts +30 -0
  192. package/dist/modes/interactive/components/tool-panel.d.ts.map +1 -0
  193. package/dist/modes/interactive/components/tool-panel.js +130 -0
  194. package/dist/modes/interactive/components/tool-panel.js.map +1 -0
  195. package/dist/modes/interactive/interactive-mode.d.ts +44 -9
  196. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  197. package/dist/modes/interactive/interactive-mode.js +282 -70
  198. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  199. package/dist/modes/interactive/model-catalog-refresh.d.ts +7 -0
  200. package/dist/modes/interactive/model-catalog-refresh.d.ts.map +1 -0
  201. package/dist/modes/interactive/model-catalog-refresh.js +34 -0
  202. package/dist/modes/interactive/model-catalog-refresh.js.map +1 -0
  203. package/dist/modes/interactive/theme/apex.json +62 -63
  204. package/dist/modes/interactive/theme/dark.json +2 -0
  205. package/dist/modes/interactive/theme/light.json +2 -0
  206. package/dist/modes/interactive/theme/theme-controller.d.ts +10 -2
  207. package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
  208. package/dist/modes/interactive/theme/theme-controller.js +25 -11
  209. package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
  210. package/dist/modes/interactive/theme/theme-schema.json +9 -1
  211. package/dist/modes/interactive/theme/theme.d.ts +14 -3
  212. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  213. package/dist/modes/interactive/theme/theme.js +55 -6
  214. package/dist/modes/interactive/theme/theme.js.map +1 -1
  215. package/dist/modes/json-event.d.ts +4 -1
  216. package/dist/modes/json-event.d.ts.map +1 -1
  217. package/dist/modes/json-event.js +5 -2
  218. package/dist/modes/json-event.js.map +1 -1
  219. package/dist/server/create-harness.d.ts.map +1 -1
  220. package/dist/server/create-harness.js +2 -0
  221. package/dist/server/create-harness.js.map +1 -1
  222. package/dist/themes/apex-logo.d.ts +36 -16
  223. package/dist/themes/apex-logo.d.ts.map +1 -1
  224. package/dist/themes/apex-logo.js +32 -28
  225. package/dist/themes/apex-logo.js.map +1 -1
  226. package/dist/utils/tools-manager.d.ts +10 -1
  227. package/dist/utils/tools-manager.d.ts.map +1 -1
  228. package/dist/utils/tools-manager.js +15 -20
  229. package/dist/utils/tools-manager.js.map +1 -1
  230. package/docs/compaction.md +3 -3
  231. package/docs/custom-provider.md +1 -1
  232. package/docs/environment-variables.md +1 -0
  233. package/docs/extensions.md +4 -0
  234. package/docs/json.md +6 -4
  235. package/docs/keybindings.md +8 -2
  236. package/docs/rpc.md +15 -4
  237. package/docs/sdk.md +9 -0
  238. package/docs/settings.md +63 -0
  239. package/docs/terminal-setup.md +23 -2
  240. package/docs/themes.md +24 -3
  241. package/docs/tui.md +2 -2
  242. package/docs/usage.md +3 -2
  243. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  244. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  245. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  246. package/examples/extensions/gondolin/package-lock.json +2 -2
  247. package/examples/extensions/gondolin/package.json +1 -1
  248. package/examples/extensions/sandbox/package-lock.json +2 -2
  249. package/examples/extensions/sandbox/package.json +1 -1
  250. package/examples/extensions/subagent/README.md +2 -0
  251. package/examples/extensions/subagent/agents.ts +40 -9
  252. package/examples/extensions/subagent/index.ts +21 -3
  253. package/examples/extensions/with-deps/package-lock.json +2 -2
  254. package/examples/extensions/with-deps/package.json +1 -1
  255. package/npm-shrinkwrap.json +27 -78
  256. package/package.json +6 -6
@@ -16,6 +16,7 @@ export declare class ModelSelectorComponent extends Container implements Focusab
16
16
  set focused(value: boolean);
17
17
  private headerContainer;
18
18
  private listContainer;
19
+ private footerContainer;
19
20
  private allModels;
20
21
  private scopedModelItems;
21
22
  private activeModels;
@@ -51,6 +52,7 @@ export declare class ModelSelectorComponent extends Container implements Focusab
51
52
  /** Escape returns to the provider list whenever there is a provider list worth returning to. */
52
53
  private canGoBack;
53
54
  private renderHeader;
55
+ private renderFooter;
54
56
  private getScopeText;
55
57
  private getHintText;
56
58
  private setScope;
@@ -1 +1 @@
1
- {"version":3,"file":"model-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/model-selector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAkB,MAAM,uBAAuB,CAAC;AACnE,OAAO,EACN,SAAS,EACT,KAAK,SAAS,EAGd,KAAK,EAKL,KAAK,GAAG,EACR,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAYzE,UAAU,eAAe;IACxB,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAgBD;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,SAAU,YAAW,SAAS;IACzE,OAAO,CAAC,WAAW,CAAQ;IAG3B,OAAO,CAAC,QAAQ,CAAS;IACzB,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IACD,OAAO,CAAC,eAAe,CAAY;IACnC,OAAO,CAAC,aAAa,CAAY;IACjC,OAAO,CAAC,SAAS,CAAmB;IACpC,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,YAAY,CAAmB;IACvC,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,YAAY,CAAC,CAAa;IAClC,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,gBAAgB,CAA8B;IACtD,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,oBAAoB,CAAgC;IAC5D,OAAO,CAAC,oBAAoB,CAAS;IACrC,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,YAAY,CAAiC;IACrD,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,IAAI,CAA+B;IAC3C,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAyB;IAChE,OAAO,CAAC,cAAc,CAAC,CAAgC;IACvD,OAAO,CAAC,MAAM,CAAS;IAEvB,YACC,GAAG,EAAE,GAAG,EACR,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,SAAS,EACpC,eAAe,EAAE,eAAe,EAChC,YAAY,EAAE,YAAY,EAC1B,YAAY,EAAE,aAAa,CAAC,eAAe,CAAC,EAC5C,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,IAAI,EACrC,QAAQ,EAAE,MAAM,IAAI,EACpB,kBAAkB,CAAC,EAAE,MAAM,EAiD3B;IAED,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,sBAAsB;YAmBhB,aAAa;IA0C3B,OAAO,IAAI,IAAI,CAKd;IAED,OAAO,CAAC,UAAU;IAalB,OAAO,CAAC,WAAW;IA2BnB,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,OAAO;IAUf,OAAO,CAAC,WAAW;IAInB,gGAAgG;IAChG,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,YAAY;IAqBpB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,QAAQ;IAUhB,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,SAAS;IAejB,OAAO,CAAC,UAAU;IA8ClB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAuCjC;IAED,OAAO,CAAC,gBAAgB;IAUxB,OAAO,CAAC,YAAY;IAOpB,cAAc,IAAI,KAAK,CAEtB;CACD","sourcesContent":["import { type Model, modelsAreEqual } from \"@earendil-works/pi-ai\";\nimport {\n\tContainer,\n\ttype Focusable,\n\tfuzzyFilter,\n\tgetKeybindings,\n\tInput,\n\tKey,\n\tmatchesKey,\n\tSpacer,\n\tText,\n\ttype TUI,\n} from \"@earendil-works/pi-tui\";\nimport type { ModelRuntime } from \"../../../core/model-runtime.ts\";\nimport type { SettingsManager } from \"../../../core/settings-manager.ts\";\nimport { getModelSelectorSearchText } from \"../model-search.ts\";\nimport { theme } from \"../theme/theme.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\nimport { keyHint, rawKeyHint } from \"./keybinding-hints.ts\";\n\ninterface ModelItem {\n\tprovider: string;\n\tid: string;\n\tmodel: Model<any>;\n}\n\ninterface ScopedModelItem {\n\tmodel: Model<any>;\n\tthinkingLevel?: string;\n}\n\ntype ModelScope = \"all\" | \"scoped\";\n\n/**\n * Which of the two steps the selector is on. `provider: null` lists every provider's models at once,\n * which is how a `/model <search>` invocation and a single-provider setup both open.\n */\ntype Step = { kind: \"providers\" } | { kind: \"models\"; provider: string | null };\n\ntype Row =\n\t| { kind: \"provider\"; provider: string; count: number; hasCurrent: boolean }\n\t| { kind: \"model\"; item: ModelItem };\n\nconst MAX_VISIBLE_ROWS = 10;\n\n/**\n * Component that renders a two-step model selector: provider first, then that provider's models.\n */\nexport class ModelSelectorComponent extends Container implements Focusable {\n\tprivate searchInput: Input;\n\n\t// Focusable implementation - propagate to searchInput for IME cursor positioning\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.searchInput.focused = value;\n\t}\n\tprivate headerContainer: Container;\n\tprivate listContainer: Container;\n\tprivate allModels: ModelItem[] = [];\n\tprivate scopedModelItems: ModelItem[] = [];\n\tprivate activeModels: ModelItem[] = [];\n\tprivate rows: Row[] = [];\n\tprivate filteredRows: Row[] = [];\n\tprivate selectedIndex: number = 0;\n\tprivate currentModel?: Model<any>;\n\tprivate settingsManager: SettingsManager;\n\tprivate modelRuntime: ModelRuntime;\n\tprivate onSelectCallback: (model: Model<any>) => void;\n\tprivate onCancelCallback: () => void;\n\tprivate errorMessage?: string;\n\tprivate refreshStatusMessage = \"Refreshing model catalogs…\";\n\tprivate refreshStatusSuccess = false;\n\tprivate tui: TUI;\n\tprivate scopedModels: ReadonlyArray<ScopedModelItem>;\n\tprivate scope: ModelScope = \"all\";\n\tprivate step: Step = { kind: \"providers\" };\n\tprivate readonly refreshAbortController = new AbortController();\n\tprivate refreshTimeout?: ReturnType<typeof setTimeout>;\n\tprivate closed = false;\n\n\tconstructor(\n\t\ttui: TUI,\n\t\tcurrentModel: Model<any> | undefined,\n\t\tsettingsManager: SettingsManager,\n\t\tmodelRuntime: ModelRuntime,\n\t\tscopedModels: ReadonlyArray<ScopedModelItem>,\n\t\tonSelect: (model: Model<any>) => void,\n\t\tonCancel: () => void,\n\t\tinitialSearchInput?: string,\n\t) {\n\t\tsuper();\n\n\t\tthis.tui = tui;\n\t\tthis.currentModel = currentModel;\n\t\tthis.settingsManager = settingsManager;\n\t\tthis.modelRuntime = modelRuntime;\n\t\tthis.scopedModels = scopedModels;\n\t\tthis.scope = scopedModels.length > 0 ? \"scoped\" : \"all\";\n\t\tthis.onSelectCallback = onSelect;\n\t\tthis.onCancelCallback = onCancel;\n\n\t\t// Add top border\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\tthis.headerContainer = new Container();\n\t\tthis.addChild(this.headerContainer);\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create search input\n\t\tthis.searchInput = new Input();\n\t\tif (initialSearchInput) {\n\t\t\tthis.searchInput.setValue(initialSearchInput);\n\t\t}\n\t\tthis.searchInput.onSubmit = () => this.confirmSelection();\n\t\tthis.addChild(this.searchInput);\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create list container\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Add bottom border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Render the current snapshot immediately, then refresh in the background.\n\t\tthis.loadModelsFromSnapshot();\n\t\tthis.step = this.initialStep(initialSearchInput);\n\t\tthis.rebuildRows();\n\t\tthis.selectedIndex = this.defaultSelectedIndex();\n\t\tthis.renderHeader();\n\t\tthis.filterRows(this.searchInput.getValue());\n\t\tthis.tui.requestRender();\n\t\tvoid this.refreshModels();\n\t}\n\n\tprivate initialStep(initialSearchInput: string | undefined): Step {\n\t\tconst providers = this.activeProviders();\n\t\tif (initialSearchInput) return { kind: \"models\", provider: null };\n\t\tif (providers.length <= 1) return { kind: \"models\", provider: providers[0] ?? null };\n\t\treturn { kind: \"providers\" };\n\t}\n\n\tprivate activeProviders(): string[] {\n\t\treturn [...new Set(this.activeModels.map((item) => item.provider))];\n\t}\n\n\tprivate loadModelsFromSnapshot(): void {\n\t\tconst models = this.modelRuntime.getAvailableSnapshot().map((model: Model<any>) => ({\n\t\t\tprovider: model.provider,\n\t\t\tid: model.id,\n\t\t\tmodel,\n\t\t}));\n\t\tthis.allModels = this.sortModels(models);\n\t\tthis.scopedModels = this.scopedModels.map((scoped) => {\n\t\t\tconst refreshed = this.modelRuntime.getModel(scoped.model.provider, scoped.model.id);\n\t\t\treturn refreshed ? { ...scoped, model: refreshed } : scoped;\n\t\t});\n\t\tthis.scopedModelItems = this.scopedModels.map((scoped) => ({\n\t\t\tprovider: scoped.model.provider,\n\t\t\tid: scoped.model.id,\n\t\t\tmodel: scoped.model,\n\t\t}));\n\t\tthis.activeModels = this.scope === \"scoped\" ? this.scopedModelItems : this.allModels;\n\t}\n\n\tprivate async refreshModels(): Promise<void> {\n\t\tconst timeoutMs = 15_000;\n\t\tlet timedOut = false;\n\t\tthis.refreshTimeout = setTimeout(() => {\n\t\t\ttimedOut = true;\n\t\t\tthis.refreshAbortController.abort();\n\t\t}, timeoutMs);\n\t\ttry {\n\t\t\tconst result = await this.modelRuntime.refresh({ signal: this.refreshAbortController.signal });\n\t\t\tif (this.closed) return;\n\t\t\tthis.refreshStatusMessage = \"\";\n\t\t\tif (result.aborted && timedOut) {\n\t\t\t\tthis.errorMessage = \"Model refresh timed out; showing cached models.\";\n\t\t\t} else if (result.errors.size === 1) {\n\t\t\t\tthis.errorMessage = `Could not refresh ${result.errors.keys().next().value}; showing cached models.`;\n\t\t\t} else if (result.errors.size > 1) {\n\t\t\t\tthis.errorMessage = `Could not refresh ${result.errors.size} model catalogs (${[...result.errors.keys()].join(\", \")}); showing cached models.`;\n\t\t\t} else {\n\t\t\t\tthis.errorMessage = this.modelRuntime.getError();\n\t\t\t\tif (!this.errorMessage) {\n\t\t\t\t\tthis.refreshStatusMessage = \"Model catalogs refreshed.\";\n\t\t\t\t\tthis.refreshStatusSuccess = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.loadModelsFromSnapshot();\n\t\t\tthis.rebuildRows();\n\t\t\tthis.renderHeader();\n\t\t\tthis.filterRows(this.searchInput.getValue());\n\t\t\tthis.tui.requestRender();\n\t\t} catch (error) {\n\t\t\tif (this.closed) return;\n\t\t\tthis.refreshStatusMessage = \"\";\n\t\t\tthis.errorMessage = timedOut\n\t\t\t\t? \"Model refresh timed out; showing cached models.\"\n\t\t\t\t: `Could not refresh model catalogs: ${error instanceof Error ? error.message : String(error)}`;\n\t\t\tthis.updateList();\n\t\t\tthis.tui.requestRender();\n\t\t} finally {\n\t\t\tif (this.refreshTimeout) clearTimeout(this.refreshTimeout);\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tif (this.closed) return;\n\t\tthis.closed = true;\n\t\tif (this.refreshTimeout) clearTimeout(this.refreshTimeout);\n\t\tthis.refreshAbortController.abort();\n\t}\n\n\tprivate sortModels(models: ModelItem[]): ModelItem[] {\n\t\tconst sorted = [...models];\n\t\t// Sort: current model first, then by provider\n\t\tsorted.sort((a, b) => {\n\t\t\tconst aIsCurrent = modelsAreEqual(this.currentModel, a.model);\n\t\t\tconst bIsCurrent = modelsAreEqual(this.currentModel, b.model);\n\t\t\tif (aIsCurrent && !bIsCurrent) return -1;\n\t\t\tif (!aIsCurrent && bIsCurrent) return 1;\n\t\t\treturn a.provider.localeCompare(b.provider);\n\t\t});\n\t\treturn sorted;\n\t}\n\n\tprivate rebuildRows(): void {\n\t\tif (this.step.kind === \"providers\") {\n\t\t\tconst byProvider = new Map<string, ModelItem[]>();\n\t\t\tfor (const item of this.activeModels) {\n\t\t\t\tconst existing = byProvider.get(item.provider);\n\t\t\t\tif (existing) existing.push(item);\n\t\t\t\telse byProvider.set(item.provider, [item]);\n\t\t\t}\n\t\t\tconst providerRows: Row[] = [...byProvider].map(([provider, items]) => ({\n\t\t\t\tkind: \"provider\",\n\t\t\t\tprovider,\n\t\t\t\tcount: items.length,\n\t\t\t\thasCurrent: items.some((item) => modelsAreEqual(this.currentModel, item.model)),\n\t\t\t}));\n\t\t\tproviderRows.sort((a, b) => {\n\t\t\t\tif (a.kind !== \"provider\" || b.kind !== \"provider\") return 0;\n\t\t\t\tif (a.hasCurrent !== b.hasCurrent) return a.hasCurrent ? -1 : 1;\n\t\t\t\treturn a.provider.localeCompare(b.provider);\n\t\t\t});\n\t\t\tthis.rows = providerRows;\n\t\t\treturn;\n\t\t}\n\t\tconst provider = this.step.provider;\n\t\tconst models = provider === null ? this.activeModels : this.activeModels.filter((m) => m.provider === provider);\n\t\tthis.rows = models.map((item) => ({ kind: \"model\", item }));\n\t}\n\n\tprivate defaultSelectedIndex(): number {\n\t\tconst index = this.rows.findIndex((row) =>\n\t\t\trow.kind === \"provider\" ? row.hasCurrent : modelsAreEqual(this.currentModel, row.item.model),\n\t\t);\n\t\treturn index >= 0 ? index : 0;\n\t}\n\n\tprivate setStep(step: Step): void {\n\t\tthis.step = step;\n\t\tthis.searchInput.setValue(\"\");\n\t\tthis.rebuildRows();\n\t\tthis.selectedIndex = this.defaultSelectedIndex();\n\t\tthis.renderHeader();\n\t\tthis.filterRows(\"\");\n\t\tthis.tui.requestRender();\n\t}\n\n\tprivate isDrilledIn(): boolean {\n\t\treturn this.step.kind === \"models\" && this.step.provider !== null;\n\t}\n\n\t/** Escape returns to the provider list whenever there is a provider list worth returning to. */\n\tprivate canGoBack(): boolean {\n\t\treturn this.step.kind === \"models\" && this.activeProviders().length > 1;\n\t}\n\n\tprivate renderHeader(): void {\n\t\tthis.headerContainer.clear();\n\n\t\tif (this.step.kind === \"providers\") {\n\t\t\tthis.headerContainer.addChild(new Text(theme.bold(theme.fg(\"accent\", \"Select a provider\")), 0, 0));\n\t\t} else {\n\t\t\tconst label = this.step.provider ?? \"All providers\";\n\t\t\tconst crumb = `${theme.bold(theme.fg(\"accent\", label))}${theme.fg(\"muted\", \" › select a model\")}`;\n\t\t\tthis.headerContainer.addChild(new Text(crumb, 0, 0));\n\t\t}\n\n\t\tif (this.scopedModelItems.length > 0) {\n\t\t\tthis.headerContainer.addChild(new Text(this.getScopeText(), 0, 0));\n\t\t} else if (!this.isDrilledIn()) {\n\t\t\tconst hintText = \"Only showing models from configured providers. Use /login to add providers.\";\n\t\t\tthis.headerContainer.addChild(new Text(theme.fg(\"warning\", hintText), 0, 0));\n\t\t}\n\n\t\tthis.headerContainer.addChild(new Text(this.getHintText(), 0, 0));\n\t}\n\n\tprivate getScopeText(): string {\n\t\tconst allText = this.scope === \"all\" ? theme.fg(\"accent\", \"all\") : theme.fg(\"muted\", \"all\");\n\t\tconst scopedText = this.scope === \"scoped\" ? theme.fg(\"accent\", \"scoped\") : theme.fg(\"muted\", \"scoped\");\n\t\treturn `${theme.fg(\"muted\", \"Scope: \")}${allText}${theme.fg(\"muted\", \" | \")}${scopedText}`;\n\t}\n\n\tprivate getHintText(): string {\n\t\tconst hints: string[] = [];\n\t\tif (this.scopedModelItems.length > 0) {\n\t\t\thints.push(keyHint(\"tui.input.tab\", \"scope\") + theme.fg(\"muted\", \" (all/scoped)\"));\n\t\t}\n\t\tif (this.canGoBack()) hints.push(rawKeyHint(\"escape\", \"providers\"), rawKeyHint(\"ctrl+c\", \"close\"));\n\t\telse hints.push(keyHint(\"tui.select.cancel\", \"close\"));\n\t\treturn hints.join(theme.fg(\"muted\", \" · \"));\n\t}\n\n\tprivate setScope(scope: ModelScope): void {\n\t\tif (this.scope === scope) return;\n\t\tthis.scope = scope;\n\t\tthis.activeModels = this.scope === \"scoped\" ? this.scopedModelItems : this.allModels;\n\t\tthis.rebuildRows();\n\t\tthis.selectedIndex = this.defaultSelectedIndex();\n\t\tthis.renderHeader();\n\t\tthis.filterRows(this.searchInput.getValue());\n\t}\n\n\tprivate rowSearchText(row: Row): string {\n\t\tif (row.kind === \"provider\") return row.provider;\n\t\treturn getModelSelectorSearchText({ id: row.item.id, provider: row.item.provider, name: row.item.model.name });\n\t}\n\n\tprivate filterRows(query: string): void {\n\t\tthis.filteredRows = query ? fuzzyFilter(this.rows, query, (row) => this.rowSearchText(row)) : this.rows;\n\t\t// When filtering by a query, move the selector to the top row so the best\n\t\t// match is highlighted. When the query is cleared, keep the current position\n\t\t// clamped to the (restored) list length.\n\t\tthis.selectedIndex = query ? 0 : Math.min(this.selectedIndex, Math.max(0, this.filteredRows.length - 1));\n\t\tthis.updateList();\n\t}\n\n\tprivate renderRow(row: Row, isSelected: boolean): string {\n\t\tconst prefix = isSelected ? theme.fg(\"accent\", \"→ \") : \" \";\n\t\tif (row.kind === \"provider\") {\n\t\t\tconst label = isSelected ? theme.fg(\"accent\", row.provider) : row.provider;\n\t\t\tconst count = theme.fg(\"muted\", ` (${row.count} model${row.count === 1 ? \"\" : \"s\"})`);\n\t\t\tconst checkmark = row.hasCurrent ? theme.fg(\"success\", \" ✓\") : \"\";\n\t\t\treturn `${prefix}${label}${count}${checkmark}`;\n\t\t}\n\t\tconst label = isSelected ? theme.fg(\"accent\", row.item.id) : row.item.id;\n\t\tconst showProvider = this.step.kind === \"models\" && this.step.provider === null;\n\t\tconst badge = showProvider ? ` ${theme.fg(\"muted\", `[${row.item.provider}]`)}` : \"\";\n\t\tconst checkmark = modelsAreEqual(this.currentModel, row.item.model) ? theme.fg(\"success\", \" ✓\") : \"\";\n\t\treturn `${prefix}${label}${badge}${checkmark}`;\n\t}\n\n\tprivate updateList(): void {\n\t\tthis.listContainer.clear();\n\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(MAX_VISIBLE_ROWS / 2), this.filteredRows.length - MAX_VISIBLE_ROWS),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + MAX_VISIBLE_ROWS, this.filteredRows.length);\n\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst row = this.filteredRows[i];\n\t\t\tif (!row) continue;\n\t\t\tthis.listContainer.addChild(new Text(this.renderRow(row, i === this.selectedIndex), 0, 0));\n\t\t}\n\n\t\t// Add scroll indicator if needed\n\t\tif (startIndex > 0 || endIndex < this.filteredRows.length) {\n\t\t\tconst scrollInfo = theme.fg(\"muted\", ` (${this.selectedIndex + 1}/${this.filteredRows.length})`);\n\t\t\tthis.listContainer.addChild(new Text(scrollInfo, 0, 0));\n\t\t}\n\n\t\t// Show error message or \"no results\" if empty\n\t\tif (this.errorMessage) {\n\t\t\t// Show error in red\n\t\t\tconst errorLines = this.errorMessage.split(\"\\n\");\n\t\t\tfor (const line of errorLines) {\n\t\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"error\", line), 0, 0));\n\t\t\t}\n\t\t} else if (this.filteredRows.length === 0) {\n\t\t\tconst empty = this.step.kind === \"providers\" ? \" No matching providers\" : \" No matching models\";\n\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"muted\", empty), 0, 0));\n\t\t} else {\n\t\t\tconst selected = this.filteredRows[this.selectedIndex];\n\t\t\tif (selected?.kind === \"model\") {\n\t\t\t\tthis.listContainer.addChild(new Spacer(1));\n\t\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"muted\", ` Model Name: ${selected.item.model.name}`), 0, 0));\n\t\t\t}\n\t\t}\n\t\tif (this.refreshStatusMessage) {\n\t\t\tthis.listContainer.addChild(new Spacer(1));\n\t\t\tthis.listContainer.addChild(\n\t\t\t\tnew Text(theme.fg(this.refreshStatusSuccess ? \"success\" : \"muted\", ` ${this.refreshStatusMessage}`), 0, 0),\n\t\t\t);\n\t\t}\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getKeybindings();\n\t\tif (kb.matches(keyData, \"tui.input.tab\")) {\n\t\t\tif (this.scopedModelItems.length > 0) {\n\t\t\t\tthis.setScope(this.scope === \"all\" ? \"scoped\" : \"all\");\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\t// Up arrow - wrap to bottom when at top\n\t\tif (kb.matches(keyData, \"tui.select.up\")) {\n\t\t\tif (this.filteredRows.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === 0 ? this.filteredRows.length - 1 : this.selectedIndex - 1;\n\t\t\tthis.updateList();\n\t\t}\n\t\t// Down arrow - wrap to top when at bottom\n\t\telse if (kb.matches(keyData, \"tui.select.down\")) {\n\t\t\tif (this.filteredRows.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === this.filteredRows.length - 1 ? 0 : this.selectedIndex + 1;\n\t\t\tthis.updateList();\n\t\t}\n\t\t// Enter\n\t\telse if (kb.matches(keyData, \"tui.select.confirm\")) {\n\t\t\tthis.confirmSelection();\n\t\t}\n\t\t// Escape or Ctrl+C\n\t\telse if (kb.matches(keyData, \"tui.select.cancel\")) {\n\t\t\t// Escape steps back through the two levels; Ctrl+C always closes.\n\t\t\tif (matchesKey(keyData, Key.escape) && this.canGoBack()) {\n\t\t\t\tthis.setStep({ kind: \"providers\" });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.dispose();\n\t\t\tthis.onCancelCallback();\n\t\t}\n\t\t// Pass everything else to search input\n\t\telse {\n\t\t\tthis.searchInput.handleInput(keyData);\n\t\t\tthis.filterRows(this.searchInput.getValue());\n\t\t}\n\t}\n\n\tprivate confirmSelection(): void {\n\t\tconst row = this.filteredRows[this.selectedIndex];\n\t\tif (!row) return;\n\t\tif (row.kind === \"provider\") {\n\t\t\tthis.setStep({ kind: \"models\", provider: row.provider });\n\t\t\treturn;\n\t\t}\n\t\tthis.handleSelect(row.item.model);\n\t}\n\n\tprivate handleSelect(model: Model<any>): void {\n\t\tthis.dispose();\n\t\t// Save as new default\n\t\tthis.settingsManager.setDefaultModelAndProvider(model.provider, model.id);\n\t\tthis.onSelectCallback(model);\n\t}\n\n\tgetSearchInput(): Input {\n\t\treturn this.searchInput;\n\t}\n}\n"]}
1
+ {"version":3,"file":"model-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/model-selector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAkB,MAAM,uBAAuB,CAAC;AACnE,OAAO,EACN,SAAS,EACT,KAAK,SAAS,EAGd,KAAK,EAKL,KAAK,GAAG,EACR,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAazE,UAAU,eAAe;IACxB,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAgBD;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,SAAU,YAAW,SAAS;IACzE,OAAO,CAAC,WAAW,CAAQ;IAG3B,OAAO,CAAC,QAAQ,CAAS;IACzB,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IACD,OAAO,CAAC,eAAe,CAAY;IACnC,OAAO,CAAC,aAAa,CAAY;IACjC,OAAO,CAAC,eAAe,CAAY;IACnC,OAAO,CAAC,SAAS,CAAmB;IACpC,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,YAAY,CAAmB;IACvC,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,YAAY,CAAC,CAAa;IAClC,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,gBAAgB,CAA8B;IACtD,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,oBAAoB,CAAgC;IAC5D,OAAO,CAAC,oBAAoB,CAAS;IACrC,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,YAAY,CAAiC;IACrD,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,IAAI,CAA+B;IAC3C,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAyB;IAChE,OAAO,CAAC,cAAc,CAAC,CAAgC;IACvD,OAAO,CAAC,MAAM,CAAS;IAEvB,YACC,GAAG,EAAE,GAAG,EACR,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,SAAS,EACpC,eAAe,EAAE,eAAe,EAChC,YAAY,EAAE,YAAY,EAC1B,YAAY,EAAE,aAAa,CAAC,eAAe,CAAC,EAC5C,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,IAAI,EACrC,QAAQ,EAAE,MAAM,IAAI,EACpB,kBAAkB,CAAC,EAAE,MAAM,EAuD3B;IAED,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,sBAAsB;YAmBhB,aAAa;IA0C3B,OAAO,IAAI,IAAI,CAKd;IAED,OAAO,CAAC,UAAU;IAalB,OAAO,CAAC,WAAW;IA2BnB,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,OAAO;IAUf,OAAO,CAAC,WAAW;IAInB,gGAAgG;IAChG,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,YAAY;IAqBpB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,QAAQ;IAUhB,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,SAAS;IAejB,OAAO,CAAC,UAAU;IA8ClB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAuCjC;IAED,OAAO,CAAC,gBAAgB;IAUxB,OAAO,CAAC,YAAY;IAOpB,cAAc,IAAI,KAAK,CAEtB;CACD","sourcesContent":["import { type Model, modelsAreEqual } from \"@earendil-works/pi-ai\";\nimport {\n\tContainer,\n\ttype Focusable,\n\tfuzzyFilter,\n\tgetKeybindings,\n\tInput,\n\tKey,\n\tmatchesKey,\n\tSpacer,\n\tText,\n\ttype TUI,\n} from \"@earendil-works/pi-tui\";\nimport type { ModelRuntime } from \"../../../core/model-runtime.ts\";\nimport type { SettingsManager } from \"../../../core/settings-manager.ts\";\nimport { refreshModelCatalogs } from \"../model-catalog-refresh.ts\";\nimport { getModelSelectorSearchText } from \"../model-search.ts\";\nimport { theme } from \"../theme/theme.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\nimport { keyHint, keyText, rawKeyHint } from \"./keybinding-hints.ts\";\n\ninterface ModelItem {\n\tprovider: string;\n\tid: string;\n\tmodel: Model<any>;\n}\n\ninterface ScopedModelItem {\n\tmodel: Model<any>;\n\tthinkingLevel?: string;\n}\n\ntype ModelScope = \"all\" | \"scoped\";\n\n/**\n * Which of the two steps the selector is on. `provider: null` lists every provider's models at once,\n * which is how a `/model <search>` invocation and a single-provider setup both open.\n */\ntype Step = { kind: \"providers\" } | { kind: \"models\"; provider: string | null };\n\ntype Row =\n\t| { kind: \"provider\"; provider: string; count: number; hasCurrent: boolean }\n\t| { kind: \"model\"; item: ModelItem };\n\nconst MAX_VISIBLE_ROWS = 10;\n\n/**\n * Component that renders a two-step model selector: provider first, then that provider's models.\n */\nexport class ModelSelectorComponent extends Container implements Focusable {\n\tprivate searchInput: Input;\n\n\t// Focusable implementation - propagate to searchInput for IME cursor positioning\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.searchInput.focused = value;\n\t}\n\tprivate headerContainer: Container;\n\tprivate listContainer: Container;\n\tprivate footerContainer: Container;\n\tprivate allModels: ModelItem[] = [];\n\tprivate scopedModelItems: ModelItem[] = [];\n\tprivate activeModels: ModelItem[] = [];\n\tprivate rows: Row[] = [];\n\tprivate filteredRows: Row[] = [];\n\tprivate selectedIndex: number = 0;\n\tprivate currentModel?: Model<any>;\n\tprivate settingsManager: SettingsManager;\n\tprivate modelRuntime: ModelRuntime;\n\tprivate onSelectCallback: (model: Model<any>) => void;\n\tprivate onCancelCallback: () => void;\n\tprivate errorMessage?: string;\n\tprivate refreshStatusMessage = \"Refreshing model catalogs…\";\n\tprivate refreshStatusSuccess = false;\n\tprivate tui: TUI;\n\tprivate scopedModels: ReadonlyArray<ScopedModelItem>;\n\tprivate scope: ModelScope = \"all\";\n\tprivate step: Step = { kind: \"providers\" };\n\tprivate readonly refreshAbortController = new AbortController();\n\tprivate refreshTimeout?: ReturnType<typeof setTimeout>;\n\tprivate closed = false;\n\n\tconstructor(\n\t\ttui: TUI,\n\t\tcurrentModel: Model<any> | undefined,\n\t\tsettingsManager: SettingsManager,\n\t\tmodelRuntime: ModelRuntime,\n\t\tscopedModels: ReadonlyArray<ScopedModelItem>,\n\t\tonSelect: (model: Model<any>) => void,\n\t\tonCancel: () => void,\n\t\tinitialSearchInput?: string,\n\t) {\n\t\tsuper();\n\n\t\tthis.tui = tui;\n\t\tthis.currentModel = currentModel;\n\t\tthis.settingsManager = settingsManager;\n\t\tthis.modelRuntime = modelRuntime;\n\t\tthis.scopedModels = scopedModels;\n\t\tthis.scope = scopedModels.length > 0 ? \"scoped\" : \"all\";\n\t\tthis.onSelectCallback = onSelect;\n\t\tthis.onCancelCallback = onCancel;\n\n\t\t// Add top border\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\tthis.headerContainer = new Container();\n\t\tthis.addChild(this.headerContainer);\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create search input\n\t\tthis.searchInput = new Input();\n\t\tif (initialSearchInput) {\n\t\t\tthis.searchInput.setValue(initialSearchInput);\n\t\t}\n\t\tthis.searchInput.onSubmit = () => this.confirmSelection();\n\t\tthis.addChild(this.searchInput);\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create list container\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Keybindings live under the list, where the eye already is once it has\n\t\t// finished reading. Above the list they were the first thing read and the\n\t\t// least useful.\n\t\tthis.footerContainer = new Container();\n\t\tthis.addChild(this.footerContainer);\n\n\t\t// Add bottom border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Render the current snapshot immediately, then refresh in the background.\n\t\tthis.loadModelsFromSnapshot();\n\t\tthis.step = this.initialStep(initialSearchInput);\n\t\tthis.rebuildRows();\n\t\tthis.selectedIndex = this.defaultSelectedIndex();\n\t\tthis.renderHeader();\n\t\tthis.filterRows(this.searchInput.getValue());\n\t\tthis.tui.requestRender();\n\t\tvoid this.refreshModels();\n\t}\n\n\tprivate initialStep(initialSearchInput: string | undefined): Step {\n\t\tconst providers = this.activeProviders();\n\t\tif (initialSearchInput) return { kind: \"models\", provider: null };\n\t\tif (providers.length <= 1) return { kind: \"models\", provider: providers[0] ?? null };\n\t\treturn { kind: \"providers\" };\n\t}\n\n\tprivate activeProviders(): string[] {\n\t\treturn [...new Set(this.activeModels.map((item) => item.provider))];\n\t}\n\n\tprivate loadModelsFromSnapshot(): void {\n\t\tconst models = this.modelRuntime.getAvailableSnapshot().map((model: Model<any>) => ({\n\t\t\tprovider: model.provider,\n\t\t\tid: model.id,\n\t\t\tmodel,\n\t\t}));\n\t\tthis.allModels = this.sortModels(models);\n\t\tthis.scopedModels = this.scopedModels.map((scoped) => {\n\t\t\tconst refreshed = this.modelRuntime.getModel(scoped.model.provider, scoped.model.id);\n\t\t\treturn refreshed ? { ...scoped, model: refreshed } : scoped;\n\t\t});\n\t\tthis.scopedModelItems = this.scopedModels.map((scoped) => ({\n\t\t\tprovider: scoped.model.provider,\n\t\t\tid: scoped.model.id,\n\t\t\tmodel: scoped.model,\n\t\t}));\n\t\tthis.activeModels = this.scope === \"scoped\" ? this.scopedModelItems : this.allModels;\n\t}\n\n\tprivate async refreshModels(): Promise<void> {\n\t\tconst timeoutMs = 15_000;\n\t\tlet timedOut = false;\n\t\tthis.refreshTimeout = setTimeout(() => {\n\t\t\ttimedOut = true;\n\t\t\tthis.refreshAbortController.abort();\n\t\t}, timeoutMs);\n\t\ttry {\n\t\t\tconst result = await refreshModelCatalogs(this.modelRuntime, this.refreshAbortController.signal);\n\t\t\tif (this.closed) return;\n\t\t\tthis.refreshStatusMessage = \"\";\n\t\t\tif (result.aborted && timedOut) {\n\t\t\t\tthis.errorMessage = \"Model refresh timed out; showing cached models.\";\n\t\t\t} else if (result.errors.size === 1) {\n\t\t\t\tthis.errorMessage = `Could not refresh ${result.errors.keys().next().value}; showing cached models.`;\n\t\t\t} else if (result.errors.size > 1) {\n\t\t\t\tthis.errorMessage = `Could not refresh ${result.errors.size} model catalogs (${[...result.errors.keys()].join(\", \")}); showing cached models.`;\n\t\t\t} else {\n\t\t\t\tthis.errorMessage = this.modelRuntime.getError();\n\t\t\t\tif (!this.errorMessage) {\n\t\t\t\t\tthis.refreshStatusMessage = \"Model catalogs refreshed.\";\n\t\t\t\t\tthis.refreshStatusSuccess = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.loadModelsFromSnapshot();\n\t\t\tthis.rebuildRows();\n\t\t\tthis.renderHeader();\n\t\t\tthis.filterRows(this.searchInput.getValue());\n\t\t\tthis.tui.requestRender();\n\t\t} catch (error) {\n\t\t\tif (this.closed) return;\n\t\t\tthis.refreshStatusMessage = \"\";\n\t\t\tthis.errorMessage = timedOut\n\t\t\t\t? \"Model refresh timed out; showing cached models.\"\n\t\t\t\t: `Could not refresh model catalogs: ${error instanceof Error ? error.message : String(error)}`;\n\t\t\tthis.updateList();\n\t\t\tthis.tui.requestRender();\n\t\t} finally {\n\t\t\tif (this.refreshTimeout) clearTimeout(this.refreshTimeout);\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tif (this.closed) return;\n\t\tthis.closed = true;\n\t\tif (this.refreshTimeout) clearTimeout(this.refreshTimeout);\n\t\tthis.refreshAbortController.abort();\n\t}\n\n\tprivate sortModels(models: ModelItem[]): ModelItem[] {\n\t\tconst sorted = [...models];\n\t\t// Sort: current model first, then by provider\n\t\tsorted.sort((a, b) => {\n\t\t\tconst aIsCurrent = modelsAreEqual(this.currentModel, a.model);\n\t\t\tconst bIsCurrent = modelsAreEqual(this.currentModel, b.model);\n\t\t\tif (aIsCurrent && !bIsCurrent) return -1;\n\t\t\tif (!aIsCurrent && bIsCurrent) return 1;\n\t\t\treturn a.provider.localeCompare(b.provider);\n\t\t});\n\t\treturn sorted;\n\t}\n\n\tprivate rebuildRows(): void {\n\t\tif (this.step.kind === \"providers\") {\n\t\t\tconst byProvider = new Map<string, ModelItem[]>();\n\t\t\tfor (const item of this.activeModels) {\n\t\t\t\tconst existing = byProvider.get(item.provider);\n\t\t\t\tif (existing) existing.push(item);\n\t\t\t\telse byProvider.set(item.provider, [item]);\n\t\t\t}\n\t\t\tconst providerRows: Row[] = [...byProvider].map(([provider, items]) => ({\n\t\t\t\tkind: \"provider\",\n\t\t\t\tprovider,\n\t\t\t\tcount: items.length,\n\t\t\t\thasCurrent: items.some((item) => modelsAreEqual(this.currentModel, item.model)),\n\t\t\t}));\n\t\t\tproviderRows.sort((a, b) => {\n\t\t\t\tif (a.kind !== \"provider\" || b.kind !== \"provider\") return 0;\n\t\t\t\tif (a.hasCurrent !== b.hasCurrent) return a.hasCurrent ? -1 : 1;\n\t\t\t\treturn a.provider.localeCompare(b.provider);\n\t\t\t});\n\t\t\tthis.rows = providerRows;\n\t\t\treturn;\n\t\t}\n\t\tconst provider = this.step.provider;\n\t\tconst models = provider === null ? this.activeModels : this.activeModels.filter((m) => m.provider === provider);\n\t\tthis.rows = models.map((item) => ({ kind: \"model\", item }));\n\t}\n\n\tprivate defaultSelectedIndex(): number {\n\t\tconst index = this.rows.findIndex((row) =>\n\t\t\trow.kind === \"provider\" ? row.hasCurrent : modelsAreEqual(this.currentModel, row.item.model),\n\t\t);\n\t\treturn index >= 0 ? index : 0;\n\t}\n\n\tprivate setStep(step: Step): void {\n\t\tthis.step = step;\n\t\tthis.searchInput.setValue(\"\");\n\t\tthis.rebuildRows();\n\t\tthis.selectedIndex = this.defaultSelectedIndex();\n\t\tthis.renderHeader();\n\t\tthis.filterRows(\"\");\n\t\tthis.tui.requestRender();\n\t}\n\n\tprivate isDrilledIn(): boolean {\n\t\treturn this.step.kind === \"models\" && this.step.provider !== null;\n\t}\n\n\t/** Escape returns to the provider list whenever there is a provider list worth returning to. */\n\tprivate canGoBack(): boolean {\n\t\treturn this.step.kind === \"models\" && this.activeProviders().length > 1;\n\t}\n\n\tprivate renderHeader(): void {\n\t\tthis.headerContainer.clear();\n\n\t\tif (this.step.kind === \"providers\") {\n\t\t\tthis.headerContainer.addChild(new Text(theme.bold(theme.fg(\"accent\", \"Select a provider\")), 0, 0));\n\t\t} else {\n\t\t\tconst label = this.step.provider ?? \"All providers\";\n\t\t\tconst crumb = `${theme.bold(theme.fg(\"accent\", label))}${theme.fg(\"muted\", \" › select a model\")}`;\n\t\t\tthis.headerContainer.addChild(new Text(crumb, 0, 0));\n\t\t}\n\n\t\tif (this.scopedModelItems.length > 0) {\n\t\t\tthis.headerContainer.addChild(new Text(this.getScopeText(), 0, 0));\n\t\t} else if (!this.isDrilledIn()) {\n\t\t\tconst hintText = \"Only showing models from configured providers. Use /login to add providers.\";\n\t\t\tthis.headerContainer.addChild(new Text(theme.fg(\"warning\", hintText), 0, 0));\n\t\t}\n\n\t\tthis.renderFooter();\n\t}\n\n\tprivate renderFooter(): void {\n\t\tthis.footerContainer.clear();\n\t\tthis.footerContainer.addChild(new Text(this.getHintText(), 0, 0));\n\t}\n\n\tprivate getScopeText(): string {\n\t\tconst allText = this.scope === \"all\" ? theme.fg(\"accent\", \"all\") : theme.fg(\"muted\", \"all\");\n\t\tconst scopedText = this.scope === \"scoped\" ? theme.fg(\"accent\", \"scoped\") : theme.fg(\"muted\", \"scoped\");\n\t\treturn `${theme.fg(\"muted\", \"Scope: \")}${allText}${theme.fg(\"muted\", \" | \")}${scopedText}`;\n\t}\n\n\tprivate getHintText(): string {\n\t\tconst hints: string[] = [\n\t\t\t// Both arrows, since either one moves the selection.\n\t\t\trawKeyHint(`${keyText(\"tui.select.up\")}/${keyText(\"tui.select.down\")}`, \"move\"),\n\t\t\tkeyHint(\"tui.select.confirm\", \"select\"),\n\t\t];\n\t\tif (this.scopedModelItems.length > 0) {\n\t\t\thints.push(keyHint(\"tui.input.tab\", \"scope\") + theme.fg(\"muted\", \" (all/scoped)\"));\n\t\t}\n\t\tif (this.canGoBack()) hints.push(rawKeyHint(\"escape\", \"providers\"), rawKeyHint(\"ctrl+c\", \"close\"));\n\t\telse hints.push(keyHint(\"tui.select.cancel\", \"close\"));\n\t\treturn hints.join(theme.fg(\"borderMuted\", \" · \"));\n\t}\n\n\tprivate setScope(scope: ModelScope): void {\n\t\tif (this.scope === scope) return;\n\t\tthis.scope = scope;\n\t\tthis.activeModels = this.scope === \"scoped\" ? this.scopedModelItems : this.allModels;\n\t\tthis.rebuildRows();\n\t\tthis.selectedIndex = this.defaultSelectedIndex();\n\t\tthis.renderHeader();\n\t\tthis.filterRows(this.searchInput.getValue());\n\t}\n\n\tprivate rowSearchText(row: Row): string {\n\t\tif (row.kind === \"provider\") return row.provider;\n\t\treturn getModelSelectorSearchText({ id: row.item.id, provider: row.item.provider, name: row.item.model.name });\n\t}\n\n\tprivate filterRows(query: string): void {\n\t\tthis.filteredRows = query ? fuzzyFilter(this.rows, query, (row) => this.rowSearchText(row)) : this.rows;\n\t\t// When filtering by a query, move the selector to the top row so the best\n\t\t// match is highlighted. When the query is cleared, keep the current position\n\t\t// clamped to the (restored) list length.\n\t\tthis.selectedIndex = query ? 0 : Math.min(this.selectedIndex, Math.max(0, this.filteredRows.length - 1));\n\t\tthis.updateList();\n\t}\n\n\tprivate renderRow(row: Row, isSelected: boolean): string {\n\t\tconst prefix = isSelected ? theme.fg(\"accent\", \"→ \") : \" \";\n\t\tif (row.kind === \"provider\") {\n\t\t\tconst label = isSelected ? theme.fg(\"accent\", row.provider) : row.provider;\n\t\t\tconst count = theme.fg(\"muted\", ` (${row.count} model${row.count === 1 ? \"\" : \"s\"})`);\n\t\t\tconst checkmark = row.hasCurrent ? theme.fg(\"success\", \" ✓\") : \"\";\n\t\t\treturn `${prefix}${label}${count}${checkmark}`;\n\t\t}\n\t\tconst label = isSelected ? theme.fg(\"accent\", row.item.id) : row.item.id;\n\t\tconst showProvider = this.step.kind === \"models\" && this.step.provider === null;\n\t\tconst badge = showProvider ? ` ${theme.fg(\"muted\", `[${row.item.provider}]`)}` : \"\";\n\t\tconst checkmark = modelsAreEqual(this.currentModel, row.item.model) ? theme.fg(\"success\", \" ✓\") : \"\";\n\t\treturn `${prefix}${label}${badge}${checkmark}`;\n\t}\n\n\tprivate updateList(): void {\n\t\tthis.listContainer.clear();\n\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(MAX_VISIBLE_ROWS / 2), this.filteredRows.length - MAX_VISIBLE_ROWS),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + MAX_VISIBLE_ROWS, this.filteredRows.length);\n\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst row = this.filteredRows[i];\n\t\t\tif (!row) continue;\n\t\t\tthis.listContainer.addChild(new Text(this.renderRow(row, i === this.selectedIndex), 0, 0));\n\t\t}\n\n\t\t// Add scroll indicator if needed\n\t\tif (startIndex > 0 || endIndex < this.filteredRows.length) {\n\t\t\tconst scrollInfo = theme.fg(\"muted\", ` (${this.selectedIndex + 1}/${this.filteredRows.length})`);\n\t\t\tthis.listContainer.addChild(new Text(scrollInfo, 0, 0));\n\t\t}\n\n\t\t// Show error message or \"no results\" if empty\n\t\tif (this.errorMessage) {\n\t\t\t// Show error in red\n\t\t\tconst errorLines = this.errorMessage.split(\"\\n\");\n\t\t\tfor (const line of errorLines) {\n\t\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"error\", line), 0, 0));\n\t\t\t}\n\t\t} else if (this.filteredRows.length === 0) {\n\t\t\tconst empty = this.step.kind === \"providers\" ? \" No matching providers\" : \" No matching models\";\n\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"muted\", empty), 0, 0));\n\t\t} else {\n\t\t\tconst selected = this.filteredRows[this.selectedIndex];\n\t\t\tif (selected?.kind === \"model\") {\n\t\t\t\tthis.listContainer.addChild(new Spacer(1));\n\t\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"muted\", ` Model Name: ${selected.item.model.name}`), 0, 0));\n\t\t\t}\n\t\t}\n\t\tif (this.refreshStatusMessage) {\n\t\t\tthis.listContainer.addChild(new Spacer(1));\n\t\t\tthis.listContainer.addChild(\n\t\t\t\tnew Text(theme.fg(this.refreshStatusSuccess ? \"success\" : \"muted\", ` ${this.refreshStatusMessage}`), 0, 0),\n\t\t\t);\n\t\t}\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getKeybindings();\n\t\tif (kb.matches(keyData, \"tui.input.tab\")) {\n\t\t\tif (this.scopedModelItems.length > 0) {\n\t\t\t\tthis.setScope(this.scope === \"all\" ? \"scoped\" : \"all\");\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\t// Up arrow - wrap to bottom when at top\n\t\tif (kb.matches(keyData, \"tui.select.up\")) {\n\t\t\tif (this.filteredRows.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === 0 ? this.filteredRows.length - 1 : this.selectedIndex - 1;\n\t\t\tthis.updateList();\n\t\t}\n\t\t// Down arrow - wrap to top when at bottom\n\t\telse if (kb.matches(keyData, \"tui.select.down\")) {\n\t\t\tif (this.filteredRows.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === this.filteredRows.length - 1 ? 0 : this.selectedIndex + 1;\n\t\t\tthis.updateList();\n\t\t}\n\t\t// Enter\n\t\telse if (kb.matches(keyData, \"tui.select.confirm\")) {\n\t\t\tthis.confirmSelection();\n\t\t}\n\t\t// Escape or Ctrl+C\n\t\telse if (kb.matches(keyData, \"tui.select.cancel\")) {\n\t\t\t// Escape steps back through the two levels; Ctrl+C always closes.\n\t\t\tif (matchesKey(keyData, Key.escape) && this.canGoBack()) {\n\t\t\t\tthis.setStep({ kind: \"providers\" });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.dispose();\n\t\t\tthis.onCancelCallback();\n\t\t}\n\t\t// Pass everything else to search input\n\t\telse {\n\t\t\tthis.searchInput.handleInput(keyData);\n\t\t\tthis.filterRows(this.searchInput.getValue());\n\t\t}\n\t}\n\n\tprivate confirmSelection(): void {\n\t\tconst row = this.filteredRows[this.selectedIndex];\n\t\tif (!row) return;\n\t\tif (row.kind === \"provider\") {\n\t\t\tthis.setStep({ kind: \"models\", provider: row.provider });\n\t\t\treturn;\n\t\t}\n\t\tthis.handleSelect(row.item.model);\n\t}\n\n\tprivate handleSelect(model: Model<any>): void {\n\t\tthis.dispose();\n\t\t// Save as new default\n\t\tthis.settingsManager.setDefaultModelAndProvider(model.provider, model.id);\n\t\tthis.onSelectCallback(model);\n\t}\n\n\tgetSearchInput(): Input {\n\t\treturn this.searchInput;\n\t}\n}\n"]}
@@ -1,9 +1,10 @@
1
1
  import { modelsAreEqual } from "@earendil-works/pi-ai";
2
2
  import { Container, fuzzyFilter, getKeybindings, Input, Key, matchesKey, Spacer, Text, } from "@earendil-works/pi-tui";
3
+ import { refreshModelCatalogs } from "../model-catalog-refresh.js";
3
4
  import { getModelSelectorSearchText } from "../model-search.js";
4
5
  import { theme } from "../theme/theme.js";
5
6
  import { DynamicBorder } from "./dynamic-border.js";
6
- import { keyHint, rawKeyHint } from "./keybinding-hints.js";
7
+ import { keyHint, keyText, rawKeyHint } from "./keybinding-hints.js";
7
8
  const MAX_VISIBLE_ROWS = 10;
8
9
  /**
9
10
  * Component that renders a two-step model selector: provider first, then that provider's models.
@@ -21,6 +22,7 @@ export class ModelSelectorComponent extends Container {
21
22
  }
22
23
  headerContainer;
23
24
  listContainer;
25
+ footerContainer;
24
26
  allModels = [];
25
27
  scopedModelItems = [];
26
28
  activeModels = [];
@@ -70,6 +72,11 @@ export class ModelSelectorComponent extends Container {
70
72
  this.listContainer = new Container();
71
73
  this.addChild(this.listContainer);
72
74
  this.addChild(new Spacer(1));
75
+ // Keybindings live under the list, where the eye already is once it has
76
+ // finished reading. Above the list they were the first thing read and the
77
+ // least useful.
78
+ this.footerContainer = new Container();
79
+ this.addChild(this.footerContainer);
73
80
  // Add bottom border
74
81
  this.addChild(new DynamicBorder());
75
82
  // Render the current snapshot immediately, then refresh in the background.
@@ -119,7 +126,7 @@ export class ModelSelectorComponent extends Container {
119
126
  this.refreshAbortController.abort();
120
127
  }, timeoutMs);
121
128
  try {
122
- const result = await this.modelRuntime.refresh({ signal: this.refreshAbortController.signal });
129
+ const result = await refreshModelCatalogs(this.modelRuntime, this.refreshAbortController.signal);
123
130
  if (this.closed)
124
131
  return;
125
132
  this.refreshStatusMessage = "";
@@ -249,7 +256,11 @@ export class ModelSelectorComponent extends Container {
249
256
  const hintText = "Only showing models from configured providers. Use /login to add providers.";
250
257
  this.headerContainer.addChild(new Text(theme.fg("warning", hintText), 0, 0));
251
258
  }
252
- this.headerContainer.addChild(new Text(this.getHintText(), 0, 0));
259
+ this.renderFooter();
260
+ }
261
+ renderFooter() {
262
+ this.footerContainer.clear();
263
+ this.footerContainer.addChild(new Text(this.getHintText(), 0, 0));
253
264
  }
254
265
  getScopeText() {
255
266
  const allText = this.scope === "all" ? theme.fg("accent", "all") : theme.fg("muted", "all");
@@ -257,7 +268,11 @@ export class ModelSelectorComponent extends Container {
257
268
  return `${theme.fg("muted", "Scope: ")}${allText}${theme.fg("muted", " | ")}${scopedText}`;
258
269
  }
259
270
  getHintText() {
260
- const hints = [];
271
+ const hints = [
272
+ // Both arrows, since either one moves the selection.
273
+ rawKeyHint(`${keyText("tui.select.up")}/${keyText("tui.select.down")}`, "move"),
274
+ keyHint("tui.select.confirm", "select"),
275
+ ];
261
276
  if (this.scopedModelItems.length > 0) {
262
277
  hints.push(keyHint("tui.input.tab", "scope") + theme.fg("muted", " (all/scoped)"));
263
278
  }
@@ -265,7 +280,7 @@ export class ModelSelectorComponent extends Container {
265
280
  hints.push(rawKeyHint("escape", "providers"), rawKeyHint("ctrl+c", "close"));
266
281
  else
267
282
  hints.push(keyHint("tui.select.cancel", "close"));
268
- return hints.join(theme.fg("muted", " · "));
283
+ return hints.join(theme.fg("borderMuted", " · "));
269
284
  }
270
285
  setScope(scope) {
271
286
  if (this.scope === scope)
@@ -1 +1 @@
1
- {"version":3,"file":"model-selector.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/model-selector.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Model, modelsAreEqual } from \"@earendil-works/pi-ai\";\nimport {\n\tContainer,\n\ttype Focusable,\n\tfuzzyFilter,\n\tgetKeybindings,\n\tInput,\n\tKey,\n\tmatchesKey,\n\tSpacer,\n\tText,\n\ttype TUI,\n} from \"@earendil-works/pi-tui\";\nimport type { ModelRuntime } from \"../../../core/model-runtime.ts\";\nimport type { SettingsManager } from \"../../../core/settings-manager.ts\";\nimport { getModelSelectorSearchText } from \"../model-search.ts\";\nimport { theme } from \"../theme/theme.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\nimport { keyHint, rawKeyHint } from \"./keybinding-hints.ts\";\n\ninterface ModelItem {\n\tprovider: string;\n\tid: string;\n\tmodel: Model<any>;\n}\n\ninterface ScopedModelItem {\n\tmodel: Model<any>;\n\tthinkingLevel?: string;\n}\n\ntype ModelScope = \"all\" | \"scoped\";\n\n/**\n * Which of the two steps the selector is on. `provider: null` lists every provider's models at once,\n * which is how a `/model <search>` invocation and a single-provider setup both open.\n */\ntype Step = { kind: \"providers\" } | { kind: \"models\"; provider: string | null };\n\ntype Row =\n\t| { kind: \"provider\"; provider: string; count: number; hasCurrent: boolean }\n\t| { kind: \"model\"; item: ModelItem };\n\nconst MAX_VISIBLE_ROWS = 10;\n\n/**\n * Component that renders a two-step model selector: provider first, then that provider's models.\n */\nexport class ModelSelectorComponent extends Container implements Focusable {\n\tprivate searchInput: Input;\n\n\t// Focusable implementation - propagate to searchInput for IME cursor positioning\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.searchInput.focused = value;\n\t}\n\tprivate headerContainer: Container;\n\tprivate listContainer: Container;\n\tprivate allModels: ModelItem[] = [];\n\tprivate scopedModelItems: ModelItem[] = [];\n\tprivate activeModels: ModelItem[] = [];\n\tprivate rows: Row[] = [];\n\tprivate filteredRows: Row[] = [];\n\tprivate selectedIndex: number = 0;\n\tprivate currentModel?: Model<any>;\n\tprivate settingsManager: SettingsManager;\n\tprivate modelRuntime: ModelRuntime;\n\tprivate onSelectCallback: (model: Model<any>) => void;\n\tprivate onCancelCallback: () => void;\n\tprivate errorMessage?: string;\n\tprivate refreshStatusMessage = \"Refreshing model catalogs…\";\n\tprivate refreshStatusSuccess = false;\n\tprivate tui: TUI;\n\tprivate scopedModels: ReadonlyArray<ScopedModelItem>;\n\tprivate scope: ModelScope = \"all\";\n\tprivate step: Step = { kind: \"providers\" };\n\tprivate readonly refreshAbortController = new AbortController();\n\tprivate refreshTimeout?: ReturnType<typeof setTimeout>;\n\tprivate closed = false;\n\n\tconstructor(\n\t\ttui: TUI,\n\t\tcurrentModel: Model<any> | undefined,\n\t\tsettingsManager: SettingsManager,\n\t\tmodelRuntime: ModelRuntime,\n\t\tscopedModels: ReadonlyArray<ScopedModelItem>,\n\t\tonSelect: (model: Model<any>) => void,\n\t\tonCancel: () => void,\n\t\tinitialSearchInput?: string,\n\t) {\n\t\tsuper();\n\n\t\tthis.tui = tui;\n\t\tthis.currentModel = currentModel;\n\t\tthis.settingsManager = settingsManager;\n\t\tthis.modelRuntime = modelRuntime;\n\t\tthis.scopedModels = scopedModels;\n\t\tthis.scope = scopedModels.length > 0 ? \"scoped\" : \"all\";\n\t\tthis.onSelectCallback = onSelect;\n\t\tthis.onCancelCallback = onCancel;\n\n\t\t// Add top border\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\tthis.headerContainer = new Container();\n\t\tthis.addChild(this.headerContainer);\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create search input\n\t\tthis.searchInput = new Input();\n\t\tif (initialSearchInput) {\n\t\t\tthis.searchInput.setValue(initialSearchInput);\n\t\t}\n\t\tthis.searchInput.onSubmit = () => this.confirmSelection();\n\t\tthis.addChild(this.searchInput);\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create list container\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Add bottom border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Render the current snapshot immediately, then refresh in the background.\n\t\tthis.loadModelsFromSnapshot();\n\t\tthis.step = this.initialStep(initialSearchInput);\n\t\tthis.rebuildRows();\n\t\tthis.selectedIndex = this.defaultSelectedIndex();\n\t\tthis.renderHeader();\n\t\tthis.filterRows(this.searchInput.getValue());\n\t\tthis.tui.requestRender();\n\t\tvoid this.refreshModels();\n\t}\n\n\tprivate initialStep(initialSearchInput: string | undefined): Step {\n\t\tconst providers = this.activeProviders();\n\t\tif (initialSearchInput) return { kind: \"models\", provider: null };\n\t\tif (providers.length <= 1) return { kind: \"models\", provider: providers[0] ?? null };\n\t\treturn { kind: \"providers\" };\n\t}\n\n\tprivate activeProviders(): string[] {\n\t\treturn [...new Set(this.activeModels.map((item) => item.provider))];\n\t}\n\n\tprivate loadModelsFromSnapshot(): void {\n\t\tconst models = this.modelRuntime.getAvailableSnapshot().map((model: Model<any>) => ({\n\t\t\tprovider: model.provider,\n\t\t\tid: model.id,\n\t\t\tmodel,\n\t\t}));\n\t\tthis.allModels = this.sortModels(models);\n\t\tthis.scopedModels = this.scopedModels.map((scoped) => {\n\t\t\tconst refreshed = this.modelRuntime.getModel(scoped.model.provider, scoped.model.id);\n\t\t\treturn refreshed ? { ...scoped, model: refreshed } : scoped;\n\t\t});\n\t\tthis.scopedModelItems = this.scopedModels.map((scoped) => ({\n\t\t\tprovider: scoped.model.provider,\n\t\t\tid: scoped.model.id,\n\t\t\tmodel: scoped.model,\n\t\t}));\n\t\tthis.activeModels = this.scope === \"scoped\" ? this.scopedModelItems : this.allModels;\n\t}\n\n\tprivate async refreshModels(): Promise<void> {\n\t\tconst timeoutMs = 15_000;\n\t\tlet timedOut = false;\n\t\tthis.refreshTimeout = setTimeout(() => {\n\t\t\ttimedOut = true;\n\t\t\tthis.refreshAbortController.abort();\n\t\t}, timeoutMs);\n\t\ttry {\n\t\t\tconst result = await this.modelRuntime.refresh({ signal: this.refreshAbortController.signal });\n\t\t\tif (this.closed) return;\n\t\t\tthis.refreshStatusMessage = \"\";\n\t\t\tif (result.aborted && timedOut) {\n\t\t\t\tthis.errorMessage = \"Model refresh timed out; showing cached models.\";\n\t\t\t} else if (result.errors.size === 1) {\n\t\t\t\tthis.errorMessage = `Could not refresh ${result.errors.keys().next().value}; showing cached models.`;\n\t\t\t} else if (result.errors.size > 1) {\n\t\t\t\tthis.errorMessage = `Could not refresh ${result.errors.size} model catalogs (${[...result.errors.keys()].join(\", \")}); showing cached models.`;\n\t\t\t} else {\n\t\t\t\tthis.errorMessage = this.modelRuntime.getError();\n\t\t\t\tif (!this.errorMessage) {\n\t\t\t\t\tthis.refreshStatusMessage = \"Model catalogs refreshed.\";\n\t\t\t\t\tthis.refreshStatusSuccess = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.loadModelsFromSnapshot();\n\t\t\tthis.rebuildRows();\n\t\t\tthis.renderHeader();\n\t\t\tthis.filterRows(this.searchInput.getValue());\n\t\t\tthis.tui.requestRender();\n\t\t} catch (error) {\n\t\t\tif (this.closed) return;\n\t\t\tthis.refreshStatusMessage = \"\";\n\t\t\tthis.errorMessage = timedOut\n\t\t\t\t? \"Model refresh timed out; showing cached models.\"\n\t\t\t\t: `Could not refresh model catalogs: ${error instanceof Error ? error.message : String(error)}`;\n\t\t\tthis.updateList();\n\t\t\tthis.tui.requestRender();\n\t\t} finally {\n\t\t\tif (this.refreshTimeout) clearTimeout(this.refreshTimeout);\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tif (this.closed) return;\n\t\tthis.closed = true;\n\t\tif (this.refreshTimeout) clearTimeout(this.refreshTimeout);\n\t\tthis.refreshAbortController.abort();\n\t}\n\n\tprivate sortModels(models: ModelItem[]): ModelItem[] {\n\t\tconst sorted = [...models];\n\t\t// Sort: current model first, then by provider\n\t\tsorted.sort((a, b) => {\n\t\t\tconst aIsCurrent = modelsAreEqual(this.currentModel, a.model);\n\t\t\tconst bIsCurrent = modelsAreEqual(this.currentModel, b.model);\n\t\t\tif (aIsCurrent && !bIsCurrent) return -1;\n\t\t\tif (!aIsCurrent && bIsCurrent) return 1;\n\t\t\treturn a.provider.localeCompare(b.provider);\n\t\t});\n\t\treturn sorted;\n\t}\n\n\tprivate rebuildRows(): void {\n\t\tif (this.step.kind === \"providers\") {\n\t\t\tconst byProvider = new Map<string, ModelItem[]>();\n\t\t\tfor (const item of this.activeModels) {\n\t\t\t\tconst existing = byProvider.get(item.provider);\n\t\t\t\tif (existing) existing.push(item);\n\t\t\t\telse byProvider.set(item.provider, [item]);\n\t\t\t}\n\t\t\tconst providerRows: Row[] = [...byProvider].map(([provider, items]) => ({\n\t\t\t\tkind: \"provider\",\n\t\t\t\tprovider,\n\t\t\t\tcount: items.length,\n\t\t\t\thasCurrent: items.some((item) => modelsAreEqual(this.currentModel, item.model)),\n\t\t\t}));\n\t\t\tproviderRows.sort((a, b) => {\n\t\t\t\tif (a.kind !== \"provider\" || b.kind !== \"provider\") return 0;\n\t\t\t\tif (a.hasCurrent !== b.hasCurrent) return a.hasCurrent ? -1 : 1;\n\t\t\t\treturn a.provider.localeCompare(b.provider);\n\t\t\t});\n\t\t\tthis.rows = providerRows;\n\t\t\treturn;\n\t\t}\n\t\tconst provider = this.step.provider;\n\t\tconst models = provider === null ? this.activeModels : this.activeModels.filter((m) => m.provider === provider);\n\t\tthis.rows = models.map((item) => ({ kind: \"model\", item }));\n\t}\n\n\tprivate defaultSelectedIndex(): number {\n\t\tconst index = this.rows.findIndex((row) =>\n\t\t\trow.kind === \"provider\" ? row.hasCurrent : modelsAreEqual(this.currentModel, row.item.model),\n\t\t);\n\t\treturn index >= 0 ? index : 0;\n\t}\n\n\tprivate setStep(step: Step): void {\n\t\tthis.step = step;\n\t\tthis.searchInput.setValue(\"\");\n\t\tthis.rebuildRows();\n\t\tthis.selectedIndex = this.defaultSelectedIndex();\n\t\tthis.renderHeader();\n\t\tthis.filterRows(\"\");\n\t\tthis.tui.requestRender();\n\t}\n\n\tprivate isDrilledIn(): boolean {\n\t\treturn this.step.kind === \"models\" && this.step.provider !== null;\n\t}\n\n\t/** Escape returns to the provider list whenever there is a provider list worth returning to. */\n\tprivate canGoBack(): boolean {\n\t\treturn this.step.kind === \"models\" && this.activeProviders().length > 1;\n\t}\n\n\tprivate renderHeader(): void {\n\t\tthis.headerContainer.clear();\n\n\t\tif (this.step.kind === \"providers\") {\n\t\t\tthis.headerContainer.addChild(new Text(theme.bold(theme.fg(\"accent\", \"Select a provider\")), 0, 0));\n\t\t} else {\n\t\t\tconst label = this.step.provider ?? \"All providers\";\n\t\t\tconst crumb = `${theme.bold(theme.fg(\"accent\", label))}${theme.fg(\"muted\", \" › select a model\")}`;\n\t\t\tthis.headerContainer.addChild(new Text(crumb, 0, 0));\n\t\t}\n\n\t\tif (this.scopedModelItems.length > 0) {\n\t\t\tthis.headerContainer.addChild(new Text(this.getScopeText(), 0, 0));\n\t\t} else if (!this.isDrilledIn()) {\n\t\t\tconst hintText = \"Only showing models from configured providers. Use /login to add providers.\";\n\t\t\tthis.headerContainer.addChild(new Text(theme.fg(\"warning\", hintText), 0, 0));\n\t\t}\n\n\t\tthis.headerContainer.addChild(new Text(this.getHintText(), 0, 0));\n\t}\n\n\tprivate getScopeText(): string {\n\t\tconst allText = this.scope === \"all\" ? theme.fg(\"accent\", \"all\") : theme.fg(\"muted\", \"all\");\n\t\tconst scopedText = this.scope === \"scoped\" ? theme.fg(\"accent\", \"scoped\") : theme.fg(\"muted\", \"scoped\");\n\t\treturn `${theme.fg(\"muted\", \"Scope: \")}${allText}${theme.fg(\"muted\", \" | \")}${scopedText}`;\n\t}\n\n\tprivate getHintText(): string {\n\t\tconst hints: string[] = [];\n\t\tif (this.scopedModelItems.length > 0) {\n\t\t\thints.push(keyHint(\"tui.input.tab\", \"scope\") + theme.fg(\"muted\", \" (all/scoped)\"));\n\t\t}\n\t\tif (this.canGoBack()) hints.push(rawKeyHint(\"escape\", \"providers\"), rawKeyHint(\"ctrl+c\", \"close\"));\n\t\telse hints.push(keyHint(\"tui.select.cancel\", \"close\"));\n\t\treturn hints.join(theme.fg(\"muted\", \" · \"));\n\t}\n\n\tprivate setScope(scope: ModelScope): void {\n\t\tif (this.scope === scope) return;\n\t\tthis.scope = scope;\n\t\tthis.activeModels = this.scope === \"scoped\" ? this.scopedModelItems : this.allModels;\n\t\tthis.rebuildRows();\n\t\tthis.selectedIndex = this.defaultSelectedIndex();\n\t\tthis.renderHeader();\n\t\tthis.filterRows(this.searchInput.getValue());\n\t}\n\n\tprivate rowSearchText(row: Row): string {\n\t\tif (row.kind === \"provider\") return row.provider;\n\t\treturn getModelSelectorSearchText({ id: row.item.id, provider: row.item.provider, name: row.item.model.name });\n\t}\n\n\tprivate filterRows(query: string): void {\n\t\tthis.filteredRows = query ? fuzzyFilter(this.rows, query, (row) => this.rowSearchText(row)) : this.rows;\n\t\t// When filtering by a query, move the selector to the top row so the best\n\t\t// match is highlighted. When the query is cleared, keep the current position\n\t\t// clamped to the (restored) list length.\n\t\tthis.selectedIndex = query ? 0 : Math.min(this.selectedIndex, Math.max(0, this.filteredRows.length - 1));\n\t\tthis.updateList();\n\t}\n\n\tprivate renderRow(row: Row, isSelected: boolean): string {\n\t\tconst prefix = isSelected ? theme.fg(\"accent\", \"→ \") : \" \";\n\t\tif (row.kind === \"provider\") {\n\t\t\tconst label = isSelected ? theme.fg(\"accent\", row.provider) : row.provider;\n\t\t\tconst count = theme.fg(\"muted\", ` (${row.count} model${row.count === 1 ? \"\" : \"s\"})`);\n\t\t\tconst checkmark = row.hasCurrent ? theme.fg(\"success\", \" ✓\") : \"\";\n\t\t\treturn `${prefix}${label}${count}${checkmark}`;\n\t\t}\n\t\tconst label = isSelected ? theme.fg(\"accent\", row.item.id) : row.item.id;\n\t\tconst showProvider = this.step.kind === \"models\" && this.step.provider === null;\n\t\tconst badge = showProvider ? ` ${theme.fg(\"muted\", `[${row.item.provider}]`)}` : \"\";\n\t\tconst checkmark = modelsAreEqual(this.currentModel, row.item.model) ? theme.fg(\"success\", \" ✓\") : \"\";\n\t\treturn `${prefix}${label}${badge}${checkmark}`;\n\t}\n\n\tprivate updateList(): void {\n\t\tthis.listContainer.clear();\n\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(MAX_VISIBLE_ROWS / 2), this.filteredRows.length - MAX_VISIBLE_ROWS),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + MAX_VISIBLE_ROWS, this.filteredRows.length);\n\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst row = this.filteredRows[i];\n\t\t\tif (!row) continue;\n\t\t\tthis.listContainer.addChild(new Text(this.renderRow(row, i === this.selectedIndex), 0, 0));\n\t\t}\n\n\t\t// Add scroll indicator if needed\n\t\tif (startIndex > 0 || endIndex < this.filteredRows.length) {\n\t\t\tconst scrollInfo = theme.fg(\"muted\", ` (${this.selectedIndex + 1}/${this.filteredRows.length})`);\n\t\t\tthis.listContainer.addChild(new Text(scrollInfo, 0, 0));\n\t\t}\n\n\t\t// Show error message or \"no results\" if empty\n\t\tif (this.errorMessage) {\n\t\t\t// Show error in red\n\t\t\tconst errorLines = this.errorMessage.split(\"\\n\");\n\t\t\tfor (const line of errorLines) {\n\t\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"error\", line), 0, 0));\n\t\t\t}\n\t\t} else if (this.filteredRows.length === 0) {\n\t\t\tconst empty = this.step.kind === \"providers\" ? \" No matching providers\" : \" No matching models\";\n\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"muted\", empty), 0, 0));\n\t\t} else {\n\t\t\tconst selected = this.filteredRows[this.selectedIndex];\n\t\t\tif (selected?.kind === \"model\") {\n\t\t\t\tthis.listContainer.addChild(new Spacer(1));\n\t\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"muted\", ` Model Name: ${selected.item.model.name}`), 0, 0));\n\t\t\t}\n\t\t}\n\t\tif (this.refreshStatusMessage) {\n\t\t\tthis.listContainer.addChild(new Spacer(1));\n\t\t\tthis.listContainer.addChild(\n\t\t\t\tnew Text(theme.fg(this.refreshStatusSuccess ? \"success\" : \"muted\", ` ${this.refreshStatusMessage}`), 0, 0),\n\t\t\t);\n\t\t}\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getKeybindings();\n\t\tif (kb.matches(keyData, \"tui.input.tab\")) {\n\t\t\tif (this.scopedModelItems.length > 0) {\n\t\t\t\tthis.setScope(this.scope === \"all\" ? \"scoped\" : \"all\");\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\t// Up arrow - wrap to bottom when at top\n\t\tif (kb.matches(keyData, \"tui.select.up\")) {\n\t\t\tif (this.filteredRows.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === 0 ? this.filteredRows.length - 1 : this.selectedIndex - 1;\n\t\t\tthis.updateList();\n\t\t}\n\t\t// Down arrow - wrap to top when at bottom\n\t\telse if (kb.matches(keyData, \"tui.select.down\")) {\n\t\t\tif (this.filteredRows.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === this.filteredRows.length - 1 ? 0 : this.selectedIndex + 1;\n\t\t\tthis.updateList();\n\t\t}\n\t\t// Enter\n\t\telse if (kb.matches(keyData, \"tui.select.confirm\")) {\n\t\t\tthis.confirmSelection();\n\t\t}\n\t\t// Escape or Ctrl+C\n\t\telse if (kb.matches(keyData, \"tui.select.cancel\")) {\n\t\t\t// Escape steps back through the two levels; Ctrl+C always closes.\n\t\t\tif (matchesKey(keyData, Key.escape) && this.canGoBack()) {\n\t\t\t\tthis.setStep({ kind: \"providers\" });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.dispose();\n\t\t\tthis.onCancelCallback();\n\t\t}\n\t\t// Pass everything else to search input\n\t\telse {\n\t\t\tthis.searchInput.handleInput(keyData);\n\t\t\tthis.filterRows(this.searchInput.getValue());\n\t\t}\n\t}\n\n\tprivate confirmSelection(): void {\n\t\tconst row = this.filteredRows[this.selectedIndex];\n\t\tif (!row) return;\n\t\tif (row.kind === \"provider\") {\n\t\t\tthis.setStep({ kind: \"models\", provider: row.provider });\n\t\t\treturn;\n\t\t}\n\t\tthis.handleSelect(row.item.model);\n\t}\n\n\tprivate handleSelect(model: Model<any>): void {\n\t\tthis.dispose();\n\t\t// Save as new default\n\t\tthis.settingsManager.setDefaultModelAndProvider(model.provider, model.id);\n\t\tthis.onSelectCallback(model);\n\t}\n\n\tgetSearchInput(): Input {\n\t\treturn this.searchInput;\n\t}\n}\n"]}
1
+ {"version":3,"file":"model-selector.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/model-selector.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Model, modelsAreEqual } from \"@earendil-works/pi-ai\";\nimport {\n\tContainer,\n\ttype Focusable,\n\tfuzzyFilter,\n\tgetKeybindings,\n\tInput,\n\tKey,\n\tmatchesKey,\n\tSpacer,\n\tText,\n\ttype TUI,\n} from \"@earendil-works/pi-tui\";\nimport type { ModelRuntime } from \"../../../core/model-runtime.ts\";\nimport type { SettingsManager } from \"../../../core/settings-manager.ts\";\nimport { refreshModelCatalogs } from \"../model-catalog-refresh.ts\";\nimport { getModelSelectorSearchText } from \"../model-search.ts\";\nimport { theme } from \"../theme/theme.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\nimport { keyHint, keyText, rawKeyHint } from \"./keybinding-hints.ts\";\n\ninterface ModelItem {\n\tprovider: string;\n\tid: string;\n\tmodel: Model<any>;\n}\n\ninterface ScopedModelItem {\n\tmodel: Model<any>;\n\tthinkingLevel?: string;\n}\n\ntype ModelScope = \"all\" | \"scoped\";\n\n/**\n * Which of the two steps the selector is on. `provider: null` lists every provider's models at once,\n * which is how a `/model <search>` invocation and a single-provider setup both open.\n */\ntype Step = { kind: \"providers\" } | { kind: \"models\"; provider: string | null };\n\ntype Row =\n\t| { kind: \"provider\"; provider: string; count: number; hasCurrent: boolean }\n\t| { kind: \"model\"; item: ModelItem };\n\nconst MAX_VISIBLE_ROWS = 10;\n\n/**\n * Component that renders a two-step model selector: provider first, then that provider's models.\n */\nexport class ModelSelectorComponent extends Container implements Focusable {\n\tprivate searchInput: Input;\n\n\t// Focusable implementation - propagate to searchInput for IME cursor positioning\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.searchInput.focused = value;\n\t}\n\tprivate headerContainer: Container;\n\tprivate listContainer: Container;\n\tprivate footerContainer: Container;\n\tprivate allModels: ModelItem[] = [];\n\tprivate scopedModelItems: ModelItem[] = [];\n\tprivate activeModels: ModelItem[] = [];\n\tprivate rows: Row[] = [];\n\tprivate filteredRows: Row[] = [];\n\tprivate selectedIndex: number = 0;\n\tprivate currentModel?: Model<any>;\n\tprivate settingsManager: SettingsManager;\n\tprivate modelRuntime: ModelRuntime;\n\tprivate onSelectCallback: (model: Model<any>) => void;\n\tprivate onCancelCallback: () => void;\n\tprivate errorMessage?: string;\n\tprivate refreshStatusMessage = \"Refreshing model catalogs…\";\n\tprivate refreshStatusSuccess = false;\n\tprivate tui: TUI;\n\tprivate scopedModels: ReadonlyArray<ScopedModelItem>;\n\tprivate scope: ModelScope = \"all\";\n\tprivate step: Step = { kind: \"providers\" };\n\tprivate readonly refreshAbortController = new AbortController();\n\tprivate refreshTimeout?: ReturnType<typeof setTimeout>;\n\tprivate closed = false;\n\n\tconstructor(\n\t\ttui: TUI,\n\t\tcurrentModel: Model<any> | undefined,\n\t\tsettingsManager: SettingsManager,\n\t\tmodelRuntime: ModelRuntime,\n\t\tscopedModels: ReadonlyArray<ScopedModelItem>,\n\t\tonSelect: (model: Model<any>) => void,\n\t\tonCancel: () => void,\n\t\tinitialSearchInput?: string,\n\t) {\n\t\tsuper();\n\n\t\tthis.tui = tui;\n\t\tthis.currentModel = currentModel;\n\t\tthis.settingsManager = settingsManager;\n\t\tthis.modelRuntime = modelRuntime;\n\t\tthis.scopedModels = scopedModels;\n\t\tthis.scope = scopedModels.length > 0 ? \"scoped\" : \"all\";\n\t\tthis.onSelectCallback = onSelect;\n\t\tthis.onCancelCallback = onCancel;\n\n\t\t// Add top border\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\tthis.headerContainer = new Container();\n\t\tthis.addChild(this.headerContainer);\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create search input\n\t\tthis.searchInput = new Input();\n\t\tif (initialSearchInput) {\n\t\t\tthis.searchInput.setValue(initialSearchInput);\n\t\t}\n\t\tthis.searchInput.onSubmit = () => this.confirmSelection();\n\t\tthis.addChild(this.searchInput);\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create list container\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Keybindings live under the list, where the eye already is once it has\n\t\t// finished reading. Above the list they were the first thing read and the\n\t\t// least useful.\n\t\tthis.footerContainer = new Container();\n\t\tthis.addChild(this.footerContainer);\n\n\t\t// Add bottom border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Render the current snapshot immediately, then refresh in the background.\n\t\tthis.loadModelsFromSnapshot();\n\t\tthis.step = this.initialStep(initialSearchInput);\n\t\tthis.rebuildRows();\n\t\tthis.selectedIndex = this.defaultSelectedIndex();\n\t\tthis.renderHeader();\n\t\tthis.filterRows(this.searchInput.getValue());\n\t\tthis.tui.requestRender();\n\t\tvoid this.refreshModels();\n\t}\n\n\tprivate initialStep(initialSearchInput: string | undefined): Step {\n\t\tconst providers = this.activeProviders();\n\t\tif (initialSearchInput) return { kind: \"models\", provider: null };\n\t\tif (providers.length <= 1) return { kind: \"models\", provider: providers[0] ?? null };\n\t\treturn { kind: \"providers\" };\n\t}\n\n\tprivate activeProviders(): string[] {\n\t\treturn [...new Set(this.activeModels.map((item) => item.provider))];\n\t}\n\n\tprivate loadModelsFromSnapshot(): void {\n\t\tconst models = this.modelRuntime.getAvailableSnapshot().map((model: Model<any>) => ({\n\t\t\tprovider: model.provider,\n\t\t\tid: model.id,\n\t\t\tmodel,\n\t\t}));\n\t\tthis.allModels = this.sortModels(models);\n\t\tthis.scopedModels = this.scopedModels.map((scoped) => {\n\t\t\tconst refreshed = this.modelRuntime.getModel(scoped.model.provider, scoped.model.id);\n\t\t\treturn refreshed ? { ...scoped, model: refreshed } : scoped;\n\t\t});\n\t\tthis.scopedModelItems = this.scopedModels.map((scoped) => ({\n\t\t\tprovider: scoped.model.provider,\n\t\t\tid: scoped.model.id,\n\t\t\tmodel: scoped.model,\n\t\t}));\n\t\tthis.activeModels = this.scope === \"scoped\" ? this.scopedModelItems : this.allModels;\n\t}\n\n\tprivate async refreshModels(): Promise<void> {\n\t\tconst timeoutMs = 15_000;\n\t\tlet timedOut = false;\n\t\tthis.refreshTimeout = setTimeout(() => {\n\t\t\ttimedOut = true;\n\t\t\tthis.refreshAbortController.abort();\n\t\t}, timeoutMs);\n\t\ttry {\n\t\t\tconst result = await refreshModelCatalogs(this.modelRuntime, this.refreshAbortController.signal);\n\t\t\tif (this.closed) return;\n\t\t\tthis.refreshStatusMessage = \"\";\n\t\t\tif (result.aborted && timedOut) {\n\t\t\t\tthis.errorMessage = \"Model refresh timed out; showing cached models.\";\n\t\t\t} else if (result.errors.size === 1) {\n\t\t\t\tthis.errorMessage = `Could not refresh ${result.errors.keys().next().value}; showing cached models.`;\n\t\t\t} else if (result.errors.size > 1) {\n\t\t\t\tthis.errorMessage = `Could not refresh ${result.errors.size} model catalogs (${[...result.errors.keys()].join(\", \")}); showing cached models.`;\n\t\t\t} else {\n\t\t\t\tthis.errorMessage = this.modelRuntime.getError();\n\t\t\t\tif (!this.errorMessage) {\n\t\t\t\t\tthis.refreshStatusMessage = \"Model catalogs refreshed.\";\n\t\t\t\t\tthis.refreshStatusSuccess = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.loadModelsFromSnapshot();\n\t\t\tthis.rebuildRows();\n\t\t\tthis.renderHeader();\n\t\t\tthis.filterRows(this.searchInput.getValue());\n\t\t\tthis.tui.requestRender();\n\t\t} catch (error) {\n\t\t\tif (this.closed) return;\n\t\t\tthis.refreshStatusMessage = \"\";\n\t\t\tthis.errorMessage = timedOut\n\t\t\t\t? \"Model refresh timed out; showing cached models.\"\n\t\t\t\t: `Could not refresh model catalogs: ${error instanceof Error ? error.message : String(error)}`;\n\t\t\tthis.updateList();\n\t\t\tthis.tui.requestRender();\n\t\t} finally {\n\t\t\tif (this.refreshTimeout) clearTimeout(this.refreshTimeout);\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tif (this.closed) return;\n\t\tthis.closed = true;\n\t\tif (this.refreshTimeout) clearTimeout(this.refreshTimeout);\n\t\tthis.refreshAbortController.abort();\n\t}\n\n\tprivate sortModels(models: ModelItem[]): ModelItem[] {\n\t\tconst sorted = [...models];\n\t\t// Sort: current model first, then by provider\n\t\tsorted.sort((a, b) => {\n\t\t\tconst aIsCurrent = modelsAreEqual(this.currentModel, a.model);\n\t\t\tconst bIsCurrent = modelsAreEqual(this.currentModel, b.model);\n\t\t\tif (aIsCurrent && !bIsCurrent) return -1;\n\t\t\tif (!aIsCurrent && bIsCurrent) return 1;\n\t\t\treturn a.provider.localeCompare(b.provider);\n\t\t});\n\t\treturn sorted;\n\t}\n\n\tprivate rebuildRows(): void {\n\t\tif (this.step.kind === \"providers\") {\n\t\t\tconst byProvider = new Map<string, ModelItem[]>();\n\t\t\tfor (const item of this.activeModels) {\n\t\t\t\tconst existing = byProvider.get(item.provider);\n\t\t\t\tif (existing) existing.push(item);\n\t\t\t\telse byProvider.set(item.provider, [item]);\n\t\t\t}\n\t\t\tconst providerRows: Row[] = [...byProvider].map(([provider, items]) => ({\n\t\t\t\tkind: \"provider\",\n\t\t\t\tprovider,\n\t\t\t\tcount: items.length,\n\t\t\t\thasCurrent: items.some((item) => modelsAreEqual(this.currentModel, item.model)),\n\t\t\t}));\n\t\t\tproviderRows.sort((a, b) => {\n\t\t\t\tif (a.kind !== \"provider\" || b.kind !== \"provider\") return 0;\n\t\t\t\tif (a.hasCurrent !== b.hasCurrent) return a.hasCurrent ? -1 : 1;\n\t\t\t\treturn a.provider.localeCompare(b.provider);\n\t\t\t});\n\t\t\tthis.rows = providerRows;\n\t\t\treturn;\n\t\t}\n\t\tconst provider = this.step.provider;\n\t\tconst models = provider === null ? this.activeModels : this.activeModels.filter((m) => m.provider === provider);\n\t\tthis.rows = models.map((item) => ({ kind: \"model\", item }));\n\t}\n\n\tprivate defaultSelectedIndex(): number {\n\t\tconst index = this.rows.findIndex((row) =>\n\t\t\trow.kind === \"provider\" ? row.hasCurrent : modelsAreEqual(this.currentModel, row.item.model),\n\t\t);\n\t\treturn index >= 0 ? index : 0;\n\t}\n\n\tprivate setStep(step: Step): void {\n\t\tthis.step = step;\n\t\tthis.searchInput.setValue(\"\");\n\t\tthis.rebuildRows();\n\t\tthis.selectedIndex = this.defaultSelectedIndex();\n\t\tthis.renderHeader();\n\t\tthis.filterRows(\"\");\n\t\tthis.tui.requestRender();\n\t}\n\n\tprivate isDrilledIn(): boolean {\n\t\treturn this.step.kind === \"models\" && this.step.provider !== null;\n\t}\n\n\t/** Escape returns to the provider list whenever there is a provider list worth returning to. */\n\tprivate canGoBack(): boolean {\n\t\treturn this.step.kind === \"models\" && this.activeProviders().length > 1;\n\t}\n\n\tprivate renderHeader(): void {\n\t\tthis.headerContainer.clear();\n\n\t\tif (this.step.kind === \"providers\") {\n\t\t\tthis.headerContainer.addChild(new Text(theme.bold(theme.fg(\"accent\", \"Select a provider\")), 0, 0));\n\t\t} else {\n\t\t\tconst label = this.step.provider ?? \"All providers\";\n\t\t\tconst crumb = `${theme.bold(theme.fg(\"accent\", label))}${theme.fg(\"muted\", \" › select a model\")}`;\n\t\t\tthis.headerContainer.addChild(new Text(crumb, 0, 0));\n\t\t}\n\n\t\tif (this.scopedModelItems.length > 0) {\n\t\t\tthis.headerContainer.addChild(new Text(this.getScopeText(), 0, 0));\n\t\t} else if (!this.isDrilledIn()) {\n\t\t\tconst hintText = \"Only showing models from configured providers. Use /login to add providers.\";\n\t\t\tthis.headerContainer.addChild(new Text(theme.fg(\"warning\", hintText), 0, 0));\n\t\t}\n\n\t\tthis.renderFooter();\n\t}\n\n\tprivate renderFooter(): void {\n\t\tthis.footerContainer.clear();\n\t\tthis.footerContainer.addChild(new Text(this.getHintText(), 0, 0));\n\t}\n\n\tprivate getScopeText(): string {\n\t\tconst allText = this.scope === \"all\" ? theme.fg(\"accent\", \"all\") : theme.fg(\"muted\", \"all\");\n\t\tconst scopedText = this.scope === \"scoped\" ? theme.fg(\"accent\", \"scoped\") : theme.fg(\"muted\", \"scoped\");\n\t\treturn `${theme.fg(\"muted\", \"Scope: \")}${allText}${theme.fg(\"muted\", \" | \")}${scopedText}`;\n\t}\n\n\tprivate getHintText(): string {\n\t\tconst hints: string[] = [\n\t\t\t// Both arrows, since either one moves the selection.\n\t\t\trawKeyHint(`${keyText(\"tui.select.up\")}/${keyText(\"tui.select.down\")}`, \"move\"),\n\t\t\tkeyHint(\"tui.select.confirm\", \"select\"),\n\t\t];\n\t\tif (this.scopedModelItems.length > 0) {\n\t\t\thints.push(keyHint(\"tui.input.tab\", \"scope\") + theme.fg(\"muted\", \" (all/scoped)\"));\n\t\t}\n\t\tif (this.canGoBack()) hints.push(rawKeyHint(\"escape\", \"providers\"), rawKeyHint(\"ctrl+c\", \"close\"));\n\t\telse hints.push(keyHint(\"tui.select.cancel\", \"close\"));\n\t\treturn hints.join(theme.fg(\"borderMuted\", \" · \"));\n\t}\n\n\tprivate setScope(scope: ModelScope): void {\n\t\tif (this.scope === scope) return;\n\t\tthis.scope = scope;\n\t\tthis.activeModels = this.scope === \"scoped\" ? this.scopedModelItems : this.allModels;\n\t\tthis.rebuildRows();\n\t\tthis.selectedIndex = this.defaultSelectedIndex();\n\t\tthis.renderHeader();\n\t\tthis.filterRows(this.searchInput.getValue());\n\t}\n\n\tprivate rowSearchText(row: Row): string {\n\t\tif (row.kind === \"provider\") return row.provider;\n\t\treturn getModelSelectorSearchText({ id: row.item.id, provider: row.item.provider, name: row.item.model.name });\n\t}\n\n\tprivate filterRows(query: string): void {\n\t\tthis.filteredRows = query ? fuzzyFilter(this.rows, query, (row) => this.rowSearchText(row)) : this.rows;\n\t\t// When filtering by a query, move the selector to the top row so the best\n\t\t// match is highlighted. When the query is cleared, keep the current position\n\t\t// clamped to the (restored) list length.\n\t\tthis.selectedIndex = query ? 0 : Math.min(this.selectedIndex, Math.max(0, this.filteredRows.length - 1));\n\t\tthis.updateList();\n\t}\n\n\tprivate renderRow(row: Row, isSelected: boolean): string {\n\t\tconst prefix = isSelected ? theme.fg(\"accent\", \"→ \") : \" \";\n\t\tif (row.kind === \"provider\") {\n\t\t\tconst label = isSelected ? theme.fg(\"accent\", row.provider) : row.provider;\n\t\t\tconst count = theme.fg(\"muted\", ` (${row.count} model${row.count === 1 ? \"\" : \"s\"})`);\n\t\t\tconst checkmark = row.hasCurrent ? theme.fg(\"success\", \" ✓\") : \"\";\n\t\t\treturn `${prefix}${label}${count}${checkmark}`;\n\t\t}\n\t\tconst label = isSelected ? theme.fg(\"accent\", row.item.id) : row.item.id;\n\t\tconst showProvider = this.step.kind === \"models\" && this.step.provider === null;\n\t\tconst badge = showProvider ? ` ${theme.fg(\"muted\", `[${row.item.provider}]`)}` : \"\";\n\t\tconst checkmark = modelsAreEqual(this.currentModel, row.item.model) ? theme.fg(\"success\", \" ✓\") : \"\";\n\t\treturn `${prefix}${label}${badge}${checkmark}`;\n\t}\n\n\tprivate updateList(): void {\n\t\tthis.listContainer.clear();\n\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(MAX_VISIBLE_ROWS / 2), this.filteredRows.length - MAX_VISIBLE_ROWS),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + MAX_VISIBLE_ROWS, this.filteredRows.length);\n\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst row = this.filteredRows[i];\n\t\t\tif (!row) continue;\n\t\t\tthis.listContainer.addChild(new Text(this.renderRow(row, i === this.selectedIndex), 0, 0));\n\t\t}\n\n\t\t// Add scroll indicator if needed\n\t\tif (startIndex > 0 || endIndex < this.filteredRows.length) {\n\t\t\tconst scrollInfo = theme.fg(\"muted\", ` (${this.selectedIndex + 1}/${this.filteredRows.length})`);\n\t\t\tthis.listContainer.addChild(new Text(scrollInfo, 0, 0));\n\t\t}\n\n\t\t// Show error message or \"no results\" if empty\n\t\tif (this.errorMessage) {\n\t\t\t// Show error in red\n\t\t\tconst errorLines = this.errorMessage.split(\"\\n\");\n\t\t\tfor (const line of errorLines) {\n\t\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"error\", line), 0, 0));\n\t\t\t}\n\t\t} else if (this.filteredRows.length === 0) {\n\t\t\tconst empty = this.step.kind === \"providers\" ? \" No matching providers\" : \" No matching models\";\n\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"muted\", empty), 0, 0));\n\t\t} else {\n\t\t\tconst selected = this.filteredRows[this.selectedIndex];\n\t\t\tif (selected?.kind === \"model\") {\n\t\t\t\tthis.listContainer.addChild(new Spacer(1));\n\t\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"muted\", ` Model Name: ${selected.item.model.name}`), 0, 0));\n\t\t\t}\n\t\t}\n\t\tif (this.refreshStatusMessage) {\n\t\t\tthis.listContainer.addChild(new Spacer(1));\n\t\t\tthis.listContainer.addChild(\n\t\t\t\tnew Text(theme.fg(this.refreshStatusSuccess ? \"success\" : \"muted\", ` ${this.refreshStatusMessage}`), 0, 0),\n\t\t\t);\n\t\t}\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getKeybindings();\n\t\tif (kb.matches(keyData, \"tui.input.tab\")) {\n\t\t\tif (this.scopedModelItems.length > 0) {\n\t\t\t\tthis.setScope(this.scope === \"all\" ? \"scoped\" : \"all\");\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\t// Up arrow - wrap to bottom when at top\n\t\tif (kb.matches(keyData, \"tui.select.up\")) {\n\t\t\tif (this.filteredRows.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === 0 ? this.filteredRows.length - 1 : this.selectedIndex - 1;\n\t\t\tthis.updateList();\n\t\t}\n\t\t// Down arrow - wrap to top when at bottom\n\t\telse if (kb.matches(keyData, \"tui.select.down\")) {\n\t\t\tif (this.filteredRows.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === this.filteredRows.length - 1 ? 0 : this.selectedIndex + 1;\n\t\t\tthis.updateList();\n\t\t}\n\t\t// Enter\n\t\telse if (kb.matches(keyData, \"tui.select.confirm\")) {\n\t\t\tthis.confirmSelection();\n\t\t}\n\t\t// Escape or Ctrl+C\n\t\telse if (kb.matches(keyData, \"tui.select.cancel\")) {\n\t\t\t// Escape steps back through the two levels; Ctrl+C always closes.\n\t\t\tif (matchesKey(keyData, Key.escape) && this.canGoBack()) {\n\t\t\t\tthis.setStep({ kind: \"providers\" });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.dispose();\n\t\t\tthis.onCancelCallback();\n\t\t}\n\t\t// Pass everything else to search input\n\t\telse {\n\t\t\tthis.searchInput.handleInput(keyData);\n\t\t\tthis.filterRows(this.searchInput.getValue());\n\t\t}\n\t}\n\n\tprivate confirmSelection(): void {\n\t\tconst row = this.filteredRows[this.selectedIndex];\n\t\tif (!row) return;\n\t\tif (row.kind === \"provider\") {\n\t\t\tthis.setStep({ kind: \"models\", provider: row.provider });\n\t\t\treturn;\n\t\t}\n\t\tthis.handleSelect(row.item.model);\n\t}\n\n\tprivate handleSelect(model: Model<any>): void {\n\t\tthis.dispose();\n\t\t// Save as new default\n\t\tthis.settingsManager.setDefaultModelAndProvider(model.provider, model.id);\n\t\tthis.onSelectCallback(model);\n\t}\n\n\tgetSearchInput(): Input {\n\t\treturn this.searchInput;\n\t}\n}\n"]}
@@ -3,7 +3,7 @@ import { Container, type ScrollViewScrollbar, SettingsList } from "@earendil-wor
3
3
  import type { ThinkingLevel } from "apex-code-agent-core";
4
4
  import type { PermissionModeOrigin } from "../../../core/permissions/startup.ts";
5
5
  import { type PermissionMode } from "../../../core/permissions/store.ts";
6
- import type { DefaultProjectTrust, MermaidRenderingMode, TuiMode, WarningSettings } from "../../../core/settings-manager.ts";
6
+ import type { DefaultProjectTrust, FullscreenExitOutput, MermaidRenderingMode, TuiMode, WarningSettings } from "../../../core/settings-manager.ts";
7
7
  import { type TerminalTheme } from "../theme/theme.ts";
8
8
  /**
9
9
  * What outranks a user-scope write, phrased for someone deciding whether to bother.
@@ -47,6 +47,7 @@ export interface SettingsConfig {
47
47
  clearOnShrink: boolean;
48
48
  showTerminalProgress: boolean;
49
49
  tuiMode: TuiMode;
50
+ fullscreenExitOutput: FullscreenExitOutput;
50
51
  fullscreenScrollbar: ScrollViewScrollbar;
51
52
  warnings: WarningSettings;
52
53
  }
@@ -81,6 +82,7 @@ export interface SettingsCallbacks {
81
82
  onClearOnShrinkChange: (enabled: boolean) => void;
82
83
  onShowTerminalProgressChange: (enabled: boolean) => void;
83
84
  onTuiModeChange: (mode: TuiMode) => void;
85
+ onFullscreenExitOutputChange: (output: FullscreenExitOutput) => void;
84
86
  onFullscreenScrollbarChange: (mode: ScrollViewScrollbar) => void;
85
87
  onWarningsChange: (warnings: WarningSettings) => void;
86
88
  onCancel: () => void;