feat(配置管理): 添加提供商URL配置支持并优化UI

扩展配置系统以支持自定义提供商API端点URL,包括Gemini、Kiro、Qwen和Antigravity服务的可配置基础URL。同时优化了配置表单的UI,添加了可选标记和占位符提示。

- 在config.json.example中添加各提供商URL配置项
- 修改各提供商核心服务以支持自定义URL
- 更新配置管理器和UI管理器以处理新增URL字段
- 优化前端配置表单,添加可选标记和占位符
- 扩展字段映射和顺序定义以包含URL配置项
- 改进授权模态框,添加手动URL输入支持
This commit is contained in:
hex2077 2025-12-19 16:31:48 +08:00
parent 5af83e4473
commit e943819539
14 changed files with 456 additions and 141 deletions

View file

@ -23,5 +23,15 @@
"CRON_NEAR_MINUTES": 1,
"CRON_REFRESH_TOKEN": false,
"PROVIDER_POOLS_FILE_PATH": "provider_pools.json",
"MAX_ERROR_COUNT": 3
}
"MAX_ERROR_COUNT": 3,
"QWEN_BASE_URL": "https://portal.qwen.ai/v1",
"QWEN_OAUTH_BASE_URL": "https://chat.qwen.ai",
"GEMINI_BASE_URL": "https://cloudcode-pa.googleapis.com",
"ANTIGRAVITY_BASE_URL_DAILY": "https://daily-cloudcode-pa.sandbox.googleapis.com",
"ANTIGRAVITY_BASE_URL_AUTOPUSH": "https://autopush-cloudcode-pa.sandbox.googleapis.com",
"KIRO_REFRESH_URL": "https://prod.{{region}}.auth.desktop.kiro.dev/refreshToken",
"KIRO_REFRESH_IDC_URL": "https://oidc.{{region}}.amazonaws.com/token",
"KIRO_BASE_URL": "https://codewhisperer.{{region}}.amazonaws.com/generateAssistantResponse",
"KIRO_AMAZON_Q_URL": "https://codewhisperer.{{region}}.amazonaws.com/SendMessageStreaming",
"KIRO_USAGE_LIMITS_URL": "https://q.{{region}}.amazonaws.com/getUsageLimits"
}

View file

@ -451,10 +451,10 @@ async initializeAuth(forceRefresh = false) {
this.region = 'us-east-1'; // Set default region
}
this.refreshUrl = KIRO_CONSTANTS.REFRESH_URL.replace("{{region}}", this.region);
this.refreshIDCUrl = KIRO_CONSTANTS.REFRESH_IDC_URL.replace("{{region}}", this.region);
this.baseUrl = KIRO_CONSTANTS.BASE_URL.replace("{{region}}", this.region);
this.amazonQUrl = KIRO_CONSTANTS.AMAZON_Q_URL.replace("{{region}}", this.region);
this.refreshUrl = (this.config.KIRO_REFRESH_URL || KIRO_CONSTANTS.REFRESH_URL).replace("{{region}}", this.region);
this.refreshIDCUrl = (this.config.KIRO_REFRESH_IDC_URL || KIRO_CONSTANTS.REFRESH_IDC_URL).replace("{{region}}", this.region);
this.baseUrl = (this.config.KIRO_BASE_URL || KIRO_CONSTANTS.BASE_URL).replace("{{region}}", this.region);
this.amazonQUrl = (KIRO_CONSTANTS.AMAZON_Q_URL).replace("{{region}}", this.region);
} catch (error) {
console.warn(`[Kiro Auth] Error during credential loading: ${error.message}`);
}

View file

