Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ module.exports = {
"^default-shell$": "<rootDir>/src/__mocks__/default-shell.js",
"^os-name$": "<rootDir>/src/__mocks__/os-name.js",
"^strip-bom$": "<rootDir>/src/__mocks__/strip-bom.js",
"^execa$": "<rootDir>/src/__mocks__/execa.js",
"^@roo/(.*)$": "<rootDir>/src/$1",
"^@src/(.*)$": "<rootDir>/webview-ui/src/$1",
},
Expand Down
76 changes: 24 additions & 52 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@
"monaco-vscode-textmate-theme-converter": "^0.1.7",
"node-cache": "^5.1.2",
"node-ipc": "^12.0.0",
"openai": "^4.78.1",
"openai": "^6.42.0",
"os-name": "^6.0.0",
"p-wait-for": "^5.0.2",
"pdf-parse": "^1.1.1",
Expand All @@ -435,7 +435,7 @@
"vscode-material-icons": "^0.1.1",
"web-tree-sitter": "^0.22.6",
"workerpool": "^9.2.0",
"zod": "^3.23.8"
"zod": "^3.25.76"
},
"devDependencies": {
"@changesets/cli": "^2.27.10",
Expand Down
38 changes: 38 additions & 0 deletions src/__mocks__/execa.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
class ExecaError extends Error {
constructor(message = "Command failed", options = {}) {
super(message)
this.name = "ExecaError"
this.exitCode = options.exitCode ?? 1
this.signal = options.signal
}
}

const mockFn = (implementation) => (typeof jest === "undefined" ? implementation : jest.fn(implementation))

const createSubprocess = () => {
const subprocess = Promise.resolve({
exitCode: 0,
stdout: "",
stderr: "",
all: "",
})

subprocess.pid = 1234
subprocess.iterable = mockFn(async function* () {})
subprocess.kill = mockFn(() => true)

return subprocess
}

const execa = mockFn((firstArg) => {
if (firstArg && typeof firstArg === "object" && !Array.isArray(firstArg)) {
return mockFn(() => createSubprocess())
}

return createSubprocess()
})

module.exports = {
execa,
ExecaError,
}
4 changes: 4 additions & 0 deletions src/__mocks__/vscode.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ const vscode = {
Workspace: 2,
WorkspaceFolder: 3,
},
CodeActionKind: {
QuickFix: { value: "quickfix" },
RefactorRewrite: { value: "refactor.rewrite" },
},
Position: class {
constructor(line, character) {
this.line = line
Expand Down
8 changes: 6 additions & 2 deletions src/api/providers/__tests__/deepseek.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,17 @@ describe("DeepSeekHandler", () => {
...mockOptions,
apiModelId: "invalid-model",
})
const defaultHandler = new DeepSeekHandler({
...mockOptions,
apiModelId: undefined,
})
const model = handlerWithInvalidModel.getModel()
expect(model.id).toBe("invalid-model") // Returns provided ID
expect(model.info).toBeDefined()
// With the current implementation, it's the same object reference when using default model info
expect(model.info).toBe(handler.getModel().info)
expect(model.info).toBe(defaultHandler.getModel().info)
// Should have the same base properties
expect(model.info.contextWindow).toBe(handler.getModel().info.contextWindow)
expect(model.info.contextWindow).toBe(defaultHandler.getModel().info.contextWindow)
// And should have supportsPromptCache set to true
expect(model.info.supportsPromptCache).toBe(true)
})
Expand Down
2 changes: 1 addition & 1 deletion src/api/providers/__tests__/openai-native.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ describe("OpenAiNativeHandler", () => {
openAiNativeApiKey: "test-api-key",
})
const modelInfo = handlerWithoutModel.getModel()
expect(modelInfo.id).toBe("gpt-4.1") // Default model
expect(modelInfo.id).toBe("gpt-5.5") // Default model
expect(modelInfo.info).toBeDefined()
})
})
Expand Down
16 changes: 8 additions & 8 deletions src/api/providers/__tests__/xai.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,17 @@ describe("XAIHandler", () => {
})

test("should return specified model when valid model is provided", () => {
const testModelId = "grok-2-latest"
const testModelId = "grok-build-0.1"
const handlerWithModel = new XAIHandler({ apiModelId: testModelId })
const model = handlerWithModel.getModel()

expect(model.id).toBe(testModelId)
expect(model.info).toEqual(xaiModels[testModelId])
})

