@pentoshi/clai 3.9.7 → 3.11.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.
- package/LICENSE +21 -0
- package/README.md +81 -9
- package/dist/agent/context-manager.d.ts +2 -1
- package/dist/agent/context-manager.js +16 -2
- package/dist/agent/context-manager.js.map +1 -1
- package/dist/agent/loop-guard.d.ts +18 -0
- package/dist/agent/loop-guard.js +50 -2
- package/dist/agent/loop-guard.js.map +1 -1
- package/dist/agent/must-continue.js +4 -8
- package/dist/agent/must-continue.js.map +1 -1
- package/dist/agent/prompt-composer.js +8 -8
- package/dist/agent/prompt-composer.js.map +1 -1
- package/dist/agent/runner.d.ts +1 -0
- package/dist/agent/runner.js +238 -74
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/session-policy.d.ts +1 -2
- package/dist/agent/session-policy.js +3 -2
- package/dist/agent/session-policy.js.map +1 -1
- package/dist/agent/tool-call-parser.js +6 -6
- package/dist/agent/tool-call-parser.js.map +1 -1
- package/dist/app/adapters/current-agent-adapter.js +1 -0
- package/dist/app/adapters/current-agent-adapter.js.map +1 -1
- package/dist/app/controllers/session-controller.js +2 -0
- package/dist/app/controllers/session-controller.js.map +1 -1
- package/dist/app/controllers/session-turn-request.d.ts +1 -0
- package/dist/app/controllers/session-turn-request.js +4 -1
- package/dist/app/controllers/session-turn-request.js.map +1 -1
- package/dist/app/ports/agent-port.d.ts +1 -0
- package/dist/attachments/clipboard-image.js +5 -4
- package/dist/attachments/clipboard-image.js.map +1 -1
- package/dist/attachments/image-content.d.ts +20 -2
- package/dist/attachments/image-content.js +201 -1
- package/dist/attachments/image-content.js.map +1 -1
- package/dist/attachments/image-prepare.d.ts +25 -0
- package/dist/attachments/image-prepare.js +390 -0
- package/dist/attachments/image-prepare.js.map +1 -0
- package/dist/attachments/service.d.ts +4 -2
- package/dist/attachments/service.js +51 -21
- package/dist/attachments/service.js.map +1 -1
- package/dist/commands/providers.d.ts +5 -2
- package/dist/commands/providers.js +121 -7
- package/dist/commands/providers.js.map +1 -1
- package/dist/index.js +12 -4
- package/dist/index.js.map +1 -1
- package/dist/llm/agentrouter.js +2 -2
- package/dist/llm/agentrouter.js.map +1 -1
- package/dist/llm/anthropic.d.ts +5 -4
- package/dist/llm/anthropic.js +28 -25
- package/dist/llm/anthropic.js.map +1 -1
- package/dist/llm/aws-mantle.js +19 -20
- package/dist/llm/aws-mantle.js.map +1 -1
- package/dist/llm/bynara.js +2 -2
- package/dist/llm/bynara.js.map +1 -1
- package/dist/llm/capabilities.d.ts +19 -0
- package/dist/llm/capabilities.js +273 -16
- package/dist/llm/capabilities.js.map +1 -1
- package/dist/llm/gemini.js +16 -8
- package/dist/llm/gemini.js.map +1 -1
- package/dist/llm/groq.js +2 -2
- package/dist/llm/groq.js.map +1 -1
- package/dist/llm/http.d.ts +7 -1
- package/dist/llm/http.js +210 -8
- package/dist/llm/http.js.map +1 -1
- package/dist/llm/kimchi.js +2 -2
- package/dist/llm/kimchi.js.map +1 -1
- package/dist/llm/lightning.d.ts +11 -0
- package/dist/llm/lightning.js +108 -0
- package/dist/llm/lightning.js.map +1 -0
- package/dist/llm/modal.d.ts +16 -0
- package/dist/llm/modal.js +182 -0
- package/dist/llm/modal.js.map +1 -0
- package/dist/llm/nvidia.js +2 -2
- package/dist/llm/nvidia.js.map +1 -1
- package/dist/llm/ollama.js +3 -3
- package/dist/llm/ollama.js.map +1 -1
- package/dist/llm/openai.js +2 -2
- package/dist/llm/openai.js.map +1 -1
- package/dist/llm/openrouter.js +2 -2
- package/dist/llm/openrouter.js.map +1 -1
- package/dist/llm/provider.d.ts +11 -0
- package/dist/llm/provider.js +316 -1
- package/dist/llm/provider.js.map +1 -1
- package/dist/llm/qwen-cloud.js +2 -5
- package/dist/llm/qwen-cloud.js.map +1 -1
- package/dist/llm/router.js +127 -7
- package/dist/llm/router.js.map +1 -1
- package/dist/llm/system-messages.d.ts +5 -0
- package/dist/llm/system-messages.js +14 -0
- package/dist/llm/system-messages.js.map +1 -1
- package/dist/llm/token-usage.d.ts +13 -0
- package/dist/llm/token-usage.js +31 -0
- package/dist/llm/token-usage.js.map +1 -1
- package/dist/llm/tokenrouter.d.ts +15 -0
- package/dist/llm/tokenrouter.js +106 -0
- package/dist/llm/tokenrouter.js.map +1 -0
- package/dist/modes/agent.d.ts +1 -0
- package/dist/modes/agent.js.map +1 -1
- package/dist/modes/ask.js +15 -4
- package/dist/modes/ask.js.map +1 -1
- package/dist/prompts/embedded.js +2 -2
- package/dist/prompts/embedded.js.map +1 -1
- package/dist/prompts/index.d.ts +6 -0
- package/dist/prompts/index.js +39 -21
- package/dist/prompts/index.js.map +1 -1
- package/dist/prompts/system.agent.md +15 -27
- package/dist/prompts/system.ask.md +1 -1
- package/dist/repl/slash-commands.js +53 -0
- package/dist/repl/slash-commands.js.map +1 -1
- package/dist/repl.js +16 -9
- package/dist/repl.js.map +1 -1
- package/dist/store/config.d.ts +55 -0
- package/dist/store/config.js +127 -0
- package/dist/store/config.js.map +1 -1
- package/dist/store/history.d.ts +2 -1
- package/dist/store/history.js +15 -16
- package/dist/store/history.js.map +1 -1
- package/dist/store/keys.js +41 -2
- package/dist/store/keys.js.map +1 -1
- package/dist/tools/definitions.js +31 -6
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/image.d.ts +0 -7
- package/dist/tools/image.js +76 -22
- package/dist/tools/image.js.map +1 -1
- package/dist/tools/jobs.d.ts +11 -1
- package/dist/tools/jobs.js +67 -0
- package/dist/tools/jobs.js.map +1 -1
- package/dist/tools/ocr.d.ts +28 -0
- package/dist/tools/ocr.js +316 -0
- package/dist/tools/ocr.js.map +1 -0
- package/dist/tools/pdf.d.ts +0 -12
- package/dist/tools/pdf.js +436 -150
- package/dist/tools/pdf.js.map +1 -1
- package/dist/tools/registry.js +5 -0
- package/dist/tools/registry.js.map +1 -1
- package/dist/tui/format-keys.js +10 -0
- package/dist/tui/format-keys.js.map +1 -1
- package/dist/tui-v2/actions/action-id.d.ts +1 -1
- package/dist/tui-v2/actions/action-id.js +3 -0
- package/dist/tui-v2/actions/action-id.js.map +1 -1
- package/dist/tui-v2/actions/format-shortcuts.js +4 -1
- package/dist/tui-v2/actions/format-shortcuts.js.map +1 -1
- package/dist/tui-v2/actions/keymap.js +6 -1
- package/dist/tui-v2/actions/keymap.js.map +1 -1
- package/dist/tui-v2/app/App.js +3 -1
- package/dist/tui-v2/app/App.js.map +1 -1
- package/dist/tui-v2/app/commands/key-commands.js +117 -15
- package/dist/tui-v2/app/commands/key-commands.js.map +1 -1
- package/dist/tui-v2/app/commands/picker-commands.js +80 -11
- package/dist/tui-v2/app/commands/picker-commands.js.map +1 -1
- package/dist/tui-v2/bootstrap/console-guard.d.ts +20 -0
- package/dist/tui-v2/bootstrap/console-guard.js +73 -0
- package/dist/tui-v2/bootstrap/console-guard.js.map +1 -0
- package/dist/tui-v2/bootstrap/start-tui-v2.js +14 -0
- package/dist/tui-v2/bootstrap/start-tui-v2.js.map +1 -1
- package/dist/tui-v2/components/composer/composer-input-box.js +5 -1
- package/dist/tui-v2/components/composer/composer-input-box.js.map +1 -1
- package/dist/tui-v2/components/jobs/jobs-panel.js +25 -1
- package/dist/tui-v2/components/jobs/jobs-panel.js.map +1 -1
- package/dist/tui-v2/components/modal/keys-modal.js +15 -8
- package/dist/tui-v2/components/modal/keys-modal.js.map +1 -1
- package/dist/tui-v2/components/modal/secret-modal.js +13 -4
- package/dist/tui-v2/components/modal/secret-modal.js.map +1 -1
- package/dist/tui-v2/components/pager/pager.js +103 -12
- package/dist/tui-v2/components/pager/pager.js.map +1 -1
- package/dist/tui-v2/components/status/status-line.d.ts +8 -2
- package/dist/tui-v2/components/status/status-line.js +12 -8
- package/dist/tui-v2/components/status/status-line.js.map +1 -1
- package/dist/tui-v2/components/transcript/thinking-block.d.ts +14 -4
- package/dist/tui-v2/components/transcript/thinking-block.js +53 -8
- package/dist/tui-v2/components/transcript/thinking-block.js.map +1 -1
- package/dist/tui-v2/components/transcript/transcript-row.d.ts +2 -0
- package/dist/tui-v2/components/transcript/transcript-row.js +2 -2
- package/dist/tui-v2/components/transcript/transcript-row.js.map +1 -1
- package/dist/tui-v2/components/transcript/transcript-view.js +8 -1
- package/dist/tui-v2/components/transcript/transcript-view.js.map +1 -1
- package/dist/tui-v2/composer/composer-action-port.d.ts +15 -3
- package/dist/tui-v2/composer/composer-action-port.js +49 -6
- package/dist/tui-v2/composer/composer-action-port.js.map +1 -1
- package/dist/tui-v2/composer/composer-editor.js +37 -44
- package/dist/tui-v2/composer/composer-editor.js.map +1 -1
- package/dist/tui-v2/composer/draft-actions.d.ts +31 -0
- package/dist/tui-v2/composer/draft-actions.js +45 -0
- package/dist/tui-v2/composer/draft-actions.js.map +1 -0
- package/dist/tui-v2/composer/textarea-keybindings.d.ts +1 -1
- package/dist/tui-v2/composer/textarea-keybindings.js +33 -0
- package/dist/tui-v2/composer/textarea-keybindings.js.map +1 -1
- package/dist/tui-v2/composer/use-draft-actions.d.ts +32 -0
- package/dist/tui-v2/composer/use-draft-actions.js +59 -0
- package/dist/tui-v2/composer/use-draft-actions.js.map +1 -0
- package/dist/tui-v2/controllers/overlay-controller.d.ts +13 -0
- package/dist/tui-v2/controllers/overlay-controller.js +5 -2
- package/dist/tui-v2/controllers/overlay-controller.js.map +1 -1
- package/dist/tui-v2/rendering/artifact-pager-source.d.ts +10 -0
- package/dist/tui-v2/rendering/artifact-pager-source.js +6 -0
- package/dist/tui-v2/rendering/artifact-pager-source.js.map +1 -1
- package/dist/tui-v2/rendering/job-tail-source.d.ts +21 -0
- package/dist/tui-v2/rendering/job-tail-source.js +87 -0
- package/dist/tui-v2/rendering/job-tail-source.js.map +1 -0
- package/dist/tui-v2/state/use-has-draft.d.ts +6 -0
- package/dist/tui-v2/state/use-has-draft.js +13 -0
- package/dist/tui-v2/state/use-has-draft.js.map +1 -0
- package/dist/types.d.ts +7 -1
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -1
- package/dist/ui/mentions.d.ts +7 -2
- package/dist/ui/mentions.js +52 -117
- package/dist/ui/mentions.js.map +1 -1
- package/dist/version.generated.d.ts +2 -2
- package/dist/version.generated.js +2 -2
- package/dist/version.generated.js.map +1 -1
- package/package.json +1 -1
package/dist/llm/provider.js
CHANGED
|
@@ -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
|
package/dist/llm/provider.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/llm/provider.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AA0B1C,MAAM,CAAC,MAAM,eAAe,GAA+B;IACzD,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,QAAQ;IAChB,UAAU,EAAE,YAAY;IACxB,MAAM,EAAE,QAAQ;IAChB,SAAS,EAAE,WAAW;IACtB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,QAAQ;IAChB,GAAG,EAAE,QAAQ;IACb,IAAI,EAAE,QAAQ;IACd,WAAW,EAAE,aAAa;IAC1B,cAAc,EAAE,aAAa;IAC7B,MAAM,EAAE,aAAa;IACrB,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,QAAQ;IAChB,YAAY,EAAE,YAAY;IAC1B,MAAM,EAAE,QAAQ;IAChB,KAAK,EAAE,QAAQ;IACf,MAAM,EAAE,QAAQ;IAChB,eAAe,EAAE,QAAQ;IACzB,UAAU,EAAE,QAAQ;IACpB,IAAI,EAAE,QAAQ;IACd,IAAI,EAAE,YAAY;IAClB,YAAY,EAAE,YAAY;IAC1B,SAAS,EAAE,YAAY;IACvB,SAAS,EAAE,YAAY;
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/llm/provider.ts"],"names":[],"mappings":"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"}
|
package/dist/llm/qwen-cloud.js
CHANGED
|
@@ -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
|
|
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":"AACA,OAAO,EACL,aAAa,GAGd,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,sBAAsB,EACtB,kBAAkB,EAClB,QAAQ,
|
|
1
|
+
{"version":3,"file":"qwen-cloud.js","sourceRoot":"","sources":["../../src/llm/qwen-cloud.ts"],"names":[],"mappings":"AACA,OAAO,EACL,aAAa,GAGd,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,sBAAsB,EACtB,kBAAkB,EAClB,QAAQ,EACR,wBAAwB,GACzB,MAAM,WAAW,CAAC;AAEnB,yEAAyE;AACzE,2FAA2F;AAC3F,MAAM,OAAO,GAAG,wDAAwD,CAAC;AAEzE,IAAI,YAAY,GAAoB,IAAI,CAAC;AACzC,IAAI,aAAa,GAAG,CAAC,CAAC;AACtB,MAAM,YAAY,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEpC,MAAM,CAAC,MAAM,iBAAiB,GAAgB;IAC5C,EAAE,EAAE,YAAY;IAChB,WAAW,EAAE,YAAY;IACzB,YAAY,EAAE,aAAa,CAAC,YAAY,CAAC;IACzC,MAAM,EAAE,mBAAmB;IAC3B,uEAAuE;IACvE,iDAAiD;IACjD,WAAW,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,yBAAyB,CAAC,IAAI,CAAC,GAAG,CAAC;IACjE,KAAK,CAAC,UAAU,CAAC,IAAkB;QACjC,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;QACpE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,YAAY,IAAI,GAAG,GAAG,aAAa,GAAG,YAAY,EAAE,CAAC;YACvD,OAAO,YAAY,CAAC;QACtB,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,SAAS,EAAE;YAChD,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,IAAI,CAAC,MAAM,EAAE,EAAE;SACpD,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAoC,QAAQ,CAAC,CAAC;QACzE,MAAM,MAAM,GAAG,wBAAwB,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;QAC5D,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,YAAY,GAAG,MAAM,CAAC;YACtB,aAAa,GAAG,GAAG,CAAC;QACtB,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,IAAkB;QAC3B,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;QACpE,MAAM,oBAAoB,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACnD,CAAC;IACD,KAAK,CAAC,QAAQ,CACZ,OAA0B,EAC1B,IAAkB;QAElB,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;QACpE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,aAAa,CAAC,YAAY,CAAC,CAAC;QAC3D,MAAM,OAAO,GAAG,MAAM,wBAAwB,CAAC;YAC7C,QAAQ,EAAE,YAAY;YACtB,UAAU,EAAE,YAAY;YACxB,OAAO;YACP,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,KAAK;YACL,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,SAAS,EAAE,OAAO,CAAC,QAAQ;YAC3B,cAAc,EAAE,QAAQ;YACxB,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,iBAAiB,EAAE,OAAO,CAAC,iBAAiB;SAC7C,CAAC,CAAC;QACH,OAAO,kBAAkB,CAAC,YAAY,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IAC1D,CAAC;IACD,KAAK,CAAC,MAAM,CACV,OAA0B,EAC1B,IAAkB,EAClB,OAAgC;QAEhC,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;QACpE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,aAAa,CAAC,YAAY,CAAC,CAAC;QAC3D,MAAM,OAAO,GAAG,MAAM,sBAAsB,CAAC;YAC3C,QAAQ,EAAE,YAAY;YACtB,UAAU,EAAE,YAAY;YACxB,OAAO;YACP,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,KAAK;YACL,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,OAAO;YACP,eAAe,EAAE,OAAO,CAAC,eAAe;YACxC,SAAS,EAAE,OAAO,CAAC,QAAQ;YAC3B,cAAc,EAAE,QAAQ;YACxB,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,iBAAiB,EAAE,OAAO,CAAC,iBAAiB;SAC7C,CAAC,CAAC;QACH,OAAO,kBAAkB,CAAC,YAAY,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IAC1D,CAAC;CACF,CAAC"}
|
package/dist/llm/router.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 ??
|
|
606
|
-
:
|
|
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
|