diff --git a/open-sse/utils/cursorAgentProtobuf.ts b/open-sse/utils/cursorAgentProtobuf.ts index 24cee2332b..bb17d9c944 100644 --- a/open-sse/utils/cursorAgentProtobuf.ts +++ b/open-sse/utils/cursorAgentProtobuf.ts @@ -18,6 +18,25 @@ import zlib from "node:zlib"; import crypto from "node:crypto"; +import { + WT_VARINT, + WT_LEN, + encodeVarint, + encodeTag, + encodeBytes, + encodeString, + encodeMessage, + encodeUInt32Field, + encodeBoolField, + encodeDoubleField, + decodeVarint, + checkedLen, + decodeFields, + findField, + decodeStringField, + decodeVarintField, + type Field, +} from "./cursorAgentProtobuf/wire.ts"; // ─── Field numbers (from agent.proto descriptor) ─────────────────────────── @@ -225,125 +244,6 @@ const LIST_VALUES = 1; // ListValue.values = repeated Value const MAP_KEY = 1; const MAP_VALUE = 2; -// ─── Wire-type constants ─────────────────────────────────────────────────── - -const WT_VARINT = 0; -const WT_LEN = 2; - -// ─── Primitive encoders ──────────────────────────────────────────────────── - -function encodeVarint(value: number | bigint): Buffer { - let v = typeof value === "bigint" ? value : BigInt(value); - const bytes: number[] = []; - while (v > 0x7fn) { - bytes.push(Number(v & 0x7fn) | 0x80); - v >>= 7n; - } - bytes.push(Number(v)); - return Buffer.from(bytes); -} - -function encodeTag(fieldNumber: number, wireType: number): Buffer { - return encodeVarint((fieldNumber << 3) | wireType); -} - -function encodeBytes(fieldNumber: number, value: Buffer | Uint8Array): Buffer { - const buf = Buffer.isBuffer(value) ? value : Buffer.from(value); - return Buffer.concat([encodeTag(fieldNumber, WT_LEN), encodeVarint(buf.length), buf]); -} - -function encodeString(fieldNumber: number, value: string): Buffer { - return encodeBytes(fieldNumber, Buffer.from(value, "utf8")); -} - -function encodeMessage(fieldNumber: number, parts: Buffer[]): Buffer { - const inner = Buffer.concat(parts); - return Buffer.concat([encodeTag(fieldNumber, WT_LEN), encodeVarint(inner.length), inner]); -} - -function encodeUInt32Field(fieldNumber: number, value: number): Buffer { - return Buffer.concat([encodeTag(fieldNumber, WT_VARINT), encodeVarint(value)]); -} - -function encodeBoolField(fieldNumber: number, value: boolean): Buffer { - return Buffer.concat([encodeTag(fieldNumber, WT_VARINT), encodeVarint(value ? 1 : 0)]); -} - -function encodeDoubleField(fieldNumber: number, value: number): Buffer { - // wire type 1 = 64-bit fixed (double) - const buf = Buffer.alloc(8); - buf.writeDoubleLE(value, 0); - return Buffer.concat([encodeTag(fieldNumber, 1), buf]); -} - -// ─── Primitive decoders ──────────────────────────────────────────────────── - -function decodeVarint(buf: Buffer, offset: number): [bigint, number] { - let result = 0n; - let shift = 0n; - let pos = offset; - while (pos < buf.length) { - const byte = buf[pos++]; - result |= BigInt(byte & 0x7f) << shift; - if ((byte & 0x80) === 0) return [result, pos]; - shift += 7n; - } - throw new Error("varint truncated"); -} - -type Field = - | { fieldNumber: number; wireType: 0; varint: bigint } - | { fieldNumber: number; wireType: 2; bytes: Buffer }; - -/** - * Validate a length-delimited field's declared length against the bytes that - * actually remain in the buffer. Cursor's frames are well-formed, but a - * corrupted or hostile upstream could declare a length that overruns the - * buffer; without this guard `Buffer.subarray` silently clamps to EOF and a - * truncated tool argument (or any nested message) is decoded as empty/partial - * data instead of being recognized as malformed. Throwing lets the caller — - * `processFrame`, wrapped in driveH2's per-frame try/catch — skip the bad - * frame rather than act on corrupted fields. Also rejects absurd lengths that - * would not fit a JS safe integer. - */ -function checkedLen(len: bigint, pos: number, buf: Buffer): number { - if (len < 0n || len > BigInt(buf.length - pos)) { - throw new Error( - `length-delimited field overruns buffer (len=${len}, remaining=${buf.length - pos})` - ); - } - return Number(len); -} - -function decodeFields(buf: Buffer): Field[] { - const fields: Field[] = []; - let pos = 0; - while (pos < buf.length) { - const [tag, np] = decodeVarint(buf, pos); - pos = np; - const fieldNumber = Number(tag >> 3n); - const wireType = Number(tag & 0x7n); - if (wireType === WT_VARINT) { - const [v, np2] = decodeVarint(buf, pos); - pos = np2; - fields.push({ fieldNumber, wireType: 0, varint: v }); - } else if (wireType === WT_LEN) { - const [len, np2] = decodeVarint(buf, pos); - pos = np2; - const lenN = checkedLen(len, pos, buf); - fields.push({ fieldNumber, wireType: 2, bytes: buf.subarray(pos, pos + lenN) }); - pos += lenN; - } else if (wireType === 5) { - pos += 4; - } else if (wireType === 1) { - pos += 8; - } else { - throw new Error(`unsupported wireType ${wireType}`); - } - } - return fields; -} - // ─── Connect-RPC framing ─────────────────────────────────────────────────── const FLAG_NONE = 0x00; @@ -547,9 +447,7 @@ export function encodeAgentRunRequest(input: AgentRunInput): Buffer { const selectedContextParts: Buffer[] = []; if (input.images && input.images.length > 0) { for (const img of input.images) { - selectedContextParts.push( - encodeMessage(SC_SELECTED_IMAGES, [encodeSelectedImageBody(img)]) - ); + selectedContextParts.push(encodeMessage(SC_SELECTED_IMAGES, [encodeSelectedImageBody(img)])); } } // The empty selected_context placeholder and mode=1 match cursor-agent's @@ -652,24 +550,6 @@ export type DecodedDelta = | { kind: "kv_server_message" } | { kind: "unknown"; field: number }; -function findField(fields: Field[], fieldNumber: number): Field | undefined { - return fields.find((f) => f.fieldNumber === fieldNumber); -} - -function decodeStringField(buf: Buffer, fieldNumber: number): string { - const fields = decodeFields(buf); - const f = findField(fields, fieldNumber); - if (f && f.wireType === 2) return f.bytes.toString("utf8"); - return ""; -} - -function decodeVarintField(buf: Buffer, fieldNumber: number): number { - const fields = decodeFields(buf); - const f = findField(fields, fieldNumber); - if (f && f.wireType === 0) return Number(f.varint); - return 0; -} - export function decodeAgentServerMessage(payload: Buffer): DecodedDelta[] { const out: DecodedDelta[] = []; for (const top of decodeFields(payload)) { diff --git a/open-sse/utils/cursorAgentProtobuf/wire.ts b/open-sse/utils/cursorAgentProtobuf/wire.ts new file mode 100644 index 0000000000..bd2d0a9697 --- /dev/null +++ b/open-sse/utils/cursorAgentProtobuf/wire.ts @@ -0,0 +1,143 @@ +// Low-level protobuf wire-format primitives for the Cursor Agent codec, extracted +// verbatim from ../cursorAgentProtobuf.ts (god-file decomposition). Pure and +// dependency-free (Buffer only): varint/tag/length-delimited encode+decode and the +// generic field walker. Framing, the value codec, and the message encoders/decoders +// all build on this layer. Nothing here was part of the module's public API, so the +// host imports these back internally (no re-export). + +// ─── Wire-type constants ─────────────────────────────────────────────────── + +export const WT_VARINT = 0; +export const WT_LEN = 2; + +// ─── Primitive encoders ──────────────────────────────────────────────────── + +export function encodeVarint(value: number | bigint): Buffer { + let v = typeof value === "bigint" ? value : BigInt(value); + const bytes: number[] = []; + while (v > 0x7fn) { + bytes.push(Number(v & 0x7fn) | 0x80); + v >>= 7n; + } + bytes.push(Number(v)); + return Buffer.from(bytes); +} + +export function encodeTag(fieldNumber: number, wireType: number): Buffer { + return encodeVarint((fieldNumber << 3) | wireType); +} + +export function encodeBytes(fieldNumber: number, value: Buffer | Uint8Array): Buffer { + const buf = Buffer.isBuffer(value) ? value : Buffer.from(value); + return Buffer.concat([encodeTag(fieldNumber, WT_LEN), encodeVarint(buf.length), buf]); +} + +export function encodeString(fieldNumber: number, value: string): Buffer { + return encodeBytes(fieldNumber, Buffer.from(value, "utf8")); +} + +export function encodeMessage(fieldNumber: number, parts: Buffer[]): Buffer { + const inner = Buffer.concat(parts); + return Buffer.concat([encodeTag(fieldNumber, WT_LEN), encodeVarint(inner.length), inner]); +} + +export function encodeUInt32Field(fieldNumber: number, value: number): Buffer { + return Buffer.concat([encodeTag(fieldNumber, WT_VARINT), encodeVarint(value)]); +} + +export function encodeBoolField(fieldNumber: number, value: boolean): Buffer { + return Buffer.concat([encodeTag(fieldNumber, WT_VARINT), encodeVarint(value ? 1 : 0)]); +} + +export function encodeDoubleField(fieldNumber: number, value: number): Buffer { + // wire type 1 = 64-bit fixed (double) + const buf = Buffer.alloc(8); + buf.writeDoubleLE(value, 0); + return Buffer.concat([encodeTag(fieldNumber, 1), buf]); +} + +// ─── Primitive decoders ──────────────────────────────────────────────────── + +export function decodeVarint(buf: Buffer, offset: number): [bigint, number] { + let result = 0n; + let shift = 0n; + let pos = offset; + while (pos < buf.length) { + const byte = buf[pos++]; + result |= BigInt(byte & 0x7f) << shift; + if ((byte & 0x80) === 0) return [result, pos]; + shift += 7n; + } + throw new Error("varint truncated"); +} + +export type Field = + | { fieldNumber: number; wireType: 0; varint: bigint } + | { fieldNumber: number; wireType: 2; bytes: Buffer }; + +/** + * Validate a length-delimited field's declared length against the bytes that + * actually remain in the buffer. Cursor's frames are well-formed, but a + * corrupted or hostile upstream could declare a length that overruns the + * buffer; without this guard `Buffer.subarray` silently clamps to EOF and a + * truncated tool argument (or any nested message) is decoded as empty/partial + * data instead of being recognized as malformed. Throwing lets the caller — + * `processFrame`, wrapped in driveH2's per-frame try/catch — skip the bad + * frame rather than act on corrupted fields. Also rejects absurd lengths that + * would not fit a JS safe integer. + */ +export function checkedLen(len: bigint, pos: number, buf: Buffer): number { + if (len < 0n || len > BigInt(buf.length - pos)) { + throw new Error( + `length-delimited field overruns buffer (len=${len}, remaining=${buf.length - pos})` + ); + } + return Number(len); +} + +export function decodeFields(buf: Buffer): Field[] { + const fields: Field[] = []; + let pos = 0; + while (pos < buf.length) { + const [tag, np] = decodeVarint(buf, pos); + pos = np; + const fieldNumber = Number(tag >> 3n); + const wireType = Number(tag & 0x7n); + if (wireType === WT_VARINT) { + const [v, np2] = decodeVarint(buf, pos); + pos = np2; + fields.push({ fieldNumber, wireType: 0, varint: v }); + } else if (wireType === WT_LEN) { + const [len, np2] = decodeVarint(buf, pos); + pos = np2; + const lenN = checkedLen(len, pos, buf); + fields.push({ fieldNumber, wireType: 2, bytes: buf.subarray(pos, pos + lenN) }); + pos += lenN; + } else if (wireType === 5) { + pos += 4; + } else if (wireType === 1) { + pos += 8; + } else { + throw new Error(`unsupported wireType ${wireType}`); + } + } + return fields; +} + +export function findField(fields: Field[], fieldNumber: number): Field | undefined { + return fields.find((f) => f.fieldNumber === fieldNumber); +} + +export function decodeStringField(buf: Buffer, fieldNumber: number): string { + const fields = decodeFields(buf); + const f = findField(fields, fieldNumber); + if (f && f.wireType === 2) return f.bytes.toString("utf8"); + return ""; +} + +export function decodeVarintField(buf: Buffer, fieldNumber: number): number { + const fields = decodeFields(buf); + const f = findField(fields, fieldNumber); + if (f && f.wireType === 0) return Number(f.varint); + return 0; +} diff --git a/tests/unit/cursor-protobuf-wire-split.test.ts b/tests/unit/cursor-protobuf-wire-split.test.ts new file mode 100644 index 0000000000..384114807a --- /dev/null +++ b/tests/unit/cursor-protobuf-wire-split.test.ts @@ -0,0 +1,83 @@ +// Split-guard for the cursorAgentProtobuf wire-primitive extraction (god-file +// decomposition): the low-level protobuf wire codec (varint/tag/length-delimited +// encode+decode + the generic field walker) moved verbatim from cursorAgentProtobuf.ts +// into cursorAgentProtobuf/wire.ts. These primitives were module-private, so the host's +// public API is unchanged; the host imports them back internally. The locks pin the +// leaf's surface, the encode↔decode round-trip invariants, the overrun guard, and that +// the host now imports the wire leaf instead of defining the primitives inline. +import { test } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; + +import * as wire from "../../open-sse/utils/cursorAgentProtobuf/wire.ts"; + +test("wire leaf exposes the primitives and wire-type constants", () => { + assert.equal(wire.WT_VARINT, 0); + assert.equal(wire.WT_LEN, 2); + for (const fn of [ + "encodeVarint", + "encodeTag", + "encodeBytes", + "encodeString", + "encodeMessage", + "encodeUInt32Field", + "encodeBoolField", + "encodeDoubleField", + "decodeVarint", + "checkedLen", + "decodeFields", + "findField", + "decodeStringField", + "decodeVarintField", + ]) { + assert.equal( + typeof (wire as Record)[fn], + "function", + `${fn} must be exported` + ); + } +}); + +test("varint round-trips across byte-boundaries and bigints", () => { + for (const n of [0, 1, 127, 128, 300, 16384, 2 ** 31]) { + const [decoded, next] = wire.decodeVarint(wire.encodeVarint(n), 0); + assert.equal(decoded, BigInt(n), `varint ${n}`); + assert.equal(next, wire.encodeVarint(n).length); + } + const big = 9007199254740993n; // > Number.MAX_SAFE_INTEGER + assert.equal(wire.decodeVarint(wire.encodeVarint(big), 0)[0], big); +}); + +test("string / uint32 / bool fields round-trip through decodeFields", () => { + assert.equal(wire.decodeStringField(wire.encodeString(1, "héllo"), 1), "héllo"); + assert.equal(wire.decodeStringField(wire.encodeString(3, ""), 3), ""); + assert.equal(wire.decodeVarintField(wire.encodeUInt32Field(2, 42), 2), 42); + assert.equal(wire.decodeVarintField(wire.encodeBoolField(4, true), 4), 1); + assert.equal(wire.decodeVarintField(wire.encodeBoolField(4, false), 4), 0); +}); + +test("decodeFields tags length-delimited vs varint fields and encodeMessage nests", () => { + const nested = wire.encodeMessage(5, [wire.encodeString(1, "x"), wire.encodeUInt32Field(2, 7)]); + const [outer] = wire.decodeFields(nested); + assert.equal(outer.fieldNumber, 5); + assert.equal(outer.wireType, 2); + if (outer.wireType === 2) { + const inner = wire.decodeFields(outer.bytes); + assert.equal(inner.length, 2); + assert.equal(wire.decodeStringField(outer.bytes, 1), "x"); + assert.equal(wire.decodeVarintField(outer.bytes, 2), 7); + } +}); + +test("checkedLen rejects a length that overruns the buffer", () => { + assert.throws(() => wire.checkedLen(5n, 0, Buffer.alloc(3)), /overruns buffer/); + assert.equal(wire.checkedLen(3n, 0, Buffer.alloc(3)), 3); +}); + +test("host imports the wire leaf and no longer defines the primitives inline", () => { + const host = fs.readFileSync(path.join("open-sse", "utils", "cursorAgentProtobuf.ts"), "utf-8"); + assert.match(host, /from "\.\/cursorAgentProtobuf\/wire\.ts"/); + assert.doesNotMatch(host, /^function encodeVarint\(/m, "encodeVarint must live in the wire leaf"); + assert.doesNotMatch(host, /^function decodeFields\(/m, "decodeFields must live in the wire leaf"); +});