113 lines
4.1 KiB
TypeScript
113 lines
4.1 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import type { ReplyPayload } from "../types.js";
|
|
import type { TypingSignaler } from "./typing-mode.js";
|
|
|
|
const hoisted = vi.hoisted(() => {
|
|
const loadSessionStoreMock = vi.fn();
|
|
const scheduleFollowupDrainMock = vi.fn();
|
|
return { loadSessionStoreMock, scheduleFollowupDrainMock };
|
|
});
|
|
|
|
vi.mock("../../config/sessions.js", async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import("../../config/sessions.js")>();
|
|
return {
|
|
...actual,
|
|
loadSessionStore: (...args: unknown[]) => hoisted.loadSessionStoreMock(...args),
|
|
};
|
|
});
|
|
|
|
vi.mock("./queue.js", async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import("./queue.js")>();
|
|
return {
|
|
...actual,
|
|
scheduleFollowupDrain: (...args: unknown[]) => hoisted.scheduleFollowupDrainMock(...args),
|
|
};
|
|
});
|
|
|
|
const {
|
|
createShouldEmitToolOutput,
|
|
createShouldEmitToolResult,
|
|
finalizeWithFollowup,
|
|
isAudioPayload,
|
|
signalTypingIfNeeded,
|
|
} = await import("./agent-runner-helpers.js");
|
|
|
|
describe("agent runner helpers", () => {
|
|
beforeEach(() => {
|
|
hoisted.loadSessionStoreMock.mockClear();
|
|
hoisted.scheduleFollowupDrainMock.mockClear();
|
|
});
|
|
|
|
it("detects audio payloads from mediaUrl/mediaUrls", () => {
|
|
expect(isAudioPayload({ mediaUrl: "https://example.test/audio.mp3" })).toBe(true);
|
|
expect(isAudioPayload({ mediaUrls: ["https://example.test/video.mp4"] })).toBe(false);
|
|
expect(isAudioPayload({ mediaUrls: ["https://example.test/voice.m4a"] })).toBe(true);
|
|
});
|
|
|
|
it("uses fallback verbose level when session context is missing", () => {
|
|
expect(createShouldEmitToolResult({ resolvedVerboseLevel: "off" })()).toBe(false);
|
|
expect(createShouldEmitToolResult({ resolvedVerboseLevel: "on" })()).toBe(true);
|
|
expect(createShouldEmitToolOutput({ resolvedVerboseLevel: "on" })()).toBe(false);
|
|
expect(createShouldEmitToolOutput({ resolvedVerboseLevel: "full" })()).toBe(true);
|
|
});
|
|
|
|
it("uses session verbose level when present", () => {
|
|
hoisted.loadSessionStoreMock.mockReturnValue({
|
|
"agent:main:main": { verboseLevel: "full" },
|
|
});
|
|
const shouldEmitResult = createShouldEmitToolResult({
|
|
sessionKey: "agent:main:main",
|
|
storePath: "/tmp/store.json",
|
|
resolvedVerboseLevel: "off",
|
|
});
|
|
const shouldEmitOutput = createShouldEmitToolOutput({
|
|
sessionKey: "agent:main:main",
|
|
storePath: "/tmp/store.json",
|
|
resolvedVerboseLevel: "off",
|
|
});
|
|
expect(shouldEmitResult()).toBe(true);
|
|
expect(shouldEmitOutput()).toBe(true);
|
|
});
|
|
|
|
it("falls back when store read fails or session value is invalid", () => {
|
|
hoisted.loadSessionStoreMock.mockImplementation(() => {
|
|
throw new Error("boom");
|
|
});
|
|
const fallbackOn = createShouldEmitToolResult({
|
|
sessionKey: "agent:main:main",
|
|
storePath: "/tmp/store.json",
|
|
resolvedVerboseLevel: "on",
|
|
});
|
|
expect(fallbackOn()).toBe(true);
|
|
|
|
hoisted.loadSessionStoreMock.mockClear();
|
|
hoisted.loadSessionStoreMock.mockReturnValue({
|
|
"agent:main:main": { verboseLevel: "weird" },
|
|
});
|
|
const fallbackFull = createShouldEmitToolOutput({
|
|
sessionKey: "agent:main:main",
|
|
storePath: "/tmp/store.json",
|
|
resolvedVerboseLevel: "full",
|
|
});
|
|
expect(fallbackFull()).toBe(true);
|
|
});
|
|
|
|
it("schedules followup drain and returns the original value", () => {
|
|
const runFollowupTurn = vi.fn();
|
|
const value = { ok: true };
|
|
expect(finalizeWithFollowup(value, "queue-key", runFollowupTurn)).toBe(value);
|
|
expect(hoisted.scheduleFollowupDrainMock).toHaveBeenCalledWith("queue-key", runFollowupTurn);
|
|
});
|
|
|
|
it("signals typing only when any payload has text or media", async () => {
|
|
const signalRunStart = vi.fn().mockResolvedValue(undefined);
|
|
const typingSignals = { signalRunStart } as unknown as TypingSignaler;
|
|
const emptyPayloads: ReplyPayload[] = [{ text: " " }, {}];
|
|
await signalTypingIfNeeded(emptyPayloads, typingSignals);
|
|
expect(signalRunStart).not.toHaveBeenCalled();
|
|
|
|
await signalTypingIfNeeded([{ mediaUrl: "https://example.test/img.png" }], typingSignals);
|
|
expect(signalRunStart).toHaveBeenCalledOnce();
|
|
});
|
|
});
|