import { AudioResampler } from "@livekit/rtc-node";
import { ThrowsPromise } from "@livekit/throws-transformer/throws";
import { context as otelContext } from "@opentelemetry/api";
import {
  ChatMessage,
  FunctionCall,
  FunctionCallOutput,
  isInstructions
} from "../llm/chat_context.js";
import {
  ToolError,
  isAgentHandoff,
  isFunctionTool,
  isToolError,
  sortedToolNames
} from "../llm/tool_context.js";
import { parseFunctionArguments } from "../llm/utils.js";
import { isZodSchema, parseZodSchema } from "../llm/zod-utils.js";
import { log } from "../log.js";
import { IdentityTransform } from "../stream/identity_transform.js";
import { traceTypes, tracer } from "../telemetry/index.js";
import { USERDATA_TIMED_TRANSCRIPT, isFlushSentinel } from "../types.js";
import {
  Future,
  IdleTimeoutError,
  Task,
  shortuuid,
  toError,
  waitForAbort,
  waitUntilAborted,
  waitUntilTimeout
} from "../utils.js";
import {
  _setActivityTaskInfo,
  functionCallStorage,
  isStopResponse
} from "./agent.js";
import {
  AudioOutput,
  isTimedString
} from "./io.js";
import { toSnakeCaseDeep } from "./report.js";
import { RunContext } from "./run_context.js";
import { getMockTool } from "./testing/run_result.js";
import { ToolExecutor, buildExecutorMap } from "./tool_executor.js";
import { applyTextTransforms } from "./transcription/text_transforms.js";
const DEFAULT_TTS_READ_IDLE_TIMEOUT_MS = 1e4;
const DEFAULT_FORWARD_AUDIO_IDLE_TIMEOUT_MS = 1e4;
class _LLMGenerationData {
  constructor(textStream, toolCallStream) {
    this.textStream = textStream;
    this.toolCallStream = toolCallStream;
    this.id = shortuuid("item_");
    this.generatedToolCalls = [];
  }
  generatedText = "";
  generatedToolCalls;
  generatedExtra = {};
  id;
  ttft;
}
class _ToolOutput {
  output;
  firstToolFut;
  constructor() {
    this.output = [];
    this.firstToolFut = new Future();
  }
}
class _SanitizedOutput {
  toolCall;
  toolCallOutput;
  replyRequired;
  agentTask;
  constructor(toolCall, toolCallOutput, replyRequired, agentTask) {
    this.toolCall = toolCall;
    this.toolCallOutput = toolCallOutput;
    this.replyRequired = replyRequired;
    this.agentTask = agentTask;
  }
  static create(params) {
    const { toolCall, toolCallOutput, replyRequired = true, agentTask } = params;
    return new _SanitizedOutput(toolCall, toolCallOutput, replyRequired, agentTask);
  }
}
function isValidToolOutput(toolOutput) {
  const validTypes = ["string", "number", "boolean"];
  if (validTypes.includes(typeof toolOutput)) {
    return true;
  }
  if (toolOutput === void 0 || toolOutput === null) {
    return true;
  }
  if (Array.isArray(toolOutput)) {
    return toolOutput.every(isValidToolOutput);
  }
  if (toolOutput instanceof Set) {
    return Array.from(toolOutput).every(isValidToolOutput);
  }
  if (toolOutput instanceof Map) {
    return Array.from(toolOutput.values()).every(isValidToolOutput);
  }
  if (toolOutput instanceof Object) {
    return Object.entries(toolOutput).every(
      ([key, value]) => validTypes.includes(typeof key) && isValidToolOutput(value)
    );
  }
  return false;
}
class ToolExecutionOutput {
  constructor(toolCall, toolCallOutput, agentTask, rawOutput, rawException, replyRequired) {
    this.toolCall = toolCall;
    this.toolCallOutput = toolCallOutput;
    this.agentTask = agentTask;
    this.rawOutput = rawOutput;
    this.rawException = rawException;
    this.replyRequired = replyRequired;
  }
  static create(params) {
    const {
      toolCall,
      toolCallOutput,
      agentTask,
      rawOutput,
      rawException,
      replyRequired = true
    } = params;
    return new ToolExecutionOutput(
      toolCall,
      toolCallOutput,
      agentTask,
      rawOutput,
      rawException,
      replyRequired
    );
  }
}
class _JsOutput {
  toolCall;
  output;
  exception;
  #logger = log();
  constructor(toolCall, output, exception) {
    this.toolCall = toolCall;
    this.output = output;
    this.exception = exception;
  }
  static create(params) {
    const { toolCall, output = void 0, exception = void 0 } = params;
    return new _JsOutput(toolCall, output, exception);
  }
  sanitize() {
    if (isToolError(this.exception)) {
      return _SanitizedOutput.create({
        toolCall: FunctionCall.create({ ...this.toolCall }),
        toolCallOutput: FunctionCallOutput.create({
          name: this.toolCall.name,
          callId: this.toolCall.callId,
          output: this.exception.message,
          isError: true
        })
      });
    }
    if (isStopResponse(this.exception)) {
      return _SanitizedOutput.create({
        toolCall: FunctionCall.create({ ...this.toolCall })
      });
    }
    if (this.exception !== void 0) {
      return _SanitizedOutput.create({
        toolCall: FunctionCall.create({ ...this.toolCall }),
        toolCallOutput: FunctionCallOutput.create({
          name: this.toolCall.name,
          callId: this.toolCall.callId,
          output: "An internal error occurred while executing the tool.",
          // Don't send the actual error message, as it may contain sensitive information
          isError: true
        })
      });
    }
    let agentTask = void 0;
    let toolOutput = this.output;
    if (isAgentHandoff(this.output)) {
      agentTask = this.output.agent;
      toolOutput = this.output.returns;
    }
    if (!isValidToolOutput(toolOutput)) {
      this.#logger.error(
        {
          callId: this.toolCall.callId,
          function: this.toolCall.name
        },
        `AI function ${this.toolCall.name} returned an invalid output`
      );
      return _SanitizedOutput.create({
        toolCall: FunctionCall.create({ ...this.toolCall }),
        toolCallOutput: void 0
      });
    }
    return _SanitizedOutput.create({
      toolCall: FunctionCall.create({ ...this.toolCall }),
      toolCallOutput: FunctionCallOutput.create({
        name: this.toolCall.name,
        callId: this.toolCall.callId,
        output: toolOutput !== void 0 ? JSON.stringify(toolOutput) : "",
        // take the string representation of the output
        isError: false
      }),
      replyRequired: toolOutput !== void 0,
      // require a reply if the tool returned an output
      agentTask
    });
  }
}
function createToolOutput(params) {
  const { toolCall, output, exception } = params;
  const logger = log();
  let finalOutput = output;
  let finalException = exception;
  if (output instanceof Error) {
    finalException = output;
    finalOutput = void 0;
  }
  if (isToolError(finalException)) {
    return ToolExecutionOutput.create({
      toolCall: FunctionCall.create({ ...toolCall }),
      toolCallOutput: FunctionCallOutput.create({
        name: toolCall.name,
        callId: toolCall.callId,
        output: finalException.message,
        isError: true
      }),
      rawOutput: finalOutput,
      rawException: finalException
    });
  }
  if (isStopResponse(finalException)) {
    return ToolExecutionOutput.create({
      toolCall: FunctionCall.create({ ...toolCall }),
      rawOutput: finalOutput,
      rawException: finalException
    });
  }
  if (finalException !== void 0) {
    return ToolExecutionOutput.create({
      toolCall: FunctionCall.create({ ...toolCall }),
      toolCallOutput: FunctionCallOutput.create({
        name: toolCall.name,
        callId: toolCall.callId,
        output: "An internal error occurred",
        // Don't send the actual error message, as it may contain sensitive information
        isError: true
      }),
      rawOutput: finalOutput,
      rawException: finalException
    });
  }
  let agentTask = void 0;
  let toolOutput = finalOutput;
  if (isAgentHandoff(finalOutput)) {
    agentTask = finalOutput.agent;
    toolOutput = finalOutput.returns;
  }
  if (!isValidToolOutput(toolOutput)) {
    logger.error(
      {
        callId: toolCall.callId,
        output: finalOutput
      },
      `AI function ${toolCall.name} returned an invalid output`
    );
    return ToolExecutionOutput.create({
      toolCall: FunctionCall.create({ ...toolCall }),
      rawOutput: finalOutput,
      rawException: finalException
    });
  }
  return ToolExecutionOutput.create({
    toolCall: FunctionCall.create({ ...toolCall }),
    toolCallOutput: FunctionCallOutput.create({
      name: toolCall.name,
      callId: toolCall.callId,
      output: toolOutput !== void 0 ? JSON.stringify(toolOutput) : "",
      // take the string representation of the output
      isError: false
    }),
    replyRequired: toolOutput !== void 0,
    // require a reply if the tool returned an output
    agentTask,
    rawOutput: finalOutput,
    rawException: finalException
  });
}
const INSTRUCTIONS_MESSAGE_ID = "lk.agent_task.instructions";
function updateInstructions(options) {
  const { chatCtx, instructions, addIfMissing } = options;
  const idx = chatCtx.indexById(INSTRUCTIONS_MESSAGE_ID);
  if (idx !== void 0) {
    if (chatCtx.items[idx].type === "message") {
      chatCtx.items[idx] = ChatMessage.create({
        id: INSTRUCTIONS_MESSAGE_ID,
        role: "system",
        content: [instructions],
        createdAt: chatCtx.items[idx].createdAt
      });
    } else {
      throw new Error('expected the instructions inside the chatCtx to be of type "message"');
    }
  } else if (addIfMissing) {
    chatCtx.items.unshift(
      ChatMessage.create({
        id: INSTRUCTIONS_MESSAGE_ID,
        role: "system",
        content: [instructions]
      })
    );
  }
}
function applyInstructionsModality(chatCtx, options) {
  const { modality } = options;
  const idx = chatCtx.indexById(INSTRUCTIONS_MESSAGE_ID);
  if (idx === void 0) return;
  const item = chatCtx.items[idx];
  if (item.type !== "message") return;
  const hasModalitySpecific = item.content.some((c) => isInstructions(c));
  if (!hasModalitySpecific) return;
  chatCtx.items[idx] = ChatMessage.create({
    id: item.id,
    role: item.role,
    content: item.content.map((c) => isInstructions(c) ? c.asModality(modality) : c),
    interrupted: item.interrupted,
    createdAt: item.createdAt,
    transcriptConfidence: item.transcriptConfidence,
    metrics: item.metrics,
    extra: item.extra
  });
}
function performLLMInference(node, chatCtx, toolCtx, modelSettings, controller, model, provider) {
  const logger = log();
  const textStream = new IdentityTransform();
  const toolCallStream = new IdentityTransform();
  const textWriter = textStream.writable.getWriter();
  const toolCallWriter = toolCallStream.writable.getWriter();
  const data = new _LLMGenerationData(textStream.readable, toolCallStream.readable);
  const _performLLMInferenceImpl = async (signal, span) => {
    span.setAttribute(
      traceTypes.ATTR_CHAT_CTX,
      // snake_case wire shape, matching Python's `chat_ctx.to_dict()` for this span attribute
      // (toJSON() emits camelCase). Defaults exclude image/audio/timestamps like the Python side.
      JSON.stringify(toSnakeCaseDeep(chatCtx.toJSON()))
    );
    span.setAttribute(traceTypes.ATTR_FUNCTION_TOOLS, JSON.stringify(sortedToolNames(toolCtx)));
    if (model) {
      span.setAttribute(traceTypes.ATTR_GEN_AI_REQUEST_MODEL, model);
    }
    if (provider) {
      span.setAttribute(traceTypes.ATTR_GEN_AI_PROVIDER_NAME, provider);
    }
    let llmStreamReader = null;
    let llmStream = null;
    const startTime = performance.now() / 1e3;
    let firstTokenReceived = false;
    try {
      llmStream = await node(chatCtx, toolCtx, modelSettings);
      if (llmStream === null) {
        await textWriter.close();
        return;
      }
      const abortPromise = waitForAbort(signal);
      llmStreamReader = llmStream.getReader();
      while (true) {
        if (signal.aborted) break;
        const result = await ThrowsPromise.race([llmStreamReader.read(), abortPromise]);
        if (result === void 0) break;
        const { done, value: chunk } = result;
        if (done) break;
        if (!firstTokenReceived) {
          firstTokenReceived = true;
          data.ttft = performance.now() / 1e3 - startTime;
        }
        if (isFlushSentinel(chunk)) {
          await textWriter.write(chunk);
        } else if (typeof chunk === "string") {
          data.generatedText += chunk;
          await textWriter.write(chunk);
        } else {
          if (chunk.delta === void 0) {
            continue;
          }
          if (chunk.delta.toolCalls) {
            for (const tool of chunk.delta.toolCalls) {
              if (tool.type !== "function_call") continue;
              const toolCall = FunctionCall.create({
                id: `${data.id}/fnc_${data.generatedToolCalls.length}`,
                callId: tool.callId,
                name: tool.name,
                args: tool.args,
                // Preserve thought signature for Gemini 3+ thinking mode
                thoughtSignature: tool.thoughtSignature,
                extra: tool.extra || {}
              });
              data.generatedToolCalls.push(toolCall);
              await toolCallWriter.write(toolCall);
            }
          }
          if (chunk.delta.extra) {
            Object.assign(data.generatedExtra, chunk.delta.extra);
          }
          if (chunk.delta.content) {
            data.generatedText += chunk.delta.content;
            await textWriter.write(chunk.delta.content);
          }
        }
      }
      span.setAttribute(traceTypes.ATTR_RESPONSE_TEXT, data.generatedText);
      if (data.ttft !== void 0) {
        span.setAttribute(traceTypes.ATTR_RESPONSE_TTFT, data.ttft);
      }
    } catch (error) {
      if (error instanceof DOMException && error.name === "AbortError") {
        return;
      }
      logger.error({ error }, "error in llm node");
      throw error;
    } finally {
      llmStreamReader == null ? void 0 : llmStreamReader.releaseLock();
      await (llmStream == null ? void 0 : llmStream.cancel());
      await textWriter.close();
      await toolCallWriter.close();
    }
  };
  const currentContext = otelContext.active();
  const inferenceTask = async (signal) => tracer.startActiveSpan(async (span) => _performLLMInferenceImpl(signal, span), {
    name: "llm_node",
    context: currentContext
  });
  return [
    Task.from((controller2) => inferenceTask(controller2.signal), controller, "performLLMInference"),
    data
  ];
}
function performTTSInference(node, text, modelSettings, controller, model, provider, readIdleTimeout = DEFAULT_TTS_READ_IDLE_TIMEOUT_MS, textTransforms) {
  const logger = log();
  const audioStream = new IdentityTransform();
  const outputWriter = audioStream.writable.getWriter();
  const audioOutputStream = audioStream.readable;
  const timedTextsFut = new Future();
  const timedTextsStream = new IdentityTransform();
  const timedTextsWriter = timedTextsStream.writable.getWriter();
  const textOnlyStream = new IdentityTransform();
  const textOnlyWriter = textOnlyStream.writable.getWriter();
  (async () => {
    const reader = text.getReader();
    try {
      while (true) {
        const { done, value } = await reader.read();
        if (done) {
          break;
        }
        const textValue = typeof value === "string" ? value : value.text;
        await textOnlyWriter.write(textValue);
      }
      await textOnlyWriter.close();
    } catch (e) {
      await textOnlyWriter.abort(e);
    } finally {
      reader.releaseLock();
    }
  })();
  let ttfb;
  const genData = {
    audioStream: audioOutputStream,
    timedTextsFut,
    ttfb: void 0
  };
  const _performTTSInferenceImpl = async (signal, span) => {
    if (model) {
      span.setAttribute(traceTypes.ATTR_GEN_AI_REQUEST_MODEL, model);
    }
    if (provider) {
      span.setAttribute(traceTypes.ATTR_GEN_AI_PROVIDER_NAME, provider);
    }
    let ttsStreamReader = null;
    let ttsStream = null;
    const startTime = performance.now() / 1e3;
    let firstByteReceived = false;
    try {
      const ttsInput = textTransforms ? applyTextTransforms(textOnlyStream.readable, textTransforms) : textOnlyStream.readable;
      ttsStream = await node(ttsInput, modelSettings);
      if (ttsStream === null) {
        timedTextsFut.resolve(null);
        await outputWriter.close();
        await timedTextsWriter.close();
        return;
      }
      if (!timedTextsFut.done) {
        timedTextsFut.resolve(timedTextsStream.readable);
      }
      ttsStreamReader = ttsStream.getReader();
      while (true) {
        if (signal.aborted) {
          break;
        }
        const { done, value: frame } = await waitUntilTimeout(
          ttsStreamReader.read(),
          readIdleTimeout
        );
        if (done) {
          break;
        }
        if (!firstByteReceived) {
          firstByteReceived = true;
          ttfb = performance.now() / 1e3 - startTime;
          genData.ttfb = ttfb;
          span.setAttribute(traceTypes.ATTR_RESPONSE_TTFB, ttfb);
        }
        await outputWriter.write(frame);
        const timedTranscripts = frame.userdata[USERDATA_TIMED_TRANSCRIPT];
        if (timedTranscripts && timedTranscripts.length > 0) {
          for (const timedText of timedTranscripts) {
            await timedTextsWriter.write(timedText);
          }
        }
      }
    } catch (error) {
      if (error instanceof IdleTimeoutError) {
        logger.warn("TTS stream stalled after producing audio, forcing close");
      } else if (error instanceof DOMException && error.name === "AbortError") {
        return;
      } else {
        throw error;
      }
    } finally {
      if (!timedTextsFut.done) {
        timedTextsFut.resolve(null);
      }
      ttsStreamReader == null ? void 0 : ttsStreamReader.releaseLock();
      await (ttsStream == null ? void 0 : ttsStream.cancel());
      await outputWriter.close();
      await timedTextsWriter.close();
    }
  };
  const currentContext = otelContext.active();
  const inferenceTask = async (signal) => tracer.startActiveSpan(async (span) => _performTTSInferenceImpl(signal, span), {
    name: "tts_node",
    context: currentContext
  });
  return [
    Task.from((controller2) => inferenceTask(controller2.signal), controller, "performTTSInference"),
    genData
  ];
}
async function forwardText(source, out, signal, textOutput) {
  const reader = source.getReader();
  try {
    while (true) {
      if (signal.aborted) {
        break;
      }
      const { done, value: delta } = await reader.read();
      if (done) break;
      const deltaIsTimedString = isTimedString(delta);
      const textDelta = deltaIsTimedString ? delta.text : delta;
      out.text += textDelta;
      if (textOutput !== null) {
        await textOutput.captureText(delta);
      }
      if (!out.firstTextFut.done) {
        out.firstTextFut.resolve();
      }
    }
  } finally {
    if (textOutput !== null) {
      textOutput.flush();
    }
    reader == null ? void 0 : reader.releaseLock();
  }
}
function performTextForwarding(source, controller, textOutput) {
  const out = {
    text: "",
    firstTextFut: new Future()
  };
  return [
    Task.from(
      (controller2) => forwardText(source, out, controller2.signal, textOutput),
      controller,
      "performTextForwarding"
    ),
    out
  ];
}
function forwardedTextFor(output) {
  var _a;
  if (output.played === "skipped") return "";
  if (output.played === "partial" && output.synchronizedTranscript) {
    return output.synchronizedTranscript;
  }
  return ((_a = output.textOut) == null ? void 0 : _a.text) ?? "";
}
async function forwardAudio(ttsStream, audioOutput, out, idleTimeout, signal) {
  const logger = log();
  const reader = ttsStream.getReader();
  let resampler = null;
  let hasCapturedOwnFrame = false;
  const onPlaybackStarted = (ev) => {
    if (hasCapturedOwnFrame && !out.firstFrameFut.done) {
      out.firstFrameFut.resolve(ev.createdAt);
    }
  };
  try {
    audioOutput.on(AudioOutput.EVENT_PLAYBACK_STARTED, onPlaybackStarted);
    audioOutput.resume();
    while (true) {
      if (signal == null ? void 0 : signal.aborted) {
        break;
      }
      const { done, value: frame } = await waitUntilTimeout(reader.read(), idleTimeout);
      if (done) break;
      out.audio.push(frame);
      if (out.startedForwardingAt === void 0) {
        out.startedForwardingAt = Date.now();
      }
      if (audioOutput.sampleRate && audioOutput.sampleRate !== frame.sampleRate && !resampler) {
        resampler = new AudioResampler(frame.sampleRate, audioOutput.sampleRate, frame.channels);
      }
      hasCapturedOwnFrame = true;
      if (resampler) {
        for (const f of resampler.push(frame)) {
          await audioOutput.captureFrame(f);
        }
      } else {
        await audioOutput.captureFrame(frame);
      }
    }
    if (resampler) {
      for (const f of resampler.flush()) {
        await audioOutput.captureFrame(f);
      }
    }
  } catch (e) {
    if (e instanceof IdleTimeoutError) {
      logger.warn("audio forwarding stalled waiting for TTS frames, forcing close");
    } else {
      throw e;
    }
  } finally {
    audioOutput.off(AudioOutput.EVENT_PLAYBACK_STARTED, onPlaybackStarted);
    if (!out.firstFrameFut.done) {
      out.firstFrameFut.reject(new Error("audio forwarding cancelled before playback started"));
    }
    reader == null ? void 0 : reader.releaseLock();
    audioOutput.flush();
    if (signal == null ? void 0 : signal.aborted) {
      audioOutput.clearBuffer();
    }
    resampler == null ? void 0 : resampler.close();
  }
}
function performAudioForwarding(ttsStream, audioOutput, controller, idleTimeout = DEFAULT_FORWARD_AUDIO_IDLE_TIMEOUT_MS) {
  const out = {
    audio: [],
    firstFrameFut: new Future()
  };
  return [
    Task.from(
      (controller2) => forwardAudio(ttsStream, audioOutput, out, idleTimeout, controller2.signal),
      controller,
      "performAudioForwarding"
    ),
    out
  ];
}
function performToolExecutions({
  session,
  speechHandle,
  toolCtx,
  toolChoice,
  toolCallStream,
  onToolExecutionStarted = () => {
  },
  onToolExecutionCompleted = () => {
  },
  controller
}) {
  const logger = log();
  const toolOutput = {
    output: [],
    firstToolStartedFuture: new Future()
  };
  const toolCompleted = (out) => {
    onToolExecutionCompleted(out);
    toolOutput.output.push(out);
  };
  const executeToolsTask = async (controller2) => {
    const signal = controller2.signal;
    const reader = toolCallStream.getReader();
    const activity = session._activity;
    const defaultExecutor = (activity == null ? void 0 : activity._toolExecutor) ?? new ToolExecutor({ owningActivity: null });
    const executorByName = buildExecutorMap({
      toolsets: toolCtx.toolsets,
      defaultExecutor
    });
    const tasks = [];
    while (!signal.aborted) {
      const { done, value: toolCall } = await reader.read();
      if (signal.aborted) break;
      if (done) break;
      if (toolChoice === "none") {
        logger.error(
          {
            function: toolCall.name,
            speech_id: speechHandle.id
          },
          "received a tool call with toolChoice set to 'none', ignoring"
        );
        continue;
      }
      const tool = toolCtx.getFunctionTool(toolCall.name);
      if (!tool) {
        const availableTools = sortedToolNames(toolCtx).join(", ");
        const message = `Unknown function: ${toolCall.name} - available tools: ${availableTools}`;
        logger.warn(
          {
            function: toolCall.name,
            speech_id: speechHandle.id
          },
          `unknown AI function ${toolCall.name}`
        );
        toolCompleted(
          createToolOutput({
            toolCall,
            exception: new ToolError(message)
          })
        );
        continue;
      }
      if (!isFunctionTool(tool)) {
        logger.error(
          {
            function: toolCall.name,
            speech_id: speechHandle.id
          },
          `unknown tool type: ${typeof tool}`
        );
        continue;
      }
      let parsedArgs;
      try {
        const rawArgs = toolCall.args || "{}";
        const jsonArgs = parseFunctionArguments(rawArgs);
        const canonicalArgs = JSON.stringify(jsonArgs);
        if (canonicalArgs !== rawArgs) {
          toolCall.args = canonicalArgs;
        }
        if (isZodSchema(tool.parameters)) {
          const result = await parseZodSchema(tool.parameters, jsonArgs);
          if (result.success) {
            parsedArgs = result.data;
          } else {
            throw result.error;
          }
        } else {
          parsedArgs = jsonArgs;
        }
      } catch (rawError) {
        const error = toError(rawError);
        logger.error(
          {
            function: toolCall.name,
            arguments: toolCall.args,
            speech_id: speechHandle.id,
            error: error.message
          },
          `tried to call AI function ${toolCall.name} with invalid arguments`
        );
        toolCompleted(
          createToolOutput({
            toolCall,
            exception: new ToolError(`Invalid arguments for ${toolCall.name}: ${error.message}`)
          })
        );
        continue;
      }
      if (!toolOutput.firstToolStartedFuture.done) {
        toolOutput.firstToolStartedFuture.resolve();
      }
      onToolExecutionStarted(toolCall);
      logger.info(
        {
          function: toolCall.name,
          arguments: parsedArgs,
          speech_id: speechHandle.id
        },
        "Executing LLM tool call"
      );
      const _tracableToolExecutionImpl = async (toolExecTask, span) => {
        span.setAttribute(traceTypes.ATTR_FUNCTION_TOOL_NAME, toolCall.name);
        span.setAttribute(traceTypes.ATTR_FUNCTION_TOOL_ARGS, toolCall.args);
        let toolOutput2;
        try {
          const { result, isAborted } = await waitUntilAborted(toolExecTask, signal);
          toolOutput2 = createToolOutput({
            toolCall,
            exception: isAborted ? new Error("tool call was aborted") : void 0,
            output: isAborted ? void 0 : result
          });
          if (toolOutput2.toolCallOutput) {
            span.setAttribute(
              traceTypes.ATTR_FUNCTION_TOOL_OUTPUT,
              toolOutput2.toolCallOutput.output
            );
            span.setAttribute(
              traceTypes.ATTR_FUNCTION_TOOL_IS_ERROR,
              toolOutput2.toolCallOutput.isError
            );
          }
        } catch (rawError) {
          logger.error(
            {
              function: toolCall.name,
              speech_id: speechHandle.id,
              error: toError(rawError).message
            },
            "exception occurred while executing tool"
          );
          toolOutput2 = createToolOutput({
            toolCall,
            exception: toError(rawError)
          });
          if (toolOutput2.toolCallOutput) {
            span.setAttribute(
              traceTypes.ATTR_FUNCTION_TOOL_OUTPUT,
              toolOutput2.toolCallOutput.output
            );
            span.setAttribute(traceTypes.ATTR_FUNCTION_TOOL_IS_ERROR, true);
          }
        } finally {
          if (!toolOutput2) throw new Error("toolOutput is undefined");
          toolCompleted(toolOutput2);
        }
      };
      const tracableToolExecution = (toolExecTask) => tracer.startActiveSpan(async (span) => _tracableToolExecutionImpl(toolExecTask, span), {
        name: "function_tool"
      });
      const toolTask = Task.from(
        async () => {
          const currentTask = Task.current();
          if (currentTask) {
            _setActivityTaskInfo(currentTask, {
              speechHandle,
              functionCall: toolCall,
              inlineTask: true
            });
          }
          const toolExecution = functionCallStorage.run(
            { functionCall: toolCall, speechHandle },
            async () => {
              const runCtx = new RunContext(session, speechHandle, toolCall);
              const mock = getMockTool(session.currentAgent, toolCall.name);
              const toolToExecute = mock ? {
                ...tool,
                execute: mock
              } : tool;
              if (mock) {
                logger.debug(
                  {
                    function: toolCall.name,
                    arguments: parsedArgs,
                    speech_id: speechHandle.id
                  },
                  "executing mock tool"
                );
              }
              const executor = executorByName.get(toolCall.name) ?? defaultExecutor;
              return await executor.execute({
                tool: toolToExecute,
                runCtx,
                rawArguments: parsedArgs,
                abortSignal: signal
              });
            }
          );
          await tracableToolExecution(toolExecution);
        },
        controller2,
        `performToolExecution:${toolCall.name}`
      );
      _setActivityTaskInfo(toolTask, {
        speechHandle,
        functionCall: toolCall,
        inlineTask: true
      });
      tasks.push(toolTask);
    }
    await ThrowsPromise.allSettled(tasks.map((task) => task.result));
    if (toolOutput.output.length > 0) {
      logger.debug(
        {
          speech_id: speechHandle.id
        },
        "tools execution completed"
      );
    }
  };
  return [Task.from(executeToolsTask, controller, "performToolExecutions"), toolOutput];
}
function removeInstructions(chatCtx) {
  while (true) {
    const idx = chatCtx.indexById(INSTRUCTIONS_MESSAGE_ID);
    if (idx !== void 0) {
      chatCtx.items.splice(idx, 1);
    } else {
      break;
    }
  }
}
export {
  DEFAULT_FORWARD_AUDIO_IDLE_TIMEOUT_MS,
  DEFAULT_TTS_READ_IDLE_TIMEOUT_MS,
  INSTRUCTIONS_MESSAGE_ID,
  ToolExecutionOutput,
  _JsOutput,
  _LLMGenerationData,
  _SanitizedOutput,
  _ToolOutput,
  applyInstructionsModality,
  createToolOutput,
  forwardedTextFor,
  performAudioForwarding,
  performLLMInference,
  performTTSInference,
  performTextForwarding,
  performToolExecutions,
  removeInstructions,
  updateInstructions
};
//# sourceMappingURL=generation.js.map