SPB Git

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%
8.8 KB

# CORS Matrix — Browser Feasibility per Provider (Phase 0.A)

Method. Every verdict below is empirical, not guessed: on 2026-07-31 each provider's chat endpoint was probed with (1) a browser-identical OPTIONS preflight (Origin: https://www.zyquo.cloud, Access-Control-Request-Method: POST, Access-Control-Request-Headers: authorization,content-type — or the Anthropic header set), and (2) a real authenticated POST carrying the same Origin, inspecting the Access-Control-Allow-* response headers. A streaming (stream: true) request was additionally verified to return content-type: text/event-stream with access-control-allow-origin intact. Probe script: scripts/cors-probe.sh (keys read from gitignored .keys.local.json; never committed).

# Verdict table

# Provider Endpoint Preflight POST ACAO Verdict
1 OpenAI https://api.openai.com/v1/chat/completions 200, allows authorization,content-type, max-age 86400 * (echoes origin on preflight) ✅ direct
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
3 xAI https://api.x.ai/v1/chat/completions 200, allow-methods: *, allow-headers: * * ✅ direct
4 Mistral https://api.mistral.ai/v1/chat/completions 200, allows Authorization, Content-Type, … * ✅ direct
5 Google Gemini (OpenAI-compat) https://generativelanguage.googleapis.com/v1beta/openai/chat/completions 200, echoes origin, max-age 3600 echoes origin ✅ direct
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
7 DeepSeek https://api.deepseek.com/chat/completions 200, allow-methods: POST, echoes origin echoes origin ✅ direct
8 Kimi (Moonshot) https://api.moonshot.ai/v1/chat/completions 204, echoes origin, credentials true echoes origin ✅ direct
9 Perplexity https://api.perplexity.ai/chat/completions 204, *, generous header allowlist, max-age 1728000 * ✅ direct
10 Together AI https://api.together.xyz/v1/chat/completions 200, * * ✅ direct
11 DeepInfra https://api.deepinfra.com/v1/openai/chat/completions 200, * * ✅ direct
12 Cerebras https://api.cerebras.ai/v1/chat/completions 200, *, max-age 600 * ✅ direct

Bottom line: all 12 providers are callable directly from the browser today. 11 are plain ✅; Anthropic is ⚠️ only in the sense that the client must add anthropic-dangerous-direct-browser-access: true on every request (the header name is Anthropic's deliberate reminder that browser-resident keys are exposed — which matches this app's transparent bring-your-own-key model).

# Probe caveats observed (not CORS failures)

  • Perplexity rejects max_tokens < 16 (400 max_tokens must be at least 16) — keep test calls ≥ 16.
  • 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.
  • 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.
  • DeepSeek serves deepseek-chat as deepseek-v4-flash (alias resolution).
  • xAI resolves legacy aliases (probe with grok-3-mini answered as grok-4.3).
  • 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.

# Documented stance (official docs, web-researched + verified 2026-07-31)

  • 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, anthropic-sdk-typescript, Simon Willison's explainer.
  • 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, openai-node)
  • 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)
  • 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)
  • xAI — docs silent on CORS for chat; ephemeral client tokens exist only for the realtime/audio API. (docs.x.ai)
  • 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, cerebras-cloud-sdk-node)
  • Mistral / DeepSeek / Kimi (Moonshot) / Perplexity / DeepInfra — docs silent on CORS either way; Perplexity and Moonshot carry generic "never expose keys client-side" guidance. (Mistral, DeepSeek, Kimi, Perplexity, DeepInfra)

Implication: outside Anthropic, permissive CORS is undocumented behavior that any provider could withdraw without notice — which is why the per-provider proxy override ships even though everything works today. The universal "don't ship your key in client code" warnings target developers embedding their own key; Zyquo Cloud Web's model is the opposite: each user brings their own key, which never leaves their device except toward the provider itself.

# Fallback design (proxy / Zyquo Router) — still shipped

Even though everything works today, CORS policies are provider-controlled and can change without notice. The app therefore ships a per-provider base-URL override (Settings → Providers & Keys → "Proxy / base URL"):

  • Any provider's base URL can be overridden with a user-supplied CORS proxy or a locally running Zyquo Router (the family's local gateway, OpenAI-compatible).
  • When a request fails with a network/CORS-shaped error (fetch TypeError with no HTTP status), the error toast explains: "This provider may be blocking browser calls — set a proxy URL in Settings or run Zyquo Router locally."
  • The override is stored in zyquo.cloud.web.settings; the CSP includes a runtime-managed connect-src strategy (see Phase 8) so a user-set proxy origin remains reachable.

# Streaming over fetch

  • Verified: streaming responses (text/event-stream) carry the same access-control-allow-origin headers (OpenAI probe above), so fetch + ReadableStream SSE parsing works browser-side.
  • All 11 OpenAI-compatible providers use data: SSE lines with a [DONE] terminator; Anthropic uses named event: blocks. One parser (providers/sse.ts, ported from the native SSEParser) handles both.
  • Cancellation: AbortController.abort() on the fetch tears down the stream — used by Stop and new-message preemption.