import { AsyncLocalStorage } from "node:async_hooks";
import { ReadableStream } from "node:stream/web";
import {
  LLM as InferenceLLM,
  STT as InferenceSTT,
  TTS as InferenceTTS
} from "../inference/index.js";
import { ReadonlyChatContext } from "../llm/chat_context.js";
import {
  ChatContext,
  LLM
} from "../llm/index.js";
import { StreamAdapter as STTStreamAdapter } from "../stt/index.js";
import { SentenceTokenizer as BasicSentenceTokenizer } from "../tokenize/basic/index.js";
import { SynthesizeStream, StreamAdapter as TTSStreamAdapter } from "../tts/index.js";
import { USERDATA_TIMED_TRANSCRIPT } from "../types.js";
const asyncLocalStorage = new AsyncLocalStorage();
const STOP_RESPONSE_SYMBOL = Symbol("StopResponse");
class StopResponse extends Error {
  constructor() {
    super();
    this.name = "StopResponse";
    Object.defineProperty(this, STOP_RESPONSE_SYMBOL, {
      value: true
    });
  }
}
function isStopResponse(value) {
  return value !== void 0 && value !== null && typeof value === "object" && STOP_RESPONSE_SYMBOL in value;
}
class Agent {
  _id;
  turnDetection;
  _stt;
  _vad;
  _llm;
  _tts;
  _useTtsAlignedTranscript;
  /** @internal */
  _agentActivity;
  /** @internal */
  _chatCtx;
  /** @internal */
  _instructions;
  /** @internal */
  _tools;
  constructor({
    id,
    instructions,
    chatCtx,
    tools,
    turnDetection,
    stt,
    vad,
    llm,
    tts,
    useTtsAlignedTranscript
  }) {
    if (id) {
      this._id = id;
    } else {
      const className = this.constructor.name;
      if (className === "Agent") {
        this._id = "default_agent";
      } else {
        this._id = className.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
      }
    }
    this._instructions = instructions;
    this._tools = { ...tools };
    this._chatCtx = chatCtx ? chatCtx.copy({
      toolCtx: this._tools
    }) : ChatContext.empty();
    this.turnDetection = turnDetection;
    this._vad = vad;
    if (typeof stt === "string") {
      this._stt = InferenceSTT.fromModelString(stt);
    } else {
      this._stt = stt;
    }
    if (typeof llm === "string") {
      this._llm = InferenceLLM.fromModelString(llm);
    } else {
      this._llm = llm;
    }
    if (typeof tts === "string") {
      this._tts = InferenceTTS.fromModelString(tts);
    } else {
      this._tts = tts;
    }
    this._useTtsAlignedTranscript = useTtsAlignedTranscript;
    this._agentActivity = void 0;
  }
  get vad() {
    return this._vad;
  }
  get stt() {
    return this._stt;
  }
  get llm() {
    return this._llm;
  }
  get tts() {
    return this._tts;
  }
  get useTtsAlignedTranscript() {
    return this._useTtsAlignedTranscript;
  }
  get chatCtx() {
    return new ReadonlyChatContext(this._chatCtx.items);
  }
  get id() {
    return this._id;
  }
  get instructions() {
    return this._instructions;
  }
  get toolCtx() {
    return { ...this._tools };
  }
  get session() {
    return this.getActivityOrThrow().agentSession;
  }
  async onEnter() {
  }
  async onExit() {
  }
  async transcriptionNode(text, modelSettings) {
    return Agent.default.transcriptionNode(this, text, modelSettings);
  }
  async onUserTurnCompleted(_chatCtx, _newMessage) {
  }
  async sttNode(audio, modelSettings) {
    return Agent.default.sttNode(this, audio, modelSettings);
  }
  async llmNode(chatCtx, toolCtx, modelSettings) {
    return Agent.default.llmNode(this, chatCtx, toolCtx, modelSettings);
  }
  async ttsNode(text, modelSettings) {
    return Agent.default.ttsNode(this, text, modelSettings);
  }
  async realtimeAudioOutputNode(audio, modelSettings) {
    return Agent.default.realtimeAudioOutputNode(this, audio, modelSettings);
  }
  // realtime_audio_output_node
  getActivityOrThrow() {
    if (!this._agentActivity) {
      throw new Error("Agent activity not found");
    }
    return this._agentActivity;
  }
  async updateChatCtx(chatCtx) {
    if (!this._agentActivity) {
      this._chatCtx = chatCtx.copy({ toolCtx: this.toolCtx });
      return;
    }
    this._agentActivity.updateChatCtx(chatCtx);
  }
  static default = {
    async sttNode(agent, audio, _modelSettings) {
      var _a;
      const activity = agent.getActivityOrThrow();
      if (!activity.stt) {
        throw new Error("sttNode called but no STT node is available");
      }
      let wrapped_stt = activity.stt;
      if (!wrapped_stt.capabilities.streaming) {
        const vad = agent.vad || activity.vad;
        if (!vad) {
          throw new Error(
            "STT does not support streaming, add a VAD to the AgentTask/VoiceAgent to enable streaming"
          );
        }
        wrapped_stt = new STTStreamAdapter(wrapped_stt, vad);
      }
      const connOptions = activity.agentSession.connOptions.sttConnOptions;
      const stream = wrapped_stt.stream({ connOptions });
      const audioInputStartedAt = ((_a = activity.agentSession._recorderIO) == null ? void 0 : _a.recordingStartedAt) ?? // Use recording start time if available
      activity.agentSession._startedAt ?? // Fallback to session start time
      Date.now();
      stream.startTimeOffset = (Date.now() - audioInputStartedAt) / 1e3;
      stream.updateInputStream(audio);
      let cleaned = false;
      const cleanup = () => {
        if (cleaned) return;
        cleaned = true;
        stream.detachInputStream();
        stream.close();
      };
      return new ReadableStream({
        async start(controller) {
          try {
            for await (const event of stream) {
              controller.enqueue(event);
            }
            controller.close();
          } finally {
            cleanup();
          }
        },
        cancel() {
          cleanup();
        }
      });
    },
    async llmNode(agent, chatCtx, toolCtx, modelSettings) {
      const activity = agent.getActivityOrThrow();
      if (!activity.llm) {
        throw new Error("llmNode called but no LLM node is available");
      }
      if (!(activity.llm instanceof LLM)) {
        throw new Error(
          "llmNode should only be used with LLM (non-multimodal/realtime APIs) nodes"
        );
      }
      const { toolChoice } = modelSettings;
      const connOptions = activity.agentSession.connOptions.llmConnOptions;
      const stream = activity.llm.chat({
        chatCtx,
        toolCtx,
        toolChoice,
        connOptions
      });
      let cleaned = false;
      const cleanup = () => {
        if (cleaned) return;
        cleaned = true;
        stream.close();
      };
      return new ReadableStream({
        async start(controller) {
          try {
            for await (const chunk of stream) {
              controller.enqueue(chunk);
            }
            controller.close();
          } finally {
            cleanup();
          }
        },
        cancel() {
          cleanup();
        }
      });
    },
    async ttsNode(agent, text, _modelSettings) {
      const activity = agent.getActivityOrThrow();
      if (!activity.tts) {
        throw new Error("ttsNode called but no TTS node is available");
      }
      let wrapped_tts = activity.tts;
      if (!activity.tts.capabilities.streaming) {
        wrapped_tts = new TTSStreamAdapter(wrapped_tts, new BasicSentenceTokenizer());
      }
      const connOptions = activity.agentSession.connOptions.ttsConnOptions;
      const stream = wrapped_tts.stream({ connOptions });
      stream.updateInputStream(text);
      let cleaned = false;
      const cleanup = () => {
        if (cleaned) return;
        cleaned = true;
        stream.close();
      };
      return new ReadableStream({
        async start(controller) {
          try {
            for await (const chunk of stream) {
              if (chunk === SynthesizeStream.END_OF_STREAM) {
                break;
              }
              if (chunk.timedTranscripts && chunk.timedTranscripts.length > 0) {
                chunk.frame.userdata[USERDATA_TIMED_TRANSCRIPT] = chunk.timedTranscripts;
              }
              controller.enqueue(chunk.frame);
            }
            controller.close();
          } finally {
            cleanup();
          }
        },
        cancel() {
          cleanup();
        }
      });
    },
    async transcriptionNode(agent, text, _modelSettings) {
      return text;
    },
    async realtimeAudioOutputNode(_agent, audio, _modelSettings) {
      return audio;
    }
  };
}
export {
  Agent,
  STOP_RESPONSE_SYMBOL,
  StopResponse,
  asyncLocalStorage,
  isStopResponse
};
//# sourceMappingURL=agent.js.map