"use strict";
var import_vitest = require("vitest");
var import_chat_context = require("../llm/chat_context.cjs");
var import_realtime = require("../llm/realtime.cjs");
var import_log = require("../log.cjs");
var import_agent = require("./agent.cjs");
var import_agent_activity = require("./agent_activity.cjs");
(0, import_log.initializeLogger)({ pretty: false, level: "silent" });
function createFakeActivity(agent, stt, inputStartedAt) {
  const detachedPipeline = { id: Symbol("pipeline") };
  const activity = {
    agent,
    audioRecognition: {
      detachSttPipeline: import_vitest.vi.fn(async () => detachedPipeline),
      inputStartedAt
    },
    stt,
    llm: void 0,
    tools: [],
    realtimeSession: void 0
  };
  return { activity, detachedPipeline };
}
async function detachResources(oldActivity, newActivity) {
  const fn = import_agent_activity.AgentActivity.prototype._detachReusableResources;
  return await fn.call(oldActivity, newActivity);
}
(0, import_vitest.describe)("AgentActivity STT handoff reuse eligibility", () => {
  (0, import_vitest.it)("reuses the pipeline when both activities share the same STT instance and sttNode", async () => {
    var _a;
    const sharedStt = { id: "shared-stt" };
    const oldActivity = createFakeActivity(new import_agent.Agent({ instructions: "a" }), sharedStt);
    const newActivity = createFakeActivity(new import_agent.Agent({ instructions: "b" }), sharedStt);
    const resources = await detachResources(oldActivity.activity, newActivity.activity);
    (0, import_vitest.expect)(resources.sttPipeline).toBe(oldActivity.detachedPipeline);
    (0, import_vitest.expect)((_a = oldActivity.activity.audioRecognition) == null ? void 0 : _a.detachSttPipeline).toHaveBeenCalledTimes(1);
  });
  (0, import_vitest.it)("carries the original input start time with a reused STT pipeline", async () => {
    const sharedStt = { id: "shared-stt" };
    const oldInputStartedAt = Date.now() - 6e4;
    const oldActivity = createFakeActivity(
      new import_agent.Agent({ instructions: "a" }),
      sharedStt,
      oldInputStartedAt
    );
    const newActivity = createFakeActivity(new import_agent.Agent({ instructions: "b" }), sharedStt);
    const resources = await detachResources(oldActivity.activity, newActivity.activity);
    (0, import_vitest.expect)(resources.sttPipeline).toBe(oldActivity.detachedPipeline);
    (0, import_vitest.expect)(resources.sttInputStartedAt).toBe(oldInputStartedAt);
  });
  (0, import_vitest.it)("does not reuse when the STT instances differ", async () => {
    var _a;
    const oldActivity = createFakeActivity(new import_agent.Agent({ instructions: "a" }), { id: "stt-a" });
    const newActivity = createFakeActivity(new import_agent.Agent({ instructions: "b" }), { id: "stt-b" });
    const resources = await detachResources(oldActivity.activity, newActivity.activity);
    (0, import_vitest.expect)(resources.sttPipeline).toBeUndefined();
    (0, import_vitest.expect)((_a = oldActivity.activity.audioRecognition) == null ? void 0 : _a.detachSttPipeline).not.toHaveBeenCalled();
  });
  (0, import_vitest.it)("does not reuse when either activity has no STT", async () => {
    var _a;
    const sharedStt = { id: "shared-stt" };
    const oldActivity = createFakeActivity(new import_agent.Agent({ instructions: "a" }), void 0);
    const newActivity = createFakeActivity(new import_agent.Agent({ instructions: "b" }), sharedStt);
    const resources = await detachResources(oldActivity.activity, newActivity.activity);
    (0, import_vitest.expect)(resources.sttPipeline).toBeUndefined();
    (0, import_vitest.expect)((_a = oldActivity.activity.audioRecognition) == null ? void 0 : _a.detachSttPipeline).not.toHaveBeenCalled();
  });
  (0, import_vitest.it)("does not reuse when the agents override sttNode differently", async () => {
    var _a;
    const sharedStt = { id: "shared-stt" };
    class AgentA extends import_agent.Agent {
      async sttNode(_audio, _modelSettings) {
        return null;
      }
    }
    class AgentB extends import_agent.Agent {
      async sttNode(_audio, _modelSettings) {
        return null;
      }
    }
    const oldActivity = createFakeActivity(new AgentA({ instructions: "a" }), sharedStt);
    const newActivity = createFakeActivity(new AgentB({ instructions: "b" }), sharedStt);
    const resources = await detachResources(oldActivity.activity, newActivity.activity);
    (0, import_vitest.expect)(resources.sttPipeline).toBeUndefined();
    (0, import_vitest.expect)((_a = oldActivity.activity.audioRecognition) == null ? void 0 : _a.detachSttPipeline).not.toHaveBeenCalled();
  });
  (0, import_vitest.it)("does not reuse when the new agent inherits the same custom sttNode implementation", async () => {
    var _a;
    const sharedStt = { id: "shared-stt" };
    class AgentA extends import_agent.Agent {
      async sttNode(_audio, _modelSettings) {
        return null;
      }
    }
    class AgentB extends AgentA {
    }
    const oldActivity = createFakeActivity(new AgentA({ instructions: "a" }), sharedStt);
    const newActivity = createFakeActivity(new AgentB({ instructions: "b" }), sharedStt);
    const resources = await detachResources(oldActivity.activity, newActivity.activity);
    (0, import_vitest.expect)(resources.sttPipeline).toBeUndefined();
    (0, import_vitest.expect)((_a = oldActivity.activity.audioRecognition) == null ? void 0 : _a.detachSttPipeline).not.toHaveBeenCalled();
  });
  (0, import_vitest.it)("does not reuse when the old activity has no audioRecognition", async () => {
    const sharedStt = { id: "shared-stt" };
    const oldActivity = createFakeActivity(new import_agent.Agent({ instructions: "a" }), sharedStt);
    const newActivity = createFakeActivity(new import_agent.Agent({ instructions: "b" }), sharedStt);
    oldActivity.activity.audioRecognition = void 0;
    const resources = await detachResources(oldActivity.activity, newActivity.activity);
    (0, import_vitest.expect)(resources.sttPipeline).toBeUndefined();
  });
});
(0, import_vitest.describe)("AgentActivity RT session reuse eligibility", () => {
  function createFakeRtSession() {
    return {
      chatCtx: import_chat_context.ChatContext.empty(),
      off: import_vitest.vi.fn(),
      on: import_vitest.vi.fn(),
      interrupt: import_vitest.vi.fn(),
      clearAudio: import_vitest.vi.fn(),
      close: import_vitest.vi.fn(async () => {
      })
    };
  }
  class FakeRealtimeModel extends import_realtime.RealtimeModel {
    get model() {
      return "fake";
    }
    label() {
      return "fake.RealtimeModel";
    }
    session() {
      throw new Error("not implemented");
    }
    async close() {
    }
  }
  function createFakeRealtimeModel(capabilitiesOverrides = {}) {
    const capabilities = {
      messageTruncation: false,
      turnDetection: false,
      userTranscription: false,
      autoToolReplyGeneration: false,
      audioOutput: true,
      manualFunctionCalls: false,
      midSessionChatCtxUpdate: false,
      midSessionInstructionsUpdate: false,
      midSessionToolsUpdate: false,
      ...capabilitiesOverrides
    };
    return new FakeRealtimeModel(capabilities);
  }
  function createRtActivity(agent, llm, rtSession) {
    return {
      agent,
      audioRecognition: void 0,
      stt: void 0,
      llm,
      tools: agent.toolCtx,
      realtimeSession: rtSession
    };
  }
  (0, import_vitest.it)("reuses RT session when same LLM, same instructions, equivalent context, and same tools", async () => {
    const sharedLlm = createFakeRealtimeModel();
    const rtSession = createFakeRtSession();
    const oldAgent = new import_agent.Agent({ instructions: "hello" });
    const newAgent = new import_agent.Agent({ instructions: "hello" });
    const oldActivity = createRtActivity(oldAgent, sharedLlm, rtSession);
    const newActivity = createRtActivity(newAgent, sharedLlm);
    const resources = await detachResources(oldActivity, newActivity);
    (0, import_vitest.expect)(resources.rtSession).toBe(rtSession);
    (0, import_vitest.expect)(oldActivity.realtimeSession).toBeUndefined();
    (0, import_vitest.expect)(rtSession.off).toHaveBeenCalled();
  });
  (0, import_vitest.it)("does not reuse RT session when LLM instances differ", async () => {
    const rtSession = createFakeRtSession();
    const oldLlm = createFakeRealtimeModel();
    const newLlm = createFakeRealtimeModel();
    const oldActivity = createRtActivity(new import_agent.Agent({ instructions: "a" }), oldLlm, rtSession);
    const newActivity = createRtActivity(new import_agent.Agent({ instructions: "a" }), newLlm);
    const resources = await detachResources(oldActivity, newActivity);
    (0, import_vitest.expect)(resources.rtSession).toBeUndefined();
  });
  (0, import_vitest.it)("does not reuse RT session when instructions differ and midSessionInstructionsUpdate is false", async () => {
    const sharedLlm = createFakeRealtimeModel({ midSessionInstructionsUpdate: false });
    const rtSession = createFakeRtSession();
    const oldActivity = createRtActivity(new import_agent.Agent({ instructions: "old" }), sharedLlm, rtSession);
    const newActivity = createRtActivity(new import_agent.Agent({ instructions: "new" }), sharedLlm);
    const resources = await detachResources(oldActivity, newActivity);
    (0, import_vitest.expect)(resources.rtSession).toBeUndefined();
  });
  (0, import_vitest.it)("reuses RT session when instructions differ but midSessionInstructionsUpdate is true", async () => {
    const sharedLlm = createFakeRealtimeModel({ midSessionInstructionsUpdate: true });
    const rtSession = createFakeRtSession();
    const oldActivity = createRtActivity(new import_agent.Agent({ instructions: "old" }), sharedLlm, rtSession);
    const newActivity = createRtActivity(new import_agent.Agent({ instructions: "new" }), sharedLlm);
    const resources = await detachResources(oldActivity, newActivity);
    (0, import_vitest.expect)(resources.rtSession).toBe(rtSession);
  });
  (0, import_vitest.it)("reuses RT session when context differs but midSessionChatCtxUpdate is true", async () => {
    const sharedLlm = createFakeRealtimeModel({ midSessionChatCtxUpdate: true });
    const rtSession = createFakeRtSession();
    const oldActivity = createRtActivity(new import_agent.Agent({ instructions: "same" }), sharedLlm, rtSession);
    const newActivity = createRtActivity(new import_agent.Agent({ instructions: "same" }), sharedLlm);
    const resources = await detachResources(oldActivity, newActivity);
    (0, import_vitest.expect)(resources.rtSession).toBe(rtSession);
  });
  (0, import_vitest.it)("does not reuse when no RT session exists", async () => {
    const sharedLlm = createFakeRealtimeModel();
    const oldActivity = createRtActivity(new import_agent.Agent({ instructions: "a" }), sharedLlm, void 0);
    const newActivity = createRtActivity(new import_agent.Agent({ instructions: "a" }), sharedLlm);
    const resources = await detachResources(oldActivity, newActivity);
    (0, import_vitest.expect)(resources.rtSession).toBeUndefined();
  });
  (0, import_vitest.it)("does not reuse when LLM is not a RealtimeModel", async () => {
    const rtSession = createFakeRtSession();
    const nonRealtimeLlm = { id: "plain-llm" };
    const oldActivity = createRtActivity(
      new import_agent.Agent({ instructions: "a" }),
      nonRealtimeLlm,
      rtSession
    );
    const newActivity = createRtActivity(new import_agent.Agent({ instructions: "a" }), nonRealtimeLlm);
    const resources = await detachResources(oldActivity, newActivity);
    (0, import_vitest.expect)(resources.rtSession).toBeUndefined();
  });
});
(0, import_vitest.describe)("AgentActivity blockNewTurns (handoff transition)", () => {
  function createBareActivity() {
    const activity = Object.create(import_agent_activity.AgentActivity.prototype);
    activity.logger = { warn: import_vitest.vi.fn(), debug: import_vitest.vi.fn(), info: import_vitest.vi.fn(), error: import_vitest.vi.fn() };
    activity.cancelPreemptiveGeneration = import_vitest.vi.fn();
    activity.createSpeechTask = import_vitest.vi.fn(() => ({ cancel: import_vitest.vi.fn() }));
    return activity;
  }
  const endOfTurnInfo = {
    newTranscript: "hello again",
    transcriptConfidence: 1,
    transcriptionDelay: 0,
    endOfUtteranceDelay: 0,
    startedSpeakingAt: void 0,
    stoppedSpeakingAt: void 0
  };
  (0, import_vitest.it)("onEndOfTurn skips a new user turn while new turns are blocked, even when scheduling is still running", async () => {
    const activity = createBareActivity();
    activity._schedulingPaused = false;
    activity.newTurnsBlocked = false;
    activity.blockNewTurns();
    const handled = await activity.onEndOfTurn(endOfTurnInfo);
    (0, import_vitest.expect)(handled).toBe(true);
    (0, import_vitest.expect)(activity.cancelPreemptiveGeneration).toHaveBeenCalledTimes(1);
    (0, import_vitest.expect)(activity.createSpeechTask).not.toHaveBeenCalled();
  });
  (0, import_vitest.it)("onEndOfTurn schedules the turn normally when new turns are not blocked", async () => {
    const activity = createBareActivity();
    activity._schedulingPaused = false;
    activity.newTurnsBlocked = false;
    activity.agent = { stt: void 0 };
    activity.agentSession = { stt: void 0 };
    activity._currentSpeech = void 0;
    activity._userTurnCompletedTask = void 0;
    const handled = await activity.onEndOfTurn(endOfTurnInfo);
    (0, import_vitest.expect)(handled).toBe(true);
    (0, import_vitest.expect)(activity.createSpeechTask).toHaveBeenCalledTimes(1);
  });
  (0, import_vitest.it)("onUserTurnExceeded is independent of newTurnsBlocked", () => {
    const activity = createBareActivity();
    activity._schedulingPaused = false;
    activity.newTurnsBlocked = false;
    activity.userTurnExceededLocked = false;
    activity.userTurnExceededTask = void 0;
    activity.blockNewTurns();
    const ev = {
      type: "user_turn_exceeded",
      transcript: "hi",
      accumulatedTranscript: "hi",
      accumulatedWordCount: 10,
      duration: 5e3,
      createdAt: Date.now()
    };
    activity.onUserTurnExceeded(ev);
    (0, import_vitest.expect)(activity.createSpeechTask).toHaveBeenCalledTimes(1);
  });
  (0, import_vitest.it)("userTurnCompleted skips before the callback when new turns are blocked (not closing)", async () => {
    const activity = createBareActivity();
    activity._schedulingPaused = false;
    activity.newTurnsBlocked = false;
    activity._currentSpeech = void 0;
    const onUserTurnCompleted = import_vitest.vi.fn(async () => {
    });
    activity.agent = { llm: void 0, chatCtx: import_chat_context.ChatContext.empty(), onUserTurnCompleted };
    activity.agentSession = { llm: void 0, _closing: false, _conversationItemAdded: import_vitest.vi.fn() };
    activity.blockNewTurns();
    await activity.userTurnCompleted(endOfTurnInfo);
    (0, import_vitest.expect)(onUserTurnCompleted).not.toHaveBeenCalled();
    (0, import_vitest.expect)(activity.agentSession._conversationItemAdded).not.toHaveBeenCalled();
    (0, import_vitest.expect)(activity.createSpeechTask).not.toHaveBeenCalled();
  });
  (0, import_vitest.it)("userTurnCompleted commits the skipped message to chat ctx when closing", async () => {
    const activity = createBareActivity();
    activity._schedulingPaused = false;
    activity.newTurnsBlocked = false;
    activity._currentSpeech = void 0;
    const push = import_vitest.vi.fn();
    const conversationItemAdded = import_vitest.vi.fn();
    activity.agent = {
      llm: void 0,
      chatCtx: import_chat_context.ChatContext.empty(),
      _chatCtx: { items: { push } },
      onUserTurnCompleted: import_vitest.vi.fn(async () => {
      })
    };
    activity.agentSession = {
      llm: void 0,
      _closing: true,
      _conversationItemAdded: conversationItemAdded
    };
    activity.blockNewTurns();
    await activity.userTurnCompleted(endOfTurnInfo);
    (0, import_vitest.expect)(push).toHaveBeenCalledTimes(1);
    (0, import_vitest.expect)(conversationItemAdded).toHaveBeenCalledTimes(1);
    (0, import_vitest.expect)(activity.createSpeechTask).not.toHaveBeenCalled();
  });
  (0, import_vitest.it)("userTurnCompleted re-checks after the callback when a handoff blocks new turns mid-callback", async () => {
    const activity = createBareActivity();
    activity._schedulingPaused = false;
    activity.newTurnsBlocked = false;
    activity._currentSpeech = void 0;
    const onUserTurnCompleted = import_vitest.vi.fn(async () => {
      activity.newTurnsBlocked = true;
    });
    const plainLlm = { id: "plain-llm" };
    activity.agent = { llm: plainLlm, chatCtx: import_chat_context.ChatContext.empty(), onUserTurnCompleted };
    activity.agentSession = { llm: void 0, _closing: false, _conversationItemAdded: import_vitest.vi.fn() };
    await activity.userTurnCompleted(endOfTurnInfo);
    (0, import_vitest.expect)(onUserTurnCompleted).toHaveBeenCalledTimes(1);
    (0, import_vitest.expect)(activity.createSpeechTask).not.toHaveBeenCalled();
  });
});
(0, import_vitest.describe)("cleanupReusableResources", () => {
  (0, import_vitest.it)("closes both STT pipeline and RT session", async () => {
    const sttClose = import_vitest.vi.fn(async () => {
    });
    const rtClose = import_vitest.vi.fn(async () => {
    });
    const resources = {
      sttPipeline: { close: sttClose },
      rtSession: { close: rtClose }
    };
    await (0, import_agent_activity.cleanupReusableResources)(resources, (0, import_log.log)());
    (0, import_vitest.expect)(sttClose).toHaveBeenCalledTimes(1);
    (0, import_vitest.expect)(rtClose).toHaveBeenCalledTimes(1);
    (0, import_vitest.expect)(resources.sttPipeline).toBeUndefined();
    (0, import_vitest.expect)(resources.rtSession).toBeUndefined();
  });
  (0, import_vitest.it)("handles partial resources (only STT)", async () => {
    const sttClose = import_vitest.vi.fn(async () => {
    });
    const resources = {
      sttPipeline: { close: sttClose }
    };
    await (0, import_agent_activity.cleanupReusableResources)(resources, (0, import_log.log)());
    (0, import_vitest.expect)(sttClose).toHaveBeenCalledTimes(1);
    (0, import_vitest.expect)(resources.sttPipeline).toBeUndefined();
  });
  (0, import_vitest.it)("handles empty resources", async () => {
    const resources = {};
    await (0, import_agent_activity.cleanupReusableResources)(resources, (0, import_log.log)());
  });
});
//# sourceMappingURL=agent_activity_handoff.test.cjs.map