Spaces:
Runtime error
Runtime error
| import { z } from "zod"; | |
| import { | |
| ACCOUNT_FALLBACK_STRATEGY_VALUES, | |
| ROUTING_STRATEGY_VALUES, | |
| } from "@/shared/constants/routingStrategies"; | |
| import { SUPPORTED_BATCH_ENDPOINTS } from "@/shared/constants/batchEndpoints"; | |
| import { MAX_REQUEST_BODY_LIMIT_MB, MIN_REQUEST_BODY_LIMIT_MB } from "@/shared/constants/bodySize"; | |
| import { COMBO_CONFIG_MODES } from "@/shared/constants/comboConfigMode"; | |
| import { providerAllowsOptionalApiKey } from "@/shared/constants/providers"; | |
| import { HIDEABLE_SIDEBAR_ITEM_IDS } from "@/shared/constants/sidebarVisibility"; | |
| import { | |
| isForbiddenUpstreamHeaderName, | |
| isForbiddenCustomHeaderName, | |
| } from "@/shared/constants/upstreamHeaders"; | |
| import { MAX_TIMER_TIMEOUT_MS } from "@/shared/utils/runtimeTimeouts"; | |
| // ──── Combo Schemas ──── | |
| export const comboStepMetaSchema = { | |
| id: z.string().trim().min(1).max(200).optional(), | |
| weight: z.number().min(0).max(100).optional().default(0), | |
| label: z.string().trim().min(1).max(200).optional(), | |
| }; | |
| export const comboModelStepInputSchema = z.object({ | |
| kind: z.literal("model").optional(), | |
| provider: z.string().trim().min(1).max(120).optional(), | |
| providerId: z.string().trim().min(1).max(120).optional(), | |
| model: z.string().trim().min(1).max(300), | |
| connectionId: z.string().trim().min(1).max(200).nullable().optional(), | |
| tags: z.array(z.string().trim().min(1).max(100)).max(20).optional(), | |
| ...comboStepMetaSchema, | |
| }); | |
| export const comboRefStepInputSchema = z.object({ | |
| kind: z.literal("combo-ref"), | |
| comboName: z.string().trim().min(1).max(100), | |
| ...comboStepMetaSchema, | |
| }); | |
| // A combo entry can be a plain string (legacy), a legacy object, or a structured ComboStep. | |
| export const comboModelEntry = z.union([ | |
| z.string().trim().min(1).max(300), | |
| comboModelStepInputSchema, | |
| comboRefStepInputSchema, | |
| ]); | |
| export const shadowRoutingSchema = z | |
| .object({ | |
| enabled: z.boolean().optional(), | |
| targets: z.array(comboModelEntry).max(20).optional(), | |
| sampleRate: z.coerce.number().min(0).max(1).optional(), | |
| maxTargets: z.coerce.number().int().min(1).max(10).optional(), | |
| timeoutMs: z.coerce.number().int().min(1000).max(120000).optional(), | |
| }) | |
| .strict(); | |
| export const evalRoutingSchema = z | |
| .object({ | |
| enabled: z.boolean().optional(), | |
| suiteIds: z.array(z.string().trim().min(1).max(200)).max(50).optional(), | |
| maxAgeHours: z.coerce.number().min(1).max(8760).optional(), | |
| minCases: z.coerce.number().int().min(1).max(100000).optional(), | |
| qualityWeight: z.coerce.number().min(0).max(1).optional(), | |
| latencyWeight: z.coerce.number().min(0).max(1).optional(), | |
| cacheTtlMs: z.coerce.number().int().min(1000).max(300000).optional(), | |
| }) | |
| .strict(); | |
| export const comboStrategySchema = z.enum(ROUTING_STRATEGY_VALUES); | |
| export const scoringWeightsSchema = z | |
| .object({ | |
| quota: z.number().min(0).max(1), | |
| health: z.number().min(0).max(1), | |
| costInv: z.number().min(0).max(1), | |
| latencyInv: z.number().min(0).max(1), | |
| taskFit: z.number().min(0).max(1), | |
| stability: z.number().min(0).max(1), | |
| tierPriority: z.number().min(0).max(1).optional().default(0.05), | |
| tierAffinity: z.number().min(0).max(1).optional().default(0.05), | |
| specificityMatch: z.number().min(0).max(1).optional().default(0.05), | |
| contextAffinity: z.number().min(0).max(1).optional().default(0.08), | |
| resetWindowAffinity: z.number().min(0).max(1).optional().default(0), | |
| }) | |
| .optional(); | |
| export const compositeTierEntrySchema = z | |
| .object({ | |
| stepId: z.string().trim().min(1).max(200), | |
| fallbackTier: z.string().trim().min(1).max(100).optional(), | |
| label: z.string().trim().min(1).max(200).optional(), | |
| description: z.string().trim().min(1).max(500).optional(), | |
| }) | |
| .strict(); | |
| export const compositeTiersSchema = z | |
| .object({ | |
| defaultTier: z.string().trim().min(1).max(100), | |
| tiers: z.record(z.string().trim().min(1).max(100), compositeTierEntrySchema), | |
| }) | |
| .strict(); | |
| export const compressionModeSchema = z.enum([ | |
| "off", | |
| "lite", | |
| "standard", | |
| "aggressive", | |
| "ultra", | |
| "rtk", | |
| "stacked", | |
| ]); | |
| export const comboCompressionOverrideSchema = z.union([z.literal(""), compressionModeSchema]); | |
| export const slaRoutingPolicySchema = z | |
| .object({ | |
| targetP95Ms: z.coerce.number().int().positive().max(300000).optional(), | |
| maxErrorRate: z.coerce.number().min(0).max(1).optional(), | |
| maxCostPer1MTokens: z.coerce.number().positive().max(1000000).optional(), | |
| hardConstraints: z.boolean().optional(), | |
| }) | |
| .strict(); | |
| export const comboRuntimeConfigSchema = z | |
| .object({ | |
| strategy: comboStrategySchema.optional(), | |
| maxRetries: z.coerce.number().int().min(0).max(10).optional(), | |
| retryDelayMs: z.coerce.number().int().min(0).max(60000).optional(), | |
| fallbackDelayMs: z.coerce.number().int().min(0).max(60000).optional(), | |
| timeoutMs: z.coerce.number().int().min(1000).optional(), | |
| targetTimeoutMs: z.coerce.number().int().min(0).max(MAX_TIMER_TIMEOUT_MS).optional(), | |
| concurrencyPerModel: z.coerce.number().int().min(1).max(20).optional(), | |
| queueTimeoutMs: z.coerce.number().int().min(1000).max(120000).optional(), | |
| // #3872: pre-cascade semaphore queue depth (round-robin). 0 = fail over immediately. | |
| queueDepth: z.coerce.number().int().min(0).max(100).optional(), | |
| // Per-combo sticky round-robin batch size. When unset, handleRoundRobinCombo | |
| // falls back to the global `settings.stickyRoundRobinLimit` so the existing | |
| // knob still controls the default. 0 clamps to 1 (no batching) upstream. | |
| stickyRoundRobinLimit: z.coerce.number().int().min(0).max(1000).optional(), | |
| stickyWeightedLimit: z.coerce.number().int().min(0).max(1000).optional(), | |
| healthCheckEnabled: z.boolean().optional(), | |
| healthCheckTimeoutMs: z.coerce.number().int().min(100).max(30000).optional(), | |
| handoffThreshold: z.coerce.number().min(0.5).max(0.94).optional(), | |
| handoffModel: z.string().trim().max(200).optional(), | |
| handoffProviders: z.array(z.string().trim().min(1).max(100)).max(10).optional(), | |
| maxMessagesForSummary: z.coerce.number().int().min(5).max(100).optional(), | |
| maxComboDepth: z.coerce.number().int().min(1).max(10).optional(), | |
| nestedComboMode: z.enum(["flatten", "execute"]).optional(), | |
| trackMetrics: z.boolean().optional(), | |
| reasoningTokenBufferEnabled: z.boolean().optional(), | |
| compressionMode: compressionModeSchema.optional(), | |
| failoverBeforeRetry: z.boolean().optional(), | |
| maxSetRetries: z.coerce.number().int().min(0).max(10).optional(), | |
| setRetryDelayMs: z.coerce.number().int().min(0).max(60000).optional(), | |
| zeroLatencyOptimizationsEnabled: z.boolean().optional(), | |
| hedging: z.boolean().optional(), | |
| hedgeDelayMs: z.coerce.number().int().min(0).max(60000).optional(), | |
| fallbackCompressionMode: compressionModeSchema.optional(), | |
| fallbackCompressionThreshold: z.coerce.number().int().min(0).max(2_000_000).optional(), | |
| predictiveTtftMs: z.coerce.number().int().min(0).max(300000).optional(), | |
| // Auto-Combo / LKGP Extensions | |
| candidatePool: z.array(z.string().min(1)).optional(), | |
| weights: scoringWeightsSchema.optional(), | |
| modePack: z.string().max(100).optional(), | |
| budgetCap: z.number().positive().optional(), | |
| explorationRate: z.number().min(0).max(1).optional(), | |
| routerStrategy: z.string().optional(), | |
| slaTargetP95Ms: z.coerce.number().int().positive().max(300000).optional(), | |
| slaMaxErrorRate: z.coerce.number().min(0).max(1).optional(), | |
| slaMaxCostPer1MTokens: z.coerce.number().positive().max(1000000).optional(), | |
| slaHardConstraints: z.boolean().optional(), | |
| sla: slaRoutingPolicySchema.optional(), | |
| compositeTiers: compositeTiersSchema.optional(), | |
| resetAwareSessionWeight: z.coerce.number().min(0).max(100).optional(), | |
| resetAwareWeeklyWeight: z.coerce.number().min(0).max(100).optional(), | |
| resetAwareTieBandPercent: z.coerce.number().min(0).max(100).optional(), | |
| resetAwareExhaustionGuardPercent: z.coerce.number().min(0).max(100).optional(), | |
| resetAwareQuotaCacheTtlMs: z.coerce.number().int().min(0).max(300_000).optional(), | |
| resetAwareQuotaCacheMaxStaleMs: z.coerce.number().int().min(0).max(3_600_000).optional(), | |
| resetWindowWindows: z.array(z.enum(["weekly", "session", "monthly"])).optional(), | |
| resetWindowIncludeSession: z.boolean().optional(), | |
| resetWindowTieBandMs: z.coerce.number().int().min(0).max(86_400_000).optional(), | |
| resetWindowQuotaCacheTtlMs: z.coerce.number().int().min(0).max(300_000).optional(), | |
| resetWindowQuotaCacheMaxStaleMs: z.coerce.number().int().min(0).max(3_600_000).optional(), | |
| shadowRouting: shadowRoutingSchema.optional(), | |
| evalRouting: evalRoutingSchema.optional(), | |
| // Fusion strategy (open-sse/services/fusion.ts): the panel is the combo's | |
| // targets; `judgeModel` synthesizes the final answer (defaults to the first | |
| // panel model when unset); `fusionTuning` controls quorum-grace collection. | |
| judgeModel: z.string().trim().max(200).optional(), | |
| fusionTuning: z | |
| .object({ | |
| minPanel: z.coerce.number().int().min(1).max(50).optional(), | |
| stragglerGraceMs: z.coerce.number().int().min(0).max(120_000).optional(), | |
| panelHardTimeoutMs: z.coerce.number().int().min(1000).max(600_000).optional(), | |
| }) | |
| .strict() | |
| .optional(), | |
| }) | |
| .passthrough() | |
| .transform((config) => { | |
| // Backward-compat shim: combos stored prior to v3.8.33 may carry zero-latency | |
| // feature flags (fallbackCompressionMode !== "off", hedging === true, or | |
| // predictiveTtftMs > 0) without the accompanying zeroLatencyOptimizationsEnabled | |
| // gate that the new schema requires. Auto-promote the flag when any such feature | |
| // is enabled but the gate is unset/false, so stored combos continue to round-trip | |
| // through PUT /api/combos/{id} without returning 400. This replaces the prior | |
| // superRefine that hard-rejected these payloads (see issue #4382). | |
| if (config.zeroLatencyOptimizationsEnabled === true) return config; | |
| const hasZeroLatencyFeature = | |
| config.hedging === true || | |
| (typeof config.predictiveTtftMs === "number" && config.predictiveTtftMs > 0) || | |
| (!!config.fallbackCompressionMode && config.fallbackCompressionMode !== "off"); | |
| if (hasZeroLatencyFeature) { | |
| return { ...config, zeroLatencyOptimizationsEnabled: true }; | |
| } | |
| return config; | |
| }); | |
| export const comboNameSchema = z | |
| .string() | |
| .trim() | |
| .min(1, "Name is required") | |
| .max(100) | |
| .regex( | |
| /^[a-zA-Z0-9_/.\-\[\] ]+$/, | |
| "Name can only contain letters, numbers, spaces, -, _, /, ., [ and ]." | |
| ); | |
| export const createComboSchema = z.object({ | |
| name: comboNameSchema, | |
| description: z.string().max(2000).optional(), | |
| models: z.array(comboModelEntry).optional().default([]), | |
| strategy: comboStrategySchema.optional().default("priority"), | |
| config: comboRuntimeConfigSchema.optional(), | |
| allowedProviders: z.array(z.string().max(200)).optional(), | |
| system_message: z.string().max(50000).optional(), | |
| tool_filter_regex: z.string().max(1000).optional(), | |
| context_cache_protection: z.boolean().optional(), | |
| context_length: z.number().int().min(1000).max(2000000).optional(), | |
| // Optional embedding dimensions override for embedding combos. | |
| // When set, the value is injected into every upstream embedding request as | |
| // the `dimensions` field (and translated to `outputDimensionality` for Gemini). | |
| // Stored as a string to match the OpenAI API convention; coerced to number | |
| // by the embedding handler. Leave unset to use each model's default. | |
| dimensions: z.string().regex(/^\d+$/, "dimensions must be a positive integer string").optional().nullable(), | |
| }); | |
| export const updateComboDefaultsSchema = z | |
| .object({ | |
| comboDefaults: comboRuntimeConfigSchema.optional(), | |
| providerOverrides: z.record(z.string().trim().min(1), comboRuntimeConfigSchema).optional(), | |
| }) | |
| .superRefine((value, ctx) => { | |
| if (!value.comboDefaults && !value.providerOverrides) { | |
| ctx.addIssue({ | |
| code: z.ZodIssueCode.custom, | |
| message: "Nothing to update", | |
| path: [], | |
| }); | |
| } | |
| if (value.comboDefaults?.compositeTiers) { | |
| ctx.addIssue({ | |
| code: z.ZodIssueCode.custom, | |
| message: "compositeTiers is only supported on concrete combos", | |
| path: ["comboDefaults", "compositeTiers"], | |
| }); | |
| } | |
| for (const [providerId, config] of Object.entries(value.providerOverrides || {})) { | |
| if (config?.compositeTiers) { | |
| ctx.addIssue({ | |
| code: z.ZodIssueCode.custom, | |
| message: "compositeTiers is only supported on concrete combos", | |
| path: ["providerOverrides", providerId, "compositeTiers"], | |
| }); | |
| } | |
| } | |
| }); | |
| export const updateComboSchema = z | |
| .object({ | |
| name: comboNameSchema.optional(), | |
| description: z.string().max(2000).optional().nullable(), | |
| models: z.array(comboModelEntry).optional(), | |
| strategy: comboStrategySchema.optional(), | |
| config: comboRuntimeConfigSchema.optional(), | |
| isActive: z.boolean().optional(), | |
| allowedProviders: z.array(z.string().max(200)).optional(), | |
| system_message: z.string().max(50000).optional(), | |
| tool_filter_regex: z.string().max(1000).optional(), | |
| context_cache_protection: z.boolean().optional(), | |
| context_length: z.number().int().min(1000).max(2000000).optional().nullable(), | |
| compressionOverride: comboCompressionOverrideSchema.optional(), | |
| dimensions: z.string().regex(/^\d+$/, "dimensions must be a positive integer string").optional().nullable(), | |
| }) | |
| .superRefine((value, ctx) => { | |
| if ( | |
| value.name === undefined && | |
| value.description === undefined && | |
| value.models === undefined && | |
| value.strategy === undefined && | |
| value.config === undefined && | |
| value.isActive === undefined && | |
| value.allowedProviders === undefined && | |
| value.system_message === undefined && | |
| value.tool_filter_regex === undefined && | |
| value.context_cache_protection === undefined && | |
| value.context_length === undefined && | |
| value.compressionOverride === undefined && | |
| value.dimensions === undefined | |
| ) { | |
| ctx.addIssue({ | |
| code: z.ZodIssueCode.custom, | |
| message: "No valid fields to update", | |
| path: [], | |
| }); | |
| } | |
| }); | |
| export const reorderCombosSchema = z | |
| .object({ | |
| comboIds: z.array(z.string().trim().min(1).max(200)).min(1).max(1000), | |
| }) | |
| .superRefine((value, ctx) => { | |
| if (new Set(value.comboIds).size !== value.comboIds.length) { | |
| ctx.addIssue({ | |
| code: z.ZodIssueCode.custom, | |
| message: "comboIds must be unique", | |
| path: ["comboIds"], | |
| }); | |
| } | |
| }); | |
| export const testComboSchema = z.object({ | |
| comboName: z.string().trim().min(1, "comboName is required"), | |
| }); | |