import { once } from "node:events";
import { setTimeout as waitFor } from "node:timers/promises";
import { getJobContext } from "../../job.js";
import {
  RealtimeModel,
  Toolset,
  tool
} from "../../llm/index.js";
import { log } from "../../log.js";
import { AgentSessionEventTypes } from "../../voice/events.js";
const END_CALL_REPLY_TIMEOUT = 5e3;
function onceEvent(session, event, options) {
  return once(session, event, options).then(
    ([payload]) => payload,
    (err) => {
      var _a;
      if ((_a = options == null ? void 0 : options.signal) == null ? void 0 : _a.aborted) return void 0;
      throw err;
    }
  );
}
const END_CALL_DESCRIPTION = `
Ends the current call and disconnects immediately.

Call when:
- The user clearly indicates they are done (e.g., "that's all, bye").

Do not call when:
- The user asks to pause, hold, or transfer.
- Intent is unclear.

This is the final action the agent can take.
Once called, no further interaction is possible with the user.
Don't generate any other text or response when the tool is called.
`;
function createEndCallTool({
  extraDescription = "",
  deleteRoom = true,
  endInstructions = "say goodbye to the user",
  onToolCalled,
  onToolCompleted
} = {}) {
  const delayedSessionShutdown = async (session, signal) => {
    const speech = onceEvent(session, AgentSessionEventTypes.SpeechCreated, { signal }).then(
      (event) => event == null ? void 0 : event.speechHandle
    );
    const timeout = waitFor(END_CALL_REPLY_TIMEOUT, "timeout", { signal }).catch(
      () => void 0
    );
    const winner = await Promise.race([speech, timeout]);
    if (signal.aborted) return;
    if (winner === "timeout") {
      log().warn("tool reply timed out, shutting down session");
      session.shutdown();
    } else if (winner) {
      await winner.waitForPlayout();
      session.shutdown();
    }
  };
  return Toolset.create({
    id: "end_call",
    tools: [
      tool({
        name: "end_call",
        description: `${END_CALL_DESCRIPTION}
${extraDescription}`,
        execute: async (_args, { ctx, abortSignal }) => {
          log().debug("end_call tool called");
          const session = ctx.session;
          const llm = session.currentAgent.getActivityOrThrow().llm;
          const controller = new AbortController();
          const signal = abortSignal ? AbortSignal.any([abortSignal, controller.signal]) : controller.signal;
          void onceEvent(session, AgentSessionEventTypes.Close, { signal }).then((event) => {
            if (!event) return;
            controller.abort();
            const jobCtx = getJobContext(false);
            if (!jobCtx) return;
            if (deleteRoom) {
              jobCtx.addShutdownCallback(async () => {
                log().info("deleting the room because the user ended the call");
                await jobCtx.deleteRoom();
              });
            }
            jobCtx.shutdown(String(event.reason));
          }).catch((error) => log().error({ error }, "error during end call shutdown"));
          ctx.speechHandle.addDoneCallback(() => {
            if (!(llm instanceof RealtimeModel) || !llm.capabilities.autoToolReplyGeneration) {
              session.shutdown();
              return;
            }
            void delayedSessionShutdown(session, signal).catch(
              (error) => log().error({ error }, "error during delayed session shutdown")
            );
          });
          if (onToolCalled) {
            await onToolCalled({ ctx, arguments: {} });
          }
          const completedEvent = {
            ctx,
            output: endInstructions === null ? void 0 : { type: "output", value: endInstructions }
          };
          if (onToolCompleted) {
            await onToolCompleted(completedEvent);
          }
          return endInstructions ?? void 0;
        }
      })
    ]
  });
}
export {
  END_CALL_DESCRIPTION,
  createEndCallTool
};
//# sourceMappingURL=end_call.js.map