mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
fix(claude): select the correct Opus 5 context window
Bare Opus 5 IDs resolve to 200K behind third-party gateways. Expose explicit context variants and require a Claude Code version that recognizes the model.
This commit is contained in:
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## 0.2.2 - 2026-07-25
|
||||
|
||||
### Fixed
|
||||
|
||||
- Claude Opus 5 now defaults to its 1M context window.
|
||||
|
||||
## 0.2.1 - 2026-07-24
|
||||
|
||||
### Added
|
||||
|
||||
72
package-lock.json
generated
72
package-lock.json
generated
@@ -38106,7 +38106,7 @@
|
||||
"version": "0.2.1",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.17.1",
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.3.214",
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.3.220",
|
||||
"@anthropic-ai/sdk": "^0.104.2",
|
||||
"@getpaseo/client": "0.2.1",
|
||||
"@getpaseo/highlight": "0.2.1",
|
||||
@@ -38155,22 +38155,22 @@
|
||||
}
|
||||
},
|
||||
"packages/server/node_modules/@anthropic-ai/claude-agent-sdk": {
|
||||
"version": "0.3.214",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.214.tgz",
|
||||
"integrity": "sha512-wt5ImwhU+p259Zt4K/Q9v5xVi6ruxYO5+KFICyJxnjs/QFEClAeSRqhcXx1J8jgGfatPW1faw09hkm66680UjA==",
|
||||
"version": "0.3.220",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.220.tgz",
|
||||
"integrity": "sha512-glc7SdwPkOkLw8oxwLo9PKTdLJGqW/PIR4urWXFoRtX9YllwozsEVc5Tc1+EvLSkfrsxPJqQWqOgpjUOQXf1oA==",
|
||||
"license": "SEE LICENSE IN README.md",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.214",
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.214",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.214",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.214",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.214",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.214",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.214",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.214"
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.220",
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.220",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.220",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.220",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.220",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.220",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.220",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.220"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@anthropic-ai/sdk": ">=0.93.0",
|
||||
@@ -38179,9 +38179,9 @@
|
||||
}
|
||||
},
|
||||
"packages/server/node_modules/@anthropic-ai/claude-agent-sdk/node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64": {
|
||||
"version": "0.3.214",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.214.tgz",
|
||||
"integrity": "sha512-vAAOeVtlXs3p7MpFVfvfu9ja32eaKtB+MDZnPxCbdzxJk5nofCHlNsHa1UJwXHOsP9lOnFYNIccYztsZ4DNaJA==",
|
||||
"version": "0.3.220",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.220.tgz",
|
||||
"integrity": "sha512-7VxlbEosK7DODiOnsjoVd0DSJzbnaPrM2jelMHI0y8zx1UnLS3WC6EFUXbvy74F2sXqEznh2tzn7EKWInaRN6Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -38192,9 +38192,9 @@
|
||||
]
|
||||
},
|
||||
"packages/server/node_modules/@anthropic-ai/claude-agent-sdk/node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64": {
|
||||
"version": "0.3.214",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.214.tgz",
|
||||
"integrity": "sha512-NTbV8U2yucxCWqEiDC7L0MUehNmd8x3Op8OGzLZRhMF3xmQBy2DxpXiNZgSwwKWNDC3HdgKmJM5ZBbT7XrF/0g==",
|
||||
"version": "0.3.220",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.220.tgz",
|
||||
"integrity": "sha512-X9RwDsSmbF6ultKZroaip+DL8WRgC64gHbrAwrRlAFSPNZV7zmJyP2ur8rW7KrxqmtuehdMMkw8+SAC/6hD2PA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -38205,9 +38205,9 @@
|
||||
]
|
||||
},
|
||||
"packages/server/node_modules/@anthropic-ai/claude-agent-sdk/node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64": {
|
||||
"version": "0.3.214",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.214.tgz",
|
||||
"integrity": "sha512-KBCf+BlusG0ZcgvpjjHwv1kh+6WiR8vJbPjpR2udwkrtcQgKLN+24l+FeaQEzO2TL+ExCmM1KC4tNPvnPpy+tw==",
|
||||
"version": "0.3.220",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.220.tgz",
|
||||
"integrity": "sha512-WkROPwWskqhKR9XgnmseHQ6rLi9zM9qt57IWoToIjL/eXOqDWipp7JXZ1L5ud+LrA42dunHPZfBwD/vXZ+A7LA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -38218,9 +38218,9 @@
|
||||
]
|
||||
},
|
||||
"packages/server/node_modules/@anthropic-ai/claude-agent-sdk/node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64-musl": {
|
||||
"version": "0.3.214",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.214.tgz",
|
||||
"integrity": "sha512-i06wQRsmevE7spY1ryYfs+NP+xdZ1FwAyTjDaF0k/xG+cgtzZYghTFUemdYF3GVwgWgpcava9GiCFDT3DoHI6w==",
|
||||
"version": "0.3.220",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.220.tgz",
|
||||
"integrity": "sha512-OHoZOZ8Cf2TBr6oXIXPwyvUxj9jrq2w8E4poA8dMpacXszcPSPiCQCMuuOh4aWJzfeJE1+TtWxhKMVb2csXyZQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -38231,9 +38231,9 @@
|
||||
]
|
||||
},
|
||||
"packages/server/node_modules/@anthropic-ai/claude-agent-sdk/node_modules/@anthropic-ai/claude-agent-sdk-linux-x64": {
|
||||
"version": "0.3.214",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.214.tgz",
|
||||
"integrity": "sha512-vqadSceJkBKHaTUszxI35uTuECGWtsHWK/mOwIJ4b9DUtYYySz6EJEk4gHg4ccutisJ/oRVCpFXHIKFb+osrKw==",
|
||||
"version": "0.3.220",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.220.tgz",
|
||||
"integrity": "sha512-tkTJFnpR9VifvWX2fmkCAPkT6+8Wk/gVu8B5jsVekKZPiZoWRHmMXO30BnZn+f0TZhgYP+82PSX3S8crH1kn+w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -38244,9 +38244,9 @@
|
||||
]
|
||||
},
|
||||
"packages/server/node_modules/@anthropic-ai/claude-agent-sdk/node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl": {
|
||||
"version": "0.3.214",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.214.tgz",
|
||||
"integrity": "sha512-948RstHDhs0E69h+2dKYWIAL1kcwEme4zvtgNUwP8XpKXzWOhrTKmd6MAokHn25zwfuSx5d+HE0XHfFH6R4hLg==",
|
||||
"version": "0.3.220",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.220.tgz",
|
||||
"integrity": "sha512-K+FWj+LcGhC1Z7wqeWoLxm1iemcba5xKpLLFVwYm4V6HyMx3ruYd/2r2TiQtjT+JWeNFWIys0ScHiItR6vWAiA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -38257,9 +38257,9 @@
|
||||
]
|
||||
},
|
||||
"packages/server/node_modules/@anthropic-ai/claude-agent-sdk/node_modules/@anthropic-ai/claude-agent-sdk-win32-arm64": {
|
||||
"version": "0.3.214",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.214.tgz",
|
||||
"integrity": "sha512-CEKlPFCPv+ee79utQMEDSsgeo2f27ulhNHpjrKIt1jXz5G04J7lAWN/QwvPDv9XaLBkWpyfqZWiPXlRcwuaO/w==",
|
||||
"version": "0.3.220",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.220.tgz",
|
||||
"integrity": "sha512-rIwgq0UwQExWl6KrHUyC4w5KwpL9l6nd95aUTx6RitexaAuEw//xtfTVLnuE4hDDQZFkzEwpdKc3nxDWoGcUbA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -38270,9 +38270,9 @@
|
||||
]
|
||||
},
|
||||
"packages/server/node_modules/@anthropic-ai/claude-agent-sdk/node_modules/@anthropic-ai/claude-agent-sdk-win32-x64": {
|
||||
"version": "0.3.214",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.214.tgz",
|
||||
"integrity": "sha512-cJMJfFoR9IWBZWnTtt9PnEm89hGOsZjoDNjOCEOF3+x+g/ANIXAjMJTcaQYv2HKh6MylWZ0AkxBASI+twkukaQ==",
|
||||
"version": "0.3.220",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.220.tgz",
|
||||
"integrity": "sha512-MuOuXhbr66HlGaWXD2f3w0k2PsvmnbkwcUZ0dAe2poFLdl72GC2dapwwOBefxm9QmoNqk9+jmv/dSKGOVWyvLw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.17.1",
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.3.214",
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.3.220",
|
||||
"@anthropic-ai/sdk": "^0.104.2",
|
||||
"@getpaseo/client": "0.2.1",
|
||||
"@getpaseo/highlight": "0.2.1",
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
convertClaudeHistoryEntry,
|
||||
normalizeClaudeAskUserQuestionRequestInput,
|
||||
normalizeClaudeAskUserQuestionUpdatedInput,
|
||||
resolveClaudeCodeVersion,
|
||||
toClaudeSdkMcpConfig,
|
||||
} from "./agent.js";
|
||||
import { claudeProjectDirSync } from "./project-dir.js";
|
||||
@@ -405,6 +406,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => {
|
||||
const client = new ClaudeAgentClient({
|
||||
logger,
|
||||
resolveBinary: async () => "/test/claude/bin",
|
||||
resolveVersion: async () => "2.1.219",
|
||||
configDir: emptyConfigDir,
|
||||
});
|
||||
const { models } = await client.fetchCatalog({
|
||||
@@ -414,6 +416,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => {
|
||||
});
|
||||
|
||||
expect(models.map((m) => m.id)).toEqual([
|
||||
"claude-opus-5[1m]",
|
||||
"claude-opus-5",
|
||||
"claude-fable-5",
|
||||
"claude-opus-4-8[1m]",
|
||||
@@ -434,7 +437,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => {
|
||||
}
|
||||
|
||||
const defaultModel = models.find((m) => m.isDefault);
|
||||
expect(defaultModel?.id).toBe("claude-opus-5");
|
||||
expect(defaultModel?.id).toBe("claude-opus-5[1m]");
|
||||
} finally {
|
||||
await fs.rm(emptyConfigDir, { recursive: true, force: true });
|
||||
}
|
||||
@@ -446,6 +449,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => {
|
||||
const client = new ClaudeAgentClient({
|
||||
logger,
|
||||
resolveBinary: async () => "/test/claude/bin",
|
||||
resolveVersion: async () => "2.1.219",
|
||||
configDir: emptyConfigDir,
|
||||
});
|
||||
const { models } = await client.fetchCatalog({
|
||||
@@ -475,6 +479,10 @@ describe("ClaudeAgentClient.fetchCatalog", () => {
|
||||
describe("ClaudeAgentClient binary resolution", () => {
|
||||
const logger = createTestLogger();
|
||||
|
||||
test("resolves the installed Claude Code version", async () => {
|
||||
await expect(resolveClaudeCodeVersion()).resolves.toMatch(/^\d+\.\d+\.\d+$/);
|
||||
});
|
||||
|
||||
test("loads user, project, and local Claude settings", async () => {
|
||||
const queryReturn = vi.fn();
|
||||
queryReturn.mockResolvedValue(undefined);
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
import {
|
||||
CLAUDE_DISABLED_THINKING_OPTION_ID,
|
||||
CLAUDE_ULTRACODE_THINKING_OPTION_ID,
|
||||
parseClaudeCodeVersion,
|
||||
resolveClaudeDisabledThinkingForModel,
|
||||
} from "./model-manifest.js";
|
||||
import { parsePartialJsonObject } from "./partial-json.js";
|
||||
@@ -363,6 +364,7 @@ interface ClaudeAgentClientOptions {
|
||||
runtimeSettings?: ProviderRuntimeSettings;
|
||||
queryFactory?: ClaudeQueryFactory;
|
||||
resolveBinary?: () => Promise<string>;
|
||||
resolveVersion?: () => Promise<string | null>;
|
||||
configDir?: string;
|
||||
}
|
||||
|
||||
@@ -1439,6 +1441,7 @@ export class ClaudeAgentClient implements AgentClient {
|
||||
private readonly runtimeSettings?: ProviderRuntimeSettings;
|
||||
private readonly queryFactory?: ClaudeQueryFactory;
|
||||
private readonly resolveBinary: () => Promise<string>;
|
||||
private readonly resolveVersion: () => Promise<string | null>;
|
||||
private readonly configDir?: string;
|
||||
|
||||
constructor(options: ClaudeAgentClientOptions) {
|
||||
@@ -1447,6 +1450,8 @@ export class ClaudeAgentClient implements AgentClient {
|
||||
this.runtimeSettings = options.runtimeSettings;
|
||||
this.queryFactory = options.queryFactory;
|
||||
this.resolveBinary = options.resolveBinary ?? (() => resolveClaudeBinary(this.runtimeSettings));
|
||||
this.resolveVersion =
|
||||
options.resolveVersion ?? (() => resolveClaudeCodeVersion(this.runtimeSettings));
|
||||
this.configDir = options.configDir;
|
||||
}
|
||||
|
||||
@@ -1498,7 +1503,17 @@ export class ClaudeAgentClient implements AgentClient {
|
||||
|
||||
async fetchCatalog(_options: FetchCatalogOptions): Promise<ProviderCatalog> {
|
||||
// Claude exposes a global catalog here; cwd/force are intentionally irrelevant.
|
||||
const models = await getClaudeModelsWithSettings(this.logger, this.configDir);
|
||||
let claudeCodeVersion: string | null = null;
|
||||
try {
|
||||
claudeCodeVersion = await this.resolveVersion();
|
||||
} catch (error) {
|
||||
this.logger.warn({ err: error }, "Failed to resolve Claude Code version for model catalog");
|
||||
}
|
||||
const models = await getClaudeModelsWithSettings(
|
||||
this.logger,
|
||||
this.configDir,
|
||||
claudeCodeVersion,
|
||||
);
|
||||
const modes = detectIneligibleAutoModeTransport(
|
||||
createProviderEnv({ baseEnv: process.env, runtimeSettings: this.runtimeSettings }),
|
||||
)
|
||||
@@ -1620,6 +1635,26 @@ async function resolveClaudeBinary(runtimeSettings?: ProviderRuntimeSettings): P
|
||||
);
|
||||
}
|
||||
|
||||
export async function resolveClaudeCodeVersion(
|
||||
runtimeSettings?: ProviderRuntimeSettings,
|
||||
): Promise<string | null> {
|
||||
const launch = await resolveProviderLaunch({
|
||||
commandConfig: runtimeSettings?.command,
|
||||
defaultBinary: "claude",
|
||||
});
|
||||
const availability = await checkProviderLaunchAvailable(launch);
|
||||
if (!availability.available) {
|
||||
return null;
|
||||
}
|
||||
const executable = availability.resolvedPath ?? launch.command;
|
||||
const { stdout, stderr } = await execCommand(executable, [...launch.args, "--version"], {
|
||||
...createProviderEnvSpec({ runtimeSettings }),
|
||||
timeout: 5_000,
|
||||
});
|
||||
const version = parseClaudeCodeVersion(`${stdout}\n${stderr}`);
|
||||
return version?.join(".") ?? null;
|
||||
}
|
||||
|
||||
async function resolveClaudeAuth(
|
||||
launch: ResolvedProviderLaunch,
|
||||
availability: { resolvedPath: string | null },
|
||||
|
||||
@@ -6,7 +6,8 @@ interface ClaudeModelManifestEntry {
|
||||
id: string;
|
||||
label: string;
|
||||
description: string;
|
||||
isDefault?: boolean;
|
||||
defaultPriority?: number;
|
||||
minimumClaudeCodeVersion?: string;
|
||||
contextWindowMaxTokens?: number;
|
||||
effortLevels?: readonly ClaudeEffortLevel[];
|
||||
supportsThinkingDisabled?: boolean;
|
||||
@@ -30,12 +31,22 @@ export const CLAUDE_DISABLED_THINKING_OPTION_ID = "off";
|
||||
export const CLAUDE_ULTRACODE_THINKING_OPTION_ID = "ultracode";
|
||||
|
||||
export const CLAUDE_MODEL_MANIFEST = [
|
||||
{
|
||||
id: "claude-opus-5[1m]",
|
||||
label: "Opus 5 1M",
|
||||
description: "Opus 5 with 1M context window",
|
||||
defaultPriority: 2,
|
||||
minimumClaudeCodeVersion: "2.1.219",
|
||||
contextWindowMaxTokens: 1_000_000,
|
||||
effortLevels: CLAUDE_EFFORT_LEVELS.xhigh,
|
||||
supportsThinkingDisabled: true,
|
||||
},
|
||||
{
|
||||
id: "claude-opus-5",
|
||||
label: "Opus 5",
|
||||
description: "Opus 5 · Latest release",
|
||||
isDefault: true,
|
||||
contextWindowMaxTokens: 1_000_000,
|
||||
description: "Opus 5 · 200K context window",
|
||||
minimumClaudeCodeVersion: "2.1.219",
|
||||
contextWindowMaxTokens: 200_000,
|
||||
effortLevels: CLAUDE_EFFORT_LEVELS.xhigh,
|
||||
supportsThinkingDisabled: true,
|
||||
},
|
||||
@@ -59,6 +70,7 @@ export const CLAUDE_MODEL_MANIFEST = [
|
||||
id: "claude-opus-4-8",
|
||||
label: "Opus 4.8",
|
||||
description: "Opus 4.8 · Previous release",
|
||||
defaultPriority: 1,
|
||||
contextWindowMaxTokens: 200_000,
|
||||
effortLevels: CLAUDE_EFFORT_LEVELS.xhigh,
|
||||
supportsThinkingDisabled: true,
|
||||
@@ -155,31 +167,74 @@ function buildThinkingOptions(
|
||||
return options;
|
||||
}
|
||||
|
||||
export function getClaudeManifestModels(): AgentModelDefinition[] {
|
||||
return CLAUDE_MODEL_MANIFEST.map((model) => {
|
||||
export function getClaudeManifestModels(claudeCodeVersion?: string | null): AgentModelDefinition[] {
|
||||
const availableModels: readonly ClaudeModelManifestEntry[] = CLAUDE_MODEL_MANIFEST.filter(
|
||||
(model) => isModelAvailableInClaudeCode(model, claudeCodeVersion),
|
||||
);
|
||||
const defaultModel = availableModels.reduce<ClaudeModelManifestEntry | undefined>(
|
||||
(selected, candidate) =>
|
||||
(candidate.defaultPriority ?? 0) > (selected?.defaultPriority ?? 0) ? candidate : selected,
|
||||
undefined,
|
||||
);
|
||||
|
||||
return availableModels.map((model) => {
|
||||
const thinkingOptions = buildThinkingOptions(
|
||||
"effortLevels" in model ? model.effortLevels : undefined,
|
||||
"supportsThinkingDisabled" in model && model.supportsThinkingDisabled,
|
||||
model.effortLevels,
|
||||
model.supportsThinkingDisabled === true,
|
||||
);
|
||||
return {
|
||||
const definition: AgentModelDefinition = {
|
||||
provider: "claude",
|
||||
id: model.id,
|
||||
label: model.label,
|
||||
description: model.description,
|
||||
...("isDefault" in model && model.isDefault ? { isDefault: true } : {}),
|
||||
...(model.contextWindowMaxTokens !== undefined
|
||||
? { contextWindowMaxTokens: model.contextWindowMaxTokens }
|
||||
: {}),
|
||||
...(thinkingOptions
|
||||
? {
|
||||
thinkingOptions,
|
||||
defaultThinkingOptionId: "effortLevels" in model ? model.effortLevels?.[0] : undefined,
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
if (model === defaultModel) {
|
||||
definition.isDefault = true;
|
||||
}
|
||||
if (model.contextWindowMaxTokens !== undefined) {
|
||||
definition.contextWindowMaxTokens = model.contextWindowMaxTokens;
|
||||
}
|
||||
if (thinkingOptions) {
|
||||
definition.thinkingOptions = thinkingOptions;
|
||||
definition.defaultThinkingOptionId = model.effortLevels?.[0];
|
||||
}
|
||||
return definition;
|
||||
});
|
||||
}
|
||||
|
||||
function isModelAvailableInClaudeCode(
|
||||
model: ClaudeModelManifestEntry,
|
||||
claudeCodeVersion: string | null | undefined,
|
||||
): boolean {
|
||||
if (!model.minimumClaudeCodeVersion || claudeCodeVersion === undefined) {
|
||||
return true;
|
||||
}
|
||||
if (claudeCodeVersion === null) {
|
||||
return false;
|
||||
}
|
||||
return compareVersions(claudeCodeVersion, model.minimumClaudeCodeVersion) >= 0;
|
||||
}
|
||||
|
||||
function compareVersions(left: string, right: string): number {
|
||||
const leftParts = parseClaudeCodeVersion(left);
|
||||
const rightParts = parseClaudeCodeVersion(right);
|
||||
if (!leftParts || !rightParts) {
|
||||
return -1;
|
||||
}
|
||||
for (let index = 0; index < leftParts.length; index += 1) {
|
||||
const difference = leftParts[index] - rightParts[index];
|
||||
if (difference !== 0) {
|
||||
return difference;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function parseClaudeCodeVersion(value: string): [number, number, number] | null {
|
||||
const match = value.match(/\b(\d+)\.(\d+)\.(\d+)\b/);
|
||||
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
|
||||
}
|
||||
|
||||
export interface ClaudeDisabledThinkingResolution {
|
||||
supported: boolean;
|
||||
fallbackThinkingOptionId: string | undefined;
|
||||
|
||||
@@ -38,10 +38,18 @@ async function createClaudeConfigDirWithRawSettings(settings: string): Promise<s
|
||||
return configDir;
|
||||
}
|
||||
|
||||
function createCatalogClient(claudeCodeVersion = "2.1.219"): ClaudeAgentClient {
|
||||
return new ClaudeAgentClient({
|
||||
logger: createTestLogger(),
|
||||
resolveVersion: async () => claudeCodeVersion,
|
||||
});
|
||||
}
|
||||
|
||||
describe("getClaudeModels", () => {
|
||||
it("returns all claude models", () => {
|
||||
const models = getClaudeModels();
|
||||
expect(models.map((m) => m.id)).toEqual([
|
||||
"claude-opus-5[1m]",
|
||||
"claude-opus-5",
|
||||
"claude-fable-5",
|
||||
"claude-opus-4-8[1m]",
|
||||
@@ -61,7 +69,7 @@ describe("getClaudeModels", () => {
|
||||
const models = getClaudeModels();
|
||||
const defaults = models.filter((m) => m.isDefault);
|
||||
expect(defaults).toHaveLength(1);
|
||||
expect(defaults[0].id).toBe("claude-opus-5");
|
||||
expect(defaults[0].id).toBe("claude-opus-5[1m]");
|
||||
});
|
||||
|
||||
it("defines context window sizes in the catalog", () => {
|
||||
@@ -71,7 +79,8 @@ describe("getClaudeModels", () => {
|
||||
|
||||
expect(contextWindows).toEqual(
|
||||
new Map([
|
||||
["claude-opus-5", 1_000_000],
|
||||
["claude-opus-5[1m]", 1_000_000],
|
||||
["claude-opus-5", 200_000],
|
||||
["claude-fable-5", 1_000_000],
|
||||
["claude-opus-4-8[1m]", 1_000_000],
|
||||
["claude-opus-4-8", 200_000],
|
||||
@@ -87,6 +96,15 @@ describe("getClaudeModels", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("filters models by their minimum Claude Code version", () => {
|
||||
const oldVersionModels = getClaudeModels("2.1.218");
|
||||
expect(oldVersionModels.map((model) => model.id)).not.toContain("claude-opus-5[1m]");
|
||||
expect(oldVersionModels.map((model) => model.id)).not.toContain("claude-opus-5");
|
||||
expect(oldVersionModels.find((model) => model.isDefault)?.id).toBe("claude-opus-4-8");
|
||||
expect(getClaudeModels("2.1.219").map((model) => model.id)).toContain("claude-opus-5[1m]");
|
||||
expect(getClaudeModels("2.1.219").map((model) => model.id)).toContain("claude-opus-5");
|
||||
});
|
||||
|
||||
it("derives thinking options from model effort capabilities", () => {
|
||||
const models = new Map(getClaudeModels().map((model) => [model.id, model]));
|
||||
|
||||
@@ -175,7 +193,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => {
|
||||
},
|
||||
});
|
||||
vi.stubEnv("CLAUDE_CONFIG_DIR", configDir);
|
||||
const client = new ClaudeAgentClient({ logger: createTestLogger() });
|
||||
const client = createCatalogClient();
|
||||
|
||||
const { models } = await client.fetchCatalog({
|
||||
scope: "workspace",
|
||||
@@ -228,7 +246,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => {
|
||||
const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "paseo-claude-models-"));
|
||||
createdClaudeConfigDirs.push(configDir);
|
||||
vi.stubEnv("CLAUDE_CONFIG_DIR", configDir);
|
||||
const client = new ClaudeAgentClient({ logger: createTestLogger() });
|
||||
const client = createCatalogClient();
|
||||
|
||||
const { models } = await client.fetchCatalog({
|
||||
scope: "workspace",
|
||||
@@ -242,7 +260,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => {
|
||||
it("falls back to hardcoded models when settings.json is malformed", async () => {
|
||||
const configDir = await createClaudeConfigDirWithRawSettings("{ nope");
|
||||
vi.stubEnv("CLAUDE_CONFIG_DIR", configDir);
|
||||
const client = new ClaudeAgentClient({ logger: createTestLogger() });
|
||||
const client = createCatalogClient();
|
||||
|
||||
const { models } = await client.fetchCatalog({
|
||||
scope: "workspace",
|
||||
@@ -262,7 +280,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => {
|
||||
},
|
||||
});
|
||||
vi.stubEnv("CLAUDE_CONFIG_DIR", configDir);
|
||||
const client = new ClaudeAgentClient({ logger: createTestLogger() });
|
||||
const client = createCatalogClient();
|
||||
|
||||
const { models } = await client.fetchCatalog({
|
||||
scope: "workspace",
|
||||
@@ -282,7 +300,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => {
|
||||
},
|
||||
});
|
||||
vi.stubEnv("CLAUDE_CONFIG_DIR", configDir);
|
||||
const client = new ClaudeAgentClient({ logger: createTestLogger() });
|
||||
const client = createCatalogClient();
|
||||
|
||||
const { models } = await client.fetchCatalog({
|
||||
scope: "workspace",
|
||||
@@ -295,10 +313,24 @@ describe("ClaudeAgentClient.fetchCatalog", () => {
|
||||
"glm-5.1",
|
||||
]);
|
||||
});
|
||||
|
||||
it("omits models that require a newer Claude Code version", async () => {
|
||||
const client = createCatalogClient("2.1.218");
|
||||
|
||||
const { models } = await client.fetchCatalog({
|
||||
scope: "workspace",
|
||||
cwd: os.tmpdir(),
|
||||
force: true,
|
||||
});
|
||||
|
||||
expect(models.map((model) => model.id)).not.toContain("claude-opus-5[1m]");
|
||||
expect(models.map((model) => model.id)).not.toContain("claude-opus-5");
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeClaudeRuntimeModelId", () => {
|
||||
it("returns exact match for known model IDs", () => {
|
||||
expect(normalizeClaudeRuntimeModelId("claude-opus-5[1m]")).toBe("claude-opus-5[1m]");
|
||||
expect(normalizeClaudeRuntimeModelId("claude-opus-5")).toBe("claude-opus-5");
|
||||
expect(normalizeClaudeRuntimeModelId("claude-fable-5")).toBe("claude-fable-5");
|
||||
expect(normalizeClaudeRuntimeModelId("claude-sonnet-5")).toBe("claude-sonnet-5");
|
||||
@@ -318,6 +350,7 @@ describe("normalizeClaudeRuntimeModelId", () => {
|
||||
});
|
||||
|
||||
it("preserves [1m] suffix from runtime model strings", () => {
|
||||
expect(normalizeClaudeRuntimeModelId("claude-opus-5[1m]")).toBe("claude-opus-5[1m]");
|
||||
expect(normalizeClaudeRuntimeModelId("claude-opus-4-6[1m]")).toBe("claude-opus-4-6[1m]");
|
||||
});
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ const CLAUDE_SETTINGS_MODEL_ENV_KEYS = [
|
||||
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
||||
] as const;
|
||||
|
||||
export function getClaudeModels(): AgentModelDefinition[] {
|
||||
return getClaudeManifestModels();
|
||||
export function getClaudeModels(claudeCodeVersion?: string | null): AgentModelDefinition[] {
|
||||
return getClaudeManifestModels(claudeCodeVersion);
|
||||
}
|
||||
|
||||
export function findClaudeModel(
|
||||
@@ -34,8 +34,9 @@ export function findClaudeModel(
|
||||
export async function getClaudeModelsWithSettings(
|
||||
logger: Logger,
|
||||
configDir?: string,
|
||||
claudeCodeVersion?: string | null,
|
||||
): Promise<AgentModelDefinition[]> {
|
||||
const hardcodedModels = getClaudeModels();
|
||||
const hardcodedModels = getClaudeModels(claudeCodeVersion);
|
||||
const settingsModels = await readClaudeSettingsModels(logger, configDir);
|
||||
if (settingsModels.length === 0) {
|
||||
return hardcodedModels;
|
||||
|
||||
Reference in New Issue
Block a user