Files
OmniRoute/open-sse/executors/cursor.ts
diegosouzapw 48467bc514 feat(core): add db health checks and provider interoperability
Add automated SQLite health diagnostics with optional auto-repair,
startup and scheduled execution, authenticated API routes, an MCP tool,
and dashboard visibility for status and repair actions.

Improve provider compatibility by adding Cursor usage fetching and
v3.1.0 parity headers, introducing a per-connection Codex Responses
store opt-in with session fallback, fixing Codex non-stream combo and
SSE translation behavior, sanitizing Gemini googleSearch tool payloads
and Qwen thinking tool_choice handling, and hardening cleanup and call
log storage paths.
2026-04-13 13:11:30 -03:00

911 lines
28 KiB
TypeScript

declare const EdgeRuntime: string | undefined;
/**
* CursorExecutor — Handles communication with the Cursor IDE API.
*
* This executor is the most complex due to Cursor's non-standard protocol:
*
* SECTION 1: Authentication (generateChecksum)
* - SHA-256 based checksum using machine ID and timestamp
* - WorkOS token refresh for session management
*
* SECTION 2: Request Encoding (transformRequest, buildHeaders)
* - ConnectRPC Protobuf binary encoding via cursorProtobuf.js
* - Chat body construction with model routing
*
* SECTION 3: Response Parsing (executeStream, parseEvent*)
* - Binary EventStream → SSE text conversion
* - Gzip decompression of response frames
* - HTTP/2 support with h2 fallback to fetch
*
* @see cursorProtobuf.js for Protobuf encoding/decoding utilities
*/
import { BaseExecutor, mergeUpstreamExtraHeaders } from "./base.ts";
import { PROVIDERS, HTTP_STATUS } from "../config/constants.ts";
import {
generateCursorBody,
parseConnectRPCFrame,
extractTextFromResponse,
} from "../utils/cursorProtobuf.ts";
import { estimateUsage } from "../utils/usageTracking.ts";
import { FORMATS } from "../translator/formats.ts";
import crypto from "crypto";
import { v5 as uuidv5 } from "uuid";
import zlib from "zlib";
const CURSOR_CLIENT_VERSION = "3.1.0";
const CURSOR_USER_AGENT = `Cursor/${CURSOR_CLIENT_VERSION}`;
// Detect cloud environment
const isCloudEnv = () => {
if (typeof caches !== "undefined" && typeof caches === "object") return true;
if (typeof EdgeRuntime !== "undefined") return true;
return false;
};
// Lazy import http2 (only in Node.js environment)
let http2 = null;
if (!isCloudEnv()) {
try {
http2 = await import("http2");
} catch {
// http2 not available
}
}
// --- SECTION 1: Authentication Constants ---
const COMPRESS_FLAG = {
NONE: 0x00,
GZIP: 0x01,
GZIP_ALT: 0x02,
GZIP_BOTH: 0x03,
};
const CURSOR_STREAM_DEBUG = process.env.CURSOR_STREAM_DEBUG === "1";
const debugLog = (...args: unknown[]) => {
if (CURSOR_STREAM_DEBUG) console.log(...args);
};
function decompressPayload(payload, flags) {
// Check if payload is JSON error (starts with {"error")
if (payload.length > 10 && payload[0] === 0x7b && payload[1] === 0x22) {
try {
const text = payload.toString("utf-8");
if (text.startsWith('{"error"')) {
debugLog(`[DECOMPRESS] Detected JSON error, skipping decompression`);
return payload;
}
} catch {}
}
if (
flags === COMPRESS_FLAG.GZIP ||
flags === COMPRESS_FLAG.GZIP_ALT ||
flags === COMPRESS_FLAG.GZIP_BOTH
) {
// Primary: try gzip decompression (standard gzip header 0x1f 0x8b)
try {
return zlib.gunzipSync(payload);
} catch (gzipErr) {
// Fallback: GZIP_ALT (0x02) and GZIP_BOTH (0x03) frames sometimes use
// raw zlib deflate format instead of gzip wrapping (#250)
try {
return zlib.inflateSync(payload);
} catch (deflateErr) {
// Last resort: try raw deflate (no zlib header)
try {
return zlib.inflateRawSync(payload);
} catch (rawErr) {
debugLog(
`[DECOMPRESS ERROR] flags=${flags}, payloadSize=${payload.length}, gzip=${gzipErr.message}, deflate=${deflateErr.message}, raw=${rawErr.message}`
);
debugLog(
`[DECOMPRESS ERROR] First 50 bytes (hex):`,
payload.slice(0, 50).toString("hex")
);
debugLog(
`[DECOMPRESS ERROR] First 50 bytes (utf8):`,
payload
.slice(0, 50)
.toString("utf8")
.replace(/[^\x20-\x7E]/g, ".")
);
// Try to use payload as-is if all decompression methods fail
return payload;
}
}
}
}
return payload;
}
function createErrorResponse(jsonError) {
const errorMsg =
jsonError?.error?.details?.[0]?.debug?.details?.title ||
jsonError?.error?.details?.[0]?.debug?.details?.detail ||
jsonError?.error?.message ||
"API Error";
const isRateLimit = jsonError?.error?.code === "resource_exhausted";
return new Response(
JSON.stringify({
error: {
message: errorMsg,
type: isRateLimit ? "rate_limit_error" : "api_error",
code: jsonError?.error?.details?.[0]?.debug?.error || "unknown",
},
}),
{
status: isRateLimit ? HTTP_STATUS.RATE_LIMITED : HTTP_STATUS.BAD_REQUEST,
headers: { "Content-Type": "application/json" },
}
);
}
function parseCursorJsonErrorFrame(text: string) {
try {
return JSON.parse(text);
} catch {
return null;
}
}
function isToolBoundaryAbort(jsonError: unknown, toolCallCount: number) {
if (!jsonError || toolCallCount <= 0) return false;
const e = jsonError as Record<string, unknown>;
const err = e?.error as Record<string, unknown> | undefined;
const details = (err?.details as Record<string, unknown>[] | undefined)?.[0];
const debug = details?.debug as Record<string, unknown> | undefined;
const debugDetails = debug?.details as Record<string, unknown> | undefined;
const code = (err?.code as string) || "";
const debugError = (debug?.error as string) || "";
const title = (debugDetails?.title as string) || "";
const detail = (debugDetails?.detail as string) || "";
const message = `${title} ${detail}`.toLowerCase();
const isAbortedCode = code === "aborted" || debugError === "ERROR_USER_ABORTED_REQUEST";
return isAbortedCode && message.includes("tool call ended before result was received");
}
function mergeToolCallDelta(existing, incoming) {
const mergedName = incoming?.function?.name || existing?.function?.name || "";
const existingArgs = existing?.function?.arguments || "";
const deltaArgs = incoming?.function?.arguments || "";
return {
id: incoming.id || existing.id,
type: "function",
function: {
name: mergedName,
arguments: `${existingArgs}${deltaArgs}`,
},
isLast: Boolean(existing?.isLast || incoming?.isLast),
index: existing?.index ?? incoming?.index ?? 0,
};
}
type CursorHttpResponse = {
status: number;
headers: Record<string, unknown>;
body: Buffer;
};
export class CursorExecutor extends BaseExecutor {
constructor() {
super("cursor", PROVIDERS.cursor);
}
buildUrl() {
return `${this.config.baseUrl}${this.config.chatPath || ""}`;
}
// Jyh cipher checksum for Cursor API authentication
generateChecksum(machineId) {
const timestamp = Math.floor(Date.now() / 1000000);
const byteArray = new Uint8Array([
(timestamp >> 40) & 0xff,
(timestamp >> 32) & 0xff,
(timestamp >> 24) & 0xff,
(timestamp >> 16) & 0xff,
(timestamp >> 8) & 0xff,
timestamp & 0xff,
]);
let t = 165;
for (let i = 0; i < byteArray.length; i++) {
byteArray[i] = ((byteArray[i] ^ t) + (i % 256)) & 0xff;
t = byteArray[i];
}
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
let encoded = "";
for (let i = 0; i < byteArray.length; i += 3) {
const a = byteArray[i];
const b = i + 1 < byteArray.length ? byteArray[i + 1] : 0;
const c = i + 2 < byteArray.length ? byteArray[i + 2] : 0;
encoded += alphabet[a >> 2];
encoded += alphabet[((a & 3) << 4) | (b >> 4)];
if (i + 1 < byteArray.length) {
encoded += alphabet[((b & 15) << 2) | (c >> 6)];
}
if (i + 2 < byteArray.length) {
encoded += alphabet[c & 63];
}
}
return `${encoded}${machineId}`;
}
buildHeaders(credentials) {
const accessToken = credentials.accessToken;
const machineId = credentials.providerSpecificData?.machineId;
const ghostMode = credentials.providerSpecificData?.ghostMode !== false;
if (!machineId) {
throw new Error("Machine ID is required for Cursor API");
}
const cleanToken = accessToken.includes("::") ? accessToken.split("::")[1] : accessToken;
return {
authorization: `Bearer ${cleanToken}`,
"connect-accept-encoding": "gzip",
"connect-protocol-version": "1",
"content-type": "application/connect+proto",
"user-agent": CURSOR_USER_AGENT,
"x-amzn-trace-id": `Root=${crypto.randomUUID()}`,
"x-client-key": crypto.createHash("sha256").update(cleanToken).digest("hex"),
"x-cursor-checksum": this.generateChecksum(machineId),
"x-cursor-client-version": CURSOR_CLIENT_VERSION,
"x-cursor-client-type": "ide",
"x-cursor-client-os":
process.platform === "win32"
? "windows"
: process.platform === "darwin"
? "macos"
: "linux",
"x-cursor-client-arch": process.arch === "arm64" ? "aarch64" : "x64",
"x-cursor-client-device-type": "desktop",
"x-cursor-user-agent": CURSOR_USER_AGENT,
"x-cursor-config-version": crypto.randomUUID(),
"x-cursor-timezone": Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC",
"x-ghost-mode": ghostMode ? "true" : "false",
"x-request-id": crypto.randomUUID(),
"x-session-id": uuidv5(cleanToken, uuidv5.DNS),
};
}
transformRequest(model, body, stream, credentials) {
// Messages are already translated by chatCore (claude→openai→cursor)
// Do NOT call buildCursorRequest again — double-translation drops tool_results
const messages = body.messages || [];
const tools = body.tools || [];
const reasoningEffort = body.reasoning_effort || null;
return generateCursorBody(messages, model, tools, reasoningEffort);
}
async makeFetchRequest(
url: string,
headers: Record<string, string>,
body: Uint8Array,
signal?: AbortSignal
): Promise<CursorHttpResponse> {
const response = await fetch(url, {
method: "POST",
headers,
body: body as unknown as BodyInit,
signal,
});
return {
status: response.status,
headers: Object.fromEntries(response.headers.entries()),
body: Buffer.from(await response.arrayBuffer()),
};
}
makeHttp2Request(
url: string,
headers: Record<string, string>,
body: Uint8Array,
signal?: AbortSignal
): Promise<CursorHttpResponse> {
if (!http2) {
throw new Error("http2 module not available");
}
return new Promise<CursorHttpResponse>((resolve, reject) => {
const urlObj = new URL(url);
const client = http2.connect(`https://${urlObj.host}`);
const chunks = [];
let responseHeaders = {};
client.on("error", reject);
const req = client.request({
":method": "POST",
":path": urlObj.pathname,
":authority": urlObj.host,
":scheme": "https",
...headers,
});
req.on("response", (hdrs) => {
responseHeaders = hdrs;
});
req.on("data", (chunk) => {
chunks.push(chunk);
});
req.on("end", () => {
client.close();
resolve({
status:
typeof responseHeaders[":status"] === "number"
? responseHeaders[":status"]
: Number(responseHeaders[":status"] || HTTP_STATUS.SERVER_ERROR),
headers: responseHeaders,
body: Buffer.concat(chunks),
});
});
req.on("error", (err) => {
client.close();
reject(err);
});
if (signal) {
signal.addEventListener("abort", () => {
req.close();
client.close();
reject(new Error("Request aborted"));
});
}
req.write(body);
req.end();
});
}
async execute({ model, body, stream, credentials, signal, log, upstreamExtraHeaders }) {
const url = this.buildUrl();
const headers = this.buildHeaders(credentials);
mergeUpstreamExtraHeaders(headers, upstreamExtraHeaders);
const transformedBody = await this.transformRequest(model, body, stream, credentials);
try {
const response: CursorHttpResponse = http2
? await this.makeHttp2Request(url, headers, transformedBody, signal)
: await this.makeFetchRequest(url, headers, transformedBody, signal);
if (response.status !== 200) {
const errorText = response.body?.toString() || "Unknown error";
const errorResponse = new Response(
JSON.stringify({
error: {
message: `[${response.status}]: ${errorText}`,
type: "invalid_request_error",
code: "",
},
}),
{
status: response.status,
headers: { "Content-Type": "application/json" },
}
);
return { response: errorResponse, url, headers, transformedBody: body };
}
const transformedResponse =
stream !== false
? this.transformProtobufToSSE(response.body, model, body)
: this.transformProtobufToJSON(response.body, model, body);
return { response: transformedResponse, url, headers, transformedBody: body };
} catch (error) {
const errorResponse = new Response(
JSON.stringify({
error: {
message: error.message,
type: "connection_error",
code: "",
},
}),
{
status: HTTP_STATUS.SERVER_ERROR,
headers: { "Content-Type": "application/json" },
}
);
return { response: errorResponse, url, headers, transformedBody: body };
}
}
transformProtobufToJSON(buffer, model, body) {
const responseId = `chatcmpl-cursor-${Date.now()}`;
const created = Math.floor(Date.now() / 1000);
let offset = 0;
let totalContent = "";
const toolCalls = [];
const toolCallsMap = new Map(); // Track streaming tool calls by ID
const finalizedIds = new Set<string>();
let frameCount = 0;
debugLog(`[CURSOR BUFFER] Total length: ${buffer.length} bytes`);
while (offset < buffer.length) {
if (offset + 5 > buffer.length) {
debugLog(
`[CURSOR BUFFER] Reached end, offset=${offset}, remaining=${buffer.length - offset}`
);
break;
}
const flags = buffer[offset];
const length = buffer.readUInt32BE(offset + 1);
debugLog(
`[CURSOR BUFFER] Frame ${frameCount + 1}: flags=0x${flags.toString(16).padStart(2, "0")}, length=${length}`
);
if (offset + 5 + length > buffer.length) {
debugLog(
`[CURSOR BUFFER] Incomplete frame, offset=${offset}, length=${length}, buffer.length=${buffer.length}`
);
break;
}
let payload = buffer.slice(offset + 5, offset + 5 + length);
offset += 5 + length;
frameCount++;
payload = decompressPayload(payload, flags);
if (!payload) {
debugLog(`[CURSOR BUFFER] Frame ${frameCount}: decompression failed, skipping`);
continue;
}
// Check for JSON error frames (byte guard: skip toString on non-JSON frames)
if (payload.length > 0 && payload[0] === 0x7b) {
try {
const text = payload.toString("utf-8");
if (text.includes('"error"')) {
const hasContent = totalContent || toolCallsMap.size > 0;
debugLog(
`[CURSOR BUFFER] Error frame (hasContent=${hasContent}): ${text.slice(0, 500)}`
);
if (hasContent) {
break;
}
return createErrorResponse(JSON.parse(text));
}
} catch {}
}
const result = extractTextFromResponse(new Uint8Array(payload));
debugLog(`[CURSOR DECODED] Frame ${frameCount}:`, result);
if (result.error) {
const hasContent = totalContent || toolCallsMap.size > 0;
debugLog(`[CURSOR BUFFER] Decoded error (hasContent=${hasContent}): ${result.error}`);
// If we already have content, treat error as stream termination
if (hasContent) {
break;
}
return new Response(
JSON.stringify({
error: {
message: result.error,
type: "rate_limit_error",
code: "rate_limited",
},
}),
{
status: HTTP_STATUS.RATE_LIMITED,
headers: { "Content-Type": "application/json" },
}
);
}
if (result.toolCall) {
const tc = result.toolCall;
if (toolCallsMap.has(tc.id)) {
// Accumulate arguments for existing tool call
const existing = toolCallsMap.get(tc.id);
existing.function.arguments += tc.function.arguments;
existing.isLast = tc.isLast;
} else {
// New tool call
toolCallsMap.set(tc.id, { ...tc });
}
// Push to final array when isLast is true
if (tc.isLast) {
const finalToolCall = toolCallsMap.get(tc.id);
finalizedIds.add(tc.id);
toolCalls.push({
id: finalToolCall.id,
type: finalToolCall.type,
function: {
name: finalToolCall.function.name,
arguments: finalToolCall.function.arguments,
},
});
}
}
if (result.text) totalContent += result.text;
}
debugLog(
`[CURSOR BUFFER] Parsed ${frameCount} frames, toolCallsMap size: ${toolCallsMap.size}, finalized toolCalls: ${toolCalls.length}`
);
// Finalize all remaining tool calls in map (in case stream ended without isLast=true)
for (const [id, tc] of toolCallsMap.entries()) {
// Check if already in final array
if (!finalizedIds.has(id)) {
debugLog(`[CURSOR BUFFER] Finalizing incomplete tool call: ${id}, isLast=${tc.isLast}`);
toolCalls.push({
id: tc.id,
type: tc.type,
function: {
name: tc.function.name,
arguments: tc.function.arguments,
},
});
}
}
debugLog(`[CURSOR BUFFER] Final toolCalls count: ${toolCalls.length}`);
const message: Record<string, unknown> = {
role: "assistant",
content: totalContent || null,
};
if (toolCalls.length > 0) {
message.tool_calls = toolCalls;
}
const usage = estimateUsage(body, totalContent.length, FORMATS.OPENAI);
const completion = {
id: responseId,
object: "chat.completion",
created,
model,
choices: [
{
index: 0,
message,
finish_reason: toolCalls.length > 0 ? "tool_calls" : "stop",
},
],
usage,
};
return new Response(JSON.stringify(completion), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
transformProtobufToSSE(buffer, model, body) {
const responseId = `chatcmpl-cursor-${Date.now()}`;
const created = Math.floor(Date.now() / 1000);
const chunks = [];
let offset = 0;
let totalContent = "";
const toolCalls = [];
const toolCallsMap = new Map(); // Track streaming tool calls by ID
const finalizedIds = new Set<string>();
const emittedToolCallIds = new Set<string>();
let frameCount = 0;
debugLog(`[CURSOR BUFFER SSE] Total length: ${buffer.length} bytes`);
while (offset < buffer.length) {
if (offset + 5 > buffer.length) {
debugLog(
`[CURSOR BUFFER SSE] Reached end, offset=${offset}, remaining=${buffer.length - offset}`
);
break;
}
const flags = buffer[offset];
const length = buffer.readUInt32BE(offset + 1);
debugLog(
`[CURSOR BUFFER SSE] Frame ${frameCount + 1}: flags=0x${flags.toString(16).padStart(2, "0")}, length=${length}`
);
if (offset + 5 + length > buffer.length) {
debugLog(
`[CURSOR BUFFER SSE] Incomplete frame, offset=${offset}, length=${length}, buffer.length=${buffer.length}`
);
break;
}
let payload = buffer.slice(offset + 5, offset + 5 + length);
offset += 5 + length;
frameCount++;
payload = decompressPayload(payload, flags);
if (!payload) {
debugLog(`[CURSOR BUFFER SSE] Frame ${frameCount}: decompression failed, skipping`);
continue;
}
// Check for JSON error frames (byte-guard: only decode if starts with '{')
if (payload[0] === 0x7b) {
try {
const text = payload.toString("utf-8");
if (text.includes('"error"')) {
const hasContent = chunks.length > 0 || totalContent || toolCallsMap.size > 0;
debugLog(
`[CURSOR BUFFER SSE] Error frame (hasContent=${hasContent}): ${text.slice(0, 500)}`
);
if (hasContent) {
break;
}
return createErrorResponse(JSON.parse(text));
}
} catch {}
}
const result = extractTextFromResponse(new Uint8Array(payload));
debugLog(`[CURSOR DECODED SSE] Frame ${frameCount}:`, result);
if (result.error) {
const hasContent = chunks.length > 0 || totalContent || toolCallsMap.size > 0;
debugLog(`[CURSOR BUFFER SSE] Decoded error (hasContent=${hasContent}): ${result.error}`);
// If we already have content, treat error as stream termination
if (hasContent) {
break;
}
return new Response(
JSON.stringify({
error: {
message: result.error,
type: "rate_limit_error",
code: "rate_limited",
},
}),
{
status: HTTP_STATUS.RATE_LIMITED,
headers: { "Content-Type": "application/json" },
}
);
}
if (result.toolCall) {
const tc = result.toolCall;
if (chunks.length === 0) {
chunks.push(
`data: ${JSON.stringify({
id: responseId,
object: "chat.completion.chunk",
created,
model,
choices: [
{
index: 0,
delta: { role: "assistant", content: "" },
finish_reason: null,
},
],
})}\n\n`
);
}
if (toolCallsMap.has(tc.id)) {
// Accumulate arguments for existing tool call
const existing = toolCallsMap.get(tc.id);
const oldArgsLen = existing.function.arguments.length;
existing.function.arguments += tc.function.arguments;
existing.isLast = tc.isLast;
// Stream the delta arguments
if (tc.function.arguments) {
emittedToolCallIds.add(tc.id);
chunks.push(
`data: ${JSON.stringify({
id: responseId,
object: "chat.completion.chunk",
created,
model,
choices: [
{
index: 0,
delta: {
tool_calls: [
{
index: existing.index,
id: tc.id,
type: "function",
function: {
name: tc.function.name,
arguments: tc.function.arguments,
},
},
],
},
finish_reason: null,
},
],
})}\n\n`
);
}
} else {
// New tool call - assign index and add to map
const toolCallIndex = toolCalls.length;
finalizedIds.add(tc.id);
toolCalls.push({ ...tc, index: toolCallIndex });
toolCallsMap.set(tc.id, { ...tc, index: toolCallIndex });
// Stream initial tool call with name
emittedToolCallIds.add(tc.id);
chunks.push(
`data: ${JSON.stringify({
id: responseId,
object: "chat.completion.chunk",
created,
model,
choices: [
{
index: 0,
delta: {
tool_calls: [
{
index: toolCallIndex,
id: tc.id,
type: "function",
function: {
name: tc.function.name,
arguments: tc.function.arguments,
},
},
],
},
finish_reason: null,
},
],
})}\n\n`
);
}
}
if (result.text) {
totalContent += result.text;
chunks.push(
`data: ${JSON.stringify({
id: responseId,
object: "chat.completion.chunk",
created,
model,
choices: [
{
index: 0,
delta:
chunks.length === 0 && toolCalls.length === 0
? { role: "assistant", content: result.text }
: { content: result.text },
finish_reason: null,
},
],
})}\n\n`
);
}
}
debugLog(
`[CURSOR BUFFER SSE] Parsed ${frameCount} frames, toolCallsMap size: ${toolCallsMap.size}, toolCalls array: ${toolCalls.length}`
);
// Finalize all remaining tool calls in map (stream may have ended without isLast=true)
for (const [id, tc] of toolCallsMap.entries()) {
if (!finalizedIds.has(id)) {
debugLog(`[CURSOR BUFFER SSE] Finalizing incomplete tool call: ${id}, isLast=${tc.isLast}`);
const toolCallIndex = toolCalls.length;
toolCalls.push({
id: tc.id,
type: tc.type,
index: toolCallIndex,
function: {
name: tc.function.name,
arguments: tc.function.arguments,
},
});
// Emit SSE chunk for the finalized tool call if not already emitted
if (!emittedToolCallIds.has(tc.id)) {
chunks.push(
`data: ${JSON.stringify({
id: responseId,
object: "chat.completion.chunk",
created,
model,
choices: [
{
index: 0,
delta: {
tool_calls: [
{
index: toolCallIndex,
id: tc.id,
type: "function",
function: {
name: tc.function.name,
arguments: tc.function.arguments,
},
},
],
},
finish_reason: null,
},
],
})}\n\n`
);
}
}
}
if (chunks.length === 0 && toolCalls.length === 0) {
chunks.push(
`data: ${JSON.stringify({
id: responseId,
object: "chat.completion.chunk",
created,
model,
choices: [
{
index: 0,
delta: { role: "assistant", content: "" },
finish_reason: null,
},
],
})}\n\n`
);
}
const usage = estimateUsage(body, totalContent.length, FORMATS.OPENAI);
chunks.push(
`data: ${JSON.stringify({
id: responseId,
object: "chat.completion.chunk",
created,
model,
choices: [
{
index: 0,
delta: {},
finish_reason: toolCalls.length > 0 ? "tool_calls" : "stop",
},
],
usage,
})}\n\n`
);
chunks.push("data: [DONE]\n\n");
return new Response(chunks.join(""), {
status: 200,
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
});
}
async refreshCredentials() {
return null;
}
}
export default CursorExecutor;