mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
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:
@@ -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,
|
||||
|
||||
@@ -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, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
Reference in New Issue
Block a user