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 (
+
+ );
+ }
+
+ 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) => (
+
+ {tier}
+ setTierDefault(tier, value)}
+ disabled={saving}
+ >
+
+
+
+
+ {THINKING_LEVELS.map((level) => (
+
+ {level.label}
+
+ ))}
+
+
+
+ ))}
+
+
+ )}
+
+ {/* 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 */}
+
+
+
+ Refresh
+
+
+ >
+ );
+}
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}
+
+
+ {/* W1: Retry button on error */}
+
+
+ Retry
+
+
)}
{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",