claude-code-router/examples/plugins/cursor-proxy-plugin.cjs
musistudio 9eec586e60 init
2026-06-17 11:58:37 +08:00

7080 lines
231 KiB
JavaScript

"use strict";
const http = require("node:http");
const https = require("node:https");
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const zlib = require("node:zlib");
const DEFAULT_CURSOR_HOSTS = [
"api.cursor.sh",
"api2.cursor.sh",
"api3.cursor.sh",
"api4.cursor.sh",
"api.cursor.com",
"api2.cursor.com",
"*.cursor.sh",
"*.cursor.com",
"*.cursorapi.com"
];
const HOP_BY_HOP_HEADERS = new Set([
"connection",
"keep-alive",
"proxy-authenticate",
"proxy-authorization",
"proxy-connection",
"te",
"trailer",
"transfer-encoding",
"upgrade"
]);
const INTERNAL_HEADER_PREFIX = "x-ccr-";
const DEFAULT_GATEWAY_URL = "http://127.0.0.1:3456";
const MAX_JSON_SCAN_DEPTH = 8;
const DEFAULT_BIDI_WAIT_MS = 15000;
const DEFAULT_BIDI_SETTLE_MS = 500;
const DEFAULT_BIDI_MAX_SETTLE_MS = 2000;
const DEFAULT_BIDI_MISSING_CONTEXT_WAIT_MS = 6000;
const DEFAULT_BIDI_SESSION_TTL_MS = 10 * 60 * 1000;
const DEFAULT_CURSOR_CONTEXT_TTL_MS = 2 * 60 * 1000;
const DEFAULT_CURSOR_CONTEXT_MAX_ENTRIES = 64;
const DEFAULT_GATEWAY_TIMEOUT_MS = 10 * 60 * 1000;
const DEFAULT_CURSOR_TOOL_BRIDGE_TIMEOUT_MS = 2 * 60 * 1000;
const DEFAULT_CURSOR_TOOL_BRIDGE_MAX_ROUNDS = 8;
const DEFAULT_PASSTHROUGH_LOG_LIMIT = 3;
const DEFAULT_DECODE_DIAGNOSTIC_SAMPLE_LIMIT = 8;
const DEFAULT_DECODE_DIAGNOSTIC_SAMPLE_CHARS = 180;
const DEFAULT_DECODE_DUMP_DIR = path.join(os.tmpdir(), "ccr-cursor-decode-dumps");
const DEFAULT_DECODE_TREE_DEPTH = 12;
const CURSOR_PROXY_ROUTE_TYPES = new Set(["always", "image", "long-context", "model", "model-prefix", "thinking", "web-search"]);
const CURSOR_NATIVE_RPC_PATH_PATTERN = /^\/(?:aiserver|agent)\.v\d+\.[^/]+\/[^/]+$/i;
const CURSOR_NATIVE_LLM_SERVICE_PATTERN =
/^(?:AiService|AgentService|ChatService|ComposerService|CppService|TerminalService)$/i;
const CURSOR_NATIVE_LLM_METHOD_PATTERN =
/^(?:(?:Stream|Run|Generate|Create|Submit|Start).*(?:Agent|Chat|Completion|Completions|Composer|Cpp|Edit|Edits|Inline|Message|Prompt|Response|Terminal)|Complete(?:Chat|Completion|Edit|Inline|Terminal).*)$/i;
const CURSOR_NATIVE_SUPPORTED_TOOL_FIELDS = new Set([29, 51]);
const CURSOR_NATIVE_SUPPORTED_TOOL_ENUM_NAMES = new Map([
[1, "READ_SEMSEARCH_FILES"],
[3, "RIPGREP_SEARCH"],
[5, "READ_FILE"],
[6, "LIST_DIR"],
[8, "FILE_SEARCH"],
[9, "SEMANTIC_SEARCH_FULL"],
[39, "LIST_DIR_V2"],
[40, "READ_FILE_V2"],
[41, "RIPGREP_RAW_SEARCH"],
[42, "GLOB_FILE_SEARCH"]
]);
const CURSOR_NATIVE_SUPPORTED_TOOL_TO_SPEC_NAME = new Map([
[3, "grep_search"],
[5, "read_file"],
[6, "list_dir"],
[8, "glob_file_search"],
[9, "codebase_search"],
[39, "list_dir"],
[40, "read_file"],
[41, "grep_search"],
[42, "glob_file_search"]
]);
const CURSOR_NATIVE_BUILTIN_TOOL_NAMES = [
"read_file",
"list_dir",
"grep_search",
"glob_file_search",
"codebase_search",
"shell",
"delete_file",
"edit_file",
"read_lints",
"web_search",
"web_fetch",
"task",
"await_task",
"todo_read",
"todo_write",
"ask_question",
"switch_mode",
"generate_image",
"list_mcp_resources",
"read_mcp_resource",
"get_mcp_tools",
"call_mcp_tool",
"set_active_branch"
];
const CURSOR_SYSTEM_PROMPT_KEYS = [
"customInstruction",
"customInstructions",
"developer",
"developerInstruction",
"developerInstructions",
"developerPrompt",
"globalInstruction",
"globalInstructions",
"instructions",
"roleInstruction",
"roleInstructions",
"system",
"systemInstruction",
"systemInstructions",
"systemPrompt",
"system_prompt"
];
const CURSOR_TOOL_KEYS = [
"availableTools",
"available_tools",
"availableToolSchemas",
"available_tool_schemas",
"clientSideToolDefinitions",
"clientSideTools",
"functionDefinitions",
"function_definitions",
"functions",
"mcpTools",
"mcp_tools",
"toolSchemas",
"tool_schemas",
"toolDefinitions",
"tool_definitions",
"tools"
];
const CURSOR_TOOL_CHOICE_KEYS = [
"toolChoice",
"toolSelection",
"tool_choice",
"tool_selection"
];
const UUID_PATTERN = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
module.exports = {
async setup(ctx) {
const options = isRecord(ctx.pluginConfig) ? ctx.pluginConfig : {};
const gatewayUrl = trimTrailingSlash(stringValue(options.gatewayUrl) || configuredGatewayUrl(ctx.config));
const gatewayApiKey = stringValue(options.gatewayApiKey) || configuredGatewayApiKey(ctx.config);
const hosts = normalizeStringList(options.hosts, options.host) || DEFAULT_CURSOR_HOSTS;
const paths = options.paths === false ? undefined : normalizePathList(options.paths);
const runtime = {
bidiSessionTtlMs: numberOption(options.bidiSessionTtlMs, DEFAULT_BIDI_SESSION_TTL_MS),
bidiMaxSettleMs: numberOption(options.bidiMaxSettleMs, DEFAULT_BIDI_MAX_SETTLE_MS),
bidiMissingContextWaitMs: numberOption(options.bidiMissingContextWaitMs, DEFAULT_BIDI_MISSING_CONTEXT_WAIT_MS),
bidiSettleMs: numberOption(options.bidiSettleMs, DEFAULT_BIDI_SETTLE_MS),
bidiSessions: new Map(),
bidiWaitMs: numberOption(options.bidiWaitMs, DEFAULT_BIDI_WAIT_MS),
cursorContextCollector: options.cursorContextCollector !== false,
cursorContextMaxEntries: numberOption(options.cursorContextMaxEntries, DEFAULT_CURSOR_CONTEXT_MAX_ENTRIES),
cursorContextTtlMs: numberOption(options.cursorContextTtlMs, DEFAULT_CURSOR_CONTEXT_TTL_MS),
cursorContexts: new Map(),
cursorBidiProto: options.cursorBidiProto !== false,
cursorConnectJson: options.cursorConnectJson !== false,
cursorNativeProto: options.cursorNativeProto !== false,
cursorNativeLlmMethods: normalizeStringList(options.cursorNativeLlmMethods, options.cursorNativeLlmMethod) || [],
decodeDiagnosticSampleChars: numberOption(
options.decodeDiagnosticSampleChars ?? options.cursorDecodeDiagnosticSampleChars,
DEFAULT_DECODE_DIAGNOSTIC_SAMPLE_CHARS
),
decodeDiagnosticSampleLimit: numberOption(
options.decodeDiagnosticSampleLimit ?? options.cursorDecodeDiagnosticSampleLimit,
DEFAULT_DECODE_DIAGNOSTIC_SAMPLE_LIMIT
),
decodeDumpDir: stringValue(options.decodeDumpDir) ||
stringValue(options.cursorDecodeDumpDir) ||
stringValue(process.env.CCR_CURSOR_DECODE_DUMP_DIR) ||
DEFAULT_DECODE_DUMP_DIR,
decodeDumpFull: options.decodeDumpFull !== false && options.cursorDecodeDumpFull !== false,
decodeDiagnostics: options.decodeDiagnostics !== false && options.cursorDecodeDiagnostics !== false,
decodeTreeDepth: numberOption(options.decodeTreeDepth ?? options.cursorDecodeTreeDepth, DEFAULT_DECODE_TREE_DEPTH),
forwardDecodedCursorTools: options.forwardDecodedCursorTools !== false && options.cursorForwardDecodedTools !== false,
forwardCursorNativeBuiltinTools: options.forwardCursorNativeBuiltinTools !== false &&
options.cursorForwardNativeBuiltinTools !== false,
bridgeOpenAIToolCalls: options.bridgeOpenAIToolCalls !== false && options.cursorBridgeOpenAIToolCalls !== false,
showUnbridgedToolCallWarning: options.showUnbridgedToolCallWarning === true ||
options.cursorShowUnbridgedToolCallWarning === true,
toolCallBridgeTimeoutMs: numberOption(
options.toolCallBridgeTimeoutMs ?? options.cursorToolCallBridgeTimeoutMs,
DEFAULT_CURSOR_TOOL_BRIDGE_TIMEOUT_MS
),
maxToolCallRounds: numberOption(
options.maxToolCallRounds ?? options.cursorMaxToolCallRounds,
DEFAULT_CURSOR_TOOL_BRIDGE_MAX_ROUNDS
),
inlineToolCallContinuation: options.inlineToolCallContinuation === true ||
options.cursorInlineToolCallContinuation === true,
cursorMcpSkipApproval: options.cursorMcpSkipApproval === true || options.mcpSkipApproval === true,
chatCompletionSystemPrompt: stringValue(options.systemPrompt) ||
stringValue(options.openaiSystemPrompt) ||
stringValue(options.defaultSystemPrompt),
chatCompletionToolChoice: normalizeToolChoice(
options.toolChoice ?? options.openaiToolChoice ?? options.defaultToolChoice
),
chatCompletionTools: normalizeConfiguredTools(options.tools ?? options.openaiTools ?? options.defaultTools),
defaultModel: stringValue(options.defaultModel) || configuredDefaultModel(ctx.config),
fallbackToCursor: options.fallbackToCursor !== false,
gatewayApiKey,
gatewayTimeoutMs: numberOption(options.gatewayTimeoutMs, DEFAULT_GATEWAY_TIMEOUT_MS),
gatewayUrl,
logger: ctx.logger,
options,
passthroughLogCounts: new Map(),
passthroughLogLimit: numberOption(options.passthroughLogLimit, DEFAULT_PASSTHROUGH_LOG_LIMIT),
routing: normalizeCursorProxyRouting(options.routing, options),
targetModel: stringValue(options.targetModel),
targetProvider: stringValue(options.targetProvider),
targetProviders: stringValue(options.targetProviders),
warnedMissingChatContext: false,
warnedSuppressedDecodedCursorTools: false
};
const backend = await ctx.registerHttpBackend({
id: "cursor-proxy-adapter",
async handler(request, response) {
await handleCursorProxyRequest(runtime, request, response);
}
});
hosts.forEach((host, index) => {
ctx.registerProxyRoute({
host,
id: `cursor-proxy-${host.replace(/[^a-z0-9]+/gi, "-") || index}`,
paths,
preserveHost: true,
upstream: backend.url
});
});
ctx.registerGatewayRoute({
auth: "none",
handler(_request, response, helpers) {
helpers.sendJson(response, 200, {
backend: backend.url,
bidiMaxSettleMs: runtime.bidiMaxSettleMs,
bidiMissingContextWaitMs: runtime.bidiMissingContextWaitMs,
bidiSettleMs: runtime.bidiSettleMs,
cursorBidiProto: runtime.cursorBidiProto,
cursorConnectJson: runtime.cursorConnectJson,
cursorNativeProto: runtime.cursorNativeProto,
fallbackToCursor: runtime.fallbackToCursor,
gatewayUrl,
hosts,
collector: {
auth: options.collectorAuth === "gateway" ? "gateway" : "none",
contexts: runtime.cursorContexts.size,
enabled: runtime.cursorContextCollector,
endpoints: runtime.cursorContextCollector ? ["POST /plugins/cursor-proxy/collector"] : [],
maxEntries: runtime.cursorContextMaxEntries,
ttlMs: runtime.cursorContextTtlMs
},
debug: {
auth: options.debugAuth === "gateway" ? "gateway" : "none",
endpoints: ["GET /plugins/cursor-proxy/debug/sessions"]
},
openaiCompatContext: {
systemPrompt: Boolean(runtime.chatCompletionSystemPrompt),
toolChoice: runtime.chatCompletionToolChoice !== undefined,
tools: runtime.chatCompletionTools.length
},
agentRunToolForwarding: {
bridgeOpenAIToolCalls: runtime.bridgeOpenAIToolCalls,
cursorMcpSkipApproval: runtime.cursorMcpSkipApproval,
forwardDecodedCursorTools: runtime.forwardDecodedCursorTools,
forwardCursorNativeBuiltinTools: runtime.forwardCursorNativeBuiltinTools,
inlineToolCallContinuation: runtime.inlineToolCallContinuation,
maxToolCallRounds: runtime.maxToolCallRounds,
toolCallBridgeTimeoutMs: runtime.toolCallBridgeTimeoutMs,
showUnbridgedToolCallWarning: runtime.showUnbridgedToolCallWarning
},
paths: paths || ["*"],
plugin: "cursor-proxy",
routing: {
defaultTarget: runtime.routing.defaultTarget || undefined,
enabled: runtime.routing.enabled,
rules: runtime.routing.rules.length
},
sessions: runtime.bidiSessions.size,
targetModel: runtime.targetModel || undefined,
targetProvider: runtime.targetProvider || undefined,
targetProviders: runtime.targetProviders || undefined
});
},
id: "cursor-proxy-status",
method: "GET",
path: "/plugins/cursor-proxy"
});
ctx.registerGatewayRoute({
auth: options.debugAuth === "gateway" ? "gateway" : "none",
handler(_request, response, helpers) {
helpers.sendJson(response, 200, {
collector: {
contexts: runtime.cursorContexts.size,
ttlMs: runtime.cursorContextTtlMs
},
plugin: "cursor-proxy",
sessions: describeBidiSessions(runtime)
});
},
id: "cursor-proxy-debug-sessions",
method: "GET",
path: "/plugins/cursor-proxy/debug/sessions"
});
if (runtime.cursorContextCollector) {
ctx.registerGatewayRoute({
auth: options.collectorAuth === "gateway" ? "gateway" : "none",
handler(request, response, helpers) {
return handleCursorContextCollector(runtime, request, response, helpers);
},
id: "cursor-proxy-context-collector",
method: "POST",
pathPrefix: "/plugins/cursor-proxy/collector"
});
}
ctx.logger.info(
`Cursor proxy adapter listening at ${backend.url} for ${hosts.join(", ")} ` +
`(${paths?.join(", ") || "all paths"}) and forwarding JSON and Cursor Agent LLM traffic to ${gatewayUrl}`
);
}
};
async function handleCursorProxyRequest(runtime, request, response) {
const originalUrl = originalRequestUrl(request);
const url = new URL(request.url || "/", "http://cursor-proxy.local");
const method = (request.method || "GET").toUpperCase();
if (method === "OPTIONS") {
response.writeHead(204, corsHeaders());
response.end();
return;
}
const requestBody = await readRequestBody(request);
if (runtime.cursorBidiProto) {
const handled = await handleCursorBidiProtoRequest(runtime, request, response, url, requestBody, originalUrl);
if (handled) {
return;
}
}
if (runtime.cursorNativeProto) {
const handled = await handleCursorNativeProtoRequest(runtime, request, response, url, requestBody);
if (handled) {
return;
}
}
const route = resolveGatewayRoute(runtime, request, url, requestBody);
if (!route) {
if (runtime.fallbackToCursor && originalUrl) {
logCursorPassthrough(runtime, method, url.pathname, request.headers);
await forwardToUrl({
body: requestBody,
headers: buildPassthroughHeaders(request.headers, originalUrl),
method,
response,
url: originalUrl
});
return;
}
sendJson(response, 415, {
error: {
code: "unsupported_cursor_request",
message:
"Cursor proxy could not map this request to a CCR gateway protocol. " +
"OpenAI, Anthropic, Gemini JSON requests and Cursor Agent RunSSE protobuf requests are supported; other native Cursor RPC is passed through when fallbackToCursor is enabled."
}
});
return;
}
await forwardToUrl({
body: route.body,
headers: buildGatewayHeaders(runtime, request.headers, route, route.body),
method: route.method,
response,
url: new URL(route.path, runtime.gatewayUrl)
});
}
async function handleCursorContextCollector(runtime, request, response, helpers) {
const body = await helpers.readBody(request);
const payload = readJsonLikePayload(body);
if (!isRecord(payload)) {
helpers.sendJson(response, 400, {
error: {
code: "invalid_cursor_context",
message: "Cursor context collector expects a JSON object."
}
});
return;
}
const chatRequest = normalizeCollectedCursorChatRequest(runtime, payload);
if (!chatRequest) {
helpers.sendJson(response, 400, {
error: {
code: "unsupported_cursor_context",
message:
"Cursor context collector could not find chat messages, system instructions, or tools in this payload."
}
});
return;
}
const keys = collectCursorContextKeys(payload, chatRequest);
const stored = storeCursorContext(runtime, {
chatRequest,
createdAt: Date.now(),
keys,
summary: summarizeChatRequest(chatRequest)
});
helpers.sendJson(response, 200, {
contextKeys: stored.keys,
stored: true,
summary: formatChatSummary(stored.summary)
});
}
async function handleCursorBidiProtoRequest(runtime, request, response, url, requestBody) {
const method = (request.method || "GET").toUpperCase();
const path = normalizePath(url.pathname);
if (method !== "POST") {
return false;
}
if (path === "/aiserver.v1.BidiService/BidiAppend") {
const append = decodeBidiAppendRequest(requestBody);
if (!append.requestId) {
const waitingSession = findSingleWaitingBidiSession(runtime);
if (!waitingSession) {
return false;
}
append.requestId = waitingSession.requestId;
runtime.logger?.debug?.(
`Cursor proxy inferred BidiAppend request id ${append.requestId} from the only waiting RunSSE session ` +
`(seq=${append.appendSeqno}, body=${requestBody.length}, body_hex=${append.bodyHex}, decoded_body=${append.rpcBodyEncoding}, ` +
`decoded_hex=${append.rpcBodyHex}, fields=${formatProtoFieldSummary(append.rpcBody)}).`
);
}
const session = getBidiSession(runtime, append.requestId, true);
const runRequest = appendBidiSessionRequest(session, append);
if (runRequest) {
setBidiSessionRunRequest(session, runRequest);
runtime.logger?.debug?.(
`Cursor proxy captured AgentRunRequest for Bidi request ${append.requestId} ` +
`(seq=${append.appendSeqno}, appends=${session.appends.size}, ${formatAgentRunRequestSummary(runtime, runRequest)}).`
);
} else if (session.waiters.length > 0) {
runtime.logger?.debug?.(
`Cursor proxy buffered BidiAppend for ${append.requestId} but has not decoded AgentRunRequest yet ` +
`(seq=${append.appendSeqno}, body=${requestBody.length}, body_hex=${append.bodyHex}, decoded_body=${append.rpcBodyEncoding}, ` +
`decoded_hex=${append.rpcBodyHex}, data=${append.data.length}, ` +
`data_bytes=${append.dataBytes.length}, data_hex=${append.dataHex}, data_binary=${append.dataBinary.length}, appends=${session.appends.size}, ` +
`combined_data=${combinedAppendDataLength(session)}, combined_data_bytes=${combinedAppendDataBytesLength(session)}, combined_data_binary=${combinedAppendDataBinaryLength(session)}, ` +
`fields=${formatProtoFieldSummary(append.rpcBody)}, data_binary_fields=${formatProtoFieldSummary(append.dataBinary)}).`
);
logCursorDecodeDiagnostics(
runtime,
"debug",
`Cursor proxy BidiAppend decode diagnostics for ${append.requestId}: ` +
formatBidiSessionDecodeDiagnostic(runtime, session)
);
}
sendBinary(response, 200, Buffer.alloc(0), protoHeaders());
return true;
}
if (path === "/agent.v1.AgentService/RunSSE") {
const runSse = decodeRunSseRequest(requestBody);
const requestId = runSse.requestId || (runSse.runRequest ? createSyntheticBidiRequestId() : "");
if (!requestId) {
logCursorDecodeDiagnostics(
runtime,
"warn",
`Cursor proxy could not identify AgentService/RunSSE request id ` +
`(body=${requestBody.length}, decoded_body=${runSse.rpcBodyEncoding}, fields=${formatProtoFieldSummary(runSse.rpcBody)}, ` +
`candidates=${formatCandidateBufferSummary(runtime, runSse.candidates)}).`
);
return false;
}
const session = getBidiSession(runtime, requestId, true);
session.lastSeen = Date.now();
if (runSse.runRequest) {
setBidiSessionRunRequest(session, runSse.runRequest);
runtime.logger?.debug?.(
`Cursor proxy captured AgentRunRequest from AgentService/RunSSE body for Bidi request ${requestId} ` +
`(body=${requestBody.length}, fields=${formatProtoFieldSummary(runSse.rpcBody)}, ` +
`${formatAgentRunRequestSummary(runtime, runSse.runRequest)}).`
);
const chatRequest = convertAgentRunRequestToOpenAIChat(runtime, runSse.runRequest);
if (chatRequest && isSimplifiedAgentChatRequest(chatRequest)) {
logCursorDecodeDiagnostics(
runtime,
"warn",
`Cursor proxy RunSSE AgentRunRequest decoded without Cursor context for ${requestId}: ` +
formatAgentRunRequestDecodeDiagnostic(runtime, runSse.runRequest, chatRequest) +
`; runsse_candidates=${formatCandidateBufferSummary(runtime, runSse.candidates)}`
);
} else {
logCursorDecodeDiagnostics(
runtime,
"debug",
`Cursor proxy RunSSE decode diagnostics for ${requestId}: ` +
formatAgentRunRequestDecodeDiagnostic(runtime, runSse.runRequest, chatRequest) +
`; runsse_candidates=${formatCandidateBufferSummary(runtime, runSse.candidates)}`
);
}
}
if (!session.runRequest) {
const runRequest = extractAgentRunRequestFromBidiSession(session);
if (runRequest) {
setBidiSessionRunRequest(session, runRequest);
}
}
runtime.logger?.debug?.(`Cursor proxy handling AgentService/RunSSE for Bidi request ${requestId}.`);
await handleAgentRunSse(runtime, request, response, session);
return true;
}
return false;
}
async function handleAgentRunSse(runtime, request, response, session) {
response.writeHead(200, corsHeaders(connectProtoHeaders()));
const initialRunRequest = session.runRequest || await waitForBidiRunRequest(session, runtime.bidiWaitMs);
if (!initialRunRequest) {
runtime.logger?.warn?.(
`Cursor proxy timed out waiting for BidiAppend AgentRunRequest for ${session.requestId} ` +
`(${formatBidiSessionDiagnostic(session)}).`
);
writeCursorConnectMessage(response, encodeCursorAgentTextDelta(
"Cursor proxy did not receive the AgentRunRequest payload for this RunSSE request."
));
writeCursorConnectMessage(response, encodeCursorAgentTurnEnded());
finishCursorConnectStream(response);
return;
}
await waitForBidiRunRequestSettle(session, runtime.bidiSettleMs, runtime.bidiMaxSettleMs);
const runRequest = session.runRequest || initialRunRequest;
let chatRequest = convertAgentRunRequestToOpenAIChat(runtime, runRequest);
if (!chatRequest) {
runtime.logger?.warn?.(`Cursor proxy could not decode AgentRunRequest ${session.requestId} into chat messages.`);
writeCursorConnectMessage(response, encodeCursorAgentTextDelta(
"Cursor proxy could not decode this Cursor Agent request into a gateway chat request."
));
writeCursorConnectMessage(response, encodeCursorAgentTurnEnded());
finishCursorConnectStream(response);
return;
}
chatRequest = applyCollectedCursorContext(runtime, session, runRequest, chatRequest);
if (isSimplifiedAgentChatRequest(chatRequest) && runtime.bidiMissingContextWaitMs > 0) {
const richer = await waitForRicherBidiChatRequest(runtime, session, runtime.bidiMissingContextWaitMs, chatRequest);
if (richer) {
chatRequest = richer.chatRequest;
}
}
chatRequest = applyCollectedCursorContext(runtime, session, session.runRequest || runRequest, chatRequest);
logAgentChatConversion(runtime, session, chatRequest);
await streamAgentChatWithCursorToolBridge(runtime, request, response, session, chatRequest);
}
async function streamAgentChatWithCursorToolBridge(runtime, request, response, session, initialChatRequest) {
let chatRequest = initialChatRequest;
let runRequest = session.runRequest;
let observedRunRequestVersion = session.runRequestVersion || 0;
const maxRounds = Math.max(1, Math.trunc(Number(runtime.maxToolCallRounds) || DEFAULT_CURSOR_TOOL_BRIDGE_MAX_ROUNDS));
for (let round = 0; round < maxRounds; round += 1) {
const bridgeContext = buildCursorToolBridgeContext(runtime, session, runRequest, chatRequest, round);
const outcome = await streamGatewayChatToCursor(runtime, request, response, chatRequest, bridgeContext);
if (!outcome || outcome.type !== "tool_calls") {
return;
}
if (runtime.inlineToolCallContinuation !== true) {
runtime.logger?.debug?.(
`Cursor proxy handed bridged tool_calls to Cursor Agent for ${session.requestId}; ` +
`ending this RunSSE so Cursor can execute tools and send the next AgentRunRequest ` +
`(${summarizeGatewayToolCalls(outcome.toolCalls)}).`
);
writeCursorConnectMessage(response, encodeCursorAgentTurnEnded());
finishCursorConnectStream(response);
return;
}
const nextRunRequest = await waitForBidiRunRequestAfter(
session,
observedRunRequestVersion,
runtime.toolCallBridgeTimeoutMs
);
if (!nextRunRequest) {
runtime.logger?.warn?.(
`Cursor proxy bridged upstream tool_calls to Cursor Agent for ${session.requestId}, ` +
`but did not receive a follow-up AgentRunRequest with tool results within ` +
`${runtime.toolCallBridgeTimeoutMs}ms (${summarizeGatewayToolCalls(outcome.toolCalls)}).`
);
writeCursorConnectMessage(response, encodeCursorAgentTextDelta(
"\n\n[Cursor proxy] Cursor did not return tool results after the proxy emitted Cursor Agent tool events."
));
writeCursorConnectMessage(response, encodeCursorAgentTurnEnded());
finishCursorConnectStream(response);
return;
}
observedRunRequestVersion = session.runRequestVersion || observedRunRequestVersion;
await waitForBidiRunRequestSettle(session, runtime.bidiSettleMs, runtime.bidiMaxSettleMs);
runRequest = session.runRequest || nextRunRequest;
let nextChatRequest = convertAgentRunRequestToOpenAIChat(runtime, runRequest);
if (!nextChatRequest) {
runtime.logger?.warn?.(
`Cursor proxy received follow-up AgentRunRequest for ${session.requestId}, ` +
"but could not decode it into OpenAI chat messages after tool execution."
);
writeCursorConnectMessage(response, encodeCursorAgentTextDelta(
"\n\n[Cursor proxy] Cursor returned a follow-up payload, but the proxy could not decode the tool result context."
));
writeCursorConnectMessage(response, encodeCursorAgentTurnEnded());
finishCursorConnectStream(response);
return;
}
nextChatRequest = applyCollectedCursorContext(runtime, session, runRequest, nextChatRequest);
const resultMessages = findToolResultMessages(nextChatRequest?.messages, outcome.toolCalls);
logCursorToolBridgeFollowUp(
runtime,
session,
runRequest,
nextChatRequest,
outcome.toolCalls,
resultMessages,
round + 1
);
if (resultMessages.length === 0) {
runtime.logger?.warn?.(
`Cursor proxy received a follow-up AgentRunRequest for ${session.requestId} after bridging ` +
`tool_calls, but decoded no matching tool result messages; stopping instead of repeating the ` +
`same tool call (${summarizeGatewayToolCalls(outcome.toolCalls)}, ${formatChatRequestSummary(nextChatRequest)}).`
);
writeCursorConnectMessage(response, encodeCursorAgentTextDelta(
"\n\n[Cursor proxy] Cursor returned a follow-up request, but the proxy decoded no matching tool result message. " +
"Stopped before repeating the same tool call; check cursor-proxy decode dumps/logs for the follow-up payload."
));
writeCursorConnectMessage(response, encodeCursorAgentTurnEnded());
finishCursorConnectStream(response);
return;
}
writeCursorMcpToolResultEvents(runtime, response, outcome.toolCalls, bridgeContext, nextChatRequest, outcome.modelCallId);
chatRequest = mergeToolCallContinuationChatRequest(chatRequest, outcome.toolCalls, nextChatRequest);
logAgentToolContinuation(runtime, session, outcome.toolCalls, chatRequest, round + 1);
}
runtime.logger?.warn?.(
`Cursor proxy stopped tool-call bridge for ${session.requestId} after ${maxRounds} rounds ` +
`(${formatChatRequestSummary(chatRequest)}).`
);
writeCursorConnectMessage(response, encodeCursorAgentTextDelta(
`\n\n[Cursor proxy] Stopped after ${maxRounds} tool-call bridge rounds.`
));
writeCursorConnectMessage(response, encodeCursorAgentTurnEnded());
finishCursorConnectStream(response);
}
async function handleCursorNativeProtoRequest(runtime, request, response, url, requestBody) {
const method = (request.method || "GET").toUpperCase();
const path = normalizePath(url.pathname);
if (method !== "POST" || !isCursorNativeLlmRpcPath(runtime, path) || requestBody.length === 0) {
return false;
}
const chatRequest = convertCursorNativeRequestToOpenAIChat(runtime, path, requestBody);
if (!chatRequest) {
return false;
}
runtime.logger?.debug?.(
`Cursor proxy converted native Cursor RPC ${path} to OpenAI chat ` +
`(messages=${Array.isArray(chatRequest.messages) ? chatRequest.messages.length : 0}, ` +
`tools=${Array.isArray(chatRequest.tools) ? chatRequest.tools.length : 0}).`
);
if (isSimplifiedAgentChatRequest(chatRequest)) {
logCursorDecodeDiagnostics(
runtime,
"warn",
`Cursor proxy native Cursor RPC ${path} decoded without system/tools: ` +
formatCursorNativeProtoDecodeDiagnostic(runtime, path, requestBody, chatRequest)
);
}
response.writeHead(200, corsHeaders(connectProtoHeaders()));
await streamGatewayChatToCursor(runtime, request, response, chatRequest);
return true;
}
function convertCursorNativeRequestToOpenAIChat(runtime, path, requestBody) {
const candidateBuffers = cursorNativeCandidateBuffers(requestBody);
const runRequest = extractAgentRunRequestFromCandidates(candidateBuffers);
if (runRequest) {
return convertAgentRunRequestToOpenAIChat(runtime, runRequest);
}
const decodedValues = collectCursorNativeDecodedValues(candidateBuffers);
const fromDecodedValues = convertCursorDecodedValuesToOpenAIChat(runtime, path, decodedValues, {
loosePromptFallback: false,
requireInteractiveMessage: true
});
if (fromDecodedValues) {
return fromDecodedValues;
}
const jsonPayload = readJsonLikePayload(requestBody);
if (jsonPayload !== undefined) {
return convertCursorJsonPayloadToOpenAIChat(runtime, path, jsonPayload);
}
return undefined;
}
function cursorNativeCandidateBuffers(body) {
const candidates = [];
const decoded = decodeCursorProtoBody(body);
candidates.push(decoded.body);
candidates.push(...binaryCandidateBuffers(body));
if (decoded.body && !decoded.body.equals?.(body)) {
candidates.push(...binaryCandidateBuffers(decoded.body));
}
return uniqueBuffers(candidates);
}
function collectCursorNativeDecodedValues(candidateBuffers) {
const values = [];
for (const buffer of candidateBuffers) {
if (!Buffer.isBuffer(buffer) || buffer.length === 0) {
continue;
}
if (looksLikeTextBuffer(buffer)) {
values.push(buffer.toString("utf8"));
const parsed = readJsonText(buffer.toString("utf8"));
if (parsed !== undefined) {
values.push(parsed);
}
}
for (const item of decodeAllProtoStrings(buffer)) {
if (!item.value || looksLikeNoiseString(item.value)) {
continue;
}
values.push(item.value);
const parsed = readJsonText(item.value);
if (parsed !== undefined) {
values.push(parsed);
}
for (const decoded of decodeEmbeddedStringValues(item.value)) {
values.push(decoded);
}
}
}
return uniqueDecodedValues(values);
}
function convertCursorDecodedValuesToOpenAIChat(runtime, path, values, options = {}) {
if (!Array.isArray(values) || values.length === 0) {
return undefined;
}
const messages = extractMessagesFromValues(values);
if (messages.length === 0) {
const prompt = findFirstStringFromValues(values, [
"input",
"instruction",
"message",
"prompt",
"query",
"text",
"userMessage",
"user_message"
]) || (options.loosePromptFallback === false ? undefined : findBestPromptStringFromValues(values));
if (prompt) {
messages.push({ content: prompt, role: "user" });
}
}
if (messages.length === 0) {
return undefined;
}
const systemPrompt = selectPreferredSystemPrompt(
extractSystemPromptFromValues(values),
findBestSystemPromptStringFromValues(values)
);
if (systemPrompt && !messages.some((message) => message.role === "system")) {
messages.unshift({ content: systemPrompt, role: "system" });
}
const compactedMessages = compactChatMessages(messages);
if (compactedMessages.length === 0) {
return undefined;
}
if (options.requireInteractiveMessage !== false && !hasInteractiveChatMessage(compactedMessages)) {
return undefined;
}
const tools = extractToolsFromValues(values);
const toolChoice = extractToolChoiceFromValues(values);
const model =
findFirstStringFromValues(values, ["model", "modelName", "selectedModel", "intentModel", "chatModel"]) ||
runtime.defaultModel ||
"cursor-proxy";
return compactObject({
frequency_penalty: findFirstNumberFromValues(values, ["frequency_penalty", "frequencyPenalty"]),
max_tokens: findFirstNumberFromValues(values, ["max_tokens", "maxTokens", "maxOutputTokens", "output_tokens"]),
messages: compactedMessages,
model,
presence_penalty: findFirstNumberFromValues(values, ["presence_penalty", "presencePenalty"]),
stream: path.toLowerCase().includes("stream") || findFirstBooleanFromValues(values, ["stream", "shouldStream"]) !== false,
temperature: findFirstNumberFromValues(values, ["temperature"]),
tool_choice: toolChoice,
tools: tools.length > 0 ? tools : undefined,
top_p: findFirstNumberFromValues(values, ["top_p", "topP"])
});
}
function hasInteractiveChatMessage(messages) {
return messages.some((message) => ["assistant", "tool", "user"].includes(message.role));
}
function normalizeCollectedCursorChatRequest(runtime, payload) {
const candidates = collectCursorContextPayloadCandidates(payload);
for (const candidate of candidates) {
const direct = normalizeCollectedOpenAIChatPayload(runtime, candidate);
if (direct) {
return direct;
}
const converted = convertCursorDecodedValuesToOpenAIChat(runtime, "/collector", [candidate], {
loosePromptFallback: false,
requireInteractiveMessage: false
});
if (converted) {
return converted;
}
}
return undefined;
}
function collectCursorContextPayloadCandidates(payload) {
const candidates = [];
const add = (value) => {
if (value === undefined || value === null) {
return;
}
if (typeof value === "string") {
const parsed = readJsonText(value);
if (parsed !== undefined) {
candidates.push(parsed);
}
return;
}
candidates.push(value);
};
if (isRecord(payload)) {
for (const key of [
"openai",
"openaiRequest",
"openai_request",
"chat",
"chatRequest",
"chat_request",
"request",
"body",
"payload",
"message"
]) {
add(payload[key]);
}
}
add(payload);
return uniqueDecodedValues(candidates);
}
function normalizeCollectedOpenAIChatPayload(runtime, payload) {
if (!isRecord(payload)) {
return undefined;
}
const messages = Array.isArray(payload.messages)
? compactChatMessages(payload.messages.map((message) => isRecord(message) ? message : undefined).filter(Boolean))
: extractMessages(payload);
const systemPrompt = extractSystemPrompt(payload);
if (systemPrompt && !messages.some((message) => message.role === "system")) {
messages.unshift({ content: systemPrompt, role: "system" });
}
const compactedMessages = compactChatMessages(messages);
const tools = Array.isArray(payload.tools) ? normalizeToolList(payload.tools) : extractTools(payload);
const toolChoice = normalizeToolChoice(payload.tool_choice ?? payload.toolChoice) ?? extractToolChoice(payload);
if (
compactedMessages.length === 0 &&
tools.length === 0 &&
!systemPrompt &&
toolChoice === undefined
) {
return undefined;
}
return compactObject({
frequency_penalty: numberFromParameter(payload.frequency_penalty ?? payload.frequencyPenalty),
max_tokens: numberFromParameter(payload.max_tokens ?? payload.maxTokens ?? payload.maxOutputTokens ?? payload.output_tokens),
messages: compactedMessages.length > 0 ? compactedMessages : undefined,
model: stringValue(payload.model) ||
stringValue(payload.modelName) ||
stringValue(payload.selectedModel) ||
runtime.defaultModel ||
"cursor-proxy",
parallel_tool_calls: typeof payload.parallel_tool_calls === "boolean" ? payload.parallel_tool_calls : undefined,
presence_penalty: numberFromParameter(payload.presence_penalty ?? payload.presencePenalty),
reasoning_effort: payload.reasoning_effort,
reasoning_split: payload.reasoning_split,
response_format: isRecord(payload.response_format) ? payload.response_format : undefined,
stream: payload.stream === undefined ? true : Boolean(payload.stream),
temperature: numberFromParameter(payload.temperature),
tool_choice: toolChoice,
tools: tools.length > 0 ? tools : undefined,
top_p: numberFromParameter(payload.top_p ?? payload.topP)
});
}
function convertCursorJsonPayloadToOpenAIChat(runtime, path, payload) {
const unwrapped = unwrapCursorJson(payload);
if (unwrapped === undefined) {
return undefined;
}
const converted = convertCursorDecodedValuesToOpenAIChat(runtime, path, [unwrapped], {
loosePromptFallback: false,
requireInteractiveMessage: true
});
if (converted) {
return converted;
}
const body = Buffer.from(`${JSON.stringify(unwrapped)}\n`, "utf8");
return convertCursorJsonToOpenAIChat(runtime, path, body);
}
function decodeBidiAppendRequest(body) {
const decodedBody = decodeCursorProtoBody(body);
const rpcBody = decodedBody.body;
const dataBytes = concatProtoBytesFields(rpcBody, 1);
const dataBinary = concatProtoBytesFields(rpcBody, 4);
return {
appendSeqno: decodeProtoIntField(rpcBody, 3),
bodyHex: body.subarray(0, 32).toString("hex"),
data: dataBytes.length > 0 ? dataBytes.toString("utf8") : "",
dataBytes,
dataHex: dataBytes.subarray(0, 32).toString("hex"),
dataBinary,
rawBody: body,
rpcBody,
rpcBodyEncoding: decodedBody.encoding,
rpcBodyHex: rpcBody.subarray(0, 32).toString("hex"),
requestId: decodeBidiAppendRequestId(rpcBody)
};
}
function decodeBidiAppendRequestId(rpcBody) {
const requestIdMessage = decodeProtoMessageFields(rpcBody, 2)[0];
return decodeProtoStringField(requestIdMessage, 1) ||
decodeProtoStringField(rpcBody, 2) ||
findFirstUuidString(requestIdMessage) ||
findFirstUuidString(rpcBody) ||
"";
}
function decodeRunSseRequest(body) {
const decodedBody = decodeCursorProtoBody(body);
const rpcBody = decodedBody.body;
const candidates = runSseCandidateBuffers(body, rpcBody);
return {
candidates,
rpcBodyEncoding: decodedBody.encoding,
requestId: decodeBidiRequestId(rpcBody),
rpcBody,
runRequest: extractAgentRunRequestFromCandidates(candidates)
};
}
function decodeBidiRequestId(body) {
return decodeProtoStringField(body, 1) ||
decodeProtoStringField(decodeProtoMessageFields(body, 1)[0], 1) ||
findFirstUuidString(body) ||
"";
}
function findFirstUuidString(buffer) {
for (const item of decodeAllProtoStrings(buffer)) {
const match = item.value.match(UUID_PATTERN);
if (match) {
return match[0];
}
}
return "";
}
function createSyntheticBidiRequestId() {
return `runsse-${Date.now()}-${Math.random().toString(16).slice(2)}`;
}
function decodeCursorProtoBody(body) {
const fallbackBody = decodeConnectEnvelope(body);
const candidates = [];
const addCandidate = (buffer, encoding) => {
if (Buffer.isBuffer(buffer) && buffer.length > 0) {
candidates.push({ body: buffer, encoding });
}
};
addCandidate(fallbackBody, "connect");
for (const frame of decodeConnectMessages(body)) {
addCandidate(frame, "connect-frame");
}
const decompressedBody = decompressConnectPayload(body);
if (decompressedBody) {
addCandidate(decompressedBody, "compressed");
for (const frame of decodeConnectMessages(decompressedBody)) {
addCandidate(frame, "compressed-connect-frame");
}
}
for (const candidate of candidates) {
if (hasReadableProtoFields(candidate.body)) {
return candidate;
}
}
return { body: fallbackBody, encoding: "raw" };
}
function appendBidiSessionRequest(session, append) {
session.lastSeen = Date.now();
session.appendSeqno = append.appendSeqno;
let key = append.appendSeqno || session.appends.size + 1;
while (session.appends.has(key)) {
key += 1e-6;
}
session.appends.set(key, append);
while (session.appends.size > 128) {
const firstKey = session.appends.keys().next().value;
session.appends.delete(firstKey);
}
return extractAgentRunRequestFromBidiSession(session);
}
function extractAgentRunRequestFromBidiSession(session) {
return extractAgentRunRequestFromCandidates(bidiSessionCandidateBuffers(session));
}
function bidiSessionCandidateBuffers(session) {
const appends = sortedBidiAppends(session);
const candidates = [];
const combinedDataBinary = Buffer.concat(appends.map((append) => append.dataBinary).filter((buffer) => buffer.length > 0));
if (combinedDataBinary.length > 0) {
candidates.push(combinedDataBinary);
candidates.push(...decodeConnectMessages(combinedDataBinary));
}
const combinedDataBytes = Buffer.concat(appends.map((append) => append.dataBytes).filter((buffer) => buffer.length > 0));
if (combinedDataBytes.length > 0) {
candidates.push(combinedDataBytes);
candidates.push(...decodeConnectMessages(combinedDataBytes));
}
const combinedData = appends.map((append) => append.data || "").join("");
if (combinedData) {
candidates.push(...stringCandidateBuffers(combinedData));
candidates.push(...stringCandidateBuffers(combinedData.replace(/\s+/g, "")));
}
for (const append of appends) {
candidates.push(...bidiAppendCandidateBuffers(append));
}
return candidates;
}
function runSseCandidateBuffers(body, rpcBody) {
const candidates = [];
candidates.push(...binaryCandidateBuffers(rpcBody));
candidates.push(...binaryCandidateBuffers(body));
return candidates;
}
function sortedBidiAppends(session) {
return [...session.appends.entries()]
.sort((left, right) => Number(left[0]) - Number(right[0]))
.map((entry) => entry[1]);
}
function combinedAppendDataLength(session) {
return sortedBidiAppends(session).reduce((total, append) => total + append.data.length, 0);
}
function combinedAppendDataBytesLength(session) {
return sortedBidiAppends(session).reduce((total, append) => total + append.dataBytes.length, 0);
}
function combinedAppendDataBinaryLength(session) {
return sortedBidiAppends(session).reduce((total, append) => total + append.dataBinary.length, 0);
}
function formatBidiSessionDiagnostic(session) {
const appends = sortedBidiAppends(session);
const lastAppend = appends[appends.length - 1];
return [
`appends=${session.appends.size}`,
`last_seq=${lastAppend?.appendSeqno || session.appendSeqno || 0}`,
`combined_data=${combinedAppendDataLength(session)}`,
`combined_data_bytes=${combinedAppendDataBytesLength(session)}`,
`combined_data_binary=${combinedAppendDataBinaryLength(session)}`,
`last_data=${lastAppend?.data.length || 0}`,
`last_data_bytes=${lastAppend?.dataBytes.length || 0}`,
`last_data_hex=${lastAppend?.dataHex || ""}`,
`last_data_binary=${lastAppend?.dataBinary.length || 0}`,
`last_fields=${lastAppend ? formatProtoFieldSummary(lastAppend.rpcBody) : "empty"}`,
`last_data_binary_fields=${lastAppend ? formatProtoFieldSummary(lastAppend.dataBinary) : "empty"}`,
`append_details=${formatBidiAppendDetails(appends)}`
].join(", ");
}
function formatBidiAppendDetails(appends) {
if (!appends.length) {
return "empty";
}
return appends.slice(-16).map((append) => {
return [
`seq:${append.appendSeqno || 0}`,
`data:${append.data.length}`,
`data_bytes:${append.dataBytes.length}`,
`data_hex:${append.dataHex}`,
`data_binary:${append.dataBinary.length}`,
`fields:${formatProtoFieldSummary(append.rpcBody)}`,
`data_binary_fields:${formatProtoFieldSummary(append.dataBinary)}`
].join("/");
}).join("|");
}
function extractAgentRunRequestFromBidiAppend(append) {
return extractAgentRunRequestFromCandidates(bidiAppendCandidateBuffers(append));
}
function bidiAppendCandidateBuffers(append) {
const candidates = [];
candidates.push(...binaryCandidateBuffers(append.rawBody));
candidates.push(...binaryCandidateBuffers(append.rpcBody));
if (append.dataBinary.length > 0) {
candidates.push(...binaryCandidateBuffers(append.dataBinary));
}
if (append.dataBytes.length > 0) {
candidates.push(...binaryCandidateBuffers(append.dataBytes));
}
if (append.data) {
candidates.push(...stringCandidateBuffers(append.data));
}
return candidates;
}
function binaryCandidateBuffers(buffer) {
if (!Buffer.isBuffer(buffer) || buffer.length === 0) {
return [];
}
const candidates = [buffer];
const framed = decodeConnectMessages(buffer);
if (framed.length > 0 && !(framed.length === 1 && framed[0] === buffer)) {
candidates.push(...framed);
}
const decompressed = decompressConnectPayload(buffer);
if (decompressed && decompressed.length > 0 && !decompressed.equals(buffer)) {
candidates.push(decompressed);
candidates.push(...decodeConnectMessages(decompressed));
}
candidates.push(...lengthPrefixedCandidateBuffers(buffer));
if (looksLikeTextBuffer(buffer)) {
candidates.push(...stringCandidateBuffers(buffer.toString("utf8")));
}
return candidates;
}
function lengthPrefixedCandidateBuffers(buffer) {
const candidates = [];
const varintLength = readProtoVarint(buffer, 0);
if (varintLength) {
const start = varintLength.offset;
const end = start + Number(varintLength.value);
if (end > start && end <= buffer.length) {
candidates.push(buffer.subarray(start, end));
}
}
if (buffer.length >= 4) {
const bigEndianLength = buffer.readUInt32BE(0);
if (bigEndianLength > 0 && bigEndianLength <= buffer.length - 4) {
candidates.push(buffer.subarray(4, 4 + bigEndianLength));
}
const littleEndianLength = buffer.readUInt32LE(0);
if (littleEndianLength > 0 && littleEndianLength <= buffer.length - 4) {
candidates.push(buffer.subarray(4, 4 + littleEndianLength));
}
}
return candidates;
}
function looksLikeTextBuffer(buffer) {
const sampleLength = Math.min(buffer.length, 512);
if (sampleLength === 0) {
return false;
}
let textLike = 0;
for (let index = 0; index < sampleLength; index += 1) {
const byte = buffer[index];
if (byte === 9 || byte === 10 || byte === 13 || (byte >= 32 && byte <= 126)) {
textLike += 1;
}
}
return textLike / sampleLength > 0.9;
}
function stringCandidateBuffers(value) {
const candidates = [];
const text = String(value || "");
if (!text) {
return candidates;
}
candidates.push(Buffer.from(text, "utf8"));
const trimmed = text.trim();
if (!trimmed) {
return candidates;
}
for (const candidate of base64CandidateBuffers(trimmed)) {
candidates.push(candidate);
}
if (/^(?:[0-9a-f]{2})+$/i.test(trimmed)) {
candidates.push(Buffer.from(trimmed, "hex"));
}
if (/%[0-9a-f]{2}/i.test(trimmed)) {
try {
candidates.push(...stringCandidateBuffers(decodeURIComponent(trimmed)));
} catch {
// Not URI encoded.
}
}
const parsed = readJsonText(trimmed);
if (parsed) {
for (const item of findStringValuesByKeys(parsed, ["data", "dataBinary", "data_binary", "payload", "message"])) {
candidates.push(...stringCandidateBuffers(item));
}
}
return candidates;
}
function decodeEmbeddedStringValues(value, depth = 0) {
if (depth > 3 || typeof value !== "string") {
return [];
}
const trimmed = value.trim();
if (!trimmed || looksLikeNoiseString(trimmed)) {
return [];
}
const decoded = [];
const parsed = readJsonText(trimmed);
if (parsed !== undefined) {
decoded.push(parsed);
for (const item of findStringValuesByKeys(parsed, ["body", "data", "dataBinary", "data_binary", "message", "payload", "request"])) {
decoded.push(...decodeEmbeddedStringValues(item, depth + 1));
}
}
if (/%[0-9a-f]{2}/i.test(trimmed)) {
try {
const uriDecoded = decodeURIComponent(trimmed);
if (uriDecoded !== trimmed) {
decoded.push(uriDecoded);
decoded.push(...decodeEmbeddedStringValues(uriDecoded, depth + 1));
}
} catch {
// Not URI encoded.
}
}
for (const buffer of base64CandidateBuffers(trimmed)) {
if (!Buffer.isBuffer(buffer) || buffer.length === 0) {
continue;
}
if (looksLikeTextBuffer(buffer)) {
const text = buffer.toString("utf8").trim();
if (text && text !== trimmed && !looksLikeNoiseString(text)) {
decoded.push(text);
decoded.push(...decodeEmbeddedStringValues(text, depth + 1));
}
}
for (const item of decodeAllProtoStrings(buffer)) {
if (item.value && !looksLikeNoiseString(item.value)) {
decoded.push(item.value);
decoded.push(...decodeEmbeddedStringValues(item.value, depth + 1));
}
}
}
return uniqueDecodedValues(decoded);
}
function uniqueBuffers(buffers) {
const result = [];
const seen = new Set();
for (const buffer of buffers) {
if (!Buffer.isBuffer(buffer) || buffer.length === 0) {
continue;
}
const key = `${buffer.length}:${buffer.subarray(0, 32).toString("hex")}`;
if (seen.has(key)) {
continue;
}
seen.add(key);
result.push(buffer);
}
return result;
}
function uniqueDecodedValues(values) {
const result = [];
const seen = new Set();
for (const value of values) {
if (value === undefined || value === null) {
continue;
}
const key = typeof value === "string" ? `s:${value}` : `j:${safeJsonStringify(value)}`;
if (seen.has(key)) {
continue;
}
seen.add(key);
result.push(value);
}
return result;
}
function safeJsonStringify(value) {
try {
return JSON.stringify(value);
} catch {
return String(value);
}
}
function base64CandidateBuffers(text) {
const candidates = [];
const compact = text.replace(/\s+/g, "");
if (!compact || compact.length < 4) {
return candidates;
}
if (/^[A-Za-z0-9+/]+={0,2}$/.test(compact)) {
candidates.push(Buffer.from(padBase64(compact), "base64"));
}
if (/^[A-Za-z0-9_-]+={0,2}$/.test(compact)) {
candidates.push(Buffer.from(padBase64(compact), "base64url"));
}
return candidates;
}
function padBase64(value) {
const remainder = value.length % 4;
return remainder === 0 ? value : `${value}${"=".repeat(4 - remainder)}`;
}
function extractAgentRunRequestFromCandidates(candidates) {
const uniqueCandidates = [];
const seen = new Set();
for (const candidate of candidates) {
if (!Buffer.isBuffer(candidate) || candidate.length === 0) {
continue;
}
const key = `${candidate.length}:${candidate.subarray(0, 16).toString("hex")}`;
if (seen.has(key)) {
continue;
}
seen.add(key);
uniqueCandidates.push(candidate);
const runRequest = extractAgentRunRequest(candidate, 0);
if (runRequest) {
return runRequest;
}
}
for (const candidate of uniqueCandidates) {
const runRequest = extractAgentRunRequestFromOffsets(candidate);
if (runRequest) {
return runRequest;
}
}
return undefined;
}
function extractAgentRunRequest(message, depth) {
if (!Buffer.isBuffer(message) || message.length === 0 || depth > 8) {
return undefined;
}
if (looksLikeAgentRunRequest(message)) {
return message;
}
const runRequest = decodeProtoMessageFields(message, 1)[0];
if (looksLikeAgentRunRequest(runRequest)) {
return runRequest;
}
for (const field of readLengthDelimitedProtoFields(message)) {
const nested = extractAgentRunRequest(field.value, depth + 1);
if (nested) {
return nested;
}
}
return undefined;
}
function extractAgentRunRequestFromOffsets(message) {
if (!Buffer.isBuffer(message) || message.length < 2) {
return undefined;
}
const maxOffset = Math.min(16, message.length - 1);
for (let offset = 1; offset <= maxOffset; offset += 1) {
const nested = extractAgentRunRequest(message.subarray(offset), 1);
if (nested) {
return nested;
}
}
return undefined;
}
function looksLikeAgentRunRequest(message) {
if (!Buffer.isBuffer(message) || message.length === 0) {
return false;
}
return Boolean(
looksLikeConversationState(decodeProtoMessageFields(message, 1)[0]) ||
looksLikeConversationAction(decodeProtoMessageFields(message, 2)[0]) ||
looksLikeModelDetails(decodeProtoMessageFields(message, 3)[0]) ||
looksLikeRequestedModel(decodeProtoMessageFields(message, 9)[0]) ||
decodeProtoStringField(message, 5) ||
decodeProtoStringField(message, 8) ||
decodeProtoStringField(message, 18)
);
}
function looksLikeConversationState(message) {
return Buffer.isBuffer(message) && (
decodeProtoStringFields(message, 1).length > 0 ||
decodeProtoMessageFields(message, 8).length > 0
);
}
function looksLikeConversationAction(message) {
if (!Buffer.isBuffer(message)) {
return false;
}
const userMessageAction = decodeProtoMessageFields(message, 1)[0];
const startPlanAction = decodeProtoMessageFields(message, 6)[0];
return Boolean(
decodeUserMessage(decodeProtoMessageFields(userMessageAction, 1)[0]) ||
decodeProtoMessageFields(userMessageAction, 4).some((item) => decodeUserMessage(item)) ||
decodeUserMessage(decodeProtoMessageFields(startPlanAction, 1)[0])
);
}
function looksLikeModelDetails(message) {
return Buffer.isBuffer(message) && Boolean(decodeProtoStringField(message, 1));
}
function looksLikeRequestedModel(message) {
return Buffer.isBuffer(message) && Boolean(decodeProtoStringField(message, 1));
}
function getBidiSession(runtime, requestId, create) {
cleanupBidiSessions(runtime);
let session = runtime.bidiSessions.get(requestId);
if (!session && create) {
session = {
createdAt: Date.now(),
lastSeen: Date.now(),
lastRunRequestAt: 0,
requestId,
runRequest: undefined,
appends: new Map(),
waiters: []
};
runtime.bidiSessions.set(requestId, session);
}
return session;
}
function findSingleWaitingBidiSession(runtime) {
cleanupBidiSessions(runtime);
const waitingSessions = [...runtime.bidiSessions.values()].filter((session) => session.waiters.length > 0);
return waitingSessions.length === 1 ? waitingSessions[0] : undefined;
}
function cleanupBidiSessions(runtime) {
const now = Date.now();
for (const [requestId, session] of runtime.bidiSessions) {
if (now - session.lastSeen > runtime.bidiSessionTtlMs) {
runtime.bidiSessions.delete(requestId);
}
}
}
function describeBidiSessions(runtime) {
cleanupBidiSessions(runtime);
return [...runtime.bidiSessions.values()]
.map((session) => describeBidiSession(runtime, session))
.sort((left, right) => right.lastSeenAt - left.lastSeenAt)
.slice(0, 25);
}
function describeBidiSession(runtime, session) {
const appends = sortedBidiAppends(session);
const chatRequest = session.runRequest ? convertAgentRunRequestToOpenAIChat(runtime, session.runRequest) : undefined;
return {
ageMs: Date.now() - session.createdAt,
appendDetails: formatBidiAppendDetails(appends),
appendSeqno: session.appendSeqno || 0,
appends: appends.length,
chat: chatRequest ? summarizeChatRequest(chatRequest) : undefined,
combinedDataBinaryBytes: combinedAppendDataBinaryLength(session),
combinedDataBytes: combinedAppendDataBytesLength(session),
combinedDataTextBytes: combinedAppendDataLength(session),
decodedHints: summarizeBidiDecodedHints(session),
lastRunRequestAt: session.lastRunRequestAt || 0,
lastSeenAgoMs: Date.now() - session.lastSeen,
lastSeenAt: session.lastSeen,
requestId: session.requestId,
waiters: session.waiters.length
};
}
function summarizeBidiDecodedHints(session) {
const strings = [];
const addStringsFromBuffer = (buffer) => {
if (!Buffer.isBuffer(buffer) || buffer.length === 0) {
return;
}
if (looksLikeTextBuffer(buffer)) {
strings.push(buffer.toString("utf8"));
}
for (const item of decodeAllProtoStrings(buffer)) {
strings.push(item.value);
}
};
for (const append of sortedBidiAppends(session)) {
addStringsFromBuffer(append.rpcBody);
addStringsFromBuffer(append.dataBytes);
addStringsFromBuffer(append.dataBinary);
}
const uniqueStrings = [...new Set(strings.filter((value) => value && !looksLikeNoiseString(value)))];
const toolKeyPattern = new RegExp(`\\b(?:${CURSOR_TOOL_KEYS.map(escapeRegExp).join("|")})\\b`, "i");
return {
jsonLikeStrings: uniqueStrings.filter((value) => readJsonText(value) !== undefined).length,
maxStringLength: uniqueStrings.reduce((max, value) => Math.max(max, value.length), 0),
strings: uniqueStrings.length,
systemLikeStrings: uniqueStrings.filter(looksLikeSystemPromptText).length,
toolLikeStrings: uniqueStrings.filter((value) => toolKeyPattern.test(value)).length
};
}
function setBidiSessionRunRequest(session, runRequest) {
session.runRequest = runRequest;
session.lastRunRequestAt = Date.now();
session.runRequestVersion = (session.runRequestVersion || 0) + 1;
const waiters = session.waiters.splice(0);
waiters.forEach((resolve) => resolve(runRequest));
}
function waitForBidiRunRequest(session, timeoutMs) {
if (session.runRequest) {
return Promise.resolve(session.runRequest);
}
return new Promise((resolve) => {
const timer = setTimeout(() => {
const index = session.waiters.indexOf(done);
if (index >= 0) {
session.waiters.splice(index, 1);
}
resolve(undefined);
}, timeoutMs);
const done = (runRequest) => {
clearTimeout(timer);
resolve(runRequest);
};
session.waiters.push(done);
});
}
function waitForBidiRunRequestAfter(session, runRequestVersion, timeoutMs) {
const observedVersion = Math.max(0, Number(runRequestVersion) || 0);
if ((session.runRequestVersion || 0) > observedVersion && session.runRequest) {
return Promise.resolve(session.runRequest);
}
return new Promise((resolve) => {
const timer = setTimeout(() => {
const index = session.waiters.indexOf(done);
if (index >= 0) {
session.waiters.splice(index, 1);
}
resolve(undefined);
}, timeoutMs);
const done = (runRequest) => {
clearTimeout(timer);
if ((session.runRequestVersion || 0) > observedVersion) {
resolve(runRequest);
} else {
resolve(undefined);
}
};
session.waiters.push(done);
});
}
async function waitForBidiRunRequestSettle(session, settleMs, maxSettleMs) {
const settleWindow = Math.max(0, Number(settleMs) || 0);
const maxWindow = Math.max(settleWindow, Number(maxSettleMs) || 0);
if (!session.runRequest || settleWindow <= 0 || maxWindow <= 0) {
return;
}
const startedAt = Date.now();
while (Date.now() - startedAt < maxWindow) {
const updatedAt = session.lastRunRequestAt || startedAt;
const quietFor = Date.now() - updatedAt;
if (quietFor >= settleWindow) {
return;
}
const remainingSettle = settleWindow - quietFor;
const remainingMax = maxWindow - (Date.now() - startedAt);
await sleep(Math.max(1, Math.min(remainingSettle, remainingMax)));
}
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function waitForRicherBidiChatRequest(runtime, session, timeoutMs, baseChatRequest) {
const startedAt = Date.now();
const deadline = startedAt + Math.max(0, Number(timeoutMs) || 0);
let observedRunRequestAt = session.lastRunRequestAt || startedAt;
let currentChatRequest = baseChatRequest;
while (Date.now() < deadline) {
if (session.runRequest) {
const decoded = convertAgentRunRequestToOpenAIChat(runtime, session.runRequest);
if (decoded) {
currentChatRequest = decoded;
}
}
const collected = findCollectedCursorContext(runtime, session, session.runRequest, currentChatRequest);
if (collected) {
runtime.logger?.debug?.(
`Cursor proxy found collected Cursor context for ${session.requestId} after ` +
`${Date.now() - startedAt}ms (${formatChatRequestSummary(collected.chatRequest)}).`
);
return { chatRequest: collected.chatRequest, runRequest: session.runRequest };
}
if ((session.lastRunRequestAt || 0) > observedRunRequestAt && session.runRequest) {
observedRunRequestAt = session.lastRunRequestAt;
const chatRequest = currentChatRequest || convertAgentRunRequestToOpenAIChat(runtime, session.runRequest);
if (chatRequest && !isSimplifiedAgentChatRequest(chatRequest)) {
runtime.logger?.debug?.(
`Cursor proxy found richer AgentRunRequest for ${session.requestId} after ` +
`${Date.now() - startedAt}ms (${formatChatRequestSummary(chatRequest)}).`
);
return { chatRequest, runRequest: session.runRequest };
}
}
await sleep(Math.min(100, Math.max(1, deadline - Date.now())));
}
return undefined;
}
function collectCursorContextKeys(payload, chatRequest) {
const keys = new Set();
const add = (value) => {
const text = stringValue(value);
if (!text) {
return;
}
keys.add(text);
for (const match of text.matchAll(new RegExp(UUID_PATTERN.source, "ig"))) {
keys.add(match[0]);
}
};
if (isRecord(payload)) {
for (const key of [
"requestId",
"request_id",
"bidiRequestId",
"bidi_request_id",
"runId",
"run_id",
"conversationId",
"conversation_id",
"clientRequestId",
"client_request_id",
"chatId",
"chat_id",
"threadId",
"thread_id",
"sessionId",
"session_id",
"traceId",
"trace_id"
]) {
add(payload[key]);
}
}
for (const value of findStringValuesByKeys(payload, [
"requestId",
"request_id",
"bidiRequestId",
"bidi_request_id",
"conversationId",
"conversation_id",
"clientRequestId",
"client_request_id"
])) {
add(value);
}
add(lastUserMessageText(chatRequest));
if (keys.size === 0) {
keys.add("__latest__");
}
keys.add("__latest__");
return [...keys];
}
function storeCursorContext(runtime, context) {
cleanupCursorContexts(runtime);
const keys = context.keys.length > 0 ? context.keys : ["__latest__"];
const stored = { ...context, keys };
for (const key of keys) {
runtime.cursorContexts.set(key, stored);
}
trimCursorContexts(runtime);
return stored;
}
function cleanupCursorContexts(runtime) {
const ttlMs = Math.max(0, Number(runtime.cursorContextTtlMs) || 0);
if (ttlMs <= 0) {
return;
}
const now = Date.now();
for (const [key, context] of runtime.cursorContexts) {
if (now - context.createdAt > ttlMs) {
runtime.cursorContexts.delete(key);
}
}
}
function trimCursorContexts(runtime) {
const maxEntries = Math.max(1, Number(runtime.cursorContextMaxEntries) || DEFAULT_CURSOR_CONTEXT_MAX_ENTRIES);
while (runtime.cursorContexts.size > maxEntries) {
let oldestKey;
let oldestAt = Infinity;
for (const [key, context] of runtime.cursorContexts) {
if (context.createdAt < oldestAt) {
oldestAt = context.createdAt;
oldestKey = key;
}
}
if (!oldestKey) {
return;
}
runtime.cursorContexts.delete(oldestKey);
}
}
function findCollectedCursorContext(runtime, session, runRequest, chatRequest) {
if (!runtime.cursorContextCollector || !runtime.cursorContexts?.size) {
return undefined;
}
cleanupCursorContexts(runtime);
const keys = new Set([session?.requestId]);
if (runRequest) {
for (const item of decodeAllProtoStrings(runRequest)) {
for (const match of item.value.matchAll(new RegExp(UUID_PATTERN.source, "ig"))) {
keys.add(match[0]);
}
}
}
for (const key of keys) {
const context = key ? runtime.cursorContexts.get(key) : undefined;
if (context) {
return context;
}
}
if (chatRequest) {
const matchedByPrompt = findCollectedCursorContextByPrompt(runtime, chatRequest);
if (matchedByPrompt) {
return matchedByPrompt;
}
}
if (!chatRequest) {
const latest = runtime.cursorContexts.get("__latest__");
return latest && Date.now() - latest.createdAt <= runtime.cursorContextTtlMs ? latest : undefined;
}
return undefined;
}
function findCollectedCursorContextByPrompt(runtime, chatRequest) {
const prompt = lastUserMessageText(chatRequest);
if (!prompt) {
return undefined;
}
const uniqueContexts = uniqueContextsNewestFirst(runtime.cursorContexts);
return uniqueContexts.find((context) => chatRequestContainsUserText(context.chatRequest, prompt));
}
function uniqueContextsNewestFirst(contextsByKey) {
const seen = new Set();
const contexts = [];
for (const context of contextsByKey.values()) {
if (seen.has(context)) {
continue;
}
seen.add(context);
contexts.push(context);
}
return contexts.sort((left, right) => right.createdAt - left.createdAt);
}
function chatRequestContainsUserText(chatRequest, text) {
const normalizedNeedle = normalizePromptFingerprint(text);
if (!normalizedNeedle) {
return false;
}
return (Array.isArray(chatRequest?.messages) ? chatRequest.messages : []).some((message) => {
if (message.role !== "user") {
return false;
}
const normalizedContent = normalizePromptFingerprint(stringifyContent(message.content));
return normalizedContent === normalizedNeedle ||
normalizedContent.includes(normalizedNeedle) ||
normalizedNeedle.includes(normalizedContent);
});
}
function normalizePromptFingerprint(value) {
return String(value || "").replace(/\s+/g, " ").trim().slice(-4000);
}
function lastUserMessageText(chatRequest) {
const messages = Array.isArray(chatRequest?.messages) ? chatRequest.messages : [];
for (let index = messages.length - 1; index >= 0; index -= 1) {
if (messages[index]?.role === "user") {
return stringifyContent(messages[index].content) || "";
}
}
return "";
}
function applyCollectedCursorContext(runtime, session, runRequest, chatRequest) {
const collected = findCollectedCursorContext(runtime, session, runRequest, chatRequest);
if (!collected) {
return chatRequest;
}
const merged = mergeCollectedCursorChatRequest(chatRequest, collected.chatRequest);
runtime.logger?.debug?.(
`Cursor proxy applied collected Cursor context for ${session.requestId} ` +
`(${formatChatRequestSummary(merged)}).`
);
return merged;
}
function mergeCollectedCursorChatRequest(baseRequest, collectedRequest) {
const baseMessages = Array.isArray(baseRequest?.messages) ? baseRequest.messages : [];
const collectedMessages = Array.isArray(collectedRequest?.messages) ? collectedRequest.messages : [];
const collectedHasInteractiveMessage = hasInteractiveChatMessage(collectedMessages);
const messages = collectedHasInteractiveMessage
? collectedMessages
: compactChatMessages([
...collectedMessages,
...baseMessages
]);
return compactObject({
...baseRequest,
...collectedRequest,
messages: messages.length > 0 ? messages : baseMessages,
model: stringValue(collectedRequest?.model) || stringValue(baseRequest?.model) || "cursor-proxy",
stream: true,
tool_choice: collectedRequest?.tool_choice ?? baseRequest?.tool_choice,
tools: Array.isArray(collectedRequest?.tools) && collectedRequest.tools.length > 0
? collectedRequest.tools
: baseRequest?.tools
});
}
function convertAgentRunRequestToOpenAIChat(runtime, runRequest) {
const messages = [];
const contextValues = decodeAgentRunRequestContextValues(runRequest);
const fallbackSystemPrompt = decodeProtoStringField(runRequest, 5);
const systemPrompt = composeAgentSystemPrompt(
contextValues,
selectPreferredSystemPrompt(
decodeProtoStringField(runRequest, 8),
extractSystemPromptFromValues(contextValues),
findBestSystemPromptStringFromValues(contextValues),
looksLikeSystemPromptText(fallbackSystemPrompt) ? fallbackSystemPrompt : undefined
)
);
if (systemPrompt) {
messages.push({ content: systemPrompt, role: "system" });
}
const conversationState = decodeProtoMessageFields(runRequest, 1)[0];
if (conversationState) {
messages.push(...decodeConversationStateMessages(conversationState));
}
const action = decodeProtoMessageFields(runRequest, 2)[0];
if (action) {
messages.push(...decodeConversationActionMessages(action));
}
const compactedMessages = compactChatMessages(messages);
if (compactedMessages.length === 0) {
const fallbackPrompt = decodeAllProtoStrings(runRequest)
.map((item) => item.value)
.filter((value) => value.length > 3 && !looksLikeNoiseString(value))
.sort((a, b) => b.length - a.length)[0];
if (fallbackPrompt) {
compactedMessages.push({ content: fallbackPrompt, role: "user" });
}
}
if (compactedMessages.length === 0) {
return undefined;
}
const requestedModel = decodeProtoMessageFields(runRequest, 9)[0];
const modelDetails = decodeProtoMessageFields(runRequest, 3)[0];
const parameters = decodeRequestedModelParameters(requestedModel);
const decodedTools = uniqueTools([
...extractToolsFromValues(contextValues),
...extractCursorNativeToolsFromRunRequest(runtime, runRequest, contextValues, systemPrompt)
]);
const tools = shouldForwardDecodedCursorTools(runtime) ? decodedTools : [];
const toolChoice = tools.length > 0 ? extractToolChoiceFromValues(contextValues) : undefined;
const model =
decodeProtoStringField(requestedModel, 1) ||
decodeProtoStringField(modelDetails, 1) ||
decodeProtoStringField(runRequest, 18) ||
runtime.defaultModel ||
"cursor-proxy";
return compactObject({
max_tokens: numberFromParameter(parameters.max_tokens || parameters.maxTokens || parameters.output_tokens),
messages: compactedMessages,
model,
stream: true,
temperature: numberFromParameter(parameters.temperature),
tool_choice: toolChoice,
tools: tools.length > 0 ? tools : undefined,
top_p: numberFromParameter(parameters.top_p || parameters.topP)
});
}
function logAgentChatConversion(runtime, session, chatRequest) {
const summary = summarizeChatRequest(chatRequest);
const decodedToolSummary = summarizeDecodedCursorToolForwarding(runtime, session?.runRequest, chatRequest);
const diagnostic =
`${formatChatSummary(summary)}, ` +
`decoded_tools=${decodedToolSummary.decodedTools}, ` +
`native_tool_enums=${decodedToolSummary.nativeToolEnums}, ` +
`suppressed_decoded_tools=${decodedToolSummary.suppressedTools}, ` +
`appends=${session.appends.size}, last_seq=${session.appendSeqno || 0}`;
const dumpPath = writeAgentRunRequestDecodeDump(runtime, session, session.runRequest, chatRequest, "initial");
const dumpSuffix = dumpPath ? ` decode_dump=${dumpPath}` : "";
const missingSystem = summary.system === 0;
const missingTools = summary.tools === 0 && decodedToolSummary.suppressedTools === 0;
if (decodedToolSummary.suppressedTools > 0 && runtime.warnedSuppressedDecodedCursorTools !== true) {
runtime.warnedSuppressedDecodedCursorTools = true;
runtime.logger?.warn?.(
`Cursor proxy decoded ${decodedToolSummary.suppressedTools} Cursor tools from AgentRunRequest, ` +
"but did not forward them to the upstream model because config.forwardDecodedCursorTools=false. " +
"OpenAI tool_calls -> Cursor Agent tool events bridge is available when decoded tools are forwarded." +
dumpSuffix
);
}
if (summary.simplified || missingSystem || missingTools) {
const missingContext = [
missingSystem ? "system" : "",
missingTools ? "tools" : ""
].filter(Boolean).join("/") || "context";
runtime.logger?.warn?.(
`Cursor proxy decoded AgentRunRequest ${session.requestId} without ${missingContext} (${diagnostic}). ` +
"The decoded Cursor payload does not expose Agent context; post full Cursor hook context to " +
"/plugins/cursor-proxy/collector, or configure cursor-proxy config.systemPrompt/config.tools " +
`for static fallback context if this Cursor flow omits it.${dumpSuffix}`
);
logCursorDecodeDiagnostics(
runtime,
"warn",
`Cursor proxy AgentRunRequest detailed decode diagnostics for ${session.requestId}: ` +
formatAgentRunRequestDecodeDiagnostic(runtime, session.runRequest, chatRequest) +
`; bidi_session=${formatBidiSessionDecodeDiagnostic(runtime, session)}`
);
return;
}
runtime.logger?.debug?.(`Cursor proxy decoded AgentRunRequest ${session.requestId} (${diagnostic}).${dumpSuffix}`);
if (Buffer.isBuffer(session.runRequest)) {
const contextValues = decodeAgentRunRequestContextValues(session.runRequest);
const systemPrompt = chatRequest?.messages?.find((message) => message?.role === "system")?.content;
const nativeExtraction = describeCursorNativeToolExtraction(runtime, session.runRequest, contextValues, systemPrompt);
runtime.logger?.debug?.(
`Cursor proxy native tool extraction for ${session.requestId}: ` +
safeJsonStringify(nativeExtraction)
);
}
}
function formatAgentRunRequestSummary(runtime, runRequest) {
const chatRequest = convertAgentRunRequestToOpenAIChat(runtime, runRequest);
return chatRequest ? formatChatRequestSummary(chatRequest) : "chat=unavailable";
}
function formatChatRequestSummary(chatRequest) {
return formatChatSummary(summarizeChatRequest(chatRequest));
}
function formatChatSummary(summary) {
return [
`messages=${summary.messages}`,
`system=${summary.system}`,
`user=${summary.user}`,
`assistant=${summary.assistant}`,
`tools=${summary.tools}`,
`roles=${summary.roles.join(",") || "none"}`
].join(", ");
}
function summarizeChatRequest(chatRequest) {
const messages = Array.isArray(chatRequest?.messages) ? chatRequest.messages : [];
const roles = messages.map((message) => normalizeRole(stringValue(message.role)) || "unknown");
const system = roles.filter((role) => role === "system").length;
const user = roles.filter((role) => role === "user").length;
const assistant = roles.filter((role) => role === "assistant").length;
const tools = Array.isArray(chatRequest?.tools) ? chatRequest.tools.length : 0;
return {
assistant,
messages: messages.length,
roles,
simplified: messages.length > 0 && system === 0 && tools === 0 && roles.every((role) => role === "user"),
system,
tools,
user
};
}
function shouldForwardDecodedCursorTools(runtime) {
return runtime?.forwardDecodedCursorTools === true;
}
function summarizeDecodedCursorToolForwarding(runtime, runRequest, chatRequest) {
const contextValues = Buffer.isBuffer(runRequest) ? decodeAgentRunRequestContextValues(runRequest) : [];
const decodedTools = Buffer.isBuffer(runRequest)
? uniqueTools([
...extractToolsFromValues(contextValues),
...extractCursorNativeToolsFromRunRequest(runtime, runRequest, contextValues)
])
: [];
const forwardedTools = Array.isArray(chatRequest?.tools) ? chatRequest.tools.length : 0;
const forwardDecodedCursorTools = shouldForwardDecodedCursorTools(runtime);
return {
decodedTools: decodedTools.length,
forwardDecodedCursorTools,
forwardedTools,
nativeToolEnums: Buffer.isBuffer(runRequest) ? extractCursorSupportedToolEnums(runRequest).length : 0,
suppressedTools: forwardDecodedCursorTools ? 0 : decodedTools.length
};
}
function shouldLogDecodeDiagnostics(runtime) {
return runtime?.decodeDiagnostics !== false;
}
function logCursorDecodeDiagnostics(runtime, level, message) {
if (!shouldLogDecodeDiagnostics(runtime)) {
return;
}
const logger = runtime?.logger;
if (level === "warn") {
logger?.warn?.(message);
return;
}
logger?.debug?.(message);
}
function formatAgentRunRequestDecodeDiagnostic(runtime, runRequest, chatRequest) {
if (!Buffer.isBuffer(runRequest)) {
return safeJsonStringify({ error: "missing-run-request" });
}
const embeddedPayloads = decodeJsonPayloadsFromProtoStrings(runRequest);
const contextValues = decodeAgentRunRequestContextValues(runRequest);
const fallbackSystemPrompt = decodeProtoStringField(runRequest, 5);
const systemPrompt = composeAgentSystemPrompt(
contextValues,
selectPreferredSystemPrompt(
decodeProtoStringField(runRequest, 8),
extractSystemPromptFromValues(contextValues),
findBestSystemPromptStringFromValues(contextValues),
looksLikeSystemPromptText(fallbackSystemPrompt) ? fallbackSystemPrompt : undefined
)
);
const tools = extractToolsFromValues(contextValues);
const toolChoice = extractToolChoiceFromValues(contextValues);
const toolFilePaths = extractCursorToolFilePaths(contextValues);
const instructionFilePaths = extractCursorInstructionFilePaths(contextValues);
const instructionTexts = extractCursorInstructionTexts(contextValues);
const nativeSupportedTools = extractCursorSupportedToolEnums(runRequest);
const nativeTools = extractCursorNativeToolsFromRunRequest(runtime, runRequest, contextValues, systemPrompt);
const decodedTools = uniqueTools([...tools, ...nativeTools]);
const toolForwarding = summarizeDecodedCursorToolForwarding(runtime, runRequest, chatRequest);
const nativeExtraction = describeCursorNativeToolExtraction(runtime, runRequest, contextValues, systemPrompt);
return safeJsonStringify(compactObject({
bytes: runRequest.length,
chat: chatRequest ? summarizeChatRequest(chatRequest) : undefined,
contextValues: summarizeDecodedValuesForDiagnostics(runtime, contextValues),
directStrings: compactObject({
field5: summarizeLogString(runtime, fallbackSystemPrompt),
field8: summarizeLogString(runtime, decodeProtoStringField(runRequest, 8)),
field18: summarizeLogString(runtime, decodeProtoStringField(runRequest, 18))
}),
embeddedJson: summarizeJsonPayloadsForDiagnostics(runtime, embeddedPayloads),
extracted: compactObject({
instructionFiles: instructionFilePaths.length,
instructionFileSamples: instructionFilePaths.slice(0, decodeDiagnosticSampleLimit(runtime)),
instructionTexts: instructionTexts.length,
nativeExtraction,
nativeSupportedToolEnums: summarizeCursorSupportedToolEnums(runtime, nativeSupportedTools),
nativeToolNames: summarizeToolNamesForDiagnostics(runtime, nativeTools),
nativeTools: nativeTools.length,
systemPrompt: summarizeLogString(runtime, systemPrompt),
toolFiles: toolFilePaths.length,
toolFileSamples: toolFilePaths.slice(0, decodeDiagnosticSampleLimit(runtime)),
toolChoice,
toolForwarding,
toolNames: summarizeToolNamesForDiagnostics(runtime, decodedTools),
tools: decodedTools.length
}),
fields: formatProtoFieldSummary(runRequest),
shapeReasons: agentRunRequestShapeReasons(runRequest),
strings: summarizeProtoStringsForDiagnostics(runtime, decodeAllProtoStrings(runRequest)),
topLevel: summarizeAgentRunRequestTopLevel(runRequest)
}));
}
function formatBidiSessionDecodeDiagnostic(runtime, session) {
if (!session) {
return safeJsonStringify({ error: "missing-session" });
}
const appends = sortedBidiAppends(session);
return safeJsonStringify({
appendDetails: formatBidiAppendDetails(appends),
appends: appends.length,
candidates: summarizeCandidateBuffersForDiagnostics(runtime, bidiSessionCandidateBuffers(session)),
combinedDataBinaryBytes: combinedAppendDataBinaryLength(session),
combinedDataBytes: combinedAppendDataBytesLength(session),
combinedDataTextBytes: combinedAppendDataLength(session),
decodedHints: summarizeBidiDecodedHints(session),
lastRunRequestAt: session.lastRunRequestAt || 0,
requestId: session.requestId
});
}
function formatCursorNativeProtoDecodeDiagnostic(runtime, path, requestBody, chatRequest) {
const candidates = cursorNativeCandidateBuffers(requestBody);
const runRequest = extractAgentRunRequestFromCandidates(candidates);
return safeJsonStringify(compactObject({
candidates: summarizeCandidateBuffersForDiagnostics(runtime, candidates),
chat: summarizeChatRequest(chatRequest),
path,
requestBytes: Buffer.isBuffer(requestBody) ? requestBody.length : 0,
runRequest: runRequest ? JSON.parse(formatAgentRunRequestDecodeDiagnostic(runtime, runRequest, chatRequest)) : undefined
}));
}
function writeAgentRunRequestDecodeDump(runtime, session, runRequest, chatRequest, label = "") {
if (!shouldLogDecodeDiagnostics(runtime) || runtime?.decodeDumpFull === false || !Buffer.isBuffer(runRequest)) {
return "";
}
const dumpDir = stringValue(runtime.decodeDumpDir) || DEFAULT_DECODE_DUMP_DIR;
try {
fs.mkdirSync(dumpDir, { recursive: true });
const labelSuffix = label ? `-${sanitizeRouteId(label)}` : "";
const fileName = `${sanitizeRouteId(session?.requestId || "agent-run")}${labelSuffix}-${Date.now()}.json`;
const filePath = path.join(dumpDir, fileName);
fs.writeFileSync(
filePath,
`${JSON.stringify(buildAgentRunRequestDecodeDump(runtime, session, runRequest, chatRequest, label), null, 2)}\n`,
"utf8"
);
return filePath;
} catch (error) {
runtime?.logger?.warn?.(`Cursor proxy failed to write full decode dump: ${formatError(error)}`);
return "";
}
}
function buildAgentRunRequestDecodeDump(runtime, session, runRequest, chatRequest, label = "") {
const contextValues = decodeAgentRunRequestContextValues(runRequest);
const embeddedPayloads = decodeJsonPayloadsFromProtoStrings(runRequest);
const toolFilePaths = extractCursorToolFilePaths(contextValues);
const instructionFilePaths = extractCursorInstructionFilePaths(contextValues);
const tools = extractToolsFromValues(contextValues);
const nativeSupportedTools = extractCursorSupportedToolEnums(runRequest);
const nativeTools = extractCursorNativeToolsFromRunRequest(
runtime,
runRequest,
contextValues,
chatRequest?.messages?.find((message) => message?.role === "system")?.content
);
const appends = session ? sortedBidiAppends(session) : [];
const toolForwarding = summarizeDecodedCursorToolForwarding(runtime, runRequest, chatRequest);
return {
appends: appends.map((append) => ({
appendSeqno: append.appendSeqno,
dataBase64: append.dataBytes.length > 0 ? append.dataBytes.toString("base64") : undefined,
dataBinaryBase64: append.dataBinary.length > 0 ? append.dataBinary.toString("base64") : undefined,
dataBytes: append.dataBytes.length,
dataText: append.data || undefined,
requestId: append.requestId,
rpcBodyBase64: append.rpcBody.length > 0 ? append.rpcBody.toString("base64") : undefined,
rpcBodyBytes: append.rpcBody.length,
rpcBodyEncoding: append.rpcBodyEncoding
})),
chatRequest,
contextValues: contextValues.map((value) => serializeDiagnosticValue(value)),
embeddedJsonPayloads: embeddedPayloads.map((value) => serializeDiagnosticValue(value)),
extracted: {
instructionFileContents: instructionFilePaths.map((filePath) => ({
content: readTextFileIfSmall(filePath, 256 * 1024),
path: filePath
})),
instructionFilePaths,
nativeSupportedTools,
nativeExtraction: describeCursorNativeToolExtraction(
runtime,
runRequest,
contextValues,
chatRequest?.messages?.find((message) => message?.role === "system")?.content
),
nativeToolNames: nativeTools.map((tool) => stringValue(tool?.function?.name) || stringValue(tool?.name) || "unknown"),
nativeTools,
systemPrompt: chatRequest?.messages?.find((message) => message?.role === "system")?.content,
toolFileContents: toolFilePaths.map((filePath) => ({
json: readJsonText(readTextFileIfSmall(filePath, 256 * 1024)),
path: filePath
})),
toolFilePaths,
toolForwarding,
toolNames: uniqueTools([...tools, ...nativeTools])
.map((tool) => stringValue(tool?.function?.name) || stringValue(tool?.name) || stringValue(tool?.type) || "unknown"),
tools: uniqueTools([...tools, ...nativeTools])
},
label,
proto: decodeProtoTree(runRequest, {
maxDepth: Math.max(1, Math.trunc(Number(runtime?.decodeTreeDepth) || DEFAULT_DECODE_TREE_DEPTH)),
path: ""
}),
raw: {
base64: runRequest.toString("base64"),
bytes: runRequest.length,
fieldSummary: formatProtoFieldSummary(runRequest),
hex: runRequest.toString("hex")
},
requestId: session?.requestId || "",
strings: decodeAllProtoStringsWithPaths(runRequest),
summary: {
chat: chatRequest ? summarizeChatRequest(chatRequest) : undefined,
contextValues: summarizeDecodedValuesForDiagnostics(runtime, contextValues),
embeddedJson: summarizeJsonPayloadsForDiagnostics(runtime, embeddedPayloads),
fields: formatProtoFieldSummary(runRequest),
shapeReasons: agentRunRequestShapeReasons(runRequest)
},
timestamp: new Date().toISOString()
};
}
function decodeProtoTree(buffer, options = {}, depth = 0, seen = new Set()) {
if (!Buffer.isBuffer(buffer)) {
return { error: "not-buffer" };
}
const maxDepth = Math.max(0, Math.trunc(Number(options.maxDepth) || DEFAULT_DECODE_TREE_DEPTH));
const currentPath = stringValue(options.path) || "";
const key = `${buffer.length}:${buffer.subarray(0, 32).toString("hex")}`;
if (seen.has(key)) {
return { bytes: buffer.length, circular: true, path: currentPath };
}
seen.add(key);
const fields = [];
forEachProtoField(buffer, (field) => {
const fieldPath = currentPath ? `${currentPath}.${field.fieldNumber}` : String(field.fieldNumber);
fields.push(decodeProtoFieldForDump(field, fieldPath, maxDepth, depth, seen));
});
seen.delete(key);
return {
bytes: buffer.length,
fields,
path: currentPath
};
}
function decodeProtoFieldForDump(field, fieldPath, maxDepth, depth, seen) {
const base = {
fieldNumber: field.fieldNumber,
path: fieldPath,
wireType: field.wireType,
wireTypeName: protoWireTypeName(field.wireType)
};
if (field.wireType === 0) {
return { ...base, value: field.value.toString() };
}
if (field.wireType === 1 || field.wireType === 5) {
return {
...base,
base64: field.value.toString("base64"),
bytes: field.value.length,
hex: field.value.toString("hex")
};
}
if (field.wireType !== 2) {
return base;
}
const text = field.value.length > 0 && looksLikeTextBuffer(field.value) ? field.value.toString("utf8") : undefined;
const parsedJson = text !== undefined ? readJsonText(text) : undefined;
const canNest = depth < maxDepth && field.value.length > 0 && hasReadableProtoFields(field.value);
return compactObject({
...base,
base64: field.value.toString("base64"),
bytes: field.value.length,
children: canNest ? decodeProtoTree(field.value, { maxDepth, path: fieldPath }, depth + 1, seen).fields : undefined,
json: parsedJson !== undefined ? serializeDiagnosticValue(parsedJson) : undefined,
text
});
}
function decodeAllProtoStringsWithPaths(buffer, pathPrefix = "", depth = 0) {
if (!Buffer.isBuffer(buffer) || depth > 12) {
return [];
}
const values = [];
forEachProtoField(buffer, (field) => {
const fieldPath = pathPrefix ? `${pathPrefix}.${field.fieldNumber}` : String(field.fieldNumber);
if (field.wireType !== 2 || field.value.length === 0) {
return;
}
if (looksLikeTextBuffer(field.value)) {
const text = field.value.toString("utf8");
values.push({
fieldNumber: field.fieldNumber,
length: text.length,
path: fieldPath,
value: text
});
}
values.push(...decodeAllProtoStringsWithPaths(field.value, fieldPath, depth + 1));
});
return values;
}
function protoWireTypeName(wireType) {
if (wireType === 0) {
return "varint";
}
if (wireType === 1) {
return "fixed64";
}
if (wireType === 2) {
return "length-delimited";
}
if (wireType === 5) {
return "fixed32";
}
return "unknown";
}
function serializeDiagnosticValue(value, seen = new Set()) {
if (typeof value === "bigint") {
return value.toString();
}
if (Buffer.isBuffer(value)) {
return {
base64: value.toString("base64"),
bytes: value.length,
hex: value.toString("hex")
};
}
if (Array.isArray(value)) {
return value.map((item) => serializeDiagnosticValue(item, seen));
}
if (isRecord(value)) {
if (seen.has(value)) {
return "[Circular]";
}
seen.add(value);
const result = {};
for (const [key, rawValue] of Object.entries(value)) {
result[key] = serializeDiagnosticValue(rawValue, seen);
}
seen.delete(value);
return result;
}
return value;
}
function formatCandidateBufferSummary(runtime, candidates) {
return safeJsonStringify(summarizeCandidateBuffersForDiagnostics(runtime, candidates));
}
function summarizeCandidateBuffersForDiagnostics(runtime, candidates) {
const unique = uniqueBuffers(Array.isArray(candidates) ? candidates : []);
return {
samples: unique
.slice(0, decodeDiagnosticSampleLimit(runtime))
.map((buffer, index) => summarizeBufferForDiagnostics(runtime, buffer, index)),
total: Array.isArray(candidates) ? candidates.length : 0,
unique: unique.length
};
}
function summarizeBufferForDiagnostics(runtime, buffer, index) {
const strings = decodeAllProtoStrings(buffer);
return compactObject({
bytes: Buffer.isBuffer(buffer) ? buffer.length : 0,
fields: formatProtoFieldSummary(buffer),
headHex: Buffer.isBuffer(buffer) ? buffer.subarray(0, 32).toString("hex") : undefined,
index,
shapeReasons: agentRunRequestShapeReasons(buffer),
strings: summarizeProtoStringsForDiagnostics(runtime, strings),
textSample: Buffer.isBuffer(buffer) && looksLikeTextBuffer(buffer)
? truncateForLog(runtime, buffer.toString("utf8"))
: undefined
});
}
function summarizeAgentRunRequestTopLevel(runRequest) {
const conversationState = decodeProtoMessageFields(runRequest, 1)[0];
const action = decodeProtoMessageFields(runRequest, 2)[0];
const modelDetails = decodeProtoMessageFields(runRequest, 3)[0];
const requestedModel = decodeProtoMessageFields(runRequest, 9)[0];
return compactObject({
action: summarizeProtoBufferForDiagnostics(action),
conversationActionMessages: action ? decodeConversationActionMessages(action).length : 0,
conversationState: summarizeProtoBufferForDiagnostics(conversationState),
conversationStateMessages: conversationState ? decodeConversationStateMessages(conversationState).length : 0,
modelDetails: summarizeProtoBufferForDiagnostics(modelDetails),
requestedModel: summarizeProtoBufferForDiagnostics(requestedModel)
});
}
function summarizeProtoBufferForDiagnostics(buffer) {
if (!Buffer.isBuffer(buffer) || buffer.length === 0) {
return undefined;
}
return {
bytes: buffer.length,
fields: formatProtoFieldSummary(buffer),
strings: decodeAllProtoStrings(buffer).length
};
}
function agentRunRequestShapeReasons(buffer) {
if (!Buffer.isBuffer(buffer) || buffer.length === 0) {
return [];
}
const reasons = [];
const conversationState = decodeProtoMessageFields(buffer, 1)[0];
const conversationAction = decodeProtoMessageFields(buffer, 2)[0];
const modelDetails = decodeProtoMessageFields(buffer, 3)[0];
const requestedModel = decodeProtoMessageFields(buffer, 9)[0];
const field5 = decodeProtoStringField(buffer, 5);
const field8 = decodeProtoStringField(buffer, 8);
const field18 = decodeProtoStringField(buffer, 18);
if (looksLikeConversationState(conversationState)) {
reasons.push("field1:conversation_state");
}
if (looksLikeConversationAction(conversationAction)) {
reasons.push("field2:conversation_action");
}
if (looksLikeModelDetails(modelDetails)) {
reasons.push("field3:model_details");
}
if (looksLikeRequestedModel(requestedModel)) {
reasons.push("field9:requested_model");
}
if (field5) {
reasons.push(`field5:string:${field5.length}`);
}
if (field8) {
reasons.push(`field8:string:${field8.length}`);
}
if (field18) {
reasons.push(`field18:string:${field18.length}`);
}
return reasons;
}
function summarizeProtoStringsForDiagnostics(runtime, strings) {
const values = Array.isArray(strings)
? strings.filter((item) => item && typeof item.value === "string" && item.value.length > 0)
: [];
const unique = [];
const seen = new Set();
const toolKeyPattern = new RegExp(`\\b(?:${CURSOR_TOOL_KEYS.map(escapeRegExp).join("|")})\\b`, "i");
for (const item of values) {
const key = `${item.fieldNumber}:${item.value}`;
if (seen.has(key)) {
continue;
}
seen.add(key);
unique.push(item);
}
return {
count: values.length,
jsonLike: values.filter((item) => readJsonText(item.value) !== undefined).length,
jsonLikeSamples: sampleProtoStringItemsForDiagnostics(
runtime,
unique.filter((item) => readJsonText(item.value) !== undefined)
),
maxLength: values.reduce((max, item) => Math.max(max, item.value.length), 0),
samples: unique
.filter((item) => !looksLikeNoiseString(item.value))
.slice(0, decodeDiagnosticSampleLimit(runtime))
.map((item) => ({
field: item.fieldNumber,
length: item.value.length,
sample: truncateForLog(runtime, item.value)
})),
systemLike: values.filter((item) => looksLikeSystemPromptText(item.value)).length,
systemLikeSamples: sampleProtoStringItemsForDiagnostics(
runtime,
unique.filter((item) => looksLikeSystemPromptText(item.value))
),
toolKeyLike: values.filter((item) => toolKeyPattern.test(item.value)).length,
toolKeyLikeSamples: sampleProtoStringItemsForDiagnostics(
runtime,
unique.filter((item) => toolKeyPattern.test(item.value))
),
unique: unique.length
};
}
function sampleProtoStringItemsForDiagnostics(runtime, items) {
return (Array.isArray(items) ? items : [])
.filter((item) => item && typeof item.value === "string" && !looksLikeNoiseString(item.value))
.slice(0, decodeDiagnosticSampleLimit(runtime))
.map((item) => ({
field: item.fieldNumber,
length: item.value.length,
sample: truncateForLog(runtime, item.value)
}));
}
function summarizeDecodedValuesForDiagnostics(runtime, values) {
const list = Array.isArray(values) ? values : [];
const strings = list.filter((value) => typeof value === "string");
const records = list.filter(isRecord);
const arrays = list.filter(Array.isArray);
const toolKeyPattern = new RegExp(`\\b(?:${CURSOR_TOOL_KEYS.map(escapeRegExp).join("|")})\\b`, "i");
return {
arrays: arrays.length,
records: records.length,
samples: list
.filter((value) => typeof value === "string" && !looksLikeNoiseString(value))
.slice(0, decodeDiagnosticSampleLimit(runtime))
.map((value) => ({
length: value.length,
sample: truncateForLog(runtime, value)
})),
strings: strings.length,
systemLike: strings.filter(looksLikeSystemPromptText).length,
systemLikeSamples: strings
.filter(looksLikeSystemPromptText)
.slice(0, decodeDiagnosticSampleLimit(runtime))
.map((value) => ({
length: value.length,
sample: truncateForLog(runtime, value)
})),
toolKeyLike: strings.filter((value) => toolKeyPattern.test(value)).length,
toolKeyLikeSamples: strings
.filter((value) => toolKeyPattern.test(value))
.slice(0, decodeDiagnosticSampleLimit(runtime))
.map((value) => ({
length: value.length,
sample: truncateForLog(runtime, value)
})),
total: list.length
};
}
function summarizeJsonPayloadsForDiagnostics(runtime, payloads) {
const values = Array.isArray(payloads) ? payloads : [];
return {
count: values.length,
samples: values
.slice(0, decodeDiagnosticSampleLimit(runtime))
.map((value) => summarizeJsonPayloadForDiagnostics(runtime, value))
};
}
function summarizeJsonPayloadForDiagnostics(runtime, value) {
const tools = extractTools(value);
const messages = extractMessages(value);
return compactObject({
keys: isRecord(value) ? Object.keys(value).slice(0, 24) : undefined,
messageRoles: messages.map((message) => normalizeRole(stringValue(message.role)) || "unknown").slice(0, 16),
messages: messages.length,
systemPrompt: summarizeLogString(runtime, extractSystemPrompt(value)),
toolChoice: extractToolChoice(value),
toolNames: summarizeToolNamesForDiagnostics(runtime, tools),
tools: tools.length,
type: Array.isArray(value) ? "array" : typeof value
});
}
function summarizeToolNamesForDiagnostics(runtime, tools) {
if (!Array.isArray(tools) || tools.length === 0) {
return [];
}
return tools
.slice(0, decodeDiagnosticSampleLimit(runtime))
.map((tool) => stringValue(tool?.function?.name) || stringValue(tool?.name) || stringValue(tool?.type) || "unknown");
}
function summarizeLogString(runtime, value) {
if (typeof value !== "string" || value.length === 0) {
return undefined;
}
return {
length: value.length,
sample: truncateForLog(runtime, value)
};
}
function truncateForLog(runtime, value) {
const text = String(value || "").replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/g, "?");
const maxLength = Math.max(16, Math.trunc(Number(runtime?.decodeDiagnosticSampleChars) || DEFAULT_DECODE_DIAGNOSTIC_SAMPLE_CHARS));
return text.length > maxLength ? `${text.slice(0, maxLength)}...<${text.length}>` : text;
}
function decodeDiagnosticSampleLimit(runtime) {
return Math.max(1, Math.trunc(Number(runtime?.decodeDiagnosticSampleLimit) || DEFAULT_DECODE_DIAGNOSTIC_SAMPLE_LIMIT));
}
function isSimplifiedAgentChatRequest(chatRequest) {
return summarizeChatRequest(chatRequest).simplified;
}
function decodeConversationStateMessages(conversationState) {
const messages = [];
for (const rawJson of decodeProtoStringFields(conversationState, 1)) {
const parsed = readJsonText(rawJson);
const normalized = normalizeMessages(parsed);
if (normalized.length > 0) {
messages.push(...normalized);
} else if (parsed !== undefined && typeof parsed !== "string") {
const embeddedMessages = extractMessages(parsed);
const systemPrompt = extractSystemPrompt(parsed);
if (systemPrompt) {
messages.push({ content: systemPrompt, role: "system" });
}
if (embeddedMessages.length > 0) {
messages.push(...embeddedMessages);
}
} else if (typeof parsed === "string" && parsed.trim()) {
messages.push({ content: parsed.trim(), role: "system" });
} else if (rawJson.trim()) {
messages.push({ content: rawJson.trim(), role: "system" });
}
}
for (const turn of decodeProtoMessageFields(conversationState, 8)) {
messages.push(...decodeConversationTurnMessages(turn));
}
return messages;
}
function decodeConversationTurnMessages(turn) {
const messages = [];
const agentTurn = decodeProtoMessageFields(turn, 1)[0];
if (!agentTurn) {
return messages;
}
const userMessage = decodeUserMessage(decodeProtoMessageFields(agentTurn, 1)[0]);
if (userMessage) {
messages.push({ content: userMessage, role: "user" });
}
const assistantChunks = [];
for (const step of decodeProtoMessageFields(agentTurn, 2)) {
const assistantMessage = decodeProtoMessageFields(step, 1)[0];
const text = decodeProtoStringField(assistantMessage, 1);
if (text) {
assistantChunks.push(text);
}
}
if (assistantChunks.length > 0) {
messages.push({ content: assistantChunks.join("\n"), role: "assistant" });
}
return messages;
}
function decodeConversationActionMessages(action) {
const messages = [];
const userMessageAction = decodeProtoMessageFields(action, 1)[0];
if (userMessageAction) {
for (const prepend of decodeProtoMessageFields(userMessageAction, 4)) {
const text = decodeUserMessage(prepend);
if (text) {
messages.push({ content: text, role: "user" });
}
}
const text = decodeUserMessage(decodeProtoMessageFields(userMessageAction, 1)[0]);
if (text) {
messages.push({ content: text, role: "user" });
}
}
const startPlanAction = decodeProtoMessageFields(action, 6)[0];
const startPlanUserMessage = decodeUserMessage(decodeProtoMessageFields(startPlanAction, 1)[0]);
if (startPlanUserMessage) {
messages.push({ content: startPlanUserMessage, role: "user" });
}
return messages;
}
function decodeUserMessage(message) {
return decodeProtoStringField(message, 1) || decodeProtoStringField(message, 8) || "";
}
function decodeRequestedModelParameters(requestedModel) {
const result = {};
for (const parameter of decodeProtoMessageFields(requestedModel, 3)) {
const id = decodeProtoStringField(parameter, 1);
const value = decodeProtoStringField(parameter, 2);
if (id && value) {
result[id] = value;
}
}
return result;
}
function compactChatMessages(messages) {
const result = [];
for (const message of messages) {
if (!message?.role) {
continue;
}
const content = normalizeChatMessageContent(message.content);
const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls.filter(Boolean) : undefined;
const functionCall = isRecord(message.function_call) ? message.function_call : undefined;
if (!hasChatMessageContent(content) && (!toolCalls || toolCalls.length === 0) && !functionCall) {
continue;
}
const compacted = compactObject({
...message,
content: hasChatMessageContent(content) ? content : message.role === "assistant" ? "" : undefined,
function_call: functionCall,
tool_calls: toolCalls && toolCalls.length > 0 ? toolCalls : undefined
});
const last = result[result.length - 1];
if (isDuplicateSystemMessage(last, compacted)) {
continue;
}
if (canMergeChatMessages(last, compacted)) {
last.content = `${last.content}\n\n${compacted.content.trim()}`;
} else {
result.push(compacted);
}
}
return result;
}
function isDuplicateSystemMessage(left, right) {
return left?.role === "system" &&
right?.role === "system" &&
typeof left.content === "string" &&
typeof right.content === "string" &&
left.content.trim() === right.content.trim() &&
!hasChatToolFields(left) &&
!hasChatToolFields(right);
}
function canMergeChatMessages(left, right) {
return left?.role === right?.role &&
typeof left.content === "string" &&
typeof right.content === "string" &&
!hasChatToolFields(left) &&
!hasChatToolFields(right);
}
function hasChatToolFields(message) {
return Boolean(
message?.function_call ||
message?.name ||
message?.tool_call_id ||
(Array.isArray(message?.tool_calls) && message.tool_calls.length > 0)
);
}
function hasChatMessageContent(content) {
if (typeof content === "string") {
return content.trim().length > 0;
}
if (Array.isArray(content)) {
return content.length > 0;
}
return content !== undefined && content !== null;
}
function normalizeChatMessageContent(content) {
if (typeof content === "string") {
return content.trim();
}
if (Array.isArray(content)) {
return content.filter((item) => item !== undefined && item !== null);
}
return content;
}
function numberFromParameter(value) {
if (value === undefined) {
return undefined;
}
const number = Number(value);
return Number.isFinite(number) ? number : undefined;
}
function looksLikeNoiseString(value) {
return /^[0-9a-f-]{16,}$/i.test(value) ||
/^cursor[-_a-z0-9.]*$/i.test(value) ||
value.startsWith("data:") ||
value.startsWith("file:") ||
value.startsWith("vscode:");
}
function looksLikeSystemPromptText(value) {
if (!value || value.length < 20 || looksLikeNoiseString(value)) {
return false;
}
return /\b(system|developer|instruction|assistant|rules?|tools?|you are|you should|must)\b/i.test(value) ||
/你是|系统|规则|工具|助手|必须|请遵守/.test(value);
}
function routeJsonRequestBody(runtime, requestBody) {
const payload = readJsonLikePayload(requestBody);
if (!isRecord(payload)) {
return { body: requestBody, routed: false };
}
const routedPayload = { ...payload };
const decision = applyCursorProxyRouting(runtime, routedPayload);
if (!decision.routed) {
return { body: requestBody, routed: false };
}
return {
body: Buffer.from(`${JSON.stringify(routedPayload)}\n`, "utf8"),
routed: true
};
}
function prepareOpenAIChatRequestBody(runtime, requestBody) {
const payload = readJsonLikePayload(requestBody);
if (!isRecord(payload)) {
return { body: requestBody, routed: false };
}
const preparedPayload = { ...payload };
const contextChanged = applyOpenAICompatChatContext(runtime, preparedPayload);
const usageChanged = ensureOpenAIStreamUsage(preparedPayload);
const changed = contextChanged || usageChanged;
const decision = applyCursorProxyRouting(runtime, preparedPayload);
if (!changed && !decision.routed) {
return { body: requestBody, routed: false };
}
return {
body: Buffer.from(`${JSON.stringify(preparedPayload)}\n`, "utf8"),
routed: decision.routed
};
}
function ensureOpenAIStreamUsage(body) {
if (body.stream !== true) {
return false;
}
const streamOptions = isRecord(body.stream_options) ? { ...body.stream_options } : {};
if (streamOptions.include_usage === true) {
return false;
}
body.stream_options = { ...streamOptions, include_usage: true };
return true;
}
function applyOpenAICompatChatContext(runtime, body) {
if (!Array.isArray(body.messages)) {
return false;
}
let changed = applyCollectedOpenAICompatChatContext(runtime, body);
const messages = body.messages.filter((message) => isRecord(message));
const missingSystem = !hasSystemInstruction(body, messages);
const missingTools = !Array.isArray(body.tools) || body.tools.length === 0;
const missingToolChoice = body.tool_choice === undefined && body.toolChoice === undefined;
const simplified = isSimplifiedOpenAICompatChat(body, messages);
if (missingSystem && runtime.chatCompletionSystemPrompt) {
body.messages = [
{ content: runtime.chatCompletionSystemPrompt, role: "system" },
...body.messages
];
changed = true;
}
if (missingTools && runtime.chatCompletionTools.length > 0) {
body.tools = runtime.chatCompletionTools;
changed = true;
}
if (missingToolChoice && body.tools && runtime.chatCompletionToolChoice !== undefined) {
body.tool_choice = runtime.chatCompletionToolChoice;
changed = true;
}
if (simplified && !changed && runtime.warnedMissingChatContext !== true) {
runtime.warnedMissingChatContext = true;
runtime.logger?.warn?.(
"Cursor proxy received an OpenAI-compatible chat request with only user messages and no system/tools. " +
"The incoming request does not contain Cursor Agent context; configure cursor-proxy config.systemPrompt/config.tools " +
"or use Cursor's native Agent traffic so the proxy can forward tools and system prompts."
);
}
return changed;
}
function applyCollectedOpenAICompatChatContext(runtime, body) {
const collected = findCollectedCursorContext(runtime, undefined, undefined, body);
if (!collected) {
return false;
}
const merged = mergeCollectedCursorChatRequest(body, collected.chatRequest);
for (const key of Object.keys(body)) {
delete body[key];
}
Object.assign(body, merged);
runtime.logger?.debug?.(
`Cursor proxy applied collected Cursor context to OpenAI-compatible request ` +
`(${formatChatRequestSummary(body)}).`
);
return true;
}
function hasSystemInstruction(body, messages) {
return Boolean(
body.system !== undefined ||
body.systemPrompt !== undefined ||
body.instructions !== undefined ||
messages.some((message) => normalizeRole(stringValue(message.role)) === "system")
);
}
function isSimplifiedOpenAICompatChat(body, messages) {
return messages.length > 0 &&
messages.every((message) => normalizeRole(stringValue(message.role)) === "user") &&
!hasSystemInstruction(body, messages) &&
(!Array.isArray(body.tools) || body.tools.length === 0);
}
function applyCursorProxyRouting(runtime, body) {
if (!isRecord(body)) {
return { requestedModel: "", routed: false };
}
const requestedModel = stringValue(body.model) || runtime.defaultModel || "cursor-proxy";
const routing = runtime.routing;
if (routing && routing.enabled !== false) {
const route = resolveCursorProxyRoute(routing, body, requestedModel);
if (route?.target) {
body.model = route.target;
runtime.logger?.debug?.(
`Cursor proxy routed model ${requestedModel || "<empty>"} to ${route.target} (${route.reason}).`
);
return {
requestedModel,
reason: route.reason,
routed: true,
routedModel: route.target
};
}
}
const legacyTarget = normalizeRouteTarget(
composeRouteTarget(runtime.targetProvider, runtime.targetModel) ||
stringValue(runtime.targetModel)
);
if (legacyTarget) {
body.model = legacyTarget;
return {
requestedModel,
reason: "legacy-target",
routed: true,
routedModel: legacyTarget
};
}
if (isCursorDefaultModel(requestedModel) && runtime.defaultModel && runtime.defaultModel !== requestedModel) {
body.model = runtime.defaultModel;
return {
requestedModel,
reason: "configured-default-model",
routed: true,
routedModel: runtime.defaultModel
};
}
return { requestedModel, routed: false };
}
function resolveCursorProxyRoute(routing, body, requestedModel) {
for (const rule of routing.rules || []) {
if (rule.enabled === false || !rule.target) {
continue;
}
if (matchesCursorProxyRouteRule(rule, body, requestedModel)) {
return {
reason: rule.id ? `plugin-rule:${rule.id}` : `plugin-rule:${rule.type}`,
target: rule.target
};
}
}
if (routing.defaultTarget) {
return {
reason: "plugin-default",
target: routing.defaultTarget
};
}
return undefined;
}
function matchesCursorProxyRouteRule(rule, body, requestedModel) {
switch (rule.type) {
case "always":
return true;
case "image":
return hasImageContent(body?.messages) || hasImageContent(body?.input);
case "long-context":
return estimateTokenCount(body) > (rule.threshold || 200000);
case "model":
return Boolean(rule.model && requestedModel === rule.model);
case "model-prefix":
return Boolean(rule.pattern && requestedModel?.startsWith(rule.pattern));
case "thinking":
return Boolean(body?.thinking || body?.reasoning || body?.reasoning_effort);
case "web-search":
return hasWebSearchTool(body?.tools);
default:
return false;
}
}
function normalizeCursorProxyRouting(value, options = {}) {
const fallbackTarget = normalizeRouteTarget(
composeRouteTarget(options.targetProvider, options.targetModel) ||
stringValue(options.targetModel)
);
const record = isRecord(value) ? value : {};
const rules = [];
if (isRecord(record.modelMap)) {
for (const [model, target] of Object.entries(record.modelMap)) {
const normalizedTarget = normalizeRouteTarget(stringValue(target));
const normalizedModel = stringValue(model);
if (!normalizedModel || !normalizedTarget) {
continue;
}
rules.push({
enabled: true,
id: `model-${sanitizeRouteId(normalizedModel)}`,
model: normalizedModel,
name: normalizedModel,
target: normalizedTarget,
type: "model"
});
}
}
if (Array.isArray(record.rules)) {
record.rules.forEach((rule, index) => {
const normalized = normalizeCursorProxyRoutingRule(rule, index);
if (normalized) {
rules.push(normalized);
}
});
}
return {
defaultTarget: normalizeRouteTarget(stringValue(record.default) || stringValue(record.defaultTarget)) || fallbackTarget,
enabled: value === false ? false : record.enabled !== false,
rules
};
}
function normalizeCursorProxyRoutingRule(value, index) {
if (!isRecord(value)) {
return undefined;
}
const type = stringValue(value.type) || "model";
if (!CURSOR_PROXY_ROUTE_TYPES.has(type)) {
return undefined;
}
const target = normalizeRouteTarget(
stringValue(value.target) ||
composeRouteTarget(value.targetProvider, value.targetModel) ||
stringValue(value.targetModel)
);
if (!target) {
return undefined;
}
const model = stringValue(value.model) || stringValue(value.sourceModel);
const pattern = stringValue(value.pattern) || (type === "model-prefix" ? model : undefined);
const threshold = positiveNumber(value.threshold) || positiveNumber(value.tokenThreshold);
const id = stringValue(value.id) || `${type}-${index + 1}`;
return {
enabled: value.enabled !== false,
id,
name: stringValue(value.name) || id,
...(model ? { model } : {}),
...(pattern ? { pattern } : {}),
target,
...(threshold ? { threshold } : {}),
type
};
}
function normalizeRouteTarget(value) {
const raw = stringValue(value);
if (!raw) {
return undefined;
}
const commaIndex = raw.indexOf(",");
if (commaIndex > 0 && commaIndex < raw.length - 1) {
const provider = raw.slice(0, commaIndex).trim();
const model = raw.slice(commaIndex + 1).trim();
return provider && model ? `${provider}/${model}` : undefined;
}
return raw;
}
function composeRouteTarget(providerValue, modelValue) {
const provider = stringValue(providerValue);
const model = stringValue(modelValue);
if (provider && model) {
return `${provider}/${model}`;
}
return model || provider;
}
function positiveNumber(value) {
const number = Number(value);
return Number.isFinite(number) && number > 0 ? number : undefined;
}
function hasWebSearchTool(tools) {
return Array.isArray(tools) && tools.some((tool) => isRecord(tool) && stringValue(tool.type)?.startsWith("web_search"));
}
function hasImageContent(value) {
return value !== undefined && JSON.stringify(value).includes("\"image\"");
}
function estimateTokenCount(body) {
return Math.max(1, Math.ceil(JSON.stringify(body || {}).length / 4));
}
function isCursorDefaultModel(model) {
const normalized = stringValue(model).toLowerCase();
return normalized === "default" || normalized === "auto" || normalized === "cursor-default";
}
function sanitizeRouteId(value) {
return String(value || "")
.toLowerCase()
.replace(/[^a-z0-9._-]+/g, "-")
.replace(/^-+|-+$/g, "")
.slice(0, 80) || "route";
}
function escapeRegExp(value) {
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
async function streamGatewayChatToCursor(runtime, request, response, chatRequest, bridgeContext) {
const requestBody = { ...chatRequest, stream: true };
ensureOpenAIStreamUsage(requestBody);
const routingDecision = applyCursorProxyRouting(runtime, requestBody);
const body = Buffer.from(`${JSON.stringify(requestBody)}\n`, "utf8");
const headers = buildGatewayHeaders(
runtime,
{},
{ body, method: "POST", path: "/v1/chat/completions", protocol: "openai", skipRuntimeTargetHeaders: routingDecision.routed },
body
);
const targetUrl = new URL("/v1/chat/completions", runtime.gatewayUrl);
const transport = targetUrl.protocol === "https:" ? https : http;
return await new Promise((resolve) => {
let settled = false;
const streamState = createGatewayStreamState();
const finishTurn = (outcome = { type: "done" }) => {
if (!settled) {
settled = true;
writeCursorConnectMessage(response, encodeCursorAgentTurnEnded());
finishCursorConnectStream(response);
resolve(outcome);
}
};
const finishToolBridge = () => {
if (settled) {
return;
}
const toolCalls = finalizeGatewayToolCalls(streamState);
if (toolCalls.length === 0 && streamState.finishReason !== "tool_calls") {
finishTurn({ type: "done" });
return;
}
const summary = summarizeGatewayToolCalls(toolCalls);
const canBridge = bridgeContext && runtime.bridgeOpenAIToolCalls === true && toolCalls.length > 0;
if (!canBridge) {
handleUnbridgedGatewayToolCalls(runtime, response, toolCalls, streamState, bridgeContext);
finishTurn({ finishReason: streamState.finishReason, toolCalls, type: "done" });
return;
}
const bridgeResult = bridgeGatewayToolCallsToCursor(runtime, response, toolCalls, bridgeContext, streamState);
if (bridgeResult.sent !== toolCalls.length || bridgeResult.missing.length > 0) {
handleUnbridgedGatewayToolCalls(runtime, response, toolCalls, streamState, bridgeContext, bridgeResult);
finishTurn({ finishReason: streamState.finishReason, toolCalls, type: "done" });
return;
}
settled = true;
runtime.logger?.debug?.(
`Cursor proxy bridged upstream tool_calls to Cursor Agent ` +
`(${summary || "tool_calls requested"}, model_call_id=${streamState.modelCallId || "unknown"}).`
);
resolve({
finishReason: streamState.finishReason,
modelCallId: streamState.modelCallId,
toolCalls,
type: "tool_calls"
});
};
const upstreamRequest = transport.request(
{
headers,
hostname: targetUrl.hostname,
method: "POST",
path: `${targetUrl.pathname}${targetUrl.search}`,
port: targetUrl.port || (targetUrl.protocol === "https:" ? 443 : 80),
protocol: targetUrl.protocol
},
(upstreamResponse) => {
const chunks = [];
let sseBuffer = "";
const statusCode = upstreamResponse.statusCode || 502;
const contentType = readHeader(upstreamResponse.headers["content-type"]).toLowerCase();
upstreamResponse.on("data", (chunk) => {
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
if (statusCode >= 400 || !contentType.includes("event-stream")) {
chunks.push(buffer);
return;
}
sseBuffer += buffer.toString("utf8");
const lines = sseBuffer.split(/\r?\n/);
sseBuffer = lines.pop() || "";
for (const line of lines) {
handleGatewayStreamLine(response, line, streamState);
}
});
upstreamResponse.once("end", () => {
if (statusCode >= 400) {
const message = Buffer.concat(chunks).toString("utf8").trim() || `Gateway returned HTTP ${statusCode}.`;
writeCursorConnectMessage(response, encodeCursorAgentTextDelta(message));
finishTurn({ statusCode, type: "done" });
return;
} else if (!contentType.includes("event-stream")) {
const text = Buffer.concat(chunks).toString("utf8");
collectGatewayToolCallsFromJsonText(text, streamState);
const message = extractTextFromGatewayJson(text);
if (message) {
writeCursorConnectMessage(response, encodeCursorAgentTextDelta(message));
}
finishToolBridge();
return;
} else if (sseBuffer.trim()) {
handleGatewayStreamLine(response, sseBuffer, streamState);
}
finishToolBridge();
});
upstreamResponse.once("error", (error) => {
writeCursorConnectMessage(response, encodeCursorAgentTextDelta(formatError(error)));
finishTurn({ error, type: "done" });
});
}
);
upstreamRequest.once("error", (error) => {
writeCursorConnectMessage(response, encodeCursorAgentTextDelta(formatError(error)));
finishTurn({ error, type: "done" });
});
upstreamRequest.setTimeout(runtime.gatewayTimeoutMs, () => {
upstreamRequest.destroy(new Error(`Cursor proxy gateway request to ${targetUrl.toString()} timed out.`));
});
upstreamRequest.end(body);
});
}
function createGatewayStreamState() {
return {
finishReason: "",
modelCallId: "",
toolCallIndexKeys: new Map(),
toolCallParts: new Map(),
toolCalls: []
};
}
function handleGatewayStreamLine(response, line, streamState) {
const trimmed = line.trim();
if (!trimmed.startsWith("data:")) {
return;
}
const data = trimmed.slice(5).trim();
if (!data || data === "[DONE]") {
return;
}
const payload = readJsonText(data);
if (!payload) {
return;
}
const text = extractTextFromGatewayPayload(payload);
if (text) {
writeCursorConnectMessage(response, encodeCursorAgentTextDelta(text));
}
collectGatewayToolCallsFromPayload(payload, streamState);
}
function extractTextFromGatewayPayload(payload) {
return stringifyContent(payload.choices?.[0]?.delta?.content) ||
stringifyContent(payload.choices?.[0]?.message?.content) ||
stringifyContent(payload.delta) ||
stringifyContent(payload.output_text) ||
"";
}
function collectGatewayToolCallsFromJsonText(text, streamState) {
const payload = readJsonText(text);
if (!payload) {
return;
}
collectGatewayToolCallsFromPayload(payload, streamState);
}
function collectGatewayToolCallsFromPayload(payload, streamState) {
if (!isRecord(payload) || !streamState) {
return;
}
if (typeof payload.id === "string" && payload.id) {
streamState.modelCallId = payload.id;
}
const choices = Array.isArray(payload.choices) ? payload.choices : [];
choices.forEach((choice) => {
if (!isRecord(choice)) {
return;
}
const finishReason = stringValue(choice.finish_reason);
if (finishReason) {
streamState.finishReason = finishReason;
}
const indexOffset = streamState.toolCallParts.size;
collectGatewayToolCallDeltas(choice.delta?.tool_calls, streamState, indexOffset);
collectGatewayToolCallDeltas(choice.message?.tool_calls, streamState, indexOffset);
collectGatewayToolCallDeltas(choice.tool_calls, streamState, indexOffset);
});
collectGatewayToolCallDeltas(payload.tool_calls, streamState, streamState.toolCallParts.size);
}
function collectGatewayToolCallDeltas(value, streamState, indexOffset) {
const values = Array.isArray(value) ? value : value ? [value] : [];
values.forEach((item, index) => {
applyGatewayToolCallDelta(item, streamState, indexOffset + index);
});
}
function applyGatewayToolCallDelta(value, streamState, fallbackIndex) {
if (!isRecord(value)) {
return;
}
const raw = unwrapToolCall(value);
if (!isRecord(raw)) {
return;
}
const fn = isRecord(raw.function) ? raw.function : raw;
const rawIndex = Number(raw.index);
const index = Number.isInteger(rawIndex) ? rawIndex : fallbackIndex;
const id = stringValue(raw.id) || stringValue(raw.tool_call_id) || stringValue(raw.toolCallId) || stringValue(raw.callId);
const indexKey = `index:${index}`;
const mappedKey = streamState.toolCallIndexKeys.get(indexKey);
let key = id || mappedKey || indexKey;
let part = streamState.toolCallParts.get(key);
if (id && mappedKey && mappedKey !== id && streamState.toolCallParts.has(mappedKey)) {
part = streamState.toolCallParts.get(mappedKey);
streamState.toolCallParts.delete(mappedKey);
} else if (id && !mappedKey && streamState.toolCallParts.has(indexKey)) {
part = streamState.toolCallParts.get(indexKey);
streamState.toolCallParts.delete(indexKey);
}
if (id) {
key = id;
streamState.toolCallIndexKeys.set(indexKey, id);
}
part = part || {
arguments: "",
id: id || `call_${index + 1}`,
index,
name: "",
type: stringValue(raw.type) || "function"
};
if (id) {
part.id = id;
}
const name =
stringValue(fn.name) ||
stringValue(raw.name) ||
stringValue(raw.toolName) ||
stringValue(raw.functionName);
if (name) {
part.name = name;
}
const rawArguments = firstDefined(fn.arguments, raw.arguments, fn.input, raw.input, raw.args, fn.parameters, raw.parameters);
if (typeof rawArguments === "string") {
part.arguments += rawArguments;
} else if (rawArguments !== undefined) {
part.arguments = normalizeToolArguments(rawArguments);
}
if (part.type !== "function") {
part.type = "function";
}
streamState.toolCallParts.set(key, part);
}
function finalizeGatewayToolCalls(streamState) {
const parts = [...(streamState?.toolCallParts?.values?.() || [])]
.sort((left, right) => (left.index || 0) - (right.index || 0));
const calls = parts
.filter((part) => part.name)
.map((part, index) => ({
function: {
arguments: part.arguments || "{}",
name: part.name
},
id: part.id || `call_${index + 1}`,
type: "function"
}));
const normalized = uniqueToolCalls([
...normalizeToolCallList(streamState?.toolCalls),
...calls
]);
if (streamState) {
streamState.toolCalls = normalized;
}
return normalized;
}
function summarizeGatewayToolCalls(toolCalls) {
const calls = Array.isArray(toolCalls) ? toolCalls : [];
return calls
.slice(0, 5)
.map((call) => {
const name = stringValue(call?.function?.name) || stringValue(call?.name) || "unknown";
const args = stringValue(call?.function?.arguments) || stringValue(call?.arguments) || "";
return args ? `${name}(${truncateForLog(undefined, args)})` : name;
})
.join(", ");
}
function buildCursorToolBridgeContext(runtime, session, runRequest, chatRequest, round) {
const contextValues = Buffer.isBuffer(runRequest) ? decodeAgentRunRequestContextValues(runRequest) : [];
const toolIndex = buildCursorMcpToolIndex(contextValues);
const decodedTools = Array.isArray(chatRequest?.tools) ? chatRequest.tools.length : 0;
const nativeToolNames = (Array.isArray(chatRequest?.tools) ? chatRequest.tools : [])
.map((tool) => stringValue(tool?.function?.name) || stringValue(tool?.name))
.filter((name) => lookupCursorNativeToolSpec(name));
runtime.logger?.debug?.(
`Cursor proxy prepared tool bridge context for ${session.requestId} ` +
`(round=${round}, decoded_tools=${decodedTools}, cursor_native_tools=${nativeToolNames.length}, ` +
`cursor_native_names=${nativeToolNames.slice(0, decodeDiagnosticSampleLimit(runtime)).join(",") || "none"}, ` +
`cursor_mcp_tools=${toolIndex.tools.length}, ` +
`providers=${[...new Set(toolIndex.tools.map((tool) => tool.providerIdentifier))].join(",") || "none"}).`
);
return {
chatRequest,
round,
runRequest,
session,
toolIndex
};
}
function buildCursorMcpToolIndex(values) {
const byName = new Map();
const tools = [];
for (const filePath of extractCursorToolFilePaths(values)) {
const metadata = readCursorMcpToolMetadata(filePath);
if (!metadata) {
continue;
}
tools.push(metadata);
addCursorMcpToolIndexEntry(byName, metadata.name, metadata);
addCursorMcpToolIndexEntry(byName, metadata.toolName, metadata);
addCursorMcpToolIndexEntry(byName, `${metadata.providerIdentifier}.${metadata.toolName}`, metadata);
addCursorMcpToolIndexEntry(byName, `${metadata.providerIdentifier}_${metadata.toolName}`, metadata);
addCursorMcpToolIndexEntry(byName, `${metadata.providerIdentifier}:${metadata.toolName}`, metadata);
}
return { byName, tools };
}
function addCursorMcpToolIndexEntry(index, name, metadata) {
const key = normalizeToolBridgeName(name);
if (!key || index.has(key)) {
return;
}
index.set(key, metadata);
}
function lookupCursorMcpToolMetadata(bridgeContext, name) {
const index = bridgeContext?.toolIndex?.byName;
if (!(index instanceof Map)) {
return undefined;
}
return index.get(normalizeToolBridgeName(name));
}
function normalizeToolBridgeName(value) {
return String(value || "").trim().toLowerCase();
}
function readCursorMcpToolMetadata(filePath) {
const pathMetadata = parseCursorMcpToolFilePath(filePath);
if (!pathMetadata) {
return undefined;
}
const text = readTextFileIfSmall(filePath, 512 * 1024);
const parsed = readJsonText(text);
if (!isRecord(parsed)) {
return {
...pathMetadata,
name: pathMetadata.toolName
};
}
const name = stringValue(parsed.name) || pathMetadata.toolName;
return {
...pathMetadata,
description: stringValue(parsed.description),
name,
schema: isRecord(parsed.arguments) ? parsed.arguments : isRecord(parsed.parameters) ? parsed.parameters : undefined
};
}
function parseCursorMcpToolFilePath(filePath) {
const normalized = normalizeCursorMcpFilePath(filePath);
if (!normalized) {
return undefined;
}
const parts = normalized.split(path.sep);
const markerIndex = parts.lastIndexOf("mcps");
if (markerIndex < 0 || parts[markerIndex + 2] !== "tools") {
return undefined;
}
const providerIdentifier = parts[markerIndex + 1];
const fileName = parts[markerIndex + 3] || "";
const toolName = fileName.endsWith(".json") ? fileName.slice(0, -".json".length) : path.basename(fileName, ".json");
if (!providerIdentifier || !toolName) {
return undefined;
}
return {
filePath: normalized,
providerIdentifier,
toolName
};
}
function bridgeGatewayToolCallsToCursor(runtime, response, toolCalls, bridgeContext, streamState) {
const missing = [];
let sent = 0;
toolCalls.forEach((call, index) => {
const name = stringValue(call?.function?.name) || stringValue(call?.name);
const nativeSpec = lookupCursorNativeToolSpec(name);
if (nativeSpec) {
runtime.logger?.debug?.(
`Cursor proxy bridging OpenAI tool_call ${name || `call_${index + 1}`} ` +
`to Cursor native ${nativeSpec.name} (field=${nativeSpec.toolCallField}).`
);
writeCursorConnectMessage(
response,
encodeCursorAgentNativeToolCallStarted(call, nativeSpec, {
modelCallId: streamState?.modelCallId
})
);
sent += 1;
return;
}
const metadata = lookupCursorMcpToolMetadata(bridgeContext, name);
if (!metadata) {
missing.push(name || `call_${index + 1}`);
return;
}
runtime.logger?.debug?.(
`Cursor proxy bridging OpenAI tool_call ${name || `call_${index + 1}`} ` +
`to Cursor MCP ${metadata.providerIdentifier}/${metadata.toolName}.`
);
writeCursorConnectMessage(
response,
encodeCursorAgentMcpToolCallStarted(call, metadata, {
modelCallId: streamState?.modelCallId,
skipApproval: runtime.cursorMcpSkipApproval
})
);
sent += 1;
});
return { missing, sent };
}
function handleUnbridgedGatewayToolCalls(runtime, response, toolCalls, streamState, bridgeContext, bridgeResult) {
const summary = summarizeGatewayToolCalls(toolCalls);
const missing = Array.isArray(bridgeResult?.missing) ? bridgeResult.missing : [];
const reason = runtime.bridgeOpenAIToolCalls !== true
? "tool bridge disabled"
: !bridgeContext
? "no Cursor Agent bridge context"
: toolCalls.length === 0
? "finish_reason=tool_calls but no complete tool_calls were decoded"
: missing.length > 0
? `missing Cursor tool metadata for ${missing.join(", ")}`
: "tool bridge could not emit all tool events";
runtime.logger?.warn?.(
`Cursor proxy received upstream tool_calls but could not bridge them to Cursor Agent ` +
`(${reason}; ${summary || "tool_calls requested"}; model_call_id=${streamState?.modelCallId || "unknown"}).`
);
if (runtime?.showUnbridgedToolCallWarning === true) {
writeCursorConnectMessage(response, encodeCursorAgentTextDelta(
`\n\n[Cursor proxy] Upstream requested tool execution (${summary || "tool_calls"}), ` +
`but the proxy could not emit Cursor Agent tool events: ${reason}.`
));
}
}
function writeCursorMcpToolResultEvents(runtime, response, toolCalls, bridgeContext, chatRequest, modelCallId) {
const resultMessages = findToolResultMessages(chatRequest?.messages, toolCalls);
if (resultMessages.length === 0) {
runtime.logger?.debug?.(
`Cursor proxy did not find decoded tool result messages for completed Cursor MCP tool events ` +
`(${summarizeGatewayToolCalls(toolCalls)}).`
);
return;
}
for (const call of toolCalls) {
const name = stringValue(call?.function?.name);
const metadata = lookupCursorMcpToolMetadata(bridgeContext, name);
if (!metadata) {
continue;
}
const resultMessage = findToolResultMessageForCall(resultMessages, call);
if (!resultMessage) {
continue;
}
writeCursorConnectMessage(
response,
encodeCursorAgentMcpToolCallCompleted(call, metadata, resultMessage, {
modelCallId,
skipApproval: runtime.cursorMcpSkipApproval
})
);
}
}
function findToolResultMessages(messages, toolCalls) {
const ids = new Set((Array.isArray(toolCalls) ? toolCalls : []).map((call) => call.id).filter(Boolean));
const names = new Set((Array.isArray(toolCalls) ? toolCalls : [])
.map((call) => stringValue(call?.function?.name))
.filter(Boolean));
return (Array.isArray(messages) ? messages : [])
.filter((message) => message?.role === "tool")
.filter((message) => {
const id = stringValue(message.tool_call_id);
const name = stringValue(message.name);
return (id && ids.has(id)) || (name && names.has(name)) || (ids.size === 0 && names.size === 0);
});
}
function findToolResultMessageForCall(messages, call) {
const id = stringValue(call?.id);
const name = stringValue(call?.function?.name);
return (Array.isArray(messages) ? messages : []).find((message) => {
const messageId = stringValue(message.tool_call_id);
const messageName = stringValue(message.name);
return (id && messageId === id) || (name && messageName === name);
}) || (Array.isArray(messages) ? messages[0] : undefined);
}
function mergeToolCallContinuationChatRequest(previousRequest, toolCalls, nextRequest) {
const previousMessages = Array.isArray(previousRequest?.messages) ? previousRequest.messages : [];
const nextMessages = Array.isArray(nextRequest?.messages) ? nextRequest.messages : [];
const assistantToolCallMessage = createAssistantToolCallMessage(toolCalls);
const mergedMessages = [];
appendUniqueChatMessages(mergedMessages, previousMessages);
if (!hasAssistantToolCallsForIds(mergedMessages, toolCalls)) {
appendUniqueChatMessages(mergedMessages, [assistantToolCallMessage]);
}
appendUniqueChatMessages(mergedMessages, nextMessages);
return compactObject({
...previousRequest,
...nextRequest,
messages: compactChatMessages(mergedMessages),
stream: true,
tool_choice: nextRequest?.tool_choice ?? previousRequest?.tool_choice,
tools: Array.isArray(nextRequest?.tools) && nextRequest.tools.length > 0
? nextRequest.tools
: previousRequest?.tools
});
}
function createAssistantToolCallMessage(toolCalls) {
return {
content: "",
role: "assistant",
tool_calls: normalizeToolCallList(toolCalls)
};
}
function appendUniqueChatMessages(target, messages) {
const seen = new Set(target.map(chatMessageFingerprint));
for (const message of Array.isArray(messages) ? messages : []) {
const fingerprint = chatMessageFingerprint(message);
if (!fingerprint || seen.has(fingerprint)) {
continue;
}
seen.add(fingerprint);
target.push(message);
}
}
function chatMessageFingerprint(message) {
if (!isRecord(message)) {
return "";
}
return safeJsonStringify(compactObject({
content: message.content,
function_call: message.function_call,
name: message.name,
role: message.role,
tool_call_id: message.tool_call_id,
tool_calls: message.tool_calls
}));
}
function hasAssistantToolCallsForIds(messages, toolCalls) {
const ids = new Set((Array.isArray(toolCalls) ? toolCalls : []).map((call) => call.id).filter(Boolean));
if (ids.size === 0) {
return false;
}
return (Array.isArray(messages) ? messages : []).some((message) => {
if (message?.role !== "assistant" || !Array.isArray(message.tool_calls)) {
return false;
}
return message.tool_calls.some((call) => ids.has(call.id));
});
}
function logAgentToolContinuation(runtime, session, toolCalls, chatRequest, round) {
runtime.logger?.debug?.(
`Cursor proxy continuing Agent RunSSE after Cursor tool results for ${session.requestId} ` +
`(round=${round}, tool_calls=${summarizeGatewayToolCalls(toolCalls) || "none"}, ` +
`${formatChatRequestSummary(chatRequest)}).`
);
}
function logCursorToolBridgeFollowUp(runtime, session, runRequest, chatRequest, toolCalls, resultMessages, round) {
const dumpPath = writeAgentRunRequestDecodeDump(
runtime,
session,
runRequest,
chatRequest,
`tool-bridge-round-${round}`
);
const resultSummary = (Array.isArray(resultMessages) ? resultMessages : [])
.slice(0, decodeDiagnosticSampleLimit(runtime))
.map((message) => {
const id = stringValue(message.tool_call_id) || "no-id";
const name = stringValue(message.name) || "no-name";
const content = truncateForLog(runtime, stringifyContent(message.content) || "");
return `${name}/${id}:${content}`;
})
.join(" | ") || "none";
runtime.logger?.debug?.(
`Cursor proxy decoded tool bridge follow-up for ${session.requestId} ` +
`(round=${round}, tool_calls=${summarizeGatewayToolCalls(toolCalls) || "none"}, ` +
`result_messages=${Array.isArray(resultMessages) ? resultMessages.length : 0}, ` +
`results=${resultSummary}, ${formatChatRequestSummary(chatRequest)}` +
`${dumpPath ? `, decode_dump=${dumpPath}` : ""}).`
);
if (!Array.isArray(resultMessages) || resultMessages.length > 0) {
return;
}
logCursorDecodeDiagnostics(
runtime,
"warn",
`Cursor proxy tool bridge follow-up for ${session.requestId} did not decode any matching tool result messages; ` +
`tool_calls=${summarizeGatewayToolCalls(toolCalls) || "none"}; ` +
`run_request=${formatAgentRunRequestDecodeDiagnostic(runtime, runRequest, chatRequest)}; ` +
`bidi_session=${formatBidiSessionDecodeDiagnostic(runtime, session)}`
);
}
function extractTextFromGatewayJson(text) {
const payload = readJsonText(text);
if (!payload) {
return text.trim();
}
const choice = Array.isArray(payload.choices) ? payload.choices[0] : undefined;
const hasToolCalls = normalizeToolCallList(choice?.message?.tool_calls).length > 0 ||
normalizeToolCallList(choice?.delta?.tool_calls).length > 0 ||
normalizeToolCallList(payload.tool_calls).length > 0;
return stringifyContent(payload.choices?.[0]?.message?.content) ||
stringifyContent(payload.choices?.[0]?.delta?.content) ||
stringifyContent(payload.output_text) ||
stringifyContent(payload.content) ||
(hasToolCalls ? "" : text.trim());
}
function writeCursorConnectMessage(response, message) {
if (!response.writableEnded) {
response.write(connectEnvelope(0, message));
}
}
function finishCursorConnectStream(response) {
if (!response.writableEnded) {
response.end(connectEnvelope(0x02, Buffer.from("{}", "utf8")));
}
}
function encodeCursorAgentTextDelta(text) {
return protoMessage(1, protoMessage(1, protoRawString(1, text)));
}
function encodeCursorAgentTurnEnded() {
return protoMessage(1, protoMessage(14, Buffer.alloc(0)));
}
function encodeCursorAgentMcpToolCallStarted(call, metadata, options = {}) {
const update = Buffer.concat([
protoString(1, call.id),
protoMessage(2, encodeCursorMcpToolCall(call, metadata, undefined, options)),
protoString(3, options.modelCallId)
]);
return protoMessage(1, protoMessage(2, update));
}
function encodeCursorAgentMcpToolCallCompleted(call, metadata, resultMessage, options = {}) {
const update = Buffer.concat([
protoString(1, call.id),
protoMessage(2, encodeCursorMcpToolCall(call, metadata, resultMessage, options)),
protoString(3, options.modelCallId)
]);
return protoMessage(1, protoMessage(3, update));
}
function encodeCursorAgentNativeToolCallStarted(call, spec, options = {}) {
const update = Buffer.concat([
protoString(1, call.id),
protoMessage(2, encodeCursorNativeToolCall(call, spec)),
protoString(3, options.modelCallId)
]);
return protoMessage(1, protoMessage(2, update));
}
function encodeCursorNativeToolCall(call, spec) {
const args = parseToolArgumentsObject(call?.function?.arguments);
return protoMessage(spec.toolCallField, protoMessage(1, spec.encodeArgs(args, call)));
}
function encodeCursorMcpToolCall(call, metadata, resultMessage, options = {}) {
const mcpToolCall = Buffer.concat([
protoMessage(1, encodeCursorMcpArgs(call, metadata, options)),
resultMessage ? protoMessage(2, encodeCursorMcpResult(resultMessage)) : Buffer.alloc(0)
]);
return protoMessage(15, mcpToolCall);
}
function encodeCursorMcpArgs(call, metadata, options = {}) {
const args = parseToolArgumentsObject(call?.function?.arguments);
return Buffer.concat([
protoString(1, stringValue(call?.function?.name) || metadata.name || metadata.toolName),
encodeProtoStringValueMapField(2, args),
protoString(3, call?.id),
protoString(4, metadata.providerIdentifier),
protoString(5, metadata.toolName || metadata.name),
options.skipApproval ? protoBool(8, true) : Buffer.alloc(0)
]);
}
function encodeCursorMcpResult(resultMessage) {
const text = stringifyContent(resultMessage?.content) || "";
const textContent = protoRawString(1, text);
const contentItem = protoMessage(1, textContent);
const success = protoMessage(1, contentItem);
return protoMessage(1, success);
}
function parseToolArgumentsObject(value) {
if (isRecord(value)) {
return value;
}
if (typeof value === "string") {
const parsed = readJsonText(value);
if (isRecord(parsed)) {
return parsed;
}
if (parsed !== undefined) {
return { value: parsed };
}
return value.trim() ? { input: value } : {};
}
if (value === undefined || value === null) {
return {};
}
return { value };
}
function encodeCursorNativeReadArgs(args) {
return Buffer.concat([
protoString(1, stringArg(args, ["path", "file", "file_path", "target_file"])),
optionalProtoInt(2, numberArg(args, ["offset", "start_line"])),
optionalProtoInt(3, numberArg(args, ["limit", "num_lines", "line_count"])),
optionalProtoBool(5, booleanArg(args, ["include_line_numbers", "includeLineNumbers"]))
]);
}
function encodeCursorNativeLsArgs(args, call) {
const ignore = arrayArg(args, ["ignore", "ignores", "exclude"]).filter((item) => typeof item === "string");
return Buffer.concat([
protoString(1, stringArg(args, ["path", "directory", "dir", "target_directory"]) || "."),
Buffer.concat(ignore.map((item) => protoString(2, item))),
protoString(3, call?.id)
]);
}
function encodeCursorNativeGrepArgs(args, call) {
return Buffer.concat([
protoString(1, stringArg(args, ["pattern", "query", "regex"])),
protoString(2, stringArg(args, ["path", "target_directory", "directory"])),
protoString(3, stringArg(args, ["glob", "include", "include_pattern"])),
protoString(4, stringArg(args, ["output_mode", "outputMode"])),
optionalProtoInt(5, numberArg(args, ["context_before", "contextBefore"])),
optionalProtoInt(6, numberArg(args, ["context_after", "contextAfter"])),
optionalProtoInt(7, numberArg(args, ["context"])),
optionalProtoBool(8, booleanArg(args, ["case_insensitive", "caseInsensitive", "ignore_case"])),
protoString(9, stringArg(args, ["type"])),
optionalProtoInt(10, numberArg(args, ["head_limit", "headLimit", "limit"])),
optionalProtoBool(11, booleanArg(args, ["multiline"])),
protoString(12, stringArg(args, ["sort"])),
optionalProtoBool(13, booleanArg(args, ["sort_ascending", "sortAscending"])),
protoString(14, call?.id),
optionalProtoInt(16, numberArg(args, ["offset"]))
]);
}
function encodeCursorNativeGlobArgs(args) {
return Buffer.concat([
protoString(1, stringArg(args, ["target_directory", "targetDirectory", "path", "directory"])),
protoString(2, stringArg(args, ["glob_pattern", "globPattern", "pattern", "query"]) || "*")
]);
}
function encodeCursorNativeSemSearchArgs(args) {
const targetDirectories = arrayArg(args, ["target_directories", "targetDirectories", "directories"])
.filter((item) => typeof item === "string");
return Buffer.concat([
protoString(1, stringArg(args, ["query", "question"])),
Buffer.concat(targetDirectories.map((item) => protoString(2, item))),
protoString(3, stringArg(args, ["explanation", "reason"]))
]);
}
function encodeCursorNativeShellArgs(args, call) {
const simpleCommands = arrayArg(args, ["simple_commands", "simpleCommands"])
.filter((item) => typeof item === "string");
return Buffer.concat([
protoString(1, stringArg(args, ["command", "cmd"])),
protoString(2, stringArg(args, ["working_directory", "workingDirectory", "cwd", "path"])),
optionalProtoInt(3, numberArg(args, ["timeout", "timeout_ms", "timeoutMs"])),
protoString(4, call?.id),
Buffer.concat(simpleCommands.map((item) => protoString(5, item))),
optionalProtoBool(11, booleanArg(args, ["is_background", "isBackground", "background"])),
optionalProtoBool(12, booleanArg(args, ["skip_approval", "skipApproval"])),
optionalProtoInt(13, timeoutBehaviorArg(args)),
optionalProtoInt(14, numberArg(args, ["hard_timeout", "hardTimeout"])),
protoString(15, stringArg(args, ["description", "reason"])),
optionalProtoBool(17, booleanArg(args, ["close_stdin", "closeStdin"]))
]);
}
function encodeCursorNativeDeleteArgs(args, call) {
return Buffer.concat([
protoString(1, stringArg(args, ["path", "file", "file_path", "target_file"])),
protoString(2, call?.id)
]);
}
function encodeCursorNativeEditArgs(args) {
return Buffer.concat([
protoString(1, stringArg(args, ["path", "file", "file_path", "target_file"])),
protoRawString(6, stringArg(args, ["stream_content", "streamContent", "content", "file_text", "text", "new_content"]))
]);
}
function encodeCursorNativeReadLintsArgs(args) {
const paths = arrayArg(args, ["paths", "path", "files", "file"])
.filter((item) => typeof item === "string");
return Buffer.concat(paths.map((item) => protoString(1, item)));
}
function encodeCursorNativeWebSearchArgs(args, call) {
return Buffer.concat([
protoString(1, stringArg(args, ["search_term", "searchTerm", "query", "q"])),
protoString(2, call?.id)
]);
}
function encodeCursorNativeUrlArgs(args, call) {
return Buffer.concat([
protoString(1, stringArg(args, ["url", "uri"])),
protoString(2, call?.id)
]);
}
function encodeCursorNativeTaskArgs(args) {
const attachments = arrayArg(args, ["attachments", "files", "paths"])
.filter((item) => typeof item === "string");
const respondingToMessageIds = arrayArg(args, ["responding_to_message_ids", "respondingToMessageIds"])
.filter((item) => typeof item === "string");
return Buffer.concat([
protoString(1, stringArg(args, ["description", "title", "summary"])),
protoRawString(2, stringArg(args, ["prompt", "task", "input", "instructions"])),
protoString(4, stringArg(args, ["model"])),
protoString(5, stringArg(args, ["resume", "resume_from_id", "resumeFromId"])),
protoString(6, stringArg(args, ["agent_id", "agentId"])),
Buffer.concat(attachments.map((item) => protoString(7, item))),
optionalProtoInt(8, taskModeArg(args)),
Buffer.concat(respondingToMessageIds.map((item) => protoString(9, item))),
optionalProtoInt(10, taskEnvironmentArg(args))
]);
}
function encodeCursorNativeAwaitArgs(args) {
return Buffer.concat([
protoString(1, stringArg(args, ["task_id", "taskId", "id"])),
optionalProtoInt(2, numberArg(args, ["block_until_ms", "blockUntilMs", "timeout_ms", "timeoutMs"])),
protoString(3, stringArg(args, ["regex", "pattern"]))
]);
}
function encodeCursorNativeReadTodosArgs(args) {
const statuses = arrayArg(args, ["status_filter", "statusFilter", "statuses", "status"])
.map(todoStatusArg)
.filter((value) => value > 0);
const ids = arrayArg(args, ["id_filter", "idFilter", "ids", "id"])
.filter((item) => typeof item === "string");
return Buffer.concat([
Buffer.concat(statuses.map((item) => optionalProtoInt(1, item))),
Buffer.concat(ids.map((item) => protoString(2, item)))
]);
}
function encodeCursorNativeUpdateTodosArgs(args) {
const todos = arrayArg(args, ["todos", "items"])
.filter(isRecord)
.map(encodeCursorNativeTodoItem);
return Buffer.concat([
Buffer.concat(todos.map((item) => protoMessage(1, item))),
optionalProtoBool(2, booleanArg(args, ["merge"]))
]);
}
function encodeCursorNativeTodoItem(item) {
const dependencies = arrayArg(item, ["dependencies", "depends_on", "dependsOn"])
.filter((value) => typeof value === "string");
return Buffer.concat([
protoString(1, stringArg(item, ["id"])),
protoRawString(2, stringArg(item, ["content", "text", "title"])),
optionalProtoInt(3, todoStatusArg(item?.status)),
optionalProtoInt(4, numberArg(item, ["created_at", "createdAt"])),
optionalProtoInt(5, numberArg(item, ["updated_at", "updatedAt"])),
Buffer.concat(dependencies.map((dependency) => protoString(6, dependency)))
]);
}
function encodeCursorNativeAskQuestionArgs(args, call) {
const questions = arrayArg(args, ["questions", "items"])
.filter(isRecord)
.map(encodeCursorNativeQuestion);
return Buffer.concat([
protoString(1, stringArg(args, ["title", "header"])),
Buffer.concat(questions.map((question) => protoMessage(2, question))),
optionalProtoBool(5, booleanArg(args, ["run_async", "runAsync"])),
protoString(6, stringArg(args, ["async_original_tool_call_id", "asyncOriginalToolCallId"]))
]);
}
function encodeCursorNativeQuestion(question) {
const options = arrayArg(question, ["options", "choices"])
.filter(isRecord)
.map(encodeCursorNativeQuestionOption);
return Buffer.concat([
protoString(1, stringArg(question, ["id"])),
protoRawString(2, stringArg(question, ["prompt", "question", "text"])),
Buffer.concat(options.map((option) => protoMessage(3, option))),
optionalProtoBool(4, booleanArg(question, ["allow_multiple", "allowMultiple", "multiple"]))
]);
}
function encodeCursorNativeQuestionOption(option) {
return Buffer.concat([
protoString(1, stringArg(option, ["id", "value"])),
protoRawString(2, stringArg(option, ["label", "text", "description"]))
]);
}
function encodeCursorNativeSwitchModeArgs(args, call) {
return Buffer.concat([
protoString(1, stringArg(args, ["target_mode_id", "targetModeId", "mode", "mode_id"])),
protoRawString(2, stringArg(args, ["explanation", "reason"])),
protoString(3, call?.id)
]);
}
function encodeCursorNativeGenerateImageArgs(args) {
const references = arrayArg(args, ["reference_image_paths", "referenceImagePaths", "references"])
.filter((item) => typeof item === "string");
return Buffer.concat([
protoRawString(1, stringArg(args, ["description", "prompt"])),
protoString(2, stringArg(args, ["file_path", "filePath", "path"])),
Buffer.concat(references.map((item) => protoString(5, item)))
]);
}
function encodeCursorNativeListMcpResourcesArgs(args) {
return protoString(1, stringArg(args, ["server", "provider", "provider_identifier", "providerIdentifier"]));
}
function encodeCursorNativeReadMcpResourceArgs(args, call) {
return Buffer.concat([
protoString(1, stringArg(args, ["server", "provider", "provider_identifier", "providerIdentifier"])),
protoString(2, stringArg(args, ["uri", "url"])),
protoString(3, stringArg(args, ["download_path", "downloadPath", "path"])),
protoString(4, call?.id)
]);
}
function encodeCursorNativeGetMcpToolsArgs(args, call) {
return Buffer.concat([
protoString(1, stringArg(args, ["server", "provider", "provider_identifier", "providerIdentifier"])),
protoString(2, stringArg(args, ["tool_name", "toolName", "name"])),
protoString(3, stringArg(args, ["pattern", "query"])),
protoString(4, call?.id)
]);
}
function encodeCursorNativeCallMcpToolArgs(args, call) {
const server = stringArg(args, ["server", "provider", "provider_identifier", "providerIdentifier"]);
const toolName = stringArg(args, ["tool_name", "toolName", "name"]);
const rawArgs = firstDefined(args?.arguments, args?.args, args?.parameters, args?.input);
const toolArgs = isRecord(rawArgs) ? rawArgs : {};
return Buffer.concat([
protoString(1, toolName),
encodeProtoStringValueMapField(2, toolArgs),
protoString(3, call?.id),
protoString(4, server),
protoString(5, toolName),
optionalProtoBool(8, booleanArg(args, ["skip_approval", "skipApproval"]))
]);
}
function encodeCursorNativeSetActiveBranchArgs(args) {
return Buffer.concat([
protoString(1, stringArg(args, ["path", "repo_path", "repoPath"])),
protoString(2, stringArg(args, ["branch_name", "branchName", "branch"]))
]);
}
function stringArg(args, keys) {
for (const key of keys) {
if (typeof args?.[key] === "string" && args[key].trim()) {
return args[key].trim();
}
}
return "";
}
function numberArg(args, keys) {
for (const key of keys) {
const value = args?.[key];
if (value === undefined || value === null || value === "") {
continue;
}
const number = Number(value);
if (Number.isFinite(number)) {
return number;
}
}
return undefined;
}
function booleanArg(args, keys) {
for (const key of keys) {
const value = args?.[key];
if (typeof value === "boolean") {
return value;
}
}
return undefined;
}
function arrayArg(args, keys) {
for (const key of keys) {
const value = args?.[key];
if (Array.isArray(value)) {
return value;
}
if (typeof value === "string" && value.trim()) {
return [value.trim()];
}
}
return [];
}
function timeoutBehaviorArg(args) {
const value = stringArg(args, ["timeout_behavior", "timeoutBehavior"]).toLowerCase();
if (value === "cancel") {
return 1;
}
if (value === "background") {
return 2;
}
return undefined;
}
function taskModeArg(args) {
const value = stringArg(args, ["mode"]).toLowerCase();
if (value === "agent" || value === "default") {
return 1;
}
if (value === "ask" || value === "plan") {
return 2;
}
const number = numberArg(args, ["mode"]);
return Number.isFinite(number) ? number : undefined;
}
function taskEnvironmentArg(args) {
const value = stringArg(args, ["environment", "env"]).toLowerCase();
if (value === "local" || value === "ide") {
return 1;
}
if (value === "cloud" || value === "remote") {
return 2;
}
const number = numberArg(args, ["environment", "env"]);
return Number.isFinite(number) ? number : undefined;
}
function todoStatusArg(value) {
const normalized = typeof value === "string" ? value.trim().toLowerCase().replace(/[-\s]+/g, "_") : "";
if (normalized === "pending" || normalized === "todo") {
return 1;
}
if (normalized === "in_progress" || normalized === "inprogress" || normalized === "doing") {
return 2;
}
if (normalized === "completed" || normalized === "complete" || normalized === "done") {
return 3;
}
if (normalized === "cancelled" || normalized === "canceled") {
return 4;
}
const number = Number(value);
return Number.isFinite(number) && number > 0 ? number : undefined;
}
function encodeProtoStringValueMapField(fieldNumber, value) {
if (!isRecord(value)) {
return Buffer.alloc(0);
}
return Buffer.concat(Object.entries(value).map(([key, rawValue]) => {
const entry = Buffer.concat([
protoString(1, key),
protoMessage(2, encodeGoogleValue(rawValue))
]);
return protoMessage(fieldNumber, entry);
}));
}
function encodeGoogleStruct(value) {
if (!isRecord(value)) {
return Buffer.alloc(0);
}
return Buffer.concat(Object.entries(value).map(([key, rawValue]) => {
const field = Buffer.concat([
protoString(1, key),
protoMessage(2, encodeGoogleValue(rawValue))
]);
return protoMessage(1, field);
}));
}
function encodeGoogleListValue(value) {
const items = Array.isArray(value) ? value : [];
return Buffer.concat(items.map((item) => protoMessage(1, encodeGoogleValue(item))));
}
function encodeGoogleValue(value) {
if (value === null || value === undefined) {
return Buffer.concat([protoTag(1, 0), protoVarint(0)]);
}
if (typeof value === "number") {
return Number.isFinite(value) ? protoDouble(2, value) : protoRawString(3, String(value));
}
if (typeof value === "string") {
return protoRawString(3, value);
}
if (typeof value === "boolean") {
return protoBool(4, value, true);
}
if (Array.isArray(value)) {
return protoMessage(6, encodeGoogleListValue(value));
}
if (isRecord(value)) {
return protoMessage(5, encodeGoogleStruct(value));
}
return protoRawString(3, String(value));
}
function resolveGatewayRoute(runtime, request, url, requestBody) {
const method = (request.method || "GET").toUpperCase();
const path = normalizePath(url.pathname);
const search = url.search || "";
if (method === "GET" || method === "HEAD") {
if (isModelsPath(path)) {
return { body: undefined, method, path: `/v1/models${search}`, protocol: "openai" };
}
return undefined;
}
if (method !== "POST" && method !== "PUT" && method !== "PATCH") {
return undefined;
}
if (isOpenAIChatPath(path)) {
const routed = prepareOpenAIChatRequestBody(runtime, requestBody);
return {
body: routed.body,
method,
path: `/v1/chat/completions${search}`,
protocol: "openai",
skipRuntimeTargetHeaders: routed.routed
};
}
if (isOpenAIResponsesPath(path)) {
const routed = routeJsonRequestBody(runtime, requestBody);
return {
body: routed.body,
method,
path: `/v1/responses${search}`,
protocol: "openai",
skipRuntimeTargetHeaders: routed.routed
};
}
if (isAnthropicMessagesPath(path)) {
const routed = routeJsonRequestBody(runtime, requestBody);
return {
body: routed.body,
method,
path: `/v1/messages${search}`,
protocol: "anthropic",
skipRuntimeTargetHeaders: routed.routed
};
}
if (isAnthropicCountTokensPath(path)) {
const routed = routeJsonRequestBody(runtime, requestBody);
return {
body: routed.body,
method,
path: `/v1/messages/count_tokens${search}`,
protocol: "anthropic",
skipRuntimeTargetHeaders: routed.routed
};
}
if (isGeminiPath(path)) {
const routed = routeJsonRequestBody(runtime, requestBody);
return {
body: routed.body,
method,
path: `${path}${search}`,
protocol: "gemini",
skipRuntimeTargetHeaders: routed.routed
};
}
if (runtime.cursorConnectJson && isCursorConnectJsonRequest(request.headers, path)) {
const converted = convertCursorJsonToOpenAIChat(runtime, path, requestBody);
if (converted) {
const routingDecision = applyCursorProxyRouting(runtime, converted);
return {
body: Buffer.from(`${JSON.stringify(converted)}\n`, "utf8"),
method: "POST",
path: "/v1/chat/completions",
protocol: "openai",
skipRuntimeTargetHeaders: routingDecision.routed
};
}
}
return undefined;
}
function buildGatewayHeaders(runtime, sourceHeaders, route, body) {
const headers = copyForwardHeaders(sourceHeaders);
delete headers.host;
delete headers.authorization;
delete headers["x-api-key"];
delete headers["content-length"];
delete headers["content-encoding"];
delete headers["accept-encoding"];
for (const key of Object.keys(headers)) {
if (key.startsWith(INTERNAL_HEADER_PREFIX)) {
delete headers[key];
}
}
headers["content-type"] = route.body ? (headers["content-type"] || "application/json") : "application/json";
headers.accept = headers.accept || "*/*";
headers["x-ccr-client"] = "Cursor";
headers["x-ccr-cursor-proxy"] = "1";
if (body) {
headers["content-length"] = String(Buffer.byteLength(body));
}
if (runtime.gatewayApiKey) {
headers.authorization = `Bearer ${runtime.gatewayApiKey}`;
headers["x-api-key"] = runtime.gatewayApiKey;
}
const useRuntimeTargetHeaders = route.skipRuntimeTargetHeaders !== true;
if (route.targetProvider || (useRuntimeTargetHeaders && runtime.targetProvider)) {
headers["x-target-provider"] = route.targetProvider || runtime.targetProvider;
}
if (route.targetProviders || (useRuntimeTargetHeaders && runtime.targetProviders)) {
headers["x-target-providers"] = route.targetProviders || runtime.targetProviders;
}
if (route.targetModel || (useRuntimeTargetHeaders && runtime.targetModel)) {
headers["x-target-model"] = route.targetModel || runtime.targetModel;
}
if (route.protocol === "anthropic") {
headers["anthropic-version"] = headers["anthropic-version"] || "2023-06-01";
}
return headers;
}
function buildPassthroughHeaders(sourceHeaders, originalUrl) {
const headers = copyForwardHeaders(sourceHeaders);
for (const key of Object.keys(headers)) {
if (key.startsWith(INTERNAL_HEADER_PREFIX)) {
delete headers[key];
}
}
delete headers["content-length"];
headers.host = originalUrl.host;
return headers;
}
function logCursorPassthrough(runtime, method, path, headers) {
const normalizedPath = normalizePath(path);
const key = `${method} ${normalizedPath}`;
const count = (runtime.passthroughLogCounts.get(key) || 0) + 1;
runtime.passthroughLogCounts.set(key, count);
if (count > runtime.passthroughLogLimit) {
return;
}
const suffix = count === runtime.passthroughLogLimit
? "; suppressing repeated logs for this RPC"
: "";
runtime.logger?.debug?.(
`Cursor proxy passing through non-chat Cursor RPC ${method} ${normalizedPath} ` +
`(${readHeader(headers["content-type"]) || "no content-type"})${suffix}.`
);
}
function copyForwardHeaders(sourceHeaders) {
const headers = {};
for (const [key, value] of Object.entries(sourceHeaders)) {
const normalized = key.toLowerCase();
if (HOP_BY_HOP_HEADERS.has(normalized) || value === undefined) {
continue;
}
headers[normalized] = Array.isArray(value) ? value.join(",") : String(value);
}
return headers;
}
async function forwardToUrl({ body, headers, method, response, url }) {
const targetUrl = url instanceof URL ? url : new URL(url);
const transport = targetUrl.protocol === "https:" ? https : http;
await new Promise((resolve) => {
const upstreamRequest = transport.request(
{
headers,
hostname: targetUrl.hostname,
method,
path: `${targetUrl.pathname}${targetUrl.search}`,
port: targetUrl.port || (targetUrl.protocol === "https:" ? 443 : 80),
protocol: targetUrl.protocol
},
(upstreamResponse) => {
const statusCode = upstreamResponse.statusCode || 502;
response.writeHead(statusCode, filterResponseHeaders(upstreamResponse.headers));
upstreamResponse.pipe(response);
upstreamResponse.once("end", resolve);
upstreamResponse.once("error", (error) => {
if (!response.headersSent) {
sendJson(response, 502, { error: { message: formatError(error) } });
} else {
response.destroy(error);
}
resolve();
});
}
);
upstreamRequest.once("error", (error) => {
if (!response.headersSent) {
sendJson(response, 502, { error: { message: formatError(error) } });
} else {
response.destroy(error);
}
resolve();
});
upstreamRequest.setTimeout(120000, () => {
upstreamRequest.destroy(new Error(`Cursor proxy upstream request to ${targetUrl.toString()} timed out.`));
});
if (body && Buffer.byteLength(body) > 0 && method !== "GET" && method !== "HEAD") {
upstreamRequest.end(body);
} else {
upstreamRequest.end();
}
});
}
function convertCursorJsonToOpenAIChat(runtime, path, body) {
const payload = unwrapCursorJson(readJsonLikePayload(body));
if (!payload) {
return undefined;
}
const messages = extractMessages(payload);
if (messages.length === 0) {
const prompt = findFirstStringByKeys(payload, ["prompt", "query", "instruction", "text"]);
if (prompt) {
messages.push({ content: prompt, role: "user" });
}
}
if (messages.length === 0) {
return undefined;
}
const systemPrompt = extractSystemPrompt(payload);
if (systemPrompt && !messages.some((message) => message.role === "system")) {
messages.unshift({ content: systemPrompt, role: "system" });
}
const compactedMessages = compactChatMessages(messages);
if (compactedMessages.length === 0) {
return undefined;
}
const tools = extractTools(payload);
const toolChoice = extractToolChoice(payload);
const model =
findFirstStringByKeys(payload, ["model", "modelName", "selectedModel", "intentModel", "chatModel"]) ||
runtime.defaultModel ||
"cursor-proxy";
const stream = path.toLowerCase().includes("stream") || Boolean(findFirstBooleanByKeys(payload, ["stream", "shouldStream"]));
return compactObject({
frequency_penalty: findFirstNumberByKeys(payload, ["frequency_penalty", "frequencyPenalty"]),
max_tokens: findFirstNumberByKeys(payload, ["max_tokens", "maxTokens", "maxOutputTokens"]),
messages: compactedMessages,
model,
presence_penalty: findFirstNumberByKeys(payload, ["presence_penalty", "presencePenalty"]),
stream,
temperature: findFirstNumberByKeys(payload, ["temperature"]),
tool_choice: toolChoice,
tools: tools.length > 0 ? tools : undefined,
top_p: findFirstNumberByKeys(payload, ["top_p", "topP"])
});
}
function extractMessages(value) {
const candidates = [];
const rootMessages = normalizeMessages(value);
if (rootMessages.length > 0) {
candidates.push(rootMessages);
}
walkJson(value, (item) => {
if (!Array.isArray(item) || item.length === 0) {
return;
}
const normalizedItems = item.map(normalizeMessages);
const messages = normalizedItems.flat();
const matchedItems = normalizedItems.filter((messagesForItem) => messagesForItem.length > 0).length;
if (messages.length > 0 && matchedItems >= Math.ceil(item.length * 0.5)) {
candidates.push(messages);
}
});
candidates.sort((a, b) => scoreMessages(b) - scoreMessages(a));
return candidates[0] || [];
}
function extractMessagesFromValues(values) {
const candidates = [];
for (const value of values) {
const messages = extractMessages(value);
if (messages.length > 0) {
candidates.push(messages);
}
}
candidates.sort((a, b) => scoreMessages(b) - scoreMessages(a));
return [...(candidates[0] || [])];
}
function scoreMessages(messages) {
return messages.length * 10 +
messages.filter((message) => message.role === "system").length * 4 +
messages.filter((message) => message.role === "tool").length * 3 +
messages.filter((message) => Array.isArray(message.tool_calls) && message.tool_calls.length > 0).length * 3;
}
function normalizeMessages(value) {
const messages = [];
const message = normalizeMessage(value);
if (message) {
messages.push(message);
}
const toolResults = normalizeToolResultMessages(value);
if (toolResults.length > 0) {
messages.push(...toolResults);
}
return messages;
}
function normalizeMessage(value) {
if (!isRecord(value)) {
return undefined;
}
const role = normalizeRole(
stringValue(value.role) ||
stringValue(value.speaker) ||
stringValue(value.author) ||
stringValue(value.type) ||
stringValue(value.kind)
);
if (!role) {
return undefined;
}
const content = normalizeMessageContent(value);
const toolCalls = normalizeToolCallsFromMessage(value);
const functionCall = normalizeFunctionCall(value.function_call || value.functionCall);
if (!hasChatMessageContent(content) && toolCalls.length === 0 && !functionCall) {
return undefined;
}
return compactObject({
content,
function_call: functionCall,
name: role === "tool" ? stringValue(value.name) || stringValue(value.toolName) || stringValue(value.functionName) : undefined,
role,
tool_call_id: role === "tool" ? toolCallIdFromRecord(value) : undefined,
tool_calls: toolCalls.length > 0 ? toolCalls : undefined
});
}
function normalizeRole(value) {
if (!value) {
return undefined;
}
const normalized = value.toLowerCase().replace(/^role_/, "").replace(/[-\s]+/g, "_");
if (["customer", "human", "human_message", "request", "user", "user_message"].includes(normalized)) {
return "user";
}
if (["agent", "ai", "assistant", "assistant_message", "bot", "model", "model_message"].includes(normalized)) {
return "assistant";
}
if (["developer", "developer_message", "system", "system_message"].includes(normalized)) {
return "system";
}
if (["function", "function_result", "tool", "tool_result", "tool_result_error", "tool_response"].includes(normalized)) {
return "tool";
}
return undefined;
}
function normalizeMessageContent(value) {
for (const key of [
"bubbleText",
"content",
"displayText",
"markdown",
"message",
"observation",
"output",
"plainText",
"prompt",
"rawText",
"result",
"text",
"userMessage",
"value",
"error"
]) {
if (Object.prototype.hasOwnProperty.call(value, key)) {
const content = normalizeContentValue(value[key]);
if (hasChatMessageContent(content)) {
return content;
}
}
}
return undefined;
}
function normalizeContentValue(value) {
if (typeof value === "string") {
return value;
}
if (typeof value === "number" || typeof value === "boolean") {
return String(value);
}
if (Array.isArray(value)) {
const parts = [];
const text = [];
for (const item of value) {
if (isToolCallContentBlock(item) || isToolResultContentBlock(item)) {
continue;
}
const part = normalizeOpenAIContentPart(item);
if (part) {
parts.push(part);
continue;
}
const itemText = stringifyContent(item);
if (itemText) {
text.push(itemText);
}
}
if (parts.length > 0 && parts.every((part) => part.type === "text")) {
return [...text, ...parts.map((part) => part.text)].filter(Boolean).join("\n");
}
if (parts.length > 0 && text.length === 0) {
return parts;
}
if (parts.length > 0 || text.length > 0) {
return [
...text.map((item) => ({ text: item, type: "text" })),
...parts
];
}
return undefined;
}
if (isRecord(value)) {
if (isToolCallContentBlock(value) || isToolResultContentBlock(value)) {
return undefined;
}
const part = normalizeOpenAIContentPart(value);
if (part?.type === "text") {
return part.text;
}
if (part) {
return [part];
}
const text = stringifyContent(value);
if (text) {
return text;
}
try {
return JSON.stringify(value);
} catch {
return undefined;
}
}
return undefined;
}
function normalizeOpenAIContentPart(value) {
if (!isRecord(value)) {
return undefined;
}
const type = stringValue(value.type)?.toLowerCase();
if ((type === "text" || type === "input_text") && typeof value.text === "string") {
return { text: value.text, type: "text" };
}
if (type === "image_url" && (isRecord(value.image_url) || typeof value.image_url === "string")) {
return {
image_url: typeof value.image_url === "string" ? { url: value.image_url } : value.image_url,
type: "image_url"
};
}
if ((type === "image" || type === "input_image") && typeof value.url === "string") {
return { image_url: { url: value.url }, type: "image_url" };
}
return undefined;
}
function normalizeToolCallsFromMessage(value) {
const calls = [];
for (const key of ["tool_calls", "toolCalls", "function_calls", "functionCalls", "calls"]) {
calls.push(...normalizeToolCallList(value[key]));
}
calls.push(...normalizeToolCallList(value.tool_call || value.toolCall));
if (Array.isArray(value.content)) {
for (const block of value.content) {
if (isToolCallContentBlock(block)) {
calls.push(...normalizeToolCallList(block));
}
}
}
return uniqueToolCalls(calls);
}
function normalizeToolCallList(value) {
const values = Array.isArray(value) ? value : value ? [value] : [];
return values.map(normalizeToolCall).filter(Boolean);
}
function normalizeToolCall(value, index) {
const raw = unwrapToolCall(value);
if (!isRecord(raw)) {
return undefined;
}
const fn = isRecord(raw.function) ? raw.function : raw;
const name =
stringValue(fn.name) ||
stringValue(raw.name) ||
stringValue(raw.toolName) ||
stringValue(raw.functionName);
if (!name) {
return undefined;
}
const args = firstDefined(fn.arguments, raw.arguments, fn.input, raw.input, raw.args, fn.parameters, raw.parameters);
return {
function: {
arguments: normalizeToolArguments(args),
name
},
id: stringValue(raw.id) || stringValue(raw.tool_call_id) || stringValue(raw.toolCallId) || stringValue(raw.callId) || `call_${index + 1}`,
type: "function"
};
}
function unwrapToolCall(value) {
if (!isRecord(value)) {
return value;
}
if (isRecord(value.toolCall)) {
return value.toolCall;
}
if (isRecord(value.tool_call)) {
return value.tool_call;
}
if (isRecord(value.functionCall)) {
return value.functionCall;
}
if (isRecord(value.function_call)) {
return value.function_call;
}
return value;
}
function normalizeFunctionCall(value) {
if (!isRecord(value)) {
return undefined;
}
const name = stringValue(value.name) || stringValue(value.functionName);
if (!name) {
return undefined;
}
return {
arguments: normalizeToolArguments(firstDefined(value.arguments, value.input, value.args, value.parameters)),
name
};
}
function normalizeToolResultMessages(value) {
if (!isRecord(value)) {
return [];
}
const blocks = [];
if (Array.isArray(value.content)) {
blocks.push(...value.content);
}
if (isToolResultContentBlock(value)) {
blocks.push(value);
}
return blocks.map(normalizeToolResultBlock).filter(Boolean);
}
function normalizeToolResultBlock(value) {
const raw = unwrapToolResult(value);
if (!isRecord(raw)) {
return undefined;
}
const toolCallId = toolCallIdFromRecord(raw) || stringValue(raw.tool_use_id) || stringValue(raw.toolUseId);
const name = stringValue(raw.name) || stringValue(raw.toolName) || stringValue(raw.functionName);
const content = normalizeContentValue(firstDefined(raw.content, raw.result, raw.output, raw.text, raw.error)) || "";
if (!toolCallId && !name) {
return undefined;
}
return compactObject({
content,
name,
role: "tool",
tool_call_id: toolCallId
});
}
function unwrapToolResult(value) {
if (!isRecord(value)) {
return value;
}
if (isRecord(value.toolResult)) {
return value.toolResult;
}
if (isRecord(value.tool_result)) {
return value.tool_result;
}
return value;
}
function isToolCallContentBlock(value) {
const raw = unwrapToolCall(value);
if (!isRecord(raw)) {
return false;
}
const type = stringValue(raw.type)?.toLowerCase().replace(/[-\s]+/g, "_");
return Boolean(
type === "tool_call" ||
type === "tool_use" ||
type === "function_call" ||
isRecord(value.toolCall) ||
isRecord(value.tool_call) ||
isRecord(value.functionCall) ||
isRecord(value.function_call)
);
}
function isToolResultContentBlock(value) {
const raw = unwrapToolResult(value);
if (!isRecord(raw)) {
return false;
}
const type = stringValue(raw.type)?.toLowerCase().replace(/[-\s]+/g, "_");
return Boolean(
type === "tool_result" ||
type === "tool_result_error" ||
type === "function_result" ||
isRecord(value.toolResult) ||
isRecord(value.tool_result)
);
}
function toolCallIdFromRecord(value) {
return stringValue(value.tool_call_id) ||
stringValue(value.toolCallId) ||
stringValue(value.callId) ||
stringValue(value.id);
}
function uniqueToolCalls(calls) {
const result = [];
const seen = new Set();
for (const call of calls) {
const key = call.id || `${call.function?.name}:${call.function?.arguments}`;
if (seen.has(key)) {
continue;
}
seen.add(key);
result.push(call);
}
return result;
}
function normalizeToolArguments(value) {
if (typeof value === "string") {
return value.trim() || "{}";
}
if (value === undefined) {
return "{}";
}
try {
return JSON.stringify(value);
} catch {
return "{}";
}
}
function stringifyContent(value) {
if (typeof value === "string") {
return value;
}
if (typeof value === "number" || typeof value === "boolean") {
return String(value);
}
if (Array.isArray(value)) {
return value.map(stringifyContent).filter(Boolean).join("\n");
}
if (isRecord(value)) {
if (typeof value.text === "string") {
return value.text;
}
if (typeof value.content === "string") {
return value.content;
}
if (typeof value.markdown === "string") {
return value.markdown;
}
if (typeof value.value === "string") {
return value.value;
}
}
return undefined;
}
function extractSystemPrompt(value) {
return findFirstByKeys(value, CURSOR_SYSTEM_PROMPT_KEYS, stringifyPromptCandidate);
}
function extractSystemPromptFromValues(values) {
for (const value of values) {
const systemPrompt = extractSystemPrompt(value);
if (systemPrompt) {
return systemPrompt;
}
}
return undefined;
}
function selectPreferredSystemPrompt(...candidates) {
for (const candidate of candidates) {
const content = typeof candidate === "string" ? candidate.trim() : "";
if (!content || isLikelyCursorInstructionText(content) || looksLikeCursorToolDescriptionText(content)) {
continue;
}
return content;
}
return undefined;
}
function composeAgentSystemPrompt(values, preferredSystemPrompt) {
const sections = [];
const toolDescriptionFingerprints = new Set(
extractToolsFromValues(values)
.map((tool) => normalizePromptFingerprint(tool?.function?.description || tool?.description))
.filter(Boolean)
);
const addSection = (text) => {
const content = typeof text === "string" ? text.trim() : "";
if (!content || looksLikeNoiseString(content) || isCursorMcpToolFilePath(content)) {
return;
}
if (looksLikeCursorToolDescriptionText(content) || toolDescriptionFingerprints.has(normalizePromptFingerprint(content))) {
return;
}
if (sections.some((item) => normalizePromptFingerprint(item) === normalizePromptFingerprint(content))) {
return;
}
sections.push(content);
};
addSection(preferredSystemPrompt);
const workspaceContext = composeCursorWorkspaceContext(values);
if (workspaceContext) {
addSection(workspaceContext);
}
if (sections.length === 0) {
return undefined;
}
return truncateComposedSystemPrompt(sections.join("\n\n---\n\n"));
}
function extractCursorInstructionTexts(values) {
const instructions = [];
const addInstruction = (text) => {
const content = typeof text === "string" ? text.trim() : "";
if (!content || !isLikelyCursorInstructionText(content)) {
return;
}
if (instructions.some((item) => normalizePromptFingerprint(item) === normalizePromptFingerprint(content))) {
return;
}
instructions.push(content);
};
for (const value of Array.isArray(values) ? values : [values]) {
if (typeof value !== "string") {
continue;
}
addInstruction(value);
}
for (const filePath of extractCursorInstructionFilePaths(values)) {
const content = readTextFileIfSmall(filePath, 128 * 1024);
if (content) {
addInstruction(content);
}
}
return instructions
.sort((left, right) => scoreCursorInstructionText(right) - scoreCursorInstructionText(left))
.slice(0, 12);
}
function extractCursorInstructionFilePaths(values) {
const result = [];
const seen = new Set();
const addPath = (filePath) => {
const normalized = typeof filePath === "string" ? filePath.trim() : "";
if (!normalized || seen.has(normalized)) {
return;
}
seen.add(normalized);
result.push(normalized);
};
for (const toolPath of extractCursorToolFilePaths(values)) {
const marker = "/tools/";
const markerIndex = toolPath.lastIndexOf(marker);
if (markerIndex > 0) {
addPath(`${toolPath.slice(0, markerIndex)}/INSTRUCTIONS.md`);
}
}
for (const value of Array.isArray(values) ? values : [values]) {
if (typeof value !== "string") {
continue;
}
if (/\/mcps\/[^/]+\/INSTRUCTIONS\.md$/.test(value.trim())) {
addPath(value.trim());
}
for (const match of value.matchAll(/\/[^\s"'`<>]+\/mcps\/[^\s"'`<>]+\/INSTRUCTIONS\.md/g)) {
addPath(match[0]);
}
}
return result;
}
function isLikelyCursorInstructionText(value) {
const text = String(value || "").trim();
if (text.length < 250 || text.length > 120000 || looksLikeNoiseString(text) || looksLikeJsonText(text)) {
return false;
}
if (isCursorMcpToolFilePath(text) || looksLikeGitStatusText(text)) {
return false;
}
return /\bMCP\b|\bCORE WORKFLOW\b|\bCDP USAGE\b|\bCRITICAL\b|\bYou MUST\b|\bUse [a-zA-Z0-9_]+ when\b|\ballows you to\b|A Cursor Canvas/.test(text);
}
function scoreCursorInstructionText(value) {
const text = String(value || "");
let score = Math.min(text.length, 10000) / 100;
if (/\bMCP\b/.test(text)) {
score += 50;
}
if (/\bCORE WORKFLOW\b|\bCDP USAGE\b/.test(text)) {
score += 35;
}
if (/A Cursor Canvas/.test(text)) {
score += 25;
}
if (/\bUse [a-zA-Z0-9_]+ when\b/.test(text)) {
score += 20;
}
return score;
}
function composeCursorWorkspaceContext(values) {
const strings = (Array.isArray(values) ? values : [values]).filter((value) => typeof value === "string");
const workspaceRoot = strings.find((value) =>
/^\/.+/.test(value) &&
!value.includes("/.cursor/") &&
!value.includes("/mcps/") &&
!value.endsWith(".json") &&
value.length < 500
);
const os = strings.find((value) => /^(?:darwin|linux|win32)\s+\S+/i.test(value.trim()));
const shell = strings.find((value) => /^(?:zsh|bash|fish|pwsh|powershell|cmd)$/i.test(value.trim()));
const timezone = strings.find((value) => /^[A-Za-z_]+\/[A-Za-z0-9_+\-]+$/.test(value.trim()));
const gitStatus = strings.find(looksLikeGitStatusText);
const lines = [];
if (workspaceRoot) {
lines.push(`Workspace root: ${workspaceRoot}`);
}
if (os) {
lines.push(`OS: ${os.trim()}`);
}
if (shell) {
lines.push(`Shell: ${shell.trim()}`);
}
if (timezone) {
lines.push(`Timezone: ${timezone.trim()}`);
}
if (gitStatus) {
lines.push(`Git status:\n${gitStatus.trim()}`);
}
return lines.length > 0 ? `Cursor workspace context:\n${lines.join("\n")}` : "";
}
function looksLikeGitStatusText(value) {
const text = String(value || "").trim();
if (!text || text.length > 20000) {
return false;
}
const lines = text.split(/\r?\n/).filter(Boolean);
if (lines.length === 0) {
return false;
}
return lines.filter((line) => /^(?:[ MADRCU?!]{1,2}|\?\?)\s+/.test(line)).length >= Math.ceil(lines.length * 0.5);
}
function truncateComposedSystemPrompt(value) {
const maxLength = 32000;
return value.length > maxLength ? `${value.slice(0, maxLength)}\n\n[Cursor context truncated at ${maxLength} characters]` : value;
}
function readTextFileIfSmall(filePath, maxBytes) {
try {
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
return "";
}
const stat = fs.statSync(filePath);
if (stat.size > maxBytes) {
return "";
}
return fs.readFileSync(filePath, "utf8");
} catch {
return "";
}
}
function stringifyPromptCandidate(value) {
const content = normalizeContentValue(value);
if (typeof content === "string") {
return content.trim() || undefined;
}
if (Array.isArray(content)) {
const text = stringifyContent(content).trim();
return text || undefined;
}
return undefined;
}
function extractCursorNativeToolsFromValues(values) {
const names = extractCursorNativeToolNames(values);
return names.map((name) => cursorNativeToolSpecToOpenAITool(lookupCursorNativeToolSpec(name))).filter(Boolean);
}
function extractCursorNativeToolsFromRunRequest(runtime, runRequest, values, systemPrompt) {
const extraction = describeCursorNativeToolExtraction(runtime, runRequest, values, systemPrompt);
return extraction.names.map((name) => cursorNativeToolSpecToOpenAITool(lookupCursorNativeToolSpec(name))).filter(Boolean);
}
function describeCursorNativeToolExtraction(runtime, runRequest, values, systemPrompt) {
const sourceValues = [...(Array.isArray(values) ? values : [values]), systemPrompt].filter(Boolean);
const textNames = extractCursorNativeToolNames(sourceValues);
const nativeSupportedTools = extractCursorSupportedToolEnums(runRequest);
const enumNames = nativeSupportedTools
.map((item) => CURSOR_NATIVE_SUPPORTED_TOOL_TO_SPEC_NAME.get(item.value))
.filter(Boolean);
const fallbackApplied = shouldForwardCursorNativeBuiltinTools(runtime) &&
enumNames.length === 0 &&
hasCursorWorkspaceContext(values);
const names = uniqueStrings([
...textNames,
...enumNames,
...(fallbackApplied ? CURSOR_NATIVE_BUILTIN_TOOL_NAMES : [])
]);
const unsupportedBuiltins = CURSOR_NATIVE_BUILTIN_TOOL_NAMES.filter((name) => !lookupCursorNativeToolSpec(name));
return {
enumNames: uniqueStrings(enumNames),
enumValues: summarizeCursorSupportedToolEnums(runtime, nativeSupportedTools),
fallbackApplied,
fallbackReason: fallbackApplied
? "AgentRunRequest exposed workspace context and MCP descriptors but no Cursor native tool enum list"
: undefined,
forwardCursorNativeBuiltinTools: shouldForwardCursorNativeBuiltinTools(runtime),
hasWorkspaceContext: hasCursorWorkspaceContext(values),
names,
textNames: uniqueStrings(textNames),
unsupportedBuiltins
};
}
function shouldForwardCursorNativeBuiltinTools(runtime) {
return runtime?.forwardCursorNativeBuiltinTools !== false;
}
function hasCursorWorkspaceContext(values) {
return Boolean(composeCursorWorkspaceContext(values));
}
function extractCursorSupportedToolEnums(buffer) {
if (!Buffer.isBuffer(buffer) || buffer.length === 0) {
return [];
}
const results = [];
const seenBuffers = new Set();
collectCursorSupportedToolEnums(buffer, "", 0, seenBuffers, results);
const seenValues = new Set();
return results.filter((item) => {
const key = `${item.path}:${item.value}`;
if (seenValues.has(key)) {
return false;
}
seenValues.add(key);
return true;
});
}
function collectCursorSupportedToolEnums(buffer, currentPath, depth, seenBuffers, results) {
if (!Buffer.isBuffer(buffer) || buffer.length === 0 || depth > 14) {
return;
}
const key = `${buffer.length}:${buffer.subarray(0, 24).toString("hex")}`;
if (seenBuffers.has(key)) {
return;
}
seenBuffers.add(key);
forEachProtoField(buffer, (field) => {
const fieldPath = currentPath ? `${currentPath}.${field.fieldNumber}` : String(field.fieldNumber);
if (CURSOR_NATIVE_SUPPORTED_TOOL_FIELDS.has(field.fieldNumber)) {
for (const value of decodeCursorSupportedToolEnumField(field)) {
const name = CURSOR_NATIVE_SUPPORTED_TOOL_ENUM_NAMES.get(value);
if (name) {
results.push({
name,
path: fieldPath,
value
});
}
}
}
if (field.wireType === 2 && shouldRecurseIntoCursorProtoField(field.value, depth)) {
collectCursorSupportedToolEnums(field.value, fieldPath, depth + 1, seenBuffers, results);
}
});
}
function decodeCursorSupportedToolEnumField(field) {
if (field.wireType === 0) {
return [Number(field.value)].filter((value) => CURSOR_NATIVE_SUPPORTED_TOOL_ENUM_NAMES.has(value));
}
if (field.wireType !== 2 || field.value.length === 0) {
return [];
}
const values = decodePackedCursorSupportedToolEnums(field.value);
return values.length > 0 ? values : [];
}
function decodePackedCursorSupportedToolEnums(buffer) {
const values = [];
let offset = 0;
while (offset < buffer.length) {
const value = readProtoVarint(buffer, offset);
if (!value) {
return [];
}
offset = value.offset;
const number = Number(value.value);
if (!CURSOR_NATIVE_SUPPORTED_TOOL_ENUM_NAMES.has(number)) {
return [];
}
values.push(number);
}
return values;
}
function shouldRecurseIntoCursorProtoField(buffer, depth) {
if (!Buffer.isBuffer(buffer) || buffer.length === 0 || depth >= 14) {
return false;
}
if (looksLikeTextBuffer(buffer) && !hasReadableProtoFields(buffer)) {
return false;
}
return true;
}
function summarizeCursorSupportedToolEnums(runtime, tools) {
if (!Array.isArray(tools) || tools.length === 0) {
return [];
}
return tools.slice(0, decodeDiagnosticSampleLimit(runtime)).map((item) => ({
name: item.name,
path: item.path,
tool: CURSOR_NATIVE_SUPPORTED_TOOL_TO_SPEC_NAME.get(item.value),
value: item.value
}));
}
function extractCursorNativeToolNames(values) {
const names = new Set();
const text = (Array.isArray(values) ? values : [values])
.filter((value) => typeof value === "string")
.join("\n");
for (const spec of cursorNativeToolSpecs()) {
for (const alias of [spec.name, ...(spec.aliases || [])]) {
if (new RegExp(`(^|[^A-Za-z0-9_])${escapeRegExp(alias)}([^A-Za-z0-9_]|$)`).test(text)) {
names.add(spec.name);
break;
}
}
}
return [...names];
}
function cursorNativeToolSpecToOpenAITool(spec) {
if (!spec) {
return undefined;
}
return {
function: {
description: spec.description,
name: spec.name,
parameters: spec.parameters
},
type: "function"
};
}
function lookupCursorNativeToolSpec(name) {
const normalized = normalizeToolBridgeName(name);
return cursorNativeToolSpecs().find((spec) =>
normalizeToolBridgeName(spec.name) === normalized ||
(spec.aliases || []).some((alias) => normalizeToolBridgeName(alias) === normalized)
);
}
function cursorNativeToolSpecs() {
return [
{
aliases: ["Read", "read", "readFile"],
description: "Read a file through Cursor's native read_file tool. Use this for known project files.",
encodeArgs: encodeCursorNativeReadArgs,
name: "read_file",
parameters: {
additionalProperties: false,
properties: {
include_line_numbers: { type: "boolean" },
limit: { type: "integer" },
offset: { type: "integer" },
path: { type: "string" }
},
required: ["path"],
type: "object"
},
toolCallField: 8
},
{
aliases: ["Ls", "ListDir", "list_directory", "ls"],
description: "List a directory through Cursor's native list_dir tool.",
encodeArgs: encodeCursorNativeLsArgs,
name: "list_dir",
parameters: {
additionalProperties: false,
properties: {
ignore: { items: { type: "string" }, type: "array" },
path: { type: "string" }
},
required: ["path"],
type: "object"
},
toolCallField: 13
},
{
aliases: ["Grep", "grep", "ripgrep_search", "ripgrep_raw_search"],
description: "Search exact text or regular expressions through Cursor's native grep_search tool.",
encodeArgs: encodeCursorNativeGrepArgs,
name: "grep_search",
parameters: {
additionalProperties: false,
properties: {
case_insensitive: { type: "boolean" },
context: { type: "integer" },
context_after: { type: "integer" },
context_before: { type: "integer" },
glob: { type: "string" },
head_limit: { type: "integer" },
multiline: { type: "boolean" },
offset: { type: "integer" },
output_mode: { type: "string" },
path: { type: "string" },
pattern: { type: "string" },
sort: { type: "string" },
sort_ascending: { type: "boolean" },
type: { type: "string" }
},
required: ["pattern"],
type: "object"
},
toolCallField: 5
},
{
aliases: ["Glob", "glob", "file_search", "FileSearch"],
description: "Find files by glob pattern through Cursor's native glob/file search tool.",
encodeArgs: encodeCursorNativeGlobArgs,
name: "glob_file_search",
parameters: {
additionalProperties: false,
properties: {
glob_pattern: { type: "string" },
pattern: { type: "string" },
target_directory: { type: "string" }
},
required: ["glob_pattern"],
type: "object"
},
toolCallField: 4
},
{
aliases: ["SemanticSearch", "semantic_search", "sem_search"],
description: "Run Cursor's native semantic codebase search tool.",
encodeArgs: encodeCursorNativeSemSearchArgs,
name: "codebase_search",
parameters: {
additionalProperties: false,
properties: {
explanation: { type: "string" },
query: { type: "string" },
target_directories: { items: { type: "string" }, type: "array" }
},
required: ["query"],
type: "object"
},
toolCallField: 16
},
{
aliases: ["Shell", "run_terminal_cmd", "run_terminal_command", "run_terminal_command_v2"],
description: "Run a shell command through Cursor's native shell tool.",
encodeArgs: encodeCursorNativeShellArgs,
name: "shell",
parameters: {
additionalProperties: false,
properties: {
close_stdin: { type: "boolean" },
command: { type: "string" },
description: { type: "string" },
hard_timeout: { type: "integer" },
is_background: { type: "boolean" },
simple_commands: { items: { type: "string" }, type: "array" },
skip_approval: { type: "boolean" },
timeout: { type: "integer" },
timeout_behavior: { enum: ["cancel", "background"], type: "string" },
working_directory: { type: "string" }
},
required: ["command"],
type: "object"
},
toolCallField: 1
},
{
aliases: ["Delete", "delete", "delete_file"],
description: "Delete a file through Cursor's native delete tool.",
encodeArgs: encodeCursorNativeDeleteArgs,
name: "delete_file",
parameters: {
additionalProperties: false,
properties: {
path: { type: "string" }
},
required: ["path"],
type: "object"
},
toolCallField: 3
},
{
aliases: ["Edit", "edit", "edit_file"],
description: "Apply a full-file edit through Cursor's native edit tool.",
encodeArgs: encodeCursorNativeEditArgs,
name: "edit_file",
parameters: {
additionalProperties: false,
properties: {
content: { type: "string" },
path: { type: "string" },
stream_content: { type: "string" }
},
required: ["path", "content"],
type: "object"
},
toolCallField: 12
},
{
aliases: ["ReadLints", "read_lints"],
description: "Read linter diagnostics through Cursor's native read_lints tool.",
encodeArgs: encodeCursorNativeReadLintsArgs,
name: "read_lints",
parameters: {
additionalProperties: false,
properties: {
paths: { items: { type: "string" }, type: "array" }
},
required: ["paths"],
type: "object"
},
toolCallField: 14
},
{
aliases: ["WebSearch", "web_search"],
description: "Run a web search through Cursor's native web_search tool.",
encodeArgs: encodeCursorNativeWebSearchArgs,
name: "web_search",
parameters: {
additionalProperties: false,
properties: {
query: { type: "string" },
search_term: { type: "string" }
},
required: ["query"],
type: "object"
},
toolCallField: 18
},
{
aliases: ["WebFetch", "web_fetch", "fetch_url"],
description: "Fetch and convert a web page through Cursor's native web_fetch tool.",
encodeArgs: encodeCursorNativeUrlArgs,
name: "web_fetch",
parameters: {
additionalProperties: false,
properties: {
url: { type: "string" }
},
required: ["url"],
type: "object"
},
toolCallField: 37
},
{
aliases: ["Task", "task"],
description: "Start a Cursor native subagent task.",
encodeArgs: encodeCursorNativeTaskArgs,
name: "task",
parameters: {
additionalProperties: false,
properties: {
agent_id: { type: "string" },
attachments: { items: { type: "string" }, type: "array" },
description: { type: "string" },
model: { type: "string" },
prompt: { type: "string" },
resume: { type: "string" }
},
required: ["description", "prompt"],
type: "object"
},
toolCallField: 19
},
{
aliases: ["Await", "await", "await_task"],
description: "Await a Cursor native background task.",
encodeArgs: encodeCursorNativeAwaitArgs,
name: "await_task",
parameters: {
additionalProperties: false,
properties: {
block_until_ms: { type: "integer" },
regex: { type: "string" },
task_id: { type: "string" }
},
required: ["task_id"],
type: "object"
},
toolCallField: 42
},
{
aliases: ["TodoRead", "ReadTodos", "todo_read"],
description: "Read Cursor's native todo list.",
encodeArgs: encodeCursorNativeReadTodosArgs,
name: "todo_read",
parameters: {
additionalProperties: false,
properties: {
id_filter: { items: { type: "string" }, type: "array" },
status_filter: {
items: { enum: ["pending", "in_progress", "completed", "cancelled"], type: "string" },
type: "array"
}
},
type: "object"
},
toolCallField: 10
},
{
aliases: ["TodoWrite", "todo_write"],
description: "Update Cursor's native todo list.",
encodeArgs: encodeCursorNativeUpdateTodosArgs,
name: "todo_write",
parameters: {
additionalProperties: false,
properties: {
merge: { type: "boolean" },
todos: {
items: {
additionalProperties: false,
properties: {
content: { type: "string" },
dependencies: { items: { type: "string" }, type: "array" },
id: { type: "string" },
status: { enum: ["pending", "in_progress", "completed", "cancelled"], type: "string" }
},
required: ["content", "status"],
type: "object"
},
type: "array"
}
},
required: ["todos"],
type: "object"
},
toolCallField: 9
},
{
aliases: ["AskQuestion", "ask_question"],
description: "Ask the user a structured question through Cursor.",
encodeArgs: encodeCursorNativeAskQuestionArgs,
name: "ask_question",
parameters: {
additionalProperties: false,
properties: {
questions: {
items: {
additionalProperties: false,
properties: {
allow_multiple: { type: "boolean" },
id: { type: "string" },
options: {
items: {
additionalProperties: false,
properties: {
id: { type: "string" },
label: { type: "string" }
},
required: ["id", "label"],
type: "object"
},
type: "array"
},
prompt: { type: "string" }
},
required: ["id", "prompt"],
type: "object"
},
type: "array"
},
run_async: { type: "boolean" },
title: { type: "string" }
},
required: ["title", "questions"],
type: "object"
},
toolCallField: 23
},
{
aliases: ["SwitchMode", "switch_mode"],
description: "Switch Cursor agent mode.",
encodeArgs: encodeCursorNativeSwitchModeArgs,
name: "switch_mode",
parameters: {
additionalProperties: false,
properties: {
explanation: { type: "string" },
target_mode_id: { type: "string" }
},
required: ["target_mode_id"],
type: "object"
},
toolCallField: 25
},
{
aliases: ["GenerateImage", "generate_image"],
description: "Generate an image through Cursor's native generate_image tool.",
encodeArgs: encodeCursorNativeGenerateImageArgs,
name: "generate_image",
parameters: {
additionalProperties: false,
properties: {
description: { type: "string" },
file_path: { type: "string" },
reference_image_paths: { items: { type: "string" }, type: "array" }
},
required: ["description"],
type: "object"
},
toolCallField: 28
},
{
aliases: ["ListMcpResources", "list_mcp_resources"],
description: "List MCP resources through Cursor.",
encodeArgs: encodeCursorNativeListMcpResourcesArgs,
name: "list_mcp_resources",
parameters: {
additionalProperties: false,
properties: {
server: { type: "string" }
},
type: "object"
},
toolCallField: 20
},
{
aliases: ["FetchMcpResource", "ReadMcpResource", "read_mcp_resource"],
description: "Fetch an MCP resource through Cursor.",
encodeArgs: encodeCursorNativeReadMcpResourceArgs,
name: "read_mcp_resource",
parameters: {
additionalProperties: false,
properties: {
download_path: { type: "string" },
server: { type: "string" },
uri: { type: "string" }
},
required: ["server", "uri"],
type: "object"
},
toolCallField: 21
},
{
aliases: ["GetMcpTools", "get_mcp_tools"],
description: "Discover MCP tool schemas through Cursor.",
encodeArgs: encodeCursorNativeGetMcpToolsArgs,
name: "get_mcp_tools",
parameters: {
additionalProperties: false,
properties: {
pattern: { type: "string" },
server: { type: "string" },
tool_name: { type: "string" }
},
type: "object"
},
toolCallField: 44
},
{
aliases: ["CallMcpTool", "call_mcp_tool"],
description: "Call an MCP tool through Cursor's native MCP bridge.",
encodeArgs: encodeCursorNativeCallMcpToolArgs,
name: "call_mcp_tool",
parameters: {
additionalProperties: false,
properties: {
arguments: { additionalProperties: true, type: "object" },
args: { additionalProperties: true, type: "object" },
server: { type: "string" },
tool_name: { type: "string" }
},
required: ["server", "tool_name"],
type: "object"
},
toolCallField: 15
},
{
aliases: ["SetActiveBranch", "set_active_branch"],
description: "Set Cursor's active branch metadata.",
encodeArgs: encodeCursorNativeSetActiveBranchArgs,
name: "set_active_branch",
parameters: {
additionalProperties: false,
properties: {
branch_name: { type: "string" },
path: { type: "string" }
},
required: ["path", "branch_name"],
type: "object"
},
toolCallField: 46
}
];
}
function extractToolsFromValues(values) {
const tools = [];
for (const value of values) {
tools.push(...extractTools(value));
}
tools.push(...extractToolsFromCursorToolFiles(values));
return uniqueTools(tools);
}
function extractTools(value) {
const tools = [];
if (typeof value === "string") {
tools.push(...extractToolsFromCursorToolFiles([value]));
return uniqueTools(tools);
}
const keySet = new Set(CURSOR_TOOL_KEYS.map((key) => key.toLowerCase()));
walkJson(value, (item) => {
if (!isRecord(item)) {
return;
}
for (const [key, rawValue] of Object.entries(item)) {
if (!keySet.has(key.toLowerCase())) {
continue;
}
tools.push(...normalizeToolList(rawValue));
}
});
return uniqueTools(tools);
}
function extractToolsFromCursorToolFiles(values) {
const tools = [];
for (const filePath of extractCursorToolFilePaths(values)) {
const tool = readCursorToolDefinition(filePath);
if (tool) {
tools.push(tool);
}
}
return uniqueTools(tools);
}
function extractCursorToolFilePaths(values) {
const result = [];
const seen = new Set();
const addPath = (value) => {
const filePath = normalizeCursorMcpFilePath(value);
if (!filePath || seen.has(filePath)) {
return;
}
seen.add(filePath);
result.push(filePath);
};
for (const value of Array.isArray(values) ? values : [values]) {
if (typeof value !== "string") {
continue;
}
addPath(value);
for (const match of value.matchAll(/\/[^\s"'`<>]+\/mcps\/[^\s"'`<>]+\/tools\/[^\s"'`<>]+\.json/g)) {
addPath(match[0]);
}
}
return result;
}
function normalizeCursorMcpFilePath(value) {
const text = typeof value === "string" ? value.trim() : "";
if (!text || !text.includes("/mcps/") || !text.endsWith(".json")) {
return "";
}
if (!/\/mcps\/[^/]+\/tools\/[^/]+\.json$/.test(text)) {
return "";
}
return text;
}
function isCursorMcpToolFilePath(value) {
return Boolean(normalizeCursorMcpFilePath(value));
}
function readCursorToolDefinition(filePath) {
try {
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
return undefined;
}
const parsed = readJsonText(fs.readFileSync(filePath, "utf8"));
if (!isRecord(parsed)) {
return undefined;
}
return normalizeTool(parsed);
} catch {
return undefined;
}
}
function normalizeToolList(value) {
if (Array.isArray(value)) {
return value.map((item) => normalizeTool(item)).filter(Boolean);
}
if (isRecord(value)) {
return Object.entries(value).map(([name, item]) => {
if (!isRecord(item)) {
return normalizeTool({ description: stringifyContent(item), name });
}
const tool = { ...item };
if (!stringValue(tool.name)) {
tool.name = name;
}
return normalizeTool(tool);
}).filter(Boolean);
}
return [];
}
function normalizeConfiguredTools(value) {
if (isRecord(value) && (Array.isArray(value.tools) || isRecord(value.tools))) {
return normalizeToolList(value.tools);
}
return normalizeToolList(value);
}
function normalizeTool(value) {
if (!isRecord(value)) {
return undefined;
}
const type = stringValue(value.type);
if (type && type.toLowerCase().startsWith("web_search")) {
return { ...value, type };
}
const fn = isRecord(value.function) ? value.function : value;
const name =
stringValue(fn.name) ||
stringValue(value.name) ||
stringValue(value.toolName) ||
stringValue(value.functionName);
if (!name) {
return undefined;
}
const parameters = normalizeToolParameters(firstDefined(
fn.parameters,
value.parameters,
fn.arguments,
value.arguments,
fn.input_schema,
value.input_schema,
fn.inputSchema,
value.inputSchema,
fn.schema,
value.schema
));
return {
function: compactObject({
description: stringValue(fn.description) || stringValue(value.description),
name,
parameters
}),
type: "function"
};
}
function normalizeToolParameters(value) {
if (isRecord(value)) {
return value;
}
if (typeof value === "string") {
const parsed = readJsonText(value);
if (isRecord(parsed)) {
return parsed;
}
}
return { properties: {}, type: "object" };
}
function uniqueTools(tools) {
const result = [];
const seen = new Set();
for (const tool of tools) {
const key = tool.type === "function" ? `function:${tool.function?.name || ""}` : `${tool.type}:${tool.name || ""}`;
if (!key || seen.has(key)) {
continue;
}
seen.add(key);
result.push(tool);
}
return result;
}
function extractToolChoiceFromValues(values) {
for (const value of values) {
const toolChoice = extractToolChoice(value);
if (toolChoice !== undefined) {
return toolChoice;
}
}
return undefined;
}
function extractToolChoice(value) {
return findFirstByKeys(value, CURSOR_TOOL_CHOICE_KEYS, normalizeToolChoice);
}
function normalizeToolChoice(value) {
if (typeof value === "string" && value.trim()) {
const normalized = value.trim().toLowerCase();
if (["auto", "none", "required"].includes(normalized)) {
return normalized;
}
return { function: { name: value.trim() }, type: "function" };
}
if (!isRecord(value)) {
return undefined;
}
const type = stringValue(value.type);
if (type && ["auto", "none", "required"].includes(type.toLowerCase())) {
return type.toLowerCase();
}
const fn = isRecord(value.function) ? value.function : value;
const name = stringValue(fn.name) || stringValue(value.name) || stringValue(value.toolName);
return name ? { function: { name }, type: "function" } : undefined;
}
function decodeJsonPayloadsFromProtoStrings(buffer) {
const payloads = [];
for (const item of decodeAllProtoStrings(buffer)) {
const parsed = readJsonText(item.value);
if (parsed !== undefined) {
payloads.push(parsed);
}
}
return payloads;
}
function decodeAgentRunRequestContextValues(buffer) {
const values = [];
for (const item of decodeAllProtoStrings(buffer)) {
const text = typeof item.value === "string" ? item.value.trim() : "";
if (!text || looksLikeNoiseString(text)) {
continue;
}
values.push(text);
const parsed = readJsonText(text);
if (parsed !== undefined) {
values.push(parsed);
}
values.push(...decodeEmbeddedStringValues(text));
}
return uniqueDecodedValues(values);
}
function firstDefined(...values) {
return values.find((value) => value !== undefined);
}
function readJsonLikePayload(body) {
if (!body || body.length === 0) {
return undefined;
}
const text = body.toString("utf8").trim();
if (!text) {
return undefined;
}
try {
return JSON.parse(text);
} catch {
for (const line of text.split(/\r?\n/)) {
const trimmed = line.trim();
if (!trimmed.startsWith("{")) {
continue;
}
try {
return JSON.parse(trimmed);
} catch {
// Try the next line.
}
}
}
return undefined;
}
function unwrapCursorJson(value) {
if (!isRecord(value)) {
return value;
}
if (isRecord(value.message)) {
return value.message;
}
if (isRecord(value.request)) {
return value.request;
}
if (isRecord(value.payload)) {
return value.payload;
}
return value;
}
function findFirstStringByKeys(value, keys) {
return findFirstByKeys(value, keys, (item) => typeof item === "string" && item.trim() ? item.trim() : undefined);
}
function findFirstStringFromValues(values, keys) {
for (const value of values) {
const found = findFirstStringByKeys(value, keys);
if (found) {
return found;
}
}
return undefined;
}
function findStringValuesByKeys(value, keys) {
const keySet = new Set(keys.map((key) => key.toLowerCase()));
const found = [];
walkJson(value, (item) => {
if (!isRecord(item)) {
return;
}
for (const [key, rawValue] of Object.entries(item)) {
if (keySet.has(key.toLowerCase()) && typeof rawValue === "string" && rawValue.trim()) {
found.push(rawValue.trim());
}
}
});
return found;
}
function findFirstNumberByKeys(value, keys) {
return findFirstByKeys(value, keys, (item) => {
const number = Number(item);
return Number.isFinite(number) ? number : undefined;
});
}
function findFirstNumberFromValues(values, keys) {
for (const value of values) {
const found = findFirstNumberByKeys(value, keys);
if (found !== undefined) {
return found;
}
}
return undefined;
}
function findFirstBooleanByKeys(value, keys) {
return findFirstByKeys(value, keys, (item) => typeof item === "boolean" ? item : undefined);
}
function findFirstBooleanFromValues(values, keys) {
for (const value of values) {
const found = findFirstBooleanByKeys(value, keys);
if (found !== undefined) {
return found;
}
}
return undefined;
}
function findBestSystemPromptStringFromValues(values) {
return values
.filter((value) => typeof value === "string")
.map((value) => value.trim())
.filter((value) =>
looksLikeSystemPromptText(value) &&
!isLikelyCursorInstructionText(value) &&
!looksLikeCursorToolDescriptionText(value)
)
.sort((left, right) => right.length - left.length)[0];
}
function looksLikeCursorToolDescriptionText(value) {
const text = String(value || "").trim();
if (!text || text.length > 1600 || looksLikeJsonText(text)) {
return false;
}
const firstLine = text.split(/\r?\n/, 1)[0] || text;
if (/\bChrome DevTools Protocol\b|\bCDP\b.*\bInput\.\*|\btarget browser tab\b/i.test(text)) {
return true;
}
if (
/^(?:Use|Call|Invoke)\s+[`'"]?[A-Za-z0-9_.:-]+[`'"]?\s+(?:to|when|for)\b/i.test(firstLine) ||
/\b(?:browser|move_agent|cursor|mcp)_[a-z0-9_]+\b/i.test(text)
) {
return true;
}
const startsLikeToolDescription =
/^(?:Move|Open|Create|Rename|Navigate|Capture|Click|Type|Set|Select|Press|Scroll|Drag|Get|Highlight|List|Send|Take|Lock|Read|Write|Search|Delete|Run|Fetch|Find|Inspect|Execute|Apply)\b/.test(firstLine);
const hasToolUsageLanguage =
/\bUse (?:this|ONLY|instead|by default|browser_|move_agent_|cursor|the )\b/i.test(text) ||
/\bDo not use\b|\bDo not call\b|\bMust be called\b|\bCall this\b/i.test(text) ||
/\bDefaults? to\b|\bRequired for\b|\bOptional\b/i.test(text);
const referencesToolName = /\b[a-z][a-z0-9]+(?:_[a-z0-9]+)+\b/.test(text);
return startsLikeToolDescription && hasToolUsageLanguage && (referencesToolName || text.length < 900);
}
function findBestPromptStringFromValues(values) {
return values
.filter((value) => typeof value === "string")
.map((value) => value.trim())
.filter((value) =>
value.length > 2 &&
value.length < 200000 &&
!looksLikeNoiseString(value) &&
!looksLikeSystemPromptText(value) &&
!looksLikeJsonText(value)
)
.sort((left, right) => scorePromptString(right) - scorePromptString(left))[0];
}
function scorePromptString(value) {
const hasNaturalLanguage = /[\p{L}\p{N}]/u.test(value) ? 20 : 0;
const hasWhitespace = /\s/.test(value) ? 5 : 0;
const lengthScore = Math.min(value.length, 4000) / 100;
return hasNaturalLanguage + hasWhitespace + lengthScore;
}
function looksLikeJsonText(value) {
const trimmed = String(value || "").trim();
return (trimmed.startsWith("{") && trimmed.endsWith("}")) ||
(trimmed.startsWith("[") && trimmed.endsWith("]"));
}
function findFirstByKeys(value, keys, read) {
const keySet = new Set(keys.map((key) => key.toLowerCase()));
let found;
walkJson(value, (item) => {
if (found !== undefined || !isRecord(item)) {
return;
}
for (const [key, rawValue] of Object.entries(item)) {
if (!keySet.has(key.toLowerCase())) {
continue;
}
const readValue = read(rawValue);
if (readValue !== undefined) {
found = readValue;
return;
}
}
});
return found;
}
function walkJson(value, visit, depth = 0, seen = new Set()) {
if (depth > MAX_JSON_SCAN_DEPTH || value === undefined || value === null) {
return;
}
if (typeof value === "object") {
if (seen.has(value)) {
return;
}
seen.add(value);
}
visit(value);
if (Array.isArray(value)) {
value.forEach((item) => walkJson(item, visit, depth + 1, seen));
return;
}
if (isRecord(value)) {
Object.values(value).forEach((item) => walkJson(item, visit, depth + 1, seen));
}
}
function isCursorConnectJsonRequest(headers, path) {
const contentType = readHeader(headers["content-type"]).toLowerCase();
return isCursorNativeRpcPath(path) && (
contentType.includes("json") ||
contentType.includes("connect+json") ||
contentType === ""
);
}
function isCursorNativeLlmRpcPath(runtime, path) {
if (!isCursorNativeRpcPath(path)) {
return false;
}
const normalized = normalizePath(path);
const lower = normalized.toLowerCase();
if (lower === "/aiserver.v1.bidiservice/bidiappend") {
return false;
}
if (lower === "/agent.v1.agentservice/runsse") {
return true;
}
if (matchesConfiguredNativeLlmMethod(runtime, normalized)) {
return true;
}
const parsed = parseCursorNativeRpcPath(normalized);
if (!parsed) {
return false;
}
return CURSOR_NATIVE_LLM_SERVICE_PATTERN.test(parsed.service) &&
CURSOR_NATIVE_LLM_METHOD_PATTERN.test(parsed.method);
}
function isCursorNativeRpcPath(path) {
return CURSOR_NATIVE_RPC_PATH_PATTERN.test(normalizePath(path));
}
function parseCursorNativeRpcPath(path) {
const match = normalizePath(path).match(/^\/(?:aiserver|agent)\.v\d+\.([^/]+)\/([^/]+)$/i);
return match ? { method: match[2], service: match[1] } : undefined;
}
function matchesConfiguredNativeLlmMethod(runtime, path) {
const configured = Array.isArray(runtime?.cursorNativeLlmMethods) ? runtime.cursorNativeLlmMethods : [];
if (configured.length === 0) {
return false;
}
const normalized = normalizePath(path).toLowerCase();
const parsed = parseCursorNativeRpcPath(path);
const method = parsed?.method.toLowerCase();
return configured.some((item) => {
const value = String(item || "").trim().toLowerCase();
if (!value) {
return false;
}
return normalized === normalizePath(value).toLowerCase() || method === value;
});
}
function isOpenAIChatPath(path) {
return path === "/chat/completions" || path === "/v1/chat/completions" || path.endsWith("/chat/completions");
}
function isOpenAIResponsesPath(path) {
return path === "/responses" || path === "/v1/responses" || path.endsWith("/responses");
}
function isAnthropicMessagesPath(path) {
return path === "/messages" || path === "/v1/messages" || path.endsWith("/v1/messages");
}
function isAnthropicCountTokensPath(path) {
return path === "/v1/messages/count_tokens" || path.endsWith("/messages/count_tokens");
}
function isModelsPath(path) {
return path === "/models" || path === "/v1/models" || path.startsWith("/v1/models/");
}
function isGeminiPath(path) {
return /^\/v1(?:beta)?\/models\/[^/]+:(?:generateContent|streamGenerateContent)$/i.test(path);
}
function originalRequestUrl(request) {
const explicit = readHeader(request.headers["x-ccr-original-url"]);
if (explicit) {
try {
return new URL(explicit);
} catch {
return undefined;
}
}
const host = readHeader(request.headers.host);
if (!host) {
return undefined;
}
return new URL(`https://${host}${request.url || "/"}`);
}
function readRequestBody(request) {
return new Promise((resolve, reject) => {
const chunks = [];
request.on("data", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
request.on("end", () => resolve(Buffer.concat(chunks)));
request.on("error", reject);
});
}
function filterResponseHeaders(headers) {
const filtered = {};
for (const [key, value] of Object.entries(headers)) {
if (!HOP_BY_HOP_HEADERS.has(key.toLowerCase()) && value !== undefined) {
filtered[key] = Array.isArray(value) ? value.map(String) : String(value);
}
}
return filtered;
}
function corsHeaders(extra = {}) {
return {
"access-control-allow-headers": "authorization,connect-protocol-version,content-type,x-api-key,x-client-version",
"access-control-allow-methods": "GET,POST,PUT,PATCH,OPTIONS",
"access-control-allow-origin": "*",
...extra
};
}
function sendJson(response, statusCode, payload) {
response.writeHead(statusCode, corsHeaders({ "content-type": "application/json" }));
response.end(`${JSON.stringify(payload)}\n`);
}
function sendBinary(response, statusCode, body, headers) {
response.writeHead(statusCode, corsHeaders(headers || { "content-type": "application/octet-stream" }));
response.end(body || Buffer.alloc(0));
}
function protoHeaders(extra = {}) {
return {
"cache-control": "no-store",
"connect-protocol-version": "1",
"content-type": "application/proto",
...extra
};
}
function connectProtoHeaders(extra = {}) {
return {
"cache-control": "no-store",
"connect-content-encoding": "identity",
"connect-protocol-version": "1",
"content-type": "application/connect+proto",
...extra
};
}
function connectEnvelope(flags, data) {
const payload = Buffer.isBuffer(data) ? data : Buffer.from(data || []);
const header = Buffer.alloc(5);
header[0] = flags;
header.writeUInt32BE(payload.length, 1);
return Buffer.concat([header, payload]);
}
function decodeConnectEnvelope(buffer) {
const messages = decodeConnectMessages(buffer);
if (messages.length === 0) {
return Buffer.isBuffer(buffer) ? buffer : Buffer.alloc(0);
}
return messages.length === 1 ? messages[0] : Buffer.concat(messages);
}
function decodeConnectMessages(buffer) {
if (!Buffer.isBuffer(buffer) || buffer.length < 5) {
return Buffer.isBuffer(buffer) && buffer.length > 0 ? [buffer] : [];
}
const frames = [];
let offset = 0;
while (offset + 5 <= buffer.length) {
const flags = buffer[offset];
if ((flags & ~0x03) !== 0) {
return frames.length ? frames : [buffer];
}
const length = buffer.readUInt32BE(offset + 1);
const start = offset + 5;
const end = start + length;
if (end > buffer.length) {
return frames.length ? frames : [buffer];
}
if ((flags & 0x02) === 0 && length > 0) {
const payload = buffer.subarray(start, end);
frames.push((flags & 0x01) === 0 ? payload : decompressConnectPayload(payload) || payload);
}
offset = end;
}
if (offset === buffer.length) {
return frames;
}
return frames.length > 0 && buffer.length - offset <= 4 ? frames : [buffer];
}
function decompressConnectPayload(payload) {
for (const decompress of [zlib.gunzipSync, zlib.inflateSync, zlib.brotliDecompressSync]) {
try {
return decompress(payload);
} catch {
// Try the next compression format.
}
}
return undefined;
}
function readJsonText(text) {
if (typeof text !== "string") {
return undefined;
}
const trimmed = text.trim();
if (!trimmed) {
return undefined;
}
try {
return JSON.parse(trimmed);
} catch {
return undefined;
}
}
function protoString(fieldNumber, value) {
const text = stringValue(value);
if (!text) {
return Buffer.alloc(0);
}
return protoBytes(fieldNumber, Buffer.from(text, "utf8"), true);
}
function protoRawString(fieldNumber, value) {
if (typeof value !== "string" || value.length === 0) {
return Buffer.alloc(0);
}
return protoBytes(fieldNumber, Buffer.from(value, "utf8"), true);
}
function protoBytes(fieldNumber, value, includeEmpty) {
const payload = Buffer.isBuffer(value) ? value : Buffer.from(value || []);
if (!includeEmpty && payload.length === 0) {
return Buffer.alloc(0);
}
return Buffer.concat([protoTag(fieldNumber, 2), protoVarint(payload.length), payload]);
}
function protoMessage(fieldNumber, body) {
const payload = Buffer.isBuffer(body) ? body : Buffer.from(body || []);
return Buffer.concat([protoTag(fieldNumber, 2), protoVarint(payload.length), payload]);
}
function protoBool(fieldNumber, value, includeFalse) {
if (!value && !includeFalse) {
return Buffer.alloc(0);
}
return Buffer.concat([protoTag(fieldNumber, 0), protoVarint(value ? 1 : 0)]);
}
function optionalProtoBool(fieldNumber, value) {
return typeof value === "boolean" ? protoBool(fieldNumber, value, true) : Buffer.alloc(0);
}
function optionalProtoInt(fieldNumber, value) {
if (!Number.isFinite(Number(value))) {
return Buffer.alloc(0);
}
return Buffer.concat([protoTag(fieldNumber, 0), protoVarint(value)]);
}
function protoDouble(fieldNumber, value) {
const buffer = Buffer.alloc(8);
buffer.writeDoubleLE(Number(value) || 0, 0);
return Buffer.concat([protoTag(fieldNumber, 1), buffer]);
}
function protoTag(fieldNumber, wireType) {
return protoVarint((Number(fieldNumber) << 3) | Number(wireType));
}
function protoVarint(value) {
let number = typeof value === "bigint" ? value : BigInt(Math.max(0, Math.trunc(Number(value) || 0)));
const bytes = [];
while (number >= 0x80n) {
bytes.push(Number((number & 0x7fn) | 0x80n));
number >>= 7n;
}
bytes.push(Number(number));
return Buffer.from(bytes);
}
function decodeProtoStringField(buffer, fieldNumber) {
const field = readProtoField(buffer, fieldNumber);
return field?.wireType === 2 ? stringValue(field.value.toString("utf8")) : undefined;
}
function decodeProtoStringFields(buffer, fieldNumber) {
return readProtoFields(buffer, fieldNumber)
.filter((field) => field.wireType === 2)
.map((field) => field.value.toString("utf8"))
.filter((value) => value.length > 0);
}
function decodeAllProtoStrings(buffer, depth = 0) {
if (!Buffer.isBuffer(buffer) || depth > 6) {
return [];
}
const values = [];
forEachProtoField(buffer, (field) => {
if (field.wireType === 2 && field.value.length > 0) {
const text = field.value.toString("utf8");
if (/^[\t\n\r -~\u00a0-\uffff]+$/.test(text)) {
values.push({ fieldNumber: field.fieldNumber, value: text });
}
values.push(...decodeAllProtoStrings(field.value, depth + 1));
}
});
return values;
}
function decodeProtoBytesField(buffer, fieldNumber) {
const field = readProtoField(buffer, fieldNumber);
return field?.wireType === 2 ? field.value : undefined;
}
function decodeProtoBytesFields(buffer, fieldNumber) {
return readProtoFields(buffer, fieldNumber)
.filter((field) => field.wireType === 2)
.map((field) => field.value);
}
function concatProtoBytesFields(buffer, fieldNumber) {
const fields = decodeProtoBytesFields(buffer, fieldNumber).filter((value) => value.length > 0);
return fields.length > 0 ? Buffer.concat(fields) : Buffer.alloc(0);
}
function decodeProtoMessageFields(buffer, fieldNumber) {
return readProtoFields(buffer, fieldNumber)
.filter((field) => field.wireType === 2)
.map((field) => field.value);
}
function readLengthDelimitedProtoFields(buffer) {
const fields = [];
forEachProtoField(buffer, (field) => {
if (field.wireType === 2 && field.value.length > 0) {
fields.push(field);
}
});
return fields;
}
function hasReadableProtoFields(buffer) {
let found = false;
forEachProtoField(buffer, () => {
found = true;
});
return found;
}
function decodeProtoIntField(buffer, fieldNumber) {
const field = readProtoField(buffer, fieldNumber);
return field?.wireType === 0 ? Number(field.value) : 0;
}
function readProtoFields(buffer, targetFieldNumber) {
if (!Buffer.isBuffer(buffer)) {
return [];
}
const matches = [];
forEachProtoField(buffer, (field) => {
if (field.fieldNumber === targetFieldNumber) {
matches.push({
value: field.value,
wireType: field.wireType
});
}
});
return matches;
}
function readProtoField(buffer, targetFieldNumber) {
return readProtoFields(buffer, targetFieldNumber)[0];
}
function forEachProtoField(buffer, visitor) {
if (!Buffer.isBuffer(buffer)) {
return;
}
let offset = 0;
while (offset < buffer.length) {
const tag = readProtoVarint(buffer, offset);
if (!tag) {
return;
}
offset = tag.offset;
const fieldNumber = Number(tag.value >> 3n);
const wireType = Number(tag.value & 0x07n);
if (fieldNumber <= 0) {
return;
}
if (wireType === 0) {
const value = readProtoVarint(buffer, offset);
if (!value) {
return;
}
offset = value.offset;
visitor({ fieldNumber, value: value.value, wireType });
continue;
}
if (wireType === 1) {
if (offset + 8 > buffer.length) {
return;
}
const value = buffer.subarray(offset, offset + 8);
offset += 8;
visitor({ fieldNumber, value, wireType });
continue;
}
if (wireType === 2) {
const length = readProtoVarint(buffer, offset);
if (!length) {
return;
}
offset = length.offset;
const end = offset + Number(length.value);
if (end > buffer.length) {
return;
}
const value = buffer.subarray(offset, end);
offset = end;
visitor({ fieldNumber, value, wireType });
continue;
}
if (wireType === 5) {
if (offset + 4 > buffer.length) {
return;
}
const value = buffer.subarray(offset, offset + 4);
offset += 4;
visitor({ fieldNumber, value, wireType });
continue;
}
return;
}
}
function readProtoVarint(buffer, offset) {
let result = 0n;
let shift = 0n;
for (let index = offset; index < buffer.length; index += 1) {
const byte = buffer[index];
result |= BigInt(byte & 0x7f) << shift;
if ((byte & 0x80) === 0) {
return {
offset: index + 1,
value: result
};
}
shift += 7n;
if (shift > 70n) {
return undefined;
}
}
return undefined;
}
function formatProtoFieldSummary(buffer) {
if (!Buffer.isBuffer(buffer) || buffer.length === 0) {
return "empty";
}
const fields = [];
forEachProtoField(buffer, (field) => {
if (fields.length >= 12) {
return;
}
fields.push(`${field.fieldNumber}:${field.wireType}:${field.wireType === 2 ? field.value.length : String(field.value)}`);
});
return fields.length ? fields.join(",") : `unreadable:${buffer.length}`;
}
function configuredGatewayUrl(config) {
if (typeof config?.routerEndpoint === "string" && config.routerEndpoint.trim()) {
return config.routerEndpoint.trim();
}
const host = config?.gateway?.host && config.gateway.host !== "0.0.0.0" ? config.gateway.host : "127.0.0.1";
const port = config?.gateway?.port || config?.PORT || 3456;
return `http://${host}:${port}`;
}
function configuredGatewayApiKey(config) {
const apiKeys = Array.isArray(config?.APIKEYS) ? config.APIKEYS : [];
return apiKeys.map((item) => stringValue(item?.key)).find(Boolean) || stringValue(config?.APIKEY);
}
function configuredDefaultModel(config) {
const routerDefault = stringValue(config?.Router?.default);
if (routerDefault) {
return routeTargetModel(routerDefault) || routerDefault;
}
const preferredProvider = stringValue(config?.preferredProvider);
const providers = Array.isArray(config?.Providers) ? config.Providers : [];
const preferred = preferredProvider
? providers.find((provider) => provider?.name === preferredProvider)
: undefined;
return firstProviderModel(preferred) || providers.map(firstProviderModel).find(Boolean);
}
function routeTargetModel(value) {
const parts = value.split(",");
return parts.length > 1 ? parts.slice(1).join(",").trim() : undefined;
}
function firstProviderModel(provider) {
return Array.isArray(provider?.models) ? provider.models.map(stringValue).find(Boolean) : undefined;
}
function normalizeStringList(...values) {
const result = [];
for (const value of values) {
if (Array.isArray(value)) {
value.forEach((item) => {
const normalized = stringValue(item);
if (normalized) {
result.push(normalized.toLowerCase());
}
});
continue;
}
const normalized = stringValue(value);
if (normalized) {
result.push(normalized.toLowerCase());
}
}
return result.length ? uniqueStrings(result) : undefined;
}
function normalizePathList(value) {
if (!Array.isArray(value)) {
return undefined;
}
const result = value
.map((item) => normalizePath(stringValue(item)))
.filter(Boolean);
return result.length ? uniqueStrings(result) : undefined;
}
function normalizePath(value) {
if (!value) {
return "";
}
const trimmed = value.trim();
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
}
function trimTrailingSlash(value) {
return (value || DEFAULT_GATEWAY_URL).replace(/\/+$/, "");
}
function numberOption(value, fallback) {
const number = Number(value);
return Number.isFinite(number) && number > 0 ? number : fallback;
}
function uniqueStrings(values) {
return [...new Set(values.filter(Boolean))];
}
function compactObject(value) {
const result = {};
for (const [key, rawValue] of Object.entries(value)) {
if (rawValue !== undefined) {
result[key] = rawValue;
}
}
return result;
}
function readHeader(value) {
if (Array.isArray(value)) {
return value[0]?.trim() || "";
}
return typeof value === "string" ? value.trim() : "";
}
function stringValue(value) {
return typeof value === "string" && value.trim() ? value.trim() : undefined;
}
function isRecord(value) {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function formatError(error) {
return error instanceof Error ? error.message : String(error);
}