{"version":3,"file":"message-reconciliation.cjs","names":[],"sources":["../../src/stream/message-reconciliation.ts"],"sourcesContent":["import type { BaseMessage } from \"@langchain/core/messages\";\n\nexport interface ReconcileMessagesFromValuesOptions {\n  /**\n   * Messages from the authoritative `values.messages` snapshot.\n   */\n  readonly valueMessages: readonly BaseMessage[];\n  /**\n   * Current message projection, including stream-assembled in-flight messages.\n   */\n  readonly currentMessages: readonly BaseMessage[];\n  /**\n   * Index from message id to current message position.\n   */\n  readonly currentIndexById: ReadonlyMap<string, number>;\n  /**\n   * Ids observed in the most recent previous `values.messages` snapshot.\n   * If one of these ids is missing from the next snapshot, it is treated as\n   * an explicit server-side removal.\n   */\n  readonly previousValueMessageIds: ReadonlySet<string>;\n  /**\n   * Optional stream-id filter. When supplied, only these current ids are\n   * eligible to override the values snapshot. When omitted, any id present in\n   * `currentIndexById` is eligible, preserving the root controller's historic\n   * behavior.\n   */\n  readonly streamedMessageIds?: ReadonlySet<string>;\n  /**\n   * Allows callers to keep a values message even when a streamed message with\n   * the same id exists. Used by the root controller when the values message\n   * carries finalized tool-call data missing from the streamed message.\n   */\n  readonly preferValuesMessage?: (\n    valuesMessage: BaseMessage,\n    streamedMessage: BaseMessage\n  ) => boolean;\n}\n\nexport interface ReconciledMessages {\n  readonly messages: readonly BaseMessage[];\n  readonly valueMessageIds: Set<string>;\n}\n\n/**\n * Merge an authoritative `values.messages` snapshot with the current streamed\n * message projection.\n *\n * Values remain authoritative for ordering and removals. Streamed messages\n * remain authoritative for in-flight content until the server echoes them in a\n * values snapshot, and stream-only messages are preserved until they either\n * appear in values or are known to have been removed.\n */\nexport function reconcileMessagesFromValues({\n  valueMessages,\n  currentMessages,\n  currentIndexById,\n  previousValueMessageIds,\n  streamedMessageIds,\n  preferValuesMessage,\n}: ReconcileMessagesFromValuesOptions): ReconciledMessages {\n  const valueMessageIds = new Set<string>();\n  const merged: BaseMessage[] = [];\n\n  for (const valuesMessage of valueMessages) {\n    const id = normalizedMessageId(valuesMessage);\n    if (id == null) {\n      merged.push(valuesMessage);\n      continue;\n    }\n\n    valueMessageIds.add(id);\n    const streamIdx = currentIndexById.get(id);\n    const canUseStreamed =\n      streamIdx != null &&\n      (streamedMessageIds == null || streamedMessageIds.has(id));\n    const streamedMessage = canUseStreamed\n      ? currentMessages[streamIdx]\n      : undefined;\n\n    if (\n      streamedMessage != null &&\n      preferValuesMessage?.(valuesMessage, streamedMessage) !== true\n    ) {\n      merged.push(streamedMessage);\n    } else {\n      merged.push(valuesMessage);\n    }\n  }\n\n  for (const existing of currentMessages) {\n    const id = normalizedMessageId(existing);\n    if (id == null) continue;\n    if (valueMessageIds.has(id)) continue;\n    if (previousValueMessageIds.has(id)) continue;\n    if (streamedMessageIds != null && !streamedMessageIds.has(id)) continue;\n    merged.push(existing);\n  }\n\n  return {\n    messages: messagesEqualList(currentMessages, merged)\n      ? currentMessages\n      : merged,\n    valueMessageIds,\n  };\n}\n\n/**\n * Build a position index for keyed messages.\n */\nexport function buildMessageIndex(\n  messages: readonly BaseMessage[]\n): Map<string, number> {\n  const index = new Map<string, number>();\n  messages.forEach((message, idx) => {\n    const id = normalizedMessageId(message);\n    if (id != null) index.set(id, idx);\n  });\n  return index;\n}\n\n/**\n * Decide whether a values message carries tool-call data missing from the\n * streamed message.\n */\nexport function shouldPreferValuesMessageForToolCalls(\n  valuesMessage: BaseMessage,\n  streamedMessage: BaseMessage\n): boolean {\n  const valuesToolCalls = getMessageToolCalls(valuesMessage);\n  if (valuesToolCalls.length === 0) return false;\n\n  const streamedToolCalls = getMessageToolCalls(streamedMessage);\n  if (streamedToolCalls.length < valuesToolCalls.length) return true;\n\n  const streamedIds = new Set(\n    streamedToolCalls\n      .map((toolCall) => toolCall.id)\n      .filter((id): id is string => typeof id === \"string\" && id.length > 0)\n  );\n  if (\n    valuesToolCalls.some((toolCall) => {\n      return typeof toolCall.id === \"string\" && !streamedIds.has(toolCall.id);\n    })\n  ) {\n    return true;\n  }\n\n  // Values snapshots carry the finalized tool-call args. Prefer them only when\n  // they add meaningful data, so empty placeholder args do not replace an\n  // otherwise useful streamed message.\n  return valuesToolCalls.some((valuesToolCall) => {\n    const streamedToolCall = streamedToolCalls.find(\n      (candidate) =>\n        typeof valuesToolCall.id === \"string\" &&\n        candidate.id === valuesToolCall.id\n    );\n    return (\n      streamedToolCall != null &&\n      hasMeaningfulArgs(valuesToolCall.args) &&\n      !jsonishEqual(valuesToolCall.args, streamedToolCall.args)\n    );\n  });\n}\n\nfunction hasMeaningfulArgs(args: unknown): boolean {\n  if (args == null) return false;\n  if (typeof args === \"string\") return args.length > 0;\n  if (typeof args === \"object\") return Object.keys(args).length > 0;\n  return true;\n}\n\nexport function messagesEqualList(\n  previous: readonly BaseMessage[],\n  next: readonly BaseMessage[]\n): boolean {\n  if (previous === next) return true;\n  if (previous.length !== next.length) return false;\n  for (let i = 0; i < previous.length; i += 1) {\n    if (!messagesEqual(previous[i], next[i])) return false;\n  }\n  return true;\n}\n\nexport function messagesEqual(\n  previous: BaseMessage | undefined,\n  next: BaseMessage | undefined\n): boolean {\n  if (previous === next) return true;\n  if (previous == null || next == null) return false;\n  const previousRecord = previous as unknown as Record<string, unknown>;\n  const nextRecord = next as unknown as Record<string, unknown>;\n  const previousType =\n    typeof previous.getType === \"function\"\n      ? previous.getType()\n      : previousRecord.type;\n  const nextType =\n    typeof next.getType === \"function\" ? next.getType() : nextRecord.type;\n\n  return (\n    previous.id === next.id &&\n    previousType === nextType &&\n    jsonishEqual(previous.content, next.content) &&\n    previousRecord.tool_call_id === nextRecord.tool_call_id &&\n    previousRecord.status === nextRecord.status &&\n    jsonishEqual(\n      previousRecord.additional_kwargs,\n      nextRecord.additional_kwargs\n    ) &&\n    jsonishEqual(\n      previousRecord.response_metadata,\n      nextRecord.response_metadata\n    ) &&\n    jsonishEqual(previousRecord.tool_calls, nextRecord.tool_calls) &&\n    jsonishEqual(\n      previousRecord.tool_call_chunks,\n      nextRecord.tool_call_chunks\n    ) &&\n    jsonishEqual(previousRecord.usage_metadata, nextRecord.usage_metadata)\n  );\n}\n\nfunction normalizedMessageId(message: BaseMessage): string | undefined {\n  return typeof message.id === \"string\" && message.id.length > 0\n    ? message.id\n    : undefined;\n}\n\nfunction getMessageToolCalls(\n  message: BaseMessage\n): Array<{ id?: string; name?: string; args?: unknown }> {\n  const raw = (message as unknown as { tool_calls?: unknown }).tool_calls;\n  if (!Array.isArray(raw)) return [];\n  return raw.filter(\n    (toolCall): toolCall is { id?: string; name?: string; args?: unknown } =>\n      toolCall != null && typeof toolCall === \"object\"\n  );\n}\n\nfunction jsonishEqual(previous: unknown, next: unknown): boolean {\n  return jsonishEqualAtDepth(previous, next, 0);\n}\n\nfunction jsonishEqualAtDepth(\n  previous: unknown,\n  next: unknown,\n  depth: number\n): boolean {\n  if (Object.is(previous, next)) return true;\n  if (previous == null || next == null) return false;\n  if (typeof previous !== \"object\" || typeof next !== \"object\") return false;\n  if (depth >= 4) return false;\n\n  if (Array.isArray(previous) || Array.isArray(next)) {\n    if (!Array.isArray(previous) || !Array.isArray(next)) return false;\n    if (previous.length !== next.length) return false;\n    for (let i = 0; i < previous.length; i += 1) {\n      if (!jsonishEqualAtDepth(previous[i], next[i], depth + 1)) return false;\n    }\n    return true;\n  }\n\n  const previousRecord = previous as Record<string, unknown>;\n  const nextRecord = next as Record<string, unknown>;\n  const previousKeys = Object.keys(previousRecord).filter(\n    (key) => typeof previousRecord[key] !== \"function\"\n  );\n  const nextKeys = Object.keys(nextRecord).filter(\n    (key) => typeof nextRecord[key] !== \"function\"\n  );\n  if (previousKeys.length !== nextKeys.length) return false;\n\n  for (const key of previousKeys) {\n    if (!Object.prototype.hasOwnProperty.call(nextRecord, key)) return false;\n    if (!jsonishEqualAtDepth(previousRecord[key], nextRecord[key], depth + 1)) {\n      return false;\n    }\n  }\n  return true;\n}\n"],"mappings":"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"}