Merge pull request #221 from leonaii/main

feat(orchids): 集成 Orchids 平台作为新的 Claude 提供商
This commit is contained in:
何夕2077 2026-01-13 13:25:08 +08:00 committed by GitHub
commit 51992f30dc
17 changed files with 1062 additions and 13 deletions

6
.gitignore vendored
View file

@ -12,4 +12,8 @@ usage-cache.json
*_oauth_creds.json
*-auth-token.json
api-potluck-keys.json
api-potluck-data.json
api-potluck-data.json
# Orchids credentials
configs/orchids/*_orchids_creds/
configs/orchids/*.json
!configs/orchids/*.example

View file

@ -135,6 +135,21 @@
"lastErrorTime": null
}
],
"claude-orchids-oauth": [
{
"customName": "Orchids节点1",
"ORCHIDS_CREDS_FILE_PATH": "./configs/orchids/orchids_creds.json",
"uuid": "xxx-xxx-xxx",
"checkModelName": "claude-sonnet-4-5",
"checkHealth": false,
"isHealthy": true,
"isDisabled": false,
"lastUsed": null,
"usageCount": 0,
"errorCount": 0,
"lastErrorTime": null
}
],
"openai-qwen-oauth": [
{
"customName": "Qwen OAuth节点",

26
package-lock.json generated
View file

@ -18,7 +18,8 @@
"open": "^10.2.0",
"socks-proxy-agent": "^8.0.5",
"undici": "^7.12.0",
"uuid": "^11.1.0"
"uuid": "^11.1.0",
"ws": "^8.19.0"
},
"devDependencies": {
"@babel/preset-env": "^7.28.0",
@ -100,6 +101,7 @@
"integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
@ -2958,6 +2960,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001726",
"electron-to-chromium": "^1.5.173",
@ -6590,6 +6593,27 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
}
},
"node_modules/ws": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/wsl-utils": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz",

View file

@ -14,7 +14,8 @@
"open": "^10.2.0",
"socks-proxy-agent": "^8.0.5",
"undici": "^7.12.0",
"uuid": "^11.1.0"
"uuid": "^11.1.0",
"ws": "^8.19.0"
},
"devDependencies": {
"@babel/preset-env": "^7.28.0",

View file

@ -2001,3 +2001,282 @@ export async function importAwsCredentials(credentials, skipDuplicateCheck = fal
}
}
// ============================================================================
// Orchids OAuth 配置和处理函数
// ============================================================================
/**
* Orchids OAuth 配置
*/
const ORCHIDS_OAUTH_CONFIG = {
// Clerk Token 端点
clerkTokenEndpoint: 'https://clerk.orchids.app/v1/client/sessions/{sessionId}/tokens',
clerkJsVersion: '5.114.0',
// 凭据存储
credentialsDir: 'orchids',
credentialsFile: 'orchids_creds.json',
// 日志前缀
logPrefix: '[Orchids Auth]'
};
/**
* 解析 Orchids 凭据字符串简化版
* 只需要 __client JWT 即可其他参数通过 Clerk API 自动获取
*
* 支持的格式:
* 1. JWT 字符串: "eyJhbGciOiJSUzI1NiJ9..." ( payload 中提取 rotating_token)
* 2. __client=xxx 格式: "__client=eyJhbGciOiJSUzI1NiJ9..."
* 3. 完整 Cookies 格式兼容旧版: "__client=xxx; __session=xxx"
* 4. JWT|xxx 格式兼容旧版
*
* @param {string} inputString - 输入字符串
* @returns {Object} 解析后的凭据数据
*/
function parseOrchidsCredentials(inputString) {
if (!inputString || typeof inputString !== 'string') {
throw new Error('Invalid input string');
}
const trimmedInput = inputString.trim();
// 格式1: 纯 JWT 字符串(三段式,以点分隔)
if (trimmedInput.split('.').length === 3 && !trimmedInput.includes('=') && !trimmedInput.includes('|')) {
console.log('[Orchids Auth] Detected pure JWT format');
// 尝试从 JWT payload 中提取 rotating_token
let rotatingToken = null;
try {
const parts = trimmedInput.split('.');
if (parts.length === 3) {
// 解码 JWT payload (Base64URL -> Base64 -> JSON)
let payloadBase64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
// 添加 padding
while (payloadBase64.length % 4) {
payloadBase64 += '=';
}
const payloadJson = Buffer.from(payloadBase64, 'base64').toString('utf8');
const payload = JSON.parse(payloadJson);
if (payload.rotating_token) {
rotatingToken = payload.rotating_token;
console.log('[Orchids Auth] Extracted rotating_token from JWT payload');
}
}
} catch (e) {
console.warn('[Orchids Auth] Failed to extract rotating_token from JWT payload:', e.message);
}
return {
type: 'jwt',
clientJwt: trimmedInput,
rotatingToken: rotatingToken
};
}
// 格式2: __client=xxx 格式(可能包含或不包含 __session
if (trimmedInput.includes('__client=')) {
const clientMatch = trimmedInput.match(/__client=([^;]+)/);
if (clientMatch) {
const clientValue = clientMatch[1].trim();
// 处理可能的 | 分隔符(如 JWT|rotating_token
let jwtPart = clientValue;
let rotatingToken = null;
if (clientValue.includes('|')) {
const parts = clientValue.split('|');
jwtPart = parts[0];
rotatingToken = parts[1] || null;
}
if (jwtPart.split('.').length === 3) {
console.log('[Orchids Auth] Detected __client cookie format');
return {
type: 'jwt',
clientJwt: jwtPart,
rotatingToken: rotatingToken
};
}
}
throw new Error('Invalid __client value. Expected a valid JWT.');
}
// 格式3: JWT|rotating_token 格式
if (trimmedInput.includes('|')) {
const parts = trimmedInput.split('|');
if (parts.length >= 1) {
const jwtPart = parts[0].trim();
const rotatingToken = parts.length >= 2 ? parts[1].trim() : null;
if (jwtPart.split('.').length === 3) {
console.log('[Orchids Auth] Detected JWT|rotating_token format');
return {
type: 'jwt',
clientJwt: jwtPart,
rotatingToken: rotatingToken
};
}
}
}
throw new Error('Invalid format. Please provide the __client cookie value (JWT format). Example: eyJhbGciOiJSUzI1NiJ9...');
}
/**
* 解析 Orchids JWT Token 字符串 (保留用于向后兼容)
* @deprecated 请使用 parseOrchidsCredentials
* 格式: JWT|rotating_token
* JWT 包含 id (client_id) rotating_token
* @param {string} tokenString - 完整的 token 字符串
* @returns {Object} 解析后的 token 数据
*/
function parseOrchidsToken(tokenString) {
const result = parseOrchidsCredentials(tokenString);
if (result.type === 'legacy') {
return {
clientId: result.clientId,
rotatingToken: result.rotatingToken,
jwt: result.jwt,
rawPayload: result.rawPayload
};
}
// 对于新格式,返回兼容的结构
return {
clientId: null,
rotatingToken: result.clientValue,
jwt: null,
rawPayload: null
};
}
/**
* Clerk 获取 session token
* @param {string} sessionId - Clerk session ID
* @param {string} cookies - Cookie 字符串
* @returns {Promise<string>} JWT token
*/
async function getClerkSessionToken(sessionId, cookies) {
const tokenUrl = ORCHIDS_OAUTH_CONFIG.clerkTokenEndpoint
.replace('{sessionId}', sessionId) +
`?_clerk_js_version=${ORCHIDS_OAUTH_CONFIG.clerkJsVersion}`;
const response = await fetch(tokenUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Cookie': cookies,
'Origin': 'https://www.orchids.app'
}
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Clerk token request failed: ${response.status} ${errorText}`);
}
const data = await response.json();
return data.jwt;
}
/**
* 导入 Orchids 凭据并生成凭据文件简化版
* 只需要 __client JWT其他参数在运行时通过 Clerk API 自动获取
*
* @param {string} inputString - __client JWT 字符串
* @param {Object} options - 额外选项
* - workingDir: 默认工作目录
* @returns {Promise<Object>} 导入结果
*/
export async function importOrchidsToken(inputString, options = {}) {
try {
console.log(`${ORCHIDS_OAUTH_CONFIG.logPrefix} Parsing Orchids credentials (simplified)...`);
// 解析凭据 - 只提取 clientJwt
const credData = parseOrchidsCredentials(inputString);
if (!credData.clientJwt) {
throw new Error('Failed to extract clientJwt from input');
}
// 凭据数据 - 保存 clientJwt 和可选的 rotatingToken
const credentialsData = {
// 核心字段__client JWT必需的凭据
clientJwt: credData.clientJwt,
// 导入时间
importedAt: new Date().toISOString()
};
// 如果存在 rotatingToken也保存它可选备用
if (credData.rotatingToken) {
credentialsData.rotatingToken = credData.rotatingToken;
console.log(`${ORCHIDS_OAUTH_CONFIG.logPrefix} rotatingToken also saved for future use.`);
}
// 生成文件路径: configs/orchids/{timestamp}_orchids_creds/{timestamp}_orchids_creds.json
const timestamp = Date.now();
const folderName = `${timestamp}_orchids_creds`;
const targetDir = path.join(process.cwd(), 'configs', ORCHIDS_OAUTH_CONFIG.credentialsDir, folderName);
await fs.promises.mkdir(targetDir, { recursive: true });
const filename = `${folderName}.json`;
const credPath = path.join(targetDir, filename);
await fs.promises.writeFile(credPath, JSON.stringify(credentialsData, null, 2));
const relativePath = path.relative(process.cwd(), credPath);
console.log(`${ORCHIDS_OAUTH_CONFIG.logPrefix} Credentials saved to: ${relativePath}`);
console.log(`${ORCHIDS_OAUTH_CONFIG.logPrefix} Only clientJwt is stored. Session info will be fetched at runtime.`);
// 广播事件
broadcastEvent('oauth_success', {
provider: 'claude-orchids-oauth',
relativePath: relativePath,
timestamp: new Date().toISOString()
});
// 自动关联新生成的凭据到 Pools
await autoLinkProviderConfigs(CONFIG);
return {
success: true,
path: relativePath,
message: 'Credentials imported successfully. Session info will be fetched at runtime via Clerk API.'
};
} catch (error) {
console.error(`${ORCHIDS_OAUTH_CONFIG.logPrefix} Token import failed:`, error);
return {
success: false,
error: error.message
};
}
}
/**
* 处理 Orchids OAuth手动导入模式 - 简化版
* 只需要 __client JWT其他参数自动获取
* @param {Object} currentConfig - 当前配置对象
* @param {Object} options - 额外选项
* @returns {Promise<Object>} 返回导入说明
*/
export async function handleOrchidsOAuth(currentConfig, options = {}) {
// Orchids 使用简化的手动导入模式
// 只需要 __client cookie 的值
return {
authUrl: null,
authInfo: {
provider: 'claude-orchids-oauth',
method: 'manual-import',
instructions: [
'1. 登录 Orchids 平台 (https://orchids.app)',
'2. 打开浏览器开发者工具 (F12)',
'3. 切换到 Application > Cookies > https://orchids.app',
'4. 找到 __client 并复制其值(一个长的 JWT 字符串)',
'5. 使用 "导入 Token" 功能粘贴该值'
],
tokenFormat: 'eyJhbGciOiJSUzI1NiJ9...',
example: 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImNsaWVudF8uLi4',
note: '只需要 __client 的值即可sessionId 等参数会自动获取'
}
};
}

View file

@ -4,6 +4,7 @@ import { AntigravityApiService } from './gemini/antigravity-core.js'; // 导入A
import { OpenAIApiService } from './openai/openai-core.js'; // 导入OpenAIApiService
import { ClaudeApiService } from './claude/claude-core.js'; // 导入ClaudeApiService
import { KiroApiService } from './claude/claude-kiro.js'; // 导入KiroApiService
import { OrchidsApiService } from './claude/claude-orchids.js'; // 导入OrchidsApiService
import { QwenApiService } from './openai/qwen-core.js'; // 导入QwenApiService
import { IFlowApiService } from './openai/iflow-core.js'; // 导入IFlowApiService
import { MODEL_PROVIDER } from '../utils/common.js'; // 导入 MODEL_PROVIDER
@ -318,6 +319,50 @@ export class KiroApiServiceAdapter extends ApiServiceAdapter {
}
}
// Orchids API 服务适配器
export class OrchidsApiServiceAdapter extends ApiServiceAdapter {
constructor(config) {
super();
this.orchidsApiService = new OrchidsApiService(config);
}
async generateContent(model, requestBody) {
if (!this.orchidsApiService.isInitialized) {
await this.orchidsApiService.initialize();
}
return this.orchidsApiService.generateContent(model, requestBody);
}
async *generateContentStream(model, requestBody) {
if (!this.orchidsApiService.isInitialized) {
await this.orchidsApiService.initialize();
}
yield* this.orchidsApiService.generateContentStream(model, requestBody);
}
async listModels() {
return this.orchidsApiService.listModels();
}
async refreshToken() {
if (this.orchidsApiService.isExpiryDateNear()) {
return this.orchidsApiService.initializeAuth(true);
}
return Promise.resolve();
}
async getUsageLimits() {
if (!this.orchidsApiService.isInitialized) {
await this.orchidsApiService.initialize();
}
return this.orchidsApiService.getUsageLimits();
}
countTokens(requestBody) {
return this.orchidsApiService.countTokens(requestBody);
}
}
// Qwen API 服务适配器
export class QwenApiServiceAdapter extends ApiServiceAdapter {
constructor(config) {
@ -434,6 +479,9 @@ export function getServiceAdapter(config) {
case MODEL_PROVIDER.IFLOW_API:
serviceInstances[providerKey] = new IFlowApiServiceAdapter(config);
break;
case MODEL_PROVIDER.ORCHIDS_API:
serviceInstances[providerKey] = new OrchidsApiServiceAdapter(config);
break;
default:
throw new Error(`Unsupported model provider: ${provider}`);
}

View file

@ -33,6 +33,14 @@ export const PROVIDER_MODELS = {
'claude-sonnet-4-20250514',
'claude-3-7-sonnet-20250219'
],
'claude-orchids-oauth': [
'claude-sonnet-4-5',
'claude-opus-4-5',
'claude-haiku-4-5',
'gemini-3',
'gemini-3-flash',
'gpt-5.2'
],
'openai-custom': [],
'openaiResponses-custom': [],
'openai-qwen-oauth': [

View file

@ -268,6 +268,11 @@ export async function handleUIApiRequests(method, pathParam, req, res, currentCo
return await oauthApi.handleImportAwsCredentials(req, res);
}
// Import Orchids token
if (method === 'POST' && pathParam === '/api/orchids/import-token') {
return await oauthApi.handleImportOrchidsToken(req, res);
}
// Get plugins list
if (method === 'GET' && pathParam === '/api/plugins') {
return await pluginApi.handleGetPlugins(req, res);

View file

@ -28,6 +28,7 @@ export async function scanConfigFiles(currentConfig, providerPoolManager) {
addToUsedPaths(usedPaths, currentConfig.QWEN_OAUTH_CREDS_FILE_PATH);
addToUsedPaths(usedPaths, currentConfig.ANTIGRAVITY_OAUTH_CREDS_FILE_PATH);
addToUsedPaths(usedPaths, currentConfig.IFLOW_TOKEN_FILE_PATH);
addToUsedPaths(usedPaths, currentConfig.ORCHIDS_CREDS_FILE_PATH);
// 使用最新的提供商池数据
let providerPools = currentConfig.providerPools;
@ -44,6 +45,7 @@ export async function scanConfigFiles(currentConfig, providerPoolManager) {
addToUsedPaths(usedPaths, provider.QWEN_OAUTH_CREDS_FILE_PATH);
addToUsedPaths(usedPaths, provider.ANTIGRAVITY_OAUTH_CREDS_FILE_PATH);
addToUsedPaths(usedPaths, provider.IFLOW_TOKEN_FILE_PATH);
addToUsedPaths(usedPaths, provider.ORCHIDS_CREDS_FILE_PATH);
}
}
}
@ -214,6 +216,17 @@ function getFileUsageInfo(relativePath, fileName, usedPaths, currentConfig) {
});
}
if (currentConfig.ORCHIDS_CREDS_FILE_PATH &&
(pathsEqual(relativePath, currentConfig.ORCHIDS_CREDS_FILE_PATH) ||
pathsEqual(relativePath, currentConfig.ORCHIDS_CREDS_FILE_PATH.replace(/\\/g, '/')))) {
usageInfo.usageType = 'main_config';
usageInfo.usageDetails.push({
type: 'Main Config',
location: 'Orchids OAuth credentials file path',
configKey: 'ORCHIDS_CREDS_FILE_PATH'
});
}
// 检查提供商池中的使用情况
if (currentConfig.providerPools) {
// 使用 flatMap 将双重循环优化为单层循环 O(n)
@ -284,6 +297,18 @@ function getFileUsageInfo(relativePath, fileName, usedPaths, currentConfig) {
configKey: 'IFLOW_TOKEN_FILE_PATH'
});
}
if (provider.ORCHIDS_CREDS_FILE_PATH &&
(pathsEqual(relativePath, provider.ORCHIDS_CREDS_FILE_PATH) ||
pathsEqual(relativePath, provider.ORCHIDS_CREDS_FILE_PATH.replace(/\\/g, '/')))) {
providerUsages.push({
type: 'Provider Pool',
location: `Orchids OAuth credentials (node ${index + 1})`,
providerType: providerType,
providerIndex: index,
configKey: 'ORCHIDS_CREDS_FILE_PATH'
});
}
if (providerUsages.length > 0) {
usageInfo.usageType = 'provider_pool';

View file

@ -1,12 +1,14 @@
import { getRequestBody } from '../utils/common.js';
import {
handleGeminiCliOAuth,
handleGeminiAntigravityOAuth,
handleQwenOAuth,
handleKiroOAuth,
handleIFlowOAuth,
batchImportKiroRefreshTokensStream,
importAwsCredentials
import {
handleGeminiCliOAuth,
handleGeminiAntigravityOAuth,
handleQwenOAuth,
handleKiroOAuth,
handleIFlowOAuth,
handleOrchidsOAuth,
batchImportKiroRefreshTokensStream,
importAwsCredentials,
importOrchidsToken
} from '../auth/oauth-handlers.js';
/**
@ -49,6 +51,11 @@ export async function handleGenerateAuthUrl(req, res, currentConfig, providerTyp
const result = await handleIFlowOAuth(currentConfig, options);
authUrl = result.authUrl;
authInfo = result.authInfo;
} else if (providerType === 'claude-orchids-oauth') {
// Orchids OAuth手动导入模式
const result = await handleOrchidsOAuth(currentConfig, options);
authUrl = result.authUrl;
authInfo = result.authInfo;
} else {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
@ -303,4 +310,286 @@ export async function handleImportAwsCredentials(req, res) {
}));
return true;
}
}
/**
* 导入 Orchids Token
* 支持三种格式
* 1. cookieString 格式 (完整的 Cookie 字符串包含 __client __session)
* 2. token 字符串格式 (JWT|rotating_token) - 已废弃
* 3. credentials 对象格式 (cookies, clerkSessionId, userId, workingDir)
*/
export async function handleImportOrchidsToken(req, res) {
try {
const body = await getRequestBody(req);
const { token, credentials, workingDir, cookieString } = body;
// 新格式:完整的 Cookie 字符串
if (cookieString && typeof cookieString === 'string') {
console.log('[Orchids Import] Starting cookie string import...');
// 解析 Cookie 字符串
const parsedResult = parseOrchidsCookieString(cookieString);
if (!parsedResult.success) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
success: false,
error: parsedResult.error
}));
return true;
}
// 保存凭据
const result = await saveOrchidsCredentials(parsedResult.credentials);
if (result.success) {
console.log(`[Orchids Import] Successfully imported credentials to: ${result.path}`);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
success: true,
path: result.path,
sessionId: result.sessionId,
userId: result.userId,
message: 'Orchids credentials imported successfully'
}));
} else {
const statusCode = result.error === 'duplicate' ? 409 : 500;
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
success: false,
error: result.error,
existingPath: result.existingPath || null
}));
}
return true;
}
// 如果提供了 credentials 对象,直接保存
if (credentials && typeof credentials === 'object') {
console.log('[Orchids Import] Starting credentials import...');
// 验证必需字段
if (!credentials.cookies && (!credentials.clerkSessionId)) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
success: false,
error: 'credentials must contain cookies or clerkSessionId'
}));
return true;
}
// 直接保存凭据
const result = await saveOrchidsCredentials(credentials);
if (result.success) {
console.log(`[Orchids Import] Successfully imported token to: ${result.path}`);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
success: true,
path: result.path,
sessionId: result.sessionId,
userId: result.userId,
message: 'Orchids credentials imported successfully'
}));
} else {
const statusCode = result.error === 'duplicate' ? 409 : 500;
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
success: false,
error: result.error,
existingPath: result.existingPath || null
}));
}
return true;
}
// 原有的 token 字符串格式
if (!token || typeof token !== 'string') {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
success: false,
error: 'cookieString, token string or credentials object is required'
}));
return true;
}
console.log('[Orchids Import] Starting token import...');
const result = await importOrchidsToken(token, { workingDir });
if (result.success) {
console.log(`[Orchids Import] Successfully imported token to: ${result.path}`);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
success: true,
path: result.path,
sessionId: result.sessionId,
userId: result.userId,
message: 'Orchids token imported successfully'
}));
} else {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
success: false,
error: result.error
}));
}
return true;
} catch (error) {
console.error('[Orchids Import] Error:', error);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
success: false,
error: error.message
}));
return true;
}
}
/**
* 直接保存 Orchids 凭据 UI 表单提交
*/
async function saveOrchidsCredentials(credentials) {
const fs = await import('fs');
const path = await import('path');
const { broadcastEvent } = await import('../services/ui-manager.js');
const { autoLinkProviderConfigs } = await import('../services/service-manager.js');
const { CONFIG } = await import('../core/config-manager.js');
try {
// 准备凭据数据
const credentialsData = {
cookies: credentials.cookies || '',
clerkSessionId: credentials.clerkSessionId || `sess_${Date.now()}`,
userId: credentials.userId || 'user_unknown',
workingDir: credentials.workingDir || 'E:\\path\\to\\default\\project',
expiresAt: credentials.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
importedAt: new Date().toISOString()
};
// 生成文件路径: configs/orchids/{timestamp}_orchids_creds/{timestamp}_orchids_creds.json
// 与 importOrchidsToken 保持一致的目录结构
const timestamp = Date.now();
const folderName = `${timestamp}_orchids_creds`;
const targetDir = path.default.join(process.cwd(), 'configs', 'orchids', folderName);
await fs.promises.mkdir(targetDir, { recursive: true });
const filename = `${folderName}.json`;
const credPath = path.default.join(targetDir, filename);
await fs.promises.writeFile(credPath, JSON.stringify(credentialsData, null, 2));
const relativePath = path.default.relative(process.cwd(), credPath);
console.log(`[Orchids Import] Credentials saved to: ${relativePath}`);
// 广播事件
broadcastEvent('oauth_success', {
provider: 'claude-orchids-oauth',
relativePath: relativePath,
timestamp: new Date().toISOString()
});
// 自动关联新生成的凭据到 Pools
await autoLinkProviderConfigs(CONFIG);
return {
success: true,
path: relativePath,
sessionId: credentialsData.clerkSessionId,
userId: credentialsData.userId
};
} catch (error) {
console.error('[Orchids Import] Save credentials failed:', error);
return {
success: false,
error: error.message
};
}
}
/**
* 解析 Orchids Cookie 字符串
* 从完整的 Cookie 字符串中提取 __client__session clerkSessionId
* @param {string} cookieString - 完整的 Cookie 字符串
* @returns {Object} 解析结果
*/
function parseOrchidsCookieString(cookieString) {
try {
// 提取 __client cookie
const clientMatch = cookieString.match(/__client=([^;]+)/);
if (!clientMatch) {
return { success: false, error: 'Cookie 中缺少 __client' };
}
const clientCookie = clientMatch[1].trim();
// 提取 __session cookie
const sessionMatch = cookieString.match(/__session=([^;]+)/);
if (!sessionMatch) {
return { success: false, error: 'Cookie 中缺少 __session' };
}
const sessionCookie = sessionMatch[1].trim();
// 从 __session JWT 中解析 clerkSessionId (sid) 和 userId (sub)
let clerkSessionId = null;
let userId = 'user_unknown';
try {
const sessionParts = sessionCookie.split('.');
if (sessionParts.length === 3) {
const payloadBase64 = sessionParts[1].replace(/-/g, '+').replace(/_/g, '/');
const payloadJson = Buffer.from(payloadBase64, 'base64').toString('utf-8');
const payload = JSON.parse(payloadJson);
if (payload.sid) {
clerkSessionId = payload.sid;
}
if (payload.sub) {
userId = payload.sub;
}
}
} catch (e) {
console.warn('[Orchids Import] Failed to parse __session JWT:', e.message);
}
// 如果无法从 __session 获取 sid尝试从 __client 获取
if (!clerkSessionId) {
try {
const clientParts = clientCookie.split('.');
if (clientParts.length === 3) {
const payloadBase64 = clientParts[1].replace(/-/g, '+').replace(/_/g, '/');
const payloadJson = Buffer.from(payloadBase64, 'base64').toString('utf-8');
const payload = JSON.parse(payloadJson);
// 从 client_id 推断 session_id
if (payload.id && payload.id.startsWith('client_')) {
clerkSessionId = 'sess_' + payload.id.substring(7);
}
}
} catch (e) {
console.warn('[Orchids Import] Failed to parse __client JWT:', e.message);
}
}
if (!clerkSessionId) {
return { success: false, error: '无法从 Cookie 中提取 Session ID' };
}
// 构建 cookies 字符串(只保留 __client 和 __session
const cookies = `__client=${clientCookie}; __session=${sessionCookie}`;
return {
success: true,
credentials: {
cookies: cookies,
clerkSessionId: clerkSessionId,
userId: userId,
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
}
};
} catch (error) {
return { success: false, error: `解析 Cookie 失败: ${error.message}` };
}
}

View file

@ -65,6 +65,7 @@ export const MODEL_PROVIDER = {
OPENAI_CUSTOM_RESPONSES: 'openaiResponses-custom',
CLAUDE_CUSTOM: 'claude-custom',
KIRO_API: 'claude-kiro-oauth',
ORCHIDS_API: 'claude-orchids-oauth',
QWEN_API: 'openai-qwen-oauth',
IFLOW_API: 'openai-iflow',
}

View file

@ -65,6 +65,17 @@ export const PROVIDER_MAPPINGS = [
displayName: 'iFlow API',
needsProjectId: false,
urlKeys: ['IFLOW_BASE_URL']
},
{
// Orchids OAuth 配置
dirName: 'orchids',
patterns: ['configs/orchids/', '/orchids/'],
providerType: 'claude-orchids-oauth',
credPathKey: 'ORCHIDS_CREDS_FILE_PATH',
defaultCheckModel: 'claude-sonnet-4-5',
displayName: 'Orchids OAuth',
needsProjectId: false,
urlKeys: ['ORCHIDS_BASE_URL']
}
];

View file

@ -181,6 +181,32 @@ const translations = {
'oauth.iflow.step2': '使用您的 iFlow 账号登录并授权',
'oauth.iflow.step3': '授权完成后,系统会自动获取 API Key',
'oauth.iflow.step4': '凭据文件可在上传配置管理中查看和管理',
// Orchids OAuth
'oauth.orchids.title': 'Orchids 凭据导入',
'oauth.orchids.formatToken': 'JWT Token 格式',
'oauth.orchids.tokenLabel': 'JWT Token 字符串',
'oauth.orchids.tokenPlaceholder': 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImNsaWVudF94eHgiLCJyb3RhdGluZ190b2tlbiI6Inh4eCJ9.xxx',
'oauth.orchids.tokenInstructions': '粘贴 JWT Token 字符串(支持纯 JWT 格式rotating_token 会从 JWT payload 中自动提取)',
'oauth.orchids.getSteps': '获取步骤:',
'oauth.orchids.tokenStep1': '访问 www.orchids.app 并登录',
'oauth.orchids.tokenStep2': '按 F12 打开开发者工具 → Application 标签',
'oauth.orchids.tokenStep3': '在左侧找到 Cookies → www.orchids.app',
'oauth.orchids.tokenStep4': '找到 __client cookie 的值',
'oauth.orchids.tokenStep5': '复制完整的 JWT 值(以 eyJ 开头的字符串)',
'oauth.orchids.tokenFormat': '格式eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImNsaWVudF94eHgiLCJyb3RhdGluZ190b2tlbiI6Inh4eCJ9.xxx',
'oauth.orchids.confirmImport': '确认导入',
'oauth.orchids.importing': '导入中...',
'oauth.orchids.success': 'Orchids 凭据导入成功',
'oauth.orchids.parseSuccess': 'Token 解析成功',
'oauth.orchids.detectedToken': '检测到有效的 JWT Token',
'oauth.orchids.errorEmpty': '请输入凭据',
'oauth.orchids.errorTokenInvalid': 'Token 格式错误,请输入有效的 JWT Token',
'oauth.orchids.errorJwtParse': 'JWT 解析失败',
'oauth.orchids.errorMissingRotating': 'JWT payload 中缺少 rotating_token 字段',
'oauth.orchids.importFailed': '导入失败',
'oauth.orchids.clientId': 'Client ID',
'oauth.orchids.rotatingToken': 'Rotating Token',
// Config
'config.title': '配置管理',
@ -348,6 +374,7 @@ const translations = {
'providers.stat.usageCount': '使用次数',
'providers.stat.errorCount': '错误次数',
'providers.auth.generate': '生成授权',
'providers.auth.importToken': '导入 Token',
// Modal Provider Manager
'modal.provider.manage': '管理 {type} 提供商配置',
@ -676,6 +703,32 @@ const translations = {
'oauth.iflow.step2': 'Log in with your iFlow account and authorize',
'oauth.iflow.step3': 'After authorization, the system will automatically fetch the API Key',
'oauth.iflow.step4': 'Credentials files can be viewed and managed in Upload Config',
// Orchids OAuth
'oauth.orchids.title': 'Orchids Credential Import',
'oauth.orchids.formatToken': 'JWT Token Format',
'oauth.orchids.tokenLabel': 'JWT Token String',
'oauth.orchids.tokenPlaceholder': 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImNsaWVudF94eHgiLCJyb3RhdGluZ190b2tlbiI6Inh4eCJ9.xxx',
'oauth.orchids.tokenInstructions': 'Paste JWT Token string (rotating_token will be automatically extracted from JWT payload)',
'oauth.orchids.getSteps': 'How to get:',
'oauth.orchids.tokenStep1': 'Visit www.orchids.app and log in',
'oauth.orchids.tokenStep2': 'Press F12 to open Developer Tools → Application tab',
'oauth.orchids.tokenStep3': 'Find Cookies → www.orchids.app on the left',
'oauth.orchids.tokenStep4': 'Find the __client cookie value',
'oauth.orchids.tokenStep5': 'Copy the full JWT value (string starting with eyJ)',
'oauth.orchids.tokenFormat': 'Format: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImNsaWVudF94eHgiLCJyb3RhdGluZ190b2tlbiI6Inh4eCJ9.xxx',
'oauth.orchids.confirmImport': 'Confirm Import',
'oauth.orchids.importing': 'Importing...',
'oauth.orchids.success': 'Orchids credentials imported successfully',
'oauth.orchids.parseSuccess': 'Token parsed successfully',
'oauth.orchids.detectedToken': 'Valid JWT Token detected',
'oauth.orchids.errorEmpty': 'Please enter credentials',
'oauth.orchids.errorTokenInvalid': 'Token format error, please enter a valid JWT Token',
'oauth.orchids.errorJwtParse': 'JWT parse failed',
'oauth.orchids.errorMissingRotating': 'Missing rotating_token field in JWT payload',
'oauth.orchids.importFailed': 'Import failed',
'oauth.orchids.clientId': 'Client ID',
'oauth.orchids.rotatingToken': 'Rotating Token',
// Config
'config.title': 'Configuration Management',
@ -843,6 +896,7 @@ const translations = {
'providers.stat.usageCount': 'Usage Count',
'providers.stat.errorCount': 'Error Count',
'providers.auth.generate': 'Gen Auth',
'providers.auth.importToken': 'Import Token',
// Modal Provider Manager
'modal.provider.manage': 'Manage {type} Provider Config',

View file

@ -212,6 +212,7 @@ function renderProviders(providers) {
'openai-custom',
'claude-custom',
'claude-kiro-oauth',
'claude-orchids-oauth',
'openai-qwen-oauth',
'openaiResponses-custom',
'openai-iflow'
@ -423,12 +424,22 @@ async function openProviderManager(providerType) {
*/
function generateAuthButton(providerType) {
// 只为支持OAuth的提供商显示授权按钮
const oauthProviders = ['gemini-cli-oauth', 'gemini-antigravity', 'openai-qwen-oauth', 'claude-kiro-oauth', 'openai-iflow'];
const oauthProviders = ['gemini-cli-oauth', 'gemini-antigravity', 'openai-qwen-oauth', 'claude-kiro-oauth', 'claude-orchids-oauth', 'openai-iflow'];
if (!oauthProviders.includes(providerType)) {
return '';
}
// Orchids 提供商使用不同的按钮文本
if (providerType === 'claude-orchids-oauth') {
return `
<button class="generate-auth-btn" title="导入 Orchids Token">
<i class="fas fa-seedling" style="color: #10b981;"></i>
<span data-i18n="providers.auth.importToken">${t('providers.auth.importToken') || '导入 Token'}</span>
</button>
`;
}
return `
<button class="generate-auth-btn" title="生成OAuth授权链接">
<i class="fas fa-key"></i>
@ -447,10 +458,218 @@ async function handleGenerateAuthUrl(providerType) {
showKiroAuthMethodSelector(providerType);
return;
}
// 如果是 Orchids OAuth显示 Cookie 导入对话框
if (providerType === 'claude-orchids-oauth') {
showOrchidsCookieImportDialog(providerType);
return;
}
await executeGenerateAuthUrl(providerType, {});
}
/**
* 显示 Orchids Token 导入对话框
* 支持两种格式
* 1. JWT 格式eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...JWT payload 中包含 rotating_token
* 2. JWT|rotating_token 格式eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...|W7lqx1t8HIxMh0ScDZUB
* @param {string} providerType - 提供商类型
*/
function showOrchidsCookieImportDialog(providerType) {
const modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.style.display = 'flex';
modal.innerHTML = `
<div class="modal-content" style="max-width: 750px;">
<div class="modal-header">
<h3><i class="fas fa-seedling" style="color: #10b981;"></i> <span>${t('oauth.orchids.title')}</span></h3>
<button class="modal-close">&times;</button>
</div>
<div class="modal-body">
<!-- JWT Token 格式说明 -->
<div id="orchidsTokenInstructions" class="orchids-import-instructions" style="margin-bottom: 16px; padding: 12px; background: #ecfdf5; border: 1px solid #a7f3d0; border-radius: 8px;">
<p style="margin: 0; font-size: 14px; color: #065f46;">
<i class="fas fa-info-circle"></i>
${t('oauth.orchids.tokenInstructions')}
</p>
<div style="margin-top: 12px; padding: 10px; background: #d1fae5; border-radius: 6px; font-size: 13px;">
<p style="margin: 0 0 8px 0; font-weight: 600; color: #047857;">
<i class="fas fa-lightbulb"></i> ${t('oauth.orchids.getSteps')}
</p>
<ol style="margin: 0; padding-left: 20px; color: #065f46;">
<li>${t('oauth.orchids.tokenStep1')}</li>
<li>${t('oauth.orchids.tokenStep2')}</li>
<li>${t('oauth.orchids.tokenStep3')}</li>
<li>${t('oauth.orchids.tokenStep4')}</li>
<li>${t('oauth.orchids.tokenStep5')}</li>
</ol>
</div>
<div style="margin-top: 8px; padding: 8px; background: #d1fae5; border-radius: 6px; font-size: 11px; font-family: monospace; word-break: break-all; color: #047857;">
${t('oauth.orchids.tokenFormat')}
</div>
</div>
<div class="form-group" style="margin-bottom: 16px;">
<label id="orchidsInputLabel" style="display: block; margin-bottom: 8px; font-weight: 600; color: #374151;">
${t('oauth.orchids.tokenLabel')} <span style="color: #ef4444;">*</span>
</label>
<textarea id="orchidsTokenInput" rows="4"
style="width: 100%; padding: 10px 12px; border: 1px solid #d1d5db; border-radius: 8px; font-family: monospace; font-size: 12px; resize: vertical;"
placeholder="${t('oauth.orchids.tokenPlaceholder')}"
></textarea>
</div>
<div id="orchidsValidationResult" style="display: none; margin-bottom: 16px; padding: 12px; border-radius: 8px;"></div>
<!-- 解析预览 -->
<div id="orchidsParsePreview" style="display: none; margin-bottom: 16px; padding: 12px; background: #f0fdf4; border: 1px solid #bbf7d0; border-radius: 8px;">
<p style="margin: 0 0 8px 0; font-weight: 600; color: #166534;">
<i class="fas fa-check-circle"></i> ${t('oauth.orchids.parseSuccess')}
</p>
<div id="orchidsParseDetails" style="font-size: 12px; color: #065f46;"></div>
</div>
</div>
<div class="modal-footer">
<button class="modal-cancel">${t('modal.provider.cancel')}</button>
<button class="modal-submit" id="orchidsSubmitBtn" style="background: #10b981; color: white;">
<i class="fas fa-check"></i>
<span>${t('oauth.orchids.confirmImport')}</span>
</button>
</div>
</div>
`;
document.body.appendChild(modal);
const closeBtn = modal.querySelector('.modal-close');
const cancelBtn = modal.querySelector('.modal-cancel');
const submitBtn = modal.querySelector('#orchidsSubmitBtn');
const tokenInput = modal.querySelector('#orchidsTokenInput');
const validationResult = modal.querySelector('#orchidsValidationResult');
const parsePreview = modal.querySelector('#orchidsParsePreview');
const parseDetails = modal.querySelector('#orchidsParseDetails');
// 实时验证输入
tokenInput.addEventListener('input', () => {
const inputValue = tokenInput.value.trim();
if (!inputValue) {
validationResult.style.display = 'none';
parsePreview.style.display = 'none';
return;
}
// 检测 JWT 格式
if (inputValue.startsWith('eyJ') && inputValue.split('.').length === 3) {
// 尝试解析 JWT
try {
let jwt = inputValue;
let rotatingTokenFromSeparator = null;
// 检查是否有 | 分隔符
if (inputValue.includes('|')) {
const parts = inputValue.split('|');
jwt = parts[0];
rotatingTokenFromSeparator = parts[1];
}
const jwtParts = jwt.split('.');
if (jwtParts.length === 3) {
let payloadBase64 = jwtParts[1].replace(/-/g, '+').replace(/_/g, '/');
// 添加 padding
while (payloadBase64.length % 4) {
payloadBase64 += '=';
}
const payloadJson = atob(payloadBase64);
const payload = JSON.parse(payloadJson);
// 检查是否有 rotating_token从 payload 或分隔符后)
const rotatingToken = payload.rotating_token || rotatingTokenFromSeparator;
if (rotatingToken) {
validationResult.style.cssText = 'display: block; background: #f0fdf4; border: 1px solid #bbf7d0; color: #166534;';
validationResult.innerHTML = '<i class="fas fa-check-circle"></i> ' + t('oauth.orchids.detectedToken');
parsePreview.style.display = 'block';
parseDetails.innerHTML = `
<div style="display: grid; gap: 4px;">
<div><strong>${t('oauth.orchids.clientId')}:</strong> <code style="background: #d1fae5; padding: 1px 4px; border-radius: 2px;">${payload.id || 'N/A'}</code></div>
<div><strong>${t('oauth.orchids.rotatingToken')}:</strong> <code style="background: #d1fae5; padding: 1px 4px; border-radius: 2px;">${rotatingToken.substring(0, 30)}...</code></div>
</div>
`;
} else {
validationResult.style.cssText = 'display: block; background: #fef2f2; border: 1px solid #fecaca; color: #991b1b;';
validationResult.innerHTML = '<i class="fas fa-exclamation-triangle"></i> ' + t('oauth.orchids.errorMissingRotating');
parsePreview.style.display = 'none';
}
}
} catch (e) {
validationResult.style.cssText = 'display: block; background: #fef2f2; border: 1px solid #fecaca; color: #991b1b;';
validationResult.innerHTML = '<i class="fas fa-exclamation-triangle"></i> ' + t('oauth.orchids.errorJwtParse') + ': ' + e.message;
parsePreview.style.display = 'none';
}
} else {
validationResult.style.cssText = 'display: block; background: #fef2f2; border: 1px solid #fecaca; color: #991b1b;';
validationResult.innerHTML = '<i class="fas fa-exclamation-triangle"></i> ' + t('oauth.orchids.errorTokenInvalid');
parsePreview.style.display = 'none';
}
});
// 关闭按钮事件
[closeBtn, cancelBtn].forEach(btn => {
btn.addEventListener('click', () => modal.remove());
});
// 提交按钮事件
submitBtn.addEventListener('click', async () => {
const inputValue = tokenInput.value.trim();
// 验证输入
if (!inputValue) {
validationResult.style.cssText = 'display: block; background: #fef2f2; border: 1px solid #fecaca; color: #991b1b;';
validationResult.innerHTML = '<i class="fas fa-exclamation-triangle"></i> ' + t('oauth.orchids.errorEmpty');
return;
}
// JWT 格式验证(支持纯 JWT 和 JWT|rotating_token 格式)
let jwt = inputValue;
if (inputValue.includes('|')) {
jwt = inputValue.split('|')[0];
}
if (!jwt.startsWith('eyJ') || jwt.split('.').length !== 3) {
validationResult.style.cssText = 'display: block; background: #fef2f2; border: 1px solid #fecaca; color: #991b1b;';
validationResult.innerHTML = '<i class="fas fa-exclamation-triangle"></i> ' + t('oauth.orchids.errorTokenInvalid');
return;
}
// 禁用按钮
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> <span>' + t('oauth.orchids.importing') + '</span>';
try {
const response = await window.apiClient.post('/orchids/import-token', { token: inputValue });
if (response.success) {
showToast(t('common.success'), t('oauth.orchids.success'), 'success');
modal.remove();
loadProviders();
loadConfigList();
} else {
validationResult.style.cssText = 'display: block; background: #fef2f2; border: 1px solid #fecaca; color: #991b1b;';
validationResult.innerHTML = '<i class="fas fa-times-circle"></i> ' + (response.error || t('oauth.orchids.importFailed'));
}
} catch (error) {
console.error('Orchids import failed:', error);
validationResult.style.cssText = 'display: block; background: #fef2f2; border: 1px solid #fecaca; color: #991b1b;';
validationResult.innerHTML = '<i class="fas fa-times-circle"></i> ' + t('oauth.orchids.importFailed') + ': ' + error.message;
} finally {
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-check"></i> <span>' + t('oauth.orchids.confirmImport') + '</span>';
}
});
}
/**
* 显示 Kiro OAuth 认证方式选择对话框
* @param {string} providerType - 提供商类型

View file

@ -811,6 +811,12 @@ function detectProviderFromPath(filePath) {
providerType: 'gemini-antigravity',
displayName: 'Gemini Antigravity',
shortName: 'antigravity'
},
{
patterns: ['configs/orchids/', '/orchids/'],
providerType: 'claude-orchids-oauth',
displayName: 'Orchids OAuth',
shortName: 'orchids-oauth'
}
];

View file

@ -58,6 +58,10 @@
<i class="fas fa-stream"></i>
<span>iFlow OAuth</span>
</button>
<button type="button" class="provider-tag" data-value="claude-orchids-oauth">
<i class="fas fa-seedling"></i>
<span>Orchids OAuth</span>
</button>
</div>
<small class="form-text" data-i18n="config.modelProviderHelp">点击选择启动时初始化的模型提供商 (必须至少选择一个)</small>
</div>
@ -109,6 +113,10 @@
<i class="fas fa-stream"></i>
<span>iFlow OAuth</span>
</button>
<button type="button" class="provider-tag" data-value="claude-orchids-oauth">
<i class="fas fa-seedling"></i>
<span>Orchids OAuth</span>
</button>
</div>
<small class="form-text" data-i18n="config.proxy.enabledProvidersNote">点击选择需要通过代理访问的提供商,未选中的提供商将直接连接</small>
</div>

View file

@ -497,6 +497,58 @@
</div>
</div>
<div class="routing-example-card" data-provider="claude-orchids-oauth-card">
<div class="routing-card-header">
<i class="fas fa-seedling"></i>
<h4 data-i18n="dashboard.routing.nodeName.orchids">Orchids OAuth</h4>
<span class="provider-badge oauth" data-i18n="dashboard.routing.free">突破限制/免费使用</span>
</div>
<div class="routing-card-content">
<!-- 协议标签切换 -->
<div class="protocol-tabs">
<button class="protocol-tab" data-protocol="openai" data-i18n="dashboard.routing.openai">OpenAI协议</button>
<button class="protocol-tab active" data-protocol="claude" data-i18n="dashboard.routing.claude">Claude协议</button>
</div>
<!-- OpenAI协议示例 -->
<div class="protocol-content" data-protocol="openai">
<div class="endpoint-info">
<label data-i18n="dashboard.routing.endpoint">端点路径:</label>
<code class="endpoint-path">/claude-orchids-oauth/v1/chat/completions</code>
</div>
<div class="usage-example">
<label data-i18n="dashboard.routing.exampleOpenAI">使用示例 (OpenAI格式):</label>
<pre><code>curl http://localhost:3000/claude-orchids-oauth/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"model": "claude-sonnet-4-5",
"messages": [{"role": "user", "content": "Hello!"}],
"max_tokens": 8192
}'</code></pre>
</div>
</div>
<!-- Claude协议示例 -->
<div class="protocol-content active" data-protocol="claude">
<div class="endpoint-info">
<label data-i18n="dashboard.routing.endpoint">端点路径:</label>
<code class="endpoint-path">/claude-orchids-oauth/v1/messages</code>
</div>
<div class="usage-example">
<label data-i18n="dashboard.routing.exampleClaude">使用示例 (Claude格式):</label>
<pre><code>curl http://localhost:3000/claude-orchids-oauth/v1/messages \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{
"model": "claude-sonnet-4-5",
"max_tokens": 8192,
"messages": [{"role": "user", "content": "Hello!"}]
}'</code></pre>
</div>
</div>
</div>
</div>
</div>
<div class="routing-tips">