mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: enable Claude Code provider to run natively on Windows (#5615)
This commit is contained in:
parent
e0196320b5
commit
824c49487b
2 changed files with 63 additions and 48 deletions
|
|
@ -1,4 +1,9 @@
|
|||
import { describe, test, expect, vi, beforeEach } from "vitest"
|
||||
import { describe, test, expect, vi, beforeEach, afterEach } from "vitest"
|
||||
|
||||
// Mock os module
|
||||
vi.mock("os", () => ({
|
||||
platform: vi.fn(() => "darwin"), // Default to non-Windows
|
||||
}))
|
||||
|
||||
// Mock vscode workspace
|
||||
vi.mock("vscode", () => ({
|
||||
|
|
@ -118,56 +123,53 @@ describe("runClaudeCode", () => {
|
|||
expect(typeof result[Symbol.asyncIterator]).toBe("function")
|
||||
})
|
||||
|
||||
test("should use stdin instead of command line arguments for messages", async () => {
|
||||
test("should handle platform-specific stdin behavior", async () => {
|
||||
const { runClaudeCode } = await import("../run")
|
||||
const messages = [{ role: "user" as const, content: "Hello world!" }]
|
||||
const systemPrompt = "You are a helpful assistant"
|
||||
const options = {
|
||||
systemPrompt: "You are a helpful assistant",
|
||||
systemPrompt,
|
||||
messages,
|
||||
}
|
||||
|
||||
const generator = runClaudeCode(options)
|
||||
// Test on Windows
|
||||
const os = await import("os")
|
||||
vi.mocked(os.platform).mockReturnValue("win32")
|
||||
|
||||
// Consume the generator to completion
|
||||
const generator = runClaudeCode(options)
|
||||
const results = []
|
||||
for await (const chunk of generator) {
|
||||
results.push(chunk)
|
||||
}
|
||||
|
||||
// Verify execa was called with correct arguments (no JSON.stringify(messages) in args)
|
||||
expect(mockExeca).toHaveBeenCalledWith(
|
||||
"claude",
|
||||
expect.arrayContaining([
|
||||
"-p",
|
||||
"--system-prompt",
|
||||
"You are a helpful assistant",
|
||||
"--verbose",
|
||||
"--output-format",
|
||||
"stream-json",
|
||||
"--disallowedTools",
|
||||
expect.any(String),
|
||||
"--max-turns",
|
||||
"1",
|
||||
]),
|
||||
expect.objectContaining({
|
||||
stdin: "pipe",
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
}),
|
||||
)
|
||||
|
||||
// Verify the arguments do NOT contain the stringified messages
|
||||
// On Windows, should NOT have --system-prompt in args
|
||||
const [, args] = mockExeca.mock.calls[0]
|
||||
expect(args).not.toContain(JSON.stringify(messages))
|
||||
expect(args).not.toContain("--system-prompt")
|
||||
|
||||
// Verify messages were written to stdin with callback
|
||||
// Should pass both system prompt and messages via stdin
|
||||
const expectedStdinData = JSON.stringify({ systemPrompt, messages })
|
||||
expect(mockStdin.write).toHaveBeenCalledWith(expectedStdinData, "utf8", expect.any(Function))
|
||||
|
||||
// Reset mocks for non-Windows test
|
||||
vi.clearAllMocks()
|
||||
mockExeca.mockReturnValue(createMockProcess())
|
||||
|
||||
// Test on non-Windows
|
||||
vi.mocked(os.platform).mockReturnValue("darwin")
|
||||
|
||||
const generator2 = runClaudeCode(options)
|
||||
const results2 = []
|
||||
for await (const chunk of generator2) {
|
||||
results2.push(chunk)
|
||||
}
|
||||
|
||||
// On non-Windows, should have --system-prompt in args
|
||||
const [, args2] = mockExeca.mock.calls[0]
|
||||
expect(args2).toContain("--system-prompt")
|
||||
expect(args2).toContain(systemPrompt)
|
||||
|
||||
// Should only pass messages via stdin
|
||||
expect(mockStdin.write).toHaveBeenCalledWith(JSON.stringify(messages), "utf8", expect.any(Function))
|
||||
expect(mockStdin.end).toHaveBeenCalled()
|
||||
|
||||
// Verify we got the expected mock output
|
||||
expect(results).toHaveLength(2)
|
||||
expect(results[0]).toEqual({ type: "text", text: "Hello" })
|
||||
expect(results[1]).toEqual({ type: "text", text: " world" })
|
||||
})
|
||||
|
||||
test("should include model parameter when provided", async () => {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { execa } from "execa"
|
|||
import { ClaudeCodeMessage } from "./types"
|
||||
import readline from "readline"
|
||||
import { CLAUDE_CODE_DEFAULT_MAX_OUTPUT_TOKENS } from "@roo-code/types"
|
||||
import * as os from "os"
|
||||
|
||||
const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0)
|
||||
|
||||
|
|
@ -118,11 +119,17 @@ function runProcess({
|
|||
maxOutputTokens,
|
||||
}: ClaudeCodeOptions & { maxOutputTokens?: number }) {
|
||||
const claudePath = path || "claude"
|
||||
const isWindows = os.platform() === "win32"
|
||||
|
||||
const args = [
|
||||
"-p",
|
||||
"--system-prompt",
|
||||
systemPrompt,
|
||||
// Build args based on platform
|
||||
const args = ["-p"]
|
||||
|
||||
// Pass system prompt as flag on non-Windows, via stdin on Windows (avoids cmd length limits)
|
||||
if (!isWindows) {
|
||||
args.push("--system-prompt", systemPrompt)
|
||||
}
|
||||
|
||||
args.push(
|
||||
"--verbose",
|
||||
"--output-format",
|
||||
"stream-json",
|
||||
|
|
@ -131,7 +138,7 @@ function runProcess({
|
|||
// Roo Code will handle recursive calls
|
||||
"--max-turns",
|
||||
"1",
|
||||
]
|
||||
)
|
||||
|
||||
if (modelId) {
|
||||
args.push("--model", modelId)
|
||||
|
|
@ -154,16 +161,22 @@ function runProcess({
|
|||
timeout: CLAUDE_CODE_TIMEOUT,
|
||||
})
|
||||
|
||||
// Write messages to stdin after process is spawned
|
||||
// This avoids the E2BIG error on Linux when passing large messages as command line arguments
|
||||
// Linux has a per-argument limit of ~128KiB for execve() system calls
|
||||
const messagesJson = JSON.stringify(messages)
|
||||
// Prepare stdin data: Windows gets both system prompt & messages (avoids 8191 char limit),
|
||||
// other platforms get messages only (avoids Linux E2BIG error from ~128KiB execve limit)
|
||||
let stdinData: string
|
||||
if (isWindows) {
|
||||
stdinData = JSON.stringify({
|
||||
systemPrompt,
|
||||
messages,
|
||||
})
|
||||
} else {
|
||||
stdinData = JSON.stringify(messages)
|
||||
}
|
||||
|
||||
// Use setImmediate to ensure the process has been spawned before writing to stdin
|
||||
// This prevents potential race conditions where stdin might not be ready
|
||||
// Use setImmediate to ensure process is spawned before writing (prevents stdin race conditions)
|
||||
setImmediate(() => {
|
||||
try {
|
||||
child.stdin.write(messagesJson, "utf8", (error) => {
|
||||
child.stdin.write(stdinData, "utf8", (error: Error | null | undefined) => {
|
||||
if (error) {
|
||||
console.error("Error writing to Claude Code stdin:", error)
|
||||
child.kill()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue