gsd-pi 2.72.0 → 2.73.0

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 (247) hide show
  1. package/README.md +12 -2
  2. package/dist/cli.js +59 -3
  3. package/dist/onboarding.js +10 -0
  4. package/dist/resources/extensions/async-jobs/await-tool.js +7 -4
  5. package/dist/resources/extensions/async-jobs/job-manager.js +28 -3
  6. package/dist/resources/extensions/claude-code-cli/partial-builder.js +40 -12
  7. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +48 -23
  8. package/dist/resources/extensions/gsd/auto/loop.js +84 -1
  9. package/dist/resources/extensions/gsd/auto-post-unit.js +6 -0
  10. package/dist/resources/extensions/gsd/auto-recovery.js +11 -0
  11. package/dist/resources/extensions/gsd/auto.js +25 -19
  12. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +9 -11
  13. package/dist/resources/extensions/gsd/commands-handlers.js +4 -1
  14. package/dist/resources/extensions/gsd/context-injector.js +1 -1
  15. package/dist/resources/extensions/gsd/custom-workflow-engine.js +3 -7
  16. package/dist/resources/extensions/gsd/definition-io.js +15 -0
  17. package/dist/resources/extensions/gsd/dispatch-guard.js +4 -0
  18. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +6 -3
  19. package/dist/resources/extensions/gsd/git-service.js +11 -8
  20. package/dist/resources/extensions/gsd/gitignore.js +12 -6
  21. package/dist/resources/extensions/gsd/gsd-db.js +49 -6
  22. package/dist/resources/extensions/gsd/key-manager.js +2 -0
  23. package/dist/resources/extensions/gsd/preferences-skills.js +2 -34
  24. package/dist/resources/extensions/gsd/preferences-types.js +15 -0
  25. package/dist/resources/extensions/gsd/preferences.js +16 -3
  26. package/dist/resources/extensions/gsd/prompt-loader.js +4 -1
  27. package/dist/resources/extensions/gsd/prompts/discuss.md +122 -13
  28. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  29. package/dist/resources/extensions/gsd/state.js +21 -1
  30. package/dist/resources/extensions/gsd/workflow-projections.js +7 -0
  31. package/dist/resources/extensions/gsd/worktree-manager.js +30 -3
  32. package/dist/resources/extensions/gsd/write-intercept.js +10 -1
  33. package/dist/resources/extensions/ollama/index.js +4 -5
  34. package/dist/resources/extensions/ollama/ollama-client.js +35 -6
  35. package/dist/resources/extensions/ollama/ollama-discovery.js +32 -6
  36. package/dist/web/standalone/.next/BUILD_ID +1 -1
  37. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  38. package/dist/web/standalone/.next/build-manifest.json +2 -2
  39. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  40. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  41. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  42. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  50. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  52. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  60. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  63. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  69. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  79. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  84. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  87. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  94. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  95. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  96. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  97. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  101. package/dist/web/standalone/.next/server/app/index.html +1 -1
  102. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  108. package/dist/web/standalone/.next/server/app/page.js +2 -2
  109. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  111. package/dist/web/standalone/.next/server/chunks/2331.js +16 -16
  112. package/dist/web/standalone/.next/server/chunks/4741.js +12 -12
  113. package/dist/web/standalone/.next/server/chunks/5822.js +2 -2
  114. package/dist/web/standalone/.next/server/chunks/63.js +8 -8
  115. package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
  116. package/dist/web/standalone/.next/server/edge-runtime-webpack.js +2 -0
  117. package/dist/web/standalone/.next/server/functions-config-manifest.json +0 -9
  118. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/middleware-manifest.json +29 -2
  120. package/dist/web/standalone/.next/server/middleware.js +4 -12
  121. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  122. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  123. package/dist/web/standalone/.next/server/webpack-runtime.js +1 -1
  124. package/package.json +1 -1
  125. package/packages/pi-ai/dist/env-api-keys.js +1 -0
  126. package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
  127. package/packages/pi-ai/dist/models.custom.d.ts +105 -0
  128. package/packages/pi-ai/dist/models.custom.d.ts.map +1 -1
  129. package/packages/pi-ai/dist/models.custom.js +97 -0
  130. package/packages/pi-ai/dist/models.custom.js.map +1 -1
  131. package/packages/pi-ai/dist/models.generated.d.ts +648 -140
  132. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  133. package/packages/pi-ai/dist/models.generated.js +867 -370
  134. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  135. package/packages/pi-ai/dist/models.generated.test.d.ts +2 -0
  136. package/packages/pi-ai/dist/models.generated.test.d.ts.map +1 -0
  137. package/packages/pi-ai/dist/models.generated.test.js +334 -0
  138. package/packages/pi-ai/dist/models.generated.test.js.map +1 -0
  139. package/packages/pi-ai/dist/models.test.js +105 -0
  140. package/packages/pi-ai/dist/models.test.js.map +1 -1
  141. package/packages/pi-ai/dist/types.d.ts +1 -1
  142. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  143. package/packages/pi-ai/dist/types.js.map +1 -1
  144. package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  145. package/packages/pi-ai/dist/utils/oauth/github-copilot.js +5 -1
  146. package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  147. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.d.ts +2 -0
  148. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.d.ts.map +1 -0
  149. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js +57 -0
  150. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js.map +1 -0
  151. package/packages/pi-ai/src/env-api-keys.ts +1 -0
  152. package/packages/pi-ai/src/models.custom.ts +98 -0
  153. package/packages/pi-ai/src/models.generated.test.ts +373 -0
  154. package/packages/pi-ai/src/models.generated.ts +867 -370
  155. package/packages/pi-ai/src/models.test.ts +135 -0
  156. package/packages/pi-ai/src/types.ts +1 -0
  157. package/packages/pi-ai/src/utils/oauth/github-copilot.test.ts +71 -0
  158. package/packages/pi-ai/src/utils/oauth/github-copilot.ts +4 -1
  159. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  160. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
  161. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  162. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  163. package/packages/pi-coding-agent/dist/core/sdk.js +9 -0
  164. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  165. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +36 -0
  166. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  167. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  168. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +87 -12
  169. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  170. package/packages/pi-coding-agent/package.json +1 -1
  171. package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
  172. package/packages/pi-coding-agent/src/core/sdk.ts +10 -0
  173. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +72 -0
  174. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +84 -12
  175. package/packages/pi-tui/dist/components/__tests__/editor.test.js +12 -0
  176. package/packages/pi-tui/dist/components/__tests__/editor.test.js.map +1 -1
  177. package/packages/pi-tui/dist/components/__tests__/input.test.js +12 -0
  178. package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
  179. package/packages/pi-tui/dist/keys.d.ts.map +1 -1
  180. package/packages/pi-tui/dist/keys.js +27 -0
  181. package/packages/pi-tui/dist/keys.js.map +1 -1
  182. package/packages/pi-tui/src/components/__tests__/editor.test.ts +18 -0
  183. package/packages/pi-tui/src/components/__tests__/input.test.ts +18 -0
  184. package/packages/pi-tui/src/keys.ts +32 -0
  185. package/pkg/package.json +1 -1
  186. package/src/resources/extensions/async-jobs/await-tool.test.ts +40 -7
  187. package/src/resources/extensions/async-jobs/await-tool.ts +7 -4
  188. package/src/resources/extensions/async-jobs/job-manager.ts +33 -3
  189. package/src/resources/extensions/claude-code-cli/partial-builder.ts +45 -12
  190. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +49 -24
  191. package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +91 -2
  192. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +112 -0
  193. package/src/resources/extensions/gsd/auto/loop.ts +89 -1
  194. package/src/resources/extensions/gsd/auto-post-unit.ts +7 -0
  195. package/src/resources/extensions/gsd/auto-recovery.ts +10 -0
  196. package/src/resources/extensions/gsd/auto.ts +25 -20
  197. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +8 -10
  198. package/src/resources/extensions/gsd/commands-handlers.ts +5 -1
  199. package/src/resources/extensions/gsd/context-injector.ts +1 -1
  200. package/src/resources/extensions/gsd/custom-workflow-engine.ts +4 -8
  201. package/src/resources/extensions/gsd/definition-io.ts +18 -0
  202. package/src/resources/extensions/gsd/dispatch-guard.ts +5 -0
  203. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +6 -3
  204. package/src/resources/extensions/gsd/git-service.ts +11 -8
  205. package/src/resources/extensions/gsd/gitignore.ts +12 -6
  206. package/src/resources/extensions/gsd/gsd-db.ts +54 -6
  207. package/src/resources/extensions/gsd/key-manager.ts +2 -0
  208. package/src/resources/extensions/gsd/preferences-skills.ts +2 -36
  209. package/src/resources/extensions/gsd/preferences-types.ts +16 -0
  210. package/src/resources/extensions/gsd/preferences.ts +19 -6
  211. package/src/resources/extensions/gsd/prompt-loader.ts +6 -1
  212. package/src/resources/extensions/gsd/prompts/discuss.md +122 -13
  213. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  214. package/src/resources/extensions/gsd/state.ts +20 -0
  215. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +27 -0
  216. package/src/resources/extensions/gsd/tests/block-db-writes.test.ts +63 -0
  217. package/src/resources/extensions/gsd/tests/definition-io.test.ts +57 -0
  218. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +26 -0
  219. package/src/resources/extensions/gsd/tests/doctor-heal-fixable-warnings.test.ts +14 -0
  220. package/src/resources/extensions/gsd/tests/false-degraded-mode-warning.test.ts +104 -0
  221. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +107 -5
  222. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +8 -6
  223. package/src/resources/extensions/gsd/tests/key-manager.test.ts +63 -0
  224. package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +54 -0
  225. package/src/resources/extensions/gsd/tests/plan-milestone-artifact-verification.test.ts +62 -0
  226. package/src/resources/extensions/gsd/tests/post-unit-state-rebuild.test.ts +34 -0
  227. package/src/resources/extensions/gsd/tests/preferences-formatting.test.ts +87 -0
  228. package/src/resources/extensions/gsd/tests/preferences.test.ts +53 -0
  229. package/src/resources/extensions/gsd/tests/projection-regression.test.ts +96 -1
  230. package/src/resources/extensions/gsd/tests/prompt-loader-working-directory.test.ts +19 -0
  231. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +97 -0
  232. package/src/resources/extensions/gsd/tests/stale-slice-rows.test.ts +41 -0
  233. package/src/resources/extensions/gsd/workflow-projections.ts +8 -0
  234. package/src/resources/extensions/gsd/worktree-manager.ts +29 -3
  235. package/src/resources/extensions/gsd/write-intercept.ts +10 -1
  236. package/src/resources/extensions/ollama/index.ts +4 -5
  237. package/src/resources/extensions/ollama/ollama-client.ts +35 -6
  238. package/src/resources/extensions/ollama/ollama-discovery.ts +37 -6
  239. package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +54 -0
  240. package/dist/resources/extensions/gsd/auto-observability.js +0 -54
  241. package/dist/resources/extensions/gsd/file-watcher.js +0 -80
  242. package/dist/resources/extensions/gsd/rtk-status.js +0 -43
  243. package/src/resources/extensions/gsd/auto-observability.ts +0 -72
  244. package/src/resources/extensions/gsd/file-watcher.ts +0 -100
  245. package/src/resources/extensions/gsd/rtk-status.ts +0 -53
  246. /package/dist/web/standalone/.next/static/{Y0I7CjXJl-tWoV__KidV4 → KSZ2dcC3p4z6lOmUpPpzr}/_buildManifest.js +0 -0
  247. /package/dist/web/standalone/.next/static/{Y0I7CjXJl-tWoV__KidV4 → KSZ2dcC3p4z6lOmUpPpzr}/_ssgManifest.js +0 -0
