Jelajahi Sumber

feat(i18n,actions): i18n client restriction keys + thread blockedClients through actions

- messages/*/provider-chain.json: client_restriction in filterReasons/filterDetails, client_restriction_filtered in reasons
- messages/*/dashboard.json: 7 Claude Code sub-client labels in presetClients, blockedClients in userEditSection.fields
- actions/users.ts: blockedClients in all create/update calls
- actions/providers.ts: allowedClients/blockedClients in all create/update calls
ding113 1 bulan lalu
induk
melakukan
b1c03716ad

+ 14 - 1
messages/en/dashboard.json

@@ -1826,6 +1826,12 @@
           "customLabel": "Custom Client Pattern",
           "customPlaceholder": "Enter pattern (e.g., 'xcode', 'my-ide')"
         },
+        "blockedClients": {
+          "label": "Blocked Clients",
+          "description": "Clients matching these patterns will be rejected, even if they match allowed clients.",
+          "customLabel": "Custom Block Pattern",
+          "customPlaceholder": "Enter pattern (e.g., 'xcode', 'my-ide')"
+        },
         "allowedModels": {
           "label": "Model Restrictions",
           "placeholder": "Enter model name or select from dropdown",
@@ -1849,7 +1855,14 @@
         "claude-cli": "Claude Code CLI",
         "gemini-cli": "Gemini CLI",
         "factory-cli": "Droid CLI",
-        "codex-cli": "Codex CLI"
+        "codex-cli": "Codex CLI",
+        "claude-code": "Claude Code (all)",
+        "claude-code-cli": "Claude Code CLI",
+        "claude-code-cli-sdk": "Claude Code CLI SDK",
+        "claude-code-vscode": "Claude Code VSCode",
+        "claude-code-sdk-ts": "Claude Code SDK (TypeScript)",
+        "claude-code-sdk-py": "Claude Code SDK (Python)",
+        "claude-code-gh-action": "Claude Code GitHub Action"
       }
     },
     "keyEditSection": {

+ 6 - 3
messages/en/provider-chain.json

@@ -55,7 +55,8 @@
     "session_reuse": "Session Reuse",
     "initial_selection": "Initial Selection",
     "endpoint_pool_exhausted": "Endpoint Pool Exhausted",
-    "vendor_type_all_timeout": "Vendor-Type All Endpoints Timeout"
+    "vendor_type_all_timeout": "Vendor-Type All Endpoints Timeout",
+    "client_restriction_filtered": "Client Restricted"
   },
   "filterReasons": {
     "rate_limited": "Rate Limited",
@@ -70,13 +71,15 @@
     "group_mismatch": "Group Mismatch",
     "health_check_failed": "Health Check Failed",
     "endpoint_circuit_open": "Endpoint Circuit Open",
-    "endpoint_disabled": "Endpoint Disabled"
+    "endpoint_disabled": "Endpoint Disabled",
+    "client_restriction": "Client Restriction"
   },
   "filterDetails": {
     "vendor_type_circuit_open": "Vendor-type temporarily circuit-broken",
     "circuit_open": "Circuit breaker open",
     "circuit_half_open": "Circuit breaker half-open",
-    "rate_limited": "Rate limited"
+    "rate_limited": "Rate limited",
+    "provider_client_restriction": "Provider skipped due to client restriction"
   },
   "details": {
     "selectionMethod": "Selection",

+ 14 - 1
messages/ja/dashboard.json

@@ -1762,6 +1762,12 @@
           "customLabel": "カスタムクライアントパターン",
           "customPlaceholder": "パターンを入力(例:'xcode', 'my-ide')"
         },
