From 42e8288ddef0c8418be2dd3bd4b1cfda0b2f70e0 Mon Sep 17 00:00:00 2001 From: VDA Date: Sat, 6 Dec 2025 06:21:10 +0000 Subject: [PATCH] feat: add Opus 4.5 model selection support - Add model selection dropdown for Claude provider - Support switching between Opus 4.5 and Sonnet 4 - Update claude-agent-sdk to v0.1.60 for Opus 4.5 compatibility - Persist model preference in localStorage --- package-lock.json | 86 ++++++++++++++++++++++++++++++-- package.json | 2 +- src/components/ChatInterface.jsx | 65 +++++++++++++++--------- 3 files changed, 125 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index f1fddf2e..f0326f96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.12.0", "license": "MIT", "dependencies": { - "@anthropic-ai/claude-agent-sdk": "^0.1.29", + "@anthropic-ai/claude-agent-sdk": "^0.1.60", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-html": "^6.4.9", "@codemirror/lang-javascript": "^6.2.4", @@ -103,9 +103,9 @@ } }, "node_modules/@anthropic-ai/claude-agent-sdk": { - "version": "0.1.29", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.1.29.tgz", - "integrity": "sha512-VbR2ybPdJHVKAD3pQdruVw8LdXoPbk5J59xU/bQoMNzAsBckHrD2LhupMJrBxLUWxLaPkIUlNKquGBRbkoK84Q==", + "version": "0.1.60", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.1.60.tgz", + "integrity": "sha512-Kl7zo4yNiUs3fRc9CQ5kcRuihdPEzH26boC5E8szO9WMNwPFBfJExLfYZDAcYmFaE3+M6mLpuYzmTGLxSoXrhg==", "license": "SEE LICENSE IN README.md", "engines": { "node": ">=18.0.0" @@ -116,12 +116,90 @@ "@img/sharp-linux-arm": "^0.33.5", "@img/sharp-linux-arm64": "^0.33.5", "@img/sharp-linux-x64": "^0.33.5", + "@img/sharp-linuxmusl-arm64": "^0.33.5", + "@img/sharp-linuxmusl-x64": "^0.33.5", "@img/sharp-win32-x64": "^0.33.5" }, "peerDependencies": { "zod": "^3.24.1" } }, + "node_modules/@anthropic-ai/claude-agent-sdk/node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@anthropic-ai/claude-agent-sdk/node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@anthropic-ai/claude-agent-sdk/node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@anthropic-ai/claude-agent-sdk/node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", diff --git a/package.json b/package.json index 74c62fc6..21295b70 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "author": "Claude Code UI Contributors", "license": "MIT", "dependencies": { - "@anthropic-ai/claude-agent-sdk": "^0.1.29", + "@anthropic-ai/claude-agent-sdk": "^0.1.60", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-html": "^6.4.9", "@codemirror/lang-javascript": "^6.2.4", diff --git a/src/components/ChatInterface.jsx b/src/components/ChatInterface.jsx index 0ced6857..8fe4893e 100644 --- a/src/components/ChatInterface.jsx +++ b/src/components/ChatInterface.jsx @@ -1705,6 +1705,9 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess const [cursorModel, setCursorModel] = useState(() => { return localStorage.getItem('cursor-model') || 'gpt-5'; }); + const [claudeModel, setClaudeModel] = useState(() => { + return localStorage.getItem('claude-model') || 'opus'; + }); // Load permission mode for the current session useEffect(() => { if (selectedSession?.id) { @@ -2036,7 +2039,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess projectName: selectedProject.name, sessionId: currentSessionId, provider, - model: provider === 'cursor' ? cursorModel : 'claude-sonnet-4.5', + model: provider === 'cursor' ? cursorModel : claudeModel, tokenUsage: tokenBudget }; @@ -3852,7 +3855,8 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess resume: !!currentSessionId, toolsSettings: toolsSettings, permissionMode: permissionMode, - images: uploadedImages // Pass images to backend + images: uploadedImages, // Pass images to backend + model: claudeModel } }); } @@ -3872,7 +3876,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess if (selectedProject) { safeLocalStorage.removeItem(`draft_input_${selectedProject.name}`); } - }, [input, isLoading, selectedProject, attachedImages, currentSessionId, selectedSession, provider, permissionMode, onSessionActive, cursorModel, sendMessage, setInput, setAttachedImages, setUploadingImages, setImageErrors, setIsTextareaExpanded, textareaRef, setChatMessages, setIsLoading, setCanAbortSession, setClaudeStatus, setIsUserScrolledUp, scrollToBottom]); + }, [input, isLoading, selectedProject, attachedImages, currentSessionId, selectedSession, provider, permissionMode, onSessionActive, cursorModel, claudeModel, sendMessage, setInput, setAttachedImages, setUploadingImages, setImageErrors, setIsTextareaExpanded, textareaRef, setChatMessages, setIsLoading, setCanAbortSession, setClaudeStatus, setIsUserScrolledUp, scrollToBottom]); // Store handleSubmit in ref so handleCustomCommand can access it useEffect(() => { @@ -4271,30 +4275,45 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess - {/* Model Selection for Cursor - Always reserve space to prevent jumping */} -
+ {/* Model Selection - Show for both Claude and Cursor */} +
- + {provider === 'claude' ? ( + + ) : ( + + )}
- +

- {provider === 'claude' - ? 'Ready to use Claude AI. Start typing your message below.' + {provider === 'claude' + ? `Ready to use Claude with ${claudeModel === 'opus' ? 'Opus 4.5' : 'Sonnet 4'}. Start typing your message below.` : provider === 'cursor' ? `Ready to use Cursor with ${cursorModel}. Start typing your message below.` : 'Select a provider above to begin'