docs(ai): polish BYOK compatibility guidance
- Publish the compatibility reference in the VitePress site and link localized AI guides to the canonical English page - Correct current Anthropic and credential-storage behavior - Make CORS, streaming, pricing, context, and reasoning measurements reproducible and security-safe
This commit is contained in:
parent
1ebaac7be2
commit
0036d762e2
|
|
@ -1,276 +0,0 @@
|
|||
# BYOK provider & model compatibility
|
||||
|
||||
OpenPencil has no backend. When you bring your own API key, the browser talks to the provider
|
||||
directly — so a provider only works in the **web build** if it sets CORS headers correctly, and a
|
||||
model is only useful for the AI chat panel if its **streaming tool calls** are well-formed.
|
||||
|
||||
Both of those vary a lot between providers, and neither is documented by the providers themselves.
|
||||
This page records what has actually been measured.
|
||||
|
||||
**This is a living list — please add to it.** See [Contributing results](#contributing-results).
|
||||
|
||||
Results are dated because providers change behaviour without announcement. Treat anything older
|
||||
than a few months as unverified.
|
||||
|
||||
---
|
||||
|
||||
## Provider CORS support
|
||||
|
||||
Whether the **web build** can reach the provider at all. The desktop build bypasses CORS entirely
|
||||
(it routes through `tauriFetch`), so a ❌ here still works on desktop.
|
||||
|
||||
| Provider | Base URL | Browser | Notes | Tested |
|
||||
| -------------------- | ------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
|
||||
| OpenRouter | `https://openrouter.ai/api/v1` | ✅ | `ACAO: *` | 2026-07-30 |
|
||||
| OpenAI | `https://api.openai.com/v1` | ✅ | `ACAO: *` | 2026-07-30 |
|
||||
| Groq | `https://api.groq.com/openai/v1` | ✅ | `ACAO: *` | 2026-07-30 |
|
||||
| Google | `https://generativelanguage.googleapis.com` | ✅ | echoes origin | 2026-07-30 |
|
||||
| DeepSeek | `https://api.deepseek.com` | ✅ | echoes origin | 2026-07-30 |
|
||||
| Z.ai | `https://api.z.ai/api/anthropic` | ✅ | echoes origin | 2026-07-30 |
|
||||
| MiniMax | `https://api.minimax.io/v1` | ✅ | echoes origin | 2026-07-30 |
|
||||
| Nebius AI Studio | `https://api.studio.nebius.com/v1` | ✅ | `ACAO: *` | 2026-07-30 |
|
||||
| Nebius Token Factory | `https://api.tokenfactory.<region>.nebius.com/v1` | ✅ | `ACAO: *`; catalog differs per region | 2026-07-30 |
|
||||
| Anthropic | `https://api.anthropic.com` | ⚠️ | Needs `anthropic-dangerous-direct-browser-access: true`, which we don't yet send — see [#436](https://github.com/open-pencil/open-pencil/issues/436) | 2026-07-30 |
|
||||
| Scaleway | `https://api.scaleway.ai/<project-id>/v1` | ⚠️ | `ACAO: *` on success, **absent on errors** — a bad key reads as a network failure | 2026-07-31 |
|
||||
| TensorX | `https://api.tensorx.ai/v1` | ⚠️ | `ACAO: *` on success, **absent on errors** (401/403/500). Reported to their support | 2026-07-31 |
|
||||
|
||||
### The common failure
|
||||
|
||||
A provider that answers the `OPTIONS` preflight correctly but omits `Access-Control-Allow-Origin`
|
||||
on the **actual response** will fail in the browser with a generic `Failed to fetch`. OpenPencil
|
||||
surfaces that as _"Could not reach this endpoint from the browser"_ — which is indistinguishable
|
||||
from a wrong API key. **Always verify with curl before assuming the app is at fault.**
|
||||
|
||||
### Test the success path, not just an error
|
||||
|
||||
Several providers (Scaleway, TensorX) send CORS headers on a `200` but **not** on `401`/`403`/`500`
|
||||
— errors are rejected at a gateway that never adds the header. So an unauthenticated probe tells
|
||||
you nothing: it looks identical to a provider that is genuinely broken.
|
||||
|
||||
Always test with a **valid key and a successful response**. The inference only runs one way — if
|
||||
error responses carry `Access-Control-Allow-Origin`, the success path will too, but the reverse
|
||||
does not hold.
|
||||
|
||||
Providers in this state are marked ⚠️ rather than ❌: normal use works, but every failure the user
|
||||
hits — expired key, exhausted quota, bad model name — reaches them as an opaque network error
|
||||
instead of the real reason.
|
||||
|
||||
---
|
||||
|
||||
## Model tool-calling quality
|
||||
|
||||
The AI chat panel is an agent loop, so a model is only usable if it reliably emits **streaming**
|
||||
tool calls that the AI SDK can parse. Two independent things go wrong:
|
||||
|
||||
- **`id` correctness** — if a streamed `tool_calls` delta introduces a new index without an `id`,
|
||||
`@ai-sdk/openai` throws `Expected 'id' to be a string.` and the stream dies.
|
||||
- **Argument validity** — arguments are streamed in fragments and concatenated. If the fragments
|
||||
are misrouted, the result isn't valid JSON and the tool silently never fires.
|
||||
|
||||
Cost matters as much as correctness here. The chat panel is an agent loop that runs up to
|
||||
`MAX_AGENT_STEPS` (50) and resends the tool schemas on every step, so it is input-token heavy and
|
||||
a high per-token price compounds fast.
|
||||
|
||||
Capability columns are **measured**, not taken from a spec sheet — see
|
||||
[Auto-lookup via models.dev](#auto-lookup-via-modelsdev) for why that distinction matters.
|
||||
`Tools` counts prompts that produced at least one tool call, over `id`-correctness and
|
||||
argument-validity totals. `Vision` is verified by sending a solid-colour image and asking the
|
||||
model to name it.
|
||||
|
||||
**Record the output-token budget with every result** — it changes the outcome. Scaleway rows below
|
||||
were measured at `max_tokens: 16384`; Nebius and TensorX rows at 2000, so their "emits calls"
|
||||
figures are not directly comparable and any failure there may be starvation rather than incapacity.
|
||||
|
||||
| Model | Provider | /1M in–out | Ctx | Tools @16k | Vision | Verdict | Tested |
|
||||
| ------------------------------------- | -------- | -------------- | ---- | ------------------------ | ------ | ----------------------------------------------------------------------------------------------------- | ---------- |
|
||||
| `mistral-small-3.2-24b-instruct-2506` | Scaleway | €0.15 – €0.35 | 128k | ✅ 3/3, 0/238, 6/6 | ✅ | **Recommended** — cheapest with vision, fully clean | 2026-07-31 |
|
||||
| `gemma-4-26b-a4b-it` | Scaleway | €0.25 – €0.50 | 256k | ✅ 3/3, 0/77, 8/8 | ✅ | Same but 2× the context — pick this for large documents | 2026-07-31 |
|
||||
| `gpt-oss-120b` | Scaleway | €0.15 – €0.60 | 128k | ✅ 3/3, 0/118, 3/3 | ❌ | Cheap and clean, but text-only | 2026-07-31 |
|
||||
| `qwen3.6-35b-a3b` | Scaleway | €0.25 – €1.50 | 128k | ✅ 3/3, 0/106, 9/9 | ✅ | Clean; heavy reasoner, needs the larger budget | 2026-07-31 |
|
||||
| `qwen3.5-397b-a17b` | Scaleway | €0.60 – €3.60 | 256k | ✅ 3/3, 0/92, 10/10 | ✅ | Most calls per turn; strongest but priciest here | 2026-07-31 |
|
||||
| `mistral-medium-3.5-128b` | Scaleway | €1.50 – €7.50 | 256k | not tested | ✅ | Vision confirmed; tool calling unverified | 2026-07-31 |
|
||||
| `glm-5.2` | Scaleway | €1.80 – €5.50 | 256k | not tested | ❌ | API rejects images: "not a multimodal model" | 2026-07-31 |
|
||||
| `moonshotai/Kimi-K2.7-Code` | Nebius | $0.95 – $4.00 | 262k | ✅ 3/3, 0/114, 4/4 @2k | ❌ | Clean, but pricier than the Scaleway set | 2026-07-30 |
|
||||
| `moonshotai/Kimi-K3` | Nebius | $3.00 – $15.00 | 1M | ✅ 3/3, 0 bad, 8/8 @2k | ❌ | Flawless, but ~20× the cost of `gpt-oss-120b` | 2026-07-30 |
|
||||
| `moonshotai/kimi-k3` | TensorX | — | — | ✅ 2/3, 0 bad, 7/7 @2k | — | As above | 2026-07-30 |
|
||||
| `openai/gpt-oss-120b` | Nebius | $0.15 – $0.60 | 128k | ❌ misroutes final chunk | ❌ | Broken on Nebius only — same model is fine on Scaleway | 2026-07-30 |
|
||||
| `moonshotai/Kimi-K2.6` | Nebius | — | — | ⚠️ 1/3 @2k | — | Finished on `stop`, not `length` — likely a real failure, but re-test at 16k before trusting this row | 2026-07-30 |
|
||||
|
||||
### Auto-lookup via models.dev
|
||||
|
||||
[models.dev](https://models.dev) publishes a machine-readable catalog at
|
||||
<https://models.dev/api.json> (~3 MB, 176 providers, including `scaleway`, `nebius`, `groq` and
|
||||
`openrouter`). Each model carries `tool_call`, `attachment` (vision), `reasoning`,
|
||||
`cost.input`/`cost.output`, `limit.context` and `modalities`.
|
||||
|
||||
```bash
|
||||
curl -s https://models.dev/api.json \
|
||||
| jq '.scaleway.models["gemma-4-26b-a4b-it"] | {tool_call, attachment, reasoning, cost, limit}'
|
||||
```
|
||||
|
||||
**Use it to prefill a row, not to fill in the measured columns.** Checked against the models above,
|
||||
it was wrong on two of seven:
|
||||
|
||||
| Model | models.dev | Measured |
|
||||
| ---------------------------------- | ------------ | -------------------------------------------------- |
|
||||
| `scaleway/gpt-oss-120b` | vision true | Refuses: "I don't have the ability to view images" |
|
||||
| `scaleway/mistral-small-3.2-24b-…` | vision false | Correctly names the colour in an image |
|
||||
|
||||
It also lists `nebius/openai/gpt-oss-120b` as vision `false` while marking the Scaleway copy
|
||||
`true` — the same model, contradicting itself — and reports `tool_call: true` for every model
|
||||
here, including `qwen3.6-35b-a3b` (silent on 2/3 prompts) and Nebius's `gpt-oss-120b` (broken by
|
||||
the deployment bug below).
|
||||
|
||||
That's the general shape of it: models.dev describes a model's **nominal** capabilities, while
|
||||
what breaks in practice is a property of the **deployment**. Price and context window are safe to
|
||||
copy from it; tool-calling and vision are not.
|
||||
|
||||
Prices are provider list prices gathered from public pricing aggregators, not measured — confirm
|
||||
against your provider's own page before relying on them.
|
||||
|
||||
The same model can behave differently on different providers — `gpt-oss-120b` is broken on Nebius
|
||||
and clean on Scaleway. **Record the provider alongside the model; a model row without one is not
|
||||
reproducible.**
|
||||
|
||||
Note that "emits calls" counts prompts that produced at least one tool call, not how many. Models
|
||||
differ in style: `qwen3.5-397b-a17b` issued 3–4 parallel calls per turn where `gpt-oss-120b` issued
|
||||
one. In a 50-step agent loop, one-call-then-observe is legitimate rather than weaker, so don't read
|
||||
the count as a quality ranking.
|
||||
|
||||
### Known issue: vLLM misroutes the final argument chunk
|
||||
|
||||
On some vLLM deployments the closing `}` of a tool call's arguments is emitted under `index + 1`
|
||||
with no `id`:
|
||||
|
||||
```
|
||||
1 [{"index":0,"id":"chatcmpl-tool-…","function":{"arguments":"","name":"create_node"},"type":"function"}]
|
||||
… deltas 2–11 stream the arguments, all index 0
|
||||
12 [{"index":1,"function":{"arguments":"}"}}] ← wrong index, no id
|
||||
```
|
||||
|
||||
This both crashes the stream _and_ leaves call 0's arguments unterminated. A client-side workaround
|
||||
must reattach argument-only deltas (no `id`, no `function.name`) to the most recently opened call,
|
||||
ignoring the claimed index — synthesizing a missing `id` alone converts a visible crash into a
|
||||
silent no-op.
|
||||
|
||||
Models that emit one complete tool call per chunk (Kimi-K3 on every provider tested) cannot hit
|
||||
this bug at all.
|
||||
|
||||
**This is a deployment bug, not a model bug.** `gpt-oss-120b` misroutes on Nebius but streams
|
||||
cleanly on Scaleway — 0 violations across 135 deltas. Before writing a client-side workaround,
|
||||
check whether another provider serving the same model is unaffected.
|
||||
|
||||
### Known issue: reasoning models starve on a small output budget
|
||||
|
||||
A reasoning model can spend its **entire** output budget thinking and never emit a tool call. The
|
||||
result is `finish_reason: length` with zero tool calls — no error, no partial output. From the
|
||||
outside it is indistinguishable from a model that simply can't call tools.
|
||||
|
||||
Measured on `qwen3.6-35b-a3b`, same prompts, only `max_tokens` changed:
|
||||
|
||||
| `max_tokens` | Result |
|
||||
| ------------ | ----------------------------------------------- |
|
||||
| 2000 | 1/3 prompts — the rest hit `length` mid-thought |
|
||||
| 16384 | 3/3 prompts, 0 bad ids, 9/9 valid args |
|
||||
|
||||
One prompt needed 10,417 characters of reasoning before its first tool call. **This page originally
|
||||
scored two models as broken purely because the probe used 2000 tokens.** If a model produces no tool
|
||||
calls, check `finish_reason` before concluding anything — `length` means starved, `stop` means it
|
||||
genuinely declined.
|
||||
|
||||
`reasoning_effort` is the other lever, though supported values are provider-specific:
|
||||
|
||||
| Setting | Reasoning | Tool calls |
|
||||
| ---------------------------------------------------- | --------- | ---------- |
|
||||
| default | 6,583 ch | ❌ 0 |
|
||||
| `"reasoning_effort": "none"` | 0 ch | ✅ 3 |
|
||||
| `"reasoning_effort": "low"` | 5,088 ch | ✅ 3 |
|
||||
| `"chat_template_kwargs": {"enable_thinking": false}` | 5,133 ch | ✅ 3 |
|
||||
|
||||
Only `"none"` actually eliminated reasoning on Scaleway; `"low"` had no measurable effect.
|
||||
OpenPencil cannot send this today — see
|
||||
[#454](https://github.com/open-pencil/open-pencil/issues/454).
|
||||
|
||||
### Known issue: reasoning models and the connection test
|
||||
|
||||
Some models (e.g. Kimi-K2.6) spend output tokens on `reasoning` before `content`. The connection
|
||||
test uses `maxOutputTokens: 1`, so `content` comes back `null`. The test still passes because it
|
||||
only checks that the request didn't throw — but don't tighten it to assert on returned text, or
|
||||
reasoning models will fail it spuriously.
|
||||
|
||||
---
|
||||
|
||||
## Contributing results
|
||||
|
||||
Add a row, keep it dated, and say how you tested. Negative results are as useful as positive ones.
|
||||
|
||||
### 1. Test CORS
|
||||
|
||||
Run this from a shell — curl isn't subject to CORS, so you're inspecting headers, not behaviour.
|
||||
What matters is `access-control-allow-origin` on the **POST**, not the `OPTIONS`.
|
||||
|
||||
```bash
|
||||
BASE="https://api.example.com/v1"
|
||||
KEY="sk-…"
|
||||
|
||||
# The real response — this is the one that counts
|
||||
curl -sD - -o /dev/null -X POST "$BASE/chat/completions" \
|
||||
-H "Origin: https://openpencil.dev" \
|
||||
-H "Authorization: Bearer $KEY" \
|
||||
-H "content-type: application/json" \
|
||||
-d '{"model":"MODEL","max_tokens":5,"messages":[{"role":"user","content":"hi"}]}' \
|
||||
| grep -i "^HTTP\|access-control"
|
||||
```
|
||||
|
||||
✅ if `access-control-allow-origin` is present on the POST response (`*` or your origin).
|
||||
❌ if it only appears on the `OPTIONS` preflight.
|
||||
|
||||
### 2. Test tool-call streaming
|
||||
|
||||
Ask the model to make several tool calls, then check every call has an `id` and that the
|
||||
concatenated arguments parse as JSON:
|
||||
|
||||
```bash
|
||||
curl -sN -X POST "$BASE/chat/completions" \
|
||||
-H "Authorization: Bearer $KEY" -H "content-type: application/json" \
|
||||
-d '{"model":"MODEL","stream":true,"max_tokens":2000,
|
||||
"messages":[{"role":"user","content":"Draw a frog: green ellipse body, two white eye circles, a mouth line. Use create_node for each."}],
|
||||
"tools":[{"type":"function","function":{"name":"create_node","description":"Create a node on the canvas",
|
||||
"parameters":{"type":"object","properties":{"type":{"type":"string","enum":["ELLIPSE","RECTANGLE","LINE","TEXT"]},
|
||||
"x":{"type":"number"},"y":{"type":"number"},"width":{"type":"number"},"height":{"type":"number"},
|
||||
"fill":{"type":"string"}},"required":["type","x","y"]}}}]}' \
|
||||
| grep -o '"tool_calls":\[[^]]*\]'
|
||||
```
|
||||
|
||||
Check each first-delta-per-index has an `id`, and that concatenating every `arguments` fragment
|
||||
per index yields valid JSON. Use more than one prompt — failures are often intermittent and depend
|
||||
on where the JSON happens to land on a chunk boundary.
|
||||
|
||||
### 3. Add your row
|
||||
|
||||
Include the date, the exact model ID and base URL, and a one-line verdict. If something is broken,
|
||||
say what the failure looks like from the user's side — that's what makes the row actionable.
|
||||
|
||||
Include the provider's list price too. A model that streams flawlessly but costs 25× the
|
||||
alternative isn't the right default for an agent loop, and a table without prices makes that easy
|
||||
to miss.
|
||||
|
||||
**And record the `max_tokens` you used.** A reasoning model starved of output budget produces no
|
||||
tool calls and looks broken. Two models on this page were wrongly marked "avoid" for exactly that
|
||||
reason. Use at least 16384, and check `finish_reason` before recording a failure.
|
||||
|
||||
### A note on regional catalogs
|
||||
|
||||
Some providers serve different models per region behind different hostnames. Nebius Token Factory,
|
||||
for example, carries Kimi-K3 on `eu-west2` but not on `us-central1`. Always record the exact base
|
||||
URL you tested — "provider X doesn't have model Y" is only true for the endpoint you checked.
|
||||
|
||||
---
|
||||
|
||||
## Security note
|
||||
|
||||
BYOK in a browser means the user's API key lives in browser storage. OpenPencil encrypts it at rest
|
||||
(AES-GCM in IndexedDB, with a non-extractable `CryptoKey`) and defaults to memory-only unless the
|
||||
user opts into "remember"; the desktop build uses the OS keychain instead. But any script running
|
||||
on the origin can still use the key in place, so prefer scoped keys with spend caps where the
|
||||
provider offers them.
|
||||
|
|
@ -68,6 +68,14 @@ export const programmableSidebar = (
|
|||
{ text: labels.jsxRenderer, link: `${prefix}/programmable/jsx-renderer` },
|
||||
{ text: labels.mcpServer, link: `${prefix}/programmable/mcp-server` },
|
||||
{ text: labels.aiChat, link: `${prefix}/programmable/ai-chat` },
|
||||
...(!prefix
|
||||
? [
|
||||
{
|
||||
text: 'BYOK Compatibility',
|
||||
link: '/programmable/byok-provider-compatibility',
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{ text: labels.collaboration, link: `${prefix}/programmable/collaboration` },
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ Drücken Sie <kbd>⌘</kbd><kbd>J</kbd> (<kbd>Ctrl</kbd> + <kbd>J</kbd>), um den
|
|||
| **OpenAI-kompatibel** | Jeder Endpunkt mit OpenAI-API-Format | Eigene Basis-URL + Schlüssel. Unterstützt Completions- und Responses-API-Umschaltung. |
|
||||
| **Anthropic-kompatibel** | Jeder Endpunkt mit Anthropic-API-Format | Eigene Basis-URL + Schlüssel |
|
||||
|
||||
Kein Backend, kein Abonnement — Ihr Schlüssel kommuniziert direkt mit dem Anbieter.
|
||||
Kein Backend, kein Abonnement — Ihr Schlüssel kommuniziert direkt mit dem Anbieter. Browser-Anfragen unterliegen den CORS-Richtlinien des Anbieters, und Modelldeployments unterscheiden sich bei der Zuverlässigkeit gestreamter Tool-Aufrufe. Messwerte und reproduzierbare Tests finden Sie in der englischen Referenz zur [BYOK-Anbieter- und Modellkompatibilität](/programmable/byok-provider-compatibility).
|
||||
|
||||
## Funktionen
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ Presiona <kbd>⌘</kbd><kbd>J</kbd> (<kbd>Ctrl</kbd> + <kbd>J</kbd>) para abrir
|
|||
| **Compatible con OpenAI** | Cualquier endpoint con formato de API OpenAI | URL base personalizada + clave. Admite alternancia entre API de Completions y Responses. |
|
||||
| **Compatible con Anthropic** | Cualquier endpoint con formato de API Anthropic | URL base personalizada + clave |
|
||||
|
||||
Sin backend, sin suscripción — tu clave se comunica directamente con el proveedor.
|
||||
Sin backend ni suscripción: tu clave se comunica directamente con el proveedor. Las solicitudes del navegador están sujetas a la política CORS del proveedor, y la fiabilidad de las llamadas a herramientas en streaming varía entre despliegues. Consulta la referencia en inglés sobre [compatibilidad de proveedores y modelos BYOK](/programmable/byok-provider-compatibility) para ver resultados medidos y pruebas reproducibles.
|
||||
|
||||
## Funciones
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ Appuyez sur <kbd>⌘</kbd><kbd>J</kbd> (<kbd>Ctrl</kbd> + <kbd>J</kbd>) pour ouv
|
|||
| **Compatible OpenAI** | Tout endpoint au format API OpenAI | URL de base personnalisée + clé. Supporte le basculement entre API Completions et Responses. |
|
||||
| **Compatible Anthropic** | Tout endpoint au format API Anthropic | URL de base personnalisée + clé |
|
||||
|
||||
Pas de backend, pas d'abonnement — votre clé communique directement avec le fournisseur.
|
||||
Pas de backend ni d’abonnement : votre clé communique directement avec le fournisseur. Les requêtes du navigateur sont soumises à la politique CORS du fournisseur, et la fiabilité des appels d’outils en streaming varie selon les déploiements. Consultez la référence en anglais sur la [compatibilité des fournisseurs et modèles BYOK](/programmable/byok-provider-compatibility) pour les résultats mesurés et les tests reproductibles.
|
||||
|
||||
## Fonctionnalités
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ Premi <kbd>⌘</kbd><kbd>J</kbd> (<kbd>Ctrl</kbd> + <kbd>J</kbd>) per aprire l'a
|
|||
| **Compatibile OpenAI** | Qualsiasi endpoint con formato API OpenAI | URL base personalizzato + chiave. Supporta toggle tra API Completions e Responses. |
|
||||
| **Compatibile Anthropic** | Qualsiasi endpoint con formato API Anthropic | URL base personalizzato + chiave |
|
||||
|
||||
Nessun backend, nessun abbonamento — la tua chiave comunica direttamente con il provider.
|
||||
Nessun backend o abbonamento: la tua chiave comunica direttamente con il provider. Le richieste del browser dipendono dalla policy CORS del provider e l’affidabilità delle chiamate agli strumenti in streaming varia tra i deployment. Consulta il riferimento in inglese sulla [compatibilità di provider e modelli BYOK](/programmable/byok-provider-compatibility) per risultati misurati e test riproducibili.
|
||||
|
||||
## Funzionalità
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ Naciśnij <kbd>⌘</kbd><kbd>J</kbd> (<kbd>Ctrl</kbd> + <kbd>J</kbd>), aby otwor
|
|||
| **Kompatybilny z OpenAI** | Dowolny endpoint w formacie API OpenAI | Własny bazowy URL + klucz. Obsługuje przełączanie między API Completions i Responses. |
|
||||
| **Kompatybilny z Anthropic** | Dowolny endpoint w formacie API Anthropic | Własny bazowy URL + klucz |
|
||||
|
||||
Bez backendu, bez subskrypcji — Twój klucz komunikuje się bezpośrednio z dostawcą.
|
||||
Bez backendu i subskrypcji — klucz komunikuje się bezpośrednio z dostawcą. Żądania z przeglądarki podlegają zasadom CORS dostawcy, a niezawodność strumieniowych wywołań narzędzi zależy od wdrożenia modelu. Zobacz anglojęzyczną stronę [zgodności dostawców i modeli BYOK](/programmable/byok-provider-compatibility), aby poznać zmierzone wyniki i odtwarzalne testy.
|
||||
|
||||
## Funkcje
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ You can configure multiple reusable models and separately assign models for desi
|
|||
| **OpenAI-compatible** | Any endpoint with OpenAI API format | Custom base URL + key. Supports Completions and Responses API toggle. |
|
||||
| **Anthropic-compatible** | Any endpoint with Anthropic API format | Custom base URL + key |
|
||||
|
||||
No backend, no subscription — your key talks directly to the provider.
|
||||
No backend, no subscription — your key talks directly to the provider. Browser requests are subject to each provider's CORS policy, and model deployments vary in how reliably they stream tool calls. See [BYOK provider and model compatibility](./byok-provider-compatibility) for measured results and reproduction steps.
|
||||
|
||||
## What It Can Do
|
||||
|
||||
|
|
|
|||
350
packages/docs/programmable/byok-provider-compatibility.md
Normal file
350
packages/docs/programmable/byok-provider-compatibility.md
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
# BYOK provider and model compatibility
|
||||
|
||||
OpenPencil has no backend. When you bring your own API key, the browser talks to the provider
|
||||
directly — so a provider only works in the **web build** if it sets CORS headers correctly, and a
|
||||
model is only useful for the AI chat panel if its **streaming tool calls** are well-formed.
|
||||
|
||||
Both of those vary a lot between providers, and neither is documented by the providers themselves.
|
||||
This page records what has actually been measured.
|
||||
|
||||
**This is a living list — please add to it.** See [Contributing results](#contributing-results).
|
||||
|
||||
Results are dated because providers change behaviour without announcement. Treat anything older
|
||||
than a few months as unverified.
|
||||
|
||||
---
|
||||
|
||||
## Provider CORS support
|
||||
|
||||
Whether the **web build** can reach the provider at all. The desktop build bypasses CORS entirely
|
||||
(it routes through `tauriFetch`), so a ❌ here still works on desktop.
|
||||
|
||||
| Provider | Base URL | Browser | Notes | Tested |
|
||||
| -------------------- | ------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------- | ---------- |
|
||||
| OpenRouter | `https://openrouter.ai/api/v1` | ✅ | `ACAO: *` | 2026-07-30 |
|
||||
| OpenAI | `https://api.openai.com/v1` | ✅ | `ACAO: *` | 2026-07-30 |
|
||||
| Groq | `https://api.groq.com/openai/v1` | ✅ | `ACAO: *` | 2026-07-30 |
|
||||
| Google | `https://generativelanguage.googleapis.com` | ✅ | echoes origin | 2026-07-30 |
|
||||
| DeepSeek | `https://api.deepseek.com` | ✅ | echoes origin | 2026-07-30 |
|
||||
| Z.ai | `https://api.z.ai/api/anthropic` | ✅ | echoes origin | 2026-07-30 |
|
||||
| MiniMax | `https://api.minimax.io/v1` | ✅ | echoes origin | 2026-07-30 |
|
||||
| Nebius AI Studio | `https://api.studio.nebius.com/v1` | ✅ | `ACAO: *` | 2026-07-30 |
|
||||
| Nebius Token Factory | `https://api.tokenfactory.<region>.nebius.com/v1` | ✅ | `ACAO: *`; catalog differs per region | 2026-07-30 |
|
||||
| Anthropic | `https://api.anthropic.com` | ✅ | Requires `anthropic-dangerous-direct-browser-access: true`; OpenPencil sends it in the web app | 2026-07-30 |
|
||||
| Scaleway | `https://api.scaleway.ai/<project-id>/v1` | ⚠️ | `ACAO: *` on success, **absent on errors** — a bad key reads as a network failure | 2026-07-31 |
|
||||
| TensorX | `https://api.tensorx.ai/v1` | ⚠️ | `ACAO: *` on success, **absent on errors** (401/403/500). Reported to their support | 2026-07-31 |
|
||||
|
||||
### The common failure
|
||||
|
||||
A provider that answers the `OPTIONS` preflight correctly but omits `Access-Control-Allow-Origin`
|
||||
on the **actual response** will fail in the browser with a generic `Failed to fetch`. OpenPencil
|
||||
surfaces that as _"Could not reach this endpoint from the browser"_ — which is indistinguishable
|
||||
from a wrong API key. **Always verify with curl before assuming the app is at fault.**
|
||||
|
||||
### Test the success path, not just an error
|
||||
|
||||
Several providers (Scaleway, TensorX) send CORS headers on a `200` but **not** on `401`/`403`/`500`
|
||||
— errors are rejected at a gateway that never adds the header. So an unauthenticated probe tells
|
||||
you nothing: it looks identical to a provider that is genuinely broken.
|
||||
|
||||
Always test with a **valid key and a successful response**. Do not infer success-path CORS from
|
||||
an error response: providers can route successful and failed requests through different middleware.
|
||||
Record error-response CORS separately so users know whether an expired key, exhausted quota, or bad
|
||||
model name will surface as a useful API error or an opaque network failure.
|
||||
|
||||
---
|
||||
|
||||
## Model tool-calling quality
|
||||
|
||||
The AI chat panel is an agent loop, so a model is only usable if it reliably emits **streaming**
|
||||
tool calls that the AI SDK can parse. Two independent things go wrong:
|
||||
|
||||
- **`id` correctness** — if a streamed `tool_calls` delta introduces a new index without an `id`,
|
||||
`@ai-sdk/openai` throws `Expected 'id' to be a string.` and the stream dies.
|
||||
- **Argument validity** — arguments are streamed in fragments and concatenated. If the fragments
|
||||
are misrouted, the result isn't valid JSON and the tool silently never fires.
|
||||
|
||||
Cost matters as much as correctness here. The chat panel is an agent loop that runs up to
|
||||
`MAX_AGENT_STEPS` (50) and resends the tool schemas on every step, so it is input-token heavy and
|
||||
a high per-token price compounds fast.
|
||||
|
||||
Vision is measured by sending a solid-colour image and asking the model to name it. Tool-call and
|
||||
vision results are measurements; context limits and prices are catalog metadata, identified below
|
||||
with their source dates.
|
||||
|
||||
**Record the output-token budget with every result** — it changes the outcome. Scaleway rows below
|
||||
were measured at `max_tokens: 16384`; Nebius and TensorX rows at 2000, so their "emits calls"
|
||||
figures are not directly comparable and any failure there may be starvation rather than incapacity.
|
||||
|
||||
Most results below used provider-default reasoning settings. The separate reasoning comparison
|
||||
explicitly identifies each override it tested. Future rows must record any non-default
|
||||
`reasoning_effort` or `chat_template_kwargs.enable_thinking` value.
|
||||
|
||||
The `Tools` cells use `prompts, bad ID deltas, valid arguments`: for example,
|
||||
`3/3 prompts · 0/238 bad IDs · 6/6 valid args`. A prompt passes when it emits at least one tool
|
||||
call; ID counts cover inspected streamed tool-call deltas; argument counts cover completed calls
|
||||
whose concatenated argument fragments parse as JSON.
|
||||
|
||||
| Model | Provider | /1M in–out | Ctx¹ | Tools | Vision | Verdict | Tested |
|
||||
| ------------------------------------- | -------- | -------------- | ---- | -------------------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------- | ---------- |
|
||||
| `mistral-small-3.2-24b-instruct-2506` | Scaleway | €0.15 – €0.35 | 128k | ✅ 3/3 prompts · 0/238 bad IDs · 6/6 valid args | ✅ | **Recommended** — cheapest with vision, fully clean | 2026-07-31 |
|
||||
| `gemma-4-26b-a4b-it` | Scaleway | €0.25 – €0.50 | 256k | ✅ 3/3 prompts · 0/77 bad IDs · 8/8 valid args | ✅ | Same but 2× the context — pick this for large documents | 2026-07-31 |
|
||||
| `gpt-oss-120b` | Scaleway | €0.15 – €0.60 | 128k | ✅ 3/3 prompts · 0/118 bad IDs · 3/3 valid args | ❌ | Cheap and clean, but text-only | 2026-07-31 |
|
||||
| `qwen3.6-35b-a3b` | Scaleway | €0.25 – €1.50 | 128k | ✅ 3/3 prompts · 0/106 bad IDs · 9/9 valid args | ✅ | Clean; heavy reasoner, needs the larger budget | 2026-07-31 |
|
||||
| `qwen3.5-397b-a17b` | Scaleway | €0.60 – €3.60 | 256k | ✅ 3/3 prompts · 0/92 bad IDs · 10/10 valid args | ✅ | Most calls per turn; strongest but priciest here | 2026-07-31 |
|
||||
| `mistral-medium-3.5-128b` | Scaleway | €1.50 – €7.50 | 256k | — not tested | ✅ | Vision confirmed; tool calling unverified | 2026-07-31 |
|
||||
| `glm-5.2` | Scaleway | €1.80 – €5.50 | 256k | — not tested | ❌ | API rejects images: "not a multimodal model" | 2026-07-31 |
|
||||
| `moonshotai/Kimi-K2.7-Code` | Nebius | $0.95 – $4.00 | 262k | ✅ 3/3 prompts · 0/114 bad IDs · 4/4 valid args² | ❌ | Clean, but pricier than the Scaleway set | 2026-07-30 |
|
||||
| `moonshotai/Kimi-K3` | Nebius | $3.00 – $15.00 | 1M | ✅ 3/3 prompts · 0 bad IDs³ · 8/8 valid args² | ❌ | Flawless, but ~20× the cost of `gpt-oss-120b` | 2026-07-30 |
|
||||
| `moonshotai/kimi-k3` | TensorX | — | — | ⚠️ 2/3 prompts · 0 bad IDs³ · 7/7 valid args² | — | One prompt did not emit a call | 2026-07-30 |
|
||||
| `openai/gpt-oss-120b` | Nebius | $0.15 – $0.60 | 128k | ❌ final argument chunk routed to the wrong index² | ❌ | Broken on Nebius only — same model is fine on Scaleway | 2026-07-30 |
|
||||
| `moonshotai/Kimi-K2.6` | Nebius | — | — | ⚠️ 1/3 prompts² | — | Finished on `stop`, not `length` — likely a real failure, but re-test at 16k before trusting this row | 2026-07-30 |
|
||||
|
||||
¹ Nominal catalog context limit from models.dev, retrieved 2026-07-31; not measured here.
|
||||
² Measured with `max_tokens: 2000`; re-test failures at 16384 or the highest supported budget.
|
||||
³ The original notes recorded no malformed IDs but not the total inspected delta count.
|
||||
|
||||
### Auto-lookup via models.dev
|
||||
|
||||
[models.dev](https://models.dev) publishes a machine-readable catalog at
|
||||
<https://models.dev/api.json> (~3 MB, 176 providers, including `scaleway`, `nebius`, `groq` and
|
||||
`openrouter`). Each model carries `tool_call`, `attachment` (vision), `reasoning`,
|
||||
`cost.input`/`cost.output`, `limit.context` and `modalities`.
|
||||
|
||||
```bash
|
||||
curl -s https://models.dev/api.json \
|
||||
| jq '.scaleway.models["gemma-4-26b-a4b-it"] | {tool_call, attachment, reasoning, cost, limit}'
|
||||
```
|
||||
|
||||
**Use it to prefill a row, not to fill in the measured columns.** Checked against the models above,
|
||||
it was wrong on two of seven:
|
||||
|
||||
| Model | models.dev | Measured |
|
||||
| ---------------------------------- | ------------ | -------------------------------------------------- |
|
||||
| `scaleway/gpt-oss-120b` | vision true | Refuses: "I don't have the ability to view images" |
|
||||
| `scaleway/mistral-small-3.2-24b-…` | vision false | Correctly names the colour in an image |
|
||||
|
||||
It also lists `nebius/openai/gpt-oss-120b` as vision `false` while marking the Scaleway copy
|
||||
`true` — the same model, contradicting itself — and reports `tool_call: true` for every model
|
||||
here, including `qwen3.6-35b-a3b` (silent on 2/3 prompts) and Nebius's `gpt-oss-120b` (broken by
|
||||
the deployment bug below).
|
||||
|
||||
That's the general shape of it: models.dev describes a model's **nominal** capabilities, while
|
||||
what breaks in practice is a property of the **deployment**. Catalog prices and context windows
|
||||
can prefill a row, but must be labeled as metadata with a source and retrieval date; tool-calling
|
||||
and vision require live tests.
|
||||
|
||||
Prices shown in the table were transcribed from public pricing aggregators on 2026-07-31 rather
|
||||
than measured or verified against provider invoices. Because the original notes did not preserve
|
||||
per-row source URLs, treat every price as unverified historical metadata and confirm it against the
|
||||
provider's current pricing page before making a decision. New rows must include a source URL and a
|
||||
separate price-as-of date.
|
||||
|
||||
The same model can behave differently on different providers — `gpt-oss-120b` is broken on Nebius
|
||||
and clean on Scaleway. **Record the provider alongside the model; a model row without one is not
|
||||
reproducible.**
|
||||
|
||||
Note that "emits calls" counts prompts that produced at least one tool call, not how many. Models
|
||||
differ in style: `qwen3.5-397b-a17b` issued 3–4 parallel calls per turn where `gpt-oss-120b` issued
|
||||
one. In a 50-step agent loop, one-call-then-observe is legitimate rather than weaker, so don't read
|
||||
the count as a quality ranking.
|
||||
|
||||
### Known issue: vLLM misroutes the final argument chunk
|
||||
|
||||
On some vLLM deployments the closing `}` of a tool call's arguments is emitted under `index + 1`
|
||||
with no `id`:
|
||||
|
||||
```
|
||||
1 [{"index":0,"id":"chatcmpl-tool-…","function":{"arguments":"","name":"create_node"},"type":"function"}]
|
||||
… deltas 2–11 stream the arguments, all index 0
|
||||
12 [{"index":1,"function":{"arguments":"}"}}] ← wrong index, no id
|
||||
```
|
||||
|
||||
This both crashes the stream _and_ leaves call 0's arguments unterminated. Do not blindly attach
|
||||
an argument-only delta to the most recently opened call: concurrent or interleaved calls make that
|
||||
ambiguous and can corrupt another call. A client-side workaround may reattach the fragment only
|
||||
when exactly one serialized call is open; it must reject ambiguous streams. Synthesizing a missing
|
||||
`id` alone converts a visible crash into a silent no-op.
|
||||
|
||||
Models that emit one complete tool call per chunk (Kimi-K3 on every provider tested) cannot hit
|
||||
this bug at all.
|
||||
|
||||
**This is a deployment bug, not a model bug.** `gpt-oss-120b` misroutes on Nebius but streams
|
||||
cleanly on Scaleway — 0 violations across 135 deltas. Before writing a client-side workaround,
|
||||
check whether another provider serving the same model is unaffected.
|
||||
|
||||
### Known issue: reasoning models starve on a small output budget
|
||||
|
||||
A reasoning model can spend its **entire** output budget thinking and never emit a tool call. The
|
||||
result is `finish_reason: length` with zero tool calls — no error, no partial output. From the
|
||||
outside it is indistinguishable from a model that simply can't call tools.
|
||||
|
||||
Measured on `qwen3.6-35b-a3b`, same prompts, only `max_tokens` changed:
|
||||
|
||||
| `max_tokens` | Result |
|
||||
| ------------ | ----------------------------------------------- |
|
||||
| 2000 | 1/3 prompts — the rest hit `length` mid-thought |
|
||||
| 16384 | 3/3 prompts, 0 bad ids, 9/9 valid args |
|
||||
|
||||
One prompt needed 10,417 characters of reasoning before its first tool call. **This page originally
|
||||
scored two models as broken purely because the probe used 2000 tokens.** If a model produces no tool
|
||||
calls, check `finish_reason` before concluding anything — `length` means starved, `stop` means it
|
||||
genuinely declined.
|
||||
|
||||
`reasoning_effort` is the other lever, though supported values are provider-specific:
|
||||
|
||||
| Setting | Reasoning | Tool calls |
|
||||
| ---------------------------------------------------- | --------- | ---------- |
|
||||
| default | 6,583 ch | ❌ 0 |
|
||||
| `"reasoning_effort": "none"` | 0 ch | ✅ 3 |
|
||||
| `"reasoning_effort": "low"` | 5,088 ch | ✅ 3 |
|
||||
| `"chat_template_kwargs": {"enable_thinking": false}` | 5,133 ch | ✅ 3 |
|
||||
|
||||
Only `"none"` actually eliminated reasoning on Scaleway; `"low"` had no measurable effect.
|
||||
OpenPencil cannot send this today — see
|
||||
[#454](https://github.com/open-pencil/open-pencil/issues/454).
|
||||
|
||||
### Known issue: reasoning models and the connection test
|
||||
|
||||
Some models (e.g. Kimi-K2.6) spend output tokens on `reasoning` before `content`. The connection
|
||||
test uses `maxOutputTokens: 1`, so `content` comes back `null`. The test still passes because it
|
||||
only checks that the request didn't throw — but don't tighten it to assert on returned text, or
|
||||
reasoning models will fail it spuriously.
|
||||
|
||||
---
|
||||
|
||||
## Contributing results
|
||||
|
||||
Add a row, keep it dated, and say how you tested. Negative results are as useful as positive ones.
|
||||
|
||||
### 1. Test CORS
|
||||
|
||||
Run these checks from a shell. Curl is not subject to CORS, so this inspects the headers a browser
|
||||
would enforce. A provider must pass both checks: the preflight must allow the origin, `POST` method,
|
||||
and requested headers, and a successful POST must include `Access-Control-Allow-Origin`.
|
||||
|
||||
Read the key without placing it in shell history or the curl process arguments. The temporary curl
|
||||
configuration is owner-readable only and is removed on exit.
|
||||
|
||||
```bash
|
||||
BASE="https://api.example.com/v1"
|
||||
MODEL="MODEL"
|
||||
ORIGIN="https://openpencil.dev"
|
||||
read -rsp "API key: " API_KEY; printf '\n'
|
||||
CURL_CONFIG="$(mktemp)"
|
||||
chmod 600 "$CURL_CONFIG"
|
||||
trap 'rm -f "$CURL_CONFIG"' EXIT
|
||||
printf 'header = "Authorization: Bearer %s"\n' "$API_KEY" > "$CURL_CONFIG"
|
||||
unset API_KEY
|
||||
|
||||
# Browser preflight: verify the origin, POST method, and both requested headers.
|
||||
curl -sD - -o /dev/null -X OPTIONS "$BASE/chat/completions" \
|
||||
-H "Origin: $ORIGIN" \
|
||||
-H "Access-Control-Request-Method: POST" \
|
||||
-H "Access-Control-Request-Headers: authorization,content-type" \
|
||||
| grep -iE '^HTTP|^access-control-allow-(origin|methods|headers):'
|
||||
|
||||
# Successful response: use a valid key and model, then verify the origin header again.
|
||||
curl --config "$CURL_CONFIG" -sD - -o /dev/null -X POST "$BASE/chat/completions" \
|
||||
-H "Origin: $ORIGIN" \
|
||||
-H "content-type: application/json" \
|
||||
-d "{\"model\":\"$MODEL\",\"max_tokens\":5,\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}" \
|
||||
| grep -iE '^HTTP|^access-control-allow-origin:'
|
||||
```
|
||||
|
||||
Also repeat the POST with an invalid key and record error-response CORS separately. It does not
|
||||
prove whether successful requests work, but it determines whether users see useful authentication
|
||||
errors or only an opaque network failure.
|
||||
|
||||
### 2. Test tool-call streaming
|
||||
|
||||
Ask the model to make several tool calls and save the raw SSE stream. Use at least 16384 output
|
||||
tokens when supported; if the endpoint accepts less, record its highest supported budget.
|
||||
|
||||
```bash
|
||||
STREAM_FILE="$(mktemp)"
|
||||
trap 'rm -f "$CURL_CONFIG" "$STREAM_FILE"' EXIT
|
||||
|
||||
curl --config "$CURL_CONFIG" -sN -X POST "$BASE/chat/completions" \
|
||||
-H "content-type: application/json" \
|
||||
-d "{\"model\":\"$MODEL\",\"stream\":true,\"max_tokens\":16384,
|
||||
\"messages\":[{\"role\":\"user\",\"content\":\"Draw a frog: green ellipse body, two white eye circles, a mouth line. Use create_node for each.\"}],
|
||||
\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"create_node\",\"description\":\"Create a node on the canvas\",
|
||||
\"parameters\":{\"type\":\"object\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"ELLIPSE\",\"RECTANGLE\",\"LINE\",\"TEXT\"]},
|
||||
\"x\":{\"type\":\"number\"},\"y\":{\"type\":\"number\"},\"width\":{\"type\":\"number\"},\"height\":{\"type\":\"number\"},
|
||||
\"fill\":{\"type\":\"string\"}},\"required\":[\"type\",\"x\",\"y\"]}}}]}" \
|
||||
> "$STREAM_FILE"
|
||||
```
|
||||
|
||||
Parse each SSE event as JSON, group fragments by tool-call index, require an ID on the first delta
|
||||
for each index, and validate the concatenated arguments:
|
||||
|
||||
```bash
|
||||
STREAM_FILE="$STREAM_FILE" node <<'JS'
|
||||
const fs = require('node:fs')
|
||||
const calls = new Map()
|
||||
let malformedFirstDeltas = 0
|
||||
|
||||
for (const line of fs.readFileSync(process.env.STREAM_FILE, 'utf8').split(/\r?\n/)) {
|
||||
if (!line.startsWith('data:')) continue
|
||||
const data = line.slice(5).trim()
|
||||
if (!data || data === '[DONE]') continue
|
||||
const event = JSON.parse(data)
|
||||
for (const call of event.choices?.[0]?.delta?.tool_calls ?? []) {
|
||||
const current = calls.get(call.index)
|
||||
if (!current) {
|
||||
if (typeof call.id !== 'string' || !call.id) malformedFirstDeltas++
|
||||
calls.set(call.index, { id: call.id, arguments: call.function?.arguments ?? '' })
|
||||
} else {
|
||||
current.arguments += call.function?.arguments ?? ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let invalidArguments = 0
|
||||
for (const [index, call] of calls) {
|
||||
try {
|
||||
JSON.parse(call.arguments)
|
||||
} catch {
|
||||
invalidArguments++
|
||||
console.error(`tool call ${index}: invalid arguments`)
|
||||
}
|
||||
}
|
||||
console.log({ calls: calls.size, malformedFirstDeltas, invalidArguments })
|
||||
if (malformedFirstDeltas || invalidArguments) process.exitCode = 1
|
||||
JS
|
||||
```
|
||||
|
||||
Use more than one prompt. Failures are often intermittent and can depend on where JSON lands on a
|
||||
chunk boundary. Also record `finish_reason`; a `length` result requires a larger-budget retest
|
||||
before it can be classified as a tool-calling failure.
|
||||
|
||||
### 3. Add your row
|
||||
|
||||
Include the compatibility test date, exact model ID, base URL pattern or regional hostname,
|
||||
output-token budget, reasoning configuration, and a one-line verdict. If something is broken, say
|
||||
what the failure looks like from the user's side — that's what makes the row actionable.
|
||||
|
||||
Include the provider's list price, its source URL, and a separate price-as-of date. A model that
|
||||
streams flawlessly but costs 25× the alternative is not the right default for an agent loop, and a
|
||||
table without price provenance goes stale silently.
|
||||
|
||||
**Record the `max_tokens` you used.** A reasoning model starved of output budget produces no tool
|
||||
calls and looks broken. Two models on this page were wrongly marked "avoid" for exactly that
|
||||
reason. Use at least 16384 when supported, and check `finish_reason` before recording a failure.
|
||||
|
||||
### A note on regional catalogs
|
||||
|
||||
Some providers serve different models per region behind different hostnames. Nebius Token Factory,
|
||||
for example, carries Kimi-K3 on `eu-west2` but not on `us-central1`. Always record the exact base
|
||||
URL you tested — "provider X doesn't have model Y" is only true for the endpoint you checked.
|
||||
|
||||
---
|
||||
|
||||
## Security note
|
||||
|
||||
In the browser, credentials default to encrypted IndexedDB persistence so users do not have to
|
||||
re-enter them; Settings also offers session-only storage, which keeps secrets in memory until the
|
||||
tab closes. The desktop build uses the OS credential store. Browser scripts running on the origin
|
||||
can still use a credential in place, so prefer scoped keys with spend caps where the provider
|
||||
offers them.
|
||||
|
|
@ -25,7 +25,7 @@ description: Встроенный ИИ-ассистент с более чем 9
|
|||
| **Совместимый с OpenAI** | Любой эндпоинт с форматом API OpenAI | Собственный базовый URL + ключ. Поддержка переключения между API Completions и Responses. |
|
||||
| **Совместимый с Anthropic** | Любой эндпоинт с форматом API Anthropic | Собственный базовый URL + ключ |
|
||||
|
||||
Без сервера, без подписки — ключ обращается напрямую к провайдеру.
|
||||
Без сервера и подписки — ключ обращается напрямую к провайдеру. Запросы из браузера зависят от политики CORS провайдера, а надёжность потоковых вызовов инструментов различается между развёртываниями моделей. Измеренные результаты и воспроизводимые проверки приведены в англоязычном справочнике [совместимости BYOK-провайдеров и моделей](/programmable/byok-provider-compatibility).
|
||||
|
||||
## Возможности
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue