@alan-ai-hq/agent-manager 0.1.113 → 0.1.115

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 (655) hide show
  1. package/LICENSE +88 -0
  2. package/dist/__tests__/binding-switch-git.test.d.ts +2 -0
  3. package/dist/__tests__/binding-switch-git.test.d.ts.map +1 -0
  4. package/dist/__tests__/binding-switch-git.test.js +555 -0
  5. package/dist/__tests__/binding-switch-git.test.js.map +1 -0
  6. package/dist/__tests__/cli-ensure-install.test.d.ts +2 -0
  7. package/dist/__tests__/cli-ensure-install.test.d.ts.map +1 -0
  8. package/dist/__tests__/cli-ensure-install.test.js +177 -0
  9. package/dist/__tests__/cli-ensure-install.test.js.map +1 -0
  10. package/dist/__tests__/cli-executable.test.d.ts +2 -0
  11. package/dist/__tests__/cli-executable.test.d.ts.map +1 -0
  12. package/dist/__tests__/cli-executable.test.js +155 -0
  13. package/dist/__tests__/cli-executable.test.js.map +1 -0
  14. package/dist/__tests__/cli-executable.windows.test.d.ts +2 -0
  15. package/dist/__tests__/cli-executable.windows.test.d.ts.map +1 -0
  16. package/dist/__tests__/cli-executable.windows.test.js +123 -0
  17. package/dist/__tests__/cli-executable.windows.test.js.map +1 -0
  18. package/dist/__tests__/cli-help.test.d.ts +2 -0
  19. package/dist/__tests__/cli-help.test.d.ts.map +1 -0
  20. package/dist/__tests__/cli-help.test.js +47 -0
  21. package/dist/__tests__/cli-help.test.js.map +1 -0
  22. package/dist/__tests__/computer-readiness.test.d.ts +2 -0
  23. package/dist/__tests__/computer-readiness.test.d.ts.map +1 -0
  24. package/dist/__tests__/computer-readiness.test.js +63 -0
  25. package/dist/__tests__/computer-readiness.test.js.map +1 -0
  26. package/dist/__tests__/core-agent-provider-auth-env.test.d.ts +2 -0
  27. package/dist/__tests__/core-agent-provider-auth-env.test.d.ts.map +1 -0
  28. package/dist/__tests__/core-agent-provider-auth-env.test.js +93 -0
  29. package/dist/__tests__/core-agent-provider-auth-env.test.js.map +1 -0
  30. package/dist/__tests__/core-agent-workflow-context.test.d.ts +2 -0
  31. package/dist/__tests__/core-agent-workflow-context.test.d.ts.map +1 -0
  32. package/dist/__tests__/core-agent-workflow-context.test.js +139 -0
  33. package/dist/__tests__/core-agent-workflow-context.test.js.map +1 -0
  34. package/dist/__tests__/daemon-native-hooks.test.d.ts +2 -0
  35. package/dist/__tests__/daemon-native-hooks.test.d.ts.map +1 -0
  36. package/dist/__tests__/daemon-native-hooks.test.js +94 -0
  37. package/dist/__tests__/daemon-native-hooks.test.js.map +1 -0
  38. package/dist/__tests__/daemon-reconcile-ask.test.d.ts +2 -0
  39. package/dist/__tests__/daemon-reconcile-ask.test.d.ts.map +1 -0
  40. package/dist/__tests__/daemon-reconcile-ask.test.js +100 -0
  41. package/dist/__tests__/daemon-reconcile-ask.test.js.map +1 -0
  42. package/dist/__tests__/daemon-runner-status.test.d.ts +2 -0
  43. package/dist/__tests__/daemon-runner-status.test.d.ts.map +1 -0
  44. package/dist/__tests__/daemon-runner-status.test.js +102 -0
  45. package/dist/__tests__/daemon-runner-status.test.js.map +1 -0
  46. package/dist/__tests__/daemon-socket.integration.test.d.ts +2 -0
  47. package/dist/__tests__/daemon-socket.integration.test.d.ts.map +1 -0
  48. package/dist/__tests__/daemon-socket.integration.test.js +435 -0
  49. package/dist/__tests__/daemon-socket.integration.test.js.map +1 -0
  50. package/dist/__tests__/daemon.test.d.ts +2 -0
  51. package/dist/__tests__/daemon.test.d.ts.map +1 -0
  52. package/dist/__tests__/daemon.test.js +4884 -0
  53. package/dist/__tests__/daemon.test.js.map +1 -0
  54. package/dist/__tests__/ensure-shared-checkout-branch.test.d.ts +2 -0
  55. package/dist/__tests__/ensure-shared-checkout-branch.test.d.ts.map +1 -0
  56. package/dist/__tests__/ensure-shared-checkout-branch.test.js +129 -0
  57. package/dist/__tests__/ensure-shared-checkout-branch.test.js.map +1 -0
  58. package/dist/__tests__/event-journal-stress.test.d.ts +2 -0
  59. package/dist/__tests__/event-journal-stress.test.d.ts.map +1 -0
  60. package/dist/__tests__/event-journal-stress.test.js +124 -0
  61. package/dist/__tests__/event-journal-stress.test.js.map +1 -0
  62. package/dist/__tests__/event-journal.test.d.ts +2 -0
  63. package/dist/__tests__/event-journal.test.d.ts.map +1 -0
  64. package/dist/__tests__/event-journal.test.js +264 -0
  65. package/dist/__tests__/event-journal.test.js.map +1 -0
  66. package/dist/__tests__/event-outbox-persist-failure.test.d.ts +2 -0
  67. package/dist/__tests__/event-outbox-persist-failure.test.d.ts.map +1 -0
  68. package/dist/__tests__/event-outbox-persist-failure.test.js +143 -0
  69. package/dist/__tests__/event-outbox-persist-failure.test.js.map +1 -0
  70. package/dist/__tests__/event-outbox.test.d.ts +2 -0
  71. package/dist/__tests__/event-outbox.test.d.ts.map +1 -0
  72. package/dist/__tests__/event-outbox.test.js +602 -0
  73. package/dist/__tests__/event-outbox.test.js.map +1 -0
  74. package/dist/__tests__/execution-activity-tracker.test.d.ts +2 -0
  75. package/dist/__tests__/execution-activity-tracker.test.d.ts.map +1 -0
  76. package/dist/__tests__/execution-activity-tracker.test.js +64 -0
  77. package/dist/__tests__/execution-activity-tracker.test.js.map +1 -0
  78. package/dist/__tests__/execution-lease-client.test.d.ts +2 -0
  79. package/dist/__tests__/execution-lease-client.test.d.ts.map +1 -0
  80. package/dist/__tests__/execution-lease-client.test.js +274 -0
  81. package/dist/__tests__/execution-lease-client.test.js.map +1 -0
  82. package/dist/__tests__/fake-provider-cli.test.d.ts +2 -0
  83. package/dist/__tests__/fake-provider-cli.test.d.ts.map +1 -0
  84. package/dist/__tests__/fake-provider-cli.test.js +46 -0
  85. package/dist/__tests__/fake-provider-cli.test.js.map +1 -0
  86. package/dist/__tests__/generated-images.test.d.ts +2 -0
  87. package/dist/__tests__/generated-images.test.d.ts.map +1 -0
  88. package/dist/__tests__/generated-images.test.js +132 -0
  89. package/dist/__tests__/generated-images.test.js.map +1 -0
  90. package/dist/__tests__/helpers/sandbox-outbox.d.ts +3 -0
  91. package/dist/__tests__/helpers/sandbox-outbox.d.ts.map +1 -0
  92. package/dist/__tests__/helpers/sandbox-outbox.js +23 -0
  93. package/dist/__tests__/helpers/sandbox-outbox.js.map +1 -0
  94. package/dist/__tests__/install-command.test.d.ts +2 -0
  95. package/dist/__tests__/install-command.test.d.ts.map +1 -0
  96. package/dist/__tests__/install-command.test.js +185 -0
  97. package/dist/__tests__/install-command.test.js.map +1 -0
  98. package/dist/__tests__/integration-cli.test.d.ts +2 -0
  99. package/dist/__tests__/integration-cli.test.d.ts.map +1 -0
  100. package/dist/__tests__/integration-cli.test.js +150 -0
  101. package/dist/__tests__/integration-cli.test.js.map +1 -0
  102. package/dist/__tests__/lifecycle-contracts.test.d.ts +2 -0
  103. package/dist/__tests__/lifecycle-contracts.test.d.ts.map +1 -0
  104. package/dist/__tests__/lifecycle-contracts.test.js +66 -0
  105. package/dist/__tests__/lifecycle-contracts.test.js.map +1 -0
  106. package/dist/__tests__/lifecycle-logger.test.d.ts +2 -0
  107. package/dist/__tests__/lifecycle-logger.test.d.ts.map +1 -0
  108. package/dist/__tests__/lifecycle-logger.test.js +49 -0
  109. package/dist/__tests__/lifecycle-logger.test.js.map +1 -0
  110. package/dist/__tests__/local-daemon-lifecycle.test.d.ts +2 -0
  111. package/dist/__tests__/local-daemon-lifecycle.test.d.ts.map +1 -0
  112. package/dist/__tests__/local-daemon-lifecycle.test.js +594 -0
  113. package/dist/__tests__/local-daemon-lifecycle.test.js.map +1 -0
  114. package/dist/__tests__/local-git-status.test.d.ts +2 -0
  115. package/dist/__tests__/local-git-status.test.d.ts.map +1 -0
  116. package/dist/__tests__/local-git-status.test.js +31 -0
  117. package/dist/__tests__/local-git-status.test.js.map +1 -0
  118. package/dist/__tests__/mcp-config-adapters-files.test.d.ts +2 -0
  119. package/dist/__tests__/mcp-config-adapters-files.test.d.ts.map +1 -0
  120. package/dist/__tests__/mcp-config-adapters-files.test.js +486 -0
  121. package/dist/__tests__/mcp-config-adapters-files.test.js.map +1 -0
  122. package/dist/__tests__/mcp-config-adapters.test.d.ts +2 -0
  123. package/dist/__tests__/mcp-config-adapters.test.d.ts.map +1 -0
  124. package/dist/__tests__/mcp-config-adapters.test.js +492 -0
  125. package/dist/__tests__/mcp-config-adapters.test.js.map +1 -0
  126. package/dist/__tests__/mcp-sync.test.d.ts +2 -0
  127. package/dist/__tests__/mcp-sync.test.d.ts.map +1 -0
  128. package/dist/__tests__/mcp-sync.test.js +21 -0
  129. package/dist/__tests__/mcp-sync.test.js.map +1 -0
  130. package/dist/__tests__/model-scan-runner.test.d.ts +2 -0
  131. package/dist/__tests__/model-scan-runner.test.d.ts.map +1 -0
  132. package/dist/__tests__/model-scan-runner.test.js +80 -0
  133. package/dist/__tests__/model-scan-runner.test.js.map +1 -0
  134. package/dist/__tests__/native-hook-command.test.d.ts +2 -0
  135. package/dist/__tests__/native-hook-command.test.d.ts.map +1 -0
  136. package/dist/__tests__/native-hook-command.test.js +386 -0
  137. package/dist/__tests__/native-hook-command.test.js.map +1 -0
  138. package/dist/__tests__/native-hook-config.test.d.ts +2 -0
  139. package/dist/__tests__/native-hook-config.test.d.ts.map +1 -0
  140. package/dist/__tests__/native-hook-config.test.js +374 -0
  141. package/dist/__tests__/native-hook-config.test.js.map +1 -0
  142. package/dist/__tests__/native-hook-drain.test.d.ts +2 -0
  143. package/dist/__tests__/native-hook-drain.test.d.ts.map +1 -0
  144. package/dist/__tests__/native-hook-drain.test.js +197 -0
  145. package/dist/__tests__/native-hook-drain.test.js.map +1 -0
  146. package/dist/__tests__/native-hook-inbox.test.d.ts +2 -0
  147. package/dist/__tests__/native-hook-inbox.test.d.ts.map +1 -0
  148. package/dist/__tests__/native-hook-inbox.test.js +139 -0
  149. package/dist/__tests__/native-hook-inbox.test.js.map +1 -0
  150. package/dist/__tests__/native-session.test.d.ts +2 -0
  151. package/dist/__tests__/native-session.test.d.ts.map +1 -0
  152. package/dist/__tests__/native-session.test.js +41 -0
  153. package/dist/__tests__/native-session.test.js.map +1 -0
  154. package/dist/__tests__/native-stream-outbox.test.d.ts +2 -0
  155. package/dist/__tests__/native-stream-outbox.test.d.ts.map +1 -0
  156. package/dist/__tests__/native-stream-outbox.test.js +231 -0
  157. package/dist/__tests__/native-stream-outbox.test.js.map +1 -0
  158. package/dist/__tests__/native-stream-platform.test.d.ts +2 -0
  159. package/dist/__tests__/native-stream-platform.test.d.ts.map +1 -0
  160. package/dist/__tests__/native-stream-platform.test.js +20 -0
  161. package/dist/__tests__/native-stream-platform.test.js.map +1 -0
  162. package/dist/__tests__/native-transcript-cursor.test.d.ts +2 -0
  163. package/dist/__tests__/native-transcript-cursor.test.d.ts.map +1 -0
  164. package/dist/__tests__/native-transcript-cursor.test.js +60 -0
  165. package/dist/__tests__/native-transcript-cursor.test.js.map +1 -0
  166. package/dist/__tests__/native-transcript-stream.test.d.ts +2 -0
  167. package/dist/__tests__/native-transcript-stream.test.d.ts.map +1 -0
  168. package/dist/__tests__/native-transcript-stream.test.js +378 -0
  169. package/dist/__tests__/native-transcript-stream.test.js.map +1 -0
  170. package/dist/__tests__/network-diagnostics.test.d.ts +2 -0
  171. package/dist/__tests__/network-diagnostics.test.d.ts.map +1 -0
  172. package/dist/__tests__/network-diagnostics.test.js +26 -0
  173. package/dist/__tests__/network-diagnostics.test.js.map +1 -0
  174. package/dist/__tests__/observable-map.test.d.ts +2 -0
  175. package/dist/__tests__/observable-map.test.d.ts.map +1 -0
  176. package/dist/__tests__/observable-map.test.js +54 -0
  177. package/dist/__tests__/observable-map.test.js.map +1 -0
  178. package/dist/__tests__/outbox-baseline.bench.test.d.ts +2 -0
  179. package/dist/__tests__/outbox-baseline.bench.test.d.ts.map +1 -0
  180. package/dist/__tests__/outbox-baseline.bench.test.js +108 -0
  181. package/dist/__tests__/outbox-baseline.bench.test.js.map +1 -0
  182. package/dist/__tests__/provider-auth-preflight.test.d.ts +2 -0
  183. package/dist/__tests__/provider-auth-preflight.test.d.ts.map +1 -0
  184. package/dist/__tests__/provider-auth-preflight.test.js +123 -0
  185. package/dist/__tests__/provider-auth-preflight.test.js.map +1 -0
  186. package/dist/__tests__/provider-model-discovery.test.d.ts +2 -0
  187. package/dist/__tests__/provider-model-discovery.test.d.ts.map +1 -0
  188. package/dist/__tests__/provider-model-discovery.test.js +1071 -0
  189. package/dist/__tests__/provider-model-discovery.test.js.map +1 -0
  190. package/dist/__tests__/resource-diagnostics.test.d.ts +2 -0
  191. package/dist/__tests__/resource-diagnostics.test.d.ts.map +1 -0
  192. package/dist/__tests__/resource-diagnostics.test.js +138 -0
  193. package/dist/__tests__/resource-diagnostics.test.js.map +1 -0
  194. package/dist/__tests__/resume-fallback-env.test.d.ts +2 -0
  195. package/dist/__tests__/resume-fallback-env.test.d.ts.map +1 -0
  196. package/dist/__tests__/resume-fallback-env.test.js +28 -0
  197. package/dist/__tests__/resume-fallback-env.test.js.map +1 -0
  198. package/dist/__tests__/run-journal.test.d.ts +2 -0
  199. package/dist/__tests__/run-journal.test.d.ts.map +1 -0
  200. package/dist/__tests__/run-journal.test.js +121 -0
  201. package/dist/__tests__/run-journal.test.js.map +1 -0
  202. package/dist/__tests__/run-start-gate.test.d.ts +2 -0
  203. package/dist/__tests__/run-start-gate.test.d.ts.map +1 -0
  204. package/dist/__tests__/run-start-gate.test.js +18 -0
  205. package/dist/__tests__/run-start-gate.test.js.map +1 -0
  206. package/dist/__tests__/runtime-capabilities-sync.test.d.ts +2 -0
  207. package/dist/__tests__/runtime-capabilities-sync.test.d.ts.map +1 -0
  208. package/dist/__tests__/runtime-capabilities-sync.test.js +66 -0
  209. package/dist/__tests__/runtime-capabilities-sync.test.js.map +1 -0
  210. package/dist/__tests__/runtime-config.test.d.ts +2 -0
  211. package/dist/__tests__/runtime-config.test.d.ts.map +1 -0
  212. package/dist/__tests__/runtime-config.test.js +137 -0
  213. package/dist/__tests__/runtime-config.test.js.map +1 -0
  214. package/dist/__tests__/runtime-presenter-heartbeat.test.d.ts +2 -0
  215. package/dist/__tests__/runtime-presenter-heartbeat.test.d.ts.map +1 -0
  216. package/dist/__tests__/runtime-presenter-heartbeat.test.js +253 -0
  217. package/dist/__tests__/runtime-presenter-heartbeat.test.js.map +1 -0
  218. package/dist/__tests__/runtime-registration.test.d.ts +2 -0
  219. package/dist/__tests__/runtime-registration.test.d.ts.map +1 -0
  220. package/dist/__tests__/runtime-registration.test.js +109 -0
  221. package/dist/__tests__/runtime-registration.test.js.map +1 -0
  222. package/dist/__tests__/sandbox-outbox-storage.test.d.ts +2 -0
  223. package/dist/__tests__/sandbox-outbox-storage.test.d.ts.map +1 -0
  224. package/dist/__tests__/sandbox-outbox-storage.test.js +81 -0
  225. package/dist/__tests__/sandbox-outbox-storage.test.js.map +1 -0
  226. package/dist/__tests__/sandbox-outbox.test.d.ts +2 -0
  227. package/dist/__tests__/sandbox-outbox.test.d.ts.map +1 -0
  228. package/dist/__tests__/sandbox-outbox.test.js +300 -0
  229. package/dist/__tests__/sandbox-outbox.test.js.map +1 -0
  230. package/dist/__tests__/sandbox-workflow-context.test.d.ts +2 -0
  231. package/dist/__tests__/sandbox-workflow-context.test.d.ts.map +1 -0
  232. package/dist/__tests__/sandbox-workflow-context.test.js +600 -0
  233. package/dist/__tests__/sandbox-workflow-context.test.js.map +1 -0
  234. package/dist/__tests__/service-manager-deadline.test.d.ts +2 -0
  235. package/dist/__tests__/service-manager-deadline.test.d.ts.map +1 -0
  236. package/dist/__tests__/service-manager-deadline.test.js +36 -0
  237. package/dist/__tests__/service-manager-deadline.test.js.map +1 -0
  238. package/dist/__tests__/service-manager.test.d.ts +2 -0
  239. package/dist/__tests__/service-manager.test.d.ts.map +1 -0
  240. package/dist/__tests__/service-manager.test.js +769 -0
  241. package/dist/__tests__/service-manager.test.js.map +1 -0
  242. package/dist/__tests__/service-stop-ownership.test.d.ts +2 -0
  243. package/dist/__tests__/service-stop-ownership.test.d.ts.map +1 -0
  244. package/dist/__tests__/service-stop-ownership.test.js +89 -0
  245. package/dist/__tests__/service-stop-ownership.test.js.map +1 -0
  246. package/dist/__tests__/skill-invocation.test.d.ts +2 -0
  247. package/dist/__tests__/skill-invocation.test.d.ts.map +1 -0
  248. package/dist/__tests__/skill-invocation.test.js +40 -0
  249. package/dist/__tests__/skill-invocation.test.js.map +1 -0
  250. package/dist/__tests__/sleep-inhibitor-real.test.d.ts +2 -0
  251. package/dist/__tests__/sleep-inhibitor-real.test.d.ts.map +1 -0
  252. package/dist/__tests__/sleep-inhibitor-real.test.js +68 -0
  253. package/dist/__tests__/sleep-inhibitor-real.test.js.map +1 -0
  254. package/dist/__tests__/sleep-inhibitor.test.d.ts +2 -0
  255. package/dist/__tests__/sleep-inhibitor.test.d.ts.map +1 -0
  256. package/dist/__tests__/sleep-inhibitor.test.js +236 -0
  257. package/dist/__tests__/sleep-inhibitor.test.js.map +1 -0
  258. package/dist/__tests__/status-local-workspace.test.d.ts +2 -0
  259. package/dist/__tests__/status-local-workspace.test.d.ts.map +1 -0
  260. package/dist/__tests__/status-local-workspace.test.js +121 -0
  261. package/dist/__tests__/status-local-workspace.test.js.map +1 -0
  262. package/dist/__tests__/suspension-detector.test.d.ts +2 -0
  263. package/dist/__tests__/suspension-detector.test.d.ts.map +1 -0
  264. package/dist/__tests__/suspension-detector.test.js +112 -0
  265. package/dist/__tests__/suspension-detector.test.js.map +1 -0
  266. package/dist/__tests__/web-presenter-parent-events.test.d.ts +2 -0
  267. package/dist/__tests__/web-presenter-parent-events.test.d.ts.map +1 -0
  268. package/dist/__tests__/web-presenter-parent-events.test.js +168 -0
  269. package/dist/__tests__/web-presenter-parent-events.test.js.map +1 -0
  270. package/dist/__tests__/workspace-probe-concurrency.test.d.ts +2 -0
  271. package/dist/__tests__/workspace-probe-concurrency.test.d.ts.map +1 -0
  272. package/dist/__tests__/workspace-probe-concurrency.test.js +54 -0
  273. package/dist/__tests__/workspace-probe-concurrency.test.js.map +1 -0
  274. package/dist/__tests__/workspace-relocation.test.d.ts +2 -0
  275. package/dist/__tests__/workspace-relocation.test.d.ts.map +1 -0
  276. package/dist/__tests__/workspace-relocation.test.js +66 -0
  277. package/dist/__tests__/workspace-relocation.test.js.map +1 -0
  278. package/dist/__tests__/workspace-rpc.test.d.ts +2 -0
  279. package/dist/__tests__/workspace-rpc.test.d.ts.map +1 -0
  280. package/dist/__tests__/workspace-rpc.test.js +90 -0
  281. package/dist/__tests__/workspace-rpc.test.js.map +1 -0
  282. package/dist/__tests__/workspace-run-lease.test.d.ts +2 -0
  283. package/dist/__tests__/workspace-run-lease.test.d.ts.map +1 -0
  284. package/dist/__tests__/workspace-run-lease.test.js +331 -0
  285. package/dist/__tests__/workspace-run-lease.test.js.map +1 -0
  286. package/dist/__tests__/ws-client-connection.test.d.ts +2 -0
  287. package/dist/__tests__/ws-client-connection.test.d.ts.map +1 -0
  288. package/dist/__tests__/ws-client-connection.test.js +295 -0
  289. package/dist/__tests__/ws-client-connection.test.js.map +1 -0
  290. package/dist/__tests__/ws-client-heartbeat-volatile.test.d.ts +2 -0
  291. package/dist/__tests__/ws-client-heartbeat-volatile.test.d.ts.map +1 -0
  292. package/dist/__tests__/ws-client-heartbeat-volatile.test.js +63 -0
  293. package/dist/__tests__/ws-client-heartbeat-volatile.test.js.map +1 -0
  294. package/dist/__tests__/ws-client-heartbeat.integration.test.d.ts +2 -0
  295. package/dist/__tests__/ws-client-heartbeat.integration.test.d.ts.map +1 -0
  296. package/dist/__tests__/ws-client-heartbeat.integration.test.js +236 -0
  297. package/dist/__tests__/ws-client-heartbeat.integration.test.js.map +1 -0
  298. package/dist/__tests__/ws-client-user-message-ack.integration.test.d.ts +2 -0
  299. package/dist/__tests__/ws-client-user-message-ack.integration.test.d.ts.map +1 -0
  300. package/dist/__tests__/ws-client-user-message-ack.integration.test.js +211 -0
  301. package/dist/__tests__/ws-client-user-message-ack.integration.test.js.map +1 -0
  302. package/dist/antigravity-api-key-config.d.ts +2 -0
  303. package/dist/antigravity-api-key-config.d.ts.map +1 -0
  304. package/dist/antigravity-api-key-config.js +79 -0
  305. package/dist/antigravity-api-key-config.js.map +1 -0
  306. package/dist/binding-switch-git.d.ts +26 -0
  307. package/dist/binding-switch-git.d.ts.map +1 -0
  308. package/dist/binding-switch-git.js +871 -0
  309. package/dist/binding-switch-git.js.map +1 -0
  310. package/dist/cli-ensure-install.d.ts +34 -0
  311. package/dist/cli-ensure-install.d.ts.map +1 -0
  312. package/dist/cli-ensure-install.js +154 -0
  313. package/dist/cli-ensure-install.js.map +1 -0
  314. package/dist/cli-executable.d.ts +44 -0
  315. package/dist/cli-executable.d.ts.map +1 -0
  316. package/dist/cli-executable.js +435 -0
  317. package/dist/cli-executable.js.map +1 -0
  318. package/dist/cli-help.d.ts +11 -0
  319. package/dist/cli-help.d.ts.map +1 -0
  320. package/dist/cli-help.js +65 -0
  321. package/dist/cli-help.js.map +1 -0
  322. package/dist/computer-readiness.d.ts +41 -0
  323. package/dist/computer-readiness.d.ts.map +1 -0
  324. package/dist/computer-readiness.js +191 -0
  325. package/dist/computer-readiness.js.map +1 -0
  326. package/dist/core-agent.d.ts +32 -0
  327. package/dist/core-agent.d.ts.map +1 -0
  328. package/dist/core-agent.js +331 -0
  329. package/dist/core-agent.js.map +1 -0
  330. package/dist/daemon-execute.d.ts +42 -0
  331. package/dist/daemon-execute.d.ts.map +1 -0
  332. package/dist/daemon-execute.js +666 -0
  333. package/dist/daemon-execute.js.map +1 -0
  334. package/dist/daemon-helpers.d.ts +150 -0
  335. package/dist/daemon-helpers.d.ts.map +1 -0
  336. package/dist/daemon-helpers.js +157 -0
  337. package/dist/daemon-helpers.js.map +1 -0
  338. package/dist/daemon-local-control.d.ts +208 -0
  339. package/dist/daemon-local-control.d.ts.map +1 -0
  340. package/dist/daemon-local-control.js +826 -0
  341. package/dist/daemon-local-control.js.map +1 -0
  342. package/dist/daemon-local-http.d.ts +57 -0
  343. package/dist/daemon-local-http.d.ts.map +1 -0
  344. package/dist/daemon-local-http.js +375 -0
  345. package/dist/daemon-local-http.js.map +1 -0
  346. package/dist/daemon-native-hooks.d.ts +17 -0
  347. package/dist/daemon-native-hooks.d.ts.map +1 -0
  348. package/dist/daemon-native-hooks.js +54 -0
  349. package/dist/daemon-native-hooks.js.map +1 -0
  350. package/dist/daemon-provider-scan.d.ts +33 -0
  351. package/dist/daemon-provider-scan.d.ts.map +1 -0
  352. package/dist/daemon-provider-scan.js +187 -0
  353. package/dist/daemon-provider-scan.js.map +1 -0
  354. package/dist/daemon-runner-status.d.ts +164 -0
  355. package/dist/daemon-runner-status.d.ts.map +1 -0
  356. package/dist/daemon-runner-status.js +424 -0
  357. package/dist/daemon-runner-status.js.map +1 -0
  358. package/dist/daemon-start.d.ts +5 -0
  359. package/dist/daemon-start.d.ts.map +1 -0
  360. package/dist/daemon-start.js +1313 -0
  361. package/dist/daemon-start.js.map +1 -0
  362. package/dist/daemon-workspace.d.ts +36 -0
  363. package/dist/daemon-workspace.d.ts.map +1 -0
  364. package/dist/daemon-workspace.js +154 -0
  365. package/dist/daemon-workspace.js.map +1 -0
  366. package/dist/daemon.d.ts +59 -0
  367. package/dist/daemon.d.ts.map +1 -0
  368. package/dist/daemon.js +681 -0
  369. package/dist/daemon.js.map +1 -0
  370. package/dist/event-journal-crypto.d.ts +14 -0
  371. package/dist/event-journal-crypto.d.ts.map +1 -0
  372. package/dist/event-journal-crypto.js +89 -0
  373. package/dist/event-journal-crypto.js.map +1 -0
  374. package/dist/event-journal-paths.d.ts +10 -0
  375. package/dist/event-journal-paths.d.ts.map +1 -0
  376. package/dist/event-journal-paths.js +29 -0
  377. package/dist/event-journal-paths.js.map +1 -0
  378. package/dist/event-journal-recovery.d.ts +83 -0
  379. package/dist/event-journal-recovery.d.ts.map +1 -0
  380. package/dist/event-journal-recovery.js +333 -0
  381. package/dist/event-journal-recovery.js.map +1 -0
  382. package/dist/event-journal-types.d.ts +54 -0
  383. package/dist/event-journal-types.d.ts.map +1 -0
  384. package/dist/event-journal-types.js +10 -0
  385. package/dist/event-journal-types.js.map +1 -0
  386. package/dist/event-journal-writer.d.ts +44 -0
  387. package/dist/event-journal-writer.d.ts.map +1 -0
  388. package/dist/event-journal-writer.js +182 -0
  389. package/dist/event-journal-writer.js.map +1 -0
  390. package/dist/event-outbox.d.ts +113 -0
  391. package/dist/event-outbox.d.ts.map +1 -0
  392. package/dist/event-outbox.js +707 -0
  393. package/dist/event-outbox.js.map +1 -0
  394. package/dist/execution-activity-tracker.d.ts +43 -0
  395. package/dist/execution-activity-tracker.d.ts.map +1 -0
  396. package/dist/execution-activity-tracker.js +116 -0
  397. package/dist/execution-activity-tracker.js.map +1 -0
  398. package/dist/execution-lease-client.d.ts +62 -0
  399. package/dist/execution-lease-client.d.ts.map +1 -0
  400. package/dist/execution-lease-client.js +271 -0
  401. package/dist/execution-lease-client.js.map +1 -0
  402. package/dist/fake-provider-cli.d.ts +13 -0
  403. package/dist/fake-provider-cli.d.ts.map +1 -0
  404. package/dist/fake-provider-cli.js +53 -0
  405. package/dist/fake-provider-cli.js.map +1 -0
  406. package/dist/generated-images.d.ts +2 -0
  407. package/dist/generated-images.d.ts.map +1 -0
  408. package/dist/generated-images.js +2 -0
  409. package/dist/generated-images.js.map +1 -0
  410. package/dist/index.cjs +47033 -45514
  411. package/dist/index.d.ts +2 -0
  412. package/dist/index.d.ts.map +1 -0
  413. package/dist/index.js +224 -0
  414. package/dist/index.js.map +1 -0
  415. package/dist/install-command.d.ts +2 -0
  416. package/dist/install-command.d.ts.map +1 -0
  417. package/dist/install-command.js +96 -0
  418. package/dist/install-command.js.map +1 -0
  419. package/dist/integration-cli.d.ts +14 -0
  420. package/dist/integration-cli.d.ts.map +1 -0
  421. package/dist/integration-cli.js +80 -0
  422. package/dist/integration-cli.js.map +1 -0
  423. package/dist/lifecycle-logger.d.ts +25 -0
  424. package/dist/lifecycle-logger.d.ts.map +1 -0
  425. package/dist/lifecycle-logger.js +73 -0
  426. package/dist/lifecycle-logger.js.map +1 -0
  427. package/dist/local-daemon-lifecycle.d.ts +112 -0
  428. package/dist/local-daemon-lifecycle.d.ts.map +1 -0
  429. package/dist/local-daemon-lifecycle.js +433 -0
  430. package/dist/local-daemon-lifecycle.js.map +1 -0
  431. package/dist/local-git-status.d.ts +14 -0
  432. package/dist/local-git-status.d.ts.map +1 -0
  433. package/dist/local-git-status.js +43 -0
  434. package/dist/local-git-status.js.map +1 -0
  435. package/dist/managed-mcp.d.ts +7 -0
  436. package/dist/managed-mcp.d.ts.map +1 -0
  437. package/dist/managed-mcp.js +16 -0
  438. package/dist/managed-mcp.js.map +1 -0
  439. package/dist/mcp-config-adapters.d.ts +57 -0
  440. package/dist/mcp-config-adapters.d.ts.map +1 -0
  441. package/dist/mcp-config-adapters.js +929 -0
  442. package/dist/mcp-config-adapters.js.map +1 -0
  443. package/dist/mcp-sync.d.ts +44 -0
  444. package/dist/mcp-sync.d.ts.map +1 -0
  445. package/dist/mcp-sync.js +68 -0
  446. package/dist/mcp-sync.js.map +1 -0
  447. package/dist/model-scan-runner.d.ts +15 -0
  448. package/dist/model-scan-runner.d.ts.map +1 -0
  449. package/dist/model-scan-runner.js +56 -0
  450. package/dist/model-scan-runner.js.map +1 -0
  451. package/dist/native-hook-command.d.ts +31 -0
  452. package/dist/native-hook-command.d.ts.map +1 -0
  453. package/dist/native-hook-command.js +289 -0
  454. package/dist/native-hook-command.js.map +1 -0
  455. package/dist/native-hook-config.d.ts +43 -0
  456. package/dist/native-hook-config.d.ts.map +1 -0
  457. package/dist/native-hook-config.js +601 -0
  458. package/dist/native-hook-config.js.map +1 -0
  459. package/dist/native-hook-drain.d.ts +34 -0
  460. package/dist/native-hook-drain.d.ts.map +1 -0
  461. package/dist/native-hook-drain.js +83 -0
  462. package/dist/native-hook-drain.js.map +1 -0
  463. package/dist/native-hook-inbox.d.ts +40 -0
  464. package/dist/native-hook-inbox.d.ts.map +1 -0
  465. package/dist/native-hook-inbox.js +217 -0
  466. package/dist/native-hook-inbox.js.map +1 -0
  467. package/dist/native-project-config.d.ts +11 -0
  468. package/dist/native-project-config.d.ts.map +1 -0
  469. package/dist/native-project-config.js +49 -0
  470. package/dist/native-project-config.js.map +1 -0
  471. package/dist/native-session.d.ts +20 -0
  472. package/dist/native-session.d.ts.map +1 -0
  473. package/dist/native-session.js +213 -0
  474. package/dist/native-session.js.map +1 -0
  475. package/dist/native-stream-journal.d.ts +22 -0
  476. package/dist/native-stream-journal.d.ts.map +1 -0
  477. package/dist/native-stream-journal.js +98 -0
  478. package/dist/native-stream-journal.js.map +1 -0
  479. package/dist/native-stream-outbox.d.ts +40 -0
  480. package/dist/native-stream-outbox.d.ts.map +1 -0
  481. package/dist/native-stream-outbox.js +358 -0
  482. package/dist/native-stream-outbox.js.map +1 -0
  483. package/dist/native-stream-platform.d.ts +4 -0
  484. package/dist/native-stream-platform.d.ts.map +1 -0
  485. package/dist/native-stream-platform.js +11 -0
  486. package/dist/native-stream-platform.js.map +1 -0
  487. package/dist/native-transcript-cursor.d.ts +33 -0
  488. package/dist/native-transcript-cursor.d.ts.map +1 -0
  489. package/dist/native-transcript-cursor.js +108 -0
  490. package/dist/native-transcript-cursor.js.map +1 -0
  491. package/dist/native-transcript-stream.d.ts +20 -0
  492. package/dist/native-transcript-stream.d.ts.map +1 -0
  493. package/dist/native-transcript-stream.js +438 -0
  494. package/dist/native-transcript-stream.js.map +1 -0
  495. package/dist/network-diagnostics.d.ts +8 -0
  496. package/dist/network-diagnostics.d.ts.map +1 -0
  497. package/dist/network-diagnostics.js +56 -0
  498. package/dist/network-diagnostics.js.map +1 -0
  499. package/dist/observable-map.d.ts +13 -0
  500. package/dist/observable-map.d.ts.map +1 -0
  501. package/dist/observable-map.js +30 -0
  502. package/dist/observable-map.js.map +1 -0
  503. package/dist/provider-auth-preflight.d.ts +19 -0
  504. package/dist/provider-auth-preflight.d.ts.map +1 -0
  505. package/dist/provider-auth-preflight.js +107 -0
  506. package/dist/provider-auth-preflight.js.map +1 -0
  507. package/dist/provider-model-discovery.d.ts +33 -0
  508. package/dist/provider-model-discovery.d.ts.map +1 -0
  509. package/dist/provider-model-discovery.js +731 -0
  510. package/dist/provider-model-discovery.js.map +1 -0
  511. package/dist/resource-diagnostics.d.ts +21 -0
  512. package/dist/resource-diagnostics.d.ts.map +1 -0
  513. package/dist/resource-diagnostics.js +161 -0
  514. package/dist/resource-diagnostics.js.map +1 -0
  515. package/dist/resume-fallback-env.d.ts +3 -0
  516. package/dist/resume-fallback-env.d.ts.map +1 -0
  517. package/dist/resume-fallback-env.js +14 -0
  518. package/dist/resume-fallback-env.js.map +1 -0
  519. package/dist/run-journal.d.ts +51 -0
  520. package/dist/run-journal.d.ts.map +1 -0
  521. package/dist/run-journal.js +193 -0
  522. package/dist/run-journal.js.map +1 -0
  523. package/dist/runtime-backends.d.ts +2 -0
  524. package/dist/runtime-backends.d.ts.map +1 -0
  525. package/dist/runtime-backends.js +2 -0
  526. package/dist/runtime-backends.js.map +1 -0
  527. package/dist/runtime-capabilities-sync.d.ts +40 -0
  528. package/dist/runtime-capabilities-sync.d.ts.map +1 -0
  529. package/dist/runtime-capabilities-sync.js +56 -0
  530. package/dist/runtime-capabilities-sync.js.map +1 -0
  531. package/dist/runtime-config.d.ts +26 -0
  532. package/dist/runtime-config.d.ts.map +1 -0
  533. package/dist/runtime-config.js +170 -0
  534. package/dist/runtime-config.js.map +1 -0
  535. package/dist/runtime-presenter.d.ts +81 -0
  536. package/dist/runtime-presenter.d.ts.map +1 -0
  537. package/dist/runtime-presenter.js +422 -0
  538. package/dist/runtime-presenter.js.map +1 -0
  539. package/dist/runtime-registration.d.ts +40 -0
  540. package/dist/runtime-registration.d.ts.map +1 -0
  541. package/dist/runtime-registration.js +143 -0
  542. package/dist/runtime-registration.js.map +1 -0
  543. package/dist/sandbox-outbox-storage.d.ts +13 -0
  544. package/dist/sandbox-outbox-storage.d.ts.map +1 -0
  545. package/dist/sandbox-outbox-storage.js +98 -0
  546. package/dist/sandbox-outbox-storage.js.map +1 -0
  547. package/dist/sandbox-outbox.d.ts +115 -0
  548. package/dist/sandbox-outbox.d.ts.map +1 -0
  549. package/dist/sandbox-outbox.js +213 -0
  550. package/dist/sandbox-outbox.js.map +1 -0
  551. package/dist/sandbox.d.ts +34 -0
  552. package/dist/sandbox.d.ts.map +1 -0
  553. package/dist/sandbox.js +469 -0
  554. package/dist/sandbox.js.map +1 -0
  555. package/dist/service-manager.d.ts +113 -0
  556. package/dist/service-manager.d.ts.map +1 -0
  557. package/dist/service-manager.js +1065 -0
  558. package/dist/service-manager.js.map +1 -0
  559. package/dist/skills/__tests__/local-skill-source.test.d.ts +2 -0
  560. package/dist/skills/__tests__/local-skill-source.test.d.ts.map +1 -0
  561. package/dist/skills/__tests__/local-skill-source.test.js +373 -0
  562. package/dist/skills/__tests__/local-skill-source.test.js.map +1 -0
  563. package/dist/skills/__tests__/managed-skill-store.test.d.ts +2 -0
  564. package/dist/skills/__tests__/managed-skill-store.test.d.ts.map +1 -0
  565. package/dist/skills/__tests__/managed-skill-store.test.js +114 -0
  566. package/dist/skills/__tests__/managed-skill-store.test.js.map +1 -0
  567. package/dist/skills/__tests__/runtime-skill-coordinator.test.d.ts +2 -0
  568. package/dist/skills/__tests__/runtime-skill-coordinator.test.d.ts.map +1 -0
  569. package/dist/skills/__tests__/runtime-skill-coordinator.test.js +251 -0
  570. package/dist/skills/__tests__/runtime-skill-coordinator.test.js.map +1 -0
  571. package/dist/skills/__tests__/runtime-skill-manager.test.d.ts +2 -0
  572. package/dist/skills/__tests__/runtime-skill-manager.test.d.ts.map +1 -0
  573. package/dist/skills/__tests__/runtime-skill-manager.test.js +664 -0
  574. package/dist/skills/__tests__/runtime-skill-manager.test.js.map +1 -0
  575. package/dist/skills/__tests__/skill-bundle.test.d.ts +2 -0
  576. package/dist/skills/__tests__/skill-bundle.test.d.ts.map +1 -0
  577. package/dist/skills/__tests__/skill-bundle.test.js +42 -0
  578. package/dist/skills/__tests__/skill-bundle.test.js.map +1 -0
  579. package/dist/skills/__tests__/skill-control-client.test.d.ts +2 -0
  580. package/dist/skills/__tests__/skill-control-client.test.d.ts.map +1 -0
  581. package/dist/skills/__tests__/skill-control-client.test.js +49 -0
  582. package/dist/skills/__tests__/skill-control-client.test.js.map +1 -0
  583. package/dist/skills/__tests__/skill-source-config.test.d.ts +2 -0
  584. package/dist/skills/__tests__/skill-source-config.test.d.ts.map +1 -0
  585. package/dist/skills/__tests__/skill-source-config.test.js +295 -0
  586. package/dist/skills/__tests__/skill-source-config.test.js.map +1 -0
  587. package/dist/skills/local-skill-source.d.ts +77 -0
  588. package/dist/skills/local-skill-source.d.ts.map +1 -0
  589. package/dist/skills/local-skill-source.js +660 -0
  590. package/dist/skills/local-skill-source.js.map +1 -0
  591. package/dist/skills/managed-skill-store.d.ts +44 -0
  592. package/dist/skills/managed-skill-store.d.ts.map +1 -0
  593. package/dist/skills/managed-skill-store.js +160 -0
  594. package/dist/skills/managed-skill-store.js.map +1 -0
  595. package/dist/skills/runtime-skill-coordinator.d.ts +35 -0
  596. package/dist/skills/runtime-skill-coordinator.d.ts.map +1 -0
  597. package/dist/skills/runtime-skill-coordinator.js +166 -0
  598. package/dist/skills/runtime-skill-coordinator.js.map +1 -0
  599. package/dist/skills/runtime-skill-manager.d.ts +89 -0
  600. package/dist/skills/runtime-skill-manager.d.ts.map +1 -0
  601. package/dist/skills/runtime-skill-manager.js +934 -0
  602. package/dist/skills/runtime-skill-manager.js.map +1 -0
  603. package/dist/skills/skill-bundle.d.ts +2 -0
  604. package/dist/skills/skill-bundle.d.ts.map +1 -0
  605. package/dist/skills/skill-bundle.js +2 -0
  606. package/dist/skills/skill-bundle.js.map +1 -0
  607. package/dist/skills/skill-cli.d.ts +2 -0
  608. package/dist/skills/skill-cli.d.ts.map +1 -0
  609. package/dist/skills/skill-cli.js +81 -0
  610. package/dist/skills/skill-cli.js.map +1 -0
  611. package/dist/skills/skill-control-client.d.ts +20 -0
  612. package/dist/skills/skill-control-client.d.ts.map +1 -0
  613. package/dist/skills/skill-control-client.js +56 -0
  614. package/dist/skills/skill-control-client.js.map +1 -0
  615. package/dist/skills/skill-invocation.d.ts +10 -0
  616. package/dist/skills/skill-invocation.d.ts.map +1 -0
  617. package/dist/skills/skill-invocation.js +24 -0
  618. package/dist/skills/skill-invocation.js.map +1 -0
  619. package/dist/sleep-inhibitor.d.ts +32 -0
  620. package/dist/sleep-inhibitor.d.ts.map +1 -0
  621. package/dist/sleep-inhibitor.js +297 -0
  622. package/dist/sleep-inhibitor.js.map +1 -0
  623. package/dist/suspension-detector.d.ts +24 -0
  624. package/dist/suspension-detector.d.ts.map +1 -0
  625. package/dist/suspension-detector.js +59 -0
  626. package/dist/suspension-detector.js.map +1 -0
  627. package/dist/types.d.ts +2 -0
  628. package/dist/types.d.ts.map +1 -0
  629. package/dist/types.js +2 -0
  630. package/dist/types.js.map +1 -0
  631. package/dist/version.d.ts +2 -0
  632. package/dist/version.d.ts.map +1 -0
  633. package/dist/version.js +2 -0
  634. package/dist/version.js.map +1 -0
  635. package/dist/web-presenter.d.ts +52 -0
  636. package/dist/web-presenter.d.ts.map +1 -0
  637. package/dist/web-presenter.js +264 -0
  638. package/dist/web-presenter.js.map +1 -0
  639. package/dist/workspace-relocation.d.ts +22 -0
  640. package/dist/workspace-relocation.d.ts.map +1 -0
  641. package/dist/workspace-relocation.js +90 -0
  642. package/dist/workspace-relocation.js.map +1 -0
  643. package/dist/workspace-rpc.d.ts +2 -0
  644. package/dist/workspace-rpc.d.ts.map +1 -0
  645. package/dist/workspace-rpc.js +15 -0
  646. package/dist/workspace-rpc.js.map +1 -0
  647. package/dist/workspace-run-lease.d.ts +116 -0
  648. package/dist/workspace-run-lease.d.ts.map +1 -0
  649. package/dist/workspace-run-lease.js +320 -0
  650. package/dist/workspace-run-lease.js.map +1 -0
  651. package/dist/ws-client.d.ts +168 -0
  652. package/dist/ws-client.d.ts.map +1 -0
  653. package/dist/ws-client.js +440 -0
  654. package/dist/ws-client.js.map +1 -0
  655. package/package.json +4 -3
