Skip to content

Commit 2c9a790

Browse files
committed
Fix JSON parse error
1 parent 0c8e03c commit 2c9a790

File tree

4 files changed

+56
-51
lines changed

4 files changed

+56
-51
lines changed

src/api/providers/anthropic-vertex.ts

Lines changed: 31 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,16 @@
11
import { Anthropic } from "@anthropic-ai/sdk"
22
import { AnthropicVertex } from "@anthropic-ai/vertex-sdk"
3-
import { Stream as AnthropicStream } from "@anthropic-ai/sdk/streaming"
4-
import { GoogleAuth } from "google-auth-library"
3+
import { GoogleAuth, JWTInput } from "google-auth-library"
54

65
import { ApiHandlerOptions, ModelInfo, vertexDefaultModelId, VertexModelId, vertexModels } from "../../shared/api"
76
import { ApiStream } from "../transform/stream"
7+
import { safeJsonParse } from "../../shared/safeJsonParse"
88

99
import { getModelParams, SingleCompletionHandler } from "../index"
1010
import { BaseProvider } from "./base-provider"
1111
import { ANTHROPIC_DEFAULT_MAX_TOKENS } from "./constants"
1212
import { formatMessageForCache } from "../transform/vertex-caching"
1313

14-
interface VertexUsage {
15-
input_tokens?: number
16-
output_tokens?: number
17-
cache_creation_input_tokens?: number
18-
cache_read_input_tokens?: number
19-
}
20-
21-
interface VertexMessageResponse {
22-
content: Array<{ type: "text"; text: string }>
23-
}
24-
25-
interface VertexMessageStreamEvent {
26-
type: "message_start" | "message_delta" | "content_block_start" | "content_block_delta"
27-
message?: {
28-
usage: VertexUsage
29-
}
30-
usage?: {
31-
output_tokens: number
32-
}
33-
content_block?: { type: "text"; text: string } | { type: "thinking"; thinking: string }
34-
index?: number
35-
delta?: { type: "text_delta"; text: string } | { type: "thinking_delta"; thinking: string }
36-
}
37-
3814
// https://docs.anthropic.com/en/api/claude-on-vertex-ai
3915
export class AnthropicVertexHandler extends BaseProvider implements SingleCompletionHandler {
4016
protected options: ApiHandlerOptions
@@ -55,7 +31,7 @@ export class AnthropicVertexHandler extends BaseProvider implements SingleComple
5531
region,
5632
googleAuth: new GoogleAuth({
5733
scopes: ["https://www.googleapis.com/auth/cloud-platform"],
58-
credentials: JSON.parse(this.options.vertexJsonCredentials),
34+
credentials: safeJsonParse<JWTInput>(this.options.vertexJsonCredentials, undefined),
5935
}),
6036
})
6137
} else if (this.options.vertexKeyFile) {
@@ -73,14 +49,18 @@ export class AnthropicVertexHandler extends BaseProvider implements SingleComple
7349
}
7450

7551
override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
76-
const model = this.getModel()
77-
let { id, temperature, maxTokens, thinking } = model
78-
const useCache = model.info.supportsPromptCache
52+
let {
53+
id,
54+
info: { supportsPromptCache },
55+
temperature,
56+
maxTokens,
57+
thinking,
58+
} = this.getModel()
7959

8060
// Find indices of user messages that we want to cache
8161
// We only cache the last two user messages to stay within the 4-block limit
8262
// (1 block for system + 1 block each for last two user messages = 3 total)
83-
const userMsgIndices = useCache
63+
const userMsgIndices = supportsPromptCache
8464
? messages.reduce((acc, msg, i) => (msg.role === "user" ? [...acc, i] : acc), [] as number[])
8565
: []
8666

