974 lines · 31.9 KB · TypeScript
RawDownload
import { describe, expect, it } from "vite-plus/test"; import type * as EffectAcpSchema from "effect-acp/schema"; import {  decideToolCallUpdateEmission,  extractModelConfigId,  mergeToolCallState,  parsePermissionRequest,  parseSessionModeState,  parseSessionUpdateEvent,  sessionUpdateCountsAsLoadReplayActivity,  sessionUpdateIsReplay,  syntheticLoadSessionResponseFromInitialize,  toolCallProgressLength,  type AcpToolCallState,} from "./AcpRuntimeModel.ts"; describe("AcpRuntimeModel", () => {  it("parses session mode state from typed ACP session setup responses", () => {    const modeState = parseSessionModeState({      sessionId: "session-1",      modes: {        currentModeId: " code ",        availableModes: [          { id: " ask ", name: " Ask ", description: " Request approval " },          { id: " code ", name: " Code " },        ],      },      configOptions: [],    } satisfies EffectAcpSchema.NewSessionResponse);     expect(modeState).toEqual({      currentModeId: "code",      availableModes: [        { id: "ask", name: "Ask", description: "Request approval" },        { id: "code", name: "Code" },      ],    });  });   it("extracts the model config id from typed ACP config options", () => {    const modelConfigId = extractModelConfigId({      sessionId: "session-1",      configOptions: [        {          id: "approval",          name: "Approval Mode",          category: "permission",          type: "select",          currentValue: "ask",          options: [{ value: "ask", name: "Ask" }],        },        {          id: "model",          name: "Model",          category: "model",          type: "select",          currentValue: "default",          options: [{ value: "default", name: "Auto" }],        },      ],    } satisfies EffectAcpSchema.NewSessionResponse);     expect(modelConfigId).toBe("model");  });   it("detects Grok session replay updates from _meta.isReplay", () => {    expect(      sessionUpdateIsReplay({        _meta: { isReplay: true },        sessionId: "session-1",        update: {          sessionUpdate: "agent_message_chunk",          content: { type: "text", text: "replayed" },        },      } satisfies EffectAcpSchema.SessionNotification),    ).toBe(true);    expect(      sessionUpdateIsReplay({        sessionId: "session-1",        update: {          sessionUpdate: "agent_message_chunk",          content: { type: "text", text: "live" },        },      } satisfies EffectAcpSchema.SessionNotification),    ).toBe(false);  });   it("ignores Grok keepalive chunks when tracking session/load replay activity", () => {    expect(      sessionUpdateCountsAsLoadReplayActivity({        sessionId: "session-1",        update: {          sessionUpdate: "agent_message_chunk",          content: { type: "text", text: "" },        },      } satisfies EffectAcpSchema.SessionNotification),    ).toBe(false);    expect(      sessionUpdateCountsAsLoadReplayActivity({        _meta: { isReplay: true },        sessionId: "session-1",        update: {          sessionUpdate: "tool_call",          toolCallId: "replay-tool",          title: "Replay",          kind: "search",          status: "completed",        },      } satisfies EffectAcpSchema.SessionNotification),    ).toBe(true);  });   it("ignores load-replay activity from other sessions while a load gate is active", () => {    expect(      sessionUpdateCountsAsLoadReplayActivity(        {          sessionId: "session-other",          update: {            sessionUpdate: "agent_message_chunk",            content: { type: "text", text: "unrelated" },          },        } satisfies EffectAcpSchema.SessionNotification,        "session-loading",      ),    ).toBe(false);    expect(      sessionUpdateCountsAsLoadReplayActivity(        {          sessionId: "session-loading",          update: {            sessionUpdate: "agent_message_chunk",            content: { type: "text", text: "replay" },          },        } satisfies EffectAcpSchema.SessionNotification,        "session-loading",      ),    ).toBe(true);  });   it("counts mode/config/session/usage updates as load-replay activity", () => {    expect(      sessionUpdateCountsAsLoadReplayActivity({        sessionId: "session-1",        update: {          sessionUpdate: "current_mode_update",          currentModeId: "code",        },      } satisfies EffectAcpSchema.SessionNotification),    ).toBe(true);    expect(      sessionUpdateCountsAsLoadReplayActivity({        sessionId: "session-1",        update: {          sessionUpdate: "config_option_update",          configOptions: [],        },      } satisfies EffectAcpSchema.SessionNotification),    ).toBe(true);    expect(      sessionUpdateCountsAsLoadReplayActivity({        sessionId: "session-1",        update: {          sessionUpdate: "session_info_update",          title: "Restored",        },      } satisfies EffectAcpSchema.SessionNotification),    ).toBe(true);    expect(      sessionUpdateCountsAsLoadReplayActivity({        sessionId: "session-1",        update: {          sessionUpdate: "usage_update",          used: 1,          size: 10,        },      } satisfies EffectAcpSchema.SessionNotification),    ).toBe(true);  });   it("builds a synthetic load response from initialize model state", () => {    const response = syntheticLoadSessionResponseFromInitialize({      protocolVersion: 1,      _meta: {        modelState: {          currentModelId: "grok-build",          availableModels: [{ modelId: "grok-build", name: "Grok Build" }],        },      },    } satisfies EffectAcpSchema.InitializeResponse);     expect(response.models?.currentModelId).toBe("grok-build");    expect(response._meta).toMatchObject({ t3SessionLoadReady: "replay_idle" });  });   it("accepts initialize model descriptions with null", () => {    const response = syntheticLoadSessionResponseFromInitialize({      protocolVersion: 1,      _meta: {        modelState: {          currentModelId: "grok-build",          availableModels: [{ modelId: "grok-build", name: "Grok Build", description: null }],        },      },    } satisfies EffectAcpSchema.InitializeResponse);     expect(response.models?.availableModels[0]?.description).toBeNull();  });   it("ignores malformed initialize model state in synthetic load responses", () => {    const response = syntheticLoadSessionResponseFromInitialize({      protocolVersion: 1,      _meta: {        modelState: {          currentModelId: "grok-build",          availableModels: [null],        },        modeState: {          currentModeId: "code",          availableModes: [{ id: "code", name: 12 }],        },      },    } as EffectAcpSchema.InitializeResponse);     expect(response.models).toBeUndefined();    expect(response.modes).toBeUndefined();    expect(response._meta).toMatchObject({ t3SessionLoadReady: "replay_idle" });  });   it("builds a synthetic load response with initialize mode state", () => {    const response = syntheticLoadSessionResponseFromInitialize({      protocolVersion: 1,      _meta: {        modeState: {          currentModeId: "code",          availableModes: [            { id: "ask", name: "Ask" },            { id: "code", name: "Code" },          ],        },      },    } satisfies EffectAcpSchema.InitializeResponse);     expect(response.modes?.currentModeId).toBe("code");    expect(response.modes?.availableModes).toHaveLength(2);  });   it("projects typed ACP tool call updates into runtime events", () => {    const created = parseSessionUpdateEvent({      sessionId: "session-1",      update: {        sessionUpdate: "tool_call",        toolCallId: "tool-1",        title: "Terminal",        kind: "execute",        status: "pending",        rawInput: {          executable: "bun",          args: ["run", "typecheck"],        },        content: [          {            type: "content",            content: {              type: "text",              text: "Running checks",            },          },        ],      },    } satisfies EffectAcpSchema.SessionNotification);     expect(created.events).toEqual([      {        _tag: "ToolCallUpdated",        toolCall: {          toolCallId: "tool-1",          kind: "execute",          title: "Ran command",          status: "pending",          command: "bun run typecheck",          detail: "bun run typecheck",          data: {            toolCallId: "tool-1",            kind: "execute",            command: "bun run typecheck",            rawInput: {              executable: "bun",              args: ["run", "typecheck"],            },            content: [              {                type: "content",                content: {                  type: "text",                  text: "Running checks",                },              },            ],          },        },        rawPayload: {          sessionId: "session-1",          update: {            sessionUpdate: "tool_call",            toolCallId: "tool-1",            title: "Terminal",            kind: "execute",            status: "pending",            rawInput: {              executable: "bun",              args: ["run", "typecheck"],            },            content: [              {                type: "content",                content: {                  type: "text",                  text: "Running checks",                },              },            ],          },        },      },    ]);     const updated = parseSessionUpdateEvent({      sessionId: "session-1",      update: {        sessionUpdate: "tool_call_update",        toolCallId: "tool-1",        status: "completed",        rawOutput: { exitCode: 0 },      },    } satisfies EffectAcpSchema.SessionNotification);     expect(updated.events).toHaveLength(1);    expect(updated.events[0]?._tag).toBe("ToolCallUpdated");    const createdEvent = created.events[0];    const updatedEvent = updated.events[0];    if (createdEvent?._tag === "ToolCallUpdated" && updatedEvent?._tag === "ToolCallUpdated") {      expect(mergeToolCallState(createdEvent.toolCall, updatedEvent.toolCall)).toMatchObject({        toolCallId: "tool-1",        status: "completed",        title: "Ran command",        detail: "bun run typecheck",        command: "bun run typecheck",      });    }  });   it("trims padded current mode updates before emitting a mode change", () => {    const result = parseSessionUpdateEvent({      sessionId: "session-1",      update: {        sessionUpdate: "current_mode_update",        currentModeId: " code ",      },    } satisfies EffectAcpSchema.SessionNotification);     expect(result.modeId).toBe("code");    expect(result.events).toEqual([      {        _tag: "ModeChanged",        modeId: "code",      },    ]);  });   it("projects typed ACP plan and content updates", () => {    const planResult = parseSessionUpdateEvent({      sessionId: "session-1",      update: {        sessionUpdate: "plan",        entries: [          { content: " Inspect state ", priority: "high", status: "completed" },          { content: "", priority: "medium", status: "in_progress" },        ],      },    } satisfies EffectAcpSchema.SessionNotification);     expect(planResult.events).toEqual([      {        _tag: "PlanUpdated",        payload: {          plan: [            { step: "Inspect state", status: "completed" },            { step: "Step 2", status: "inProgress" },          ],        },        rawPayload: {          sessionId: "session-1",          update: {            sessionUpdate: "plan",            entries: [              { content: " Inspect state ", priority: "high", status: "completed" },              { content: "", priority: "medium", status: "in_progress" },            ],          },        },      },    ]);     const contentResult = parseSessionUpdateEvent({      sessionId: "session-1",      update: {        sessionUpdate: "agent_message_chunk",        content: {          type: "text",          text: "hello from acp",        },      },    } satisfies EffectAcpSchema.SessionNotification);     expect(contentResult.events).toEqual([      {        _tag: "ContentDelta",        text: "hello from acp",        rawPayload: {          sessionId: "session-1",          update: {            sessionUpdate: "agent_message_chunk",            content: {              type: "text",              text: "hello from acp",            },          },        },      },    ]);  });   it("keeps thought chunks separate from assistant text", () => {    const notification = {      sessionId: "session-1",      update: {        sessionUpdate: "agent_thought_chunk",        content: { type: "text", text: "Inspect the current implementation first." },      },    } satisfies EffectAcpSchema.SessionNotification;     expect(parseSessionUpdateEvent(notification).events).toEqual([      {        _tag: "ThoughtDelta",        text: "Inspect the current implementation first.",        rawPayload: notification,      },    ]);  });   it("preserves native command inputs and empty command lists", () => {    const availableCommands = [      { name: "plan", description: "Plan a task", input: { hint: "task" } },      { name: "logout", description: "Sign out" },    ] satisfies ReadonlyArray<EffectAcpSchema.AvailableCommand>;     for (const commands of [availableCommands, []]) {      const notification = {        sessionId: "session-1",        update: { sessionUpdate: "available_commands_update", availableCommands: commands },      } satisfies EffectAcpSchema.SessionNotification;      expect(parseSessionUpdateEvent(notification).events).toEqual([        {          _tag: "AvailableCommandsUpdated",          availableCommands: commands,          rawPayload: notification,        },      ]);    }  });   it("keeps permission request parsing compatible with loose extension payloads", () => {    const request = parsePermissionRequest({      sessionId: "session-1",      options: [        {          optionId: "allow-once",          name: "Allow once",          kind: "allow_once",        },      ],      toolCall: {        toolCallId: "tool-1",        title: "`cat package.json`",        kind: "execute",        status: "pending",        content: [          {            type: "content",            content: {              type: "text",              text: "Not in allowlist",            },          },        ],      },    }); 

Lines 1–500 of 974.