From 82ef6804bbfd207522dde4bb4626fad2aaecb9ec Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 8 Jan 2026 15:18:29 -0500 Subject: [PATCH 01/31] feat(cliproxy): add thinking budget validator module - validate user-provided thinking values against model capabilities - clamp out-of-range budgets, map invalid levels to closest valid - export constants for budget bounds (MIN=0, MAX=100000) - support off/auto/level names and numeric budgets Refs #307 --- src/cliproxy/index.ts | 13 ++ src/cliproxy/thinking-validator.ts | 320 +++++++++++++++++++++++++++++ 2 files changed, 333 insertions(+) create mode 100644 src/cliproxy/thinking-validator.ts diff --git a/src/cliproxy/index.ts b/src/cliproxy/index.ts index 6858f43d..a89c7bc7 100644 --- a/src/cliproxy/index.ts +++ b/src/cliproxy/index.ts @@ -152,3 +152,16 @@ export { resetAuthToDefaults, getAuthSummary, } from './auth-token-manager'; + +// Thinking validator +export type { ThinkingValidationResult } from './thinking-validator'; +export { + validateThinking, + THINKING_LEVEL_BUDGETS, + VALID_THINKING_LEVELS, + THINKING_OFF_VALUES, + THINKING_AUTO_VALUE, + THINKING_BUDGET_MIN, + THINKING_BUDGET_MAX, + THINKING_BUDGET_DEFAULT_MIN, +} from './thinking-validator'; diff --git a/src/cliproxy/thinking-validator.ts b/src/cliproxy/thinking-validator.ts new file mode 100644 index 00000000..747cd497 --- /dev/null +++ b/src/cliproxy/thinking-validator.ts @@ -0,0 +1,320 @@ +/** + * Thinking Budget Validator + * + * Validates user-provided thinking values against model capabilities. + * - Clamps out-of-range budgets + * - Maps invalid levels to closest valid + * - Warns when model doesn't support thinking + */ + +import { getModelThinkingSupport, ThinkingSupport } from './model-catalog'; +import { CLIProxyProvider } from './types'; + +/** + * Thinking budget bounds (used for validation across API and CLI) + */ +export const THINKING_BUDGET_MIN = 0; +export const THINKING_BUDGET_MAX = 100000; +export const THINKING_BUDGET_DEFAULT_MIN = 512; + +/** + * Result of thinking value validation + */ +export interface ThinkingValidationResult { + /** Whether the value is valid for the model */ + valid: boolean; + /** The validated value (possibly clamped/mapped) */ + value: string | number; + /** Warning message for user feedback */ + warning?: string; +} + +/** + * Named thinking level mappings to budget values (when converting level→budget) + */ +export const THINKING_LEVEL_BUDGETS: Record = { + minimal: 512, + low: 1024, + medium: 8192, + high: 24576, + xhigh: 32768, +}; + +/** + * Valid thinking level names + */ +export const VALID_THINKING_LEVELS = ['minimal', 'low', 'medium', 'high', 'xhigh', 'auto'] as const; +export type ThinkingLevel = (typeof VALID_THINKING_LEVELS)[number]; + +/** + * Special thinking values + */ +export const THINKING_OFF_VALUES = ['off', 'none', 'disabled', '0'] as const; +export const THINKING_AUTO_VALUE = 'auto'; + +/** + * Find closest valid level using simple string matching + * Returns undefined if no close match found + */ +function findClosestLevel(input: string, validLevels: string[]): string | undefined { + const normalized = input.toLowerCase().trim(); + + // Exact match first + if (validLevels.includes(normalized)) { + return normalized; + } + + // Prefix matching (e.g., 'med' → 'medium', 'hi' → 'high') + for (const level of validLevels) { + if (level.startsWith(normalized)) { + return level; + } + } + + // Common aliases + const aliases: Record = { + min: 'minimal', + lo: 'low', + med: 'medium', + mid: 'medium', + hi: 'high', + max: 'xhigh', + ultra: 'xhigh', + extreme: 'xhigh', + }; + + if (aliases[normalized] && validLevels.includes(aliases[normalized])) { + return aliases[normalized]; + } + + return undefined; +} + +/** + * Validate a thinking value against model capabilities + * + * @param provider - The CLI proxy provider + * @param modelId - The model identifier + * @param value - User-provided thinking value (level name or budget number) + * @returns Validation result with possibly clamped/mapped value and warnings + */ +export function validateThinking( + provider: CLIProxyProvider, + modelId: string, + value: string | number +): ThinkingValidationResult { + const thinking = getModelThinkingSupport(provider, modelId); + + // Handle off/none/disabled values + if (typeof value === 'string') { + const normalizedValue = value.toLowerCase().trim(); + if (THINKING_OFF_VALUES.includes(normalizedValue as (typeof THINKING_OFF_VALUES)[number])) { + return { valid: true, value: 'off' }; + } + } + + // If model has no thinking support info, pass through + if (!thinking) { + return { + valid: true, + value, + warning: `Model ${modelId} has unknown thinking support. Value passed through unchanged.`, + }; + } + + // Model doesn't support thinking at all + if (thinking.type === 'none') { + return { + valid: false, + value: 'off', + warning: `Model ${modelId} does not support extended thinking. Thinking disabled.`, + }; + } + + // Handle 'auto' value + if (typeof value === 'string' && value.toLowerCase().trim() === THINKING_AUTO_VALUE) { + if (!thinking.dynamicAllowed) { + return { + valid: false, + value: + thinking.type === 'budget' ? (thinking.min ?? 1024) : (thinking.levels?.[0] ?? 'low'), + warning: `Model ${modelId} does not support dynamic/auto thinking. Using minimum value.`, + }; + } + return { valid: true, value: 'auto' }; + } + + // Budget-type models + if (thinking.type === 'budget') { + return validateBudgetThinking(thinking, value, modelId); + } + + // Level-type models + if (thinking.type === 'levels') { + return validateLevelThinking(thinking, value, modelId); + } + + // Fallback + return { valid: true, value }; +} + +/** + * Validate budget-type thinking value + */ +function validateBudgetThinking( + thinking: ThinkingSupport, + value: string | number, + modelId: string +): ThinkingValidationResult { + const min = thinking.min ?? THINKING_BUDGET_MIN; + const max = thinking.max ?? THINKING_BUDGET_MAX; + + // Convert level name to budget if needed + let budget: number; + if (typeof value === 'string') { + const normalizedLevel = value.toLowerCase().trim(); + if (normalizedLevel in THINKING_LEVEL_BUDGETS) { + budget = THINKING_LEVEL_BUDGETS[normalizedLevel]; + } else { + // Strict number parsing: reject partial parses like "123abc" + const parsed = Number(value); + if (isNaN(parsed) || !Number.isFinite(parsed) || value.trim() !== String(parsed)) { + // Not a valid number, try to find closest level + const closest = findClosestLevel(value, Object.keys(THINKING_LEVEL_BUDGETS)); + if (closest) { + budget = THINKING_LEVEL_BUDGETS[closest]; + } else { + return { + valid: false, + value: min || THINKING_BUDGET_DEFAULT_MIN, + warning: `Invalid thinking value "${value}" for ${modelId}. Using minimum budget ${min || THINKING_BUDGET_DEFAULT_MIN}.`, + }; + } + } else { + budget = parsed; + } + } + } else { + budget = value; + } + + // Reject negative budgets + if (budget < THINKING_BUDGET_MIN) { + return { + valid: false, + value: min || THINKING_BUDGET_DEFAULT_MIN, + warning: `Negative thinking budget not allowed. Using minimum ${min || THINKING_BUDGET_DEFAULT_MIN}.`, + }; + } + + // Check zero budget + if (budget === 0 && !thinking.zeroAllowed) { + return { + valid: false, + value: min || THINKING_BUDGET_DEFAULT_MIN, + warning: `Model ${modelId} does not support zero thinking budget. Using minimum ${min || THINKING_BUDGET_DEFAULT_MIN}.`, + }; + } + + // Clamp to valid range + if (budget < min) { + return { + valid: true, + value: min, + warning: `Thinking budget ${budget} below minimum for ${modelId}. Clamped to ${min}.`, + }; + } + + if (budget > max) { + return { + valid: true, + value: max, + warning: `Thinking budget ${budget} exceeds maximum for ${modelId}. Clamped to ${max}.`, + }; + } + + return { valid: true, value: budget }; +} + +/** + * Validate level-type thinking value + */ +function validateLevelThinking( + thinking: ThinkingSupport, + value: string | number, + modelId: string +): ThinkingValidationResult { + const validLevels = thinking.levels ?? []; + + // If numeric, try to map to closest level by budget + if (typeof value === 'number') { + // Find closest level by comparing to level budgets + let closestLevel = validLevels[0] ?? 'low'; + let closestDiff = Infinity; + + for (const level of validLevels) { + const levelBudget = THINKING_LEVEL_BUDGETS[level] ?? 8192; + const diff = Math.abs(levelBudget - value); + if (diff < closestDiff) { + closestDiff = diff; + closestLevel = level; + } + } + + return { + valid: true, + value: closestLevel, + warning: `Model ${modelId} uses named levels. Mapped budget ${value} to "${closestLevel}".`, + }; + } + + // String level + const normalizedLevel = value.toLowerCase().trim(); + + // Check if it's a valid level for this model + if (validLevels.includes(normalizedLevel)) { + return { valid: true, value: normalizedLevel }; + } + + // Try to find closest match + const closest = findClosestLevel(normalizedLevel, validLevels); + if (closest) { + return { + valid: true, + value: closest, + warning: `Level "${value}" not valid for ${modelId}. Mapped to "${closest}".`, + }; + } + + // Try to map from standard level names to model's levels + const standardToModelLevel: Record = {}; + const levelOrder = ['minimal', 'low', 'medium', 'high', 'xhigh']; + + // Build mapping based on position + for (let i = 0; i < validLevels.length; i++) { + // Map first half of standard levels to lower model levels, second half to higher + const standardIdx = Math.floor((i / validLevels.length) * levelOrder.length); + for (let j = standardIdx; j < levelOrder.length; j++) { + if (!standardToModelLevel[levelOrder[j]]) { + standardToModelLevel[levelOrder[j]] = validLevels[Math.min(i, validLevels.length - 1)]; + } + } + } + + const mapped = standardToModelLevel[normalizedLevel]; + if (mapped) { + return { + valid: true, + value: mapped, + warning: `Level "${value}" mapped to "${mapped}" for ${modelId} (available: ${validLevels.join(', ')}).`, + }; + } + + // Default to first level + const defaultLevel = validLevels[0] ?? 'low'; + return { + valid: false, + value: defaultLevel, + warning: `Invalid level "${value}" for ${modelId}. Using "${defaultLevel}" (available: ${validLevels.join(', ')}).`, + }; +} From ebf7e04b725d09d2fae10e36b9a45b57f8272069 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 8 Jan 2026 15:18:44 -0500 Subject: [PATCH 02/31] feat(cliproxy): add ThinkingSupport to model catalog - define ThinkingSupport interface (budget/levels/none types) - add thinking config to agy and gemini models - add getModelThinkingSupport() and supportsThinking() helpers Refs #307 --- src/cliproxy/model-catalog.ts | 64 +++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/cliproxy/model-catalog.ts b/src/cliproxy/model-catalog.ts index ae8e9530..d206e59c 100644 --- a/src/cliproxy/model-catalog.ts +++ b/src/cliproxy/model-catalog.ts @@ -7,6 +7,25 @@ import { CLIProxyProvider } from './types'; +/** + * Thinking support configuration for a model. + * Defines how thinking/reasoning budget can be controlled. + */ +export interface ThinkingSupport { + /** Type of thinking control: 'budget' (token count), 'levels' (named levels), 'none' */ + type: 'budget' | 'levels' | 'none'; + /** Minimum budget tokens (for budget type) */ + min?: number; + /** Maximum budget tokens (for budget type) */ + max?: number; + /** Valid level names (for levels type) */ + levels?: string[]; + /** Whether zero/disabled thinking is allowed */ + zeroAllowed?: boolean; + /** Whether dynamic/auto thinking is allowed */ + dynamicAllowed?: boolean; +} + /** * Model entry definition */ @@ -27,6 +46,8 @@ export interface ModelEntry { deprecated?: boolean; /** Deprecation reason/message */ deprecationReason?: string; + /** Thinking/reasoning support configuration */ + thinking?: ThinkingSupport; } /** @@ -54,21 +75,37 @@ export const MODEL_CATALOG: Partial> = id: 'gemini-claude-opus-4-5-thinking', name: 'Claude Opus 4.5 Thinking', description: 'Most capable, extended thinking', + thinking: { + type: 'budget', + min: 1024, + max: 100000, + zeroAllowed: false, + dynamicAllowed: true, + }, }, { id: 'gemini-claude-sonnet-4-5-thinking', name: 'Claude Sonnet 4.5 Thinking', description: 'Balanced with extended thinking', + thinking: { + type: 'budget', + min: 1024, + max: 100000, + zeroAllowed: false, + dynamicAllowed: true, + }, }, { id: 'gemini-claude-sonnet-4-5', name: 'Claude Sonnet 4.5', description: 'Fast and capable', + thinking: { type: 'none' }, }, { id: 'gemini-3-pro-preview', name: 'Gemini 3 Pro', description: 'Google latest model via Antigravity', + thinking: { type: 'levels', levels: ['low', 'high'], dynamicAllowed: true }, }, ], }, @@ -82,11 +119,19 @@ export const MODEL_CATALOG: Partial> = name: 'Gemini 3 Pro', tier: 'paid', description: 'Latest model, requires paid Google account', + thinking: { type: 'levels', levels: ['low', 'high'], dynamicAllowed: true }, }, { id: 'gemini-2.5-pro', name: 'Gemini 2.5 Pro', description: 'Stable, works with free Google account', + thinking: { + type: 'budget', + min: 128, + max: 32768, + zeroAllowed: false, + dynamicAllowed: true, + }, }, ], }, @@ -149,3 +194,22 @@ export function getModelDeprecationReason( const model = findModel(provider, modelId); return model?.deprecationReason; } + +/** + * Get thinking support configuration for a model + */ +export function getModelThinkingSupport( + provider: CLIProxyProvider, + modelId: string +): ThinkingSupport | undefined { + const model = findModel(provider, modelId); + return model?.thinking; +} + +/** + * Check if model supports thinking/reasoning + */ +export function supportsThinking(provider: CLIProxyProvider, modelId: string): boolean { + const thinking = getModelThinkingSupport(provider, modelId); + return thinking !== undefined && thinking.type !== 'none'; +} From 0c2fd9cf5f4142a5a096cfa030b489ba9b6260bc Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 8 Jan 2026 15:19:00 -0500 Subject: [PATCH 03/31] feat(config): add ThinkingConfig to unified config - add ThinkingConfig type with mode/override/tier_defaults - add getThinkingConfig() and getEffectiveThinkingBudget() helpers - support auto/off/manual modes with tier-based defaults Refs #307 --- src/config/unified-config-loader.ts | 36 ++++++++++++++++ src/config/unified-config-types.ts | 66 ++++++++++++++++++++++++++++- 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/src/config/unified-config-loader.ts b/src/config/unified-config-loader.ts index 9f3935b2..17d28742 100644 --- a/src/config/unified-config-loader.ts +++ b/src/config/unified-config-loader.ts @@ -18,7 +18,9 @@ import { DEFAULT_GLOBAL_ENV, DEFAULT_CLIPROXY_SERVER_CONFIG, DEFAULT_QUOTA_MANAGEMENT_CONFIG, + DEFAULT_THINKING_CONFIG, GlobalEnvConfig, + ThinkingConfig, } from './unified-config-types'; import { isUnifiedConfigEnabled } from './feature-flags'; @@ -242,6 +244,20 @@ function mergeWithDefaults(partial: Partial): UnifiedConfig { DEFAULT_QUOTA_MANAGEMENT_CONFIG.manual.tier_lock, }, }, + // Thinking config - auto/manual/off control for reasoning budget + thinking: { + mode: partial.thinking?.mode ?? DEFAULT_THINKING_CONFIG.mode, + override: partial.thinking?.override, + tier_defaults: { + opus: partial.thinking?.tier_defaults?.opus ?? DEFAULT_THINKING_CONFIG.tier_defaults.opus, + sonnet: + partial.thinking?.tier_defaults?.sonnet ?? DEFAULT_THINKING_CONFIG.tier_defaults.sonnet, + haiku: + partial.thinking?.tier_defaults?.haiku ?? DEFAULT_THINKING_CONFIG.tier_defaults.haiku, + }, + provider_overrides: partial.thinking?.provider_overrides, + show_warnings: partial.thinking?.show_warnings ?? DEFAULT_THINKING_CONFIG.show_warnings, + }, }; } @@ -575,3 +591,23 @@ export function getGlobalEnvConfig(): GlobalEnvConfig { env: config.global_env?.env ?? { ...DEFAULT_GLOBAL_ENV }, }; } + +/** + * Get thinking configuration. + * Returns defaults if not configured. + */ +export function getThinkingConfig(): ThinkingConfig { + const config = loadOrCreateUnifiedConfig(); + return { + mode: config.thinking?.mode ?? DEFAULT_THINKING_CONFIG.mode, + override: config.thinking?.override, + tier_defaults: { + opus: config.thinking?.tier_defaults?.opus ?? DEFAULT_THINKING_CONFIG.tier_defaults.opus, + sonnet: + config.thinking?.tier_defaults?.sonnet ?? DEFAULT_THINKING_CONFIG.tier_defaults.sonnet, + haiku: config.thinking?.tier_defaults?.haiku ?? DEFAULT_THINKING_CONFIG.tier_defaults.haiku, + }, + provider_overrides: config.thinking?.provider_overrides, + show_warnings: config.thinking?.show_warnings ?? DEFAULT_THINKING_CONFIG.show_warnings, + }; +} diff --git a/src/config/unified-config-types.ts b/src/config/unified-config-types.ts index e5eec79b..ecb3e636 100644 --- a/src/config/unified-config-types.ts +++ b/src/config/unified-config-types.ts @@ -17,8 +17,9 @@ * Version 5 = Remote proxy configuration (connect to remote CLIProxyAPI) * Version 6 = Customizable auth tokens (API key and management secret) * Version 7 = Quota management for hybrid auto+manual account control + * Version 8 = Thinking/reasoning budget configuration */ -export const UNIFIED_CONFIG_VERSION = 7; +export const UNIFIED_CONFIG_VERSION = 8; /** * Account configuration (formerly in profiles.json). @@ -424,6 +425,66 @@ export const DEFAULT_QUOTA_MANAGEMENT_CONFIG: QuotaManagementConfig = { manual: { ...DEFAULT_MANUAL_QUOTA_CONFIG }, }; +// ============================================================================ +// THINKING CONFIGURATION (v8+) +// ============================================================================ + +/** + * Thinking mode for auto/manual/off control. + * - auto: Apply tier-based defaults (opus→high, sonnet→medium, haiku→low) + * - off: Disable thinking entirely + * - manual: Use explicit override value + */ +export type ThinkingMode = 'auto' | 'off' | 'manual'; + +/** + * Tier-to-thinking level defaults. + * Maps Claude tier names to thinking level names. + */ +export interface ThinkingTierDefaults { + /** Thinking level for opus tier (default: 'high') */ + opus: string; + /** Thinking level for sonnet tier (default: 'medium') */ + sonnet: string; + /** Thinking level for haiku tier (default: 'low') */ + haiku: string; +} + +/** + * Thinking configuration section. + * Controls thinking/reasoning budget injection for CLIProxy providers. + */ +export interface ThinkingConfig { + /** Thinking mode (default: 'auto') */ + mode: ThinkingMode; + /** Manual override value (level name or budget number) */ + override?: string | number; + /** Tier-to-level mapping */ + tier_defaults: ThinkingTierDefaults; + /** Per-provider overrides (e.g., { gemini: { opus: 'high' } }) */ + provider_overrides?: Record>; + /** Show warning when values are clamped (default: true) */ + show_warnings?: boolean; +} + +/** + * Default thinking tier defaults. + */ +export const DEFAULT_THINKING_TIER_DEFAULTS: ThinkingTierDefaults = { + opus: 'high', + sonnet: 'medium', + haiku: 'low', +}; + +/** + * Default thinking configuration. + */ +export const DEFAULT_THINKING_CONFIG: ThinkingConfig = { + mode: 'auto', + tier_defaults: { ...DEFAULT_THINKING_TIER_DEFAULTS }, + show_warnings: true, +}; + /** * Main unified configuration structure. * Stored in ~/.ccs/config.yaml @@ -451,6 +512,8 @@ export interface UnifiedConfig { cliproxy_server?: CliproxyServerConfig; /** Quota management configuration (v7+) */ quota_management?: QuotaManagementConfig; + /** Thinking/reasoning budget configuration (v8+) */ + thinking?: ThinkingConfig; } /** @@ -540,6 +603,7 @@ export function createEmptyUnifiedConfig(): UnifiedConfig { copilot: { ...DEFAULT_COPILOT_CONFIG }, cliproxy_server: { ...DEFAULT_CLIPROXY_SERVER_CONFIG }, quota_management: { ...DEFAULT_QUOTA_MANAGEMENT_CONFIG }, + thinking: { ...DEFAULT_THINKING_CONFIG }, }; } From 014b5e68b8d9486ed697509e6e6fc506671af36a Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 8 Jan 2026 15:19:15 -0500 Subject: [PATCH 04/31] feat(cliproxy): inject thinking suffix into model config - add getEffectiveThinkingValue() to resolve tier defaults - append (budget) or (level) suffix to model ID - validate thinking value against model capabilities - respect mode: auto uses tier defaults, manual uses override Refs #307 --- src/cliproxy/config-generator.ts | 147 ++++++++++++++++++++++++++++++- 1 file changed, 146 insertions(+), 1 deletion(-) diff --git a/src/cliproxy/config-generator.ts b/src/cliproxy/config-generator.ts index e2d9c40f..5ef52cb1 100644 --- a/src/cliproxy/config-generator.ts +++ b/src/cliproxy/config-generator.ts @@ -15,14 +15,159 @@ import { expandPath } from '../utils/helpers'; import { warn } from '../utils/ui'; import { CLIProxyProvider, ProviderConfig, ProviderModelMapping } from './types'; import { getModelMappingFromConfig, getEnvVarsFromConfig } from './base-config-loader'; -import { loadOrCreateUnifiedConfig, getGlobalEnvConfig } from '../config/unified-config-loader'; +import { + loadOrCreateUnifiedConfig, + getGlobalEnvConfig, + getThinkingConfig, +} from '../config/unified-config-loader'; import { getEffectiveApiKey, getEffectiveManagementSecret } from './auth-token-manager'; +import { supportsThinking } from './model-catalog'; +import { ThinkingConfig } from '../config/unified-config-types'; +import { validateThinking } from './thinking-validator'; /** Settings file structure for user overrides */ interface ProviderSettings { env: NodeJS.ProcessEnv; } +/** + * Detect current tier from model name. + * Looks at ANTHROPIC_MODEL to determine if using opus/sonnet/haiku tier. + */ +export type ModelTier = 'opus' | 'sonnet' | 'haiku'; + +/** + * Detect tier from model name. + * Returns 'sonnet' as default if unclear. + */ +export function detectTierFromModel(modelName: string): ModelTier { + const lower = modelName.toLowerCase(); + if (lower.includes('opus')) return 'opus'; + if (lower.includes('haiku')) return 'haiku'; + return 'sonnet'; // Default to sonnet (most common) +} + +/** + * Apply thinking suffix to model name. + * CLIProxyAPIPlus parses suffixes like model(level) or model(budget). + * + * @param model - Base model name + * @param thinkingValue - Level name (e.g., 'high') or numeric budget + * @returns Model name with thinking suffix, e.g., "gemini-3-pro-preview(high)" + */ +export function applyThinkingSuffix(model: string, thinkingValue: string | number): string { + // Don't apply if already has suffix + if (model.includes('(') && model.includes(')')) { + return model; + } + return `${model}(${thinkingValue})`; +} + +/** + * Get thinking value for tier based on config. + * Respects provider-specific overrides if configured. + */ +export function getThinkingValueForTier( + tier: ModelTier, + provider: CLIProxyProvider, + thinkingConfig: ThinkingConfig +): string { + // Check provider-specific override first + const providerOverride = thinkingConfig.provider_overrides?.[provider]?.[tier]; + if (providerOverride) { + return providerOverride; + } + // Fall back to global tier default + return thinkingConfig.tier_defaults[tier]; +} + +/** + * Apply thinking configuration to env vars. + * Modifies ANTHROPIC_MODEL and tier models with thinking suffixes. + * + * @param envVars - Environment variables to modify + * @param provider - CLIProxy provider + * @param thinkingOverride - Optional CLI override (takes priority over config) + * @returns Modified env vars with thinking suffixes applied + */ +export function applyThinkingConfig( + envVars: NodeJS.ProcessEnv, + provider: CLIProxyProvider, + thinkingOverride?: string | number +): NodeJS.ProcessEnv { + const thinkingConfig = getThinkingConfig(); + const result = { ...envVars }; + + // Check if thinking is off + if (thinkingConfig.mode === 'off' && thinkingOverride === undefined) { + return result; + } + + // Get base model to check thinking support + const baseModel = result.ANTHROPIC_MODEL || ''; + if (!supportsThinking(provider, baseModel)) { + return result; // Model doesn't support thinking + } + + // Determine thinking value to use + let thinkingValue: string | number; + + if (thinkingOverride !== undefined) { + // CLI override takes priority + thinkingValue = thinkingOverride; + } else if (thinkingConfig.mode === 'manual' && thinkingConfig.override !== undefined) { + // Config manual mode with override + thinkingValue = thinkingConfig.override; + } else if (thinkingConfig.mode === 'auto') { + // Auto mode: detect tier and apply default + const tier = detectTierFromModel(baseModel); + thinkingValue = getThinkingValueForTier(tier, provider, thinkingConfig); + } else { + return result; // No thinking to apply + } + + // Validate thinking value against model capabilities + const validation = validateThinking(provider, baseModel, thinkingValue); + if (validation.warning && thinkingConfig.show_warnings !== false) { + console.warn(warn(validation.warning)); + } + thinkingValue = validation.value; + + // If validation says off, don't apply suffix + if (thinkingValue === 'off') { + return result; + } + + // Apply thinking suffix to main model + if (result.ANTHROPIC_MODEL) { + result.ANTHROPIC_MODEL = applyThinkingSuffix(result.ANTHROPIC_MODEL, thinkingValue); + } + + // Apply to tier models if they support thinking + const tierModels = [ + 'ANTHROPIC_DEFAULT_OPUS_MODEL', + 'ANTHROPIC_DEFAULT_SONNET_MODEL', + 'ANTHROPIC_DEFAULT_HAIKU_MODEL', + ] as const; + + for (const tierVar of tierModels) { + const model = result[tierVar]; + if (model && supportsThinking(provider, model)) { + // Get tier-specific thinking value + const tier = tierVar.includes('OPUS') + ? 'opus' + : tierVar.includes('SONNET') + ? 'sonnet' + : 'haiku'; + const tierThinkingValue = + thinkingOverride ?? getThinkingValueForTier(tier, provider, thinkingConfig); + result[tierVar] = applyThinkingSuffix(model, tierThinkingValue); + } + } + + return result; +} + /** * Validate port is a valid positive integer (1-65535). * Returns default port if invalid. From 4d361b2ecf9032271eb4fa292b82a2205139b81b Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 8 Jan 2026 15:19:42 -0500 Subject: [PATCH 05/31] feat(cli): add --thinking flag for runtime budget override - parse --thinking=value from CLI arguments - pass thinking value to config generator - update help command with --thinking documentation Refs #307 --- src/cliproxy/cliproxy-executor.ts | 29 +++++++++++++++++++++++++++-- src/commands/help-command.ts | 4 ++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/cliproxy/cliproxy-executor.ts b/src/cliproxy/cliproxy-executor.ts index 03c8c04f..ecaaff24 100644 --- a/src/cliproxy/cliproxy-executor.ts +++ b/src/cliproxy/cliproxy-executor.ts @@ -27,6 +27,7 @@ import { CLIPROXY_DEFAULT_PORT, getCliproxyWritablePath, validatePort, + applyThinkingConfig, } from './config-generator'; import { checkRemoteProxy } from './remote-proxy-client'; import { isAuthenticated } from './auth-handler'; @@ -311,6 +312,21 @@ export async function execClaudeWithCLIProxy( setNickname = argsWithoutProxy[nicknameIdx + 1]; } + // Parse --thinking flag for thinking budget control + // Supports: level names (low, medium, high, xhigh, auto), numeric budget, or 'off'/'none' + let thinkingOverride: string | number | undefined; + const thinkingIdx = argsWithoutProxy.indexOf('--thinking'); + if ( + thinkingIdx !== -1 && + argsWithoutProxy[thinkingIdx + 1] && + !argsWithoutProxy[thinkingIdx + 1].startsWith('-') + ) { + const val = argsWithoutProxy[thinkingIdx + 1]; + // Parse as number if numeric, otherwise keep as string (level name) + const numVal = parseInt(val, 10); + thinkingOverride = !isNaN(numVal) ? numVal : val; + } + // Handle --accounts: list accounts and exit if (showAccounts) { const accounts = getProviderAccounts(provider); @@ -704,6 +720,10 @@ export async function execClaudeWithCLIProxy( ) : getEffectiveEnvVars(provider, cfg.port, cfg.customSettingsPath, remoteRewriteConfig); + // Apply thinking configuration to model (auto tier-based or manual override) + // This adds thinking suffix like model(high) or model(8192) for CLIProxyAPIPlus + applyThinkingConfig(envVars, provider, thinkingOverride); + // Codex-only: inject OpenAI reasoning effort based on tier model mapping. // Maps by request.model: // - OPUS/default model → xhigh @@ -789,6 +809,7 @@ export async function execClaudeWithCLIProxy( '--accounts', '--use', '--nickname', + '--thinking', '--incognito', '--no-incognito', '--import', @@ -798,8 +819,12 @@ export async function execClaudeWithCLIProxy( const claudeArgs = argsWithoutProxy.filter((arg, idx) => { // Filter out CCS flags if (ccsFlags.includes(arg)) return false; - // Filter out value after --use or --nickname - if (argsWithoutProxy[idx - 1] === '--use' || argsWithoutProxy[idx - 1] === '--nickname') + // Filter out value after --use, --nickname, or --thinking + if ( + argsWithoutProxy[idx - 1] === '--use' || + argsWithoutProxy[idx - 1] === '--nickname' || + argsWithoutProxy[idx - 1] === '--thinking' + ) return false; return true; }); diff --git a/src/commands/help-command.ts b/src/commands/help-command.ts index b443b5f2..42de0433 100644 --- a/src/commands/help-command.ts +++ b/src/commands/help-command.ts @@ -172,6 +172,10 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim(); ['ccs --accounts', 'List all accounts'], ['ccs --use ', 'Switch to account'], ['ccs --config', 'Change model (agy, gemini)'], + [ + 'ccs --thinking ', + 'Set thinking budget (low/medium/high/xhigh/auto/off or number)', + ], ['ccs --logout', 'Clear authentication'], ['ccs --headless', 'Headless auth (for SSH)'], ['ccs kiro --import', 'Import token from Kiro IDE'], From 9a2598fb61904e1124f5142a179f0407a1f1c13a Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 8 Jan 2026 15:19:57 -0500 Subject: [PATCH 06/31] feat(api): add /api/thinking endpoints for budget config - GET /api/thinking returns current config - PUT /api/thinking updates mode/override/tier_defaults - validate override bounds and level names Refs #307 --- src/web-server/routes/misc-routes.ts | 103 +++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/src/web-server/routes/misc-routes.ts b/src/web-server/routes/misc-routes.ts index f88339b4..53cc1306 100644 --- a/src/web-server/routes/misc-routes.ts +++ b/src/web-server/routes/misc-routes.ts @@ -11,7 +11,15 @@ import { loadOrCreateUnifiedConfig, saveUnifiedConfig, getGlobalEnvConfig, + getThinkingConfig, } from '../../config/unified-config-loader'; +import type { ThinkingConfig } from '../../config/unified-config-types'; +import { + THINKING_BUDGET_MIN, + THINKING_BUDGET_MAX, + VALID_THINKING_LEVELS, + THINKING_OFF_VALUES, +} from '../../cliproxy'; import { validateFilePath } from './route-helpers'; const router = Router(); @@ -221,4 +229,99 @@ router.put('/global-env', (req: Request, res: Response): void => { } }); +// ==================== Thinking Configuration ==================== + +/** + * GET /api/thinking - Get thinking budget configuration + * Returns the thinking section from config.yaml + */ +router.get('/thinking', (_req: Request, res: Response): void => { + try { + const config = getThinkingConfig(); + res.json({ config }); + } catch (error) { + res.status(500).json({ error: (error as Error).message }); + } +}); + +/** + * PUT /api/thinking - Update thinking budget configuration + * Updates the thinking section in config.yaml + */ +router.put('/thinking', (req: Request, res: Response): void => { + try { + const updates = req.body as Partial; + const config = loadOrCreateUnifiedConfig(); + + // Validate mode if provided + if (updates.mode !== undefined) { + const validModes = ['auto', 'off', 'manual']; + if (!validModes.includes(updates.mode)) { + res.status(400).json({ error: `Invalid mode: must be one of ${validModes.join(', ')}` }); + return; + } + } + + // Validate override if provided (budget or level) + if (updates.override !== undefined) { + if (typeof updates.override === 'number') { + if ( + !Number.isFinite(updates.override) || + updates.override < THINKING_BUDGET_MIN || + updates.override > THINKING_BUDGET_MAX + ) { + res.status(400).json({ + error: `Invalid override: must be between ${THINKING_BUDGET_MIN} and ${THINKING_BUDGET_MAX}`, + }); + return; + } + } else if (typeof updates.override === 'string') { + const normalizedValue = updates.override.toLowerCase().trim(); + const validValues = [...VALID_THINKING_LEVELS, ...THINKING_OFF_VALUES] as readonly string[]; + if (!validValues.includes(normalizedValue)) { + res.status(400).json({ + error: `Invalid override: must be a level name (${VALID_THINKING_LEVELS.join(', ')}) or a number`, + }); + return; + } + } + } + + // Validate tier_defaults if provided + if (updates.tier_defaults !== undefined) { + const validLevels = [...VALID_THINKING_LEVELS] as string[]; + for (const [tier, level] of Object.entries(updates.tier_defaults)) { + if (!['opus', 'sonnet', 'haiku'].includes(tier)) { + res.status(400).json({ error: `Invalid tier: ${tier}` }); + return; + } + if (!validLevels.includes(level)) { + res.status(400).json({ + error: `Invalid level for ${tier}: must be one of ${validLevels.join(', ')}`, + }); + return; + } + } + } + + // Update thinking section + config.thinking = { + mode: updates.mode ?? config.thinking?.mode ?? 'auto', + override: updates.override ?? config.thinking?.override, + tier_defaults: { + opus: updates.tier_defaults?.opus ?? config.thinking?.tier_defaults?.opus ?? 'high', + sonnet: updates.tier_defaults?.sonnet ?? config.thinking?.tier_defaults?.sonnet ?? 'medium', + haiku: updates.tier_defaults?.haiku ?? config.thinking?.tier_defaults?.haiku ?? 'low', + }, + provider_overrides: updates.provider_overrides ?? config.thinking?.provider_overrides, + show_warnings: updates.show_warnings ?? config.thinking?.show_warnings ?? true, + }; + + saveUnifiedConfig(config); + res.json({ success: true, config: config.thinking }); + } catch (error) { + res.status(500).json({ error: (error as Error).message }); + } +}); + export default router; From 0a95f361a25415fb06bda06a16b0419ce2651119 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 8 Jan 2026 15:20:12 -0500 Subject: [PATCH 07/31] feat(ui): add Thinking settings tab to dashboard - add ThinkingSection with mode selector and tier defaults - add useThinkingConfig hook for API integration - add tab navigation for Thinking settings - add ThinkingConfig types for UI Refs #307 --- .../settings/components/tab-navigation.tsx | 6 +- ui/src/pages/settings/hooks/index.ts | 1 + .../settings/hooks/use-thinking-config.ts | 121 ++++++++++ ui/src/pages/settings/index.tsx | 2 + .../settings/sections/thinking/index.tsx | 208 ++++++++++++++++++ ui/src/pages/settings/types.ts | 20 +- 6 files changed, 356 insertions(+), 2 deletions(-) create mode 100644 ui/src/pages/settings/hooks/use-thinking-config.ts create mode 100644 ui/src/pages/settings/sections/thinking/index.tsx diff --git a/ui/src/pages/settings/components/tab-navigation.tsx b/ui/src/pages/settings/components/tab-navigation.tsx index a318d81d..fb203163 100644 --- a/ui/src/pages/settings/components/tab-navigation.tsx +++ b/ui/src/pages/settings/components/tab-navigation.tsx @@ -4,7 +4,7 @@ */ import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; -import { Globe, Settings2, Server, KeyRound } from 'lucide-react'; +import { Globe, Settings2, Server, KeyRound, Brain } from 'lucide-react'; import type { SettingsTab } from '../types'; interface TabNavigationProps { @@ -24,6 +24,10 @@ export function TabNavigation({ activeTab, onTabChange }: TabNavigationProps) { Global Env + + + Thinking + Proxy diff --git a/ui/src/pages/settings/hooks/index.ts b/ui/src/pages/settings/hooks/index.ts index 2797059d..7dbfd881 100644 --- a/ui/src/pages/settings/hooks/index.ts +++ b/ui/src/pages/settings/hooks/index.ts @@ -8,3 +8,4 @@ export { useWebSearchConfig } from './use-websearch-config'; export { useGlobalEnvConfig } from './use-globalenv-config'; export { useProxyConfig } from './use-proxy-config'; export { useRawConfig } from './use-raw-config'; +export { useThinkingConfig } from './use-thinking-config'; diff --git a/ui/src/pages/settings/hooks/use-thinking-config.ts b/ui/src/pages/settings/hooks/use-thinking-config.ts new file mode 100644 index 00000000..89f7bb31 --- /dev/null +++ b/ui/src/pages/settings/hooks/use-thinking-config.ts @@ -0,0 +1,121 @@ +/** + * Thinking Config Hook + * Manages thinking budget configuration with direct API calls + */ + +import { useCallback, useState } from 'react'; +import type { ThinkingConfig, ThinkingMode } from '../types'; + +const DEFAULT_THINKING_CONFIG: ThinkingConfig = { + mode: 'auto', + tier_defaults: { + opus: 'high', + sonnet: 'medium', + haiku: 'low', + }, + show_warnings: true, +}; + +export function useThinkingConfig() { + const [config, setConfig] = useState(null); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(false); + + const fetchConfig = useCallback(async () => { + try { + setLoading(true); + setError(null); + const res = await fetch('/api/thinking'); + if (!res.ok) throw new Error('Failed to load Thinking config'); + const data = await res.json(); + setConfig(data.config || DEFAULT_THINKING_CONFIG); + } catch (err) { + setError((err as Error).message); + setConfig(DEFAULT_THINKING_CONFIG); + } finally { + setLoading(false); + } + }, []); + + const saveConfig = useCallback( + async (updates: Partial) => { + const currentConfig = config || DEFAULT_THINKING_CONFIG; + const optimisticConfig = { ...currentConfig, ...updates }; + setConfig(optimisticConfig); + + try { + setSaving(true); + setError(null); + + const res = await fetch('/api/thinking', { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(optimisticConfig), + }); + + if (!res.ok) { + const data = await res.json(); + throw new Error(data.error || 'Failed to save'); + } + + const data = await res.json(); + setConfig(data.config); + setSuccess(true); + setTimeout(() => setSuccess(false), 1500); + } catch (err) { + setConfig(currentConfig); + setError((err as Error).message); + } finally { + setSaving(false); + } + }, + [config] + ); + + const setMode = useCallback( + (mode: ThinkingMode) => { + saveConfig({ mode }); + }, + [saveConfig] + ); + + const setTierDefault = useCallback( + (tier: 'opus' | 'sonnet' | 'haiku', value: string) => { + const currentDefaults = config?.tier_defaults || DEFAULT_THINKING_CONFIG.tier_defaults; + saveConfig({ + tier_defaults: { ...currentDefaults, [tier]: value }, + }); + }, + [config, saveConfig] + ); + + const setShowWarnings = useCallback( + (show: boolean) => { + saveConfig({ show_warnings: show }); + }, + [saveConfig] + ); + + const setManualOverride = useCallback( + (value: string | number | undefined) => { + saveConfig({ mode: 'manual', override: value }); + }, + [saveConfig] + ); + + return { + config: config || DEFAULT_THINKING_CONFIG, + loading, + saving, + error, + success, + fetchConfig, + saveConfig, + setMode, + setTierDefault, + setShowWarnings, + setManualOverride, + }; +} diff --git a/ui/src/pages/settings/index.tsx b/ui/src/pages/settings/index.tsx index ce86a63c..3068e5fb 100644 --- a/ui/src/pages/settings/index.tsx +++ b/ui/src/pages/settings/index.tsx @@ -17,6 +17,7 @@ import type { SettingsTab } from './types'; // Lazy-loaded sections const WebSearchSection = lazy(() => import('./sections/websearch')); const GlobalEnvSection = lazy(() => import('./sections/globalenv-section')); +const ThinkingSection = lazy(() => import('./sections/thinking')); const ProxySection = lazy(() => import('./sections/proxy')); const AuthSection = lazy(() => import('./sections/auth-section')); @@ -57,6 +58,7 @@ function SettingsPageInner() { }> {activeTab === 'websearch' && } {activeTab === 'globalenv' && } + {activeTab === 'thinking' && } {activeTab === 'proxy' && } {activeTab === 'auth' && } diff --git a/ui/src/pages/settings/sections/thinking/index.tsx b/ui/src/pages/settings/sections/thinking/index.tsx new file mode 100644 index 00000000..fb5d5c17 --- /dev/null +++ b/ui/src/pages/settings/sections/thinking/index.tsx @@ -0,0 +1,208 @@ +/** + * Thinking Section + * Settings section for thinking budget configuration + */ + +import { useEffect } from 'react'; +import { Button } from '@/components/ui/button'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { Switch } from '@/components/ui/switch'; +import { Label } from '@/components/ui/label'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { RefreshCw, CheckCircle2, AlertCircle, Brain } from 'lucide-react'; +import { useThinkingConfig } from '../../hooks'; +import type { ThinkingMode } from '../../types'; + +const THINKING_LEVELS = [ + { value: 'minimal', label: 'Minimal (512 tokens)' }, + { value: 'low', label: 'Low (1K tokens)' }, + { value: 'medium', label: 'Medium (8K tokens)' }, + { value: 'high', label: 'High (24K tokens)' }, + { value: 'xhigh', label: 'Extra High (32K tokens)' }, + { value: 'auto', label: 'Auto (dynamic)' }, +]; + +export default function ThinkingSection() { + const { + config, + loading, + saving, + error, + success, + fetchConfig, + setMode, + setTierDefault, + setShowWarnings, + } = useThinkingConfig(); + + useEffect(() => { + fetchConfig(); + }, [fetchConfig]); + + if (loading) { + return ( +
+
+ + Loading... +
+
+ ); + } + + return ( + <> + {/* Toast-style alerts */} +
+ {error && ( + + + {error} + + )} + {success && ( +
+ + Saved +
+ )} +
+ + {/* Scrollable Content */} + +
+
+ +

+ Configure extended thinking/reasoning for supported models. +

+
+ + {/* Mode Selection */} +
+

Thinking Mode

+
+ {(['auto', 'off', 'manual'] as ThinkingMode[]).map((mode) => ( +
setMode(mode)} + > +
+

{mode}

+

+ {mode === 'auto' && 'Automatically set thinking based on model tier'} + {mode === 'off' && 'Disable extended thinking'} + {mode === 'manual' && 'Use --thinking flag to control manually'} +

+
+
+
+ ))} +
+
+ + {/* Tier Defaults (only shown when mode is auto) */} + {config.mode === 'auto' && ( +
+

Tier Defaults

+

+ Default thinking level for each model tier when in auto mode. +

+ +
+ {(['opus', 'sonnet', 'haiku'] as const).map((tier) => ( +
+ + +
+ ))} +
+
+ )} + + {/* Show Warnings Toggle */} +
+
+

