import { randomUUID } from "node:crypto";
import { createContainer } from "awilix";
import {
  convertToModelMessages,
  generateObject,
  hasToolCall,
  stepCountIs,
  streamObject,
  streamText,
  Experimental_Agent as ToolLoopAgent
} from "ai";
import { createModelFactory, resolveAllowRuntimeOverride } from "./model-factory.js";
import {
  resolveAiAgentTools,
  AgentPolicyError,
  desanitizeToolNameForDisplay,
  sanitizeToolNameForModel
} from "./agent-tools.js";
import { resolveEffectiveMutationPolicy } from "./agent-policy.js";
import { toolRegistry } from "./tool-registry.js";
import {
  attachmentPartsToUiFileParts,
  resolveAttachmentPartsForAgent,
  summarizeAttachmentPartsForPrompt
} from "./attachment-parts.js";
import { AiAgentPromptOverrideRepository } from "../data/repositories/AiAgentPromptOverrideRepository.js";
import { AiAgentMutationPolicyOverrideRepository } from "../data/repositories/AiAgentMutationPolicyOverrideRepository.js";
import { AiAgentRuntimeOverrideRepository } from "../data/repositories/AiAgentRuntimeOverrideRepository.js";
import { AiTenantModelAllowlistRepository } from "../data/repositories/AiTenantModelAllowlistRepository.js";
import { composeSystemPromptWithOverride } from "./prompt-override-merge.js";
import { isAgentTaskPlanEnabled } from "./agent-registry.js";
import { isKnownMutationPolicy } from "./agent-policy.js";
import { recordTokenUsage } from "./token-usage-recorder.js";
import { injectTaskPlanIntoStream } from "./task-plan-stream.js";
import { TASK_PLAN_RUNTIME_PROMPT_SECTION } from "./task-plan-labels.js";
import "./llm-bootstrap.js";
const WRAPPER_DEFAULT_LOOP_CHAT = { maxSteps: 10 };
const WRAPPER_DEFAULT_LOOP_OBJECT = {};
function resolveLoopBudgetPreset(preset) {
  switch (preset) {
    case "tight":
      return { maxSteps: 3, budget: { maxToolCalls: 3, maxWallClockMs: 1e4, maxTokens: 5e4 } };
    case "loose":
      return { maxSteps: 20, budget: { maxToolCalls: 20, maxWallClockMs: 12e4, maxTokens: 5e5 } };
    case "default":
      return void 0;
  }
}
const SSE_ENCODER = new TextEncoder();
function appendLoopFinishToStream(baseResponse, finalizeLoopTrace) {
  const { readable, writable } = new TransformStream();
  const writer = writable.getWriter();
  async function pump() {
    if (!baseResponse.body) {
      await writer.close();
      return;
    }
    const reader = baseResponse.body.getReader();
    try {
      for (; ; ) {
        const { value, done } = await reader.read();
        if (done) break;
        await writer.write(value);
      }
      const trace = finalizeLoopTrace();
      const eventLine = `data: ${JSON.stringify({ type: "loop-finish", trace })}

`;
      await writer.write(SSE_ENCODER.encode(eventLine));
    } catch {
    } finally {
      reader.releaseLock();
      await writer.close().catch(() => void 0);
    }
  }
  void pump();
  return new Response(readable, {
    status: baseResponse.status,
    headers: baseResponse.headers
  });
}
function assertLoopRuntimeOverrideAllowed(agent, callerLoop) {
  if (!callerLoop) return;
  const allowed = agent.loop?.allowRuntimeOverride ?? true;
  if (!allowed) {
    throw new AgentPolicyError(
      "loop_runtime_override_disabled",
      `Agent "${agent.id}" has disabled per-call loop overrides (loop.allowRuntimeOverride: false). Remove the loop override to proceed.`
    );
  }
}
function readModuleLoopEnv(moduleId) {
  const prefix = moduleId.toUpperCase();
  const partial = {};
  const maxStepsRaw = process.env[`${prefix}_AI_LOOP_MAX_STEPS`];
  if (maxStepsRaw) {
    const parsed = parseInt(maxStepsRaw.trim(), 10);
    if (!isNaN(parsed) && parsed > 0) partial.maxSteps = parsed;
  }
  const maxWallClockRaw = process.env[`${prefix}_AI_LOOP_MAX_WALL_CLOCK_MS`];
  const maxTokensRaw = process.env[`${prefix}_AI_LOOP_MAX_TOKENS`];
  if (maxWallClockRaw || maxTokensRaw) {
    const budgetPartial = {};
    if (maxWallClockRaw) {
      const parsed = parseInt(maxWallClockRaw.trim(), 10);
      if (!isNaN(parsed) && parsed > 0) budgetPartial.maxWallClockMs = parsed;
    }
    if (maxTokensRaw) {
      const parsed = parseInt(maxTokensRaw.trim(), 10);
      if (!isNaN(parsed) && parsed > 0) budgetPartial.maxTokens = parsed;
    }
    if (Object.keys(budgetPartial).length > 0) partial.budget = budgetPartial;
  }
  return partial;
}
function resolveEffectiveLoopConfig(agent, callerLoop, wrapperDefault) {
  assertLoopRuntimeOverrideAllowed(agent, callerLoop);
  const effectiveDefault = wrapperDefault ?? WRAPPER_DEFAULT_LOOP_CHAT;
  const legacyMaxSteps = typeof agent.maxSteps === "number" && agent.maxSteps > 0 && !agent.loop ? { maxSteps: agent.maxSteps } : void 0;
  const base = {
    ...effectiveDefault,
    ...legacyMaxSteps ?? {},
    ...agent.loop ?? {}
  };
  const envOverride = readModuleLoopEnv(agent.moduleId);
  const withEnv = {
    ...base,
    ...envOverride,
    ...envOverride.budget != null ? { budget: { ...base.budget ?? {}, ...envOverride.budget } } : {}
  };
  const withCaller = callerLoop ? { ...withEnv, ...callerLoop } : withEnv;
  if (withCaller.disabled === true) {
    return { ...withCaller, maxSteps: 1 };
  }
  return withCaller;
}
class BudgetEnforcer {
  constructor(budget, abortController) {
    this.budget = budget;
    this.abortController = abortController;
    this.toolCallsUsed = 0;
    this.tokensUsed = 0;
    this.abortReason = null;
    this.turnStartMs = Date.now();
  }
  get hasActiveBudget() {
    const b = this.budget;
    return b !== void 0 && (b.maxToolCalls !== void 0 || b.maxWallClockMs !== void 0 || b.maxTokens !== void 0);
  }
  recordStep(usage) {
    if (!this.budget) return;
    this.toolCallsUsed += usage.toolCalls ?? 0;
    this.tokensUsed += (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
    this.checkLimits();
  }
  checkLimits() {
    const b = this.budget;
    if (!b) return;
    if (b.maxToolCalls !== void 0 && this.toolCallsUsed >= b.maxToolCalls) {
      this.abort("budget-tool-calls");
      return;
    }
    const elapsedMs = Date.now() - this.turnStartMs;
    if (b.maxWallClockMs !== void 0 && elapsedMs >= b.maxWallClockMs) {
      this.abort("budget-wall-clock");
      return;
    }
    if (b.maxTokens !== void 0 && this.tokensUsed >= b.maxTokens) {
      this.abort("budget-tokens");
    }
  }
  abort(reason) {
    if (this.abortReason !== null) return;
    this.abortReason = reason;
    console.info(
      `[AI Agents] Budget exceeded \u2014 aborting turn. Reason: ${reason}. toolCalls=${this.toolCallsUsed}, tokens=${this.tokensUsed}, elapsedMs=${Date.now() - this.turnStartMs}.`
    );
    this.abortController.abort(reason);
  }
  wire(userOnStepFinish) {
    if (!this.hasActiveBudget) return userOnStepFinish;
    return async (event) => {
      this.recordStep({
        inputTokens: event.usage?.inputTokens,
        outputTokens: event.usage?.outputTokens,
        toolCalls: event.toolCalls?.length
      });
      if (userOnStepFinish) {
        try {
          await userOnStepFinish(event);
        } catch (err) {
          console.error("[AI Agents] User onStepFinish threw; ignoring:", err);
        }
      }
    };
  }
}
function buildLoopTraceCollector(agentId, sessionId, turnId, userOnStepFinish) {
  const turnStartMs = Date.now();
  const steps = [];
  const onStepFinish = async (event) => {
    const stepIndex = steps.length;
    const toolCalls = (event.toolCalls ?? []).map((tc) => {
      const raw = tc;
      return {
        toolName: raw.toolName ? desanitizeToolNameForDisplay(raw.toolName) : "unknown",
        args: raw.args ?? {},
        result: raw.result,
        error: raw.experimental_toToolResultError ? {
          code: String(raw.experimental_toToolResultError?.code ?? "unknown"),
          message: String(raw.experimental_toToolResultError?.message ?? "")
        } : void 0,
        repairAttempted: raw.repairAttempted === true,
        durationMs: typeof raw.startTime === "number" && typeof raw.endTime === "number" ? raw.endTime - raw.startTime : 0
      };
    });
    const textDelta = event.text ?? "";
    const finishReason = event.finishReason ?? "stop";
    const modelId = event.response?.modelId ?? "unknown";
    steps.push({
      stepIndex,
      modelId,
      toolCalls,
      textDelta,
      usage: {
        inputTokens: event.usage?.inputTokens ?? 0,
        outputTokens: event.usage?.outputTokens ?? 0
      },
      finishReason
    });
    if (userOnStepFinish) {
      try {
        await userOnStepFinish(event);
      } catch (err) {
        console.error("[AI Agents] User onStepFinish in LoopTrace collector threw; ignoring:", err);
      }
    }
  };
  function finalize(abortReason) {
    const totalDurationMs = Date.now() - turnStartMs;
    const totalUsage = steps.reduce(
      (acc, step) => ({
        inputTokens: acc.inputTokens + step.usage.inputTokens,
        outputTokens: acc.outputTokens + step.usage.outputTokens
      }),
      { inputTokens: 0, outputTokens: 0 }
    );
    let stopReason = "finish-reason";
    if (abortReason === "budget-tool-calls") stopReason = "budget-tool-calls";
    else if (abortReason === "budget-wall-clock") stopReason = "budget-wall-clock";
    else if (abortReason === "budget-tokens") stopReason = "budget-tokens";
    return {
      agentId,
      sessionId,
      turnId,
      steps,
      stopReason,
      totalDurationMs,
      totalUsage
    };
  }
  return { onStepFinish, finalize };
}
function translateStopConditions(loopConfig, mapToolName = (toolName) => toolName) {
  const effectiveMaxSteps = loopConfig.maxSteps ?? 10;
  const userConditions = [];
  const rawStopWhen = loopConfig.stopWhen;
  if (rawStopWhen) {
    const items = Array.isArray(rawStopWhen) ? rawStopWhen : [rawStopWhen];
    for (const item of items) {
      if (item.kind === "stepCount") {
        userConditions.push(stepCountIs(item.count));
      } else if (item.kind === "hasToolCall") {
        userConditions.push(hasToolCall(mapToolName(item.toolName)));
      } else if (item.kind === "custom") {
        userConditions.push(item.stop);
      }
    }
  }
  return [...userConditions, stepCountIs(effectiveMaxSteps)];
}
function normalizeAllowedToolNameForAgent(toolName, agent) {
  if (agent.allowedTools.includes(toolName)) return toolName;
  const dottedName = desanitizeToolNameForDisplay(toolName);
  return agent.allowedTools.includes(dottedName) ? dottedName : null;
}
function mergeStepOverrides(wrapperOverride, userOverride, agent, wrappedToolRegistry) {
  if (!userOverride) return wrapperOverride;
  const merged = { ...wrapperOverride };
  const userWithTools = userOverride;
  if (userOverride.model !== void 0) {
    merged.model = userOverride.model;
  }
  if (userOverride.toolChoice !== void 0) {
    merged.toolChoice = userOverride.toolChoice;
  }
  if (userOverride.activeTools !== void 0) {
    const filtered = userOverride.activeTools.flatMap((name) => {
      const normalized = normalizeAllowedToolNameForAgent(name, agent);
      const allowed = normalized !== null;
      if (!allowed) {
        console.warn(
          `[AI Agents] loop:active_tools_filtered \u2014 tool "${name}" is not in agent "${agent.id}" allowedTools; dropping from activeTools.`
        );
      }
      return normalized ? [normalized] : [];
    });
    merged.activeTools = filtered;
  }
  if (userWithTools.tools !== void 0) {
    const userTools = userWithTools.tools;
    const mergedTools = {};
    for (const [toolKey, userHandler] of Object.entries(userTools)) {
      const wrappedHandler = wrappedToolRegistry[toolKey];
      if (!wrappedHandler) {
        console.warn(
          `[AI Agents] mergeStepOverrides \u2014 tool "${toolKey}" from user prepareStep is not in the wrapper tool registry; dropping.`
        );
        continue;
      }
      if (userHandler !== wrappedHandler) {
        const toolDef = toolRegistry.getTool(
          toolKey.replace(/__/g, ".")
        );
        if (toolDef?.isMutation === true) {
          throw new AgentPolicyError(
            "loop_violates_mutation_policy",
            `User prepareStep returned a tools map with raw (unwrapped) mutation handler for "${toolKey}". This bypasses the mutation-approval gate and is rejected.`
          );
        }
      }
      mergedTools[toolKey] = wrappedHandler;
    }
    merged.tools = mergedTools;
  }
  return merged;
}
function mapPrepareStepResultForModel(result, wrappedTools) {
  if (!result?.activeTools) return result;
  const activeTools = result.activeTools.map((toolName) => sanitizeToolNameForModel(toolName)).filter((toolName) => wrappedTools[toolName] !== void 0);
  return { ...result, activeTools };
}
function mapToolChoiceForModel(toolChoice) {
  if (!toolChoice || typeof toolChoice !== "object" || toolChoice.type !== "tool") {
    return toolChoice;
  }
  return {
    ...toolChoice,
    toolName: sanitizeToolNameForModel(toolChoice.toolName)
  };
}
function mapActiveToolsForModel(activeTools, wrappedTools) {
  if (!activeTools) return void 0;
  return activeTools.map((toolName) => sanitizeToolNameForModel(toolName)).filter((toolName) => wrappedTools[toolName] !== void 0);
}
function buildWrapperPrepareStep(agent, effectiveLoop, wrappedTools) {
  return async (state) => {
    const wrapperOverride = {};
    if (effectiveLoop.activeTools && effectiveLoop.activeTools.length > 0) {
      wrapperOverride.activeTools = effectiveLoop.activeTools.flatMap((name) => {
        const normalized = normalizeAllowedToolNameForAgent(name, agent);
        const allowed = normalized !== null;
        if (!allowed) {
          console.warn(
            `[AI Agents] loop:active_tools_filtered \u2014 tool "${name}" is not in agent "${agent.id}" allowedTools; dropping from activeTools.`
          );
        }
        return normalized ? [normalized] : [];
      });
    }
    if (effectiveLoop.prepareStep) {
      let userOverride;
      try {
        userOverride = await effectiveLoop.prepareStep(state);
      } catch (error) {
        console.error(
          `[AI Agents] User prepareStep threw for agent "${agent.id}"; ignoring user override:`,
          error
        );
        return mapPrepareStepResultForModel(wrapperOverride, wrappedTools);
      }
      return mapPrepareStepResultForModel(
        mergeStepOverrides(wrapperOverride, userOverride, agent, wrappedTools),
        wrappedTools
      );
    }
    return mapPrepareStepResultForModel(wrapperOverride, wrappedTools);
  };
}
function assertLoopObjectModeCompatible(loopConfig) {
  const unsupportedFields = [];
  if (loopConfig.prepareStep !== void 0) unsupportedFields.push("prepareStep");
  if (loopConfig.repairToolCall !== void 0) unsupportedFields.push("repairToolCall");
  if (loopConfig.stopWhen !== void 0) unsupportedFields.push("stopWhen");
  if (loopConfig.activeTools !== void 0) unsupportedFields.push("activeTools");
  if (loopConfig.toolChoice !== void 0) unsupportedFields.push("toolChoice");
  if (unsupportedFields.length > 0) {
    throw new AgentPolicyError(
      "loop_unsupported_in_object_mode",
      `Object-mode agents do not support these loop primitives: ${unsupportedFields.join(", ")}. Use runAiAgentText for agents that require these loop controls.`
    );
  }
}
function resolveAgentModel(agent, modelOverride, providerOverride, container, baseUrlOverride, tenantOverride, requestOverride, tenantAllowlist) {
  const effectiveContainer = container ?? createContainer();
  const resolution = createModelFactory(effectiveContainer).resolveModel({
    moduleId: agent.moduleId,
    agentDefaultModel: agent.defaultModel,
    agentDefaultProvider: agent.defaultProvider,
    agentDefaultBaseUrl: agent.defaultBaseUrl,
    callerOverride: modelOverride,
    providerOverride,
    baseUrlOverride,
    allowRuntimeOverride: resolveAllowRuntimeOverride(agent),
    tenantOverride: tenantOverride ?? void 0,
    requestOverride: requestOverride ?? void 0,
    tenantAllowlist: tenantAllowlist ?? null
  });
  return {
    model: resolution.model,
    modelId: resolution.modelId,
    providerId: resolution.providerId
  };
}
async function resolveTenantAllowlistSnapshot(container, tenantId, organizationId) {
  if (!tenantId || !container) return null;
  let em = null;
  try {
    em = container.resolve("em");
  } catch {
    em = null;
  }
  if (!em) return null;
  try {
    const repo = new AiTenantModelAllowlistRepository(em);
    return await repo.getSnapshot({
      tenantId,
      organizationId: organizationId ?? null
    });
  } catch (error) {
    console.warn(
      "[AI Agents] Tenant allowlist lookup failed; falling back to env-only enforcement.",
      error
    );
    return null;
  }
}
async function composeSystemPrompt(agent, pageContext, container, tenantId, organizationId) {
  const baseFromOverride = await resolveBaseSystemPromptWithOverride(
    agent,
    container,
    tenantId,
    organizationId
  );
  const resolve = agent.resolvePageContext;
  if (!resolve) return baseFromOverride;
  const entityType = pageContext?.entityType;
  const recordId = pageContext?.recordId;
  if (typeof entityType !== "string" || entityType.length === 0) return baseFromOverride;
  if (typeof recordId !== "string" || recordId.length === 0) return baseFromOverride;
  if (!container) {
    console.warn(
      `[AI Agents] Agent "${agent.id}" declares resolvePageContext but no container was passed to runAiAgentText; skipping hydration.`
    );
    return baseFromOverride;
  }
  const hydrationInput = {
    entityType,
    recordId,
    container,
    tenantId,
    organizationId
  };
  try {
    const hydrated = await resolve(hydrationInput);
    if (typeof hydrated === "string" && hydrated.trim().length > 0) {
      return `${baseFromOverride}

${hydrated}`;
    }
  } catch (error) {
    console.error(
      `[AI Agents] resolvePageContext for agent "${agent.id}" failed; continuing without hydration:`,
      error
    );
  }
  return baseFromOverride;
}
async function resolveBaseSystemPromptWithOverride(agent, container, tenantId, organizationId) {
  const base = agent.systemPrompt;
  if (!tenantId || !container) return base;
  let em = null;
  try {
    em = container.resolve("em");
  } catch {
    em = null;
  }
  if (!em) return base;
  try {
    const repo = new AiAgentPromptOverrideRepository(em);
    const latest = await repo.getLatest(agent.id, {
      tenantId,
      organizationId: organizationId ?? null
    });
    if (!latest || !latest.sections || Object.keys(latest.sections).length === 0) {
      return base;
    }
    return composeSystemPromptWithOverride(base, { sections: latest.sections });
  } catch (error) {
    console.warn(
      `[AI Agents] Prompt-override lookup failed for agent "${agent.id}"; falling back to built-in prompt.`,
      error
    );
    return base;
  }
}
async function resolveMutationPolicyOverride(agentId, container, tenantId, organizationId) {
  if (!tenantId || !container) return null;
  let em = null;
  try {
    em = container.resolve("em");
  } catch {
    em = null;
  }
  if (!em) return null;
  try {
    const repo = new AiAgentMutationPolicyOverrideRepository(em);
    const row = await repo.get(agentId, { tenantId, organizationId: organizationId ?? null });
    if (!row) return null;
    const raw = row.mutationPolicy;
    if (!isKnownMutationPolicy(raw)) {
      console.warn(
        `[AI Agents] Ignoring corrupt mutationPolicy override row for agent "${agentId}": "${raw}". Falling back to code-declared policy.`
      );
      return null;
    }
    return raw;
  } catch (error) {
    console.warn(
      `[AI Agents] mutationPolicy override lookup failed for agent "${agentId}"; falling back to code-declared policy.`,
      error
    );
    return null;
  }
}
async function resolveRuntimeModelOverride(agentId, container, tenantId, organizationId) {
  if (!tenantId || !container) return null;
  let em = null;
  try {
    em = container.resolve("em");
  } catch {
    em = null;
  }
  if (!em) return null;
  try {
    const repo = new AiAgentRuntimeOverrideRepository(em);
    const row = await repo.getDefault({
      tenantId,
      organizationId: organizationId ?? null,
      agentId
    });
    if (!row) return null;
    return {
      providerId: row.providerId ?? null,
      modelId: row.modelId ?? null,
      baseURL: row.baseUrl ?? null
    };
  } catch (error) {
    console.warn(
      `[AI Agents] Runtime model override lookup failed for agent "${agentId}"; falling back to lower-priority sources.`,
      error
    );
    return null;
  }
}
function ensureUiMessageShape(messages) {
  return messages.map((message, index) => {
    const raw = message;
    if (Array.isArray(raw.parts) && raw.parts.length > 0) {
      return { ...message, id: raw.id ?? `msg-${index}` };
    }
    const textContent = typeof raw.content === "string" ? raw.content : "";
    return {
      id: raw.id ?? `msg-${index}`,
      role: raw.role ?? "user",
      parts: [{ type: "text", text: textContent }]
    };
  });
}
function attachAttachmentsToMessages(messages, parts) {
  if (parts.length === 0) return messages;
  const fileParts = attachmentPartsToUiFileParts(parts);
  if (fileParts.length === 0) return messages;
  const next = messages.slice();
  let lastUserIndex = -1;
  for (let index = next.length - 1; index >= 0; index -= 1) {
    const candidate = next[index];
    if (candidate?.role === "user") {
      lastUserIndex = index;
      break;
    }
  }
  if (lastUserIndex === -1) {
    next.push({
      id: "ai-runtime-attachments",
      role: "user",
      parts: fileParts
    });
    return next;
  }
  const source = next[lastUserIndex];
  const existingParts = Array.isArray(source.parts) ? source.parts : [];
  next[lastUserIndex] = {
    ...next[lastUserIndex],
    parts: [...existingParts, ...fileParts]
  };
  return next;
}
function appendAttachmentSummary(systemPrompt, parts) {
  const summary = summarizeAttachmentPartsForPrompt(parts);
  if (!summary) return systemPrompt;
  return `${systemPrompt}

${summary}`;
}
function buildRuntimeMutationPolicySection(agent, mutationPolicyOverride) {
  const effective = resolveEffectiveMutationPolicy(
    agent.mutationPolicy ?? null,
    mutationPolicyOverride ?? null,
    agent.id
  );
  const lines = [];
  lines.push("MUTATION POLICY (RUNTIME)");
  lines.push(`Declared agent policy: ${agent.mutationPolicy ?? "read-only"}.`);
  if (mutationPolicyOverride && mutationPolicyOverride !== agent.mutationPolicy) {
    lines.push(`Tenant override active: ${mutationPolicyOverride}.`);
  }
  lines.push(`Effective policy: ${effective}.`);
  const direct = [];
  const gated = [];
  const conditional = [];
  const blocked = [];
  for (const toolName of agent.allowedTools) {
    const tool = toolRegistry.getTool(toolName);
    if (!tool || tool.isMutation !== true) continue;
    if (effective === "read-only") {
      blocked.push(toolName);
      continue;
    }
    if (effective === "confirm-required") {
      gated.push(toolName);
      continue;
    }
    if (typeof tool.isDestructive === "function") {
      conditional.push(toolName);
    } else if (tool.isDestructive === true) {
      gated.push(toolName);
    } else {
      direct.push(toolName);
    }
  }
  if (direct.length === 0 && gated.length === 0 && conditional.length === 0 && blocked.length === 0) {
    return null;
  }
  if (direct.length > 0) {
    lines.push("");
    lines.push(
      `Tools that WILL RUN DIRECTLY (no approval card, no pending action) under the effective policy: ${direct.join(", ")}.`
    );
    lines.push(
      'When you call any of these and the call returns successfully, the change has ALREADY BEEN APPLIED. Report it in the past tense ("Updated \u2026", "Added \u2026", "Created \u2026"). Do NOT tell the operator the action is "pending your approval" or "awaiting confirmation" \u2014 that would be a false statement under the current policy.'
    );
  }
  if (gated.length > 0) {
    lines.push("");
    lines.push(
      `Tools that REQUIRE APPROVAL under the effective policy: ${gated.join(", ")}.`
    );
    lines.push(
      'When you call any of these, the dispatcher returns an "awaiting confirmation" envelope and renders an inline approval card. Tell the operator the change is pending their confirmation; do NOT claim it has been applied.'
    );
  }
  if (conditional.length > 0) {
    lines.push("");
    lines.push(
      `Tools whose approval requirement DEPENDS ON THE INPUT under the effective policy: ${conditional.join(", ")}.`
    );
    lines.push(
      'These multi-operation tools gate ONLY the destructive branches (typically `operation: "delete"` or similar). Read the tool result envelope: if it carries `status: "pending-confirmation"` then the change is pending \u2014 tell the operator it needs their approval. If it carries direct success data, the change has ALREADY BEEN APPLIED \u2014 report it in the past tense. Never assume one branch behaves like another.'
    );
  }
  if (blocked.length > 0) {
    lines.push("");
    lines.push(
      `Tools that are BLOCKED under the effective policy (read-only): ${blocked.join(", ")}.`
    );
    lines.push(
      "Calls to these tools are refused before the handler runs. Do not attempt them; instead direct the operator to the matching backoffice page or to switch the tenant policy if they have permission."
    );
  }
  lines.push("");
  lines.push(
    'This RUNTIME policy block always wins over any conflicting "approval card" language earlier in the prompt \u2014 the static prompt is written for the most restrictive case but real behavior depends on the per-call policy described here.'
  );
  return lines.join("\n");
}
function appendRuntimeMutationPolicy(systemPrompt, agent, mutationPolicyOverride) {
  const block = buildRuntimeMutationPolicySection(agent, mutationPolicyOverride);
  if (!block) return systemPrompt;
  return `${systemPrompt}

${block}`;
}
function appendRuntimeTaskPlanPrompt(systemPrompt, agent) {
  if (!isAgentTaskPlanEnabled(agent)) return systemPrompt;
  return `${systemPrompt}

${TASK_PLAN_RUNTIME_PROMPT_SECTION}`;
}
async function runAiAgentText(input) {
  const [mutationPolicyOverride, tenantRuntimeOverride, tenantAllowlistSnapshot] = await Promise.all([
    resolveMutationPolicyOverride(
      input.agentId,
      input.container,
      input.authContext.tenantId,
      input.authContext.organizationId
    ),
    resolveRuntimeModelOverride(
      input.agentId,
      input.container,
      input.authContext.tenantId,
      input.authContext.organizationId
    ),
    resolveTenantAllowlistSnapshot(
      input.container,
      input.authContext.tenantId,
      input.authContext.organizationId
    )
  ]);
  const effectiveSessionId = (input.sessionId ?? input.conversationId) || randomUUID();
  const { agent, tools } = await resolveAiAgentTools({
    agentId: input.agentId,
    authContext: input.authContext,
    pageContext: input.pageContext,
    attachmentIds: input.attachmentIds,
    mutationPolicyOverride,
    container: input.container,
    conversationId: effectiveSessionId
  });
  const resolvedAttachments = await resolveAttachmentPartsForAgent({
    agent,
    attachmentIds: input.attachmentIds,
    authContext: input.authContext,
    container: input.container
  });
  const baseSystemPrompt = await composeSystemPrompt(
    agent,
    input.pageContext,
    input.container,
    input.authContext.tenantId,
    input.authContext.organizationId
  );
  const systemPrompt = appendRuntimeMutationPolicy(
    appendRuntimeTaskPlanPrompt(appendAttachmentSummary(baseSystemPrompt, resolvedAttachments), agent),
    agent,
    mutationPolicyOverride
  );
  const resolvedModel = resolveAgentModel(
    agent,
    input.modelOverride,
    input.providerOverride,
    input.container,
    input.baseUrlOverride,
    tenantRuntimeOverride,
    input.requestOverride,
    tenantAllowlistSnapshot
  );
  const { model } = resolvedModel;
  const normalizedMessages = ensureUiMessageShape(input.messages);
  const hydratedMessages = attachAttachmentsToMessages(normalizedMessages, resolvedAttachments);
  const modelMessages = await convertToModelMessages(hydratedMessages);
  const effectiveLoop = resolveEffectiveLoopConfig(agent, input.loop, WRAPPER_DEFAULT_LOOP_CHAT);
  const stopConditions = translateStopConditions(effectiveLoop, sanitizeToolNameForModel);
  const wrapperPrepareStep = buildWrapperPrepareStep(agent, effectiveLoop, tools);
  const sdkActiveTools = mapActiveToolsForModel(effectiveLoop.activeTools, tools);
  const sdkToolChoice = mapToolChoiceForModel(effectiveLoop.toolChoice);
  const turnId = randomUUID();
  const loopTraceCollector = buildLoopTraceCollector(agent.id, effectiveSessionId, turnId, effectiveLoop.onStepFinish);
  const abortController = new AbortController();
  const budgetEnforcer = new BudgetEnforcer(effectiveLoop.budget, abortController);
  const tracedOnStepFinish = budgetEnforcer.wire(loopTraceCollector.onStepFinish);
  let currentStepIndex = 0;
  const wiredOnStepFinish = async (event) => {
    const capturedStepIndex = currentStepIndex;
    currentStepIndex += 1;
    if (tracedOnStepFinish) {
      await tracedOnStepFinish(event);
    }
    if (input.container) {
      const rawEvent = event;
      void recordTokenUsage(
        {
          authContext: input.authContext,
          agentId: agent.id,
          moduleId: agent.moduleId,
          sessionId: effectiveSessionId,
          turnId,
          stepIndex: capturedStepIndex,
          providerId: resolvedModel.providerId,
          modelId: resolvedModel.modelId,
          usage: {
            inputTokens: rawEvent.usage?.inputTokens,
            outputTokens: rawEvent.usage?.outputTokens,
            cachedInputTokens: rawEvent.usage?.cachedInputTokens,
            reasoningTokens: rawEvent.usage?.reasoningTokens
          },
          finishReason: rawEvent.finishReason,
          loopAbortReason: budgetEnforcer.abortReason ?? void 0
        },
        input.container
      );
    }
  };
  let wallClockTimer;
  if (effectiveLoop.budget?.maxWallClockMs) {
    wallClockTimer = setTimeout(() => {
      budgetEnforcer.recordStep({ toolCalls: 0 });
    }, effectiveLoop.budget.maxWallClockMs);
  }
  let builtToolLoopAgent;
  if (agent.executionEngine === "tool-loop-agent") {
    const agentSettings = {
      model,
      tools,
      stopWhen: stopConditions,
      prepareStep: wrapperPrepareStep,
      onStepFinish: wiredOnStepFinish,
      ...effectiveLoop.repairToolCall !== void 0 ? { experimental_repairToolCall: effectiveLoop.repairToolCall } : {},
      ...sdkActiveTools !== void 0 ? { activeTools: sdkActiveTools } : {},
      ...sdkToolChoice !== void 0 ? { toolChoice: sdkToolChoice } : {}
    };
    builtToolLoopAgent = new ToolLoopAgent(agentSettings);
  }
  const preparedOptions = {
    model,
    tools,
    system: systemPrompt,
    messages: modelMessages,
    maxSteps: effectiveLoop.maxSteps ?? 10,
    stopWhen: stopConditions,
    prepareStep: wrapperPrepareStep,
    onStepFinish: wiredOnStepFinish,
    onStepStart: effectiveLoop.onStepStart,
    onToolCallStart: effectiveLoop.onToolCallStart,
    onToolCallFinish: effectiveLoop.onToolCallFinish,
    experimental_repairToolCall: effectiveLoop.repairToolCall,
    activeTools: sdkActiveTools,
    toolChoice: sdkToolChoice,
    abortSignal: abortController.signal,
    finalizeLoopTrace: () => loopTraceCollector.finalize(budgetEnforcer.abortReason),
    ...builtToolLoopAgent !== void 0 ? { toolLoopAgent: builtToolLoopAgent } : {}
  };
  const taskPlanId = `turn_${turnId}`;
  if (input.generateText) {
    try {
      const callbackResult = await input.generateText(preparedOptions);
      const baseResponse2 = callbackResult.toUIMessageStreamResponse({
        sendReasoning: true,
        headers: {
          "Cache-Control": "no-cache, no-transform",
          Connection: "keep-alive"
        }
      });
      const withTaskPlan2 = injectTaskPlanIntoStream(baseResponse2, taskPlanId);
      if (input.emitLoopTrace) {
        return appendLoopFinishToStream(withTaskPlan2, preparedOptions.finalizeLoopTrace);
      }
      return withTaskPlan2;
    } finally {
      if (wallClockTimer !== void 0) clearTimeout(wallClockTimer);
    }
  }
  if (builtToolLoopAgent !== void 0) {
    const agentStreamResult = await builtToolLoopAgent.stream({
      messages: modelMessages,
      abortSignal: abortController.signal,
      onStepFinish: wiredOnStepFinish
    });
    if (wallClockTimer !== void 0) {
      const clearTimer = () => clearTimeout(wallClockTimer);
      Promise.resolve(agentStreamResult.consumeStream()).then(clearTimer, clearTimer);
    }
    const baseResponse2 = agentStreamResult.toUIMessageStreamResponse({
      sendReasoning: true,
      headers: {
        "Cache-Control": "no-cache, no-transform",
        Connection: "keep-alive"
      }
    });
    const withTaskPlan2 = injectTaskPlanIntoStream(baseResponse2, taskPlanId);
    if (input.emitLoopTrace) {
      return appendLoopFinishToStream(withTaskPlan2, preparedOptions.finalizeLoopTrace);
    }
    return withTaskPlan2;
  }
  const result = streamText({
    model,
    system: systemPrompt,
    messages: modelMessages,
    tools,
    stopWhen: stopConditions,
    prepareStep: wrapperPrepareStep,
    onStepFinish: wiredOnStepFinish,
    experimental_onStepStart: effectiveLoop.onStepStart,
    experimental_onToolCallStart: effectiveLoop.onToolCallStart,
    experimental_onToolCallFinish: effectiveLoop.onToolCallFinish,
    experimental_repairToolCall: effectiveLoop.repairToolCall,
    ...sdkActiveTools !== void 0 ? { activeTools: sdkActiveTools } : {},
    ...sdkToolChoice !== void 0 ? { toolChoice: sdkToolChoice } : {},
    abortSignal: abortController.signal
  });
  if (wallClockTimer !== void 0) {
    const clearTimer = () => clearTimeout(wallClockTimer);
    Promise.resolve(result.consumeStream()).then(clearTimer, clearTimer);
  }
  const baseResponse = result.toUIMessageStreamResponse({
    sendReasoning: true,
    headers: {
      "Cache-Control": "no-cache, no-transform",
      Connection: "keep-alive"
    }
  });
  const withTaskPlan = injectTaskPlanIntoStream(baseResponse, taskPlanId);
  if (input.emitLoopTrace) {
    return appendLoopFinishToStream(withTaskPlan, preparedOptions.finalizeLoopTrace);
  }
  return withTaskPlan;
}
function normalizeObjectMessages(input) {
  if (typeof input === "string") {
    return [
      {
        id: "user-input",
        role: "user",
        parts: [{ type: "text", text: input }]
      }
    ];
  }
  return input;
}
function resolveStructuredOutput(agent, override) {
  if (override) {
    return {
      schemaName: override.schemaName,
      schema: override.schema,
      mode: override.mode ?? "generate"
    };
  }
  const declared = agent.output;
  if (!declared) {
    throw new AgentPolicyError(
      "execution_mode_not_supported",
      `Agent "${agent.id}" does not declare a structured-output schema; pass runAiAgentObject({ output }) or declare agent.output.`
    );
  }
  return {
    schemaName: declared.schemaName,
    schema: declared.schema,
    mode: declared.mode ?? "generate"
  };
}
async function runAiAgentObject(input) {
  const [mutationPolicyOverride, tenantRuntimeOverride, tenantAllowlistSnapshot] = await Promise.all([
    resolveMutationPolicyOverride(
      input.agentId,
      input.container,
      input.authContext.tenantId,
      input.authContext.organizationId
    ),
    resolveRuntimeModelOverride(
      input.agentId,
      input.container,
      input.authContext.tenantId,
      input.authContext.organizationId
    ),
    resolveTenantAllowlistSnapshot(
      input.container,
      input.authContext.tenantId,
      input.authContext.organizationId
    )
  ]);
  const { agent, tools } = await resolveAiAgentTools({
    agentId: input.agentId,
    authContext: input.authContext,
    pageContext: input.pageContext,
    attachmentIds: input.attachmentIds,
    requestedExecutionMode: "object",
    mutationPolicyOverride,
    container: input.container
  });
  const resolvedOutput = resolveStructuredOutput(agent, input.output);
  const resolvedAttachments = await resolveAttachmentPartsForAgent({
    agent,
    attachmentIds: input.attachmentIds,
    authContext: input.authContext,
    container: input.container
  });
  const baseSystemPrompt = await composeSystemPrompt(
    agent,
    input.pageContext,
    input.container,
    input.authContext.tenantId,
    input.authContext.organizationId
  );
  const systemPrompt = appendRuntimeMutationPolicy(
    appendRuntimeTaskPlanPrompt(appendAttachmentSummary(baseSystemPrompt, resolvedAttachments), agent),
    agent,
    mutationPolicyOverride
  );
  const { model } = resolveAgentModel(
    agent,
    input.modelOverride,
    input.providerOverride,
    input.container,
    input.baseUrlOverride,
    tenantRuntimeOverride,
    input.requestOverride,
    tenantAllowlistSnapshot
  );
  const normalizedMessages = ensureUiMessageShape(normalizeObjectMessages(input.input));
  const hydratedMessages = attachAttachmentsToMessages(
    normalizedMessages,
    resolvedAttachments
  );
  const modelMessages = await convertToModelMessages(hydratedMessages);
  void tools;
  const effectiveObjectSessionId = input.sessionId ?? randomUUID();
  const objectTurnId = randomUUID();
  void effectiveObjectSessionId;
  void objectTurnId;
  if (input.loop) {
    assertLoopObjectModeCompatible(input.loop);
  }
  const effectiveLoop = resolveEffectiveLoopConfig(agent, input.loop, WRAPPER_DEFAULT_LOOP_OBJECT);
  const abortController = new AbortController();
  const preparedObjectOptions = {
    model,
    system: systemPrompt,
    messages: modelMessages,
    schemaName: resolvedOutput.schemaName,
    schema: resolvedOutput.schema,
    maxSteps: effectiveLoop.maxSteps,
    onStepFinish: effectiveLoop.onStepFinish,
    onStepStart: effectiveLoop.onStepStart,
    abortSignal: abortController.signal
  };
  if (input.generateObject) {
    const callbackResult = await input.generateObject(preparedObjectOptions);
    const typedResult = callbackResult;
    if ("partialObjectStream" in typedResult) {
      const streamResult = typedResult;
      return {
        mode: "stream",
        object: streamResult.object,
        partialObjectStream: streamResult.partialObjectStream,
        textStream: streamResult.textStream,
        finishReason: streamResult.finishReason,
        usage: streamResult.usage
      };
    }
    const genResult = typedResult;
    return {
      mode: "generate",
      object: genResult.object,
      finishReason: genResult.finishReason,
      usage: genResult.usage
    };
  }
  if (resolvedOutput.mode === "stream") {
    const streamArgs = {
      model,
      system: systemPrompt,
      messages: modelMessages,
      schema: resolvedOutput.schema,
      schemaName: resolvedOutput.schemaName,
      ...effectiveLoop.maxSteps !== void 0 ? { maxSteps: effectiveLoop.maxSteps } : {},
      onStepFinish: effectiveLoop.onStepFinish,
      onStepStart: effectiveLoop.onStepStart,
      abortSignal: abortController.signal
    };
    const result2 = streamObject(streamArgs);
    return {
      mode: "stream",
      object: result2.object,
      partialObjectStream: result2.partialObjectStream,
      textStream: result2.textStream,
      finishReason: result2.finishReason,
      usage: result2.usage
    };
  }
  const generateArgs = {
    model,
    system: systemPrompt,
    messages: modelMessages,
    schema: resolvedOutput.schema,
    schemaName: resolvedOutput.schemaName,
    ...effectiveLoop.maxSteps !== void 0 ? { maxSteps: effectiveLoop.maxSteps } : {},
    onStepFinish: effectiveLoop.onStepFinish,
    onStepStart: effectiveLoop.onStepStart,
    abortSignal: abortController.signal
  };
  const result = await generateObject(generateArgs);
  return {
    mode: "generate",
    object: result.object,
    finishReason: result.finishReason,
    usage: result.usage
  };
}
export {
  AgentPolicyError,
  BudgetEnforcer,
  assertLoopObjectModeCompatible,
  buildLoopTraceCollector,
  buildWrapperPrepareStep,
  composeSystemPrompt,
  mergeStepOverrides,
  resolveEffectiveLoopConfig,
  resolveLoopBudgetPreset,
  runAiAgentObject,
  runAiAgentText,
  translateStopConditions
};
//# sourceMappingURL=agent-runtime.js.map