@ -77,6 +77,15 @@ export async function initializeConfig(args = process.argv.slice(2), configFileP
KIRO_OAUTH_CREDS_FILE_PATH: null,
QWEN_OAUTH_CREDS_FILE_PATH: null,
PROJECT_ID: null,
// Provider URLs
QWEN_BASE_URL: null,
QWEN_OAUTH_BASE_URL: null,
GEMINI_BASE_URL: null,
ANTIGRAVITY_BASE_URL_DAILY: null,
ANTIGRAVITY_BASE_URL_AUTOPUSH: null,
KIRO_REFRESH_URL: null,
KIRO_REFRESH_IDC_URL: null,
KIRO_BASE_URL: null,
SYSTEM_PROMPT_FILE_PATH: INPUT_SYSTEM_PROMPT_FILE, // Default value
SYSTEM_PROMPT_MODE: 'append',
PROMPT_LOG_BASE_NAME: "prompt_log",
@ -155,6 +164,48 @@ export async function initializeConfig(args = process.argv.slice(2), configFileP
console.warn(`[Config Warning] --claude-base-url flag requires a value.`);
}
}
// Provider URL arguments
else if (args[i] === '--qwen-base-url') {
if (i + 1 < args.length) {
currentConfig.QWEN_BASE_URL = args[i + 1];
i++;
}
} else if (args[i] === '--qwen-oauth-base-url') {
if (i + 1 < args.length) {
currentConfig.QWEN_OAUTH_BASE_URL = args[i + 1];
i++;
}
} else if (args[i] === '--gemini-base-url') {
if (i + 1 < args.length) {
currentConfig.GEMINI_BASE_URL = args[i + 1];
i++;
}
} else if (args[i] === '--antigravity-base-url-daily') {
if (i + 1 < args.length) {
currentConfig.ANTIGRAVITY_BASE_URL_DAILY = args[i + 1];
i++;
}
} else if (args[i] === '--antigravity-base-url-autopush') {
if (i + 1 < args.length) {
currentConfig.ANTIGRAVITY_BASE_URL_AUTOPUSH = args[i + 1];
i++;
}
} else if (args[i] === '--kiro-refresh-url') {
if (i + 1 < args.length) {
currentConfig.KIRO_REFRESH_URL = args[i + 1];
i++;
}
} else if (args[i] === '--kiro-refresh-idc-url') {
if (i + 1 < args.length) {
currentConfig.KIRO_REFRESH_IDC_URL = args[i + 1];
i++;
}
} else if (args[i] === '--kiro-base-url') {
if (i + 1 < args.length) {
currentConfig.KIRO_BASE_URL = args[i + 1];
i++;
}
}
// Gemini-specific arguments
else if (args[i] === '--gemini-oauth-creds-base64') {
if (i + 1 < args.length) {

View file

@ -29,8 +29,8 @@ const httpsAgent = new https.Agent({
const AUTH_REDIRECT_PORT = 8086;
const CREDENTIALS_DIR = '.antigravity';
const CREDENTIALS_FILE = 'oauth_creds.json';
const ANTIGRAVITY_BASE_URL_DAILY = 'https://daily-cloudcode-pa.sandbox.googleapis.com';
const ANTIGRAVITY_BASE_URL_AUTOPUSH = 'https://autopush-cloudcode-pa.sandbox.googleapis.com';
const DEFAULT_ANTIGRAVITY_BASE_URL_DAILY = 'https://daily-cloudcode-pa.sandbox.googleapis.com';
const DEFAULT_ANTIGRAVITY_BASE_URL_AUTOPUSH = 'https://autopush-cloudcode-pa.sandbox.googleapis.com';
const ANTIGRAVITY_API_VERSION = 'v1internal';
const OAUTH_CLIENT_ID = '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com';
const OAUTH_CLIENT_SECRET = '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';
@ -240,14 +240,18 @@ export class AntigravityApiService {
this.config = config;
this.host = config.HOST;
this.oauthCredsFilePath = config.ANTIGRAVITY_OAUTH_CREDS_FILE_PATH;
this.baseURL = ANTIGRAVITY_BASE_URL_DAILY; // 使用通用 GEMINI_BASE_URL 配置
this.baseURL = DEFAULT_ANTIGRAVITY_BASE_URL_DAILY; // 使用通用 GEMINI_BASE_URL 配置
this.userAgent = DEFAULT_USER_AGENT; // 支持通用 USER_AGENT 配置
this.projectId = config.PROJECT_ID;
// Initialize instance-specific endpoints
this.baseUrlDaily = config.ANTIGRAVITY_BASE_URL_DAILY || DEFAULT_ANTIGRAVITY_BASE_URL_DAILY;
this.baseUrlAutopush = config.ANTIGRAVITY_BASE_URL_AUTOPUSH || DEFAULT_ANTIGRAVITY_BASE_URL_AUTOPUSH;
// 多环境降级顺序
this.baseURLs = this.baseURL ? [this.baseURL] : [
ANTIGRAVITY_BASE_URL_DAILY,
ANTIGRAVITY_BASE_URL_AUTOPUSH
this.baseUrlDaily,
this.baseUrlAutopush
// ANTIGRAVITY_BASE_URL_PROD // 生产环境已注释
];
}

View file

@ -28,8 +28,8 @@ const httpsAgent = new https.Agent({
const AUTH_REDIRECT_PORT = 8085;
const CREDENTIALS_DIR = '.gemini';
const CREDENTIALS_FILE = 'oauth_creds.json';
const CODE_ASSIST_ENDPOINT = 'https://cloudcode-pa.googleapis.com';
const CODE_ASSIST_API_VERSION = 'v1internal';
const DEFAULT_CODE_ASSIST_ENDPOINT = 'https://cloudcode-pa.googleapis.com';
const DEFAULT_CODE_ASSIST_API_VERSION = 'v1internal';
const OAUTH_CLIENT_ID = '681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com';
const OAUTH_CLIENT_SECRET = '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';
const GEMINI_MODELS = getProviderModels('gemini-cli-oauth');
@ -209,6 +209,9 @@ export class GeminiApiService {
this.oauthCredsBase64 = config.GEMINI_OAUTH_CREDS_BASE64;
this.oauthCredsFilePath = config.GEMINI_OAUTH_CREDS_FILE_PATH;
this.projectId = config.PROJECT_ID;
this.codeAssistEndpoint = config.GEMINI_BASE_URL || DEFAULT_CODE_ASSIST_ENDPOINT;
this.apiVersion = DEFAULT_CODE_ASSIST_API_VERSION;
}
async initialize() {
@ -409,7 +412,7 @@ export class GeminiApiService {
try {
const requestOptions = {
url: `${CODE_ASSIST_ENDPOINT}/${CODE_ASSIST_API_VERSION}:${method}`,
url: `${this.codeAssistEndpoint}/${this.apiVersion}:${method}`,
method: "POST",
headers: { "Content-Type": "application/json" },
responseType: "json",
@ -453,7 +456,7 @@ export class GeminiApiService {
try {
const requestOptions = {
url: `${CODE_ASSIST_ENDPOINT}/${CODE_ASSIST_API_VERSION}:${method}`,
url: `${this.codeAssistEndpoint}/${this.apiVersion}:${method}`,
method: "POST",
params: { alt: "sse" },
headers: { "Content-Type": "application/json" },
@ -606,7 +609,7 @@ export class GeminiApiService {
// 调用 retrieveUserQuota 接口获取用户配额信息
try {
const quotaURL = `${CODE_ASSIST_ENDPOINT}/${CODE_ASSIST_API_VERSION}:retrieveUserQuota`;
const quotaURL = `${this.codeAssistEndpoint}/${this.apiVersion}:retrieveUserQuota`;
const requestBody = {
project: `projects/${this.projectId}`
};

View file

@ -30,16 +30,12 @@ const DEFAULT_LOCK_CONFIG = {
attemptInterval: 200,
};
const QWEN_OAUTH_BASE_URL = 'https://chat.qwen.ai';
const QWEN_OAUTH_DEVICE_CODE_ENDPOINT = `${QWEN_OAUTH_BASE_URL}/api/v1/oauth2/device/code`;
const QWEN_OAUTH_TOKEN_ENDPOINT = `${QWEN_OAUTH_BASE_URL}/api/v1/oauth2/token`;
const DEFAULT_QWEN_OAUTH_BASE_URL = 'https://chat.qwen.ai';
const DEFAULT_QWEN_BASE_URL = 'https://portal.qwen.ai/v1';
const QWEN_OAUTH_CLIENT_ID = 'f0304373b74a44d2b584a3fb70ca9e56';
const QWEN_OAUTH_SCOPE = 'openid profile email model.completion';
const QWEN_OAUTH_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code';
// const DEFAULT_QWEN_BASE_URL = 'https://dashscope.aliyuncs.com/compatible-mode/v1';
const DEFAULT_QWEN_BASE_URL = 'https://portal.qwen.ai/v1';
export const QwenOAuth2Event = {
AuthUri: 'auth-uri',
AuthProgress: 'auth-progress',
@ -181,6 +177,13 @@ export class QwenApiService {
this.currentAxiosInstance = null;
this.tokenManagerOptions = { credentialFilePath: this._getQwenCachedCredentialPath() };
this.useSystemProxy = config?.USE_SYSTEM_PROXY_QWEN ?? false;
// Initialize instance-specific endpoints
this.baseUrl = config.QWEN_BASE_URL || DEFAULT_QWEN_BASE_URL;
const oauthBaseUrl = config.QWEN_OAUTH_BASE_URL || DEFAULT_QWEN_OAUTH_BASE_URL;
this.oauthDeviceCodeEndpoint = `${oauthBaseUrl}/api/v1/oauth2/device/code`;
this.oauthTokenEndpoint = `${oauthBaseUrl}/api/v1/oauth2/token`;
console.log(`[Qwen] System proxy ${this.useSystemProxy ? 'enabled' : 'disabled'}`);
this.qwenClient = new QwenOAuth2Client(config, this.useSystemProxy);
}
@ -205,7 +208,7 @@ export class QwenApiService {
});
const axiosConfig = {
baseURL: DEFAULT_QWEN_BASE_URL,
baseURL: this.baseUrl,
httpAgent,
httpsAgent,
headers: {
@ -386,7 +389,7 @@ export class QwenApiService {
}
getCurrentEndpoint(resourceUrl) {
const baseEndpoint = resourceUrl || DEFAULT_QWEN_BASE_URL;
const baseEndpoint = resourceUrl || this.baseUrl;
const suffix = '/v1';
const normalizedUrl = baseEndpoint.startsWith('http') ?
@ -887,7 +890,8 @@ class QwenOAuth2Client {
client_id: QWEN_OAUTH_CLIENT_ID,
};
try {
const response = await commonFetch(QWEN_OAUTH_TOKEN_ENDPOINT, {
const endpoint = this.oauthTokenEndpoint;
const response = await commonFetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
body: objectToUrlEncoded(bodyData),
@ -917,7 +921,8 @@ class QwenOAuth2Client {
code_challenge_method: options.code_challenge_method,
};
try {
const response = await commonFetch(QWEN_OAUTH_DEVICE_CODE_ENDPOINT, {
const endpoint = this.oauthDeviceCodeEndpoint;
const response = await commonFetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
body: objectToUrlEncoded(bodyData),
@ -936,7 +941,8 @@ class QwenOAuth2Client {
code_verifier: options.code_verifier,
};
try {
const response = await commonFetch(QWEN_OAUTH_TOKEN_ENDPOINT, {
const endpoint = this.oauthTokenEndpoint;
const response = await commonFetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
body: objectToUrlEncoded(bodyData),

View file

@ -19,7 +19,8 @@ export const PROVIDER_MAPPINGS = [
credPathKey: 'KIRO_OAUTH_CREDS_FILE_PATH',
defaultCheckModel: 'claude-haiku-4-5',
displayName: 'Claude Kiro OAuth',
needsProjectId: false
needsProjectId: false,
urlKeys: ['KIRO_BASE_URL', 'KIRO_REFRESH_URL', 'KIRO_REFRESH_IDC_URL']
},
{
// Gemini CLI OAuth 配置
@ -29,7 +30,8 @@ export const PROVIDER_MAPPINGS = [
credPathKey: 'GEMINI_OAUTH_CREDS_FILE_PATH',
defaultCheckModel: 'gemini-2.5-flash',
displayName: 'Gemini CLI OAuth',
needsProjectId: true
needsProjectId: true,
urlKeys: ['GEMINI_BASE_URL']
},
{
// Qwen OAuth 配置
@ -39,7 +41,8 @@ export const PROVIDER_MAPPINGS = [
credPathKey: 'QWEN_OAUTH_CREDS_FILE_PATH',
defaultCheckModel: 'qwen3-coder-plus',
displayName: 'Qwen OAuth',
needsProjectId: false
needsProjectId: false,
urlKeys: ['QWEN_BASE_URL', 'QWEN_OAUTH_BASE_URL']
},
{
// Antigravity OAuth 配置
@ -49,7 +52,8 @@ export const PROVIDER_MAPPINGS = [
credPathKey: 'ANTIGRAVITY_OAUTH_CREDS_FILE_PATH',
defaultCheckModel: 'gemini-2.5-computer-use-preview-10-2025',
displayName: 'Gemini Antigravity',
needsProjectId: true
needsProjectId: true,
urlKeys: ['ANTIGRAVITY_BASE_URL_DAILY', 'ANTIGRAVITY_BASE_URL_AUTOPUSH']
}
];
@ -282,10 +286,11 @@ export async function isValidOAuthCredentials(filePath) {
* @param {string} options.credPath - 凭据文件路径
* @param {string} options.defaultCheckModel - 默认检测模型
* @param {boolean} options.needsProjectId - 是否需要 PROJECT_ID
* @param {Array} options.urlKeys - 可选的 URL 配置项键名列表
* @returns {Object} 新的提供商配置对象
*/
export function createProviderConfig(options) {
const { credPathKey, credPath, defaultCheckModel, needsProjectId } = options;
const { credPathKey, credPath, defaultCheckModel, needsProjectId, urlKeys } = options;
const newProvider = {
[credPathKey]: credPath,
@ -307,6 +312,13 @@ export function createProviderConfig(options) {
if (needsProjectId) {
newProvider.PROJECT_ID = '';
}
// 初始化可选的 URL 配置项
if (urlKeys && Array.isArray(urlKeys)) {
urlKeys.forEach(key => {
newProvider[key] = '';
});
}
return newProvider;
}

View file

@ -628,6 +628,16 @@ export async function handleUIApiRequests(method, pathParam, req, res, currentCo
if (newConfig.KIRO_OAUTH_CREDS_BASE64 !== undefined) currentConfig.KIRO_OAUTH_CREDS_BASE64 = newConfig.KIRO_OAUTH_CREDS_BASE64;
if (newConfig.KIRO_OAUTH_CREDS_FILE_PATH !== undefined) currentConfig.KIRO_OAUTH_CREDS_FILE_PATH = newConfig.KIRO_OAUTH_CREDS_FILE_PATH;
if (newConfig.QWEN_OAUTH_CREDS_FILE_PATH !== undefined) currentConfig.QWEN_OAUTH_CREDS_FILE_PATH = newConfig.QWEN_OAUTH_CREDS_FILE_PATH;
// New Provider URLs
if (newConfig.QWEN_BASE_URL !== undefined) currentConfig.QWEN_BASE_URL = newConfig.QWEN_BASE_URL;
if (newConfig.QWEN_OAUTH_BASE_URL !== undefined) currentConfig.QWEN_OAUTH_BASE_URL = newConfig.QWEN_OAUTH_BASE_URL;
if (newConfig.GEMINI_BASE_URL !== undefined) currentConfig.GEMINI_BASE_URL = newConfig.GEMINI_BASE_URL;
if (newConfig.ANTIGRAVITY_BASE_URL_DAILY !== undefined) currentConfig.ANTIGRAVITY_BASE_URL_DAILY = newConfig.ANTIGRAVITY_BASE_URL_DAILY;
if (newConfig.ANTIGRAVITY_BASE_URL_AUTOPUSH !== undefined) currentConfig.ANTIGRAVITY_BASE_URL_AUTOPUSH = newConfig.ANTIGRAVITY_BASE_URL_AUTOPUSH;
if (newConfig.KIRO_REFRESH_URL !== undefined) currentConfig.KIRO_REFRESH_URL = newConfig.KIRO_REFRESH_URL;
if (newConfig.KIRO_REFRESH_IDC_URL !== undefined) currentConfig.KIRO_REFRESH_IDC_URL = newConfig.KIRO_REFRESH_IDC_URL;
if (newConfig.KIRO_BASE_URL !== undefined) currentConfig.KIRO_BASE_URL = newConfig.KIRO_BASE_URL;
if (newConfig.SYSTEM_PROMPT_FILE_PATH !== undefined) currentConfig.SYSTEM_PROMPT_FILE_PATH = newConfig.SYSTEM_PROMPT_FILE_PATH;
if (newConfig.SYSTEM_PROMPT_MODE !== undefined) currentConfig.SYSTEM_PROMPT_MODE = newConfig.SYSTEM_PROMPT_MODE;
if (newConfig.PROMPT_LOG_BASE_NAME !== undefined) currentConfig.PROMPT_LOG_BASE_NAME = newConfig.PROMPT_LOG_BASE_NAME;
@ -680,6 +690,17 @@ export async function handleUIApiRequests(method, pathParam, req, res, currentCo
KIRO_OAUTH_CREDS_BASE64: currentConfig.KIRO_OAUTH_CREDS_BASE64,
KIRO_OAUTH_CREDS_FILE_PATH: currentConfig.KIRO_OAUTH_CREDS_FILE_PATH,
QWEN_OAUTH_CREDS_FILE_PATH: currentConfig.QWEN_OAUTH_CREDS_FILE_PATH,
// Provider URLs
QWEN_BASE_URL: currentConfig.QWEN_BASE_URL,
QWEN_OAUTH_BASE_URL: currentConfig.QWEN_OAUTH_BASE_URL,
GEMINI_BASE_URL: currentConfig.GEMINI_BASE_URL,
ANTIGRAVITY_BASE_URL_DAILY: currentConfig.ANTIGRAVITY_BASE_URL_DAILY,
ANTIGRAVITY_BASE_URL_AUTOPUSH: currentConfig.ANTIGRAVITY_BASE_URL_AUTOPUSH,
KIRO_REFRESH_URL: currentConfig.KIRO_REFRESH_URL,
KIRO_REFRESH_IDC_URL: currentConfig.KIRO_REFRESH_IDC_URL,
KIRO_BASE_URL: currentConfig.KIRO_BASE_URL,
KIRO_AMAZON_Q_URL: currentConfig.KIRO_AMAZON_Q_URL,
KIRO_USAGE_LIMITS_URL: currentConfig.KIRO_USAGE_LIMITS_URL,
SYSTEM_PROMPT_FILE_PATH: currentConfig.SYSTEM_PROMPT_FILE_PATH,
SYSTEM_PROMPT_MODE: currentConfig.SYSTEM_PROMPT_MODE,
PROMPT_LOG_BASE_NAME: currentConfig.PROMPT_LOG_BASE_NAME,

View file

@ -32,6 +32,12 @@ async function loadConfiguration() {
if (projectIdEl) projectIdEl.value = data.PROJECT_ID || '';
if (geminiOauthCredsBase64El) geminiOauthCredsBase64El.value = data.GEMINI_OAUTH_CREDS_BASE64 || '';
if (geminiOauthCredsFilePathEl) geminiOauthCredsFilePathEl.value = data.GEMINI_OAUTH_CREDS_FILE_PATH || '';
const geminiBaseUrlEl = document.getElementById('geminiBaseUrl');
if (geminiBaseUrlEl) geminiBaseUrlEl.value = data.GEMINI_BASE_URL || '';
const antigravityBaseUrlDailyEl = document.getElementById('antigravityBaseUrlDaily');
if (antigravityBaseUrlDailyEl) antigravityBaseUrlDailyEl.value = data.ANTIGRAVITY_BASE_URL_DAILY || '';
const antigravityBaseUrlAutopushEl = document.getElementById('antigravityBaseUrlAutopush');
if (antigravityBaseUrlAutopushEl) antigravityBaseUrlAutopushEl.value = data.ANTIGRAVITY_BASE_URL_AUTOPUSH || '';
// OpenAI Custom
const openaiApiKeyEl = document.getElementById('openaiApiKey');
@ -53,10 +59,20 @@ async function loadConfiguration() {
if (kiroOauthCredsBase64El) kiroOauthCredsBase64El.value = data.KIRO_OAUTH_CREDS_BASE64 || '';
if (kiroOauthCredsFilePathEl) kiroOauthCredsFilePathEl.value = data.KIRO_OAUTH_CREDS_FILE_PATH || '';
const kiroBaseUrlEl = document.getElementById('kiroBaseUrl');
if (kiroBaseUrlEl) kiroBaseUrlEl.value = data.KIRO_BASE_URL || '';
const kiroRefreshUrlEl = document.getElementById('kiroRefreshUrl');
if (kiroRefreshUrlEl) kiroRefreshUrlEl.value = data.KIRO_REFRESH_URL || '';
const kiroRefreshIdcUrlEl = document.getElementById('kiroRefreshIdcUrl');
if (kiroRefreshIdcUrlEl) kiroRefreshIdcUrlEl.value = data.KIRO_REFRESH_IDC_URL || '';
// Qwen OAuth
const qwenOauthCredsFilePathEl = document.getElementById('qwenOauthCredsFilePath');
if (qwenOauthCredsFilePathEl) qwenOauthCredsFilePathEl.value = data.QWEN_OAUTH_CREDS_FILE_PATH || '';
const qwenBaseUrlEl = document.getElementById('qwenBaseUrl');
if (qwenBaseUrlEl) qwenBaseUrlEl.value = data.QWEN_BASE_URL || '';
const qwenOauthBaseUrlEl = document.getElementById('qwenOauthBaseUrl');
if (qwenOauthBaseUrlEl) qwenOauthBaseUrlEl.value = data.QWEN_OAUTH_BASE_URL || '';
// OpenAI Responses
const openaiResponsesApiKeyEl = document.getElementById('openaiResponsesApiKey');
@ -150,6 +166,13 @@ async function saveConfiguration() {
config.GEMINI_OAUTH_CREDS_BASE64 = null;
config.GEMINI_OAUTH_CREDS_FILE_PATH = document.getElementById('geminiOauthCredsFilePath')?.value || '';
}
config.GEMINI_BASE_URL = document.getElementById('geminiBaseUrl')?.value || null;
break;
case 'gemini-antigravity':
config.ANTIGRAVITY_BASE_URL_DAILY = document.getElementById('antigravityBaseUrlDaily')?.value || null;
config.ANTIGRAVITY_BASE_URL_AUTOPUSH = document.getElementById('antigravityBaseUrlAutopush')?.value || null;
config.ANTIGRAVITY_OAUTH_CREDS_FILE_PATH = document.getElementById('antigravityOauthCredsFilePath')?.value || '';
break;
case 'openai-custom':
@ -171,10 +194,15 @@ async function saveConfiguration() {
config.KIRO_OAUTH_CREDS_BASE64 = null;
config.KIRO_OAUTH_CREDS_FILE_PATH = document.getElementById('kiroOauthCredsFilePath')?.value || '';
}
config.KIRO_BASE_URL = document.getElementById('kiroBaseUrl')?.value || null;
config.KIRO_REFRESH_URL = document.getElementById('kiroRefreshUrl')?.value || null;
config.KIRO_REFRESH_IDC_URL = document.getElementById('kiroRefreshIdcUrl')?.value || null;
break;
case 'openai-qwen-oauth':
config.QWEN_OAUTH_CREDS_FILE_PATH = document.getElementById('qwenOauthCredsFilePath')?.value || '';
config.QWEN_BASE_URL = document.getElementById('qwenBaseUrl')?.value || null;
config.QWEN_OAUTH_BASE_URL = document.getElementById('qwenOauthBaseUrl')?.value || null;
break;
case 'openaiResponses-custom':

View file

@ -435,7 +435,10 @@ function renderProviderList(providers) {
* @returns {string} HTML字符串
*/
function renderProviderConfig(provider) {
// 获取字段映射,确保顺序一致
// 获取该提供商类型的所有字段定义(从 utils.js
const fieldConfigs = getProviderTypeFields(currentProviderType);
// 获取字段显示顺序
const fieldOrder = getFieldOrder(provider);
// 先渲染基础配置字段customName、checkModelName 和 checkHealth
@ -447,6 +450,10 @@ function renderProviderConfig(provider) {
const value = provider[fieldKey];
const displayValue = value || '';
// 查找字段定义以获取 placeholder
const fieldDef = fieldConfigs.find(f => f.id === fieldKey) || fieldConfigs.find(f => f.id.toUpperCase() === fieldKey.toUpperCase()) || {};
const placeholder = fieldDef.placeholder || (fieldKey === 'customName' ? '节点自定义名称' : (fieldKey === 'checkModelName' ? '例如: gpt-3.5-turbo' : ''));
// 如果是 customName 字段,使用普通文本输入框
if (fieldKey === 'customName') {
html += `
@ -457,7 +464,7 @@ function renderProviderConfig(provider) {
readonly
data-config-key="${fieldKey}"
data-config-value="${value || ''}"
placeholder="节点自定义名称">
placeholder="${placeholder}">
</div>
`;
} else if (fieldKey === 'checkHealth') {
@ -485,7 +492,8 @@ function renderProviderConfig(provider) {
value="${displayValue}"
readonly
data-config-key="${fieldKey}"
data-config-value="${value || ''}">
data-config-value="${value || ''}"
placeholder="${placeholder}">
</div>
`;
}
@ -504,6 +512,7 @@ function renderProviderConfig(provider) {
const field1IsPassword = field1Key.toLowerCase().includes('key') || field1Key.toLowerCase().includes('password');
const field1IsOAuthFilePath = field1Key.includes('OAUTH_CREDS_FILE_PATH');
const field1DisplayValue = field1IsPassword && field1Value ? '••••••••' : (field1Value || '');
const field1Def = fieldConfigs.find(f => f.id === field1Key) || fieldConfigs.find(f => f.id.toUpperCase() === field1Key.toUpperCase()) || {};
if (field1IsPassword) {
html += `
@ -514,8 +523,9 @@ function renderProviderConfig(provider) {
value="${field1DisplayValue}"
readonly
data-config-key="${field1Key}"
data-config-value="${field1Value || ''}">
<button type="button" class="password-toggle" data-target="${field1Key}">
data-config-value="${field1Value || ''}"
placeholder="${field1Def.placeholder || ''}">
<button type="button" class="password-toggle" data-target="${field1Key}">
<i class="fas fa-eye"></i>
</button>
</div>
@ -532,8 +542,9 @@ function renderProviderConfig(provider) {
value="${field1Value || ''}"
readonly
data-config-key="${field1Key}"
data-config-value="${field1Value || ''}">
<button type="button" class="btn btn-outline upload-btn" data-target="edit-${provider.uuid}-${field1Key}" aria-label="上传文件" disabled>
data-config-value="${field1Value || ''}"
placeholder="${field1Def.placeholder || ''}">
<button type="button" class="btn btn-outline upload-btn" data-target="edit-${provider.uuid}-${field1Key}" aria-label="上传文件" disabled>
<i class="fas fa-upload"></i>
</button>
</div>
@ -547,7 +558,8 @@ function renderProviderConfig(provider) {
value="${field1DisplayValue}"
readonly
data-config-key="${field1Key}"
data-config-value="${field1Value || ''}">
data-config-value="${field1Value || ''}"
placeholder="${field1Def.placeholder || ''}">
</div>
`;
}
@ -560,6 +572,7 @@ function renderProviderConfig(provider) {
const field2IsPassword = field2Key.toLowerCase().includes('key') || field2Key.toLowerCase().includes('password');
const field2IsOAuthFilePath = field2Key.includes('OAUTH_CREDS_FILE_PATH');
const field2DisplayValue = field2IsPassword && field2Value ? '••••••••' : (field2Value || '');
const field2Def = fieldConfigs.find(f => f.id === field2Key) || fieldConfigs.find(f => f.id.toUpperCase() === field2Key.toUpperCase()) || {};
if (field2IsPassword) {
html += `
@ -570,7 +583,8 @@ function renderProviderConfig(provider) {
value="${field2DisplayValue}"
readonly
data-config-key="${field2Key}"
data-config-value="${field2Value || ''}">
data-config-value="${field2Value || ''}"
placeholder="${field2Def.placeholder || ''}">
<button type="button" class="password-toggle" data-target="${field2Key}">
<i class="fas fa-eye"></i>
</button>
@ -588,7 +602,8 @@ function renderProviderConfig(provider) {
value="${field2Value || ''}"
readonly
data-config-key="${field2Key}"
data-config-value="${field2Value || ''}">
data-config-value="${field2Value || ''}"
placeholder="${field2Def.placeholder || ''}">
<button type="button" class="btn btn-outline upload-btn" data-target="edit-${provider.uuid}-${field2Key}" aria-label="上传文件" disabled>
<i class="fas fa-upload"></i>
</button>
@ -603,7 +618,8 @@ function renderProviderConfig(provider) {
value="${field2DisplayValue}"
readonly
data-config-key="${field2Key}"
data-config-value="${field2Value || ''}">
data-config-value="${field2Value || ''}"
placeholder="${field2Def.placeholder || ''}">
</div>
`;
}
@ -652,54 +668,48 @@ function getFieldOrder(provider) {
'openai-custom': ['OPENAI_API_KEY', 'OPENAI_BASE_URL'],
'openaiResponses-custom': ['OPENAI_API_KEY', 'OPENAI_BASE_URL'],
'claude-custom': ['CLAUDE_API_KEY', 'CLAUDE_BASE_URL'],
'gemini-cli-oauth': ['PROJECT_ID', 'GEMINI_OAUTH_CREDS_FILE_PATH'],
'claude-kiro-oauth': ['KIRO_OAUTH_CREDS_FILE_PATH'],
'openai-qwen-oauth': ['QWEN_OAUTH_CREDS_FILE_PATH'],
'gemini-antigravity': ['PROJECT_ID', 'ANTIGRAVITY_OAUTH_CREDS_FILE_PATH']
'gemini-cli-oauth': ['PROJECT_ID', 'GEMINI_OAUTH_CREDS_FILE_PATH', 'GEMINI_BASE_URL'],
'claude-kiro-oauth': ['KIRO_OAUTH_CREDS_FILE_PATH', 'KIRO_BASE_URL', 'KIRO_REFRESH_URL'],
'openai-qwen-oauth': ['QWEN_OAUTH_CREDS_FILE_PATH', 'QWEN_BASE_URL', 'QWEN_OAUTH_BASE_URL'],
'gemini-antigravity': ['PROJECT_ID', 'ANTIGRAVITY_OAUTH_CREDS_FILE_PATH', 'ANTIGRAVITY_BASE_URL_DAILY', 'ANTIGRAVITY_BASE_URL_AUTOPUSH']
};
// 获取所有其他配置项
// 尝试从全局或当前模态框上下文中推断提供商类型
let providerType = currentProviderType;
if (!providerType) {
if (provider.OPENAI_API_KEY && provider.OPENAI_BASE_URL) {
providerType = 'openai-custom';
} else if (provider.CLAUDE_API_KEY && provider.CLAUDE_BASE_URL) {
providerType = 'claude-custom';
} else if (provider.GEMINI_OAUTH_CREDS_FILE_PATH) {
providerType = 'gemini-cli-oauth';
} else if (provider.KIRO_OAUTH_CREDS_FILE_PATH) {
providerType = 'claude-kiro-oauth';
} else if (provider.QWEN_OAUTH_CREDS_FILE_PATH) {
providerType = 'openai-qwen-oauth';
} else if (provider.ANTIGRAVITY_OAUTH_CREDS_FILE_PATH) {
providerType = 'gemini-antigravity';
}
}
// 获取该类型应该具有的所有字段(预定义顺序)
const predefinedOrder = providerType ? (fieldOrderMap[providerType] || []) : [];
// 获取当前对象中存在且不在预定义列表中的其他字段
const otherFields = Object.keys(provider).filter(key =>
!excludedFields.includes(key) && !orderedFields.includes(key)
!excludedFields.includes(key) &&
!orderedFields.includes(key) &&
!predefinedOrder.includes(key)
);
otherFields.sort();
// 合并所有要显示的字段
const allExpectedFields = [...orderedFields, ...predefinedOrder, ...otherFields];
// 尝试从 provider 中推断提供商类型
let providerType = null;
if (provider.OPENAI_API_KEY && provider.OPENAI_BASE_URL) {
providerType = 'openai-custom'; // 或 openaiResponses-custom顺序相同
} else if (provider.CLAUDE_API_KEY && provider.CLAUDE_BASE_URL) {
providerType = 'claude-custom';
} else if (provider.GEMINI_OAUTH_CREDS_FILE_PATH) {
providerType = 'gemini-cli-oauth';
} else if (provider.KIRO_OAUTH_CREDS_FILE_PATH) {
providerType = 'claude-kiro-oauth';
} else if (provider.QWEN_OAUTH_CREDS_FILE_PATH) {
providerType = 'openai-qwen-oauth';
} else if (provider.ANTIGRAVITY_OAUTH_CREDS_FILE_PATH) {
providerType = 'gemini-antigravity';
}
// 如果能识别提供商类型,使用预定义的顺序
if (providerType && fieldOrderMap[providerType]) {
const predefinedOrder = fieldOrderMap[providerType];
const orderedOtherFields = [];
const remainingFields = [...otherFields];
// 先按预定义顺序添加字段
predefinedOrder.forEach(fieldKey => {
const index = remainingFields.indexOf(fieldKey);
if (index !== -1) {
orderedOtherFields.push(fieldKey);
remainingFields.splice(index, 1);
}
});
// 剩余字段按字母顺序添加
remainingFields.sort();
orderedOtherFields.push(...remainingFields);
return [...orderedFields, ...orderedOtherFields].filter(key => provider.hasOwnProperty(key));
}
// 只有在字段确实存在于 provider 中,或者它是该提供商类型的预定义字段时才显示
return allExpectedFields.filter(key =>
provider.hasOwnProperty(key) || predefinedOrder.includes(key)
);
// 如果无法识别提供商类型,按字母顺序排序
otherFields.sort();
@ -1071,16 +1081,21 @@ function showAddProviderForm(providerType) {
function addDynamicConfigFields(form, providerType) {
const configFields = form.querySelector('#dynamicConfigFields');
// 获取该提供商类型的字段配置
const providerFields = getProviderTypeFields(providerType);
// 获取该提供商类型的字段配置(已经在 utils.js 中包含了 URL 字段)
const allFields = getProviderTypeFields(providerType);
// 过滤掉已经在 form-grid 中硬编码显示的三个基础字段,避免重复
const baseFields = ['customName', 'checkModelName', 'checkHealth'];
const filteredFields = allFields.filter(f => !baseFields.some(bf => f.id.toLowerCase().includes(bf.toLowerCase())));
let fields = '';
if (providerFields.length > 0) {
if (filteredFields.length > 0) {
// 分组显示,每行两个字段
for (let i = 0; i < providerFields.length; i += 2) {
for (let i = 0; i < filteredFields.length; i += 2) {
fields += '<div class="form-grid">';
const field1 = providerFields[i];
const field1 = filteredFields[i];
// 检查是否为密码类型字段
const isPassword1 = field1.type === 'password';
// 检查是否为OAuth凭据文件路径字段
@ -1122,7 +1137,7 @@ function addDynamicConfigFields(form, providerType) {
`;
}
const field2 = providerFields[i + 1];
const field2 = filteredFields[i + 1];
if (field2) {
// 检查是否为密码类型字段
const isPassword2 = field2.type === 'password';
@ -1215,35 +1230,14 @@ async function addProvider(providerType) {
checkHealth
};
// 根据提供商类型收集配置
switch (providerType) {
case 'openai-custom':
providerConfig.OPENAI_API_KEY = document.getElementById('newOpenaiApiKey')?.value || '';
providerConfig.OPENAI_BASE_URL = document.getElementById('newOpenaiBaseUrl')?.value || '';
break;
case 'openaiResponses-custom':
providerConfig.OPENAI_API_KEY = document.getElementById('newOpenaiApiKey')?.value || '';
providerConfig.OPENAI_BASE_URL = document.getElementById('newOpenaiBaseUrl')?.value || '';
break;
case 'claude-custom':
providerConfig.CLAUDE_API_KEY = document.getElementById('newClaudeApiKey')?.value || '';
providerConfig.CLAUDE_BASE_URL = document.getElementById('newClaudeBaseUrl')?.value || '';
break;
case 'gemini-cli-oauth':
providerConfig.PROJECT_ID = document.getElementById('newProjectId')?.value || '';
providerConfig.GEMINI_OAUTH_CREDS_FILE_PATH = document.getElementById('newGeminiOauthCredsFilePath')?.value || '';
break;
case 'claude-kiro-oauth':
providerConfig.KIRO_OAUTH_CREDS_FILE_PATH = document.getElementById('newKiroOauthCredsFilePath')?.value || '';
break;
case 'openai-qwen-oauth':
providerConfig.QWEN_OAUTH_CREDS_FILE_PATH = document.getElementById('newQwenOauthCredsFilePath')?.value || '';
break;
case 'gemini-antigravity':
providerConfig.PROJECT_ID = document.getElementById('newProjectId')?.value || '';
providerConfig.ANTIGRAVITY_OAUTH_CREDS_FILE_PATH = document.getElementById('newAntigravityOauthCredsFilePath')?.value || '';
break;
}
// 根据提供商类型动态收集配置字段(自动匹配 utils.js 中的定义)
const allFields = getProviderTypeFields(providerType);
allFields.forEach(field => {
const element = document.getElementById(`new${field.id}`);
if (element) {
providerConfig[field.id] = element.value || '';
}
});
try {
await window.apiClient.post('/providers', {

View file

@ -488,8 +488,104 @@ function showAuthModal(authUrl, authInfo) {
// 在浏览器中打开按钮
const openBtn = modal.querySelector('.open-auth-btn');
openBtn.addEventListener('click', () => {
window.open(authUrl, '_blank');
showToast('已在新标签页中打开授权页面', 'success');
// 使用子窗口打开,以便监听 URL 变化
const width = 600;
const height = 700;
const left = (window.screen.width - width) / 2;
const top = (window.screen.height - height) / 2;
const authWindow = window.open(
authUrl,
'OAuthAuthWindow',
`width=${width},height=${height},left=${left},top=${top},status=no,resizable=yes,scrollbars=yes`
);
if (authWindow) {
showToast('已打开授权窗口,请在窗口中完成操作', 'info');
// 添加手动输入回调 URL 的 UI
const urlSection = modal.querySelector('.auth-url-section');
const manualInputHtml = `
<div class="manual-callback-section" style="margin-top: 20px; padding: 15px; background: #fffbeb; border: 1px solid #fef3c7; border-radius: 8px;">
<h4 style="color: #92400e; margin-bottom: 8px;"><i class="fas fa-exclamation-circle"></i> </h4>
<p style="font-size: 0.875rem; color: #b45309; margin-bottom: 10px;">如果授权窗口重定向后显示无法访问请将该窗口地址栏的 <strong>完整 URL</strong> </p>
<div class="auth-url-container" style="display: flex; gap: 5px;">
<input type="text" class="manual-callback-input" placeholder="粘贴回调 URL (包含 code=...)" style="flex: 1; padding: 8px; border: 1px solid #fcd34d; border-radius: 4px; background: white; color: black;">
<button class="btn btn-success apply-callback-btn" style="padding: 8px 15px; white-space: nowrap; background: #059669; color: white; border: none; border-radius: 4px; cursor: pointer;">
<i class="fas fa-check"></i>
</button>
</div>
</div>
`;
urlSection.insertAdjacentHTML('afterend', manualInputHtml);
const manualInput = modal.querySelector('.manual-callback-input');
const applyBtn = modal.querySelector('.apply-callback-btn');
// 处理回调 URL 的核心逻辑
const processCallback = (urlStr) => {
try {
// 尝试清理 URL有些用户可能会复制多余的文字
const cleanUrlStr = urlStr.trim().match(/https?:\/\/[^\s]+/)?.[0] || urlStr.trim();
const url = new URL(cleanUrlStr);
if (url.searchParams.has('code') || url.searchParams.has('token')) {
clearInterval(pollTimer);
// 构造本地可处理的 URL只修改 hostname保持原始 URL 的端口号不变
const localUrl = new URL(url.href);
localUrl.hostname = window.location.hostname;
localUrl.protocol = window.location.protocol;
showToast('正在完成授权...', 'info');
// 优先在子窗口中跳转(如果没关)
if (authWindow && !authWindow.closed) {
authWindow.location.href = localUrl.href;
} else {
// 备选方案:通过隐藏 iframe 或者是 fetch
const img = new Image();
img.src = localUrl.href;
}
setTimeout(() => {
showToast('授权完成!', 'success');
if (authWindow && !authWindow.closed) authWindow.close();
modal.remove();
// 刷新列表以显示新状态
loadProviders();
}, 2500);
} else {
showToast('该 URL 似乎不包含有效的授权代码', 'warning');
}
} catch (err) {
console.error('处理回调失败:', err);
showToast('无效的 URL 格式', 'error');
}
};
applyBtn.addEventListener('click', () => {
processCallback(manualInput.value);
});
// 启动定时器轮询子窗口 URL
const pollTimer = setInterval(() => {
try {
if (authWindow.closed) {
clearInterval(pollTimer);
return;
}
// 如果能读到说明回到了同域
const currentUrl = authWindow.location.href;
if (currentUrl && (currentUrl.includes('code=') || currentUrl.includes('token='))) {
processCallback(currentUrl);
}
} catch (e) {
// 跨域受限是正常的
}
}, 1000);
} else {
showToast('授权窗口被浏览器拦截,请允许弹出窗口', 'error');
}
});
// 点击遮罩层关闭

View file

@ -309,11 +309,12 @@ body {
color: var(--text-primary);
}
.form-group label .optional-mark {
.optional-tag, .form-group label .optional-mark {
font-size: 0.75rem;
color: var(--text-secondary);
font-weight: 400;
margin-left: 0.25rem;
opacity: 0.8;
}
.form-control {

View file

@ -65,7 +65,14 @@ function getFieldLabel(key) {
'GEMINI_OAUTH_CREDS_FILE_PATH': 'OAuth凭据文件路径',
'KIRO_OAUTH_CREDS_FILE_PATH': 'OAuth凭据文件路径',
'QWEN_OAUTH_CREDS_FILE_PATH': 'OAuth凭据文件路径',
'ANTIGRAVITY_OAUTH_CREDS_FILE_PATH': 'OAuth凭据文件路径'
'ANTIGRAVITY_OAUTH_CREDS_FILE_PATH': 'OAuth凭据文件路径',
'GEMINI_BASE_URL': 'Gemini Base URL',
'KIRO_BASE_URL': 'Base URL',
'KIRO_REFRESH_URL': 'Refresh URL',
'QWEN_BASE_URL': 'Qwen Base URL',
'QWEN_OAUTH_BASE_URL': 'OAuth Base URL',
'ANTIGRAVITY_BASE_URL_DAILY': 'Daily Base URL',
'ANTIGRAVITY_BASE_URL_AUTOPUSH': 'Autopush Base URL'
};
return labelMap[key] || key;
@ -80,88 +87,136 @@ function getProviderTypeFields(providerType) {
const fieldConfigs = {
'openai-custom': [
{
id: 'OpenaiApiKey',
id: 'OPENAI_API_KEY',
label: 'OpenAI API Key',
type: 'password',
placeholder: 'sk-...'
},
{
id: 'OpenaiBaseUrl',
id: 'OPENAI_BASE_URL',
label: 'OpenAI Base URL',
type: 'text',
value: 'https://api.openai.com/v1'
placeholder: 'https://api.openai.com/v1'
}
],
'openaiResponses-custom': [
{
id: 'OpenaiApiKey',
id: 'OPENAI_API_KEY',
label: 'OpenAI API Key',
type: 'password',
placeholder: 'sk-...'
},
{
id: 'OpenaiBaseUrl',
id: 'OPENAI_BASE_URL',
label: 'OpenAI Base URL',
type: 'text',
value: 'https://api.openai.com/v1'
placeholder: 'https://api.openai.com/v1'
}
],
'claude-custom': [
{
id: 'ClaudeApiKey',
id: 'CLAUDE_API_KEY',
label: 'Claude API Key',
type: 'password',
placeholder: 'sk-ant-...'
},
{
id: 'ClaudeBaseUrl',
id: 'CLAUDE_BASE_URL',
label: 'Claude Base URL',
type: 'text',
value: 'https://api.anthropic.com'
placeholder: 'https://api.anthropic.com'
}
],
'gemini-cli-oauth': [
{
id: 'ProjectId',
id: 'PROJECT_ID',
label: '项目ID',
type: 'text',
placeholder: 'Google Cloud项目ID'
},
{
id: 'GeminiOauthCredsFilePath',
id: 'GEMINI_OAUTH_CREDS_FILE_PATH',
label: 'OAuth凭据文件路径',
type: 'text',
placeholder: '例如: ~/.gemini/oauth_creds.json'
},
{
id: 'GEMINI_BASE_URL',
label: 'Gemini Base URL <span class="optional-tag">(选填)</span>',
type: 'text',
placeholder: 'https://cloudcode-pa.googleapis.com'
}
],
'claude-kiro-oauth': [
{
id: 'KiroOauthCredsFilePath',
id: 'KIRO_OAUTH_CREDS_FILE_PATH',
label: 'OAuth凭据文件路径',
type: 'text',
placeholder: '例如: ~/.aws/sso/cache/kiro-auth-token.json'
},
{
id: 'KIRO_BASE_URL',
label: 'Base URL <span class="optional-tag">(选填)</span>',
type: 'text',
placeholder: 'https://codewhisperer.{{region}}.amazonaws.com/generateAssistantResponse'
},
{
id: 'KIRO_REFRESH_URL',
label: 'Refresh URL <span class="optional-tag">(选填)</span>',
type: 'text',
placeholder: 'https://prod.{{region}}.auth.desktop.kiro.dev/refreshToken'
},
{
id: 'KIRO_REFRESH_IDC_URL',
label: 'Refresh IDC URL <span class="optional-tag">(选填)</span>',
type: 'text',
placeholder: 'https://oidc.{{region}}.amazonaws.com/token'
}
],
'openai-qwen-oauth': [
{
id: 'QwenOauthCredsFilePath',
id: 'QWEN_OAUTH_CREDS_FILE_PATH',
label: 'OAuth凭据文件路径',
type: 'text',
placeholder: '例如: ~/.qwen/oauth_creds.json'
},
{
id: 'QWEN_BASE_URL',
label: 'Qwen Base URL <span class="optional-tag">(选填)</span>',
type: 'text',
placeholder: 'https://portal.qwen.ai/v1'
},
{
id: 'QWEN_OAUTH_BASE_URL',
label: 'OAuth Base URL <span class="optional-tag">(选填)</span>',
type: 'text',
placeholder: 'https://chat.qwen.ai'
}
],
'gemini-antigravity': [
{
id: 'ProjectId',
id: 'PROJECT_ID',
label: '项目ID (选填)',
type: 'text',
placeholder: 'Google Cloud项目ID (留空自动发现)'
},
{
id: 'AntigravityOauthCredsFilePath',
id: 'ANTIGRAVITY_OAUTH_CREDS_FILE_PATH',
label: 'OAuth凭据文件路径',
type: 'text',
placeholder: '例如: ~/.antigravity/oauth_creds.json'
},
{
id: 'ANTIGRAVITY_BASE_URL_DAILY',
label: 'Daily Base URL <span class="optional-tag">(选填)</span>',
type: 'text',
placeholder: 'https://daily-cloudcode-pa.sandbox.googleapis.com'
},
{
id: 'ANTIGRAVITY_BASE_URL_AUTOPUSH',
label: 'Autopush Base URL <span class="optional-tag">(选填)</span>',
type: 'text',
placeholder: 'https://autopush-cloudcode-pa.sandbox.googleapis.com'
}
]
};

View file

@ -496,7 +496,11 @@
<!-- Gemini CLI OAuth 配置 -->
<div class="provider-config" data-provider="gemini-cli-oauth">
<div class="form-group">
<label for="projectId">项目ID</label>
<label for="geminiBaseUrl">Gemini Base URL <span class="optional-tag">(选填)</span></label>
<input type="text" id="geminiBaseUrl" class="form-control" placeholder="https://cloudcode-pa.googleapis.com">
</div>
<div class="form-group">
<label for="projectId">项目ID <span class="optional-tag">(选填)</span></label>
<input type="text" id="projectId" class="form-control" placeholder="Google Cloud项目ID">
</div>
<div class="form-group">
@ -529,6 +533,14 @@
<!-- Gemini Antigravity 配置 -->
<div class="provider-config" data-provider="gemini-antigravity" style="display: none;">
<div class="form-group">
<label for="antigravityBaseUrlDaily">Daily Base URL <span class="optional-tag">(选填)</span></label>
<input type="text" id="antigravityBaseUrlDaily" class="form-control" placeholder="https://daily-cloudcode-pa.sandbox.googleapis.com">
</div>
<div class="form-group">
<label for="antigravityBaseUrlAutopush">Autopush Base URL <span class="optional-tag">(选填)</span></label>
<input type="text" id="antigravityBaseUrlAutopush" class="form-control" placeholder="https://autopush-cloudcode-pa.sandbox.googleapis.com">
</div>
<div class="form-group">
<label for="antigravityOauthCredsFilePath">OAuth凭据文件路径</label>
<div class="file-input-group">
@ -577,6 +589,20 @@
<!-- Claude Kiro OAuth 配置 -->
<div class="provider-config" data-provider="claude-kiro-oauth" style="display: none;">
<div class="form-row">
<div class="form-group">
<label for="kiroBaseUrl">Base URL <span class="optional-tag">(选填)</span></label>
<input type="text" id="kiroBaseUrl" class="form-control" placeholder="https://codewhisperer.{{region}}.amazonaws.com/generateAssistantResponse">
</div>
<div class="form-group">
<label for="kiroRefreshUrl">Refresh URL <span class="optional-tag">(选填)</span></label>
<input type="text" id="kiroRefreshUrl" class="form-control" placeholder="https://prod.{{region}}.auth.desktop.kiro.dev/refreshToken">
</div>
<div class="form-group">
<label for="kiroRefreshIdcUrl">Refresh IDC URL <span class="optional-tag">(选填)</span></label>
<input type="text" id="kiroRefreshIdcUrl" class="form-control" placeholder="https://oidc.{{region}}.amazonaws.com/token">
</div>
</div>
<div class="form-group">
<label>OAuth凭据</label>
<div class="radio-group">
@ -611,6 +637,14 @@
<!-- Qwen OAuth 配置 -->
<div class="provider-config" data-provider="openai-qwen-oauth" style="display: none;">
<div class="form-group">
<label for="qwenBaseUrl">Qwen Base URL <span class="optional-tag">(选填)</span></label>
<input type="text" id="qwenBaseUrl" class="form-control" placeholder="https://portal.qwen.ai/v1">
</div>
<div class="form-group">
<label for="qwenOauthBaseUrl">OAuth Base URL <span class="optional-tag">(选填)</span></label>
<input type="text" id="qwenOauthBaseUrl" class="form-control" placeholder="https://chat.qwen.ai">
</div>
<div class="form-group">
<label for="qwenOauthCredsFilePath">OAuth凭据文件路径</label>
<div class="file-input-group">