diff --git a/src/cliproxy/cliproxy-executor.ts b/src/cliproxy/cliproxy-executor.ts index df768b0d..e0c245a8 100644 --- a/src/cliproxy/cliproxy-executor.ts +++ b/src/cliproxy/cliproxy-executor.ts @@ -26,8 +26,8 @@ import { } from './config-generator'; import { isAuthenticated } from './auth-handler'; import { CLIProxyProvider, ExecutorConfig } from './types'; -import { configureProviderModel } from './model-config'; -import { supportsModelConfig } from './model-catalog'; +import { configureProviderModel, getCurrentModel } from './model-config'; +import { supportsModelConfig, isModelBroken, getModelIssueUrl, findModel } from './model-catalog'; /** Default executor configuration */ const DEFAULT_CONFIG: ExecutorConfig = { @@ -170,7 +170,24 @@ export async function execClaudeWithCLIProxy( await configureProviderModel(provider, false); // false = only if not configured } - // 5. Ensure user settings file exists (creates from defaults if not) + // 5. Check for known broken models and warn user + const currentModel = getCurrentModel(provider); + if (currentModel && isModelBroken(provider, currentModel)) { + const modelEntry = findModel(provider, currentModel); + const issueUrl = getModelIssueUrl(provider, currentModel); + console.error(''); + console.error( + `[!] Warning: ${modelEntry?.name || currentModel} has known issues with Claude Code` + ); + console.error(' Tool calls will fail. Use "gemini-3-pro-preview" instead.'); + if (issueUrl) { + console.error(` Tracking: ${issueUrl}`); + } + console.error(` Run "ccs ${provider} --config" to change model.`); + console.error(''); + } + + // 6. Ensure user settings file exists (creates from defaults if not) ensureProviderSettings(provider); // 6. Generate config file diff --git a/src/cliproxy/model-catalog.ts b/src/cliproxy/model-catalog.ts index 5c609330..767fdfaf 100644 --- a/src/cliproxy/model-catalog.ts +++ b/src/cliproxy/model-catalog.ts @@ -19,6 +19,10 @@ export interface ModelEntry { tier?: 'free' | 'paid'; /** Optional description for the model */ description?: string; + /** Model has known issues - show warning when selected */ + broken?: boolean; + /** Issue URL for broken models */ + issueUrl?: string; } /** @@ -46,16 +50,22 @@ export const MODEL_CATALOG: Partial> = id: 'gemini-claude-opus-4-5-thinking', name: 'Claude Opus 4.5 Thinking', description: 'Most capable, extended thinking', + broken: true, + issueUrl: 'https://github.com/router-for-me/CLIProxyAPI/issues/415', }, { id: 'gemini-claude-sonnet-4-5-thinking', name: 'Claude Sonnet 4.5 Thinking', description: 'Balanced with extended thinking', + broken: true, + issueUrl: 'https://github.com/router-for-me/CLIProxyAPI/issues/415', }, { id: 'gemini-claude-sonnet-4-5', name: 'Claude Sonnet 4.5', description: 'Fast and capable', + broken: true, + issueUrl: 'https://github.com/router-for-me/CLIProxyAPI/issues/415', }, { id: 'gemini-3-pro-preview', @@ -107,3 +117,19 @@ export function findModel(provider: CLIProxyProvider, modelId: string): ModelEnt if (!catalog) return undefined; return catalog.models.find((m) => m.id === modelId); } + +/** + * Check if model has known issues + */ +export function isModelBroken(provider: CLIProxyProvider, modelId: string): boolean { + const model = findModel(provider, modelId); + return model?.broken === true; +} + +/** + * Get issue URL for broken model + */ +export function getModelIssueUrl(provider: CLIProxyProvider, modelId: string): string | undefined { + const model = findModel(provider, modelId); + return model?.issueUrl; +} diff --git a/src/cliproxy/model-config.ts b/src/cliproxy/model-config.ts index b5b4311c..0e9c9190 100644 --- a/src/cliproxy/model-config.ts +++ b/src/cliproxy/model-config.ts @@ -45,7 +45,8 @@ export function getCurrentModel(provider: CLIProxyProvider): string | undefined function formatModelOption(model: ModelEntry): string { // Tier badge: clarify that "paid" means paid Google account (not free tier) const tierBadge = model.tier === 'paid' ? color(' [Paid Tier]', 'warning') : ''; - return `${model.name}${tierBadge}`; + const brokenBadge = model.broken ? color(' [BROKEN]', 'error') : ''; + return `${model.name}${tierBadge}${brokenBadge}`; } /** @@ -55,8 +56,9 @@ function formatModelDetailed(model: ModelEntry, isCurrent: boolean): string { const marker = isCurrent ? color('>', 'success') : ' '; const name = isCurrent ? bold(model.name) : model.name; const tierBadge = model.tier === 'paid' ? color(' [Paid Tier]', 'warning') : ''; + const brokenBadge = model.broken ? color(' [BROKEN]', 'error') : ''; const desc = model.description ? dim(` - ${model.description}`) : ''; - return ` ${marker} ${name}${tierBadge}${desc}`; + return ` ${marker} ${name}${tierBadge}${brokenBadge}${desc}`; } /**