Skip to content

Commit 02fce95

Browse files
Merge branch 'Zoo-Code-Org:main' into fix/tab-mode-model-isolation
2 parents 92ad632 + 367013f commit 02fce95

11 files changed

Lines changed: 108 additions & 37 deletions

File tree

apps/vscode-e2e/src/suite/providers/xai.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const XAI_API_KEY = process.env.XAI_API_KEY
1212
const XAI_BASE_URL = "https://api.x.ai/v1"
1313
const XAI_RESPONSES_URL = `${XAI_BASE_URL}/responses`
1414
// Primary model for the full round-trip test (completion-text assertion included).
15-
const XAI_MODEL_ID = "grok-4.20"
15+
const XAI_MODEL_ID = "grok-4.5"
1616
// Fast variants: tested for API parameter contract only. They consistently call
1717
// attempt_completion with an empty result field after a no-tool-error recovery
1818
// loop, so they cannot satisfy the completion-text assertion at this time.
@@ -492,7 +492,7 @@ suite("xAI provider", function () {
492492
const readCallId = modelFixture?.readCallId ?? "call_xai_read_001"
493493

494494
if (request.functionCallOutputIds.some((id) => id === readCallId)) {
495-
// Use recorded turn2 when it contains a function_call (grok-4.20).
495+
// Use recorded turn2 when it contains a function_call (grok-4.5).
496496
// Fast models return plain text in turn2 — hand-craft attempt_completion
497497
// so the task can reach completion.
498498
const turn2HasFunctionCall = (modelFixture?.turn2 as ResponsesStreamEvent[] | undefined)?.some(

packages/types/src/providers/xai.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,25 @@ import type { ModelInfo } from "../model.js"
33
// https://docs.x.ai/docs/api-reference
44
export type XAIModelId = keyof typeof xaiModels
55

6-
export const xaiDefaultModelId: XAIModelId = "grok-4.20"
6+
export const xaiDefaultModelId: XAIModelId = "grok-4.5"
77

88
export const xaiModels = {
9+
"grok-4.5": {
10+
maxTokens: 65_536,
11+
contextWindow: 500_000,
12+
supportsImages: true,
13+
supportsPromptCache: true,
14+
inputPrice: 2.0,
15+
outputPrice: 6.0,
16+
cacheWritesPrice: 0.5,
17+
cacheReadsPrice: 0.5,
18+
description:
19+
"xAI's flagship Grok 4.5 model with 500K context, configurable reasoning (low/medium/high), and agentic tool calling via Responses API.",
20+
supportsReasoningEffort: ["low", "medium", "high"],
21+
reasoningEffort: "high",
22+
includedTools: ["search_replace"],
23+
excludedTools: ["apply_diff"],
24+
},
925
"grok-4.20": {
1026
maxTokens: 65_536,
1127
contextWindow: 2_000_000,

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/api/providers/__tests__/xai.spec.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ describe("XAIHandler", () => {
255255
await expect(handler.completePrompt("test prompt")).rejects.toThrow(`xAI completion error: ${errorMessage}`)
256256
})
257257

258-
it("should include reasoning_effort for mini models", async () => {
258+
it("should include reasoning effort for mini models in Responses API format", async () => {
259259
const miniModelHandler = new XAIHandler({
260260
apiModelId: "grok-3-mini",
261261
reasoningEffort: "high",
@@ -269,7 +269,48 @@ describe("XAIHandler", () => {
269269
expect(mockResponsesCreate).toHaveBeenCalledWith(
270270
expect.objectContaining({
271271
reasoning: expect.objectContaining({
272-
reasoning_effort: "high",
272+
effort: "high",
273+
}),
274+
}),
275+
)
276+
})
277+
278+
it("should include reasoning effort for grok-4.5 with default high effort", async () => {
279+
const grok45Handler = new XAIHandler({
280+
apiModelId: "grok-4.5",
281+
})
282+
283+
mockResponsesCreate.mockResolvedValueOnce(mockStream([]))
284+
285+
const stream = grok45Handler.createMessage("test prompt", [])
286+
await stream.next()
287+
288+
expect(mockResponsesCreate).toHaveBeenCalledWith(
289+
expect.objectContaining({
290+
model: "grok-4.5",
291+
reasoning: expect.objectContaining({
292+
effort: "high",
293+
}),
294+
}),
295+
)
296+
})
297+
298+
it("should include reasoning effort for grok-4.5 with custom low effort", async () => {
299+
const grok45Handler = new XAIHandler({
300+
apiModelId: "grok-4.5",
301+
reasoningEffort: "low",
302+
})
303+
304+
mockResponsesCreate.mockResolvedValueOnce(mockStream([]))
305+
306+
const stream = grok45Handler.createMessage("test prompt", [])
307+
await stream.next()
308+
309+
expect(mockResponsesCreate).toHaveBeenCalledWith(
310+
expect.objectContaining({
311+
model: "grok-4.5",
312+
reasoning: expect.objectContaining({
313+
effort: "low",
273314
}),
274315
}),
275316
)

src/api/providers/xai.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,11 @@ export class XAIHandler extends BaseProvider implements SingleCompletionHandler
120120
requestBody.parallel_tool_calls = metadata?.parallelToolCalls ?? true
121121
}
122122

123-
// Pass reasoning effort for models that support it (e.g., mini models)
123+
// Pass reasoning effort for models that support it (e.g., grok-4.5, grok-3-mini).
124+
// The xAI Responses API uses `reasoning: { effort }` format (not `reasoning_effort`
125+
// which is the Chat Completions format), so we convert from the OpenAI params shape.
124126
if (model.reasoning) {
125-
requestBody.reasoning = model.reasoning
127+
requestBody.reasoning = { effort: model.reasoning.reasoning_effort }
126128
}
127129

128130
let stream: AsyncIterable<any>

webview-ui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
"i18next": "^25.0.0",
5151
"katex": "^0.16.11",
5252
"lru-cache": "^11.1.0",
53-
"lucide-react": "^0.577.0",
53+
"lucide-react": "^1.18.0",
5454
"mermaid": "^11.4.1",
5555
"posthog-js": "^1.227.2",
5656
"pretty-bytes": "^7.0.0",

webview-ui/src/components/chat/TaskHeader.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { memo, useRef, useState, useMemo } from "react"
22
import { useTranslation } from "react-i18next"
3-
import { ChevronUp, ChevronDown, HardDriveDownload, HardDriveUpload, FoldVertical, ArrowLeft } from "lucide-react"
3+
import { ChevronUp, ChevronDown, HardDriveDownload, HardDriveUpload, ListChevronsDownUp, ArrowLeft } from "lucide-react"
44
import prettyBytes from "pretty-bytes"
55

66
import type { ClineMessage } from "@roo-code/types"
@@ -82,7 +82,7 @@ const TaskHeader = ({
8282
const condenseButton = (
8383
<LucideIconButton
8484
title={t("chat:task.condenseContext")}
85-
icon={FoldVertical}
85+
icon={ListChevronsDownUp}
8686
disabled={buttonsDisabled}
8787
onClick={() => currentTaskItem && handleCondenseContext(currentTaskItem.id)}
8888
/>
@@ -275,6 +275,16 @@ const TaskHeader = ({
275275
</>
276276
)}
277277
</div>
278+
<div
279+
className="flex items-center gap-1 ml-8 w-60 min-w-[120px] shrink"
280+
onClick={(e) => e.stopPropagation()}>
281+
<ContextWindowProgress
282+
contextWindow={contextWindow}
283+
contextTokens={contextTokens || 0}
284+
maxTokens={maxTokens ?? undefined}
285+
/>
286+
{condenseButton}
287+
</div>
278288
</div>
279289
)}
280290
{/* Expanded state: Show task text and images */}
@@ -315,7 +325,7 @@ const TaskHeader = ({
315325
<ContextWindowProgress
316326
contextWindow={contextWindow}
317327
contextTokens={contextTokens || 0}
318-
maxTokens={maxTokens || undefined}
328+
maxTokens={maxTokens ?? undefined}
319329
/>
320330
{condenseButton}
321331
</div>

webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -133,15 +133,22 @@ describe("TaskHeader", () => {
133133
expect(screen.queryByText(/\$/)).not.toBeInTheDocument()
134134
})
135135

136+
it("should render the condense context button in the collapsed state", () => {
137+
renderTaskHeader()
138+
// Button is visible without expanding the task header
139+
const buttons = screen.getAllByRole("button")
140+
const condenseButton = buttons.find((button) => button.querySelector("svg.lucide-list-chevrons-down-up"))
141+
expect(condenseButton).toBeDefined()
142+
expect(condenseButton?.querySelector("svg")).toBeInTheDocument()
143+
})
144+
136145
it("should render the condense context button when expanded", () => {
137146
renderTaskHeader()
138-
// First click to expand the task header
139147
const taskHeader = screen.getByText("Test task")
140148
fireEvent.click(taskHeader)
141149

142-
// Now find the condense button in the expanded state
143150
const buttons = screen.getAllByRole("button")
144-
const condenseButton = buttons.find((button) => button.querySelector("svg.lucide-fold-vertical"))
151+
const condenseButton = buttons.find((button) => button.querySelector("svg.lucide-list-chevrons-down-up"))
145152
expect(condenseButton).toBeDefined()
146153
expect(condenseButton?.querySelector("svg")).toBeInTheDocument()
147154
})
@@ -150,29 +157,24 @@ describe("TaskHeader", () => {
150157
const handleCondenseContext = vi.fn()
151158
renderTaskHeader({ handleCondenseContext })
152159

153-
// First click to expand the task header
154-
const taskHeader = screen.getByText("Test task")
155-
fireEvent.click(taskHeader)
156-
157-
// Find the button that contains the FoldVertical icon
160+
// Button is clickable in collapsed state without expanding first
158161
const buttons = screen.getAllByRole("button")
159-
const condenseButton = buttons.find((button) => button.querySelector("svg.lucide-fold-vertical"))
162+
const condenseButton = buttons.find((button) => button.querySelector("svg.lucide-list-chevrons-down-up"))
160163
expect(condenseButton).toBeDefined()
161164
fireEvent.click(condenseButton!)
162165
expect(handleCondenseContext).toHaveBeenCalledWith("test-task-id")
166+
// Clicking the condense button must not expand the header (stopPropagation guard).
167+
// The expanded state renders the "chat:task.title" label, which stays absent while collapsed.
168+
expect(screen.queryByText("chat:task.title")).not.toBeInTheDocument()
163169
})
164170

165171
it("should disable the condense context button when buttonsDisabled is true", () => {
166172
const handleCondenseContext = vi.fn()
167173
renderTaskHeader({ buttonsDisabled: true, handleCondenseContext })
168174

169-
// First click to expand the task header
170-
const taskHeader = screen.getByText("Test task")
171-
fireEvent.click(taskHeader)
172-
173-
// Find the button that contains the FoldVertical icon
175+
// Button is disabled in collapsed state without expanding first
174176
const buttons = screen.getAllByRole("button")
175-
const condenseButton = buttons.find((button) => button.querySelector("svg.lucide-fold-vertical"))
177+
const condenseButton = buttons.find((button) => button.querySelector("svg.lucide-list-chevrons-down-up"))
176178
expect(condenseButton).toBeDefined()
177179
expect(condenseButton).toBeDisabled()
178180
fireEvent.click(condenseButton!)

webview-ui/src/components/chat/context-management/CondensationResultRow.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useState } from "react"
22
import { useTranslation } from "react-i18next"
33
import { VSCodeBadge } from "@vscode/webview-ui-toolkit/react"
4-
import { FoldVertical } from "lucide-react"
4+
import { ListChevronsDownUp } from "lucide-react"
55

66
import type { ContextCondense } from "@roo-code/types"
77

@@ -32,7 +32,7 @@ export function CondensationResultRow({ data }: CondensationResultRowProps) {
3232
className="flex items-center justify-between cursor-pointer select-none"
3333
onClick={() => setIsExpanded(!isExpanded)}>
3434
<div className="flex items-center gap-2 flex-grow">
35-
<FoldVertical size={16} className="text-vscode-foreground" />
35+
<ListChevronsDownUp size={16} className="text-vscode-foreground" />
3636
<span className="font-bold text-vscode-foreground">
3737
{t("chat:contextManagement.condensation.title")}
3838
</span>

webview-ui/src/components/chat/context-management/TruncationResultRow.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useState } from "react"
22
import { useTranslation } from "react-i18next"
3-
import { FoldVertical } from "lucide-react"
3+
import { ScissorsLineDashed } from "lucide-react"
44

55
import type { ContextTruncation } from "@roo-code/types"
66

@@ -33,7 +33,7 @@ export function TruncationResultRow({ data }: TruncationResultRowProps) {
3333
className="flex items-center justify-between cursor-pointer select-none"
3434
onClick={() => setIsExpanded(!isExpanded)}>
3535
<div className="flex items-center gap-2 flex-grow">
36-
<FoldVertical size={16} className="text-vscode-foreground" />
36+
<ScissorsLineDashed size={16} className="text-vscode-foreground" />
3737
<span className="font-bold text-vscode-foreground">
3838
{t("chat:contextManagement.truncation.title")}
3939
</span>

0 commit comments

Comments
 (0)