diff --git a/src/components/ui/provider-brand-icon.tsx b/src/components/ui/provider-brand-icon.tsx index bd3bb4518..65f85cd05 100644 --- a/src/components/ui/provider-brand-icon.tsx +++ b/src/components/ui/provider-brand-icon.tsx @@ -37,6 +37,9 @@ interface ProviderBrandIconByKeyProps { export function ProviderBrandIconByKey({ iconKey: key, size = 18 }: ProviderBrandIconByKeyProps) { if (key === 'tokendance') return ; if (key === 'openrouter') return ; + // Requesty has no @lobehub/icons entry yet; render its logo from models.dev + // the same way ModelSelectorLogo already does for the `requesty` provider. + if (key === 'requesty') return Requesty; if (key === 'zhipu') return ; if (key === 'kimi') return ; if (key === 'moonshot') return ; diff --git a/src/lib/provider-catalog.ts b/src/lib/provider-catalog.ts index bbe88f483..62637e352 100644 --- a/src/lib/provider-catalog.ts +++ b/src/lib/provider-catalog.ts @@ -866,6 +866,46 @@ export const VENDOR_PRESETS: VendorPreset[] = [ }, }, + // ── Requesty ── + // Requesty is an OpenAI-compatible model router (like OpenRouter, but its + // gateway speaks the OpenAI Chat Completions wire protocol at + // `https://router.requesty.ai/v1`). It therefore rides the generic + // `openai-compatible` path (CodePilot / Codex runtimes via the AI SDK chat + // transport) rather than OpenRouter's Anthropic-skin `protocol: 'openrouter'`. + // Because the base URL ends in `/v1` and the preset is not catalog-only, the + // "搜索并添加模型" dialog reaches Requesty's `/v1/models` through the generic + // reliable-provider probe (canReliablyFetchModels / canSearchUpstreamModels), + // so no dedicated catalog fetcher is needed. `provider/model` naming matches + // OpenRouter. `defaultModels` is an intentional alias-only bootstrap (a few + // popular SKUs); user-added models are normal usage, not drift, so no + // `fixedCatalog`/`modelDiscoveryMode` gate. + { + key: 'requesty', + name: 'Requesty', + description: 'Use Requesty to access multiple models', + descriptionZh: '通过 Requesty 访问多种模型', + protocol: 'openai-compatible', + authStyle: 'api_key', + baseUrl: 'https://router.requesty.ai/v1', + defaultEnvOverrides: {}, + defaultModels: [ + { modelId: 'openai/gpt-4o-mini', displayName: 'GPT-4o mini', role: 'default', capabilities: { toolUse: true, vision: true } }, + { modelId: 'openai/gpt-4o', displayName: 'GPT-4o', capabilities: { toolUse: true, vision: true } }, + { modelId: 'anthropic/claude-sonnet-4-5', displayName: 'Claude Sonnet 4.5', capabilities: { toolUse: true, vision: true, reasoning: true } }, + { modelId: 'google/gemini-2.5-flash', displayName: 'Gemini 2.5 Flash', capabilities: { toolUse: true, vision: true } }, + { modelId: 'deepseek/deepseek-chat', displayName: 'DeepSeek Chat', capabilities: { toolUse: true } }, + ], + defaultRoleModels: { default: 'openai/gpt-4o-mini' }, + fields: ['api_key'], + iconKey: 'requesty', + meta: { + apiKeyUrl: 'https://app.requesty.ai/api-keys', + docsUrl: 'https://docs.requesty.ai', + pricingUrl: 'https://app.requesty.ai/model-library', + billingModel: 'pay_as_you_go', + }, + }, + // ── Zhipu GLM (China) ── { key: 'glm-cn', diff --git a/src/lib/provider-icon-rule.ts b/src/lib/provider-icon-rule.ts index 75f227541..35473d218 100644 --- a/src/lib/provider-icon-rule.ts +++ b/src/lib/provider-icon-rule.ts @@ -18,6 +18,7 @@ import { isTokenDanceBaseUrl } from "./tokendance"; export type ProviderIconKey = | "tokendance" | "openrouter" + | "requesty" | "zhipu" | "kimi" | "moonshot" @@ -45,6 +46,10 @@ export function getProviderIconKey(name: string, baseUrl: string): ProviderIconK if (isTokenDanceBaseUrl(baseUrl) || lower.includes("tokendance") || lower.includes("词元跳动")) return "tokendance"; if (lower.includes("openrouter")) return "openrouter"; + // Requesty: OpenAI-compatible model router. Scoped to brand-unique + // fragments (`requesty.ai` host / `requesty` name) so it can't steal + // another vendor's icon. + if (url.includes("requesty.ai") || lower.includes("requesty")) return "requesty"; // OpenCode Go: the provider name carries a protocol suffix — "OpenCode Go // (OpenAI)" / "(Anthropic)". Match BEFORE the openai / anthropic name // matchers below, which would otherwise steal the wrong brand logo