Files
OmniRoute/tests/unit/cursor-agent-protobuf.test.ts
Makcim Ivanov c9be34a870 fix(cursor): bridge native TodoWrite completions (#8432)
Co-authored-by: Makcim Ivanov <10184529+makcimbx@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
2026-07-26 12:11:31 -03:00

754 lines
29 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import {
resolveRequestedModel,
normalizeCursorModelId,
encodeAgentRunRequest,
buildAgentRequestBody,
iterateConnectFrames,
decodeAgentServerMessage,
flattenMessages,
wrapConnectFrame,
encodeExecReadRejected,
encodeExecWriteRejected,
encodeExecDeleteRejected,
encodeExecLsRejected,
encodeExecShellRejected,
encodeExecBackgroundShellSpawnRejected,
encodeExecGrepError,
encodeExecFetchError,
encodeExecWriteShellStdinError,
encodeExecDiagnosticsResult,
encodeExecMcpResult,
encodeExecMcpError,
encodeKvGetBlobResult,
encodeKvSetBlobResult,
encodeMcpToolDefinitionBody,
jsonSchemaToProtobufValue,
encodeRequestContextResponse,
openAIToolsToMcpDefs,
} from "../../open-sse/utils/cursorAgentProtobuf";
test("resolveRequestedModel maps cursor-agent's client-side aliases", () => {
assert.deepEqual(resolveRequestedModel("auto"), { modelId: "default", parameters: [] });
assert.deepEqual(resolveRequestedModel("composer-2-fast"), {
modelId: "composer-2",
parameters: [{ id: "fast", value: "true" }],
});
// #7289: pinned Claude ids with an effort suffix split into the base id +
// an "effort" ModelParameter — cursor's server has no route for the
// suffixed id verbatim (see cursor-model-effort-suffix-7289.test.ts).
assert.deepEqual(resolveRequestedModel("claude-4.6-sonnet-medium"), {
modelId: "claude-4.6-sonnet",
parameters: [{ id: "effort", value: "medium" }],
});
assert.deepEqual(resolveRequestedModel("composer-2"), { modelId: "composer-2", parameters: [] });
});
test("normalizeCursorModelId canonicalizes composer spelling variants", () => {
// Known-equivalent spellings cursor would otherwise reject.
assert.equal(normalizeCursorModelId("composer-2-5"), "composer-2.5");
assert.equal(normalizeCursorModelId("composer-2.5-sdk"), "composer-2.5");
assert.equal(normalizeCursorModelId("composer-latest"), "composer-2.5");
assert.equal(normalizeCursorModelId("COMPOSER-2-5"), "composer-2.5"); // case-insensitive
assert.equal(normalizeCursorModelId(" composer-latest "), "composer-2.5"); // trimmed
assert.equal(normalizeCursorModelId(""), "composer-2.5"); // empty → default model
assert.equal(normalizeCursorModelId("composer-2-5-fast"), "composer-2.5-fast");
// Canonical and unrelated ids pass through verbatim (no behavior change).
assert.equal(normalizeCursorModelId("composer-2.5"), "composer-2.5");
assert.equal(normalizeCursorModelId("composer-2.5-fast"), "composer-2.5-fast");
assert.equal(normalizeCursorModelId("auto"), "auto");
assert.equal(normalizeCursorModelId("claude-4.6-sonnet-medium"), "claude-4.6-sonnet-medium");
});
test("resolveRequestedModel normalizes variants then applies auto/-fast rules", () => {
// Variant of composer-2.5 → canonical id, no parameters.
assert.deepEqual(resolveRequestedModel("composer-2-5"), {
modelId: "composer-2.5",
parameters: [],
});
// Variant of the fast model → split into base id + fast parameter.
assert.deepEqual(resolveRequestedModel("composer-2-5-fast"), {
modelId: "composer-2.5",
parameters: [{ id: "fast", value: "true" }],
});
// Empty model id resolves to the working default rather than a server reject.
assert.deepEqual(resolveRequestedModel(""), { modelId: "composer-2.5", parameters: [] });
});
// ─── decode bounds hardening (malformed/hostile wire data) ──────────────────
test("decodeAgentServerMessage throws on a length-delimited field that overruns the buffer", () => {
function v(n: number): Buffer {
const out: number[] = [];
while (n > 0x7f) {
out.push((n & 0x7f) | 0x80);
n >>>= 7;
}
out.push(n);
return Buffer.from(out);
}
function tag(field: number, wt: number) {
return v((field << 3) | wt);
}
// field 1 (LEN) declaring length 200, but only 3 payload bytes follow.
// Before hardening Buffer.subarray silently clamped to EOF, decoding a
// truncated message as if it were complete; now checkedLen rejects it so
// processFrame skips the corrupt frame instead of acting on partial data.
const malformed = Buffer.concat([tag(1, 2), v(200), Buffer.from([1, 2, 3])]);
assert.throws(() => decodeAgentServerMessage(malformed), /overruns buffer/);
});
test("decode bounds hardening does not affect well-formed frames", () => {
// A correctly-sized frame still decodes (regression guard for the new check).
function v(n: number): Buffer {
const out: number[] = [];
while (n > 0x7f) {
out.push((n & 0x7f) | 0x80);
n >>>= 7;
}
out.push(n);
return Buffer.from(out);
}
function tag(field: number, wt: number) {
return v((field << 3) | wt);
}
function lp(field: number, payload: Buffer) {
return Buffer.concat([tag(field, 2), v(payload.length), payload]);
}
const tdu = lp(1, Buffer.from("ok", "utf8"));
const iu = lp(1, tdu);
const asm = lp(1, iu);
assert.deepEqual(decodeAgentServerMessage(asm), [{ kind: "text", text: "ok" }]);
});
test("decodeAgentServerMessage preserves a native TodoWrite completion", () => {
function v(n: number): Buffer {
const out: number[] = [];
while (n > 0x7f) {
out.push((n & 0x7f) | 0x80);
n >>>= 7;
}
out.push(n);
return Buffer.from(out);
}
function tag(field: number, wireType: number): Buffer {
return v((field << 3) | wireType);
}
function lp(field: number, payload: Buffer): Buffer {
return Buffer.concat([tag(field, 2), v(payload.length), payload]);
}
function s(field: number, value: string): Buffer {
return lp(field, Buffer.from(value, "utf8"));
}
function vi(field: number, value: number): Buffer {
return Buffer.concat([tag(field, 0), v(value)]);
}
function todo(content: string, status: number): Buffer {
// TodoItem { content (2), status (3) }
return lp(1, Buffer.concat([s(2, content), vi(3, status)]));
}
// ToolCallCompletedUpdate {
// tool_call_id (1),
// tool_call (2): ToolCall {
// todo_write (9): TodoWriteDetails {
// args (1): TodoWriteArgs { todos (1 repeated), merge (2) }
// }
// }
// }
const todoArgs = Buffer.concat([todo("Inspect files", 3), todo("Write report", 2), vi(2, 0)]);
const todoDetails = lp(1, todoArgs);
const toolCall = lp(9, todoDetails);
const completed = Buffer.concat([s(1, "toolu_todo_1"), lp(2, toolCall)]);
const interactionUpdate = lp(3, completed);
const asm = lp(1, interactionUpdate);
assert.deepEqual(decodeAgentServerMessage(asm), [
{
kind: "native_todo_write",
toolCallId: "toolu_todo_1",
merge: false,
todos: [
{ content: "Inspect files", status: "completed" },
{ content: "Write report", status: "in_progress" },
],
},
{ kind: "tool_call_completed" },
]);
});
test("native TodoWrite decoding rejects duplicate singular fields and malformed IDs", () => {
function v(n: number): Buffer {
const out: number[] = [];
while (n > 0x7f) {
out.push((n & 0x7f) | 0x80);
n >>>= 7;
}
out.push(n);
return Buffer.from(out);
}
function tag(field: number, wireType: number): Buffer {
return v((field << 3) | wireType);
}
function lp(field: number, payload: Buffer): Buffer {
return Buffer.concat([tag(field, 2), v(payload.length), payload]);
}
function s(field: number, value: string): Buffer {
return lp(field, Buffer.from(value, "utf8"));
}
function vi(field: number, value: number): Buffer {
return Buffer.concat([tag(field, 0), v(value)]);
}
function todoItem(parts: Buffer[]): Buffer {
return lp(1, Buffer.concat(parts));
}
function completed(idFields: Buffer[], todoWriteDetails: Buffer): Buffer {
const toolCall = lp(9, todoWriteDetails);
return lp(1, lp(3, Buffer.concat([...idFields, lp(2, toolCall)])));
}
const genericOnly = [{ kind: "tool_call_completed" }];
const completeArgs = Buffer.concat([todoItem([s(2, "Keep me"), vi(3, 3)]), vi(2, 1)]);
const duplicateArgsExploit = completed(
[s(1, "todo-1")],
Buffer.concat([lp(1, Buffer.alloc(0)), lp(1, completeArgs)])
);
assert.deepEqual(decodeAgentServerMessage(duplicateArgsExploit), genericOnly);
const duplicateStatus = completed(
[s(1, "todo-2")],
lp(1, todoItem([s(2, "Keep me"), vi(3, 1), vi(3, 99)]))
);
assert.deepEqual(decodeAgentServerMessage(duplicateStatus), genericOnly);
const duplicateMerge = completed(
[s(1, "todo-3")],
lp(1, Buffer.concat([todoItem([s(2, "Keep me"), vi(3, 3)]), vi(2, 0), vi(2, 1)]))
);
assert.deepEqual(decodeAgentServerMessage(duplicateMerge), genericOnly);
const duplicateContent = completed(
[s(1, "todo-4")],
lp(1, todoItem([s(2, "First"), s(2, "Second"), vi(3, 3)]))
);
assert.deepEqual(decodeAgentServerMessage(duplicateContent), genericOnly);
const emptyId = completed([s(1, "")], lp(1, completeArgs));
assert.deepEqual(decodeAgentServerMessage(emptyId), genericOnly);
const duplicateId = completed([s(1, "todo-5"), s(1, "todo-6")], lp(1, completeArgs));
assert.deepEqual(decodeAgentServerMessage(duplicateId), genericOnly);
const invalidUtf8Id = completed([lp(1, Buffer.from([0xff]))], lp(1, completeArgs));
assert.deepEqual(decodeAgentServerMessage(invalidUtf8Id), genericOnly);
const unknownStatus = completed([s(1, "todo-7")], lp(1, todoItem([s(2, "Keep me"), vi(3, 99)])));
assert.deepEqual(decodeAgentServerMessage(unknownStatus), genericOnly);
const missingStatus = completed([s(1, "todo-8")], lp(1, todoItem([s(2, "Keep me")])));
assert.deepEqual(decodeAgentServerMessage(missingStatus), genericOnly);
const wrongWireStatus = completed(
[s(1, "todo-9")],
lp(1, todoItem([s(2, "Keep me"), s(3, "completed")]))
);
assert.deepEqual(decodeAgentServerMessage(wrongWireStatus), genericOnly);
const invalidUtf8Content = completed(
[s(1, "todo-10")],
lp(1, todoItem([lp(2, Buffer.from([0xff])), vi(3, 3)]))
);
assert.deepEqual(decodeAgentServerMessage(invalidUtf8Content), genericOnly);
});
test("encodeAgentRunRequest embeds user text and resolves the model id", () => {
const buf = encodeAgentRunRequest({
modelId: "auto",
userText: "say only PING",
conversationId: "00000000-0000-0000-0000-000000000000",
messageId: "11111111-1111-1111-1111-111111111111",
});
// Verifiable substrings: cursor-agent itself emits these on the wire.
const text = buf.toString("latin1");
assert.ok(text.includes("say only PING"), "user text present");
assert.ok(text.includes("default"), "auto rewritten to 'default'");
assert.ok(text.includes("00000000-0000-0000-0000-000000000000"), "conversation id present");
});
test("encodeAgentRunRequest emits composer parameters", () => {
const buf = encodeAgentRunRequest({
modelId: "composer-2-fast",
userText: "hi",
});
const text = buf.toString("latin1");
assert.ok(text.includes("composer-2"), "split model id present");
assert.ok(text.includes("fast"), "parameter id 'fast' present");
assert.ok(text.includes("true"), "parameter value 'true' present");
});
test("encodeAgentRunRequest sends ModelDetails for pinned thinking models (#3714)", () => {
// #3714: pinned Claude/GPT thinking variants returned an empty turn when sent only via
// RequestedModel (field 9, bare model_id). cursor-agent's working wire format also
// carries a ModelDetails envelope with model_id + display_model_id + display_name.
// #7289: the trailing effort suffix ("-xhigh") is now split off into a separate
// ModelParameter — the BASE id is what's shared across RequestedModel + ModelDetails.
const modelId = "claude-opus-4-7-thinking-xhigh";
const baseModelId = "claude-opus-4-7-thinking";
const buf = encodeAgentRunRequest({ modelId, userText: "hi" });
const text = buf.toString("latin1");
const occurrences = text.split(baseModelId).length - 1;
// RequestedModel.model_id (1) + ModelDetails {model_id, display_model_id, display_name}
// (3) → the base id must appear at least 4 times.
assert.ok(
occurrences >= 4,
`base model id must be encoded in both RequestedModel and ModelDetails (got ${occurrences})`
);
assert.ok(text.includes("effort"), "effort parameter id present (#7289)");
assert.ok(text.includes("xhigh"), "effort parameter value present (#7289)");
});
test("encodeAgentRunRequest keeps RequestedModel + parameters alongside ModelDetails (#3714)", () => {
// The ModelDetails addition is additive: server-routed ids and the -fast parameter
// path (carried only by RequestedModel) must be unaffected.
const composer = encodeAgentRunRequest({ modelId: "composer-2-fast", userText: "hi" });
const composerText = composer.toString("latin1");
assert.ok(composerText.includes("composer-2"), "split model id still present");
assert.ok(composerText.includes("fast"), "'-fast' parameter id still present (RequestedModel)");
assert.ok(
composerText.includes("true"),
"'-fast' parameter value still present (RequestedModel)"
);
// auto → default, now appearing in both RequestedModel and ModelDetails.
const auto = encodeAgentRunRequest({ modelId: "auto", userText: "hi" });
const autoOccurrences = auto.toString("latin1").split("default").length - 1;
assert.ok(
autoOccurrences >= 4,
`'default' must appear in RequestedModel + ModelDetails (got ${autoOccurrences})`
);
});
test("buildAgentRequestBody wraps the message in a Connect-RPC frame", () => {
const buf = buildAgentRequestBody({ modelId: "claude-4.6-sonnet-medium", userText: "hi" });
// First byte is flags (0x00 = uncompressed); next 4 bytes are big-endian length.
assert.equal(buf[0], 0x00);
const length = buf.readUInt32BE(1);
assert.equal(length, buf.length - 5);
});
test("iterateConnectFrames + decodeAgentServerMessage extract a text delta", () => {
// Synthesize a server-side delta frame: AgentServerMessage { interaction_update {
// text_delta { text = "hello" } } }
function v(n: number): Buffer {
const out: number[] = [];
while (n > 0x7f) {
out.push((n & 0x7f) | 0x80);
n >>>= 7;
}
out.push(n);
return Buffer.from(out);
}
function tag(field: number, wt: number) {
return v((field << 3) | wt);
}
function lenPrefixed(field: number, payload: Buffer) {
return Buffer.concat([tag(field, 2), v(payload.length), payload]);
}
const textDelta = Buffer.from("hello", "utf8");
const tdu = lenPrefixed(1, textDelta); // TextDeltaUpdate { text }
const iu = lenPrefixed(1, tdu); // InteractionUpdate { text_delta }
const asm = lenPrefixed(1, iu); // AgentServerMessage { interaction_update }
const framed = wrapConnectFrame(asm);
const frames = [...iterateConnectFrames(framed)];
assert.equal(frames.length, 1);
const deltas = decodeAgentServerMessage(frames[0].payload);
assert.deepEqual(deltas, [{ kind: "text", text: "hello" }]);
});
test("flattenMessages handles a simple single user message", () => {
assert.equal(flattenMessages([{ role: "user", content: "hello there" }]), "hello there");
});
test("flattenMessages prepends system prompts and labels multi-turn", () => {
const out = flattenMessages([
{ role: "system", content: "be brief" },
{ role: "user", content: "hi" },
{ role: "assistant", content: "hello" },
{ role: "user", content: "again" },
]);
assert.ok(out.startsWith("be brief"));
assert.ok(out.includes("User: hi"));
assert.ok(out.includes("Assistant: hello"));
assert.ok(out.includes("User: again"));
});
test("flattenMessages flattens content arrays", () => {
const out = flattenMessages([
{
role: "user",
content: [
{ type: "text", text: "part1" },
{ type: "text", text: "part2" },
],
},
]);
assert.equal(out, "part1\npart2");
});
// ─── Phase 1: rejection encoders ───────────────────────────────────────────
//
// All ExecClientMessage frames share a structure:
// wrapConnectFrame(
// AgentClientMessage {
// exec_client_message (2): ExecClientMessage {
// id (1): execMsgId,
// exec_id (15): execId,
// <result_field>: <result_payload>,
// }
// }
// )
//
// We unwrap via decodeFields and assert: (1) framing is valid, (2) the
// ExecClientMessage carries the right id+exec_id, (3) the result field
// number matches the variant, (4) the payload bytes contain the rejection
// reason as a substring (sufficient since Buffer.from(reason, "utf8") is
// embedded verbatim by encodeString).
function unwrapFrame(buf: Buffer): Buffer {
const frames = [...iterateConnectFrames(buf)];
assert.equal(frames.length, 1);
return frames[0].payload;
}
function assertEcmShape(
framed: Buffer,
expectedExecMsgId: number,
expectedExecId: string,
expectedResultField: number
) {
const payload = unwrapFrame(framed);
// Top-level: AgentClientMessage { exec_client_message (2): ECM }
// Just verify the fragment contains the exec id and result-field tag.
assert.ok(payload.includes(Buffer.from(expectedExecId, "utf8")), "exec_id present");
// Tag for result field with WT_LEN (2): (field<<3)|2
const resultTag = (expectedResultField << 3) | 2;
let found = false;
for (let i = 0; i < payload.length; i++) {
if (payload[i] === resultTag) {
found = true;
break;
}
}
assert.ok(found, `result tag for field ${expectedResultField} present`);
void expectedExecMsgId; // varint encoded; covered by other tests
}
test("encodeExecReadRejected wraps in read_result (field 7) with reason", () => {
const framed = encodeExecReadRejected(42, "exec-abc", "/etc/passwd", "denied");
assertEcmShape(framed, 42, "exec-abc", 7);
const payload = unwrapFrame(framed);
assert.ok(payload.includes(Buffer.from("/etc/passwd", "utf8")));
assert.ok(payload.includes(Buffer.from("denied", "utf8")));
});
test("encodeExecWriteRejected wraps in write_result (field 3)", () => {
const framed = encodeExecWriteRejected(1, "x", "/tmp/foo", "no");
assertEcmShape(framed, 1, "x", 3);
});
test("encodeExecDeleteRejected wraps in delete_result (field 4)", () => {
const framed = encodeExecDeleteRejected(1, "x", "/tmp/foo", "no");
assertEcmShape(framed, 1, "x", 4);
});
test("encodeExecLsRejected wraps in ls_result (field 8)", () => {
const framed = encodeExecLsRejected(1, "x", "/tmp", "no");
assertEcmShape(framed, 1, "x", 8);
});
test("encodeExecShellRejected carries command and working directory", () => {
const framed = encodeExecShellRejected(7, "exec-7", "rm -rf /", "/home", "denied");
assertEcmShape(framed, 7, "exec-7", 2);
const payload = unwrapFrame(framed);
assert.ok(payload.includes(Buffer.from("rm -rf /", "utf8")));
assert.ok(payload.includes(Buffer.from("/home", "utf8")));
assert.ok(payload.includes(Buffer.from("denied", "utf8")));
});
test("encodeExecBackgroundShellSpawnRejected uses field 16", () => {
const framed = encodeExecBackgroundShellSpawnRejected(1, "x", "sleep", "/", "no");
assertEcmShape(framed, 1, "x", 16);
});
test("encodeExecGrepError uses field 5 with error message", () => {
const framed = encodeExecGrepError(1, "exec-grep", "regex too complex");
assertEcmShape(framed, 1, "exec-grep", 5);
const payload = unwrapFrame(framed);
assert.ok(payload.includes(Buffer.from("regex too complex", "utf8")));
});
test("encodeExecFetchError uses field 20 with url and error", () => {
const framed = encodeExecFetchError(1, "x", "https://example.com", "timeout");
assertEcmShape(framed, 1, "x", 20);
const payload = unwrapFrame(framed);
assert.ok(payload.includes(Buffer.from("https://example.com", "utf8")));
assert.ok(payload.includes(Buffer.from("timeout", "utf8")));
});
test("encodeExecWriteShellStdinError uses field 23", () => {
const framed = encodeExecWriteShellStdinError(1, "x", "no shell");
assertEcmShape(framed, 1, "x", 23);
});
test("encodeExecDiagnosticsResult is empty success on field 9", () => {
const framed = encodeExecDiagnosticsResult(1, "exec-diag");
assertEcmShape(framed, 1, "exec-diag", 9);
});
// ─── Phase 1: MCP encoders ──────────────────────────────────────────────────
test("encodeExecMcpResult wraps text content in mcp_result (field 11)", () => {
const framed = encodeExecMcpResult(1, "exec-mcp", "tool output", false);
assertEcmShape(framed, 1, "exec-mcp", 11);
const payload = unwrapFrame(framed);
assert.ok(payload.includes(Buffer.from("tool output", "utf8")));
});
test("encodeExecMcpResult sets is_error when isError=true", () => {
const framed = encodeExecMcpResult(1, "x", "err msg", true);
// is_error field would be encoded as varint 1 — verify framing only
assertEcmShape(framed, 1, "x", 11);
});
test("encodeExecMcpError wraps error in mcp_result", () => {
const framed = encodeExecMcpError(1, "exec-err", "tool crashed");
assertEcmShape(framed, 1, "exec-err", 11);
const payload = unwrapFrame(framed);
assert.ok(payload.includes(Buffer.from("tool crashed", "utf8")));
});
// ─── Phase 1: KV blob encoders ──────────────────────────────────────────────
test("encodeKvGetBlobResult wraps in kv_client_message (field 3)", () => {
const framed = encodeKvGetBlobResult(99, Buffer.from("blob-content", "utf8"));
const payload = unwrapFrame(framed);
// Top-level field 3 (kv_client_message) tag = (3<<3)|2 = 26
assert.equal(payload[0], 26);
assert.ok(payload.includes(Buffer.from("blob-content", "utf8")));
});
test("encodeKvSetBlobResult is an empty ack", () => {
const framed = encodeKvSetBlobResult(7);
const payload = unwrapFrame(framed);
assert.equal(payload[0], 26); // (3<<3)|2 = kv_client_message
});
// ─── Phase 1: MCP tool definition body ──────────────────────────────────────
test("encodeMcpToolDefinitionBody encodes name, description, schema bytes", () => {
const body = encodeMcpToolDefinitionBody({
name: "get_weather",
description: "Look up current weather",
inputSchemaBytes: Buffer.from("schema-bytes"),
providerIdentifier: "omniroute",
toolName: "get_weather",
});
assert.ok(body.includes(Buffer.from("get_weather", "utf8")));
assert.ok(body.includes(Buffer.from("Look up current weather", "utf8")));
assert.ok(body.includes(Buffer.from("schema-bytes")));
assert.ok(body.includes(Buffer.from("omniroute", "utf8")));
});
test("encodeMcpToolDefinitionBody omits optional providerIdentifier and toolName", () => {
const body = encodeMcpToolDefinitionBody({
name: "n",
description: "d",
inputSchemaBytes: Buffer.alloc(0),
});
assert.ok(!body.includes(Buffer.from("omniroute", "utf8")));
});
// ─── Phase 1: encodeRequestContextResponse with tools ───────────────────────
test("encodeRequestContextResponse with no tools produces empty RequestContext (existing behavior)", () => {
const framed = encodeRequestContextResponse(5, "exec-rc");
assertEcmShape(framed, 5, "exec-rc", 10); // request_context_result = field 10
});
test("encodeRequestContextResponse with tools embeds tool name and schema", () => {
const framed = encodeRequestContextResponse(5, "exec-rc", [
{
name: "get_weather",
description: "lookup",
inputSchemaBytes: jsonSchemaToProtobufValue({
type: "object",
properties: { city: { type: "string" } },
}),
providerIdentifier: "omniroute",
toolName: "get_weather",
},
]);
assertEcmShape(framed, 5, "exec-rc", 10);
const payload = unwrapFrame(framed);
assert.ok(payload.includes(Buffer.from("get_weather", "utf8")));
assert.ok(payload.includes(Buffer.from("city", "utf8")));
});
// ─── Phase 1: jsonSchemaToProtobufValue ─────────────────────────────────────
//
// google.protobuf.Value is a oneof message. We verify the encoded bytes
// start with the right field tag for each kind, and (for nested types) that
// the inner content survives.
test("jsonSchemaToProtobufValue encodes a string as field 3", () => {
const buf = jsonSchemaToProtobufValue("hello");
// tag (3<<3)|2 = 26
assert.equal(buf[0], 26);
assert.ok(buf.includes(Buffer.from("hello", "utf8")));
});
test("jsonSchemaToProtobufValue encodes a number as field 2 (double)", () => {
const buf = jsonSchemaToProtobufValue(3.14);
// tag (2<<3)|1 = 17
assert.equal(buf[0], 17);
assert.equal(buf.length, 9); // 1-byte tag + 8-byte double
assert.equal(buf.readDoubleLE(1), 3.14);
});
test("jsonSchemaToProtobufValue encodes a bool as field 4", () => {
const buf = jsonSchemaToProtobufValue(true);
// tag (4<<3)|0 = 32
assert.equal(buf[0], 32);
assert.equal(buf[1], 1);
});
test("jsonSchemaToProtobufValue encodes null as field 1", () => {
const buf = jsonSchemaToProtobufValue(null);
// tag (1<<3)|0 = 8
assert.equal(buf[0], 8);
assert.equal(buf[1], 0);
});
test("jsonSchemaToProtobufValue encodes a list as field 6", () => {
const buf = jsonSchemaToProtobufValue(["a", "b"]);
// tag (6<<3)|2 = 50
assert.equal(buf[0], 50);
assert.ok(buf.includes(Buffer.from("a", "utf8")));
assert.ok(buf.includes(Buffer.from("b", "utf8")));
});
test("jsonSchemaToProtobufValue encodes a struct as field 5", () => {
const buf = jsonSchemaToProtobufValue({ key: "value" });
// tag (5<<3)|2 = 42
assert.equal(buf[0], 42);
assert.ok(buf.includes(Buffer.from("key", "utf8")));
assert.ok(buf.includes(Buffer.from("value", "utf8")));
});
test("jsonSchemaToProtobufValue encodes a nested OpenAI tool input_schema", () => {
const schema = {
type: "object",
properties: {
city: { type: "string", description: "the city" },
units: { type: "string", enum: ["c", "f"] },
},
required: ["city"],
};
const buf = jsonSchemaToProtobufValue(schema);
// Outer tag is struct (5)
assert.equal(buf[0], 42);
assert.ok(buf.includes(Buffer.from("city", "utf8")));
assert.ok(buf.includes(Buffer.from("units", "utf8")));
assert.ok(buf.includes(Buffer.from("required", "utf8")));
assert.ok(buf.includes(Buffer.from("the city", "utf8")));
});
// ─── Phase 3: tools in AgentRunRequest ─────────────────────────────────────
test("openAIToolsToMcpDefs converts OpenAI tool array to McpToolDefinition[]", () => {
const defs = openAIToolsToMcpDefs([
{
type: "function",
function: {
name: "get_weather",
description: "lookup",
parameters: { type: "object", properties: { city: { type: "string" } } },
},
},
]);
assert.equal(defs.length, 1);
assert.equal(defs[0].name, "get_weather");
assert.equal(defs[0].description, "lookup");
assert.equal(defs[0].toolName, "get_weather");
assert.equal(defs[0].providerIdentifier, "omniroute");
assert.ok(defs[0].inputSchemaBytes.length > 0);
// Schema bytes are a Struct (field 5)
assert.equal(defs[0].inputSchemaBytes[0], 42);
});
test("openAIToolsToMcpDefs supplies a default schema if parameters omitted", () => {
const defs = openAIToolsToMcpDefs([{ type: "function", function: { name: "no_params" } }]);
assert.equal(defs.length, 1);
// Default schema is { type: "object", properties: {} } — encoded as struct
assert.equal(defs[0].inputSchemaBytes[0], 42);
});
test("encodeAgentRunRequest with tools embeds tool name and schema in mcp_tools", () => {
const buf = encodeAgentRunRequest({
modelId: "claude-4.6-sonnet-medium",
userText: "what's the weather?",
tools: [
{
type: "function",
function: {
name: "get_weather",
description: "Look up current weather",
parameters: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"],
},
},
},
],
});
const text = buf.toString("latin1");
assert.ok(text.includes("get_weather"), "tool name present");
assert.ok(text.includes("Look up current weather"), "tool description present");
assert.ok(text.includes("city"), "tool schema field present");
assert.ok(text.includes("omniroute"), "provider_identifier set");
});
test("encodeAgentRunRequest without tools preserves empty mcp_tools placeholder", () => {
const bufNoTools = encodeAgentRunRequest({
modelId: "auto",
userText: "hi",
});
const bufEmptyTools = encodeAgentRunRequest({
modelId: "auto",
userText: "hi",
tools: [],
});
// Both should produce essentially the same shape — neither embeds tools.
// Lengths may differ by message-id randomness; just verify neither contains
// tool-related markers.
assert.ok(!bufNoTools.toString("latin1").includes("omniroute"));
assert.ok(!bufEmptyTools.toString("latin1").includes("omniroute"));
});
test("encodeAgentRunRequest with multiple tools embeds all of them", () => {
const buf = encodeAgentRunRequest({
modelId: "auto",
userText: "test",
tools: [
{ type: "function", function: { name: "tool_a", description: "A" } },
{ type: "function", function: { name: "tool_b", description: "B" } },
],
});
const text = buf.toString("latin1");
assert.ok(text.includes("tool_a"));
assert.ok(text.includes("tool_b"));
});