// src/message-parts/index.ts
var LCM_MESSAGE_PART_KINDS = [
  "text",
  "tool_call",
  "tool_result",
  "patch",
  "file_read",
  "file_write",
  "step_start",
  "step_finish",
  "snapshot",
  "retry"
];
var SECRET_KEY_RE = /(api[_-]?key|authorization|bearer|credential|password|secret|token)/i;
var MAX_PAYLOAD_STRING = 8e3;
var MAX_FILE_SCAN_CHARS = 2e4;
function isLcmMessagePartKind(value) {
  return typeof value === "string" && LCM_MESSAGE_PART_KINDS.includes(value);
}
function parseMessageParts(input, options = {}) {
  const explicit = normalizeExplicitParts(input);
  if (explicit.length > 0) return explicit;
  const format = options.sourceFormat ?? inferSourceFormat(input);
  switch (format) {
    case "openai":
      return withRenderedFallback(parseOpenAiMessageParts(input, options), options);
    case "anthropic":
      return withRenderedFallback(parseAnthropicMessageParts(input, options), options);
    case "openclaw":
      return withRenderedFallback(parseOpenClawMessageParts(input, options), options);
    case "lossless-claw":
    case "remnic":
      return withRenderedFallback(normalizeExplicitParts(input), options);
    default:
      return renderedFallbackParts(options);
  }
}
function normalizeExplicitParts(input) {
  const rawParts = pickArray(input, "parts") ?? pickArray(input, "message_parts");
  if (!rawParts) return [];
  const parts = [];
  rawParts.forEach((raw, index) => {
    if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
    const obj = raw;
    const kind = normalizeKind(obj.kind ?? obj.type);
    if (!kind) return;
    const payload = obj.payload && typeof obj.payload === "object" && !Array.isArray(obj.payload) ? obj.payload : { value: sanitizePayload(obj) };
    const toolName = asNonEmptyString(obj.toolName ?? obj.tool_name ?? obj.name);
    const filePath = asNonEmptyString(obj.filePath ?? obj.file_path ?? obj.path);
    const ordinal = typeof obj.ordinal === "number" && Number.isInteger(obj.ordinal) ? Math.max(0, obj.ordinal) : index;
    parts.push({
      ordinal,
      kind,
      payload: sanitizePayload(payload),
      toolName,
      filePath,
      createdAt: asNonEmptyString(obj.createdAt ?? obj.created_at)
    });
  });
  return parts;
}
function parseOpenAiMessageParts(input, _options = {}) {
  const items = gatherOpenAiItems(input);
  const parts = [];
  for (const item of items) {
    const type = asNonEmptyString(item.type) ?? asNonEmptyString(item.kind);
    if (!type) continue;
    if (isOpenAiContentBlock(item)) {
      const text = asNonEmptyString(item.text ?? item.content);
      if (text) parts.push(makePart("text", { type, text }, { filePath: firstFilePath(text) }));
      continue;
    }
    if (type === "message") {
      for (const block of gatherContentBlocks(item.content)) {
        const text = asNonEmptyString(block.text ?? block.content);
        if (text) parts.push(makePart("text", { type, text }, { filePath: firstFilePath(text) }));
      }
      continue;
    }
    if (type === "function_call") {
      const toolName = asNonEmptyString(item.name ?? item.tool_name);
      const callId = asNonEmptyString(item.call_id ?? item.callId);
      const itemId = asNonEmptyString(item.id);
      const payload = {
        id: callId ?? itemId,
        ...callId ? { call_id: callId } : {},
        ...itemId && itemId !== callId ? { response_item_id: itemId } : {},
        name: toolName,
        arguments: parseMaybeJson(item.arguments)
      };
      parts.push(classifyToolPart(toolName, payload));
      continue;
    }
    if (type === "function_call_output") {
      const output = asNonEmptyString(item.output) ?? JSON.stringify(sanitizePayload(item.output ?? item));
      const callId = asNonEmptyString(item.call_id ?? item.callId);
      const itemId = asNonEmptyString(item.id);
      parts.push(makePart("tool_result", {
        id: callId ?? itemId,
        ...callId ? { call_id: callId } : {},
        ...itemId && itemId !== callId ? { response_item_id: itemId } : {},
        output
      }, {
        filePath: firstFilePath(output)
      }));
      continue;
    }
    if (type === "reasoning") {
      parts.push(makePart("step_start", { type, summary: sanitizePayload(item.summary ?? item) }));
      continue;
    }
    if (type === "retry") {
      parts.push(makePart("retry", { type, item: sanitizePayload(item) }));
    }
  }
  return withOrdinals(parts);
}
function parseAnthropicMessageParts(input, _options = {}) {
  const blocks = gatherContentBlocks(
    Array.isArray(input) ? input : input && typeof input === "object" ? input.content : input
  );
  const parts = [];
  for (const block of blocks) {
    const type = asNonEmptyString(block.type);
    if (type === "text") {
      const text = asNonEmptyString(block.text);
      if (text) parts.push(makePart("text", { type, text }, { filePath: firstFilePath(text) }));
      continue;
    }
    if (type === "tool_use") {
      const toolName = asNonEmptyString(block.name);
      parts.push(classifyToolPart(toolName, {
        id: block.id,
        name: toolName,
        input: sanitizePayload(block.input)
      }));
      continue;
    }
    if (type === "tool_result") {
      const content = block.content;
      const rendered = renderUnknownContent(content);
      parts.push(makePart("tool_result", {
        id: block.tool_use_id,
        content: sanitizePayload(content),
        ...typeof block.is_error === "boolean" ? { is_error: block.is_error } : {},
        ...typeof block.isError === "boolean" ? { isError: block.isError } : {}
      }, {
        filePath: firstFilePath(rendered)
      }));
      continue;
    }
    if (type === "thinking") {
      parts.push(makePart("step_start", {
        type,
        thinking: truncateString(asNonEmptyString(block.thinking) ?? ""),
        signature: asNonEmptyString(block.signature)
      }));
      continue;
    }
    if (type === "redacted_thinking") {
      parts.push(makePart("step_finish", { type }));
    }
  }
  return withOrdinals(parts);
}
function parseOpenClawMessageParts(input, options = {}) {
  const explicit = normalizeExplicitParts(input);
  if (explicit.length > 0) return explicit;
  if (!input || typeof input !== "object") return [];
  const obj = input;
  const content = obj.content;
  if (Array.isArray(content)) {
    const hasOpenAiBlocks = content.some(isOpenAiContentBlock);
    if (hasOpenAiBlocks) return parseOpenAiMessageParts(content, options);
    const hasAnthropicBlocks = content.some(
      (block) => block && typeof block === "object" && typeof block.type === "string"
    );
    if (hasAnthropicBlocks) return parseAnthropicMessageParts({ content }, options);
  }
  const toolName = asNonEmptyString(obj.toolName ?? obj.tool_name ?? obj.name);
  if (toolName) {
    return withOrdinals([
      classifyToolPart(toolName, {
        name: toolName,
        input: sanitizePayload(obj.input ?? obj.arguments ?? obj.params),
        output: sanitizePayload(obj.output ?? obj.result)
      })
    ]);
  }
  const rendered = options.allowRenderedFallback === false ? null : options.renderedContent ?? asNonEmptyString(obj.content);
  return rendered ? withOrdinals(partsFromRenderedText(rendered)) : [];
}
function partsFromRenderedText(text) {
  if (text.includes("*** Begin Patch")) {
    const paths2 = extractFilePaths(text);
    const patchPaths = extractPatchPaths(text);
    return withOrdinals((patchPaths.length > 0 ? patchPaths : paths2).map(
      (filePath) => makePart("patch", { text: truncateString(text) }, { filePath })
    ));
  }
  const paths = extractFilePaths(text);
  if (paths.length === 0) return [];
  return withOrdinals(paths.map(
    (filePath) => makePart("file_read", { text: truncateString(text) }, { filePath })
  ));
}
function inferSourceFormat(input) {
  if (input && typeof input === "object") {
    const obj = input;
    const explicit = asNonEmptyString(obj.sourceFormat ?? obj.source_format);
    if (explicit === "openai" || explicit === "anthropic" || explicit === "openclaw" || explicit === "lossless-claw" || explicit === "remnic") {
      return explicit;
    }
    if (Array.isArray(obj.output)) return "openai";
    if (isOpenAiResponseItem(obj)) return "openai";
    if (Array.isArray(obj.content) && obj.content.some(isOpenAiContentBlock)) return "openai";
    if (Array.isArray(obj.content)) return "anthropic";
  }
  if (Array.isArray(input)) {
    return input.some(
      (item) => isRecord(item) && (isOpenAiResponseItem(item) || isOpenAiContentBlock(item))
    ) ? "openai" : "anthropic";
  }
  return void 0;
}
function isOpenAiResponseItem(obj) {
  const type = asNonEmptyString(obj.type ?? obj.kind);
  return type === "message" || type === "function_call" || type === "function_call_output" || type === "reasoning" || type === "retry";
}
function isOpenAiContentBlock(value) {
  if (!isRecord(value)) return false;
  const type = asNonEmptyString(value.type);
  return type === "input_text" || type === "output_text" || type === "input_image" || type === "input_file" || type === "refusal";
}
function gatherOpenAiItems(input) {
  if (Array.isArray(input)) return input.filter(isRecord);
  if (!isRecord(input)) return [];
  if (Array.isArray(input.output)) return input.output.filter(isRecord);
  if (Array.isArray(input.items)) return input.items.filter(isRecord);
  return [input];
}
function gatherContentBlocks(input) {
  if (Array.isArray(input)) return input.filter(isRecord);
  if (typeof input === "string") return [{ type: "text", text: input }];
  if (isRecord(input)) return [input];
  return [];
}
function classifyToolPart(toolName, payload) {
  const normalized = (toolName ?? "").toLowerCase();
  const rendered = renderUnknownContent(payload);
  const filePath = firstFilePathFromObject(payload) ?? firstFilePath(rendered) ?? null;
  if (normalized.includes("apply_patch") || rendered.includes("*** Begin Patch")) {
    return makePart("patch", payload, { toolName, filePath: filePath ?? extractPatchPaths(rendered)[0] ?? null });
  }
  if (/(write|edit|multiedit|create|save)/i.test(normalized)) {
    return makePart("file_write", payload, { toolName, filePath });
  }
  if (/(read|grep|glob|search|list|ls)/i.test(normalized)) {
    return makePart("file_read", payload, { toolName, filePath });
  }
  return makePart("tool_call", payload, { toolName, filePath });
}
function makePart(kind, payload, options = {}) {
  return {
    kind,
    payload: sanitizePayload(payload),
    toolName: options.toolName ?? null,
    filePath: options.filePath ?? null
  };
}
function withOrdinals(parts) {
  return parts.map((part, ordinal) => ({ ...part, ordinal: part.ordinal ?? ordinal }));
}
function withRenderedFallback(parts, options) {
  return parts.length > 0 ? parts : renderedFallbackParts(options);
}
function renderedFallbackParts(options) {
  if (options.allowRenderedFallback === false) {
    return [];
  }
  const rendered = asNonEmptyString(options.renderedContent);
  return rendered ? partsFromRenderedText(rendered) : [];
}
function normalizeKind(value) {
  if (isLcmMessagePartKind(value)) return value;
  if (value === "tool_use" || value === "function_call") return "tool_call";
  if (value === "function_call_output") return "tool_result";
  if (value === "thinking" || value === "reasoning") return "step_start";
  return null;
}
function pickArray(input, key) {
  if (!input || typeof input !== "object" || Array.isArray(input)) return null;
  const value = input[key];
  return Array.isArray(value) ? value : null;
}
function asNonEmptyString(value) {
  if (typeof value !== "string") return null;
  const trimmed = value.trim();
  return trimmed.length > 0 ? trimmed : null;
}
function isRecord(value) {
  return !!value && typeof value === "object" && !Array.isArray(value);
}
function parseMaybeJson(value) {
  if (typeof value !== "string") return sanitizePayload(value);
  try {
    return sanitizePayload(JSON.parse(value));
  } catch {
    return truncateString(value);
  }
}
function sanitizePayload(value, depth = 0) {
  if (value === null || value === void 0) return value;
  if (typeof value === "string") return truncateString(value);
  if (typeof value === "number" || typeof value === "boolean") return value;
  if (Array.isArray(value)) {
    if (depth >= 4) return "[truncated]";
    return value.slice(0, 100).map((item) => sanitizePayload(item, depth + 1));
  }
  if (typeof value === "object") {
    if (depth >= 4) return "[truncated]";
    const out = {};
    for (const [key, child] of Object.entries(value)) {
      out[key] = SECRET_KEY_RE.test(key) ? "[redacted]" : sanitizePayload(child, depth + 1);
    }
    return out;
  }
  return String(value);
}
function truncateString(value) {
  return value.length > MAX_PAYLOAD_STRING ? `${value.slice(0, MAX_PAYLOAD_STRING)}...[truncated]` : value;
}
function renderUnknownContent(value) {
  if (typeof value === "string") return value;
  try {
    return JSON.stringify(value ?? "");
  } catch {
    return String(value ?? "");
  }
}
function firstFilePathFromObject(value) {
  if (!isRecord(value)) return null;
  const keys = ["file_path", "filePath", "path", "filename", "cwd"];
  for (const key of keys) {
    const candidate = asNonEmptyString(value[key]);
    if (candidate) return candidate;
  }
  for (const child of Object.values(value)) {
    if (typeof child === "string") {
      const fromText = extractPatchPaths(child)[0] ?? firstFilePath(child);
      if (fromText) return fromText;
    }
    if (isRecord(child)) {
      const nested = firstFilePathFromObject(child);
      if (nested) return nested;
    }
  }
  return null;
}
function firstFilePath(text) {
  return extractFilePaths(text)[0] ?? null;
}
function extractFilePaths(text) {
  const out = /* @__PURE__ */ new Set();
  let token = "";
  const scanLength = Math.min(text.length, MAX_FILE_SCAN_CHARS);
  for (let index = 0; index <= scanLength; index += 1) {
    const char = index < scanLength ? text[index] : " ";
    if (isFilePathTokenSeparator(char)) {
      addFilePathCandidate(out, token);
      token = "";
      continue;
    }
    token += char;
    if (token.length > 512) {
      addFilePathCandidate(out, token);
      token = "";
    }
  }
  return [...out].slice(0, 20);
}
function isFilePathTokenSeparator(char) {
  return char === " " || char === "\n" || char === "\r" || char === "	" || char === '"' || char === "'" || char === "`" || char === "(" || char === ")" || char === "[" || char === "]" || char === "{" || char === "}" || char === "<" || char === ">" || char === ",";
}
function addFilePathCandidate(out, raw) {
  const candidate = trimFilePathPunctuation(raw);
  if (candidate.length === 0 || candidate.includes("://")) return;
  if (isLikelyFilePath(candidate)) out.add(candidate);
}
function trimFilePathPunctuation(raw) {
  let start = 0;
  let end = raw.length;
  while (start < end && isLeadingFilePathPunctuation(raw[start])) start += 1;
  while (end > start && isTrailingFilePathPunctuation(raw[end - 1])) end -= 1;
  return raw.slice(start, end);
}
function isLeadingFilePathPunctuation(char) {
  return char === ":" || char === ";" || char === "!" || char === "?" || char === "|" || char === "*" || char === "=";
}
function isTrailingFilePathPunctuation(char) {
  return char === "." || char === ":" || char === ";" || char === "!" || char === "?" || char === "|" || char === "*" || char === "=";
}
function isLikelyFilePath(value) {
  if (value.startsWith("/") || value.startsWith("./") || value.startsWith("../") || value.startsWith("~/")) {
    return hasValidFileExtension(value);
  }
  if (value.includes("/")) return hasValidFileExtension(value);
  return hasValidFileExtension(value);
}
function hasValidFileExtension(value) {
  const lastSlash = value.lastIndexOf("/");
  const basename = value.slice(lastSlash + 1);
  const dot = basename.lastIndexOf(".");
  if (dot <= 0 || dot === basename.length - 1) return false;
  const ext = basename.slice(dot + 1);
  if (ext.length < 1 || ext.length > 12) return false;
  for (const char of ext) {
    if (!isFileExtensionChar(char)) return false;
  }
  return true;
}
function isFileExtensionChar(char) {
  const code = char.charCodeAt(0);
  return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || char === "_" || char === "+" || char === "-";
}
function extractPatchPaths(text) {
  const out = /* @__PURE__ */ new Set();
  for (const line of text.split(/\r?\n/)) {
    const match = line.match(/^\*\*\* (?:Add|Update|Delete) File: (.+)$/);
    if (match?.[1]) out.add(match[1].trim());
    const move = line.match(/^\*\*\* Move to: (.+)$/);
    if (move?.[1]) out.add(move[1].trim());
  }
  return [...out].slice(0, 20);
}

export {
  isLcmMessagePartKind,
  parseMessageParts,
  normalizeExplicitParts,
  parseOpenAiMessageParts,
  parseAnthropicMessageParts,
  parseOpenClawMessageParts,
  partsFromRenderedText
};
//# sourceMappingURL=chunk-IBX3VFOM.js.map