Selaa lähdekoodia

feat(types,schema,validation): add client restriction fields across foundation layer

- types/user.ts: blockedClients in 5 interfaces
- types/provider.ts: allowedClients/blockedClients in Provider, ProviderDisplay, batch types
- types/message.ts: client_restriction_filtered in ProviderChainItem.reason, client_restriction in filteredProviders.reason
- drizzle/schema.ts: blocked_clients on users, allowed_clients/blocked_clients on providers
- validation/schemas.ts: blockedClients in user schemas, allowed_clients/blocked_clients in provider schemas
- actions/providers.ts: thread new fields through
- lib/provider-patch-contract.ts: add allowed_clients/blocked_clients to patch contract
ding113 1 kuukausi sitten
vanhempi
sitoutus
b4d509993e

+ 7 - 0
drizzle/meta/_journal.json

@@ -519,6 +519,13 @@
       "when": 1771527016184,
       "tag": "0073_magical_manta",
       "breakpoints": true
+    },
+    {
+      "idx": 74,
+      "version": "7",
+      "when": 1771600203231,
+      "tag": "0074_wide_retro_girl",
+      "breakpoints": true
     }
   ]
 }

+ 4 - 0
src/actions/providers.ts

@@ -273,6 +273,8 @@ export async function getProviders(): Promise<ProviderDisplay[]> {
         preserveClientIp: provider.preserveClientIp,
         modelRedirects: provider.modelRedirects,
         allowedModels: provider.allowedModels,
+        allowedClients: provider.allowedClients,
+        blockedClients: provider.blockedClients,
         mcpPassthroughType: provider.mcpPassthroughType,
         mcpPassthroughUrl: provider.mcpPassthroughUrl,
         limit5hUsd: provider.limit5hUsd,
@@ -1512,6 +1514,8 @@ const PATCH_FIELD_TO_PROVIDER_KEY: Record<ProviderBatchPatchField, keyof Provide
   group_tag: "groupTag",
   model_redirects: "modelRedirects",
   allowed_models: "allowedModels",
+  allowed_clients: "allowedClients",
+  blocked_clients: "blockedClients",
   anthropic_thinking_budget_preference: "anthropicThinkingBudgetPreference",
   anthropic_adaptive_thinking: "anthropicAdaptiveThinking",
   preserve_client_ip: "preserveClientIp",

+ 29 - 0
src/lib/validation/schemas.ts

@@ -202,6 +202,12 @@ export const CreateUserSchema = z.object({
     .max(50, "客户端模式数量不能超过50个")
     .optional()
     .default([]),
+  // Blocked clients (CLI/IDE restrictions)
+  blockedClients: z
+    .array(z.string().max(64, "客户端模式长度不能超过64个字符"))
+    .max(50, "客户端模式数量不能超过50个")
+    .optional()
+    .default([]),
   // Allowed models (AI model restrictions)
   allowedModels: z
     .array(z.string().max(64, "模型名称长度不能超过64个字符"))
@@ -326,6 +332,11 @@ export const UpdateUserSchema = z.object({
     .array(z.string().max(64, "客户端模式长度不能超过64个字符"))
     .max(50, "客户端模式数量不能超过50个")
     .optional(),
+  // Blocked clients (CLI/IDE restrictions)
+  blockedClients: z
+    .array(z.string().max(64, "客户端模式长度不能超过64个字符"))
+    .max(50, "客户端模式数量不能超过50个")
+    .optional(),
   // Allowed models (AI model restrictions)
   allowedModels: z
     .array(z.string().max(64, "模型名称长度不能超过64个字符"))
@@ -437,6 +448,16 @@ export const CreateProviderSchema = z
     preserve_client_ip: z.boolean().optional().default(false),
     model_redirects: z.record(z.string(), z.string()).nullable().optional(),
     allowed_models: z.array(z.string()).nullable().optional(),
+    allowed_clients: z
+      .array(z.string().max(64, "客户端模式长度不能超过64个字符"))
+      .max(50, "客户端模式数量不能超过50个")
+      .optional()
+      .default([]),
+    blocked_clients: z
+      .array(z.string().max(64, "客户端模式长度不能超过64个字符"))
+      .max(50, "客户端模式数量不能超过50个")
+      .optional()
+      .default([]),
     // MCP 透传配置
     mcp_passthrough_type: z.enum(["none", "minimax", "glm", "custom"]).optional().default("none"),
     mcp_passthrough_url: z
@@ -643,6 +664,14 @@ export const UpdateProviderSchema = z
     preserve_client_ip: z.boolean().optional(),
     model_redirects: z.record(z.string(), z.string()).nullable().optional(),
     allowed_models: z.array(z.string()).nullable().optional(),
+    allowed_clients: z
+      .array(z.string().max(64, "客户端模式长度不能超过64个字符"))
+      .max(50, "客户端模式数量不能超过50个")
+      .optional(),
+    blocked_clients: z
+      .array(z.string().max(64, "客户端模式长度不能超过64个字符"))
+      .max(50, "客户端模式数量不能超过50个")
+      .optional(),
     // MCP 透传配置
     mcp_passthrough_type: z.enum(["none", "minimax", "glm", "custom"]).optional(),
     mcp_passthrough_url: z

+ 4 - 2
src/types/message.ts

@@ -34,7 +34,8 @@ export interface ProviderChainItem {
     | "client_error_non_retryable" // 不可重试的客户端错误(Prompt 超限、内容过滤、PDF 限制、Thinking 格式)
     | "http2_fallback" // HTTP/2 协议错误,回退到 HTTP/1.1(不切换供应商、不计入熔断器)
     | "endpoint_pool_exhausted" // 端点池耗尽(所有端点熔断或不可用,严格模式阻止降级)
-    | "vendor_type_all_timeout"; // 供应商类型全端点超时(524),触发 vendor-type 临时熔断
+    | "vendor_type_all_timeout" // 供应商类型全端点超时(524),触发 vendor-type 临时熔断
+    | "client_restriction_filtered"; // Provider skipped due to client restriction (neutral, no circuit breaker)
 
   // === 选择方法(细化) ===
   selectionMethod?:
@@ -171,7 +172,8 @@ export interface ProviderChainItem {
         | "type_mismatch"
         | "model_not_allowed"
         | "context_1m_disabled" // 供应商禁用了 1M 上下文功能
-        | "disabled";
+        | "disabled"
+        | "client_restriction"; // Provider filtered due to client restriction
       details?: string; // 额外信息(如费用:$15.2/$15)
     }>;
 

+ 12 - 0
src/types/provider.ts

@@ -65,6 +65,8 @@ export type ProviderBatchPatchField =
   | "group_tag"
   | "model_redirects"
   | "allowed_models"
+  | "allowed_clients"
+  | "blocked_clients"
   | "anthropic_thinking_budget_preference"
   | "anthropic_adaptive_thinking"
   // Routing
@@ -112,6 +114,8 @@ export interface ProviderBatchPatchDraft {
   group_tag?: ProviderPatchDraftInput<string>;
   model_redirects?: ProviderPatchDraftInput<Record<string, string>>;
   allowed_models?: ProviderPatchDraftInput<string[]>;
+  allowed_clients?: ProviderPatchDraftInput<string[]>;
+  blocked_clients?: ProviderPatchDraftInput<string[]>;
   anthropic_thinking_budget_preference?: ProviderPatchDraftInput<AnthropicThinkingBudgetPreference>;
   anthropic_adaptive_thinking?: ProviderPatchDraftInput<AnthropicAdaptiveThinkingConfig>;
   // Routing
@@ -160,6 +164,8 @@ export interface ProviderBatchPatch {
   group_tag: ProviderPatchOperation<string>;
   model_redirects: ProviderPatchOperation<Record<string, string>>;
   allowed_models: ProviderPatchOperation<string[]>;
+  allowed_clients: ProviderPatchOperation<string[]>;
+  blocked_clients: ProviderPatchOperation<string[]>;
   anthropic_thinking_budget_preference: ProviderPatchOperation<AnthropicThinkingBudgetPreference>;
   anthropic_adaptive_thinking: ProviderPatchOperation<AnthropicAdaptiveThinkingConfig>;
   // Routing
@@ -208,6 +214,8 @@ export interface ProviderBatchApplyUpdates {
   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;
   // Routing
@@ -285,6 +293,8 @@ export interface Provider {
   // - 非 Anthropic 提供商:声明列表(提供商声称支持的模型,可选)
   // - null 或空数组:Anthropic 允许所有 claude 模型,非 Anthropic 允许任意模型
   allowedModels: string[] | null;
+  allowedClients: string[]; // Allowed client patterns (empty = no restriction)
+  blockedClients: string[]; // Blocked client patterns (blacklist, checked before allowedClients)
 
   // MCP 透传类型:控制是否启用 MCP 透传功能
   // 'none': 不启用(默认)
@@ -390,6 +400,8 @@ export interface ProviderDisplay {
   modelRedirects: Record<string, string> | null;
   // 模型列表(双重语义)
   allowedModels: string[] | null;
+  allowedClients: string[]; // Allowed client patterns (empty = no restriction)
+  blockedClients: string[]; // Blocked client patterns (blacklist, checked before allowedClients)
   // MCP 透传类型
   mcpPassthroughType: McpPassthroughType;
   // MCP 透传 URL

+ 5 - 0
src/types/user.ts

@@ -27,6 +27,7 @@ export interface User {
   expiresAt?: Date | null; // 用户过期时间
   // Allowed clients (CLI/IDE restrictions)
   allowedClients?: string[]; // 允许的客户端模式(空数组=无限制)
+  blockedClients?: string[]; // Blocked client patterns (blacklist, checked before allowedClients)
   // Allowed models (AI model restrictions)
   allowedModels?: string[]; // 允许的AI模型(空数组=无限制)
 }
@@ -55,6 +56,7 @@ export interface CreateUserData {
   expiresAt?: Date | null;
   // Allowed clients (CLI/IDE restrictions)
   allowedClients?: string[];
+  blockedClients?: string[]; // Blocked client patterns (blacklist, checked before allowedClients)
   // Allowed models (AI model restrictions)
   allowedModels?: string[];
 }
@@ -83,6 +85,7 @@ export interface UpdateUserData {
   expiresAt?: Date | null;
   // Allowed clients (CLI/IDE restrictions)
   allowedClients?: string[];
+  blockedClients?: string[]; // Blocked client patterns (blacklist, checked before allowedClients)
   // Allowed models (AI model restrictions)
   allowedModels?: string[];
 }
@@ -156,6 +159,7 @@ export interface UserDisplay {
   expiresAt?: Date | null; // 用户过期时间
   // Allowed clients (CLI/IDE restrictions)
   allowedClients?: string[]; // 允许的客户端模式(空数组=无限制)
+  blockedClients?: string[]; // Blocked client patterns (blacklist, checked before allowedClients)
   // Allowed models (AI model restrictions)
   allowedModels?: string[]; // 允许的AI模型(空数组=无限制)
 }
@@ -173,6 +177,7 @@ export interface KeyDialogUserContext {
   limitTotalUsd?: number | null;
   limitConcurrentSessions?: number;
   allowedClients?: string[];
+  blockedClients?: string[]; // Blocked client patterns (blacklist, checked before allowedClients)
   allowedModels?: string[];
 }