feat(executors): Improved UI components for displaying provider limits and usage statistics in the dashboard.

This commit is contained in:
decolua
2026-02-05 18:38:50 +07:00
parent 249fc28c49
commit 32aefe5a76
21 changed files with 1112 additions and 310 deletions
+254 -107
View File
@@ -5,6 +5,9 @@ import {
parseConnectRPCFrame,
extractTextFromResponse
} from "../utils/cursorProtobuf.js";
import { estimateUsage } from "../utils/usageTracking.js";
import { FORMATS } from "../translator/formats.js";
import { buildCursorRequest } from "../translator/request/openai-to-cursor.js";
import crypto from "crypto";
import { v5 as uuidv5 } from "uuid";
import zlib from "zlib";
@@ -34,11 +37,26 @@ const COMPRESS_FLAG = {
};
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"')) {
console.log(`[DECOMPRESS] Detected JSON error, skipping decompression`);
return payload;
}
} catch {}
}
if (flags === COMPRESS_FLAG.GZIP || flags === COMPRESS_FLAG.GZIP_ALT || flags === COMPRESS_FLAG.GZIP_BOTH) {
try {
return zlib.gunzipSync(payload);
} catch {
return null;
} catch (err) {
console.log(`[DECOMPRESS ERROR] flags=${flags}, payloadSize=${payload.length}, error=${err.message}`);
console.log(`[DECOMPRESS ERROR] First 50 bytes (hex):`, payload.slice(0, 50).toString('hex'));
console.log(`[DECOMPRESS ERROR] First 50 bytes (utf8):`, payload.slice(0, 50).toString('utf8').replace(/[^\x20-\x7E]/g, '.'));
// Try to use payload as-is if decompression fails
return payload;
}
}
return payload;
@@ -147,8 +165,10 @@ export class CursorExecutor extends BaseExecutor {
}
transformRequest(model, body, stream, credentials) {
const messages = body.messages || [];
const tools = body.tools || [];
// Call translator to convert OpenAI format to Cursor format
const translatedBody = buildCursorRequest(model, body, stream, credentials);
const messages = translatedBody.messages || [];
const tools = translatedBody.tools || body.tools || [];
const reasoningEffort = body.reasoning_effort || null;
return generateCursorBody(messages, model, tools, reasoningEffort);
}
@@ -243,8 +263,8 @@ export class CursorExecutor extends BaseExecutor {
}
const transformedResponse = stream !== false
? this.transformProtobufToSSE(response.body, model)
: this.transformProtobufToJSON(response.body, model);
? this.transformProtobufToSSE(response.body, model, body)
: this.transformProtobufToJSON(response.body, model, body);
return { response: transformedResponse, url, headers, transformedBody: body };
} catch (error) {
@@ -262,108 +282,43 @@ export class CursorExecutor extends BaseExecutor {
}
}
transformProtobufToJSON(buffer, model) {
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
let frameCount = 0;
console.log(`[CURSOR BUFFER] Total length: ${buffer.length} bytes`);
while (offset < buffer.length) {
if (offset + 5 > buffer.length) break;
if (offset + 5 > buffer.length) {
console.log(`[CURSOR BUFFER] Reached end, offset=${offset}, remaining=${buffer.length - offset}`);
break;
}
const flags = buffer[offset];
const length = buffer.readUInt32BE(offset + 1);
if (offset + 5 + length > buffer.length) break;
console.log(`[CURSOR BUFFER] Frame ${frameCount + 1}: flags=0x${flags.toString(16).padStart(2, '0')}, length=${length}`);
if (offset + 5 + length > buffer.length) {
console.log(`[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) continue;
try {
const text = payload.toString("utf-8");
if (text.startsWith("{") && text.includes('"error"')) {
return createErrorResponse(JSON.parse(text));
}
} catch {}
const result = extractTextFromResponse(new Uint8Array(payload));
if (result.error) {
return new Response(JSON.stringify({
error: {
message: result.error,
type: "rate_limit_error",
code: "rate_limited"
}
}), {
status: 429,
headers: { "Content-Type": "application/json" }
});
if (!payload) {
console.log(`[CURSOR BUFFER] Frame ${frameCount}: decompression failed, skipping`);
continue;
}
if (result.toolCall) toolCalls.push(result.toolCall);
if (result.text) totalContent += result.text;
}
const message = {
role: "assistant",
content: totalContent || null
};
if (toolCalls.length > 0) {
message.tool_calls = toolCalls;
}
const completion = {
id: responseId,
object: "chat.completion",
created,
model,
choices: [{
index: 0,
message,
finish_reason: toolCalls.length > 0 ? "tool_calls" : "stop"
}],
usage: {
prompt_tokens: 10,
completion_tokens: Math.max(1, Math.floor(totalContent.length / 4)),
total_tokens: 10 + Math.max(1, Math.floor(totalContent.length / 4))
}
};
return new Response(JSON.stringify(completion), {
status: 200,
headers: { "Content-Type": "application/json" }
});
}
transformProtobufToSSE(buffer, model) {
const responseId = `chatcmpl-cursor-${Date.now()}`;
const created = Math.floor(Date.now() / 1000);
const chunks = [];
let offset = 0;
let totalContent = "";
const toolCalls = [];
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;
payload = decompressPayload(payload, flags);
if (!payload) continue;
try {
const text = payload.toString("utf-8");
@@ -373,6 +328,7 @@ export class CursorExecutor extends BaseExecutor {
} catch {}
const result = extractTextFromResponse(new Uint8Array(payload));
console.log(`[CURSOR DECODED] Frame ${frameCount}:`, result);
if (result.error) {
return new Response(JSON.stringify({
@@ -388,7 +344,149 @@ export class CursorExecutor extends BaseExecutor {
}
if (result.toolCall) {
toolCalls.push(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);
toolCalls.push({
id: finalToolCall.id,
type: finalToolCall.type,
function: {
name: finalToolCall.function.name,
arguments: finalToolCall.function.arguments
}
});
}
}
if (result.text) totalContent += result.text;
}
console.log(`[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 (!toolCalls.find(t => t.id === id)) {
console.log(`[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
}
});
}
}
console.log(`[CURSOR BUFFER] Final toolCalls count: ${toolCalls.length}`);
const message = {
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
let frameCount = 0;
console.log(`[CURSOR BUFFER SSE] Total length: ${buffer.length} bytes`);
while (offset < buffer.length) {
if (offset + 5 > buffer.length) {
console.log(`[CURSOR BUFFER SSE] Reached end, offset=${offset}, remaining=${buffer.length - offset}`);
break;
}
const flags = buffer[offset];
const length = buffer.readUInt32BE(offset + 1);
console.log(`[CURSOR BUFFER SSE] Frame ${frameCount + 1}: flags=0x${flags.toString(16).padStart(2, '0')}, length=${length}`);
if (offset + 5 + length > buffer.length) {
console.log(`[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) {
console.log(`[CURSOR BUFFER SSE] Frame ${frameCount}: decompression failed, skipping`);
continue;
}
try {
const text = payload.toString("utf-8");
if (text.startsWith("{") && text.includes('"error"')) {
return createErrorResponse(JSON.parse(text));
}
} catch {}
const result = extractTextFromResponse(new Uint8Array(payload));
console.log(`[CURSOR DECODED SSE] Frame ${frameCount}:`, result);
if (result.error) {
return new Response(JSON.stringify({
error: {
message: result.error,
type: "rate_limit_error",
code: "rate_limited"
}
}), {
status: 429,
headers: { "Content-Type": "application/json" }
});
}
if (result.toolCall) {
const tc = result.toolCall;
if (chunks.length === 0) {
chunks.push(`data: ${JSON.stringify({
@@ -404,17 +502,66 @@ export class CursorExecutor extends BaseExecutor {
})}\n\n`);
}
chunks.push(`data: ${JSON.stringify({
id: responseId,
object: "chat.completion.chunk",
created,
model,
choices: [{
index: 0,
delta: { tool_calls: [{ index: toolCalls.length - 1, ...result.toolCall }] },
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) {
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;
toolCalls.push({ ...tc, index: toolCallIndex });
toolCallsMap.set(tc.id, { ...tc, index: toolCallIndex });
// Stream initial tool call with name
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) {
@@ -435,6 +582,8 @@ export class CursorExecutor extends BaseExecutor {
}
}
console.log(`[CURSOR BUFFER SSE] Parsed ${frameCount} frames, toolCallsMap size: ${toolCallsMap.size}, toolCalls array: ${toolCalls.length}`);
if (chunks.length === 0 && toolCalls.length === 0) {
chunks.push(`data: ${JSON.stringify({
id: responseId,
@@ -449,6 +598,8 @@ export class CursorExecutor extends BaseExecutor {
})}\n\n`);
}
const usage = estimateUsage(body, totalContent.length, FORMATS.OPENAI);
chunks.push(`data: ${JSON.stringify({
id: responseId,
object: "chat.completion.chunk",
@@ -459,11 +610,7 @@ export class CursorExecutor extends BaseExecutor {
delta: {},
finish_reason: toolCalls.length > 0 ? "tool_calls" : "stop"
}],
usage: {
prompt_tokens: 0,
completion_tokens: Math.max(1, Math.floor(totalContent.length / 4)),
total_tokens: Math.max(1, Math.floor(totalContent.length / 4))
}
usage
})}\n\n`);
chunks.push("data: [DONE]\n\n");