feat(client): advertise reasoning merge capability

This commit is contained in:
Mohamed Boudra
2026-05-02 20:30:25 +07:00
parent ee1c7e6956
commit ca318f29cf
3 changed files with 73 additions and 4 deletions
@@ -60,10 +60,12 @@ function createMockTransport() {
return {
transport,
sent,
triggerOpen: () => {
triggerOpen: (options?: { preserveSent?: boolean }) => {
onOpen();
// Ignore HELLO handshake payloads in assertions.
sent.length = 0;
if (!options?.preserveSent) {
// Ignore HELLO handshake payloads in assertions.
sent.length = 0;
}
onMessage(
JSON.stringify({
type: "session",
@@ -214,6 +216,35 @@ test("passes password as HTTP bearer header and WebSocket subprotocol", async ()
});
});
test("advertises reasoning_merge_enum in hello", async () => {
const logger = createMockLogger();
const mock = createMockTransport();
const client = new DaemonClient({
url: "ws://test",
clientId: "clsk_unit_test",
logger,
reconnect: { enabled: false },
transportFactory: () => mock.transport,
});
clients.push(client);
const connectPromise = client.connect();
mock.triggerOpen({ preserveSent: true });
await connectPromise;
expect(mock.sent).toHaveLength(1);
expect(JSON.parse(mock.sent[0] as string)).toEqual({
type: "hello",
clientId: "clsk_unit_test",
clientType: "cli",
protocolVersion: 1,
capabilities: {
reasoning_merge_enum: true,
},
});
});
test("does not reconnect after close when ensureConnected is called", async () => {
const logger = createMockLogger();
const mock = createMockTransport();
@@ -1,4 +1,5 @@
import type { z } from "zod";
import { CLIENT_CAPS } from "../shared/client-capabilities.js";
import {
AgentCreateFailedStatusPayloadSchema,
AgentCreatedStatusPayloadSchema,
@@ -3877,6 +3878,9 @@ export class DaemonClient {
clientId: this.config.clientId,
clientType: this.config.clientType ?? "cli",
protocolVersion: 1,
capabilities: {
[CLIENT_CAPS.reasoningMergeEnum]: true,
},
...(this.config.appVersion ? { appVersion: this.config.appVersion } : {}),
}),
);
@@ -15,6 +15,7 @@ import {
encodeTerminalStreamFrame,
TerminalStreamOpcode,
} from "../shared/terminal-stream-protocol.js";
import { CLIENT_CAPS } from "../shared/client-capabilities.js";
type SocketListener = (...args: unknown[]) => void;
@@ -47,6 +48,7 @@ const sessionMock = vi.hoisted(() => {
cleanup = vi.fn(async () => {});
handleMessage = vi.fn(async () => {});
handleBinaryFrame = vi.fn((_frame: unknown) => {});
supports = vi.fn((capability: string) => this.args.clientCapabilities?.[capability] === true);
getClientActivity = vi.fn(() => null);
resetPeakInflight = vi.fn(() => {});
getRuntimeMetrics = vi.fn(() => ({
@@ -293,12 +295,16 @@ function createDownloadInProgressSpeechReadinessSnapshot(): SpeechReadinessSnaps
};
}
function createHelloMessage(clientId: string) {
function createHelloMessage(
clientId: string,
options?: { capabilities?: Record<string, boolean> },
) {
return {
type: "hello" as const,
clientId,
clientType: "cli" as const,
protocolVersion: 1,
...(options?.capabilities ? { capabilities: options.capabilities } : {}),
};
}
@@ -400,6 +406,34 @@ describe("relay external socket reconnect behavior", () => {
await server.close();
});
test("passes hello capabilities through to the created session", async () => {
const server = createServer();
const socket = new MockSocket();
await (server as unknown as WebSocketServerInternals).attachSocket(
socket,
createDirectRequest(),
);
socket.emit(
"message",
JSON.stringify(
createHelloMessage("client-capabilities", {
capabilities: { [CLIENT_CAPS.reasoningMergeEnum]: true },
}),
),
);
await Promise.resolve();
expect(sessionMock.instances).toHaveLength(1);
const session = sessionMock.instances[0]!;
expect(session.args.clientCapabilities).toEqual({
[CLIENT_CAPS.reasoningMergeEnum]: true,
});
expect(session.supports(CLIENT_CAPS.reasoningMergeEnum)).toBe(true);
await server.close();
});
test("closes pending connection when hello timeout elapses", async () => {
const server = createServer();