@@ -0,0 +1,1071 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdirSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { collapseCursorDiscoveredModelIds } from "@alan-ai/shared/runtime-discovered-models";
6
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
7
+ import * as cli from "../cli-executable.js";
8
+ import { applyZenEntitlement, discoverProviderModelDetails, discoverProviderModels, extractClaudeModelIdsFromExecutable, extractCodexDiscoveredModelIdsFromDebugJson, extractCodexModelSlugsFromDebugJson, extractGrokModelIdsFromModelsOutput, extractModelIdsFromJson, extractModelIdsFromLabeledList, extractOpenCodeDiscoveredModelIdsFromVerboseOutput, extractOpenCodeModelBillingFromVerboseOutput, extractOpenCodeModelCapabilitiesFromVerboseOutput, filterClaudeDatedModelIds, filterClaudePrefixModelIds, getCatalogModelIds, isValidClaudeModelId, readConfiguredOpenCodeProviders, } from "../provider-model-discovery.js";
9
+ // Captured from a live `grok models` run against grok 1.0.3 (2026-08-14).
10
+ const GROK_MODELS_FIXTURE = `You are logged in with grok.com.
11
+
12
+ Default model: grok-4.6
13
+
14
+ Available models:
15
+ * grok-4.6 (default)
16
+ - grok-4.5
17
+ `;
18
+ // big-pickle and deepseek-v4-flash-free blocks are trimmed but otherwise verbatim
19
+ // captures from a real `opencode models --verbose` (opencode 1.17.18) run — see
20
+ // notes/opencode-cli-reverse-engineering.md. some-non-reasoning-model is synthetic
21
+ // (no reasoning:false model was visible in the live capture, only free zen models
22
+ // were) but follows the exact same schema, to cover the "skip entirely" path.
23
+ const OPENCODE_VERBOSE_FIXTURE = `opencode/big-pickle
24
+ {
25
+ "id": "big-pickle",
26
+ "providerID": "opencode",
27
+ "name": "Big Pickle",
28
+ "capabilities": {
29
+ "temperature": true,
30
+ "reasoning": true,
31
+ "attachment": false,
32
+ "toolcall": true
33
+ },
34
+ "release_date": "2025-10-17",
35
+ "variants": {}
36
+ }
37
+ opencode/deepseek-v4-flash-free
38
+ {
39
+ "id": "deepseek-v4-flash-free",
40
+ "providerID": "opencode",
41
+ "name": "DeepSeek V4 Flash Free",
42
+ "capabilities": {
43
+ "temperature": true,
44
+ "reasoning": true,
45
+ "attachment": false,
46
+ "toolcall": true
47
+ },
48
+ "release_date": "2026-04-24",
49
+ "variants": {
50
+ "low": { "reasoningEffort": "low" },
51
+ "medium": { "reasoningEffort": "medium" },
52
+ "high": { "reasoningEffort": "high" },
53
+ "max": { "reasoningEffort": "max" }
54
+ }
55
+ }
56
+ opencode/some-non-reasoning-model
57
+ {
58
+ "id": "some-non-reasoning-model",
59
+ "providerID": "opencode",
60
+ "name": "Some Non Reasoning Model",
61
+ "capabilities": {
62
+ "temperature": true,
63
+ "reasoning": false,
64
+ "attachment": false,
65
+ "toolcall": true
66
+ },
67
+ "release_date": "2026-01-01",
68
+ "variants": {}
69
+ }
70
+ `;
71
+ /**
72
+ * Discovery also hydrates missing metadata from `$XDG_CACHE_HOME/opencode/models.json`.
73
+ * Passing an empty env would fall back to `~/.cache`, so on a machine that has actually
74
+ * run opencode these tests would read the developer's real 94-model catalogue and their
75
+ * exact-equality assertions would depend on whoever ran them. Point every case at a
76
+ * directory that does not exist.
77
+ */
78
+ const NO_CACHE_ENV = { XDG_CACHE_HOME: join(tmpdir(), `opencode-no-cache-${randomUUID()}`) };
79
+ // OpenCode discovery also asks Zen which models the credential can dispatch. Default
80
+ // every test to offline so the CLI-scan cases assert the raw scan and no test can
81
+ // silently depend on opencode.ai; the entitlement cases stub `fetch` explicitly.
82
+ beforeEach(() => {
83
+ vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("offline"));
84
+ });
85
+ afterEach(() => {
86
+ vi.restoreAllMocks();
87
+ });
88
+ describe("provider-model-discovery", () => {
89
+ it("extracts model ids from JSON arrays and envelopes", () => {
90
+ expect(extractModelIdsFromJson([{ id: "gpt-5.5" }, { model: "claude-sonnet-5" }])).toEqual([
91
+ "gpt-5.5",
92
+ "claude-sonnet-5",
93
+ ]);
94
+ expect(extractModelIdsFromJson({ models: [{ id: "opencode/big-pickle" }] })).toEqual([
95
+ "opencode/big-pickle",
96
+ ]);
97
+ });
98
+ it("keeps OpenCode model ids separate from their native variants", () => {
99
+ expect(extractOpenCodeDiscoveredModelIdsFromVerboseOutput(OPENCODE_VERBOSE_FIXTURE)).toEqual([
100
+ "opencode/big-pickle",
101
+ "opencode/deepseek-v4-flash-free",
102
+ "opencode/some-non-reasoning-model",
103
+ ]);
104
+ });
105
+ it("reads OpenCode billing from live cost data, not model names", () => {
106
+ const text = [
107
+ "opencode/big-pickle",
108
+ "{",
109
+ ' "cost": { "input": 0, "output": 0 },',
110
+ ' "capabilities": { "reasoning": true },',
111
+ ' "variants": {}',
112
+ "}",
113
+ "opencode/future-model-without-a-free-suffix",
114
+ "{",
115
+ ' "cost": { "input": 0.25, "output": 0.5 },',
116
+ ' "capabilities": { "reasoning": false },',
117
+ ' "variants": {}',
118
+ "}",
119
+ ].join("\n");
120
+ expect(extractOpenCodeModelBillingFromVerboseOutput(text)).toEqual({
121
+ "opencode/big-pickle": "free",
122
+ "opencode/future-model-without-a-free-suffix": "paid",
123
+ });
124
+ });
125
+ it("carries verbose OpenCode billing alongside the discovered ids", async () => {
126
+ const probe = vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
127
+ status: 0,
128
+ stdout: [
129
+ "opencode/big-pickle",
130
+ "{",
131
+ ' "cost": { "input": 0, "output": 0 },',
132
+ ' "capabilities": { "reasoning": true },',
133
+ ' "variants": { "none": {}, "extended-thinking": {} }',
134
+ "}",
135
+ ].join("\n"),
136
+ stderr: "",
137
+ error: undefined,
138
+ });
139
+ try {
140
+ await expect(discoverProviderModelDetails("opencode_cli", "/bin/opencode", NO_CACHE_ENV)).resolves.toEqual({
141
+ models: ["opencode/big-pickle"],
142
+ modelVariants: { "opencode/big-pickle": ["none", "extended-thinking"] },
143
+ modelBilling: { "opencode/big-pickle": "free" },
144
+ });
145
+ }
146
+ finally {
147
+ probe.mockRestore();
148
+ }
149
+ });
150
+ it("is not confused by a brace inside a JSON string value", () => {
151
+ const text = [
152
+ "opencode/weird-name",
153
+ "{",
154
+ ' "id": "weird-name",',
155
+ ' "name": "Uses a { brace } in its name",',
156
+ ' "capabilities": { "reasoning": true },',
157
+ ' "variants": { "high": { "reasoningEffort": "high" } }',
158
+ "}",
159
+ ].join("\n");
160
+ expect(extractOpenCodeDiscoveredModelIdsFromVerboseOutput(text)).toEqual([
161
+ "opencode/weird-name",
162
+ ]);
163
+ expect(extractOpenCodeModelCapabilitiesFromVerboseOutput(text)).toEqual([
164
+ { id: "opencode/weird-name", variants: ["high"] },
165
+ ]);
166
+ });
167
+ it("retains a provider-defined variant without corrupting the base id", () => {
168
+ const text = [
169
+ "opencode/future-model",
170
+ "{",
171
+ ' "capabilities": { "reasoning": true },',
172
+ ' "variants": { "extreme": { "reasoningEffort": "extreme" } }',
173
+ "}",
174
+ ].join("\n");
175
+ expect(extractOpenCodeDiscoveredModelIdsFromVerboseOutput(text)).toEqual([
176
+ "opencode/future-model",
177
+ ]);
178
+ expect(extractOpenCodeModelCapabilitiesFromVerboseOutput(text)).toEqual([
179
+ { id: "opencode/future-model", variants: ["extreme"] },
180
+ ]);
181
+ });
182
+ it("parses codex debug models JSON slugs", () => {
183
+ expect(extractCodexModelSlugsFromDebugJson({
184
+ models: [
185
+ { slug: "gpt-5.5", visibility: "list" },
186
+ { slug: "codex-auto-review", visibility: "hidden" },
187
+ { slug: "gpt-5.4-mini", visibility: "hide" },
188
+ ],
189
+ })).toEqual(["gpt-5.5"]);
190
+ });
191
+ it("expands codex supported reasoning levels into effort-suffixed scan ids", () => {
192
+ expect(extractCodexDiscoveredModelIdsFromDebugJson({
193
+ models: [
194
+ {
195
+ slug: "gpt-5.6-sol",
196
+ visibility: "list",
197
+ supported_reasoning_levels: [
198
+ { effort: "low" },
199
+ { effort: "medium" },
200
+ { effort: "high" },
201
+ { effort: "xhigh" },
202
+ { effort: "max" },
203
+ { effort: "ultra" },
204
+ ],
205
+ },
206
+ {
207
+ slug: "gpt-5.6-luna",
208
+ visibility: "list",
209
+ supported_reasoning_levels: [{ effort: "low" }, { effort: "high" }],
210
+ },
211
+ ],
212
+ })).toEqual([
213
+ "gpt-5.6-sol-low",
214
+ "gpt-5.6-sol-medium",
215
+ "gpt-5.6-sol-high",
216
+ "gpt-5.6-sol-xhigh",
217
+ "gpt-5.6-sol-max",
218
+ "gpt-5.6-sol-ultra",
219
+ "gpt-5.6-luna-low",
220
+ "gpt-5.6-luna-high",
221
+ ]);
222
+ });
223
+ it("parses cursor-agent style labeled model lists", () => {
224
+ const stdout = [
225
+ "Available models",
226
+ "",
227
+ "auto - Auto (current, default)",
228
+ "gpt-5.3-codex - Codex 5.3",
229
+ "composer-2.5-fast - Composer 2.5 Fast",
230
+ "Tip: use --model <id> to switch.",
231
+ ].join("\n");
232
+ expect(extractModelIdsFromLabeledList(stdout)).toEqual([
233
+ "auto",
234
+ "gpt-5.3-codex",
235
+ "composer-2.5-fast",
236
+ ]);
237
+ });
238
+ it("parses grok models text without treating the login banner as a model id", () => {
239
+ expect(extractGrokModelIdsFromModelsOutput(GROK_MODELS_FIXTURE)).toEqual([
240
+ "grok-4.6",
241
+ "grok-4.5",
242
+ ]);
243
+ });
244
+ it("falls back to Default model when the bullet list is missing", () => {
245
+ expect(extractGrokModelIdsFromModelsOutput("You are logged in with grok.com.\n\nDefault model: grok-4.6\n")).toEqual(["grok-4.6"]);
246
+ });
247
+ it("parses tab-separated agy model rows without keeping display labels", () => {
248
+ const stdout = [
249
+ "Fetching available models...",
250
+ "gemini-3.6-flash-high\tGemini 3.6 Flash (High)",
251
+ "claude-sonnet-4-6\tClaude Sonnet 4.6 (Thinking)",
252
+ ].join("\n");
253
+ expect(extractModelIdsFromLabeledList(stdout)).toEqual([
254
+ "gemini-3.6-flash-high",
255
+ "claude-sonnet-4-6",
256
+ ]);
257
+ });
258
+ it("drops short claude id prefixes when a longer sibling exists", () => {
259
+ expect(filterClaudePrefixModelIds(["claude-opus-4", "claude-opus-4-6", "claude-sonnet-4-6"])).toEqual(["claude-opus-4-6", "claude-sonnet-4-6"]);
260
+ });
261
+ it("rejects invalid claude scrape tokens", () => {
262
+ expect(isValidClaudeModelId("claude-fable-5.md")).toBe(false);
263
+ expect(isValidClaudeModelId("claude-sonnet-4.6")).toBe(false);
264
+ expect(isValidClaudeModelId("claude-opus-4-6-20251101")).toBe(false);
265
+ expect(isValidClaudeModelId("claude-opus-4-8")).toBe(true);
266
+ expect(isValidClaudeModelId("sonnet")).toBe(true);
267
+ });
268
+ it("rejects internal binary codenames scraped across hyphens", () => {
269
+ // The greedy scrape regex ran into an internal string in the Claude binary and
270
+ // produced "claude-fable-5-mythos-5", which then evicted the real "claude-fable-5".
271
+ expect(isValidClaudeModelId("claude-fable-5-mythos-5")).toBe(false);
272
+ // Known non-numeric qualifiers stay valid.
273
+ expect(isValidClaudeModelId("claude-opus-4-6-fast")).toBe(true);
274
+ expect(isValidClaudeModelId("claude-sonnet-5-thinking")).toBe(true);
275
+ // A real base model survives.
276
+ expect(isValidClaudeModelId("claude-fable-5")).toBe(true);
277
+ });
278
+ it("never evicts a curated catalogue id via the prefix filter", () => {
279
+ // Even if a bogus longer sibling slips through, a shipped catalogue model must remain.
280
+ const result = filterClaudePrefixModelIds(["claude-fable-5", "claude-fable-5-mythos-5"]);
281
+ expect(result).toContain("claude-fable-5");
282
+ });
283
+ it("drops dated claude ids when an undated sibling exists", () => {
284
+ expect(filterClaudeDatedModelIds([
285
+ "claude-opus-4-6",
286
+ "claude-opus-4-6-20251101",
287
+ "claude-sonnet-4-5-20250929",
288
+ ])).toEqual(["claude-opus-4-6", "claude-sonnet-4-5-20250929"]);
289
+ });
290
+ it("extracts claude model ids embedded in the executable", () => {
291
+ const tmp = join(tmpdir(), `claude-exe-${randomUUID()}`);
292
+ writeFileSync(tmp, Buffer.from("prefix claude-sonnet-4-6 middle claude-opus-4 claude-opus-4-8 claude-fable-5.md suffix", "latin1"));
293
+ try {
294
+ expect(extractClaudeModelIdsFromExecutable(tmp)).toEqual(expect.arrayContaining([
295
+ "claude-sonnet-4-6",
296
+ "claude-opus-4-8",
297
+ "sonnet",
298
+ "opus",
299
+ "haiku",
300
+ "fable",
301
+ ]));
302
+ expect(extractClaudeModelIdsFromExecutable(tmp)).not.toEqual(expect.arrayContaining(["claude-fable-5.md", "claude-opus-4"]));
303
+ }
304
+ finally {
305
+ unlinkSync(tmp);
306
+ }
307
+ });
308
+ it("returns seed catalog ids for known providers", () => {
309
+ expect(getCatalogModelIds("claude_cli")).toEqual([""]);
310
+ expect(getCatalogModelIds("codex_app_server")).toEqual([]);
311
+ expect(getCatalogModelIds("antigravity_cli")).toEqual([""]);
312
+ expect(getCatalogModelIds("droid_cli").length).toBeGreaterThan(0);
313
+ expect(getCatalogModelIds("copilot_cli").length).toBeGreaterThan(0);
314
+ });
315
+ });
316
+ describe("discoverProviderModels droid", () => {
317
+ afterEach(() => {
318
+ vi.restoreAllMocks();
319
+ });
320
+ it("uses catalog models without probing the interactive TUI", async () => {
321
+ const probe = vi.spyOn(cli, "runCliProbeAsync");
322
+ const models = await discoverProviderModels("droid_cli", "/bin/droid", {});
323
+ expect(models).toEqual(getCatalogModelIds("droid_cli"));
324
+ expect(probe).not.toHaveBeenCalled();
325
+ });
326
+ });
327
+ describe("discoverProviderModels copilot", () => {
328
+ afterEach(() => {
329
+ vi.restoreAllMocks();
330
+ });
331
+ it("uses catalog models without probing — Copilot CLI 1.0.75 has no non-interactive model list", async () => {
332
+ const probe = vi.spyOn(cli, "runCliProbeAsync");
333
+ const models = await discoverProviderModels("copilot_cli", "/opt/homebrew/bin/copilot", {});
334
+ expect(models).toEqual(getCatalogModelIds("copilot_cli"));
335
+ expect(probe).not.toHaveBeenCalled();
336
+ });
337
+ });
338
+ describe("discoverProviderModels", () => {
339
+ afterEach(() => {
340
+ vi.restoreAllMocks();
341
+ });
342
+ it("probes codex debug models and expands reasoning levels for effort merge", async () => {
343
+ vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
344
+ status: 0,
345
+ stdout: JSON.stringify({
346
+ models: [
347
+ {
348
+ slug: "gpt-5.6-sol",
349
+ visibility: "list",
350
+ supported_reasoning_levels: [{ effort: "low" }, { effort: "high" }],
351
+ },
352
+ { slug: "gpt-5.3-codex-spark", visibility: "list" },
353
+ ],
354
+ }),
355
+ stderr: "",
356
+ error: undefined,
357
+ });
358
+ const models = await discoverProviderModels("codex_app_server", "/bin/codex", {});
359
+ expect(models).toEqual(["gpt-5.6-sol-low", "gpt-5.6-sol-high", "gpt-5.3-codex-spark"]);
360
+ expect(cli.runCliProbeAsync).toHaveBeenCalledWith("/bin/codex", expect.objectContaining({ TERM: "xterm-256color" }), ["debug", "models"], 8_000, { maxOutputChars: 1024 * 1024 });
361
+ });
362
+ it("does not offer a truncated Codex JSON document as a model", async () => {
363
+ vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
364
+ status: 0,
365
+ stdout: `{"models":[{"slug":"gpt-5.6-sol","description":"${"x".repeat(200)}`,
366
+ stderr: "",
367
+ error: undefined,
368
+ });
369
+ const models = await discoverProviderModels("codex_app_server", "/bin/codex", {});
370
+ expect(models).toEqual(getCatalogModelIds("codex_app_server"));
371
+ });
372
+ it("probes antigravity with stdin closed", async () => {
373
+ vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
374
+ status: 0,
375
+ stdout: [
376
+ "gemini-3.6-flash-medium\tGemini 3.6 Flash (Medium)",
377
+ "claude-sonnet-4-6\tClaude Sonnet 4.6 (Thinking)",
378
+ ].join("\n"),
379
+ stderr: "",
380
+ error: undefined,
381
+ });
382
+ const models = await discoverProviderModels("antigravity_cli", "/bin/agy", {});
383
+ expect(models).toEqual(["gemini-3.6-flash-medium", "claude-sonnet-4-6"]);
384
+ expect(cli.runCliProbeAsync).toHaveBeenCalledWith("/bin/agy", expect.any(Object), ["models"], 12_000, { closeStdin: true });
385
+ });
386
+ it("probes opencode models --verbose and returns dispatchable base ids", async () => {
387
+ vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
388
+ status: 0,
389
+ stdout: OPENCODE_VERBOSE_FIXTURE,
390
+ stderr: "",
391
+ error: undefined,
392
+ });
393
+ const models = await discoverProviderModels("opencode_cli", "/bin/opencode", {
394
+ ...NO_CACHE_ENV,
395
+ OPENCODE_API_KEY: "sk-live",
396
+ });
397
+ expect(models).toEqual([
398
+ "opencode/big-pickle",
399
+ "opencode/deepseek-v4-flash-free",
400
+ "opencode/some-non-reasoning-model",
401
+ ]);
402
+ // The real working command — NOT the `--json`/`list --json` shapes this backend
403
+ // used to (wrongly) probe, which exit 1 against the real CLI.
404
+ expect(cli.runCliProbeAsync).toHaveBeenCalledWith("/bin/opencode", expect.any(Object), ["models", "--verbose"], 8_000);
405
+ });
406
+ it("reuses the OpenCode model scan for opencode_serve", async () => {
407
+ vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
408
+ status: 0,
409
+ stdout: OPENCODE_VERBOSE_FIXTURE,
410
+ stderr: "",
411
+ error: undefined,
412
+ });
413
+ const models = await discoverProviderModels("opencode_serve", "/bin/opencode", NO_CACHE_ENV);
414
+ expect(models).toEqual(expect.arrayContaining(["opencode/big-pickle"]));
415
+ expect(cli.runCliProbeAsync).toHaveBeenCalledWith("/bin/opencode", expect.any(Object), ["models", "--verbose"], 8_000);
416
+ });
417
+ it("falls back to the static OpenCode catalog when the CLI probe fails entirely", async () => {
418
+ vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
419
+ status: 1,
420
+ stdout: "",
421
+ stderr: "unknown flag",
422
+ error: undefined,
423
+ });
424
+ const models = await discoverProviderModels("opencode_cli", "/bin/opencode", {
425
+ ...NO_CACHE_ENV,
426
+ OPENCODE_API_KEY: "sk-live",
427
+ });
428
+ expect(models.length).toBeGreaterThan(0);
429
+ expect(models).toEqual(expect.arrayContaining(["opencode/big-pickle"]));
430
+ });
431
+ it("probes grok models text and returns live ids including grok-4.6", async () => {
432
+ vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
433
+ status: 0,
434
+ stdout: GROK_MODELS_FIXTURE,
435
+ stderr: "",
436
+ error: undefined,
437
+ });
438
+ const models = await discoverProviderModels("grok_cli", "/bin/grok", {});
439
+ expect(models).toEqual(["grok-4.6", "grok-4.5"]);
440
+ // Real command only — `models --json` is not supported by grok 1.0.3.
441
+ expect(cli.runCliProbeAsync).toHaveBeenCalledTimes(1);
442
+ expect(cli.runCliProbeAsync).toHaveBeenCalledWith("/bin/grok", expect.objectContaining({ TERM: "xterm-256color" }), ["models"], 8_000);
443
+ });
444
+ it("falls back to the static Grok catalog when the CLI probe fails", async () => {
445
+ vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
446
+ status: 1,
447
+ stdout: "",
448
+ stderr: "not logged in",
449
+ error: undefined,
450
+ });
451
+ const models = await discoverProviderModels("grok_cli", "/bin/grok", {});
452
+ expect(models).toEqual(getCatalogModelIds("grok_cli"));
453
+ expect(models).toEqual(expect.arrayContaining(["grok-4.6", "grok-4.5"]));
454
+ });
455
+ it("falls back to executable scrape when claude CLI has no model list", async () => {
456
+ vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
457
+ status: 1,
458
+ stdout: "",
459
+ stderr: "unknown option",
460
+ error: undefined,
461
+ });
462
+ const tmp = join(tmpdir(), `claude-exe-${randomUUID()}`);
463
+ writeFileSync(tmp, Buffer.from("claude-sonnet-4-6 claude-opus-4-8", "latin1"));
464
+ try {
465
+ const models = await discoverProviderModels("claude_cli", tmp, {});
466
+ expect(models).toEqual(expect.arrayContaining(["claude-sonnet-4-6", "claude-opus-4-8", "sonnet", "opus"]));
467
+ }
468
+ finally {
469
+ unlinkSync(tmp);
470
+ }
471
+ });
472
+ it("returns raw cursor-agent models without collapsing at discovery time", async () => {
473
+ vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
474
+ status: 0,
475
+ stdout: [
476
+ "gpt-5.3-codex-low - Codex 5.3 Low",
477
+ "gpt-5.3-codex-high - Codex 5.3 High",
478
+ "composer-2.5-fast - Composer 2.5 Fast",
479
+ ].join("\n"),
480
+ stderr: "",
481
+ error: undefined,
482
+ });
483
+ const models = await discoverProviderModels("cursor_agent_cli", "/bin/cursor-agent", {});
484
+ expect(models).toEqual(["gpt-5.3-codex-low", "gpt-5.3-codex-high", "composer-2.5-fast"]);
485
+ expect(models).not.toEqual(collapseCursorDiscoveredModelIds([
486
+ "gpt-5.3-codex-low",
487
+ "gpt-5.3-codex-high",
488
+ "composer-2.5-fast",
489
+ ]));
490
+ });
491
+ it("carries every model's native variants from the verbose scan", async () => {
492
+ const fixture = [
493
+ "opencode/big-pickle",
494
+ "{",
495
+ ' "id": "big-pickle",',
496
+ ' "capabilities": { "reasoning": true },',
497
+ ' "variants": {}',
498
+ "}",
499
+ "opencode/deepseek-v4-flash-free",
500
+ "{",
501
+ ' "id": "deepseek-v4-flash-free",',
502
+ ' "capabilities": { "reasoning": true },',
503
+ ' "variants": { "high": { "reasoningEffort": "high" }, "max": { "reasoningEffort": "max" } }',
504
+ "}",
505
+ "opencode/north-mini-code-free",
506
+ "{",
507
+ ' "id": "north-mini-code-free",',
508
+ ' "capabilities": { "reasoning": true },',
509
+ ' "variants": { "none": {}, "high": {} }',
510
+ "}",
511
+ "opencode/claude-sonnet-5",
512
+ "{",
513
+ ' "id": "claude-sonnet-5",',
514
+ ' "capabilities": { "reasoning": true },',
515
+ ' "variants": { "low": {}, "medium": {}, "high": {} }',
516
+ "}",
517
+ ].join("\n");
518
+ vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
519
+ status: 0,
520
+ stdout: fixture,
521
+ stderr: "",
522
+ error: undefined,
523
+ });
524
+ const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
525
+ ...NO_CACHE_ENV,
526
+ OPENCODE_API_KEY: "sk-live",
527
+ });
528
+ expect(discovery).toEqual({
529
+ models: [
530
+ "opencode/big-pickle",
531
+ "opencode/deepseek-v4-flash-free",
532
+ "opencode/north-mini-code-free",
533
+ "opencode/claude-sonnet-5",
534
+ ],
535
+ modelVariants: {
536
+ "opencode/big-pickle": [],
537
+ "opencode/deepseek-v4-flash-free": ["high", "max"],
538
+ "opencode/north-mini-code-free": ["none", "high"],
539
+ "opencode/claude-sonnet-5": ["low", "medium", "high"],
540
+ },
541
+ });
542
+ expect(cli.runCliProbeAsync).toHaveBeenCalledWith("/bin/opencode", expect.any(Object), ["models", "--verbose"], 8_000);
543
+ });
544
+ it("falls back to the static OpenCode catalog when the CLI probe fails entirely", async () => {
545
+ vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
546
+ status: 1,
547
+ stdout: "",
548
+ stderr: "unknown flag",
549
+ error: undefined,
550
+ });
551
+ const models = await discoverProviderModels("opencode_cli", "/bin/opencode", {
552
+ ...NO_CACHE_ENV,
553
+ OPENCODE_API_KEY: "sk-live",
554
+ });
555
+ expect(models.length).toBeGreaterThan(0);
556
+ expect(models).toEqual(expect.arrayContaining(["opencode/big-pickle"]));
557
+ });
558
+ });
559
+ describe("extractOpenCodeDiscoveredModelIdsFromVerboseOutput", () => {
560
+ it("retains provider-defined variants in the structured capability result", () => {
561
+ const capabilities = extractOpenCodeModelCapabilitiesFromVerboseOutput([
562
+ "opencode/custom-model",
563
+ "{",
564
+ ' "capabilities": { "reasoning": true },',
565
+ ' "variants": { "extended-thinking": {}, "vendor_x": {} }',
566
+ "}",
567
+ ].join("\n"));
568
+ expect(capabilities).toEqual([
569
+ { id: "opencode/custom-model", variants: ["extended-thinking", "vendor_x"] },
570
+ ]);
571
+ });
572
+ it("does not encode reasoning variants into model ids", () => {
573
+ const text = [
574
+ "opencode/deepseek-v4-flash-free",
575
+ "{",
576
+ ' "capabilities": { "reasoning": true },',
577
+ ' "variants": { "high": {}, "max": {} }',
578
+ "}",
579
+ ].join("\n");
580
+ expect(extractOpenCodeDiscoveredModelIdsFromVerboseOutput(text)).toEqual([
581
+ "opencode/deepseek-v4-flash-free",
582
+ ]);
583
+ });
584
+ it("emits bare id for reasoning model with no recognized variants", () => {
585
+ const text = [
586
+ "opencode/big-pickle",
587
+ "{",
588
+ ' "capabilities": { "reasoning": true },',
589
+ ' "variants": {}',
590
+ "}",
591
+ ].join("\n");
592
+ expect(extractOpenCodeDiscoveredModelIdsFromVerboseOutput(text)).toEqual([
593
+ "opencode/big-pickle",
594
+ ]);
595
+ });
596
+ it("emits bare id for non-reasoning model (no variants emitted)", () => {
597
+ const text = [
598
+ "opencode/some-model",
599
+ "{",
600
+ ' "capabilities": { "reasoning": false },',
601
+ ' "variants": { "high": {} }',
602
+ "}",
603
+ ].join("\n");
604
+ expect(extractOpenCodeDiscoveredModelIdsFromVerboseOutput(text)).toEqual([
605
+ "opencode/some-model",
606
+ ]);
607
+ });
608
+ it("preserves OpenCode's none variant instead of renaming it minimal", () => {
609
+ const text = [
610
+ "opencode/north-mini-code-free",
611
+ "{",
612
+ ' "capabilities": { "reasoning": true },',
613
+ ' "variants": { "none": {}, "high": {} }',
614
+ "}",
615
+ ].join("\n");
616
+ expect(extractOpenCodeModelCapabilitiesFromVerboseOutput(text)).toEqual([
617
+ { id: "opencode/north-mini-code-free", variants: ["none", "high"] },
618
+ ]);
619
+ });
620
+ });
621
+ // Live evidence (2026-09-01, opencode 1.18.23): `opencode models opencode` lists 61
622
+ // models for a key whose workspace has no payment method, while Zen's own
623
+ // GET /zen/v1/models returns 5 for that same key. The other 56 answer
624
+ // `401 CreditsError` on dispatch — the catalogue is not the entitlement.
625
+ describe("OpenCode Zen entitlement", () => {
626
+ const zenResponse = (ids) => ({ ok: true, json: async () => ({ data: ids.map((id) => ({ id })) }) });
627
+ const scan = (stdout) => {
628
+ vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
629
+ status: 0,
630
+ stdout,
631
+ stderr: "",
632
+ error: undefined,
633
+ });
634
+ };
635
+ const verbose = (ids) => ids
636
+ .map((id) => [
637
+ id,
638
+ "{",
639
+ ` "id": "${id.split("/").slice(1).join("/")}",`,
640
+ ' "cost": { "input": 0, "output": 0 },',
641
+ ' "capabilities": { "reasoning": true },',
642
+ ' "variants": { "high": {} }',
643
+ "}",
644
+ ].join("\n"))
645
+ .join("\n");
646
+ afterEach(() => {
647
+ vi.restoreAllMocks();
648
+ });
649
+ it("drops scanned Zen models the credential cannot dispatch", () => {
650
+ expect(applyZenEntitlement({ models: ["opencode/big-pickle", "opencode/claude-opus-5"] }, new Set(["big-pickle"]))).toEqual({
651
+ models: ["opencode/big-pickle"],
652
+ catalogModels: ["opencode/big-pickle", "opencode/claude-opus-5"],
653
+ });
654
+ });
655
+ // opencode-go's list endpoint returns the same 33 models with and without a key, and
656
+ // BYOK ids belong to the user's own provider credentials — neither is Zen's to filter.
657
+ it("leaves opencode-go and BYOK namespaces untouched", () => {
658
+ expect(applyZenEntitlement({
659
+ models: ["opencode-go/glm-5.3", "anthropic/claude-opus-5", "opencode/claude-opus-5"],
660
+ modelVariants: { "opencode-go/glm-5.3": ["low", "high", "max"] },
661
+ }, new Set(["big-pickle"]))).toEqual({
662
+ models: ["opencode-go/glm-5.3", "anthropic/claude-opus-5", "opencode/big-pickle"],
663
+ catalogModels: [
664
+ "opencode-go/glm-5.3",
665
+ "anthropic/claude-opus-5",
666
+ "opencode/claude-opus-5",
667
+ "opencode/big-pickle",
668
+ ],
669
+ modelVariants: { "opencode-go/glm-5.3": ["low", "high", "max"] },
670
+ });
671
+ });
672
+ // `opencode/laguna-s-2.1-free` dispatches on a free key yet never appears in
673
+ // `opencode models --verbose`, so intersecting would hide a model that works.
674
+ it("adds back an entitled model the CLI scan never reported", () => {
675
+ const merged = applyZenEntitlement({ models: ["opencode/big-pickle"], modelBilling: { "opencode/big-pickle": "free" } }, new Set(["big-pickle", "laguna-s-2.1-free"]));
676
+ expect(merged.models).toEqual(["opencode/big-pickle", "opencode/laguna-s-2.1-free"]);
677
+ expect(merged.modelBilling).toEqual({ "opencode/big-pickle": "free" });
678
+ });
679
+ // The shared catalogue is fed from catalogModels, so a key entitled to 1 of 2 models
680
+ // must still report both — otherwise one unpaid key shrinks the catalogue for everyone.
681
+ it("keeps the unfiltered list and its metadata for the catalogue feed", () => {
682
+ expect(applyZenEntitlement({
683
+ models: ["opencode/big-pickle", "opencode/claude-opus-5"],
684
+ modelVariants: { "opencode/big-pickle": ["high"], "opencode/claude-opus-5": ["max"] },
685
+ modelBilling: { "opencode/big-pickle": "free", "opencode/claude-opus-5": "paid" },
686
+ }, new Set(["big-pickle"]))).toEqual({
687
+ models: ["opencode/big-pickle"],
688
+ catalogModels: ["opencode/big-pickle", "opencode/claude-opus-5"],
689
+ modelVariants: { "opencode/big-pickle": ["high"], "opencode/claude-opus-5": ["max"] },
690
+ modelBilling: { "opencode/big-pickle": "free", "opencode/claude-opus-5": "paid" },
691
+ });
692
+ });
693
+ it("carries an entitled-but-unscanned model into the catalogue feed too", () => {
694
+ expect(applyZenEntitlement({ models: ["opencode/big-pickle"] }, new Set(["big-pickle", "laguna-s-2.1-free"])).catalogModels).toEqual(["opencode/big-pickle", "opencode/laguna-s-2.1-free"]);
695
+ });
696
+ it("filters the scan with the key from the runtime env", async () => {
697
+ scan(verbose(["opencode/big-pickle", "opencode/claude-opus-5"]));
698
+ const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(zenResponse(["big-pickle"]));
699
+ const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
700
+ ...NO_CACHE_ENV,
701
+ OPENCODE_API_KEY: "sk-live",
702
+ });
703
+ expect(discovery.models).toEqual(["opencode/big-pickle"]);
704
+ expect(fetchSpy).toHaveBeenCalledWith("https://opencode.ai/zen/v1/models", expect.objectContaining({ headers: { Authorization: "Bearer sk-live" } }));
705
+ });
706
+ // A local runtime has no injected env — the user ran `opencode auth login`, which
707
+ // writes the same document the cloud passes as OPENCODE_AUTH_CONTENT.
708
+ it("reads the credential out of an auth.json document", async () => {
709
+ scan(verbose(["opencode/big-pickle", "opencode/claude-opus-5"]));
710
+ const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(zenResponse(["big-pickle"]));
711
+ await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
712
+ ...NO_CACHE_ENV,
713
+ OPENCODE_AUTH_CONTENT: JSON.stringify({ opencode: { type: "api", key: "sk-from-file" } }),
714
+ });
715
+ expect(fetchSpy).toHaveBeenCalledWith("https://opencode.ai/zen/v1/models", expect.objectContaining({ headers: { Authorization: "Bearer sk-from-file" } }));
716
+ });
717
+ // Unauthenticated the endpoint serves the full public catalogue, so the no-credential
718
+ // case needs no branch — and must not send an empty bearer.
719
+ it("asks anonymously when there is no credential anywhere", async () => {
720
+ scan(verbose(["opencode/big-pickle"]));
721
+ const fetchSpy = vi
722
+ .spyOn(globalThis, "fetch")
723
+ .mockResolvedValue(zenResponse(["big-pickle", "claude-opus-5"]));
724
+ const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
725
+ ...NO_CACHE_ENV,
726
+ XDG_DATA_HOME: join(tmpdir(), `no-opencode-auth-${randomUUID()}`),
727
+ });
728
+ // The anonymous union still asks Zen, but paid ids it returns are dropped: without
729
+ // a credential they all 401 at dispatch. big-pickle survives on its free billing.
730
+ expect(discovery.models).toEqual(["opencode/big-pickle"]);
731
+ expect(discovery.catalogModels).toContain("opencode/claude-opus-5");
732
+ expect(fetchSpy).toHaveBeenCalledWith("https://opencode.ai/zen/v1/models", expect.objectContaining({ headers: {} }));
733
+ });
734
+ it("keeps the unfiltered scan when Zen is unreachable", async () => {
735
+ scan(verbose(["opencode/big-pickle", "opencode/claude-opus-5"]));
736
+ vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("ENOTFOUND"));
737
+ const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
738
+ ...NO_CACHE_ENV,
739
+ OPENCODE_API_KEY: "sk-live",
740
+ });
741
+ expect(discovery.models).toEqual(["opencode/big-pickle", "opencode/claude-opus-5"]);
742
+ });
743
+ it("keeps the unfiltered scan when Zen answers with an error status", async () => {
744
+ scan(verbose(["opencode/big-pickle", "opencode/claude-opus-5"]));
745
+ vi.spyOn(globalThis, "fetch").mockResolvedValue({ ok: false, status: 500 });
746
+ const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
747
+ ...NO_CACHE_ENV,
748
+ OPENCODE_API_KEY: "sk-live",
749
+ });
750
+ expect(discovery.models).toEqual(["opencode/big-pickle", "opencode/claude-opus-5"]);
751
+ });
752
+ });
753
+ // `opencode models --verbose` prints only what the current credential can serve — six
754
+ // models unauthenticated, sixty-one with a key — while the CLI has already downloaded the
755
+ // full catalogue to ~/.cache/opencode/models.json and is just filtering its own output.
756
+ // Reading that file recovers the hidden effort tiers with no extra network call.
757
+ describe("OpenCode catalogue cache hydration", () => {
758
+ let cacheHome;
759
+ const writeCache = (payload) => {
760
+ mkdirSync(join(cacheHome, "opencode"), { recursive: true });
761
+ writeFileSync(join(cacheHome, "opencode", "models.json"), JSON.stringify(payload));
762
+ };
763
+ const scan = (stdout) => {
764
+ vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
765
+ status: 0,
766
+ stdout,
767
+ stderr: "",
768
+ error: undefined,
769
+ });
770
+ };
771
+ const verboseEntry = (id, variants) => [
772
+ id,
773
+ "{",
774
+ ` "id": "${id.split("/").slice(1).join("/")}",`,
775
+ ' "cost": { "input": 0, "output": 0 },',
776
+ ' "capabilities": { "reasoning": true },',
777
+ ` "variants": { ${variants.map((v) => `"${v}": {}`).join(", ")} }`,
778
+ "}",
779
+ ].join("\n");
780
+ const zen = (ids) => ({ ok: true, json: async () => ({ data: ids.map((id) => ({ id })) }) });
781
+ beforeEach(() => {
782
+ cacheHome = join(tmpdir(), `opencode-cache-${randomUUID()}`);
783
+ });
784
+ afterEach(() => {
785
+ rmSync(cacheHome, { recursive: true, force: true });
786
+ vi.restoreAllMocks();
787
+ });
788
+ it("fills effort tiers and billing for models the CLI never printed", async () => {
789
+ scan(verboseEntry("opencode/big-pickle", []));
790
+ vi.spyOn(globalThis, "fetch").mockResolvedValue(zen(["big-pickle", "claude-opus-4-8"]));
791
+ writeCache({
792
+ opencode: {
793
+ models: {
794
+ "claude-opus-4-8": {
795
+ reasoning_options: [{ type: "effort", values: ["low", "high", "max"] }],
796
+ cost: { input: 5, output: 25 },
797
+ },
798
+ },
799
+ },
800
+ });
801
+ // Keyed: without a credential the anonymous free-only rule would hide this paid id.
802
+ const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
803
+ XDG_CACHE_HOME: cacheHome,
804
+ OPENCODE_API_KEY: "sk-live",
805
+ });
806
+ expect(discovery.models).toContain("opencode/claude-opus-4-8");
807
+ expect(discovery.modelVariants?.["opencode/claude-opus-4-8"]).toEqual(["low", "high", "max"]);
808
+ expect(discovery.modelBilling?.["opencode/claude-opus-4-8"]).toBe("paid");
809
+ });
810
+ // Live 2026-09-01: for claude-haiku-4-5, claude-sonnet-4/4-5, qwen3.5/3.6-plus and
811
+ // ling-3.0-flash-fin-free the CLI reports tiers the cache does not describe as efforts.
812
+ // Letting the cache win would delete effort levels that currently work.
813
+ it("keeps the live scan's tiers when the cache disagrees", async () => {
814
+ scan(verboseEntry("opencode/claude-haiku-4-5", ["high", "max"]));
815
+ vi.spyOn(globalThis, "fetch").mockResolvedValue(zen(["claude-haiku-4-5"]));
816
+ writeCache({
817
+ opencode: {
818
+ models: {
819
+ "claude-haiku-4-5": {
820
+ reasoning_options: [{ type: "effort", values: ["low", "medium"] }],
821
+ cost: { input: 1, output: 5 },
822
+ },
823
+ },
824
+ },
825
+ });
826
+ const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
827
+ XDG_CACHE_HOME: cacheHome,
828
+ });
829
+ expect(discovery.modelVariants?.["opencode/claude-haiku-4-5"]).toEqual(["high", "max"]);
830
+ });
831
+ // Nineteen OpenCode models express reasoning as `budget_tokens`, and the CLI synthesises
832
+ // high/max from them by a mapping that lives in its binary. Recording `[]` would assert
833
+ // "no effort tiers" and override a catalogue entry that is more correct than we are.
834
+ it("contributes nothing for a budget_tokens-only model rather than asserting none", async () => {
835
+ scan(verboseEntry("opencode/big-pickle", []));
836
+ vi.spyOn(globalThis, "fetch").mockResolvedValue(zen(["big-pickle", "claude-sonnet-4"]));
837
+ writeCache({
838
+ opencode: {
839
+ models: {
840
+ "claude-sonnet-4": {
841
+ reasoning_options: [{ type: "budget_tokens", min: 1024 }],
842
+ cost: { input: 3, output: 15 },
843
+ },
844
+ },
845
+ },
846
+ });
847
+ const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
848
+ XDG_CACHE_HOME: cacheHome,
849
+ OPENCODE_API_KEY: "sk-live",
850
+ });
851
+ expect(discovery.models).toContain("opencode/claude-sonnet-4");
852
+ expect(discovery.modelVariants?.["opencode/claude-sonnet-4"]).toBeUndefined();
853
+ expect(discovery.modelBilling?.["opencode/claude-sonnet-4"]).toBe("paid");
854
+ });
855
+ it("hydrates the opencode-go namespace too", async () => {
856
+ scan(verboseEntry("opencode-go/glm-5.3", []));
857
+ vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("offline"));
858
+ writeCache({
859
+ "opencode-go": {
860
+ models: {
861
+ "qwen3.8-max": {
862
+ reasoning_options: [{ type: "effort", values: ["low", "medium", "xhigh"] }],
863
+ cost: { input: 2, output: 6 },
864
+ },
865
+ },
866
+ },
867
+ });
868
+ const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
869
+ XDG_CACHE_HOME: cacheHome,
870
+ });
871
+ // Not offered (the scan never saw it), but metadata is ready if a later scan does.
872
+ expect(discovery.modelVariants?.["opencode-go/qwen3.8-max"]).toBeUndefined();
873
+ });
874
+ // Zen serves a deprecated model for a grace period, during which it appears in the
875
+ // anonymous /models yet fails for anonymous callers (live: deepseek-v4-flash-free).
876
+ it("hides cache-deprecated models from the credential-less picker", async () => {
877
+ scan(verboseEntry("opencode/big-pickle", []));
878
+ vi.spyOn(globalThis, "fetch").mockResolvedValue(zen(["big-pickle", "deepseek-v4-flash-free"]));
879
+ writeCache({
880
+ opencode: {
881
+ models: {
882
+ "deepseek-v4-flash-free": { status: "deprecated", cost: { input: 0, output: 0 } },
883
+ },
884
+ },
885
+ });
886
+ const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
887
+ XDG_CACHE_HOME: cacheHome,
888
+ XDG_DATA_HOME: join(tmpdir(), `no-auth-${randomUUID()}`),
889
+ });
890
+ expect(discovery.models).toEqual(["opencode/big-pickle"]);
891
+ // The catalogue feed keeps it: Zen still serves it, so removal must not fire.
892
+ expect(discovery.catalogModels).toContain("opencode/deepseek-v4-flash-free");
893
+ });
894
+ // laguna-s-2.1-free is deprecated AND entitled AND dispatches on a real key. The
895
+ // entitlement is the stronger signal — a key's own list must never be second-guessed.
896
+ it("keeps a deprecated model the credential is entitled to", async () => {
897
+ scan(verboseEntry("opencode/big-pickle", []));
898
+ vi.spyOn(globalThis, "fetch").mockResolvedValue(zen(["big-pickle", "laguna-s-2.1-free"]));
899
+ writeCache({
900
+ opencode: {
901
+ models: {
902
+ "laguna-s-2.1-free": { status: "deprecated", cost: { input: 0, output: 0 } },
903
+ },
904
+ },
905
+ });
906
+ const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
907
+ XDG_CACHE_HOME: cacheHome,
908
+ OPENCODE_API_KEY: "sk-live",
909
+ });
910
+ expect(discovery.models).toContain("opencode/laguna-s-2.1-free");
911
+ });
912
+ it("survives a missing cache file", async () => {
913
+ scan(verboseEntry("opencode/big-pickle", ["high"]));
914
+ vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("offline"));
915
+ const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
916
+ XDG_CACHE_HOME: join(cacheHome, "does-not-exist"),
917
+ });
918
+ expect(discovery.models).toEqual(["opencode/big-pickle"]);
919
+ expect(discovery.modelVariants?.["opencode/big-pickle"]).toEqual(["high"]);
920
+ });
921
+ it("survives a corrupt cache file", async () => {
922
+ scan(verboseEntry("opencode/big-pickle", ["high"]));
923
+ vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("offline"));
924
+ mkdirSync(join(cacheHome, "opencode"), { recursive: true });
925
+ writeFileSync(join(cacheHome, "opencode", "models.json"), "{ not json");
926
+ const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
927
+ XDG_CACHE_HOME: cacheHome,
928
+ });
929
+ expect(discovery.models).toEqual(["opencode/big-pickle"]);
930
+ });
931
+ });
932
+ // OpenCode auto-detects ambient cloud credential chains — a `~/.aws` configured for
933
+ // unrelated tooling made it list 79 Bedrock models for a user who never connected
934
+ // Bedrock to OpenCode. Only providers explicitly configured inside OpenCode (an
935
+ // auth.json entry or a provider API-key env var) may contribute models.
936
+ describe("OpenCode configured-provider filter", () => {
937
+ let dataHome;
938
+ const writeAuth = (doc) => {
939
+ mkdirSync(join(dataHome, "opencode"), { recursive: true });
940
+ writeFileSync(join(dataHome, "opencode", "auth.json"), JSON.stringify(doc));
941
+ };
942
+ const scanWith = (ids) => {
943
+ vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
944
+ status: 0,
945
+ stdout: ids
946
+ .map((id) => [
947
+ id,
948
+ "{",
949
+ ` "id": "${id.split("/").slice(1).join("/")}",`,
950
+ ' "cost": { "input": 0, "output": 0 },',
951
+ ' "capabilities": { "reasoning": true },',
952
+ ' "variants": {}',
953
+ "}",
954
+ ].join("\n"))
955
+ .join("\n"),
956
+ stderr: "",
957
+ error: undefined,
958
+ });
959
+ };
960
+ beforeEach(() => {
961
+ dataHome = join(tmpdir(), `opencode-auth-${randomUUID()}`);
962
+ });
963
+ afterEach(() => {
964
+ rmSync(dataHome, { recursive: true, force: true });
965
+ vi.restoreAllMocks();
966
+ });
967
+ it("drops ambient-credential providers and keeps auth.json ones", async () => {
968
+ scanWith([
969
+ "opencode/big-pickle",
970
+ "openrouter/z-ai/glm-latest",
971
+ "amazon-bedrock/anthropic.claude-opus-5",
972
+ ]);
973
+ writeAuth({ openrouter: { type: "api", key: "sk-or" } });
974
+ const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
975
+ XDG_DATA_HOME: dataHome,
976
+ XDG_CACHE_HOME: join(tmpdir(), `no-cache-${randomUUID()}`),
977
+ });
978
+ expect(discovery.models).toEqual(["opencode/big-pickle", "openrouter/z-ai/glm-latest"]);
979
+ });
980
+ it("admits a provider configured through its explicit env key", () => {
981
+ const configured = readConfiguredOpenCodeProviders({
982
+ XDG_DATA_HOME: join(tmpdir(), `no-auth-${randomUUID()}`),
983
+ OPENROUTER_API_KEY: "sk-or",
984
+ });
985
+ expect(configured.has("openrouter")).toBe(true);
986
+ expect(configured.has("amazon-bedrock")).toBe(false);
987
+ // Zen's namespaces are always in scope; entitlement governs them downstream.
988
+ expect(configured.has("opencode")).toBe(true);
989
+ expect(configured.has("opencode-go")).toBe(true);
990
+ });
991
+ it("reads providers from an injected OPENCODE_AUTH_CONTENT document", () => {
992
+ const configured = readConfiguredOpenCodeProviders({
993
+ XDG_DATA_HOME: join(tmpdir(), `no-auth-${randomUUID()}`),
994
+ OPENCODE_AUTH_CONTENT: JSON.stringify({
995
+ opencode: { type: "api", key: "sk" },
996
+ "amazon-bedrock": { type: "api", key: "explicit" },
997
+ }),
998
+ });
999
+ // Explicitly configured Bedrock is a decision, not an ambient chain — allowed.
1000
+ expect(configured.has("amazon-bedrock")).toBe(true);
1001
+ });
1002
+ });
1003
+ // Without a credential every paid Zen model answers 401 CreditsError at dispatch, so
1004
+ // the anonymous picker carries only the free tier — same list `opencode models` itself
1005
+ // prints when unauthenticated. BYOK namespaces keep their own key and are untouched.
1006
+ describe("OpenCode anonymous free-tier filter", () => {
1007
+ const zen = (ids) => ({ ok: true, json: async () => ({ data: ids.map((id) => ({ id })) }) });
1008
+ afterEach(() => {
1009
+ vi.restoreAllMocks();
1010
+ });
1011
+ it("hides paid Zen models when no credential exists anywhere", async () => {
1012
+ vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
1013
+ status: 0,
1014
+ stdout: [
1015
+ "opencode/big-pickle",
1016
+ "{",
1017
+ ' "cost": { "input": 0, "output": 0 },',
1018
+ ' "capabilities": { "reasoning": true },',
1019
+ ' "variants": {}',
1020
+ "}",
1021
+ "opencode/claude-opus-5",
1022
+ "{",
1023
+ ' "cost": { "input": 5, "output": 25 },',
1024
+ ' "capabilities": { "reasoning": true },',
1025
+ ' "variants": {}',
1026
+ "}",
1027
+ "openrouter/z-ai/glm-latest",
1028
+ "{",
1029
+ ' "cost": { "input": 1, "output": 3 },',
1030
+ ' "capabilities": { "reasoning": true },',
1031
+ ' "variants": {}',
1032
+ "}",
1033
+ ].join("\n"),
1034
+ stderr: "",
1035
+ error: undefined,
1036
+ });
1037
+ vi.spyOn(globalThis, "fetch").mockResolvedValue(zen(["big-pickle", "claude-opus-5"]));
1038
+ const dataHome = join(tmpdir(), `anon-auth-${randomUUID()}`);
1039
+ mkdirSync(join(dataHome, "opencode"), { recursive: true });
1040
+ writeFileSync(join(dataHome, "opencode", "auth.json"), JSON.stringify({ openrouter: { type: "api", key: "sk-or" } }));
1041
+ const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
1042
+ XDG_DATA_HOME: dataHome,
1043
+ XDG_CACHE_HOME: join(tmpdir(), `no-cache-${randomUUID()}`),
1044
+ });
1045
+ expect(discovery.models).toEqual(["opencode/big-pickle", "openrouter/z-ai/glm-latest"]);
1046
+ // The catalogue feed still carries the paid ids — global state, not this machine's.
1047
+ expect(discovery.catalogModels).toContain("opencode/claude-opus-5");
1048
+ });
1049
+ it("keeps paid Zen models when a key is present — entitlement governs, not billing", async () => {
1050
+ vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
1051
+ status: 0,
1052
+ stdout: [
1053
+ "opencode/muse-spark-1.2",
1054
+ "{",
1055
+ ' "cost": { "input": 1.25, "output": 4.25 },',
1056
+ ' "capabilities": { "reasoning": true },',
1057
+ ' "variants": {}',
1058
+ "}",
1059
+ ].join("\n"),
1060
+ stderr: "",
1061
+ error: undefined,
1062
+ });
1063
+ vi.spyOn(globalThis, "fetch").mockResolvedValue(zen(["muse-spark-1.2"]));
1064
+ const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
1065
+ OPENCODE_API_KEY: "sk-live",
1066
+ XDG_CACHE_HOME: join(tmpdir(), `no-cache-${randomUUID()}`),
1067
+ });
1068
+ expect(discovery.models).toEqual(["opencode/muse-spark-1.2"]);
1069
+ });
1070
+ });
1071
+ //# sourceMappingURL=provider-model-discovery.test.js.map