mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 04:18:05 +00:00
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:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user