refactor(cursor): simplify review — Set lookups, byte guards, XML escaping

- Replace O(n*k) chunks.some(includes) with emittedToolCallIds Set
- Replace O(n) toolCalls.find with finalizedIds Set (JSON + SSE paths)
- Add byte guard (0x7b check) before payload.toString on every frame
- Add escapeXml() to prevent tag injection in tool result blocks
- Reuse module-level TextDecoder in cursorProtobuf instead of per-call
- Replace [...Set].every() spread with for..of loop
This commit is contained in:
Rafael Calleja
2026-03-10 09:49:51 +01:00
parent 70008e67e4
commit 25cd0b3612
3 changed files with 99 additions and 44 deletions

View File

@@ -140,6 +140,41 @@ function createErrorResponse(jsonError) {
);
}
function parseCursorJsonErrorFrame(text: string) {
try {
return JSON.parse(text);
} catch {
return null;
}
}
function isToolBoundaryAbort(jsonError: any, toolCallCount: number) {
if (!jsonError || toolCallCount <= 0) return false;
const code = jsonError?.error?.code || "";
const debugError = jsonError?.error?.details?.[0]?.debug?.error || "";
const title = jsonError?.error?.details?.[0]?.debug?.details?.title || "";
const detail = jsonError?.error?.details?.[0]?.debug?.details?.detail || "";
const message = `${title} ${detail}`.toLowerCase();
const isAbortedCode = code === "aborted" || debugError === "ERROR_USER_ABORTED_REQUEST";
return isAbortedCode && message.includes("tool call ended before result was received");
}
function mergeToolCallDelta(existing, incoming) {
const mergedName = incoming?.function?.name || existing?.function?.name || "";
const existingArgs = existing?.function?.arguments || "";
const deltaArgs = incoming?.function?.arguments || "";
return {
id: incoming.id || existing.id,
type: "function",
function: {
name: mergedName,
arguments: `${existingArgs}${deltaArgs}`,
},
isLast: Boolean(existing?.isLast || incoming?.isLast),
index: existing?.index ?? incoming?.index ?? 0,
};
}
type CursorHttpResponse = {
status: number;
headers: Record<string, unknown>;
@@ -383,6 +418,7 @@ export class CursorExecutor extends BaseExecutor {
let totalContent = "";
const toolCalls = [];
const toolCallsMap = new Map(); // Track streaming tool calls by ID
const finalizedIds = new Set<string>();
let frameCount = 0;
debugLog(`[CURSOR BUFFER] Total length: ${buffer.length} bytes`);
@@ -419,19 +455,22 @@ export class CursorExecutor extends BaseExecutor {
continue;
}
// Check for JSON error frames
try {
const text = payload.toString("utf-8");
if (text.startsWith("{") && text.includes('"error"')) {
const hasContent = totalContent || toolCallsMap.size > 0;
debugLog(`[CURSOR BUFFER] Error frame (hasContent=${hasContent}): ${text.slice(0, 500)}`);
// If we already have content, treat error as stream termination (not fatal)
if (hasContent) {
break;
// Check for JSON error frames (byte guard: skip toString on non-JSON frames)
if (payload.length > 0 && payload[0] === 0x7b) {
try {
const text = payload.toString("utf-8");
if (text.includes('"error"')) {
const hasContent = totalContent || toolCallsMap.size > 0;
debugLog(
`[CURSOR BUFFER] Error frame (hasContent=${hasContent}): ${text.slice(0, 500)}`
);
if (hasContent) {
break;
}
return createErrorResponse(JSON.parse(text));
}
return createErrorResponse(JSON.parse(text));
}
} catch {}
} catch {}
}
const result = extractTextFromResponse(new Uint8Array(payload));
debugLog(`[CURSOR DECODED] Frame ${frameCount}:`, result);
@@ -474,6 +513,7 @@ export class CursorExecutor extends BaseExecutor {
// Push to final array when isLast is true
if (tc.isLast) {
const finalToolCall = toolCallsMap.get(tc.id);
finalizedIds.add(tc.id);
toolCalls.push({
id: finalToolCall.id,
type: finalToolCall.type,
@@ -495,7 +535,7 @@ export class CursorExecutor extends BaseExecutor {
// Finalize all remaining tool calls in map (in case stream ended without isLast=true)
for (const [id, tc] of toolCallsMap.entries()) {
// Check if already in final array
if (!toolCalls.find((t) => t.id === id)) {
if (!finalizedIds.has(id)) {
debugLog(`[CURSOR BUFFER] Finalizing incomplete tool call: ${id}, isLast=${tc.isLast}`);
toolCalls.push({
id: tc.id,
@@ -551,6 +591,8 @@ export class CursorExecutor extends BaseExecutor {
let totalContent = "";
const toolCalls = [];
const toolCallsMap = new Map(); // Track streaming tool calls by ID
const finalizedIds = new Set<string>();
const emittedToolCallIds = new Set<string>();
let frameCount = 0;
debugLog(`[CURSOR BUFFER SSE] Total length: ${buffer.length} bytes`);
@@ -587,22 +629,22 @@ export class CursorExecutor extends BaseExecutor {
continue;
}
// Check for JSON error frames
try {
const text = payload.toString("utf-8");
if (text.startsWith("{") && text.includes('"error"')) {
const hasContent = chunks.length > 0 || totalContent || toolCallsMap.size > 0;
// Log the full error for debugging
debugLog(
`[CURSOR BUFFER SSE] Error frame (hasContent=${hasContent}): ${text.slice(0, 500)}`
);
// If we already have content, treat error as stream termination (not fatal)
if (hasContent) {
break;
// Check for JSON error frames (byte-guard: only decode if starts with '{')
if (payload[0] === 0x7b) {
try {
const text = payload.toString("utf-8");
if (text.includes('"error"')) {
const hasContent = chunks.length > 0 || totalContent || toolCallsMap.size > 0;
debugLog(
`[CURSOR BUFFER SSE] Error frame (hasContent=${hasContent}): ${text.slice(0, 500)}`
);
if (hasContent) {
break;
}
return createErrorResponse(JSON.parse(text));
}
return createErrorResponse(JSON.parse(text));
}
} catch {}
} catch {}
}
const result = extractTextFromResponse(new Uint8Array(payload));
debugLog(`[CURSOR DECODED SSE] Frame ${frameCount}:`, result);
@@ -659,6 +701,7 @@ export class CursorExecutor extends BaseExecutor {
// Stream the delta arguments
if (tc.function.arguments) {
emittedToolCallIds.add(tc.id);
chunks.push(
`data: ${JSON.stringify({
id: responseId,
@@ -690,10 +733,12 @@ export class CursorExecutor extends BaseExecutor {
} else {
// New tool call - assign index and add to map
const toolCallIndex = toolCalls.length;
finalizedIds.add(tc.id);
toolCalls.push({ ...tc, index: toolCallIndex });
toolCallsMap.set(tc.id, { ...tc, index: toolCallIndex });
// Stream initial tool call with name
emittedToolCallIds.add(tc.id);
chunks.push(
`data: ${JSON.stringify({
id: responseId,
@@ -753,7 +798,7 @@ export class CursorExecutor extends BaseExecutor {
// Finalize all remaining tool calls in map (stream may have ended without isLast=true)
for (const [id, tc] of toolCallsMap.entries()) {
if (!toolCalls.find((t) => t.id === id)) {
if (!finalizedIds.has(id)) {
debugLog(`[CURSOR BUFFER SSE] Finalizing incomplete tool call: ${id}, isLast=${tc.isLast}`);
const toolCallIndex = toolCalls.length;
toolCalls.push({
@@ -767,7 +812,7 @@ export class CursorExecutor extends BaseExecutor {
});
// Emit SSE chunk for the finalized tool call if not already emitted
if (!chunks.some((c) => c.includes(tc.id))) {
if (!emittedToolCallIds.has(tc.id)) {
chunks.push(
`data: ${JSON.stringify({
id: responseId,

View File

@@ -37,13 +37,17 @@ function sanitizeToolResultText(text: string): string {
return text.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, "");
}
function escapeXml(text: string): string {
return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
function buildToolResultBlock(toolName: string, toolCallId: string, resultText: string): string {
const cleanResult = sanitizeToolResultText(resultText || "");
return [
"<tool_result>",
`<tool_name>${toolName || "tool"}</tool_name>`,
`<tool_call_id>${toolCallId || ""}</tool_call_id>`,
`<result>${cleanResult}</result>`,
`<tool_name>${escapeXml(toolName || "tool")}</tool_name>`,
`<tool_call_id>${escapeXml(toolCallId || "")}</tool_call_id>`,
`<result>${escapeXml(cleanResult)}</result>`,
"</tool_result>",
].join("\n");
}

View File

@@ -12,6 +12,7 @@ import zlib from "zlib";
const DEBUG = process.env.CURSOR_PROTOBUF_DEBUG === "1";
const log = (tag, ...args) => DEBUG && console.log(`[PROTOBUF:${tag}]`, ...args);
const textDecoder = new TextDecoder();
/**
* Schema version — bump when updating field definitions.
@@ -502,10 +503,15 @@ export function encodeRequest(messages, modelName, tools = [], reasoningEffort =
.map((tr) => tr?.tool_call_id)
.filter((id) => typeof id === "string")
);
const sameIds =
currentIds.size > 0 &&
currentIds.size === nextIds.size &&
[...currentIds].every((id) => nextIds.has(id));
let sameIds = currentIds.size > 0 && currentIds.size === nextIds.size;
if (sameIds) {
for (const id of currentIds) {
if (!nextIds.has(id)) {
sameIds = false;
break;
}
}
}
if (!(nextHasToolResults && sameIds)) {
normalizedMessages.push({
@@ -744,12 +750,12 @@ function extractToolCall(toolCallData) {
// Extract tool call ID
if (toolCall.has(FIELD.TOOL_ID)) {
toolCallId = new TextDecoder().decode(toolCall.get(FIELD.TOOL_ID)[0].value);
toolCallId = textDecoder.decode(toolCall.get(FIELD.TOOL_ID)[0].value);
}
// Extract tool name
if (toolCall.has(FIELD.TOOL_NAME)) {
toolName = new TextDecoder().decode(toolCall.get(FIELD.TOOL_NAME)[0].value);
toolName = textDecoder.decode(toolCall.get(FIELD.TOOL_NAME)[0].value);
}
// Extract is_last flag
@@ -768,11 +774,11 @@ function extractToolCall(toolCallData) {
const tool = decodeMessage(mcpParams.get(FIELD.MCP_TOOLS_LIST)[0].value);
if (tool.has(FIELD.MCP_NESTED_NAME)) {
toolName = new TextDecoder().decode(tool.get(FIELD.MCP_NESTED_NAME)[0].value);
toolName = textDecoder.decode(tool.get(FIELD.MCP_NESTED_NAME)[0].value);
}
if (tool.has(FIELD.MCP_NESTED_PARAMS)) {
rawArgs = new TextDecoder().decode(tool.get(FIELD.MCP_NESTED_PARAMS)[0].value);
rawArgs = textDecoder.decode(tool.get(FIELD.MCP_NESTED_PARAMS)[0].value);
}
}
} catch (err) {
@@ -782,7 +788,7 @@ function extractToolCall(toolCallData) {
// Fallback to raw_args
if (!rawArgs && toolCall.has(FIELD.TOOL_RAW_ARGS)) {
rawArgs = new TextDecoder().decode(toolCall.get(FIELD.TOOL_RAW_ARGS)[0].value);
rawArgs = textDecoder.decode(toolCall.get(FIELD.TOOL_RAW_ARGS)[0].value);
}
if (toolCallId && toolName) {
@@ -807,7 +813,7 @@ function extractTextAndThinking(responseData) {
// Extract text
if (nested.has(FIELD.RESPONSE_TEXT)) {
text = new TextDecoder().decode(nested.get(FIELD.RESPONSE_TEXT)[0].value);
text = textDecoder.decode(nested.get(FIELD.RESPONSE_TEXT)[0].value);
}
// Extract thinking
@@ -815,7 +821,7 @@ function extractTextAndThinking(responseData) {
try {
const thinkingMsg = decodeMessage(nested.get(FIELD.THINKING)[0].value);
if (thinkingMsg.has(FIELD.THINKING_TEXT)) {
thinking = new TextDecoder().decode(thinkingMsg.get(FIELD.THINKING_TEXT)[0].value);
thinking = textDecoder.decode(thinkingMsg.get(FIELD.THINKING_TEXT)[0].value);
}
} catch (err) {
log("EXTRACT", `Thinking parse error: ${err.message}`);