test("should include reasoning_effort parameter for mini models", async () => {
const miniModelHandler = new XAIHandler({
apiModelId: "grok-3-mini-beta",
test("should include reasoning_effort parameter for Grok 4.3", async () => {
const reasoningModelHandler = new XAIHandler({
apiModelId: "grok-4.3",
reasoningEffort: "high",
})

Expand All @@ -87,7 +87,7 @@ describe("XAIHandler", () => {
})

// Start generating a message
const messageGenerator = miniModelHandler.createMessage("test prompt", [])
const messageGenerator = reasoningModelHandler.createMessage("test prompt", [])
await messageGenerator.next() // Start the generator

// Check that reasoning_effort was included
Expand All @@ -98,9 +98,9 @@ describe("XAIHandler", () => {
)
})

test("should not include reasoning_effort parameter for non-mini models", async () => {
test("should not include reasoning_effort parameter for non-reasoning models", async () => {
const regularModelHandler = new XAIHandler({
apiModelId: "grok-2-latest",
apiModelId: "grok-build-0.1",
reasoningEffort: "high",
})

Expand Down Expand Up @@ -254,7 +254,7 @@ describe("XAIHandler", () => {

test("createMessage should pass correct parameters to OpenAI client", async () => {
// Setup a handler with specific model
const modelId = "grok-2-latest"
const modelId = "grok-build-0.1"
const modelInfo = xaiModels[modelId]
const handlerWithModel = new XAIHandler({ apiModelId: modelId })

Expand Down
24 changes: 21 additions & 3 deletions src/api/providers/anthropic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ import { BaseProvider } from "./base-provider"
import { ANTHROPIC_DEFAULT_MAX_TOKENS } from "./constants"
import { SingleCompletionHandler, getModelParams } from "../index"

const ANTHROPIC_MODELS_WITHOUT_SAMPLING_PARAMS = new Set(["claude-fable-5", "claude-opus-4-8", "claude-sonnet-4-6"])

function omitsSamplingParams(modelId: string) {
return ANTHROPIC_MODELS_WITHOUT_SAMPLING_PARAMS.has(modelId) || modelId.startsWith("claude-opus-4-7")
}

export class AnthropicHandler extends BaseProvider implements SingleCompletionHandler {
private options: ApiHandlerOptions
private client: Anthropic
Expand All @@ -34,8 +40,16 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
let stream: AnthropicStream<Anthropic.Messages.RawMessageStreamEvent>
const cacheControl: CacheControlEphemeral = { type: "ephemeral" }
let { id: modelId, maxTokens, thinking, temperature, virtualId } = this.getModel()
let requestTemperature: number | undefined = temperature
if (omitsSamplingParams(modelId)) {
requestTemperature = undefined
}

switch (modelId) {
case "claude-fable-5":
case "claude-opus-4-8":
case "claude-sonnet-4-6":
case "claude-haiku-4-5-20251001":
case "claude-3-7-sonnet-20250219":
case "claude-3-5-sonnet-20241022":
case "claude-3-5-haiku-20241022":
Expand Down Expand Up @@ -63,7 +77,7 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
{
model: modelId,
max_tokens: maxTokens ?? ANTHROPIC_DEFAULT_MAX_TOKENS,
temperature,
temperature: requestTemperature,
thinking,
// Setting cache breakpoint for system prompt so new tasks can reuse it.
system: [{ text: systemPrompt, type: "text", cache_control: cacheControl }],
Expand Down Expand Up @@ -129,7 +143,7 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
stream = (await this.client.messages.create({
model: modelId,
max_tokens: maxTokens ?? ANTHROPIC_DEFAULT_MAX_TOKENS,
temperature,
temperature: requestTemperature,
system: [{ text: systemPrompt, type: "text" }],
messages,
stream: true,
Expand Down Expand Up @@ -273,12 +287,16 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa

async completePrompt(prompt: string) {
let { id: model, temperature } = this.getModel()
let requestTemperature: number | undefined = temperature
if (omitsSamplingParams(model)) {
requestTemperature = undefined
}

const message = await this.client.messages.create({
model,
max_tokens: ANTHROPIC_DEFAULT_MAX_TOKENS,
thinking: undefined,
temperature,
temperature: requestTemperature,
messages: [{ role: "user", content: prompt }],
stream: false,
})
Expand Down
Loading
Loading