@@ -100,26 +80,25 @@ export class AnthropicVertexHandler extends BaseProvider implements SingleComple
10080
* This ensures we stay under the 4-block limit while maintaining effective caching
10181
* for the most relevant context.
10282
*/
103-
const params = {
83+
const params: Anthropic.Messages.MessageCreateParamsStreaming = {
10484
model: id,
105-
max_tokens: maxTokens,
85+
max_tokens: maxTokens ?? ANTHROPIC_DEFAULT_MAX_TOKENS,
10686
temperature,
10787
thinking,
10888
// Cache the system prompt if caching is enabled.
109-
system: useCache
89+
system: supportsPromptCache
11090
? [{ text: systemPrompt, type: "text" as const, cache_control: { type: "ephemeral" } }]
11191
: systemPrompt,
11292
messages: messages.map((message, index) => {
11393
// Only cache the last two user messages.
114-
const shouldCache = useCache && (index === lastUserMsgIndex || index === secondLastMsgUserIndex)
94+
const shouldCache =
95+
supportsPromptCache && (index === lastUserMsgIndex || index === secondLastMsgUserIndex)
11596
return formatMessageForCache(message, shouldCache)
11697
}),
11798
stream: true,
11899
}
119100

120-
const stream = (await this.client.messages.create(
121-
params as Anthropic.Messages.MessageCreateParamsStreaming,
122-
)) as unknown as AnthropicStream<VertexMessageStreamEvent>
101+
const stream = await this.client.messages.create(params)
123102

124103
for await (const chunk of stream) {
125104
switch (chunk.type) {
@@ -130,8 +109,8 @@ export class AnthropicVertexHandler extends BaseProvider implements SingleComple
130109
type: "usage",
131110
inputTokens: usage.input_tokens || 0,
132111
outputTokens: usage.output_tokens || 0,
133-
cacheWriteTokens: usage.cache_creation_input_tokens,
134-
cacheReadTokens: usage.cache_read_input_tokens,
112+
cacheWriteTokens: usage.cache_creation_input_tokens || undefined,
113+
cacheReadTokens: usage.cache_read_input_tokens || undefined,
135114
}
136115

137116
break
@@ -164,6 +143,7 @@ export class AnthropicVertexHandler extends BaseProvider implements SingleComple
164143
break
165144
}
166145
}
146+
167147
break
168148
}
169149
case "content_block_delta": {
@@ -177,6 +157,7 @@ export class AnthropicVertexHandler extends BaseProvider implements SingleComple
177157
break
178158
}
179159
}
160+
180161
break
181162
}
182163
}
@@ -203,27 +184,31 @@ export class AnthropicVertexHandler extends BaseProvider implements SingleComple
203184