+        "blockedClients": {
+          "label": "ブロックするクライアント",
+          "description": "これらのパターンに一致するクライアントは、許可リストに一致しても拒否されます。",
+          "customLabel": "カスタムブロックパターン",
+          "customPlaceholder": "パターンを入力(例: 'xcode'、'my-ide')"
+        },
         "allowedModels": {
           "label": "モデル制限",
           "placeholder": "モデル名を入力またはドロップダウンから選択",
@@ -1785,7 +1791,14 @@
         "claude-cli": "Claude Code CLI",
         "gemini-cli": "Gemini CLI",
         "factory-cli": "Droid CLI",
-        "codex-cli": "Codex CLI"
+        "codex-cli": "Codex CLI",
+        "claude-code": "Claude Code(全て)",
+        "claude-code-cli": "Claude Code CLI",
+        "claude-code-cli-sdk": "Claude Code CLI SDK",
+        "claude-code-vscode": "Claude Code VSCode",
+        "claude-code-sdk-ts": "Claude Code SDK(TypeScript)",
+        "claude-code-sdk-py": "Claude Code SDK(Python)",
+        "claude-code-gh-action": "Claude Code GitHub Action"
       }
     },
     "keyEditSection": {

+ 6 - 3
messages/ja/provider-chain.json

@@ -55,7 +55,8 @@
     "session_reuse": "セッション再利用",
     "initial_selection": "初期選択",
     "endpoint_pool_exhausted": "エンドポイントプール枯渇",
-    "vendor_type_all_timeout": "ベンダータイプ全エンドポイントタイムアウト"
+    "vendor_type_all_timeout": "ベンダータイプ全エンドポイントタイムアウト",
+    "client_restriction_filtered": "クライアント制限"
   },
   "filterReasons": {
     "rate_limited": "レート制限",
@@ -70,13 +71,15 @@
     "group_mismatch": "グループ不一致",
     "health_check_failed": "ヘルスチェック失敗",
     "endpoint_circuit_open": "エンドポイントサーキットオープン",
-    "endpoint_disabled": "エンドポイント無効"
+    "endpoint_disabled": "エンドポイント無効",
+    "client_restriction": "クライアント制限"
   },
   "filterDetails": {
     "vendor_type_circuit_open": "ベンダータイプ一時サーキットブレイク",
     "circuit_open": "サーキットブレーカーオープン",
     "circuit_half_open": "サーキットブレーカーハーフオープン",
-    "rate_limited": "レート制限"
+    "rate_limited": "レート制限",
+    "provider_client_restriction": "クライアント制限によりプロバイダーをスキップ"
   },
   "details": {
     "selectionMethod": "選択方法",

+ 14 - 1
messages/ru/dashboard.json

@@ -1810,6 +1810,12 @@
           "customLabel": "Пользовательские шаблоны клиентов",
           "customPlaceholder": "Введите шаблон (например, 'xcode', 'my-ide')"
         },
+        "blockedClients": {
+          "label": "Заблокированные клиенты",
+          "description": "Клиенты, соответствующие этим шаблонам, будут отклонены, даже если они соответствуют разрешённым.",
+          "customLabel": "Пользовательский шаблон блокировки",
+          "customPlaceholder": "Введите шаблон (например, 'xcode', 'my-ide')"
+        },
         "allowedModels": {
           "label": "Ограничения моделей",
           "placeholder": "Введите название модели или выберите из списка",
@@ -1833,7 +1839,14 @@
         "claude-cli": "Claude Code CLI",
         "gemini-cli": "Gemini CLI",
         "factory-cli": "Droid CLI",
-        "codex-cli": "Codex CLI"
+        "codex-cli": "Codex CLI",
+        "claude-code": "Claude Code (все)",
+        "claude-code-cli": "Claude Code CLI",
+        "claude-code-cli-sdk": "Claude Code CLI SDK",
+        "claude-code-vscode": "Claude Code VSCode",
+        "claude-code-sdk-ts": "Claude Code SDK (TypeScript)",
+        "claude-code-sdk-py": "Claude Code SDK (Python)",
+        "claude-code-gh-action": "Claude Code GitHub Action"
       }
     },
     "keyEditSection": {

+ 6 - 3
messages/ru/provider-chain.json

@@ -55,7 +55,8 @@
     "session_reuse": "Повторное использование сессии",
     "initial_selection": "Первоначальный выбор",
     "endpoint_pool_exhausted": "Пул конечных точек исчерпан",
-    "vendor_type_all_timeout": "Тайм-аут всех конечных точек типа поставщика"
+    "vendor_type_all_timeout": "Тайм-аут всех конечных точек типа поставщика",
+    "client_restriction_filtered": "Клиент ограничен"
   },
   "filterReasons": {
     "rate_limited": "Ограничение скорости",
@@ -70,13 +71,15 @@
     "group_mismatch": "Несоответствие группы",
     "health_check_failed": "Проверка состояния не пройдена",
     "endpoint_circuit_open": "Автомат конечной точки открыт",
-    "endpoint_disabled": "Эндпоинт отключен"
+    "endpoint_disabled": "Эндпоинт отключен",
+    "client_restriction": "Ограничение клиента"
   },
   "filterDetails": {
     "vendor_type_circuit_open": "Временное размыкание типа поставщика",
     "circuit_open": "Размыкатель открыт",
     "circuit_half_open": "Размыкатель полуоткрыт",
-    "rate_limited": "Ограничение скорости"
+    "rate_limited": "Ограничение скорости",
+    "provider_client_restriction": "Провайдер пропущен из-за ограничения клиента"
   },
   "details": {
     "selectionMethod": "Метод выбора",

+ 14 - 1
messages/zh-CN/dashboard.json

@@ -1785,6 +1785,12 @@
           "customLabel": "自定义客户端模式",
           "customPlaceholder": "输入自定义模式(如:'xcode', 'my-ide')"
         },
