diff --git a/.github/workflows/update-contributors.yml b/.github/workflows/update-contributors.yml
index c3c932760..5709bdc10 100644
--- a/.github/workflows/update-contributors.yml
+++ b/.github/workflows/update-contributors.yml
@@ -1,46 +1,67 @@
-name: Update Contributors
+name: Update Contributors # Refresh contrib.rocks image cache
on:
- push:
- branches:
- - main
workflow_dispatch:
+permissions:
+ contents: write
+ pull-requests: write
+
jobs:
- update-contributors:
+ refresh-contrib-cache:
runs-on: ubuntu-latest
- permissions:
- contents: write # Needed for pushing changes.
- pull-requests: write # Needed for creating PRs.
steps:
- - name: Checkout code
+ - name: Checkout
uses: actions/checkout@v4
- - name: Setup Node.js and pnpm
- uses: ./.github/actions/setup-node-pnpm
- - name: Disable Husky
+
+ - name: Bump cacheBust in all README files
run: |
- echo "HUSKY=0" >> $GITHUB_ENV
- git config --global core.hooksPath /dev/null
- - name: Update contributors and format
+ set -euo pipefail
+ TS="$(date +%s)"
+ # Target only the root README.md and localized READMEs under locales/*/README.md
+ mapfile -t FILES < <(git ls-files README.md 'locales/*/README.md' || true)
+
+ if [ "${#FILES[@]}" -eq 0 ]; then
+ echo "No target README files found." >&2
+ exit 1
+ fi
+
+ UPDATED=0
+ for f in "${FILES[@]}"; do
+ if grep -q 'cacheBust=' "$f"; then
+ # Use portable sed in GNU environment of ubuntu-latest
+ sed -i -E "s/cacheBust=[0-9]+/cacheBust=${TS}/g" "$f"
+ echo "Updated cacheBust in $f"
+ UPDATED=1
+ else
+ echo "Warning: cacheBust parameter not found in $f" >&2
+ fi
+ done
+
+ if [ "$UPDATED" -eq 0 ]; then
+ echo "No files were updated. Ensure READMEs embed contrib.rocks with cacheBust param." >&2
+ exit 1
+ fi
+
+ - name: Detect changes
+ id: changes
run: |
- pnpm update-contributors
- npx prettier --write README.md locales/*/README.md
- if git diff --quiet; then echo "changes=false" >> $GITHUB_OUTPUT; else echo "changes=true" >> $GITHUB_OUTPUT; fi
- id: check-changes
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ if git diff --quiet; then
+ echo "changed=false" >> $GITHUB_OUTPUT
+ else
+ echo "changed=true" >> $GITHUB_OUTPUT
+ fi
+
- name: Create Pull Request
- if: steps.check-changes.outputs.changes == 'true'
+ if: steps.changes.outputs.changed == 'true'
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "docs: update contributors list [skip ci]"
committer: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
- branch: update-contributors
+ branch: refresh-contrib-cache
delete-branch: true
- title: "Update contributors list"
+ title: "Refresh contrib.rocks image cache (all READMEs)"
body: |
- Automated update of contributors list and related files
-
- This PR was created automatically by a GitHub Action workflow and includes all changed files.
+ Automated refresh of the contrib.rocks image cache by bumping the cacheBust parameter in README.md and locales/*/README.md.
base: main
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 50b14a020..187a94d19 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,36 @@
# Roo Code Changelog
+## [3.28.14] - 2025-09-30
+
+
+
+- Add support for GLM-4.6 model for z.ai provider (#8406 by @dmarkey, PR by @roomote)
+
+## [3.28.13] - 2025-09-29
+
+- Fix: Remove topP parameter from Bedrock inference config (#8377 by @ronyblum, PR by @daniel-lxs)
+- Fix: Correct Vertex AI Sonnet 4.5 model configuration (#8387 by @nickcatal, PR by @mrubens!)
+
+## [3.28.12] - 2025-09-29
+
+- Fix: Correct Anthropic Sonnet 4.5 model ID and add Bedrock 1M context checkbox (thanks @daniel-lxs!)
+
+## [3.28.11] - 2025-09-29
+
+- Fix: Correct AWS Bedrock Claude Sonnet 4.5 model identifier (#8371 by @sunhyung, PR by @app/roomote)
+- Fix: Correct Claude Sonnet 4.5 model ID format (thanks @daniel-lxs!)
+
+## [3.28.10] - 2025-09-29
+
+
+
+- Feat: Add Sonnet 4.5 support (thanks @daniel-lxs!)
+- Fix: Resolve max_completion_tokens issue for GPT-5 models in LiteLLM provider (#6979 by @lx1054331851, PR by @roomote)
+- Fix: Make chat icons properly sized with shrink-0 class (thanks @mrubens!)
+- Enhancement: Track telemetry settings changes for better analytics (thanks @mrubens!)
+- Web: Add testimonials section to website (thanks @brunobergher!)
+- CI: Refresh contrib.rocks cache workflow for contributor badges (thanks @hannesrudolph!)
+
## [3.28.9] - 2025-09-26

diff --git a/apps/web-roo-code/src/app/page.tsx b/apps/web-roo-code/src/app/page.tsx
index b062d5845..6aa9d34db 100644
--- a/apps/web-roo-code/src/app/page.tsx
+++ b/apps/web-roo-code/src/app/page.tsx
@@ -14,6 +14,7 @@ import {
} from "@/components/homepage"
import { EXTERNAL_LINKS } from "@/lib/constants"
import { ArrowRight } from "lucide-react"
+import { StructuredData } from "@/components/structured-data"
// Invalidate cache when a request comes in, at most once every hour.
export const revalidate = 3600
@@ -23,6 +24,7 @@ export default async function Home() {
return (
<>
+
diff --git a/apps/web-roo-code/src/components/homepage/testimonials.tsx b/apps/web-roo-code/src/components/homepage/testimonials.tsx
index 01236dfe7..84d9e6cf4 100644
--- a/apps/web-roo-code/src/components/homepage/testimonials.tsx
+++ b/apps/web-roo-code/src/components/homepage/testimonials.tsx
@@ -4,45 +4,114 @@ import { useRef, useCallback, useEffect } from "react"
import { motion } from "framer-motion"
import useEmblaCarousel from "embla-carousel-react"
import AutoPlay from "embla-carousel-autoplay"
-import { ChevronLeft, ChevronRight } from "lucide-react"
+import { ChevronLeft, ChevronRight, Star } from "lucide-react"
export interface Testimonial {
- id: number
name: string
role: string
- company: string
- image?: string
+ origin: string
quote: string
+ image?: string
+ stars?: number
}
export const testimonials: Testimonial[] = [
{
- id: 1,
name: "Luca",
role: "Reviewer",
- company: "VS Code Marketplace",
+ origin: "VS Code Marketplace",
quote: "Roo Code is an absolute game-changer! 🚀 It makes coding faster, easier, and more intuitive with its smart AI-powered suggestions, real-time debugging, and automation features. The seamless integration with VS Code is a huge plus, and the constant updates ensure it keeps getting better",
+ stars: 5,
},
{
- id: 2,
name: "Taro Woollett-Chiba",
role: "AI Product Lead",
- company: "Vendidit",
+ origin: "Vendidit",
quote: "Easily the best AI code editor. Roo Code has the best features and capabilities, along with the best development team. I swear, they're the fastest to support new models and implement useful functionality whenever users mention it... simply amazing.",
},
{
- id: 3,
name: "Can Nuri",
role: "Reviewer",
- company: "VS Code Marketplace",
+ origin: "VS Code Marketplace",
quote: "Roo Code is one of the most inspiring projects I have seen for a long time. It shapes the way I think and deal with software development.",
+ stars: 5,
},
{
- id: 4,
name: "Michael",
role: "Reviewer",
- company: "VS Code Marketplace",
+ origin: "VS Code Marketplace",
quote: "I switched from Windsurf to Roo Code in January and honestly, it's been a huge upgrade. Windsurf kept making mistakes and being dumb when I ask it for things. Roo just gets it. Projects that used to take a full day now wrap up before lunch. ",
+ stars: 5,
+ },
+ {
+ name: "Darien Hardin",
+ role: "Reviewer",
+ origin: "VS Code Marketplace",
+ quote: "By far the best coding tool I have used. Looking forward to where this goes in the future. Also, their Discord is an excellent resource with many knowledgeable users sharing their discoveries.",
+ stars: 5,
+ },
+ {
+ name: "Wiliam Azzam",
+ role: "Reviewer",
+ origin: "VS Code Marketplace",
+ quote: "I've tried Cursor, Windsurf, Cline, Trae and others, and although using RooCode with OpenRouter is more expensive, it is also far more effective. Its agents and initial setup, and learning how to use Code/Architect/Orchestrator, help a great deal in developing quality projects.",
+ stars: 5,
+ },
+ {
+ name: "Matěj Zapletal",
+ role: "Reviewer",
+ origin: "VS Code Marketplace",
+ quote: "Definitely the best AI coding agent extension.",
+ stars: 5,
+ },
+ {
+ name: "Ali Davachi",
+ role: "Reviewer",
+ origin: "VS Code Marketplace",
+ quote: "We tried the rest, now we are using the best. The alternatives are more restrictive. I didn't use competitors for a reason. This team is killing it.",
+ stars: 5,
+ },
+ {
+ name: "Ryan Booth",
+ role: "Reviewer",
+ origin: "VS Code Marketplace",
+ quote: "I work inside Roo about 60+ hours a week and usually roo is building something at all hours of the day. An amazing tool by an amazing team!",
+ stars: 5,
+ },
+ {
+ name: "Matthew Martin",
+ role: "Reviewer",
+ origin: "VS Code Marketplace",
+ quote: "i spent a fortune trying to dial in various tools to get them to work the way i want, and then i found roocode. customizable for your flavors on your terms. this is what i always wanted.",
+ stars: 5,
+ },
+ {
+ name: "Edwin Jacques",
+ role: "Reviewer",
+ origin: "VS Code Marketplace",
+ quote: "The BEST. Super fast, no-nonsense, UI that makes sense, many API provider choices, responsive, helpful developer community.",
+ stars: 5,
+ },
+ {
+ name: "Sean McCann",
+ role: "Reviewer",
+ origin: "VS Code Marketplace",
+ quote: "Roo Code is impressively capable while staying refreshingly simple. It integrates seamlessly into VS Code and handles everything from generating code to refactoring with accuracy and speed. It feels like a natural part of the workflow—no clutter, just results. Extra points for the flexibility of the different agents and the ability to customize them to fit the job.",
+ stars: 5,
+ },
+ {
+ name: "Colin Tate",
+ role: "Reviewer",
+ origin: "VS Code Marketplace",
+ quote: "Absolutely amazing extension. I had tried Cursor previously, and this just beats it hands down. I've used it for several large projects now, and it is now my go-to for creating things that would normally take weeks or months. Highly recommended.",
+ stars: 5,
+ },
+ {
+ name: "Michael Scott",
+ role: "Reviewer",
+ origin: "VS Code Marketplace",
+ quote: "I've used all the IDEs and all the assistants - Roo Code is hands down the best of them. It's also one of the few that lets you bring your own API keys - no subscriptions required, just pay as you need/go! Fantastic team and support as well!",
+ stars: 5,
},
]
@@ -58,8 +127,8 @@ export function Testimonials() {
[
AutoPlay({
playOnInit: true,
- delay: 4000,
- stopOnInteraction: true,
+ delay: 3_500,
+ stopOnInteraction: false,
stopOnMouseEnter: true,
stopOnFocusIn: true,
}),
@@ -122,17 +191,17 @@ export function Testimonials() {
-
+
- AI-forward developers are using Roo Code
+ Developers really shipping with AI are using Roo Code
- Join more than 800k people revolutionizing their workflow worldwide
+ Join more than 1M people revolutionizing their workflow worldwide
+ })
+ queryClient = new QueryClient({
+ defaultOptions: {
+ queries: { retry: false },
+ mutations: { retry: false },
+ },
+ })
+ ;(useExtensionState as any).mockReturnValue(defaultExtensionState)
+ })
+
+ // TODO: Fix underlying issue - dialog appears even when no user changes have been made
+ // This happens because some component is triggering setCachedStateField during initialization
+ // without properly marking it as a non-user action
+ it.skip("should not show unsaved changes when settings are automatically initialized", async () => {
+ const onDone = vi.fn()
+
+ render(
+
+
+ ,
+ )
+
+ // Wait for the component to render
+ await waitFor(() => {
+ expect(screen.getByTestId("api-options")).toBeInTheDocument()
+ })
+
+ // Wait for any async state updates to complete
+ await waitFor(() => {
+ const saveButton = screen.getByTestId("save-button") as HTMLButtonElement
+ expect(saveButton.disabled).toBe(true)
+ })
+
+ // Click the Done button
+ const doneButton = screen.getByText("settings:common.done")
+ fireEvent.click(doneButton)
+
+ // Should not show unsaved changes dialog - onDone should be called immediately
+ await waitFor(() => {
+ expect(onDone).toHaveBeenCalled()
+ })
+
+ // Verify no dialog appeared
+ expect(screen.queryByText("settings:unsavedChangesDialog.title")).not.toBeInTheDocument()
+ })
+
+ // TODO: Fix underlying issue - see above
+ it.skip("should not trigger unsaved changes for automatic model initialization", async () => {
+ const onDone = vi.fn()
+
+ // Mock ApiOptions to simulate ModelPicker initialization
+ vi.mocked(ApiOptions).mockImplementation(({ setApiConfigurationField, apiConfiguration }) => {
+ const [hasInitialized, setHasInitialized] = React.useState(false)
+
+ React.useEffect(() => {
+ // Only run once and only if not already initialized
+ if (!hasInitialized && apiConfiguration?.apiModelId === "") {
+ // Simulate automatic initialization from empty string to a value
+ setApiConfigurationField("apiModelId", "default-model", false)
+ setHasInitialized(true)
+ }
+ }, [hasInitialized, apiConfiguration?.apiModelId, setApiConfigurationField])
+
+ return
ApiOptions with Init
+ })
+
+ render(
+
+
+ ,
+ )
+
+ // Wait for the component to render and effects to run
+ await waitFor(() => {
+ expect(screen.getByTestId("api-options")).toBeInTheDocument()
+ })
+
+ // Give time for effects to complete
+ await new Promise((resolve) => setTimeout(resolve, 100))
+
+ // Check that save button is disabled (no changes detected)
+ const saveButton = screen.getByTestId("save-button") as HTMLButtonElement
+ expect(saveButton.disabled).toBe(true)
+
+ // Click the Done button
+ const doneButton = screen.getByText("settings:common.done")
+ fireEvent.click(doneButton)
+
+ // Should not show unsaved changes dialog
+ expect(screen.queryByText("settings:unsavedChangesDialog.title")).not.toBeInTheDocument()
+
+ // onDone should be called
+ expect(onDone).toHaveBeenCalled()
+ })
+
+ it("should show unsaved changes when user makes actual changes", async () => {
+ const onDone = vi.fn()
+
+ // Create a custom mock for this test that simulates user interaction
+ const ApiOptionsWithButton = vi.fn(({ setApiConfigurationField }) => {
+ const handleUserChange = () => {
+ // Simulate user action (isUserAction = true by default)
+ setApiConfigurationField("apiModelId", "user-selected-model")
+ }
+
+ return (
+
+
+
+ )
+ })
+
+ // Override the mock for this specific test
+ vi.mocked(ApiOptions).mockImplementation(ApiOptionsWithButton)
+
+ render(
+
+
+ ,
+ )
+
+ // Wait for the component to render
+ await waitFor(() => {
+ expect(screen.getByTestId("api-options")).toBeInTheDocument()
+ })
+
+ // Simulate user changing a setting
+ const changeButton = screen.getByTestId("change-model")
+ fireEvent.click(changeButton)
+
+ // Click the Done button
+ const doneButton = screen.getByText("settings:common.done")
+ fireEvent.click(doneButton)
+
+ // Should show unsaved changes dialog
+ await waitFor(() => {
+ expect(screen.getByText("settings:unsavedChangesDialog.title")).toBeInTheDocument()
+ })
+
+ // onDone should not be called yet
+ expect(onDone).not.toHaveBeenCalled()
+ })
+
+ // TODO: Fix underlying issue - see above
+ it.skip("should handle initialization from undefined to value without triggering unsaved changes", async () => {
+ const onDone = vi.fn()
+
+ // Start with undefined apiModelId
+ const stateWithUndefined = {
+ ...defaultExtensionState,
+ apiConfiguration: {
+ apiProvider: "openai",
+ apiModelId: undefined,
+ },
+ }
+ ;(useExtensionState as any).mockReturnValue(stateWithUndefined)
+
+ render(
+
+
+ ,
+ )
+
+ // Wait for initialization
+ await waitFor(() => {
+ expect(screen.getByTestId("api-options")).toBeInTheDocument()
+ })
+
+ // Wait for save button to be disabled (no changes)
+ await waitFor(() => {
+ const saveButton = screen.getByTestId("save-button") as HTMLButtonElement
+ expect(saveButton.disabled).toBe(true)
+ })
+
+ // Click Done button
+ const doneButton = screen.getByText("settings:common.done")
+ fireEvent.click(doneButton)
+
+ // Should call onDone immediately without showing dialog
+ await waitFor(() => {
+ expect(onDone).toHaveBeenCalled()
+ })
+
+ // Verify no dialog appeared
+ expect(screen.queryByText("settings:unsavedChangesDialog.title")).not.toBeInTheDocument()
+ })
+
+ // TODO: Fix underlying issue - see above
+ it.skip("should handle initialization from null to value without triggering unsaved changes", async () => {
+ const onDone = vi.fn()
+
+ // Start with null apiModelId
+ const stateWithNull = {
+ ...defaultExtensionState,
+ apiConfiguration: {
+ apiProvider: "openai",
+ apiModelId: null,
+ },
+ }
+ ;(useExtensionState as any).mockReturnValue(stateWithNull)
+
+ render(
+
+
+ ,
+ )
+
+ // Wait for initialization
+ await waitFor(() => {
+ expect(screen.getByTestId("api-options")).toBeInTheDocument()
+ })
+
+ // Wait for save button to be disabled (no changes)
+ await waitFor(() => {
+ const saveButton = screen.getByTestId("save-button") as HTMLButtonElement
+ expect(saveButton.disabled).toBe(true)
+ })
+
+ // Click Done button
+ const doneButton = screen.getByText("settings:common.done")
+ fireEvent.click(doneButton)
+
+ // Should call onDone immediately without showing dialog
+ await waitFor(() => {
+ expect(onDone).toHaveBeenCalled()
+ })
+
+ // Verify no dialog appeared
+ expect(screen.queryByText("settings:unsavedChangesDialog.title")).not.toBeInTheDocument()
+ })
+
+ // TODO: Fix underlying issue - see above
+ it.skip("should not trigger changes when ApiOptions syncs model IDs during mount", async () => {
+ const onDone = vi.fn()
+
+ // This specifically tests the bug we fixed where ApiOptions' useEffect
+ // was syncing selectedModelId with apiModelId and incorrectly triggering
+ // change detection because it wasn't passing isUserAction=false
+
+ // Mock ApiOptions to simulate the actual sync behavior
+ vi.mocked(ApiOptions).mockImplementation(({ setApiConfigurationField, apiConfiguration }) => {
+ const [hasSynced, setHasSynced] = React.useState(false)
+
+ React.useEffect(() => {
+ // Simulate the automatic sync that happens in the real component
+ // This should NOT trigger unsaved changes because isUserAction=false
+ // Only sync once to avoid multiple calls
+ if (!hasSynced && apiConfiguration?.apiModelId === "") {
+ setApiConfigurationField("apiModelId", "synced-model", false)
+ setHasSynced(true)
+ }
+ }, [hasSynced, apiConfiguration?.apiModelId, setApiConfigurationField])
+
+ return
ApiOptions
+ })
+
+ render(
+
+
+ ,
+ )
+
+ // Wait for component to fully mount and ApiOptions effect to run
+ await waitFor(() => {
+ expect(screen.getByTestId("api-options")).toBeInTheDocument()
+ })
+
+ // Wait for any async effects to complete
+ await new Promise((resolve) => setTimeout(resolve, 100))
+
+ // Save button should still be disabled (no user changes)
+ const saveButton = screen.getByTestId("save-button") as HTMLButtonElement
+ expect(saveButton.disabled).toBe(true)
+
+ // Clicking done should not show dialog
+ const doneButton = screen.getByText("settings:common.done")
+ fireEvent.click(doneButton)
+
+ // Should call onDone directly without showing unsaved changes dialog
+ await waitFor(() => {
+ expect(onDone).toHaveBeenCalled()
+ })
+
+ // No dialog should appear
+ expect(screen.queryByText("settings:unsavedChangesDialog.title")).not.toBeInTheDocument()
+ })
+})
diff --git a/webview-ui/src/components/settings/providers/Anthropic.tsx b/webview-ui/src/components/settings/providers/Anthropic.tsx
index ede2b9020..feef788d4 100644
--- a/webview-ui/src/components/settings/providers/Anthropic.tsx
+++ b/webview-ui/src/components/settings/providers/Anthropic.tsx
@@ -22,7 +22,8 @@ export const Anthropic = ({ apiConfiguration, setApiConfigurationField }: Anthro
const [anthropicBaseUrlSelected, setAnthropicBaseUrlSelected] = useState(!!apiConfiguration?.anthropicBaseUrl)
// Check if the current model supports 1M context beta
- const supports1MContextBeta = selectedModel?.id === "claude-sonnet-4-20250514"
+ const supports1MContextBeta =
+ selectedModel?.id === "claude-sonnet-4-20250514" || selectedModel?.id === "claude-sonnet-4-5"
const handleInputChange = useCallback(
(
diff --git a/webview-ui/src/components/settings/providers/Bedrock.tsx b/webview-ui/src/components/settings/providers/Bedrock.tsx
index 6c871e570..1b3143fa0 100644
--- a/webview-ui/src/components/settings/providers/Bedrock.tsx
+++ b/webview-ui/src/components/settings/providers/Bedrock.tsx
@@ -2,12 +2,7 @@ import { useCallback, useState, useEffect } from "react"
import { Checkbox } from "vscrui"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
-import {
- type ProviderSettings,
- type ModelInfo,
- BEDROCK_REGIONS,
- BEDROCK_CLAUDE_SONNET_4_MODEL_ID,
-} from "@roo-code/types"
+import { type ProviderSettings, type ModelInfo, BEDROCK_REGIONS, BEDROCK_1M_CONTEXT_MODEL_IDS } from "@roo-code/types"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, StandardTooltip } from "@src/components/ui"
@@ -24,8 +19,9 @@ export const Bedrock = ({ apiConfiguration, setApiConfigurationField, selectedMo
const { t } = useAppTranslation()
const [awsEndpointSelected, setAwsEndpointSelected] = useState(!!apiConfiguration?.awsBedrockEndpointEnabled)
- // Check if the selected model supports 1M context (Claude Sonnet 4)
- const supports1MContextBeta = apiConfiguration?.apiModelId === BEDROCK_CLAUDE_SONNET_4_MODEL_ID
+ // Check if the selected model supports 1M context (Claude Sonnet 4 / 4.5)
+ const supports1MContextBeta =
+ !!apiConfiguration?.apiModelId && BEDROCK_1M_CONTEXT_MODEL_IDS.includes(apiConfiguration.apiModelId as any)
// Update the endpoint enabled state when the configuration changes
useEffect(() => {
diff --git a/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts b/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts
index 9d8c627b7..1157bdf2e 100644
--- a/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts
+++ b/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts
@@ -5,7 +5,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { renderHook } from "@testing-library/react"
import type { Mock } from "vitest"
-import { ProviderSettings, ModelInfo, BEDROCK_CLAUDE_SONNET_4_MODEL_ID } from "@roo-code/types"
+import { ProviderSettings, ModelInfo, BEDROCK_1M_CONTEXT_MODEL_IDS } from "@roo-code/types"
import { useSelectedModel } from "../useSelectedModel"
import { useRouterModels } from "../useRouterModels"
@@ -474,28 +474,28 @@ describe("useSelectedModel", () => {
it("should enable 1M context window for Bedrock Claude Sonnet 4 when awsBedrock1MContext is true", () => {
const apiConfiguration: ProviderSettings = {
apiProvider: "bedrock",
- apiModelId: BEDROCK_CLAUDE_SONNET_4_MODEL_ID,
+ apiModelId: BEDROCK_1M_CONTEXT_MODEL_IDS[0],
awsBedrock1MContext: true,
}
const wrapper = createWrapper()
const { result } = renderHook(() => useSelectedModel(apiConfiguration), { wrapper })
- expect(result.current.id).toBe(BEDROCK_CLAUDE_SONNET_4_MODEL_ID)
+ expect(result.current.id).toBe(BEDROCK_1M_CONTEXT_MODEL_IDS[0])
expect(result.current.info?.contextWindow).toBe(1_000_000)
})
it("should use default context window for Bedrock Claude Sonnet 4 when awsBedrock1MContext is false", () => {
const apiConfiguration: ProviderSettings = {
apiProvider: "bedrock",
- apiModelId: BEDROCK_CLAUDE_SONNET_4_MODEL_ID,
+ apiModelId: BEDROCK_1M_CONTEXT_MODEL_IDS[0],
awsBedrock1MContext: false,
}
const wrapper = createWrapper()
const { result } = renderHook(() => useSelectedModel(apiConfiguration), { wrapper })
- expect(result.current.id).toBe(BEDROCK_CLAUDE_SONNET_4_MODEL_ID)
+ expect(result.current.id).toBe(BEDROCK_1M_CONTEXT_MODEL_IDS[0])
expect(result.current.info?.contextWindow).toBe(200_000)
})
diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts
index 3a24df2f8..0d0514b4d 100644
--- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts
+++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts
@@ -55,7 +55,7 @@ import {
qwenCodeDefaultModelId,
qwenCodeModels,
vercelAiGatewayDefaultModelId,
- BEDROCK_CLAUDE_SONNET_4_MODEL_ID,
+ BEDROCK_1M_CONTEXT_MODEL_IDS,
deepInfraDefaultModelId,
} from "@roo-code/types"
@@ -200,8 +200,8 @@ function getSelectedModel({
}
}
- // Apply 1M context for Claude Sonnet 4 when enabled
- if (id === BEDROCK_CLAUDE_SONNET_4_MODEL_ID && apiConfiguration.awsBedrock1MContext && baseInfo) {
+ // Apply 1M context for Claude Sonnet 4 / 4.5 when enabled
+ if (BEDROCK_1M_CONTEXT_MODEL_IDS.includes(id as any) && apiConfiguration.awsBedrock1MContext && baseInfo) {
// Create a new ModelInfo object with updated context window
const info: ModelInfo = {
...baseInfo,
@@ -367,11 +367,11 @@ function getSelectedModel({
// Apply 1M context beta tier pricing for Claude Sonnet 4
if (
provider === "anthropic" &&
- id === "claude-sonnet-4-20250514" &&
+ (id === "claude-sonnet-4-20250514" || id === "claude-sonnet-4-5") &&
apiConfiguration.anthropicBeta1MContext &&
baseInfo
) {
- // Type assertion since we know claude-sonnet-4-20250514 has tiers
+ // Type assertion since we know claude-sonnet-4-20250514 and claude-sonnet-4-5 have tiers
const modelWithTiers = baseInfo as typeof baseInfo & {
tiers?: Array<{
contextWindow: number