import { mkdtemp, mkdir, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { SDK_PROVIDER_DEFINITIONS } from "../adapter/SdkProviderSpi.js";
import { buildAcodeCommandOutputPath, buildClaudeCodeLaunchConfig, buildRuntimeEnv, buildSdkOutputMetadata, buildToolResultTimelineActionInfo, countTerminalOutputLines, createTerminalOutputDecoder, decodeTerminalOutputChunk, evaluatePersistedSessionReuse, formatCommandInactivityTimeoutNotice, formatCommandOutputSpilloverNotice, formatTerminalPrompt, formatSdkOutputEvent, normalizeTerminalCommandInput, parseTerminalDirectoryChangeTarget, resolveSdkInputMessage, resolveTerminalOutputEncoding, resolveSdkProviderId, resolveSdkSessionDisplayStatus, resolveRuntimeStatusFromProviderEvent, resolveStatusChangeRuntimeState, resolveStatusChangeUsage, shouldProbeSdkProcessPid, } from "./terminal.js";
describe("terminal route validation", () => {
    it("requires agentId and workspacePath for start", () => {
        const validateStartRequest = (body) => {
            if (!body.agentId || !body.workspacePath)
                return {
                    valid: false,
                    error: "agentId and workspacePath are required",
                };
            return { valid: true };
        };
        expect(validateStartRequest({}).valid).toBe(false);
        expect(validateStartRequest({ agentId: "agent-1", workspacePath: "/path" })
            .valid).toBe(true);
    });
    it("uses SDK default command when not provided", () => {
        const resolveCommand = (command) => command || "acode";
        expect(resolveCommand(undefined)).toBe("acode");
        expect(resolveCommand("codex")).toBe("codex");
    });
    it("uses SDK launch mode for unsupported mode values", () => {
        const resolveLaunchMode = (mode) => mode === "sdk" ? "sdk" : "sdk";
        expect(resolveLaunchMode(undefined)).toBe("sdk");
        expect(resolveLaunchMode("sdk")).toBe("sdk");
        expect(resolveLaunchMode("unsupported")).toBe("sdk");
    });
    it("keeps shortcut mode available for manual key injection", () => {
        const buildInputResponse = (mode) => mode === "shortcut"
            ? { ok: true, mode: "shortcut" }
            : { ok: true, mode: "sdk" };
        expect(buildInputResponse("shortcut")).toEqual({
            ok: true,
            mode: "shortcut",
        });
        expect(buildInputResponse(undefined)).toEqual({ ok: true, mode: "sdk" });
    });
    it("does not downgrade unsupported shortcut input to normal SDK messages", () => {
        const buildShortcutFallbackResponse = (hasShortcutSupport) => hasShortcutSupport
            ? { status: 200, body: { ok: true, mode: "shortcut" } }
            : {
                status: 400,
                body: {
                    error: "shortcut input is not supported by this SDK provider",
                },
            };
        expect(buildShortcutFallbackResponse(false)).toEqual({
            status: 400,
            body: { error: "shortcut input is not supported by this SDK provider" },
        });
    });
    it("requires sessionId and input for SDK terminal input", () => {
        const validateInputRequest = (body) => {
            if (!body.sessionId || body.input === undefined || body.input === null) {
                return { valid: false, error: "sessionId and input are required" };
            }
            return { valid: true };
        };
        expect(validateInputRequest({}).valid).toBe(false);
        expect(validateInputRequest({ sessionId: "session-1", input: "continue" }).valid).toBe(true);
    });
    it("keeps resize contract as SDK no-op compatibility endpoint", () => {
        const buildResizeResponse = (cols, rows) => ({
            ok: true,
            mode: "sdk",
            noop: true,
            cols,
            rows,
        });
        expect(buildResizeResponse(120, 30)).toEqual({
            ok: true,
            mode: "sdk",
            noop: true,
            cols: 120,
            rows: 30,
        });
    });
    it("requires sessionId for stop", () => {
        const validateStopRequest = (body) => {
            if (!body.sessionId)
                return { valid: false, error: "sessionId is required" };
            return { valid: true };
        };
        expect(validateStopRequest({}).valid).toBe(false);
        expect(validateStopRequest({ sessionId: "session-1" }).valid).toBe(true);
    });
});
describe("SDK startup idle command ordering", () => {
    it("skips PID probing for in-process ACode sessions", () => {
        expect(shouldProbeSdkProcessPid("acode")).toBe(false);
        expect(shouldProbeSdkProcessPid("claude-code")).toBe(true);
    });
    it("sets idle commands immediately after adapter startup and before PID probing or persistence", async () => {
        const calls = [];
        const adapter = {
            async startSession() {
                calls.push("startSession");
            },
            setIdleCommands() {
                calls.push("setIdleCommands");
            },
        };
        const startFlow = async () => {
            await adapter.startSession();
            adapter.setIdleCommands();
            calls.push("probePid");
            calls.push("persistSession");
        };
        await startFlow();
        expect(calls).toEqual([
            "startSession",
            "setIdleCommands",
            "probePid",
            "persistSession",
        ]);
    });
});
describe("terminal configuration", () => {
    it("builds correct SDK runtime environment", () => {
        const env = buildRuntimeEnv("agent-123", undefined, { PATH: "/usr/bin" });
        expect(env.AWS_AGENT_ID).toBe("agent-123");
        expect(env.AWS_MCP_LAUNCH_BINDING_ID).toBeUndefined();
        expect(env.AWS_MCP_CLAIM_LAUNCH_BINDING).toBe("true");
    });
    it("merges launch env overrides without allowing agent identity override", () => {
        const env = buildRuntimeEnv("agent-123", "/workspace", { PATH: "/usr/bin" }, {
            ANTHROPIC_BASE_URL: " https://api.example.com ",
            ANTHROPIC_AUTH_TOKEN: "secret-token",
            ANTHROPIC_MODEL: "claude-sonnet-4-5",
            AWS_AGENT_ID: "malicious-agent",
            EMPTY_VALUE: "   ",
        });
        expect(env.ANTHROPIC_BASE_URL).toBe("https://api.example.com");
        expect(env.ANTHROPIC_AUTH_TOKEN).toBe("secret-token");
        expect(env.ANTHROPIC_MODEL).toBe("claude-sonnet-4-5");
        expect(env.EMPTY_VALUE).toBeUndefined();
        expect(env.AWS_AGENT_ID).toBe("agent-123");
        expect(env.AWS_WORKSPACE_PATH).toBe("/workspace");
    });
    it("merges saved ClaudeCode AI settings into SDK launch env with request overrides taking precedence", () => {
        const launchConfig = buildClaudeCodeLaunchConfig({
            env: {
                ANTHROPIC_BASE_URL: "https://saved.example.com",
                ANTHROPIC_AUTH_TOKEN: "sk-saved",
                ANTHROPIC_MODEL: "claude-saved",
                EMPTY_SAVED: "   ",
            },
        }, {
            ANTHROPIC_MODEL: " claude-request ",
            EXTRA_VALUE: "enabled",
        });
        expect(launchConfig).toEqual({
            envOverrides: {
                ANTHROPIC_BASE_URL: "https://saved.example.com",
                ANTHROPIC_AUTH_TOKEN: "sk-saved",
                ANTHROPIC_MODEL: "claude-request",
                EXTRA_VALUE: "enabled",
            },
            model: "claude-request",
        });
    });
    it("uses CODEX_MODEL from request env overrides as SDK launch model", () => {
        const launchConfig = buildClaudeCodeLaunchConfig(null, {
            OPENAI_API_KEY: "sk-codex",
            CODEX_MODEL: " gpt-5.3-codex ",
        });
        expect(launchConfig).toEqual({
            envOverrides: {
                OPENAI_API_KEY: "sk-codex",
                CODEX_MODEL: "gpt-5.3-codex",
            },
            model: "gpt-5.3-codex",
        });
    });
});
describe("persisted SDK session reuse", () => {
    it("does not reuse a persisted session without a PID", () => {
        const decision = evaluatePersistedSessionReuse({}, () => true);
        expect(decision).toEqual({ reusable: false, reason: "missing-pid" });
    });
    it("does not reuse a persisted session when its PID is no longer running", () => {
        const decision = evaluatePersistedSessionReuse({ pid: 1234 }, () => false);
        expect(decision).toEqual({ reusable: false, reason: "dead-pid" });
    });
    it("reuses a persisted session only when its PID is still running for externally controllable providers", () => {
        const decision = evaluatePersistedSessionReuse({ pid: 1234, command: "claude" }, () => true);
        expect(decision).toEqual({ reusable: true, reason: "active-pid" });
    });
    it("does not reuse ACode persisted sessions because their control channel is in-process only", () => {
        const decision = evaluatePersistedSessionReuse({ pid: 1234, command: "acode" }, () => true);
        expect(decision).toEqual({
            reusable: false,
            reason: "in-process-provider",
        });
    });
});
describe("SDK session display status", () => {
    it("prefers adapter fine-grained status over coarse running fallback", () => {
        expect(resolveSdkSessionDisplayStatus("waiting_input", "running")).toBe("waiting_input");
        expect(resolveSdkSessionDisplayStatus("thinking", "running")).toBe("thinking");
    });
    it("uses in-memory runtime status when adapter status is temporarily unavailable", () => {
        expect(resolveSdkSessionDisplayStatus(undefined, "tool_using")).toBe("tool_using");
    });
    it("falls back to running only when no fine-grained state exists", () => {
        expect(resolveSdkSessionDisplayStatus(undefined, undefined)).toBe("running");
    });
});
describe("SDK provider event runtime status mapping", () => {
    const event = (type, data = {}) => ({
        type,
        sessionId: "session-1",
        timestamp: new Date().toISOString(),
        data,
    });
    it("maps text and thinking stream events to thinking status", () => {
        expect(resolveRuntimeStatusFromProviderEvent(event("thinking"))?.status).toBe("thinking");
        expect(resolveRuntimeStatusFromProviderEvent(event("text_delta"))?.status).toBe("thinking");
    });
    it("keeps command output chunks from changing the runtime status", () => {
        expect(resolveRuntimeStatusFromProviderEvent(event("command_output", { text: "stdout chunk", stream: "stdout" }))).toBeUndefined();
    });
    it("maps AI request events to thinking status with request details", () => {
        const state = resolveRuntimeStatusFromProviderEvent(event("ai_request", {
            model: "test-model",
            endpoint: "https://example.invalid/v1/chat/completions",
            requestMethod: "POST",
            requestUrl: "https://example.invalid/v1/chat/completions",
            requestHeaders: {
                "Content-Type": "application/json",
                Authorization: "Bearer ***",
            },
            stream: true,
            messageCount: 3,
            toolCount: 2,
            lastUserMessagePreview: "inspect status",
            requestBody: '{"model":"test-model"}',
        }));
        expect(state?.status).toBe("thinking");
        expect(state?.actionInfo).toMatchObject({
            actionType: "ai_request",
            actionLabel: "AI请求",
        });
        expect(state?.actionInfo?.actionDetail).toContain("模型: test-model");
        expect(state?.actionInfo?.actionDetail).toContain("接口: https://example.invalid/v1/chat/completions");
        expect(state?.actionInfo?.actionDetail).toContain("方法: POST");
        expect(state?.actionInfo?.actionDetail).toContain("请求头:\nContent-Type: application/json\nAuthorization: Bearer ***");
        expect(state?.actionInfo?.actionDetail).toContain("最后请求: inspect status");
        expect(state?.actionInfo?.actionDetail).toContain('请求体:\n{"model":"test-model"}');
    });
    it("does not let idle polling text chunks override waiting_input", () => {
        expect(resolveRuntimeStatusFromProviderEvent(event("thinking"), "waiting_input")).toBeUndefined();
        expect(resolveRuntimeStatusFromProviderEvent(event("text_delta"), "waiting_input")).toBeUndefined();
    });
    it("still reports AI request details when a request starts from waiting_input", () => {
        expect(resolveRuntimeStatusFromProviderEvent(event("ai_request", {
            model: "test-model",
            requestBody: '{"model":"test-model"}',
        }), "waiting_input")?.actionInfo).toMatchObject({
            actionType: "ai_request",
            actionLabel: "AI请求",
            actionDetail: expect.stringContaining('请求体:\n{"model":"test-model"}'),
        });
    });
    it("maps tool start events to tool_using with action info", () => {
        const state = resolveRuntimeStatusFromProviderEvent(event("tool_use_start", {
            toolName: "mcp__aws__my_task",
            toolUseId: "tool-1",
        }));
        expect(state?.status).toBe("tool_using");
        expect(state?.actionInfo?.actionType).toBe("mcp");
        expect(state?.actionInfo?.actionId).toBe("tool-1");
    });
    it("maps turn completion to idle waiting_input status", () => {
        expect(resolveRuntimeStatusFromProviderEvent(event("turn_complete"))?.status).toBe("waiting_input");
    });
    it("keeps idle tool action info with waiting_input status for display", () => {
        const state = resolveRuntimeStatusFromProviderEvent(event("tool_use_start", {
            actionType: "idle",
            actionLabel: "监听消息",
            actionDetail: "poll_message",
            toolUseId: "tool-idle-1",
        }));
        expect(state?.status).toBe("waiting_input");
        expect(state?.actionInfo).toMatchObject({
            actionType: "idle",
            actionLabel: "监听消息",
            actionDetail: "poll_message",
            actionId: "tool-idle-1",
        });
    });
});
describe("terminal command helpers", () => {
    it("expands a whole mini-term clipboard paste file path into SDK input content", async () => {
        const tempDir = await mkdtemp(path.join(os.tmpdir(), "terminal-input-"));
        const clipboardDir = path.join(tempDir, "mini-term-clipboard");
        await mkdir(clipboardDir, { recursive: true });
        const pasteFile = path.join(clipboardDir, "paste-1780491671137.txt");
        await writeFile(pasteFile, "real pasted prompt", "utf-8");
        await expect(resolveSdkInputMessage(`"${pasteFile}"`)).resolves.toBe("real pasted prompt");
    });
    it("keeps explicit commands that mention mini-term clipboard files unchanged", async () => {
        const command = 'type "C:\\Users\\ZHUANG~1\\AppData\\Local\\Temp\\mini-term-clipboard\\paste-1780491671137.txt"';
        await expect(resolveSdkInputMessage(command)).resolves.toBe(command);
    });
    it("normalizes carriage-return terminal input into executable command text", () => {
        expect(normalizeTerminalCommandInput("npm test\r")).toBe("npm test");
        expect(normalizeTerminalCommandInput(" pwd \n")).toBe("pwd");
    });
    it("parses cd commands without treating other shell commands as directory changes", () => {
        expect(parseTerminalDirectoryChangeTarget("cd")).toBeNull();
        expect(parseTerminalDirectoryChangeTarget('cd /d "D:\\code\\repo"')).toBe("D:\\code\\repo");
        expect(parseTerminalDirectoryChangeTarget("npm run build")).toBeUndefined();
    });
    it("formats the working directory prompt as terminal output text", () => {
        expect(formatTerminalPrompt("D:\\code\\repo")).toBe("\x1b[36mD:\\code\\repo>\x1b[0m ");
    });
    it("defaults Windows terminal command output to GB18030 decoding", () => {
        expect(resolveTerminalOutputEncoding("win32", {})).toBe("gb18030");
        expect(resolveTerminalOutputEncoding("linux", {})).toBe("utf-8");
        expect(resolveTerminalOutputEncoding("win32", {
            AWS_TERMINAL_OUTPUT_ENCODING: "utf-8",
        })).toBe("utf-8");
    });
    it("decodes GB18030 Chinese output across chunk boundaries", () => {
        const decoder = createTerminalOutputDecoder("gb18030");
        const first = decodeTerminalOutputChunk(decoder, Buffer.from([0xd6]));
        const second = decodeTerminalOutputChunk(decoder, Buffer.from([0xd0, 0xce, 0xc4]));
        const flushed = decodeTerminalOutputChunk(decoder, undefined, true);
        expect(`${first}${second}${flushed}`).toBe("中文");
    });
    it("counts terminal output lines without treating a trailing line break as an extra line", () => {
        expect(countTerminalOutputLines("")).toBe(0);
        expect(countTerminalOutputLines("one")).toBe(1);
        expect(countTerminalOutputLines("one\ntwo\n")).toBe(2);
        expect(countTerminalOutputLines("one\r\ntwo\rthree")).toBe(3);
    });
    it("builds ACode command output spillover paths under the workspace cmd-dist directory", () => {
        const outputPath = buildAcodeCommandOutputPath(path.join("D:", "code", "repo"), new Date(2026, 1, 3, 4, 5, 6, 7));
        expect(outputPath).toBe(path.join("D:", "code", "repo", ".agentswork", "acode", "cmd-dist", "2026-02-03", "04", "05", "20260203-040506-007-cmd.out"));
    });
    it("formats ACode long-output and inactivity notices for terminal users", () => {
        expect(formatCommandOutputSpilloverNotice("/repo/out.txt", 300)).toContain("命令输出超过 300 行");
        expect(formatCommandOutputSpilloverNotice("/repo/out.txt", 300)).toContain("/repo/out.txt");
        expect(formatCommandInactivityTimeoutNotice(60_000)).toContain("超过 60 秒没有新的输出");
    });
});
describe("resolveSdkProviderId", () => {
    it("exposes codex through the SDK provider SPI", () => {
        expect(SDK_PROVIDER_DEFINITIONS.map((definition) => definition.runtimeMode)).toContain("codex");
        expect(SDK_PROVIDER_DEFINITIONS.find((definition) => definition.runtimeMode === "codex")).toMatchObject({
            providerId: "codex",
            defaultCommand: "codex",
        });
    });
    it("resolves codex commands to codex provider", () => {
        expect(resolveSdkProviderId("codex")).toBe("codex");
        expect(resolveSdkProviderId("npx codex --model gpt-5-codex")).toBe("codex");
    });
    it("resolves opencode commands to opencode provider", () => {
        expect(resolveSdkProviderId("opencode")).toBe("opencode");
    });
    it("falls back to acode for unknown or empty commands while keeping claude mapped to claude-code", () => {
        expect(resolveSdkProviderId("claude")).toBe("claude-code");
        expect(resolveSdkProviderId("acode")).toBe("acode");
        expect(resolveSdkProviderId("custom-binary")).toBe("acode");
        expect(resolveSdkProviderId(undefined)).toBe("acode");
    });
});
describe("SDK status usage forwarding", () => {
    it("normalizes idle tool status changes to waiting_input", () => {
        expect(resolveStatusChangeRuntimeState("tool_using", {
            actionType: "idle",
            actionLabel: "监听消息",
            actionDetail: "aws-mcp/poll_message",
        })).toEqual({
            status: "waiting_input",
            actionInfo: {
                actionType: "idle",
                actionLabel: "监听消息",
                actionDetail: "aws-mcp/poll_message",
            },
        });
    });
    it("keeps accumulated usage from status-change action info", () => {
        expect(resolveStatusChangeUsage({
            usage: { inputTokens: 12000, outputTokens: 345 },
        })).toEqual({
            inputTokens: 12000,
            outputTokens: 345,
        });
        expect(resolveStatusChangeUsage({ usage: { inputTokens: 0, outputTokens: 0 } })).toBeUndefined();
        expect(resolveStatusChangeUsage({
            actionType: "mcp",
            actionLabel: "Calling MCP",
        })).toBeUndefined();
    });
    it("forwards structured AI response body through action result", () => {
        const runtimeStatus = resolveRuntimeStatusFromProviderEvent({
            type: "turn_complete",
            sessionId: "session-1",
            timestamp: new Date(0).toISOString(),
            data: {
                usage: { inputTokens: 12, outputTokens: 4 },
                responseBody: '{"choices":[{"message":{"content":"done"}}]}',
                finishReason: "stop",
            },
        });
        expect(runtimeStatus).toEqual({
            status: "waiting_input",
            actionInfo: {
                actionType: "ai_request",
                actionLabel: "AI响应",
                actionResult: '{"choices":[{"message":{"content":"done"}}]}',
                usage: { inputTokens: 12, outputTokens: 4 },
            },
        });
    });
    it("includes file paths and commands in tool status action details", () => {
        expect(resolveRuntimeStatusFromProviderEvent({
            type: "tool_use_start",
            sessionId: "session-1",
            timestamp: new Date(0).toISOString(),
            data: {
                toolName: "builtin/read_file",
                toolInput: { path: "src/views/App.vue" },
                toolUseId: "tool-read",
            },
        })).toEqual({
            status: "tool_using",
            actionInfo: {
                actionType: "read_file",
                actionLabel: "读取文件",
                actionDetail: "src/views/App.vue",
                actionId: "tool-read",
            },
        });
        expect(resolveRuntimeStatusFromProviderEvent({
            type: "tool_use_start",
            sessionId: "session-1",
            timestamp: new Date(0).toISOString(),
            data: {
                toolName: "bash",
                toolInput: { command: "npm run test:agent-timeline-panel" },
                toolUseId: "tool-bash",
            },
        })).toEqual({
            status: "tool_using",
            actionInfo: {
                actionType: "bash",
                actionLabel: "执行命令",
                actionDetail: "npm run test:agent-timeline-panel",
                actionId: "tool-bash",
            },
        });
    });
});
describe("SDK output forwarding", () => {
    it("formats assistant text provider events for the terminal output callback path", () => {
        const textEvent = {
            type: "text_delta",
            sessionId: "session-1",
            timestamp: new Date(0).toISOString(),
            data: { text: "hello" },
        };
        expect(formatSdkOutputEvent(textEvent)).toBe("hello");
    });
    it("formats thinking provider events as typed realtime output chunks", () => {
        const thinkingEvent = {
            type: "thinking",
            sessionId: "session-1",
            timestamp: new Date(0).toISOString(),
            data: { text: "internal prompt or reasoning" },
        };
        expect(formatSdkOutputEvent(thinkingEvent)).toBe("internal prompt or reasoning");
    });
    it("formats command output provider events as realtime output chunks", () => {
        const commandOutputEvent = {
            type: "command_output",
            sessionId: "session-1",
            timestamp: new Date(0).toISOString(),
            data: {
                text: "stdout chunk",
                stream: "stdout",
                sequence: 7,
                cwd: "/repo",
            },
        };
        expect(formatSdkOutputEvent(commandOutputEvent)).toBe("stdout chunk");
    });
    it("formats SDK errors as terminal output and ignores non-output events", () => {
        const errorEvent = {
            type: "error",
            sessionId: "session-1",
            timestamp: new Date(0).toISOString(),
            data: { text: "boom" },
        };
        const toolEvent = {
            type: "tool_use_start",
            sessionId: "session-1",
            timestamp: new Date(0).toISOString(),
            data: { toolName: "read_file" },
        };
        expect(formatSdkOutputEvent(errorEvent)).toBe("\r\n[SDK Error] boom\r\n");
        expect(formatSdkOutputEvent(toolEvent)).toBeUndefined();
    });
    it("formats AI request events as immediate realtime ACode output", () => {
        const requestEvent = {
            type: "ai_request",
            sessionId: "session-1",
            timestamp: new Date(0).toISOString(),
            data: {
                model: "test-model",
                lastUserMessagePreview: "inspect status",
            },
        };
        expect(formatSdkOutputEvent(requestEvent)).toBe("\r\n[ACode] 正在请求模型 test-model：inspect status\r\n");
    });
    it("builds ACode log metadata for output callbacks", () => {
        const requestEvent = {
            type: "ai_request",
            sessionId: "session-1",
            timestamp: new Date(0).toISOString(),
            data: {
                model: "test-model",
                endpoint: "openai-compatible",
                toolCount: 2,
            },
        };
        const errorEvent = {
            type: "error",
            sessionId: "session-1",
            timestamp: new Date(0).toISOString(),
            data: { text: "provider failed" },
        };
        expect(buildSdkOutputMetadata(requestEvent)).toEqual({
            sourceEventType: "ai_request",
            logCategory: "acode",
            logLevel: "info",
            logMessage: "AI request started: test-model @ openai-compatible, tools=2",
        });
        expect(buildSdkOutputMetadata(errorEvent)).toEqual({
            sourceEventType: "error",
            logCategory: "acode",
            logLevel: "error",
            logMessage: "provider failed",
        });
    });
    it("builds ordered ACode log metadata for command output callbacks", () => {
        const commandOutputEvent = {
            type: "command_output",
            sessionId: "session-1",
            timestamp: new Date(0).toISOString(),
            data: {
                text: "stdout chunk",
                stream: "stdout",
                sequence: 7,
                cwd: "/repo",
            },
        };
        expect(buildSdkOutputMetadata(commandOutputEvent)).toEqual({
            sourceEventType: "command_output",
            logCategory: "acode",
            logLevel: "info",
            logMessage: "命令输出(stdout #7): stdout chunk",
            stream: "stdout",
            sequence: 7,
            cwd: "/repo",
        });
    });
    it("does not mark streaming text chunks as standalone ACode log rows", () => {
        const metadata = buildSdkOutputMetadata({
            type: "text_delta",
            sessionId: "session-1",
            data: { text: "word" },
        });
        expect(metadata).toEqual({
            sourceEventType: "text_delta",
            logCategory: "acode",
        });
    });
    it("includes file paths and commands in ACode tool log metadata", () => {
        const readEvent = {
            type: "tool_use_start",
            sessionId: "session-1",
            timestamp: new Date(0).toISOString(),
            data: {
                toolName: "read_file",
                toolInput: { path: "src/index.ts" },
            },
        };
        const editEvent = {
            type: "tool_use_start",
            sessionId: "session-1",
            timestamp: new Date(0).toISOString(),
            data: {
                toolName: "edit",
                toolInput: { file_path: "src/app.ts" },
            },
        };
        const writeEvent = {
            type: "tool_use_end",
            sessionId: "session-1",
            timestamp: new Date(0).toISOString(),
            data: {
                toolName: "write_file",
                toolInput: { path: "src/new-file.ts" },
            },
        };
        const bashEvent = {
            type: "tool_use_start",
            sessionId: "session-1",
            timestamp: new Date(0).toISOString(),
            data: {
                toolName: "bash",
                toolInput: { command: "npm run build -- --mode production" },
            },
        };
        expect(buildSdkOutputMetadata(readEvent).logMessage).toBe("开始读取文件: src/index.ts");
        expect(buildSdkOutputMetadata(editEvent).logMessage).toBe("开始编辑文件: src/app.ts");
        expect(buildSdkOutputMetadata(writeEvent).logMessage).toBe("完成写入文件: src/new-file.ts");
        expect(buildSdkOutputMetadata(bashEvent).logMessage).toBe("开始执行命令: npm run build -- --mode production");
    });
});
describe("SDK tool result timeline action info", () => {
    it("keeps MCP tool results on MCP timeline events", () => {
        const event = {
            type: "tool_use_end",
            sessionId: "session-1",
            timestamp: new Date(0).toISOString(),
            data: {
                toolName: "mcp__aws-mcp__get_profile",
                toolUseId: "tool-1",
                toolResult: '{"displayName":"Agent"}',
            },
        };
        expect(buildToolResultTimelineActionInfo(event)).toEqual({
            actionType: "mcp",
            actionLabel: "MCP返回",
            actionDetail: "mcp__aws-mcp__get_profile",
            actionId: "tool-1",
            actionResult: '{"displayName":"Agent"}',
        });
    });
    it("forwards bash tool results to command timeline events", () => {
        const event = {
            type: "tool_use_end",
            sessionId: "session-1",
            timestamp: new Date(0).toISOString(),
            data: {
                toolName: "bash",
                toolInput: { command: "npm run build" },
                toolUseId: "tool-2",
                toolResult: "build passed",
            },
        };
        expect(buildToolResultTimelineActionInfo(event)).toEqual({
            actionType: "bash",
            actionLabel: "执行命令",
            actionDetail: "npm run build",
            actionId: "tool-2",
            actionResult: "build passed",
        });
    });
    it("forwards read tool results to file-read timeline events", () => {
        const event = {
            type: "tool_use_end",
            sessionId: "session-1",
            timestamp: new Date(0).toISOString(),
            data: {
                toolName: "read_file",
                toolInput: { path: "src/index.ts" },
                actionId: "tool-3",
                actionResult: "export const ok = true",
            },
        };
        expect(buildToolResultTimelineActionInfo(event)).toEqual({
            actionType: "read_file",
            actionLabel: "读取文件",
            actionDetail: "src/index.ts",
            actionId: "tool-3",
            actionResult: "export const ok = true",
        });
    });
    it("keeps MCP-named filesystem results on semantic file-read timeline events", () => {
        const event = {
            type: "tool_use_end",
            sessionId: "session-1",
            timestamp: new Date(0).toISOString(),
            data: {
                toolName: "mcp__filesystem__read_file",
                toolInput: { path: "src/index.ts" },
                toolUseId: "tool-4",
                toolResult: "export const ok = true",
            },
        };
        expect(buildToolResultTimelineActionInfo(event)).toEqual({
            actionType: "read_file",
            actionLabel: "读取文件",
            actionDetail: "src/index.ts",
            actionId: "tool-4",
            actionResult: "export const ok = true",
        });
    });
    it("ignores tool result events without displayable result content", () => {
        const event = {
            type: "tool_use_end",
            sessionId: "session-1",
            timestamp: new Date(0).toISOString(),
            data: { toolName: "read_file", toolResult: "   " },
        };
        expect(buildToolResultTimelineActionInfo(event)).toBeUndefined();
    });
});
