feat(providers): support OpenAI-compatible / Azure / Google Gemini endpoints (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.
This commit is contained in:
pftom
2026-04-29 07:44:00 +08:00
parent 94941f59a9
commit 32f5f857d2
16 changed files with 636 additions and 48 deletions
+5 -3
View File
@@ -1,5 +1,6 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { useT } from '../i18n';
import { providerLabel } from '../providers/presets';
import type {
AgentInfo,
AppConfig,
@@ -82,16 +83,17 @@ export function EntryView({
const envMetaLine = useMemo(() => {
if (config.mode === 'api') {
const provider = providerLabel(config.provider);
try {
return `${config.model} · ${new URL(config.baseUrl).host}`;
return `${provider} · ${config.model} · ${new URL(config.baseUrl).host}`;
} catch {
return config.model;
return `${provider} · ${config.model}`;
}
}
return currentAgent
? `${currentAgent.name}${currentAgent.version ? ` · ${currentAgent.version}` : ''}`
: t('settings.noAgentSelected');
}, [config.mode, config.model, config.baseUrl, currentAgent, t]);
}, [config.mode, config.model, config.baseUrl, config.provider, currentAgent, t]);
// 'Use this prompt' on an example card is a fast path — skip the form and
// create the project immediately with sane defaults derived from the skill,