mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-11 12:09:03 +00:00
Merge pull request #1677 from kaitranntt/kai/feat/1651-context-window
feat(cliproxy): expose model context windows
This commit is contained in:
10 files changed
+315
-26
No files matched your search
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"scope": "src/**/*.{ts,tsx,js,jsx,mjs,cjs}",
|
||||
"syncFs": {
|
||||
"totalOccurrences": 2379,
|
||||
"filesAffected": 255,
|
||||
"totalOccurrences": 2382,
|
||||
"filesAffected": 256,
|
||||
"hotpathOccurrences": 1103,
|
||||
"hotpathFilesAffected": 151,
|
||||
"topHotpathFiles": [
|
||||
@@ -741,6 +741,10 @@
|
||||
"file": "src/cliproxy/auth/oauth-process.ts",
|
||||
"loc": 1048
|
||||
},
|
||||
{
|
||||
"file": "src/cliproxy/config/env-builder.ts",
|
||||
"loc": 1045
|
||||
},
|
||||
{
|
||||
"file": "src/web-server/routes/settings-routes.ts",
|
||||
"loc": 1042
|
||||
@@ -753,10 +757,6 @@
|
||||
"file": "src/cliproxy/proxy/tool-sanitization-proxy.ts",
|
||||
"loc": 1020
|
||||
},
|
||||
{
|
||||
"file": "src/cliproxy/config/env-builder.ts",
|
||||
"loc": 1017
|
||||
},
|
||||
{
|
||||
"file": "src/commands/cliproxy/variant-subcommand.ts",
|
||||
"loc": 997
|
||||
|
||||
@@ -6,8 +6,8 @@ Scope: `src/**/*.{ts,tsx,js,jsx,mjs,cjs}`
|
||||
|
||||
| Metric | Value |
|
||||
|---|---:|
|
||||
| Sync fs occurrences (all) | 2379 |
|
||||
| Sync fs files affected (all) | 255 |
|
||||
| Sync fs occurrences (all) | 2382 |
|
||||
| Sync fs files affected (all) | 256 |
|
||||
| Sync fs occurrences (runtime hotpaths) | 1103 |
|
||||
| Sync fs files affected (runtime hotpaths) | 151 |
|
||||
| Legacy shim markers | 456 |
|
||||
@@ -95,10 +95,10 @@ Scope: `src/**/*.{ts,tsx,js,jsx,mjs,cjs}`
|
||||
| `src/cursor/cursor-executor.ts` | 1234 |
|
||||
| `src/web-server/model-pricing.ts` | 1127 |
|
||||
| `src/cliproxy/auth/oauth-process.ts` | 1048 |
|
||||
| `src/cliproxy/config/env-builder.ts` | 1045 |
|
||||
| `src/web-server/routes/settings-routes.ts` | 1042 |
|
||||
| `src/cliproxy/config/generator.ts` | 1034 |
|
||||
| `src/cliproxy/proxy/tool-sanitization-proxy.ts` | 1020 |
|
||||
| `src/cliproxy/config/env-builder.ts` | 1017 |
|
||||
| `src/commands/cliproxy/variant-subcommand.ts` | 997 |
|
||||
| `src/cliproxy/quota/quota-manager.ts` | 954 |
|
||||
| `src/web-server/services/codex-dashboard-service.ts` | 940 |
|
||||
|
||||
@@ -75,6 +75,18 @@ describe('model-catalog compatibility lookups', () => {
|
||||
expect(catalog?.models[0]?.extendedContext).toBeUndefined();
|
||||
});
|
||||
|
||||
it('maps positive integer live context length into contextWindow metadata', () => {
|
||||
const catalog = mergeCatalog('xai', [
|
||||
{
|
||||
id: 'grok-4.3',
|
||||
display_name: 'Grok 4.3 Live',
|
||||
context_length: 1_000_000,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(catalog?.models[0]?.contextWindow).toBe(1_000_000);
|
||||
});
|
||||
|
||||
it('does not re-add stale static-only models when live catalog data is present', () => {
|
||||
const catalog = mergeCatalog('gemini', [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
||||
|
||||
import { invalidateConfigCache } from '../../../config/config-loader-facade';
|
||||
import { clearConfigCache } from '../base-config-loader';
|
||||
import { applyClaudeAutoCompactWindow } from '../env-builder';
|
||||
import { buildClaudeEnvironment } from '../../executor/env-resolver';
|
||||
|
||||
describe('applyClaudeAutoCompactWindow', () => {
|
||||
it('injects the catalog window for the effective model after suffix normalization', () => {
|
||||
const env = applyClaudeAutoCompactWindow(
|
||||
{
|
||||
ANTHROPIC_MODEL: 'gpt-5.6-sol-xhigh[1m]',
|
||||
},
|
||||
'codex'
|
||||
);
|
||||
|
||||
expect(env.CLAUDE_CODE_AUTO_COMPACT_WINDOW).toBe('372000');
|
||||
});
|
||||
|
||||
it('uses the selected provider when the same model family exists in multiple catalogs', () => {
|
||||
const env = applyClaudeAutoCompactWindow(
|
||||
{
|
||||
ANTHROPIC_MODEL: 'claude-opus-4-6-thinking',
|
||||
},
|
||||
'agy'
|
||||
);
|
||||
|
||||
expect(env.CLAUDE_CODE_AUTO_COMPACT_WINDOW).toBe('200000');
|
||||
});
|
||||
|
||||
it('does not inject a value for unknown or custom models', () => {
|
||||
const env = applyClaudeAutoCompactWindow(
|
||||
{
|
||||
ANTHROPIC_MODEL: 'custom-model-with-unknown-window',
|
||||
},
|
||||
'codex'
|
||||
);
|
||||
|
||||
expect('CLAUDE_CODE_AUTO_COMPACT_WINDOW' in env).toBe(false);
|
||||
});
|
||||
|
||||
it('preserves an explicit user value', () => {
|
||||
const env = applyClaudeAutoCompactWindow(
|
||||
{
|
||||
ANTHROPIC_MODEL: 'gpt-5.6-sol',
|
||||
CLAUDE_CODE_AUTO_COMPACT_WINDOW: '123456',
|
||||
},
|
||||
'codex'
|
||||
);
|
||||
|
||||
expect(env.CLAUDE_CODE_AUTO_COMPACT_WINDOW).toBe('123456');
|
||||
});
|
||||
|
||||
it('treats an explicit empty value as authoritative', () => {
|
||||
const env = applyClaudeAutoCompactWindow(
|
||||
{
|
||||
ANTHROPIC_MODEL: 'gpt-5.6-sol',
|
||||
CLAUDE_CODE_AUTO_COMPACT_WINDOW: '',
|
||||
},
|
||||
'codex'
|
||||
);
|
||||
|
||||
expect(env.CLAUDE_CODE_AUTO_COMPACT_WINDOW).toBe('');
|
||||
});
|
||||
|
||||
it('does not mistake a differently cased variable for the Claude Code key', () => {
|
||||
const env = applyClaudeAutoCompactWindow(
|
||||
{
|
||||
ANTHROPIC_MODEL: 'gpt-5.6-sol',
|
||||
claude_code_auto_compact_window: '123456',
|
||||
},
|
||||
'codex'
|
||||
);
|
||||
|
||||
expect(env.claude_code_auto_compact_window).toBe('123456');
|
||||
expect(env.CLAUDE_CODE_AUTO_COMPACT_WINDOW).toBe('372000');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Claude launch auto-compact integration', () => {
|
||||
let tempHome: string;
|
||||
let originalCcsHome: string | undefined;
|
||||
let originalAutoCompactWindow: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
originalCcsHome = process.env.CCS_HOME;
|
||||
originalAutoCompactWindow = process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW;
|
||||
tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-auto-compact-'));
|
||||
process.env.CCS_HOME = tempHome;
|
||||
delete process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW;
|
||||
invalidateConfigCache();
|
||||
clearConfigCache();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (originalCcsHome === undefined) delete process.env.CCS_HOME;
|
||||
else process.env.CCS_HOME = originalCcsHome;
|
||||
|
||||
if (originalAutoCompactWindow === undefined) {
|
||||
delete process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW;
|
||||
} else {
|
||||
process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW = originalAutoCompactWindow;
|
||||
}
|
||||
|
||||
invalidateConfigCache();
|
||||
clearConfigCache();
|
||||
fs.rmSync(tempHome, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('injects the catalog window for the provider default model on Claude launches', () => {
|
||||
const env = buildClaudeEnvironment({
|
||||
provider: 'codex',
|
||||
useRemoteProxy: false,
|
||||
localPort: 8317,
|
||||
verbose: false,
|
||||
});
|
||||
|
||||
expect(env.ANTHROPIC_MODEL).toBe('gpt-5.4(high)');
|
||||
expect(env.CLAUDE_CODE_AUTO_COMPACT_WINDOW).toBe('1050000');
|
||||
});
|
||||
|
||||
it('preserves an inherited user value on Claude launches', () => {
|
||||
process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW = '';
|
||||
|
||||
const env = buildClaudeEnvironment({
|
||||
provider: 'codex',
|
||||
useRemoteProxy: false,
|
||||
localPort: 8317,
|
||||
verbose: false,
|
||||
});
|
||||
|
||||
expect(env.CLAUDE_CODE_AUTO_COMPACT_WINDOW).toBe('');
|
||||
});
|
||||
|
||||
it('uses the composite default tier provider for model lookup', () => {
|
||||
const env = buildClaudeEnvironment({
|
||||
provider: 'codex',
|
||||
useRemoteProxy: false,
|
||||
localPort: 8317,
|
||||
verbose: false,
|
||||
isComposite: true,
|
||||
compositeTiers: {
|
||||
opus: { provider: 'agy', model: 'claude-opus-4-6-thinking' },
|
||||
sonnet: { provider: 'codex', model: 'gpt-5.6-sol' },
|
||||
haiku: { provider: 'kimi', model: 'kimi-k2' },
|
||||
},
|
||||
compositeDefaultTier: 'opus',
|
||||
});
|
||||
|
||||
expect(env.CLAUDE_CODE_AUTO_COMPACT_WINDOW).toBe('200000');
|
||||
});
|
||||
|
||||
it('preserves an explicit empty value from a custom settings file', () => {
|
||||
const settingsPath = path.join(tempHome, 'codex-custom.settings.json');
|
||||
fs.writeFileSync(
|
||||
settingsPath,
|
||||
JSON.stringify({
|
||||
env: {
|
||||
ANTHROPIC_BASE_URL: 'http://127.0.0.1:8317',
|
||||
ANTHROPIC_AUTH_TOKEN: 'test-token',
|
||||
ANTHROPIC_MODEL: 'gpt-5.6-sol',
|
||||
CLAUDE_CODE_AUTO_COMPACT_WINDOW: '',
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const env = buildClaudeEnvironment({
|
||||
provider: 'codex',
|
||||
useRemoteProxy: false,
|
||||
localPort: 8317,
|
||||
customSettingsPath: settingsPath,
|
||||
verbose: false,
|
||||
});
|
||||
|
||||
expect(env.CLAUDE_CODE_AUTO_COMPACT_WINDOW).toBe('');
|
||||
});
|
||||
});
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
} from '../../config/config-loader-facade';
|
||||
import { buildCliproxyProviderPath, buildLocalProviderBaseUrl } from './provider-route';
|
||||
import { ConfigError } from '../../errors/error-types';
|
||||
import { findModel } from '../model-catalog';
|
||||
|
||||
export { buildCliproxyProviderPath, buildLocalProviderBaseUrl } from './provider-route';
|
||||
|
||||
@@ -68,11 +69,38 @@ const CURSOR_LEGACY_ENV_OVERRIDE_KEYS = new Set([
|
||||
'ANTHROPIC_AUTH_TOKEN',
|
||||
'ANTHROPIC_API_KEY',
|
||||
]);
|
||||
const CLAUDE_AUTO_COMPACT_WINDOW_ENV = 'CLAUDE_CODE_AUTO_COMPACT_WINDOW';
|
||||
|
||||
function isObjectRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Claude Code's auto-compact window for a known effective model.
|
||||
* Existing env keys win by presence, including an explicit empty value.
|
||||
*/
|
||||
export function applyClaudeAutoCompactWindow(
|
||||
envVars: NodeJS.ProcessEnv,
|
||||
provider: CLIProxyProvider
|
||||
): NodeJS.ProcessEnv {
|
||||
if (Object.prototype.hasOwnProperty.call(envVars, CLAUDE_AUTO_COMPACT_WINDOW_ENV)) {
|
||||
return envVars;
|
||||
}
|
||||
|
||||
const modelId = envVars.ANTHROPIC_MODEL;
|
||||
if (typeof modelId !== 'string' || modelId.trim().length === 0) return envVars;
|
||||
|
||||
const contextWindow = findModel(provider, modelId)?.contextWindow;
|
||||
if (typeof contextWindow !== 'number' || !Number.isInteger(contextWindow) || contextWindow <= 0) {
|
||||
return envVars;
|
||||
}
|
||||
|
||||
return {
|
||||
...envVars,
|
||||
[CLAUDE_AUTO_COMPACT_WINDOW_ENV]: String(contextWindow),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate deprecated gemini-claude-* model names to upstream claude-* names in a settings file.
|
||||
* CLIProxyAPI registry no longer recognizes the gemini-claude-* prefix convention.
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
getRemoteEnvVars,
|
||||
getCompositeEnvVars,
|
||||
applyThinkingConfig,
|
||||
applyClaudeAutoCompactWindow,
|
||||
} from '../config/config-generator';
|
||||
import { applyExtendedContextConfig } from '../config/extended-context-config';
|
||||
import { CLIProxyProvider } from '../types';
|
||||
@@ -414,8 +415,17 @@ export function buildClaudeEnvironment(config: ProxyChainConfig): Record<string,
|
||||
CCS_PROFILE_TYPE: 'cliproxy', // Signal to WebSearch hook this is a third-party provider
|
||||
};
|
||||
|
||||
const effectiveProvider =
|
||||
isComposite && compositeTiers && compositeDefaultTier
|
||||
? compositeTiers[compositeDefaultTier].provider
|
||||
: provider;
|
||||
const autoCompactEnv = applyClaudeAutoCompactWindow(
|
||||
stripClaudeCodeEnv(mergedEnv),
|
||||
effectiveProvider
|
||||
);
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(stripClaudeCodeEnv(mergedEnv)).filter(([, v]) => v !== undefined)
|
||||
Object.entries(autoCompactEnv).filter(([, v]) => v !== undefined)
|
||||
) as Record<string, string>;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,8 @@ export interface ModelEntry {
|
||||
tier?: 'free' | 'pro' | 'ultra';
|
||||
/** Optional description for the model */
|
||||
description?: string;
|
||||
/** Total context window in tokens when authoritatively known */
|
||||
contextWindow?: number;
|
||||
/** Model has known issues - show warning when selected */
|
||||
broken?: boolean;
|
||||
/** Issue URL for broken models */
|
||||
@@ -95,6 +97,7 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'claude-opus-4-6-thinking',
|
||||
name: 'Claude Opus 4.6 Thinking',
|
||||
description: 'Latest flagship, extended thinking',
|
||||
contextWindow: 200000,
|
||||
nativeImageInput: true,
|
||||
thinking: {
|
||||
type: 'budget',
|
||||
@@ -111,6 +114,7 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'claude-sonnet-4-6',
|
||||
name: 'Claude Sonnet 4.6',
|
||||
description: 'Latest Sonnet with thinking budget support',
|
||||
contextWindow: 200000,
|
||||
nativeImageInput: true,
|
||||
thinking: {
|
||||
type: 'budget',
|
||||
@@ -194,6 +198,7 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'gpt-5.6-sol',
|
||||
name: 'GPT-5.6 Sol',
|
||||
description: 'Latest frontier agentic coding model.',
|
||||
contextWindow: 372000,
|
||||
thinking: {
|
||||
type: 'levels',
|
||||
levels: ['low', 'medium', 'high', 'xhigh'],
|
||||
@@ -206,6 +211,7 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'gpt-5.6-terra',
|
||||
name: 'GPT-5.6 Terra',
|
||||
description: 'Balanced agentic coding model for everyday work.',
|
||||
contextWindow: 372000,
|
||||
thinking: {
|
||||
type: 'levels',
|
||||
levels: ['low', 'medium', 'high', 'xhigh'],
|
||||
@@ -218,6 +224,7 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'gpt-5.6-luna',
|
||||
name: 'GPT-5.6 Luna',
|
||||
description: 'Fast and affordable agentic coding model.',
|
||||
contextWindow: 372000,
|
||||
thinking: {
|
||||
type: 'levels',
|
||||
levels: ['low', 'medium', 'high', 'xhigh'],
|
||||
@@ -232,6 +239,7 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
tier: 'pro',
|
||||
description:
|
||||
'Newest Codex-released GPT-5 family model; falls back to GPT-5.4 on free plans',
|
||||
contextWindow: 272000,
|
||||
thinking: {
|
||||
type: 'levels',
|
||||
levels: ['low', 'medium', 'high', 'xhigh'],
|
||||
@@ -244,6 +252,7 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'gpt-5.4',
|
||||
name: 'GPT-5.4',
|
||||
description: 'Recommended Codex default for most coding and agentic tasks',
|
||||
contextWindow: 1050000,
|
||||
thinking: {
|
||||
type: 'levels',
|
||||
levels: ['low', 'medium', 'high', 'xhigh'],
|
||||
@@ -256,6 +265,7 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'gpt-5.4-mini',
|
||||
name: 'GPT-5.4 Mini',
|
||||
description: 'Fast, lower-cost Codex option for lighter tasks and haiku-tier routing',
|
||||
contextWindow: 400000,
|
||||
thinking: {
|
||||
type: 'levels',
|
||||
levels: ['low', 'medium', 'high'],
|
||||
@@ -281,6 +291,7 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
tier: 'pro',
|
||||
description:
|
||||
'Research preview model for ChatGPT Pro subscribers, optimized for near-instant coding iteration',
|
||||
contextWindow: 128000,
|
||||
thinking: {
|
||||
type: 'levels',
|
||||
levels: ['low', 'medium', 'high', 'xhigh'],
|
||||
@@ -310,11 +321,13 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'grok-build-0.1',
|
||||
name: 'Grok Build 0.1',
|
||||
description: 'Fast coding model for agentic software engineering workflows',
|
||||
contextWindow: 256000,
|
||||
},
|
||||
{
|
||||
id: 'grok-4.5',
|
||||
name: 'Grok 4.5',
|
||||
description: 'Frontier model for coding, engineering, and agentic workflows',
|
||||
contextWindow: 500000,
|
||||
thinking: {
|
||||
type: 'levels',
|
||||
levels: ['low', 'medium', 'high'],
|
||||
@@ -325,6 +338,7 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'grok-4.3',
|
||||
name: 'Grok 4.3',
|
||||
description: 'General-purpose Grok model with a one-million-token context window',
|
||||
contextWindow: 1000000,
|
||||
thinking: {
|
||||
type: 'levels',
|
||||
levels: ['none', 'low', 'medium', 'high'],
|
||||
@@ -335,34 +349,40 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'grok-4.20-0309-reasoning',
|
||||
name: 'Grok 4.20 0309 Reasoning',
|
||||
description: 'Reasoning model with a two-million-token context window',
|
||||
contextWindow: 2000000,
|
||||
},
|
||||
{
|
||||
id: 'grok-4.20-0309-non-reasoning',
|
||||
name: 'Grok 4.20 0309 Non Reasoning',
|
||||
description: 'Non-reasoning model with a two-million-token context window',
|
||||
contextWindow: 2000000,
|
||||
},
|
||||
{
|
||||
id: 'grok-4.20-multi-agent-0309',
|
||||
name: 'Grok 4.20 Multi Agent 0309',
|
||||
description: 'Multi-agent model with a two-million-token context window',
|
||||
contextWindow: 2000000,
|
||||
thinking: { type: 'levels', levels: ['low', 'medium', 'high'] },
|
||||
},
|
||||
{
|
||||
id: 'grok-3-mini',
|
||||
name: 'Grok 3 Mini',
|
||||
description: 'Compact reasoning model',
|
||||
contextWindow: 131072,
|
||||
thinking: { type: 'levels', levels: ['low', 'medium', 'high'] },
|
||||
},
|
||||
{
|
||||
id: 'grok-3-mini-fast',
|
||||
name: 'Grok 3 Mini Fast',
|
||||
description: 'Faster compact reasoning model',
|
||||
contextWindow: 131072,
|
||||
thinking: { type: 'levels', levels: ['low', 'medium', 'high'] },
|
||||
},
|
||||
{
|
||||
id: 'grok-composer-2.5-fast',
|
||||
name: 'Grok Composer 2.5 Fast',
|
||||
description: 'Fast Composer model for lightweight coding tasks',
|
||||
contextWindow: 200000,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -375,56 +395,67 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'qoder/auto',
|
||||
name: 'Qoder Auto',
|
||||
description: 'Auto selects the best Qoder model for your prompt',
|
||||
contextWindow: 180000,
|
||||
},
|
||||
{
|
||||
id: 'qoder/ultimate',
|
||||
name: 'Qoder Ultimate',
|
||||
description: 'Highest quality Qoder tier',
|
||||
contextWindow: 180000,
|
||||
},
|
||||
{
|
||||
id: 'qoder/performance',
|
||||
name: 'Qoder Performance',
|
||||
description: 'Balanced quality and speed',
|
||||
contextWindow: 272000,
|
||||
},
|
||||
{
|
||||
id: 'qoder/efficient',
|
||||
name: 'Qoder Efficient',
|
||||
description: 'Cost-efficient Qoder tier',
|
||||
contextWindow: 180000,
|
||||
},
|
||||
{
|
||||
id: 'qoder/lite',
|
||||
name: 'Qoder Lite',
|
||||
description: 'Fastest and most affordable Qoder tier',
|
||||
contextWindow: 180000,
|
||||
},
|
||||
{
|
||||
id: 'qoder/qmodel',
|
||||
name: 'Qwen 3.6 Plus (via Qoder)',
|
||||
description: 'Qwen 3.6 Plus frontier model',
|
||||
contextWindow: 180000,
|
||||
},
|
||||
{
|
||||
id: 'qoder/dmodel',
|
||||
name: 'DeepSeek V4 Pro (via Qoder)',
|
||||
description: 'DeepSeek V4 Pro frontier model',
|
||||
contextWindow: 180000,
|
||||
},
|
||||
{
|
||||
id: 'qoder/dfmodel',
|
||||
name: 'DeepSeek V4 Flash (via Qoder)',
|
||||
description: 'DeepSeek V4 Flash frontier model',
|
||||
contextWindow: 180000,
|
||||
},
|
||||
{
|
||||
id: 'qoder/gm51model',
|
||||
name: 'GLM 5.1 (via Qoder)',
|
||||
description: 'GLM 5.1 frontier model',
|
||||
contextWindow: 180000,
|
||||
},
|
||||
{
|
||||
id: 'qoder/kmodel',
|
||||
name: 'Kimi K2.6 (via Qoder)',
|
||||
description: 'Kimi K2.6 frontier model',
|
||||
contextWindow: 256000,
|
||||
},
|
||||
{
|
||||
id: 'qoder/mmodel',
|
||||
name: 'MiniMax M2.7 (via Qoder)',
|
||||
description: 'MiniMax M2.7 frontier model',
|
||||
contextWindow: 180000,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -437,6 +468,7 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'kimi-k2.5',
|
||||
name: 'Kimi K2.5',
|
||||
description: 'Latest multimodal model (262K context)',
|
||||
contextWindow: 262144,
|
||||
nativeImageInput: true,
|
||||
thinking: {
|
||||
type: 'budget',
|
||||
@@ -450,6 +482,7 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'kimi-k2-thinking',
|
||||
name: 'Kimi K2 Thinking',
|
||||
description: 'Extended reasoning model',
|
||||
contextWindow: 131072,
|
||||
thinking: {
|
||||
type: 'budget',
|
||||
min: 1024,
|
||||
@@ -462,6 +495,7 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'kimi-k2',
|
||||
name: 'Kimi K2',
|
||||
description: 'Flagship coding model',
|
||||
contextWindow: 131072,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -474,6 +508,7 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'claude-sonnet-5',
|
||||
name: 'Claude Sonnet 5',
|
||||
description: 'Latest Sonnet model',
|
||||
contextWindow: 1000000,
|
||||
nativeImageInput: true,
|
||||
// Sonnet 5 uses adaptive thinking; manual budget_tokens is rejected with 400.
|
||||
thinking: {
|
||||
@@ -488,6 +523,7 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'claude-fable-5',
|
||||
name: 'Claude Fable 5',
|
||||
description: 'Most powerful model',
|
||||
contextWindow: 1000000,
|
||||
nativeImageInput: true,
|
||||
// New tier above Opus. Same adaptive-thinking surface as Opus 4.8:
|
||||
// Anthropic accepts only effort levels; manual budget_tokens is rejected with 400.
|
||||
@@ -520,6 +556,7 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'claude-opus-4-8',
|
||||
name: 'Claude Opus 4.8',
|
||||
description: 'Latest flagship model',
|
||||
contextWindow: 1000000,
|
||||
nativeImageInput: true,
|
||||
// Mirrors 4.7: Anthropic accepts only adaptive thinking levels on the
|
||||
// current Opus generation; manual budget_tokens is rejected with 400.
|
||||
@@ -535,6 +572,7 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'claude-opus-4-7',
|
||||
name: 'Claude Opus 4.7',
|
||||
description: 'Previous flagship model',
|
||||
contextWindow: 1000000,
|
||||
nativeImageInput: true,
|
||||
// Opus 4.7 only supports adaptive thinking on the Anthropic API; manual
|
||||
// thinking.type: "enabled" with budget_tokens is rejected with 400.
|
||||
@@ -552,6 +590,7 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'claude-opus-4-6',
|
||||
name: 'Claude Opus 4.6',
|
||||
description: 'Older flagship model',
|
||||
contextWindow: 1000000,
|
||||
nativeImageInput: true,
|
||||
thinking: {
|
||||
type: 'budget',
|
||||
@@ -566,6 +605,7 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'claude-sonnet-4-6',
|
||||
name: 'Claude Sonnet 4.6',
|
||||
description: 'Balanced performance and speed',
|
||||
contextWindow: 200000,
|
||||
nativeImageInput: true,
|
||||
thinking: {
|
||||
type: 'budget',
|
||||
@@ -580,6 +620,7 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'claude-opus-4-5-20251101',
|
||||
name: 'Claude Opus 4.5',
|
||||
description: 'Most capable Claude model',
|
||||
contextWindow: 200000,
|
||||
nativeImageInput: true,
|
||||
thinking: {
|
||||
type: 'budget',
|
||||
@@ -594,6 +635,7 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'claude-sonnet-4-5-20250929',
|
||||
name: 'Claude Sonnet 4.5',
|
||||
description: 'Balanced performance and speed',
|
||||
contextWindow: 200000,
|
||||
nativeImageInput: true,
|
||||
thinking: {
|
||||
type: 'budget',
|
||||
@@ -608,6 +650,7 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'claude-sonnet-4-20250514',
|
||||
name: 'Claude Sonnet 4',
|
||||
description: 'Previous generation Sonnet',
|
||||
contextWindow: 200000,
|
||||
nativeImageInput: true,
|
||||
thinking: {
|
||||
type: 'budget',
|
||||
@@ -622,6 +665,7 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
id: 'claude-haiku-4-5-20251001',
|
||||
name: 'Claude Haiku 4.5',
|
||||
description: 'Fast and efficient',
|
||||
contextWindow: 200000,
|
||||
nativeImageInput: true,
|
||||
thinking: { type: 'none' },
|
||||
},
|
||||
@@ -675,6 +719,16 @@ export function findModel(provider: CLIProxyProvider, modelId: string): ModelEnt
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const lookupCandidates = new Set([normalizedId, providerNormalizedId]);
|
||||
if (provider === 'codex') {
|
||||
for (const candidate of [...lookupCandidates]) {
|
||||
const tuningMatch = candidate.match(
|
||||
/^(.*?)(?:-(?:minimal|low|medium|high|xhigh)(?:-fast)?|-fast(?:-(?:minimal|low|medium|high|xhigh))?)$/i
|
||||
);
|
||||
if (tuningMatch?.[1]) {
|
||||
lookupCandidates.add(tuningMatch[1].trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isAntigravityProvider(provider)) {
|
||||
const migratedRaw = migrateDeniedAntigravityModelAliases(normalizedId).trim().toLowerCase();
|
||||
const migratedProvider = migrateDeniedAntigravityModelAliases(providerNormalizedId)
|
||||
|
||||
@@ -221,6 +221,13 @@ function mapRemoteToModelEntry(provider: CLIProxyProvider, remote: RemoteModelIn
|
||||
name: remote.display_name || remote.id,
|
||||
};
|
||||
if (remote.description) entry.description = remote.description;
|
||||
if (
|
||||
typeof remote.context_length === 'number' &&
|
||||
Number.isInteger(remote.context_length) &&
|
||||
remote.context_length > 0
|
||||
) {
|
||||
entry.contextWindow = remote.context_length;
|
||||
}
|
||||
// xAI context length is inherent to the model ID; its API does not accept
|
||||
// Claude's [1m] model suffix.
|
||||
if (provider !== 'xai' && remote.context_length && remote.context_length >= 1_000_000) {
|
||||
@@ -286,6 +293,7 @@ export function mergeCatalog(
|
||||
issueUrl: staticEntry.issueUrl,
|
||||
deprecated: staticEntry.deprecated,
|
||||
deprecationReason: staticEntry.deprecationReason,
|
||||
contextWindow: remoteEntry.contextWindow ?? staticEntry.contextWindow,
|
||||
});
|
||||
} else {
|
||||
mergedModels.push(remoteEntry);
|
||||
|
||||
@@ -181,6 +181,7 @@ interface CatalogJsonModel {
|
||||
name: string;
|
||||
tier?: 'free' | 'pro' | 'ultra';
|
||||
description?: string;
|
||||
contextWindow?: number;
|
||||
deprecated?: boolean;
|
||||
deprecationReason?: string;
|
||||
broken?: boolean;
|
||||
@@ -206,6 +207,7 @@ export function handleCatalogJson(): void {
|
||||
const entry: CatalogJsonModel = { id: m.id, name: m.name };
|
||||
if (m.tier !== undefined) entry.tier = m.tier;
|
||||
if (m.description !== undefined) entry.description = m.description;
|
||||
if (m.contextWindow !== undefined) entry.contextWindow = m.contextWindow;
|
||||
if (m.deprecated !== undefined) entry.deprecated = m.deprecated;
|
||||
if (m.deprecationReason !== undefined) entry.deprecationReason = m.deprecationReason;
|
||||
if (m.broken !== undefined) entry.broken = m.broken;
|
||||
|
||||
@@ -42,10 +42,7 @@ describe('cliproxy catalog --json output', () => {
|
||||
it('includes metadata fields when present on model entries', () => {
|
||||
handleCatalogJson();
|
||||
|
||||
const parsed = JSON.parse(capturedOutput[0]) as Record<
|
||||
string,
|
||||
Array<Record<string, unknown>>
|
||||
>;
|
||||
const parsed = JSON.parse(capturedOutput[0]) as Record<string, Array<Record<string, unknown>>>;
|
||||
const allModels = Object.values(parsed).flat();
|
||||
|
||||
// At least some models in the static catalog have tier set
|
||||
@@ -56,15 +53,19 @@ describe('cliproxy catalog --json output', () => {
|
||||
for (const model of withTier) {
|
||||
expect(['free', 'pro', 'ultra']).toContain(model.tier);
|
||||
}
|
||||
|
||||
const withContextWindow = allModels.filter((m) => m.contextWindow !== undefined);
|
||||
expect(withContextWindow.length).toBeGreaterThan(0);
|
||||
for (const model of withContextWindow) {
|
||||
expect(Number.isInteger(model.contextWindow)).toBe(true);
|
||||
expect(model.contextWindow as number).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('omits undefined optional fields instead of including nulls', () => {
|
||||
handleCatalogJson();
|
||||
|
||||
const parsed = JSON.parse(capturedOutput[0]) as Record<
|
||||
string,
|
||||
Array<Record<string, unknown>>
|
||||
>;
|
||||
const parsed = JSON.parse(capturedOutput[0]) as Record<string, Array<Record<string, unknown>>>;
|
||||
const allModels = Object.values(parsed).flat();
|
||||
|
||||
for (const model of allModels) {
|
||||
@@ -78,10 +79,7 @@ describe('cliproxy catalog --json output', () => {
|
||||
it('includes explicit false boolean values in output', () => {
|
||||
handleCatalogJson();
|
||||
|
||||
const parsed = JSON.parse(capturedOutput[0]) as Record<
|
||||
string,
|
||||
Array<Record<string, unknown>>
|
||||
>;
|
||||
const parsed = JSON.parse(capturedOutput[0]) as Record<string, Array<Record<string, unknown>>>;
|
||||
const allModels = Object.values(parsed).flat();
|
||||
|
||||
// Static catalog has models with extendedContext: false
|
||||
@@ -92,10 +90,7 @@ describe('cliproxy catalog --json output', () => {
|
||||
it('includes thinking configuration when present on models', () => {
|
||||
handleCatalogJson();
|
||||
|
||||
const parsed = JSON.parse(capturedOutput[0]) as Record<
|
||||
string,
|
||||
Array<Record<string, unknown>>
|
||||
>;
|
||||
const parsed = JSON.parse(capturedOutput[0]) as Record<string, Array<Record<string, unknown>>>;
|
||||
const allModels = Object.values(parsed).flat();
|
||||
|
||||
// Static catalog has thinking models (e.g. Claude Opus 4.6 Thinking)
|
||||
|
||||
Reference in new issue
Block a user