import {
  ProfilesClient
} from "./chunk-WAWGBDBT.js";
import {
  buildEmbedCode,
  buildLiveIframe,
  buildLiveUrl,
  resolvePreset
} from "./chunk-YTYCHRQ2.js";
import {
  fetchWithRetry,
  withTimeout
} from "./chunk-E6BQJL5D.js";
import {
  logger
} from "./chunk-F3NCFNUX.js";

// tools/browser/core.ts
var DEFAULT_CONFIG = {
  apiUrl: process.env.MORPH_ENVIRONMENT === "DEV" ? "http://localhost:8000" : "https://browser.morphllm.com",
  timeout: 1e6,
  // 10 minutes for complex tasks
  debug: false
};
var BrowserClient = class {
  config;
  /**
   * Profile management - create and manage browser profiles for storing login state.
   */
  profiles;
  constructor(config = {}) {
    this.config = {
      ...DEFAULT_CONFIG,
      ...config
    };
    this.profiles = new ProfilesClient(this.config);
  }
  /**
   * Execute a browser automation task
   */
  async execute(input) {
    return executeBrowserTask(input, this.config);
  }
  async createTask(input) {
    const apiUrl = this.config.apiUrl || DEFAULT_CONFIG.apiUrl;
    const debug = this.config.debug || false;
    const hasTask = typeof input.task === "string" && input.task.trim().length > 0;
    const hasDiff = typeof input.diff === "string" && input.diff.trim().length > 0;
    if (!hasTask && !hasDiff) {
      throw new Error('Browser task requires either "task" (natural language) or "diff" (PR-review planning)');
    }
    logger.debug("Browser", "create_task", { task: (input.task ?? "").slice(0, 100), url: input.url, endpoint: `${apiUrl}/browser-task/async` });
    const headers = { "Content-Type": "application/json" };
    if (this.config.apiKey) headers["Authorization"] = `Bearer ${this.config.apiKey}`;
    const response = await fetch(`${apiUrl}/browser-task/async`, {
      method: "POST",
      headers,
      body: JSON.stringify({
        task: input.task,
        diff: input.diff,
        url: input.url,
        max_steps: input.maxSteps ?? 10,
        model: input.model ?? "morph-computer-use-v0",
        viewport_width: input.viewportWidth ?? 1280,
        viewport_height: input.viewportHeight ?? 720,
        external_id: input.externalId,
        repo_id: input.repoId,
        repo_full_name: input.repoFullName,
        commit_id: input.commitId,
        record_video: input.recordVideo ?? false,
        video_width: input.videoWidth ?? input.viewportWidth ?? 1280,
        video_height: input.videoHeight ?? input.viewportHeight ?? 720,
        allow_resizing: input.allowResizing ?? false,
        structured_output: "schema" in input ? stringifyStructuredOutput(input.schema) : void 0,
        auth: input.auth,
        profile_id: input.profileId,
        force_query_params: input.forceQueryParams
      })
    });
    if (!response.ok) {
      const errorText = await response.text().catch(() => response.statusText);
      logger.error("Browser", "create_task_error", { status: response.status, error: errorText });
      throw new Error(`HTTP ${response.status}: ${errorText}`);
    }
    const result = mapTaskResult(await response.json());
    logger.debug("Browser", "task_created", { recording_id: result.recordingId, task_id: result.taskId, debug_url: !!result.debugUrl });
    if ("schema" in input) {
      return wrapTaskResponseWithSchema(result, this.config, input.schema);
    } else {
      return wrapTaskResponse(result, this.config);
    }
  }
  /**
   * Execute task with recording and wait for video to be ready
   */
  async executeWithRecording(input) {
    return executeWithRecording(input, this.config);
  }
  /**
   * Get recording status and URLs
   */
  async getRecording(recordingId) {
    return getRecording(recordingId, this.config);
  }
  /**
   * Wait for recording to complete with automatic polling
   */
  async waitForRecording(recordingId, options) {
    return waitForRecording(recordingId, this.config, options);
  }
  /**
   * Get errors from recording with screenshots
   */
  async getErrors(recordingId) {
    return getErrors(recordingId, this.config);
  }
  /**
   * Get animated WebP preview of recording
   */
  async getWebp(recordingId, options) {
    return getWebp(recordingId, this.config, options);
  }
  /**
   * Check if browser worker service is healthy
   */
  async checkHealth() {
    return checkHealth(this.config);
  }
};
async function executeBrowserTask(input, config = {}) {
  const apiUrl = config.apiUrl || DEFAULT_CONFIG.apiUrl;
  const timeout = config.timeout || DEFAULT_CONFIG.timeout;
  const debug = config.debug || false;
  if (!input.task || input.task.trim().length === 0) {
    return {
      success: false,
      error: 'Task description is required. Example: "Go to example.com and click the login button"'
    };
  }
  if (input.maxSteps !== void 0 && (input.maxSteps < 1 || input.maxSteps > 50)) {
    return {
      success: false,
      error: "maxSteps must be between 1 and 50. Use more steps for complex multi-page flows."
    };
  }
  logger.debug("Browser", "execute_start", { task: input.task.slice(0, 100), url: input.url, max_steps: input.maxSteps ?? 10, record_video: input.recordVideo ?? false });
  const startTime = Date.now();
  try {
    const headers = { "Content-Type": "application/json" };
    if (config.apiKey) headers["Authorization"] = `Bearer ${config.apiKey}`;
    const fetchPromise = fetchWithRetry(
      `${apiUrl}/browser-task`,
      {
        method: "POST",
        headers,
        body: JSON.stringify({
          task: input.task,
          url: input.url,
          max_steps: input.maxSteps ?? 10,
          model: input.model ?? "morph-computer-use-v0",
          viewport_width: input.viewportWidth ?? 1280,
          viewport_height: input.viewportHeight ?? 720,
          external_id: input.externalId,
          repo_id: input.repoId,
          commit_id: input.commitId,
          record_video: input.recordVideo ?? false,
          video_width: input.videoWidth ?? input.viewportWidth ?? 1280,
          video_height: input.videoHeight ?? input.viewportHeight ?? 720,
          allow_resizing: input.allowResizing ?? false,
          structured_output: input.structuredOutput,
          auth: input.auth,
          profile_id: input.profileId,
          force_query_params: input.forceQueryParams
        })
      },
      config.retryConfig
    );
    const response = await withTimeout(
      fetchPromise,
      timeout,
      `Browser task timed out after ${timeout}ms. Consider increasing timeout or reducing maxSteps.`
    );
    if (!response.ok) {
      const errorText = await response.text().catch(() => response.statusText);
      logger.error("Browser", "execute_error", { status: response.status, error: errorText });
      throw new Error(`HTTP ${response.status}: ${errorText}`);
    }
    const result = mapTaskResult(await response.json());
    const elapsed = Date.now() - startTime;
    logger.debug("Browser", "execute_complete", { success: result.success, latency_ms: elapsed, steps: result.stepsTaken ?? 0, recording_id: result.recordingId });
    return result;
  } catch (error) {
    if (error instanceof Error) {
      if (error.message.includes("ECONNREFUSED") || error.message.includes("fetch failed")) {
        return {
          success: false,
          error: `Cannot connect to browser worker at ${apiUrl}. Ensure the service is running and accessible. For local dev, set MORPH_ENVIRONMENT=DEV.`
        };
      }
      return {
        success: false,
        error: error.message
      };
    }
    return {
      success: false,
      error: String(error)
    };
  }
}
async function getRecording(recordingId, config = {}) {
  const apiUrl = config.apiUrl || DEFAULT_CONFIG.apiUrl;
  const debug = config.debug || false;
  if (!config.apiKey) {
    throw new Error("API key required for getRecording");
  }
  if (debug) console.log(`[Browser] getRecording: ${recordingId}`);
  const response = await fetch(`${apiUrl}/recordings/${recordingId}`, {
    method: "GET",
    headers: { "Authorization": `Bearer ${config.apiKey}` }
  });
  if (!response.ok) {
    const errorText = await response.text().catch(() => response.statusText);
    if (debug) console.error(`[Browser] getRecording error: ${response.status} - ${errorText}`);
    throw new Error(`HTTP ${response.status}: ${errorText}`);
  }
  const data = mapRecordingStatus(await response.json());
  if (debug) console.log(`[Browser] Recording status: ${data.status}`);
  return {
    ...data,
    getWebp: (options) => getWebp(recordingId, config, options),
    getErrors: () => getErrors(recordingId, config)
  };
}
async function waitForRecording(recordingId, config = {}, options = {}) {
  const timeout = options.timeout ?? 6e4;
  const pollInterval = options.pollInterval ?? 2e3;
  const startTime = Date.now();
  while (Date.now() - startTime < timeout) {
    const status = await getRecording(recordingId, config);
    if (status.status === "COMPLETED" || status.status === "ERROR") {
      return status;
    }
    await new Promise((resolve) => setTimeout(resolve, pollInterval));
  }
  throw new Error(`Recording timeout after ${timeout}ms - status still pending`);
}
async function executeWithRecording(input, config = {}) {
  const taskResult = await executeBrowserTask(input, config);
  if (taskResult.recordingId) {
    try {
      const recording = await waitForRecording(
        taskResult.recordingId,
        config,
        { timeout: 6e4, pollInterval: 2e3 }
      );
      return {
        ...taskResult,
        recording
      };
    } catch (error) {
      const errorRecording = {
        id: taskResult.recordingId,
        status: "ERROR",
        error: error instanceof Error ? error.message : String(error),
        createdAt: (/* @__PURE__ */ new Date()).toISOString(),
        getWebp: (options) => getWebp(taskResult.recordingId, config, options),
        getErrors: () => getErrors(taskResult.recordingId, config)
      };
      return {
        ...taskResult,
        recording: errorRecording
      };
    }
  }
  return taskResult;
}
async function getErrors(recordingId, config = {}) {
  const apiUrl = config.apiUrl || DEFAULT_CONFIG.apiUrl;
  const debug = config.debug || false;
  if (!config.apiKey) {
    throw new Error("API key required for getErrors");
  }
  if (debug) console.log(`[Browser] getErrors: ${recordingId}`);
  const response = await fetch(`${apiUrl}/recordings/${recordingId}/errors`, {
    method: "GET",
    headers: { "Authorization": `Bearer ${config.apiKey}` }
  });
  if (!response.ok) {
    const errorText = await response.text().catch(() => response.statusText);
    if (debug) console.error(`[Browser] getErrors error: ${response.status} - ${errorText}`);
    throw new Error(`HTTP ${response.status}: ${errorText}`);
  }
  const errors = mapErrorsResponse(await response.json());
  if (debug) console.log(`[Browser] Found ${errors.totalErrors} errors`);
  return errors;
}
function stringifyStructuredOutput(schema) {
  try {
    return JSON.stringify({
      type: "object",
      description: "Zod schema definition (Zod v3)",
      zodDef: schema._def
    });
  } catch (error) {
    console.warn("[Browser] Failed to serialize Zod schema:", error);
    return JSON.stringify({
      type: "object",
      description: "Schema serialization failed"
    });
  }
}
function parseStructuredTaskOutput(result, schema) {
  if (!result.output) {
    return { ...result, parsed: null };
  }
  try {
    const parsed = JSON.parse(result.output);
    const validated = schema.parse(parsed);
    return { ...result, parsed: validated };
  } catch (error) {
    if (error instanceof SyntaxError) {
      return { ...result, parsed: null };
    }
    throw error;
  }
}
function mapTaskResult(api) {
  if (!api || typeof api !== "object") {
    return api;
  }
  return {
    success: api.success,
    result: api.result,
    error: api.error,
    stepsTaken: api.steps_taken,
    executionTimeMs: api.execution_time_ms,
    urls: api.urls,
    actionNames: api.action_names,
    errors: api.errors,
    modelActions: api.model_actions,
    isDone: api.is_done,
    actionHistory: api.action_history,
    actionResults: api.action_results,
    hasErrors: api.has_errors,
    numberOfSteps: api.number_of_steps,
    judgement: api.judgement,
    isValidated: api.is_validated,
    replayId: api.replay_id,
    replayUrl: api.replay_url,
    recordingId: api.recording_id,
    recordingStatus: api.recording_status,
    taskId: api.task_id,
    status: api.status,
    output: api.output,
    debugUrl: api.debug_url
  };
}
function mapRecordingStatus(api) {
  return {
    id: api.id,
    status: api.status,
    replayUrl: api.replay_url,
    networkUrl: api.network_url,
    consoleUrl: api.console_url,
    videoUrl: api.video_url,
    result: api.result,
    totalEvents: api.total_events,
    fileSize: api.file_size,
    duration: api.duration,
    error: api.error,
    createdAt: api.created_at
  };
}
function mapBrowserError(api) {
  return {
    type: api.type,
    message: api.message,
    url: api.url,
    timestamp: api.timestamp,
    screenshotUrl: api.screenshot_url,
    capturedAt: api.captured_at,
    status: api.status
  };
}
function mapErrorsResponse(api) {
  return {
    recordingId: api.recording_id,
    totalErrors: api.total_errors,
    errors: Array.isArray(api.errors) ? api.errors.map(mapBrowserError) : []
  };
}
function mapWebpResponse(api) {
  return {
    webpUrl: api.webp_url,
    cached: api.cached,
    width: api.width,
    fps: api.fps,
    maxDuration: api.max_duration,
    fileSize: api.file_size,
    maxSizeMb: api.max_size_mb,
    budgetMet: api.budget_met,
    qualityUsed: api.quality_used,
    attempts: api.attempts
  };
}
async function getTaskStatus(taskId, config) {
  const apiUrl = config.apiUrl || DEFAULT_CONFIG.apiUrl;
  const debug = config.debug || false;
  if (debug) console.log(`[Browser] getTaskStatus: ${taskId}`);
  const headers = {};
  if (config.apiKey) headers["Authorization"] = `Bearer ${config.apiKey}`;
  const response = await fetch(`${apiUrl}/tasks/${taskId}`, {
    method: "GET",
    headers
  });
  if (!response.ok) {
    const errorText = await response.text().catch(() => response.statusText);
    if (debug) console.error(`[Browser] getTaskStatus error: ${response.status} - ${errorText}`);
    throw new Error(`HTTP ${response.status}: ${errorText}`);
  }
  const result = mapTaskResult(await response.json());
  if (debug) console.log(`[Browser] Task status: ${result.status}`);
  return result;
}
function generateLiveUrl(taskId, config) {
  const apiUrl = config.apiUrl || DEFAULT_CONFIG.apiUrl;
  const baseUrl = apiUrl.replace("/api", "");
  return `${baseUrl}/tasks/${taskId}/live`;
}
async function pollTaskUntilComplete(taskId, config, pollConfig = {}) {
  const interval = pollConfig.interval ?? 2e3;
  const timeout = pollConfig.timeout ?? 3e5;
  const startTime = Date.now();
  while (Date.now() - startTime < timeout) {
    const status = await getTaskStatus(taskId, config);
    if (status.status === "completed" || status.status === "failed") {
      return status;
    }
    await new Promise((resolve) => setTimeout(resolve, interval));
  }
  throw new Error(`Task polling timeout after ${timeout}ms`);
}
function wrapTaskResponse(result, config) {
  const debugUrl = result.debugUrl ?? "";
  const wrapped = {
    ...result,
    debugUrl,
    taskId: result.taskId || "",
    liveUrl: result.taskId ? generateLiveUrl(result.taskId, config) : debugUrl,
    complete: async (pollConfig) => {
      if (result.taskId) {
        return pollTaskUntilComplete(result.taskId, config, pollConfig);
      }
      if (result.recordingId) {
        const recording = await waitForRecording(
          result.recordingId,
          config,
          pollConfig
        );
        return {
          ...result,
          recordingStatus: recording.status
        };
      }
      throw new Error("Cannot poll completion: no taskId or recordingId available");
    },
    // Add Steel live session helpers - either functional or error-throwing
    getLiveUrl: debugUrl ? (options) => buildLiveUrl(debugUrl, options) : () => {
      throw new Error(
        "Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help."
      );
    },
    getLiveIframe: debugUrl ? (optionsOrPreset) => {
      const options = resolvePreset(optionsOrPreset);
      return buildLiveIframe(debugUrl, options);
    } : () => {
      throw new Error(
        "Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help."
      );
    },
    getEmbedCode: debugUrl ? () => buildEmbedCode(debugUrl) : () => {
      throw new Error(
        "Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help."
      );
    }
  };
  return wrapped;
}
function wrapTaskResponseWithSchema(result, config, schema) {
  const debugUrl = result.debugUrl ?? "";
  const parsed = result.output ? parseStructuredTaskOutput(result, schema) : { ...result, parsed: null };
  const wrapped = {
    ...parsed,
    debugUrl,
    taskId: result.taskId || "",
    liveUrl: result.taskId ? generateLiveUrl(result.taskId, config) : debugUrl,
    complete: async (pollConfig) => {
      if (result.taskId) {
        const finalResult = await pollTaskUntilComplete(result.taskId, config, pollConfig);
        return parseStructuredTaskOutput(finalResult, schema);
      }
      if (result.recordingId) {
        const recording = await waitForRecording(
          result.recordingId,
          config,
          pollConfig
        );
        return {
          ...parsed,
          recordingStatus: recording.status
        };
      }
      throw new Error("Cannot poll completion: no taskId or recordingId available");
    },
    // Add Steel live session helpers - either functional or error-throwing
    getLiveUrl: debugUrl ? (options) => buildLiveUrl(debugUrl, options) : () => {
      throw new Error(
        "Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help enabling live sessions. "
      );
    },
    getLiveIframe: debugUrl ? (optionsOrPreset) => {
      const options = resolvePreset(optionsOrPreset);
      return buildLiveIframe(debugUrl, options);
    } : () => {
      throw new Error(
        "Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help enabling live sessions."
      );
    },
    getEmbedCode: debugUrl ? () => buildEmbedCode(debugUrl) : () => {
      throw new Error(
        "Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help enabling live sessions."
      );
    }
  };
  return wrapped;
}
async function getWebp(recordingId, config = {}, options = {}) {
  const apiUrl = config.apiUrl || DEFAULT_CONFIG.apiUrl;
  const debug = config.debug || false;
  if (!config.apiKey) {
    throw new Error("API key required for getWebp");
  }
  const params = new URLSearchParams();
  if (options.maxDuration !== void 0) params.set("max_duration", String(options.maxDuration));
  if (options.fps !== void 0) params.set("fps", String(options.fps));
  if (options.width !== void 0) params.set("width", String(options.width));
  if (options.quality !== void 0) params.set("quality", String(options.quality));
  if (options.maxSizeMb !== void 0) params.set("max_size_mb", String(options.maxSizeMb));
  const url = `${apiUrl}/recordings/${recordingId}/webp${params.toString() ? "?" + params.toString() : ""}`;
  if (debug) console.log(`[Browser] getWebp: ${url}`);
  const response = await fetch(url, {
    method: "GET",
    headers: { "Authorization": `Bearer ${config.apiKey}` }
  });
  if (!response.ok) {
    const errorText = await response.text().catch(() => response.statusText);
    if (debug) console.error(`[Browser] getWebp error: ${response.status} - ${errorText}`);
    throw new Error(`HTTP ${response.status}: ${errorText}`);
  }
  const result = mapWebpResponse(await response.json());
  if (debug) console.log(`[Browser] WebP ready: ${result.webpUrl} (cached: ${result.cached})`);
  return result;
}
async function checkHealth(config = {}) {
  const apiUrl = config.apiUrl || DEFAULT_CONFIG.apiUrl;
  try {
    const response = await fetch(`${apiUrl}/health`, {
      method: "GET",
      headers: config.apiKey ? { "Authorization": `Bearer ${config.apiKey}` } : {}
    });
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    const data = await response.json();
    return {
      ok: true,
      google_configured: data.google_configured ?? false,
      database_configured: data.database_configured ?? false,
      s3_configured: data.s3_configured ?? false
    };
  } catch (error) {
    return {
      ok: false,
      google_configured: false,
      database_configured: false,
      s3_configured: false,
      error: error instanceof Error ? error.message : String(error)
    };
  }
}

export {
  BrowserClient,
  executeBrowserTask,
  getRecording,
  waitForRecording,
  executeWithRecording,
  getErrors,
  getWebp,
  checkHealth
};
//# sourceMappingURL=chunk-RDMI5MI7.js.map