mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
35 KiB
35 KiB
Claude Provider 插件文档
版本: 1.0.0 仓库:
aiclientproxy/claude-provider类型: OAuth Provider Plugin
一、概述
1.1 插件简介
Claude Provider 是 Lime 的综合性 Anthropic/Claude 插件,支持 多种认证方式 访问 Claude 模型。无论是官方 OAuth、Claude Code、Console、AWS Bedrock 还是第三方中转服务,都可以通过此插件统一管理。
1.2 支持的认证方式
| 认证方式 | 说明 | 适用场景 |
|---|---|---|
| OAuth | 标准 OAuth 2.0 + PKCE | Claude.ai 个人账户 |
| Claude Code | Claude Code CLI 认证 | 开发者工具 |
| Console | Anthropic Console OAuth | 企业/团队账户 |
| Setup Token | 只读推理 Token | 最小权限场景 |
| Bedrock | AWS Bedrock Claude | AWS 云服务 |
| CCR | 第三方中转服务 | 自定义 API 端点 |
1.3 核心能力
| 能力 | 说明 |
|---|---|
| 多认证统一管理 | 一个插件管理所有 Claude 访问方式 |
| 自动 Token 刷新 | OAuth 类型自动刷新,提前 5 分钟 |
| PKCE 安全 | OAuth 使用 PKCE 流程确保安全 |
| Cookie 快速授权 | 使用 sessionKey 自动完成 OAuth |
| 凭证加密存储 | AES-256 加密敏感信息 |
| 健康检查 | 凭证池级别健康监控 |
1.4 支持的模型
| 模型 | 说明 |
|---|---|
claude-opus-4-20250514 |
Claude Opus 4 最新版 |
claude-opus-4-5-20251101 |
Claude Opus 4.5 |
claude-sonnet-4-5-20250929 |
Claude Sonnet 4.5 |
claude-sonnet-4-20250514 |
Claude Sonnet 4 |
claude-haiku-3-5-20241022 |
Claude Haiku 3.5 |
二、插件架构
2.1 项目结构
claude-provider/
├── plugin/
│ ├── plugin.json # 插件元数据
│ └── config.json # 默认配置
│
├── src-tauri/src/ # 后端 Rust 代码
│ ├── lib.rs # 插件入口
│ ├── commands.rs # Tauri 命令
│ ├── provider.rs # ClaudeProvider 核心实现
│ ├── auth/ # 认证模块
│ │ ├── mod.rs
│ │ ├── oauth.rs # OAuth 2.0 + PKCE
│ │ ├── claude_code.rs # Claude Code 认证
│ │ ├── console.rs # Console OAuth
│ │ ├── setup_token.rs # Setup Token
│ │ ├── bedrock.rs # AWS Bedrock
│ │ └── ccr.rs # 第三方中转
│ ├── credentials.rs # 凭证管理
│ ├── token_refresh.rs # Token 刷新
│ └── api/ # API 调用
│ ├── mod.rs
│ ├── anthropic.rs # Anthropic API
│ └── bedrock.rs # Bedrock API
│
├── src/ # 前端 React UI
│ ├── index.tsx # 插件 UI 入口
│ ├── components/
│ │ ├── CredentialList.tsx # 凭证列表
│ │ ├── CredentialCard.tsx # 凭证卡片
│ │ ├── AuthMethodTabs.tsx # 认证方式选择
│ │ ├── OAuthForm.tsx # OAuth 表单
│ │ ├── ClaudeCodeForm.tsx # Claude Code 表单
│ │ ├── ConsoleForm.tsx # Console 表单
│ │ ├── BedrockForm.tsx # Bedrock 表单
│ │ ├── CCRForm.tsx # CCR 表单
│ │ └── SettingsPanel.tsx # 插件设置
│ └── types/
│ └── index.ts # 类型定义
│
└── .github/
└── workflows/
└── release.yml # 自动构建发布
2.2 plugin.json
{
"name": "claude-provider",
"version": "1.0.0",
"description": "Claude Provider - 支持 OAuth、Claude Code、Console、Bedrock、CCR 多种认证方式",
"author": "Lime Team",
"homepage": "https://github.com/aiclientproxy/claude-provider",
"license": "MIT",
"plugin_type": "oauth_provider",
"entry": "claude-provider-cli",
"min_lime_version": "1.0.0",
"provider": {
"id": "claude",
"display_name": "Claude (Anthropic)",
"target_protocol": "anthropic",
"supported_models": ["claude-*"],
"auth_types": ["oauth", "claude_code", "console", "setup_token", "bedrock", "ccr"],
"credential_schemas": {
"oauth": {
"type": "object",
"properties": {
"access_token": { "type": "string" },
"refresh_token": { "type": "string" },
"email": { "type": "string" },
"expire": { "type": "string" }
},
"required": ["access_token", "refresh_token"]
},
"claude_code": {
"type": "object",
"properties": {
"access_token": { "type": "string" },
"refresh_token": { "type": "string" },
"session_key": { "type": "string" }
}
},
"console": {
"type": "object",
"properties": {
"access_token": { "type": "string" },
"refresh_token": { "type": "string" },
"organization_id": { "type": "string" }
}
},
"setup_token": {
"type": "object",
"properties": {
"access_token": { "type": "string" }
},
"required": ["access_token"]
},
"bedrock": {
"type": "object",
"properties": {
"access_key_id": { "type": "string" },
"secret_access_key": { "type": "string" },
"session_token": { "type": "string" },
"region": { "type": "string", "default": "us-east-1" }
},
"required": ["access_key_id", "secret_access_key", "region"]
},
"ccr": {
"type": "object",
"properties": {
"api_key": { "type": "string" },
"base_url": { "type": "string" }
},
"required": ["api_key", "base_url"]
}
}
},
"binary": {
"binary_name": "claude-provider-cli",
"github_owner": "aiclientproxy",
"github_repo": "claude-provider",
"platform_binaries": {
"macos-arm64": "claude-provider-aarch64-apple-darwin",
"macos-x64": "claude-provider-x86_64-apple-darwin",
"linux-x64": "claude-provider-x86_64-unknown-linux-gnu",
"windows-x64": "claude-provider-x86_64-pc-windows-msvc.exe"
},
"checksum_file": "checksums.txt"
},
"ui": {
"surfaces": ["oauth_providers"],
"icon": "MessageSquare",
"title": "Claude Provider",
"entry": "dist/index.js",
"styles": "dist/styles.css",
"default_width": 950,
"default_height": 750,
"permissions": [
"database:read",
"database:write",
"http:request",
"crypto:encrypt",
"shell:open"
]
}
}
2.3 config.json
{
"enabled": true,
"timeout_ms": 60000,
"settings": {
"oauth": {
"client_id": "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
"auth_url": "https://claude.ai/oauth/authorize",
"token_url": "https://console.anthropic.com/v1/oauth/token",
"redirect_uri": "https://console.anthropic.com/oauth/code/callback",
"scopes": "org:create_api_key user:profile user:inference",
"scopes_setup": "user:inference"
},
"api": {
"base_url": "https://api.anthropic.com",
"version": "2023-06-01"
},
"bedrock": {
"default_region": "us-east-1",
"model_prefix": "us.anthropic."
},
"token_refresh": {
"auto_refresh": true,
"refresh_threshold_minutes": 5,
"max_retry": 3,
"retry_delay_ms": 1000
},
"encryption": {
"algorithm": "aes-256-cbc",
"key_derivation": "pbkdf2"
}
}
}
三、认证方式详解
3.1 OAuth 认证(标准 Claude.ai)
OAuth 配置
const CLAUDE_AUTH_URL: &str = "https://claude.ai/oauth/authorize";
const CLAUDE_TOKEN_URL: &str = "https://console.anthropic.com/v1/oauth/token";
const CLAUDE_CLIENT_ID: &str = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
const CLAUDE_REDIRECT_URI: &str = "https://console.anthropic.com/oauth/code/callback";
const CLAUDE_SCOPES: &str = "org:create_api_key user:profile user:inference";
PKCE 流程实现
use sha2::{Sha256, Digest};
use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
use rand::Rng;
/// 生成 OAuth 参数(PKCE)
pub fn generate_oauth_params() -> OAuthParams {
// 1. 生成随机 state
let state: [u8; 32] = rand::thread_rng().gen();
let state = URL_SAFE_NO_PAD.encode(&state);
// 2. 生成 code_verifier
let code_verifier: [u8; 32] = rand::thread_rng().gen();
let code_verifier = URL_SAFE_NO_PAD.encode(&code_verifier);
// 3. 计算 code_challenge = SHA256(code_verifier)
let mut hasher = Sha256::new();
hasher.update(code_verifier.as_bytes());
let code_challenge = URL_SAFE_NO_PAD.encode(hasher.finalize());
// 4. 构建授权 URL
let auth_url = format!(
"{}?response_type=code&client_id={}&redirect_uri={}&scope={}&state={}&code_challenge={}&code_challenge_method=S256",
CLAUDE_AUTH_URL,
CLAUDE_CLIENT_ID,
urlencoding::encode(CLAUDE_REDIRECT_URI),
urlencoding::encode(CLAUDE_SCOPES),
state,
code_challenge
);
OAuthParams {
auth_url,
code_verifier,
state,
code_challenge,
}
}
/// 交换授权码获取 Token
pub async fn exchange_authorization_code(
authorization_code: &str,
code_verifier: &str,
state: &str,
) -> Result<OAuthTokens> {
let response = reqwest::Client::new()
.post(CLAUDE_TOKEN_URL)
.json(&json!({
"client_id": CLAUDE_CLIENT_ID,
"grant_type": "authorization_code",
"code": authorization_code,
"redirect_uri": CLAUDE_REDIRECT_URI,
"code_verifier": code_verifier,
"state": state
}))
.send()
.await?;
let token_response: TokenResponse = response.json().await?;
Ok(OAuthTokens {
access_token: token_response.access_token,
refresh_token: token_response.refresh_token,
expires_at: Utc::now() + Duration::seconds(token_response.expires_in),
email: token_response.account.map(|a| a.email_address),
})
}
Cookie 快速授权
/// 使用 sessionKey 自动完成 OAuth 流程
pub async fn oauth_with_cookie(
session_key: &str,
is_setup_token: bool,
) -> Result<OAuthTokens> {
let client = reqwest::Client::new();
// 1. 获取组织信息
let orgs_response = client
.get("https://claude.ai/api/organizations")
.header("Cookie", format!("sessionKey={}", session_key))
.header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)")
.header("Origin", "https://claude.ai")
.header("Referer", "https://claude.ai/new")
.send()
.await?;
let organizations: Vec<Organization> = orgs_response.json().await?;
// 2. 选择具有 chat 能力的组织
let org = organizations
.iter()
.find(|o| o.capabilities.contains(&"chat".to_string()))
.ok_or(Error::NoValidOrganization)?;
// 3. 生成 OAuth 参数
let params = generate_oauth_params();
let scopes = if is_setup_token { CLAUDE_SCOPES_SETUP } else { CLAUDE_SCOPES };
// 4. 使用 Cookie 请求授权码
let auth_url = format!(
"{}?response_type=code&client_id={}&redirect_uri={}&scope={}&state={}&code_challenge={}&code_challenge_method=S256",
CLAUDE_AUTH_URL, CLAUDE_CLIENT_ID,
urlencoding::encode(CLAUDE_REDIRECT_URI),
urlencoding::encode(scopes),
params.state, params.code_challenge
);
let auth_response = client
.get(&auth_url)
.header("Cookie", format!("sessionKey={}", session_key))
.send()
.await?;
// 5. 解析回调中的授权码
let callback_url = auth_response.url().to_string();
let code = extract_code_from_url(&callback_url)?;
// 6. 交换 Token
exchange_authorization_code(&code, ¶ms.code_verifier, ¶ms.state).await
}
3.2 Claude Code 认证
/// Claude Code 凭证结构
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClaudeCodeCredentials {
pub access_token: String,
pub refresh_token: Option<String>,
pub session_key: Option<String>,
pub expires_at: Option<DateTime<Utc>>,
}
/// 默认凭证路径
const CLAUDE_CODE_CREDS_PATH: &str = "~/.claude/oauth_creds.json";
/// 从 Claude Code 配置加载凭证
pub fn load_claude_code_credentials() -> Result<ClaudeCodeCredentials> {
let path = expand_tilde(CLAUDE_CODE_CREDS_PATH);
let content = fs::read_to_string(&path)?;
let creds: ClaudeCodeCredentials = serde_json::from_str(&content)?;
Ok(creds)
}
3.3 Console OAuth(企业/团队)
/// Console OAuth 配置
const CONSOLE_AUTH_URL: &str = "https://console.anthropic.com/oauth/authorize";
const CONSOLE_TOKEN_URL: &str = "https://console.anthropic.com/v1/oauth/token";
/// Console 凭证结构
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConsoleCredentials {
pub access_token: String,
pub refresh_token: String,
pub organization_id: Option<String>,
pub organization_name: Option<String>,
pub expires_at: DateTime<Utc>,
}
/// Console OAuth 流程(与标准 OAuth 类似,但针对企业账户)
pub async fn console_oauth_flow(
authorization_code: &str,
code_verifier: &str,
) -> Result<ConsoleCredentials> {
// 与标准 OAuth 类似,但返回组织信息
let response = exchange_authorization_code(authorization_code, code_verifier).await?;
Ok(ConsoleCredentials {
access_token: response.access_token,
refresh_token: response.refresh_token,
organization_id: response.organization.map(|o| o.id),
organization_name: response.organization.map(|o| o.name),
expires_at: response.expires_at,
})
}
3.4 Setup Token(最小权限)
/// Setup Token - 只有推理权限,无 refresh_token
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SetupTokenCredentials {
pub access_token: String,
pub creds_type: String, // "claude_setup_token"
}
/// 使用 Cookie 获取 Setup Token
pub async fn get_setup_token(session_key: &str) -> Result<SetupTokenCredentials> {
let tokens = oauth_with_cookie(session_key, true /* is_setup_token */).await?;
Ok(SetupTokenCredentials {
access_token: tokens.access_token,
creds_type: "claude_setup_token".to_string(),
})
}
3.5 AWS Bedrock
/// AWS Bedrock 凭证结构
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BedrockCredentials {
pub access_key_id: String,
pub secret_access_key: String,
pub session_token: Option<String>,
pub region: String,
pub default_model: Option<String>,
}
/// Bedrock 模型映射
const BEDROCK_MODEL_MAP: &[(&str, &str)] = &[
("claude-opus-4-20250514", "us.anthropic.claude-opus-4-20250514-v1:0"),
("claude-sonnet-4-20250514", "us.anthropic.claude-sonnet-4-20250514-v1:0"),
("claude-sonnet-4-5-20250929", "us.anthropic.claude-sonnet-4-5-20250929-v1:0"),
("claude-haiku-3-5-20241022", "us.anthropic.claude-haiku-3-5-20241022-v1:0"),
];
/// Bedrock API 调用
pub async fn call_bedrock_api(
credentials: &BedrockCredentials,
request: &AnthropicRequest,
) -> Result<impl Stream<Item = Result<AnthropicSseEvent>>> {
// 1. 模型名映射
let model_id = map_to_bedrock_model(&request.model);
// 2. 构建 AWS 签名
let aws_credentials = AwsCredentials::new(
&credentials.access_key_id,
&credentials.secret_access_key,
credentials.session_token.as_deref(),
);
// 3. 调用 Bedrock API
let url = format!(
"https://bedrock-runtime.{}.amazonaws.com/model/{}/invoke-with-response-stream",
credentials.region,
model_id
);
let signed_request = sign_aws_request(
"POST",
&url,
&aws_credentials,
&credentials.region,
"bedrock",
&serde_json::to_vec(request)?,
)?;
// 4. 发送请求并返回流
let response = reqwest::Client::new()
.post(&url)
.headers(signed_request.headers)
.body(signed_request.body)
.send()
.await?;
Ok(parse_bedrock_stream(response.bytes_stream()))
}
3.6 CCR(第三方中转)
/// CCR(Custom Claude Relay)凭证结构
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CCRCredentials {
pub api_key: String,
pub base_url: String,
pub name: Option<String>,
}
/// CCR API 调用(直接转发)
pub async fn call_ccr_api(
credentials: &CCRCredentials,
request: &AnthropicRequest,
) -> Result<impl Stream<Item = Result<AnthropicSseEvent>>> {
let url = format!("{}/v1/messages", credentials.base_url);
let response = reqwest::Client::new()
.post(&url)
.header("x-api-key", &credentials.api_key)
.header("anthropic-version", "2023-06-01")
.header("Content-Type", "application/json")
.json(request)
.send()
.await?;
Ok(parse_anthropic_sse_stream(response.bytes_stream()))
}
四、Token 刷新机制
4.1 自动刷新逻辑
impl ClaudeProvider {
/// Token 刷新阈值(提前 5 分钟)
const REFRESH_THRESHOLD: Duration = Duration::minutes(5);
/// 检查 Token 是否需要刷新
pub fn needs_refresh(&self, credentials: &OAuthCredentials) -> bool {
if let Some(expires_at) = credentials.expires_at {
let now = Utc::now();
return now >= expires_at - Self::REFRESH_THRESHOLD;
}
true // 无过期时间则默认需要刷新
}
/// 刷新 Token(带重试)
pub async fn refresh_token_with_retry(
&self,
credentials: &mut OAuthCredentials,
max_retries: u32,
) -> Result<()> {
let mut last_error = None;
for attempt in 0..max_retries {
match self.refresh_token(credentials).await {
Ok(_) => return Ok(()),
Err(e) => {
last_error = Some(e);
// 指数退避
let delay = Duration::milliseconds(1000 * 2_i64.pow(attempt));
tokio::time::sleep(delay.to_std().unwrap()).await;
}
}
}
Err(last_error.unwrap())
}
/// 刷新 Token
async fn refresh_token(&self, credentials: &mut OAuthCredentials) -> Result<()> {
let refresh_token = credentials.refresh_token.as_ref()
.ok_or(Error::MissingRefreshToken)?;
let response = self.http_client
.post(CLAUDE_TOKEN_URL)
.json(&json!({
"client_id": CLAUDE_CLIENT_ID,
"grant_type": "refresh_token",
"refresh_token": refresh_token
}))
.send()
.await?;
if !response.status().is_success() {
let error_text = response.text().await?;
return Err(Error::TokenRefreshFailed(error_text));
}
let token_response: TokenResponse = response.json().await?;
// 更新凭证
credentials.access_token = token_response.access_token;
if let Some(new_refresh) = token_response.refresh_token {
credentials.refresh_token = Some(new_refresh);
}
credentials.expires_at = Some(Utc::now() + Duration::seconds(token_response.expires_in));
credentials.last_refresh = Some(Utc::now());
// 更新邮箱(如果有)
if let Some(account) = token_response.account {
credentials.email = Some(account.email_address);
}
Ok(())
}
}
五、前端 UI 实现
5.1 插件入口
// src/index.tsx
import { LimePluginSDK } from '@lime/plugin-sdk';
import { CredentialList } from './components/CredentialList';
import { AuthMethodTabs } from './components/AuthMethodTabs';
import { SettingsPanel } from './components/SettingsPanel';
interface PluginProps {
sdk: LimePluginSDK;
pluginId: string;
}
export default function ClaudeProviderUI({ sdk, pluginId }: PluginProps) {
const [view, setView] = useState<'list' | 'add' | 'settings'>('list');
const [credentials, setCredentials] = useState<Credential[]>([]);
useEffect(() => {
loadCredentials();
}, []);
const loadCredentials = async () => {
const result = await sdk.database.query<Credential>(
'SELECT * FROM plugin_credentials WHERE plugin_id = ? ORDER BY created_at DESC',
[pluginId]
);
setCredentials(result);
};
return (
<div className="claude-provider-ui">
<Header>
<Title>Claude Provider</Title>
<Subtitle>支持 OAuth、Claude Code、Console、Bedrock、CCR</Subtitle>
<Actions>
<Button onClick={() => setView('add')}>添加凭证</Button>
<Button onClick={() => setView('settings')}>设置</Button>
</Actions>
</Header>
{view === 'list' && (
<CredentialList
credentials={credentials}
onRefresh={loadCredentials}
sdk={sdk}
/>
)}
{view === 'add' && (
<AuthMethodTabs
sdk={sdk}
onSuccess={() => {
loadCredentials();
setView('list');
}}
onCancel={() => setView('list')}
/>
)}
{view === 'settings' && (
<SettingsPanel
sdk={sdk}
pluginId={pluginId}
onClose={() => setView('list')}
/>
)}
</div>
);
}
5.2 认证方式选择
// src/components/AuthMethodTabs.tsx
type AuthMethod = 'oauth' | 'claude_code' | 'console' | 'setup_token' | 'bedrock' | 'ccr';
interface AuthMethodTabsProps {
sdk: LimePluginSDK;
onSuccess: () => void;
onCancel: () => void;
}
export function AuthMethodTabs({ sdk, onSuccess, onCancel }: AuthMethodTabsProps) {
const [method, setMethod] = useState<AuthMethod>('oauth');
return (
<div className="auth-method-tabs">
<Tabs value={method} onChange={setMethod}>
<Tab value="oauth">
<Icon name="Key" />
OAuth
</Tab>
<Tab value="claude_code">
<Icon name="Terminal" />
Claude Code
</Tab>
<Tab value="console">
<Icon name="Building" />
Console
</Tab>
<Tab value="setup_token">
<Icon name="Lock" />
Setup Token
</Tab>
<Tab value="bedrock">
<Icon name="Cloud" />
Bedrock
</Tab>
<Tab value="ccr">
<Icon name="Server" />
CCR
</Tab>
</Tabs>
<div className="tab-content">
{method === 'oauth' && <OAuthForm sdk={sdk} onSuccess={onSuccess} />}
{method === 'claude_code' && <ClaudeCodeForm sdk={sdk} onSuccess={onSuccess} />}
{method === 'console' && <ConsoleForm sdk={sdk} onSuccess={onSuccess} />}
{method === 'setup_token' && <SetupTokenForm sdk={sdk} onSuccess={onSuccess} />}
{method === 'bedrock' && <BedrockForm sdk={sdk} onSuccess={onSuccess} />}
{method === 'ccr' && <CCRForm sdk={sdk} onSuccess={onSuccess} />}
</div>
<FormActions>
<Button variant="secondary" onClick={onCancel}>取消</Button>
</FormActions>
</div>
);
}
5.3 OAuth 表单
// src/components/OAuthForm.tsx
type OAuthMode = 'browser' | 'cookie' | 'file';
export function OAuthForm({ sdk, onSuccess }: FormProps) {
const [mode, setMode] = useState<OAuthMode>('cookie');
const [sessionKey, setSessionKey] = useState('');
const [authUrl, setAuthUrl] = useState('');
const [loading, setLoading] = useState(false);
const handleCookieAuth = async () => {
setLoading(true);
try {
await sdk.http.request('/api/claude/oauth/cookie', {
method: 'POST',
body: JSON.stringify({ sessionKey, isSetupToken: false }),
});
sdk.notification.success('OAuth 认证成功');
onSuccess();
} catch (error) {
sdk.notification.error(`认证失败: ${error.message}`);
} finally {
setLoading(false);
}
};
const handleBrowserAuth = async () => {
setLoading(true);
try {
const result = await sdk.http.request('/api/claude/oauth/start');
setAuthUrl(result.authUrl);
await sdk.shell.open(result.authUrl);
// 等待回调
const credential = await sdk.http.request('/api/claude/oauth/callback/wait', {
timeout: 120000,
});
sdk.notification.success('OAuth 认证成功');
onSuccess();
} catch (error) {
sdk.notification.error(`认证失败: ${error.message}`);
} finally {
setLoading(false);
}
};
return (
<div className="oauth-form">
<Tabs value={mode} onChange={setMode}>
<Tab value="cookie">Cookie 快速授权</Tab>
<Tab value="browser">浏览器授权</Tab>
<Tab value="file">导入文件</Tab>
</Tabs>
{mode === 'cookie' && (
<div className="cookie-mode">
<FormField>
<Label>Session Key</Label>
<TextArea
rows={3}
value={sessionKey}
onChange={(e) => setSessionKey(e.target.value)}
placeholder="从 claude.ai Cookie 中获取 sessionKey 值"
/>
<HelpText>
打开 claude.ai → F12 开发者工具 → Application → Cookies → 复制 sessionKey
</HelpText>
</FormField>
<Button onClick={handleCookieAuth} loading={loading}>
使用 Cookie 授权
</Button>
</div>
)}
{mode === 'browser' && (
<div className="browser-mode">
<Alert type="info">
点击下方按钮将打开浏览器进行 OAuth 授权
</Alert>
{authUrl && (
<div className="auth-url">
<Label>授权 URL</Label>
<CodeBlock>{authUrl}</CodeBlock>
<Button size="small" onClick={() => navigator.clipboard.writeText(authUrl)}>
复制
</Button>
</div>
)}
<Button onClick={handleBrowserAuth} loading={loading}>
{loading ? '等待授权...' : '打开浏览器授权'}
</Button>
</div>
)}
{mode === 'file' && (
<FileImportForm
sdk={sdk}
onSuccess={onSuccess}
defaultPath="~/.claude/oauth_creds.json"
/>
)}
</div>
);
}
5.4 Bedrock 表单
// src/components/BedrockForm.tsx
export function BedrockForm({ sdk, onSuccess }: FormProps) {
const [form, setForm] = useState({
accessKeyId: '',
secretAccessKey: '',
sessionToken: '',
region: 'us-east-1',
name: '',
});
const [loading, setLoading] = useState(false);
const handleSubmit = async () => {
setLoading(true);
try {
await sdk.http.request('/api/claude/bedrock/add', {
method: 'POST',
body: JSON.stringify(form),
});
sdk.notification.success('Bedrock 凭证添加成功');
onSuccess();
} catch (error) {
sdk.notification.error(`添加失败: ${error.message}`);
} finally {
setLoading(false);
}
};
return (
<div className="bedrock-form">
<Alert type="info">
使用 AWS IAM 凭证访问 Bedrock 上的 Claude 模型
</Alert>
<FormField>
<Label>凭证名称(可选)</Label>
<Input
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
placeholder="我的 Bedrock 凭证"
/>
</FormField>
<FormField>
<Label>Access Key ID *</Label>
<Input
value={form.accessKeyId}
onChange={(e) => setForm({ ...form, accessKeyId: e.target.value })}
placeholder="AKIAIOSFODNN7EXAMPLE"
/>
</FormField>
<FormField>
<Label>Secret Access Key *</Label>
<Input
type="password"
value={form.secretAccessKey}
onChange={(e) => setForm({ ...form, secretAccessKey: e.target.value })}
placeholder="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
/>
</FormField>
<FormField>
<Label>Session Token(可选)</Label>
<TextArea
rows={2}
value={form.sessionToken}
onChange={(e) => setForm({ ...form, sessionToken: e.target.value })}
placeholder="临时凭证的 Session Token"
/>
<HelpText>如果使用 STS 临时凭证,需要填写此项</HelpText>
</FormField>
<FormField>
<Label>Region *</Label>
<Select
value={form.region}
onChange={(value) => setForm({ ...form, region: value })}
>
<Option value="us-east-1">US East (N. Virginia)</Option>
<Option value="us-west-2">US West (Oregon)</Option>
<Option value="eu-west-1">EU (Ireland)</Option>
<Option value="ap-northeast-1">Asia Pacific (Tokyo)</Option>
</Select>
</FormField>
<Button onClick={handleSubmit} loading={loading}>
添加 Bedrock 凭证
</Button>
</div>
);
}
5.5 CCR 表单
// src/components/CCRForm.tsx
export function CCRForm({ sdk, onSuccess }: FormProps) {
const [form, setForm] = useState({
apiKey: '',
baseUrl: '',
name: '',
});
const [loading, setLoading] = useState(false);
const handleSubmit = async () => {
setLoading(true);
try {
await sdk.http.request('/api/claude/ccr/add', {
method: 'POST',
body: JSON.stringify(form),
});
sdk.notification.success('CCR 凭证添加成功');
onSuccess();
} catch (error) {
sdk.notification.error(`添加失败: ${error.message}`);
} finally {
setLoading(false);
}
};
return (
<div className="ccr-form">
<Alert type="info">
配置第三方 Claude 中转服务(Custom Claude Relay)
</Alert>
<FormField>
<Label>凭证名称(可选)</Label>
<Input
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
placeholder="我的 CCR 服务"
/>
</FormField>
<FormField>
<Label>API Key *</Label>
<Input
type="password"
value={form.apiKey}
onChange={(e) => setForm({ ...form, apiKey: e.target.value })}
placeholder="cr_xxxxxxxxxxxxxxxx"
/>
</FormField>
<FormField>
<Label>Base URL *</Label>
<Input
value={form.baseUrl}
onChange={(e) => setForm({ ...form, baseUrl: e.target.value })}
placeholder="https://your-relay-service.com"
/>
<HelpText>不需要包含 /v1/messages 路径</HelpText>
</FormField>
<Button onClick={handleSubmit} loading={loading}>
添加 CCR 凭证
</Button>
</div>
);
}
5.6 凭证卡片
// src/components/CredentialCard.tsx
const AUTH_TYPE_LABELS: Record<string, string> = {
oauth: 'OAuth',
claude_code: 'Claude Code',
console: 'Console',
setup_token: 'Setup Token',
bedrock: 'Bedrock',
ccr: 'CCR',
};
const AUTH_TYPE_COLORS: Record<string, string> = {
oauth: 'blue',
claude_code: 'purple',
console: 'green',
setup_token: 'yellow',
bedrock: 'orange',
ccr: 'gray',
};
export function CredentialCard({ credential, onRefresh, onDelete }: CredentialCardProps) {
const data = JSON.parse(credential.credential_data);
const authType = data.auth_type || 'oauth';
const isHealthy = credential.status === 'active';
return (
<Card className={`credential-card ${isHealthy ? 'healthy' : 'unhealthy'}`}>
<CardHeader>
<div className="status-indicator">
<StatusDot status={isHealthy ? 'green' : 'red'} />
<span>{isHealthy ? '健康' : '异常'}</span>
</div>
<Badge color={AUTH_TYPE_COLORS[authType]}>
{AUTH_TYPE_LABELS[authType]}
</Badge>
</CardHeader>
<CardBody>
<div className="info-row">
<label>名称</label>
<span>{credential.name || '未命名'}</span>
</div>
{data.email && (
<div className="info-row">
<label>邮箱</label>
<span>{data.email}</span>
</div>
)}
{data.region && (
<div className="info-row">
<label>区域</label>
<span>{data.region}</span>
</div>
)}
{data.base_url && (
<div className="info-row">
<label>Base URL</label>
<span className="truncate">{data.base_url}</span>
</div>
)}
{data.expires_at && (
<div className="info-row">
<label>过期时间</label>
<span>{formatDate(data.expires_at)}</span>
</div>
)}
</CardBody>
<CardFooter>
{['oauth', 'claude_code', 'console'].includes(authType) && (
<Button size="small" onClick={onRefresh}>刷新 Token</Button>
)}
<Button size="small" variant="danger" onClick={onDelete}>删除</Button>
</CardFooter>
</Card>
);
}
六、凭证文件格式
6.1 OAuth 凭证
{
"access_token": "sk-ant-...",
"refresh_token": "re_...",
"email": "user@example.com",
"expire": "2025-01-05T12:34:56+00:00",
"last_refresh": "2025-01-05T11:34:56+00:00",
"type": "claude_oauth"
}
6.2 Bedrock 凭证
{
"access_key_id": "AKIAIOSFODNN7EXAMPLE",
"secret_access_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"session_token": "...",
"region": "us-east-1",
"type": "bedrock"
}
6.3 CCR 凭证
{
"api_key": "cr_xxxxxxxxxxxxxxxx",
"base_url": "https://your-relay-service.com",
"type": "ccr"
}
七、错误处理
7.1 错误类型
| 错误类型 | 说明 | 处理方式 |
|---|---|---|
MissingRefreshToken |
无刷新 Token(Setup Token) | 重新授权 |
TokenRefreshFailed |
Token 刷新失败 | 重试或重新授权 |
InvalidCredentials |
凭证无效 | 检查凭证配置 |
AuthorizationRevoked |
授权已撤销 | 重新授权 |
BedrockAccessDenied |
Bedrock 访问被拒 | 检查 IAM 权限 |
CCRConnectionError |
CCR 连接失败 | 检查 Base URL |
八、开发指南
8.1 本地开发
# 克隆仓库
git clone https://github.com/aiclientproxy/claude-provider.git
cd claude-provider
# 安装依赖
pnpm install
cd src-tauri && cargo build
# 前端开发
pnpm dev
# 后端开发
cargo watch -x run
8.2 测试
# 单元测试
cargo test
# OAuth 流程测试
cargo test --test oauth
# 前端测试
pnpm test
附录
A. 环境变量
| 变量 | 说明 | 默认值 |
|---|---|---|
CLAUDE_CLIENT_ID |
OAuth Client ID | 内置 |
CLAUDE_DEBUG |
调试模式 | false |
CLAUDE_TIMEOUT_MS |
请求超时 | 60000 |