+        "blockedClients": {
+          "label": "黑名单客户端",
+          "description": "匹配这些模式的客户端将被拒绝,即使它们也匹配白名单。",
+          "customLabel": "自定义黑名单模式",
+          "customPlaceholder": "输入模式(如 'xcode'、'my-ide')"
+        },
         "allowedModels": {
           "label": "模型限制",
           "placeholder": "输入模型名称或从下拉列表选择",
@@ -1808,7 +1814,14 @@
         "claude-cli": "Claude Code CLI",
         "gemini-cli": "Gemini CLI",
         "factory-cli": "Droid CLI",
-        "codex-cli": "Codex CLI"
+        "codex-cli": "Codex CLI",
+        "claude-code": "Claude Code(全部)",
+        "claude-code-cli": "Claude Code CLI",
+        "claude-code-cli-sdk": "Claude Code CLI SDK",
+        "claude-code-vscode": "Claude Code VSCode",
+        "claude-code-sdk-ts": "Claude Code SDK(TypeScript)",
+        "claude-code-sdk-py": "Claude Code SDK(Python)",
+        "claude-code-gh-action": "Claude Code GitHub Action"
       }
     },
     "keyEditSection": {

+ 6 - 3
messages/zh-CN/provider-chain.json

@@ -55,7 +55,8 @@
     "session_reuse": "会话复用",
     "initial_selection": "首次选择",
     "endpoint_pool_exhausted": "端点池耗尽",
-    "vendor_type_all_timeout": "供应商类型全端点超时"
+    "vendor_type_all_timeout": "供应商类型全端点超时",
+    "client_restriction_filtered": "客户端受限"
   },
   "filterReasons": {
     "rate_limited": "速率限制",
@@ -70,13 +71,15 @@
     "group_mismatch": "分组不匹配",
     "health_check_failed": "健康检查失败",
     "endpoint_circuit_open": "端点已熔断",
-    "endpoint_disabled": "端点已禁用"
+    "endpoint_disabled": "端点已禁用",
+    "client_restriction": "客户端限制"
   },
   "filterDetails": {
     "vendor_type_circuit_open": "供应商类型临时熔断",
     "circuit_open": "熔断器打开",
     "circuit_half_open": "熔断器半开",
-    "rate_limited": "速率限制"
+    "rate_limited": "速率限制",
+    "provider_client_restriction": "供应商因客户端限制被跳过"
   },
   "details": {
     "selectionMethod": "选择方式",

+ 14 - 1
messages/zh-TW/dashboard.json

@@ -1770,6 +1770,12 @@
           "customLabel": "自訂用戶端模式",
           "customPlaceholder": "輸入自訂模式(如:'xcode', 'my-ide')"
         },
