@pentoshi/clai 3.9.7 → 3.10.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 (182) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +81 -9
  3. package/dist/agent/context-manager.d.ts +2 -1
  4. package/dist/agent/context-manager.js +16 -2
  5. package/dist/agent/context-manager.js.map +1 -1
  6. package/dist/agent/loop-guard.d.ts +18 -0
  7. package/dist/agent/loop-guard.js +50 -2
  8. package/dist/agent/loop-guard.js.map +1 -1
  9. package/dist/agent/must-continue.js +4 -8
  10. package/dist/agent/must-continue.js.map +1 -1
  11. package/dist/agent/prompt-composer.js +8 -8
  12. package/dist/agent/prompt-composer.js.map +1 -1
  13. package/dist/agent/runner.d.ts +1 -0
  14. package/dist/agent/runner.js +238 -74
  15. package/dist/agent/runner.js.map +1 -1
  16. package/dist/agent/session-policy.d.ts +1 -2
  17. package/dist/agent/session-policy.js +3 -2
  18. package/dist/agent/session-policy.js.map +1 -1
  19. package/dist/agent/tool-call-parser.js +6 -6
  20. package/dist/agent/tool-call-parser.js.map +1 -1
  21. package/dist/app/adapters/current-agent-adapter.js +1 -0
  22. package/dist/app/adapters/current-agent-adapter.js.map +1 -1
  23. package/dist/app/controllers/session-controller.js +2 -0
  24. package/dist/app/controllers/session-controller.js.map +1 -1
  25. package/dist/app/controllers/session-turn-request.d.ts +1 -0
  26. package/dist/app/controllers/session-turn-request.js +4 -1
  27. package/dist/app/controllers/session-turn-request.js.map +1 -1
  28. package/dist/app/ports/agent-port.d.ts +1 -0
  29. package/dist/attachments/clipboard-image.js +5 -4
  30. package/dist/attachments/clipboard-image.js.map +1 -1
  31. package/dist/attachments/image-content.d.ts +20 -2
  32. package/dist/attachments/image-content.js +201 -1
  33. package/dist/attachments/image-content.js.map +1 -1
  34. package/dist/attachments/image-prepare.d.ts +25 -0
  35. package/dist/attachments/image-prepare.js +390 -0
  36. package/dist/attachments/image-prepare.js.map +1 -0
  37. package/dist/attachments/service.d.ts +4 -2
  38. package/dist/attachments/service.js +51 -21
  39. package/dist/attachments/service.js.map +1 -1
  40. package/dist/commands/providers.d.ts +5 -2
  41. package/dist/commands/providers.js +121 -7
  42. package/dist/commands/providers.js.map +1 -1
  43. package/dist/index.js +12 -4
  44. package/dist/index.js.map +1 -1
  45. package/dist/llm/agentrouter.js +2 -2
  46. package/dist/llm/agentrouter.js.map +1 -1
  47. package/dist/llm/anthropic.d.ts +5 -4
  48. package/dist/llm/anthropic.js +28 -25
  49. package/dist/llm/anthropic.js.map +1 -1
  50. package/dist/llm/aws-mantle.js +19 -20
  51. package/dist/llm/aws-mantle.js.map +1 -1
  52. package/dist/llm/bynara.js +2 -2
  53. package/dist/llm/bynara.js.map +1 -1
  54. package/dist/llm/capabilities.d.ts +19 -0
  55. package/dist/llm/capabilities.js +273 -16
  56. package/dist/llm/capabilities.js.map +1 -1
  57. package/dist/llm/gemini.js +16 -8
  58. package/dist/llm/gemini.js.map +1 -1
  59. package/dist/llm/groq.js +2 -2
  60. package/dist/llm/groq.js.map +1 -1
  61. package/dist/llm/http.d.ts +7 -1
  62. package/dist/llm/http.js +210 -8
  63. package/dist/llm/http.js.map +1 -1
  64. package/dist/llm/kimchi.js +2 -2
  65. package/dist/llm/kimchi.js.map +1 -1
  66. package/dist/llm/lightning.d.ts +11 -0
  67. package/dist/llm/lightning.js +108 -0
  68. package/dist/llm/lightning.js.map +1 -0
  69. package/dist/llm/modal.d.ts +16 -0
  70. package/dist/llm/modal.js +182 -0
  71. package/dist/llm/modal.js.map +1 -0
  72. package/dist/llm/nvidia.js +2 -2
  73. package/dist/llm/nvidia.js.map +1 -1
  74. package/dist/llm/ollama.js +3 -3
  75. package/dist/llm/ollama.js.map +1 -1
  76. package/dist/llm/openai.js +2 -2
  77. package/dist/llm/openai.js.map +1 -1
  78. package/dist/llm/openrouter.js +2 -2
  79. package/dist/llm/openrouter.js.map +1 -1
  80. package/dist/llm/provider.d.ts +11 -0
  81. package/dist/llm/provider.js +316 -1
  82. package/dist/llm/provider.js.map +1 -1
  83. package/dist/llm/qwen-cloud.js +2 -5
  84. package/dist/llm/qwen-cloud.js.map +1 -1
  85. package/dist/llm/router.js +127 -7
  86. package/dist/llm/router.js.map +1 -1
  87. package/dist/llm/system-messages.d.ts +5 -0
  88. package/dist/llm/system-messages.js +14 -0
  89. package/dist/llm/system-messages.js.map +1 -1
  90. package/dist/llm/token-usage.d.ts +13 -0
  91. package/dist/llm/token-usage.js +31 -0
  92. package/dist/llm/token-usage.js.map +1 -1
  93. package/dist/llm/tokenrouter.d.ts +15 -0
  94. package/dist/llm/tokenrouter.js +106 -0
  95. package/dist/llm/tokenrouter.js.map +1 -0
  96. package/dist/modes/agent.d.ts +1 -0
  97. package/dist/modes/agent.js.map +1 -1
  98. package/dist/modes/ask.js +15 -4
  99. package/dist/modes/ask.js.map +1 -1
  100. package/dist/prompts/embedded.js +2 -2
  101. package/dist/prompts/embedded.js.map +1 -1
  102. package/dist/prompts/index.d.ts +6 -0
  103. package/dist/prompts/index.js +39 -21
  104. package/dist/prompts/index.js.map +1 -1
  105. package/dist/prompts/system.agent.md +15 -27
  106. package/dist/prompts/system.ask.md +1 -1
  107. package/dist/repl/slash-commands.js +53 -0
  108. package/dist/repl/slash-commands.js.map +1 -1
  109. package/dist/repl.js +16 -9
  110. package/dist/repl.js.map +1 -1
  111. package/dist/store/config.d.ts +55 -0
  112. package/dist/store/config.js +127 -0
  113. package/dist/store/config.js.map +1 -1
  114. package/dist/store/history.d.ts +2 -1
  115. package/dist/store/history.js +15 -16
  116. package/dist/store/history.js.map +1 -1
  117. package/dist/store/keys.js +41 -2
  118. package/dist/store/keys.js.map +1 -1
  119. package/dist/tools/definitions.js +31 -6
  120. package/dist/tools/definitions.js.map +1 -1
  121. package/dist/tools/image.d.ts +0 -7
  122. package/dist/tools/image.js +76 -22
  123. package/dist/tools/image.js.map +1 -1
  124. package/dist/tools/jobs.d.ts +11 -1
  125. package/dist/tools/jobs.js +67 -0
  126. package/dist/tools/jobs.js.map +1 -1
  127. package/dist/tools/ocr.d.ts +28 -0
  128. package/dist/tools/ocr.js +316 -0
  129. package/dist/tools/ocr.js.map +1 -0
  130. package/dist/tools/pdf.d.ts +0 -12
  131. package/dist/tools/pdf.js +436 -150
  132. package/dist/tools/pdf.js.map +1 -1
  133. package/dist/tools/registry.js +5 -0
  134. package/dist/tools/registry.js.map +1 -1
  135. package/dist/tui/format-keys.js +10 -0
  136. package/dist/tui/format-keys.js.map +1 -1
  137. package/dist/tui-v2/actions/action-id.d.ts +1 -1
  138. package/dist/tui-v2/actions/action-id.js +2 -0
  139. package/dist/tui-v2/actions/action-id.js.map +1 -1
  140. package/dist/tui-v2/actions/format-shortcuts.js +3 -1
  141. package/dist/tui-v2/actions/format-shortcuts.js.map +1 -1
  142. package/dist/tui-v2/actions/keymap.js +4 -1
  143. package/dist/tui-v2/actions/keymap.js.map +1 -1
  144. package/dist/tui-v2/app/commands/key-commands.js +117 -15
  145. package/dist/tui-v2/app/commands/key-commands.js.map +1 -1
  146. package/dist/tui-v2/app/commands/picker-commands.js +80 -11
  147. package/dist/tui-v2/app/commands/picker-commands.js.map +1 -1
  148. package/dist/tui-v2/components/jobs/jobs-panel.js +25 -1
  149. package/dist/tui-v2/components/jobs/jobs-panel.js.map +1 -1
  150. package/dist/tui-v2/components/modal/keys-modal.js +15 -8
  151. package/dist/tui-v2/components/modal/keys-modal.js.map +1 -1
  152. package/dist/tui-v2/components/modal/secret-modal.js +13 -4
  153. package/dist/tui-v2/components/modal/secret-modal.js.map +1 -1
  154. package/dist/tui-v2/components/pager/pager.js +103 -12
  155. package/dist/tui-v2/components/pager/pager.js.map +1 -1
  156. package/dist/tui-v2/components/transcript/thinking-block.d.ts +11 -3
  157. package/dist/tui-v2/components/transcript/thinking-block.js +5 -4
  158. package/dist/tui-v2/components/transcript/thinking-block.js.map +1 -1
  159. package/dist/tui-v2/components/transcript/transcript-row.d.ts +2 -0
  160. package/dist/tui-v2/components/transcript/transcript-row.js +2 -2
  161. package/dist/tui-v2/components/transcript/transcript-row.js.map +1 -1
  162. package/dist/tui-v2/components/transcript/transcript-view.js +8 -1
  163. package/dist/tui-v2/components/transcript/transcript-view.js.map +1 -1
  164. package/dist/tui-v2/controllers/overlay-controller.d.ts +13 -0
  165. package/dist/tui-v2/controllers/overlay-controller.js +5 -2
  166. package/dist/tui-v2/controllers/overlay-controller.js.map +1 -1
  167. package/dist/tui-v2/rendering/artifact-pager-source.d.ts +10 -0
  168. package/dist/tui-v2/rendering/artifact-pager-source.js +6 -0
  169. package/dist/tui-v2/rendering/artifact-pager-source.js.map +1 -1
  170. package/dist/tui-v2/rendering/job-tail-source.d.ts +21 -0
  171. package/dist/tui-v2/rendering/job-tail-source.js +87 -0
  172. package/dist/tui-v2/rendering/job-tail-source.js.map +1 -0
  173. package/dist/types.d.ts +7 -1
  174. package/dist/types.js +3 -0
  175. package/dist/types.js.map +1 -1
  176. package/dist/ui/mentions.d.ts +7 -2
  177. package/dist/ui/mentions.js +52 -117
  178. package/dist/ui/mentions.js.map +1 -1
  179. package/dist/version.generated.d.ts +2 -2
  180. package/dist/version.generated.js +2 -2
  181. package/dist/version.generated.js.map +1 -1
  182. package/package.json +1 -1
