import {
  buildInitialState,
  calculateContextBudget,
  enforceContextLimit,
  formatTurnMessage,
  readFinishFiles,
  toolGlob,
  toolGrep,
  toolListDirectory,
  toolRead
} from "./chunk-VNAGVCSP.js";
import {
  extractPathFromCommand,
  parseFinishFiles,
  parseReadLines
} from "./chunk-FBRNUWEB.js";
import {
  AGENT_CONFIG,
  DEFAULT_MODEL
} from "./chunk-I3J46TSB.js";
import {
  logger
} from "./chunk-F3NCFNUX.js";
import {
  SDK_VERSION
} from "./chunk-S4Y6O7CT.js";

// tools/warp_grep/agent/runner.ts
import OpenAI from "openai";
import path from "path";
var DEFAULT_API_URL = "https://api.morphllm.com";
var TOOL_SPECS = [
  {
    type: "function",
    function: {
      name: "list_directory",
      description: "Execute ls or find commands to explore directory structure. Max 500 results. Common junk directories are excluded automatically.",
      parameters: {
        type: "object",
        properties: {
          command: {
            type: "string",
            description: "Full ls or find command (e.g. ls -la src/, find . -maxdepth 2 -type f -name '*.py', find . -type d, ls -d */)."
          }
        },
        required: ["command"]
      }
    }
  },
  {
    type: "function",
    function: {
      name: "grep_search",
      description: "Search for a regex pattern in file contents. Returns matching lines with file paths and line numbers. Case-insensitive. Respects .gitignore.",
      parameters: {
        type: "object",
        properties: {
          pattern: {
            type: "string",
            description: "Regex pattern to search for in file contents (e.g. 'class\\s+\\w+Error', 'import|require|from', 'def (get|set|update)_user')."
          },
          path: {
            type: "string",
            description: "File or directory to search in. Defaults to current working directory."
          },
          glob: {
            type: "string",
            description: "Glob pattern to filter files (e.g. '*.py', '*.{ts,tsx,js,jsx,py,go}', 'src/**/*.go', '!*.test.*')."
          },
          limit: {
            type: "integer",
            description: "Limit output to first N matching lines. Shows all matches if not specified."
          }
        },
        required: ["pattern"]
      }
    }
  },
  {
    type: "function",
    function: {
      name: "glob",
      description: "Find files by name/extension using glob patterns. Returns absolute paths sorted by modification time (newest first). Respects .gitignore. Max 100 results.",
      parameters: {
        type: "object",
        properties: {
          pattern: {
            type: "string",
            description: "Glob pattern to match files (e.g. '*.py', 'src/**/*.js', '*.{ts,tsx}', 'test_*.py')."
          },
          path: {
            type: "string",
            description: "Directory to search in. Defaults to repository root."
          }
        },
        required: ["pattern"]
      }
    }
  },
  {
    type: "function",
    function: {
      name: "read",
      description: "Read entire files or specific line ranges using absolute paths.",
      parameters: {
        type: "object",
        properties: {
          path: {
            type: "string",
            description: "File path to read, using absolute path (e.g. '/home/ubuntu/repo/src/main.py' or windows path)."
          },
          lines: {
            type: "string",
            description: "Optional line range (e.g. '1-50' or '1-20,45-80'). Omit to read entire file."
          }
        },
        required: ["path"]
      }
    }
  },
  {
    type: "function",
    function: {
      name: "finish",
      description: "Submit final answer with all relevant code locations. Include imports and over-include rather than miss context.",
      parameters: {
        type: "object",
        properties: {
          files: {
            type: "string",
            description: "One file per line as path:lines (e.g. 'src/auth.py:1-15,25-50\\nsrc/user.py'). Omit line range to include entire file."
          }
        },
        required: ["files"]
      }
    }
  }
];
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 parsedUrl = new URL(baseUrl);
  const baseURL = parsedUrl.pathname === "/" ? `${baseUrl}/v1` : baseUrl;
  const client = new OpenAI({
    apiKey,
    baseURL,
    maxRetries: options.retryConfig?.maxRetries,
    timeout: timeoutMs,
    defaultHeaders: { "X-Morph-SDK-Version": SDK_VERSION }
  });
  const MAX_EMPTY_RETRIES = 1;
  logger.debug("WarpGrep", "model_call", { url: baseURL, model, message_count: messages.length });
  const callStartTime = Date.now();
  for (let attempt = 0; attempt <= MAX_EMPTY_RETRIES; attempt++) {
    let data;
    try {
      data = await client.chat.completions.create({
        model,
        temperature: 0,
        max_tokens: 2048,
        messages,
        tools: TOOL_SPECS,
        ...options.search_type ? { search_type: options.search_type } : {}
      });
    } catch (error) {
      const callLatencyMs = Date.now() - callStartTime;
      if (error instanceof OpenAI.APIError) {
        logger.error("WarpGrep", "model_call_error", { status: error.status, error: error.message, latency_ms: callLatencyMs });
        if (error.status === 404) {
          const defaultMsg = "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";
          const errorText = error.message?.trim();
          throw new Error(errorText || defaultMsg);
        }
      } else {
        logger.error("WarpGrep", "model_call_error", { status: void 0, error: error instanceof Error ? error.message : String(error), latency_ms: callLatencyMs });
      }
      throw error;
    }
    const choice = data?.choices?.[0];
    const message = choice?.message;
    if (!message) {
      if (attempt === MAX_EMPTY_RETRIES) {
        throw new Error("Invalid response from model: no message in response");
      }
      await new Promise((resolve) => setTimeout(resolve, 200));
      continue;
    }
    const toolCalls = (message.tool_calls || []).map((tc) => ({
      id: tc.id,
      type: "function",
      function: { name: tc.function.name, arguments: tc.function.arguments }
    }));
    if (message.content || toolCalls.length > 0) {
      logger.debug("WarpGrep", "model_call_complete", { latency_ms: Date.now() - callStartTime, tool_calls_count: toolCalls.length, finish_reason: choice?.finish_reason ?? "unknown" });
      return { content: message.content ?? null, tool_calls: toolCalls };
    }
    if (attempt === MAX_EMPTY_RETRIES) {
      const finishReason = choice?.finish_reason ?? "unknown";
      throw new Error(
        `Invalid response from model: no content and no tool_calls, finish_reason=${finishReason}`
      );
    }
    await new Promise((resolve) => setTimeout(resolve, 200));
  }
  throw new Error("Invalid response from model");
}
function safeParseJSON(s) {
  try {
    return JSON.parse(s);
  } catch {
    return {};
  }
}
async function executeTool(provider, name, args, repoRoot) {
  switch (name) {
    case "grep_search": {
      const grepArgs = {
        pattern: args.pattern,
        path: args.path || "."
      };
      if (args.glob) grepArgs.glob = args.glob;
      if (args.case_sensitive !== void 0) grepArgs.case_sensitive = args.case_sensitive;
      const result = await toolGrep(provider, grepArgs);
      let output = result.output;
      if (args.limit && typeof args.limit === "number") {
        const lines = output.split("\n");
        if (lines.length > args.limit) {
          output = lines.slice(0, args.limit).join("\n") + `
... (truncated at ${args.limit} lines)`;
        }
      }
      return output;
    }
    case "glob": {
      return toolGlob(provider, {
        pattern: args.pattern,
        path: args.path
      });
    }
    case "list_directory": {
      const dirPath = extractPathFromCommand(args.command || ".");
      return toolListDirectory(provider, { path: dirPath }, repoRoot);
    }
    case "read": {
      const readArgs = {
        path: args.path
      };
      if (args.lines && typeof args.lines === "string") {
        Object.assign(readArgs, parseReadLines(args.lines));
      }
      return toolRead(provider, readArgs);
    }
    default:
      return `Unknown tool: ${name}`;
  }
}
async function* runWarpGrepStreaming(config) {
  const totalStart = Date.now();
  const timeoutMs = config.timeout ?? AGENT_CONFIG.TIMEOUT_MS;
  const timings = { turns: [], timeout_ms: timeoutMs };
  const repoRoot = config.repoRoot || process.cwd();
  const model = config.model || DEFAULT_MODEL;
  const messages = [];
  const maxTurns = AGENT_CONFIG.MAX_TURNS;
  logger.debug("WarpGrep", "session_start", { search_term: config.searchTerm, model, max_turns: maxTurns });
  const initialStateStart = Date.now();
  const initialState = await buildInitialState(repoRoot, config.searchTerm, config.provider, { search_type: config.search_type });
  timings.initial_state_ms = Date.now() - initialStateStart;
  logger.debug("WarpGrep", "initial_state", { system_prompt_len: initialState.length, user_message_len: initialState.length });
  messages.push({ role: "user", content: initialState });
  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 };
    logger.debug("WarpGrep", "turn_start", { turn, message_count: messages.length });
    const beforeChars = messages.reduce((sum, m) => sum + (typeof m.content === "string" ? m.content.length : 0), 0);
    enforceContextLimit(messages);
    const afterChars = messages.reduce((sum, m) => sum + (typeof m.content === "string" ? m.content.length : 0), 0);
    if (afterChars < beforeChars) {
      logger.debug("WarpGrep", "context_truncated", { before_chars: beforeChars, after_chars: afterChars });
    }
    const modelCallStart = Date.now();
    const response = await callModel(messages, model, {
      morphApiKey: config.morphApiKey,
      morphApiUrl: config.morphApiUrl,
      retryConfig: config.retryConfig,
      timeout: timeoutMs,
      search_type: config.search_type
    }).catch((e) => {
      const errMsg = e instanceof Error ? e.message : String(e);
      console.error(`[warp_grep] Morph API call failed on turn ${turn}:`, errMsg);
      logger.error("WarpGrep", "error", { turn, error: errMsg, phase: "model_call" });
      errors.push({ message: errMsg });
      return null;
    });
    turnMetrics.morph_api_ms = Date.now() - modelCallStart;
    if (!response) {
      timings.turns.push(turnMetrics);
      break;
    }
    const toolCalls = response.tool_calls;
    logger.debug("WarpGrep", "model_response", { turn, tool_calls: toolCalls.map((tc) => tc.function.name), latency_ms: turnMetrics.morph_api_ms });
    messages.push({
      role: "assistant",
      content: response.content,
      ...toolCalls.length > 0 ? { tool_calls: toolCalls } : {}
    });
    if (toolCalls.length === 0) {
      console.error(`[warp_grep] No tool calls on turn ${turn}. Content: ${(response.content || "").slice(0, 500)}`);
      errors.push({ message: "No tool calls produced by the model." });
      terminationReason = "terminated";
      timings.turns.push(turnMetrics);
      break;
    }
    yield {
      turn,
      toolCalls: toolCalls.map((tc) => ({
        name: tc.function.name,
        arguments: safeParseJSON(tc.function.arguments)
      }))
    };
    const finishCall = toolCalls.find((tc) => tc.function.name === "finish");
    if (finishCall) {
      const args = safeParseJSON(finishCall.function.arguments);
      const filesStr = args.files || "";
      const files = parseFinishFiles(filesStr);
      finishMeta = { files };
      terminationReason = "completed";
      logger.debug("WarpGrep", "finish", { turns: turn, files: files.map((f) => f.path) });
      if (files.length === 0) {
        const payload2 = filesStr || "No relevant code found.";
        timings.turns.push(turnMetrics);
        timings.total_ms = Date.now() - totalStart;
        return {
          terminationReason: "completed",
          messages,
          finish: { payload: payload2, metadata: finishMeta },
          timings
        };
      }
      timings.turns.push(turnMetrics);
      break;
    }
    const toolExecStart = Date.now();
    const results = await Promise.all(
      toolCalls.map(async (tc) => {
        const args = safeParseJSON(tc.function.arguments);
        const argsSummary = Object.fromEntries(Object.entries(args).map(([k, v]) => [k, typeof v === "string" && v.length > 100 ? v.slice(0, 100) + "..." : v]));
        logger.debug("WarpGrep", "tool_exec", { turn, tool: tc.function.name, args_summary: argsSummary });
        const toolStart = Date.now();
        const output = await executeTool(provider, tc.function.name, args, repoRoot).catch((err) => String(err));
        logger.debug("WarpGrep", "tool_result", { turn, tool: tc.function.name, result_len: output.length, latency_ms: Date.now() - toolStart });
        return { tool_call_id: tc.id, content: output };
      })
    );
    turnMetrics.local_tools_ms = Date.now() - toolExecStart;
    for (const result of results) {
      messages.push({ role: "tool", tool_call_id: result.tool_call_id, content: result.content });
    }
    const turnMsg = formatTurnMessage(turn, maxTurns);
    const budget = calculateContextBudget(messages);
    messages.push({ role: "user", content: turnMsg + "\n" + budget });
    timings.turns.push(turnMetrics);
  }
  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) => {
      let resolvedPath = p;
      if (!p.startsWith(repoRoot)) {
        const relative = p.startsWith("/") ? p.slice(1) : p;
        resolvedPath = path.join(repoRoot, relative);
      }
      try {
        const rr = await provider.read({ path: resolvedPath, 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: resolvedPath, error: errorMsg });
        console.error(`[warp_grep] Failed to read file: ${resolvedPath} - ${errorMsg}`);
        return [`[couldn't find: ${resolvedPath}]`];
      }
    }
  );
  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 runWarpGrep(config) {
  const gen = runWarpGrepStreaming(config);
  let result = await gen.next();
  while (!result.done) {
    result = await gen.next();
  }
  return result.value;
}

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