"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var run_context_exports = {};
__export(run_context_exports, {
  RunContext: () => RunContext
});
module.exports = __toCommonJS(run_context_exports);
var import_chat_context = require("../llm/chat_context.cjs");
var import_events = require("./events.cjs");
var import_speech_handle = require("./speech_handle.cjs");
class RunContext {
  constructor(session, speechHandle, functionCall) {
    this.session = session;
    this.speechHandle = speechHandle;
    this.functionCall = functionCall;
    this.initialStepIdx = speechHandle.numSteps - 1;
  }
  initialStepIdx;
  _executor;
  _firstUpdateFuture;
  _updates = [];
  _fillerSchedulers = [];
  get userData() {
    return this.session.userData;
  }
  /**
   * Waits for the speech playout corresponding to this function call step.
   *
   * Unlike {@link SpeechHandle.waitForPlayout}, which waits for the full
   * assistant turn to complete (including all function tools),
   * this method only waits for the assistant's spoken response prior to running
   * this tool to finish playing.
   */
  async waitForPlayout() {
    return this.speechHandle._waitForGeneration(this.initialStepIdx);
  }
  get updates() {
    return this._updates;
  }
  disallowInterruptions() {
    this.speechHandle.allowInterruptions = false;
  }
  /**
   * Report progress from a long-running tool — and, on the first call, turn the tool
   * **non-blocking** so the conversation continues while `execute()` keeps running.
   *
   * Behavior depends on whether this is the first `update()` for the call:
   *
   * - **First call:** resolves the pending tool result with `message` and marks the
   *   function call non-blocking (`functionCall.extra.__livekit_agents_tool_non_blocking = true`).
   *   The framework treats `message` as the tool's immediate output to the LLM and returns
   *   control to the session, so the agent can speak/listen while the tool continues in the
   *   background. Whatever `execute()` ultimately returns is delivered later as a deferred reply.
   * - **Subsequent calls:** each `message` is enqueued via the owning executor and delivered as a
   *   fresh assistant turn, gated on the session being idle (so updates never talk over the user).
   *   The arrival cadence is therefore conversational, not immediate.
   *
   * Message rendering:
   * - A **string** is rendered through a template — {@link RunContextUpdateOptions.template} if
   *   provided, otherwise the executor's configured `updateTemplate` — with `{functionName}`,
   *   `{callId}`, and `{message}` substituted. The default template tells the model the task is
   *   still running and not to fabricate results.
   * - A **non-string** value is used as-is (no templating), letting a tool emit structured output.
   *
   * Every update is also recorded in {@link RunContext.updates} (with a `_update_N` call-id suffix
   * for the 2nd and later updates) so the full progress trail is preserved in chat history.
   *
   * No-op for delivery when the tool isn't attached to an async-capable executor (e.g. a plain
   * blocking tool): the update is still recorded but nothing is sent.
   *
   * @param message - Progress text (templated) or a structured value (sent verbatim).
   * @param options - Per-call overrides; see {@link RunContextUpdateOptions}.
   */
  async update(message, options = {}) {
    var _a;
    for (const scheduler of this._fillerSchedulers) {
      scheduler.resetDwell();
    }
    const updateStep = this._updates.length;
    const renderedMessage = typeof message === "string" ? renderTemplate(options.template ?? ((_a = this._executor) == null ? void 0 : _a.toolOptions.updateTemplate), {
      functionName: this.functionCall.name,
      callId: this.functionCall.callId,
      message
    }) : message;
    const pair = this._makeUpdatePair(
      renderedMessage,
      updateStep > 0 ? `_update_${updateStep}` : ""
    );
    this._updates.push(pair);
    if (!this._executor) {
      return;
    }
    if (this._firstUpdateFuture && !this._firstUpdateFuture.done) {
      this._firstUpdateFuture.resolve(renderedMessage);
      this.functionCall.extra.__livekit_agents_tool_non_blocking = true;
      return;
    }
    await this._executor.enqueueReply(this, pair);
  }
  async foreground(fn) {
    await this._drainPendingReply();
    return this.session.waitForIdleAndHold(fn);
  }
  async filler(source, optionsOrFn, maybeFn) {
    const options = typeof optionsOrFn === "function" ? {} : optionsOrFn;
    const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
    if (!fn) {
      throw new Error("RunContext.filler requires a callback scope");
    }
    const scheduler = new FillerScheduler(this.session, this.speechHandle, source, options);
    this._fillerSchedulers.push(scheduler);
    try {
      return await fn();
    } finally {
      await scheduler.close();
      this._fillerSchedulers = this._fillerSchedulers.filter((s) => s !== scheduler);
    }
  }
  _attachExecutor(executor, firstUpdateFuture) {
    if (this._firstUpdateFuture !== void 0) {
      throw new Error("Executor already attached");
    }
    this._executor = executor;
    this._firstUpdateFuture = firstUpdateFuture;
  }
  _detachExecutor() {
    this._executor = void 0;
    this._firstUpdateFuture = void 0;
  }
  async _drainPendingReply() {
    var _a;
    if (!((_a = this._executor) == null ? void 0 : _a.replyTask)) return;
    try {
      await this._executor.replyTask;
    } catch {
    }
  }
  _makeUpdatePair(message, callIdSuffix = "") {
    const fncCall = import_chat_context.FunctionCall.create({
      callId: `${this.functionCall.callId}${callIdSuffix}`,
      name: this.functionCall.name,
      args: this.functionCall.args,
      extra: { ...this.functionCall.extra }
    });
    return [
      fncCall,
      import_chat_context.FunctionCallOutput.create({
        name: fncCall.name,
        callId: fncCall.callId,
        output: stringifyToolOutput(message),
        isError: false
      })
    ];
  }
  _recordUpdatePair(pair) {
    this._updates.push(pair);
  }
}
function renderTemplate(template, args) {
  if (!template) return args.message;
  if (typeof template === "function") return template(args);
  return template.replaceAll("{functionName}", args.functionName).replaceAll("{callId}", args.callId).replaceAll("{message}", args.message);
}
function stringifyToolOutput(value) {
  if (typeof value === "string") return value;
  if (value === void 0 || value === null) return "";
  if (typeof value === "number" || typeof value === "boolean") return String(value);
  try {
    return JSON.stringify(value);
  } catch {
    return String(value);
  }
}
class FillerScheduler {
  constructor(session, speechHandle, source, options) {
    this.session = session;
    this.speechHandle = speechHandle;
    this.source = source;
    const { delay = 0, interval, maxSteps, signal } = options;
    if (delay < 0) {
      throw new Error("delay must be non-negative");
    }
    if (interval !== void 0 && interval < 0) {
      throw new Error("interval must be non-negative when set");
    }
    if (maxSteps !== void 0 && maxSteps < 0) {
      throw new Error("maxSteps must be non-negative when set");
    }
    this.delay = delay;
    this.interval = interval;
    this.maxSteps = maxSteps;
    if (signal == null ? void 0 : signal.aborted) {
      this.abortController.abort();
    } else {
      signal == null ? void 0 : signal.addEventListener("abort", () => this.abortController.abort(), { once: true });
    }
    this.task = this.run();
    void this.task.catch(() => void 0);
  }
  abortController = new AbortController();
  delay;
  interval;
  maxSteps;
  task;
  dwellAbortController;
  createdSpeeches = [];
  resetDwell() {
    var _a;
    (_a = this.dwellAbortController) == null ? void 0 : _a.abort();
  }
  async close() {
    this.abortController.abort();
    await this.task.catch(() => void 0);
  }
  async run() {
    const onAgentStateChanged = (event) => {
      if (event.newState === "speaking" || event.newState === "thinking") {
        this.resetDwell();
      }
    };
    const onUserStateChanged = (event) => {
      if (event.newState === "speaking") {
        this.resetDwell();
      }
    };
    this.session.on(import_events.AgentSessionEventTypes.AgentStateChanged, onAgentStateChanged);
    this.session.on(import_events.AgentSessionEventTypes.UserStateChanged, onUserStateChanged);
    const loop = this.loop();
    try {
      await this.speechHandle.waitIfNotInterrupted([loop]);
    } finally {
      this.abortController.abort();
      this.session.off(import_events.AgentSessionEventTypes.AgentStateChanged, onAgentStateChanged);
      this.session.off(import_events.AgentSessionEventTypes.UserStateChanged, onUserStateChanged);
      await loop.catch(() => void 0);
    }
  }
  async loop() {
    while (!this.abortController.signal.aborted) {
      const idleReached = await waitUnlessAborted(
        this.session.waitForIdle(),
        this.abortController.signal
      );
      if (!idleReached) return;
      this.dwellAbortController = new AbortController();
      const dwellCompleted = await sleepUnlessAborted(this.delay, [
        this.abortController.signal,
        this.dwellAbortController.signal
      ]);
      this.dwellAbortController = void 0;
      if (!dwellCompleted) {
        continue;
      }
      if (this.maxSteps !== void 0 && this.createdSpeeches.length >= this.maxSteps) {
        return;
      }
      const handle = this.createSpeech();
      if (handle) {
        this.createdSpeeches.push(handle);
      }
      if (this.interval === void 0 || this.maxSteps !== void 0 && this.createdSpeeches.length >= this.maxSteps) {
        return;
      }
      const intervalCompleted = await sleepUnlessAborted(this.interval, [
        this.abortController.signal
      ]);
      if (!intervalCompleted) return;
    }
  }
  createSpeech() {
    const value = typeof this.source === "function" ? this.source(this.createdSpeeches.length) : this.source;
    if (typeof value === "string") {
      return this.session.say(value);
    }
    if ((0, import_speech_handle.isSpeechHandle)(value)) {
      return value;
    }
    return void 0;
  }
}
async function waitUnlessAborted(promise, signal) {
  if (signal.aborted) return false;
  let onAbort;
  const abortPromise = new Promise((resolve) => {
    onAbort = () => resolve(false);
    signal.addEventListener("abort", onAbort, { once: true });
  });
  try {
    return await Promise.race([promise.then(() => true), abortPromise]);
  } finally {
    if (onAbort) {
      signal.removeEventListener("abort", onAbort);
    }
  }
}
async function sleepUnlessAborted(ms, signals) {
  if (signals.some((signal) => signal.aborted)) return false;
  return new Promise((resolve) => {
    const timeout = setTimeout(() => {
      cleanup();
      resolve(true);
    }, ms);
    const onAbort = () => {
      clearTimeout(timeout);
      cleanup();
      resolve(false);
    };
    const cleanup = () => {
      for (const signal of signals) {
        signal.removeEventListener("abort", onAbort);
      }
    };
    for (const signal of signals) {
      signal.addEventListener("abort", onAbort, { once: true });
    }
  });
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  RunContext
});
//# sourceMappingURL=run_context.cjs.map