@@ -26,6 +26,17 @@ export const providerAliases = {
26
26
  "qwen-cloud": "qwen-cloud",
27
27
  dashscope: "qwen-cloud",
28
28
  qwencloud: "qwen-cloud",
29
+ modal: "modal",
30
+ "modal-labs": "modal",
31
+ modalcom: "modal",
32
+ lightning: "lightning",
33
+ "lightning-ai": "lightning",
34
+ lightningai: "lightning",
35
+ "lightning.ai": "lightning",
36
+ litai: "lightning",
37
+ tokenrouter: "tokenrouter",
38
+ "token-router": "tokenrouter",
39
+ tr: "tokenrouter",
29
40
  };
30
41
  export const defaultModels = {
31
42
  groq: "llama-3.3-70b-versatile",
@@ -40,6 +51,12 @@ export const defaultModels = {
40
51
  ollama: "llama3.1:8b",
41
52
  bynara: "mimo-v2.5-free",
42
53
  "qwen-cloud": "qwen3.7-plus",
54
+ // Modal Endpoints serve one model per endpoint, named by its source repo id.
55
+ modal: "moonshotai/Kimi-K3",
56
+ // Lightning AI namespaces model ids by vendor.
57
+ lightning: "openai/gpt-5",
58
+ // TokenRouter ids are short and case-sensitive (kimi-k2p6, not Kimi K2.6).
59
+ tokenrouter: "kimi-k2p6",
43
60
  };
44
61
  const retiredModelReplacements = {
45
62
  groq: {
@@ -78,6 +95,12 @@ export const envVars = {
78
95
  ollama: "OLLAMA_HOST",
79
96
  bynara: "BYNARA_API_KEY",
80
97
  "qwen-cloud": "DASHSCOPE_API_KEY",
98
+ // Modal needs a *pair* of proxy-token env vars plus an endpoint URL; the
99
+ // second half (MODAL_PROXY_TOKEN_SECRET) and MODAL_BASE_URL are resolved in
100
+ // store/keys.ts and store/config.ts respectively.
101
+ modal: "MODAL_PROXY_TOKEN_ID",
102
+ lightning: "LIGHTNING_API_KEY",
103
+ tokenrouter: "TOKENROUTER_API_KEY",
81
104
  };
82
105
  export function normalizeProvider(value) {
83
106
  return providerAliases[value.trim().toLowerCase()];
@@ -97,6 +120,28 @@ export function sanitizeProviderModel(provider, model) {
97
120
  const replacement = retiredModelReplacements[provider]?.[normalized.toLowerCase()];
98
121
  return replacement ?? normalized;
99
122
  }
123
+ /**
124
+ * Normalize a user-supplied OpenAI-compatible base URL so the HTTP helpers can
125
+ * append `/chat/completions` to it. Idempotent, and tolerant of the three
126
+ * things people actually paste: a bare host, a trailing slash, or a full
127
+ * endpoint path.
128
+ *
129
+ * x--ep-y.modal.direct → https://x--ep-y.modal.direct/v1
130
+ * https://x--ep-y.modal.direct/v1/ → https://x--ep-y.modal.direct/v1
131
+ * https://host/v1/chat/completions → https://host/v1
132
+ */
133
+ export function normalizeEndpointUrl(url) {
134
+ let value = url.trim();
135
+ if (!value)
136
+ return "";
137
+ if (!/^https?:\/\//i.test(value))
138
+ value = `https://${value}`;
139
+ value = value.replace(/\/+$/, "");
140
+ value = value.replace(/\/chat\/completions$/i, "").replace(/\/models$/i, "");
141
+ if (!/\/v\d+$/i.test(value))
142
+ value = `${value}/v1`;
143
+ return value;
144
+ }
100
145
  export function maskSecret(secret) {
101
146
  // Show first 4 and last 4 characters with a fixed-width •••• separator.
102
147
  // Output is always 12 chars for keys >= 8, keeping tables compact.
@@ -120,9 +165,279 @@ export function redactSecrets(value) {
120
165
  .replace(/sk-[A-Za-z0-9._-]+/g, "sk-••••••")
121
166
  .replace(/sk-or-[A-Za-z0-9_-]+/g, "sk-or-••••••")
122
167
  .replace(/sk-ant-[A-Za-z0-9_-]+/g, "sk-ant-••••••")
123
- .replace(/nvapi-[A-Za-z0-9_-]+/g, "nvapi-••••••");
168
+ .replace(/nvapi-[A-Za-z0-9_-]+/g, "nvapi-••••••")
169
+ // Modal proxy tokens: id `wk-…`, secret `ws-…`. Require a long tail so
170
+ // ordinary words (`ws-connection`) are never mangled.
171
+ .replace(/wk-[A-Za-z0-9_-]{16,}/g, "wk-••••••")
172
+ .replace(/ws-[A-Za-z0-9_-]{16,}/g, "ws-••••••");
124
173
  }
125
174
  export const providerInfo = {
175
+ tokenrouter: `TokenRouter — one key for frontier open models
176
+
177
+ WHAT IT IS
178
+ An OpenAI-compatible gateway that fronts Kimi, DeepSeek, Qwen, GLM, GPT-OSS
179
+ and MiniMax behind a single bearer key and base URL. Chat Completions plus
180
+ the Responses API upstream; clai uses Chat Completions with SSE streaming,
181
+ native tool calling and JSON mode.
182
+
183
+ Base URL https://api.tokenrouter.com/v1 (override if your account uses
184
+ a different host — see ENDPOINTS below)
185
+ Auth Authorization: Bearer <key>
186
+ Endpoints /models · /chat/completions
187
+
188
+ MODELS (ids are exact and case-sensitive — kimi-k2p6, not "Kimi K2.6")
189
+ kimi-k2p7-code / -fast 256K ctx agentic coding, tools + vision
190
+ kimi-k2p6 256K ctx long-horizon agent work (clai default)
191
+ kimi-k2p5 256K ctx faster, cheaper coding
192
+ deepseek-v4-pro 1M ctx deep reasoning over huge codebases
193
+ deepseek-v4-flash 1M ctx cheapest long context
194
+ qwen3p7-plus 256K ctx multilingual general purpose
195
+ qwen3p6-plus 256K ctx multilingual general purpose
196
+ glm-5p1 / glm-5p1-fast 200K ctx agentic engineering
197
+ gpt-oss-120b 128K ctx open-source OpenAI model
198
+ minimax-m3 512K ctx very long context, agent harnesses
199
+ minimax-m2p7 196K ctx budget agent tasks
200
+
201
+ /model reads the live list from /models, which is filtered to the channels
202
+ your key can actually reach — trust that over this table.
203
+
204
+ COST
205
+ Prepaid balance billed per token; the dashboard header shows what is left.
206
+ There is no published free tier, so treat it as paid: clai classes it
207
+ paid-cloud and /freeonly on keeps it out of the fallback chain. Prices vary
208
+ a lot between the flash/fast variants and the pro ones — deepseek-v4-flash
209
+ is the cheap long-context option, kimi-k2p7-code the premium coding one.
210
+
211
+ SETUP
212
+ 1. Create an API key in your TokenRouter account under API Keys.
213
+ 2. clai set tokenrouter sk-yourKey
214
+ 3. clai use tokenrouter
215
+ 4. /model kimi-k2p6 (or any id from /model)
216
+
217
+ MANAGING KEYS AND ENDPOINTS IN CLAI
218
+ clai set tokenrouter <key> add a key (up to 10, rotated on failure)
219
+ clai keys masked keys + the active endpoint
220
+ clai unset tokenrouter remove every stored key
221
+ /set tokenrouter TUI: endpoint editor, then key editor
222
+ /info tokenrouter this page
223
+
224
+ Base URL (optional — defaults to api.tokenrouter.com):
225
+ clai set tokenrouter --url https://tokenrouter.me/v1
226
+ clai set tokenrouter --url <a> --url <b> store several, sticky active one
227
+ clai unset tokenrouter --url back to the default
228
+ TOKENROUTER_BASE_URL overrides the whole list.
229
+
230
+ GOOD TO KNOW
231
+ - Reasoning models return their thinking in reasoning_content; clai folds it
232
+ into the usual thinking block, so /think and /variants behave normally.
233
+ - max_tokens above a model's ceiling is clamped by the gateway rather than
234
+ rejected, but prompt + max_tokens must still fit the context window.
235
+ - The model field in responses may echo a fully-qualified upstream path
236
+ instead of the short id you sent. That is expected.
237
+ - Env var: TOKENROUTER_API_KEY (used when nothing is stored).
238
+
239
+ Docs: https://docs.tokenrouter.me`,
240
+ lightning: `Lightning AI Model APIs — one key for OpenAI, Anthropic, Google
241
+ and Lightning-hosted open models
242
+
243
+ WHAT IT IS
244
+ An OpenAI-compatible gateway at https://lightning.ai/api/v1 that fronts
245
+ frontier models from several vendors plus open models Lightning serves
246
+ itself. One account, one key, no per-vendor subscriptions, billed by the
247
+ token. Model ids are vendor-namespaced:
248
+ openai/gpt-5, openai/o3, openai/gpt-5.6-sol
249
+ anthropic/claude-opus-4-8, anthropic/claude-sonnet-4-6
250
+ google/gemini-3.5-flash, google/gemini-2.5-pro
251
+ lightning-ai/gpt-oss-120b, lightning-ai/deepseek-v4-pro,
252
+ lightning-ai/nemotron-3-ultra-550b-a55b, lightning-ai/gemma-4-31B-it
253
+
254
+ FREE TIER — WHAT YOU GET
255
+ New accounts get up to 40 million free tokens to start, and the docs
256
+ advertise no subscription or credit card to begin. After that it is
257
+ pay-per-token at each model's rate — the /models endpoint returns the
258
+ per-token input and output price for every id, so nothing is hidden.
259
+
260
+ Rate limits by plan (requests/min · tokens/min):
261
+ Free 15 · 120,000
262
+ Pro 20 · 120,000
263
+ Teams 30 · 150,000
264
+ Enterprise 300 · unlimited
265
+
266
+ Signing up needs a non-virtual phone number, and the free grant is once
267
+ per person — a second account does not get a second grant.
268
+
269
+ SETUP — STEP BY STEP
270
+ 1. Create an account at https://lightning.ai
271
+ 2. Open the Model APIs page and reveal your key:
272
+ https://lightning.ai/lightning-ai/model-apis/models?showApiKey=true
273
+ 3. Give it to clai:
274
+ clai set lightning <your-api-key>
275
+ clai use lightning
276
+ 4. Pick a model — /model lists the live catalog from the gateway:
277
+ /model openai/gpt-5
278
+ /model lightning-ai/gpt-oss-120b (cheapest open-weight option)
279
+
280
+ Or export it instead of storing it:
281
+ LIGHTNING_API_KEY=<your-api-key>
282
+
283
+ MANAGING KEYS AND ENDPOINTS IN CLAI
284
+ clai set lightning <key> add a key (up to 10, rotated on failure)
285
+ clai set lightning <key2> add another; the last that worked is sticky
286
+ clai keys masked keys + the active endpoint
287
+ clai unset lightning remove every stored key
288
+ /set lightning TUI: endpoint editor, then multi-key editor
289
+ /info lightning this page
290
+
291
+ Base URL (optional — defaults to the shared gateway above):
292
+ clai set lightning --url <url> add an endpoint, make it active
293
+ clai set lightning --url <a> --url <b> add several
294
+ clai unset lightning --url back to the default gateway
295
+ /set lightning https://... add + activate one endpoint
296
+
297
+ Point it at a private Lightning Inference deployment or a proxy that speaks
298
+ the same OpenAI routes; up to 10 URLs are stored with a sticky active one.
299
+ LIGHTNING_BASE_URL overrides the whole list.
300
+
301
+ GOOD TO KNOW
302
+ - The catalog lists one entry per published agent/preset, so the same model
303
+ id appears more than once upstream; clai dedupes it for /model.
304
+ - Cost varies enormously across the list — claude-opus and gpt-5.x cost
305
+ dollars per million tokens while lightning-ai/gpt-oss-* are cents. Check
306
+ the price on https://lightning.ai/models before long agent runs.
307
+ - Streaming, native tool calling and reasoning_effort all work; clai sends
308
+ the standard OpenAI knobs and retries without them if a model objects.
309
+ - Classed as paid-cloud, so /freeonly on keeps it out of the fallback
310
+ chain even while the free tokens last.
311
+ - Model APIs are separate from Lightning Studios/GPU credits; the token
312
+ grant is not the same balance as Studio compute credits.
313
+
314
+ Docs: https://lightning.ai/docs/platform/inference/model-apis
315
+ Models: https://lightning.ai/models
316
+ API: https://lightning.ai/api/v1 (OpenAI-compatible; /models, /chat/completions)`,
317
+ modal: `Modal Endpoints — your own serverless OpenAI-compatible endpoint
318
+
319
+ WHAT IT IS
320
+ Modal Endpoints deploy an open-weight model (Kimi, Qwen, DeepSeek, GLM,
321
+ Gemma, GPT-OSS, Nemotron, or your own fine-tune) behind a low-latency
322
+ request proxy. The endpoint serves the standard Chat Completions API under
323
+ /v1, autoscales under load, and scales to zero when idle. The endpoint
324
+ belongs to your workspace, so the base URL is unique to you:
325
+ https://<workspace>--ep-<endpoint>.<region>.modal.direct/v1
326
+
327
+ FREE TIER — WHAT YOU GET AND WHAT IT COSTS
328
+ Signing up is free. The Starter plan is $0/month + compute and includes
329
+ $30 of free compute credit every month; you unlock it by adding a payment
330
+ method, and nothing is charged until a month's usage passes the credit.
331
+ Starter also includes 3 workspace seats, 100 containers + 10 GPU
332
+ concurrency, region selection, and real-time metrics and logs.
333
+ Team is $250/month + compute with $100/month of credit, 1000 containers
334
+ and 50 GPU concurrency. Credit grants exist for early-stage startups and
335
+ for academics (up to $10k).
336
+
337
+ Billing is per-second compute (GPU + CPU) at standard Modal rates — not
338
+ per token. Because endpoints scale to zero you pay only while a container
339
+ is starting or serving; an idle endpoint costs nothing. Pinning compute to
340
+ a region applies a price multiplier. The credit is granted per month, so
341
+ keep an eye on the Credits figure in the dashboard header.
342
+
343
+ SETUP — STEP BY STEP
344
+ 1. Create a free account at https://modal.com
345
+ 2. Add a payment method (dashboard → settings → billing) to activate the
346
+ $30/month credit.
347
+ 3. Install the CLI and log in:
348
+ pip install modal
349
+ modal setup (browser login; writes tokens to ~/.modal.toml)
350
+ 4. Deploy an endpoint from the Endpoints tab in the dashboard, or:
351
+ modal endpoint create --model moonshotai/Kimi-K3 --name kimi-k3
352
+ modal endpoint create --model Qwen/Qwen3.5-4B --routing-region us-east
353
+ Provisioning takes a few minutes. Region default is us-west.
354
+ 5. Copy the endpoint URL from the endpoint Overview page, or run
355
+ modal endpoint list
356
+ 6. Create a proxy token pair (endpoints are authenticated by default):
357
+ modal workspace proxy-tokens create
358
+ This prints a token ID (wk-...) and a token secret (ws-...). THE SECRET
359
+ IS SHOWN ONLY ONCE — copy it now; it cannot be retrieved later. You can
360
+ also create one in the dashboard under workspace settings, and the
361
+ endpoint Quickstart panel lists the token IDs you already have.
362
+ On RBAC workspaces, also scope it to the endpoint's environment:
363
+ modal workspace proxy-tokens allow <token-id> <environment>
364
+ 7. Point clai at it (URL from step 5, pair from step 6):
365
+ clai set modal --url <endpoint-url>
366
+ clai set modal wk-yourTokenId:ws-yourTokenSecret
367
+ clai use modal
368
+ 8. Verify: "clai keys" shows the masked pair and the endpoint it points at,
369
+ and /model lists the models the endpoint actually serves.
370
+
371
+ KEYS — WHAT IS NEEDED AND WHERE IT GOES
372
+ Endpoint URL stored in config as modalBaseUrl (not a secret)
373
+ Proxy token ID wk-... sent as the Modal-Key header
374
+ Proxy token secret ws-... sent as the Modal-Secret header
375
+
376
+ There is no bearer API key. clai stores the pair as ONE secret shaped
377
+ "<token-id>:<token-secret>" in the OS keychain (or ~/.clai/keys.json with
378
+ restricted permissions when no keychain is available), so masking,
379
+ rotation and /unset behave exactly like every other provider.
380
+
381
+ Proxy tokens (wk-/ws-) are NOT Modal API tokens (ak-/as-). API tokens
382
+ authenticate the CLI and SDK; they will not work as endpoint headers.
383
+
384
+ MANAGING ENDPOINTS AND KEYS IN CLAI
385
+ Both are multi-entry lists with a sticky active choice, up to 10 each.
386
+
387
+ clai set modal --url <endpoint> add an endpoint, make it active
388
+ clai set modal --url <a> --url <b> add several in one call
389
+ clai set modal --url <known endpoint> re-activate one already stored
390
+ clai set modal wk-id:ws-secret add a token pair
391
+ clai set modal prompt for the pair (input hidden)
392
+ clai keys every endpoint + masked pairs, ★ active
393
+ clai unset modal --url clear the endpoint list only
394
+ clai unset modal clear the token pairs only
395
+ /set modal TUI: endpoint editor, then keys
396
+ /set modal https://... add + activate one endpoint
397
+ /info modal this page
398
+
399
+ One endpoint serves one model, so keep an endpoint per model you deploy and
400
+ switch with clai set modal --url <that endpoint> — or star a row in the
401
+ /set endpoint editor. Endpoints are not auto-rotated on failure, because a
402
+ different endpoint serves a different model; token pairs DO rotate like any
403
+ other provider (401 / 403 / 429 / 5xx / empty response moves to the next).
404
+
405
+ ENVIRONMENT VARIABLES (used only when nothing is stored)
406
+ MODAL_BASE_URL endpoint URL; overrides the stored list entirely
407
+ ("/v1" is appended if missing)
408
+ MODAL_PROXY_TOKEN_ID wk-... both halves needed, or it is ignored
409
+ MODAL_PROXY_TOKEN_SECRET ws-...
410
+ MODAL_SESSION_ID optional sticky-session id. When unset, clai
411
+ generates one per run so a whole conversation
412
+ lands on the same warm container.
413
+
414
+ GOOD TO KNOW
415
+ - Cold start: the first request after idle pays container start-up, which
416
+ can take tens of seconds on a large model. clai allows up to 3 minutes
417
+ for the first token before treating a stream as stalled.
418
+ - The model name on the wire is the source repo id (e.g.
419
+ moonshotai/Kimi-K3), not a Modal alias. /model reads the live list.
420
+ - Endpoint URLs are stored as a list, so several deployments can live side
421
+ by side; only the active one is used. "clai keys" shows them all.
422
+ - Streaming, native tool calling, structured outputs and thinking all work.
423
+ /variants on|off maps to Modal's reasoning toggle.
424
+ - Finished experimenting? "modal endpoint stop <name>" tears the endpoint
425
+ down so no stray request can wake it and spend credit.
426
+ - Modal counts as paid-cloud, so /freeonly on keeps it out of the
427
+ cross-provider fallback chain.
428
+
429
+ TROUBLESHOOTING
430
+ 401 / 403 wrong, revoked or unscoped pair — or an API token (ak-)
431
+ was pasted instead of a proxy token (wk-)
432
+ 404 endpoint URL is wrong, or the endpoint was stopped
433
+ "endpoint URL is not configured"
434
+ run clai set modal --url <endpoint>
435
+ model not found send the repo id reported by /model or modal endpoint list
436
+ slow first token cold start; the endpoint had scaled to zero
437
+
438
+ Docs: https://modal.com/docs/guide/endpoints
439
+ Auth: https://modal.com/docs/guide/webhook-proxy-auth
440
+ Pricing: https://modal.com/pricing`,
126
441
  bynara: `Current Plan
127
442
 
128
443
  Free
@@ -1 +1 @@
1
- {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/llm/provider.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/llm/provider.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { defaultModels, } from "./provider.js";
2
- import { openAiCompatibleComplete, openAiCompatiblePing, openAiCompatibleStream, toCompletionResult, readJson, } from "./http.js";
2
+ import { openAiCompatibleComplete, openAiCompatiblePing, openAiCompatibleStream, toCompletionResult, readJson, ingestOpenAiModelCatalog, } from "./http.js";
3
3
  // Qwen Cloud documents this OpenAI-compatible endpoint for international
4
4
  // accounts. See https://docs.qwencloud.com/developer-guides/getting-started/first-api-call
5
5
  const baseUrl = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1";
@@ -25,10 +25,7 @@ export const qwenCloudProvider = {
25
25
  headers: { authorization: `Bearer ${auth.apiKey}` },
26
26
  });
27
27
  const data = await readJson(response);
28
- const models = (data.data ?? [])
29
- .map((model) => model.id)
30
- .filter((model) => Boolean(model))
31
- .sort();
28
+ const models = ingestOpenAiModelCatalog("qwen-cloud", data);
32
29
  if (models.length > 0) {
33
30
  cachedModels = models;
34
31
  lastFetchTime = now;
@@ -1 +1 @@
1
- {"version":3,"file":"qwen-cloud.js","sourceRoot":"","sources":["../../src/llm/qwen-cloud.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"qwen-cloud.js","sourceRoot":"","sources":["../../src/llm/qwen-cloud.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,10 @@
1
- import { getConfig, providerCategory } from "../store/config.js";
1
+ import { getActiveProviderEndpoint, getConfig, providerCategory, providerUsesEndpoints, } from "../store/config.js";
2
2
  import { getProviderKeys, getProviderSecret, markProviderKeySuccess, } from "../store/keys.js";
3
3
  import { anthropicProvider } from "./anthropic.js";
4
4
  import { geminiProvider } from "./gemini.js";
5
5
  import { groqProvider } from "./groq.js";
6
- import { ProviderError, isReasoningUnsupportedError } from "./http.js";
7
- import { markReasoningUnsupported } from "./capabilities.js";
6
+ import { ProviderError, isImageInputUnsupportedError, isReasoningUnsupportedError, stripImagesFromMessages, } from "./http.js";
7
+ import { learnModelVisionCapability, markModelUnavailable, markReasoningUnsupported, modelAcceptsImages, visionSubstitutionOrigin, } from "./capabilities.js";
8
8
  import { markStreamEmittedBytes, streamAlreadyEmitted, streamEmittedBytes, } from "./stream-progress.js";
9
9
  import { attemptsPerKey, buildKeyAttemptPlan, formatKeyEventStatus, isImmediateKeySwitchError, isKeyCircleStopError, isKeyRotatableError, isQuotaKeyError, } from "./key-rotation.js";
10
10
  import { nvidiaProvider } from "./nvidia.js";
@@ -16,6 +16,9 @@ import { ollamaProvider } from "./ollama.js";
16
16
  import { openaiProvider } from "./openai.js";
17
17
  import { openrouterProvider } from "./openrouter.js";
18
18
  import { qwenCloudProvider } from "./qwen-cloud.js";
19
+ import { modalProvider } from "./modal.js";
20
+ import { lightningProvider } from "./lightning.js";
21
+ import { tokenrouterProvider } from "./tokenrouter.js";
19
22
  import { maskSecretTail } from "./provider.js";
20
23
  import { isToolsUnsupportedError, markTextOnlyModel, } from "./tool-protocol.js";
21
24
  const MAX_RETRIES = 6;
@@ -243,6 +246,9 @@ export const providers = {
243
246
  ollama: ollamaProvider,
244
247
  bynara: bynaraProvider,
245
248
  "qwen-cloud": qwenCloudProvider,
249
+ modal: modalProvider,
250
+ lightning: lightningProvider,
251
+ tokenrouter: tokenrouterProvider,
246
252
  };
247
253
  const fallbackOrder = [
248
254
  "nvidia",
@@ -257,6 +263,9 @@ const fallbackOrder = [
257
263
  "aws-mantle",
258
264
  "ollama",
259
265
  "qwen-cloud",
266
+ "modal",
267
+ "lightning",
268
+ "tokenrouter",
260
269
  ];
261
270
  export function buildFallbackChain(requested, freeOnly, enabled = false) {
262
271
  if (!enabled)
@@ -274,12 +283,22 @@ export async function providerAuth(provider) {
274
283
  if (provider === "ollama") {
275
284
  return { baseUrl: secret.value };
276
285
  }
286
+ // Endpoint providers carry both: the stored secret plus the active endpoint
287
+ // URL from config (Modal requires one; Lightning treats it as an override).
288
+ if (providerUsesEndpoints(provider)) {
289
+ const baseUrl = getActiveProviderEndpoint(provider);
290
+ return { apiKey: secret.value, ...(baseUrl ? { baseUrl } : {}) };
291
+ }
277
292
  return { apiKey: secret.value };
278
293
  }
279
294
  function authForSlot(providerId, value) {
280
295
  if (providerId === "ollama") {
281
296
  return { baseUrl: value };
282
297
  }
298
+ if (providerUsesEndpoints(providerId)) {
299
+ const baseUrl = getActiveProviderEndpoint(providerId);
300
+ return { apiKey: value, ...(baseUrl ? { baseUrl } : {}) };
301
+ }
283
302
  return { apiKey: value };
284
303
  }
285
304
  function makeKeyEmitter(onStatus, onKeyEvent) {
@@ -298,7 +317,11 @@ function makeKeyEmitter(onStatus, onKeyEvent) {
298
317
  async function tryCompleteOnce(provider, providerId, request, model, auth) {
299
318
  const activeRequest = { ...request, provider: providerId, model };
300
319
  try {
301
- return await provider.complete(activeRequest, auth);
320
+ const result = await provider.complete(activeRequest, auth);
321
+ if (hasImageInput(activeRequest)) {
322
+ learnModelVisionCapability(providerId, model, true);
323
+ }
324
+ return result;
302
325
  }
303
326
  catch (error) {
304
327
  if (activeRequest.tools?.length && isToolsUnsupportedError(error)) {
@@ -317,9 +340,57 @@ async function tryCompleteOnce(provider, providerId, request, model, auth) {
317
340
  markReasoningUnsupported(model);
318
341
  return await provider.complete(activeRequest, auth);
319
342
  }
343
+ if (hasImageInput(activeRequest) && isImageInputUnsupportedError(error)) {
344
+ learnModelVisionCapability(providerId, model, false);
345
+ return await provider.complete(withoutImages(activeRequest), auth);
346
+ }
347
+ const restored = revertVisionSubstitution(providerId, model, activeRequest, error);
348
+ if (restored) {
349
+ return await provider.complete(restored.request, auth);
350
+ }
320
351
  throw error;
321
352
  }
322
353
  }
354
+ function hasImageInput(request) {
355
+ return request.messages.some((message) => message.images?.length);
356
+ }
357
+ function withoutImages(request) {
358
+ return { ...request, messages: stripImagesFromMessages(request.messages) };
359
+ }
360
+ function isModelNotFoundError(error) {
361
+ const status = error && typeof error === "object" && "status" in error
362
+ ? Number(error.status)
363
+ : undefined;
364
+ if (status !== 404 && status !== 400)
365
+ return false;
366
+ const body = error && typeof error === "object" && "body" in error
367
+ ? String(error.body ?? "")
368
+ : "";
369
+ const message = error instanceof Error ? error.message : String(error);
370
+ const hay = `${message}\n${body}`.toLowerCase();
371
+ if (status === 404)
372
+ return true;
373
+ return /model[_ ]?not[_ ]?found|no such model|unknown model|model does not exist|invalid model|unavailable[- ]model/.test(hay);
374
+ }
375
+ function revertVisionSubstitution(providerId, model, request, error) {
376
+ if (!isModelNotFoundError(error))
377
+ return undefined;
378
+ const original = visionSubstitutionOrigin(providerId, model);
379
+ if (!original)
380
+ return undefined;
381
+ markModelUnavailable(providerId, model);
382
+ const keepImages = modelAcceptsImages(providerId, original);
383
+ return {
384
+ original,
385
+ request: {
386
+ ...request,
387
+ model: original,
388
+ messages: keepImages
389
+ ? request.messages
390
+ : stripImagesFromMessages(request.messages),
391
+ },
392
+ };
393
+ }
323
394
  async function tryStreamOnce(provider, providerId, request, model, auth, onToken, onStatus) {
324
395
  const activeRequest = { ...request, provider: providerId, model };
325
396
  // Count what actually reached the caller so no layer above can
@@ -329,12 +400,20 @@ async function tryStreamOnce(provider, providerId, request, model, auth, onToken
329
400
  emittedBytes += token.length;
330
401
  onToken(token);
331
402
  };
403
+ const learnVisionOnSuccess = () => {
404
+ if (hasImageInput(activeRequest)) {
405
+ learnModelVisionCapability(providerId, model, true);
406
+ }
407
+ };
332
408
  try {
333
409
  if (provider.stream) {
334
- return await provider.stream(activeRequest, auth, emit);
410
+ const streamed = await provider.stream(activeRequest, auth, emit);
411
+ learnVisionOnSuccess();
412
+ return streamed;
335
413
  }
336
414
  const result = await provider.complete(activeRequest, auth);
337
415
  emit(result.text);
416
+ learnVisionOnSuccess();
338
417
  return result;
339
418
  }
340
419
  catch (error) {
@@ -380,6 +459,41 @@ async function tryStreamOnce(provider, providerId, request, model, auth, onToken
380
459
  throw markStreamEmittedBytes(retryError, emittedBytes);
381
460
  }
382
461
  }
462
+ if (emittedBytes === 0 &&
463
+ hasImageInput(activeRequest) &&
464
+ isImageInputUnsupportedError(error)) {
465
+ learnModelVisionCapability(providerId, model, false);
466
+ onStatus?.(`ℹ ${providerId}/${model} rejected image input — retrying without the attached image(s)`);
467
+ const textOnlyRequest = withoutImages(activeRequest);
468
+ try {
469
+ if (provider.stream) {
470
+ return await provider.stream(textOnlyRequest, auth, emit);
471
+ }
472
+ const result = await provider.complete(textOnlyRequest, auth);
473
+ emit(result.text);
474
+ return result;
475
+ }
476
+ catch (retryError) {
477
+ throw markStreamEmittedBytes(retryError, emittedBytes);
478
+ }
479
+ }
480
+ if (emittedBytes === 0) {
481
+ const restored = revertVisionSubstitution(providerId, model, activeRequest, error);
482
+ if (restored) {
483
+ onStatus?.(`ℹ ${providerId}/${model} is not available on this account — falling back to ${restored.original}`);
484
+ try {
485
+ if (provider.stream) {
486
+ return await provider.stream(restored.request, auth, emit);
487
+ }
488
+ const result = await provider.complete(restored.request, auth);
489
+ emit(result.text);
490
+ return result;
491
+ }
492
+ catch (retryError) {
493
+ throw markStreamEmittedBytes(retryError, emittedBytes);
494
+ }
495
+ }
496
+ }
383
497
  throw markStreamEmittedBytes(error, emittedBytes);
384
498
  }
385
499
  }
@@ -601,9 +715,15 @@ export async function streamWithProvider(request, onToken, onStatusOrOptions) {
601
715
  }
602
716
  export async function pingProvider(providerId, secretOverride) {
603
717
  const provider = providers[providerId];
718
+ const resolved = await providerAuth(providerId);
604
719
  const auth = providerId === "ollama"
605
- ? { baseUrl: secretOverride ?? (await providerAuth(providerId)).baseUrl }
606
- : { apiKey: secretOverride ?? (await providerAuth(providerId)).apiKey };
720
+ ? { baseUrl: secretOverride ?? resolved.baseUrl }
721
+ : providerUsesEndpoints(providerId)
722
+ ? {
723
+ apiKey: secretOverride ?? resolved.apiKey,
724
+ ...(resolved.baseUrl ? { baseUrl: resolved.baseUrl } : {}),
725
+ }
726
+ : { apiKey: secretOverride ?? resolved.apiKey };
607
727
  await provider.ping(auth);
608
728
  }
609
729
  //# sourceMappingURL=router.js.map