Compare commits

...

7 Commits

14 changed files with 222 additions and 31 deletions
+1
View File
@@ -10,6 +10,7 @@ api.md
usage.json
CLAUDE.md
AGENTS.md
management-api*
antigravity_usage.json
codex_usage.json
style.md
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Router-For.ME
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+40 -11
View File
@@ -1,23 +1,23 @@
# CLI Proxy API Management Center
A single-file WebUI (React + TypeScript) for operating and troubleshooting the **CLI Proxy API** via its **Management API** (config, credentials, logs, and usage).
A single-file Web UI (React + TypeScript) for operating and troubleshooting the **CLI Proxy API** via its **Management API** (config, credentials, logs, and usage).
[中文文档](README_CN.md)
**Main Project**: https://github.com/router-for-me/CLIProxyAPI
**Example URL**: https://remote.router-for.me/
**Minimum Required Version**: ≥ 6.3.0 (recommended ≥ 6.5.0)
**Minimum Required Version**: ≥ 6.8.0 (recommended ≥ 6.8.15)
Since version 6.0.19, the WebUI ships with the main program; access it via `/management.html` on the API port once the service is running.
Since version 6.0.19, the Web UI ships with the main program; access it via `/management.html` on the API port once the service is running.
## What this is (and isnt)
- This repository is the WebUI only. It talks to the CLI Proxy API **Management API** (`/v0/management`) to read/update config, upload credentials, view logs, and inspect usage.
- This repository is the Web UI only. It talks to the CLI Proxy API **Management API** (`/v0/management`) to read/update config, upload credentials, view logs, and inspect usage.
- It is **not** a proxy and does not forward traffic.
## Quick start
### Option A: Use the WebUI bundled in CLIProxyAPI (recommended)
### Option A: Use the Web UI bundled in CLI Proxy API (recommended)
1. Start your CLI Proxy API service.
2. Open: `http://<host>:<api_port>/management.html`
@@ -32,7 +32,7 @@ npm install
npm run dev
```
Open `http://localhost:5173`, then connect to your CLI Proxy API instance.
Open `http://localhost:5173`, then connect to your CLI Proxy API backend instance.
### Option C: Build a single HTML file
@@ -42,7 +42,7 @@ npm run build
```
- Output: `dist/index.html` (all assets are inlined).
- For CLIProxyAPI bundling, the release workflow renames it to `management.html`.
- For CLI Proxy API bundling, the release workflow renames it to `management.html`.
- To preview locally: `npm run preview`
Tip: opening `dist/index.html` via `file://` may be blocked by browser CORS; serving it (preview/static server) is more reliable.
@@ -74,19 +74,48 @@ See `api.md` for the full authentication rules, server-side limits, and edge cas
## What you can manage (mapped to the UI pages)
- **Dashboard**: connection status, server version/build date, quick counts, model availability snapshot.
- **Basic Settings**: debug, proxy URL, request retry, quota fallback (switch project/preview models), usage statistics, request logging, file logging, WebSocket auth.
- **Basic Settings**: debug, proxy URL, request retry, quota fallback (switch project or preview models when limits reached), usage statistics, request logging, file logging, WebSocket auth.
- **API Keys**: manage proxy `api-keys` (add/edit/delete).
- **AI Providers**:
- Gemini/Codex/Claude key entries (base URL, headers, proxy, model aliases, excluded models, prefix).
- OpenAI-compatible providers (multiple API keys, custom headers, model alias import via `/v1/models`, optional browser-side chat/completions test).
- Gemini/Codex/Claude/Vertex key entries (base URL, headers, proxy, model aliases, excluded models, prefix).
- OpenAI-compatible providers (multiple API keys, custom headers, model alias import via `/v1/models`, optional browser-side "chat/completions" test).
- Ampcode integration (upstream URL/key, force mappings, model mapping table).
- **Auth Files**: upload/download/delete JSON credentials, filter/search/pagination, runtime-only indicators, view supported models per credential (when the server supports it), manage OAuth excluded models (supports `*` wildcards).
- **Auth Files**: upload/download/delete JSON credentials, filter/search/pagination, runtime-only indicators, view supported models per credential (when the server supports it), manage OAuth excluded models (supports `*` wildcards), configure OAuth model alias mappings.
- **OAuth**: start OAuth/device flows for supported providers, poll status, optionally submit callback `redirect_url`; includes iFlow cookie import.
- **Quota Management**: manage quota limits and usage for Claude, Antigravity, Codex, Gemini CLI, and other providers.
- **Usage**: requests/tokens charts (hour/day), per-API & per-model breakdown, cached/reasoning token breakdown, RPM/TPM window, optional cost estimation with locally-saved model pricing.
- **Config**: edit `/config.yaml` in-browser with YAML highlighting + search, then save/reload.
- **Logs**: tail logs with incremental polling, auto-refresh, search, hide management traffic, clear logs; download request error log files.
- **System**: quick links + fetch `/v1/models` (grouped view). Requires at least one proxy API key to query models.
## Tech Stack
- React 19 + TypeScript 5.9
- Vite 7 (single-file build)
- Zustand (state management)
- Axios (HTTP client)
- react-router-dom v7 (HashRouter)
- Chart.js (data visualization)
- CodeMirror 6 (YAML editor)
- SCSS Modules (styling)
- i18next (internationalization)
## Internationalization
Currently supports three languages:
- English (en)
- Simplified Chinese (zh-CN)
- Russian (ru)
The UI language is automatically detected from browser settings and can be manually switched at the bottom of the page.
## Browser Compatibility
- Build target: `ES2015`
- Supports modern browsers (Chrome, Firefox, Safari, Edge)
- Responsive layout for mobile and tablet access
## Build & release notes
- Vite produces a **single HTML** output (`dist/index.html`) with all assets inlined (via `vite-plugin-singlefile`).
+40 -12
View File
@@ -1,14 +1,14 @@
# CLI Proxy API 管理中心
用于管理与排障 **CLI Proxy API** 的单文件 WebUIReact + TypeScript),通过 **Management API** 完成配置、凭据、日志与统计等运维工作。
用于管理与故障排查 **CLI Proxy API** 的单文件 Web UIReact + TypeScript),通过 **Management API** 完成配置、凭据、日志与统计等管理操作。
[English](README.md)
**主项目**: https://github.com/router-for-me/CLIProxyAPI
**示例地址**: https://remote.router-for.me/
**最低版本要求**: ≥ 6.3.0(推荐 ≥ 6.5.0
**最低版本要求**: ≥ 6.8.0(推荐 ≥ 6.8.15
Since version 6.0.19, the WebUI ships with the main program; access it via `/management.html` on the API port once the service is running.
从6.0.19版本开始,Web UI 随主程序一起提供;服务运行后,通过 API 端口上的"/management.html"访问它。
## 这是什么(以及不是什么)
@@ -17,7 +17,7 @@ Since version 6.0.19, the WebUI ships with the main program; access it via `/man
## 快速开始
### 方式 A:使用 CLIProxyAPI 自带的 WebUI(推荐)
### 方式 A:使用 CLI Proxy API 自带的 Web UI(推荐)
1. 启动 CLI Proxy API 服务。
2. 打开:`http://<host>:<api_port>/management.html`
@@ -32,7 +32,7 @@ npm install
npm run dev
```
打开 `http://localhost:5173`,然后连接到你的 CLI Proxy API 实例。
打开 `http://localhost:5173`,然后连接到你的 CLI Proxy API 后端实例。
### 方式 C:构建单文件 HTML
@@ -42,7 +42,7 @@ npm run build
```
- 构建产物:`dist/index.html`(资源已全部内联)。
- 在 CLIProxyAPI 的发布流程里会重命名为 `management.html`
- 在 CLI Proxy API 的发布流程里会重命名为 `management.html`
- 本地预览:`npm run preview`
提示:直接用 `file://` 打开 `dist/index.html` 可能遇到浏览器 CORS 限制;更稳妥的方式是用预览/静态服务器打开。
@@ -51,7 +51,7 @@ npm run build
### API 地址怎么填
以下格式均可,WebUI 会自动归一化:
以下格式均可,Web UI 会自动归一化:
- `localhost:8317`
- `http://192.168.1.10:8317`
@@ -64,29 +64,57 @@ npm run build
- `Authorization: Bearer <MANAGEMENT_KEY>`(默认)
这与 WebUI 中API Keys页面管理的 `api-keys` 不同:后者是代理对外接口(如 OpenAI 兼容接口)给客户端使用的鉴权 key。
这与 Web UI 中"API Keys"页面管理的 `api-keys` 不同:后者是代理对外接口(如 OpenAI 兼容接口)给客户端使用的鉴权 key。
### 远程管理
当你从非 localhost 的浏览器访问时,服务端通常需要开启远程管理(例如 `allow-remote-management: true`)。
完整鉴权规则、限制与边界情况请查看 `api.md`
## 功能一览(按页面对应)
- **仪表盘**:连接状态、服务版本/构建时间、关键数量概览、可用模型概览。
- **基础设置**:调试开关、代理 URL、请求重试、配额回退(切项目/切预览模型)、使用统计、请求日志、文件日志、WebSocket 鉴权。
- **基础设置**:调试开关、代理 URL、请求重试、配额回退(达到上限时切换项目或预览模型)、使用统计、请求日志、文件日志、WebSocket 鉴权。
- **API Keys**:管理代理 `api-keys`(增/改/删)。
- **AI 提供商**
- Gemini/Codex/Claude 配置(Base URL、Headers、代理、模型别名、排除模型、Prefix)。
- Gemini/Codex/Claude/Vertex 配置(Base URL、Headers、代理、模型别名、排除模型、Prefix)。
- OpenAI 兼容提供商(多 Key、Header、自助从 `/v1/models` 拉取并导入模型别名、可选浏览器侧 `chat/completions` 测试)。
- Ampcode 集成(上游地址/密钥、强制映射、模型映射表)。
- **认证文件**:上传/下载/删除 JSON 凭据,筛选/搜索/分页,标记 runtime-only;查看单个凭据可用模型(依赖后端支持);管理 OAuth 排除模型(支持 `*` 通配符)。
- **认证文件**:上传/下载/删除 JSON 凭据,筛选/搜索/分页,标记 runtime-only;查看单个凭据可用模型(依赖后端支持);管理 OAuth 排除模型(支持 `*` 通配符);配置 OAuth 模型别名映射
- **OAuth**:对支持的提供商发起 OAuth/设备码流程,轮询状态;可选提交回调 `redirect_url`;包含 iFlow Cookie 导入。
- **配额管理**:管理 Claude、Antigravity、Codex、Gemini CLI 等提供商的配额上限与使用情况。
- **使用统计**:按小时/天图表、按 API 与按模型统计、缓存/推理 Token 拆分、RPM/TPM 时间窗、可选本地保存的模型价格用于费用估算。
- **配置文件**:浏览器内编辑 `/config.yaml`(YAML 高亮 + 搜索),保存/重载。
- **日志**:增量拉取日志、自动刷新、搜索、隐藏管理端流量、清空日志;下载请求错误日志文件。
- **系统信息**:快捷链接 + 拉取 `/v1/models` 并分组展示(需要至少一个代理 API Key 才能查询模型)。
## 技术栈
- React 19 + TypeScript 5.9
- Vite 7(单文件构建)
- Zustand(状态管理)
- AxiosHTTP 客户端)
- react-router-dom v7HashRouter
- Chart.js(数据可视化)
- CodeMirror 6YAML 编辑器)
- SCSS Modules(样式)
- i18next(国际化)
## 多语言支持
目前支持三种语言:
- 英文 (en)
- 简体中文 (zh-CN)
- 俄文 (ru)
界面语言会根据浏览器设置自动切换,也可在页面底部手动切换。
## 浏览器兼容性
- 构建目标:`ES2015`
- 支持 Chrome、Firefox、Safari、Edge 等现代浏览器
- 支持移动端响应式布局,可通过手机/平板访问
## 构建与发布说明
- 使用 Vite 输出 **单文件 HTML**`dist/index.html`),资源全部内联(`vite-plugin-singlefile`)。
@@ -125,6 +125,12 @@ export function GeminiSection({
<span className={styles.fieldValue}>{item.baseUrl}</span>
</div>
)}
{item.proxyUrl && (
<div className={styles.fieldRow}>
<span className={styles.fieldLabel}>{t('common.proxy_url')}:</span>
<span className={styles.fieldValue}>{item.proxyUrl}</span>
</div>
)}
{headerEntries.length > 0 && (
<div className={styles.headerBadgeList}>
{headerEntries.map(([key, value]) => (
+77 -8
View File
@@ -213,11 +213,14 @@ const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): Codex
const rateLimit = payload.rate_limit ?? payload.rateLimit ?? undefined;
const codeReviewLimit = payload.code_review_rate_limit ?? payload.codeReviewRateLimit ?? undefined;
const additionalRateLimits = payload.additional_rate_limits ?? payload.additionalRateLimits ?? [];
const windows: CodexQuotaWindow[] = [];
const addWindow = (
id: string,
labelKey: string,
label: string,
labelKey: string | undefined,
labelParams: Record<string, string | number> | undefined,
window?: CodexUsageWindow | null,
limitReached?: boolean,
allowed?: boolean
@@ -229,8 +232,9 @@ const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): Codex
const usedPercent = usedPercentRaw ?? (isLimitReached && resetLabel !== '-' ? 100 : null);
windows.push({
id,
label: t(labelKey),
label,
labelKey,
labelParams,
usedPercent,
resetLabel,
});
@@ -245,12 +249,13 @@ const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): Codex
const rawAllowed = rateLimit?.allowed;
const pickClassifiedWindows = (
limitInfo?: CodexRateLimitInfo | null
limitInfo?: CodexRateLimitInfo | null,
options?: { allowOrderFallback?: boolean }
): { fiveHourWindow: CodexUsageWindow | null; weeklyWindow: CodexUsageWindow | null } => {
const rawWindows = [
limitInfo?.primary_window ?? limitInfo?.primaryWindow ?? null,
limitInfo?.secondary_window ?? limitInfo?.secondaryWindow ?? null,
];
const allowOrderFallback = options?.allowOrderFallback ?? true;
const primaryWindow = limitInfo?.primary_window ?? limitInfo?.primaryWindow ?? null;
const secondaryWindow = limitInfo?.secondary_window ?? limitInfo?.secondaryWindow ?? null;
const rawWindows = [primaryWindow, secondaryWindow];
let fiveHourWindow: CodexUsageWindow | null = null;
let weeklyWindow: CodexUsageWindow | null = null;
@@ -265,20 +270,34 @@ const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): Codex
}
}
// For legacy payloads without window duration, fallback to primary/secondary ordering.
if (allowOrderFallback) {
if (!fiveHourWindow) {
fiveHourWindow = primaryWindow && primaryWindow !== weeklyWindow ? primaryWindow : null;
}
if (!weeklyWindow) {
weeklyWindow = secondaryWindow && secondaryWindow !== fiveHourWindow ? secondaryWindow : null;
}
}
return { fiveHourWindow, weeklyWindow };
};
const rateWindows = pickClassifiedWindows(rateLimit);
addWindow(
WINDOW_META.codeFiveHour.id,
t(WINDOW_META.codeFiveHour.labelKey),
WINDOW_META.codeFiveHour.labelKey,
undefined,
rateWindows.fiveHourWindow,
rawLimitReached,
rawAllowed
);
addWindow(
WINDOW_META.codeWeekly.id,
t(WINDOW_META.codeWeekly.labelKey),
WINDOW_META.codeWeekly.labelKey,
undefined,
rateWindows.weeklyWindow,
rawLimitReached,
rawAllowed
@@ -289,19 +308,67 @@ const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): Codex
const codeReviewAllowed = codeReviewLimit?.allowed;
addWindow(
WINDOW_META.codeReviewFiveHour.id,
t(WINDOW_META.codeReviewFiveHour.labelKey),
WINDOW_META.codeReviewFiveHour.labelKey,
undefined,
codeReviewWindows.fiveHourWindow,
codeReviewLimitReached,
codeReviewAllowed
);
addWindow(
WINDOW_META.codeReviewWeekly.id,
t(WINDOW_META.codeReviewWeekly.labelKey),
WINDOW_META.codeReviewWeekly.labelKey,
undefined,
codeReviewWindows.weeklyWindow,
codeReviewLimitReached,
codeReviewAllowed
);
const normalizeWindowId = (raw: string) =>
raw
.trim()
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
if (Array.isArray(additionalRateLimits)) {
additionalRateLimits.forEach((limitItem, index) => {
const rateInfo = limitItem?.rate_limit ?? limitItem?.rateLimit ?? null;
if (!rateInfo) return;
const limitName =
normalizeStringValue(limitItem?.limit_name ?? limitItem?.limitName) ??
normalizeStringValue(limitItem?.metered_feature ?? limitItem?.meteredFeature) ??
`additional-${index + 1}`;
const idPrefix = normalizeWindowId(limitName) || `additional-${index + 1}`;
const additionalPrimaryWindow = rateInfo.primary_window ?? rateInfo.primaryWindow ?? null;
const additionalSecondaryWindow = rateInfo.secondary_window ?? rateInfo.secondaryWindow ?? null;
const additionalLimitReached = rateInfo.limit_reached ?? rateInfo.limitReached;
const additionalAllowed = rateInfo.allowed;
addWindow(
`${idPrefix}-five-hour-${index}`,
t('codex_quota.additional_primary_window', { name: limitName }),
'codex_quota.additional_primary_window',
{ name: limitName },
additionalPrimaryWindow,
additionalLimitReached,
additionalAllowed
);
addWindow(
`${idPrefix}-weekly-${index}`,
t('codex_quota.additional_secondary_window', { name: limitName }),
'codex_quota.additional_secondary_window',
{ name: limitName },
additionalSecondaryWindow,
additionalLimitReached,
additionalAllowed
);
});
}
return windows;
};
@@ -493,7 +560,9 @@ const renderCodexItems = (
const clampedUsed = used === null ? null : Math.max(0, Math.min(100, used));
const remaining = clampedUsed === null ? null : Math.max(0, Math.min(100, 100 - clampedUsed));
const percentLabel = remaining === null ? '--' : `${Math.round(remaining)}%`;
const windowLabel = window.labelKey ? t(window.labelKey) : window.label;
const windowLabel = window.labelKey
? t(window.labelKey, window.labelParams as Record<string, string | number>)
: window.label;
return h(
'div',
+4
View File
@@ -194,6 +194,8 @@
"gemini_keys_add_btn": "Add Key",
"gemini_base_url_label": "Base URL (Optional):",
"gemini_base_url_placeholder": "e.g.: https://generativelanguage.googleapis.com",
"gemini_add_modal_proxy_label": "Proxy URL (Optional):",
"gemini_add_modal_proxy_placeholder": "e.g.: socks5://proxy.example.com:1080",
"gemini_edit_modal_title": "Edit Gemini API Key",
"gemini_edit_modal_key_label": "API Key:",
"gemini_delete_confirm": "Are you sure you want to delete this Gemini key?",
@@ -490,6 +492,8 @@
"secondary_window": "Weekly limit",
"code_review_primary_window": "Code review 5-hour limit",
"code_review_secondary_window": "Code review weekly limit",
"additional_primary_window": "{{name}} 5-hour limit",
"additional_secondary_window": "{{name}} weekly limit",
"plan_label": "Plan",
"plan_plus": "Plus",
"plan_team": "Team",
+4
View File
@@ -194,6 +194,8 @@
"gemini_keys_add_btn": "Добавить ключ",
"gemini_base_url_label": "Базовый URL (необязательно):",
"gemini_base_url_placeholder": "например: https://generativelanguage.googleapis.com",
"gemini_add_modal_proxy_label": "URL прокси (необязательно):",
"gemini_add_modal_proxy_placeholder": "например: socks5://proxy.example.com:1080",
"gemini_edit_modal_title": "Редактирование API-ключа Gemini",
"gemini_edit_modal_key_label": "API-ключ:",
"gemini_delete_confirm": "Удалить этот ключ Gemini?",
@@ -493,6 +495,8 @@
"secondary_window": "Недельный лимит",
"code_review_primary_window": "Лимит code review на 5 часов",
"code_review_secondary_window": "Недельный лимит code review",
"additional_primary_window": "{{name}}: лимит на 5 часов",
"additional_secondary_window": "{{name}}: недельный лимит",
"plan_label": "Тариф",
"plan_plus": "Plus",
"plan_team": "Team",
+4
View File
@@ -194,6 +194,8 @@
"gemini_keys_add_btn": "添加密钥",
"gemini_base_url_label": "Base URL (可选)",
"gemini_base_url_placeholder": "例如: https://generativelanguage.googleapis.com",
"gemini_add_modal_proxy_label": "代理 URL (可选):",
"gemini_add_modal_proxy_placeholder": "例如: socks5://proxy.example.com:1080",
"gemini_edit_modal_title": "编辑Gemini API密钥",
"gemini_edit_modal_key_label": "API密钥:",
"gemini_delete_confirm": "确定要删除这个Gemini密钥吗?",
@@ -490,6 +492,8 @@
"secondary_window": "周限额",
"code_review_primary_window": "代码审查 5 小时限额",
"code_review_secondary_window": "代码审查周限额",
"additional_primary_window": "{{name}} 5 小时限额",
"additional_secondary_window": "{{name}} 周限额",
"plan_label": "套餐",
"plan_plus": "Plus",
"plan_team": "Team",
+9
View File
@@ -21,6 +21,7 @@ const buildEmptyForm = (): GeminiFormState => ({
apiKey: '',
prefix: '',
baseUrl: '',
proxyUrl: '',
headers: [],
excludedModels: [],
excludedText: '',
@@ -138,6 +139,7 @@ export function AiProvidersGeminiEditPage() {
apiKey: form.apiKey.trim(),
prefix: form.prefix?.trim() || undefined,
baseUrl: form.baseUrl?.trim() || undefined,
proxyUrl: form.proxyUrl?.trim() || undefined,
headers: buildHeaderObject(form.headers),
excludedModels: parseExcludedModels(form.excludedText),
};
@@ -218,6 +220,13 @@ export function AiProvidersGeminiEditPage() {
onChange={(e) => setForm((prev) => ({ ...prev, baseUrl: e.target.value }))}
disabled={disableControls || saving}
/>
<Input
label={t('ai_providers.gemini_add_modal_proxy_label')}
placeholder={t('ai_providers.gemini_add_modal_proxy_placeholder')}
value={form.proxyUrl ?? ''}
onChange={(e) => setForm((prev) => ({ ...prev, proxyUrl: e.target.value }))}
disabled={disableControls || saving}
/>
<HeaderInputList
entries={form.headers}
onChange={(entries) => setForm((prev) => ({ ...prev, headers: entries }))}
+1
View File
@@ -99,6 +99,7 @@ const serializeGeminiKey = (config: GeminiKeyConfig) => {
const payload: Record<string, unknown> = { 'api-key': config.apiKey };
if (config.prefix?.trim()) payload.prefix = config.prefix.trim();
if (config.baseUrl) payload['base-url'] = config.baseUrl;
if (config.proxyUrl) payload['proxy-url'] = config.proxyUrl;
const headers = serializeHeaders(config.headers);
if (headers) payload.headers = headers;
if (config.excludedModels && config.excludedModels.length) {
+2
View File
@@ -153,6 +153,8 @@ const normalizeGeminiKeyConfig = (item: unknown): GeminiKeyConfig | null => {
if (prefix) config.prefix = prefix;
const baseUrl = record ? record['base-url'] ?? record.baseUrl ?? record['base_url'] : undefined;
if (baseUrl) config.baseUrl = String(baseUrl);
const proxyUrl = record ? record['proxy-url'] ?? record.proxyUrl ?? record['proxy_url'] : undefined;
if (proxyUrl) config.proxyUrl = String(proxyUrl);
const headers = normalizeHeaders(record?.headers);
if (headers) config.headers = headers;
const excludedModels = normalizeExcludedModels(record?.['excluded-models'] ?? record?.excludedModels);
+1
View File
@@ -20,6 +20,7 @@ export interface GeminiKeyConfig {
apiKey: string;
prefix?: string;
baseUrl?: string;
proxyUrl?: string;
headers?: Record<string, string>;
excludedModels?: string[];
}
+12
View File
@@ -88,6 +88,15 @@ export interface CodexRateLimitInfo {
secondaryWindow?: CodexUsageWindow | null;
}
export interface CodexAdditionalRateLimit {
limit_name?: string;
limitName?: string;
metered_feature?: string;
meteredFeature?: string;
rate_limit?: CodexRateLimitInfo | null;
rateLimit?: CodexRateLimitInfo | null;
}
export interface CodexUsagePayload {
plan_type?: string;
planType?: string;
@@ -95,6 +104,8 @@ export interface CodexUsagePayload {
rateLimit?: CodexRateLimitInfo | null;
code_review_rate_limit?: CodexRateLimitInfo | null;
codeReviewRateLimit?: CodexRateLimitInfo | null;
additional_rate_limits?: CodexAdditionalRateLimit[] | null;
additionalRateLimits?: CodexAdditionalRateLimit[] | null;
}
// Claude API payload types
@@ -174,6 +185,7 @@ export interface CodexQuotaWindow {
id: string;
label: string;
labelKey?: string;
labelParams?: Record<string, string | number>;
usedPercent: number | null;
resetLabel: string;
}