mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 14:16:43 +00:00
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
This commit is contained in:
@@ -337,6 +337,13 @@ export async function execClaudeWithCLIProxy(
|
|||||||
const thinkingEqArg = argsWithoutProxy.find((arg) => arg.startsWith('--thinking='));
|
const thinkingEqArg = argsWithoutProxy.find((arg) => arg.startsWith('--thinking='));
|
||||||
if (thinkingEqArg) {
|
if (thinkingEqArg) {
|
||||||
const val = thinkingEqArg.substring('--thinking='.length);
|
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)
|
// Parse as number if numeric, otherwise keep as string (level name)
|
||||||
const numVal = parseInt(val, 10);
|
const numVal = parseInt(val, 10);
|
||||||
thinkingOverride = !isNaN(numVal) ? numVal : val;
|
thinkingOverride = !isNaN(numVal) ? numVal : val;
|
||||||
|
|||||||
@@ -188,8 +188,8 @@ export function getProviderCatalog(provider: CLIProxyProvider): ProviderCatalog
|
|||||||
*/
|
*/
|
||||||
export function findModel(provider: CLIProxyProvider, modelId: string): ModelEntry | undefined {
|
export function findModel(provider: CLIProxyProvider, modelId: string): ModelEntry | undefined {
|
||||||
const catalog = MODEL_CATALOG[provider];
|
const catalog = MODEL_CATALOG[provider];
|
||||||
if (!catalog) return undefined;
|
if (!catalog || !modelId) return undefined;
|
||||||
const normalizedId = modelId.toLowerCase();
|
const normalizedId = modelId.trim().toLowerCase();
|
||||||
return catalog.models.find((m) => m.id.toLowerCase() === normalizedId);
|
return catalog.models.find((m) => m.id.toLowerCase() === normalizedId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -139,6 +139,15 @@ export function validateThinking(
|
|||||||
): ThinkingValidationResult {
|
): ThinkingValidationResult {
|
||||||
const thinking = getModelThinkingSupport(provider, modelId);
|
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
|
// Handle empty string explicitly
|
||||||
if (typeof value === 'string' && value.trim() === '') {
|
if (typeof value === 'string' && value.trim() === '') {
|
||||||
return {
|
return {
|
||||||
@@ -221,7 +230,14 @@ function validateBudgetThinking(
|
|||||||
} else {
|
} else {
|
||||||
// Strict number parsing: reject partial parses like "123abc"
|
// Strict number parsing: reject partial parses like "123abc"
|
||||||
const parsed = Number(value);
|
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
|
// Not a valid number, try to find closest level
|
||||||
const closest = findClosestLevel(value, Object.keys(THINKING_LEVEL_BUDGETS));
|
const closest = findClosestLevel(value, Object.keys(THINKING_LEVEL_BUDGETS));
|
||||||
if (closest) {
|
if (closest) {
|
||||||
|
|||||||
@@ -37,7 +37,14 @@ export function useThinkingConfig() {
|
|||||||
setError(null);
|
setError(null);
|
||||||
const res = await fetch('/api/thinking', { signal: controller.signal });
|
const res = await fetch('/api/thinking', { signal: controller.signal });
|
||||||
clearTimeout(timeoutId);
|
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();
|
const data = await res.json();
|
||||||
setConfig(data.config || DEFAULT_THINKING_CONFIG);
|
setConfig(data.config || DEFAULT_THINKING_CONFIG);
|
||||||
// W4: Store lastModified for optimistic locking
|
// W4: Store lastModified for optimistic locking
|
||||||
@@ -53,6 +60,9 @@ export function useThinkingConfig() {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return cleanup function for AbortController
|
||||||
|
return () => controller.abort();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const saveConfig = useCallback(
|
const saveConfig = useCallback(
|
||||||
@@ -84,6 +94,11 @@ export function useThinkingConfig() {
|
|||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
if (!res.ok) {
|
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();
|
const data = await res.json();
|
||||||
// W4: Handle conflict (409) with user-friendly message
|
// W4: Handle conflict (409) with user-friendly message
|
||||||
if (res.status === 409) {
|
if (res.status === 409) {
|
||||||
|
|||||||
Reference in New Issue
Block a user