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:
Kai (Tam Nhu) Tran authored and GitHub committed 2026-04-20 13:39:44 -04:00
commit 73025669ab
8 files changed
+100 -3

No files matched your search

+6
View File
@@ -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.
+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"
}
+15 -1
View File
@@ -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',
+11 -1
View File
@@ -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',
+7
View File
@@ -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
+18
View File
@@ -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');
+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', () => {
+34
View File
@@ -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', () => {