Files
ccs/src/cursor/cursor-executor.ts
T
Tam Nhu Tran 1ff4329ee4 feat(providers): instrument copilot, cursor, and glmt across daemons and executors
Provider modules now emit structured stage tags around daemon spawn /
ready / stop, executor invocations, and upstream dispatch. glmt-transformer
gets cleanup-stage error conversion; legacy glmt-proxy adds minimal
listen + retry instrumentation (full per-request stages live in proxy-server
since glmt-proxy is compat-only).

Refs #1141, #1138
2026-04-30 13:00:33 -04:00

1235 lines
35 KiB
TypeScript

/**
* Cursor Executor
* Handles HTTP/2 requests to Cursor API with protobuf encoding/decoding
*/
import type { IncomingHttpHeaders } from 'http';
import { generateCursorBody, extractTextFromResponse } from './cursor-protobuf.js';
import { buildCursorRequest } from './cursor-translator.js';
import {
isEndStreamConnectFrame,
type CursorTool,
type CursorApiCredentials,
} from './cursor-protobuf-schema.js';
import { buildCursorConnectHeaders, generateCursorChecksum } from './cursor-client-policy.js';
import { createLogger } from '../services/logging';
const logger = createLogger('cursor:executor');
import {
CursorConnectFrameError,
type FrameResult,
StreamingFrameParser,
decompressPayload,
mapCursorConnectError,
} from './cursor-stream-parser.js';
/** Executor parameters */
interface ExecutorParams {
model: string;
body: {
messages: Array<{
role: string;
content: string | Array<{ type: string; text?: string }>;
name?: string;
tool_call_id?: string;
tool_calls?: Array<{
id: string;
type: string;
function: { name: string; arguments: string };
}>;
}>;
tools?: CursorTool[];
reasoning_effort?: string;
};
stream: boolean;
credentials: CursorApiCredentials;
signal?: AbortSignal;
}
/** HTTP/2 response structure */
interface Http2Response {
status: number;
headers: IncomingHttpHeaders;
body: Buffer;
}
interface CursorExecutorErrorPayload {
message: string;
status: number;
errorType: string;
code: string;
}
/** Lazy import http2 */
let http2Module: typeof import('http2') | null = null;
async function getHttp2() {
if (http2Module) return http2Module;
try {
http2Module = await import('http2');
return http2Module;
} catch (err) {
if (process.env.CCS_DEBUG) {
console.error('[cursor] http2 module not available, falling back to fetch:', err);
}
return null;
}
}
/**
* Create error response from JSON error
*/
function toCursorErrorPayloadFromJson(jsonError: {
error?: {
code?: string;
message?: string;
details?: Array<{ debug?: { details?: { title?: string; detail?: string }; error?: string } }>;
};
}): CursorExecutorErrorPayload {
const errorMsg =
jsonError?.error?.details?.[0]?.debug?.details?.title ||
jsonError?.error?.details?.[0]?.debug?.details?.detail ||
jsonError?.error?.message ||
'API Error';
const mappedError = mapCursorConnectError(jsonError?.error?.code);
return {
message: errorMsg,
status: mappedError.status,
errorType: mappedError.errorType,
code: jsonError?.error?.details?.[0]?.debug?.error || 'unknown',
};
}
function buildCursorErrorEnvelope(error: CursorExecutorErrorPayload): string {
return JSON.stringify({
error: {
message: error.message,
type: error.errorType,
code: error.code,
status: error.status,
},
});
}
function createCursorErrorResponse(error: CursorExecutorErrorPayload): Response {
return new Response(buildCursorErrorEnvelope(error), {
status: error.status,
headers: { 'Content-Type': 'application/json' },
});
}
function toCursorExecutorErrorPayload(error: unknown): CursorExecutorErrorPayload {
if (error instanceof CursorConnectFrameError) {
return {
message: error.message,
status: error.status,
errorType: error.errorType,
code: 'cursor_protocol_error',
};
}
return {
message: error instanceof Error ? error.message : 'Cursor streaming failed.',
status: 502,
errorType: 'server_error',
code: 'cursor_error',
};
}
export class CursorExecutor {
private readonly baseUrl = 'https://api2.cursor.sh';
private readonly chatPath = '/aiserver.v1.AiService/StreamChat';
buildUrl(): string {
return `${this.baseUrl}${this.chatPath}`;
}
/**
* Generate checksum using Jyh cipher (time-based XOR with rolling key seed=165)
*/
generateChecksum(machineId: string): string {
return generateCursorChecksum(machineId);
}
buildHeaders(credentials: CursorApiCredentials): Record<string, string> {
return buildCursorConnectHeaders(credentials);
}
transformRequest(
model: string,
body: ExecutorParams['body'],
stream: boolean,
credentials: CursorApiCredentials
): Uint8Array {
const translatedBody = buildCursorRequest(model, body, stream, credentials);
const messages = translatedBody.messages || [];
const tools = (translatedBody.tools || body.tools || []) as CursorTool[];
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<Http2Response> {
const response = await fetch(url, {
method: 'POST',
headers,
body,
signal,
});
const responseHeaders: Record<string, string> = {};
response.headers.forEach((value, key) => {
responseHeaders[key] = value;
});
return {
status: response.status,
headers: responseHeaders,
body: Buffer.from(await response.arrayBuffer()),
};
}
async makeHttp2Request(
url: string,
headers: Record<string, string>,
body: Uint8Array,
signal?: AbortSignal
): Promise<Http2Response> {
const http2 = await getHttp2();
if (!http2) {
throw new Error('http2 module not available');
}
return new Promise((resolve, reject) => {
const urlObj = new URL(url);
const client = http2.connect(`https://${urlObj.host}`);
const chunks: Buffer[] = [];
let responseHeaders: IncomingHttpHeaders = {};
client.on('error', (err) => {
client.close();
reject(err);
});
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: Buffer) => {
chunks.push(chunk);
});
req.on('end', () => {
client.close();
resolve({
status: Number(responseHeaders[':status']) || 500,
headers: responseHeaders,
body: Buffer.concat(chunks),
});
});
req.on('error', (err) => {
client.close();
reject(err);
});
if (signal) {
const onAbort = () => {
req.close();
client.close();
reject(new Error('Request aborted'));
};
signal.addEventListener('abort', onAbort, { once: true });
const cleanup = () => {
signal.removeEventListener('abort', onAbort);
};
req.on('end', cleanup);
req.on('error', cleanup);
}
req.write(body);
req.end();
});
}
async execute(params: ExecutorParams): Promise<{
response: Response;
url: string;
headers: Record<string, string>;
transformedBody: ExecutorParams['body'];
}> {
const { model, body, stream, credentials, signal } = params;
const url = this.buildUrl();
const headers = this.buildHeaders(credentials);
const transformedBody = this.transformRequest(model, body, stream, credentials);
const startedAt = Date.now();
logger.stage('upstream', 'cursor.upstream.request', 'Sending Cursor upstream request', {
provider: 'cursor',
model,
stream,
});
try {
// Streaming requests use incremental HTTP/2 → SSE pipeline
if (stream) {
const response = await this.executeStreaming(
url,
headers,
transformedBody,
model,
body,
signal
);
return { response, url, headers, transformedBody: body };
}
// Non-streaming: buffer entire response then transform to JSON
const http2 = await getHttp2();
const response = 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';
logger.stage(
'cleanup',
'cursor.upstream.error',
'Cursor upstream returned non-200 status',
{ provider: 'cursor', model, status: response.status },
{ level: 'error', latencyMs: Date.now() - startedAt }
);
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 = this.transformProtobufToJSON(response.body, model, body);
logger.stage(
'transform',
'cursor.upstream.transformed',
'Cursor response transformed',
{ provider: 'cursor', model },
{ latencyMs: Date.now() - startedAt }
);
return { response: transformedResponse, url, headers, transformedBody: body };
} catch (error) {
const err = error as Error;
logger.stage(
'cleanup',
'cursor.upstream.exception',
'Cursor upstream request threw',
{ provider: 'cursor', model },
{
level: 'error',
latencyMs: Date.now() - startedAt,
error: { name: err.name, message: err.message },
}
);
const errorResponse = new Response(
JSON.stringify({
error: {
message: err.message,
type: 'connection_error',
code: '',
},
}),
{
status: 500,
headers: { 'Content-Type': 'application/json' },
}
);
return { response: errorResponse, url, headers, transformedBody: body };
}
}
/**
* Execute a streaming request, piping HTTP/2 data events through
* StreamingFrameParser for incremental SSE output.
* Falls back to buffered transformProtobufToSSE when http2 is unavailable.
*/
private async executeStreaming(
url: string,
headers: Record<string, string>,
body: Uint8Array,
model: string,
requestBody: ExecutorParams['body'],
signal?: AbortSignal
): Promise<Response> {
const http2 = await getHttp2();
if (!http2) {
const response = await this.makeFetchRequest(url, headers, body, signal);
if (response.status !== 200) {
const errorText = response.body?.toString() || 'Unknown error';
return new Response(
JSON.stringify({
error: {
message: `[${response.status}]: ${errorText}`,
type: response.status === 429 ? 'rate_limit_error' : 'invalid_request_error',
code: '',
},
}),
{ status: response.status, headers: { 'Content-Type': 'application/json' } }
);
}
return this.transformProtobufToSSE(response.body, model, requestBody);
}
const responseId = `chatcmpl-cursor-${Date.now()}`;
const created = Math.floor(Date.now() / 1000);
return new Promise((resolve, reject) => {
let settled = false;
const resolveOnce = (response: Response) => {
if (settled) return;
settled = true;
resolve(response);
};
const rejectOnce = (error: Error) => {
if (settled) return;
settled = true;
reject(error);
};
const urlObj = new URL(url);
const client = http2.connect(`https://${urlObj.host}`);
client.on('error', (err) => {
client.close();
rejectOnce(err instanceof Error ? err : new Error(String(err)));
});
const req = client.request({
':method': 'POST',
':path': urlObj.pathname,
':authority': urlObj.host,
':scheme': 'https',
...headers,
});
// Register abort before response headers arrive so cancellation works at all stages
let streamClosed = false;
let streamController: ReadableStreamDefaultController<Uint8Array> | null = null;
if (signal) {
const onAbort = () => {
streamClosed = true;
// If stream already started, close readable to unblock consumers.
if (streamController) {
try {
streamController.close();
} catch {
/* already closed */
}
}
req.close();
client.close();
rejectOnce(new Error('Request aborted'));
};
signal.addEventListener('abort', onAbort, { once: true });
const cleanup = () => signal.removeEventListener('abort', onAbort);
req.on('end', cleanup);
req.on('error', cleanup);
}
req.on('response', (hdrs) => {
const status = Number(hdrs[':status']) || 500;
if (status !== 200) {
const errorChunks: Buffer[] = [];
req.on('data', (c: Buffer) => errorChunks.push(c));
req.on('end', () => {
client.close();
const errorText = Buffer.concat(errorChunks).toString();
resolveOnce(
new Response(
JSON.stringify({
error: {
message: `[${status}]: ${errorText}`,
type: status === 429 ? 'rate_limit_error' : 'invalid_request_error',
code: '',
},
}),
{ status, headers: { 'Content-Type': 'application/json' } }
)
);
});
return;
}
// Status 200: set up incremental streaming pipeline
const parser = new StreamingFrameParser();
const enc = new TextEncoder();
const toolCallsMap = new Map<
string,
{
id: string;
type: string;
function: { name: string; arguments: string };
isLast: boolean;
index: number;
}
>();
const pendingPackets: string[] = [];
let chunkCount = 0;
let toolCallCount = 0;
let streamResponseResolved = false;
const flushPendingPackets = () => {
if (!streamController || pendingPackets.length === 0 || streamClosed) return;
for (const packet of pendingPackets.splice(0)) {
streamController.enqueue(enc.encode(packet));
}
};
const queuePacket = (packet: string) => {
if (streamClosed) return;
if (streamController) {
streamController.enqueue(enc.encode(packet));
return;
}
pendingPackets.push(packet);
};
const emitSSE = (data: string) => {
queuePacket(`data: ${data}\n\n`);
};
const emitSSEEvent = (event: string, data: string) => {
queuePacket(`event: ${event}\ndata: ${data}\n\n`);
};
const readable = new ReadableStream<Uint8Array>({
start(controller) {
streamController = controller;
flushPendingPackets();
},
});
const resolveStreamingResponse = () => {
if (streamResponseResolved || settled) return;
streamResponseResolved = true;
resolveOnce(
new Response(readable, {
status: 200,
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
},
})
);
flushPendingPackets();
};
const closeStream = () => {
if (streamClosed) return;
streamClosed = true;
if (streamController) {
try {
streamController.close();
} catch {
/* already closed */
}
}
client.close();
};
const buildChunk = (delta: Record<string, unknown>, finishReason: string | null) =>
JSON.stringify({
id: responseId,
object: 'chat.completion.chunk',
created,
model,
choices: [{ index: 0, delta, finish_reason: finishReason }],
});
const handleFrameError = (frame: Extract<FrameResult, { type: 'error' }>) => {
const errorPayload = buildCursorErrorEnvelope({
message: frame.message,
status: frame.status,
errorType: frame.errorType,
code: frame.errorType === 'rate_limit_error' ? 'rate_limited' : 'cursor_error',
});
if (!streamResponseResolved && chunkCount === 0 && toolCallCount === 0) {
streamClosed = true;
req.close();
client.close();
resolveOnce(
new Response(errorPayload, {
status: frame.status,
headers: { 'Content-Type': 'application/json' },
})
);
return;
}
resolveStreamingResponse();
emitSSEEvent('error', errorPayload);
closeStream();
};
req.on('data', (chunk: Buffer) => {
if (streamClosed) return;
for (const frame of parser.push(chunk)) {
if (frame.type === 'error') {
handleFrameError(frame);
return;
}
resolveStreamingResponse();
if (frame.type === 'toolCall') {
const tc = frame.toolCall;
// Emit role chunk on first content
if (chunkCount === 0) {
emitSSE(buildChunk({ role: 'assistant', content: '' }, null));
chunkCount++;
}
if (toolCallsMap.has(tc.id)) {
const existing = toolCallsMap.get(tc.id);
if (!existing) continue;
existing.function.arguments += tc.function.arguments;
existing.isLast = tc.isLast;
if (tc.function.arguments) {
emitSSE(
buildChunk(
{
tool_calls: [
{
index: existing.index,
id: tc.id,
type: 'function',
function: {
name: tc.function.name,
arguments: tc.function.arguments,
},
},
],
},
null
)
);
chunkCount++;
}
} else {
const idx = toolCallCount++;
toolCallsMap.set(tc.id, { ...tc, index: idx });
emitSSE(
buildChunk(
{
tool_calls: [
{
index: idx,
id: tc.id,
type: 'function',
function: {
name: tc.function.name,
arguments: tc.function.arguments,
},
},
],
},
null
)
);
chunkCount++;
}
}
if (frame.type === 'text') {
const delta =
chunkCount === 0 && toolCallCount === 0
? { role: 'assistant', content: frame.text }
: { content: frame.text };
emitSSE(buildChunk(delta, null));
chunkCount++;
}
if (frame.type === 'thinking') {
const delta =
chunkCount === 0 && toolCallCount === 0
? { role: 'assistant', reasoning_content: frame.text }
: { reasoning_content: frame.text };
emitSSE(buildChunk(delta, null));
chunkCount++;
}
}
});
req.on('end', () => {
if (streamClosed) return;
for (const frame of parser.finish()) {
if (frame.type === 'error') {
handleFrameError(frame);
return;
}
}
resolveStreamingResponse();
if (chunkCount === 0 && toolCallCount === 0) {
emitSSE(buildChunk({ role: 'assistant', content: '' }, null));
}
emitSSE(
JSON.stringify({
id: responseId,
object: 'chat.completion.chunk',
created,
model,
choices: [
{
index: 0,
delta: {},
finish_reason: toolCallCount > 0 ? 'tool_calls' : 'stop',
},
],
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
})
);
emitSSE('[DONE]');
closeStream();
});
req.on('error', (err) => {
client.close();
if (!streamResponseResolved) {
rejectOnce(err instanceof Error ? err : new Error(String(err)));
return;
}
if (!streamClosed) {
try {
streamController?.error(err);
} catch {
/* already closed */
}
}
});
});
req.on('error', (err) => {
client.close();
rejectOnce(err instanceof Error ? err : new Error(String(err)));
});
req.write(body);
req.end();
});
}
/**
* Parse protobuf buffer into frames and extract text/toolcalls.
* Shared logic between JSON and SSE transformers.
*/
private *parseProtobufFrames(buffer: Buffer): Generator<
| { type: 'error'; error: CursorExecutorErrorPayload }
| { type: 'text'; text: string }
| { type: 'thinking'; text: string }
| {
type: 'toolCall';
toolCall: {
id: string;
type: string;
function: { name: string; arguments: string };
isLast: boolean;
};
}
> {
let offset = 0;
while (offset < buffer.length) {
if (offset + 5 > buffer.length) break;
const flags = buffer[offset];
const length = buffer.readUInt32BE(offset + 1);
if (offset + 5 + length > buffer.length) break;
let payload = buffer.slice(offset + 5, offset + 5 + length);
offset += 5 + length;
try {
payload = decompressPayload(payload, flags);
} catch (error) {
yield { type: 'error', error: toCursorExecutorErrorPayload(error) };
return;
}
if (isEndStreamConnectFrame(flags)) {
try {
const json = JSON.parse(payload.toString('utf-8')) as {
error?: {
code?: string;
message?: string;
details?: Array<{
debug?: { details?: { title?: string; detail?: string }; error?: string };
}>;
};
};
const msg =
json?.error?.details?.[0]?.debug?.details?.title ||
json?.error?.details?.[0]?.debug?.details?.detail ||
json?.error?.message;
if (msg) {
const mappedError = mapCursorConnectError(json?.error?.code);
yield {
type: 'error',
error: {
message: msg,
status: mappedError.status,
errorType: mappedError.errorType,
code: json?.error?.code || 'cursor_error',
},
};
return;
}
} catch {
// Ignore successful end-stream metadata trailers.
}
continue;
}
// Check for JSON error format
try {
const text = payload.toString('utf-8');
if (text.startsWith('{') && text.includes('"error"')) {
yield { type: 'error', error: toCursorErrorPayloadFromJson(JSON.parse(text)) };
return;
}
} catch (err) {
if (process.env.CCS_DEBUG) {
console.error('[cursor] parseProtobufFrames error parsing failed:', err);
}
}
const result = extractTextFromResponse(new Uint8Array(payload));
// Check for protobuf-decoded error
if (result.error) {
const errorLower = result.error.toLowerCase();
const isRateLimit =
errorLower.includes('rate limit') ||
errorLower.includes('resource_exhausted') ||
errorLower.includes('too many requests');
yield {
type: 'error',
error: {
message: result.error,
status: isRateLimit ? 429 : 502,
errorType: isRateLimit ? 'rate_limit_error' : 'server_error',
code: isRateLimit ? 'rate_limited' : 'cursor_error',
},
};
return;
}
if (result.toolCall) {
yield { type: 'toolCall', toolCall: result.toolCall };
}
if (result.text) {
yield { type: 'text', text: result.text };
}
if (result.thinking) {
yield { type: 'thinking', text: result.thinking };
}
}
if (offset !== buffer.length) {
yield {
type: 'error',
error: {
message: 'Truncated Cursor ConnectRPC frame.',
status: 502,
errorType: 'server_error',
code: 'cursor_protocol_error',
},
};
}
}
transformProtobufToJSON(buffer: Buffer, model: string, _body: ExecutorParams['body']): Response {
const responseId = `chatcmpl-cursor-${Date.now()}`;
const created = Math.floor(Date.now() / 1000);
let totalContent = '';
let totalReasoning = '';
const toolCalls: Array<{
id: string;
type: string;
function: { name: string; arguments: string };
}> = [];
const toolCallsMap = new Map<
string,
{
id: string;
type: string;
function: { name: string; arguments: string };
isLast: boolean;
index: number;
}
>();
for (const frame of this.parseProtobufFrames(buffer)) {
if (frame.type === 'error') {
return createCursorErrorResponse(frame.error);
}
if (frame.type === 'toolCall') {
const tc = frame.toolCall;
if (toolCallsMap.has(tc.id)) {
const existing = toolCallsMap.get(tc.id);
if (!existing) continue;
existing.function.arguments += tc.function.arguments;
existing.isLast = tc.isLast;
} else {
toolCallsMap.set(tc.id, {
...tc,
index: toolCallsMap.size,
});
}
if (tc.isLast) {
const finalToolCall = toolCallsMap.get(tc.id);
if (!finalToolCall) continue;
toolCalls.push({
id: finalToolCall.id,
type: finalToolCall.type,
function: {
name: finalToolCall.function.name,
arguments: finalToolCall.function.arguments,
},
});
}
}
if (frame.type === 'text') {
totalContent += frame.text;
}
if (frame.type === 'thinking') {
totalReasoning += frame.text;
}
}
// Finalize remaining tool calls
for (const id of Array.from(toolCallsMap.keys())) {
const tc = toolCallsMap.get(id);
if (!tc) continue;
if (!toolCalls.find((t) => t.id === id)) {
toolCalls.push({
id: tc.id,
type: tc.type,
function: {
name: tc.function.name,
arguments: tc.function.arguments,
},
});
}
}
const message: {
role: string;
content: string | null;
reasoning_content?: string | null;
tool_calls?: Array<{
id: string;
type: string;
function: { name: string; arguments: string };
}>;
} = {
role: 'assistant',
content: totalContent || null,
};
if (toolCalls.length > 0) {
message.tool_calls = toolCalls;
}
if (totalReasoning) {
message.reasoning_content = totalReasoning;
}
const completion = {
id: responseId,
object: 'chat.completion',
created,
model,
choices: [
{
index: 0,
message,
finish_reason: toolCalls.length > 0 ? 'tool_calls' : 'stop',
},
],
usage: {
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
},
};
return new Response(JSON.stringify(completion), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
/** @deprecated Use executeStreaming() for real-time SSE. Retained for fetch fallback. */
transformProtobufToSSE(buffer: Buffer, model: string, _body: ExecutorParams['body']): Response {
const responseId = `chatcmpl-cursor-${Date.now()}`;
const created = Math.floor(Date.now() / 1000);
const chunks: string[] = [];
const toolCalls: Array<{
id: string;
type: string;
function: { name: string; arguments: string };
index: number;
}> = [];
const toolCallsMap = new Map<
string,
{
id: string;
type: string;
function: { name: string; arguments: string };
isLast: boolean;
index: number;
}
>();
for (const frame of this.parseProtobufFrames(buffer)) {
if (frame.type === 'error') {
if (chunks.length === 0 && toolCalls.length === 0) {
return createCursorErrorResponse(frame.error);
}
chunks.push(`event: error\ndata: ${buildCursorErrorEnvelope(frame.error)}\n\n`);
return new Response(chunks.join(''), {
status: 200,
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
},
});
}
if (frame.type === 'toolCall') {
const tc = frame.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)) {
const existing = toolCallsMap.get(tc.id);
if (!existing) continue;
existing.function.arguments += tc.function.arguments;
existing.isLast = tc.isLast;
if (tc.function.arguments) {
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 {
const toolCallIndex = toolCalls.length;
toolCalls.push({ ...tc, index: toolCallIndex });
toolCallsMap.set(tc.id, { ...tc, index: toolCallIndex });
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 (frame.type === '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: frame.text }
: { content: frame.text },
finish_reason: null,
},
],
})}\n\n`
);
}
if (frame.type === 'thinking') {
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', reasoning_content: frame.text }
: { reasoning_content: frame.text },
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`
);
}
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: {
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
},
})}\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',
},
});
}
}
export default CursorExecutor;