feat(providers): support OpenAI-compatible / Azure / Google endpoints#17
feat(providers): support OpenAI-compatible / Azure / Google endpoints#17
Conversation
…dpoints (closes #16) The hosted-API BYOK fallback was hard-wired to api.anthropic.com. Issue #16 asks for Azure (Microsoft OpenAI hosting Anthropic-style models) but the same plumbing unlocks every common BYOK target — OpenRouter, LiteLLM, DeepSeek, Groq, Together, Mistral, plus Google Gemini direct. Provider model: - New `provider: 'anthropic' | 'openai' | 'azure' | 'google'` discriminator on AppConfig (defaults to 'anthropic' so existing localStorage configs migrate seamlessly). - src/providers/model.ts routes to one of four streaming clients: anthropic.ts (existing SDK path), openai.ts (new SSE pump shared with azure.ts), azure.ts (deployment URL + api-key header), google.ts (Generative Language streamGenerateContent). - src/providers/presets.ts ships per-provider defaults (baseUrl, model suggestions, api-key placeholder, Azure api-version flag) so the UI can stay declarative. UI: - SettingsDialog now shows a provider picker on the Hosted-API tab and surfaces an Azure-only api-version field. Provider switches preserve any non-empty user values. - EntryView / AvatarMenu env meta line shows the active provider label. - en + zh-CN locales updated; README + README.zh-CN document every provider, with explicit guidance to reach AWS Bedrock / GCP Vertex Anthropic models via a server-side LiteLLM proxy (signing belongs on the server, not the browser). Why an OpenAI-compatible adapter rather than a native Bedrock/Vertex client: AWS SigV4 and GCP service-account JWTs aren't safe to do from a browser holding long-lived BYOK credentials. LiteLLM (or any Anthropic/OpenAI-compatible proxy) sidesteps that and is the same path lobe-chat uses for Bedrock/Vertex.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 32f5f857d2
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
lefarcen
left a comment
There was a problem hiding this comment.
Verdict: Comment — solid, well-scoped router refactor that does what #16 asked, and the structure is easy to extend (the four-case switch + presets row + per-provider file pattern is the right shape).
Big-picture observations:
- SSE pump robustness. The OpenAI/Azure/Google parsers split frames on
\n\nonly. Per the SSE spec, frames may also be delimited by\r\n\r\n, and several proxies (Cloudflare-fronted endpoints, certain LiteLLM configs) emit CRLF. Worth normalizing or splitting on/\r?\n\r?\n/. Also,[DONE]iscontinued rather thanbreaked — fine in practice, but a misbehaving server can leave the stream half-open. - Tool-use coverage. Only text deltas are bridged. Anthropic-side
tool_usecomes through the SDK, but the OpenAI/Azure/Google clients ignoredelta.tool_calls/parts[*].functionCallentirely. If skills/artifact prompts in this app rely on tool blocks today, switching providers silently degrades them. At minimum the limitation should be called out in the README + Settings hint; ideally the deltas are bridged into Anthropic-shaped events. - Google
?key=exposure. The API key sits in the request URL, so it shows up in DevTools Network, browser history on redirect, the HTTP referer chain, and any logging proxy in front of Google. The Generative Language API also acceptsx-goog-api-keyas a header — strictly safer for a browser BYOK path. - Provider-switch state shape.
setProvider's heuristic preserves any non-emptybaseUrl/model, but it can't tell user-typed values from the previous provider's preset, so switching Anthropic → OpenAI keepshttps://api.anthropic.com. See inline.
Migration path looks correct ({ ...DEFAULT_CONFIG, ...parsed } defaults missing provider to 'anthropic'). Noted that the test-plan checkboxes are still [ ] — recommend running through Anthropic + Azure + Gemini smoke tests before merge, since the wire-format details (api-version default, :streamGenerateContent framing, [DONE] handling) are easy to regress.
- Settings: provider switch now replaces baseUrl/model when the current
value matches the previous provider's preset, fixing "pick provider
and go" on a fresh install (anthropic→openai/azure/google no longer
inherits anthropic defaults).
- Settings: require a non-empty Base URL for every provider, not just
Azure — OpenAI/Google paths would otherwise fail at runtime.
- openai/google: split SSE frames on /\r?\n\r?\n/ so CRLF-emitting
upstreams (Cloudflare-fronted proxies, certain LiteLLM configs) work.
- openai: stop reading after `data: [DONE]` so a half-open stream from
a misbehaving proxy can't block the client.
- openai/google: classify 401/403/404/429/5xx into one-line messages a
BYOK user can act on, instead of dumping a JSON wall.
- google: send the API key via x-goog-api-key header instead of the
query string (was leaking into DevTools, history, referer chain).
- google: drop systemInstruction.role — Gemini's REST surface only
expects { parts: [...] } and some endpoints reject the role field.
- azure: pin DEFAULT_API_VERSION to the GA tag 2024-10-21 (preview
versions rotate); log the effective version once per session.
- presets: rewrite the stale `providerLabel` comment, link LiteLLM
Bedrock / Vertex provider docs alongside the proxy guidance.
- state/config: comment that the spread-merge in loadConfig() IS the
migration path for fields added after a localStorage entry exists.
- README + zh-CN: document that OpenAI/Azure/Google are text-only today
(tool calls, vision, reasoning content are not bridged) and link
LiteLLM's Bedrock / Vertex docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
👋 Thanks @pftom — turning the BYOK fallback into a four-provider router is a meaningful unlock for everyone who can't (or doesn't want to) point at Inline concerns are mostly stream-parser robustness:
Migration / settings UX is tight. ✅ |
|
PR is APPROVED ✅ but shows CONFLICTING merge state — likely needs a rebase against latest main. If you'd like me to verify the conflicts or help coordinate the rebase, ping me. Otherwise ready to merge once conflicts are resolved. |
Dismissed — accidental empty approval; defer to the prior COMMENTED review.
Summary
Closes #16. The hosted-API BYOK fallback was hard-wired to
api.anthropic.com. This PR turns that single client into a four-provider router so the same browser-side BYOK path works against Anthropic, any OpenAI-compatible endpoint (OpenRouter / LiteLLM / DeepSeek / Groq / Together / Mistral / OpenAI itself), Azure OpenAI, and Google Gemini direct.provider: 'anthropic' | 'openai' | 'azure' | 'google'toAppConfig(defaults toanthropicso existing localStorage configs migrate without touching anything).src/providers/model.tsrouter; new clientsopenai.ts(SSE/chat/completions, shared pump exported toazure.ts),azure.ts(deployment URL +api-version+api-keyheader),google.ts(:streamGenerateContent?alt=sse).src/providers/presets.tscarries per-provider defaults (baseUrl, model suggestions, api-key placeholder, Azure api-version flag) so the SettingsDialog stays declarative.SettingsDialoggains a provider segment-control on the Hosted-API tab and renders an Azure-only api-version field. Switching providers preserves any non-empty user values.EntryView/AvatarMenuenv meta line shows the active provider label.Issue
Resolves #16 — the asker wanted to point Open Design at a Microsoft OpenAI endpoint running Anthropic-style models. With this PR they pick Azure OpenAI in Settings, paste their resource endpoint as Base URL, the deployment id as Model, the key, and (optionally) an api-version. For Anthropic-on-Bedrock / Anthropic-on-Vertex they front them with a LiteLLM (or equivalent) proxy and select the Anthropic provider — proxy URL goes in Base URL.
Test plan
npm run build(typecheck + Vite build) — green locally.https://api.anthropic.com,claude-sonnet-4-5,sk-ant-…)https://openrouter.ai/api/v1,anthropic/claude-3.5-sonnet)https://<resource>.openai.azure.com, deployment id as model, validapi-version)https://generativelanguage.googleapis.com,gemini-2.0-flash, AIza key)/api/chat.