Skip to content

Commit 9d8813c

Browse files
hannesrudolphCopilotdaniel-lxsmrubens
authored andcommitted
feat: add Claude Code provider for local CLI integration (RooCodeInc#4864)
Co-authored-by: Copilot <[email protected]> Co-authored-by: Daniel <[email protected]> Co-authored-by: Daniel Riccio <[email protected]> Co-authored-by: Matt Rubens <[email protected]>
1 parent 87bce17 commit 9d8813c

File tree

39 files changed

+303
-0
lines changed

39 files changed

+303
-0
lines changed

packages/types/src/providers/claude-code.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { anthropicModels } from "./anthropic.js"
55
export type ClaudeCodeModelId = keyof typeof claudeCodeModels
66
export const claudeCodeDefaultModelId: ClaudeCodeModelId = "claude-sonnet-4-20250514"
77
export const claudeCodeModels = {
8+
<<<<<<< HEAD
89
"claude-sonnet-4-20250514": {
910
...anthropicModels["claude-sonnet-4-20250514"],
1011
supportsImages: false,
@@ -45,4 +46,11 @@ export const claudeCodeModels = {
4546
supportsReasoningBudget: false,
4647
requiredReasoningBudget: false,
4748
},
49+
=======
50+
"claude-sonnet-4-20250514": anthropicModels["claude-sonnet-4-20250514"],
51+
"claude-opus-4-20250514": anthropicModels["claude-opus-4-20250514"],
52+
"claude-3-7-sonnet-20250219": anthropicModels["claude-3-7-sonnet-20250219"],
53+
"claude-3-5-sonnet-20241022": anthropicModels["claude-3-5-sonnet-20241022"],
54+
"claude-3-5-haiku-20241022": anthropicModels["claude-3-5-haiku-20241022"],
55+
>>>>>>> 4fa735de3 (feat: add Claude Code provider for local CLI integration (#4864))
4856
} as const satisfies Record<string, ModelInfo>

src/api/providers/claude-code.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import { claudeCodeDefaultModelId, type ClaudeCodeModelId, claudeCodeModels } fr
33
import { type ApiHandler } from ".."
44
import { ApiStreamUsageChunk, type ApiStream } from "../transform/stream"
55
import { runClaudeCode } from "../../integrations/claude-code/run"
6+
<<<<<<< HEAD
67
import { filterMessagesForClaudeCode } from "../../integrations/claude-code/message-filter"
8+
=======
9+
import { ClaudeCodeMessage } from "../../integrations/claude-code/types"
10+
>>>>>>> 4fa735de3 (feat: add Claude Code provider for local CLI integration (#4864))
711
import { BaseProvider } from "./base-provider"
812
import { t } from "../../i18n"
913
import { ApiHandlerOptions } from "../../shared/api"
@@ -17,16 +21,51 @@ export class ClaudeCodeHandler extends BaseProvider implements ApiHandler {
1721
}
1822

1923
override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
24+
<<<<<<< HEAD
2025
// Filter out image blocks since Claude Code doesn't support them
2126
const filteredMessages = filterMessagesForClaudeCode(messages)
2227

2328
const claudeProcess = runClaudeCode({
2429
systemPrompt,
2530
messages: filteredMessages,
31+
=======
32+
const claudeProcess = runClaudeCode({
33+
systemPrompt,
34+
messages,
35+
>>>>>>> 4fa735de3 (feat: add Claude Code provider for local CLI integration (#4864))
2636
path: this.options.claudeCodePath,
2737
modelId: this.getModel().id,
2838
})
2939

40+
<<<<<<< HEAD
41+
=======
42+
const dataQueue: string[] = []
43+
let processError = null
44+
let errorOutput = ""
45+
let exitCode: number | null = null
46+
47+
claudeProcess.stdout.on("data", (data) => {
48+
const output = data.toString()
49+
const lines = output.split("\n").filter((line: string) => line.trim() !== "")
50+
51+
for (const line of lines) {
52+
dataQueue.push(line)
53+
}
54+
})
55+
56+
claudeProcess.stderr.on("data", (data) => {
57+
errorOutput += data.toString()
58+
})
59+
60+
claudeProcess.on("close", (code) => {
61+
exitCode = code
62+
})
63+
64+
claudeProcess.on("error", (error) => {
65+
processError = error
66+
})
67+
68+
>>>>>>> 4fa735de3 (feat: add Claude Code provider for local CLI integration (#4864))
3069
// Usage is included with assistant messages,
3170
// but cost is included in the result chunk
3271
let usage: ApiStreamUsageChunk = {
@@ -37,27 +76,62 @@ export class ClaudeCodeHandler extends BaseProvider implements ApiHandler {
3776
cacheWriteTokens: 0,
3877
}
3978

79+
<<<<<<< HEAD
4080
let isPaidUsage = true
4181

4282
for await (const chunk of claudeProcess) {
4383
if (typeof chunk === "string") {
4484
yield {
4585
type: "text",
4686
text: chunk,
87+
=======
88+
while (exitCode !== 0 || dataQueue.length > 0) {
89+
if (dataQueue.length === 0) {
90+
await new Promise((resolve) => setImmediate(resolve))
91+
}
92+
93+
if (exitCode !== null && exitCode !== 0) {
94+
if (errorOutput) {
95+
throw new Error(
96+
t("common:errors.claudeCode.processExitedWithError", {
97+
exitCode,
98+
output: errorOutput.trim(),
99+
}),
100+
)
101+
}
102+
throw new Error(t("common:errors.claudeCode.processExited", { exitCode }))
103+
}
104+
105+
const data = dataQueue.shift()
106+
if (!data) {
107+
continue
108+
}
109+
110+
const chunk = this.attemptParseChunk(data)
111+
112+
if (!chunk) {
113+
yield {
114+
type: "text",
115+
text: data || "",
116+
>>>>>>> 4fa735de3 (feat: add Claude Code provider for local CLI integration (#4864))
47117
}
48118

49119
continue
50120
}
51121

52122
if (chunk.type === "system" && chunk.subtype === "init") {
123+
<<<<<<< HEAD
53124
// Based on my tests, subscription usage sets the `apiKeySource` to "none"
54125
isPaidUsage = chunk.apiKeySource !== "none"
126+
=======
127+
>>>>>>> 4fa735de3 (feat: add Claude Code provider for local CLI integration (#4864))
55128
continue
56129
}
57130

58131
if (chunk.type === "assistant" && "message" in chunk) {
59132
const message = chunk.message
60133

134+
<<<<<<< HEAD
61135
if (message.stop_reason !== null) {
62136
const content = "text" in message.content[0] ? message.content[0] : undefined
63137

@@ -105,6 +179,28 @@ export class ClaudeCodeHandler extends BaseProvider implements ApiHandler {
105179
case "tool_use":
106180
console.error(`tool_use is not supported yet. Received: ${JSON.stringify(content)}`)
107181
break
182+
=======
183+
if (message.stop_reason !== null && message.stop_reason !== "tool_use") {
184+
const errorMessage =
185+
message.content[0]?.text ||
186+
t("common:errors.claudeCode.stoppedWithReason", { reason: message.stop_reason })
187+
188+
if (errorMessage.includes("Invalid model name")) {
189+
throw new Error(errorMessage + `\n\n${t("common:errors.claudeCode.apiKeyModelPlanMismatch")}`)
190+
}
191+
192+
throw new Error(errorMessage)
193+
}
194+
195+
for (const content of message.content) {
196+
if (content.type === "text") {
197+
yield {
198+
type: "text",
199+
text: content.text,
200+
}
201+
} else {
202+
console.warn("Unsupported content type:", content.type)
203+
>>>>>>> 4fa735de3 (feat: add Claude Code provider for local CLI integration (#4864))
108204
}
109205
}
110206

@@ -118,10 +214,23 @@ export class ClaudeCodeHandler extends BaseProvider implements ApiHandler {
118214
}
119215

120216
if (chunk.type === "result" && "result" in chunk) {
217+
<<<<<<< HEAD
121218
usage.totalCost = isPaidUsage ? chunk.total_cost_usd : 0
122219

123220
yield usage
124221
}
222+
=======
223+
// Only use the cost from the CLI if provided
224+
// Don't calculate cost as it may be $0 for subscription users
225+
usage.totalCost = chunk.cost_usd ?? 0
226+
227+
yield usage
228+
}
229+
230+
if (processError) {
231+
throw processError
232+
}
233+
>>>>>>> 4fa735de3 (feat: add Claude Code provider for local CLI integration (#4864))
125234
}
126235
}
127236

@@ -138,10 +247,19 @@ export class ClaudeCodeHandler extends BaseProvider implements ApiHandler {
138247
}
139248
}
140249

250+
<<<<<<< HEAD
141251
private attemptParse(str: string) {
142252
try {
143253
return JSON.parse(str)
144254
} catch (err) {
255+
=======
256+
// TODO: Validate instead of parsing
257+
private attemptParseChunk(data: string): ClaudeCodeMessage | null {
258+
try {
259+
return JSON.parse(data)
260+
} catch (error) {
261+
console.error("Error parsing chunk:", error)
262+
>>>>>>> 4fa735de3 (feat: add Claude Code provider for local CLI integration (#4864))
145263
return null
146264
}
147265
}

src/i18n/locales/ca/common.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"description": "Ruta opcional a la teva CLI de Claude Code. Per defecte 'claude' si no s'estableix.",
121121
"placeholder": "Per defecte: claude"
122122
}
123+
<<<<<<< HEAD
123124
}
124125
},
125126
"customModes": {
@@ -132,6 +133,8 @@
132133
"resetFailed": "Error en restablir els modes personalitzats: {{error}}",
133134
"modeNotFound": "Error d'escriptura: Mode no trobat",
134135
"noWorkspaceForProject": "No s'ha trobat cap carpeta d'espai de treball per al mode específic del projecte"
136+
=======
137+
>>>>>>> 4fa735de3 (feat: add Claude Code provider for local CLI integration (#4864))
135138
}
136139
},
137140
"mdm": {

src/i18n/locales/de/common.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"description": "Optionaler Pfad zu deiner Claude Code CLI. Standardmäßig 'claude', falls nicht festgelegt.",
121121
"placeholder": "Standard: claude"
122122
}
123+
<<<<<<< HEAD
123124
}
124125
},
125126
"customModes": {
@@ -132,6 +133,8 @@
132133
"resetFailed": "Fehler beim Zurücksetzen der benutzerdefinierten Modi: {{error}}",
133134
"modeNotFound": "Schreibfehler: Modus nicht gefunden",
134135
"noWorkspaceForProject": "Kein Arbeitsbereich-Ordner für projektspezifischen Modus gefunden"
136+
=======
137+
>>>>>>> 4fa735de3 (feat: add Claude Code provider for local CLI integration (#4864))
135138
}
136139
},
137140
"mdm": {

src/i18n/locales/es/common.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"description": "Ruta opcional a tu CLI de Claude Code. Por defecto 'claude' si no se establece.",
121121
"placeholder": "Por defecto: claude"
122122
}
123+
<<<<<<< HEAD
123124
}
124125
},
125126
"customModes": {
@@ -132,6 +133,8 @@
132133
"resetFailed": "Error al restablecer modos personalizados: {{error}}",
133134
"modeNotFound": "Error de escritura: Modo no encontrado",
134135
"noWorkspaceForProject": "No se encontró carpeta de espacio de trabajo para modo específico del proyecto"
136+
=======
137+
>>>>>>> 4fa735de3 (feat: add Claude Code provider for local CLI integration (#4864))
135138
}
136139
},
137140
"mdm": {

src/i18n/locales/fr/common.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"description": "Chemin optionnel vers votre CLI Claude Code. Par défaut 'claude' si non défini.",
121121
"placeholder": "Par défaut : claude"
122122
}
123+
<<<<<<< HEAD
123124
}
124125
},
125126
"customModes": {
@@ -132,6 +133,8 @@
132133
"resetFailed": "Échec de la réinitialisation des modes personnalisés : {{error}}",
133134
"modeNotFound": "Erreur d'écriture : Mode non trouvé",
134135
"noWorkspaceForProject": "Aucun dossier d'espace de travail trouvé pour le mode spécifique au projet"
136+
=======
137+
>>>>>>> 4fa735de3 (feat: add Claude Code provider for local CLI integration (#4864))
135138
}
136139
},
137140
"mdm": {

src/i18n/locales/hi/common.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"description": "आपके क्लाउड कोड CLI का वैकल्पिक पाथ। सेट न होने पर डिफ़ॉल्ट रूप से 'claude'。",
121121
"placeholder": "डिफ़ॉल्ट: claude"
122122
}
123+
<<<<<<< HEAD
123124
}
124125
},
125126
"customModes": {
@@ -132,6 +133,8 @@
132133
"resetFailed": "कस्टम मोड रीसेट विफल: {{error}}",
133134
"modeNotFound": "लेखन त्रुटि: मोड नहीं मिला",
134135
"noWorkspaceForProject": "प्रोजेक्ट-विशिष्ट मोड के लिए वर्कस्पेस फ़ोल्डर नहीं मिला"
136+
=======
137+
>>>>>>> 4fa735de3 (feat: add Claude Code provider for local CLI integration (#4864))
135138
}
136139
},
137140
"mdm": {

src/i18n/locales/id/common.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
}
123123
}
124124
},
125+
<<<<<<< HEAD
125126
"customModes": {
126127
"errors": {
127128
"yamlParseError": "YAML tidak valid dalam file .roomodes pada baris {{line}}. Silakan periksa:\n• Indentasi yang benar (gunakan spasi, bukan tab)\n• Tanda kutip dan kurung yang cocok\n• Sintaks YAML yang valid",
@@ -134,6 +135,8 @@
134135
"noWorkspaceForProject": "Tidak ditemukan folder workspace untuk mode khusus proyek"
135136
}
136137
},
138+
=======
139+
>>>>>>> 4fa735de3 (feat: add Claude Code provider for local CLI integration (#4864))
137140
"mdm": {
138141
"errors": {
139142
"cloud_auth_required": "Organisasi kamu memerlukan autentikasi Roo Code Cloud. Silakan masuk untuk melanjutkan.",

src/i18n/locales/it/common.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"description": "Percorso opzionale alla tua CLI Claude Code. Predefinito 'claude' se non impostato.",
121121
"placeholder": "Predefinito: claude"
122122
}
123+
<<<<<<< HEAD
123124
}
124125
},
125126
"customModes": {
@@ -132,6 +133,8 @@
132133
"resetFailed": "Reset modalità personalizzate fallito: {{error}}",
133134
"modeNotFound": "Errore di scrittura: Modalità non trovata",
134135
"noWorkspaceForProject": "Nessuna cartella workspace trovata per la modalità specifica del progetto"
136+
=======
137+
>>>>>>> 4fa735de3 (feat: add Claude Code provider for local CLI integration (#4864))
135138
}
136139
},
137140
"mdm": {

src/i18n/locales/ja/common.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"description": "Claude Code CLI へのオプションのパス。設定されていない場合は、デフォルトで「claude」になります。",
121121
"placeholder": "デフォルト: claude"
122122
}
123+
<<<<<<< HEAD
123124
}
124125
},
125126
"customModes": {
@@ -132,6 +133,8 @@
132133
"resetFailed": "カスタムモードのリセットに失敗しました:{{error}}",
133134
"modeNotFound": "書き込みエラー:モードが見つかりません",
134135
"noWorkspaceForProject": "プロジェクト固有モード用のワークスペースフォルダーが見つかりません"
136+
=======
137+
>>>>>>> 4fa735de3 (feat: add Claude Code provider for local CLI integration (#4864))
135138
}
136139
},
137140
"mdm": {

0 commit comments

Comments
 (0)