@@ -0,0 +1,373 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { MODELS } from "./models.generated.js";
4
+ import { getModel, getModels, getProviders } from "./models.js";
5
+
6
+ // ═══════════════════════════════════════════════════════════════════════════
7
+ // Regression: qwen/qwen3.6-plus missing from OpenRouter (issue #3582)
8
+ // ═══════════════════════════════════════════════════════════════════════════
9
+
10
+ describe("regression #3582 — qwen/qwen3.6-plus available via openrouter", () => {
11
+ it("qwen/qwen3.6-plus exists in MODELS['openrouter']", () => {
12
+ const model = MODELS["openrouter"]["qwen/qwen3.6-plus" as keyof (typeof MODELS)["openrouter"]];
13
+ assert.ok(model, "qwen/qwen3.6-plus must be present in MODELS.openrouter");
14
+ });
15
+
16
+ it("qwen/qwen3.6-plus is accessible via getModel()", () => {
17
+ const model = getModel("openrouter", "qwen/qwen3.6-plus" as any);
18
+ assert.ok(model, "getModel('openrouter', 'qwen/qwen3.6-plus') must return a model");
19
+ });
20
+
21
+ it("qwen/qwen3.6-plus has id matching its registry key", () => {
22
+ const model = getModel("openrouter", "qwen/qwen3.6-plus" as any);
23
+ assert.equal(model.id, "qwen/qwen3.6-plus");
24
+ });
25
+
26
+ it("qwen/qwen3.6-plus has provider set to openrouter", () => {
27
+ const model = getModel("openrouter", "qwen/qwen3.6-plus" as any);
28
+ assert.equal(model.provider, "openrouter");
29
+ });
30
+
31
+ it("qwen/qwen3.6-plus has reasoning enabled", () => {
32
+ const model = getModel("openrouter", "qwen/qwen3.6-plus" as any);
33
+ assert.equal(model.reasoning, true, "Qwen3.6 Plus is a reasoning model");
34
+ });
35
+
36
+ it("qwen/qwen3.6-plus has 1M context window", () => {
37
+ const model = getModel("openrouter", "qwen/qwen3.6-plus" as any);
38
+ assert.equal(model.contextWindow, 1_000_000);
39
+ });
40
+ });
41
+
42
+ // ═══════════════════════════════════════════════════════════════════════════
43
+ // Regression: z-ai/glm-5.1 missing from OpenRouter (issue #4069)
44
+ // ═══════════════════════════════════════════════════════════════════════════
45
+
46
+ describe("regression #4069 — z-ai/glm-5.1 available via openrouter", () => {
47
+ it("z-ai/glm-5.1 exists in MODELS['openrouter']", () => {
48
+ const model = MODELS["openrouter"]["z-ai/glm-5.1" as keyof (typeof MODELS)["openrouter"]];
49
+ assert.ok(model, "z-ai/glm-5.1 must be present in MODELS.openrouter");
50
+ });
51
+
52
+ it("z-ai/glm-5.1 is accessible via getModel()", () => {
53
+ const model = getModel("openrouter", "z-ai/glm-5.1" as any);
54
+ assert.ok(model, "getModel('openrouter', 'z-ai/glm-5.1') must return a model");
55
+ });
56
+
57
+ it("z-ai/glm-5.1 has id matching its registry key", () => {
58
+ const model = getModel("openrouter", "z-ai/glm-5.1" as any);
59
+ assert.equal(model.id, "z-ai/glm-5.1");
60
+ });
61
+
62
+ it("z-ai/glm-5.1 has provider set to openrouter", () => {
63
+ const model = getModel("openrouter", "z-ai/glm-5.1" as any);
64
+ assert.equal(model.provider, "openrouter");
65
+ });
66
+
67
+ it("z-ai/glm-5.1 has a positive context window", () => {
68
+ const model = getModel("openrouter", "z-ai/glm-5.1" as any);
69
+ assert.ok(model.contextWindow > 0);
70
+ });
71
+
72
+ it("z-ai/glm-5.1 uses the OpenRouter base URL", () => {
73
+ const model = getModel("openrouter", "z-ai/glm-5.1" as any);
74
+ assert.equal(model.baseUrl, "https://openrouter.ai/api/v1");
75
+ });
76
+ });
77
+
78
+ // ═══════════════════════════════════════════════════════════════════════════
79
+ // Structural invariants — every model in MODELS must be well-formed
80
+ // ═══════════════════════════════════════════════════════════════════════════
81
+
82
+ describe("MODELS structural invariants", () => {
83
+ type ModelEntry = { providerKey: string; modelKey: string; model: Record<string, unknown> };
84
+
85
+ function allModels(): ModelEntry[] {
86
+ const entries: ModelEntry[] = [];
87
+ for (const [providerKey, providerModels] of Object.entries(MODELS)) {
88
+ for (const [modelKey, model] of Object.entries(providerModels)) {
89
+ entries.push({ providerKey, modelKey, model: model as Record<string, unknown> });
90
+ }
91
+ }
92
+ return entries;
93
+ }
94
+
95
+ it("every model's id field matches its key in MODELS", () => {
96
+ const mismatches: string[] = [];
97
+ for (const { providerKey, modelKey, model } of allModels()) {
98
+ if (model["id"] !== modelKey) {
99
+ mismatches.push(`${providerKey}/${modelKey}: id="${model["id"]}"`);
100
+ }
101
+ }
102
+ assert.deepEqual(mismatches, [], `Models where 'id' doesn't match registry key:\n ${mismatches.join("\n ")}`);
103
+ });
104
+
105
+ it("every model's provider field matches its parent provider key", () => {
106
+ const mismatches: string[] = [];
107
+ for (const { providerKey, modelKey, model } of allModels()) {
108
+ if (model["provider"] !== providerKey) {
109
+ mismatches.push(`${providerKey}/${modelKey}: provider="${model["provider"]}"`);
110
+ }
111
+ }
112
+ assert.deepEqual(mismatches, [], `Models where 'provider' doesn't match parent key:\n ${mismatches.join("\n ")}`);
113
+ });
114
+
115
+ it("every model has a non-empty string name", () => {
116
+ const invalid: string[] = [];
117
+ for (const { providerKey, modelKey, model } of allModels()) {
118
+ if (typeof model["name"] !== "string" || model["name"].trim() === "") {
119
+ invalid.push(`${providerKey}/${modelKey}`);
120
+ }
121
+ }
122
+ assert.deepEqual(invalid, [], `Models with missing or empty name:\n ${invalid.join("\n ")}`);
123
+ });
124
+
125
+ it("every model has a non-empty string api", () => {
126
+ const invalid: string[] = [];
127
+ for (const { providerKey, modelKey, model } of allModels()) {
128
+ if (typeof model["api"] !== "string" || model["api"].trim() === "") {
129
+ invalid.push(`${providerKey}/${modelKey}`);
130
+ }
131
+ }
132
+ assert.deepEqual(invalid, [], `Models with missing or empty api:\n ${invalid.join("\n ")}`);
133
+ });
134
+
135
+ it("every model's baseUrl starts with https:// (or is empty for azure-openai-responses)", () => {
136
+ const invalid: string[] = [];
137
+ for (const { providerKey, modelKey, model } of allModels()) {
138
+ if (providerKey === "azure-openai-responses") continue;
139
+ const url = model["baseUrl"];
140
+ if (typeof url !== "string" || !url.startsWith("https://")) {
141
+ invalid.push(`${providerKey}/${modelKey}: baseUrl="${url}"`);
142
+ }
143
+ }
144
+ assert.deepEqual(invalid, [], `Models with missing or non-HTTPS baseUrl:\n ${invalid.join("\n ")}`);
145
+ });
146
+
147
+ it("azure-openai-responses models have an empty baseUrl (runtime-configured)", () => {
148
+ const models = getModels("azure-openai-responses");
149
+ assert.ok(models.length > 0, "azure-openai-responses must have at least one model");
150
+ for (const model of models) {
151
+ assert.equal(model.baseUrl, "", `azure-openai-responses/${model.id} should have empty baseUrl`);
152
+ }
153
+ });
154
+
155
+ it("every model has a boolean reasoning field", () => {
156
+ const invalid: string[] = [];
157
+ for (const { providerKey, modelKey, model } of allModels()) {
158
+ if (typeof model["reasoning"] !== "boolean") {
159
+ invalid.push(`${providerKey}/${modelKey}: reasoning=${model["reasoning"]}`);
160
+ }
161
+ }
162
+ assert.deepEqual(invalid, [], `Models with non-boolean reasoning:\n ${invalid.join("\n ")}`);
163
+ });
164
+
165
+ it("every model has a non-empty input array", () => {
166
+ const invalid: string[] = [];
167
+ for (const { providerKey, modelKey, model } of allModels()) {
168
+ const input = model["input"];
169
+ if (!Array.isArray(input) || input.length === 0) {
170
+ invalid.push(`${providerKey}/${modelKey}`);
171
+ }
172
+ }
173
+ assert.deepEqual(invalid, [], `Models with missing or empty input array:\n ${invalid.join("\n ")}`);
174
+ });
175
+
176
+ it("every model has a positive contextWindow", () => {
177
+ const invalid: string[] = [];
178
+ for (const { providerKey, modelKey, model } of allModels()) {
179
+ const cw = model["contextWindow"];
180
+ if (typeof cw !== "number" || cw <= 0 || !Number.isFinite(cw)) {
181
+ invalid.push(`${providerKey}/${modelKey}: contextWindow=${cw}`);
182
+ }
183
+ }
184
+ assert.deepEqual(invalid, [], `Models with invalid contextWindow:\n ${invalid.join("\n ")}`);
185
+ });
186
+
187
+ it("every model has a positive maxTokens", () => {
188
+ const invalid: string[] = [];
189
+ for (const { providerKey, modelKey, model } of allModels()) {
190
+ const mt = model["maxTokens"];
191
+ if (typeof mt !== "number" || mt <= 0 || !Number.isFinite(mt)) {
192
+ invalid.push(`${providerKey}/${modelKey}: maxTokens=${mt}`);
193
+ }
194
+ }
195
+ assert.deepEqual(invalid, [], `Models with invalid maxTokens:\n ${invalid.join("\n ")}`);
196
+ });
197
+
198
+ it("every model's maxTokens does not exceed contextWindow", () => {
199
+ const knownExceptions = new Set([
200
+ "openrouter/meta-llama/llama-3-8b-instruct",
201
+ "openrouter/nex-agi/deepseek-v3.1-nex-n1",
202
+ "openrouter/openai/gpt-3.5-turbo-0613",
203
+ "openrouter/z-ai/glm-5",
204
+ ]);
205
+
206
+ const invalid: string[] = [];
207
+ for (const { providerKey, modelKey, model } of allModels()) {
208
+ if (knownExceptions.has(`${providerKey}/${modelKey}`)) continue;
209
+ const cw = model["contextWindow"] as number;
210
+ const mt = model["maxTokens"] as number;
211
+ if (typeof cw === "number" && typeof mt === "number" && mt > cw) {
212
+ invalid.push(`${providerKey}/${modelKey}: maxTokens(${mt}) > contextWindow(${cw})`);
213
+ }
214
+ }
215
+ assert.deepEqual(invalid, [], `Models where maxTokens exceeds contextWindow:\n ${invalid.join("\n ")}`);
216
+ });
217
+
218
+ it("every model has a cost object with non-negative numeric fields", () => {
219
+ const knownNegativeCostModels = new Set([
220
+ "openrouter/openrouter/auto",
221
+ ]);
222
+
223
+ const invalid: string[] = [];
224
+ for (const { providerKey, modelKey, model } of allModels()) {
225
+ if (knownNegativeCostModels.has(`${providerKey}/${modelKey}`)) continue;
226
+ const cost = model["cost"] as Record<string, unknown> | undefined;
227
+ if (!cost || typeof cost !== "object") {
228
+ invalid.push(`${providerKey}/${modelKey}: missing cost object`);
229
+ continue;
230
+ }
231
+ for (const field of ["input", "output", "cacheRead", "cacheWrite"] as const) {
232
+ const val = cost[field];
233
+ if (typeof val !== "number" || val < 0 || !Number.isFinite(val)) {
234
+ invalid.push(`${providerKey}/${modelKey}: cost.${field}=${val}`);
235
+ }
236
+ }
237
+ }
238
+ assert.deepEqual(invalid, [], `Models with invalid cost fields:\n ${invalid.join("\n ")}`);
239
+ });
240
+
241
+ it("no provider has duplicate model IDs", () => {
242
+ const duplicates: string[] = [];
243
+ for (const [providerKey, providerModels] of Object.entries(MODELS)) {
244
+ const ids = Object.values(providerModels).map((m) => (m as Record<string, unknown>)["id"] as string);
245
+ const seen = new Set<string>();
246
+ for (const id of ids) {
247
+ if (seen.has(id)) duplicates.push(`${providerKey}/${id}`);
248
+ seen.add(id);
249
+ }
250
+ }
251
+ assert.deepEqual(duplicates, [], `Duplicate model IDs within a provider:\n ${duplicates.join("\n ")}`);
252
+ });
253
+ });
254
+
255
+ // ═══════════════════════════════════════════════════════════════════════════
256
+ // Registry shape
257
+ // ═══════════════════════════════════════════════════════════════════════════
258
+
259
+ describe("MODELS registry shape", () => {
260
+ it("has exactly 23 providers", () => {
261
+ const count = Object.keys(MODELS).length;
262
+ assert.equal(count, 23, `Expected 23 providers, got ${count}: ${Object.keys(MODELS).join(", ")}`);
263
+ });
264
+
265
+ it("has at least 200 models in total (sanity check)", () => {
266
+ let total = 0;
267
+ for (const providerModels of Object.values(MODELS)) {
268
+ total += Object.keys(providerModels).length;
269
+ }
270
+ assert.ok(total >= 200, `Registry has only ${total} models — unexpectedly small`);
271
+ });
272
+
273
+ it("all 23 expected providers are present", () => {
274
+ const expected = [
275
+ "amazon-bedrock",
276
+ "anthropic",
277
+ "azure-openai-responses",
278
+ "cerebras",
279
+ "github-copilot",
280
+ "google",
281
+ "google-antigravity",
282
+ "google-gemini-cli",
283
+ "google-vertex",
284
+ "groq",
285
+ "huggingface",
286
+ "kimi-coding",
287
+ "minimax",
288
+ "minimax-cn",
289
+ "mistral",
290
+ "openai",
291
+ "openai-codex",
292
+ "opencode",
293
+ "opencode-go",
294
+ "openrouter",
295
+ "vercel-ai-gateway",
296
+ "xai",
297
+ "zai",
298
+ ];
299
+ const actual = Object.keys(MODELS).sort();
300
+ assert.deepEqual(actual, expected.sort());
301
+ });
302
+
303
+ it("getProviders() returns all generated providers", () => {
304
+ const providers = getProviders();
305
+ for (const p of Object.keys(MODELS)) {
306
+ assert.ok(providers.includes(p as any), `getProviders() missing generated provider: ${p}`);
307
+ }
308
+ });
309
+ });
310
+
311
+ // ═══════════════════════════════════════════════════════════════════════════
312
+ // Removed models must not exist
313
+ // ═══════════════════════════════════════════════════════════════════════════
314
+
315
+ describe("removed models are absent from the registry", () => {
316
+ const removedModels: Array<{ provider: string; id: string }> = [
317
+ { provider: "openrouter", id: "anthropic/claude-3.5-sonnet" },
318
+ { provider: "openrouter", id: "anthropic/claude-3.5-sonnet-20240620" },
319
+ { provider: "openrouter", id: "mistralai/mistral-small-24b-instruct-2501" },
320
+ { provider: "openrouter", id: "mistralai/mistral-small-3.1-24b-instruct:free" },
321
+ { provider: "openrouter", id: "qwen/qwen3-4b:free" },
322
+ { provider: "openrouter", id: "stepfun/step-3.5-flash:free" },
323
+ { provider: "openrouter", id: "x-ai/grok-4.20-beta" },
324
+ { provider: "openrouter", id: "arcee-ai/trinity-mini:free" },
325
+ { provider: "openrouter", id: "google/gemini-3-pro-preview" },
326
+ { provider: "openrouter", id: "kwaipilot/kat-coder-pro" },
327
+ { provider: "openrouter", id: "meituan/longcat-flash-thinking" },
328
+ { provider: "vercel-ai-gateway", id: "xai/grok-2-vision" },
329
+ { provider: "anthropic", id: "claude-3-7-sonnet-latest" },
330
+ ];
331
+
332
+ for (const { provider, id } of removedModels) {
333
+ it(`${provider}/${id} has been removed`, () => {
334
+ const model = getModel(provider as any, id as any);
335
+ assert.equal(model, undefined, `${provider}/${id} should be removed but is still present`);
336
+ });
337
+ }
338
+ });
339
+
340
+ // ═══════════════════════════════════════════════════════════════════════════
341
+ // Spot-checks for notable models added in this regeneration
342
+ // ═══════════════════════════════════════════════════════════════════════════
343
+
344
+ describe("spot-checks for models added in this regeneration", () => {
345
+ const newModels: Array<{ provider: string; id: string; reasoning?: boolean }> = [
346
+ { provider: "openrouter", id: "z-ai/glm-5.1" },
347
+ { provider: "openrouter", id: "z-ai/glm-5v-turbo" },
348
+ { provider: "openrouter", id: "google/gemma-4-31b-it" },
349
+ { provider: "openrouter", id: "google/gemma-4-26b-a4b-it" },
350
+ { provider: "openrouter", id: "arcee-ai/trinity-large-thinking", reasoning: true },
351
+ { provider: "openrouter", id: "openai/gpt-audio" },
352
+ { provider: "openrouter", id: "anthropic/claude-opus-4.6-fast" },
353
+ { provider: "openrouter", id: "qwen/qwen3.6-plus" },
354
+ { provider: "groq", id: "groq/compound" },
355
+ { provider: "groq", id: "groq/compound-mini" },
356
+ { provider: "huggingface", id: "zai-org/GLM-5.1" },
357
+ { provider: "openai", id: "gpt-5.3-chat-latest" },
358
+ { provider: "mistral", id: "mistral-small-2603" },
359
+ { provider: "zai", id: "glm-5.1" },
360
+ ];
361
+
362
+ for (const { provider, id, reasoning } of newModels) {
363
+ it(`${provider}/${id} is present in the registry`, () => {
364
+ const model = getModel(provider as any, id as any);
365
+ assert.ok(model, `Expected ${provider}/${id} to be present after regeneration`);
366
+ assert.equal(model.id, id);
367
+ assert.equal(model.provider, provider);
368
+ if (reasoning !== undefined) {
369
+ assert.equal(model.reasoning, reasoning, `${id} reasoning should be ${reasoning}`);
370
+ }
371
+ });
372
+ }
373
+ });