mirror of
https://github.com/tiennm99/ccs.git
synced 2026-08-17 08:23:34 +00:00
fix(cursor): fix test isolation and daemon exit handling
- Convert PID_FILE constant to getPidFilePath() function to respect CCS_HOME changes at runtime
- Add proc.on('exit') handler to clear interval on silent process crashes
- Ensures test isolation by computing paths dynamically
This commit is contained in:
@@ -25,7 +25,13 @@ function getCursorDir(): string {
|
|||||||
return path.join(getCcsDir(), 'cursor');
|
return path.join(getCcsDir(), 'cursor');
|
||||||
}
|
}
|
||||||
|
|
||||||
const PID_FILE = path.join(getCursorDir(), 'daemon.pid');
|
/**
|
||||||
|
* Get PID file path.
|
||||||
|
* Computed at runtime to respect CCS_HOME changes (e.g., in tests).
|
||||||
|
*/
|
||||||
|
function getPidFilePath(): string {
|
||||||
|
return path.join(getCursorDir(), 'daemon.pid');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if cursor daemon is running on the specified port.
|
* Check if cursor daemon is running on the specified port.
|
||||||
@@ -77,9 +83,10 @@ export async function getDaemonStatus(port: number): Promise<CursorDaemonStatus>
|
|||||||
* Read PID from file.
|
* Read PID from file.
|
||||||
*/
|
*/
|
||||||
function getPidFromFile(): number | null {
|
function getPidFromFile(): number | null {
|
||||||
|
const pidFile = getPidFilePath();
|
||||||
try {
|
try {
|
||||||
if (fs.existsSync(PID_FILE)) {
|
if (fs.existsSync(pidFile)) {
|
||||||
const content = fs.readFileSync(PID_FILE, 'utf8').trim();
|
const content = fs.readFileSync(pidFile, 'utf8').trim();
|
||||||
const pid = parseInt(content, 10);
|
const pid = parseInt(content, 10);
|
||||||
return isNaN(pid) ? null : pid;
|
return isNaN(pid) ? null : pid;
|
||||||
}
|
}
|
||||||
@@ -93,12 +100,13 @@ function getPidFromFile(): number | null {
|
|||||||
* Write PID to file.
|
* Write PID to file.
|
||||||
*/
|
*/
|
||||||
function writePidToFile(pid: number): void {
|
function writePidToFile(pid: number): void {
|
||||||
|
const pidFile = getPidFilePath();
|
||||||
try {
|
try {
|
||||||
const dir = path.dirname(PID_FILE);
|
const dir = path.dirname(pidFile);
|
||||||
if (!fs.existsSync(dir)) {
|
if (!fs.existsSync(dir)) {
|
||||||
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
||||||
}
|
}
|
||||||
fs.writeFileSync(PID_FILE, pid.toString(), { mode: 0o600 });
|
fs.writeFileSync(pidFile, pid.toString(), { mode: 0o600 });
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore errors
|
// Ignore errors
|
||||||
}
|
}
|
||||||
@@ -108,9 +116,10 @@ function writePidToFile(pid: number): void {
|
|||||||
* Remove PID file.
|
* Remove PID file.
|
||||||
*/
|
*/
|
||||||
function removePidFile(): void {
|
function removePidFile(): void {
|
||||||
|
const pidFile = getPidFilePath();
|
||||||
try {
|
try {
|
||||||
if (fs.existsSync(PID_FILE)) {
|
if (fs.existsSync(pidFile)) {
|
||||||
fs.unlinkSync(PID_FILE);
|
fs.unlinkSync(pidFile);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore errors
|
// Ignore errors
|
||||||
@@ -197,6 +206,16 @@ export async function startDaemon(
|
|||||||
error: `Failed to start daemon: ${err.message}`,
|
error: `Failed to start daemon: ${err.message}`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
proc.on('exit', (code) => {
|
||||||
|
if (code !== 0 && code !== null) {
|
||||||
|
clearInterval(checkInterval);
|
||||||
|
resolve({
|
||||||
|
success: false,
|
||||||
|
error: `Daemon process exited with code ${code}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
resolve({
|
resolve({
|
||||||
success: false,
|
success: false,
|
||||||
|
|||||||
+721
-736
File diff suppressed because it is too large
Load Diff
@@ -3,34 +3,27 @@
|
|||||||
* Implements ConnectRPC protobuf wire format decoding
|
* Implements ConnectRPC protobuf wire format decoding
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as zlib from "zlib";
|
import * as zlib from 'zlib';
|
||||||
import {
|
import { WIRE_TYPE, FIELD, type WireType } from './cursor-protobuf-schema.js';
|
||||||
WIRE_TYPE,
|
|
||||||
FIELD,
|
|
||||||
type WireType,
|
|
||||||
} from "./cursor-protobuf-schema.js";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode a varint from buffer
|
* Decode a varint from buffer
|
||||||
* Returns [value, newOffset]
|
* Returns [value, newOffset]
|
||||||
*/
|
*/
|
||||||
export function decodeVarint(
|
export function decodeVarint(buffer: Uint8Array, offset: number): [number, number] {
|
||||||
buffer: Uint8Array,
|
let result = 0;
|
||||||
offset: number
|
let shift = 0;
|
||||||
): [number, number] {
|
let pos = offset;
|
||||||
let result = 0;
|
|
||||||
let shift = 0;
|
|
||||||
let pos = offset;
|
|
||||||
|
|
||||||
while (pos < buffer.length) {
|
while (pos < buffer.length) {
|
||||||
const b = buffer[pos];
|
const b = buffer[pos];
|
||||||
result |= (b & 0x7f) << shift;
|
result |= (b & 0x7f) << shift;
|
||||||
pos++;
|
pos++;
|
||||||
if (!(b & 0x80)) break;
|
if (!(b & 0x80)) break;
|
||||||
shift += 7;
|
shift += 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [result, pos];
|
return [result, pos];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,63 +31,60 @@ export function decodeVarint(
|
|||||||
* Returns [fieldNum, wireType, value, newOffset]
|
* Returns [fieldNum, wireType, value, newOffset]
|
||||||
*/
|
*/
|
||||||
export function decodeField(
|
export function decodeField(
|
||||||
buffer: Uint8Array,
|
buffer: Uint8Array,
|
||||||
offset: number
|
offset: number
|
||||||
): [number | null, WireType | null, Uint8Array | number | null, number] {
|
): [number | null, WireType | null, Uint8Array | number | null, number] {
|
||||||
if (offset >= buffer.length) {
|
if (offset >= buffer.length) {
|
||||||
return [null, null, null, offset];
|
return [null, null, null, offset];
|
||||||
}
|
}
|
||||||
|
|
||||||
const [tag, pos1] = decodeVarint(buffer, offset);
|
const [tag, pos1] = decodeVarint(buffer, offset);
|
||||||
const fieldNum = tag >> 3;
|
const fieldNum = tag >> 3;
|
||||||
const wireType = (tag & 0x07) as WireType;
|
const wireType = (tag & 0x07) as WireType;
|
||||||
|
|
||||||
let value: Uint8Array | number | null;
|
let value: Uint8Array | number | null;
|
||||||
let pos = pos1;
|
let pos = pos1;
|
||||||
|
|
||||||
if (wireType === WIRE_TYPE.VARINT) {
|
if (wireType === WIRE_TYPE.VARINT) {
|
||||||
[value, pos] = decodeVarint(buffer, pos);
|
[value, pos] = decodeVarint(buffer, pos);
|
||||||
} else if (wireType === WIRE_TYPE.LEN) {
|
} else if (wireType === WIRE_TYPE.LEN) {
|
||||||
const [length, pos2] = decodeVarint(buffer, pos);
|
const [length, pos2] = decodeVarint(buffer, pos);
|
||||||
value = buffer.slice(pos2, pos2 + length);
|
value = buffer.slice(pos2, pos2 + length);
|
||||||
pos = pos2 + length;
|
pos = pos2 + length;
|
||||||
} else if (wireType === WIRE_TYPE.FIXED64) {
|
} else if (wireType === WIRE_TYPE.FIXED64) {
|
||||||
value = buffer.slice(pos, pos + 8);
|
value = buffer.slice(pos, pos + 8);
|
||||||
pos += 8;
|
pos += 8;
|
||||||
} else if (wireType === WIRE_TYPE.FIXED32) {
|
} else if (wireType === WIRE_TYPE.FIXED32) {
|
||||||
value = buffer.slice(pos, pos + 4);
|
value = buffer.slice(pos, pos + 4);
|
||||||
pos += 4;
|
pos += 4;
|
||||||
} else {
|
} else {
|
||||||
value = null;
|
value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [fieldNum, wireType, value, pos];
|
return [fieldNum, wireType, value, pos];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode a protobuf message into a map of fields
|
* Decode a protobuf message into a map of fields
|
||||||
*/
|
*/
|
||||||
export function decodeMessage(
|
export function decodeMessage(
|
||||||
data: Uint8Array
|
data: Uint8Array
|
||||||
): Map<number, Array<{ wireType: WireType; value: Uint8Array | number }>> {
|
): Map<number, Array<{ wireType: WireType; value: Uint8Array | number }>> {
|
||||||
const fields = new Map<
|
const fields = new Map<number, Array<{ wireType: WireType; value: Uint8Array | number }>>();
|
||||||
number,
|
let pos = 0;
|
||||||
Array<{ wireType: WireType; value: Uint8Array | number }>
|
|
||||||
>();
|
|
||||||
let pos = 0;
|
|
||||||
|
|
||||||
while (pos < data.length) {
|
while (pos < data.length) {
|
||||||
const [fieldNum, wireType, value, newPos] = decodeField(data, pos);
|
const [fieldNum, wireType, value, newPos] = decodeField(data, pos);
|
||||||
if (fieldNum === null || wireType === null || value === null) break;
|
if (fieldNum === null || wireType === null || value === null) break;
|
||||||
|
|
||||||
if (!fields.has(fieldNum)) {
|
if (!fields.has(fieldNum)) {
|
||||||
fields.set(fieldNum, []);
|
fields.set(fieldNum, []);
|
||||||
}
|
}
|
||||||
fields.get(fieldNum)!.push({ wireType, value: value as Uint8Array | number });
|
fields.get(fieldNum)!.push({ wireType, value: value as Uint8Array | number });
|
||||||
pos = newPos;
|
pos = newPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -102,200 +92,184 @@ export function decodeMessage(
|
|||||||
* Returns frame data or null if incomplete
|
* Returns frame data or null if incomplete
|
||||||
*/
|
*/
|
||||||
export function parseConnectRPCFrame(buffer: Buffer): {
|
export function parseConnectRPCFrame(buffer: Buffer): {
|
||||||
flags: number;
|
flags: number;
|
||||||
length: number;
|
length: number;
|
||||||
payload: Uint8Array;
|
payload: Uint8Array;
|
||||||
consumed: number;
|
consumed: number;
|
||||||
} | null {
|
} | null {
|
||||||
if (buffer.length < 5) return null;
|
if (buffer.length < 5) return null;
|
||||||
|
|
||||||
const flags = buffer[0];
|
const flags = buffer[0];
|
||||||
const length =
|
const length = (buffer[1] << 24) | (buffer[2] << 16) | (buffer[3] << 8) | buffer[4];
|
||||||
(buffer[1] << 24) | (buffer[2] << 16) | (buffer[3] << 8) | buffer[4];
|
|
||||||
|
|
||||||
if (buffer.length < 5 + length) return null;
|
if (buffer.length < 5 + length) return null;
|
||||||
|
|
||||||
let payload = buffer.slice(5, 5 + length);
|
let payload = buffer.slice(5, 5 + length);
|
||||||
|
|
||||||
// Decompress if gzip
|
// Decompress if gzip
|
||||||
if (flags === 0x01 || flags === 0x02 || flags === 0x03) {
|
if (flags === 0x01 || flags === 0x02 || flags === 0x03) {
|
||||||
try {
|
try {
|
||||||
payload = Buffer.from(zlib.gunzipSync(payload));
|
payload = Buffer.from(zlib.gunzipSync(payload));
|
||||||
} catch {
|
} catch {
|
||||||
// Decompression failed, use raw payload
|
// Decompression failed, use raw payload
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
flags,
|
flags,
|
||||||
length,
|
length,
|
||||||
payload: new Uint8Array(payload),
|
payload: new Uint8Array(payload),
|
||||||
consumed: 5 + length,
|
consumed: 5 + length,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract tool call from protobuf data
|
* Extract tool call from protobuf data
|
||||||
*/
|
*/
|
||||||
function extractToolCall(toolCallData: Uint8Array): {
|
function extractToolCall(toolCallData: Uint8Array): {
|
||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
function: { name: string; arguments: string };
|
function: { name: string; arguments: string };
|
||||||
isLast: boolean;
|
isLast: boolean;
|
||||||
} | null {
|
} | null {
|
||||||
const toolCall = decodeMessage(toolCallData);
|
const toolCall = decodeMessage(toolCallData);
|
||||||
let toolCallId = "";
|
let toolCallId = '';
|
||||||
let toolName = "";
|
let toolName = '';
|
||||||
let rawArgs = "";
|
let rawArgs = '';
|
||||||
let isLast = false;
|
let isLast = false;
|
||||||
|
|
||||||
// Extract tool call ID
|
// Extract tool call ID
|
||||||
if (toolCall.has(FIELD.TOOL_ID)) {
|
if (toolCall.has(FIELD.TOOL_ID)) {
|
||||||
const fullId = new TextDecoder().decode(
|
const fullId = new TextDecoder().decode(toolCall.get(FIELD.TOOL_ID)![0].value as Uint8Array);
|
||||||
toolCall.get(FIELD.TOOL_ID)![0].value as Uint8Array
|
toolCallId = fullId.split('\n')[0]; // Take first line
|
||||||
);
|
}
|
||||||
toolCallId = fullId.split("\n")[0]; // Take first line
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract tool name
|
// Extract tool name
|
||||||
if (toolCall.has(FIELD.TOOL_NAME)) {
|
if (toolCall.has(FIELD.TOOL_NAME)) {
|
||||||
toolName = new TextDecoder().decode(
|
toolName = new TextDecoder().decode(toolCall.get(FIELD.TOOL_NAME)![0].value as Uint8Array);
|
||||||
toolCall.get(FIELD.TOOL_NAME)![0].value as Uint8Array
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract is_last flag
|
// Extract is_last flag
|
||||||
if (toolCall.has(FIELD.TOOL_IS_LAST)) {
|
if (toolCall.has(FIELD.TOOL_IS_LAST)) {
|
||||||
isLast = (toolCall.get(FIELD.TOOL_IS_LAST)![0].value as number) !== 0;
|
isLast = (toolCall.get(FIELD.TOOL_IS_LAST)![0].value as number) !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract MCP params - nested real tool info
|
// Extract MCP params - nested real tool info
|
||||||
if (toolCall.has(FIELD.TOOL_MCP_PARAMS)) {
|
if (toolCall.has(FIELD.TOOL_MCP_PARAMS)) {
|
||||||
try {
|
try {
|
||||||
const mcpParams = decodeMessage(
|
const mcpParams = decodeMessage(toolCall.get(FIELD.TOOL_MCP_PARAMS)![0].value as Uint8Array);
|
||||||
toolCall.get(FIELD.TOOL_MCP_PARAMS)![0].value as Uint8Array
|
|
||||||
);
|
|
||||||
|
|
||||||
if (mcpParams.has(FIELD.MCP_TOOLS_LIST)) {
|
if (mcpParams.has(FIELD.MCP_TOOLS_LIST)) {
|
||||||
const tool = decodeMessage(
|
const tool = decodeMessage(mcpParams.get(FIELD.MCP_TOOLS_LIST)![0].value as Uint8Array);
|
||||||
mcpParams.get(FIELD.MCP_TOOLS_LIST)![0].value as Uint8Array
|
|
||||||
);
|
|
||||||
|
|
||||||
if (tool.has(FIELD.MCP_NESTED_NAME)) {
|
if (tool.has(FIELD.MCP_NESTED_NAME)) {
|
||||||
toolName = new TextDecoder().decode(
|
toolName = new TextDecoder().decode(
|
||||||
tool.get(FIELD.MCP_NESTED_NAME)![0].value as Uint8Array
|
tool.get(FIELD.MCP_NESTED_NAME)![0].value as Uint8Array
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tool.has(FIELD.MCP_NESTED_PARAMS)) {
|
if (tool.has(FIELD.MCP_NESTED_PARAMS)) {
|
||||||
rawArgs = new TextDecoder().decode(
|
rawArgs = new TextDecoder().decode(
|
||||||
tool.get(FIELD.MCP_NESTED_PARAMS)![0].value as Uint8Array
|
tool.get(FIELD.MCP_NESTED_PARAMS)![0].value as Uint8Array
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// MCP parse error, continue
|
// MCP parse error, continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to raw_args
|
// Fallback to raw_args
|
||||||
if (!rawArgs && toolCall.has(FIELD.TOOL_RAW_ARGS)) {
|
if (!rawArgs && toolCall.has(FIELD.TOOL_RAW_ARGS)) {
|
||||||
rawArgs = new TextDecoder().decode(
|
rawArgs = new TextDecoder().decode(toolCall.get(FIELD.TOOL_RAW_ARGS)![0].value as Uint8Array);
|
||||||
toolCall.get(FIELD.TOOL_RAW_ARGS)![0].value as Uint8Array
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toolCallId && toolName) {
|
if (toolCallId && toolName) {
|
||||||
return {
|
return {
|
||||||
id: toolCallId,
|
id: toolCallId,
|
||||||
type: "function",
|
type: 'function',
|
||||||
function: {
|
function: {
|
||||||
name: toolName,
|
name: toolName,
|
||||||
arguments: rawArgs || "{}",
|
arguments: rawArgs || '{}',
|
||||||
},
|
},
|
||||||
isLast,
|
isLast,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract text and thinking from response data
|
* Extract text and thinking from response data
|
||||||
*/
|
*/
|
||||||
function extractTextAndThinking(
|
function extractTextAndThinking(responseData: Uint8Array): {
|
||||||
responseData: Uint8Array
|
text: string | null;
|
||||||
): { text: string | null; thinking: string | null } {
|
thinking: string | null;
|
||||||
const nested = decodeMessage(responseData);
|
} {
|
||||||
let text: string | null = null;
|
const nested = decodeMessage(responseData);
|
||||||
let thinking: string | null = null;
|
let text: string | null = null;
|
||||||
|
let thinking: string | null = null;
|
||||||
|
|
||||||
// Extract text
|
// Extract text
|
||||||
if (nested.has(FIELD.RESPONSE_TEXT)) {
|
if (nested.has(FIELD.RESPONSE_TEXT)) {
|
||||||
text = new TextDecoder().decode(
|
text = new TextDecoder().decode(nested.get(FIELD.RESPONSE_TEXT)![0].value as Uint8Array);
|
||||||
nested.get(FIELD.RESPONSE_TEXT)![0].value as Uint8Array
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract thinking
|
// Extract thinking
|
||||||
if (nested.has(FIELD.THINKING)) {
|
if (nested.has(FIELD.THINKING)) {
|
||||||
try {
|
try {
|
||||||
const thinkingMsg = decodeMessage(
|
const thinkingMsg = decodeMessage(nested.get(FIELD.THINKING)![0].value as Uint8Array);
|
||||||
nested.get(FIELD.THINKING)![0].value as Uint8Array
|
if (thinkingMsg.has(FIELD.THINKING_TEXT)) {
|
||||||
);
|
thinking = new TextDecoder().decode(
|
||||||
if (thinkingMsg.has(FIELD.THINKING_TEXT)) {
|
thinkingMsg.get(FIELD.THINKING_TEXT)![0].value as Uint8Array
|
||||||
thinking = new TextDecoder().decode(
|
);
|
||||||
thinkingMsg.get(FIELD.THINKING_TEXT)![0].value as Uint8Array
|
}
|
||||||
);
|
} catch {
|
||||||
}
|
// Thinking parse error, continue
|
||||||
} catch {
|
}
|
||||||
// Thinking parse error, continue
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { text, thinking };
|
return { text, thinking };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract text and tool calls from response payload
|
* Extract text and tool calls from response payload
|
||||||
*/
|
*/
|
||||||
export function extractTextFromResponse(payload: Uint8Array): {
|
export function extractTextFromResponse(payload: Uint8Array): {
|
||||||
text: string | null;
|
text: string | null;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
toolCall: {
|
toolCall: {
|
||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
function: { name: string; arguments: string };
|
function: { name: string; arguments: string };
|
||||||
isLast: boolean;
|
isLast: boolean;
|
||||||
} | null;
|
} | null;
|
||||||
thinking: string | null;
|
thinking: string | null;
|
||||||
} {
|
} {
|
||||||
try {
|
try {
|
||||||
const fields = decodeMessage(payload);
|
const fields = decodeMessage(payload);
|
||||||
|
|
||||||
// Field 1: ClientSideToolV2Call
|
// Field 1: ClientSideToolV2Call
|
||||||
if (fields.has(FIELD.TOOL_CALL)) {
|
if (fields.has(FIELD.TOOL_CALL)) {
|
||||||
const toolCall = extractToolCall(
|
const toolCall = extractToolCall(fields.get(FIELD.TOOL_CALL)![0].value as Uint8Array);
|
||||||
fields.get(FIELD.TOOL_CALL)![0].value as Uint8Array
|
if (toolCall) {
|
||||||
);
|
return { text: null, error: null, toolCall, thinking: null };
|
||||||
if (toolCall) {
|
}
|
||||||
return { text: null, error: null, toolCall, thinking: null };
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Field 2: StreamUnifiedChatResponse
|
// Field 2: StreamUnifiedChatResponse
|
||||||
if (fields.has(FIELD.RESPONSE)) {
|
if (fields.has(FIELD.RESPONSE)) {
|
||||||
const { text, thinking } = extractTextAndThinking(
|
const { text, thinking } = extractTextAndThinking(
|
||||||
fields.get(FIELD.RESPONSE)![0].value as Uint8Array
|
fields.get(FIELD.RESPONSE)![0].value as Uint8Array
|
||||||
);
|
);
|
||||||
|
|
||||||
if (text || thinking) {
|
if (text || thinking) {
|
||||||
return { text, error: null, toolCall: null, thinking };
|
return { text, error: null, toolCall: null, thinking };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { text: null, error: null, toolCall: null, thinking: null };
|
return { text: null, error: null, toolCall: null, thinking: null };
|
||||||
} catch {
|
} catch {
|
||||||
return { text: null, error: null, toolCall: null, thinking: null };
|
return { text: null, error: null, toolCall: null, thinking: null };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,260 +3,229 @@
|
|||||||
* Implements ConnectRPC protobuf wire format encoding
|
* Implements ConnectRPC protobuf wire format encoding
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { randomUUID } from "crypto";
|
import { randomUUID } from 'crypto';
|
||||||
import * as zlib from "zlib";
|
import * as zlib from 'zlib';
|
||||||
import {
|
import {
|
||||||
WIRE_TYPE,
|
WIRE_TYPE,
|
||||||
ROLE,
|
ROLE,
|
||||||
UNIFIED_MODE,
|
UNIFIED_MODE,
|
||||||
THINKING_LEVEL,
|
THINKING_LEVEL,
|
||||||
FIELD,
|
FIELD,
|
||||||
COMPRESS_FLAG,
|
COMPRESS_FLAG,
|
||||||
type WireType,
|
type WireType,
|
||||||
type RoleType,
|
type RoleType,
|
||||||
type ThinkingLevelType,
|
type ThinkingLevelType,
|
||||||
type CursorTool,
|
type CursorTool,
|
||||||
type CursorToolResult,
|
type CursorToolResult,
|
||||||
type CursorMessage,
|
type CursorMessage,
|
||||||
type FormattedMessage,
|
type FormattedMessage,
|
||||||
type MessageId,
|
type MessageId,
|
||||||
} from "./cursor-protobuf-schema.js";
|
} from './cursor-protobuf-schema.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode a varint (variable-length integer)
|
* Encode a varint (variable-length integer)
|
||||||
*/
|
*/
|
||||||
export function encodeVarint(value: number): Uint8Array {
|
export function encodeVarint(value: number): Uint8Array {
|
||||||
const bytes: number[] = [];
|
const bytes: number[] = [];
|
||||||
let val = value >>> 0; // Ensure unsigned
|
let val = value >>> 0; // Ensure unsigned
|
||||||
while (val >= 0x80) {
|
while (val >= 0x80) {
|
||||||
bytes.push((val & 0x7f) | 0x80);
|
bytes.push((val & 0x7f) | 0x80);
|
||||||
val >>>= 7;
|
val >>>= 7;
|
||||||
}
|
}
|
||||||
bytes.push(val & 0x7f);
|
bytes.push(val & 0x7f);
|
||||||
return new Uint8Array(bytes);
|
return new Uint8Array(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode a protobuf field (tag + value)
|
* Encode a protobuf field (tag + value)
|
||||||
*/
|
*/
|
||||||
export function encodeField(
|
export function encodeField(
|
||||||
fieldNum: number,
|
fieldNum: number,
|
||||||
wireType: WireType,
|
wireType: WireType,
|
||||||
value: number | string | Uint8Array
|
value: number | string | Uint8Array
|
||||||
): Uint8Array {
|
): Uint8Array {
|
||||||
const tag = (fieldNum << 3) | wireType;
|
const tag = (fieldNum << 3) | wireType;
|
||||||
const tagBytes = encodeVarint(tag);
|
const tagBytes = encodeVarint(tag);
|
||||||
|
|
||||||
if (wireType === WIRE_TYPE.VARINT) {
|
if (wireType === WIRE_TYPE.VARINT) {
|
||||||
const valueBytes = encodeVarint(value as number);
|
const valueBytes = encodeVarint(value as number);
|
||||||
return concatArrays(tagBytes, valueBytes);
|
return concatArrays(tagBytes, valueBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wireType === WIRE_TYPE.LEN) {
|
if (wireType === WIRE_TYPE.LEN) {
|
||||||
const dataBytes =
|
const dataBytes =
|
||||||
typeof value === "string"
|
typeof value === 'string'
|
||||||
? new TextEncoder().encode(value)
|
? new TextEncoder().encode(value)
|
||||||
: value instanceof Uint8Array
|
: value instanceof Uint8Array
|
||||||
? value
|
? value
|
||||||
: new Uint8Array(0);
|
: new Uint8Array(0);
|
||||||
|
|
||||||
const lengthBytes = encodeVarint(dataBytes.length);
|
const lengthBytes = encodeVarint(dataBytes.length);
|
||||||
return concatArrays(tagBytes, lengthBytes, dataBytes);
|
return concatArrays(tagBytes, lengthBytes, dataBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Uint8Array(0);
|
return new Uint8Array(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Concatenate multiple Uint8Arrays
|
* Concatenate multiple Uint8Arrays
|
||||||
*/
|
*/
|
||||||
function concatArrays(...arrays: Uint8Array[]): Uint8Array {
|
function concatArrays(...arrays: Uint8Array[]): Uint8Array {
|
||||||
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
|
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
|
||||||
const result = new Uint8Array(totalLength);
|
const result = new Uint8Array(totalLength);
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
for (const arr of arrays) {
|
for (const arr of arrays) {
|
||||||
result.set(arr, offset);
|
result.set(arr, offset);
|
||||||
offset += arr.length;
|
offset += arr.length;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode a tool result
|
* Encode a tool result
|
||||||
*/
|
*/
|
||||||
export function encodeToolResult(toolResult: CursorToolResult): Uint8Array {
|
export function encodeToolResult(toolResult: CursorToolResult): Uint8Array {
|
||||||
const toolCallId = toolResult.tool_call_id || "";
|
const toolCallId = toolResult.tool_call_id || '';
|
||||||
const toolName = toolResult.name || "";
|
const toolName = toolResult.name || '';
|
||||||
const toolIndex = toolResult.index || 0;
|
const toolIndex = toolResult.index || 0;
|
||||||
const rawArgs = toolResult.raw_args || "{}";
|
const rawArgs = toolResult.raw_args || '{}';
|
||||||
|
|
||||||
return concatArrays(
|
return concatArrays(
|
||||||
encodeField(FIELD.TOOL_RESULT_CALL_ID, WIRE_TYPE.LEN, toolCallId),
|
encodeField(FIELD.TOOL_RESULT_CALL_ID, WIRE_TYPE.LEN, toolCallId),
|
||||||
encodeField(FIELD.TOOL_RESULT_NAME, WIRE_TYPE.LEN, toolName),
|
encodeField(FIELD.TOOL_RESULT_NAME, WIRE_TYPE.LEN, toolName),
|
||||||
encodeField(FIELD.TOOL_RESULT_INDEX, WIRE_TYPE.VARINT, toolIndex),
|
encodeField(FIELD.TOOL_RESULT_INDEX, WIRE_TYPE.VARINT, toolIndex),
|
||||||
encodeField(FIELD.TOOL_RESULT_RAW_ARGS, WIRE_TYPE.LEN, rawArgs)
|
encodeField(FIELD.TOOL_RESULT_RAW_ARGS, WIRE_TYPE.LEN, rawArgs)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode a conversation message
|
* Encode a conversation message
|
||||||
*/
|
*/
|
||||||
export function encodeMessage(
|
export function encodeMessage(
|
||||||
content: string,
|
content: string,
|
||||||
role: RoleType,
|
role: RoleType,
|
||||||
messageId: string,
|
messageId: string,
|
||||||
isLast: boolean,
|
isLast: boolean,
|
||||||
hasTools: boolean,
|
hasTools: boolean,
|
||||||
toolResults: CursorToolResult[]
|
toolResults: CursorToolResult[]
|
||||||
): Uint8Array {
|
): Uint8Array {
|
||||||
return concatArrays(
|
return concatArrays(
|
||||||
encodeField(FIELD.MSG_CONTENT, WIRE_TYPE.LEN, content),
|
encodeField(FIELD.MSG_CONTENT, WIRE_TYPE.LEN, content),
|
||||||
encodeField(FIELD.MSG_ROLE, WIRE_TYPE.VARINT, role),
|
encodeField(FIELD.MSG_ROLE, WIRE_TYPE.VARINT, role),
|
||||||
encodeField(FIELD.MSG_ID, WIRE_TYPE.LEN, messageId),
|
encodeField(FIELD.MSG_ID, WIRE_TYPE.LEN, messageId),
|
||||||
...(toolResults.length > 0
|
...(toolResults.length > 0
|
||||||
? toolResults.map((tr) =>
|
? toolResults.map((tr) =>
|
||||||
encodeField(
|
encodeField(FIELD.MSG_TOOL_RESULTS, WIRE_TYPE.LEN, encodeToolResult(tr))
|
||||||
FIELD.MSG_TOOL_RESULTS,
|
)
|
||||||
WIRE_TYPE.LEN,
|
: []),
|
||||||
encodeToolResult(tr)
|
encodeField(FIELD.MSG_IS_AGENTIC, WIRE_TYPE.VARINT, hasTools ? 1 : 0),
|
||||||
)
|
encodeField(
|
||||||
)
|
FIELD.MSG_UNIFIED_MODE,
|
||||||
: []),
|
WIRE_TYPE.VARINT,
|
||||||
encodeField(FIELD.MSG_IS_AGENTIC, WIRE_TYPE.VARINT, hasTools ? 1 : 0),
|
hasTools ? UNIFIED_MODE.AGENT : UNIFIED_MODE.CHAT
|
||||||
encodeField(
|
),
|
||||||
FIELD.MSG_UNIFIED_MODE,
|
...(isLast && hasTools
|
||||||
WIRE_TYPE.VARINT,
|
? [encodeField(FIELD.MSG_SUPPORTED_TOOLS, WIRE_TYPE.LEN, encodeVarint(1))]
|
||||||
hasTools ? UNIFIED_MODE.AGENT : UNIFIED_MODE.CHAT
|
: [])
|
||||||
),
|
);
|
||||||
...(isLast && hasTools
|
|
||||||
? [
|
|
||||||
encodeField(
|
|
||||||
FIELD.MSG_SUPPORTED_TOOLS,
|
|
||||||
WIRE_TYPE.LEN,
|
|
||||||
encodeVarint(1)
|
|
||||||
),
|
|
||||||
]
|
|
||||||
: [])
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode instruction text
|
* Encode instruction text
|
||||||
*/
|
*/
|
||||||
export function encodeInstruction(text: string): Uint8Array {
|
export function encodeInstruction(text: string): Uint8Array {
|
||||||
return text
|
return text ? encodeField(FIELD.INSTRUCTION_TEXT, WIRE_TYPE.LEN, text) : new Uint8Array(0);
|
||||||
? encodeField(FIELD.INSTRUCTION_TEXT, WIRE_TYPE.LEN, text)
|
|
||||||
: new Uint8Array(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode model information
|
* Encode model information
|
||||||
*/
|
*/
|
||||||
export function encodeModel(modelName: string): Uint8Array {
|
export function encodeModel(modelName: string): Uint8Array {
|
||||||
return concatArrays(
|
return concatArrays(
|
||||||
encodeField(FIELD.MODEL_NAME, WIRE_TYPE.LEN, modelName),
|
encodeField(FIELD.MODEL_NAME, WIRE_TYPE.LEN, modelName),
|
||||||
encodeField(FIELD.MODEL_EMPTY, WIRE_TYPE.LEN, new Uint8Array(0))
|
encodeField(FIELD.MODEL_EMPTY, WIRE_TYPE.LEN, new Uint8Array(0))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode cursor settings
|
* Encode cursor settings
|
||||||
*/
|
*/
|
||||||
export function encodeCursorSetting(): Uint8Array {
|
export function encodeCursorSetting(): Uint8Array {
|
||||||
const unknown6 = concatArrays(
|
const unknown6 = concatArrays(
|
||||||
encodeField(FIELD.SETTING6_FIELD_1, WIRE_TYPE.LEN, new Uint8Array(0)),
|
encodeField(FIELD.SETTING6_FIELD_1, WIRE_TYPE.LEN, new Uint8Array(0)),
|
||||||
encodeField(FIELD.SETTING6_FIELD_2, WIRE_TYPE.LEN, new Uint8Array(0))
|
encodeField(FIELD.SETTING6_FIELD_2, WIRE_TYPE.LEN, new Uint8Array(0))
|
||||||
);
|
);
|
||||||
|
|
||||||
return concatArrays(
|
return concatArrays(
|
||||||
encodeField(FIELD.SETTING_PATH, WIRE_TYPE.LEN, "cursor\\aisettings"),
|
encodeField(FIELD.SETTING_PATH, WIRE_TYPE.LEN, 'cursor\\aisettings'),
|
||||||
encodeField(FIELD.SETTING_UNKNOWN_3, WIRE_TYPE.LEN, new Uint8Array(0)),
|
encodeField(FIELD.SETTING_UNKNOWN_3, WIRE_TYPE.LEN, new Uint8Array(0)),
|
||||||
encodeField(FIELD.SETTING_UNKNOWN_6, WIRE_TYPE.LEN, unknown6),
|
encodeField(FIELD.SETTING_UNKNOWN_6, WIRE_TYPE.LEN, unknown6),
|
||||||
encodeField(FIELD.SETTING_UNKNOWN_8, WIRE_TYPE.VARINT, 1),
|
encodeField(FIELD.SETTING_UNKNOWN_8, WIRE_TYPE.VARINT, 1),
|
||||||
encodeField(FIELD.SETTING_UNKNOWN_9, WIRE_TYPE.VARINT, 1)
|
encodeField(FIELD.SETTING_UNKNOWN_9, WIRE_TYPE.VARINT, 1)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode metadata
|
* Encode metadata
|
||||||
*/
|
*/
|
||||||
export function encodeMetadata(): Uint8Array {
|
export function encodeMetadata(): Uint8Array {
|
||||||
return concatArrays(
|
return concatArrays(
|
||||||
encodeField(FIELD.META_PLATFORM, WIRE_TYPE.LEN, process.platform || "linux"),
|
encodeField(FIELD.META_PLATFORM, WIRE_TYPE.LEN, process.platform || 'linux'),
|
||||||
encodeField(FIELD.META_ARCH, WIRE_TYPE.LEN, process.arch || "x64"),
|
encodeField(FIELD.META_ARCH, WIRE_TYPE.LEN, process.arch || 'x64'),
|
||||||
encodeField(FIELD.META_VERSION, WIRE_TYPE.LEN, process.version || "v20.0.0"),
|
encodeField(FIELD.META_VERSION, WIRE_TYPE.LEN, process.version || 'v20.0.0'),
|
||||||
encodeField(FIELD.META_CWD, WIRE_TYPE.LEN, process.cwd() || "/"),
|
encodeField(FIELD.META_CWD, WIRE_TYPE.LEN, process.cwd() || '/'),
|
||||||
encodeField(FIELD.META_TIMESTAMP, WIRE_TYPE.LEN, new Date().toISOString())
|
encodeField(FIELD.META_TIMESTAMP, WIRE_TYPE.LEN, new Date().toISOString())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode message ID
|
* Encode message ID
|
||||||
*/
|
*/
|
||||||
export function encodeMessageId(
|
export function encodeMessageId(messageId: string, role: RoleType, summaryId?: string): Uint8Array {
|
||||||
messageId: string,
|
return concatArrays(
|
||||||
role: RoleType,
|
encodeField(FIELD.MSGID_ID, WIRE_TYPE.LEN, messageId),
|
||||||
summaryId?: string
|
...(summaryId ? [encodeField(FIELD.MSGID_SUMMARY, WIRE_TYPE.LEN, summaryId)] : []),
|
||||||
): Uint8Array {
|
encodeField(FIELD.MSGID_ROLE, WIRE_TYPE.VARINT, role)
|
||||||
return concatArrays(
|
);
|
||||||
encodeField(FIELD.MSGID_ID, WIRE_TYPE.LEN, messageId),
|
|
||||||
...(summaryId
|
|
||||||
? [encodeField(FIELD.MSGID_SUMMARY, WIRE_TYPE.LEN, summaryId)]
|
|
||||||
: []),
|
|
||||||
encodeField(FIELD.MSGID_ROLE, WIRE_TYPE.VARINT, role)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode MCP tool
|
* Encode MCP tool
|
||||||
*/
|
*/
|
||||||
export function encodeMcpTool(tool: CursorTool): Uint8Array {
|
export function encodeMcpTool(tool: CursorTool): Uint8Array {
|
||||||
const toolName = tool.function?.name || tool.name || "";
|
const toolName = tool.function?.name || tool.name || '';
|
||||||
const toolDesc = tool.function?.description || tool.description || "";
|
const toolDesc = tool.function?.description || tool.description || '';
|
||||||
const inputSchema = tool.function?.parameters || tool.input_schema || {};
|
const inputSchema = tool.function?.parameters || tool.input_schema || {};
|
||||||
|
|
||||||
return concatArrays(
|
return concatArrays(
|
||||||
...(toolName
|
...(toolName ? [encodeField(FIELD.MCP_TOOL_NAME, WIRE_TYPE.LEN, toolName)] : []),
|
||||||
? [encodeField(FIELD.MCP_TOOL_NAME, WIRE_TYPE.LEN, toolName)]
|
...(toolDesc ? [encodeField(FIELD.MCP_TOOL_DESC, WIRE_TYPE.LEN, toolDesc)] : []),
|
||||||
: []),
|
...(Object.keys(inputSchema).length > 0
|
||||||
...(toolDesc
|
? [encodeField(FIELD.MCP_TOOL_PARAMS, WIRE_TYPE.LEN, JSON.stringify(inputSchema))]
|
||||||
? [encodeField(FIELD.MCP_TOOL_DESC, WIRE_TYPE.LEN, toolDesc)]
|
: []),
|
||||||
: []),
|
encodeField(FIELD.MCP_TOOL_SERVER, WIRE_TYPE.LEN, 'custom')
|
||||||
...(Object.keys(inputSchema).length > 0
|
);
|
||||||
? [
|
|
||||||
encodeField(
|
|
||||||
FIELD.MCP_TOOL_PARAMS,
|
|
||||||
WIRE_TYPE.LEN,
|
|
||||||
JSON.stringify(inputSchema)
|
|
||||||
),
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
encodeField(FIELD.MCP_TOOL_SERVER, WIRE_TYPE.LEN, "custom")
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrap payload in ConnectRPC frame (5-byte header + payload)
|
* Wrap payload in ConnectRPC frame (5-byte header + payload)
|
||||||
*/
|
*/
|
||||||
export function wrapConnectRPCFrame(
|
export function wrapConnectRPCFrame(payload: Uint8Array, compress = false): Uint8Array {
|
||||||
payload: Uint8Array,
|
let finalPayload = payload;
|
||||||
compress = false
|
let flags: number = COMPRESS_FLAG.NONE;
|
||||||
): Uint8Array {
|
|
||||||
let finalPayload = payload;
|
|
||||||
let flags: number = COMPRESS_FLAG.NONE;
|
|
||||||
|
|
||||||
if (compress) {
|
if (compress) {
|
||||||
finalPayload = new Uint8Array(zlib.gzipSync(Buffer.from(payload)));
|
finalPayload = new Uint8Array(zlib.gzipSync(Buffer.from(payload)));
|
||||||
flags = COMPRESS_FLAG.GZIP;
|
flags = COMPRESS_FLAG.GZIP;
|
||||||
}
|
}
|
||||||
|
|
||||||
const frame = new Uint8Array(5 + finalPayload.length);
|
const frame = new Uint8Array(5 + finalPayload.length);
|
||||||
frame[0] = flags;
|
frame[0] = flags;
|
||||||
frame[1] = (finalPayload.length >> 24) & 0xff;
|
frame[1] = (finalPayload.length >> 24) & 0xff;
|
||||||
frame[2] = (finalPayload.length >> 16) & 0xff;
|
frame[2] = (finalPayload.length >> 16) & 0xff;
|
||||||
frame[3] = (finalPayload.length >> 8) & 0xff;
|
frame[3] = (finalPayload.length >> 8) & 0xff;
|
||||||
frame[4] = finalPayload.length & 0xff;
|
frame[4] = finalPayload.length & 0xff;
|
||||||
frame.set(finalPayload, 5);
|
frame.set(finalPayload, 5);
|
||||||
|
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,201 +5,200 @@
|
|||||||
|
|
||||||
/** Wire types for protobuf encoding */
|
/** Wire types for protobuf encoding */
|
||||||
export const WIRE_TYPE = {
|
export const WIRE_TYPE = {
|
||||||
VARINT: 0,
|
VARINT: 0,
|
||||||
FIXED64: 1,
|
FIXED64: 1,
|
||||||
LEN: 2,
|
LEN: 2,
|
||||||
FIXED32: 5,
|
FIXED32: 5,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
/** Message role constants */
|
/** Message role constants */
|
||||||
export const ROLE = {
|
export const ROLE = {
|
||||||
USER: 1,
|
USER: 1,
|
||||||
ASSISTANT: 2,
|
ASSISTANT: 2,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
/** Unified mode constants */
|
/** Unified mode constants */
|
||||||
export const UNIFIED_MODE = {
|
export const UNIFIED_MODE = {
|
||||||
CHAT: 1,
|
CHAT: 1,
|
||||||
AGENT: 2,
|
AGENT: 2,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
/** Thinking level constants */
|
/** Thinking level constants */
|
||||||
export const THINKING_LEVEL = {
|
export const THINKING_LEVEL = {
|
||||||
UNSPECIFIED: 0,
|
UNSPECIFIED: 0,
|
||||||
MEDIUM: 1,
|
MEDIUM: 1,
|
||||||
HIGH: 2,
|
HIGH: 2,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
/** Field numbers for all protobuf messages */
|
/** Field numbers for all protobuf messages */
|
||||||
export const FIELD = {
|
export const FIELD = {
|
||||||
// StreamUnifiedChatRequestWithTools (top level)
|
// StreamUnifiedChatRequestWithTools (top level)
|
||||||
REQUEST: 1,
|
REQUEST: 1,
|
||||||
|
|
||||||
// StreamUnifiedChatRequest
|
// StreamUnifiedChatRequest
|
||||||
MESSAGES: 1,
|
MESSAGES: 1,
|
||||||
UNKNOWN_2: 2,
|
UNKNOWN_2: 2,
|
||||||
INSTRUCTION: 3,
|
INSTRUCTION: 3,
|
||||||
UNKNOWN_4: 4,
|
UNKNOWN_4: 4,
|
||||||
MODEL: 5,
|
MODEL: 5,
|
||||||
WEB_TOOL: 8,
|
WEB_TOOL: 8,
|
||||||
UNKNOWN_13: 13,
|
UNKNOWN_13: 13,
|
||||||
CURSOR_SETTING: 15,
|
CURSOR_SETTING: 15,
|
||||||
UNKNOWN_19: 19,
|
UNKNOWN_19: 19,
|
||||||
CONVERSATION_ID: 23,
|
CONVERSATION_ID: 23,
|
||||||
METADATA: 26,
|
METADATA: 26,
|
||||||
IS_AGENTIC: 27,
|
IS_AGENTIC: 27,
|
||||||
SUPPORTED_TOOLS: 29,
|
SUPPORTED_TOOLS: 29,
|
||||||
MESSAGE_IDS: 30,
|
MESSAGE_IDS: 30,
|
||||||
MCP_TOOLS: 34,
|
MCP_TOOLS: 34,
|
||||||
LARGE_CONTEXT: 35,
|
LARGE_CONTEXT: 35,
|
||||||
UNKNOWN_38: 38,
|
UNKNOWN_38: 38,
|
||||||
UNIFIED_MODE: 46,
|
UNIFIED_MODE: 46,
|
||||||
UNKNOWN_47: 47,
|
UNKNOWN_47: 47,
|
||||||
SHOULD_DISABLE_TOOLS: 48,
|
SHOULD_DISABLE_TOOLS: 48,
|
||||||
THINKING_LEVEL: 49,
|
THINKING_LEVEL: 49,
|
||||||
UNKNOWN_51: 51,
|
UNKNOWN_51: 51,
|
||||||
UNKNOWN_53: 53,
|
UNKNOWN_53: 53,
|
||||||
UNIFIED_MODE_NAME: 54,
|
UNIFIED_MODE_NAME: 54,
|
||||||
|
|
||||||
// ConversationMessage
|
// ConversationMessage
|
||||||
MSG_CONTENT: 1,
|
MSG_CONTENT: 1,
|
||||||
MSG_ROLE: 2,
|
MSG_ROLE: 2,
|
||||||
MSG_ID: 13,
|
MSG_ID: 13,
|
||||||
MSG_TOOL_RESULTS: 18,
|
MSG_TOOL_RESULTS: 18,
|
||||||
MSG_IS_AGENTIC: 29,
|
MSG_IS_AGENTIC: 29,
|
||||||
MSG_UNIFIED_MODE: 47,
|
MSG_UNIFIED_MODE: 47,
|
||||||
MSG_SUPPORTED_TOOLS: 51,
|
MSG_SUPPORTED_TOOLS: 51,
|
||||||
|
|
||||||
// ConversationMessage.ToolResult
|
// ConversationMessage.ToolResult
|
||||||
TOOL_RESULT_CALL_ID: 1,
|
TOOL_RESULT_CALL_ID: 1,
|
||||||
TOOL_RESULT_NAME: 2,
|
TOOL_RESULT_NAME: 2,
|
||||||
TOOL_RESULT_INDEX: 3,
|
TOOL_RESULT_INDEX: 3,
|
||||||
TOOL_RESULT_RAW_ARGS: 5,
|
TOOL_RESULT_RAW_ARGS: 5,
|
||||||
TOOL_RESULT_RESULT: 8,
|
TOOL_RESULT_RESULT: 8,
|
||||||
|
|
||||||
// Model
|
// Model
|
||||||
MODEL_NAME: 1,
|
MODEL_NAME: 1,
|
||||||
MODEL_EMPTY: 4,
|
MODEL_EMPTY: 4,
|
||||||
|
|
||||||
// Instruction
|
// Instruction
|
||||||
INSTRUCTION_TEXT: 1,
|
INSTRUCTION_TEXT: 1,
|
||||||
|
|
||||||
// CursorSetting
|
// CursorSetting
|
||||||
SETTING_PATH: 1,
|
SETTING_PATH: 1,
|
||||||
SETTING_UNKNOWN_3: 3,
|
SETTING_UNKNOWN_3: 3,
|
||||||
SETTING_UNKNOWN_6: 6,
|
SETTING_UNKNOWN_6: 6,
|
||||||
SETTING_UNKNOWN_8: 8,
|
SETTING_UNKNOWN_8: 8,
|
||||||
SETTING_UNKNOWN_9: 9,
|
SETTING_UNKNOWN_9: 9,
|
||||||
|
|
||||||
// CursorSetting.Unknown6
|
// CursorSetting.Unknown6
|
||||||
SETTING6_FIELD_1: 1,
|
SETTING6_FIELD_1: 1,
|
||||||
SETTING6_FIELD_2: 2,
|
SETTING6_FIELD_2: 2,
|
||||||
|
|
||||||
// Metadata
|
// Metadata
|
||||||
META_PLATFORM: 1,
|
META_PLATFORM: 1,
|
||||||
META_ARCH: 2,
|
META_ARCH: 2,
|
||||||
META_VERSION: 3,
|
META_VERSION: 3,
|
||||||
META_CWD: 4,
|
META_CWD: 4,
|
||||||
META_TIMESTAMP: 5,
|
META_TIMESTAMP: 5,
|
||||||
|
|
||||||
// MessageId
|
// MessageId
|
||||||
MSGID_ID: 1,
|
MSGID_ID: 1,
|
||||||
MSGID_SUMMARY: 2,
|
MSGID_SUMMARY: 2,
|
||||||
MSGID_ROLE: 3,
|
MSGID_ROLE: 3,
|
||||||
|
|
||||||
// MCPTool
|
// MCPTool
|
||||||
MCP_TOOL_NAME: 1,
|
MCP_TOOL_NAME: 1,
|
||||||
MCP_TOOL_DESC: 2,
|
MCP_TOOL_DESC: 2,
|
||||||
MCP_TOOL_PARAMS: 3,
|
MCP_TOOL_PARAMS: 3,
|
||||||
MCP_TOOL_SERVER: 4,
|
MCP_TOOL_SERVER: 4,
|
||||||
|
|
||||||
// StreamUnifiedChatResponseWithTools (response)
|
// StreamUnifiedChatResponseWithTools (response)
|
||||||
TOOL_CALL: 1,
|
TOOL_CALL: 1,
|
||||||
RESPONSE: 2,
|
RESPONSE: 2,
|
||||||
|
|
||||||
// ClientSideToolV2Call
|
// ClientSideToolV2Call
|
||||||
TOOL_ID: 3,
|
TOOL_ID: 3,
|
||||||
TOOL_NAME: 9,
|
TOOL_NAME: 9,
|
||||||
TOOL_RAW_ARGS: 10,
|
TOOL_RAW_ARGS: 10,
|
||||||
TOOL_IS_LAST: 11,
|
TOOL_IS_LAST: 11,
|
||||||
TOOL_MCP_PARAMS: 27,
|
TOOL_MCP_PARAMS: 27,
|
||||||
|
|
||||||
// MCPParams
|
// MCPParams
|
||||||
MCP_TOOLS_LIST: 1,
|
MCP_TOOLS_LIST: 1,
|
||||||
|
|
||||||
// MCPParams.Tool (nested)
|
// MCPParams.Tool (nested)
|
||||||
MCP_NESTED_NAME: 1,
|
MCP_NESTED_NAME: 1,
|
||||||
MCP_NESTED_PARAMS: 3,
|
MCP_NESTED_PARAMS: 3,
|
||||||
|
|
||||||
// StreamUnifiedChatResponse
|
// StreamUnifiedChatResponse
|
||||||
RESPONSE_TEXT: 1,
|
RESPONSE_TEXT: 1,
|
||||||
THINKING: 25,
|
THINKING: 25,
|
||||||
|
|
||||||
// Thinking
|
// Thinking
|
||||||
THINKING_TEXT: 1,
|
THINKING_TEXT: 1,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
/** Type definitions */
|
/** Type definitions */
|
||||||
export type WireType = (typeof WIRE_TYPE)[keyof typeof WIRE_TYPE];
|
export type WireType = (typeof WIRE_TYPE)[keyof typeof WIRE_TYPE];
|
||||||
export type RoleType = (typeof ROLE)[keyof typeof ROLE];
|
export type RoleType = (typeof ROLE)[keyof typeof ROLE];
|
||||||
export type UnifiedModeType = (typeof UNIFIED_MODE)[keyof typeof UNIFIED_MODE];
|
export type UnifiedModeType = (typeof UNIFIED_MODE)[keyof typeof UNIFIED_MODE];
|
||||||
export type ThinkingLevelType =
|
export type ThinkingLevelType = (typeof THINKING_LEVEL)[keyof typeof THINKING_LEVEL];
|
||||||
(typeof THINKING_LEVEL)[keyof typeof THINKING_LEVEL];
|
|
||||||
export type FieldNumber = (typeof FIELD)[keyof typeof FIELD];
|
export type FieldNumber = (typeof FIELD)[keyof typeof FIELD];
|
||||||
|
|
||||||
/** Cursor tool definition */
|
/** Cursor tool definition */
|
||||||
export interface CursorTool {
|
export interface CursorTool {
|
||||||
function?: {
|
function?: {
|
||||||
name?: string;
|
name?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
parameters?: Record<string, unknown>;
|
parameters?: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
name?: string;
|
name?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
input_schema?: Record<string, unknown>;
|
input_schema?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Cursor tool result */
|
/** Cursor tool result */
|
||||||
export interface CursorToolResult {
|
export interface CursorToolResult {
|
||||||
tool_call_id?: string;
|
tool_call_id?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
index?: number;
|
index?: number;
|
||||||
raw_args?: string;
|
raw_args?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Cursor message format */
|
/** Cursor message format */
|
||||||
export interface CursorMessage {
|
export interface CursorMessage {
|
||||||
role: string;
|
role: string;
|
||||||
content: string;
|
content: string;
|
||||||
tool_results?: CursorToolResult[];
|
tool_results?: CursorToolResult[];
|
||||||
tool_calls?: Array<{
|
tool_calls?: Array<{
|
||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
function: {
|
function: {
|
||||||
name: string;
|
name: string;
|
||||||
arguments: string;
|
arguments: string;
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Formatted message for encoding */
|
/** Formatted message for encoding */
|
||||||
export interface FormattedMessage {
|
export interface FormattedMessage {
|
||||||
content: string;
|
content: string;
|
||||||
role: RoleType;
|
role: RoleType;
|
||||||
messageId: string;
|
messageId: string;
|
||||||
isLast: boolean;
|
isLast: boolean;
|
||||||
hasTools: boolean;
|
hasTools: boolean;
|
||||||
toolResults: CursorToolResult[];
|
toolResults: CursorToolResult[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Message ID structure */
|
/** Message ID structure */
|
||||||
export interface MessageId {
|
export interface MessageId {
|
||||||
messageId: string;
|
messageId: string;
|
||||||
role: RoleType;
|
role: RoleType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Compression flags for ConnectRPC frames */
|
/** Compression flags for ConnectRPC frames */
|
||||||
export const COMPRESS_FLAG = {
|
export const COMPRESS_FLAG = {
|
||||||
NONE: 0x00,
|
NONE: 0x00,
|
||||||
GZIP: 0x01,
|
GZIP: 0x01,
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
+146
-163
@@ -3,210 +3,193 @@
|
|||||||
* Exports encoder/decoder functions and builds complete requests
|
* Exports encoder/decoder functions and builds complete requests
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { randomUUID } from "crypto";
|
import { randomUUID } from 'crypto';
|
||||||
import {
|
import {
|
||||||
ROLE,
|
ROLE,
|
||||||
UNIFIED_MODE,
|
UNIFIED_MODE,
|
||||||
THINKING_LEVEL,
|
THINKING_LEVEL,
|
||||||
FIELD,
|
FIELD,
|
||||||
type CursorMessage,
|
type CursorMessage,
|
||||||
type CursorTool,
|
type CursorTool,
|
||||||
type FormattedMessage,
|
type FormattedMessage,
|
||||||
type MessageId,
|
type MessageId,
|
||||||
type ThinkingLevelType,
|
type ThinkingLevelType,
|
||||||
} from "./cursor-protobuf-schema.js";
|
} from './cursor-protobuf-schema.js';
|
||||||
import {
|
import {
|
||||||
encodeField,
|
encodeField,
|
||||||
encodeVarint,
|
encodeVarint,
|
||||||
encodeMessage,
|
encodeMessage,
|
||||||
encodeInstruction,
|
encodeInstruction,
|
||||||
encodeModel,
|
encodeModel,
|
||||||
encodeCursorSetting,
|
encodeCursorSetting,
|
||||||
encodeMetadata,
|
encodeMetadata,
|
||||||
encodeMessageId,
|
encodeMessageId,
|
||||||
encodeMcpTool,
|
encodeMcpTool,
|
||||||
wrapConnectRPCFrame,
|
wrapConnectRPCFrame,
|
||||||
} from "./cursor-protobuf-encoder.js";
|
} from './cursor-protobuf-encoder.js';
|
||||||
import {
|
import {
|
||||||
decodeVarint,
|
decodeVarint,
|
||||||
decodeField,
|
decodeField,
|
||||||
decodeMessage,
|
decodeMessage,
|
||||||
parseConnectRPCFrame,
|
parseConnectRPCFrame,
|
||||||
extractTextFromResponse,
|
extractTextFromResponse,
|
||||||
} from "./cursor-protobuf-decoder.js";
|
} from './cursor-protobuf-decoder.js';
|
||||||
import { WIRE_TYPE } from "./cursor-protobuf-schema.js";
|
import { WIRE_TYPE } from './cursor-protobuf-schema.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build complete chat request protobuf
|
* Build complete chat request protobuf
|
||||||
*/
|
*/
|
||||||
export function encodeRequest(
|
export function encodeRequest(
|
||||||
messages: CursorMessage[],
|
messages: CursorMessage[],
|
||||||
modelName: string,
|
modelName: string,
|
||||||
tools: CursorTool[] = [],
|
tools: CursorTool[] = [],
|
||||||
reasoningEffort: string | null = null
|
reasoningEffort: string | null = null
|
||||||
): Uint8Array {
|
): Uint8Array {
|
||||||
const hasTools = tools?.length > 0;
|
const hasTools = tools?.length > 0;
|
||||||
const isAgentic = hasTools;
|
const isAgentic = hasTools;
|
||||||
const formattedMessages: FormattedMessage[] = [];
|
const formattedMessages: FormattedMessage[] = [];
|
||||||
const messageIds: MessageId[] = [];
|
const messageIds: MessageId[] = [];
|
||||||
|
|
||||||
// Prepare messages
|
// Prepare messages
|
||||||
for (let i = 0; i < messages.length; i++) {
|
for (let i = 0; i < messages.length; i++) {
|
||||||
const msg = messages[i];
|
const msg = messages[i];
|
||||||
const role = msg.role === "user" ? ROLE.USER : ROLE.ASSISTANT;
|
const role = msg.role === 'user' ? ROLE.USER : ROLE.ASSISTANT;
|
||||||
const msgId = randomUUID();
|
const msgId = randomUUID();
|
||||||
const isLast = i === messages.length - 1;
|
const isLast = i === messages.length - 1;
|
||||||
|
|
||||||
formattedMessages.push({
|
formattedMessages.push({
|
||||||
content: msg.content,
|
content: msg.content,
|
||||||
role,
|
role,
|
||||||
messageId: msgId,
|
messageId: msgId,
|
||||||
isLast,
|
isLast,
|
||||||
hasTools,
|
hasTools,
|
||||||
toolResults: msg.tool_results || [],
|
toolResults: msg.tool_results || [],
|
||||||
});
|
});
|
||||||
|
|
||||||
messageIds.push({ messageId: msgId, role });
|
messageIds.push({ messageId: msgId, role });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map reasoning effort to thinking level
|
// Map reasoning effort to thinking level
|
||||||
let thinkingLevel: ThinkingLevelType = THINKING_LEVEL.UNSPECIFIED;
|
let thinkingLevel: ThinkingLevelType = THINKING_LEVEL.UNSPECIFIED;
|
||||||
if (reasoningEffort === "medium") thinkingLevel = THINKING_LEVEL.MEDIUM;
|
if (reasoningEffort === 'medium') thinkingLevel = THINKING_LEVEL.MEDIUM;
|
||||||
else if (reasoningEffort === "high") thinkingLevel = THINKING_LEVEL.HIGH;
|
else if (reasoningEffort === 'high') thinkingLevel = THINKING_LEVEL.HIGH;
|
||||||
|
|
||||||
// Build arrays for messages and tools
|
// Build arrays for messages and tools
|
||||||
const messageFields = formattedMessages.map((fm) =>
|
const messageFields = formattedMessages.map((fm) =>
|
||||||
encodeField(
|
encodeField(
|
||||||
FIELD.MESSAGES,
|
FIELD.MESSAGES,
|
||||||
WIRE_TYPE.LEN,
|
WIRE_TYPE.LEN,
|
||||||
encodeMessage(
|
encodeMessage(fm.content, fm.role, fm.messageId, fm.isLast, fm.hasTools, fm.toolResults)
|
||||||
fm.content,
|
)
|
||||||
fm.role,
|
);
|
||||||
fm.messageId,
|
|
||||||
fm.isLast,
|
|
||||||
fm.hasTools,
|
|
||||||
fm.toolResults
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const messageIdFields = messageIds.map((mid) =>
|
const messageIdFields = messageIds.map((mid) =>
|
||||||
encodeField(
|
encodeField(FIELD.MESSAGE_IDS, WIRE_TYPE.LEN, encodeMessageId(mid.messageId, mid.role))
|
||||||
FIELD.MESSAGE_IDS,
|
);
|
||||||
WIRE_TYPE.LEN,
|
|
||||||
encodeMessageId(mid.messageId, mid.role)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const toolFields =
|
const toolFields =
|
||||||
tools?.length > 0
|
tools?.length > 0
|
||||||
? tools.map((tool) =>
|
? tools.map((tool) => encodeField(FIELD.MCP_TOOLS, WIRE_TYPE.LEN, encodeMcpTool(tool)))
|
||||||
encodeField(FIELD.MCP_TOOLS, WIRE_TYPE.LEN, encodeMcpTool(tool))
|
: [];
|
||||||
)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const supportedToolsField = isAgentic
|
const supportedToolsField = isAgentic
|
||||||
? [encodeField(FIELD.SUPPORTED_TOOLS, WIRE_TYPE.LEN, encodeVarint(1))]
|
? [encodeField(FIELD.SUPPORTED_TOOLS, WIRE_TYPE.LEN, encodeVarint(1))]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
// Concatenate all parts
|
// Concatenate all parts
|
||||||
const parts: Uint8Array[] = [
|
const parts: Uint8Array[] = [
|
||||||
...messageFields,
|
...messageFields,
|
||||||
encodeField(FIELD.UNKNOWN_2, WIRE_TYPE.VARINT, 1),
|
encodeField(FIELD.UNKNOWN_2, WIRE_TYPE.VARINT, 1),
|
||||||
encodeField(FIELD.INSTRUCTION, WIRE_TYPE.LEN, encodeInstruction("")),
|
encodeField(FIELD.INSTRUCTION, WIRE_TYPE.LEN, encodeInstruction('')),
|
||||||
encodeField(FIELD.UNKNOWN_4, WIRE_TYPE.VARINT, 1),
|
encodeField(FIELD.UNKNOWN_4, WIRE_TYPE.VARINT, 1),
|
||||||
encodeField(FIELD.MODEL, WIRE_TYPE.LEN, encodeModel(modelName)),
|
encodeField(FIELD.MODEL, WIRE_TYPE.LEN, encodeModel(modelName)),
|
||||||
encodeField(FIELD.WEB_TOOL, WIRE_TYPE.LEN, ""),
|
encodeField(FIELD.WEB_TOOL, WIRE_TYPE.LEN, ''),
|
||||||
encodeField(FIELD.UNKNOWN_13, WIRE_TYPE.VARINT, 1),
|
encodeField(FIELD.UNKNOWN_13, WIRE_TYPE.VARINT, 1),
|
||||||
encodeField(FIELD.CURSOR_SETTING, WIRE_TYPE.LEN, encodeCursorSetting()),
|
encodeField(FIELD.CURSOR_SETTING, WIRE_TYPE.LEN, encodeCursorSetting()),
|
||||||
encodeField(FIELD.UNKNOWN_19, WIRE_TYPE.VARINT, 1),
|
encodeField(FIELD.UNKNOWN_19, WIRE_TYPE.VARINT, 1),
|
||||||
encodeField(FIELD.CONVERSATION_ID, WIRE_TYPE.LEN, randomUUID()),
|
encodeField(FIELD.CONVERSATION_ID, WIRE_TYPE.LEN, randomUUID()),
|
||||||
encodeField(FIELD.METADATA, WIRE_TYPE.LEN, encodeMetadata()),
|
encodeField(FIELD.METADATA, WIRE_TYPE.LEN, encodeMetadata()),
|
||||||
encodeField(FIELD.IS_AGENTIC, WIRE_TYPE.VARINT, isAgentic ? 1 : 0),
|
encodeField(FIELD.IS_AGENTIC, WIRE_TYPE.VARINT, isAgentic ? 1 : 0),
|
||||||
...supportedToolsField,
|
...supportedToolsField,
|
||||||
...messageIdFields,
|
...messageIdFields,
|
||||||
...toolFields,
|
...toolFields,
|
||||||
encodeField(FIELD.LARGE_CONTEXT, WIRE_TYPE.VARINT, 0),
|
encodeField(FIELD.LARGE_CONTEXT, WIRE_TYPE.VARINT, 0),
|
||||||
encodeField(FIELD.UNKNOWN_38, WIRE_TYPE.VARINT, 0),
|
encodeField(FIELD.UNKNOWN_38, WIRE_TYPE.VARINT, 0),
|
||||||
encodeField(
|
encodeField(
|
||||||
FIELD.UNIFIED_MODE,
|
FIELD.UNIFIED_MODE,
|
||||||
WIRE_TYPE.VARINT,
|
WIRE_TYPE.VARINT,
|
||||||
isAgentic ? UNIFIED_MODE.AGENT : UNIFIED_MODE.CHAT
|
isAgentic ? UNIFIED_MODE.AGENT : UNIFIED_MODE.CHAT
|
||||||
),
|
),
|
||||||
encodeField(FIELD.UNKNOWN_47, WIRE_TYPE.LEN, ""),
|
encodeField(FIELD.UNKNOWN_47, WIRE_TYPE.LEN, ''),
|
||||||
encodeField(FIELD.SHOULD_DISABLE_TOOLS, WIRE_TYPE.VARINT, isAgentic ? 0 : 1),
|
encodeField(FIELD.SHOULD_DISABLE_TOOLS, WIRE_TYPE.VARINT, isAgentic ? 0 : 1),
|
||||||
encodeField(FIELD.THINKING_LEVEL, WIRE_TYPE.VARINT, thinkingLevel),
|
encodeField(FIELD.THINKING_LEVEL, WIRE_TYPE.VARINT, thinkingLevel),
|
||||||
encodeField(FIELD.UNKNOWN_51, WIRE_TYPE.VARINT, 0),
|
encodeField(FIELD.UNKNOWN_51, WIRE_TYPE.VARINT, 0),
|
||||||
encodeField(FIELD.UNKNOWN_53, WIRE_TYPE.VARINT, 1),
|
encodeField(FIELD.UNKNOWN_53, WIRE_TYPE.VARINT, 1),
|
||||||
encodeField(
|
encodeField(FIELD.UNIFIED_MODE_NAME, WIRE_TYPE.LEN, isAgentic ? 'Agent' : 'Ask'),
|
||||||
FIELD.UNIFIED_MODE_NAME,
|
];
|
||||||
WIRE_TYPE.LEN,
|
|
||||||
isAgentic ? "Agent" : "Ask"
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
return concatArrays(...parts);
|
return concatArrays(...parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build chat request wrapped in top-level message
|
* Build chat request wrapped in top-level message
|
||||||
*/
|
*/
|
||||||
export function buildChatRequest(
|
export function buildChatRequest(
|
||||||
messages: CursorMessage[],
|
messages: CursorMessage[],
|
||||||
modelName: string,
|
modelName: string,
|
||||||
tools: CursorTool[] = [],
|
tools: CursorTool[] = [],
|
||||||
reasoningEffort: string | null = null
|
reasoningEffort: string | null = null
|
||||||
): Uint8Array {
|
): Uint8Array {
|
||||||
return encodeField(
|
return encodeField(
|
||||||
FIELD.REQUEST,
|
FIELD.REQUEST,
|
||||||
WIRE_TYPE.LEN,
|
WIRE_TYPE.LEN,
|
||||||
encodeRequest(messages, modelName, tools, reasoningEffort)
|
encodeRequest(messages, modelName, tools, reasoningEffort)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate complete Cursor request body with ConnectRPC framing
|
* Generate complete Cursor request body with ConnectRPC framing
|
||||||
*/
|
*/
|
||||||
export function generateCursorBody(
|
export function generateCursorBody(
|
||||||
messages: CursorMessage[],
|
messages: CursorMessage[],
|
||||||
modelName: string,
|
modelName: string,
|
||||||
tools: CursorTool[] = [],
|
tools: CursorTool[] = [],
|
||||||
reasoningEffort: string | null = null
|
reasoningEffort: string | null = null
|
||||||
): Uint8Array {
|
): Uint8Array {
|
||||||
const protobuf = buildChatRequest(messages, modelName, tools, reasoningEffort);
|
const protobuf = buildChatRequest(messages, modelName, tools, reasoningEffort);
|
||||||
const framed = wrapConnectRPCFrame(protobuf, false); // Cursor doesn't support compressed requests
|
const framed = wrapConnectRPCFrame(protobuf, false); // Cursor doesn't support compressed requests
|
||||||
return framed;
|
return framed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Concatenate multiple Uint8Arrays
|
* Concatenate multiple Uint8Arrays
|
||||||
*/
|
*/
|
||||||
function concatArrays(...arrays: Uint8Array[]): Uint8Array {
|
function concatArrays(...arrays: Uint8Array[]): Uint8Array {
|
||||||
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
|
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
|
||||||
const result = new Uint8Array(totalLength);
|
const result = new Uint8Array(totalLength);
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
for (const arr of arrays) {
|
for (const arr of arrays) {
|
||||||
result.set(arr, offset);
|
result.set(arr, offset);
|
||||||
offset += arr.length;
|
offset += arr.length;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-export all functions
|
// Re-export all functions
|
||||||
export {
|
export {
|
||||||
encodeVarint,
|
encodeVarint,
|
||||||
encodeField,
|
encodeField,
|
||||||
encodeMessage,
|
encodeMessage,
|
||||||
encodeInstruction,
|
encodeInstruction,
|
||||||
encodeModel,
|
encodeModel,
|
||||||
encodeCursorSetting,
|
encodeCursorSetting,
|
||||||
encodeMetadata,
|
encodeMetadata,
|
||||||
encodeMessageId,
|
encodeMessageId,
|
||||||
encodeMcpTool,
|
encodeMcpTool,
|
||||||
wrapConnectRPCFrame,
|
wrapConnectRPCFrame,
|
||||||
decodeVarint,
|
decodeVarint,
|
||||||
decodeField,
|
decodeField,
|
||||||
decodeMessage,
|
decodeMessage,
|
||||||
parseConnectRPCFrame,
|
parseConnectRPCFrame,
|
||||||
extractTextFromResponse,
|
extractTextFromResponse,
|
||||||
};
|
};
|
||||||
|
|||||||
+96
-100
@@ -3,30 +3,26 @@
|
|||||||
* Converts OpenAI messages to Cursor format
|
* Converts OpenAI messages to Cursor format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {
|
import type { CursorMessage, CursorToolResult, CursorTool } from './cursor-protobuf-schema.js';
|
||||||
CursorMessage,
|
|
||||||
CursorToolResult,
|
|
||||||
CursorTool,
|
|
||||||
} from "./cursor-protobuf-schema.js";
|
|
||||||
|
|
||||||
/** OpenAI message format */
|
/** OpenAI message format */
|
||||||
interface OpenAIMessage {
|
interface OpenAIMessage {
|
||||||
role: string;
|
role: string;
|
||||||
content: string | Array<{ type: string; text?: string }>;
|
content: string | Array<{ type: string; text?: string }>;
|
||||||
name?: string;
|
name?: string;
|
||||||
tool_call_id?: string;
|
tool_call_id?: string;
|
||||||
tool_calls?: Array<{
|
tool_calls?: Array<{
|
||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
function: { name: string; arguments: string };
|
function: { name: string; arguments: string };
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** OpenAI request body */
|
/** OpenAI request body */
|
||||||
interface OpenAIRequestBody {
|
interface OpenAIRequestBody {
|
||||||
messages: OpenAIMessage[];
|
messages: OpenAIMessage[];
|
||||||
tools?: CursorTool[];
|
tools?: CursorTool[];
|
||||||
reasoning_effort?: string;
|
reasoning_effort?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,91 +32,91 @@ interface OpenAIRequestBody {
|
|||||||
* - assistant with tool_calls → keep tool_calls structure (Cursor supports it natively)
|
* - assistant with tool_calls → keep tool_calls structure (Cursor supports it natively)
|
||||||
*/
|
*/
|
||||||
function convertMessages(messages: OpenAIMessage[]): CursorMessage[] {
|
function convertMessages(messages: OpenAIMessage[]): CursorMessage[] {
|
||||||
const result: CursorMessage[] = [];
|
const result: CursorMessage[] = [];
|
||||||
let pendingToolResults: CursorToolResult[] = [];
|
let pendingToolResults: CursorToolResult[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < messages.length; i++) {
|
for (let i = 0; i < messages.length; i++) {
|
||||||
const msg = messages[i];
|
const msg = messages[i];
|
||||||
|
|
||||||
if (msg.role === "system") {
|
if (msg.role === 'system') {
|
||||||
result.push({
|
result.push({
|
||||||
role: "user",
|
role: 'user',
|
||||||
content: `[System Instructions]\n${msg.content}`,
|
content: `[System Instructions]\n${msg.content}`,
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.role === "tool") {
|
if (msg.role === 'tool') {
|
||||||
let toolContent = "";
|
let toolContent = '';
|
||||||
if (typeof msg.content === "string") {
|
if (typeof msg.content === 'string') {
|
||||||
toolContent = msg.content;
|
toolContent = msg.content;
|
||||||
} else if (Array.isArray(msg.content)) {
|
} else if (Array.isArray(msg.content)) {
|
||||||
for (const part of msg.content) {
|
for (const part of msg.content) {
|
||||||
if (part.type === "text" && part.text) {
|
if (part.type === 'text' && part.text) {
|
||||||
toolContent += part.text;
|
toolContent += part.text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const toolName = msg.name || "tool";
|
const toolName = msg.name || 'tool';
|
||||||
const toolCallId = msg.tool_call_id || "";
|
const toolCallId = msg.tool_call_id || '';
|
||||||
|
|
||||||
// Accumulate tool result
|
// Accumulate tool result
|
||||||
pendingToolResults.push({
|
pendingToolResults.push({
|
||||||
tool_call_id: toolCallId,
|
tool_call_id: toolCallId,
|
||||||
name: toolName,
|
name: toolName,
|
||||||
index: pendingToolResults.length,
|
index: pendingToolResults.length,
|
||||||
raw_args: toolContent,
|
raw_args: toolContent,
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.role === "user" || msg.role === "assistant") {
|
if (msg.role === 'user' || msg.role === 'assistant') {
|
||||||
let content = "";
|
let content = '';
|
||||||
|
|
||||||
if (typeof msg.content === "string") {
|
if (typeof msg.content === 'string') {
|
||||||
content = msg.content;
|
content = msg.content;
|
||||||
} else if (Array.isArray(msg.content)) {
|
} else if (Array.isArray(msg.content)) {
|
||||||
for (const part of msg.content) {
|
for (const part of msg.content) {
|
||||||
if (part.type === "text" && part.text) {
|
if (part.type === 'text' && part.text) {
|
||||||
content += part.text;
|
content += part.text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep tool_calls structure for assistant messages
|
// Keep tool_calls structure for assistant messages
|
||||||
if (msg.role === "assistant" && msg.tool_calls && msg.tool_calls.length > 0) {
|
if (msg.role === 'assistant' && msg.tool_calls && msg.tool_calls.length > 0) {
|
||||||
const assistantMsg: CursorMessage = { role: "assistant", content: "" };
|
const assistantMsg: CursorMessage = { role: 'assistant', content: '' };
|
||||||
if (content) {
|
if (content) {
|
||||||
assistantMsg.content = content;
|
assistantMsg.content = content;
|
||||||
}
|
}
|
||||||
assistantMsg.tool_calls = msg.tool_calls;
|
assistantMsg.tool_calls = msg.tool_calls;
|
||||||
|
|
||||||
// Attach pending tool results to assistant message with tool_calls
|
// Attach pending tool results to assistant message with tool_calls
|
||||||
if (pendingToolResults.length > 0) {
|
if (pendingToolResults.length > 0) {
|
||||||
assistantMsg.tool_results = pendingToolResults;
|
assistantMsg.tool_results = pendingToolResults;
|
||||||
pendingToolResults = [];
|
pendingToolResults = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push(assistantMsg);
|
result.push(assistantMsg);
|
||||||
} else if (content || pendingToolResults.length > 0) {
|
} else if (content || pendingToolResults.length > 0) {
|
||||||
const msgObj: CursorMessage = {
|
const msgObj: CursorMessage = {
|
||||||
role: msg.role,
|
role: msg.role,
|
||||||
content: content || "",
|
content: content || '',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Attach pending tool results to this message
|
// Attach pending tool results to this message
|
||||||
if (pendingToolResults.length > 0) {
|
if (pendingToolResults.length > 0) {
|
||||||
msgObj.tool_results = pendingToolResults;
|
msgObj.tool_results = pendingToolResults;
|
||||||
pendingToolResults = [];
|
pendingToolResults = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push(msgObj);
|
result.push(msgObj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -128,18 +124,18 @@ function convertMessages(messages: OpenAIMessage[]): CursorMessage[] {
|
|||||||
* Returns modified body with converted messages
|
* Returns modified body with converted messages
|
||||||
*/
|
*/
|
||||||
export function buildCursorRequest(
|
export function buildCursorRequest(
|
||||||
model: string,
|
model: string,
|
||||||
body: OpenAIRequestBody,
|
body: OpenAIRequestBody,
|
||||||
stream: boolean,
|
stream: boolean,
|
||||||
credentials: unknown
|
credentials: unknown
|
||||||
): {
|
): {
|
||||||
messages: CursorMessage[];
|
messages: CursorMessage[];
|
||||||
tools?: CursorTool[];
|
tools?: CursorTool[];
|
||||||
} {
|
} {
|
||||||
const messages = convertMessages(body.messages || []);
|
const messages = convertMessages(body.messages || []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...body,
|
...body,
|
||||||
messages,
|
messages,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-6
@@ -8,12 +8,7 @@
|
|||||||
export * from './types';
|
export * from './types';
|
||||||
|
|
||||||
// Auth
|
// Auth
|
||||||
export {
|
export { autoDetectTokens, saveCredentials, loadCredentials, checkAuthStatus } from './cursor-auth';
|
||||||
autoDetectTokens,
|
|
||||||
saveCredentials,
|
|
||||||
loadCredentials,
|
|
||||||
checkAuthStatus,
|
|
||||||
} from './cursor-auth';
|
|
||||||
|
|
||||||
// Daemon
|
// Daemon
|
||||||
export { isDaemonRunning, getDaemonStatus, startDaemon, stopDaemon } from './cursor-daemon';
|
export { isDaemonRunning, getDaemonStatus, startDaemon, stopDaemon } from './cursor-daemon';
|
||||||
|
|||||||
Reference in New Issue
Block a user