@langchain/langgraph-sdk
Version:
Client library for interacting with the LangGraph API
1 lines • 22.9 kB
Source Map (JSON)
{"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 data the current copy lacks (finalized tool calls, or\n * same-content metadata such as committed `additional_kwargs`).\n */\n readonly preferValuesMessage?: (\n valuesMessage: BaseMessage,\n streamedMessage: BaseMessage\n ) => boolean;\n /**\n * When true, treat the snapshot as a non-authoritative (older / replayed)\n * view: never drop a current message just because it is absent from this\n * snapshot. Used on reconnect, where the content pump replays older\n * checkpoints after the authoritative `getState()` seed — an older\n * snapshot legitimately lacks later messages and must not remove them.\n */\n readonly addOnly?: 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 that has moved past this\n * snapshot. When the server echoes a same-id message with equal content\n * plus enrichment (tool calls, `additional_kwargs`, …), values win.\n * Stream-only messages are preserved until they either appear in values\n * or are known to have been removed.\n */\nexport function reconcileMessagesFromValues({\n valueMessages,\n currentMessages,\n currentIndexById,\n previousValueMessageIds,\n streamedMessageIds,\n preferValuesMessage,\n addOnly,\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 // A previously-seen id missing from this snapshot is a server-side\n // removal — UNLESS this is an older/replayed snapshot (`addOnly`),\n // where the absence only means \"this earlier checkpoint predates the\n // message\", not \"the message was removed\".\n if (!addOnly && 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 should replace a same-id streamed or\n * optimistic message.\n *\n * Values win when they carry data the current copy lacks:\n * - finalized tool-call args (see\n * {@link shouldPreferValuesMessageForToolCalls})\n * - metadata enrichment or in-place nested metadata mutations with\n * unchanged content (e.g. server-committed `additional_kwargs`, or\n * `response_metadata.card.status` updating from `accepted` → `done`)\n *\n * Preferring is asymmetric: a lagging/poorer values snapshot must not\n * replace a richer current copy (seeded `getState()` metadata, messages-\n * channel `usage_metadata`, etc.) merely because the two messages differ.\n * Values that would drop nested metadata keys keep the current copy.\n *\n * In-place leaf mutations are only applied when\n * {@link ShouldPreferValuesMessageOptions.allowInPlaceMutations} is true\n * (the default). Callers pass `false` for stale/`addOnly` reconnect\n * snapshots so an older `accepted` card cannot overwrite a newer `done`.\n *\n * Streamed content still wins when it has moved past this snapshot, so\n * in-flight token streaming is not overwritten by a lagging values event.\n */\nexport interface ShouldPreferValuesMessageOptions {\n /**\n * When false, only prefer values for key-adding enrichment (or filling\n * empty metadata), not for same-key nested leaf mutations. Used for\n * lagging reconnect/`addOnly` snapshots.\n *\n * @default true\n */\n readonly allowInPlaceMutations?: boolean;\n}\n\nexport function shouldPreferValuesMessage(\n valuesMessage: BaseMessage,\n streamedMessage: BaseMessage,\n options?: ShouldPreferValuesMessageOptions\n): boolean {\n if (shouldPreferValuesMessageForToolCalls(valuesMessage, streamedMessage)) {\n return true;\n }\n if (!jsonishEqual(valuesMessage.content, streamedMessage.content)) {\n return false;\n }\n return valuesAddsMetadataEnrichment(valuesMessage, streamedMessage, options);\n}\n\n/** Metadata fields that a values echo may enrich on a same-id message. */\nconst METADATA_ENRICHMENT_KEYS = [\n \"additional_kwargs\",\n \"response_metadata\",\n \"usage_metadata\",\n] as const;\n\n/**\n * True when values adds or mutates metadata without stripping richer\n * fields already present on the current copy.\n *\n * Covers both key additions (optimistic human gaining\n * `additional_kwargs.attachments`) and — when\n * `allowInPlaceMutations` is true — in-place nested mutations of\n * existing keys (HITL card `status: accepted → done`) as long as every\n * nested key on the current field is retained.\n */\nfunction valuesAddsMetadataEnrichment(\n valuesMessage: BaseMessage,\n streamedMessage: BaseMessage,\n options?: ShouldPreferValuesMessageOptions\n): boolean {\n const allowInPlaceMutations = options?.allowInPlaceMutations !== false;\n const valuesRecord = valuesMessage as unknown as Record<string, unknown>;\n const streamedRecord = streamedMessage as unknown as Record<string, unknown>;\n\n let enriches = false;\n for (const key of METADATA_ENRICHMENT_KEYS) {\n const valuesField = valuesRecord[key];\n const streamedField = streamedRecord[key];\n if (jsonishEqual(valuesField, streamedField)) continue;\n\n const valuesEmpty = isEmptyMeta(valuesField);\n const streamedEmpty = isEmptyMeta(streamedField);\n\n if (valuesEmpty && !streamedEmpty) {\n // Preferring values would erase richer current metadata.\n return false;\n }\n if (!valuesEmpty && streamedEmpty) {\n enriches = true;\n continue;\n }\n // Both non-empty and differ.\n if (allowInPlaceMutations) {\n // Prefer values when it retains every nested key (leaf mutations\n // and added keys allowed). Dropping keys would erase richer\n // current metadata — keep the current copy.\n if (retainsAllNestedKeys(valuesField, streamedField)) {\n enriches = true;\n continue;\n }\n return false;\n }\n // Stale/addOnly snapshots: only treat as enrichment when values is\n // a shallow key-superset with equal existing values. Same-key leaf\n // mutations must not roll current metadata backward.\n if (isObjectShallowSuperset(valuesField, streamedField)) {\n enriches = true;\n continue;\n }\n return false;\n }\n return enriches;\n}\n\nfunction isEmptyMeta(value: unknown): boolean {\n if (value == null) return true;\n if (typeof value === \"string\") return value.length === 0;\n if (Array.isArray(value)) return value.length === 0;\n if (typeof value === \"object\") {\n return Object.keys(value as Record<string, unknown>).length === 0;\n }\n return false;\n}\n\n/**\n * True when `candidate` is a shallow object superset of `baseline`:\n * every baseline key is present with an equal value, and candidate has\n * at least one extra key.\n */\nfunction isObjectShallowSuperset(\n candidate: unknown,\n baseline: unknown\n): boolean {\n if (\n candidate == null ||\n baseline == null ||\n typeof candidate !== \"object\" ||\n typeof baseline !== \"object\" ||\n Array.isArray(candidate) ||\n Array.isArray(baseline)\n ) {\n return false;\n }\n const candidateRecord = candidate as Record<string, unknown>;\n const baselineRecord = baseline as Record<string, unknown>;\n const baselineKeys = Object.keys(baselineRecord);\n const candidateKeys = Object.keys(candidateRecord);\n if (candidateKeys.length <= baselineKeys.length) return false;\n for (const key of baselineKeys) {\n if (!Object.prototype.hasOwnProperty.call(candidateRecord, key)) {\n return false;\n }\n if (!jsonishEqual(candidateRecord[key], baselineRecord[key])) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * True when `candidate` retains every nested key present on `baseline`.\n * Nested leaf values may differ (in-place mutations); missing keys mean\n * preferring candidate would erase richer current metadata.\n */\nfunction retainsAllNestedKeys(candidate: unknown, baseline: unknown): boolean {\n if (baseline == null || typeof baseline !== \"object\") {\n // Leaf baseline — candidate may replace the value in place.\n return true;\n }\n if (\n candidate == null ||\n typeof candidate !== \"object\" ||\n Array.isArray(baseline) !== Array.isArray(candidate)\n ) {\n return false;\n }\n\n if (Array.isArray(baseline)) {\n const candidateArr = candidate as unknown[];\n const baselineArr = baseline as unknown[];\n if (candidateArr.length < baselineArr.length) return false;\n for (let i = 0; i < baselineArr.length; i += 1) {\n if (!retainsAllNestedKeys(candidateArr[i], baselineArr[i])) {\n return false;\n }\n }\n return true;\n }\n\n const candidateRecord = candidate as Record<string, unknown>;\n const baselineRecord = baseline as Record<string, unknown>;\n for (const key of Object.keys(baselineRecord)) {\n if (!Object.prototype.hasOwnProperty.call(candidateRecord, key)) {\n return false;\n }\n if (!retainsAllNestedKeys(candidateRecord[key], baselineRecord[key])) {\n return false;\n }\n }\n return true;\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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"}