Show Warnings

+

+ Display warnings when thinking values are clamped or adjusted +

+
+ +
+ + {/* Info Box */} +
+

CLI Override

+

+ Use --thinking flag to + override settings per session: +

+
+

ccs gemini --thinking high

+

ccs agy --thinking 16384

+

ccs gemini --thinking off

+
+
+
+ + + {/* Footer */} +
+ +
+ + ); +} diff --git a/ui/src/pages/settings/types.ts b/ui/src/pages/settings/types.ts index 9fdd73de..1dd1ae42 100644 --- a/ui/src/pages/settings/types.ts +++ b/ui/src/pages/settings/types.ts @@ -49,7 +49,25 @@ export interface GlobalEnvConfig { // === Tab Types === -export type SettingsTab = 'websearch' | 'globalenv' | 'proxy' | 'auth'; +export type SettingsTab = 'websearch' | 'globalenv' | 'proxy' | 'auth' | 'thinking'; + +// === Thinking Types === + +export type ThinkingMode = 'auto' | 'off' | 'manual'; + +export interface ThinkingTierDefaults { + opus: string; + sonnet: string; + haiku: string; +} + +export interface ThinkingConfig { + mode: ThinkingMode; + override?: string | number; + tier_defaults: ThinkingTierDefaults; + provider_overrides?: Record>; + show_warnings?: boolean; +} // === Re-exports from api-client === From 3bd3e379fe9573929bf24e1c3a925daac8578eaf Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 8 Jan 2026 15:20:37 -0500 Subject: [PATCH 08/31] test(cliproxy): add unit tests for thinking validator - test budget bounds and constants - test off/auto/level values handling - test budget-type and level-type models - test edge cases and unknown models Refs #307 --- .../unit/cliproxy/thinking-validator.test.ts | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 tests/unit/cliproxy/thinking-validator.test.ts diff --git a/tests/unit/cliproxy/thinking-validator.test.ts b/tests/unit/cliproxy/thinking-validator.test.ts new file mode 100644 index 00000000..f022313f --- /dev/null +++ b/tests/unit/cliproxy/thinking-validator.test.ts @@ -0,0 +1,171 @@ +/** + * Thinking Validator Unit Tests + * + * Tests for thinking budget validation logic + */ + +import { describe, it, expect } from 'bun:test'; +import { + validateThinking, + THINKING_LEVEL_BUDGETS, + VALID_THINKING_LEVELS, + THINKING_OFF_VALUES, + THINKING_BUDGET_MIN, + THINKING_BUDGET_MAX, + THINKING_BUDGET_DEFAULT_MIN, +} from '../../../src/cliproxy/thinking-validator'; + +describe('Thinking Validator', () => { + describe('Constants', () => { + it('should export valid budget bounds', () => { + expect(THINKING_BUDGET_MIN).toBe(0); + expect(THINKING_BUDGET_MAX).toBe(100000); + expect(THINKING_BUDGET_DEFAULT_MIN).toBe(512); + }); + + it('should export valid thinking levels', () => { + expect(VALID_THINKING_LEVELS).toContain('minimal'); + expect(VALID_THINKING_LEVELS).toContain('low'); + expect(VALID_THINKING_LEVELS).toContain('medium'); + expect(VALID_THINKING_LEVELS).toContain('high'); + expect(VALID_THINKING_LEVELS).toContain('xhigh'); + expect(VALID_THINKING_LEVELS).toContain('auto'); + }); + + it('should export level budget mappings', () => { + expect(THINKING_LEVEL_BUDGETS.minimal).toBe(512); + expect(THINKING_LEVEL_BUDGETS.low).toBe(1024); + expect(THINKING_LEVEL_BUDGETS.medium).toBe(8192); + expect(THINKING_LEVEL_BUDGETS.high).toBe(24576); + expect(THINKING_LEVEL_BUDGETS.xhigh).toBe(32768); + }); + + it('should export off values', () => { + expect(THINKING_OFF_VALUES).toContain('off'); + expect(THINKING_OFF_VALUES).toContain('none'); + expect(THINKING_OFF_VALUES).toContain('disabled'); + expect(THINKING_OFF_VALUES).toContain('0'); + }); + }); + + describe('Off values', () => { + it('should handle "off" value', () => { + const result = validateThinking('gemini', 'gemini-3-pro-preview', 'off'); + expect(result.valid).toBe(true); + expect(result.value).toBe('off'); + expect(result.warning).toBeUndefined(); + }); + + it('should handle "none" value', () => { + const result = validateThinking('gemini', 'gemini-3-pro-preview', 'none'); + expect(result.valid).toBe(true); + expect(result.value).toBe('off'); + }); + + it('should handle "disabled" value', () => { + const result = validateThinking('agy', 'claude-sonnet-4-20250514', 'disabled'); + expect(result.valid).toBe(true); + expect(result.value).toBe('off'); + }); + + it('should handle "0" string as off', () => { + const result = validateThinking('gemini', 'gemini-3-pro-preview', '0'); + expect(result.valid).toBe(true); + expect(result.value).toBe('off'); + }); + + it('should be case-insensitive for off values', () => { + const result = validateThinking('gemini', 'gemini-3-pro-preview', 'OFF'); + expect(result.valid).toBe(true); + expect(result.value).toBe('off'); + }); + }); + + describe('Named levels (levels-type models like Gemini)', () => { + it('should accept valid level names', () => { + const levels = ['low', 'medium', 'high']; + for (const level of levels) { + const result = validateThinking('gemini', 'gemini-3-pro-preview', level); + expect(result.valid).toBe(true); + } + }); + + it('should be case-insensitive for level names', () => { + const result = validateThinking('gemini', 'gemini-3-pro-preview', 'HIGH'); + expect(result.valid).toBe(true); + }); + + it('should map numeric budgets to closest level for level-type models', () => { + // Level-type models convert budget to closest named level + const result = validateThinking('gemini', 'gemini-3-pro-preview', 8192); + expect(result.valid).toBe(true); + expect(typeof result.value).toBe('string'); // Should be a level name + expect(result.warning).toContain('Mapped'); + }); + }); + + describe('Budget-type models (like Claude via agy)', () => { + // Claude models via agy use budget-type thinking + const budgetModel = 'gemini-claude-sonnet-4-5-thinking'; + + it('should accept valid numeric budget', () => { + const result = validateThinking('agy', budgetModel, 8192); + expect(result.valid).toBe(true); + expect(result.value).toBe(8192); + }); + + it('should accept numeric string budget', () => { + const result = validateThinking('agy', budgetModel, '8192'); + expect(result.valid).toBe(true); + expect(result.value).toBe(8192); + }); + + it('should reject negative budgets', () => { + const result = validateThinking('agy', budgetModel, -100); + expect(result.valid).toBe(false); + expect(result.warning).toContain('Negative'); + }); + + it('should clamp excessively high budgets', () => { + const result = validateThinking('agy', budgetModel, 999999999); + expect(result.valid).toBe(true); + expect(result.warning).toContain('Clamped'); + }); + + it('should reject partial numeric parses like "123abc"', () => { + const result = validateThinking('agy', budgetModel, '123abc'); + // Should either fail or find closest level match, not parse as 123 + expect(result.value).not.toBe(123); + }); + }); + + describe('Auto value', () => { + it('should accept auto value', () => { + const result = validateThinking('gemini', 'gemini-3-pro-preview', 'auto'); + expect(result.valid).toBe(true); + expect(result.value).toBe('auto'); + }); + }); + + describe('Unknown models', () => { + it('should pass through value for unknown models with warning', () => { + const result = validateThinking('gemini', 'unknown-model-xyz', 'high'); + expect(result.valid).toBe(true); + expect(result.warning).toContain('unknown'); + }); + }); + + describe('Edge cases', () => { + it('should handle whitespace in input', () => { + const result = validateThinking('gemini', 'gemini-3-pro-preview', ' high '); + expect(result.valid).toBe(true); + }); + + it('should handle empty string by passing through for unknown', () => { + // Empty string on known model goes to level/budget validation + const result = validateThinking('gemini', 'gemini-3-pro-preview', ''); + // For level-type models, empty string will try to match levels + expect(result.valid).toBeDefined(); // Just check it doesn't crash + }); + }); +}); From 5f8d23c60bae72cde1f281a24312813211c39140 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 8 Jan 2026 15:48:39 -0500 Subject: [PATCH 09/31] fix(cliproxy): add NaN/Infinity and empty string validation - Add Number.isFinite() check to reject NaN/Infinity budgets - Add explicit empty string handling before level validation --- src/cliproxy/thinking-validator.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/cliproxy/thinking-validator.ts b/src/cliproxy/thinking-validator.ts index 747cd497..9414eae5 100644 --- a/src/cliproxy/thinking-validator.ts +++ b/src/cliproxy/thinking-validator.ts @@ -105,6 +105,15 @@ export function validateThinking( ): ThinkingValidationResult { const thinking = getModelThinkingSupport(provider, modelId); + // Handle empty string explicitly + if (typeof value === 'string' && value.trim() === '') { + return { + valid: false, + value: 'off', + warning: 'Empty thinking value not allowed. Using "off".', + }; + } + // Handle off/none/disabled values if (typeof value === 'string') { const normalizedValue = value.toLowerCase().trim(); @@ -198,6 +207,15 @@ function validateBudgetThinking( budget = value; } + // Reject NaN/Infinity budgets + if (!Number.isFinite(budget)) { + return { + valid: false, + value: min || THINKING_BUDGET_DEFAULT_MIN, + warning: `Budget must be a finite number. Using minimum ${min || THINKING_BUDGET_DEFAULT_MIN}.`, + }; + } + // Reject negative budgets if (budget < THINKING_BUDGET_MIN) { return { From 3060373797871ce2dc1394a5176d3a4693905921 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 8 Jan 2026 15:48:55 -0500 Subject: [PATCH 10/31] fix(cli): add --thinking=value format and improve flag handling - Support --thinking=value in addition to --thinking value - Warn when multiple --thinking flags detected (uses first) - Document intentional negative number blocking --- src/cliproxy/cliproxy-executor.ts | 50 ++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/src/cliproxy/cliproxy-executor.ts b/src/cliproxy/cliproxy-executor.ts index ecaaff24..1c2bc4d7 100644 --- a/src/cliproxy/cliproxy-executor.ts +++ b/src/cliproxy/cliproxy-executor.ts @@ -314,17 +314,51 @@ export async function execClaudeWithCLIProxy( // Parse --thinking flag for thinking budget control // Supports: level names (low, medium, high, xhigh, auto), numeric budget, or 'off'/'none' + // Accepts both --thinking=value and --thinking value formats let thinkingOverride: string | number | undefined; - const thinkingIdx = argsWithoutProxy.indexOf('--thinking'); - if ( - thinkingIdx !== -1 && - argsWithoutProxy[thinkingIdx + 1] && - !argsWithoutProxy[thinkingIdx + 1].startsWith('-') - ) { - const val = argsWithoutProxy[thinkingIdx + 1]; + + // Check for --thinking=value format first + const thinkingEqArg = argsWithoutProxy.find((arg) => arg.startsWith('--thinking=')); + if (thinkingEqArg) { + const val = thinkingEqArg.substring('--thinking='.length); // Parse as number if numeric, otherwise keep as string (level name) const numVal = parseInt(val, 10); thinkingOverride = !isNaN(numVal) ? numVal : val; + + // W2: Warn if multiple --thinking flags found (check both formats) + const allThinkingFlags = argsWithoutProxy.filter( + (arg) => arg === '--thinking' || arg.startsWith('--thinking=') + ); + if (allThinkingFlags.length > 1) { + console.warn( + `[!] Multiple --thinking flags detected. Using first occurrence: ${thinkingEqArg}` + ); + } + } else { + // Fall back to --thinking value format + const thinkingIdx = argsWithoutProxy.indexOf('--thinking'); + if ( + thinkingIdx !== -1 && + argsWithoutProxy[thinkingIdx + 1] && + // W4: Intentionally block negative numbers (negative budgets don't make sense) + // Values starting with '-' are treated as next flag, not a negative number + !argsWithoutProxy[thinkingIdx + 1].startsWith('-') + ) { + const val = argsWithoutProxy[thinkingIdx + 1]; + // Parse as number if numeric, otherwise keep as string (level name) + const numVal = parseInt(val, 10); + thinkingOverride = !isNaN(numVal) ? numVal : val; + + // W2: Warn if multiple --thinking flags found (check both formats) + const allThinkingFlags = argsWithoutProxy.filter( + (arg) => arg === '--thinking' || arg.startsWith('--thinking=') + ); + if (allThinkingFlags.length > 1) { + console.warn( + `[!] Multiple --thinking flags detected. Using first occurrence: --thinking ${val}` + ); + } + } } // Handle --accounts: list accounts and exit @@ -819,6 +853,8 @@ export async function execClaudeWithCLIProxy( const claudeArgs = argsWithoutProxy.filter((arg, idx) => { // Filter out CCS flags if (ccsFlags.includes(arg)) return false; + // Filter out --thinking=value format + if (arg.startsWith('--thinking=')) return false; // Filter out value after --use, --nickname, or --thinking if ( argsWithoutProxy[idx - 1] === '--use' || From 19e52399fe4a9707dbf2878117d2db09cfa5d467 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 8 Jan 2026 15:49:10 -0500 Subject: [PATCH 11/31] fix(config): add null guard and document nested paren limitation - Add optional chaining for tier_defaults to prevent crash - Document nested parenthesis matching limitation --- src/cliproxy/config-generator.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/cliproxy/config-generator.ts b/src/cliproxy/config-generator.ts index 5ef52cb1..a80e68f1 100644 --- a/src/cliproxy/config-generator.ts +++ b/src/cliproxy/config-generator.ts @@ -54,9 +54,13 @@ export function detectTierFromModel(modelName: string): ModelTier { * @param model - Base model name * @param thinkingValue - Level name (e.g., 'high') or numeric budget * @returns Model name with thinking suffix, e.g., "gemini-3-pro-preview(high)" + * + * @note Paren matching only handles one level. Nested parens like "model(foo)(8192)" + * will be treated as already having suffix and returned unchanged. */ export function applyThinkingSuffix(model: string, thinkingValue: string | number): string { // Don't apply if already has suffix + // NOTE: This only detects ANY paren pair, not proper nesting (e.g., "model(foo)(8192)" passes through) if (model.includes('(') && model.includes(')')) { return model; } @@ -77,8 +81,8 @@ export function getThinkingValueForTier( if (providerOverride) { return providerOverride; } - // Fall back to global tier default - return thinkingConfig.tier_defaults[tier]; + // Fall back to global tier default (with null guard) + return thinkingConfig.tier_defaults?.[tier] ?? 'medium'; } /** From 31b9520d54d5fda607bace8f87a7a0989bbb3d23 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 8 Jan 2026 15:49:26 -0500 Subject: [PATCH 12/31] fix(api): add override type and provider_overrides validation - Reject objects/arrays for override field (only string/number) - Validate provider_overrides keys and values against valid levels --- src/web-server/routes/misc-routes.ts | 35 ++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/web-server/routes/misc-routes.ts b/src/web-server/routes/misc-routes.ts index 53cc1306..a8deae6d 100644 --- a/src/web-server/routes/misc-routes.ts +++ b/src/web-server/routes/misc-routes.ts @@ -264,6 +264,14 @@ router.put('/thinking', (req: Request, res: Response): void => { // Validate override if provided (budget or level) if (updates.override !== undefined) { + // C3: Reject objects/arrays - only number or string allowed + if (typeof updates.override !== 'number' && typeof updates.override !== 'string') { + res.status(400).json({ + error: 'Invalid override: must be a number or string, not object/array', + }); + return; + } + if (typeof updates.override === 'number') { if ( !Number.isFinite(updates.override) || @@ -304,6 +312,33 @@ router.put('/thinking', (req: Request, res: Response): void => { } } + // C4: Validate provider_overrides if provided + if (updates.provider_overrides !== undefined) { + if ( + typeof updates.provider_overrides !== 'object' || + updates.provider_overrides === null || + Array.isArray(updates.provider_overrides) + ) { + res.status(400).json({ error: 'Invalid provider_overrides: must be an object' }); + return; + } + const validLevels = [...VALID_THINKING_LEVELS] as string[]; + for (const [provider, level] of Object.entries(updates.provider_overrides)) { + if (typeof provider !== 'string' || provider.trim() === '') { + res + .status(400) + .json({ error: 'Invalid provider_overrides: keys must be non-empty strings' }); + return; + } + if (typeof level !== 'string' || !validLevels.includes(level)) { + res.status(400).json({ + error: `Invalid level for provider ${provider}: must be one of ${validLevels.join(', ')}`, + }); + return; + } + } + } + // Update thinking section config.thinking = { mode: updates.mode ?? config.thinking?.mode ?? 'auto', From b634f365f3c80199516fb798a5a4aba6cb36512d Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 8 Jan 2026 15:49:40 -0500 Subject: [PATCH 13/31] fix(ui): add fetch timeout and abort controller cleanup - Add 10s timeout for API requests using AbortController - Clean up pending requests on unmount to prevent memory leaks --- .../settings/hooks/use-thinking-config.ts | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/ui/src/pages/settings/hooks/use-thinking-config.ts b/ui/src/pages/settings/hooks/use-thinking-config.ts index 89f7bb31..b4571ff3 100644 --- a/ui/src/pages/settings/hooks/use-thinking-config.ts +++ b/ui/src/pages/settings/hooks/use-thinking-config.ts @@ -16,6 +16,8 @@ const DEFAULT_THINKING_CONFIG: ThinkingConfig = { show_warnings: true, }; +const FETCH_TIMEOUT = 10000; // 10 second timeout + export function useThinkingConfig() { const [config, setConfig] = useState(null); const [loading, setLoading] = useState(true); @@ -24,15 +26,24 @@ export function useThinkingConfig() { const [success, setSuccess] = useState(false); const fetchConfig = useCallback(async () => { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT); + try { setLoading(true); setError(null); - const res = await fetch('/api/thinking'); + const res = await fetch('/api/thinking', { signal: controller.signal }); + clearTimeout(timeoutId); if (!res.ok) throw new Error('Failed to load Thinking config'); const data = await res.json(); setConfig(data.config || DEFAULT_THINKING_CONFIG); } catch (err) { - setError((err as Error).message); + clearTimeout(timeoutId); + if ((err as Error).name === 'AbortError') { + setError('Request timeout - please try again'); + } else { + setError((err as Error).message); + } setConfig(DEFAULT_THINKING_CONFIG); } finally { setLoading(false); @@ -45,6 +56,9 @@ export function useThinkingConfig() { const optimisticConfig = { ...currentConfig, ...updates }; setConfig(optimisticConfig); + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT); + try { setSaving(true); setError(null); @@ -53,8 +67,11 @@ export function useThinkingConfig() { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(optimisticConfig), + signal: controller.signal, }); + clearTimeout(timeoutId); + if (!res.ok) { const data = await res.json(); throw new Error(data.error || 'Failed to save'); @@ -65,8 +82,13 @@ export function useThinkingConfig() { setSuccess(true); setTimeout(() => setSuccess(false), 1500); } catch (err) { + clearTimeout(timeoutId); setConfig(currentConfig); - setError((err as Error).message); + if ((err as Error).name === 'AbortError') { + setError('Request timeout - please try again'); + } else { + setError((err as Error).message); + } } finally { setSaving(false); } From 36bcc04133f6a0b0775d5edf897bc915b8a3efc5 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 8 Jan 2026 15:50:05 -0500 Subject: [PATCH 14/31] fix(cliproxy): add case-insensitive model lookup Normalize model IDs to lowercase for comparison in findModel() --- src/cliproxy/model-catalog.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cliproxy/model-catalog.ts b/src/cliproxy/model-catalog.ts index d206e59c..5a3d1909 100644 --- a/src/cliproxy/model-catalog.ts +++ b/src/cliproxy/model-catalog.ts @@ -153,11 +153,13 @@ export function getProviderCatalog(provider: CLIProxyProvider): ProviderCatalog /** * Find model entry by ID + * Note: Model IDs are normalized to lowercase for case-insensitive comparison */ export function findModel(provider: CLIProxyProvider, modelId: string): ModelEntry | undefined { const catalog = MODEL_CATALOG[provider]; if (!catalog) return undefined; - return catalog.models.find((m) => m.id === modelId); + const normalizedId = modelId.toLowerCase(); + return catalog.models.find((m) => m.id.toLowerCase() === normalizedId); } /** From b996153e7fe92e786ae0c1335472cbb470a03327 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 8 Jan 2026 16:24:17 -0500 Subject: [PATCH 15/31] fix(ui): add missing useThinkingConfig export to barrel file --- ui/src/pages/settings/hooks.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/src/pages/settings/hooks.ts b/ui/src/pages/settings/hooks.ts index 3b9c20d8..f85f3d25 100644 --- a/ui/src/pages/settings/hooks.ts +++ b/ui/src/pages/settings/hooks.ts @@ -10,4 +10,5 @@ export { useGlobalEnvConfig, useProxyConfig, useRawConfig, + useThinkingConfig, } from './hooks/index'; From 3ea549addddeba2c8c100d3fc7b892205904da44 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 8 Jan 2026 16:26:50 -0500 Subject: [PATCH 16/31] fix(ui): add thinking tab to URL sync conditional --- ui/src/pages/settings/hooks/use-settings-tab.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/src/pages/settings/hooks/use-settings-tab.ts b/ui/src/pages/settings/hooks/use-settings-tab.ts index 9ec10395..425983d8 100644 --- a/ui/src/pages/settings/hooks/use-settings-tab.ts +++ b/ui/src/pages/settings/hooks/use-settings-tab.ts @@ -16,7 +16,9 @@ export function useSettingsTab() { ? 'proxy' : tabParam === 'auth' ? 'auth' - : 'websearch'; + : tabParam === 'thinking' + ? 'thinking' + : 'websearch'; const setActiveTab = useCallback( (tab: SettingsTab) => { From d5652de63423ebad7afe8f8a428c271d29edb427 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 8 Jan 2026 16:55:14 -0500 Subject: [PATCH 17/31] fix(cliproxy): improve thinking flag validation and warnings - U1: reject --thinking with no value (show usage hint) - U2: warn when provider doesn't support thinking budget --- src/cliproxy/cliproxy-executor.ts | 18 ++++++++++-------- src/cliproxy/config-generator.ts | 10 +++++++++- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/cliproxy/cliproxy-executor.ts b/src/cliproxy/cliproxy-executor.ts index 1c2bc4d7..c8161dc7 100644 --- a/src/cliproxy/cliproxy-executor.ts +++ b/src/cliproxy/cliproxy-executor.ts @@ -337,14 +337,16 @@ export async function execClaudeWithCLIProxy( } else { // Fall back to --thinking value format const thinkingIdx = argsWithoutProxy.indexOf('--thinking'); - if ( - thinkingIdx !== -1 && - argsWithoutProxy[thinkingIdx + 1] && - // W4: Intentionally block negative numbers (negative budgets don't make sense) - // Values starting with '-' are treated as next flag, not a negative number - !argsWithoutProxy[thinkingIdx + 1].startsWith('-') - ) { - const val = argsWithoutProxy[thinkingIdx + 1]; + if (thinkingIdx !== -1) { + const nextArg = argsWithoutProxy[thinkingIdx + 1]; + // U1: Check if --thinking has a value (not missing or another flag) + if (!nextArg || nextArg.startsWith('-')) { + console.error(fail('--thinking requires a value')); + console.error(' Examples: --thinking low, --thinking 8192, --thinking off'); + console.error(' Levels: minimal, low, medium, high, xhigh, auto'); + process.exit(1); + } + const val = nextArg; // Parse as number if numeric, otherwise keep as string (level name) const numVal = parseInt(val, 10); thinkingOverride = !isNaN(numVal) ? numVal : val; diff --git a/src/cliproxy/config-generator.ts b/src/cliproxy/config-generator.ts index a80e68f1..9d43d520 100644 --- a/src/cliproxy/config-generator.ts +++ b/src/cliproxy/config-generator.ts @@ -110,7 +110,15 @@ export function applyThinkingConfig( // Get base model to check thinking support const baseModel = result.ANTHROPIC_MODEL || ''; if (!supportsThinking(provider, baseModel)) { - return result; // Model doesn't support thinking + // U2: Warn user if they explicitly provided --thinking but model doesn't support it + if (thinkingOverride !== undefined && thinkingConfig.show_warnings !== false) { + console.warn( + warn( + `Model ${baseModel || 'unknown'} (provider: ${provider}) does not support thinking budget. --thinking flag ignored.` + ) + ); + } + return result; } // Determine thinking value to use From f7cc9f465312ec5005edb2671f235286f31718d6 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 8 Jan 2026 16:55:31 -0500 Subject: [PATCH 18/31] fix(config): improve YAML error messages and thinking validation - U3: show line/column/snippet for YAML syntax errors - W2: warn when thinking: true used instead of object --- src/config/unified-config-loader.ts | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/config/unified-config-loader.ts b/src/config/unified-config-loader.ts index 17d28742..7db653a5 100644 --- a/src/config/unified-config-loader.ts +++ b/src/config/unified-config-loader.ts @@ -108,8 +108,23 @@ export function loadUnifiedConfig(): UnifiedConfig | null { return parsed; } catch (err) { - const error = err instanceof Error ? err.message : 'Unknown error'; - console.error(`[X] Failed to load config: ${error}`); + // U3: Provide better context for YAML syntax errors + if (err instanceof yaml.YAMLException) { + const mark = err.mark; + console.error(`[X] YAML syntax error in ${yamlPath}:`); + console.error( + ` Line ${(mark?.line ?? 0) + 1}, Column ${(mark?.column ?? 0) + 1}: ${err.reason || 'Invalid syntax'}` + ); + if (mark?.snippet) { + console.error(` ${mark.snippet}`); + } + console.error( + ` Tip: Check for missing colons, incorrect indentation, or unquoted special characters.` + ); + } else { + const error = err instanceof Error ? err.message : 'Unknown error'; + console.error(`[X] Failed to load config: ${error}`); + } return null; } } @@ -598,6 +613,16 @@ export function getGlobalEnvConfig(): GlobalEnvConfig { */ export function getThinkingConfig(): ThinkingConfig { const config = loadOrCreateUnifiedConfig(); + + // W2: Check for invalid thinking config (e.g., thinking: true instead of object) + if (config.thinking !== undefined && typeof config.thinking !== 'object') { + console.warn( + `[!] Invalid thinking config: expected object, got ${typeof config.thinking}. Using defaults.` + ); + console.warn(` Tip: Use 'thinking: { mode: auto }' instead of 'thinking: true'`); + return DEFAULT_THINKING_CONFIG; + } + return { mode: config.thinking?.mode ?? DEFAULT_THINKING_CONFIG.mode, override: config.thinking?.override, From ba19e1fcda0b360f0ca4e02d24f8fad47f249b48 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 8 Jan 2026 16:55:45 -0500 Subject: [PATCH 19/31] fix(api): add optimistic locking for thinking config - W4: return lastModified on GET /api/thinking - W4: check mtime on PUT, return 409 on conflict --- src/web-server/routes/misc-routes.ts | 47 +++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/src/web-server/routes/misc-routes.ts b/src/web-server/routes/misc-routes.ts index a8deae6d..12228ac0 100644 --- a/src/web-server/routes/misc-routes.ts +++ b/src/web-server/routes/misc-routes.ts @@ -12,6 +12,7 @@ import { saveUnifiedConfig, getGlobalEnvConfig, getThinkingConfig, + getConfigYamlPath, } from '../../config/unified-config-loader'; import type { ThinkingConfig } from '../../config/unified-config-types'; import { @@ -233,12 +234,20 @@ router.put('/global-env', (req: Request, res: Response): void => { /** * GET /api/thinking - Get thinking budget configuration - * Returns the thinking section from config.yaml + * Returns the thinking section from config.yaml with mtime for optimistic locking */ router.get('/thinking', (_req: Request, res: Response): void => { try { const config = getThinkingConfig(); - res.json({ config }); + // W4: Include mtime for optimistic locking + let lastModified: number | undefined; + try { + const stats = fs.statSync(getConfigYamlPath()); + lastModified = stats.mtimeMs; + } catch { + // File may not exist yet + } + res.json({ config, lastModified }); } catch (error) { res.status(500).json({ error: (error as Error).message }); } @@ -247,10 +256,30 @@ router.get('/thinking', (_req: Request, res: Response): void => { /** * PUT /api/thinking - Update thinking budget configuration * Updates the thinking section in config.yaml + * Supports optimistic locking via lastModified field */ router.put('/thinking', (req: Request, res: Response): void => { try { - const updates = req.body as Partial; + const { lastModified, ...updates } = req.body as Partial & { + lastModified?: number; + }; + + // W4: Optimistic locking - check if file was modified since last read + if (lastModified !== undefined) { + try { + const stats = fs.statSync(getConfigYamlPath()); + if (stats.mtimeMs > lastModified) { + res.status(409).json({ + error: 'Config was modified by another process. Please refresh and try again.', + currentMtime: stats.mtimeMs, + }); + return; + } + } catch { + // File may not exist yet, allow creation + } + } + const config = loadOrCreateUnifiedConfig(); // Validate mode if provided @@ -353,7 +382,17 @@ router.put('/thinking', (req: Request, res: Response): void => { }; saveUnifiedConfig(config); - res.json({ success: true, config: config.thinking }); + + // W4: Return new mtime for subsequent requests + let newMtime: number | undefined; + try { + const stats = fs.statSync(getConfigYamlPath()); + newMtime = stats.mtimeMs; + } catch { + // Ignore + } + + res.json({ success: true, config: config.thinking, lastModified: newMtime }); } catch (error) { res.status(500).json({ error: (error as Error).message }); } From 35f28a6e7733675813b34a3e6e2bda5907cdc393 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 8 Jan 2026 16:56:05 -0500 Subject: [PATCH 20/31] fix(ui): add provider indicator, retry button, and optimistic locking - U4: add blue info box showing supported providers - W1: add inline retry button in error alert - W4: track lastModified, handle 409 conflict with auto-refresh --- .../settings/hooks/use-thinking-config.ts | 27 +++++++++++++-- .../settings/sections/thinking/index.tsx | 33 +++++++++++++++++-- 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/ui/src/pages/settings/hooks/use-thinking-config.ts b/ui/src/pages/settings/hooks/use-thinking-config.ts index b4571ff3..9c59bbb7 100644 --- a/ui/src/pages/settings/hooks/use-thinking-config.ts +++ b/ui/src/pages/settings/hooks/use-thinking-config.ts @@ -1,9 +1,10 @@ /** * Thinking Config Hook * Manages thinking budget configuration with direct API calls + * Includes W4 optimistic locking via lastModified timestamps */ -import { useCallback, useState } from 'react'; +import { useCallback, useState, useRef } from 'react'; import type { ThinkingConfig, ThinkingMode } from '../types'; const DEFAULT_THINKING_CONFIG: ThinkingConfig = { @@ -24,6 +25,8 @@ export function useThinkingConfig() { const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(false); + // W4: Track lastModified for optimistic locking + const lastModifiedRef = useRef(undefined); const fetchConfig = useCallback(async () => { const controller = new AbortController(); @@ -37,6 +40,8 @@ export function useThinkingConfig() { if (!res.ok) throw new Error('Failed to load Thinking config'); const data = await res.json(); setConfig(data.config || DEFAULT_THINKING_CONFIG); + // W4: Store lastModified for optimistic locking + lastModifiedRef.current = data.lastModified; } catch (err) { clearTimeout(timeoutId); if ((err as Error).name === 'AbortError') { @@ -63,10 +68,16 @@ export function useThinkingConfig() { setSaving(true); setError(null); + // W4: Include lastModified for optimistic locking + const payload = { + ...optimisticConfig, + lastModified: lastModifiedRef.current, + }; + const res = await fetch('/api/thinking', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(optimisticConfig), + body: JSON.stringify(payload), signal: controller.signal, }); @@ -74,11 +85,17 @@ export function useThinkingConfig() { if (!res.ok) { const data = await res.json(); + // W4: Handle conflict (409) with user-friendly message + if (res.status === 409) { + throw new Error('Config changed by another session. Refreshing...'); + } throw new Error(data.error || 'Failed to save'); } const data = await res.json(); setConfig(data.config); + // W4: Update lastModified after successful save + lastModifiedRef.current = data.lastModified; setSuccess(true); setTimeout(() => setSuccess(false), 1500); } catch (err) { @@ -88,12 +105,16 @@ export function useThinkingConfig() { setError('Request timeout - please try again'); } else { setError((err as Error).message); + // W4: On conflict, auto-refresh to get latest + if ((err as Error).message.includes('another session')) { + setTimeout(() => fetchConfig(), 1000); + } } } finally { setSaving(false); } }, - [config] + [config, fetchConfig] ); const setMode = useCallback( diff --git a/ui/src/pages/settings/sections/thinking/index.tsx b/ui/src/pages/settings/sections/thinking/index.tsx index fb5d5c17..e4754d2c 100644 --- a/ui/src/pages/settings/sections/thinking/index.tsx +++ b/ui/src/pages/settings/sections/thinking/index.tsx @@ -16,7 +16,7 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; -import { RefreshCw, CheckCircle2, AlertCircle, Brain } from 'lucide-react'; +import { RefreshCw, CheckCircle2, AlertCircle, Brain, Info } from 'lucide-react'; import { useThinkingConfig } from '../../hooks'; import type { ThinkingMode } from '../../types'; @@ -69,8 +69,22 @@ export default function ThinkingSection() { > {error && ( - - {error} +
+
+ + {error} +
+ {/* W1: Retry button on error */} + +
)} {success && ( @@ -91,6 +105,19 @@ export default function ThinkingSection() {

+ {/* U4: Provider support indicator */} +
+ +
+

Supported Providers

+

+ Thinking budget works with: agy (Antigravity),{' '} + gemini (with thinking models). Other providers may ignore this + setting. +

+
+
+ {/* Mode Selection */}

Thinking Mode

From 7c5f36580ac357e7f63d70ed084e99c2fa24c6c4 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 8 Jan 2026 16:56:31 -0500 Subject: [PATCH 21/31] docs(cli): add extended thinking section to help - W3: explain thinking levels (off/auto/low/medium/high/xhigh) - W3: document supported providers (agy, gemini) --- src/commands/help-command.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/commands/help-command.ts b/src/commands/help-command.ts index 42de0433..ae9d7d44 100644 --- a/src/commands/help-command.ts +++ b/src/commands/help-command.ts @@ -281,6 +281,20 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim(); ['--allow-self-signed', 'Allow self-signed certs (for dev proxies)'], ]); + // W3: Thinking Budget explanation + printSubSection('Extended Thinking (--thinking)', [ + ['--thinking off', 'Disable extended thinking'], + ['--thinking auto', 'Let model decide dynamically'], + ['--thinking low', '1K tokens - Quick responses'], + ['--thinking medium', '8K tokens - Standard analysis'], + ['--thinking high', '24K tokens - Deep reasoning'], + ['--thinking xhigh', '32K tokens - Maximum depth'], + ['--thinking ', 'Custom token budget (512-100000)'], + ['', ''], + ['Note:', 'Extended thinking allocates compute for step-by-step reasoning'], + ['', 'before responding. Supported: agy, gemini (thinking models).'], + ]); + // CLI Proxy env vars printSubSection('CLI Proxy Environment Variables', [ ['CCS_PROXY_HOST', 'Remote proxy hostname'], From c8c189427221707832fa5257ece259321ee3bb52 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Fri, 9 Jan 2026 00:11:18 -0500 Subject: [PATCH 22/31] fix(ui): reduce excessive AGY quota API requests - Match staleTime to refetchInterval (60s) to prevent early refetches - Disable refetchOnWindowFocus to prevent tab switch triggers - Disable refetchOnMount to prevent HMR/navigation remount triggers --- ui/src/hooks/use-cliproxy-stats.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/src/hooks/use-cliproxy-stats.ts b/ui/src/hooks/use-cliproxy-stats.ts index cc9cf222..3b9d43d0 100644 --- a/ui/src/hooks/use-cliproxy-stats.ts +++ b/ui/src/hooks/use-cliproxy-stats.ts @@ -225,8 +225,10 @@ export function useAccountQuota(provider: string, accountId: string, enabled = t queryKey: ['account-quota', provider, accountId], queryFn: () => fetchAccountQuota(provider, accountId), enabled: enabled && provider === 'agy' && !!accountId, - staleTime: 30000, // Consider stale after 30s (tokens can refresh anytime) + staleTime: 60000, // Match refetchInterval to prevent early refetching refetchInterval: 60000, // Refresh every 1 minute + refetchOnWindowFocus: false, // Don't refetch on tab switch + refetchOnMount: false, // Don't refetch on component remount (AuthMonitor re-renders) retry: 1, }); } From 2f584802bdfd9c4486e7a9e79622319bb649379d Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Tue, 20 Jan 2026 20:20:23 -0500 Subject: [PATCH 23/31] chore(deps): add bcrypt, express-session, express-rate-limit for dashboard auth --- ui/bun.lock | 179 ++++++++++++++++++++++++++++++++++++++++++++++++ ui/package.json | 5 ++ 2 files changed, 184 insertions(+) diff --git a/ui/bun.lock b/ui/bun.lock index 2756da98..7db43b0c 100644 --- a/ui/bun.lock +++ b/ui/bun.lock @@ -23,10 +23,13 @@ "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-query": "^5.90.12", "@tanstack/react-table": "^8.21.3", + "bcrypt": "^6.0.0", "chokidar": "^5.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", + "express-rate-limit": "^8.2.1", + "express-session": "^1.18.2", "lucide-react": "^0.556.0", "prism-react-renderer": "^2.4.1", "react": "^19.2.0", @@ -49,6 +52,8 @@ "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.1", "@testing-library/user-event": "^14.6.1", + "@types/bcrypt": "^6.0.0", + "@types/express-session": "^1.18.2", "@types/node": "^24.10.1", "@types/react": "^19.2.5", "@types/react-dom": "^19.2.3", @@ -468,8 +473,14 @@ "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], + "@types/bcrypt": ["@types/bcrypt@6.0.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ=="], + + "@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="], + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], + "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="], "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], @@ -496,18 +507,34 @@ "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + "@types/express": ["@types/express@5.0.6", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", "@types/serve-static": "^2" } }, "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA=="], + + "@types/express-serve-static-core": ["@types/express-serve-static-core@5.1.1", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A=="], + + "@types/express-session": ["@types/express-session@1.18.2", "", { "dependencies": { "@types/express": "*" } }, "sha512-k+I0BxwVXsnEU2hV77cCobC08kIsn4y44C3gC0b46uxZVMaXA04lSPgRLR/bSL2w0t0ShJiG8o4jPzRG/nscFg=="], + + "@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="], + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], "@types/prismjs": ["@types/prismjs@1.26.5", "", {}, "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ=="], + "@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="], + + "@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="], + "@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="], "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], "@types/recharts": ["@types/recharts@1.8.29", "", { "dependencies": { "@types/d3-shape": "^1", "@types/react": "*" } }, "sha512-ulKklaVsnFIIhTQsQw226TnOibrddW1qUQNFVhoQEyY1Z7FRQrNecFCGt7msRuJseudzE9czVawZb17dK/aPXw=="], + "@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="], + + "@types/serve-static": ["@types/serve-static@2.2.0", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*" } }, "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ=="], + "@types/statuses": ["@types/statuses@2.0.6", "", {}, "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA=="], "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.48.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/type-utils": "8.48.1", "@typescript-eslint/utils": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.48.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA=="], @@ -548,6 +575,8 @@ "@vitest/utils": ["@vitest/utils@4.0.16", "", { "dependencies": { "@vitest/pretty-format": "4.0.16", "tinyrainbow": "^3.0.3" } }, "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA=="], + "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], @@ -574,12 +603,22 @@ "baseline-browser-mapping": ["baseline-browser-mapping@2.9.4", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ZCQ9GEWl73BVm8bu5Fts8nt7MHdbt5vY9bP6WGnUh+r3l8M7CgfyTlwsgCbMC66BNxPr6Xoce3j66Ms5YUQTNA=="], + "bcrypt": ["bcrypt@6.0.0", "", { "dependencies": { "node-addon-api": "^8.3.0", "node-gyp-build": "^4.8.4" } }, "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg=="], + "bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="], + "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], + "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], "caniuse-lite": ["caniuse-lite@1.0.30001759", "", {}, "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw=="], @@ -604,10 +643,16 @@ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + "content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="], + + "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], + "cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="], + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "css-tree": ["css-tree@3.1.0", "", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="], @@ -658,6 +703,8 @@ "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], @@ -668,20 +715,34 @@ "dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="], + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + "electron-to-chromium": ["electron-to-chromium@1.5.266", "", {}, "sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg=="], "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], + "enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="], @@ -708,10 +769,18 @@ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], + "eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], + + "express-rate-limit": ["express-rate-limit@8.2.1", "", { "dependencies": { "ip-address": "10.0.1" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g=="], + + "express-session": ["express-session@1.18.2", "", { "dependencies": { "cookie": "0.7.2", "cookie-signature": "1.0.7", "debug": "2.6.9", "depd": "~2.0.0", "on-headers": "~1.1.0", "parseurl": "~1.3.3", "safe-buffer": "5.2.1", "uid-safe": "~2.1.5" } }, "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-equals": ["fast-equals@5.3.3", "", {}, "sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw=="], @@ -724,24 +793,38 @@ "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], + + "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], "globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="], + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], @@ -750,6 +833,10 @@ "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + "headers-polyfill": ["headers-polyfill@4.0.3", "", {}, "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ=="], "hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], @@ -760,6 +847,8 @@ "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], + "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], @@ -774,8 +863,14 @@ "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], + "ip-address": ["ip-address@10.0.1", "", {}, "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA=="], + + "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], @@ -786,6 +881,8 @@ "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="], + "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], @@ -862,8 +959,18 @@ "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + "mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="], + "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], + + "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], + + "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + "min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="], "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], @@ -878,12 +985,26 @@ "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + + "node-addon-api": ["node-addon-api@8.5.0", "", {}, "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A=="], + + "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], + "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], + "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], + + "on-headers": ["on-headers@1.1.0", "", {}, "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], "outvariant": ["outvariant@1.4.3", "", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="], @@ -896,6 +1017,8 @@ "parse5": ["parse5@8.0.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA=="], + "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], @@ -920,8 +1043,18 @@ "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], + "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + "qs": ["qs@6.14.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ=="], + + "random-bytes": ["random-bytes@1.0.0", "", {}, "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ=="], + + "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], + + "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], + "react": ["react@19.2.1", "", {}, "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw=="], "react-day-picker": ["react-day-picker@9.12.0", "", { "dependencies": { "@date-fns/tz": "^1.4.1", "date-fns": "^4.1.0", "date-fns-jalali": "^4.1.0-0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-t8OvG/Zrciso5CQJu5b1A7yzEmebvST+S3pOVQJWxwjjVngyG/CA2htN/D15dLI4uTEuLLkbZyS4YYt480FAtA=="], @@ -974,6 +1107,10 @@ "rollup": ["rollup@4.53.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.3", "@rollup/rollup-android-arm64": "4.53.3", "@rollup/rollup-darwin-arm64": "4.53.3", "@rollup/rollup-darwin-x64": "4.53.3", "@rollup/rollup-freebsd-arm64": "4.53.3", "@rollup/rollup-freebsd-x64": "4.53.3", "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", "@rollup/rollup-linux-arm-musleabihf": "4.53.3", "@rollup/rollup-linux-arm64-gnu": "4.53.3", "@rollup/rollup-linux-arm64-musl": "4.53.3", "@rollup/rollup-linux-loong64-gnu": "4.53.3", "@rollup/rollup-linux-ppc64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-musl": "4.53.3", "@rollup/rollup-linux-s390x-gnu": "4.53.3", "@rollup/rollup-linux-x64-gnu": "4.53.3", "@rollup/rollup-linux-x64-musl": "4.53.3", "@rollup/rollup-openharmony-arm64": "4.53.3", "@rollup/rollup-win32-arm64-msvc": "4.53.3", "@rollup/rollup-win32-ia32-msvc": "4.53.3", "@rollup/rollup-win32-x64-gnu": "4.53.3", "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA=="], + "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], "saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="], @@ -982,12 +1119,26 @@ "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], + + "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], + "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], + "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], + + "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], + + "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], + + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], @@ -1038,6 +1189,8 @@ "tldts-core": ["tldts-core@7.0.19", "", {}, "sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A=="], + "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], + "tough-cookie": ["tough-cookie@6.0.0", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w=="], "tr46": ["tr46@6.0.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw=="], @@ -1050,12 +1203,18 @@ "type-fest": ["type-fest@5.3.1", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg=="], + "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "typescript-eslint": ["typescript-eslint@8.48.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.48.1", "@typescript-eslint/parser": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1", "@typescript-eslint/utils": "8.48.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-FbOKN1fqNoXp1hIl5KYpObVrp0mCn+CLgn479nmu2IsRMrx2vyv74MmsBLVlhg8qVwNFGbXSp8fh1zp8pEoC2A=="], + "uid-safe": ["uid-safe@2.1.5", "", { "dependencies": { "random-bytes": "~1.0.0" } }, "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA=="], + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], + "until-async": ["until-async@3.0.2", "", {}, "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw=="], "update-browserslist-db": ["update-browserslist-db@1.2.2", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA=="], @@ -1068,6 +1227,8 @@ "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], + "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], + "victory-vendor": ["victory-vendor@36.9.2", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ=="], "vite": ["vite@7.2.6", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ=="], @@ -1092,6 +1253,8 @@ "wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], "xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="], @@ -1170,6 +1333,8 @@ "@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "body-parser/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], + "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], "d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="], @@ -1182,6 +1347,14 @@ "d3-time-format/d3-time": ["d3-time@2.1.1", "", { "dependencies": { "d3-array": "2" } }, "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ=="], + "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + + "express/cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], + + "express-session/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + + "express-session/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + "loose-envify/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], "make-dir/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], @@ -1192,6 +1365,10 @@ "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], + "raw-body/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], + + "router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], + "victory-vendor/@types/d3-shape": ["@types/d3-shape@3.1.7", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg=="], "@nivo/core/@types/d3-shape/@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="], @@ -1206,6 +1383,8 @@ "d3-time-format/d3-time/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="], + "express-session/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "victory-vendor/@types/d3-shape/@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="], "d3-time-format/d3-time/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], diff --git a/ui/package.json b/ui/package.json index 23c50f38..338064ee 100644 --- a/ui/package.json +++ b/ui/package.json @@ -38,10 +38,13 @@ "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-query": "^5.90.12", "@tanstack/react-table": "^8.21.3", + "bcrypt": "^6.0.0", "chokidar": "^5.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", + "express-rate-limit": "^8.2.1", + "express-session": "^1.18.2", "lucide-react": "^0.556.0", "prism-react-renderer": "^2.4.1", "react": "^19.2.0", @@ -64,6 +67,8 @@ "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.1", "@testing-library/user-event": "^14.6.1", + "@types/bcrypt": "^6.0.0", + "@types/express-session": "^1.18.2", "@types/node": "^24.10.1", "@types/react": "^19.2.5", "@types/react-dom": "^19.2.3", From eec44d54e2ba8d2f4e5f0bc48a7e9a03f25de2d9 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Wed, 21 Jan 2026 14:27:46 -0500 Subject: [PATCH 24/31] feat(cliproxy): add model-specific reasoning effort caps Add maxLevel field to ThinkingSupport interface to cap reasoning effort at model's maximum supported level. This fixes Issue #344 where gpt-5-mini fails with xhigh reasoning (only supports high). - Add Codex provider to model catalog with gpt-5.2-codex and gpt-5-mini - Add capLevelAtMax() and capEffortAtModelMax() for runtime capping - Update UI presets with new Codex models and tier mappings --- src/cliproxy/codex-reasoning-proxy.ts | 27 +++++++++- src/cliproxy/model-catalog.ts | 43 +++++++++++++++ src/cliproxy/thinking-validator.ts | 75 +++++++++++++++++++++------ ui/src/lib/model-catalogs.ts | 26 +++++++++- 4 files changed, 152 insertions(+), 19 deletions(-) diff --git a/src/cliproxy/codex-reasoning-proxy.ts b/src/cliproxy/codex-reasoning-proxy.ts index 6931288b..d846cb14 100644 --- a/src/cliproxy/codex-reasoning-proxy.ts +++ b/src/cliproxy/codex-reasoning-proxy.ts @@ -1,6 +1,7 @@ import * as http from 'http'; import * as https from 'https'; import { URL } from 'url'; +import { getModelMaxLevel } from './model-catalog'; export type CodexReasoningEffort = 'medium' | 'high' | 'xhigh'; @@ -51,10 +52,32 @@ const EFFORT_RANK: Record = { xhigh: 3, }; +/** All valid codex effort levels in rank order */ +const EFFORT_BY_RANK: CodexReasoningEffort[] = ['medium', 'high', 'xhigh']; + function minEffort(a: CodexReasoningEffort, b: CodexReasoningEffort): CodexReasoningEffort { return EFFORT_RANK[a] <= EFFORT_RANK[b] ? a : b; } +/** + * Cap effort at model's max level from catalog. + * Returns the capped effort (or original if no cap applies). + */ +function capEffortAtModelMax(model: string, effort: CodexReasoningEffort): CodexReasoningEffort { + const maxLevel = getModelMaxLevel('codex', model); + if (!maxLevel) return effort; + + // Map maxLevel to CodexReasoningEffort (only medium/high/xhigh are valid) + const maxEffort = EFFORT_BY_RANK.find((e) => e === maxLevel); + if (!maxEffort) return effort; + + // Cap if effort exceeds max + if (EFFORT_RANK[effort] > EFFORT_RANK[maxEffort]) { + return maxEffort; + } + return effort; +} + export function buildCodexModelEffortMap( models: CodexReasoningModelMap, defaultEffort: CodexReasoningEffort = 'medium' @@ -85,7 +108,9 @@ export function getEffortForModel( defaultEffort: CodexReasoningEffort ): CodexReasoningEffort { if (!model) return defaultEffort; - return modelEffort.get(model) ?? defaultEffort; + const effort = modelEffort.get(model) ?? defaultEffort; + // Apply model-specific cap from catalog + return capEffortAtModelMax(model, effort); } export function injectReasoningEffortIntoBody( diff --git a/src/cliproxy/model-catalog.ts b/src/cliproxy/model-catalog.ts index 5a3d1909..b4c17898 100644 --- a/src/cliproxy/model-catalog.ts +++ b/src/cliproxy/model-catalog.ts @@ -20,6 +20,8 @@ export interface ThinkingSupport { max?: number; /** Valid level names (for levels type) */ levels?: string[]; + /** Maximum reasoning effort level (caps effort at this level for levels type) */ + maxLevel?: 'minimal' | 'low' | 'medium' | 'high' | 'xhigh'; /** Whether zero/disabled thinking is allowed */ zeroAllowed?: boolean; /** Whether dynamic/auto thinking is allowed */ @@ -135,6 +137,35 @@ export const MODEL_CATALOG: Partial> = }, ], }, + codex: { + provider: 'codex', + displayName: 'Copilot Codex', + defaultModel: 'gpt-5.2-codex', + models: [ + { + id: 'gpt-5.2-codex', + name: 'GPT-5.2 Codex', + description: 'Full reasoning support (xhigh)', + thinking: { + type: 'levels', + levels: ['medium', 'high', 'xhigh'], + maxLevel: 'xhigh', + dynamicAllowed: false, + }, + }, + { + id: 'gpt-5-mini', + name: 'GPT-5 Mini', + description: 'Capped at high reasoning (no xhigh)', + thinking: { + type: 'levels', + levels: ['medium', 'high'], + maxLevel: 'high', + dynamicAllowed: false, + }, + }, + ], + }, }; /** @@ -208,6 +239,18 @@ export function getModelThinkingSupport( return model?.thinking; } +/** + * Get the maximum reasoning effort level for a model. + * Returns undefined if model has no cap or is not in catalog. + */ +export function getModelMaxLevel( + provider: CLIProxyProvider, + modelId: string +): ThinkingSupport['maxLevel'] | undefined { + const thinking = getModelThinkingSupport(provider, modelId); + return thinking?.maxLevel; +} + /** * Check if model supports thinking/reasoning */ diff --git a/src/cliproxy/thinking-validator.ts b/src/cliproxy/thinking-validator.ts index 9414eae5..e64e1c46 100644 --- a/src/cliproxy/thinking-validator.ts +++ b/src/cliproxy/thinking-validator.ts @@ -40,12 +40,40 @@ export const THINKING_LEVEL_BUDGETS: Record = { xhigh: 32768, }; +/** + * Level rank for comparison (higher = more intensive) + */ +export const THINKING_LEVEL_RANK: Record = { + minimal: 1, + low: 2, + medium: 3, + high: 4, + xhigh: 5, +}; + /** * Valid thinking level names */ export const VALID_THINKING_LEVELS = ['minimal', 'low', 'medium', 'high', 'xhigh', 'auto'] as const; export type ThinkingLevel = (typeof VALID_THINKING_LEVELS)[number]; +/** + * Cap a level at the model's maximum supported level. + * Returns the capped level and whether capping occurred. + */ +export function capLevelAtMax( + level: string, + maxLevel: string | undefined +): { level: string; capped: boolean } { + if (!maxLevel) return { level, capped: false }; + const levelRank = THINKING_LEVEL_RANK[level] ?? 0; + const maxRank = THINKING_LEVEL_RANK[maxLevel] ?? 5; + if (levelRank > maxRank) { + return { level: maxLevel, capped: true }; + } + return { level, capped: false }; +} + /** * Special thinking values */ @@ -263,6 +291,24 @@ function validateLevelThinking( modelId: string ): ThinkingValidationResult { const validLevels = thinking.levels ?? []; + const maxLevel = thinking.maxLevel; + + // Helper to apply maxLevel cap and build result + const applyMaxCap = (level: string, baseWarning?: string): ThinkingValidationResult => { + const { level: cappedLevel, capped } = capLevelAtMax(level, maxLevel); + const warnings: string[] = []; + if (baseWarning) warnings.push(baseWarning); + if (capped) { + warnings.push( + `Level "${level}" exceeds max "${maxLevel}" for ${modelId}. Capped to "${cappedLevel}".` + ); + } + return { + valid: true, + value: cappedLevel, + warning: warnings.length > 0 ? warnings.join(' ') : undefined, + }; + }; // If numeric, try to map to closest level by budget if (typeof value === 'number') { @@ -279,11 +325,10 @@ function validateLevelThinking( } } - return { - valid: true, - value: closestLevel, - warning: `Model ${modelId} uses named levels. Mapped budget ${value} to "${closestLevel}".`, - }; + return applyMaxCap( + closestLevel, + `Model ${modelId} uses named levels. Mapped budget ${value} to "${closestLevel}".` + ); } // String level @@ -291,17 +336,16 @@ function validateLevelThinking( // Check if it's a valid level for this model if (validLevels.includes(normalizedLevel)) { - return { valid: true, value: normalizedLevel }; + return applyMaxCap(normalizedLevel); } // Try to find closest match const closest = findClosestLevel(normalizedLevel, validLevels); if (closest) { - return { - valid: true, - value: closest, - warning: `Level "${value}" not valid for ${modelId}. Mapped to "${closest}".`, - }; + return applyMaxCap( + closest, + `Level "${value}" not valid for ${modelId}. Mapped to "${closest}".` + ); } // Try to map from standard level names to model's levels @@ -321,11 +365,10 @@ function validateLevelThinking( const mapped = standardToModelLevel[normalizedLevel]; if (mapped) { - return { - valid: true, - value: mapped, - warning: `Level "${value}" mapped to "${mapped}" for ${modelId} (available: ${validLevels.join(', ')}).`, - }; + return applyMaxCap( + mapped, + `Level "${value}" mapped to "${mapped}" for ${modelId} (available: ${validLevels.join(', ')}).` + ); } // Default to first level diff --git a/ui/src/lib/model-catalogs.ts b/ui/src/lib/model-catalogs.ts index 13b25fc0..09c056ba 100644 --- a/ui/src/lib/model-catalogs.ts +++ b/ui/src/lib/model-catalogs.ts @@ -114,12 +114,34 @@ export const MODEL_CATALOGS: Record = { codex: { provider: 'codex', displayName: 'Codex', - defaultModel: 'gpt-5.1-codex-max', + defaultModel: 'gpt-5.2-codex', models: [ + { + id: 'gpt-5.2-codex', + name: 'GPT-5.2 Codex', + description: 'Full reasoning support (xhigh)', + presetMapping: { + default: 'gpt-5.2-codex', + opus: 'gpt-5.2-codex', + sonnet: 'gpt-5.2-codex', + haiku: 'gpt-5-mini', + }, + }, + { + id: 'gpt-5-mini', + name: 'GPT-5 Mini', + description: 'Fast, capped at high reasoning (no xhigh)', + presetMapping: { + default: 'gpt-5-mini', + opus: 'gpt-5.2-codex', + sonnet: 'gpt-5-mini', + haiku: 'gpt-5-mini', + }, + }, { id: 'gpt-5.1-codex-max', name: 'Codex Max (5.1)', - description: 'Most capable Codex model', + description: 'Legacy most capable Codex model', presetMapping: { default: 'gpt-5.1-codex-max', opus: 'gpt-5.1-codex-max-high', From 19b7a49eee3a3487e8026a165c0961d60fe4cb43 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Wed, 21 Jan 2026 17:25:33 -0500 Subject: [PATCH 25/31] feat(thinking): improve config validation and codex support - Add shouldShowWarnings() helper for cleaner warning control - Improve applyThinkingSuffix() regex for accurate suffix detection - Fix provider_overrides validation for nested tier structure - Add thinking section to YAML config export with documentation - Update UI to clarify codex uses reasoning effort levels - Remove unused setManualOverride hook --- src/cliproxy/config-generator.ts | 21 +++++++++------- src/config/unified-config-loader.ts | 19 +++++++++++++++ src/web-server/routes/misc-routes.ts | 24 +++++++++++++++---- .../settings/hooks/use-thinking-config.ts | 8 ------- .../settings/sections/thinking/index.tsx | 13 ++++++---- 5 files changed, 60 insertions(+), 25 deletions(-) diff --git a/src/cliproxy/config-generator.ts b/src/cliproxy/config-generator.ts index 10c9b5db..5abc9d44 100644 --- a/src/cliproxy/config-generator.ts +++ b/src/cliproxy/config-generator.ts @@ -25,6 +25,14 @@ import { supportsThinking } from './model-catalog'; import { ThinkingConfig } from '../config/unified-config-types'; import { validateThinking } from './thinking-validator'; +/** + * Check if warnings should be shown based on thinking config. + * Defaults to true if show_warnings is not explicitly false. + */ +function shouldShowWarnings(thinkingConfig: ThinkingConfig): boolean { + return thinkingConfig.show_warnings !== false; +} + /** Settings file structure for user overrides */ interface ProviderSettings { env: NodeJS.ProcessEnv; @@ -54,14 +62,11 @@ export function detectTierFromModel(modelName: string): ModelTier { * @param model - Base model name * @param thinkingValue - Level name (e.g., 'high') or numeric budget * @returns Model name with thinking suffix, e.g., "gemini-3-pro-preview(high)" - * - * @note Paren matching only handles one level. Nested parens like "model(foo)(8192)" - * will be treated as already having suffix and returned unchanged. */ export function applyThinkingSuffix(model: string, thinkingValue: string | number): string { - // Don't apply if already has suffix - // NOTE: This only detects ANY paren pair, not proper nesting (e.g., "model(foo)(8192)" passes through) - if (model.includes('(') && model.includes(')')) { + // Don't apply if model already ends with a parenthesized suffix (e.g., "model(high)" or "model(8192)") + // Matches: ends with "(...)" where content is non-empty + if (/\([^)]+\)$/.test(model)) { return model; } return `${model}(${thinkingValue})`; @@ -111,7 +116,7 @@ export function applyThinkingConfig( const baseModel = result.ANTHROPIC_MODEL || ''; if (!supportsThinking(provider, baseModel)) { // U2: Warn user if they explicitly provided --thinking but model doesn't support it - if (thinkingOverride !== undefined && thinkingConfig.show_warnings !== false) { + if (thinkingOverride !== undefined && shouldShowWarnings(thinkingConfig)) { console.warn( warn( `Model ${baseModel || 'unknown'} (provider: ${provider}) does not support thinking budget. --thinking flag ignored.` @@ -140,7 +145,7 @@ export function applyThinkingConfig( // Validate thinking value against model capabilities const validation = validateThinking(provider, baseModel, thinkingValue); - if (validation.warning && thinkingConfig.show_warnings !== false) { + if (validation.warning && shouldShowWarnings(thinkingConfig)) { console.warn(warn(validation.warning)); } thinkingValue = validation.value; diff --git a/src/config/unified-config-loader.ts b/src/config/unified-config-loader.ts index 134a2558..273de189 100644 --- a/src/config/unified-config-loader.ts +++ b/src/config/unified-config-loader.ts @@ -470,6 +470,25 @@ function generateYamlWithComments(config: UnifiedConfig): string { lines.push(''); } + // Thinking section (extended thinking/reasoning configuration) + if (config.thinking) { + lines.push('# ----------------------------------------------------------------------------'); + lines.push('# Thinking: Extended thinking/reasoning budget configuration'); + lines.push('# Controls reasoning depth for supported providers (agy, gemini, codex).'); + lines.push('#'); + lines.push('# Modes: auto (use tier_defaults), off (disable), manual (--thinking flag only)'); + lines.push('# Levels: minimal (512), low (1K), medium (8K), high (24K), xhigh (32K), auto'); + lines.push('# Override: Set global override value (number or level name)'); + lines.push('# Provider overrides: Per-provider tier defaults'); + lines.push('# ----------------------------------------------------------------------------'); + lines.push( + yaml + .dump({ thinking: config.thinking }, { indent: 2, lineWidth: -1, quotingType: '"' }) + .trim() + ); + lines.push(''); + } + // Dashboard auth section (only if configured) if (config.dashboard_auth?.enabled) { lines.push('# ----------------------------------------------------------------------------'); diff --git a/src/web-server/routes/misc-routes.ts b/src/web-server/routes/misc-routes.ts index 12228ac0..712b56f7 100644 --- a/src/web-server/routes/misc-routes.ts +++ b/src/web-server/routes/misc-routes.ts @@ -341,7 +341,7 @@ router.put('/thinking', (req: Request, res: Response): void => { } } - // C4: Validate provider_overrides if provided + // C4: Validate provider_overrides if provided (nested structure: Record>) if (updates.provider_overrides !== undefined) { if ( typeof updates.provider_overrides !== 'object' || @@ -352,19 +352,35 @@ router.put('/thinking', (req: Request, res: Response): void => { return; } const validLevels = [...VALID_THINKING_LEVELS] as string[]; - for (const [provider, level] of Object.entries(updates.provider_overrides)) { + const validTiers = ['opus', 'sonnet', 'haiku']; + for (const [provider, tierOverrides] of Object.entries(updates.provider_overrides)) { if (typeof provider !== 'string' || provider.trim() === '') { res .status(400) .json({ error: 'Invalid provider_overrides: keys must be non-empty strings' }); return; } - if (typeof level !== 'string' || !validLevels.includes(level)) { + // tierOverrides should be Partial + if (typeof tierOverrides !== 'object' || tierOverrides === null) { res.status(400).json({ - error: `Invalid level for provider ${provider}: must be one of ${validLevels.join(', ')}`, + error: `Invalid provider_overrides for ${provider}: must be an object with tier→level mapping`, }); return; } + for (const [tier, level] of Object.entries(tierOverrides)) { + if (!validTiers.includes(tier)) { + res.status(400).json({ + error: `Invalid tier '${tier}' in provider_overrides.${provider}: must be one of ${validTiers.join(', ')}`, + }); + return; + } + if (typeof level !== 'string' || !validLevels.includes(level)) { + res.status(400).json({ + error: `Invalid level for provider_overrides.${provider}.${tier}: must be one of ${validLevels.join(', ')}`, + }); + return; + } + } } } diff --git a/ui/src/pages/settings/hooks/use-thinking-config.ts b/ui/src/pages/settings/hooks/use-thinking-config.ts index 9c59bbb7..1c686ff6 100644 --- a/ui/src/pages/settings/hooks/use-thinking-config.ts +++ b/ui/src/pages/settings/hooks/use-thinking-config.ts @@ -141,13 +141,6 @@ export function useThinkingConfig() { [saveConfig] ); - const setManualOverride = useCallback( - (value: string | number | undefined) => { - saveConfig({ mode: 'manual', override: value }); - }, - [saveConfig] - ); - return { config: config || DEFAULT_THINKING_CONFIG, loading, @@ -159,6 +152,5 @@ export function useThinkingConfig() { setMode, setTierDefault, setShowWarnings, - setManualOverride, }; } diff --git a/ui/src/pages/settings/sections/thinking/index.tsx b/ui/src/pages/settings/sections/thinking/index.tsx index e4754d2c..6268e7fd 100644 --- a/ui/src/pages/settings/sections/thinking/index.tsx +++ b/ui/src/pages/settings/sections/thinking/index.tsx @@ -110,11 +110,14 @@ export default function ThinkingSection() {

Supported Providers

-

- Thinking budget works with: agy (Antigravity),{' '} - gemini (with thinking models). Other providers may ignore this - setting. -

+
    +
  • + Thinking budget: agy, gemini (token-based) +
  • +
  • + Reasoning effort: codex (level-based: medium/high/xhigh) +
  • +
From fbb71a228ed232035f1e14cf858b590492720b1c Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Wed, 21 Jan 2026 17:25:56 -0500 Subject: [PATCH 26/31] test: update tests for codex catalog inclusion - Fix config-generator test assertion for parseUserApiKeys - Update model-catalog tests to expect codex provider - Remove outdated codex unsupported test from model-config --- tests/unit/cliproxy/config-generator.test.js | 6 ++++-- tests/unit/cliproxy/model-catalog.test.js | 20 ++++++++++++++------ tests/unit/cliproxy/model-config.test.js | 6 ------ 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/tests/unit/cliproxy/config-generator.test.js b/tests/unit/cliproxy/config-generator.test.js index 703b5fe6..fc8aa340 100644 --- a/tests/unit/cliproxy/config-generator.test.js +++ b/tests/unit/cliproxy/config-generator.test.js @@ -318,8 +318,10 @@ auth-dir: "/test" const userKeys = parseUserApiKeys(configContent); - // Empty strings should be filtered out (only truthy values) - assert.deepStrictEqual(userKeys, ['valid-key']); + // Empty strings should be filtered out - parseUserApiKeys checks: key && key !== CCS_INTERNAL_API_KEY + // Empty string is falsy, so it's excluded along with internal key + assert.strictEqual(userKeys.length, 1, 'Should filter out empty string key'); + assert.deepStrictEqual(userKeys, ['valid-key'], 'Should only include valid non-empty keys'); }); it('handles config with Windows line endings', () => { diff --git a/tests/unit/cliproxy/model-catalog.test.js b/tests/unit/cliproxy/model-catalog.test.js index e583345b..0cd8beed 100644 --- a/tests/unit/cliproxy/model-catalog.test.js +++ b/tests/unit/cliproxy/model-catalog.test.js @@ -23,9 +23,15 @@ describe('Model Catalog', () => { assert.strictEqual(MODEL_CATALOG.gemini.displayName, 'Gemini'); }); - it('does not contain codex or qwen (not configurable)', () => { + it('contains Codex provider catalog', () => { + const { MODEL_CATALOG } = modelCatalog; + assert(MODEL_CATALOG.codex, 'Should have codex provider'); + assert.strictEqual(MODEL_CATALOG.codex.provider, 'codex'); + assert.strictEqual(MODEL_CATALOG.codex.displayName, 'Copilot Codex'); + }); + + it('does not contain qwen (not configurable)', () => { const { MODEL_CATALOG } = modelCatalog; - assert.strictEqual(MODEL_CATALOG.codex, undefined); assert.strictEqual(MODEL_CATALOG.qwen, undefined); }); }); @@ -115,9 +121,9 @@ describe('Model Catalog', () => { assert.strictEqual(supportsModelConfig('gemini'), true); }); - it('returns false for codex', () => { + it('returns true for codex', () => { const { supportsModelConfig } = modelCatalog; - assert.strictEqual(supportsModelConfig('codex'), false); + assert.strictEqual(supportsModelConfig('codex'), true); }); it('returns false for qwen', () => { @@ -142,10 +148,12 @@ describe('Model Catalog', () => { assert.strictEqual(catalog.provider, 'gemini'); }); - it('returns undefined for codex', () => { + it('returns catalog for codex', () => { const { getProviderCatalog } = modelCatalog; const catalog = getProviderCatalog('codex'); - assert.strictEqual(catalog, undefined); + assert(catalog, 'Should return catalog'); + assert.strictEqual(catalog.provider, 'codex'); + assert(Array.isArray(catalog.models)); }); }); diff --git a/tests/unit/cliproxy/model-config.test.js b/tests/unit/cliproxy/model-config.test.js index ec6f3891..66d6aa25 100644 --- a/tests/unit/cliproxy/model-config.test.js +++ b/tests/unit/cliproxy/model-config.test.js @@ -38,12 +38,6 @@ describe('Model Config', () => { }); describe('configureProviderModel', () => { - it('returns false for unsupported provider (codex)', async () => { - const { configureProviderModel } = modelConfig; - const result = await configureProviderModel('codex', true); - assert.strictEqual(result, false); - }); - it('returns false for unsupported provider (qwen)', async () => { const { configureProviderModel } = modelConfig; const result = await configureProviderModel('qwen', true); From 4491d9f3d3cfa1c44eb8156356dc978cd06b9b8a Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Wed, 21 Jan 2026 17:26:07 -0500 Subject: [PATCH 27/31] chore(deps): remove unused auth dependencies from ui - Remove bcrypt, express-rate-limit, express-session - Remove @types/bcrypt, @types/express-session - Clean up unused server-side auth packages --- ui/package.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ui/package.json b/ui/package.json index 338064ee..23c50f38 100644 --- a/ui/package.json +++ b/ui/package.json @@ -38,13 +38,10 @@ "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-query": "^5.90.12", "@tanstack/react-table": "^8.21.3", - "bcrypt": "^6.0.0", "chokidar": "^5.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", - "express-rate-limit": "^8.2.1", - "express-session": "^1.18.2", "lucide-react": "^0.556.0", "prism-react-renderer": "^2.4.1", "react": "^19.2.0", @@ -67,8 +64,6 @@ "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.1", "@testing-library/user-event": "^14.6.1", - "@types/bcrypt": "^6.0.0", - "@types/express-session": "^1.18.2", "@types/node": "^24.10.1", "@types/react": "^19.2.5", "@types/react-dom": "^19.2.3", From 3df6587e8dc4e0dd26896aa65fafb777c5bcac96 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Wed, 21 Jan 2026 17:30:25 -0500 Subject: [PATCH 28/31] chore(ui): regenerate lockfile after dependency removal --- ui/bun.lock | 179 ---------------------------------------------------- 1 file changed, 179 deletions(-) diff --git a/ui/bun.lock b/ui/bun.lock index 7db43b0c..2756da98 100644 --- a/ui/bun.lock +++ b/ui/bun.lock @@ -23,13 +23,10 @@ "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-query": "^5.90.12", "@tanstack/react-table": "^8.21.3", - "bcrypt": "^6.0.0", "chokidar": "^5.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", - "express-rate-limit": "^8.2.1", - "express-session": "^1.18.2", "lucide-react": "^0.556.0", "prism-react-renderer": "^2.4.1", "react": "^19.2.0", @@ -52,8 +49,6 @@ "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.1", "@testing-library/user-event": "^14.6.1", - "@types/bcrypt": "^6.0.0", - "@types/express-session": "^1.18.2", "@types/node": "^24.10.1", "@types/react": "^19.2.5", "@types/react-dom": "^19.2.3", @@ -473,14 +468,8 @@ "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], - "@types/bcrypt": ["@types/bcrypt@6.0.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ=="], - - "@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="], - "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], - "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], - "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="], "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], @@ -507,34 +496,18 @@ "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], - "@types/express": ["@types/express@5.0.6", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", "@types/serve-static": "^2" } }, "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA=="], - - "@types/express-serve-static-core": ["@types/express-serve-static-core@5.1.1", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A=="], - - "@types/express-session": ["@types/express-session@1.18.2", "", { "dependencies": { "@types/express": "*" } }, "sha512-k+I0BxwVXsnEU2hV77cCobC08kIsn4y44C3gC0b46uxZVMaXA04lSPgRLR/bSL2w0t0ShJiG8o4jPzRG/nscFg=="], - - "@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="], - "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], "@types/prismjs": ["@types/prismjs@1.26.5", "", {}, "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ=="], - "@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="], - - "@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="], - "@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="], "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], "@types/recharts": ["@types/recharts@1.8.29", "", { "dependencies": { "@types/d3-shape": "^1", "@types/react": "*" } }, "sha512-ulKklaVsnFIIhTQsQw226TnOibrddW1qUQNFVhoQEyY1Z7FRQrNecFCGt7msRuJseudzE9czVawZb17dK/aPXw=="], - "@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="], - - "@types/serve-static": ["@types/serve-static@2.2.0", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*" } }, "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ=="], - "@types/statuses": ["@types/statuses@2.0.6", "", {}, "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA=="], "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.48.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/type-utils": "8.48.1", "@typescript-eslint/utils": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.48.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA=="], @@ -575,8 +548,6 @@ "@vitest/utils": ["@vitest/utils@4.0.16", "", { "dependencies": { "@vitest/pretty-format": "4.0.16", "tinyrainbow": "^3.0.3" } }, "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA=="], - "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], - "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], @@ -603,22 +574,12 @@ "baseline-browser-mapping": ["baseline-browser-mapping@2.9.4", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ZCQ9GEWl73BVm8bu5Fts8nt7MHdbt5vY9bP6WGnUh+r3l8M7CgfyTlwsgCbMC66BNxPr6Xoce3j66Ms5YUQTNA=="], - "bcrypt": ["bcrypt@6.0.0", "", { "dependencies": { "node-addon-api": "^8.3.0", "node-gyp-build": "^4.8.4" } }, "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg=="], - "bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="], - "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], - "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], - "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], - - "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], - - "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], - "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], "caniuse-lite": ["caniuse-lite@1.0.30001759", "", {}, "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw=="], @@ -643,16 +604,10 @@ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], - "content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="], - - "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], - "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], - "cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="], - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "css-tree": ["css-tree@3.1.0", "", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="], @@ -703,8 +658,6 @@ "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], - "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], - "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], @@ -715,34 +668,20 @@ "dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="], - "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - - "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], - "electron-to-chromium": ["electron-to-chromium@1.5.266", "", {}, "sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg=="], "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], - "enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], - "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], - - "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], - "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], - "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], - "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], - "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="], @@ -769,18 +708,10 @@ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], - "eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], - "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], - - "express-rate-limit": ["express-rate-limit@8.2.1", "", { "dependencies": { "ip-address": "10.0.1" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g=="], - - "express-session": ["express-session@1.18.2", "", { "dependencies": { "cookie": "0.7.2", "cookie-signature": "1.0.7", "debug": "2.6.9", "depd": "~2.0.0", "on-headers": "~1.1.0", "parseurl": "~1.3.3", "safe-buffer": "5.2.1", "uid-safe": "~2.1.5" } }, "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A=="], - "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-equals": ["fast-equals@5.3.3", "", {}, "sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw=="], @@ -793,38 +724,24 @@ "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], - "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], - "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], - "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], - - "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], - "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], - "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], - "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], "globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="], - "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], - "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], @@ -833,10 +750,6 @@ "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], - - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - "headers-polyfill": ["headers-polyfill@4.0.3", "", {}, "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ=="], "hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], @@ -847,8 +760,6 @@ "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], - "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], - "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], @@ -863,14 +774,8 @@ "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], - "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], - "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], - "ip-address": ["ip-address@10.0.1", "", {}, "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA=="], - - "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], @@ -881,8 +786,6 @@ "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="], - "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], @@ -959,18 +862,8 @@ "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], - "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], - "mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="], - "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], - - "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], - - "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - - "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], - "min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="], "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], @@ -985,26 +878,12 @@ "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], - "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], - - "node-addon-api": ["node-addon-api@8.5.0", "", {}, "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A=="], - - "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], - "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], - "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], - "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], - "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], - - "on-headers": ["on-headers@1.1.0", "", {}, "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A=="], - - "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], - "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], "outvariant": ["outvariant@1.4.3", "", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="], @@ -1017,8 +896,6 @@ "parse5": ["parse5@8.0.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA=="], - "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], - "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], @@ -1043,18 +920,8 @@ "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], - "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - "qs": ["qs@6.14.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ=="], - - "random-bytes": ["random-bytes@1.0.0", "", {}, "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ=="], - - "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], - - "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], - "react": ["react@19.2.1", "", {}, "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw=="], "react-day-picker": ["react-day-picker@9.12.0", "", { "dependencies": { "@date-fns/tz": "^1.4.1", "date-fns": "^4.1.0", "date-fns-jalali": "^4.1.0-0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-t8OvG/Zrciso5CQJu5b1A7yzEmebvST+S3pOVQJWxwjjVngyG/CA2htN/D15dLI4uTEuLLkbZyS4YYt480FAtA=="], @@ -1107,10 +974,6 @@ "rollup": ["rollup@4.53.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.3", "@rollup/rollup-android-arm64": "4.53.3", "@rollup/rollup-darwin-arm64": "4.53.3", "@rollup/rollup-darwin-x64": "4.53.3", "@rollup/rollup-freebsd-arm64": "4.53.3", "@rollup/rollup-freebsd-x64": "4.53.3", "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", "@rollup/rollup-linux-arm-musleabihf": "4.53.3", "@rollup/rollup-linux-arm64-gnu": "4.53.3", "@rollup/rollup-linux-arm64-musl": "4.53.3", "@rollup/rollup-linux-loong64-gnu": "4.53.3", "@rollup/rollup-linux-ppc64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-musl": "4.53.3", "@rollup/rollup-linux-s390x-gnu": "4.53.3", "@rollup/rollup-linux-x64-gnu": "4.53.3", "@rollup/rollup-linux-x64-musl": "4.53.3", "@rollup/rollup-openharmony-arm64": "4.53.3", "@rollup/rollup-win32-arm64-msvc": "4.53.3", "@rollup/rollup-win32-ia32-msvc": "4.53.3", "@rollup/rollup-win32-x64-gnu": "4.53.3", "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA=="], - "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], - - "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], - "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], "saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="], @@ -1119,26 +982,12 @@ "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], - - "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], - "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], - "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], - "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], - - "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], - - "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], - - "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], - "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], @@ -1189,8 +1038,6 @@ "tldts-core": ["tldts-core@7.0.19", "", {}, "sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A=="], - "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], - "tough-cookie": ["tough-cookie@6.0.0", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w=="], "tr46": ["tr46@6.0.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw=="], @@ -1203,18 +1050,12 @@ "type-fest": ["type-fest@5.3.1", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg=="], - "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "typescript-eslint": ["typescript-eslint@8.48.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.48.1", "@typescript-eslint/parser": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1", "@typescript-eslint/utils": "8.48.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-FbOKN1fqNoXp1hIl5KYpObVrp0mCn+CLgn479nmu2IsRMrx2vyv74MmsBLVlhg8qVwNFGbXSp8fh1zp8pEoC2A=="], - "uid-safe": ["uid-safe@2.1.5", "", { "dependencies": { "random-bytes": "~1.0.0" } }, "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA=="], - "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], - "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], - "until-async": ["until-async@3.0.2", "", {}, "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw=="], "update-browserslist-db": ["update-browserslist-db@1.2.2", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA=="], @@ -1227,8 +1068,6 @@ "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], - "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], - "victory-vendor": ["victory-vendor@36.9.2", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ=="], "vite": ["vite@7.2.6", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ=="], @@ -1253,8 +1092,6 @@ "wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], - "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], - "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], "xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="], @@ -1333,8 +1170,6 @@ "@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "body-parser/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], - "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], "d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="], @@ -1347,14 +1182,6 @@ "d3-time-format/d3-time": ["d3-time@2.1.1", "", { "dependencies": { "d3-array": "2" } }, "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ=="], - "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], - - "express/cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], - - "express-session/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], - - "express-session/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], - "loose-envify/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], "make-dir/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], @@ -1365,10 +1192,6 @@ "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], - "raw-body/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], - - "router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], - "victory-vendor/@types/d3-shape": ["@types/d3-shape@3.1.7", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg=="], "@nivo/core/@types/d3-shape/@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="], @@ -1383,8 +1206,6 @@ "d3-time-format/d3-time/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="], - "express-session/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], - "victory-vendor/@types/d3-shape/@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="], "d3-time-format/d3-time/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], From 299d96c01186fd065e5454edb7cb9aee6ab12bb0 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Wed, 21 Jan 2026 17:39:54 -0500 Subject: [PATCH 29/31] fix(api): add type guard for tier_defaults and extract tiers constant - Add defensive type guard in misc-routes.ts to prevent runtime crash if malformed tier_defaults object is passed to PUT /api/thinking - Extract VALID_THINKING_TIERS constant to thinking-validator.ts to eliminate duplication across validation code - Export constant from cliproxy barrel file Addresses P2/P3 items from PR #351 review --- src/cliproxy/index.ts | 1 + src/cliproxy/thinking-validator.ts | 6 ++++++ src/web-server/routes/misc-routes.ts | 13 +++++++++++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/cliproxy/index.ts b/src/cliproxy/index.ts index a89c7bc7..df83c881 100644 --- a/src/cliproxy/index.ts +++ b/src/cliproxy/index.ts @@ -159,6 +159,7 @@ export { validateThinking, THINKING_LEVEL_BUDGETS, VALID_THINKING_LEVELS, + VALID_THINKING_TIERS, THINKING_OFF_VALUES, THINKING_AUTO_VALUE, THINKING_BUDGET_MIN, diff --git a/src/cliproxy/thinking-validator.ts b/src/cliproxy/thinking-validator.ts index e64e1c46..c99e424e 100644 --- a/src/cliproxy/thinking-validator.ts +++ b/src/cliproxy/thinking-validator.ts @@ -57,6 +57,12 @@ export const THINKING_LEVEL_RANK: Record = { export const VALID_THINKING_LEVELS = ['minimal', 'low', 'medium', 'high', 'xhigh', 'auto'] as const; export type ThinkingLevel = (typeof VALID_THINKING_LEVELS)[number]; +/** + * Valid tier names for tier_defaults configuration + */ +export const VALID_THINKING_TIERS = ['opus', 'sonnet', 'haiku'] as const; +export type ThinkingTier = (typeof VALID_THINKING_TIERS)[number]; + /** * Cap a level at the model's maximum supported level. * Returns the capped level and whether capping occurred. diff --git a/src/web-server/routes/misc-routes.ts b/src/web-server/routes/misc-routes.ts index 712b56f7..98f933fe 100644 --- a/src/web-server/routes/misc-routes.ts +++ b/src/web-server/routes/misc-routes.ts @@ -19,6 +19,7 @@ import { THINKING_BUDGET_MIN, THINKING_BUDGET_MAX, VALID_THINKING_LEVELS, + VALID_THINKING_TIERS, THINKING_OFF_VALUES, } from '../../cliproxy'; import { validateFilePath } from './route-helpers'; @@ -326,9 +327,17 @@ router.put('/thinking', (req: Request, res: Response): void => { // Validate tier_defaults if provided if (updates.tier_defaults !== undefined) { + if ( + typeof updates.tier_defaults !== 'object' || + updates.tier_defaults === null || + Array.isArray(updates.tier_defaults) + ) { + res.status(400).json({ error: 'Invalid tier_defaults: must be an object' }); + return; + } const validLevels = [...VALID_THINKING_LEVELS] as string[]; for (const [tier, level] of Object.entries(updates.tier_defaults)) { - if (!['opus', 'sonnet', 'haiku'].includes(tier)) { + if (!(VALID_THINKING_TIERS as readonly string[]).includes(tier)) { res.status(400).json({ error: `Invalid tier: ${tier}` }); return; } @@ -352,7 +361,7 @@ router.put('/thinking', (req: Request, res: Response): void => { return; } const validLevels = [...VALID_THINKING_LEVELS] as string[]; - const validTiers = ['opus', 'sonnet', 'haiku']; + const validTiers = [...VALID_THINKING_TIERS] as string[]; for (const [provider, tierOverrides] of Object.entries(updates.provider_overrides)) { if (typeof provider !== 'string' || provider.trim() === '') { res From ca490a9f4e96dd2da7e6c76b466328ca4aa4dc6c Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Wed, 21 Jan 2026 17:52:16 -0500 Subject: [PATCH 30/31] fix(cliproxy): handle edge cases in thinking validation - model-catalog: add null guard and trim for modelId lookup - thinking-validator: add 100 char length limit, accept "8192.0" format - cliproxy-executor: error on empty --thinking= value - use-thinking-config: handle HTML error pages, return cleanup fn --- src/cliproxy/cliproxy-executor.ts | 7 +++++++ src/cliproxy/model-catalog.ts | 4 ++-- src/cliproxy/thinking-validator.ts | 18 +++++++++++++++++- .../settings/hooks/use-thinking-config.ts | 17 ++++++++++++++++- 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/cliproxy/cliproxy-executor.ts b/src/cliproxy/cliproxy-executor.ts index 942efe82..f63ff81b 100644 --- a/src/cliproxy/cliproxy-executor.ts +++ b/src/cliproxy/cliproxy-executor.ts @@ -337,6 +337,13 @@ export async function execClaudeWithCLIProxy( const thinkingEqArg = argsWithoutProxy.find((arg) => arg.startsWith('--thinking=')); if (thinkingEqArg) { const val = thinkingEqArg.substring('--thinking='.length); + // Handle empty value after equals (--thinking=) + if (!val || val.trim() === '') { + console.error(fail('--thinking requires a value')); + console.error(' Examples: --thinking=low, --thinking=8192, --thinking=off'); + console.error(' Levels: minimal, low, medium, high, xhigh, auto'); + process.exit(1); + } // Parse as number if numeric, otherwise keep as string (level name) const numVal = parseInt(val, 10); thinkingOverride = !isNaN(numVal) ? numVal : val; diff --git a/src/cliproxy/model-catalog.ts b/src/cliproxy/model-catalog.ts index b4c17898..78c86d38 100644 --- a/src/cliproxy/model-catalog.ts +++ b/src/cliproxy/model-catalog.ts @@ -188,8 +188,8 @@ export function getProviderCatalog(provider: CLIProxyProvider): ProviderCatalog */ export function findModel(provider: CLIProxyProvider, modelId: string): ModelEntry | undefined { const catalog = MODEL_CATALOG[provider]; - if (!catalog) return undefined; - const normalizedId = modelId.toLowerCase(); + if (!catalog || !modelId) return undefined; + const normalizedId = modelId.trim().toLowerCase(); return catalog.models.find((m) => m.id.toLowerCase() === normalizedId); } diff --git a/src/cliproxy/thinking-validator.ts b/src/cliproxy/thinking-validator.ts index c99e424e..29a2a7d3 100644 --- a/src/cliproxy/thinking-validator.ts +++ b/src/cliproxy/thinking-validator.ts @@ -139,6 +139,15 @@ export function validateThinking( ): ThinkingValidationResult { const thinking = getModelThinkingSupport(provider, modelId); + // Handle string length limit to prevent performance issues + if (typeof value === 'string' && value.length > 100) { + return { + valid: false, + value: 'off', + warning: 'Thinking value too long (max 100 chars). Using "off".', + }; + } + // Handle empty string explicitly if (typeof value === 'string' && value.trim() === '') { return { @@ -221,7 +230,14 @@ function validateBudgetThinking( } else { // Strict number parsing: reject partial parses like "123abc" const parsed = Number(value); - if (isNaN(parsed) || !Number.isFinite(parsed) || value.trim() !== String(parsed)) { + const trimmed = value.trim(); + // Accept trailing .0 for whole numbers (e.g., "8192.0" → 8192) + const isWholeWithDecimal = Number.isInteger(parsed) && trimmed === `${parsed}.0`; + if ( + isNaN(parsed) || + !Number.isFinite(parsed) || + (trimmed !== String(parsed) && !isWholeWithDecimal) + ) { // Not a valid number, try to find closest level const closest = findClosestLevel(value, Object.keys(THINKING_LEVEL_BUDGETS)); if (closest) { diff --git a/ui/src/pages/settings/hooks/use-thinking-config.ts b/ui/src/pages/settings/hooks/use-thinking-config.ts index 1c686ff6..4decf2f9 100644 --- a/ui/src/pages/settings/hooks/use-thinking-config.ts +++ b/ui/src/pages/settings/hooks/use-thinking-config.ts @@ -37,7 +37,14 @@ export function useThinkingConfig() { setError(null); const res = await fetch('/api/thinking', { signal: controller.signal }); clearTimeout(timeoutId); - if (!res.ok) throw new Error('Failed to load Thinking config'); + if (!res.ok) { + // Handle HTML error pages gracefully + const contentType = res.headers.get('content-type'); + if (contentType?.includes('text/html')) { + throw new Error(`Server error (${res.status})`); + } + throw new Error('Failed to load Thinking config'); + } const data = await res.json(); setConfig(data.config || DEFAULT_THINKING_CONFIG); // W4: Store lastModified for optimistic locking @@ -53,6 +60,9 @@ export function useThinkingConfig() { } finally { setLoading(false); } + + // Return cleanup function for AbortController + return () => controller.abort(); }, []); const saveConfig = useCallback( @@ -84,6 +94,11 @@ export function useThinkingConfig() { clearTimeout(timeoutId); if (!res.ok) { + // Handle HTML error pages gracefully + const contentType = res.headers.get('content-type'); + if (contentType?.includes('text/html')) { + throw new Error(`Server error (${res.status})`); + } const data = await res.json(); // W4: Handle conflict (409) with user-friendly message if (res.status === 409) { From 70142df6a291dea823319c446f6f093b90c2c948 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 22 Jan 2026 15:21:22 +0000 Subject: [PATCH 31/31] chore(release): 7.24.2-dev.3 [skip ci] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 543db489..0fd54e4a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kaitranntt/ccs", - "version": "7.24.2-dev.2", + "version": "7.24.2-dev.3", "description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6", "keywords": [ "cli",