204185
async completePrompt(prompt: string) {
205186
try {
206-
let { id, info, temperature, maxTokens, thinking } = this.getModel()
207-
const useCache = info.supportsPromptCache
187+
let {
188+
id,
189+
info: { supportsPromptCache },
190+
temperature,
191+
maxTokens = ANTHROPIC_DEFAULT_MAX_TOKENS,
192+
thinking,
193+
} = this.getModel()
208194

209195
const params: Anthropic.Messages.MessageCreateParamsNonStreaming = {
210196
model: id,
211-
max_tokens: maxTokens ?? ANTHROPIC_DEFAULT_MAX_TOKENS,
197+
max_tokens: maxTokens,
212198
temperature,
213199
thinking,
214-
system: "", // No system prompt needed for single completions.
215200
messages: [
216201
{
217202
role: "user",
218-
content: useCache
203+
content: supportsPromptCache
219204
? [{ type: "text" as const, text: prompt, cache_control: { type: "ephemeral" } }]
220205
: prompt,
221206
},
222207
],
223208
stream: false,
224209
}
225210

226-
const response = (await this.client.messages.create(params)) as unknown as VertexMessageResponse
211+
const response = await this.client.messages.create(params)
227212
const content = response.content[0]
228213

229214
if (content.type === "text") {

src/core/Cline.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -953,12 +953,14 @@ export class Cline extends EventEmitter<ClineEvents> {
953953

954954
if (mcpEnabled ?? true) {
955955
const provider = this.providerRef.deref()
956+
956957
if (!provider) {
957958
throw new Error("Provider reference lost during view transition")
958959
}
959960

960961
// Wait for MCP hub initialization through McpServerManager
961962
mcpHub = await McpServerManager.getInstance(provider.context, provider)
963+
962964
if (!mcpHub) {
963965
throw new Error("Failed to get MCP hub from server manager")
964966
}
@@ -980,12 +982,16 @@ export class Cline extends EventEmitter<ClineEvents> {
980982
browserToolEnabled,
981983
language,
982984
} = (await this.providerRef.deref()?.getState()) ?? {}
985+
983986
const { customModes } = (await this.providerRef.deref()?.getState()) ?? {}
987+
984988
const systemPrompt = await (async () => {
985989
const provider = this.providerRef.deref()
990+
986991
if (!provider) {
987992
throw new Error("Provider not available")
988993
}
994+
989995
return SYSTEM_PROMPT(
990996
provider.context,
991997
this.cwd,
@@ -1008,7 +1014,10 @@ export class Cline extends EventEmitter<ClineEvents> {
10081014
// If the previous API request's total token usage is close to the context window, truncate the conversation history to free up space for the new request
10091015
if (previousApiReqIndex >= 0) {
10101016
const previousRequest = this.clineMessages[previousApiReqIndex]?.text
1011-
if (!previousRequest) return
1017+
1018+
if (!previousRequest) {
1019+
return
1020+
}
10121021

10131022
const {
10141023
tokensIn = 0,
@@ -1135,11 +1144,14 @@ export class Cline extends EventEmitter<ClineEvents> {
11351144
"api_req_failed",
11361145
error.message ?? JSON.stringify(serializeError(error), null, 2),
11371146
)
1147+
11381148
if (response !== "yesButtonClicked") {
11391149
// this will never happen since if noButtonClicked, we will clear current task, aborting this instance
11401150
throw new Error("API request failed")
11411151
}
1152+
11421153
await this.say("api_req_retried")
1154+
11431155
// delegate generator output from the recursive call
11441156
yield* this.attemptApiRequest(previousApiReqIndex)
11451157
return
@@ -1903,8 +1915,13 @@ export class Cline extends EventEmitter<ClineEvents> {
19031915

19041916
return didEndLoop // will always be false for now
19051917
} catch (error) {
1906-
// this should never happen since the only thing that can throw an error is the attemptApiRequest, which is wrapped in a try catch that sends an ask where if noButtonClicked, will clear current task and destroy this instance. However to avoid unhandled promise rejection, we will end this loop which will end execution of this instance (see startTask)
1907-
return true // needs to be true so parent loop knows to end task
1918+
// This should never happen since the only thing that can throw an
1919+
// error is the attemptApiRequest, which is wrapped in a try catch
1920+
// that sends an ask where if noButtonClicked, will clear current
1921+
// task and destroy this instance. However to avoid unhandled
1922+
// promise rejection, we will end this loop which will end execution
1923+
// of this instance (see `startTask`).
1924+
return true // Needs to be true so parent loop knows to end task.
19081925
}
19091926
}
19101927

webview-ui/src/utils/json.ts renamed to src/shared/safeJsonParse.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
/**
22
* Safely parses JSON without crashing on invalid input
3+
*
34
* @param jsonString The string to parse
45
* @param defaultValue Value to return if parsing fails
56
* @returns Parsed JSON object or defaultValue if parsing fails
67
*/
78
export function safeJsonParse<T>(jsonString: string | null | undefined, defaultValue?: T): T | undefined {
8-
if (!jsonString) return defaultValue
9+
if (!jsonString) {
10+
return defaultValue
11+
}
912

1013
try {
1114
return JSON.parse(jsonString) as T

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import { VSCodeBadge, VSCodeButton } from "@vscode/webview-ui-toolkit/react"
66

77
import { ClineApiReqInfo, ClineAskUseMcpServer, ClineMessage, ClineSayTool } from "@roo/shared/ExtensionMessage"
88
import { splitCommandOutput, COMMAND_OUTPUT_STRING } from "@roo/shared/combineCommandSequences"
9+
import { safeJsonParse } from "@roo/shared/safeJsonParse"
910

1011
import { useCopyToClipboard } from "@src/utils/clipboard"
11-
import { safeJsonParse } from "@src/utils/json"
1212
import { useExtensionState } from "@src/context/ExtensionStateContext"
1313
import { findMatchingResourceOrTemplate } from "@src/utils/mcp"
1414
import { vscode } from "@src/utils/vscode"

0 commit comments

Comments
 (0)