spb/zyquo-cloud-web Public MIT
Zyquo Cloud Web — every cloud model, one beautiful chat, entirely in your browser.
TypeScript 81.9%
CSS 8.9%
JavaScript 7.5%
Shell 1.1%
HTML 0.6%
1<!--2 CORS-MATRIX.md3 Zyquo Cloud Web45 Author: Simon-Pierre Boucher6 Mail: contact@spboucher.ai7-->89# CORS Matrix — Browser Feasibility per Provider (Phase 0.A)1011**Method.** Every verdict below is **empirical**, not guessed: on 2026-07-31 each12provider's chat endpoint was probed with (1) a browser-identical `OPTIONS`13preflight (`Origin: https://www.zyquo.cloud`, `Access-Control-Request-Method:14POST`, `Access-Control-Request-Headers: authorization,content-type` — or the15Anthropic header set), and (2) a **real authenticated POST** carrying the same16`Origin`, inspecting the `Access-Control-Allow-*` response headers. A streaming17(`stream: true`) request was additionally verified to return18`content-type: text/event-stream` **with** `access-control-allow-origin` intact.19Probe script: `scripts/cors-probe.sh` (keys read from gitignored20`.keys.local.json`; never committed).2122## Verdict table2324| # | Provider | Endpoint | Preflight | POST ACAO | Verdict |25|---|----------|----------|-----------|-----------|---------|26| 1 | OpenAI | `https://api.openai.com/v1/chat/completions` | 200, allows `authorization,content-type`, max-age 86400 | `*` (echoes origin on preflight) | ✅ direct |27| 2 | Anthropic | `https://api.anthropic.com/v1/messages` | 200, explicitly allows `x-api-key, anthropic-version, anthropic-dangerous-direct-browser-access` | `*` — **only when** `anthropic-dangerous-direct-browser-access: true` is sent; without it the API returns 401 `"CORS requests must set 'anthropic-dangerous-direct-browser-access' header"` | ⚠️ direct **with header** |28| 3 | xAI | `https://api.x.ai/v1/chat/completions` | 200, `allow-methods: *`, `allow-headers: *` | `*` | ✅ direct |29| 4 | Mistral | `https://api.mistral.ai/v1/chat/completions` | 200, allows `Authorization, Content-Type, …` | `*` | ✅ direct |30| 5 | Google Gemini (OpenAI-compat) | `https://generativelanguage.googleapis.com/v1beta/openai/chat/completions` | 200, echoes origin, max-age 3600 | echoes origin | ✅ direct |31| 6 | Alibaba Qwen (DashScope intl) | `https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions` | 200, echoes origin, credentials true, max-age 86400 | echoes origin | ✅ direct |32| 7 | DeepSeek | `https://api.deepseek.com/chat/completions` | 200, `allow-methods: POST`, echoes origin | echoes origin | ✅ direct |33| 8 | Kimi (Moonshot) | `https://api.moonshot.ai/v1/chat/completions` | 204, echoes origin, credentials true | echoes origin | ✅ direct |34| 9 | Perplexity | `https://api.perplexity.ai/chat/completions` | 204, `*`, generous header allowlist, max-age 1728000 | `*` | ✅ direct |35| 10 | Together AI | `https://api.together.xyz/v1/chat/completions` | 200, `*` | `*` | ✅ direct |36| 11 | DeepInfra | `https://api.deepinfra.com/v1/openai/chat/completions` | 200, `*` | `*` | ✅ direct |37| 12 | Cerebras | `https://api.cerebras.ai/v1/chat/completions` | 200, `*`, max-age 600 | `*` | ✅ direct |3839**Bottom line: all 12 providers are callable directly from the browser today.**4011 are plain ✅; Anthropic is ⚠️ only in the sense that the client must add41`anthropic-dangerous-direct-browser-access: true` on every request (the header42name is Anthropic's deliberate reminder that browser-resident keys are exposed —43which matches this app's transparent bring-your-own-key model).4445## Probe caveats observed (not CORS failures)4647- **Perplexity** rejects `max_tokens < 16` (`400 max_tokens must be at least 16`) — keep test calls ≥ 16.48- **Together** now requires serverless-hosted model IDs (e.g. `meta-llama/Llama-3.3-70B-Instruct-Turbo`); older turbo IDs 400 with a "dedicated endpoint" message. CORS headers present on the error, so the verdict stands.49- **Kimi/Cerebras** model IDs rotate quickly (`kimi-k3`, `kimi-k2.6`… / `gpt-oss-120b`, `gemma-4-31b`, `zai-glm-4.7`); 404s were model-name-only, CORS intact.50- **DeepSeek** serves `deepseek-chat` as `deepseek-v4-flash` (alias resolution).51- **xAI** resolves legacy aliases (probe with `grok-3-mini` answered as `grok-4.3`).52- Several providers echo the exact origin instead of `*` (Gemini, DashScope, DeepSeek, Kimi) — irrelevant to the app, but means responses vary by `Origin` and are not cacheable cross-origin.5354## Documented stance (official docs, web-researched + verified 2026-07-31)5556- **Anthropic — the only provider with officially documented CORS support.** Added Aug 22 2024 (Claude Platform release notes, still live): CORS headers are returned when the request carries `anthropic-dangerous-direct-browser-access: true` (what the SDKs send for `dangerouslyAllowBrowser: true`). The deliberately scary name is Anthropic's warning about browser-resident keys — Anthropic frames it as acceptable for internal tools and **bring-your-own-key apps**, which is exactly this app. Sources: [release notes](https://platform.claude.com/docs/en/release-notes/api), [anthropic-sdk-typescript](https://github.com/anthropics/anthropic-sdk-typescript), [Simon Willison's explainer](https://simonwillison.net/2024/Aug/23/anthropic-dangerous-direct-browser-access/).57- **OpenAI** — docs are silent on CORS; permissive ACAO is undocumented behavior. Official guidance is the strongest anti-browser language of the twelve ("Never deploy your key in client-side environments"); the Node SDK gates browsers behind `dangerouslyAllowBrowser: true`. ([key safety](https://help.openai.com/en/articles/5112595-best-practices-for-api-key-safety), [openai-node](https://github.com/openai/openai-node))58- **Google Gemini** — API-key doc explicitly acknowledges client-side use and its extraction risk; documented client-side alternative (ephemeral tokens) applies only to the Live API, not the OpenAI-compat chat endpoint used here. ([API keys](https://ai.google.dev/gemini-api/docs/api-key))59- **Alibaba Qwen/DashScope** — browser use officially anticipated via short-lived **temporary API keys** minted by a backend (60 s validity); permanent keys detected as publicly exposed may be auto-disabled. The app uses the **international** endpoint (`dashscope-intl.aliyuncs.com`), same as native Zyquo Cloud. ([temporary API keys](https://www.alibabacloud.com/help/en/model-studio/generate-temporary-api-key))60- **xAI** — docs silent on CORS for chat; ephemeral client tokens exist only for the realtime/audio API. ([docs.x.ai](https://docs.x.ai/developers/quickstart))61- **Together AI / Cerebras** — docs silent on CORS, but both official TS/Node SDKs list **web browsers as supported runtimes** with no browser gate. ([together-typescript](https://github.com/togethercomputer/together-typescript), [cerebras-cloud-sdk-node](https://github.com/Cerebras/cerebras-cloud-sdk-node))62- **Mistral / DeepSeek / Kimi (Moonshot) / Perplexity / DeepInfra** — docs silent on CORS either way; Perplexity and Moonshot carry generic "never expose keys client-side" guidance. ([Mistral](https://docs.mistral.ai/api), [DeepSeek](https://api-docs.deepseek.com/), [Kimi](https://platform.kimi.ai/docs/api/overview), [Perplexity](https://docs.perplexity.ai/guides/api-key-management), [DeepInfra](https://docs.deepinfra.com/quickstart))6364**Implication:** outside Anthropic, permissive CORS is undocumented behavior that65any provider could withdraw without notice — which is why the per-provider proxy66override ships even though everything works today. The universal "don't ship67your key in client code" warnings target developers embedding *their own* key;68Zyquo Cloud Web's model is the opposite: each user brings **their own** key,69which never leaves their device except toward the provider itself.7071## Fallback design (proxy / Zyquo Router) — still shipped7273Even though everything works today, CORS policies are provider-controlled and74can change without notice. The app therefore ships a **per-provider base-URL75override** (Settings → Providers & Keys → "Proxy / base URL"):7677- Any provider's base URL can be overridden with a user-supplied CORS proxy or78 a locally running **Zyquo Router** (the family's local gateway, OpenAI-compatible).79- When a request fails with a network/CORS-shaped error (fetch `TypeError` with80 no HTTP status), the error toast explains: *"This provider may be blocking81 browser calls — set a proxy URL in Settings or run Zyquo Router locally."*82- The override is stored in `zyquo.cloud.web.settings`; the CSP includes a83 runtime-managed `connect-src` strategy (see Phase 8) so a user-set proxy origin84 remains reachable.8586## Streaming over fetch8788- Verified: streaming responses (`text/event-stream`) carry the same89 `access-control-allow-origin` headers (OpenAI probe above), so90 `fetch` + `ReadableStream` SSE parsing works browser-side.91- All 11 OpenAI-compatible providers use `data:` SSE lines with a `[DONE]`92 terminator; Anthropic uses named `event:` blocks. One parser93 (`providers/sse.ts`, ported from the native `SSEParser`) handles both.94- Cancellation: `AbortController.abort()` on the fetch tears down the stream —95 used by Stop and new-message preemption.96