"use strict";
var import_web = require("stream/web");
var import_vitest = require("vitest");
var import_zod = require("zod");
var import_llm = require("../llm/index.cjs");
var import_log = require("../log.cjs");
var import_utils = require("../utils.cjs");
var import_generation = require("./generation.cjs");
function createStringStream(chunks, delayMs = 0) {
  return new import_web.ReadableStream({
    async start(controller) {
      for (const c of chunks) {
        if (delayMs > 0) {
          await (0, import_utils.delay)(delayMs);
        }
        controller.enqueue(c);
      }
      controller.close();
    }
  });
}
function createFunctionCallStream(fc) {
  return new import_web.ReadableStream({
    start(controller) {
      controller.enqueue(fc);
      controller.close();
    }
  });
}
function createFunctionCallStreamFromArray(fcs) {
  return new import_web.ReadableStream({
    start(controller) {
      for (const fc of fcs) {
        controller.enqueue(fc);
      }
      controller.close();
    }
  });
}
(0, import_vitest.describe)("Generation + Tool Execution", () => {
  (0, import_log.initializeLogger)({ pretty: false, level: "silent" });
  (0, import_vitest.afterEach)(() => {
    import_vitest.vi.useRealTimers();
  });
  (0, import_vitest.it)("should not abort tool when preamble forwarders are cleaned up", async () => {
    var _a, _b;
    import_vitest.vi.useFakeTimers();
    const replyAbortController = new AbortController();
    const forwarderController = new AbortController();
    const chunks = Array.from({ length: 50 }, () => `Hi.`);
    const fullPreambleText = chunks.join("");
    const preamble = createStringStream(chunks, 20);
    const [textForwardTask, textOut] = (0, import_generation.performTextForwarding)(
      preamble,
      forwarderController,
      null
    );
    let toolAborted = false;
    const getWeather = (0, import_llm.tool)({
      description: "weather",
      parameters: import_zod.z.object({ location: import_zod.z.string() }),
      execute: async ({ location }, { abortSignal }) => {
        abortSignal.addEventListener("abort", () => {
          toolAborted = true;
        });
        await (0, import_utils.delay)(6e3);
        return `Sunny in ${location}`;
      }
    });
    const fc = import_llm.FunctionCall.create({
      callId: "call_1",
      name: "getWeather",
      args: JSON.stringify({ location: "San Francisco" })
    });
    const toolCallStream = createFunctionCallStream(fc);
    const [execTask, toolOutput] = (0, import_generation.performToolExecutions)({
      session: {},
      speechHandle: { id: "speech_test", _itemAdded: () => {
      } },
      toolCtx: { getWeather },
      toolCallStream,
      controller: replyAbortController,
      onToolExecutionStarted: () => {
      },
      onToolExecutionCompleted: () => {
      }
    });
    await toolOutput.firstToolStartedFuture.await;
    await import_vitest.vi.advanceTimersByTimeAsync(100);
    const cancelForwarder = (0, import_utils.cancelAndWait)([textForwardTask], 5e3);
    await import_vitest.vi.advanceTimersByTimeAsync(20);
    await cancelForwarder;
    await import_vitest.vi.advanceTimersByTimeAsync(6e3);
    await execTask.result;
    (0, import_vitest.expect)(toolOutput.output.length).toBe(1);
    const out = toolOutput.output[0];
    (0, import_vitest.expect)((_a = out.toolCallOutput) == null ? void 0 : _a.isError).toBe(false);
    (0, import_vitest.expect)((_b = out.toolCallOutput) == null ? void 0 : _b.output).toContain("Sunny in San Francisco");
    (0, import_vitest.expect)(textOut.text).not.toBe(fullPreambleText);
    (0, import_vitest.expect)(toolAborted).toBe(false);
  }, 3e4);
  (0, import_vitest.it)("should return basic tool execution output", async () => {
    var _a, _b;
    const replyAbortController = new AbortController();
    const echo = (0, import_llm.tool)({
      description: "echo",
      parameters: import_zod.z.object({ msg: import_zod.z.string() }),
      execute: async ({ msg }) => `echo: ${msg}`
    });
    const fc = import_llm.FunctionCall.create({
      callId: "call_2",
      name: "echo",
      args: JSON.stringify({ msg: "hello" })
    });
    const toolCallStream = createFunctionCallStream(fc);
    const [execTask, toolOutput] = (0, import_generation.performToolExecutions)({
      session: {},
      speechHandle: { id: "speech_test2", _itemAdded: () => {
      } },
      toolCtx: { echo },
      toolCallStream,
      controller: replyAbortController
    });
    await execTask.result;
    (0, import_vitest.expect)(toolOutput.output.length).toBe(1);
    const out = toolOutput.output[0];
    (0, import_vitest.expect)((_a = out == null ? void 0 : out.toolCallOutput) == null ? void 0 : _a.isError).toBe(false);
    (0, import_vitest.expect)((_b = out == null ? void 0 : out.toolCallOutput) == null ? void 0 : _b.output).toContain("echo: hello");
  });
  (0, import_vitest.it)("should repair and canonicalize leaked template tokens in tool args", async () => {
    var _a, _b;
    const replyAbortController = new AbortController();
    const removeOrderItem = (0, import_llm.tool)({
      description: "remove order item",
      parameters: import_zod.z.object({ orderId: import_zod.z.array(import_zod.z.string()) }),
      execute: async ({ orderId }) => orderId.join(",")
    });
    const rawArgs = '{"orderId": ["<|\\"|\\"O_WAAB70<|\\"|\\"]}';
    const fc = import_llm.FunctionCall.create({
      callId: "call_repair_args",
      name: "removeOrderItem",
      args: rawArgs
    });
    const toolCallStream = createFunctionCallStream(fc);
    const [execTask, toolOutput] = (0, import_generation.performToolExecutions)({
      session: {},
      speechHandle: { id: "speech_repair", _itemAdded: () => {
      } },
      toolCtx: { removeOrderItem },
      toolCallStream,
      controller: replyAbortController
    });
    await execTask.result;
    (0, import_vitest.expect)(toolOutput.output.length).toBe(1);
    const out = toolOutput.output[0];
    (0, import_vitest.expect)((_a = out == null ? void 0 : out.toolCallOutput) == null ? void 0 : _a.isError).toBe(false);
    (0, import_vitest.expect)((_b = out == null ? void 0 : out.toolCallOutput) == null ? void 0 : _b.output).toBe('"O_WAAB70"');
    (0, import_vitest.expect)(fc.args).not.toBe(rawArgs);
    (0, import_vitest.expect)(JSON.parse(fc.args)).toEqual({ orderId: ["O_WAAB70"] });
  });
  (0, import_vitest.it)("should abort tool when reply is aborted mid-execution", async () => {
    var _a;
    const replyAbortController = new AbortController();
    let aborted = false;
    const longOp = (0, import_llm.tool)({
      description: "longOp",
      parameters: import_zod.z.object({ ms: import_zod.z.number() }),
      execute: async ({ ms }, { abortSignal }) => {
        abortSignal.addEventListener("abort", () => {
          aborted = true;
        });
        await (0, import_utils.delay)(ms);
        return "done";
      }
    });
    const fc = import_llm.FunctionCall.create({
      callId: "call_abort_1",
      name: "longOp",
      args: JSON.stringify({ ms: 5e3 })
    });
    const toolCallStream = createFunctionCallStream(fc);
    const [execTask, toolOutput] = (0, import_generation.performToolExecutions)({
      session: {},
      speechHandle: { id: "speech_abort", _itemAdded: () => {
      } },
      toolCtx: { longOp },
      toolCallStream,
      controller: replyAbortController
    });
    await toolOutput.firstToolStartedFuture.await;
    replyAbortController.abort();
    await execTask.result;
    (0, import_vitest.expect)(aborted).toBe(true);
    (0, import_vitest.expect)(toolOutput.output.length).toBe(1);
    const out = toolOutput.output[0];
    (0, import_vitest.expect)((_a = out == null ? void 0 : out.toolCallOutput) == null ? void 0 : _a.isError).toBe(true);
  }, 2e4);
  (0, import_vitest.it)("should surface zod validation errors to the LLM with field-level detail", async () => {
    var _a, _b;
    const replyAbortController = new AbortController();
    const echo = (0, import_llm.tool)({
      description: "echo",
      parameters: import_zod.z.object({ msg: import_zod.z.string() }),
      execute: async ({ msg }) => `echo: ${msg}`
    });
    const fc = import_llm.FunctionCall.create({
      callId: "call_invalid_args",
      name: "echo",
      args: JSON.stringify({ msg: 123 })
    });
    const toolCallStream = createFunctionCallStream(fc);
    const [execTask, toolOutput] = (0, import_generation.performToolExecutions)({
      session: {},
      speechHandle: { id: "speech_invalid", _itemAdded: () => {
      } },
      toolCtx: { echo },
      toolCallStream,
      controller: replyAbortController
    });
    await execTask.result;
    (0, import_vitest.expect)(toolOutput.output.length).toBe(1);
    const out = toolOutput.output[0];
    (0, import_vitest.expect)((_a = out == null ? void 0 : out.toolCallOutput) == null ? void 0 : _a.isError).toBe(true);
    const output = ((_b = out == null ? void 0 : out.toolCallOutput) == null ? void 0 : _b.output) ?? "";
    (0, import_vitest.expect)(output).toContain("Invalid arguments");
    (0, import_vitest.expect)(output).toContain("echo");
    (0, import_vitest.expect)(output).toContain("msg");
    (0, import_vitest.expect)(output).not.toContain("An internal error occurred");
  });
  (0, import_vitest.it)("should surface JSON parse errors to the LLM", async () => {
    var _a, _b;
    const replyAbortController = new AbortController();
    const echo = (0, import_llm.tool)({
      description: "echo",
      parameters: import_zod.z.object({ msg: import_zod.z.string() }),
      execute: async ({ msg }) => `echo: ${msg}`
    });
    const fc = import_llm.FunctionCall.create({
      callId: "call_invalid_json",
      name: "echo",
      args: "definitely not json"
    });
    const toolCallStream = createFunctionCallStream(fc);
    const [execTask, toolOutput] = (0, import_generation.performToolExecutions)({
      session: {},
      speechHandle: { id: "speech_bad_json", _itemAdded: () => {
      } },
      toolCtx: { echo },
      toolCallStream,
      controller: replyAbortController
    });
    await execTask.result;
    (0, import_vitest.expect)(toolOutput.output.length).toBe(1);
    const out = toolOutput.output[0];
    (0, import_vitest.expect)((_a = out == null ? void 0 : out.toolCallOutput) == null ? void 0 : _a.isError).toBe(true);
    const output = ((_b = out == null ? void 0 : out.toolCallOutput) == null ? void 0 : _b.output) ?? "";
    (0, import_vitest.expect)(output).toContain("Invalid arguments");
    (0, import_vitest.expect)(output).toContain("echo");
    (0, import_vitest.expect)(output).not.toContain("An internal error occurred");
  });
  (0, import_vitest.it)("should mask generic tool exceptions so internal details do not reach the LLM", async () => {
    var _a, _b, _c;
    const replyAbortController = new AbortController();
    const sensitive = (0, import_llm.tool)({
      description: "sensitive",
      parameters: import_zod.z.object({}),
      execute: async () => {
        throw new Error("database connection failed: postgres://admin:hunter2@db.internal:5432");
      }
    });
    const fc = import_llm.FunctionCall.create({
      callId: "call_generic_error",
      name: "sensitive",
      args: JSON.stringify({})
    });
    const toolCallStream = createFunctionCallStream(fc);
    const [execTask, toolOutput] = (0, import_generation.performToolExecutions)({
      session: {},
      speechHandle: { id: "speech_generic_err", _itemAdded: () => {
      } },
      toolCtx: { sensitive },
      toolCallStream,
      controller: replyAbortController
    });
    await execTask.result;
    (0, import_vitest.expect)(toolOutput.output.length).toBe(1);
    const out = toolOutput.output[0];
    (0, import_vitest.expect)((_a = out == null ? void 0 : out.toolCallOutput) == null ? void 0 : _a.isError).toBe(true);
    const output = ((_b = out == null ? void 0 : out.toolCallOutput) == null ? void 0 : _b.output) ?? "";
    (0, import_vitest.expect)(output).toBe("An internal error occurred");
    (0, import_vitest.expect)(output).not.toContain("database");
    (0, import_vitest.expect)(output).not.toContain("hunter2");
    (0, import_vitest.expect)(output).not.toContain("postgres");
    (0, import_vitest.expect)((_c = out == null ? void 0 : out.rawException) == null ? void 0 : _c.message).toContain("hunter2");
  });
  (0, import_vitest.it)("should forward ToolError messages to the LLM verbatim (escape hatch)", async () => {
    var _a, _b;
    const replyAbortController = new AbortController();
    const checked = (0, import_llm.tool)({
      description: "checked",
      parameters: import_zod.z.object({ qty: import_zod.z.number() }),
      execute: async ({ qty }) => {
        if (qty <= 0) {
          throw new import_llm.ToolError("qty must be positive \u2014 try again with a value greater than 0");
        }
        return qty;
      }
    });
    const fc = import_llm.FunctionCall.create({
      callId: "call_tool_error",
      name: "checked",
      args: JSON.stringify({ qty: -1 })
    });
    const toolCallStream = createFunctionCallStream(fc);
    const [execTask, toolOutput] = (0, import_generation.performToolExecutions)({
      session: {},
      speechHandle: { id: "speech_tool_error", _itemAdded: () => {
      } },
      toolCtx: { checked },
      toolCallStream,
      controller: replyAbortController
    });
    await execTask.result;
    (0, import_vitest.expect)(toolOutput.output.length).toBe(1);
    const out = toolOutput.output[0];
    (0, import_vitest.expect)((_a = out == null ? void 0 : out.toolCallOutput) == null ? void 0 : _a.isError).toBe(true);
    (0, import_vitest.expect)((_b = out == null ? void 0 : out.toolCallOutput) == null ? void 0 : _b.output).toBe(
      "qty must be positive \u2014 try again with a value greater than 0"
    );
  });
  (0, import_vitest.it)("should handle multiple tool calls within a single stream", async () => {
    var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
    const replyAbortController = new AbortController();
    const sum = (0, import_llm.tool)({
      description: "sum",
      parameters: import_zod.z.object({ a: import_zod.z.number(), b: import_zod.z.number() }),
      execute: async ({ a, b }) => a + b
    });
    const upper = (0, import_llm.tool)({
      description: "upper",
      parameters: import_zod.z.object({ s: import_zod.z.string() }),
      execute: async ({ s }) => s.toUpperCase()
    });
    const fc1 = import_llm.FunctionCall.create({
      callId: "call_multi_1",
      name: "sum",
      args: JSON.stringify({ a: 2, b: 3 })
    });
    const fc2 = import_llm.FunctionCall.create({
      callId: "call_multi_2",
      name: "upper",
      args: JSON.stringify({ s: "hey" })
    });
    const toolCallStream = createFunctionCallStreamFromArray([fc1, fc2]);
    const [execTask, toolOutput] = (0, import_generation.performToolExecutions)({
      session: {},
      speechHandle: { id: "speech_multi", _itemAdded: () => {
      } },
      toolCtx: { sum, upper },
      toolCallStream,
      controller: replyAbortController
    });
    await execTask.result;
    (0, import_vitest.expect)(toolOutput.output.length).toBe(2);
    const sorted = [...toolOutput.output].sort(
      (a, b) => a.toolCall.callId.localeCompare(b.toolCall.callId)
    );
    (0, import_vitest.expect)((_a = sorted[0]) == null ? void 0 : _a.toolCall.name).toBe("sum");
    (0, import_vitest.expect)((_c = (_b = sorted[0]) == null ? void 0 : _b.toolCallOutput) == null ? void 0 : _c.isError).toBe(false);
    (0, import_vitest.expect)((_e = (_d = sorted[0]) == null ? void 0 : _d.toolCallOutput) == null ? void 0 : _e.output).toBe("5");
    (0, import_vitest.expect)((_f = sorted[1]) == null ? void 0 : _f.toolCall.name).toBe("upper");
    (0, import_vitest.expect)((_h = (_g = sorted[1]) == null ? void 0 : _g.toolCallOutput) == null ? void 0 : _h.isError).toBe(false);
    (0, import_vitest.expect)((_j = (_i = sorted[1]) == null ? void 0 : _i.toolCallOutput) == null ? void 0 : _j.output).toBe('"HEY"');
  });
});
//# sourceMappingURL=generation_tools.test.cjs.map