import {
  buildInitialState,
  calculateContextBudget,
  enforceContextLimit,
  formatTurnMessage,
  readFinishFiles,
  toolGrep,
  toolListDirectory,
  toolRead
} from "./chunk-3MLWXJTJ.js";
import {
  AGENT_CONFIG,
  DEFAULT_MODEL
} from "./chunk-5PNMAWLC.js";
import {
  formatAgentToolOutput
} from "./chunk-APP75CBN.js";
import {
  LLMResponseParser
} from "./chunk-GHGJAQSJ.js";
import {
  getSystemPrompt
} from "./chunk-FMLHRJDF.js";
import {
  SDK_VERSION
} from "./chunk-RNKHRNSG.js";

// tools/warp_grep/agent/runner.ts
import OpenAI from "openai";
import path from "path";
var parser = new LLMResponseParser();
var DEFAULT_API_URL = "https://api.morphllm.com";
async function callModel(messages, model, options = {}) {
  const baseUrl = options.morphApiUrl || DEFAULT_API_URL;
  const apiKey = options.morphApiKey || process.env.MORPH_API_KEY || "";
  const timeoutMs = options.timeout ?? AGENT_CONFIG.TIMEOUT_MS;
  const client = new OpenAI({
    apiKey,
    baseURL: `${baseUrl}/v1`,
    maxRetries: options.retryConfig?.maxRetries,
    timeout: timeoutMs,
    defaultHeaders: { "X-Morph-SDK-Version": SDK_VERSION }
  });
  const MAX_EMPTY_RETRIES = 1;
  for (let attempt = 0; attempt <= MAX_EMPTY_RETRIES; attempt++) {
    let data;
    try {
      data = await client.chat.completions.create({
        model,
        temperature: 0,
        max_tokens: 1024,
        messages
      });
    } catch (error) {
      if (error instanceof OpenAI.APIError && error.status === 404) {
        throw new Error(
          "The endpoint you are trying to call is likely deprecated. Please update with: npm cache clean --force && npx -y @morphllm/morphmcp@latest or visit: https://morphllm.com/mcp"
        );
      }
      throw error;
    }
    const choice = data?.choices?.[0];
    const content = choice?.message?.content;
    if (content && typeof content === "string") {
      return content;
    }
    if (attempt === MAX_EMPTY_RETRIES) {
      const finishReason = choice?.finish_reason ?? "unknown";
      const hasToolCalls = Array.isArray(choice?.message?.tool_calls) && choice.message.tool_calls.length > 0;
      const choicesLen = data?.choices?.length ?? 0;
      const contentType = content === null ? "null" : content === void 0 ? "undefined" : typeof content;
      throw new Error(
        `Invalid response from model: content=${contentType}, finish_reason=${finishReason}, has_tool_calls=${hasToolCalls}, choices_length=${choicesLen}`
      );
    }
    await new Promise((resolve) => setTimeout(resolve, 200));
  }
  throw new Error("Invalid response from model");
}
async function runWarpGrep(config) {
  const totalStart = Date.now();
  const timeoutMs = config.timeout ?? AGENT_CONFIG.TIMEOUT_MS;
  const timings = { turns: [], timeout_ms: timeoutMs };
  const repoRoot = path.resolve(config.repoRoot || process.cwd());
  const messages = [];
  messages.push({ role: "system", content: getSystemPrompt() });
  const initialStateStart = Date.now();
  const initialState = await buildInitialState(repoRoot, config.query, config.provider);
  timings.initial_state_ms = Date.now() - initialStateStart;
  messages.push({ role: "user", content: initialState });
  const maxTurns = AGENT_CONFIG.MAX_TURNS;
  const model = config.model || DEFAULT_MODEL;
  const provider = config.provider;
  const errors = [];
  let finishMeta;
  let terminationReason = "terminated";
  for (let turn = 1; turn <= maxTurns; turn += 1) {
    const turnMetrics = { turn, morph_api_ms: 0, local_tools_ms: 0 };
    enforceContextLimit(messages);
    const modelCallStart = Date.now();
    const assistantContent = await callModel(messages, model, {
      morphApiKey: config.morphApiKey,
      morphApiUrl: config.morphApiUrl,
      retryConfig: config.retryConfig,
      timeout: timeoutMs
    }).catch((e) => {
      const errMsg = e instanceof Error ? e.message : String(e);
      console.error(`[warp_grep] Morph API call failed on turn ${turn}:`, errMsg);
      errors.push({ message: errMsg });
      return "";
    });
    turnMetrics.morph_api_ms = Date.now() - modelCallStart;
    if (!assistantContent) {
      console.error(`[warp_grep] Empty response from Morph API on turn ${turn}. Errors so far:`, errors);
      timings.turns.push(turnMetrics);
      break;
    }
    messages.push({ role: "assistant", content: assistantContent });
    const toolCalls = parser.parse(assistantContent);
    if (toolCalls.length === 0) {
      console.error(`[warp_grep] No tool calls parsed on turn ${turn}. Assistant content (first 500 chars):`, assistantContent.slice(0, 500));
      errors.push({ message: "No tool calls produced by the model. Your MCP is likely out of date! Update it by running: rm -rf ~/.npm/_npx && npm cache clean --force && npx -y @morphllm/morphmcp@latest" });
      terminationReason = "terminated";
      timings.turns.push(turnMetrics);
      break;
    }
    const finishCalls = toolCalls.filter((c) => c.name === "finish");
    const grepCalls = toolCalls.filter((c) => c.name === "grep");
    const listDirCalls = toolCalls.filter((c) => c.name === "list_directory");
    const readCalls = toolCalls.filter((c) => c.name === "read");
    const skipCalls = toolCalls.filter((c) => c.name === "_skip");
    const formatted = [];
    for (const c of skipCalls) {
      const msg = c.arguments?.message || "Command skipped due to parsing error";
      formatted.push(msg);
    }
    const allPromises = [];
    for (const c of grepCalls) {
      const args = c.arguments ?? {};
      allPromises.push(
        toolGrep(provider, args).then(
          ({ output }) => formatAgentToolOutput("grep", args, output, { isError: false }),
          (err) => formatAgentToolOutput("grep", args, String(err), { isError: true })
        )
      );
    }
    for (const c of listDirCalls) {
      const args = c.arguments ?? {};
      allPromises.push(
        toolListDirectory(provider, args).then(
          (p) => formatAgentToolOutput("list_directory", args, p, { isError: false }),
          (err) => formatAgentToolOutput("list_directory", args, String(err), { isError: true })
        )
      );
    }
    for (const c of readCalls) {
      const args = c.arguments ?? {};
      allPromises.push(
        toolRead(provider, args).then(
          (p) => formatAgentToolOutput("read", args, p, { isError: false }),
          (err) => formatAgentToolOutput("read", args, String(err), { isError: true })
        )
      );
    }
    const toolExecStart = Date.now();
    const allResults = await Promise.all(allPromises);
    turnMetrics.local_tools_ms = Date.now() - toolExecStart;
    for (const result of allResults) {
      formatted.push(result);
    }
    if (formatted.length > 0) {
      const turnMessage = formatTurnMessage(turn, maxTurns);
      const contextBudget = calculateContextBudget(messages);
      messages.push({ role: "user", content: formatted.join("\n") + turnMessage + "\n" + contextBudget });
    }
    timings.turns.push(turnMetrics);
    if (finishCalls.length) {
      const fc = finishCalls[0];
      const files = fc.arguments?.files ?? [];
      const textResult = fc.arguments?.textResult;
      finishMeta = { files };
      terminationReason = "completed";
      if (files.length === 0) {
        const payload2 = textResult || "No relevant code found.";
        timings.turns.push(turnMetrics);
        timings.total_ms = Date.now() - totalStart;
        return {
          terminationReason: "completed",
          messages,
          finish: { payload: payload2, metadata: finishMeta },
          timings
        };
      }
      break;
    }
  }
  if (terminationReason !== "completed" || !finishMeta) {
    timings.total_ms = Date.now() - totalStart;
    return { terminationReason, messages, errors, timings };
  }
  const parts = ["Relevant context found:"];
  for (const f of finishMeta.files) {
    const ranges = f.lines === "*" ? "*" : Array.isArray(f.lines) ? f.lines.map(([s, e]) => `${s}-${e}`).join(", ") : "*";
    parts.push(`- ${f.path}: ${ranges}`);
  }
  const payload = parts.join("\n");
  const finishResolutionStart = Date.now();
  const fileReadErrors = [];
  const resolved = await readFinishFiles(
    repoRoot,
    finishMeta.files,
    async (p, s, e) => {
      try {
        const rr = await provider.read({ path: p, start: s, end: e });
        return rr.lines.map((l) => {
          const idx = l.indexOf("|");
          return idx >= 0 ? l.slice(idx + 1) : l;
        });
      } catch (err) {
        const errorMsg = err instanceof Error ? err.message : String(err);
        fileReadErrors.push({ path: p, error: errorMsg });
        console.error(`[warp_grep] Failed to read file: ${p} - ${errorMsg}`);
        return [`[couldn't find: ${p}]`];
      }
    }
  );
  timings.finish_resolution_ms = Date.now() - finishResolutionStart;
  if (fileReadErrors.length > 0) {
    errors.push(...fileReadErrors.map((e) => ({ message: `File read error: ${e.path} - ${e.error}` })));
  }
  timings.total_ms = Date.now() - totalStart;
  return {
    terminationReason: "completed",
    messages,
    finish: { payload, metadata: finishMeta, resolved },
    timings
  };
}
async function* runWarpGrepStreaming(config) {
  const totalStart = Date.now();
  const timeoutMs = config.timeout ?? AGENT_CONFIG.TIMEOUT_MS;
  const timings = { turns: [], timeout_ms: timeoutMs };
  const repoRoot = path.resolve(config.repoRoot || process.cwd());
  const messages = [];
  messages.push({ role: "system", content: getSystemPrompt() });
  const initialStateStart = Date.now();
  const initialState = await buildInitialState(repoRoot, config.query, config.provider);
  timings.initial_state_ms = Date.now() - initialStateStart;
  messages.push({ role: "user", content: initialState });
  const maxTurns = AGENT_CONFIG.MAX_TURNS;
  const model = config.model || DEFAULT_MODEL;
  const provider = config.provider;
  const errors = [];
  let finishMeta;
  let terminationReason = "terminated";
  for (let turn = 1; turn <= maxTurns; turn += 1) {
    const turnMetrics = { turn, morph_api_ms: 0, local_tools_ms: 0 };
    enforceContextLimit(messages);
    const modelCallStart = Date.now();
    const assistantContent = await callModel(messages, model, {
      morphApiKey: config.morphApiKey,
      morphApiUrl: config.morphApiUrl,
      retryConfig: config.retryConfig,
      timeout: timeoutMs
    }).catch((e) => {
      const errMsg = e instanceof Error ? e.message : String(e);
      console.error(`[warp_grep:stream] Morph API call failed on turn ${turn}:`, errMsg);
      errors.push({ message: errMsg });
      return "";
    });
    turnMetrics.morph_api_ms = Date.now() - modelCallStart;
    if (!assistantContent) {
      console.error(`[warp_grep:stream] Empty response from Morph API on turn ${turn}. Errors so far:`, errors);
      timings.turns.push(turnMetrics);
      break;
    }
    messages.push({ role: "assistant", content: assistantContent });
    const toolCalls = parser.parse(assistantContent);
    if (toolCalls.length === 0) {
      console.error(`[warp_grep:stream] No tool calls parsed on turn ${turn}. Assistant content (first 500 chars):`, assistantContent.slice(0, 500));
      errors.push({ message: "No tool calls produced by the model. Your MCP is likely out of date! Update it by running: rm -rf ~/.npm/_npx && npm cache clean --force && npx -y @morphllm/morphmcp@latest" });
      terminationReason = "terminated";
      timings.turns.push(turnMetrics);
      break;
    }
    yield {
      turn,
      toolCalls: toolCalls.map((c) => ({
        name: c.name,
        arguments: c.arguments ?? {}
      }))
    };
    const finishCalls = toolCalls.filter((c) => c.name === "finish");
    const grepCalls = toolCalls.filter((c) => c.name === "grep");
    const listDirCalls = toolCalls.filter((c) => c.name === "list_directory");
    const readCalls = toolCalls.filter((c) => c.name === "read");
    const skipCalls = toolCalls.filter((c) => c.name === "_skip");
    const formatted = [];
    for (const c of skipCalls) {
      const msg = c.arguments?.message || "Command skipped due to parsing error";
      formatted.push(msg);
    }
    const allPromises = [];
    for (const c of grepCalls) {
      const args = c.arguments ?? {};
      allPromises.push(
        toolGrep(provider, args).then(
          ({ output }) => formatAgentToolOutput("grep", args, output, { isError: false }),
          (err) => formatAgentToolOutput("grep", args, String(err), { isError: true })
        )
      );
    }
    for (const c of listDirCalls) {
      const args = c.arguments ?? {};
      allPromises.push(
        toolListDirectory(provider, args).then(
          (p) => formatAgentToolOutput("list_directory", args, p, { isError: false }),
          (err) => formatAgentToolOutput("list_directory", args, String(err), { isError: true })
        )
      );
    }
    for (const c of readCalls) {
      const args = c.arguments ?? {};
      allPromises.push(
        toolRead(provider, args).then(
          (p) => formatAgentToolOutput("read", args, p, { isError: false }),
          (err) => formatAgentToolOutput("read", args, String(err), { isError: true })
        )
      );
    }
    const toolExecStart = Date.now();
    const allResults = await Promise.all(allPromises);
    turnMetrics.local_tools_ms = Date.now() - toolExecStart;
    for (const result of allResults) {
      formatted.push(result);
    }
    if (formatted.length > 0) {
      const turnMessage = formatTurnMessage(turn, maxTurns);
      const contextBudget = calculateContextBudget(messages);
      messages.push({ role: "user", content: formatted.join("\n") + turnMessage + "\n" + contextBudget });
    }
    timings.turns.push(turnMetrics);
    if (finishCalls.length) {
      const fc = finishCalls[0];
      const files = fc.arguments?.files ?? [];
      const textResult = fc.arguments?.textResult;
      finishMeta = { files };
      terminationReason = "completed";
      if (files.length === 0) {
        const payload2 = textResult || "No relevant code found.";
        timings.total_ms = Date.now() - totalStart;
        return {
          terminationReason: "completed",
          messages,
          finish: { payload: payload2, metadata: finishMeta },
          timings
        };
      }
      break;
    }
  }
  if (terminationReason !== "completed" || !finishMeta) {
    timings.total_ms = Date.now() - totalStart;
    return { terminationReason, messages, errors, timings };
  }
  const parts = ["Relevant context found:"];
  for (const f of finishMeta.files) {
    const ranges = f.lines === "*" ? "*" : Array.isArray(f.lines) ? f.lines.map(([s, e]) => `${s}-${e}`).join(", ") : "*";
    parts.push(`- ${f.path}: ${ranges}`);
  }
  const payload = parts.join("\n");
  const finishResolutionStart = Date.now();
  const fileReadErrors = [];
  const resolved = await readFinishFiles(
    repoRoot,
    finishMeta.files,
    async (p, s, e) => {
      try {
        const rr = await provider.read({ path: p, start: s, end: e });
        return rr.lines.map((l) => {
          const idx = l.indexOf("|");
          return idx >= 0 ? l.slice(idx + 1) : l;
        });
      } catch (err) {
        const errorMsg = err instanceof Error ? err.message : String(err);
        fileReadErrors.push({ path: p, error: errorMsg });
        console.error(`[warp_grep] Failed to read file: ${p} - ${errorMsg}`);
        return [`[couldn't find: ${p}]`];
      }
    }
  );
  timings.finish_resolution_ms = Date.now() - finishResolutionStart;
  if (fileReadErrors.length > 0) {
    errors.push(...fileReadErrors.map((e) => ({ message: `File read error: ${e.path} - ${e.error}` })));
  }
  timings.total_ms = Date.now() - totalStart;
  return {
    terminationReason: "completed",
    messages,
    finish: { payload, metadata: finishMeta, resolved },
    timings
  };
}

export {
  runWarpGrep,
  runWarpGrepStreaming
};
//# sourceMappingURL=chunk-MQWEZJTP.js.map