feat(cliproxy): add Claude Opus 4.7 support

Adds Claude Opus 4.7 as a new model to the AGY (Antigravity) and Claude
provider catalogs, with pricing, Cursor IDE support, and updated defaults
across the codebase.

- Add claude-opus-4-7-thinking and claude-opus-4-7 to AGY catalog
  (defaultModel bumped to claude-opus-4-7-thinking)
- Add claude-opus-4-7 entry to Claude provider catalog
- Register claude-opus-4-7-thinking fork alias in CLIProxy config
  (CLIPROXY_CONFIG_VERSION bumped 17 -> 18)
- Add pricing for claude-opus-4-7 and claude-opus-4-7-thinking
  ($5/$25 per million, matching Opus 4.6)
- Add claude-4.7-opus and claude-4.7-opus-fast-mode to Cursor catalog
- Update ANTHROPIC_DEFAULT_OPUS_MODEL in base-claude settings to 4.7
- Update Droid adapter and code-reviewer fallback models to 4.7
- Update unit tests for model-catalog and model-pricing

Opus 4.6 is retained as a supported fallback in both catalogs.

Built [OnSteroids](https://onsteroids.ai)
This commit is contained in:
Sergey Galuza
2026-04-20 17:49:18 +02:00
parent dea704c57d
commit 61058123e1
9 changed files with 127 additions and 18 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
"ANTHROPIC_BASE_URL": "http://127.0.0.1:8317/api/provider/claude",
"ANTHROPIC_AUTH_TOKEN": "ccs-internal-managed",
"ANTHROPIC_MODEL": "claude-sonnet-4-6",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "claude-opus-4-6",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "claude-opus-4-7",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-4-6",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-haiku-4-5-20251001"
}
+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-6-thinking';
const MODEL = process.env.REVIEW_MODEL || 'claude-opus-4-7-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.
+2 -1
View File
@@ -41,7 +41,7 @@ export const CCS_CONTROL_PANEL_SECRET = 'ccs';
* v16: Narrow stale Gemini alias cleanup to broad multi-version guessed ranges
* v17: Persist routing.strategy from CCS unified config
*/
export const CLIPROXY_CONFIG_VERSION = 17;
export const CLIPROXY_CONFIG_VERSION = 18;
interface RegenerateConfigOptions {
configPath?: string;
@@ -86,6 +86,7 @@ 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 },
];
const BUILT_IN_GEMINI_ALIAS_NAMES = new Set(
+47 -3
View File
@@ -87,12 +87,42 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
agy: {
provider: 'agy',
displayName: 'Antigravity',
defaultModel: 'claude-opus-4-6-thinking',
defaultModel: 'claude-opus-4-7-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 without thinking',
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: 'Latest flagship, extended thinking',
description: 'Previous flagship, extended thinking',
nativeImageInput: true,
thinking: {
type: 'budget',
@@ -290,10 +320,24 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
displayName: 'Claude (Anthropic)',
defaultModel: 'claude-sonnet-4-6',
models: [
{
id: 'claude-opus-4-7',
name: 'Claude Opus 4.7',
description: 'Latest flagship model',
nativeImageInput: true,
thinking: {
type: 'budget',
min: 1024,
max: 128000,
zeroAllowed: false,
dynamicAllowed: true,
},
extendedContext: true,
},
{
id: 'claude-opus-4-6',
name: 'Claude Opus 4.6',
description: 'Latest flagship model',
description: 'Previous flagship model',
nativeImageInput: true,
thinking: {
type: 'budget',
+10
View File
@@ -14,6 +14,16 @@ export const DEFAULT_CURSOR_MODEL = 'gpt-5.3-codex';
*/
export const DEFAULT_CURSOR_MODELS: CursorModel[] = [
// Anthropic Models
{
id: 'claude-4.7-opus',
name: 'Claude 4.7 Opus',
provider: 'anthropic',
},
{
id: 'claude-4.7-opus-fast-mode',
name: 'Claude 4.7 Opus (Fast mode)',
provider: 'anthropic',
},
{
id: 'claude-4.6-opus',
name: 'Claude 4.6 Opus',
+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-6',
model: creds.model || 'claude-opus-4-7',
displayName: `CCS ${creds.profile}`,
baseUrl: creds.baseUrl,
apiKey: creds.apiKey,
+13
View File
@@ -228,6 +228,19 @@ const PRICING_REGISTRY: Record<string, ModelPricing> = {
cacheCreationPerMillion: 6.25,
cacheReadPerMillion: 0.5,
},
// Claude 4.7 Opus ($5/$25)
'claude-opus-4-7': {
inputPerMillion: 5.0,
outputPerMillion: 25.0,
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
+34 -11
View File
@@ -66,7 +66,21 @@ describe('Model Catalog', () => {
describe('AGY models', () => {
it('has correct default model', () => {
const { MODEL_CATALOG } = modelCatalog;
assert.strictEqual(MODEL_CATALOG.agy.defaultModel, 'claude-opus-4-6-thinking');
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');
});
it('includes Claude Opus 4.6 Thinking', () => {
@@ -117,9 +131,9 @@ describe('Model Catalog', () => {
assert.strictEqual(flash.tier, undefined, 'AGY models should not have paid tier');
});
it('has 5 models total', () => {
it('has 7 models total', () => {
const { MODEL_CATALOG } = modelCatalog;
assert.strictEqual(MODEL_CATALOG.agy.models.length, 5);
assert.strictEqual(MODEL_CATALOG.agy.models.length, 7);
});
});
@@ -266,6 +280,13 @@ describe('Model Catalog', () => {
});
describe('findModel', () => {
it('finds Claude Opus 4.7 Thinking in agy', () => {
const { findModel } = modelCatalog;
const model = findModel('agy', 'claude-opus-4-7-thinking');
assert(model, 'Should find model');
assert.strictEqual(model.name, 'Claude Opus 4.7 Thinking');
});
it('finds Claude Opus 4.6 Thinking in agy', () => {
const { findModel } = modelCatalog;
const model = findModel('agy', 'claude-opus-4-6-thinking');
@@ -310,10 +331,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-6-thinking')).toBe(
'claude-sonnet-4-6'
expect(getSuggestedReplacementModel('agy', 'claude-opus-4-7-thinking')).toBe(
'claude-opus-4-7'
);
expect(getSuggestedReplacementModel('agy')).toBe('claude-opus-4-6-thinking');
expect(getSuggestedReplacementModel('agy')).toBe('claude-opus-4-7-thinking');
});
it('returns undefined when no provider catalog exists', () => {
@@ -374,10 +395,10 @@ describe('Model Catalog', () => {
});
describe('Thinking models ordering', () => {
it('Claude Opus 4.6 Thinking is not deprecated', () => {
it('Claude Opus 4.7 Thinking is not deprecated', () => {
const { MODEL_CATALOG } = modelCatalog;
const opus = MODEL_CATALOG.agy.models.find((m) => m.id === 'claude-opus-4-6-thinking');
assert(opus, 'Should include Claude Opus 4.6 Thinking');
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.deprecated, undefined, 'Should not be marked as deprecated');
});
@@ -397,14 +418,14 @@ describe('Model Catalog', () => {
const models = MODEL_CATALOG.agy.models;
// Find indices of thinking models
const opusIdx = models.findIndex((m) => m.id === 'claude-opus-4-6-thinking');
const opus47Idx = models.findIndex((m) => m.id === 'claude-opus-4-7-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(opusIdx < geminiIdx, 'Opus should be above Gemini');
assert(opus47Idx < geminiIdx, 'Opus 4.7 should be above Gemini');
assert(sonnetThinkingIdx < geminiIdx, 'Sonnet should be above Gemini');
});
});
@@ -412,6 +433,7 @@ 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);
});
@@ -430,6 +452,7 @@ 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);
});
+18
View File
@@ -148,6 +148,24 @@ describe('model-pricing', () => {
expect(opus46.inputPerMillion).toBe(5.0);
expect(opus46.outputPerMillion).toBe(25.0);
});
it('should return correct pricing for Claude Opus 4.7', () => {
const opus47 = getModelPricing('claude-opus-4-7');
expect(opus47.inputPerMillion).toBe(5.0);
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);
expect(opus47dated.outputPerMillion).toBe(25.0);
});
});
describe('calculateCost', () => {