import { describe, expect, it } from "vitest"; import * as protocol from "../src/generated-protocol.js"; import { classifySidecarWrittenProtocolFrame, decodeBareProtocolFrame, decodeProtocolFramePayload, encodeBareProtocolFrame, encodeProtocolFramePayload, fromGeneratedSidecarWrittenProtocolFrame, HostProtocolFrameFactory, resolveSidecarRequestFramePayload, toGeneratedProtocolFrame, } from "../src/protocol-frames.js"; import { SIDECAR_PROTOCOL_SCHEMA } from "../src/protocol-schema.js"; const textDecoder = new TextDecoder(); const ownership = { scope: "connection" as const, connection_id: "conn", }; const generatedAuthOwnership = { scope: "connection" as const, connection_id: "conn-1", }; const GENERATED_AUTH_FRAME_HEX = "00166167656e746f732d6e61746976652d73696465636172080007000000000000000006636f6e6e2d31000e67656e6572617465642d7465737405746f6b656e080001000000"; const hostCallbackRequest = { frame_type: "sidecar_request" as const, schema: SIDECAR_PROTOCOL_SCHEMA, request_id: 7, ownership, payload: { type: "host_callback" as const, invocation_id: "invocation", callback_key: "tool", input: {}, timeout_ms: 1000, }, }; describe("protocol frame conversion", () => { it("creates host-written request, response, and control frames", () => { const factory = new HostProtocolFrameFactory(); const first = factory.createRequestFrame({ ownership, payload: { type: "authenticate", client_name: "agentos", auth_token: "token", protocol_version: 8, bridge_version: 1, }, }); const second = factory.createRequestFrame({ ownership, payload: { type: "create_layer", }, }); expect(first).toMatchObject({ frame_type: "request", schema: SIDECAR_PROTOCOL_SCHEMA, request_id: 1, ownership, }); expect(second.request_id).toBe(2); expect( factory.createSidecarResponseFrame({ request: hostCallbackRequest, payload: { type: "host_callback_result", invocation_id: "invocation", result: { ok: true }, }, }), ).toEqual({ frame_type: "sidecar_response", schema: SIDECAR_PROTOCOL_SCHEMA, request_id: 7, ownership, payload: { type: "host_callback_result", invocation_id: "invocation", result: { ok: true }, }, }); expect( factory.createControlFrame({ type: "shutdown", reason: "test complete" }), ).toEqual({ frame_type: "control", schema: SIDECAR_PROTOCOL_SCHEMA, payload: { type: "shutdown", reason: "test complete" }, }); }); it("resolves sidecar request frame handlers", async () => { await expect( resolveSidecarRequestFramePayload(hostCallbackRequest, async () => ({ type: "host_callback_result", invocation_id: "invocation", result: { ok: true }, })), ).resolves.toEqual({ type: "host_callback_result", invocation_id: "invocation", result: { ok: true }, }); }); it("returns error payloads for missing or mismatched sidecar handlers", async () => { await expect( resolveSidecarRequestFramePayload(hostCallbackRequest, null), ).resolves.toMatchObject({ type: "host_callback_result", invocation_id: "invocation", error: "no sidecar request handler registered for host_callback", }); await expect( resolveSidecarRequestFramePayload(hostCallbackRequest, async () => ({ type: "js_bridge_result", call_id: "call", result: {}, })), ).resolves.toMatchObject({ type: "host_callback_result", invocation_id: "invocation", error: "sidecar handler returned js_bridge_result for host_callback", }); }); it("maps host-written request frames to generated protocol frames", () => { expect( toGeneratedProtocolFrame({ frame_type: "request", schema: SIDECAR_PROTOCOL_SCHEMA, request_id: 7, ownership, payload: { type: "authenticate", client_name: "agentos", auth_token: "token", protocol_version: 8, bridge_version: 1, }, }), ).toMatchObject({ tag: "RequestFrame", val: { requestId: 7n, payload: { tag: "AuthenticateRequest", }, }, }); }); it("encodes host-written frames as BARE protocol bytes", () => { const encoded = encodeBareProtocolFrame({ frame_type: "sidecar_response", schema: SIDECAR_PROTOCOL_SCHEMA, request_id: 8, ownership, payload: { type: "host_callback_result", invocation_id: "invocation", result: { ok: true }, }, }); expect(protocol.decodeProtocolFrame(new Uint8Array(encoded)).tag).toBe( "SidecarResponseFrame", ); const control = encodeBareProtocolFrame({ frame_type: "control", schema: SIDECAR_PROTOCOL_SCHEMA, payload: { type: "shutdown", reason: "test complete" }, }); expect(protocol.decodeProtocolFrame(new Uint8Array(control))).toMatchObject( { tag: "ControlFrame", val: { payload: { tag: "ShutdownControl", val: { reason: "test complete" }, }, }, }, ); }); it("matches native generated auth frame BARE bytes", () => { const encoded = encodeBareProtocolFrame({ frame_type: "request", schema: SIDECAR_PROTOCOL_SCHEMA, request_id: 7, ownership: generatedAuthOwnership, payload: { type: "authenticate", client_name: "generated-test", auth_token: "token", protocol_version: SIDECAR_PROTOCOL_SCHEMA.version, bridge_version: 1, }, }); expect(Buffer.from(encoded).toString("hex")).toBe(GENERATED_AUTH_FRAME_HEX); expect(protocol.decodeProtocolFrame(new Uint8Array(encoded))).toMatchObject( { tag: "RequestFrame", val: { requestId: 7n, payload: { tag: "AuthenticateRequest" }, }, }, ); }); it("decodes sidecar-written response frames from generated protocol frames", () => { expect( fromGeneratedSidecarWrittenProtocolFrame({ tag: "ResponseFrame", val: { schema: SIDECAR_PROTOCOL_SCHEMA, requestId: 9n, ownership: { tag: "ConnectionOwnership", val: { connectionId: "conn" }, }, payload: { tag: "VmCreatedResponse", val: { vmId: "vm" }, }, }, }), ).toEqual({ frame_type: "response", schema: SIDECAR_PROTOCOL_SCHEMA, request_id: 9, ownership, payload: { type: "vm_created", vm_id: "vm" }, }); }); it("decodes sidecar-written event frames from BARE bytes", () => { const bytes = protocol.encodeProtocolFrame({ tag: "EventFrame", val: { schema: SIDECAR_PROTOCOL_SCHEMA, ownership: { tag: "ConnectionOwnership", val: { connectionId: "conn" }, }, payload: { tag: "StructuredEvent", val: { name: "ready", detail: new Map([["ok", "true"]]), }, }, }, }); expect(decodeBareProtocolFrame(bytes)).toEqual({ frame_type: "event", schema: SIDECAR_PROTOCOL_SCHEMA, ownership, payload: { type: "structured", name: "ready", detail: { ok: "true" }, }, }); }); it("encodes and decodes JSON compatibility protocol frames", () => { const encoded = encodeProtocolFramePayload( { frame_type: "event", schema: SIDECAR_PROTOCOL_SCHEMA, ownership, payload: { type: "process_output", process_id: "proc", channel: "stdout", chunk: new Uint8Array([1, 2, 3]), }, }, "json", ); expect(JSON.parse(textDecoder.decode(encoded)).payload.chunk).toEqual([ 1, 2, 3, ]); const decoded = decodeProtocolFramePayload(encoded, "json"); if ( decoded.frame_type !== "event" || decoded.payload.type !== "process_output" ) { throw new Error("expected process_output event"); } expect(decoded.payload.chunk).toBeInstanceOf(Uint8Array); expect(Array.from(decoded.payload.chunk)).toEqual([1, 2, 3]); }); it("classifies sidecar-written protocol frames for RPC dispatch", () => { expect( classifySidecarWrittenProtocolFrame({ frame_type: "response", schema: SIDECAR_PROTOCOL_SCHEMA, request_id: 10, ownership, payload: { type: "vm_created", vm_id: "vm" }, }), ).toMatchObject({ kind: "response", requestId: 10, frame: { frame_type: "response" }, }); expect( classifySidecarWrittenProtocolFrame({ frame_type: "event", schema: SIDECAR_PROTOCOL_SCHEMA, ownership, payload: { type: "structured", name: "ready", detail: {}, }, }), ).toMatchObject({ kind: "event", frame: { frame_type: "event" }, }); expect( classifySidecarWrittenProtocolFrame({ frame_type: "sidecar_request", schema: SIDECAR_PROTOCOL_SCHEMA, request_id: 11, ownership, payload: { type: "host_callback", invocation_id: "invocation", callback_key: "tool", input: { ok: true }, timeout_ms: 1_000, }, }), ).toMatchObject({ kind: "sidecarRequest", frame: { frame_type: "sidecar_request" }, }); }); it("rejects host-written generated frames on the sidecar-written decode path", () => { expect(() => fromGeneratedSidecarWrittenProtocolFrame({ tag: "RequestFrame", val: { schema: SIDECAR_PROTOCOL_SCHEMA, requestId: 1n, ownership: { tag: "ConnectionOwnership", val: { connectionId: "conn" }, }, payload: { tag: "CreateLayerRequest", val: null, }, }, }), ).toThrow("unsupported BARE protocol frame tag: RequestFrame"); }); });