+        "blockedClients": {
+          "label": "黑名單客戶端",
+          "description": "符合這些模式的客戶端將被拒絕,即使它們也符合白名單。",
+          "customLabel": "自訂黑名單模式",
+          "customPlaceholder": "輸入模式(如 'xcode'、'my-ide')"
+        },
         "allowedModels": {
           "label": "Model 限制",
           "placeholder": "輸入模型名稱或從下拉選單選擇",
@@ -1793,7 +1799,14 @@
         "claude-cli": "Claude Code CLI",
         "gemini-cli": "Gemini CLI",
         "factory-cli": "Droid CLI",
-        "codex-cli": "Codex CLI"
+        "codex-cli": "Codex CLI",
+        "claude-code": "Claude Code(全部)",
+        "claude-code-cli": "Claude Code CLI",
+        "claude-code-cli-sdk": "Claude Code CLI SDK",
+        "claude-code-vscode": "Claude Code VSCode",
+        "claude-code-sdk-ts": "Claude Code SDK(TypeScript)",
+        "claude-code-sdk-py": "Claude Code SDK(Python)",
+        "claude-code-gh-action": "Claude Code GitHub Action"
       }
     },
     "keyEditSection": {

+ 6 - 3
messages/zh-TW/provider-chain.json

@@ -55,7 +55,8 @@
     "session_reuse": "會話複用",
     "initial_selection": "首次選擇",
     "endpoint_pool_exhausted": "端點池耗盡",
-    "vendor_type_all_timeout": "供應商類型全端點逾時"
+    "vendor_type_all_timeout": "供應商類型全端點逾時",
+    "client_restriction_filtered": "客戶端受限"
   },
   "filterReasons": {
     "rate_limited": "速率限制",
@@ -70,13 +71,15 @@
     "group_mismatch": "分組不匹配",
     "health_check_failed": "健康檢查失敗",
     "endpoint_circuit_open": "端點已熔斷",
-    "endpoint_disabled": "端點已停用"
+    "endpoint_disabled": "端點已停用",
+    "client_restriction": "客戶端限制"
   },
   "filterDetails": {
     "vendor_type_circuit_open": "供應商類型臨時熔斷",
     "circuit_open": "熔斷器打開",
     "circuit_half_open": "熔斷器半開",
-    "rate_limited": "速率限制"
+    "rate_limited": "速率限制",
+    "provider_client_restriction": "供應商因客戶端限制被跳過"
   },
   "details": {
     "selectionMethod": "選擇方式",

+ 14 - 0
src/actions/providers.ts

@@ -1401,6 +1401,12 @@ function mapApplyUpdatesToRepositoryFormat(
   if (applyUpdates.allowed_models !== undefined) {
     result.allowedModels = applyUpdates.allowed_models;
   }
+  if (applyUpdates.allowed_clients !== undefined) {
+    result.allowedClients = applyUpdates.allowed_clients;
+  }
+  if (applyUpdates.blocked_clients !== undefined) {
+    result.blockedClients = applyUpdates.blocked_clients;
+  }
   if (applyUpdates.anthropic_thinking_budget_preference !== undefined) {
     result.anthropicThinkingBudgetPreference = applyUpdates.anthropic_thinking_budget_preference;
   }
@@ -2035,6 +2041,8 @@ export interface BatchUpdateProvidersParams {
     group_tag?: string | null;
     model_redirects?: Record<string, string> | null;
     allowed_models?: string[] | null;
+    allowed_clients?: string[];
+    blocked_clients?: string[];
     anthropic_thinking_budget_preference?: AnthropicThinkingBudgetPreference | null;
     anthropic_adaptive_thinking?: AnthropicAdaptiveThinkingConfig | null;
   };
@@ -2083,6 +2091,12 @@ export async function batchUpdateProviders(
           ? null
           : updates.allowed_models;
     }
+    if (updates.allowed_clients !== undefined) {
+      repositoryUpdates.allowedClients = updates.allowed_clients;
+    }
+    if (updates.blocked_clients !== undefined) {
+      repositoryUpdates.blockedClients = updates.blocked_clients;
+    }
     if (updates.anthropic_thinking_budget_preference !== undefined) {
       repositoryUpdates.anthropicThinkingBudgetPreference =
         updates.anthropic_thinking_budget_preference;

+ 12 - 0
src/actions/users.ts

@@ -254,6 +254,7 @@ export async function getUsers(): Promise<UserDisplay[]> {
           isEnabled: user.isEnabled,
           expiresAt: user.expiresAt ?? null,
           allowedClients: user.allowedClients || [],
+          blockedClients: user.blockedClients || [],
           allowedModels: user.allowedModels ?? [],
           keys: keys.map((key) => {
             const stats = statisticsLookup.get(key.id);
@@ -320,6 +321,7 @@ export async function getUsers(): Promise<UserDisplay[]> {
           isEnabled: user.isEnabled,
           expiresAt: user.expiresAt ?? null,
           allowedClients: user.allowedClients || [],
+          blockedClients: user.blockedClients || [],
           allowedModels: user.allowedModels ?? [],
           keys: [],
         };
@@ -523,6 +525,7 @@ export async function getUsersBatch(
           isEnabled: user.isEnabled,
           expiresAt: user.expiresAt ?? null,
           allowedClients: user.allowedClients || [],
+          blockedClients: user.blockedClients || [],
           allowedModels: user.allowedModels ?? [],
           keys: keys.map((key) => {
             const stats = statisticsLookup.get(key.id);
@@ -585,6 +588,7 @@ export async function getUsersBatch(
           isEnabled: user.isEnabled,
           expiresAt: user.expiresAt ?? null,
           allowedClients: user.allowedClients || [],
+          blockedClients: user.blockedClients || [],
           allowedModels: user.allowedModels ?? [],
           keys: [],
         };
@@ -750,6 +754,7 @@ export async function addUser(data: {
   isEnabled?: boolean;
   expiresAt?: Date | null;
   allowedClients?: string[];
+  blockedClients?: string[];
   allowedModels?: string[];
 }): Promise<
   ActionResult<{
@@ -810,6 +815,7 @@ export async function addUser(data: {
       isEnabled: data.isEnabled,
       expiresAt: data.expiresAt,
       allowedClients: data.allowedClients || [],
+      blockedClients: data.blockedClients || [],
       allowedModels: data.allowedModels || [],
     });
 
@@ -869,6 +875,7 @@ export async function addUser(data: {
       isEnabled: validatedData.isEnabled,
       expiresAt: validatedData.expiresAt ?? null,
       allowedClients: validatedData.allowedClients ?? [],
+      blockedClients: validatedData.blockedClients ?? [],
       allowedModels: validatedData.allowedModels ?? [],
     });
 
@@ -942,6 +949,7 @@ export async function createUserOnly(data: {
   isEnabled?: boolean;
   expiresAt?: Date | null;
   allowedClients?: string[];
+  blockedClients?: string[];
   allowedModels?: string[];
 }): Promise<
   ActionResult<{
@@ -995,6 +1003,7 @@ export async function createUserOnly(data: {
       isEnabled: data.isEnabled,
       expiresAt: data.expiresAt,
       allowedClients: data.allowedClients || [],
+      blockedClients: data.blockedClients || [],
       allowedModels: data.allowedModels || [],
     });
 
@@ -1053,6 +1062,7 @@ export async function createUserOnly(data: {
       isEnabled: validatedData.isEnabled,
       expiresAt: validatedData.expiresAt ?? null,
       allowedClients: validatedData.allowedClients ?? [],
+      blockedClients: validatedData.blockedClients ?? [],
       allowedModels: validatedData.allowedModels ?? [],
     });
 
@@ -1111,6 +1121,7 @@ export async function editUser(
     isEnabled?: boolean;
     expiresAt?: Date | null;
     allowedClients?: string[];
+    blockedClients?: string[];
     allowedModels?: string[];
   }
 ): Promise<ActionResult> {
@@ -1211,6 +1222,7 @@ export async function editUser(
       isEnabled: validatedData.isEnabled,
       expiresAt: validatedData.expiresAt,
       allowedClients: validatedData.allowedClients,
+      blockedClients: validatedData.blockedClients,
       allowedModels: validatedData.allowedModels,
     });