fix(cliproxy): scope Opus 4.7 rollout to verified providers

This commit is contained in:
Tam Nhu Tran
2026-04-20 13:26:53 -04:00
parent de0f8b3264
commit 571538dc1c
11 changed files with 29 additions and 113 deletions
-30
View File
@@ -1,33 +1,3 @@
## [7.73.0](https://github.com/kaitranntt/ccs/compare/v7.72.1...v7.73.0) (2026-04-19)
### Features
* **proxy:** enhance Anthropic-to-OpenAI message transformation and schema sanitization ([2672e35](https://github.com/kaitranntt/ccs/commit/2672e35362938b054710de681760bd24c3c39e19))
### Bug Fixes
* **browser:** bootstrap managed attach profile setup ([b525033](https://github.com/kaitranntt/ccs/commit/b52503300b0c434fbb9d88283643d3ceaa673d44))
* **cliproxy:** preserve legacy openai-compat connectors on restart ([16f81fc](https://github.com/kaitranntt/ccs/commit/16f81fc8a595792f1c1a6df060f88cdf28d7afb6))
* **codex:** probe Windows cmd wrappers via cmd shell ([210ec33](https://github.com/kaitranntt/ccs/commit/210ec33c04fe88be2b651c2163f638085b1b6bd2))
* **proxy:** harden Anthropic request transformation semantics ([ebc9219](https://github.com/kaitranntt/ccs/commit/ebc92194bb9bf2810e10f64de1fa6e599c2fa156))
* **proxy:** preserve valid enums during schema normalization ([22ab58b](https://github.com/kaitranntt/ccs/commit/22ab58b02e5dc34f152ecce048c1fbf7d615bdfb))
* **proxy:** restore strict Anthropic message validation ([32d6bfd](https://github.com/kaitranntt/ccs/commit/32d6bfdda7881a42a376d0973c021f0b47eed7e2))
* **repo:** remove tracked plans workspace files ([bc4ab98](https://github.com/kaitranntt/ccs/commit/bc4ab98af41e9c9f049cc5da2e6ff356832ef14b))
* **targets:** use cmd.exe for escaped Windows wrapper launches ([a945b0b](https://github.com/kaitranntt/ccs/commit/a945b0b1047b85a7b343dfe35d8ae9680346e25f))
* **windows:** align escaped wrapper shell handling ([ccdd0b6](https://github.com/kaitranntt/ccs/commit/ccdd0b6e8e4185ec2b01186cfc69e28f171d0906))
### Tests
* **cliproxy:** assert routing persistence via unified config loader ([485fe4b](https://github.com/kaitranntt/ccs/commit/485fe4ba1c5878083c6e2b6a2d98b37208772a1f))
* **cliproxy:** cover legacy connector removal cleanup ([00d902b](https://github.com/kaitranntt/ccs/commit/00d902b6e64839cd6b89446cc5dee540f2a059b8))
* **cliproxy:** isolate routing strategy service state ([5e1e807](https://github.com/kaitranntt/ccs/commit/5e1e8070e821dbe660ea77bc96fec610937ed44a))
* **websearch:** clear CCS_PROFILE_TYPE in SearXNG fallback spawn env ([e25d791](https://github.com/kaitranntt/ccs/commit/e25d791d9bee38e7143757d37c42bd850e471ae8))
### CI
* add concurrency group and split build/test with artifact sharing ([ad54e17](https://github.com/kaitranntt/ccs/commit/ad54e179b6a3921fd26ad3ac585f5ef965b01c1b))
* **pre-push:** skip gate for delete-only pushes ([6236cb7](https://github.com/kaitranntt/ccs/commit/6236cb7feb15e954f7f65ae98e6fd98b323a4adb))
## [7.72.1](https://github.com/kaitranntt/ccs/compare/v7.72.0...v7.72.1) (2026-04-17)
### Bug Fixes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@kaitranntt/ccs",
"version": "7.73.0",
"version": "7.72.1-dev.7",
"description": "Claude Code Switch - Instant profile switching between Claude, GLM, Kimi, and more",
"keywords": [
"cli",
+1 -1
View File
@@ -26,7 +26,7 @@ interface PRContext {
// Config
const MAX_DIFF_LINES = 10000;
const CLIPROXY_URL = process.env.CLIPROXY_URL || 'http://localhost:8317';
const MODEL = process.env.REVIEW_MODEL || 'claude-opus-4-7-thinking';
const MODEL = process.env.REVIEW_MODEL || 'claude-opus-4-6-thinking';
// System prompt for code review - new style
const CODE_REVIEWER_SYSTEM_PROMPT = `You are the CCS AGY Code Reviewer, an expert AI assistant reviewing pull requests for the CCS CLI project.
+1 -5
View File
@@ -40,10 +40,8 @@ export const CCS_CONTROL_PANEL_SECRET = 'ccs';
* v15: Prune stale generated Antigravity Gemini preview aliases during regeneration
* v16: Narrow stale Gemini alias cleanup to broad multi-version guessed ranges
* v17: Persist routing.strategy from CCS unified config
* v18: Added Claude Opus 4.7 Thinking alias and promoted to default AGY model
* v19: Added Claude Opus 4.7 non-thinking alias for symmetry with Sonnet
*/
export const CLIPROXY_CONFIG_VERSION = 19;
export const CLIPROXY_CONFIG_VERSION = 17;
interface RegenerateConfigOptions {
configPath?: string;
@@ -88,8 +86,6 @@ const DEFAULT_ANTIGRAVITY_ALIASES: OAuthModelAliasEntry[] = [
// Backward compatibility: legacy sonnet thinking alias now routes to canonical model ID.
{ name: 'claude-sonnet-4-6-thinking', alias: 'claude-sonnet-4-6', fork: true },
{ name: 'claude-opus-4-6-thinking', alias: 'claude-opus-4-6-thinking', fork: true },
{ name: 'claude-opus-4-7-thinking', alias: 'claude-opus-4-7-thinking', fork: true },
{ name: 'claude-opus-4-7', alias: 'claude-opus-4-7', fork: true },
];
const BUILT_IN_GEMINI_ALIAS_NAMES = new Set(
+2 -32
View File
@@ -87,42 +87,12 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
agy: {
provider: 'agy',
displayName: 'Antigravity',
defaultModel: 'claude-opus-4-7-thinking',
defaultModel: 'claude-opus-4-6-thinking',
models: [
{
id: 'claude-opus-4-7-thinking',
name: 'Claude Opus 4.7 Thinking',
description: 'Latest flagship, extended thinking',
nativeImageInput: true,
thinking: {
type: 'budget',
min: 1024,
max: 128000,
zeroAllowed: true,
dynamicAllowed: true,
},
// TODO: Re-enable when Antigravity backend supports 1M context (currently 256k)
// extendedContext: true,
extendedContext: false,
},
{
id: 'claude-opus-4-7',
name: 'Claude Opus 4.7',
description: 'Latest flagship model',
nativeImageInput: true,
thinking: {
type: 'budget',
min: 1024,
max: 128000,
zeroAllowed: true,
dynamicAllowed: true,
},
extendedContext: false,
},
{
id: 'claude-opus-4-6-thinking',
name: 'Claude Opus 4.6 Thinking',
description: 'Previous flagship, extended thinking',
description: 'Latest flagship, extended thinking',
nativeImageInput: true,
thinking: {
type: 'budget',
+1 -1
View File
@@ -10,7 +10,7 @@ export const DEFAULT_CURSOR_MODEL = 'gpt-5.3-codex';
/**
* Default models available through Cursor IDE.
* Used as fallback when daemon is not reachable.
* Source: Cursor docs model catalog (Feb 2026)
* Source: Cursor docs / official announcements (Apr 2026)
*/
export const DEFAULT_CURSOR_MODELS: CursorModel[] = [
// Anthropic Models
+1 -1
View File
@@ -54,7 +54,7 @@ export class DroidAdapter implements TargetAdapter {
model: creds.model,
});
const modelRef = await upsertCcsModel(creds.profile, {
model: creds.model || 'claude-opus-4-7',
model: creds.model || 'claude-opus-4-6',
displayName: `CCS ${creds.profile}`,
baseUrl: creds.baseUrl,
apiKey: creds.apiKey,
-6
View File
@@ -235,12 +235,6 @@ const PRICING_REGISTRY: Record<string, ModelPricing> = {
cacheCreationPerMillion: 6.25,
cacheReadPerMillion: 0.5,
},
'claude-opus-4-7-thinking': {
inputPerMillion: 5.0,
outputPerMillion: 25.0,
cacheCreationPerMillion: 6.25,
cacheReadPerMillion: 0.5,
},
// ---------------------------------------------------------------------------
// OpenAI Models - Source: better-ccusage
+14 -30
View File
@@ -66,21 +66,7 @@ describe('Model Catalog', () => {
describe('AGY models', () => {
it('has correct default model', () => {
const { MODEL_CATALOG } = modelCatalog;
assert.strictEqual(MODEL_CATALOG.agy.defaultModel, 'claude-opus-4-7-thinking');
});
it('includes Claude Opus 4.7 Thinking', () => {
const { MODEL_CATALOG } = modelCatalog;
const opus = MODEL_CATALOG.agy.models.find((m) => m.id === 'claude-opus-4-7-thinking');
assert(opus, 'Should include Claude Opus 4.7 Thinking');
assert.strictEqual(opus.name, 'Claude Opus 4.7 Thinking');
});
it('includes Claude Opus 4.7 (non-thinking)', () => {
const { MODEL_CATALOG } = modelCatalog;
const opus = MODEL_CATALOG.agy.models.find((m) => m.id === 'claude-opus-4-7');
assert(opus, 'Should include Claude Opus 4.7');
assert.strictEqual(opus.name, 'Claude Opus 4.7');
assert.strictEqual(MODEL_CATALOG.agy.defaultModel, 'claude-opus-4-6-thinking');
});
it('includes Claude Opus 4.6 Thinking', () => {
@@ -131,9 +117,9 @@ describe('Model Catalog', () => {
assert.strictEqual(flash.tier, undefined, 'AGY models should not have paid tier');
});
it('has 7 models total', () => {
it('has 5 models total', () => {
const { MODEL_CATALOG } = modelCatalog;
assert.strictEqual(MODEL_CATALOG.agy.models.length, 7);
assert.strictEqual(MODEL_CATALOG.agy.models.length, 5);
});
});
@@ -291,11 +277,11 @@ describe('Model Catalog', () => {
});
describe('findModel', () => {
it('finds Claude Opus 4.7 Thinking in agy', () => {
it('finds Claude Opus 4.7 in claude', () => {
const { findModel } = modelCatalog;
const model = findModel('agy', 'claude-opus-4-7-thinking');
const model = findModel('claude', 'claude-opus-4-7');
assert(model, 'Should find model');
assert.strictEqual(model.name, 'Claude Opus 4.7 Thinking');
assert.strictEqual(model.name, 'Claude Opus 4.7');
});
it('finds Claude Opus 4.6 Thinking in agy', () => {
@@ -342,10 +328,10 @@ describe('Model Catalog', () => {
it('falls back to the next supported model when the default is excluded', () => {
const { getSuggestedReplacementModel } = modelCatalog;
expect(getSuggestedReplacementModel('agy', 'claude-opus-4-7-thinking')).toBe(
'claude-opus-4-7'
expect(getSuggestedReplacementModel('agy', 'claude-opus-4-6-thinking')).toBe(
'claude-sonnet-4-6'
);
expect(getSuggestedReplacementModel('agy')).toBe('claude-opus-4-7-thinking');
expect(getSuggestedReplacementModel('agy')).toBe('claude-opus-4-6-thinking');
});
it('returns undefined when no provider catalog exists', () => {
@@ -406,10 +392,10 @@ describe('Model Catalog', () => {
});
describe('Thinking models ordering', () => {
it('Claude Opus 4.7 Thinking is not deprecated', () => {
it('Claude Opus 4.6 Thinking is not deprecated', () => {
const { MODEL_CATALOG } = modelCatalog;
const opus = MODEL_CATALOG.agy.models.find((m) => m.id === 'claude-opus-4-7-thinking');
assert(opus, 'Should include Claude Opus 4.7 Thinking');
const opus = MODEL_CATALOG.agy.models.find((m) => m.id === 'claude-opus-4-6-thinking');
assert(opus, 'Should include Claude Opus 4.6 Thinking');
assert.strictEqual(opus.deprecated, undefined, 'Should not be marked as deprecated');
});
@@ -429,14 +415,14 @@ describe('Model Catalog', () => {
const models = MODEL_CATALOG.agy.models;
// Find indices of thinking models
const opus47Idx = models.findIndex((m) => m.id === 'claude-opus-4-7-thinking');
const opusIdx = models.findIndex((m) => m.id === 'claude-opus-4-6-thinking');
const sonnetThinkingIdx = models.findIndex((m) => m.id === 'claude-sonnet-4-6');
// Find indices of the remaining non-Claude model
const geminiIdx = models.findIndex((m) => m.id === 'gemini-3.1-pro-high');
// Primary Claude choices should appear ahead of Gemini fallback.
assert(opus47Idx < geminiIdx, 'Opus 4.7 should be above Gemini');
assert(opusIdx < geminiIdx, 'Opus should be above Gemini');
assert(sonnetThinkingIdx < geminiIdx, 'Sonnet should be above Gemini');
});
});
@@ -444,7 +430,6 @@ describe('Model Catalog', () => {
describe('isModelDeprecated', () => {
it('returns false for active AGY Claude models', () => {
const { isModelDeprecated } = modelCatalog;
assert.strictEqual(isModelDeprecated('agy', 'claude-opus-4-7-thinking'), false);
assert.strictEqual(isModelDeprecated('agy', 'claude-opus-4-6-thinking'), false);
assert.strictEqual(isModelDeprecated('agy', 'claude-sonnet-4-6'), false);
});
@@ -463,7 +448,6 @@ describe('Model Catalog', () => {
describe('getModelDeprecationReason', () => {
it('returns undefined for active AGY Claude models', () => {
const { getModelDeprecationReason } = modelCatalog;
assert.strictEqual(getModelDeprecationReason('agy', 'claude-opus-4-7-thinking'), undefined);
assert.strictEqual(getModelDeprecationReason('agy', 'claude-opus-4-6-thinking'), undefined);
assert.strictEqual(getModelDeprecationReason('agy', 'claude-sonnet-4-6'), undefined);
});
+8
View File
@@ -26,6 +26,13 @@ describe('DEFAULT_CURSOR_MODELS', () => {
expect(providers.has('google')).toBe(true);
});
it('keeps both Claude 4.6 Opus and Claude 4.7 Opus in the fallback catalog', () => {
const ids = DEFAULT_CURSOR_MODELS.map((model) => model.id);
expect(ids).toContain('claude-4.6-opus');
expect(ids).toContain('claude-4.7-opus');
expect(ids).toContain('claude-4.7-opus-fast-mode');
});
it('has exactly one default model', () => {
const defaults = DEFAULT_CURSOR_MODELS.filter((m) => m.isDefault);
expect(defaults).toHaveLength(1);
@@ -88,6 +95,7 @@ describe('detectProvider', () => {
it('detects anthropic models', () => {
expect(detectProvider('claude-4.5-sonnet')).toBe('anthropic');
expect(detectProvider('claude-4.6-opus')).toBe('anthropic');
expect(detectProvider('claude-4.7-opus')).toBe('anthropic');
});
it('detects openai models', () => {
-6
View File
@@ -155,12 +155,6 @@ describe('model-pricing', () => {
expect(opus47.outputPerMillion).toBe(25.0);
});
it('should return correct pricing for Claude Opus 4.7 thinking variant', () => {
const opus47t = getModelPricing('claude-opus-4-7-thinking');
expect(opus47t.inputPerMillion).toBe(5.0);
expect(opus47t.outputPerMillion).toBe(25.0);
});
it('should match date-stamped Claude Opus 4.7 to correct pricing', () => {
const opus47dated = getModelPricing('claude-opus-4-7-20260401');
expect(opus47dated.inputPerMillion).toBe(5.0);