mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-31 11:01:36 +08:00
- 支持 Claude Desktop 使用 Copilot/Codex OAuth 供应商
- 放开本地路由托管 OAuth 供应商校验,允许动态 Token - 新增 Claude Desktop Copilot/Codex 预设与账号选择 - 添加 OAuth proxy 回归测试
This commit is contained in:
@@ -370,17 +370,6 @@ pub fn validate_proxy_provider(provider: &Provider) -> Result<(), AppError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(meta) = provider.meta.as_ref() {
|
if let Some(meta) = provider.meta.as_ref() {
|
||||||
if matches!(
|
|
||||||
meta.provider_type.as_deref(),
|
|
||||||
Some("github_copilot") | Some("codex_oauth")
|
|
||||||
) {
|
|
||||||
return Err(AppError::localized(
|
|
||||||
"claude_desktop.provider.type_unsupported",
|
|
||||||
"Claude Desktop 本地路由模式暂不支持 Copilot 或 Codex OAuth 供应商",
|
|
||||||
"Claude Desktop proxy mode does not support Copilot or Codex OAuth providers yet",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(api_format) = meta.api_format.as_deref() {
|
if let Some(api_format) = meta.api_format.as_deref() {
|
||||||
if !matches!(
|
if !matches!(
|
||||||
api_format,
|
api_format,
|
||||||
@@ -419,6 +408,10 @@ fn has_proxy_base_url_and_key(provider: &Provider) -> bool {
|
|||||||
.map(str::trim)
|
.map(str::trim)
|
||||||
.is_some_and(|value| !value.is_empty());
|
.is_some_and(|value| !value.is_empty());
|
||||||
|
|
||||||
|
if is_managed_oauth_proxy_provider(provider) {
|
||||||
|
return has_base_url;
|
||||||
|
}
|
||||||
|
|
||||||
let has_key = env
|
let has_key = env
|
||||||
.and_then(|value| {
|
.and_then(|value| {
|
||||||
[
|
[
|
||||||
@@ -440,6 +433,14 @@ fn has_proxy_base_url_and_key(provider: &Provider) -> bool {
|
|||||||
has_base_url && has_key
|
has_base_url && has_key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_managed_oauth_proxy_provider(provider: &Provider) -> bool {
|
||||||
|
provider
|
||||||
|
.meta
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|meta| meta.provider_type.as_deref())
|
||||||
|
.is_some_and(|provider_type| matches!(provider_type, "github_copilot" | "codex_oauth"))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn validate_provider(provider: &Provider) -> Result<(), AppError> {
|
pub fn validate_provider(provider: &Provider) -> Result<(), AppError> {
|
||||||
if is_official_provider(provider) {
|
if is_official_provider(provider) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -1066,6 +1067,33 @@ mod tests {
|
|||||||
provider
|
provider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn oauth_proxy_provider(id: &str, provider_type: &str, api_format: &str) -> Provider {
|
||||||
|
let mut provider = Provider::with_id(
|
||||||
|
id.to_string(),
|
||||||
|
"OAuth Proxy".to_string(),
|
||||||
|
json!({
|
||||||
|
"env": {
|
||||||
|
"ANTHROPIC_BASE_URL": "https://oauth-upstream.example.com"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Some("https://example.com".to_string()),
|
||||||
|
);
|
||||||
|
provider.meta = Some(ProviderMeta {
|
||||||
|
claude_desktop_mode: Some(ClaudeDesktopMode::Proxy),
|
||||||
|
api_format: Some(api_format.to_string()),
|
||||||
|
provider_type: Some(provider_type.to_string()),
|
||||||
|
claude_desktop_model_routes: std::collections::HashMap::from([(
|
||||||
|
"claude-gpt-5-4".to_string(),
|
||||||
|
ClaudeDesktopModelRoute {
|
||||||
|
model: "gpt-5.4".to_string(),
|
||||||
|
supports_1m: Some(false),
|
||||||
|
},
|
||||||
|
)]),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
provider
|
||||||
|
}
|
||||||
|
|
||||||
fn direct_provider_with_models(id: &str) -> Provider {
|
fn direct_provider_with_models(id: &str) -> Provider {
|
||||||
let mut provider = direct_provider(id);
|
let mut provider = direct_provider(id);
|
||||||
provider.meta = Some(ProviderMeta {
|
provider.meta = Some(ProviderMeta {
|
||||||
@@ -1163,6 +1191,29 @@ mod tests {
|
|||||||
assert!(!profile.to_string().contains("kimi-k2"));
|
assert!(!profile.to_string().contains("kimi-k2"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn claude_desktop_proxy_accepts_managed_oauth_providers_without_static_key() {
|
||||||
|
for (provider_type, api_format) in [
|
||||||
|
("github_copilot", "openai_chat"),
|
||||||
|
("codex_oauth", "openai_responses"),
|
||||||
|
] {
|
||||||
|
let provider = oauth_proxy_provider(provider_type, provider_type, api_format);
|
||||||
|
validate_proxy_provider(&provider).expect("oauth proxy provider should validate");
|
||||||
|
|
||||||
|
let temp = TempDir::new().expect("tempdir");
|
||||||
|
let paths = test_paths(temp.path());
|
||||||
|
let db = test_db();
|
||||||
|
apply_provider_to_paths(&db, &provider, &paths).expect("apply oauth proxy provider");
|
||||||
|
|
||||||
|
let profile: Value = read_json_file(&paths.profile_path).expect("read profile");
|
||||||
|
assert_eq!(
|
||||||
|
profile["inferenceGatewayBaseUrl"],
|
||||||
|
json!("http://127.0.0.1:15721/claude-desktop")
|
||||||
|
);
|
||||||
|
assert_eq!(profile["inferenceModels"], json!(["claude-gpt-5-4"]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn claude_desktop_proxy_maps_known_route_and_rejects_unknown_route() {
|
fn claude_desktop_proxy_maps_known_route_and_rejects_unknown_route() {
|
||||||
let provider = proxy_provider("proxy");
|
let provider = proxy_provider("proxy");
|
||||||
|
|||||||
@@ -184,16 +184,6 @@ pub fn import_claude_desktop_providers_from_claude(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(
|
|
||||||
provider
|
|
||||||
.meta
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|meta| meta.provider_type.as_deref()),
|
|
||||||
Some("github_copilot") | Some("codex_oauth")
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut desktop_provider = provider.clone();
|
let mut desktop_provider = provider.clone();
|
||||||
desktop_provider.in_failover_queue = false;
|
desktop_provider.in_failover_queue = false;
|
||||||
let meta = desktop_provider.meta.get_or_insert_with(Default::default);
|
let meta = desktop_provider.meta.get_or_insert_with(Default::default);
|
||||||
@@ -249,12 +239,20 @@ fn suggested_claude_desktop_routes(
|
|||||||
.get("env")
|
.get("env")
|
||||||
.and_then(|value| value.as_object())?;
|
.and_then(|value| value.as_object())?;
|
||||||
let mut routes = std::collections::HashMap::new();
|
let mut routes = std::collections::HashMap::new();
|
||||||
|
let supports_1m = !matches!(
|
||||||
|
provider
|
||||||
|
.meta
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|meta| meta.provider_type.as_deref()),
|
||||||
|
Some("github_copilot") | Some("codex_oauth")
|
||||||
|
);
|
||||||
|
|
||||||
fn add_route(
|
fn add_route(
|
||||||
routes: &mut std::collections::HashMap<String, crate::provider::ClaudeDesktopModelRoute>,
|
routes: &mut std::collections::HashMap<String, crate::provider::ClaudeDesktopModelRoute>,
|
||||||
env: &serde_json::Map<String, serde_json::Value>,
|
env: &serde_json::Map<String, serde_json::Value>,
|
||||||
route_id: &str,
|
route_id: &str,
|
||||||
env_key: &str,
|
env_key: &str,
|
||||||
|
supports_1m: bool,
|
||||||
) {
|
) {
|
||||||
if let Some(model) = env
|
if let Some(model) = env
|
||||||
.get(env_key)
|
.get(env_key)
|
||||||
@@ -266,19 +264,25 @@ fn suggested_claude_desktop_routes(
|
|||||||
route_id.to_string(),
|
route_id.to_string(),
|
||||||
crate::provider::ClaudeDesktopModelRoute {
|
crate::provider::ClaudeDesktopModelRoute {
|
||||||
model: model.to_string(),
|
model: model.to_string(),
|
||||||
supports_1m: Some(true),
|
supports_1m: Some(supports_1m),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for spec in crate::claude_desktop_config::DEFAULT_PROXY_ROUTES {
|
for spec in crate::claude_desktop_config::DEFAULT_PROXY_ROUTES {
|
||||||
add_route(&mut routes, env, spec.route_id, spec.env_key);
|
add_route(&mut routes, env, spec.route_id, spec.env_key, supports_1m);
|
||||||
}
|
}
|
||||||
|
|
||||||
let primary_route = crate::claude_desktop_config::DEFAULT_PROXY_ROUTES[0];
|
let primary_route = crate::claude_desktop_config::DEFAULT_PROXY_ROUTES[0];
|
||||||
if !routes.contains_key(primary_route.route_id) {
|
if !routes.contains_key(primary_route.route_id) {
|
||||||
add_route(&mut routes, env, primary_route.route_id, "ANTHROPIC_MODEL");
|
add_route(
|
||||||
|
&mut routes,
|
||||||
|
env,
|
||||||
|
primary_route.route_id,
|
||||||
|
"ANTHROPIC_MODEL",
|
||||||
|
supports_1m,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
(!routes.is_empty()).then_some(routes)
|
(!routes.is_empty()).then_some(routes)
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ import {
|
|||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { BasicFormFields } from "./BasicFormFields";
|
import { BasicFormFields } from "./BasicFormFields";
|
||||||
|
import { CodexOAuthSection } from "./CodexOAuthSection";
|
||||||
|
import { CopilotAuthSection } from "./CopilotAuthSection";
|
||||||
import { EndpointField } from "./shared/EndpointField";
|
import { EndpointField } from "./shared/EndpointField";
|
||||||
import { ModelDropdown } from "./shared/ModelDropdown";
|
import { ModelDropdown } from "./shared/ModelDropdown";
|
||||||
import { ProviderPresetSelector } from "./ProviderPresetSelector";
|
import { ProviderPresetSelector } from "./ProviderPresetSelector";
|
||||||
@@ -61,6 +63,7 @@ import {
|
|||||||
providersApi,
|
providersApi,
|
||||||
type ClaudeDesktopDefaultRoute,
|
type ClaudeDesktopDefaultRoute,
|
||||||
} from "@/lib/api/providers";
|
} from "@/lib/api/providers";
|
||||||
|
import { resolveManagedAccountId } from "@/lib/authBinding";
|
||||||
|
|
||||||
export type ClaudeDesktopProviderFormValues = ProviderFormData & {
|
export type ClaudeDesktopProviderFormValues = ProviderFormData & {
|
||||||
presetId?: string;
|
presetId?: string;
|
||||||
@@ -231,6 +234,15 @@ export function ClaudeDesktopProviderForm({
|
|||||||
? "ANTHROPIC_API_KEY"
|
? "ANTHROPIC_API_KEY"
|
||||||
: "ANTHROPIC_AUTH_TOKEN",
|
: "ANTHROPIC_AUTH_TOKEN",
|
||||||
);
|
);
|
||||||
|
const [selectedGitHubAccountId, setSelectedGitHubAccountId] = useState<
|
||||||
|
string | null
|
||||||
|
>(() => resolveManagedAccountId(initialData?.meta, "github_copilot"));
|
||||||
|
const [selectedCodexAccountId, setSelectedCodexAccountId] = useState<
|
||||||
|
string | null
|
||||||
|
>(() => resolveManagedAccountId(initialData?.meta, "codex_oauth"));
|
||||||
|
const [codexFastMode, setCodexFastMode] = useState<boolean>(
|
||||||
|
() => initialData?.meta?.codexFastMode ?? false,
|
||||||
|
);
|
||||||
const [selectedPresetId, setSelectedPresetId] = useState<string | null>(
|
const [selectedPresetId, setSelectedPresetId] = useState<string | null>(
|
||||||
"custom",
|
"custom",
|
||||||
);
|
);
|
||||||
@@ -239,6 +251,8 @@ export function ClaudeDesktopProviderForm({
|
|||||||
category?: ProviderCategory;
|
category?: ProviderCategory;
|
||||||
isPartner?: boolean;
|
isPartner?: boolean;
|
||||||
partnerPromotionKey?: string;
|
partnerPromotionKey?: string;
|
||||||
|
providerType?: string;
|
||||||
|
requiresOAuth?: boolean;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
const [routes, setRoutes] = useState<RouteRow[]>(() =>
|
const [routes, setRoutes] = useState<RouteRow[]>(() =>
|
||||||
initialRouteRows(initialData?.meta?.claudeDesktopModelRoutes),
|
initialRouteRows(initialData?.meta?.claudeDesktopModelRoutes),
|
||||||
@@ -334,6 +348,12 @@ export function ClaudeDesktopProviderForm({
|
|||||||
}),
|
}),
|
||||||
[t],
|
[t],
|
||||||
);
|
);
|
||||||
|
const activeProviderType =
|
||||||
|
activePreset?.providerType ?? initialData?.meta?.providerType;
|
||||||
|
const usesManagedOAuth =
|
||||||
|
activePreset?.requiresOAuth === true ||
|
||||||
|
activeProviderType === "github_copilot" ||
|
||||||
|
activeProviderType === "codex_oauth";
|
||||||
|
|
||||||
const applyDesktopPreset = (preset: ClaudeDesktopProviderPreset) => {
|
const applyDesktopPreset = (preset: ClaudeDesktopProviderPreset) => {
|
||||||
form.setValue("name", preset.nameKey ? t(preset.nameKey) : preset.name);
|
form.setValue("name", preset.nameKey ? t(preset.nameKey) : preset.name);
|
||||||
@@ -386,6 +406,8 @@ export function ClaudeDesktopProviderForm({
|
|||||||
category: entry.preset.category,
|
category: entry.preset.category,
|
||||||
isPartner: entry.preset.isPartner,
|
isPartner: entry.preset.isPartner,
|
||||||
partnerPromotionKey: entry.preset.partnerPromotionKey,
|
partnerPromotionKey: entry.preset.partnerPromotionKey,
|
||||||
|
providerType: entry.preset.providerType,
|
||||||
|
requiresOAuth: entry.preset.requiresOAuth,
|
||||||
});
|
});
|
||||||
applyDesktopPreset(entry.preset);
|
applyDesktopPreset(entry.preset);
|
||||||
};
|
};
|
||||||
@@ -469,7 +491,7 @@ export function ClaudeDesktopProviderForm({
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!apiKey.trim()) {
|
if (!usesManagedOAuth && !apiKey.trim()) {
|
||||||
toast.error(
|
toast.error(
|
||||||
t("providerForm.fetchModelsNeedApiKey", {
|
t("providerForm.fetchModelsNeedApiKey", {
|
||||||
defaultValue: "请先填写 API Key",
|
defaultValue: "请先填写 API Key",
|
||||||
@@ -524,16 +546,18 @@ export function ClaudeDesktopProviderForm({
|
|||||||
|
|
||||||
const settingsConfig = clonePlainRecord(initialData?.settingsConfig);
|
const settingsConfig = clonePlainRecord(initialData?.settingsConfig);
|
||||||
const env = clonePlainRecord(settingsConfig.env);
|
const env = clonePlainRecord(settingsConfig.env);
|
||||||
const otherKey: ApiKeyField =
|
delete env.ANTHROPIC_AUTH_TOKEN;
|
||||||
apiKeyField === "ANTHROPIC_AUTH_TOKEN"
|
delete env.ANTHROPIC_API_KEY;
|
||||||
? "ANTHROPIC_API_KEY"
|
settingsConfig.env = usesManagedOAuth
|
||||||
: "ANTHROPIC_AUTH_TOKEN";
|
? {
|
||||||
delete env[otherKey];
|
...env,
|
||||||
settingsConfig.env = {
|
ANTHROPIC_BASE_URL: baseUrl.trim().replace(/\/+$/, ""),
|
||||||
...env,
|
}
|
||||||
ANTHROPIC_BASE_URL: baseUrl.trim().replace(/\/+$/, ""),
|
: {
|
||||||
[apiKeyField]: apiKey.trim(),
|
...env,
|
||||||
};
|
ANTHROPIC_BASE_URL: baseUrl.trim().replace(/\/+$/, ""),
|
||||||
|
[apiKeyField]: apiKey.trim(),
|
||||||
|
};
|
||||||
|
|
||||||
const routeMap = routeEntries.reduce<
|
const routeMap = routeEntries.reduce<
|
||||||
Record<string, ClaudeDesktopModelRoute>
|
Record<string, ClaudeDesktopModelRoute>
|
||||||
@@ -552,9 +576,25 @@ export function ClaudeDesktopProviderForm({
|
|||||||
};
|
};
|
||||||
|
|
||||||
meta.claudeDesktopModelRoutes = routeMap;
|
meta.claudeDesktopModelRoutes = routeMap;
|
||||||
|
meta.providerType = activeProviderType;
|
||||||
|
meta.authBinding =
|
||||||
|
activeProviderType === "github_copilot"
|
||||||
|
? {
|
||||||
|
source: "managed_account",
|
||||||
|
authProvider: "github_copilot",
|
||||||
|
accountId: selectedGitHubAccountId ?? undefined,
|
||||||
|
}
|
||||||
|
: activeProviderType === "codex_oauth"
|
||||||
|
? {
|
||||||
|
source: "managed_account",
|
||||||
|
authProvider: "codex_oauth",
|
||||||
|
accountId: selectedCodexAccountId ?? undefined,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
meta.codexFastMode =
|
||||||
|
activeProviderType === "codex_oauth" ? codexFastMode : undefined;
|
||||||
|
|
||||||
delete meta.endpointAutoSelect;
|
delete meta.endpointAutoSelect;
|
||||||
delete meta.providerType;
|
|
||||||
delete meta.isFullUrl;
|
delete meta.isFullUrl;
|
||||||
|
|
||||||
await onSubmit({
|
await onSubmit({
|
||||||
@@ -573,21 +613,23 @@ export function ClaudeDesktopProviderForm({
|
|||||||
|
|
||||||
const renderActionButtons = (onAdd: () => void, addLabel: string) => (
|
const renderActionButtons = (onAdd: () => void, addLabel: string) => (
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<Button
|
{!usesManagedOAuth && (
|
||||||
type="button"
|
<Button
|
||||||
variant="outline"
|
type="button"
|
||||||
size="sm"
|
variant="outline"
|
||||||
onClick={handleFetchModels}
|
size="sm"
|
||||||
disabled={isFetchingModels}
|
onClick={handleFetchModels}
|
||||||
className="h-7 gap-1"
|
disabled={isFetchingModels}
|
||||||
>
|
className="h-7 gap-1"
|
||||||
{isFetchingModels ? (
|
>
|
||||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
{isFetchingModels ? (
|
||||||
) : (
|
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||||
<Download className="h-3.5 w-3.5" />
|
) : (
|
||||||
)}
|
<Download className="h-3.5 w-3.5" />
|
||||||
{t("providerForm.fetchModels", { defaultValue: "获取模型" })}
|
)}
|
||||||
</Button>
|
{t("providerForm.fetchModels", { defaultValue: "获取模型" })}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -621,15 +663,33 @@ export function ClaudeDesktopProviderForm({
|
|||||||
|
|
||||||
<BasicFormFields form={form} />
|
<BasicFormFields form={form} />
|
||||||
|
|
||||||
<div className="space-y-1">
|
{usesManagedOAuth ? (
|
||||||
<Label>{"API Key"}</Label>
|
<div className="rounded-lg border border-border-default bg-muted/20 p-3">
|
||||||
<Input
|
{activeProviderType === "github_copilot" ? (
|
||||||
value={apiKey}
|
<CopilotAuthSection
|
||||||
onChange={(event) => setApiKey(event.target.value)}
|
selectedAccountId={selectedGitHubAccountId}
|
||||||
type="password"
|
onAccountSelect={setSelectedGitHubAccountId}
|
||||||
placeholder="sk-..."
|
/>
|
||||||
/>
|
) : (
|
||||||
</div>
|
<CodexOAuthSection
|
||||||
|
selectedAccountId={selectedCodexAccountId}
|
||||||
|
onAccountSelect={setSelectedCodexAccountId}
|
||||||
|
fastModeEnabled={codexFastMode}
|
||||||
|
onFastModeChange={setCodexFastMode}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label>{"API Key"}</Label>
|
||||||
|
<Input
|
||||||
|
value={apiKey}
|
||||||
|
onChange={(event) => setApiKey(event.target.value)}
|
||||||
|
type="password"
|
||||||
|
placeholder="sk-..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<EndpointField
|
<EndpointField
|
||||||
id="baseUrl"
|
id="baseUrl"
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ export interface ClaudeDesktopProviderPreset {
|
|||||||
mode: "direct" | "proxy";
|
mode: "direct" | "proxy";
|
||||||
apiFormat?: ClaudeDesktopApiFormat;
|
apiFormat?: ClaudeDesktopApiFormat;
|
||||||
modelRoutes?: ClaudeDesktopRoutePreset[];
|
modelRoutes?: ClaudeDesktopRoutePreset[];
|
||||||
|
providerType?: "github_copilot" | "codex_oauth";
|
||||||
|
requiresOAuth?: boolean;
|
||||||
|
|
||||||
endpointCandidates?: string[];
|
endpointCandidates?: string[];
|
||||||
theme?: PresetTheme;
|
theme?: PresetTheme;
|
||||||
@@ -155,6 +157,36 @@ export const claudeDesktopProviderPresets: ClaudeDesktopProviderPreset[] = [
|
|||||||
icon: "gemini",
|
icon: "gemini",
|
||||||
iconColor: "#4285F4",
|
iconColor: "#4285F4",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "GitHub Copilot",
|
||||||
|
websiteUrl: "https://github.com/features/copilot",
|
||||||
|
category: "third_party",
|
||||||
|
baseUrl: "https://api.githubcopilot.com",
|
||||||
|
mode: "proxy",
|
||||||
|
apiFormat: "openai_chat",
|
||||||
|
providerType: "github_copilot",
|
||||||
|
requiresOAuth: true,
|
||||||
|
modelRoutes: brandedRoutes(
|
||||||
|
"claude-sonnet-4.6",
|
||||||
|
"claude-sonnet-4.6",
|
||||||
|
"claude-haiku-4.5",
|
||||||
|
),
|
||||||
|
icon: "github",
|
||||||
|
iconColor: "#000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Codex",
|
||||||
|
websiteUrl: "https://openai.com/chatgpt/pricing",
|
||||||
|
category: "third_party",
|
||||||
|
baseUrl: "https://chatgpt.com/backend-api/codex",
|
||||||
|
mode: "proxy",
|
||||||
|
apiFormat: "openai_responses",
|
||||||
|
providerType: "codex_oauth",
|
||||||
|
requiresOAuth: true,
|
||||||
|
modelRoutes: brandedRoutes("gpt-5.4", "gpt-5.4", "gpt-5.4-mini"),
|
||||||
|
icon: "openai",
|
||||||
|
iconColor: "#000000",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "DeepSeek",
|
name: "DeepSeek",
|
||||||
websiteUrl: "https://platform.deepseek.com",
|
websiteUrl: "https://platform.deepseek.com",
|
||||||
|
|||||||
@@ -133,6 +133,8 @@ export interface AuthBinding {
|
|||||||
|
|
||||||
export interface ClaudeDesktopModelRoute {
|
export interface ClaudeDesktopModelRoute {
|
||||||
model: string;
|
model: string;
|
||||||
|
/** @deprecated Claude Desktop ignores this in the model menu; kept only to read old configs. */
|
||||||
|
displayName?: string;
|
||||||
supports1m?: boolean;
|
supports1m?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user