mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-13 14:15:56 +00:00
Merge pull request #1055 from sgaluza/feat/claude-opus-4-7-support
feat(cliproxy): add Claude Opus 4.7 support
This commit is contained in:
8 files changed
+100
-3
No files matched your search
@@ -1,4 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
# Husky v9 invokes hooks via `sh -e` (see .husky/_/h), ignoring this shebang.
|
||||
# Re-exec under bash so `set -o pipefail` and `[[ ... ]]` below work portably.
|
||||
if [ -z "${BASH_VERSION:-}" ]; then
|
||||
exec bash "$0" "$@"
|
||||
fi
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Protected branches keep the full CI parity gate.
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -290,10 +290,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,10 +10,20 @@ 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
|
||||
{
|
||||
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',
|
||||
|
||||
@@ -228,6 +228,13 @@ 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,
|
||||
},
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// OpenAI Models - Source: better-ccusage
|
||||
|
||||
@@ -142,6 +142,17 @@ describe('Model Catalog', () => {
|
||||
assert.strictEqual(sonnet.name, 'Claude Sonnet 4.6');
|
||||
});
|
||||
|
||||
it('includes Claude Opus 4.7 with provider-specific settings', () => {
|
||||
const { MODEL_CATALOG } = modelCatalog;
|
||||
const opus47 = MODEL_CATALOG.claude.models.find((m) => m.id === 'claude-opus-4-7');
|
||||
assert(opus47, 'Should include Claude Opus 4.7');
|
||||
assert.strictEqual(opus47.name, 'Claude Opus 4.7');
|
||||
// Claude provider differs from AGY: zero thinking budget is disallowed,
|
||||
// and extended (1M) context is available on the Anthropic API.
|
||||
assert.strictEqual(opus47.thinking.zeroAllowed, false);
|
||||
assert.strictEqual(opus47.extendedContext, true);
|
||||
});
|
||||
|
||||
it('retains previous 4.5 snapshot models for explicit selection', () => {
|
||||
const { MODEL_CATALOG } = modelCatalog;
|
||||
const ids = MODEL_CATALOG.claude.models.map((m) => m.id);
|
||||
@@ -266,6 +277,13 @@ describe('Model Catalog', () => {
|
||||
});
|
||||
|
||||
describe('findModel', () => {
|
||||
it('finds Claude Opus 4.7 in claude', () => {
|
||||
const { findModel } = modelCatalog;
|
||||
const model = findModel('claude', 'claude-opus-4-7');
|
||||
assert(model, 'Should find model');
|
||||
assert.strictEqual(model.name, 'Claude Opus 4.7');
|
||||
});
|
||||
|
||||
it('finds Claude Opus 4.6 Thinking in agy', () => {
|
||||
const { findModel } = modelCatalog;
|
||||
const model = findModel('agy', 'claude-opus-4-6-thinking');
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -148,6 +148,18 @@ 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 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', () => {
|
||||
@@ -218,6 +230,28 @@ describe('model-pricing', () => {
|
||||
const cost = calculateCost(usage, 'claude-opus-4-6');
|
||||
expect(cost).toBe(36.75); // 5 + 25 + 6.25 + 0.5
|
||||
});
|
||||
|
||||
it('should calculate Claude Opus 4.7 cost including cache token rates', () => {
|
||||
const usage: TokenUsage = {
|
||||
inputTokens: 1_000_000,
|
||||
outputTokens: 1_000_000,
|
||||
cacheCreationTokens: 1_000_000,
|
||||
cacheReadTokens: 1_000_000,
|
||||
};
|
||||
const cost = calculateCost(usage, 'claude-opus-4-7');
|
||||
expect(cost).toBe(36.75); // 5 + 25 + 6.25 + 0.5
|
||||
});
|
||||
|
||||
it('should calculate Claude Opus 4.7 cache cost consistently across repeat lookups', () => {
|
||||
const usage: TokenUsage = {
|
||||
inputTokens: 1_000_000,
|
||||
outputTokens: 1_000_000,
|
||||
cacheCreationTokens: 1_000_000,
|
||||
cacheReadTokens: 1_000_000,
|
||||
};
|
||||
const cost = calculateCost(usage, 'claude-opus-4-7');
|
||||
expect(cost).toBe(36.75); // 5 + 25 + 6.25 + 0.5
|
||||
});
|
||||
});
|
||||
|
||||
describe('getKnownModels', () => {
|
||||
|
||||
Reference in new issue
Block a user