AGC 支持 Anthropic 独立网关路由与后台协议选项

新增平台 Anthropic 网关路由 /api/llm/anthropic/{*path},复用账号 Router 凭据边界
OpenAI 侧规范路径改为 /api/llm/v1/responses 与 /api/llm/v1/chat/completions,旧路径保留兼容
baseUrl 不再携带路由与版本段,统一由协议自己拼 v1/<op>
cc 执行器改走平台网关,ANTHROPIC_AUTH_TOKEN 复用平台会话,不再直连 Router
后台模型目录 agentMode 增加 anthropic,管理页与客户端契约同步
补齐三条路由埋点与静态段白名单,新增三条回归测试
同步后端架构文档、模型别名文档与排障记忆
This commit is contained in:
kdletters
2026-10-01 19:41:08 +08:00
parent 44f415d057
commit d1f789cc10
14 changed files with 582 additions and 13 deletions
+1 -1
View File
@@ -1075,7 +1075,7 @@ export interface AdminRechargeRefundActionResponse {
export interface AdminWalletRestrictionResponse {
wallet: AdminProfileWalletPayload;
}
export type AdminAgcAgentMode = 'codex' | 'cc';
export type AdminAgcAgentMode = 'codex' | 'cc' | 'anthropic';
export interface AdminAgcModel {
id: string;
@@ -226,6 +226,7 @@ export function AdminAgcModelsPage({
>
<option value="codex">Codex</option>
<option value="cc">Claude Code</option>
<option value="anthropic">Anthropic 协议</option>
</select>
</td>
<td>
@@ -352,7 +352,21 @@ fn copy_env(command: &mut tokio::process::Command, name: &str) {
fn claude_base_url(llm: Option<&GameCreatorLlmConfig>) -> Option<String> {
std::env::var("ANTHROPIC_BASE_URL")
.ok()
.or_else(|| llm.map(|value| value.base_url.trim_end_matches('/').to_string()))
.or_else(|| {
let llm = llm?;
// Official models never talk to the Router directly: the account
// Router credential lives server-side, so the Claude sidecar goes
// through the platform gateway and appends `/v1/messages` itself.
if !llm.custom_enabled {
if let Some(session) = crate::platform_session::current_platform_session() {
return Some(format!(
"{}/api/llm/anthropic",
session.api_base_url.trim_end_matches('/')
));
}
}
Some(llm.base_url.trim_end_matches('/').to_string())
})
.map(|value| {
value
.trim_end_matches('/')
@@ -2351,7 +2351,7 @@ impl CodexAppServerConnection {
)
})?;
effective_llm.base_url =
format!("{}/api/llm", session.api_base_url.trim_end_matches('/'));
format!("{}/api/llm/v1", session.api_base_url.trim_end_matches('/'));
effective_llm.api_key.clear();
// Only the platform catalog identifier reaches Codex. api-server
// validates it and resolves the actual upstream model.
@@ -2619,7 +2619,7 @@ impl CodexAppServerConnection {
..
} => (
Some((
format!("{}/api/llm", api_base_url.trim_end_matches('/')),
format!("{}/api/llm/v1", api_base_url.trim_end_matches('/')),
access_token.clone(),
)),
true,
@@ -1098,7 +1098,8 @@ fn select_game_creator_model_inner(
if let Some(agent_mode) = catalog_agent_mode {
config.agent_mode = match agent_mode.trim() {
"codex" => GAME_CREATOR_AGENT_MODE_CODEX_APP_SERVER.to_string(),
"cc" => GAME_CREATOR_AGENT_MODE_CLAUDE_CODE_CLI.to_string(),
// `cc` 是 Claude 执行器的历史别名,`anthropic` 是同一执行器的显式协议值。
"cc" | "anthropic" => GAME_CREATOR_AGENT_MODE_CLAUDE_CODE_CLI.to_string(),
value => return Err(format!("模型 Agent 模式无效:{value}")),
};
}
@@ -222,7 +222,8 @@ fn build_game_creator_official_platform_llm_config(
if api_base_url.is_empty() {
return Err("登录态缺少 API Server 地址".to_string());
}
let proxy_base_url = format!("{api_base_url}/api/llm");
// baseUrl 不带路由与版本段,OpenAI 协议由 platform-llm 自己拼 `v1/<op>`。
let proxy_base_url = format!("{api_base_url}/api/llm/v1");
LlmConfig::new(
LlmProvider::OpenAiCompatible,
proxy_base_url,
@@ -3,7 +3,7 @@ import { resolveTauriInvoke } from '../app/tauri';
export type ClientLlmModel = {
displayName: string;
id: string;
agentMode: 'codex' | 'cc';
agentMode: 'codex' | 'cc' | 'anthropic';
};
export type ClientLlmModelCatalog = {