@athenaintel/react 0.32.0 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +148 -9
- package/dist/agent-runtime/src/composer-markdown.cjs.map +1 -1
- package/dist/agent-runtime/src/composer-markdown.js.map +1 -1
- package/dist/agent-runtime/src/deep-agent-transport-recipe.cjs +2 -3
- package/dist/agent-runtime/src/deep-agent-transport-recipe.cjs.map +1 -1
- package/dist/agent-runtime/src/deep-agent-transport-recipe.js +3 -4
- package/dist/agent-runtime/src/deep-agent-transport-recipe.js.map +1 -1
- package/dist/agent-runtime/src/edit-command.cjs.map +1 -1
- package/dist/agent-runtime/src/edit-command.js.map +1 -1
- package/dist/agent-runtime/src/generated/runconfig-contract.cjs +4 -0
- package/dist/agent-runtime/src/generated/runconfig-contract.cjs.map +1 -1
- package/dist/agent-runtime/src/generated/runconfig-contract.js +4 -0
- package/dist/agent-runtime/src/generated/runconfig-contract.js.map +1 -1
- package/dist/agent-runtime/src/messages.cjs +57 -2
- package/dist/agent-runtime/src/messages.cjs.map +1 -1
- package/dist/agent-runtime/src/messages.js +57 -2
- package/dist/agent-runtime/src/messages.js.map +1 -1
- package/dist/agent-runtime/src/quota-safe-local-storage-session.cjs +24 -3
- package/dist/agent-runtime/src/quota-safe-local-storage-session.cjs.map +1 -1
- package/dist/agent-runtime/src/quota-safe-local-storage-session.js +24 -3
- package/dist/agent-runtime/src/quota-safe-local-storage-session.js.map +1 -1
- package/dist/agent-runtime/src/statewire-transport.cjs +4 -13
- package/dist/agent-runtime/src/statewire-transport.cjs.map +1 -1
- package/dist/agent-runtime/src/statewire-transport.js +5 -14
- package/dist/agent-runtime/src/statewire-transport.js.map +1 -1
- package/dist/agent-runtime/src/tool-metadata.cjs +12 -1
- package/dist/agent-runtime/src/tool-metadata.cjs.map +1 -1
- package/dist/agent-runtime/src/tool-metadata.js +12 -1
- package/dist/agent-runtime/src/tool-metadata.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-thread.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-thread.js.map +1 -1
- package/dist/assets/AthenaAssetEmbed.cjs +3 -1
- package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
- package/dist/assets/AthenaAssetEmbed.d.ts +7 -0
- package/dist/assets/AthenaAssetEmbed.js +4 -2
- package/dist/assets/AthenaAssetEmbed.js.map +1 -1
- package/dist/assets/use-asset-embed.cjs +71 -71
- package/dist/assets/use-asset-embed.cjs.map +1 -1
- package/dist/assets/use-asset-embed.d.ts +20 -7
- package/dist/assets/use-asset-embed.js +72 -72
- package/dist/assets/use-asset-embed.js.map +1 -1
- package/dist/catalog/agent-mode-menu.cjs +115 -0
- package/dist/catalog/agent-mode-menu.cjs.map +1 -0
- package/dist/catalog/agent-mode-menu.d.ts +101 -0
- package/dist/catalog/agent-mode-menu.js +115 -0
- package/dist/catalog/agent-mode-menu.js.map +1 -0
- package/dist/catalog/agent-model-options.cjs +122 -0
- package/dist/catalog/agent-model-options.cjs.map +1 -0
- package/dist/catalog/agent-model-options.d.ts +115 -0
- package/dist/catalog/agent-model-options.js +122 -0
- package/dist/catalog/agent-model-options.js.map +1 -0
- package/dist/catalog/chat-pickers.cjs +235 -4
- package/dist/catalog/chat-pickers.cjs.map +1 -1
- package/dist/catalog/chat-pickers.d.ts +55 -3
- package/dist/catalog/chat-pickers.js +238 -7
- package/dist/catalog/chat-pickers.js.map +1 -1
- package/dist/catalog/runtime-catalog.cjs +121 -3
- package/dist/catalog/runtime-catalog.cjs.map +1 -1
- package/dist/catalog/runtime-catalog.d.ts +120 -0
- package/dist/catalog/runtime-catalog.js +124 -6
- package/dist/catalog/runtime-catalog.js.map +1 -1
- package/dist/catalog/use-athena-chat-mode.cjs +139 -0
- package/dist/catalog/use-athena-chat-mode.cjs.map +1 -0
- package/dist/catalog/use-athena-chat-mode.d.ts +43 -0
- package/dist/catalog/use-athena-chat-mode.js +139 -0
- package/dist/catalog/use-athena-chat-mode.js.map +1 -0
- package/dist/chat/AthenaChat.cjs +2 -0
- package/dist/chat/AthenaChat.cjs.map +1 -1
- package/dist/chat/AthenaChat.d.ts +8 -7
- package/dist/chat/AthenaChat.js +2 -0
- package/dist/chat/AthenaChat.js.map +1 -1
- package/dist/chat/super-grouping/SuperGroupingCard.cjs +30 -3
- package/dist/chat/super-grouping/SuperGroupingCard.cjs.map +1 -1
- package/dist/chat/super-grouping/SuperGroupingCard.d.ts +12 -9
- package/dist/chat/super-grouping/SuperGroupingCard.js +31 -4
- package/dist/chat/super-grouping/SuperGroupingCard.js.map +1 -1
- package/dist/chat/super-grouping/SuperGroupingTranscript.cjs.map +1 -1
- package/dist/chat/super-grouping/SuperGroupingTranscript.d.ts +8 -8
- package/dist/chat/super-grouping/SuperGroupingTranscript.js.map +1 -1
- package/dist/chat/super-grouping/activity-timeline.cjs.map +1 -1
- package/dist/chat/super-grouping/activity-timeline.d.ts +4 -4
- package/dist/chat/super-grouping/activity-timeline.js.map +1 -1
- package/dist/chat/super-grouping/index.d.ts +6 -5
- package/dist/chat/super-grouping/toolkit-map.cjs +3 -1
- package/dist/chat/super-grouping/toolkit-map.cjs.map +1 -1
- package/dist/chat/super-grouping/toolkit-map.js +3 -1
- package/dist/chat/super-grouping/toolkit-map.js.map +1 -1
- package/dist/chat-ui/index.cjs +1 -0
- package/dist/chat-ui/index.cjs.map +1 -1
- package/dist/chat-ui/index.d.ts +1 -1
- package/dist/chat-ui/index.js +2 -1
- package/dist/chat-ui/mentions/suggestions/data/api-client.cjs +33 -2
- package/dist/chat-ui/mentions/suggestions/data/api-client.cjs.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/api-client.d.ts +14 -0
- package/dist/chat-ui/mentions/suggestions/data/api-client.js +33 -2
- package/dist/chat-ui/mentions/suggestions/data/api-client.js.map +1 -1
- package/dist/chat-ui/statewire.cjs +3 -0
- package/dist/chat-ui/statewire.cjs.map +1 -1
- package/dist/chat-ui/statewire.d.ts +4 -2
- package/dist/chat-ui/statewire.js +3 -0
- package/dist/chat-ui/statewire.js.map +1 -1
- package/dist/index.cjs +4 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/use-athena-rest-query.cjs +4 -0
- package/dist/lib/use-athena-rest-query.cjs.map +1 -1
- package/dist/lib/use-athena-rest-query.d.ts +6 -0
- package/dist/lib/use-athena-rest-query.js +4 -0
- package/dist/lib/use-athena-rest-query.js.map +1 -1
- package/dist/provider/AthenaProvider.cjs +81 -9
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.d.ts +25 -5
- package/dist/provider/AthenaProvider.js +83 -11
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/provider/agent-selection-context.cjs +6 -0
- package/dist/provider/agent-selection-context.cjs.map +1 -0
- package/dist/provider/agent-selection-context.d.ts +14 -0
- package/dist/provider/agent-selection-context.js +6 -0
- package/dist/provider/agent-selection-context.js.map +1 -0
- package/dist/provider/chat-mode-context.cjs +6 -0
- package/dist/provider/chat-mode-context.cjs.map +1 -0
- package/dist/provider/chat-mode-context.d.ts +35 -0
- package/dist/provider/chat-mode-context.js +6 -0
- package/dist/provider/chat-mode-context.js.map +1 -0
- package/dist/react/package.json.cjs +1 -1
- package/dist/react/package.json.js +1 -1
- package/dist/runtime/StatewireThreadConfigLaneSync.cjs +125 -0
- package/dist/runtime/StatewireThreadConfigLaneSync.cjs.map +1 -0
- package/dist/runtime/StatewireThreadConfigLaneSync.d.ts +54 -0
- package/dist/runtime/StatewireThreadConfigLaneSync.js +125 -0
- package/dist/runtime/StatewireThreadConfigLaneSync.js.map +1 -0
- package/dist/runtime/statewire-model-lane.cjs +0 -12
- package/dist/runtime/statewire-model-lane.cjs.map +1 -1
- package/dist/runtime/statewire-model-lane.d.ts +3 -1
- package/dist/runtime/statewire-model-lane.js +0 -12
- package/dist/runtime/statewire-model-lane.js.map +1 -1
- package/dist/runtime/statewire-run-config.cjs +10 -6
- package/dist/runtime/statewire-run-config.cjs.map +1 -1
- package/dist/runtime/statewire-run-config.d.ts +7 -0
- package/dist/runtime/statewire-run-config.js +10 -6
- package/dist/runtime/statewire-run-config.js.map +1 -1
- package/dist/runtime/statewire-server-lane-context.cjs.map +1 -1
- package/dist/runtime/statewire-server-lane-context.d.ts +2 -2
- package/dist/runtime/statewire-server-lane-context.js.map +1 -1
- package/dist/runtime/statewire-thread-config-lane.cjs +84 -0
- package/dist/runtime/statewire-thread-config-lane.cjs.map +1 -0
- package/dist/runtime/statewire-thread-config-lane.d.ts +94 -0
- package/dist/runtime/statewire-thread-config-lane.js +84 -0
- package/dist/runtime/statewire-thread-config-lane.js.map +1 -0
- package/dist/runtime/useAthenaStatewireRuntime.cjs +4 -2
- package/dist/runtime/useAthenaStatewireRuntime.cjs.map +1 -1
- package/dist/runtime/useAthenaStatewireRuntime.d.ts +2 -0
- package/dist/runtime/useAthenaStatewireRuntime.js +4 -2
- package/dist/runtime/useAthenaStatewireRuntime.js.map +1 -1
- package/dist/threads/use-athena-thread-model.cjs +11 -3
- package/dist/threads/use-athena-thread-model.cjs.map +1 -1
- package/dist/threads/use-athena-thread-model.d.ts +5 -0
- package/dist/threads/use-athena-thread-model.js +12 -4
- package/dist/threads/use-athena-thread-model.js.map +1 -1
- package/package.json +3 -3
- package/dist/runtime/StatewireModelLaneSync.cjs +0 -96
- package/dist/runtime/StatewireModelLaneSync.cjs.map +0 -1
- package/dist/runtime/StatewireModelLaneSync.d.ts +0 -45
- package/dist/runtime/StatewireModelLaneSync.js +0 -96
- package/dist/runtime/StatewireModelLaneSync.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.js","sources":["../../../../agent-runtime/src/messages.ts"],"sourcesContent":["import { getExternalStoreMessages, type ThreadMessage } from '@assistant-ui/core';\nimport {\n convertExternalMessages,\n createExternalMessageConversionCache,\n createMessageConverter,\n type useExternalMessageConverter,\n} from '@assistant-ui/core/react';\nimport { convertLangChainMessages, type LangChainMessage } from '@assistant-ui/react-langgraph';\nimport { runsMaterialize, type StatewireThread } from '@assistant-ui/react-statewire';\nimport {\n ATHENA_MESSAGE_AUTHOR_KEY,\n type DeepAgentMessageAuthor,\n laneEntryAuthor,\n} from './message-author';\nimport { DEEP_AGENT_COMPOSER_MARKDOWN_KEY } from './composer-markdown';\nimport { DEEP_AGENT_TRUNCATED_CONTENT_KEY } from './generated/runconfig-contract';\nimport {\n ATHENA_TRUNCATED_TOOL_OUTPUT_ARTIFACT_KEY,\n readTruncatedToolContent,\n} from './message-window';\nimport { buildOptimisticHumanContent, type OptimisticContentPart } from './optimistic-content';\nimport { readDeepAgentStreamValues } from './statewire-state';\nimport type { DeepAgentToolOutputStore } from './tool-outputs';\nimport { groupSpokenUserTurns } from './voice-turn-grouping';\nimport type { DeepAgentWithdrawnTurns } from './withdrawn-turns';\nimport type {\n DeepAgentLaneProjection,\n DeepAgentStreamValues,\n LangGraphState,\n} from './types';\n\n/**\n * Native message converter for the deep-agent runtime.\n *\n * This is the entire \"conversion\" layer: assistant-ui's shipped\n * `convertLangChainMessages` (from `@assistant-ui/react-langgraph`) wired\n * through `createMessageConverter`. No bespoke per-message mapping — the agora\n * `athena_deep_agent` graph streams standard LangChain messages via\n * `append_langgraph_event`, and the shipped converter renders them.\n */\nexport const messageConverter = createMessageConverter(convertLangChainMessages);\n\n/**\n * One thread's conversion memory: converting through it maps every source\n * message that is unchanged since the previous pass to the IDENTICAL\n * `ThreadMessage`, so a streamed token rebuilds only the turn it lands in\n * instead of every message in the transcript (and every per-message\n * subscriber, memo and tool card below it).\n *\n * Create one per attached thread and drop it with the attach: entries are\n * keyed by source message identity (WeakMaps, so they die with the wire state\n * that holds them), and a fresh cache per attach keeps one thread's converted\n * messages from ever being served for another.\n */\nexport class DeepAgentMessageConversionCache {\n private readonly cache = createExternalMessageConversionCache();\n /**\n * Every cached entry is rebuilt when the metadata object changes identity,\n * so it is created once per cache and never per pass. The tool-args\n * key-order memory keeps a finished tool call's `argsText` in the order its\n * arguments streamed in (assistant-ui's own LangGraph runtime passes the\n * same).\n */\n private readonly metadata: useExternalMessageConverter.Metadata & {\n readonly toolArgsKeyOrderCache: Map<string, Map<string, string[]>>;\n } = { toolArgsKeyOrderCache: new Map() };\n private previous: ThreadMessage[] | null = null;\n\n /**\n * Convert one pass's LangChain messages. Returns the previous pass's array\n * itself when every converted message is unchanged, so a packet that moved\n * no message re-renders no message.\n */\n convert(messages: LangChainMessage[], isRunning: boolean): ThreadMessage[] {\n const next = convertExternalMessages(\n messages,\n convertLangChainMessages,\n isRunning,\n this.metadata,\n this.cache\n );\n if (this.previous !== null && isSameMessageList(this.previous, next)) return this.previous;\n this.previous = next;\n return next;\n }\n}\n\n/** A conversion cache for one attached thread (see {@link DeepAgentMessageConversionCache}). */\nexport function createDeepAgentMessageConversionCache(): DeepAgentMessageConversionCache {\n return new DeepAgentMessageConversionCache();\n}\n\nfunction isSameMessageList(a: readonly ThreadMessage[], b: readonly ThreadMessage[]): boolean {\n return a.length === b.length && a.every((message, index) => message === b[index]);\n}\n\nconst wirePartsToOptimisticParts = (\n parts: StatewireThread.UserMessage['parts']\n): OptimisticContentPart[] =>\n parts.flatMap((part): OptimisticContentPart[] => {\n if (part.type === 'text') return [part];\n // Image attachments travel as `file` parts on the run/* commands; map\n // them back to `image` parts so the optimistic bubble renders them.\n if (part.mediaType.startsWith('image/')) return [{ type: 'image', image: part.url }];\n return [];\n });\n\n/**\n * `metadata.custom` flag marking a bubble that is a projection of a run-lane\n * entry — a message the server holds and has NOT dispatched yet.\n *\n * The lane is server-owned and replicated to every client on the thread, so\n * an item queued on one surface projects onto all of them. Surfaces without a\n * queue panel must therefore be able to tell a queued projection apart from a\n * submitted turn, or a message another device queued renders as if this device\n * had already sent it.\n *\n * `convertLangChainMessages` exposes `additional_kwargs.metadata` (and nothing\n * else) as the thread message's `metadata.custom`.\n */\nexport const ATHENA_QUEUED_LANE_KEY = 'athena_queued_lane';\n\n/**\n * Whether a thread message's `metadata.custom` marks it as an undispatched\n * lane projection. Returns a primitive, so it is safe to use directly as a\n * `useAuiState` selector.\n */\nexport function isQueuedLaneMessage(custom: Record<string, unknown> | undefined): boolean {\n return custom?.[ATHENA_QUEUED_LANE_KEY] === true;\n}\n\ninterface LaneEntryMessageCacheEntry {\n selfAuthor: DeepAgentMessageAuthor | null;\n message: LangChainMessage;\n}\n\n/**\n * Per-entry projections, one map per lane state. Lane entries are wire\n * objects folded copy-on-write (like messages, see {@link liftedMessageCache}),\n * so an unchanged entry projects to the IDENTICAL message on every pass and its\n * bubble keeps its converted identity while it waits in a lane or for its\n * dispatch to land. An entry this client's own unacknowledged command still\n * shapes is rebuilt each time statewire re-applies that optimistic fold, and\n * re-projects until the server acknowledges it.\n */\nconst queuedLaneMessageCache = new WeakMap<\n StatewireThread.RunsQueueEntry,\n LaneEntryMessageCacheEntry\n>();\nconst dispatchedLaneMessageCache = new WeakMap<\n StatewireThread.RunsQueueEntry,\n LaneEntryMessageCacheEntry\n>();\n\n/**\n * Project one run-lane entry into the optimistic human message shape.\n *\n * `queued` marks entries still waiting in a lane; a dispatched entry becomes an\n * ordinary submitted turn and carries no marker. Either way the bubble carries\n * its author (see {@link laneEntryAuthor}) under the same `metadata.custom`\n * key the backend stamps on the persisted turn, so the optimistic and\n * authoritative copies of one send render identically — and a second poster's\n * avatar shows on their own send without waiting for the acknowledgment.\n */\nconst laneEntryMessage = ({\n entry,\n queued,\n selfAuthor,\n}: {\n entry: StatewireThread.RunsQueueEntry;\n queued: boolean;\n selfAuthor: DeepAgentMessageAuthor | null;\n}): LangChainMessage => {\n const cache = queued ? queuedLaneMessageCache : dispatchedLaneMessageCache;\n const cached = cache.get(entry);\n if (cached !== undefined && cached.selfAuthor === selfAuthor) return cached.message;\n const message = buildLaneEntryMessage({ entry, queued, selfAuthor });\n cache.set(entry, { selfAuthor, message });\n return message;\n};\n\nconst buildLaneEntryMessage = ({\n entry,\n queued,\n selfAuthor,\n}: {\n entry: StatewireThread.RunsQueueEntry;\n queued: boolean;\n selfAuthor: DeepAgentMessageAuthor | null;\n}): LangChainMessage => {\n const author = laneEntryAuthor(entry, selfAuthor);\n // The draft a composer send came from, so the optimistic bubble renders the\n // typed text the way its persisted copy will (see `composer-markdown.ts`).\n const composerMarkdown: unknown = (entry.metadata as Record<string, unknown> | undefined)?.[\n DEEP_AGENT_COMPOSER_MARKDOWN_KEY\n ];\n const metadata: Record<string, unknown> = {\n ...(queued ? { [ATHENA_QUEUED_LANE_KEY]: true } : {}),\n ...(author ? { [ATHENA_MESSAGE_AUTHOR_KEY]: author } : {}),\n ...(typeof composerMarkdown === 'string'\n ? { [DEEP_AGENT_COMPOSER_MARKDOWN_KEY]: composerMarkdown }\n : {}),\n };\n return {\n id: entry.id,\n type: 'human',\n content: buildOptimisticHumanContent(wirePartsToOptimisticParts(entry.parts)),\n ...(Object.keys(metadata).length > 0 ? { additional_kwargs: { metadata } } : {}),\n };\n};\n\nexport interface DeepAgentMaterializeOptions {\n /** The viewer, stamped onto this client's own not-yet-acknowledged sends. */\n selfAuthor?: DeepAgentMessageAuthor | null;\n}\n\n/**\n * Materialize fold for the statewire path: makes the server's dispatch record\n * concrete in the LangGraph state itself, so direct state reads see dispatched\n * messages and the lib's post-converter dispatch merge stays off.\n */\nexport const createDeepAgentMaterialize =\n ({ selfAuthor = null }: DeepAgentMaterializeOptions = {}) =>\n (draft: DeepAgentStreamValues): void => {\n runsMaterialize(draft, {\n append: (message) => {\n (draft.messages ??= []).push(\n laneEntryMessage({ entry: message, queued: false, selfAuthor })\n );\n },\n rollback: (to) => {\n if (to === null) {\n draft.messages = [];\n return;\n }\n const messages = draft.messages ?? [];\n const keepIndex = messages.findIndex((message) => message.id === to);\n // Unresolved rollback anchor: fail OPEN with the full history (mirrors the edit fold).\n if (keepIndex !== -1) messages.splice(keepIndex + 1);\n },\n });\n };\n\n/** The materialize fold without a viewer identity (surfaces that pass none). */\nexport const deepAgentMaterialize = createDeepAgentMaterialize();\n\n/**\n * Project run lanes into human messages for the converter tail, so a queued or\n * steered item stays visible below the pending-assistant placeholder until its\n * dispatch. Surfaces with a queue panel project only the steer lane\n * (`'steer-only'`); surfaces without one project both lanes (`'both'`).\n *\n * Every projected entry carries {@link ATHENA_QUEUED_LANE_KEY} so a surface\n * without a queue panel can render it as queued rather than as a submitted\n * turn.\n */\nexport const laneTailMessages = (\n state: unknown,\n laneProjection: DeepAgentLaneProjection = 'both',\n selfAuthor: DeepAgentMessageAuthor | null = null\n): LangChainMessage[] => {\n const run = (state as DeepAgentStreamValues | null)?.runs?.[0];\n if (!run) return [];\n const entries =\n laneProjection === 'steer-only' ? run.steerQueue : [...run.queue, ...run.steerQueue];\n return entries.map((entry) => laneEntryMessage({ entry, queued: true, selfAuthor }));\n};\n\n/** Stable id for the derived running-assistant placeholder. */\nexport const PENDING_ASSISTANT_MESSAGE_ID = '__athena_deep_agent_pending_assistant__';\n\n/**\n * The placeholder itself — empty-array content plus an explicit running\n * status: the thinking shimmer keys on `status running && zero content parts`,\n * and the explicit status keeps it pulsing when lane entries render below (the\n * last-message auto status would demote a mid-list placeholder to an empty\n * complete bubble). One shared, frozen object, so the converted \"Thinking…\" row\n * keeps its identity across every pass until the real reply replaces it.\n */\nconst PENDING_ASSISTANT_MESSAGE: LangChainMessage = Object.freeze({\n type: 'ai',\n content: [],\n id: PENDING_ASSISTANT_MESSAGE_ID,\n status: Object.freeze({ type: 'running' }),\n} satisfies LangChainMessage);\n\n/**\n * Append an empty assistant turn while a run is in flight and the visible\n * history does not already end in one, so the thread shows its \"Thinking…\"\n * status line from the moment a message is submitted.\n *\n * Mirrors `hasUpcomingMessage` in assistant-ui's legacy external-store runtime,\n * which synthesized this placeholder itself. `StatewireThread` renders the\n * converter's message list verbatim, so the placeholder has to come from here —\n * derived from the replicated run status on every render (never written into\n * transport state) so it cannot outlive the run, and carrying a stable id so\n * the streaming bubble's identity does not churn when the real assistant\n * message arrives.\n *\n * `tool` messages convert into assistant turns too, so a mid-run tool result\n * must not produce a second placeholder row.\n */\nexport function withPendingAssistantMessage(\n messages: LangChainMessage[],\n isRunning: boolean\n): LangChainMessage[] {\n if (!isRunning) return messages;\n const lastType = messages.at(-1)?.type;\n if (lastType === 'ai' || lastType === 'tool') return messages;\n // Never persisted, so it cannot outlive the run.\n return [...messages, PENDING_ASSISTANT_MESSAGE];\n}\n\n/** Stable id for the synthetic assistant turn that carries a failure when the run produced none. */\nconst RUN_ERROR_SYNTHETIC_AI_ID = '__athena_deep_agent_run_error__';\n\n/**\n * Surface a run failure the base-correct way: stamp `status: { type:'incomplete',\n * reason:'error', error }` onto the assistant turn so `<Thread/>` renders it via\n * `ErrorPrimitive`. This is the core runtime's model (see `useLanggraphRuntime`\n * `onError`) — NOT a fabricated `⚠️ The run failed` assistant text message, and\n * NOT a magic-string contract shared with the agora backend.\n *\n * Stamps a trailing assistant message; if the visible history does not already\n * end with an assistant turn, appends a synthetic empty assistant turn with a\n * STABLE id so it never churns across renders. A `null` error is a no-op. The\n * shipped `convertLangChainMessages` converter passes `message.status` through.\n */\nexport function applyRunErrorStatus(\n messages: LangChainMessage[],\n errorMessage: string | null\n): LangChainMessage[] {\n if (errorMessage === null) return messages;\n const status = { type: 'incomplete', reason: 'error', error: errorMessage } as const;\n const lastMessage = messages.at(-1);\n if (lastMessage?.type !== 'ai') {\n return [\n ...messages,\n { type: 'ai', content: '', id: RUN_ERROR_SYNTHETIC_AI_ID, status } satisfies LangChainMessage,\n ];\n }\n return [...messages.slice(0, -1), { ...lastMessage, status }];\n}\n\n/**\n * `additional_kwargs` flag the backend stamps on run-lifecycle marker messages\n * (agora `athena_deep_agent/run_markers.py`) — e.g. the \"⏹️ Run stopped by\n * user.\" message persisted when a run is cancelled.\n */\nexport const ATHENA_RUN_MARKER_KEY = 'athena_run_marker';\n\n/** The {@link ATHENA_RUN_MARKER_KEY} value for a user-cancelled run. */\nexport const ATHENA_RUN_MARKER_CANCELLED = 'cancelled';\n\n/**\n * `additional_kwargs` key carrying the stop source behind a cancellation\n * marker (agora `stop_signals.py`: `user_stop`, `send_now`, `transport_cancel`,\n * `unattributed`).\n */\nexport const ATHENA_RUN_MARKER_SOURCE_KEY = 'athena_run_marker_source';\n\n/** The stop source for a \"Send now\" promotion pre-empting the live run. */\nexport const ATHENA_STOP_SOURCE_SEND_NOW = 'send_now';\n\n/**\n * The stop source for a spoken follow-up that redirected a voice-delegated\n * run through the steer lane: the user spoke, they never pressed Stop, so it\n * renders as a redirect rather than as \"Conversation stopped\".\n */\nexport const ATHENA_STOP_SOURCE_VOICE_STEER = 'voice_steer';\n\n/** The stop source for a pod-drain pause (server update), not a user stop. */\nexport const ATHENA_STOP_SOURCE_POD_DRAIN = 'pod_drain';\n\n/**\n * The {@link ATHENA_RUN_MARKER_SOURCE_KEY} value for a run whose hosting\n * instance closed under it (a server update that reached its eviction\n * deadline mid-step, or a placement lost to a lease lapse). Same family as a\n * pod drain: nobody stopped the run, the backend resumes it automatically.\n */\nexport const ATHENA_STOP_SOURCE_INSTANCE_CLOSED = 'instance_closed';\n\n/**\n * Whether a cancellation marker's source names the infrastructure — a pod\n * drain or a closed instance — rather than a person or a client action. These\n * endings read as a pause while the server resumes the run.\n */\nexport function isServerUpdateStopSource(source: string | undefined): boolean {\n return source === ATHENA_STOP_SOURCE_POD_DRAIN || source === ATHENA_STOP_SOURCE_INSTANCE_CLOSED;\n}\n\n/**\n * The {@link ATHENA_RUN_MARKER_KEY} value for a failed run (graph error,\n * including the `GraphRecursionError` \"tool call limit was reached\" case).\n * The marker message's text content carries the user-facing failure copy.\n */\nexport const ATHENA_RUN_MARKER_FAILED = 'failed';\n\n/**\n * `additional_kwargs` key carrying the canonical reason a run ended (agora\n * `athena_deep_agent/termination.py`): `user_stop`, `recursion_limit`,\n * `duplicate_call_guard`, `step_budget`, `ended_after_tools`, `model_refusal`,\n * `completed`, …\n * Stamped on run-lifecycle markers and on handoff messages.\n */\nexport const ATHENA_TERMINATION_REASON_KEY = 'athena_termination_reason';\n\n/**\n * `additional_kwargs` flag on the closing message the runtime's **handoff\n * turn** wrote (one tools-disabled model call made when a guard or the step\n * budget ends the run). The message text is the agent's closing summary; the\n * flag lets clients frame it as a runtime ending rather than a normal reply.\n */\nexport const ATHENA_HANDOFF_KEY = 'athena_handoff';\n\n/**\n * Read a run-lifecycle marker out of a thread message's **bound source\n * messages**, bypassing the converter's metadata join entirely.\n *\n * Why this exists: assistant-ui chunks consecutive assistant/tool messages\n * into ONE `ThreadMessage`, and the upstream join copies `metadata.custom`\n * only from the FIRST assistant message of the chunk\n * (`external-message-converter.ts`: the merge sits inside\n * `if (assistantMessage.content.length === 0)`). A failure marker appended\n * after the assistant's own turn is therefore never the first message in its\n * chunk, so `message.metadata.custom.athena_run_marker` is silently dropped\n * and the whole-message gate can't fire.\n *\n * `getExternalStoreMessages` returns the raw LangChain messages bound to the\n * joined thread message, so the marker is found wherever it sits in the chunk.\n * Returns the marker value plus its text content (the user-facing copy).\n */\nexport interface RunMarkerInfo {\n marker: string;\n /** The stop source stamped by the backend, when the marker carries one. */\n source: string | undefined;\n /** The marker message's text — the user-facing copy. */\n text: string | undefined;\n /**\n * True when the marker is the ONLY source message behind this thread message,\n * i.e. it owns the whole row (a cancelled run drops its partial turn, so its\n * marker stands alone).\n *\n * False when the chunk also holds the assistant's own work. Replacing the row\n * in that case would hide completed prose and tool cards — the marker must be\n * styled in place instead.\n */\n isSoleSourceMessage: boolean;\n}\n\nexport function readRunMarkerFromSourceMessages(\n message: Parameters<typeof getExternalStoreMessages>[0] & {\n metadata?: { custom?: Record<string, unknown> | undefined } | undefined;\n content?: unknown;\n }\n): RunMarkerInfo | undefined {\n const sources = getExternalStoreMessages<LangChainMessage>(message);\n for (const source of sources) {\n if (source?.type !== 'ai') continue;\n const kwargs = source.additional_kwargs as Record<string, unknown> | undefined;\n const marker = kwargs?.[ATHENA_RUN_MARKER_KEY];\n if (typeof marker !== 'string') continue;\n const markerSource = kwargs?.[ATHENA_RUN_MARKER_SOURCE_KEY];\n return {\n marker,\n source: typeof markerSource === 'string' ? markerSource : undefined,\n text: typeof source.content === 'string' ? source.content : undefined,\n isSoleSourceMessage: sources.length === 1,\n };\n }\n // Fallback for surfaces whose messages carry no bound source messages (or a\n // converter that already merged the flag): read the joined metadata. Without\n // sources to count, a single text part is the best available \"marker owns the\n // row\" signal.\n const joined = message.metadata?.custom?.[ATHENA_RUN_MARKER_KEY];\n if (typeof joined !== 'string') return undefined;\n const joinedSource = message.metadata?.custom?.[ATHENA_RUN_MARKER_SOURCE_KEY];\n const parts = message.content;\n return {\n marker: joined,\n source: typeof joinedSource === 'string' ? joinedSource : undefined,\n text: undefined,\n isSoleSourceMessage: Array.isArray(parts) ? parts.length <= 1 : true,\n };\n}\n\n/**\n * The marker value when the marker is the SOLE source message behind a thread\n * message (it owns the whole row), else `undefined`.\n *\n * Returns a primitive so it is safe to use directly as a `useAuiState`\n * selector: `readRunMarkerFromSourceMessages` builds a fresh object on every\n * call, and selector snapshots are compared with `Object.is` — returning that\n * object from a selector makes `getSnapshot` unstable and loops the render.\n */\nexport function readSoleRunMarker(\n message: Parameters<typeof readRunMarkerFromSourceMessages>[0]\n): string | undefined {\n const found = readRunMarkerFromSourceMessages(message);\n return found?.isSoleSourceMessage ? found.marker : undefined;\n}\n\n/**\n * Whether a run marker renders nothing: a `send_now` promotion cancels the\n * live run as plumbing, not as a user-visible stop, so its cancellation\n * marker stays in the data for provenance but paints no divider.\n */\nexport function isSilentRunMarker(found: RunMarkerInfo): boolean {\n return (\n found.marker === ATHENA_RUN_MARKER_CANCELLED && found.source === ATHENA_STOP_SOURCE_SEND_NOW\n );\n}\n\n/**\n * Whether a cancellation marker records a server-update pause — a cooperative\n * pod drain, or the hosting instance closing under the run — rather than a\n * stop the user asked for. Rendered with distinct \"paused by a server update\"\n * copy instead of the \"Conversation stopped\" pill; the backend resumes the run.\n */\nexport function isDrainRunMarker(found: RunMarkerInfo): boolean {\n return found.marker === ATHENA_RUN_MARKER_CANCELLED && isServerUpdateStopSource(found.source);\n}\n\n/**\n * Surface the backend's top-level `additional_kwargs.athena_run_marker` flag\n * where the shipped converter can see it.\n *\n * `convertLangChainMessages` exposes ONLY `additional_kwargs.metadata` as the\n * thread message's `metadata.custom`; every other `additional_kwargs` key is\n * dropped. Marker messages persisted before the backend mirrored the flag into\n * `metadata` carry it top-level only, so this lift keeps those threads styling\n * correctly. Non-marker messages pass through with their identity unchanged,\n * and already-mirrored markers are left as-is (idempotent).\n */\nexport function liftRunMarkerIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'ai') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const marker = kwargs?.[ATHENA_RUN_MARKER_KEY];\n if (typeof marker !== 'string') return message;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (metadata[ATHENA_RUN_MARKER_KEY] === marker) return message;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: { ...metadata, [ATHENA_RUN_MARKER_KEY]: marker },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Mirror the termination reason and handoff flag into the converter's metadata\n * channel. The backend dual-writes both (top-level + `metadata`), so this lift\n * is the back-compat path for messages persisted before the mirror existed and\n * for any emitter that only stamps the top-level key. Idempotent; identity for\n * messages without the keys.\n */\nexport function liftTerminationIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'ai') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const reason = kwargs?.[ATHENA_TERMINATION_REASON_KEY];\n const handoff = kwargs?.[ATHENA_HANDOFF_KEY];\n const hasReason = typeof reason === 'string';\n const hasHandoff = handoff === true;\n if (!hasReason && !hasHandoff) return message;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n const reasonMirrored = !hasReason || metadata[ATHENA_TERMINATION_REASON_KEY] === reason;\n const handoffMirrored = !hasHandoff || metadata[ATHENA_HANDOFF_KEY] === true;\n if (reasonMirrored && handoffMirrored) return message;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: {\n ...metadata,\n ...(hasReason ? { [ATHENA_TERMINATION_REASON_KEY]: reason } : {}),\n ...(hasHandoff ? { [ATHENA_HANDOFF_KEY]: true } : {}),\n },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * `additional_kwargs` key (and `metadata` mirror) carrying a failure marker's\n * structured error records (agora `athena_deep_agent/run_markers.py`\n * `failure_error_records`): `[{ error, provider?, message? }]`, where `error`\n * is the client code a surface branches on — `context_length_exceeded`,\n * `transient_api_error` (with `provider: 'azure_foundry' |\n * 'external_model_provider'`), `content_filter`, `request_too_large`, … — the\n * same shape the legacy agent node emitted.\n */\nexport const ATHENA_ERRORS_KEY = 'errors';\n\n/**\n * Mirror a failure marker's structured `errors` records into the converter's\n * metadata channel. The backend dual-writes them (top-level + `metadata`), so\n * this lift is the back-compat path for messages that carry only the top-level\n * key (an emitter that never mirrored, or a record persisted before the mirror\n * existed). Records already present in the metadata channel win; no-ops are\n * identity.\n */\nexport function liftErrorsIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'ai') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const errors = kwargs?.[ATHENA_ERRORS_KEY];\n if (!Array.isArray(errors) || errors.length === 0) return message;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (Array.isArray(metadata[ATHENA_ERRORS_KEY])) return message;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: { ...metadata, [ATHENA_ERRORS_KEY]: errors },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Read a failure's structured `errors` records out of a thread message's\n * **bound source messages**, bypassing the converter's metadata join.\n *\n * Same reason as {@link readRunMarkerFromSourceMessages}: assistant-ui chunks\n * consecutive assistant/tool messages into ONE `ThreadMessage` and copies\n * `metadata.custom` only from the FIRST assistant message of the chunk. The\n * failure marker that carries the records is appended after the assistant's\n * own turn, so `message.metadata.custom.errors` is silently dropped whenever\n * the run produced anything before failing. The bound sources are walked from\n * the tail (the terminal failure wins); the joined metadata is the fallback\n * for surfaces whose messages carry no bound sources. Returns the raw records\n * (callers validate the shape) or `undefined`.\n */\nexport function readMessageErrorsFromSourceMessages(\n message: Parameters<typeof getExternalStoreMessages>[0] & {\n metadata?: { custom?: Record<string, unknown> | undefined } | undefined;\n }\n): unknown[] | undefined {\n const sources = getExternalStoreMessages<LangChainMessage>(message);\n for (let index = sources.length - 1; index >= 0; index -= 1) {\n const source = sources[index];\n if (source?.type !== 'ai') continue;\n const kwargs = source.additional_kwargs as Record<string, unknown> | undefined;\n const metadata = kwargs?.metadata;\n const mirrored =\n typeof metadata === 'object' && metadata !== null\n ? (metadata as Record<string, unknown>)[ATHENA_ERRORS_KEY]\n : undefined;\n const errors = Array.isArray(mirrored) ? mirrored : kwargs?.[ATHENA_ERRORS_KEY];\n if (Array.isArray(errors) && errors.length > 0) return errors;\n }\n const joined = message.metadata?.custom?.[ATHENA_ERRORS_KEY];\n return Array.isArray(joined) && joined.length > 0 ? joined : undefined;\n}\n\n/**\n * Mirror the producing model into the converter's metadata channel. Wire AI\n * messages are full LangChain dumps whose `response_metadata` reports the model\n * that generated them (`model_name` / `model`), but the shipped converter maps\n * only `additional_kwargs.metadata` into `metadata.custom` — so without this\n * lift a surface can only show the CURRENT run config's model, mislabeling\n * earlier messages after a mid-thread model switch. A model already present in\n * the metadata channel (backend-stamped provenance) wins; no-ops are identity.\n */\nexport function liftModelIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'ai') return message;\n const responseMetadata = (\n message as { response_metadata?: { model_name?: unknown; model?: unknown } }\n ).response_metadata;\n let model: string | undefined;\n if (typeof responseMetadata?.model_name === 'string' && responseMetadata.model_name) {\n model = responseMetadata.model_name;\n } else if (typeof responseMetadata?.model === 'string' && responseMetadata.model) {\n model = responseMetadata.model;\n }\n if (!model) return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (typeof metadata.model === 'string' && metadata.model) return message;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: { ...metadata, model },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Mirror a per-message conversation-stats snapshot into the converter's\n * metadata channel. Backends stamp the snapshot at the legacy location —\n * top-level `additional_kwargs.conversation_stats` (the deep-agent stats\n * middleware and the legacy agent node both write it there) — but the shipped\n * converter maps only `additional_kwargs.metadata` into `metadata.custom`, so\n * without this lift the model badge can never show a message's OWN context and\n * falls back to thread-level live numbers. A snapshot already present in the\n * metadata channel wins; no-ops are identity.\n */\nexport function liftConversationStatsIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'ai') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const stats = kwargs?.conversation_stats;\n if (typeof stats !== 'object' || stats === null) return message;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (typeof metadata.conversation_stats === 'object' && metadata.conversation_stats !== null) {\n return message;\n }\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: { ...metadata, conversation_stats: stats },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Mirror the compaction boundary flag into the converter's metadata channel.\n * A manual `/compact` persists its summary as a human message tagged with\n * top-level `additional_kwargs.compaction_start` (plus `_created_at` for the\n * divider timestamp) — the same location the legacy runtime used — but the\n * shipped converter maps only `additional_kwargs.metadata` into\n * `metadata.custom`, so without this lift the transcript's\n * \"Previous conversation summarized\" divider can never render. A flag already\n * present in the metadata channel wins; no-ops are identity.\n */\nexport function liftCompactionBoundaryIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'human') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n if (kwargs?.compaction_start !== true) return message;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (metadata.compaction_start === true) return message;\n const createdAt = kwargs?._created_at;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: {\n ...metadata,\n compaction_start: true,\n ...(typeof createdAt === 'string' ? { _created_at: createdAt } : {}),\n },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Mirror the handoff summary flag into the converter's metadata channel.\n * Handoff summaries are persisted as human messages tagged with top-level\n * `additional_kwargs.handoff_summary` (plus `_created_at`) — the same location\n * the backend uses for message metadata — but the shipped converter maps only\n * `additional_kwargs.metadata` into `metadata.custom`, so without this lift\n * the summary would render as a normal user bubble. A flag already present in\n * the metadata channel wins; no-ops are identity.\n */\nexport function liftHandoffSummaryIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'human') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n if (kwargs?.handoff_summary !== true) return message;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (metadata.handoff_summary === true) return message;\n const createdAt = kwargs?._created_at;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: {\n ...metadata,\n handoff_summary: true,\n ...(typeof createdAt === 'string' ? { _created_at: createdAt } : {}),\n },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Mirror an AOP run's provenance into the converter's metadata channel. The\n * backend stamps the kicking-off user message with top-level\n * `additional_kwargs.aop_source` (agora `events/helpers.py`\n * `_build_aop_source_metadata`), which the legacy converter surfaced as\n * `metadata.custom.aop_source` — where the \"AOP · <title>\" badge reads it. The\n * shipped `convertLangChainMessages` maps only `additional_kwargs.metadata`\n * into `metadata.custom`, so without this lift an AOP run's conversation never\n * shows which AOP asset kicked it off. Provenance already present in the\n * metadata channel wins; no-ops are identity.\n */\nexport function liftAopSourceIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'human') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const aopSource = kwargs?.aop_source;\n if (typeof aopSource !== 'object' || aopSource === null || Array.isArray(aopSource)) {\n return message;\n }\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (typeof metadata.aop_source === 'object' && metadata.aop_source !== null) return message;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: { ...metadata, aop_source: aopSource },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\nexport function liftBranchIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'human' && message.type !== 'ai') return message;\n const branch = (message as { branch?: unknown }).branch;\n if (typeof branch !== 'object' || branch === null || Array.isArray(branch)) return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (typeof metadata.branch === 'object' && metadata.branch !== null) return message;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: { ...metadata, branch },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * `additional_kwargs` keys on a ToolMessage that are infrastructure rather than\n * user-set metadata. The filter below additionally skips every `_`-prefixed key.\n */\nconst TOOL_MESSAGE_INTERNAL_KWARGS: ReadonlySet<string> = new Set([\n 'reasoning',\n 'tool_outputs',\n 'hide_from_frontend',\n]);\n\n/**\n * Mirror a ToolMessage's user-set `additional_kwargs` into\n * `artifact._tool_message_metadata` — the location `useToolCallMetadata` reads\n * persisted per-tool-call metadata from (`email_sent_at`, draft edits,\n * `calendar_event_created_at`, …).\n *\n * The `setMessageMetadata` mutation persists those keys onto the ToolMessage's\n * `additional_kwargs` in the checkpoint, and the legacy converter lifted them\n * into the artifact. The shipped `convertLangChainMessages` forwards only\n * `artifact` and drops `additional_kwargs` for tool messages, so without this\n * lift the metadata survives in the checkpoint but never reaches a tool card\n * after a reload. Kwargs values win over same-key artifact entries (the kwargs\n * are the durable write target); messages with no user-set kwargs pass through\n * by identity.\n */\nexport function liftToolMessageMetadataIntoArtifact(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'tool') return message;\n const kwargs = (message as { additional_kwargs?: Record<string, unknown> }).additional_kwargs;\n if (!kwargs) return message;\n const lifted: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(kwargs)) {\n if (key.startsWith('_') || TOOL_MESSAGE_INTERNAL_KWARGS.has(key)) continue;\n lifted[key] = value;\n }\n if (Object.keys(lifted).length === 0) return message;\n const artifact: unknown = message.artifact;\n const artifactRecord =\n typeof artifact === 'object' && artifact !== null && !Array.isArray(artifact)\n ? (artifact as Record<string, unknown>)\n : {};\n const existing = artifactRecord._tool_message_metadata;\n const existingRecord =\n typeof existing === 'object' && existing !== null && !Array.isArray(existing)\n ? (existing as Record<string, unknown>)\n : {};\n return {\n ...message,\n artifact: {\n ...artifactRecord,\n _tool_message_metadata: { ...existingRecord, ...lifted },\n },\n };\n}\n\n/**\n * Lift a windowed wire's tool-output truncation marker into the tool-call\n * part's `artifact` (`_athena_content_truncated: { chars, messageId }`).\n *\n * The host cuts long plain-text tool outputs on a windowed wire and stamps\n * `athena_content_truncated` on the wire message; the shipped converter joins\n * a ToolMessage into its assistant turn's tool-call part and keeps only\n * `artifact` and the result, so without this lift no tool card could tell the\n * output is cut nor which message to ask `history/tool_output` for. Messages\n * without the marker pass through by identity.\n */\nexport function liftTruncatedToolOutputIntoArtifact(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'tool') return message;\n const marker = readTruncatedToolContent(message);\n if (marker === null || typeof message.id !== 'string') return message;\n const artifact: unknown = message.artifact;\n const artifactRecord =\n typeof artifact === 'object' && artifact !== null && !Array.isArray(artifact)\n ? (artifact as Record<string, unknown>)\n : {};\n return {\n ...message,\n artifact: {\n ...artifactRecord,\n [ATHENA_TRUNCATED_TOOL_OUTPUT_ARTIFACT_KEY]: { chars: marker.chars, messageId: message.id },\n },\n };\n}\n\n/**\n * A content list's bare-string entries as typed text parts.\n *\n * LangChain content is `string | (string | ContentBlock)[]`, and a model with\n * no tools bound streams exactly that mix: `langchain_anthropic` coerces text\n * deltas to plain strings when no tools are bound, yet a thinking block still\n * arrives as a list block, so the merged reply is `[\"\", {thinking}, \"text\"]`\n * (the side-question lane's bare model). The shipped converter maps only typed\n * blocks and drops the strings, leaving the reasoning with no answer. Empty\n * strings are dropped; messages with no string entries pass through by\n * identity.\n */\nexport function normalizeStringContentParts(message: LangChainMessage): LangChainMessage {\n const content: unknown = message.content;\n if (!Array.isArray(content) || !content.some((part) => typeof part === 'string')) {\n return message;\n }\n const parts = content.flatMap((part: unknown) => {\n if (typeof part !== 'string') return [part];\n return part.length > 0 ? [{ type: 'text', text: part }] : [];\n });\n return { ...message, content: parts } as LangChainMessage;\n}\n\n/**\n * Per-source cache for {@link substituteToolOutput}: a substituted copy is\n * reused while the wire message and the fetched output are both unchanged, so\n * the lift cache downstream keeps its identity contract.\n */\nconst substitutedToolOutputCache = new WeakMap<\n LangChainMessage,\n { content: unknown; substituted: LangChainMessage }\n>();\n\n/**\n * A cut tool message with its full output put back, when the store holds one\n * fetched for this very output (`history/tool_output` answered for the same\n * `chars` the wire's marker reports); the marker leaves with the cut.\n * Messages that are whole, whose output has not been fetched, or whose fetched\n * output belongs to a length the host has since replaced, pass through by\n * identity — the loader sees the mismatch and fetches again.\n */\nexport function substituteToolOutput(\n message: LangChainMessage,\n toolOutputs: DeepAgentToolOutputStore\n): LangChainMessage {\n if (message.type !== 'tool' || typeof message.id !== 'string') return message;\n const marker = readTruncatedToolContent(message);\n if (marker === null) return message;\n const entry = toolOutputs.entry(message.id);\n if (entry === undefined || entry.chars !== marker.chars) return message;\n const { content } = entry;\n const cached = substitutedToolOutputCache.get(message);\n if (cached !== undefined && cached.content === content) return cached.substituted;\n const { [DEEP_AGENT_TRUNCATED_CONTENT_KEY]: _marker, ...rest } = message as Record<\n string,\n unknown\n >;\n const substituted = { ...rest, content } as unknown as LangChainMessage;\n substitutedToolOutputCache.set(message, { content, substituted });\n return substituted;\n}\n\nconst hasHiddenFrontendFlag = (value: unknown): boolean => {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return false;\n }\n const record = value as Record<string, unknown>;\n if (record.hide_from_frontend === true) return true;\n return hasHiddenFrontendFlag(record.custom);\n};\n\n/**\n * Internal orchestration messages are useful in a trace export but must never\n * become conversation bubbles. Agora stores the flag in `additional_kwargs`;\n * live converter state can expose the same value under `metadata.custom`.\n */\nexport function isMessageHiddenFromFrontend(message: unknown): boolean {\n if (typeof message !== 'object' || message === null || Array.isArray(message)) {\n return false;\n }\n const record = message as Record<string, unknown>;\n return (\n hasHiddenFrontendFlag(record.additional_kwargs) || hasHiddenFrontendFlag(record.metadata)\n );\n}\n\n/**\n * The message `type`s `convertLangChainMessages` actually maps: its\n * `switch (message.type)` covers exactly these four and has no `default`, so it\n * returns `undefined` for anything else (a `RemoveMessage` `type: \"remove\"`\n * from summarization/compaction, a partial chunk, a future type).\n */\nconst CONVERTIBLE_MESSAGE_TYPES: ReadonlySet<string> = new Set(['system', 'human', 'ai', 'tool']);\n\n/**\n * Whether `convertLangChainMessages` can map this message at all.\n *\n * An unmappable entry converts to `undefined`, which assistant-ui's\n * `convertExternalMessages` wraps as `outputs: [undefined]` and then reads\n * `output.role` off — throwing `Cannot read property 'role' of undefined` and\n * taking the entire thread down instead of dropping one message.\n */\nexport function isConvertibleChatMessage(message: unknown): boolean {\n if (typeof message !== 'object' || message === null || Array.isArray(message)) {\n return false;\n }\n const type: unknown = (message as Record<string, unknown>).type;\n return typeof type === 'string' && CONVERTIBLE_MESSAGE_TYPES.has(type);\n}\n\n/**\n * Per-source cache for the lift pipeline in {@link sanitizeLangChainMessages}.\n *\n * The pipeline runs on EVERY state pass (once per streamed token), and the two\n * lifts that fire for essentially every message — {@link liftBranchIntoMetadata}\n * (the backend stamps `branch` on all indexed messages) and\n * {@link liftModelIntoMetadata} — allocate a fresh object each time they lift.\n * Without a cache, every message's identity churns on every token, defeating\n * all downstream per-source memoization (`getExternalStoreMessages`-keyed\n * reads, converter callback caches) and re-deriving the whole thread per token.\n *\n * Object identity is a sound cache key here: every statewire fold (op apply,\n * optimistic, materialize) runs through `statewire`'s copy-on-write\n * `recordMutations`, whose `finalize` returns the ORIGINAL object for\n * untouched nodes and a fresh copy for touched ones. A message is therefore\n * REPLACED — never mutated in place — whenever any of its fields change\n * (including the streaming tail's string-insert content growth), and an\n * unchanged message keeps its exact identity across passes. The corpus test\n * (`statewire-corpus.test.ts`) pins this identity contract against the real\n * client, so a statewire upgrade that switched to in-place mutation would fail\n * there rather than silently serving stale lifts from this cache.\n *\n * Module-level on purpose: keys are the wire message objects themselves, so\n * entries die with their state trees and threads cannot collide.\n */\nconst liftedMessageCache = new WeakMap<LangChainMessage, LangChainMessage>();\n\n/** The full lift chain for one message, memoized per source object. */\nfunction liftMessageIntoMetadataChannels(message: LangChainMessage): LangChainMessage {\n const cached = liftedMessageCache.get(message);\n if (cached !== undefined) return cached;\n const lifted = liftBranchIntoMetadata(\n liftTruncatedToolOutputIntoArtifact(\n liftToolMessageMetadataIntoArtifact(\n liftAopSourceIntoMetadata(\n liftHandoffSummaryIntoMetadata(\n liftCompactionBoundaryIntoMetadata(\n liftConversationStatsIntoMetadata(\n liftModelIntoMetadata(\n liftErrorsIntoMetadata(\n liftTerminationIntoMetadata(\n liftRunMarkerIntoMetadata(normalizeStringContentParts(message))\n )\n )\n )\n )\n )\n )\n )\n )\n )\n );\n liftedMessageCache.set(message, lifted);\n return lifted;\n}\n\n/**\n * The one message pipeline every deep-agent surface renders through:\n *\n * 1. drop messages the shipped converter cannot map (`RemoveMessage` etc. —\n * an unmappable entry would crash the whole thread) and internal\n * orchestration messages flagged hidden-from-frontend;\n * 1a. turn bare-string entries of a content list into typed text parts (see\n * {@link normalizeStringContentParts}) so a tool-less model's answer next\n * to its thinking block is not dropped by the converter;\n * 2. lift run-lifecycle markers into the converter's `metadata` channel\n * (see {@link liftRunMarkerIntoMetadata}) so the cancellation row styles as\n * a status divider instead of a plain assistant bubble, and the termination\n * reason + handoff flag alongside them (see\n * {@link liftTerminationIntoMetadata}) so every surface can render a\n * runtime ending (see `runtime-messages.ts`);\n * 3. lift each AI message's producing model into the same channel\n * (see {@link liftModelIntoMetadata}) so model badges reflect the model that\n * actually generated the message, not the currently configured one;\n * 4. lift each AI message's per-message conversation-stats snapshot into the\n * same channel (see {@link liftConversationStatsIntoMetadata}) so the model\n * badge's hover card shows that message's own context usage;\n * 5. lift the compaction boundary flag on `/compact` summary messages into the\n * same channel (see {@link liftCompactionBoundaryIntoMetadata}) so the\n * transcript renders its \"Previous conversation summarized\" divider;\n * 6. lift the handoff summary flag on carried-over context messages into the\n * same channel (see {@link liftHandoffSummaryIntoMetadata}) so the\n * transcript keeps the summary behind its context panel;\n * 7. lift an AOP run's provenance on its kicking-off user message into the\n * same channel (see {@link liftAopSourceIntoMetadata}) so the message\n * renders its \"AOP · <title>\" origin badge;\n * 8. lift each ToolMessage's user-set `additional_kwargs` into\n * `artifact._tool_message_metadata` (see\n * {@link liftToolMessageMetadataIntoArtifact}) so persisted per-tool-call\n * metadata reaches tool cards after a reload;\n * 9. lift a windowed wire's tool-output truncation marker into the tool-call\n * part's `artifact` (see {@link liftTruncatedToolOutputIntoArtifact}) so a\n * tool card can fetch the whole output — after first putting back any\n * output already fetched (see {@link substituteToolOutput});\n * 10. lift the host's per-message branch stamp into the same channel\n * (see {@link liftBranchIntoMetadata}) so the branch picker renders each\n * message's edit fan-out.\n *\n * Identity-stable per source: an unchanged source message maps to the\n * IDENTICAL lifted object across passes (see {@link liftedMessageCache}).\n */\nexport function sanitizeLangChainMessages(\n messages: readonly LangChainMessage[],\n { toolOutputs }: { toolOutputs?: DeepAgentToolOutputStore } = {}\n): LangChainMessage[] {\n return messages\n .filter((message) => isConvertibleChatMessage(message) && !isMessageHiddenFromFrontend(message))\n .map((message) =>\n liftMessageIntoMetadataChannels(\n toolOutputs ? substituteToolOutput(message, toolOutputs) : message\n )\n );\n}\n\n/** Type guard for the runtime state — an object carrying a `messages` array. */\nexport function isLangGraphState(value: unknown): value is LangGraphState {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'messages' in value &&\n Array.isArray((value as LangGraphState).messages)\n );\n}\n\nexport interface ConvertDeepAgentStateOptions {\n laneProjection?: DeepAgentLaneProjection;\n /**\n * The viewer, as the backend would stamp them: the author of this client's\n * own not-yet-acknowledged lane entries (see {@link laneEntryAuthor}).\n */\n selfAuthor?: DeepAgentMessageAuthor | null;\n /**\n * Full tool outputs fetched back for a windowed wire's cut tool messages\n * (see {@link substituteToolOutput}); omitted on surfaces that never\n * declare a window.\n */\n toolOutputs?: DeepAgentToolOutputStore;\n /**\n * User turns this client took back ahead of the wire (see\n * {@link DeepAgentWithdrawnTurns}): omitted from the transcript, together\n * with the pending-assistant placeholder a withdrawn tail would earn.\n */\n withdrawnTurns?: DeepAgentWithdrawnTurns;\n /**\n * The attached thread's conversion memory (see\n * {@link createDeepAgentMessageConversionCache}): unchanged messages keep\n * their converted identity across passes. Without one every pass converts\n * the whole transcript from scratch.\n */\n conversionCache?: DeepAgentMessageConversionCache;\n}\n\n/**\n * Map server state → assistant-ui thread messages.\n *\n * `state.messages` is the authoritative LangChain history streamed by the\n * backend. The replicated run status (`runs[0].status`) drives the\n * pending-assistant placeholder and the running status. Lane entries render at\n * the tail, after the placeholder, while the run is live (the run is still\n * working the history above them); once the thread rests stopped/errored they\n * are omitted (surfaces with a queue panel list them there instead).\n * Consecutive spoken user turns collapse into one user message (see\n * {@link groupSpokenUserTurns}) before conversion.\n */\nexport function convertDeepAgentState(\n state: unknown,\n {\n laneProjection = 'both',\n selfAuthor = null,\n toolOutputs,\n withdrawnTurns,\n conversionCache,\n }: ConvertDeepAgentStateOptions = {}\n) {\n // A wire without a snapshot yet (pre-attach, replay boundary) converts as the\n // typed pre-stream default: an empty resting thread, never `undefined`.\n const values = readDeepAgentStreamValues(state);\n const status = values.runs?.[0]?.status;\n const isRunning = status === 'running';\n const isWithdrawn = (message: LangChainMessage): boolean =>\n withdrawnTurns !== undefined && message.id !== undefined && withdrawnTurns.isWithdrawn(message.id);\n const laneTail = (\n status === 'stopped' || status === 'error'\n ? []\n : laneTailMessages(values, laneProjection, selfAuthor)\n ).filter((message) => !isWithdrawn(message));\n const sanitized: LangChainMessage[] = isLangGraphState(values)\n ? sanitizeLangChainMessages(values.messages, { toolOutputs })\n : [];\n // A withdrawn tail is a turn nobody is answering: no placeholder for it.\n const tail: LangChainMessage | undefined = sanitized[sanitized.length - 1];\n const tailWithdrawn: boolean = tail !== undefined && isWithdrawn(tail);\n const stateMessages = groupSpokenUserTurns(sanitized.filter((message) => !isWithdrawn(message)));\n const sources = [\n ...withPendingAssistantMessage(stateMessages, isRunning && !tailWithdrawn),\n ...laneTail,\n ];\n return {\n messages: conversionCache\n ? conversionCache.convert(sources, isRunning)\n : messageConverter.toThreadMessages(sources, isRunning),\n isRunning,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"messages.js","sources":["../../../../agent-runtime/src/messages.ts"],"sourcesContent":["import { getExternalStoreMessages, type ThreadMessage } from '@assistant-ui/core';\nimport {\n convertExternalMessages,\n createExternalMessageConversionCache,\n createMessageConverter,\n type useExternalMessageConverter,\n} from '@assistant-ui/core/react';\nimport { convertLangChainMessages, type LangChainMessage } from '@assistant-ui/react-langgraph';\nimport { runsMaterialize, type StatewireThread } from '@assistant-ui/react-statewire';\nimport {\n ATHENA_MESSAGE_AUTHOR_KEY,\n type DeepAgentMessageAuthor,\n laneEntryAuthor,\n} from './message-author';\nimport { DEEP_AGENT_COMPOSER_MARKDOWN_KEY } from './composer-markdown';\nimport { DEEP_AGENT_TRUNCATED_CONTENT_KEY } from './generated/runconfig-contract';\nimport {\n ATHENA_TRUNCATED_TOOL_OUTPUT_ARTIFACT_KEY,\n readTruncatedToolContent,\n} from './message-window';\nimport { buildOptimisticHumanContent, type OptimisticContentPart } from './optimistic-content';\nimport { readDeepAgentStreamValues } from './statewire-state';\nimport type { DeepAgentToolOutputStore } from './tool-outputs';\nimport { groupSpokenUserTurns } from './voice-turn-grouping';\nimport type { DeepAgentWithdrawnTurns } from './withdrawn-turns';\nimport type {\n DeepAgentLaneProjection,\n DeepAgentStreamValues,\n LangGraphState,\n} from './types';\n\n/**\n * Native message converter for the deep-agent runtime.\n *\n * This is the entire \"conversion\" layer: assistant-ui's shipped\n * `convertLangChainMessages` (from `@assistant-ui/react-langgraph`) wired\n * through `createMessageConverter`. No bespoke per-message mapping — the agora\n * `athena_deep_agent` graph streams standard LangChain messages via\n * `append_langgraph_event`, and the shipped converter renders them.\n */\nexport const messageConverter = createMessageConverter(convertLangChainMessages);\n\n/**\n * One thread's conversion memory: converting through it maps every source\n * message that is unchanged since the previous pass to the IDENTICAL\n * `ThreadMessage`, so a streamed token rebuilds only the turn it lands in\n * instead of every message in the transcript (and every per-message\n * subscriber, memo and tool card below it).\n *\n * Create one per attached thread and drop it with the attach: entries are\n * keyed by source message identity (WeakMaps, so they die with the wire state\n * that holds them), and a fresh cache per attach keeps one thread's converted\n * messages from ever being served for another.\n */\nexport class DeepAgentMessageConversionCache {\n private readonly cache = createExternalMessageConversionCache();\n /**\n * Every cached entry is rebuilt when the metadata object changes identity,\n * so it is created once per cache and never per pass. The tool-args\n * key-order memory keeps a finished tool call's `argsText` in the order its\n * arguments streamed in (assistant-ui's own LangGraph runtime passes the\n * same).\n */\n private readonly metadata: useExternalMessageConverter.Metadata & {\n readonly toolArgsKeyOrderCache: Map<string, Map<string, string[]>>;\n } = { toolArgsKeyOrderCache: new Map() };\n private previous: ThreadMessage[] | null = null;\n /** `convertExternalMessages`' own rows for the previous pass, before parts were kept. */\n private previousConverted: ThreadMessage[] | null = null;\n\n /**\n * Convert one pass's LangChain messages. Returns the previous pass's array\n * itself when every converted message is unchanged, so a packet that moved\n * no message re-renders no message; a re-converted turn keeps the previous\n * pass's part objects for the parts that did not change\n * (see {@link keepUnchangedParts}).\n */\n convert(messages: LangChainMessage[], isRunning: boolean): ThreadMessage[] {\n const converted = convertExternalMessages(\n messages,\n convertLangChainMessages,\n isRunning,\n this.metadata,\n this.cache\n );\n const next =\n this.previous !== null && this.previousConverted !== null\n ? keepUnchangedParts({\n previous: this.previous,\n previousConverted: this.previousConverted,\n converted,\n })\n : converted;\n this.previousConverted = converted;\n if (this.previous !== null && isSameMessageList(this.previous, next)) return this.previous;\n this.previous = next;\n return next;\n }\n}\n\n/**\n * Whether a re-converted part renders as the previous pass's did: every own key\n * holds the same value, the source messages assistant-ui binds under a symbol\n * (a new array on every join) compared message by message.\n */\nfunction sameMessagePart(previous: object, next: object): boolean {\n if (previous === next) return true;\n const keys = Reflect.ownKeys(next);\n if (keys.length !== Reflect.ownKeys(previous).length) return false;\n for (const key of keys) {\n if (!Object.prototype.hasOwnProperty.call(previous, key)) return false;\n const before: unknown = Reflect.get(previous, key);\n const after: unknown = Reflect.get(next, key);\n if (Object.is(before, after)) continue;\n const sameSources =\n typeof key === 'symbol' &&\n Array.isArray(before) &&\n Array.isArray(after) &&\n before.length === after.length &&\n before.every((source, index) => Object.is(source, after[index]));\n if (!sameSources) return false;\n }\n return true;\n}\n\n/**\n * This pass's rows, with the previous pass's row for every message the\n * conversion returned unchanged, and the previous row's part objects kept in a\n * re-converted assistant row wherever a part is unchanged.\n *\n * assistant-ui builds every part of a re-converted turn as a new object, and its\n * thread keys each part's state (what a tool card or text part reads) on the\n * part object, so without this every tool card of the turn being generated\n * re-renders on every streamed token although only the streaming text changed.\n */\nfunction keepUnchangedParts({\n previous,\n previousConverted,\n converted,\n}: {\n previous: readonly ThreadMessage[];\n previousConverted: readonly ThreadMessage[];\n converted: readonly ThreadMessage[];\n}): ThreadMessage[] {\n let previousByConverted: Map<ThreadMessage, ThreadMessage> | null = null;\n let previousById: Map<string, ThreadMessage> | null = null;\n return converted.map((message, index) => {\n if (message === previousConverted[index]) return previous[index] ?? message;\n // An unchanged row that moved (a message landed above it) keeps its row too.\n previousByConverted ??= new Map(\n previousConverted.map((row, rowIndex) => [row, previous[rowIndex] ?? row])\n );\n const unchanged = previousByConverted.get(message);\n if (unchanged !== undefined) return unchanged;\n let before: ThreadMessage | undefined = previous[index];\n if (before?.id !== message.id) {\n previousById ??= new Map(previous.map((row) => [row.id, row]));\n before = previousById.get(message.id);\n }\n if (before?.role !== 'assistant' || message.role !== 'assistant') return message;\n const beforeContent = before.content;\n let kept = 0;\n const content = message.content.map((part, partIndex) => {\n const candidate = beforeContent[partIndex];\n if (candidate === undefined || !sameMessagePart(candidate, part)) return part;\n kept += 1;\n return candidate;\n });\n return kept === 0 ? message : { ...message, content };\n });\n}\n\n/** A conversion cache for one attached thread (see {@link DeepAgentMessageConversionCache}). */\nexport function createDeepAgentMessageConversionCache(): DeepAgentMessageConversionCache {\n return new DeepAgentMessageConversionCache();\n}\n\nfunction isSameMessageList(a: readonly ThreadMessage[], b: readonly ThreadMessage[]): boolean {\n return a.length === b.length && a.every((message, index) => message === b[index]);\n}\n\nconst wirePartsToOptimisticParts = (\n parts: StatewireThread.UserMessage['parts']\n): OptimisticContentPart[] =>\n parts.flatMap((part): OptimisticContentPart[] => {\n if (part.type === 'text') return [part];\n // Image attachments travel as `file` parts on the run/* commands; map\n // them back to `image` parts so the optimistic bubble renders them.\n if (part.mediaType.startsWith('image/')) return [{ type: 'image', image: part.url }];\n return [];\n });\n\n/**\n * `metadata.custom` flag marking a bubble that is a projection of a run-lane\n * entry — a message the server holds and has NOT dispatched yet.\n *\n * The lane is server-owned and replicated to every client on the thread, so\n * an item queued on one surface projects onto all of them. Surfaces without a\n * queue panel must therefore be able to tell a queued projection apart from a\n * submitted turn, or a message another device queued renders as if this device\n * had already sent it.\n *\n * `convertLangChainMessages` exposes `additional_kwargs.metadata` (and nothing\n * else) as the thread message's `metadata.custom`.\n */\nexport const ATHENA_QUEUED_LANE_KEY = 'athena_queued_lane';\n\n/**\n * Whether a thread message's `metadata.custom` marks it as an undispatched\n * lane projection. Returns a primitive, so it is safe to use directly as a\n * `useAuiState` selector.\n */\nexport function isQueuedLaneMessage(custom: Record<string, unknown> | undefined): boolean {\n return custom?.[ATHENA_QUEUED_LANE_KEY] === true;\n}\n\ninterface LaneEntryMessageCacheEntry {\n selfAuthor: DeepAgentMessageAuthor | null;\n message: LangChainMessage;\n}\n\n/**\n * Per-entry projections, one map per lane state. Lane entries are wire\n * objects folded copy-on-write (like messages, see {@link liftedMessageCache}),\n * so an unchanged entry projects to the IDENTICAL message on every pass and its\n * bubble keeps its converted identity while it waits in a lane or for its\n * dispatch to land. An entry this client's own unacknowledged command still\n * shapes is rebuilt each time statewire re-applies that optimistic fold, and\n * re-projects until the server acknowledges it.\n */\nconst queuedLaneMessageCache = new WeakMap<\n StatewireThread.RunsQueueEntry,\n LaneEntryMessageCacheEntry\n>();\nconst dispatchedLaneMessageCache = new WeakMap<\n StatewireThread.RunsQueueEntry,\n LaneEntryMessageCacheEntry\n>();\n\n/**\n * Project one run-lane entry into the optimistic human message shape.\n *\n * `queued` marks entries still waiting in a lane; a dispatched entry becomes an\n * ordinary submitted turn and carries no marker. Either way the bubble carries\n * its author (see {@link laneEntryAuthor}) under the same `metadata.custom`\n * key the backend stamps on the persisted turn, so the optimistic and\n * authoritative copies of one send render identically — and a second poster's\n * avatar shows on their own send without waiting for the acknowledgment.\n */\nconst laneEntryMessage = ({\n entry,\n queued,\n selfAuthor,\n}: {\n entry: StatewireThread.RunsQueueEntry;\n queued: boolean;\n selfAuthor: DeepAgentMessageAuthor | null;\n}): LangChainMessage => {\n const cache = queued ? queuedLaneMessageCache : dispatchedLaneMessageCache;\n const cached = cache.get(entry);\n if (cached !== undefined && cached.selfAuthor === selfAuthor) return cached.message;\n const message = buildLaneEntryMessage({ entry, queued, selfAuthor });\n cache.set(entry, { selfAuthor, message });\n return message;\n};\n\nconst buildLaneEntryMessage = ({\n entry,\n queued,\n selfAuthor,\n}: {\n entry: StatewireThread.RunsQueueEntry;\n queued: boolean;\n selfAuthor: DeepAgentMessageAuthor | null;\n}): LangChainMessage => {\n const author = laneEntryAuthor(entry, selfAuthor);\n // The draft a composer send came from, so the optimistic bubble renders the\n // typed text the way its persisted copy will (see `composer-markdown.ts`).\n const composerMarkdown: unknown = (entry.metadata as Record<string, unknown> | undefined)?.[\n DEEP_AGENT_COMPOSER_MARKDOWN_KEY\n ];\n const metadata: Record<string, unknown> = {\n ...(queued ? { [ATHENA_QUEUED_LANE_KEY]: true } : {}),\n ...(author ? { [ATHENA_MESSAGE_AUTHOR_KEY]: author } : {}),\n ...(typeof composerMarkdown === 'string'\n ? { [DEEP_AGENT_COMPOSER_MARKDOWN_KEY]: composerMarkdown }\n : {}),\n };\n return {\n id: entry.id,\n type: 'human',\n content: buildOptimisticHumanContent(wirePartsToOptimisticParts(entry.parts)),\n ...(Object.keys(metadata).length > 0 ? { additional_kwargs: { metadata } } : {}),\n };\n};\n\nexport interface DeepAgentMaterializeOptions {\n /** The viewer, stamped onto this client's own not-yet-acknowledged sends. */\n selfAuthor?: DeepAgentMessageAuthor | null;\n}\n\n/**\n * Materialize fold for the statewire path: makes the server's dispatch record\n * concrete in the LangGraph state itself, so direct state reads see dispatched\n * messages and the lib's post-converter dispatch merge stays off.\n */\nexport const createDeepAgentMaterialize =\n ({ selfAuthor = null }: DeepAgentMaterializeOptions = {}) =>\n (draft: DeepAgentStreamValues): void => {\n runsMaterialize(draft, {\n append: (message) => {\n (draft.messages ??= []).push(\n laneEntryMessage({ entry: message, queued: false, selfAuthor })\n );\n },\n rollback: (to) => {\n if (to === null) {\n draft.messages = [];\n return;\n }\n const messages = draft.messages ?? [];\n const keepIndex = messages.findIndex((message) => message.id === to);\n // Unresolved rollback anchor: fail OPEN with the full history (mirrors the edit fold).\n if (keepIndex !== -1) messages.splice(keepIndex + 1);\n },\n });\n };\n\n/** The materialize fold without a viewer identity (surfaces that pass none). */\nexport const deepAgentMaterialize = createDeepAgentMaterialize();\n\n/**\n * Project run lanes into human messages for the converter tail, so a queued or\n * steered item stays visible below the pending-assistant placeholder until its\n * dispatch. Surfaces with a queue panel project only the steer lane\n * (`'steer-only'`); surfaces without one project both lanes (`'both'`).\n *\n * Every projected entry carries {@link ATHENA_QUEUED_LANE_KEY} so a surface\n * without a queue panel can render it as queued rather than as a submitted\n * turn.\n */\nexport const laneTailMessages = (\n state: unknown,\n laneProjection: DeepAgentLaneProjection = 'both',\n selfAuthor: DeepAgentMessageAuthor | null = null\n): LangChainMessage[] => {\n const run = (state as DeepAgentStreamValues | null)?.runs?.[0];\n if (!run) return [];\n const entries =\n laneProjection === 'steer-only' ? run.steerQueue : [...run.queue, ...run.steerQueue];\n return entries.map((entry) => laneEntryMessage({ entry, queued: true, selfAuthor }));\n};\n\n/** Stable id for the derived running-assistant placeholder. */\nexport const PENDING_ASSISTANT_MESSAGE_ID = '__athena_deep_agent_pending_assistant__';\n\n/**\n * The placeholder itself — empty-array content plus an explicit running\n * status: the thinking shimmer keys on `status running && zero content parts`,\n * and the explicit status keeps it pulsing when lane entries render below (the\n * last-message auto status would demote a mid-list placeholder to an empty\n * complete bubble). One shared, frozen object, so the converted \"Thinking…\" row\n * keeps its identity across every pass until the real reply replaces it.\n */\nconst PENDING_ASSISTANT_MESSAGE: LangChainMessage = Object.freeze({\n type: 'ai',\n content: [],\n id: PENDING_ASSISTANT_MESSAGE_ID,\n status: Object.freeze({ type: 'running' }),\n} satisfies LangChainMessage);\n\n/**\n * Append an empty assistant turn while a run is in flight and the visible\n * history does not already end in one, so the thread shows its \"Thinking…\"\n * status line from the moment a message is submitted.\n *\n * Mirrors `hasUpcomingMessage` in assistant-ui's legacy external-store runtime,\n * which synthesized this placeholder itself. `StatewireThread` renders the\n * converter's message list verbatim, so the placeholder has to come from here —\n * derived from the replicated run status on every render (never written into\n * transport state) so it cannot outlive the run, and carrying a stable id so\n * the streaming bubble's identity does not churn when the real assistant\n * message arrives.\n *\n * `tool` messages convert into assistant turns too, so a mid-run tool result\n * must not produce a second placeholder row.\n */\nexport function withPendingAssistantMessage(\n messages: LangChainMessage[],\n isRunning: boolean\n): LangChainMessage[] {\n if (!isRunning) return messages;\n const lastType = messages.at(-1)?.type;\n if (lastType === 'ai' || lastType === 'tool') return messages;\n // Never persisted, so it cannot outlive the run.\n return [...messages, PENDING_ASSISTANT_MESSAGE];\n}\n\n/** Stable id for the synthetic assistant turn that carries a failure when the run produced none. */\nconst RUN_ERROR_SYNTHETIC_AI_ID = '__athena_deep_agent_run_error__';\n\n/**\n * Surface a run failure the base-correct way: stamp `status: { type:'incomplete',\n * reason:'error', error }` onto the assistant turn so `<Thread/>` renders it via\n * `ErrorPrimitive`. This is the core runtime's model (see `useLanggraphRuntime`\n * `onError`) — NOT a fabricated `⚠️ The run failed` assistant text message, and\n * NOT a magic-string contract shared with the agora backend.\n *\n * Stamps a trailing assistant message; if the visible history does not already\n * end with an assistant turn, appends a synthetic empty assistant turn with a\n * STABLE id so it never churns across renders. A `null` error is a no-op. The\n * shipped `convertLangChainMessages` converter passes `message.status` through.\n */\nexport function applyRunErrorStatus(\n messages: LangChainMessage[],\n errorMessage: string | null\n): LangChainMessage[] {\n if (errorMessage === null) return messages;\n const status = { type: 'incomplete', reason: 'error', error: errorMessage } as const;\n const lastMessage = messages.at(-1);\n if (lastMessage?.type !== 'ai') {\n return [\n ...messages,\n { type: 'ai', content: '', id: RUN_ERROR_SYNTHETIC_AI_ID, status } satisfies LangChainMessage,\n ];\n }\n return [...messages.slice(0, -1), { ...lastMessage, status }];\n}\n\n/**\n * `additional_kwargs` flag the backend stamps on run-lifecycle marker messages\n * (agora `athena_deep_agent/run_markers.py`) — e.g. the \"⏹️ Run stopped by\n * user.\" message persisted when a run is cancelled.\n */\nexport const ATHENA_RUN_MARKER_KEY = 'athena_run_marker';\n\n/** The {@link ATHENA_RUN_MARKER_KEY} value for a user-cancelled run. */\nexport const ATHENA_RUN_MARKER_CANCELLED = 'cancelled';\n\n/**\n * `additional_kwargs` key carrying the stop source behind a cancellation\n * marker (agora `stop_signals.py`: `user_stop`, `send_now`, `transport_cancel`,\n * `unattributed`).\n */\nexport const ATHENA_RUN_MARKER_SOURCE_KEY = 'athena_run_marker_source';\n\n/** The stop source for a \"Send now\" promotion pre-empting the live run. */\nexport const ATHENA_STOP_SOURCE_SEND_NOW = 'send_now';\n\n/**\n * The stop source for a spoken follow-up that redirected a voice-delegated\n * run through the steer lane: the user spoke, they never pressed Stop, so it\n * renders as a redirect rather than as \"Conversation stopped\".\n */\nexport const ATHENA_STOP_SOURCE_VOICE_STEER = 'voice_steer';\n\n/** The stop source for a pod-drain pause (server update), not a user stop. */\nexport const ATHENA_STOP_SOURCE_POD_DRAIN = 'pod_drain';\n\n/**\n * The {@link ATHENA_RUN_MARKER_SOURCE_KEY} value for a run whose hosting\n * instance closed under it (a server update that reached its eviction\n * deadline mid-step, or a placement lost to a lease lapse). Same family as a\n * pod drain: nobody stopped the run, the backend resumes it automatically.\n */\nexport const ATHENA_STOP_SOURCE_INSTANCE_CLOSED = 'instance_closed';\n\n/**\n * Whether a cancellation marker's source names the infrastructure — a pod\n * drain or a closed instance — rather than a person or a client action. These\n * endings read as a pause while the server resumes the run.\n */\nexport function isServerUpdateStopSource(source: string | undefined): boolean {\n return source === ATHENA_STOP_SOURCE_POD_DRAIN || source === ATHENA_STOP_SOURCE_INSTANCE_CLOSED;\n}\n\n/**\n * The {@link ATHENA_RUN_MARKER_KEY} value for a failed run (graph error,\n * including the `GraphRecursionError` \"tool call limit was reached\" case).\n * The marker message's text content carries the user-facing failure copy.\n */\nexport const ATHENA_RUN_MARKER_FAILED = 'failed';\n\n/**\n * `additional_kwargs` key carrying the canonical reason a run ended (agora\n * `athena_deep_agent/termination.py`): `user_stop`, `recursion_limit`,\n * `duplicate_call_guard`, `step_budget`, `ended_after_tools`, `model_refusal`,\n * `completed`, …\n * Stamped on run-lifecycle markers and on handoff messages.\n */\nexport const ATHENA_TERMINATION_REASON_KEY = 'athena_termination_reason';\n\n/**\n * `additional_kwargs` flag on the closing message the runtime's **handoff\n * turn** wrote (one tools-disabled model call made when a guard or the step\n * budget ends the run). The message text is the agent's closing summary; the\n * flag lets clients frame it as a runtime ending rather than a normal reply.\n */\nexport const ATHENA_HANDOFF_KEY = 'athena_handoff';\n\n/**\n * Read a run-lifecycle marker out of a thread message's **bound source\n * messages**, bypassing the converter's metadata join entirely.\n *\n * Why this exists: assistant-ui chunks consecutive assistant/tool messages\n * into ONE `ThreadMessage`, and the upstream join copies `metadata.custom`\n * only from the FIRST assistant message of the chunk\n * (`external-message-converter.ts`: the merge sits inside\n * `if (assistantMessage.content.length === 0)`). A failure marker appended\n * after the assistant's own turn is therefore never the first message in its\n * chunk, so `message.metadata.custom.athena_run_marker` is silently dropped\n * and the whole-message gate can't fire.\n *\n * `getExternalStoreMessages` returns the raw LangChain messages bound to the\n * joined thread message, so the marker is found wherever it sits in the chunk.\n * Returns the marker value plus its text content (the user-facing copy).\n */\nexport interface RunMarkerInfo {\n marker: string;\n /** The stop source stamped by the backend, when the marker carries one. */\n source: string | undefined;\n /** The marker message's text — the user-facing copy. */\n text: string | undefined;\n /**\n * True when the marker is the ONLY source message behind this thread message,\n * i.e. it owns the whole row (a cancelled run drops its partial turn, so its\n * marker stands alone).\n *\n * False when the chunk also holds the assistant's own work. Replacing the row\n * in that case would hide completed prose and tool cards — the marker must be\n * styled in place instead.\n */\n isSoleSourceMessage: boolean;\n}\n\nexport function readRunMarkerFromSourceMessages(\n message: Parameters<typeof getExternalStoreMessages>[0] & {\n metadata?: { custom?: Record<string, unknown> | undefined } | undefined;\n content?: unknown;\n }\n): RunMarkerInfo | undefined {\n const sources = getExternalStoreMessages<LangChainMessage>(message);\n for (const source of sources) {\n if (source?.type !== 'ai') continue;\n const kwargs = source.additional_kwargs as Record<string, unknown> | undefined;\n const marker = kwargs?.[ATHENA_RUN_MARKER_KEY];\n if (typeof marker !== 'string') continue;\n const markerSource = kwargs?.[ATHENA_RUN_MARKER_SOURCE_KEY];\n return {\n marker,\n source: typeof markerSource === 'string' ? markerSource : undefined,\n text: typeof source.content === 'string' ? source.content : undefined,\n isSoleSourceMessage: sources.length === 1,\n };\n }\n // Fallback for surfaces whose messages carry no bound source messages (or a\n // converter that already merged the flag): read the joined metadata. Without\n // sources to count, a single text part is the best available \"marker owns the\n // row\" signal.\n const joined = message.metadata?.custom?.[ATHENA_RUN_MARKER_KEY];\n if (typeof joined !== 'string') return undefined;\n const joinedSource = message.metadata?.custom?.[ATHENA_RUN_MARKER_SOURCE_KEY];\n const parts = message.content;\n return {\n marker: joined,\n source: typeof joinedSource === 'string' ? joinedSource : undefined,\n text: undefined,\n isSoleSourceMessage: Array.isArray(parts) ? parts.length <= 1 : true,\n };\n}\n\n/**\n * The marker value when the marker is the SOLE source message behind a thread\n * message (it owns the whole row), else `undefined`.\n *\n * Returns a primitive so it is safe to use directly as a `useAuiState`\n * selector: `readRunMarkerFromSourceMessages` builds a fresh object on every\n * call, and selector snapshots are compared with `Object.is` — returning that\n * object from a selector makes `getSnapshot` unstable and loops the render.\n */\nexport function readSoleRunMarker(\n message: Parameters<typeof readRunMarkerFromSourceMessages>[0]\n): string | undefined {\n const found = readRunMarkerFromSourceMessages(message);\n return found?.isSoleSourceMessage ? found.marker : undefined;\n}\n\n/**\n * Whether a run marker renders nothing: a `send_now` promotion cancels the\n * live run as plumbing, not as a user-visible stop, so its cancellation\n * marker stays in the data for provenance but paints no divider.\n */\nexport function isSilentRunMarker(found: RunMarkerInfo): boolean {\n return (\n found.marker === ATHENA_RUN_MARKER_CANCELLED && found.source === ATHENA_STOP_SOURCE_SEND_NOW\n );\n}\n\n/**\n * Whether a cancellation marker records a server-update pause — a cooperative\n * pod drain, or the hosting instance closing under the run — rather than a\n * stop the user asked for. Rendered with distinct \"paused by a server update\"\n * copy instead of the \"Conversation stopped\" pill; the backend resumes the run.\n */\nexport function isDrainRunMarker(found: RunMarkerInfo): boolean {\n return found.marker === ATHENA_RUN_MARKER_CANCELLED && isServerUpdateStopSource(found.source);\n}\n\n/**\n * Surface the backend's top-level `additional_kwargs.athena_run_marker` flag\n * where the shipped converter can see it.\n *\n * `convertLangChainMessages` exposes ONLY `additional_kwargs.metadata` as the\n * thread message's `metadata.custom`; every other `additional_kwargs` key is\n * dropped. Marker messages persisted before the backend mirrored the flag into\n * `metadata` carry it top-level only, so this lift keeps those threads styling\n * correctly. Non-marker messages pass through with their identity unchanged,\n * and already-mirrored markers are left as-is (idempotent).\n */\nexport function liftRunMarkerIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'ai') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const marker = kwargs?.[ATHENA_RUN_MARKER_KEY];\n if (typeof marker !== 'string') return message;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (metadata[ATHENA_RUN_MARKER_KEY] === marker) return message;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: { ...metadata, [ATHENA_RUN_MARKER_KEY]: marker },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Mirror the termination reason and handoff flag into the converter's metadata\n * channel. The backend dual-writes both (top-level + `metadata`), so this lift\n * is the back-compat path for messages persisted before the mirror existed and\n * for any emitter that only stamps the top-level key. Idempotent; identity for\n * messages without the keys.\n */\nexport function liftTerminationIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'ai') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const reason = kwargs?.[ATHENA_TERMINATION_REASON_KEY];\n const handoff = kwargs?.[ATHENA_HANDOFF_KEY];\n const hasReason = typeof reason === 'string';\n const hasHandoff = handoff === true;\n if (!hasReason && !hasHandoff) return message;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n const reasonMirrored = !hasReason || metadata[ATHENA_TERMINATION_REASON_KEY] === reason;\n const handoffMirrored = !hasHandoff || metadata[ATHENA_HANDOFF_KEY] === true;\n if (reasonMirrored && handoffMirrored) return message;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: {\n ...metadata,\n ...(hasReason ? { [ATHENA_TERMINATION_REASON_KEY]: reason } : {}),\n ...(hasHandoff ? { [ATHENA_HANDOFF_KEY]: true } : {}),\n },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * `additional_kwargs` key (and `metadata` mirror) carrying a failure marker's\n * structured error records (agora `athena_deep_agent/run_markers.py`\n * `failure_error_records`): `[{ error, provider?, message? }]`, where `error`\n * is the client code a surface branches on — `context_length_exceeded`,\n * `transient_api_error` (with `provider: 'azure_foundry' |\n * 'external_model_provider'`), `content_filter`, `request_too_large`, … — the\n * same shape the legacy agent node emitted.\n */\nexport const ATHENA_ERRORS_KEY = 'errors';\n\n/**\n * Mirror a failure marker's structured `errors` records into the converter's\n * metadata channel. The backend dual-writes them (top-level + `metadata`), so\n * this lift is the back-compat path for messages that carry only the top-level\n * key (an emitter that never mirrored, or a record persisted before the mirror\n * existed). Records already present in the metadata channel win; no-ops are\n * identity.\n */\nexport function liftErrorsIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'ai') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const errors = kwargs?.[ATHENA_ERRORS_KEY];\n if (!Array.isArray(errors) || errors.length === 0) return message;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (Array.isArray(metadata[ATHENA_ERRORS_KEY])) return message;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: { ...metadata, [ATHENA_ERRORS_KEY]: errors },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Read a failure's structured `errors` records out of a thread message's\n * **bound source messages**, bypassing the converter's metadata join.\n *\n * Same reason as {@link readRunMarkerFromSourceMessages}: assistant-ui chunks\n * consecutive assistant/tool messages into ONE `ThreadMessage` and copies\n * `metadata.custom` only from the FIRST assistant message of the chunk. The\n * failure marker that carries the records is appended after the assistant's\n * own turn, so `message.metadata.custom.errors` is silently dropped whenever\n * the run produced anything before failing. The bound sources are walked from\n * the tail (the terminal failure wins); the joined metadata is the fallback\n * for surfaces whose messages carry no bound sources. Returns the raw records\n * (callers validate the shape) or `undefined`.\n */\nexport function readMessageErrorsFromSourceMessages(\n message: Parameters<typeof getExternalStoreMessages>[0] & {\n metadata?: { custom?: Record<string, unknown> | undefined } | undefined;\n }\n): unknown[] | undefined {\n const sources = getExternalStoreMessages<LangChainMessage>(message);\n for (let index = sources.length - 1; index >= 0; index -= 1) {\n const source = sources[index];\n if (source?.type !== 'ai') continue;\n const kwargs = source.additional_kwargs as Record<string, unknown> | undefined;\n const metadata = kwargs?.metadata;\n const mirrored =\n typeof metadata === 'object' && metadata !== null\n ? (metadata as Record<string, unknown>)[ATHENA_ERRORS_KEY]\n : undefined;\n const errors = Array.isArray(mirrored) ? mirrored : kwargs?.[ATHENA_ERRORS_KEY];\n if (Array.isArray(errors) && errors.length > 0) return errors;\n }\n const joined = message.metadata?.custom?.[ATHENA_ERRORS_KEY];\n return Array.isArray(joined) && joined.length > 0 ? joined : undefined;\n}\n\n/**\n * Mirror the producing model into the converter's metadata channel. Wire AI\n * messages are full LangChain dumps whose `response_metadata` reports the model\n * that generated them (`model_name` / `model`), but the shipped converter maps\n * only `additional_kwargs.metadata` into `metadata.custom` — so without this\n * lift a surface can only show the CURRENT run config's model, mislabeling\n * earlier messages after a mid-thread model switch. A model already present in\n * the metadata channel (backend-stamped provenance) wins; no-ops are identity.\n */\nexport function liftModelIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'ai') return message;\n const responseMetadata = (\n message as { response_metadata?: { model_name?: unknown; model?: unknown } }\n ).response_metadata;\n let model: string | undefined;\n if (typeof responseMetadata?.model_name === 'string' && responseMetadata.model_name) {\n model = responseMetadata.model_name;\n } else if (typeof responseMetadata?.model === 'string' && responseMetadata.model) {\n model = responseMetadata.model;\n }\n if (!model) return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (typeof metadata.model === 'string' && metadata.model) return message;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: { ...metadata, model },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Mirror a per-message conversation-stats snapshot into the converter's\n * metadata channel. Backends stamp the snapshot at the legacy location —\n * top-level `additional_kwargs.conversation_stats` (the deep-agent stats\n * middleware and the legacy agent node both write it there) — but the shipped\n * converter maps only `additional_kwargs.metadata` into `metadata.custom`, so\n * without this lift the model badge can never show a message's OWN context and\n * falls back to thread-level live numbers. A snapshot already present in the\n * metadata channel wins; no-ops are identity.\n */\nexport function liftConversationStatsIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'ai') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const stats = kwargs?.conversation_stats;\n if (typeof stats !== 'object' || stats === null) return message;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (typeof metadata.conversation_stats === 'object' && metadata.conversation_stats !== null) {\n return message;\n }\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: { ...metadata, conversation_stats: stats },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Mirror the compaction boundary flag into the converter's metadata channel.\n * A manual `/compact` persists its summary as a human message tagged with\n * top-level `additional_kwargs.compaction_start` (plus `_created_at` for the\n * divider timestamp) — the same location the legacy runtime used — but the\n * shipped converter maps only `additional_kwargs.metadata` into\n * `metadata.custom`, so without this lift the transcript's\n * \"Previous conversation summarized\" divider can never render. A flag already\n * present in the metadata channel wins; no-ops are identity.\n */\nexport function liftCompactionBoundaryIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'human') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n if (kwargs?.compaction_start !== true) return message;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (metadata.compaction_start === true) return message;\n const createdAt = kwargs?._created_at;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: {\n ...metadata,\n compaction_start: true,\n ...(typeof createdAt === 'string' ? { _created_at: createdAt } : {}),\n },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Mirror the handoff summary flag into the converter's metadata channel.\n * Handoff summaries are persisted as human messages tagged with top-level\n * `additional_kwargs.handoff_summary` (plus `_created_at`) — the same location\n * the backend uses for message metadata — but the shipped converter maps only\n * `additional_kwargs.metadata` into `metadata.custom`, so without this lift\n * the summary would render as a normal user bubble. A flag already present in\n * the metadata channel wins; no-ops are identity.\n */\nexport function liftHandoffSummaryIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'human') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n if (kwargs?.handoff_summary !== true) return message;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (metadata.handoff_summary === true) return message;\n const createdAt = kwargs?._created_at;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: {\n ...metadata,\n handoff_summary: true,\n ...(typeof createdAt === 'string' ? { _created_at: createdAt } : {}),\n },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Mirror an AOP run's provenance into the converter's metadata channel. The\n * backend stamps the kicking-off user message with top-level\n * `additional_kwargs.aop_source` (agora `events/helpers.py`\n * `_build_aop_source_metadata`), which the legacy converter surfaced as\n * `metadata.custom.aop_source` — where the \"AOP · <title>\" badge reads it. The\n * shipped `convertLangChainMessages` maps only `additional_kwargs.metadata`\n * into `metadata.custom`, so without this lift an AOP run's conversation never\n * shows which AOP asset kicked it off. Provenance already present in the\n * metadata channel wins; no-ops are identity.\n */\nexport function liftAopSourceIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'human') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const aopSource = kwargs?.aop_source;\n if (typeof aopSource !== 'object' || aopSource === null || Array.isArray(aopSource)) {\n return message;\n }\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (typeof metadata.aop_source === 'object' && metadata.aop_source !== null) return message;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: { ...metadata, aop_source: aopSource },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\nexport function liftBranchIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'human' && message.type !== 'ai') return message;\n const branch = (message as { branch?: unknown }).branch;\n if (typeof branch !== 'object' || branch === null || Array.isArray(branch)) return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (typeof metadata.branch === 'object' && metadata.branch !== null) return message;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: { ...metadata, branch },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * `additional_kwargs` keys on a ToolMessage that are infrastructure rather than\n * user-set metadata. The filter below additionally skips every `_`-prefixed key.\n */\nconst TOOL_MESSAGE_INTERNAL_KWARGS: ReadonlySet<string> = new Set([\n 'reasoning',\n 'tool_outputs',\n 'hide_from_frontend',\n]);\n\n/**\n * Mirror a ToolMessage's user-set `additional_kwargs` into\n * `artifact._tool_message_metadata` — the location `useToolCallMetadata` reads\n * persisted per-tool-call metadata from (`email_sent_at`, draft edits,\n * `calendar_event_created_at`, …).\n *\n * The `setMessageMetadata` mutation persists those keys onto the ToolMessage's\n * `additional_kwargs` in the checkpoint, and the legacy converter lifted them\n * into the artifact. The shipped `convertLangChainMessages` forwards only\n * `artifact` and drops `additional_kwargs` for tool messages, so without this\n * lift the metadata survives in the checkpoint but never reaches a tool card\n * after a reload. Kwargs values win over same-key artifact entries (the kwargs\n * are the durable write target); messages with no user-set kwargs pass through\n * by identity.\n */\nexport function liftToolMessageMetadataIntoArtifact(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'tool') return message;\n const kwargs = (message as { additional_kwargs?: Record<string, unknown> }).additional_kwargs;\n if (!kwargs) return message;\n const lifted: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(kwargs)) {\n if (key.startsWith('_') || TOOL_MESSAGE_INTERNAL_KWARGS.has(key)) continue;\n lifted[key] = value;\n }\n if (Object.keys(lifted).length === 0) return message;\n const artifact: unknown = message.artifact;\n const artifactRecord =\n typeof artifact === 'object' && artifact !== null && !Array.isArray(artifact)\n ? (artifact as Record<string, unknown>)\n : {};\n const existing = artifactRecord._tool_message_metadata;\n const existingRecord =\n typeof existing === 'object' && existing !== null && !Array.isArray(existing)\n ? (existing as Record<string, unknown>)\n : {};\n return {\n ...message,\n artifact: {\n ...artifactRecord,\n _tool_message_metadata: { ...existingRecord, ...lifted },\n },\n };\n}\n\n/**\n * Lift a windowed wire's tool-output truncation marker into the tool-call\n * part's `artifact` (`_athena_content_truncated: { chars, messageId }`).\n *\n * The host cuts long plain-text tool outputs on a windowed wire and stamps\n * `athena_content_truncated` on the wire message; the shipped converter joins\n * a ToolMessage into its assistant turn's tool-call part and keeps only\n * `artifact` and the result, so without this lift no tool card could tell the\n * output is cut nor which message to ask `history/tool_output` for. Messages\n * without the marker pass through by identity.\n */\nexport function liftTruncatedToolOutputIntoArtifact(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'tool') return message;\n const marker = readTruncatedToolContent(message);\n if (marker === null || typeof message.id !== 'string') return message;\n const artifact: unknown = message.artifact;\n const artifactRecord =\n typeof artifact === 'object' && artifact !== null && !Array.isArray(artifact)\n ? (artifact as Record<string, unknown>)\n : {};\n return {\n ...message,\n artifact: {\n ...artifactRecord,\n [ATHENA_TRUNCATED_TOOL_OUTPUT_ARTIFACT_KEY]: { chars: marker.chars, messageId: message.id },\n },\n };\n}\n\n/**\n * A content list's bare-string entries as typed text parts.\n *\n * LangChain content is `string | (string | ContentBlock)[]`, and a model with\n * no tools bound streams exactly that mix: `langchain_anthropic` coerces text\n * deltas to plain strings when no tools are bound, yet a thinking block still\n * arrives as a list block, so the merged reply is `[\"\", {thinking}, \"text\"]`\n * (the side-question lane's bare model). The shipped converter maps only typed\n * blocks and drops the strings, leaving the reasoning with no answer. Empty\n * strings are dropped; messages with no string entries pass through by\n * identity.\n */\nexport function normalizeStringContentParts(message: LangChainMessage): LangChainMessage {\n const content: unknown = message.content;\n if (!Array.isArray(content) || !content.some((part) => typeof part === 'string')) {\n return message;\n }\n const parts = content.flatMap((part: unknown) => {\n if (typeof part !== 'string') return [part];\n return part.length > 0 ? [{ type: 'text', text: part }] : [];\n });\n return { ...message, content: parts } as LangChainMessage;\n}\n\n/**\n * Per-source cache for {@link substituteToolOutput}: a substituted copy is\n * reused while the wire message and the fetched output are both unchanged, so\n * the lift cache downstream keeps its identity contract.\n */\nconst substitutedToolOutputCache = new WeakMap<\n LangChainMessage,\n { content: unknown; substituted: LangChainMessage }\n>();\n\n/**\n * A cut tool message with its full output put back, when the store holds one\n * fetched for this very output (`history/tool_output` answered for the same\n * `chars` the wire's marker reports); the marker leaves with the cut.\n * Messages that are whole, whose output has not been fetched, or whose fetched\n * output belongs to a length the host has since replaced, pass through by\n * identity — the loader sees the mismatch and fetches again.\n */\nexport function substituteToolOutput(\n message: LangChainMessage,\n toolOutputs: DeepAgentToolOutputStore\n): LangChainMessage {\n if (message.type !== 'tool' || typeof message.id !== 'string') return message;\n const marker = readTruncatedToolContent(message);\n if (marker === null) return message;\n const entry = toolOutputs.entry(message.id);\n if (entry === undefined || entry.chars !== marker.chars) return message;\n const { content } = entry;\n const cached = substitutedToolOutputCache.get(message);\n if (cached !== undefined && cached.content === content) return cached.substituted;\n const { [DEEP_AGENT_TRUNCATED_CONTENT_KEY]: _marker, ...rest } = message as Record<\n string,\n unknown\n >;\n const substituted = { ...rest, content } as unknown as LangChainMessage;\n substitutedToolOutputCache.set(message, { content, substituted });\n return substituted;\n}\n\nconst hasHiddenFrontendFlag = (value: unknown): boolean => {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return false;\n }\n const record = value as Record<string, unknown>;\n if (record.hide_from_frontend === true) return true;\n return hasHiddenFrontendFlag(record.custom);\n};\n\n/**\n * Internal orchestration messages are useful in a trace export but must never\n * become conversation bubbles. Agora stores the flag in `additional_kwargs`;\n * live converter state can expose the same value under `metadata.custom`.\n */\nexport function isMessageHiddenFromFrontend(message: unknown): boolean {\n if (typeof message !== 'object' || message === null || Array.isArray(message)) {\n return false;\n }\n const record = message as Record<string, unknown>;\n return (\n hasHiddenFrontendFlag(record.additional_kwargs) || hasHiddenFrontendFlag(record.metadata)\n );\n}\n\n/**\n * The message `type`s `convertLangChainMessages` actually maps: its\n * `switch (message.type)` covers exactly these four and has no `default`, so it\n * returns `undefined` for anything else (a `RemoveMessage` `type: \"remove\"`\n * from summarization/compaction, a partial chunk, a future type).\n */\nconst CONVERTIBLE_MESSAGE_TYPES: ReadonlySet<string> = new Set(['system', 'human', 'ai', 'tool']);\n\n/**\n * Whether `convertLangChainMessages` can map this message at all.\n *\n * An unmappable entry converts to `undefined`, which assistant-ui's\n * `convertExternalMessages` wraps as `outputs: [undefined]` and then reads\n * `output.role` off — throwing `Cannot read property 'role' of undefined` and\n * taking the entire thread down instead of dropping one message.\n */\nexport function isConvertibleChatMessage(message: unknown): boolean {\n if (typeof message !== 'object' || message === null || Array.isArray(message)) {\n return false;\n }\n const type: unknown = (message as Record<string, unknown>).type;\n return typeof type === 'string' && CONVERTIBLE_MESSAGE_TYPES.has(type);\n}\n\n/**\n * Per-source cache for the lift pipeline in {@link sanitizeLangChainMessages}.\n *\n * The pipeline runs on EVERY state pass (once per streamed token), and the two\n * lifts that fire for essentially every message — {@link liftBranchIntoMetadata}\n * (the backend stamps `branch` on all indexed messages) and\n * {@link liftModelIntoMetadata} — allocate a fresh object each time they lift.\n * Without a cache, every message's identity churns on every token, defeating\n * all downstream per-source memoization (`getExternalStoreMessages`-keyed\n * reads, converter callback caches) and re-deriving the whole thread per token.\n *\n * Object identity is a sound cache key here: every statewire fold (op apply,\n * optimistic, materialize) runs through `statewire`'s copy-on-write\n * `recordMutations`, whose `finalize` returns the ORIGINAL object for\n * untouched nodes and a fresh copy for touched ones. A message is therefore\n * REPLACED — never mutated in place — whenever any of its fields change\n * (including the streaming tail's string-insert content growth), and an\n * unchanged message keeps its exact identity across passes. The corpus test\n * (`statewire-corpus.test.ts`) pins this identity contract against the real\n * client, so a statewire upgrade that switched to in-place mutation would fail\n * there rather than silently serving stale lifts from this cache.\n *\n * Module-level on purpose: keys are the wire message objects themselves, so\n * entries die with their state trees and threads cannot collide.\n */\nconst liftedMessageCache = new WeakMap<LangChainMessage, LangChainMessage>();\n\n/** The full lift chain for one message, memoized per source object. */\nfunction liftMessageIntoMetadataChannels(message: LangChainMessage): LangChainMessage {\n const cached = liftedMessageCache.get(message);\n if (cached !== undefined) return cached;\n const lifted = liftBranchIntoMetadata(\n liftTruncatedToolOutputIntoArtifact(\n liftToolMessageMetadataIntoArtifact(\n liftAopSourceIntoMetadata(\n liftHandoffSummaryIntoMetadata(\n liftCompactionBoundaryIntoMetadata(\n liftConversationStatsIntoMetadata(\n liftModelIntoMetadata(\n liftErrorsIntoMetadata(\n liftTerminationIntoMetadata(\n liftRunMarkerIntoMetadata(normalizeStringContentParts(message))\n )\n )\n )\n )\n )\n )\n )\n )\n )\n );\n liftedMessageCache.set(message, lifted);\n return lifted;\n}\n\n/**\n * The one message pipeline every deep-agent surface renders through:\n *\n * 1. drop messages the shipped converter cannot map (`RemoveMessage` etc. —\n * an unmappable entry would crash the whole thread) and internal\n * orchestration messages flagged hidden-from-frontend;\n * 1a. turn bare-string entries of a content list into typed text parts (see\n * {@link normalizeStringContentParts}) so a tool-less model's answer next\n * to its thinking block is not dropped by the converter;\n * 2. lift run-lifecycle markers into the converter's `metadata` channel\n * (see {@link liftRunMarkerIntoMetadata}) so the cancellation row styles as\n * a status divider instead of a plain assistant bubble, and the termination\n * reason + handoff flag alongside them (see\n * {@link liftTerminationIntoMetadata}) so every surface can render a\n * runtime ending (see `runtime-messages.ts`);\n * 3. lift each AI message's producing model into the same channel\n * (see {@link liftModelIntoMetadata}) so model badges reflect the model that\n * actually generated the message, not the currently configured one;\n * 4. lift each AI message's per-message conversation-stats snapshot into the\n * same channel (see {@link liftConversationStatsIntoMetadata}) so the model\n * badge's hover card shows that message's own context usage;\n * 5. lift the compaction boundary flag on `/compact` summary messages into the\n * same channel (see {@link liftCompactionBoundaryIntoMetadata}) so the\n * transcript renders its \"Previous conversation summarized\" divider;\n * 6. lift the handoff summary flag on carried-over context messages into the\n * same channel (see {@link liftHandoffSummaryIntoMetadata}) so the\n * transcript keeps the summary behind its context panel;\n * 7. lift an AOP run's provenance on its kicking-off user message into the\n * same channel (see {@link liftAopSourceIntoMetadata}) so the message\n * renders its \"AOP · <title>\" origin badge;\n * 8. lift each ToolMessage's user-set `additional_kwargs` into\n * `artifact._tool_message_metadata` (see\n * {@link liftToolMessageMetadataIntoArtifact}) so persisted per-tool-call\n * metadata reaches tool cards after a reload;\n * 9. lift a windowed wire's tool-output truncation marker into the tool-call\n * part's `artifact` (see {@link liftTruncatedToolOutputIntoArtifact}) so a\n * tool card can fetch the whole output — after first putting back any\n * output already fetched (see {@link substituteToolOutput});\n * 10. lift the host's per-message branch stamp into the same channel\n * (see {@link liftBranchIntoMetadata}) so the branch picker renders each\n * message's edit fan-out.\n *\n * Identity-stable per source: an unchanged source message maps to the\n * IDENTICAL lifted object across passes (see {@link liftedMessageCache}).\n */\nexport function sanitizeLangChainMessages(\n messages: readonly LangChainMessage[],\n { toolOutputs }: { toolOutputs?: DeepAgentToolOutputStore } = {}\n): LangChainMessage[] {\n return messages\n .filter((message) => isConvertibleChatMessage(message) && !isMessageHiddenFromFrontend(message))\n .map((message) =>\n liftMessageIntoMetadataChannels(\n toolOutputs ? substituteToolOutput(message, toolOutputs) : message\n )\n );\n}\n\n/** Type guard for the runtime state — an object carrying a `messages` array. */\nexport function isLangGraphState(value: unknown): value is LangGraphState {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'messages' in value &&\n Array.isArray((value as LangGraphState).messages)\n );\n}\n\nexport interface ConvertDeepAgentStateOptions {\n laneProjection?: DeepAgentLaneProjection;\n /**\n * The viewer, as the backend would stamp them: the author of this client's\n * own not-yet-acknowledged lane entries (see {@link laneEntryAuthor}).\n */\n selfAuthor?: DeepAgentMessageAuthor | null;\n /**\n * Full tool outputs fetched back for a windowed wire's cut tool messages\n * (see {@link substituteToolOutput}); omitted on surfaces that never\n * declare a window.\n */\n toolOutputs?: DeepAgentToolOutputStore;\n /**\n * User turns this client took back ahead of the wire (see\n * {@link DeepAgentWithdrawnTurns}): omitted from the transcript, together\n * with the pending-assistant placeholder a withdrawn tail would earn.\n */\n withdrawnTurns?: DeepAgentWithdrawnTurns;\n /**\n * The attached thread's conversion memory (see\n * {@link createDeepAgentMessageConversionCache}): unchanged messages keep\n * their converted identity across passes. Without one every pass converts\n * the whole transcript from scratch.\n */\n conversionCache?: DeepAgentMessageConversionCache;\n}\n\n/**\n * Map server state → assistant-ui thread messages.\n *\n * `state.messages` is the authoritative LangChain history streamed by the\n * backend. The replicated run status (`runs[0].status`) drives the\n * pending-assistant placeholder and the running status. Lane entries render at\n * the tail, after the placeholder, while the run is live (the run is still\n * working the history above them); once the thread rests stopped/errored they\n * are omitted (surfaces with a queue panel list them there instead).\n * Consecutive spoken user turns collapse into one user message (see\n * {@link groupSpokenUserTurns}) before conversion.\n */\nexport function convertDeepAgentState(\n state: unknown,\n {\n laneProjection = 'both',\n selfAuthor = null,\n toolOutputs,\n withdrawnTurns,\n conversionCache,\n }: ConvertDeepAgentStateOptions = {}\n) {\n // A wire without a snapshot yet (pre-attach, replay boundary) converts as the\n // typed pre-stream default: an empty resting thread, never `undefined`.\n const values = readDeepAgentStreamValues(state);\n const status = values.runs?.[0]?.status;\n const isRunning = status === 'running';\n const isWithdrawn = (message: LangChainMessage): boolean =>\n withdrawnTurns !== undefined && message.id !== undefined && withdrawnTurns.isWithdrawn(message.id);\n const laneTail = (\n status === 'stopped' || status === 'error'\n ? []\n : laneTailMessages(values, laneProjection, selfAuthor)\n ).filter((message) => !isWithdrawn(message));\n const sanitized: LangChainMessage[] = isLangGraphState(values)\n ? sanitizeLangChainMessages(values.messages, { toolOutputs })\n : [];\n // A withdrawn tail is a turn nobody is answering: no placeholder for it.\n const tail: LangChainMessage | undefined = sanitized[sanitized.length - 1];\n const tailWithdrawn: boolean = tail !== undefined && isWithdrawn(tail);\n const stateMessages = groupSpokenUserTurns(sanitized.filter((message) => !isWithdrawn(message)));\n const sources = [\n ...withPendingAssistantMessage(stateMessages, isRunning && !tailWithdrawn),\n ...laneTail,\n ];\n return {\n messages: conversionCache\n ? conversionCache.convert(sources, isRunning)\n : messageConverter.toThreadMessages(sources, isRunning),\n isRunning,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -52,17 +52,33 @@ const storedCommandCompatible = (protocol, command, current) => {
|
|
|
52
52
|
const offer = current.protocols.find((p) => p.name === protocol);
|
|
53
53
|
return produced !== void 0 && offer !== void 0 && inRange(produced.version, offer);
|
|
54
54
|
};
|
|
55
|
+
const STORED_STATEMENT_WIRE_FLOOR = "2026-09-13";
|
|
56
|
+
const replayableOnCurrentWire = (protocol, command, current) => command.produced !== void 0 && inRange(command.produced.wire.version, {
|
|
57
|
+
version: current.wire.version,
|
|
58
|
+
minVersion: STORED_STATEMENT_WIRE_FLOOR
|
|
59
|
+
}) && storedCommandCompatible(
|
|
60
|
+
protocol,
|
|
61
|
+
{ ...command, produced: { ...command.produced, wire: current.wire } },
|
|
62
|
+
current
|
|
63
|
+
);
|
|
55
64
|
function parkIncompatibleSessionCommands(record, current, now = Date.now()) {
|
|
56
65
|
let parked = 0;
|
|
66
|
+
let restamped = 0;
|
|
57
67
|
const lanes = {};
|
|
58
68
|
for (const [protocol, lane] of Object.entries(record.lanes)) {
|
|
59
69
|
const kept = [];
|
|
60
70
|
const failed = [];
|
|
71
|
+
let laneRestamped = 0;
|
|
61
72
|
for (const command of lane.commands) {
|
|
62
73
|
if (storedCommandCompatible(protocol, command, current)) {
|
|
63
74
|
kept.push(command);
|
|
64
75
|
continue;
|
|
65
76
|
}
|
|
77
|
+
if (replayableOnCurrentWire(protocol, command, current)) {
|
|
78
|
+
kept.push({ ...command, produced: current });
|
|
79
|
+
laneRestamped += 1;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
66
82
|
failed.push({
|
|
67
83
|
protocol,
|
|
68
84
|
produced: current,
|
|
@@ -75,13 +91,16 @@ function parkIncompatibleSessionCommands(record, current, now = Date.now()) {
|
|
|
75
91
|
});
|
|
76
92
|
}
|
|
77
93
|
parked += failed.length;
|
|
78
|
-
|
|
94
|
+
restamped += laneRestamped;
|
|
95
|
+
lanes[protocol] = failed.length === 0 && laneRestamped === 0 ? lane : {
|
|
79
96
|
...lane,
|
|
80
97
|
commands: kept,
|
|
81
|
-
failed
|
|
98
|
+
...(failed.length > 0 || lane.failed !== void 0) && {
|
|
99
|
+
failed: [...lane.failed ?? [], ...failed]
|
|
100
|
+
}
|
|
82
101
|
};
|
|
83
102
|
}
|
|
84
|
-
return parked === 0 ? { record, parked } : { record: { ...record, lanes }, parked };
|
|
103
|
+
return parked === 0 && restamped === 0 ? { record, parked, restamped } : { record: { ...record, lanes }, parked, restamped };
|
|
85
104
|
}
|
|
86
105
|
function isStorageQuotaError(error) {
|
|
87
106
|
if (typeof error !== "object" || error === null) return false;
|
|
@@ -372,6 +391,7 @@ const useQuotaSafeLocalStorageSession = ({
|
|
|
372
391
|
const migrate = react.useCallback(
|
|
373
392
|
(record, current) => {
|
|
374
393
|
const migrated = parkIncompatibleSessionCommands(record, current);
|
|
394
|
+
if (migrated.parked === 0) return migrated.record;
|
|
375
395
|
reportDegradation({
|
|
376
396
|
operation: "migrate",
|
|
377
397
|
key: storageKey,
|
|
@@ -431,6 +451,7 @@ const QuotaSafeLocalStorageSession = tap.resource(
|
|
|
431
451
|
useQuotaSafeLocalStorageSession
|
|
432
452
|
);
|
|
433
453
|
exports.QuotaSafeLocalStorageSession = QuotaSafeLocalStorageSession;
|
|
454
|
+
exports.STORED_STATEMENT_WIRE_FLOOR = STORED_STATEMENT_WIRE_FLOOR;
|
|
434
455
|
exports.isStorageQuotaError = isStorageQuotaError;
|
|
435
456
|
exports.parkIncompatibleSessionCommands = parkIncompatibleSessionCommands;
|
|
436
457
|
exports.quotaTolerantBackend = quotaTolerantBackend;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quota-safe-local-storage-session.cjs","sources":["../../../../agent-runtime/src/quota-safe-local-storage-session.ts"],"sourcesContent":["import { resource, useResource } from \"@assistant-ui/tap\";\nimport type { StatewireClient, StatewireProtocol } from \"statewire\";\nimport { KeyValueSessionStorage } from \"statewire/storage\";\nimport { useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { DEEP_AGENT_STATEWIRE_PROTOCOL } from \"./statewire-transport\";\n\nconst SESSION_PREFIX = \"aui:statewire-session:\";\nconst SESSION_LOCK_PREFIX = \"aui:statewire-session-lock:\";\nconst SESSION_ACTIVE_PREFIX = \"aui:statewire-session-active:\";\nconst DAY_MS = 24 * 60 * 60 * 1000;\n/** The stored-record formats the statewire this build ships reads (it writes 3). */\nconst READABLE_STORAGE_VERSIONS: ReadonlySet<unknown> = new Set([1, 2, 3]);\n\n/** Structural view of the browser storage global — this package compiles\n * without the DOM lib (React Native honesty; see `globals.d.ts`). */\ntype StorageLike = {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n key(index: number): string | null;\n readonly length: number;\n};\n\ntype LockGrant = object | null;\n\n/** The Web Locks API surface the session uses (`navigator.locks`). */\ntype LockManagerLike = {\n request<T>(\n name: string,\n callback: (lock: LockGrant) => T | Promise<T>\n ): Promise<T>;\n request<T>(\n name: string,\n options: { mode?: \"shared\" | \"exclusive\"; ifAvailable?: boolean },\n callback: (lock: LockGrant) => T | Promise<T>\n ): Promise<T>;\n};\n\ntype StorageEventLike = { key: string | null };\n\ntype BrowserGlobals = {\n localStorage?: StorageLike;\n navigator?: { locks?: LockManagerLike };\n addEventListener?: (\n type: \"storage\",\n listener: (event: StorageEventLike) => void\n ) => void;\n removeEventListener?: (\n type: \"storage\",\n listener: (event: StorageEventLike) => void\n ) => void;\n};\n\nconst browserGlobals = globalThis as BrowserGlobals;\n\n/** A persisted session record: per-lane failed entries (a pre-lane record carried them at the root). */\ntype SessionRecordLike = {\n failed?: unknown;\n lanes?: Record<string, { failed?: unknown } | null | undefined> | null;\n};\n\nexport function readPersistedFailedSendStamps(\n sessionKey: string\n): ReadonlyMap<string, number> | null {\n const storage = browserGlobals.localStorage;\n if (!storage) return null;\n let record: SessionRecordLike | null;\n try {\n const raw = storage.getItem(SESSION_PREFIX + sessionKey);\n record =\n raw === null ? null : (JSON.parse(raw) as SessionRecordLike | null);\n } catch {\n return null;\n }\n return readFailedSendStampsFromRecord(record);\n}\n\n/**\n * Send stamps (message id → epoch ms of the original send) off an already\n * loaded session record's failed lane — the storage-agnostic half of\n * {@link readPersistedFailedSendStamps}, for hosts whose session store is not\n * localStorage (the React Native AsyncStorage-backed store reads its record\n * synchronously through `SessionStorage.load()`).\n *\n * Returns `null` for no record (every failed entry then counts as fresh) and\n * an empty map for a record without failures.\n */\nexport function readFailedSendStampsFromRecord(\n record: SessionRecordLike | null | undefined\n): ReadonlyMap<string, number> | null {\n if (!record || typeof record !== \"object\") return null;\n const stamps = new Map<string, number>();\n const lanes =\n record.lanes && typeof record.lanes === \"object\" ? record.lanes : {};\n const failed = [\n ...(Array.isArray(record.failed) ? record.failed : []),\n ...Object.values(lanes).flatMap((lane) =>\n Array.isArray(lane?.failed) ? lane.failed : []\n ),\n ];\n for (const entry of failed) {\n const failed = entry as {\n ts?: unknown;\n params?: readonly { message?: { id?: unknown } }[];\n } | null;\n const messageId = failed?.params?.[0]?.message?.id;\n if (typeof messageId === \"string\" && typeof failed?.ts === \"number\") {\n stamps.set(messageId, failed.ts);\n }\n }\n return stamps;\n}\n\n/** A storage operation the session survived by degrading instead of failing. */\nexport type SessionStorageDegradation = {\n operation:\n | \"getItem\"\n | \"setItem\"\n | \"removeItem\"\n | \"getAllKeys\"\n | \"withUnused\"\n /** The lock manager refused the transaction fence; it ran unfenced. */\n | \"lock\"\n /**\n * The stored record is one this build cannot read (not JSON, or a newer\n * storage version): served as absent, and the key is shielded from being\n * overwritten so the record survives this tab.\n */\n | \"record\"\n /** statewire's own storage failure (a structurally invalid stored record). */\n | \"session\"\n /**\n * Queued commands persisted by an earlier statewire build no longer fit\n * the current wire/protocol range: parked in the lane's failed list (the\n * send-recovery sweep re-dispatches them) instead of blocking the mount.\n */\n | \"migrate\";\n key: string;\n error: unknown;\n};\n\ntype ProducedRange = NonNullable<StatewireClient.SessionCommand[\"produced\"]>;\n\nconst inRange = (value: string, range: StatewireProtocol.Range) =>\n value >= (range.minVersion ?? range.version) && value <= range.version;\n\n/** Mirrors statewire's own stored-command compatibility check. */\nconst storedCommandCompatible = (\n protocol: string,\n command: StatewireClient.SessionCommand,\n current: ProducedRange\n): boolean => {\n if (\n !command.produced ||\n !inRange(command.produced.wire.version, current.wire)\n )\n return false;\n if (current.protocols === \"*\") return true;\n if (command.produced.protocols === \"*\") return false;\n const produced = command.produced.protocols.find((p) => p.name === protocol);\n const offer = current.protocols.find((p) => p.name === protocol);\n return (\n produced !== undefined &&\n offer !== undefined &&\n inRange(produced.version, offer)\n );\n};\n\n/**\n * statewire's `migrate` for a record whose queued commands were produced by\n * an earlier build: every incompatible command leaves its lane's queue and is\n * retained as a `lost`/`session-lost` failed send re-stamped with the current\n * range. Client identity, lane clocks, compatible commands and existing failed\n * history are untouched, so the mount proceeds and the send-recovery sweep\n * (or the user, via the failed-send UI) re-dispatches the parked sends under\n * the negotiated format instead of the runtime throwing before it dials.\n *\n * Returns the record itself when nothing needed migrating, and the count of\n * parked commands otherwise.\n */\nexport function parkIncompatibleSessionCommands(\n record: StatewireClient.SessionRecord,\n current: ProducedRange,\n now: number = Date.now()\n): { record: StatewireClient.SessionRecord; parked: number } {\n let parked = 0;\n const lanes: Record<string, StatewireClient.SessionLane> = {};\n for (const [protocol, lane] of Object.entries(record.lanes)) {\n const kept: StatewireClient.SessionCommand[] = [];\n const failed: StatewireClient.FailedCommand[] = [];\n for (const command of lane.commands) {\n if (storedCommandCompatible(protocol, command, current)) {\n kept.push(command);\n continue;\n }\n failed.push({\n protocol,\n produced: current,\n command: command.method,\n params: command.params,\n ...(command.meta !== undefined && { meta: command.meta }),\n failure: { fate: \"lost\", cause: \"session-lost\" },\n ts: now,\n seq: command.seq,\n });\n }\n parked += failed.length;\n lanes[protocol] =\n failed.length === 0\n ? lane\n : {\n ...lane,\n commands: kept,\n failed: [...(lane.failed ?? []), ...failed],\n };\n }\n return parked === 0\n ? { record, parked }\n : { record: { ...record, lanes }, parked };\n}\n\n/**\n * Whether `error` is the browser refusing a write for lack of room, under\n * every name the engines use — `QuotaExceededError` (Chromium, WebKit, current\n * Firefox), `NS_ERROR_DOM_QUOTA_REACHED` (older Firefox), the legacy\n * `DOMException` codes 22 (WebKit) and 1014 (Firefox) — or a plain error whose\n * message carries one of those (storage proxies and WebViews rewrap the\n * exception). Olympus's `isStorageQuotaError` recognises the same set; the two\n * stay in step.\n */\nexport function isStorageQuotaError(error: unknown): boolean {\n if (typeof error !== \"object\" || error === null) return false;\n const { name, code, message } = error as {\n name?: unknown;\n code?: unknown;\n message?: unknown;\n };\n return (\n name === \"QuotaExceededError\" ||\n name === \"NS_ERROR_DOM_QUOTA_REACHED\" ||\n code === 22 ||\n code === 1014 ||\n (typeof message === \"string\" &&\n /exceeded the quota|quotaexceedederror|ns_error_dom_quota_reached/i.test(\n message\n ))\n );\n}\n\n/** The one quota shape statewire's retention sweep recognises. */\nfunction asStatewireQuotaError(error: unknown): Error {\n if (error instanceof Error && error.name === \"QuotaExceededError\")\n return error;\n const normalized = new Error(\n error instanceof Error ? error.message : \"storage quota exceeded\"\n );\n normalized.name = \"QuotaExceededError\";\n return normalized;\n}\n\n/**\n * Why `raw` is a stored record this build cannot read, or `undefined` when it\n * may be one. Mirrors the two decode failures statewire raises for a record it\n * cannot read — invalid JSON, and a `statewireStorageVersion` newer than the\n * ones this build's statewire decodes (a tab left open across a\n * statewire upgrade, reading the record a newer tab keeps) — because statewire\n * throws those out of the resource at mount instead of treating the record as\n * absent. A structurally invalid record of the right version still fails\n * inside statewire.\n */\nfunction unreadableRecord(raw: string): Error | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (error) {\n return error instanceof Error\n ? error\n : new Error(\"stored session record is not JSON\");\n }\n if (typeof parsed !== \"object\" || parsed === null) {\n return new Error(\"stored session record is not an object\");\n }\n const version = (parsed as { statewireStorageVersion?: unknown })\n .statewireStorageVersion;\n if (version !== undefined && !READABLE_STORAGE_VERSIONS.has(version)) {\n return new Error(\n `stored session record uses storage version ${String(version)}`\n );\n }\n return undefined;\n}\n\ntype SyncBackend = {\n getItem: (key: string) => string | null;\n setItem: (key: string, value: string) => void;\n removeItem: (key: string) => void;\n getAllKeys: () => readonly string[];\n withUnused: (key: string, action: () => Promise<void>) => Promise<void>;\n};\n\n/**\n * The browser backend `@assistant-ui/react-statewire`'s `LocalStorageSession`\n * uses, reproduced here because that resource does not take a backend: raw\n * `localStorage`, with retention cleanup fenced by the same Web Locks so a\n * sweep never removes a record another tab is actively sharing.\n */\nfunction browserBackend({\n storage,\n locks,\n}: {\n storage: StorageLike;\n locks: LockManagerLike;\n}): SyncBackend {\n return {\n getItem: (key) => storage.getItem(key),\n setItem: (key, value) => storage.setItem(key, value),\n removeItem: (key) => storage.removeItem(key),\n getAllKeys: () =>\n Array.from({ length: storage.length }, (_, index) =>\n storage.key(index)\n ).filter((key): key is string => key !== null),\n withUnused: async (key, action) => {\n await locks.request(\n SESSION_ACTIVE_PREFIX + key,\n { ifAvailable: true },\n async (active) => {\n if (active === null) return;\n await locks.request(\n SESSION_LOCK_PREFIX + key.slice(SESSION_PREFIX.length),\n { ifAvailable: true },\n async (lock) => {\n if (lock === null) return;\n await action();\n }\n );\n }\n );\n },\n };\n}\n\n/**\n * Wraps a synchronous key-value backend so a failing origin degrades the\n * session to memory-only persistence instead of stopping the connection.\n *\n * statewire's session storage latches the first unrecognised write failure\n * and from then on rejects every send (`stopped/storage-error`, \"session\n * storage failed\" on the connection chip). It only recovers a\n * `QuotaExceededError` by that exact name — sweeping old session records and\n * retrying the write once — and latches again when the retry fails too. Here\n * the first quota failure per key is surfaced under that name so the sweep\n * runs; the retry that follows it and every other storage failure are absorbed\n * and reported through `onDegraded`, so chat keeps working.\n *\n * An absorbed write is kept in an in-memory overlay that `getItem` serves\n * first: statewire re-reads the record through `load()` on every shared-mode\n * transaction, so a stale persisted copy would otherwise win back its old\n * `nextSeq`, `clientId` and command journal. The overlay is per tab — another\n * tab sharing the thread still reads the last persisted copy, which is the\n * memory-only degradation this backend exists to allow. A later successful\n * write clears the overlay entry.\n *\n * A key whose read threw, or whose stored value this build cannot read (see\n * {@link unreadableRecord}), is served as absent and shielded: writes to it\n * stay in the overlay for good, so a fresh lane minted over an unreadable\n * record never overwrites what another tab or a newer build persisted.\n */\nexport function quotaTolerantBackend({\n inner,\n onDegraded,\n}: {\n inner: SyncBackend;\n onDegraded: (degradation: SessionStorageDegradation) => void;\n}): SyncBackend & KeyValueSessionStorage.Backend {\n // Keys whose write already surfaced a quota error to statewire: until a\n // write succeeds again, later failures on the key are absorbed instead of\n // surfaced, or every save would re-run statewire's sweep (which evicts\n // other threads' idle records) and the failure would latch on the retry.\n const quotaSurfaced = new Set<string>();\n // Absorbed writes, served ahead of the backend (see the function docs).\n const overlay = new Map<string, string>();\n // Keys served as absent and never written through (see the function docs).\n const shielded = new Set<string>();\n // statewire re-reads and re-decodes the record on every transaction; the\n // readability check is memoised on the raw string so it costs one string\n // comparison per read, not a second parse.\n const checked = new Map<\n string,\n { raw: string; verdict: Error | undefined }\n >();\n const readable = (key: string, raw: string): Error | undefined => {\n const previous = checked.get(key);\n if (previous !== undefined && previous.raw === raw) return previous.verdict;\n const verdict = unreadableRecord(raw);\n checked.set(key, { raw, verdict });\n return verdict;\n };\n return {\n getItem: (key) => {\n const shadowed = overlay.get(key);\n if (shadowed !== undefined) return shadowed;\n if (shielded.has(key)) return null;\n let raw: string | null;\n try {\n raw = inner.getItem(key);\n } catch (error) {\n shielded.add(key);\n onDegraded({ operation: \"getItem\", key, error });\n return null;\n }\n if (raw === null) return null;\n const unreadable = readable(key, raw);\n if (unreadable === undefined) return raw;\n shielded.add(key);\n onDegraded({ operation: \"record\", key, error: unreadable });\n return null;\n },\n setItem: (key, value) => {\n if (shielded.has(key)) {\n overlay.set(key, value);\n return;\n }\n try {\n inner.setItem(key, value);\n overlay.delete(key);\n quotaSurfaced.delete(key);\n } catch (error) {\n if (isStorageQuotaError(error) && !quotaSurfaced.has(key)) {\n quotaSurfaced.add(key);\n throw asStatewireQuotaError(error);\n }\n overlay.set(key, value);\n onDegraded({ operation: \"setItem\", key, error });\n }\n },\n removeItem: (key) => {\n overlay.delete(key);\n if (shielded.has(key)) return;\n try {\n inner.removeItem(key);\n } catch (error) {\n onDegraded({ operation: \"removeItem\", key, error });\n }\n },\n getAllKeys: () => {\n const keys = new Set<string>(overlay.keys());\n try {\n for (const key of inner.getAllKeys()) keys.add(key);\n } catch (error) {\n onDegraded({ operation: \"getAllKeys\", key: \"\", error });\n }\n return [...keys];\n },\n withUnused: async (key, action) => {\n try {\n await inner.withUnused(key, action);\n } catch (error) {\n onDegraded({ operation: \"withUnused\", key, error });\n }\n },\n };\n}\n\n/** A stored record's save stamp, or `undefined` when it carries none. */\nfunction savedAtOf(raw: string | null): number | undefined {\n if (raw === null) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return undefined;\n }\n if (typeof parsed !== \"object\" || parsed === null) return undefined;\n const { savedAt, updatedAt } = parsed as {\n savedAt?: unknown;\n updatedAt?: unknown;\n };\n const stamp = savedAt ?? updatedAt;\n return typeof stamp === \"number\" && Number.isFinite(stamp)\n ? stamp\n : undefined;\n}\n\n/**\n * Removes session records last saved more than `maxAgeMs` ago, skipping the\n * keys mounted in this tab and, through `withUnused`, those another tab holds.\n *\n * statewire's own retention sweep only retires records with nothing queued and\n * nothing failed: a record still carrying an unconfirmed or failed send is\n * exempt from both its age and count bounds, so every thread left with one\n * kept its key forever. Past the TTL those sends are long out of send\n * recovery's auto-retry window, so the record goes the same way an empty one\n * does.\n */\nexport async function retireExpiredSessionRecords({\n backend,\n mounted,\n maxAgeMs,\n now = Date.now(),\n}: {\n backend: Pick<\n SyncBackend,\n \"getItem\" | \"removeItem\" | \"getAllKeys\" | \"withUnused\"\n >;\n mounted: ReadonlyMap<string, number>;\n maxAgeMs: number;\n now?: number;\n}): Promise<void> {\n for (const key of backend.getAllKeys()) {\n if (!key.startsWith(SESSION_PREFIX) || mounted.has(key)) continue;\n const savedAt = savedAtOf(backend.getItem(key));\n if (savedAt === undefined || now - savedAt <= maxAgeMs) continue;\n await backend.withUnused(key, async () => {\n if (mounted.has(key) || savedAtOf(backend.getItem(key)) !== savedAt)\n return;\n backend.removeItem(key);\n });\n }\n}\n\ntype DegradationListener = (degradation: SessionStorageDegradation) => void;\n\ntype SharedBackend = {\n backend: SyncBackend & KeyValueSessionStorage.Backend;\n listeners: Set<DegradationListener>;\n /** Storage keys of the sessions mounted in this tab, with mount counts. */\n mounted: Map<string, number>;\n};\n\n/**\n * One backend per storage object — per origin, in a browser — shared by every\n * session mounted in the tab, exactly as the upstream resource's module-level\n * backend is. statewire keys its in-tab \"active\" bookkeeping by backend\n * identity, so a mount's retention sweep only skips the records of other\n * mounts on the *same* backend; and the absorbed-write overlay has to outlive\n * a remount of the same thread (a thread switch and back) or the remount would\n * read the stale persisted copy. Degradations fan out to every mounted\n * session's reporter.\n */\nconst sharedBackends = new WeakMap<StorageLike, SharedBackend>();\n\nfunction sharedBackend({\n storage,\n locks,\n}: {\n storage: StorageLike;\n locks: LockManagerLike;\n}): SharedBackend {\n let shared = sharedBackends.get(storage);\n if (shared === undefined) {\n const listeners = new Set<DegradationListener>();\n shared = {\n listeners,\n mounted: new Map(),\n backend: quotaTolerantBackend({\n inner: browserBackend({ storage, locks }),\n onDegraded: (degradation) => {\n for (const listener of listeners) listener(degradation);\n },\n }),\n };\n sharedBackends.set(storage, shared);\n }\n return shared;\n}\n\n/**\n * Athena's browser session store: `@assistant-ui/react-statewire`'s\n * `LocalStorageSession` contract (per-thread key under the shared prefix,\n * cross-tab Web Lock, storage-event subscription, retention) on top of\n * {@link quotaTolerantBackend}, with Athena's protocol lane and retention\n * policy.\n */\nconst useQuotaSafeLocalStorageSession = ({\n threadId,\n key = (id) => id,\n ttl = 7 * DAY_MS,\n max = 50,\n onStorageDegraded,\n}: QuotaSafeLocalStorageSession.Options): StatewireClient.SessionStorage => {\n const storage = browserGlobals.localStorage;\n const locks = browserGlobals.navigator?.locks;\n if (!storage)\n throw new Error(\n \"statewire: QuotaSafeLocalStorageSession requires localStorage\"\n );\n if (!locks)\n throw new Error(\n \"statewire: QuotaSafeLocalStorageSession requires the Web Locks API (navigator.locks)\"\n );\n const sessionKey = key(threadId);\n const storageKey = SESSION_PREFIX + sessionKey;\n const lockName = SESSION_LOCK_PREFIX + sessionKey;\n\n const latestOnDegraded = useRef(onStorageDegraded);\n latestOnDegraded.current = onStorageDegraded;\n const reportDegradation = useMemo(() => {\n const reported = new Set<string>();\n return (degradation: SessionStorageDegradation) => {\n const handler = latestOnDegraded.current;\n if (handler) {\n try {\n handler(degradation);\n } catch {\n // Reporting runs inside the session's recovery path and must never\n // become the failure it is reporting.\n }\n return;\n }\n const dedupeKey = `${degradation.operation}:${degradation.key}`;\n if (reported.has(dedupeKey)) return;\n reported.add(dedupeKey);\n console.warn(\n `[statewire] session storage degraded to memory only (${degradation.operation} ${degradation.key})`,\n degradation.error\n );\n };\n }, []);\n\n const shared = sharedBackend({ storage, locks });\n // Registered during render as well as in the effect: the read statewire\n // makes while mounting below happens before any effect runs, and tap may\n // clean an effect up and re-run it within one commit.\n shared.listeners.add(reportDegradation);\n useEffect(() => {\n shared.listeners.add(reportDegradation);\n return () => {\n shared.listeners.delete(reportDegradation);\n };\n }, [shared, reportDegradation]);\n\n useEffect(() => {\n const { mounted } = shared;\n mounted.set(storageKey, (mounted.get(storageKey) ?? 0) + 1);\n retireExpiredSessionRecords({\n backend: shared.backend,\n mounted,\n maxAgeMs: ttl,\n }).catch((error: unknown) =>\n reportDegradation({ operation: \"withUnused\", key: storageKey, error })\n );\n return () => {\n const count = (mounted.get(storageKey) ?? 1) - 1;\n if (count === 0) mounted.delete(storageKey);\n else mounted.set(storageKey, count);\n };\n }, [shared, storageKey, ttl, reportDegradation]);\n\n // Hold the record's shared \"active\" lock while mounted so another tab's\n // retention sweep skips it (the same fence the upstream resource holds).\n useEffect(() => {\n let release: (() => void) | undefined;\n let disposed = false;\n Promise.resolve(\n locks.request(\n SESSION_ACTIVE_PREFIX + storageKey,\n { mode: \"shared\" },\n () => {\n if (disposed) return;\n return new Promise<void>((resolve) => {\n release = resolve;\n });\n }\n )\n ).catch((error: unknown) =>\n reportDegradation({ operation: \"lock\", key: storageKey, error })\n );\n return () => {\n disposed = true;\n release?.();\n };\n }, [locks, storageKey, reportDegradation]);\n\n // The storage resource includes migrate in its identity. A fresh callback\n // on every render replaces the store and prevents hydration from settling.\n const migrate = useCallback<\n NonNullable<StatewireClient.SessionStorage[\"migrate\"]>\n >(\n (record, current) => {\n const migrated = parkIncompatibleSessionCommands(record, current);\n reportDegradation({\n operation: \"migrate\",\n key: storageKey,\n error: new Error(\n `${migrated.parked} queued statewire command(s) predate the current protocol range; parked as failed sends`\n ),\n });\n return migrated.record;\n },\n [reportDegradation, storageKey]\n );\n\n const sessionStorage = useResource(\n KeyValueSessionStorage({\n backend: shared.backend,\n key: sessionKey,\n namespace: SESSION_PREFIX,\n protocol: DEEP_AGENT_STATEWIRE_PROTOCOL,\n migrate,\n retention: { maxAgeMs: ttl, maxEntries: max },\n // A failure statewire raises itself (a structurally invalid stored\n // record) still stops the session; it is at least reported the same way.\n onError: (error) =>\n reportDegradation({ operation: \"session\", key: storageKey, error }),\n })\n );\n\n return useMemo(\n () => ({\n ...sessionStorage,\n lock: async <T>(fn: () => T | Promise<T>): Promise<T> => {\n let granted = false;\n try {\n return await locks.request(lockName, async () => {\n granted = true;\n return fn();\n });\n } catch (error) {\n if (granted) throw error;\n // The lock manager refused the request itself (a detached or\n // back/forward-cached document, a lockdown-mode origin): the\n // transaction runs unfenced rather than stopping the session.\n reportDegradation({ operation: \"lock\", key: storageKey, error });\n return fn();\n }\n },\n subscribe: (listener: () => void) => {\n const onStorage = (event: StorageEventLike) => {\n if (event.key === storageKey) listener();\n };\n browserGlobals.addEventListener?.(\"storage\", onStorage);\n return () => browserGlobals.removeEventListener?.(\"storage\", onStorage);\n },\n }),\n [sessionStorage, locks, storageKey, lockName, reportDegradation]\n );\n};\n\n/** Athena's browser session keys and retention policy. */\nexport const QuotaSafeLocalStorageSession = resource(\n useQuotaSafeLocalStorageSession\n);\n\nexport namespace QuotaSafeLocalStorageSession {\n export type Options = {\n threadId: string;\n key?: (threadId: string) => string;\n ttl?: number;\n max?: number;\n /**\n * Called when a storage operation failed and the session carried on in\n * memory. Defaults to one `console.warn` per operation and key.\n */\n onStorageDegraded?: (degradation: SessionStorageDegradation) => void;\n };\n}\n"],"names":["storage","failed","useRef","useMemo","useEffect","useCallback","useResource","KeyValueSessionStorage","DEEP_AGENT_STATEWIRE_PROTOCOL","_a","resource"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"quota-safe-local-storage-session.cjs","sources":["../../../../agent-runtime/src/quota-safe-local-storage-session.ts"],"sourcesContent":["import { resource, useResource } from \"@assistant-ui/tap\";\nimport type { StatewireClient, StatewireProtocol } from \"statewire\";\nimport { KeyValueSessionStorage } from \"statewire/storage\";\nimport { useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { DEEP_AGENT_STATEWIRE_PROTOCOL } from \"./statewire-transport\";\n\nconst SESSION_PREFIX = \"aui:statewire-session:\";\nconst SESSION_LOCK_PREFIX = \"aui:statewire-session-lock:\";\nconst SESSION_ACTIVE_PREFIX = \"aui:statewire-session-active:\";\nconst DAY_MS = 24 * 60 * 60 * 1000;\n/** The stored-record formats the statewire this build ships reads (it writes 3). */\nconst READABLE_STORAGE_VERSIONS: ReadonlySet<unknown> = new Set([1, 2, 3]);\n\n/** Structural view of the browser storage global — this package compiles\n * without the DOM lib (React Native honesty; see `globals.d.ts`). */\ntype StorageLike = {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n key(index: number): string | null;\n readonly length: number;\n};\n\ntype LockGrant = object | null;\n\n/** The Web Locks API surface the session uses (`navigator.locks`). */\ntype LockManagerLike = {\n request<T>(\n name: string,\n callback: (lock: LockGrant) => T | Promise<T>\n ): Promise<T>;\n request<T>(\n name: string,\n options: { mode?: \"shared\" | \"exclusive\"; ifAvailable?: boolean },\n callback: (lock: LockGrant) => T | Promise<T>\n ): Promise<T>;\n};\n\ntype StorageEventLike = { key: string | null };\n\ntype BrowserGlobals = {\n localStorage?: StorageLike;\n navigator?: { locks?: LockManagerLike };\n addEventListener?: (\n type: \"storage\",\n listener: (event: StorageEventLike) => void\n ) => void;\n removeEventListener?: (\n type: \"storage\",\n listener: (event: StorageEventLike) => void\n ) => void;\n};\n\nconst browserGlobals = globalThis as BrowserGlobals;\n\n/** A persisted session record: per-lane failed entries (a pre-lane record carried them at the root). */\ntype SessionRecordLike = {\n failed?: unknown;\n lanes?: Record<string, { failed?: unknown } | null | undefined> | null;\n};\n\nexport function readPersistedFailedSendStamps(\n sessionKey: string\n): ReadonlyMap<string, number> | null {\n const storage = browserGlobals.localStorage;\n if (!storage) return null;\n let record: SessionRecordLike | null;\n try {\n const raw = storage.getItem(SESSION_PREFIX + sessionKey);\n record =\n raw === null ? null : (JSON.parse(raw) as SessionRecordLike | null);\n } catch {\n return null;\n }\n return readFailedSendStampsFromRecord(record);\n}\n\n/**\n * Send stamps (message id → epoch ms of the original send) off an already\n * loaded session record's failed lane — the storage-agnostic half of\n * {@link readPersistedFailedSendStamps}, for hosts whose session store is not\n * localStorage (the React Native AsyncStorage-backed store reads its record\n * synchronously through `SessionStorage.load()`).\n *\n * Returns `null` for no record (every failed entry then counts as fresh) and\n * an empty map for a record without failures.\n */\nexport function readFailedSendStampsFromRecord(\n record: SessionRecordLike | null | undefined\n): ReadonlyMap<string, number> | null {\n if (!record || typeof record !== \"object\") return null;\n const stamps = new Map<string, number>();\n const lanes =\n record.lanes && typeof record.lanes === \"object\" ? record.lanes : {};\n const failed = [\n ...(Array.isArray(record.failed) ? record.failed : []),\n ...Object.values(lanes).flatMap((lane) =>\n Array.isArray(lane?.failed) ? lane.failed : []\n ),\n ];\n for (const entry of failed) {\n const failed = entry as {\n ts?: unknown;\n params?: readonly { message?: { id?: unknown } }[];\n } | null;\n const messageId = failed?.params?.[0]?.message?.id;\n if (typeof messageId === \"string\" && typeof failed?.ts === \"number\") {\n stamps.set(messageId, failed.ts);\n }\n }\n return stamps;\n}\n\n/** A storage operation the session survived by degrading instead of failing. */\nexport type SessionStorageDegradation = {\n operation:\n | \"getItem\"\n | \"setItem\"\n | \"removeItem\"\n | \"getAllKeys\"\n | \"withUnused\"\n /** The lock manager refused the transaction fence; it ran unfenced. */\n | \"lock\"\n /**\n * The stored record is one this build cannot read (not JSON, or a newer\n * storage version): served as absent, and the key is shielded from being\n * overwritten so the record survives this tab.\n */\n | \"record\"\n /** statewire's own storage failure (a structurally invalid stored record). */\n | \"session\"\n /**\n * Queued commands persisted by an earlier statewire build no longer fit\n * the current wire/protocol range: parked in the lane's failed list (the\n * send-recovery sweep re-dispatches them) instead of blocking the mount.\n */\n | \"migrate\";\n key: string;\n error: unknown;\n};\n\ntype ProducedRange = NonNullable<StatewireClient.SessionCommand[\"produced\"]>;\n\nconst inRange = (value: string, range: StatewireProtocol.Range) =>\n value >= (range.minVersion ?? range.version) && value <= range.version;\n\n/** Mirrors statewire's own stored-command compatibility check. */\nconst storedCommandCompatible = (\n protocol: string,\n command: StatewireClient.SessionCommand,\n current: ProducedRange\n): boolean => {\n if (\n !command.produced ||\n !inRange(command.produced.wire.version, current.wire)\n )\n return false;\n if (current.protocols === \"*\") return true;\n if (command.produced.protocols === \"*\") return false;\n const produced = command.produced.protocols.find((p) => p.name === protocol);\n const offer = current.protocols.find((p) => p.name === protocol);\n return (\n produced !== undefined &&\n offer !== undefined &&\n inRange(produced.version, offer)\n );\n};\n\n/**\n * The oldest wire whose stored statements the current client still replays.\n * Bundled clients before #45143 offered 2026-09-13 through 2026-09-30 and\n * stamped every queued statement with 2026-09-13. The durable statement grammar\n * (protocol, method, params, meta, seq) is the same on 2026-09-30; only\n * frame-level context differs. So those statements ride the current wire\n * unchanged, while the network offer stays native 2026-09-30 only.\n */\nexport const STORED_STATEMENT_WIRE_FLOOR = \"2026-09-13\";\n\n/** A stored command the current wire replays as-is: re-stamp, don't park. */\nconst replayableOnCurrentWire = (\n protocol: string,\n command: StatewireClient.SessionCommand,\n current: ProducedRange\n): boolean =>\n command.produced !== undefined &&\n inRange(command.produced.wire.version, {\n version: current.wire.version,\n minVersion: STORED_STATEMENT_WIRE_FLOOR,\n }) &&\n storedCommandCompatible(\n protocol,\n { ...command, produced: { ...command.produced, wire: current.wire } },\n current\n );\n\n/**\n * statewire's `migrate` for a record whose queued commands were produced by\n * an earlier build. A command whose statement the current wire still replays\n * ({@link STORED_STATEMENT_WIRE_FLOOR}: an approval's `run/input` saved by a\n * previous bundled client, say) stays queued, re-stamped with the current\n * range, so it is delivered with its original seq. Every other incompatible\n * command leaves its lane's queue and is\n * retained as a `lost`/`session-lost` failed send re-stamped with the current\n * range. Client identity, lane clocks, compatible commands and existing failed\n * history are untouched, so the mount proceeds and the send-recovery sweep\n * (or the user, via the failed-send UI) re-dispatches the parked sends under\n * the negotiated format instead of the runtime throwing before it dials.\n *\n * Returns the record itself when nothing needed migrating, and the count of\n * parked commands otherwise.\n */\nexport function parkIncompatibleSessionCommands(\n record: StatewireClient.SessionRecord,\n current: ProducedRange,\n now: number = Date.now()\n): { record: StatewireClient.SessionRecord; parked: number; restamped: number } {\n let parked = 0;\n let restamped = 0;\n const lanes: Record<string, StatewireClient.SessionLane> = {};\n for (const [protocol, lane] of Object.entries(record.lanes)) {\n const kept: StatewireClient.SessionCommand[] = [];\n const failed: StatewireClient.FailedCommand[] = [];\n let laneRestamped = 0;\n for (const command of lane.commands) {\n if (storedCommandCompatible(protocol, command, current)) {\n kept.push(command);\n continue;\n }\n if (replayableOnCurrentWire(protocol, command, current)) {\n kept.push({ ...command, produced: current });\n laneRestamped += 1;\n continue;\n }\n failed.push({\n protocol,\n produced: current,\n command: command.method,\n params: command.params,\n ...(command.meta !== undefined && { meta: command.meta }),\n failure: { fate: \"lost\", cause: \"session-lost\" },\n ts: now,\n seq: command.seq,\n });\n }\n parked += failed.length;\n restamped += laneRestamped;\n lanes[protocol] =\n failed.length === 0 && laneRestamped === 0\n ? lane\n : {\n ...lane,\n commands: kept,\n ...((failed.length > 0 || lane.failed !== undefined) && {\n failed: [...(lane.failed ?? []), ...failed],\n }),\n };\n }\n return parked === 0 && restamped === 0\n ? { record, parked, restamped }\n : { record: { ...record, lanes }, parked, restamped };\n}\n\n/**\n * Whether `error` is the browser refusing a write for lack of room, under\n * every name the engines use — `QuotaExceededError` (Chromium, WebKit, current\n * Firefox), `NS_ERROR_DOM_QUOTA_REACHED` (older Firefox), the legacy\n * `DOMException` codes 22 (WebKit) and 1014 (Firefox) — or a plain error whose\n * message carries one of those (storage proxies and WebViews rewrap the\n * exception). Olympus's `isStorageQuotaError` recognises the same set; the two\n * stay in step.\n */\nexport function isStorageQuotaError(error: unknown): boolean {\n if (typeof error !== \"object\" || error === null) return false;\n const { name, code, message } = error as {\n name?: unknown;\n code?: unknown;\n message?: unknown;\n };\n return (\n name === \"QuotaExceededError\" ||\n name === \"NS_ERROR_DOM_QUOTA_REACHED\" ||\n code === 22 ||\n code === 1014 ||\n (typeof message === \"string\" &&\n /exceeded the quota|quotaexceedederror|ns_error_dom_quota_reached/i.test(\n message\n ))\n );\n}\n\n/** The one quota shape statewire's retention sweep recognises. */\nfunction asStatewireQuotaError(error: unknown): Error {\n if (error instanceof Error && error.name === \"QuotaExceededError\")\n return error;\n const normalized = new Error(\n error instanceof Error ? error.message : \"storage quota exceeded\"\n );\n normalized.name = \"QuotaExceededError\";\n return normalized;\n}\n\n/**\n * Why `raw` is a stored record this build cannot read, or `undefined` when it\n * may be one. Mirrors the two decode failures statewire raises for a record it\n * cannot read — invalid JSON, and a `statewireStorageVersion` newer than the\n * ones this build's statewire decodes (a tab left open across a\n * statewire upgrade, reading the record a newer tab keeps) — because statewire\n * throws those out of the resource at mount instead of treating the record as\n * absent. A structurally invalid record of the right version still fails\n * inside statewire.\n */\nfunction unreadableRecord(raw: string): Error | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (error) {\n return error instanceof Error\n ? error\n : new Error(\"stored session record is not JSON\");\n }\n if (typeof parsed !== \"object\" || parsed === null) {\n return new Error(\"stored session record is not an object\");\n }\n const version = (parsed as { statewireStorageVersion?: unknown })\n .statewireStorageVersion;\n if (version !== undefined && !READABLE_STORAGE_VERSIONS.has(version)) {\n return new Error(\n `stored session record uses storage version ${String(version)}`\n );\n }\n return undefined;\n}\n\ntype SyncBackend = {\n getItem: (key: string) => string | null;\n setItem: (key: string, value: string) => void;\n removeItem: (key: string) => void;\n getAllKeys: () => readonly string[];\n withUnused: (key: string, action: () => Promise<void>) => Promise<void>;\n};\n\n/**\n * The browser backend `@assistant-ui/react-statewire`'s `LocalStorageSession`\n * uses, reproduced here because that resource does not take a backend: raw\n * `localStorage`, with retention cleanup fenced by the same Web Locks so a\n * sweep never removes a record another tab is actively sharing.\n */\nfunction browserBackend({\n storage,\n locks,\n}: {\n storage: StorageLike;\n locks: LockManagerLike;\n}): SyncBackend {\n return {\n getItem: (key) => storage.getItem(key),\n setItem: (key, value) => storage.setItem(key, value),\n removeItem: (key) => storage.removeItem(key),\n getAllKeys: () =>\n Array.from({ length: storage.length }, (_, index) =>\n storage.key(index)\n ).filter((key): key is string => key !== null),\n withUnused: async (key, action) => {\n await locks.request(\n SESSION_ACTIVE_PREFIX + key,\n { ifAvailable: true },\n async (active) => {\n if (active === null) return;\n await locks.request(\n SESSION_LOCK_PREFIX + key.slice(SESSION_PREFIX.length),\n { ifAvailable: true },\n async (lock) => {\n if (lock === null) return;\n await action();\n }\n );\n }\n );\n },\n };\n}\n\n/**\n * Wraps a synchronous key-value backend so a failing origin degrades the\n * session to memory-only persistence instead of stopping the connection.\n *\n * statewire's session storage latches the first unrecognised write failure\n * and from then on rejects every send (`stopped/storage-error`, \"session\n * storage failed\" on the connection chip). It only recovers a\n * `QuotaExceededError` by that exact name — sweeping old session records and\n * retrying the write once — and latches again when the retry fails too. Here\n * the first quota failure per key is surfaced under that name so the sweep\n * runs; the retry that follows it and every other storage failure are absorbed\n * and reported through `onDegraded`, so chat keeps working.\n *\n * An absorbed write is kept in an in-memory overlay that `getItem` serves\n * first: statewire re-reads the record through `load()` on every shared-mode\n * transaction, so a stale persisted copy would otherwise win back its old\n * `nextSeq`, `clientId` and command journal. The overlay is per tab — another\n * tab sharing the thread still reads the last persisted copy, which is the\n * memory-only degradation this backend exists to allow. A later successful\n * write clears the overlay entry.\n *\n * A key whose read threw, or whose stored value this build cannot read (see\n * {@link unreadableRecord}), is served as absent and shielded: writes to it\n * stay in the overlay for good, so a fresh lane minted over an unreadable\n * record never overwrites what another tab or a newer build persisted.\n */\nexport function quotaTolerantBackend({\n inner,\n onDegraded,\n}: {\n inner: SyncBackend;\n onDegraded: (degradation: SessionStorageDegradation) => void;\n}): SyncBackend & KeyValueSessionStorage.Backend {\n // Keys whose write already surfaced a quota error to statewire: until a\n // write succeeds again, later failures on the key are absorbed instead of\n // surfaced, or every save would re-run statewire's sweep (which evicts\n // other threads' idle records) and the failure would latch on the retry.\n const quotaSurfaced = new Set<string>();\n // Absorbed writes, served ahead of the backend (see the function docs).\n const overlay = new Map<string, string>();\n // Keys served as absent and never written through (see the function docs).\n const shielded = new Set<string>();\n // statewire re-reads and re-decodes the record on every transaction; the\n // readability check is memoised on the raw string so it costs one string\n // comparison per read, not a second parse.\n const checked = new Map<\n string,\n { raw: string; verdict: Error | undefined }\n >();\n const readable = (key: string, raw: string): Error | undefined => {\n const previous = checked.get(key);\n if (previous !== undefined && previous.raw === raw) return previous.verdict;\n const verdict = unreadableRecord(raw);\n checked.set(key, { raw, verdict });\n return verdict;\n };\n return {\n getItem: (key) => {\n const shadowed = overlay.get(key);\n if (shadowed !== undefined) return shadowed;\n if (shielded.has(key)) return null;\n let raw: string | null;\n try {\n raw = inner.getItem(key);\n } catch (error) {\n shielded.add(key);\n onDegraded({ operation: \"getItem\", key, error });\n return null;\n }\n if (raw === null) return null;\n const unreadable = readable(key, raw);\n if (unreadable === undefined) return raw;\n shielded.add(key);\n onDegraded({ operation: \"record\", key, error: unreadable });\n return null;\n },\n setItem: (key, value) => {\n if (shielded.has(key)) {\n overlay.set(key, value);\n return;\n }\n try {\n inner.setItem(key, value);\n overlay.delete(key);\n quotaSurfaced.delete(key);\n } catch (error) {\n if (isStorageQuotaError(error) && !quotaSurfaced.has(key)) {\n quotaSurfaced.add(key);\n throw asStatewireQuotaError(error);\n }\n overlay.set(key, value);\n onDegraded({ operation: \"setItem\", key, error });\n }\n },\n removeItem: (key) => {\n overlay.delete(key);\n if (shielded.has(key)) return;\n try {\n inner.removeItem(key);\n } catch (error) {\n onDegraded({ operation: \"removeItem\", key, error });\n }\n },\n getAllKeys: () => {\n const keys = new Set<string>(overlay.keys());\n try {\n for (const key of inner.getAllKeys()) keys.add(key);\n } catch (error) {\n onDegraded({ operation: \"getAllKeys\", key: \"\", error });\n }\n return [...keys];\n },\n withUnused: async (key, action) => {\n try {\n await inner.withUnused(key, action);\n } catch (error) {\n onDegraded({ operation: \"withUnused\", key, error });\n }\n },\n };\n}\n\n/** A stored record's save stamp, or `undefined` when it carries none. */\nfunction savedAtOf(raw: string | null): number | undefined {\n if (raw === null) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return undefined;\n }\n if (typeof parsed !== \"object\" || parsed === null) return undefined;\n const { savedAt, updatedAt } = parsed as {\n savedAt?: unknown;\n updatedAt?: unknown;\n };\n const stamp = savedAt ?? updatedAt;\n return typeof stamp === \"number\" && Number.isFinite(stamp)\n ? stamp\n : undefined;\n}\n\n/**\n * Removes session records last saved more than `maxAgeMs` ago, skipping the\n * keys mounted in this tab and, through `withUnused`, those another tab holds.\n *\n * statewire's own retention sweep only retires records with nothing queued and\n * nothing failed: a record still carrying an unconfirmed or failed send is\n * exempt from both its age and count bounds, so every thread left with one\n * kept its key forever. Past the TTL those sends are long out of send\n * recovery's auto-retry window, so the record goes the same way an empty one\n * does.\n */\nexport async function retireExpiredSessionRecords({\n backend,\n mounted,\n maxAgeMs,\n now = Date.now(),\n}: {\n backend: Pick<\n SyncBackend,\n \"getItem\" | \"removeItem\" | \"getAllKeys\" | \"withUnused\"\n >;\n mounted: ReadonlyMap<string, number>;\n maxAgeMs: number;\n now?: number;\n}): Promise<void> {\n for (const key of backend.getAllKeys()) {\n if (!key.startsWith(SESSION_PREFIX) || mounted.has(key)) continue;\n const savedAt = savedAtOf(backend.getItem(key));\n if (savedAt === undefined || now - savedAt <= maxAgeMs) continue;\n await backend.withUnused(key, async () => {\n if (mounted.has(key) || savedAtOf(backend.getItem(key)) !== savedAt)\n return;\n backend.removeItem(key);\n });\n }\n}\n\ntype DegradationListener = (degradation: SessionStorageDegradation) => void;\n\ntype SharedBackend = {\n backend: SyncBackend & KeyValueSessionStorage.Backend;\n listeners: Set<DegradationListener>;\n /** Storage keys of the sessions mounted in this tab, with mount counts. */\n mounted: Map<string, number>;\n};\n\n/**\n * One backend per storage object — per origin, in a browser — shared by every\n * session mounted in the tab, exactly as the upstream resource's module-level\n * backend is. statewire keys its in-tab \"active\" bookkeeping by backend\n * identity, so a mount's retention sweep only skips the records of other\n * mounts on the *same* backend; and the absorbed-write overlay has to outlive\n * a remount of the same thread (a thread switch and back) or the remount would\n * read the stale persisted copy. Degradations fan out to every mounted\n * session's reporter.\n */\nconst sharedBackends = new WeakMap<StorageLike, SharedBackend>();\n\nfunction sharedBackend({\n storage,\n locks,\n}: {\n storage: StorageLike;\n locks: LockManagerLike;\n}): SharedBackend {\n let shared = sharedBackends.get(storage);\n if (shared === undefined) {\n const listeners = new Set<DegradationListener>();\n shared = {\n listeners,\n mounted: new Map(),\n backend: quotaTolerantBackend({\n inner: browserBackend({ storage, locks }),\n onDegraded: (degradation) => {\n for (const listener of listeners) listener(degradation);\n },\n }),\n };\n sharedBackends.set(storage, shared);\n }\n return shared;\n}\n\n/**\n * Athena's browser session store: `@assistant-ui/react-statewire`'s\n * `LocalStorageSession` contract (per-thread key under the shared prefix,\n * cross-tab Web Lock, storage-event subscription, retention) on top of\n * {@link quotaTolerantBackend}, with Athena's protocol lane and retention\n * policy.\n */\nconst useQuotaSafeLocalStorageSession = ({\n threadId,\n key = (id) => id,\n ttl = 7 * DAY_MS,\n max = 50,\n onStorageDegraded,\n}: QuotaSafeLocalStorageSession.Options): StatewireClient.SessionStorage => {\n const storage = browserGlobals.localStorage;\n const locks = browserGlobals.navigator?.locks;\n if (!storage)\n throw new Error(\n \"statewire: QuotaSafeLocalStorageSession requires localStorage\"\n );\n if (!locks)\n throw new Error(\n \"statewire: QuotaSafeLocalStorageSession requires the Web Locks API (navigator.locks)\"\n );\n const sessionKey = key(threadId);\n const storageKey = SESSION_PREFIX + sessionKey;\n const lockName = SESSION_LOCK_PREFIX + sessionKey;\n\n const latestOnDegraded = useRef(onStorageDegraded);\n latestOnDegraded.current = onStorageDegraded;\n const reportDegradation = useMemo(() => {\n const reported = new Set<string>();\n return (degradation: SessionStorageDegradation) => {\n const handler = latestOnDegraded.current;\n if (handler) {\n try {\n handler(degradation);\n } catch {\n // Reporting runs inside the session's recovery path and must never\n // become the failure it is reporting.\n }\n return;\n }\n const dedupeKey = `${degradation.operation}:${degradation.key}`;\n if (reported.has(dedupeKey)) return;\n reported.add(dedupeKey);\n console.warn(\n `[statewire] session storage degraded to memory only (${degradation.operation} ${degradation.key})`,\n degradation.error\n );\n };\n }, []);\n\n const shared = sharedBackend({ storage, locks });\n // Registered during render as well as in the effect: the read statewire\n // makes while mounting below happens before any effect runs, and tap may\n // clean an effect up and re-run it within one commit.\n shared.listeners.add(reportDegradation);\n useEffect(() => {\n shared.listeners.add(reportDegradation);\n return () => {\n shared.listeners.delete(reportDegradation);\n };\n }, [shared, reportDegradation]);\n\n useEffect(() => {\n const { mounted } = shared;\n mounted.set(storageKey, (mounted.get(storageKey) ?? 0) + 1);\n retireExpiredSessionRecords({\n backend: shared.backend,\n mounted,\n maxAgeMs: ttl,\n }).catch((error: unknown) =>\n reportDegradation({ operation: \"withUnused\", key: storageKey, error })\n );\n return () => {\n const count = (mounted.get(storageKey) ?? 1) - 1;\n if (count === 0) mounted.delete(storageKey);\n else mounted.set(storageKey, count);\n };\n }, [shared, storageKey, ttl, reportDegradation]);\n\n // Hold the record's shared \"active\" lock while mounted so another tab's\n // retention sweep skips it (the same fence the upstream resource holds).\n useEffect(() => {\n let release: (() => void) | undefined;\n let disposed = false;\n Promise.resolve(\n locks.request(\n SESSION_ACTIVE_PREFIX + storageKey,\n { mode: \"shared\" },\n () => {\n if (disposed) return;\n return new Promise<void>((resolve) => {\n release = resolve;\n });\n }\n )\n ).catch((error: unknown) =>\n reportDegradation({ operation: \"lock\", key: storageKey, error })\n );\n return () => {\n disposed = true;\n release?.();\n };\n }, [locks, storageKey, reportDegradation]);\n\n // The storage resource includes migrate in its identity. A fresh callback\n // on every render replaces the store and prevents hydration from settling.\n const migrate = useCallback<\n NonNullable<StatewireClient.SessionStorage[\"migrate\"]>\n >(\n (record, current) => {\n const migrated = parkIncompatibleSessionCommands(record, current);\n if (migrated.parked === 0) return migrated.record;\n reportDegradation({\n operation: \"migrate\",\n key: storageKey,\n error: new Error(\n `${migrated.parked} queued statewire command(s) predate the current protocol range; parked as failed sends`\n ),\n });\n return migrated.record;\n },\n [reportDegradation, storageKey]\n );\n\n const sessionStorage = useResource(\n KeyValueSessionStorage({\n backend: shared.backend,\n key: sessionKey,\n namespace: SESSION_PREFIX,\n protocol: DEEP_AGENT_STATEWIRE_PROTOCOL,\n migrate,\n retention: { maxAgeMs: ttl, maxEntries: max },\n // A failure statewire raises itself (a structurally invalid stored\n // record) still stops the session; it is at least reported the same way.\n onError: (error) =>\n reportDegradation({ operation: \"session\", key: storageKey, error }),\n })\n );\n\n return useMemo(\n () => ({\n ...sessionStorage,\n lock: async <T>(fn: () => T | Promise<T>): Promise<T> => {\n let granted = false;\n try {\n return await locks.request(lockName, async () => {\n granted = true;\n return fn();\n });\n } catch (error) {\n if (granted) throw error;\n // The lock manager refused the request itself (a detached or\n // back/forward-cached document, a lockdown-mode origin): the\n // transaction runs unfenced rather than stopping the session.\n reportDegradation({ operation: \"lock\", key: storageKey, error });\n return fn();\n }\n },\n subscribe: (listener: () => void) => {\n const onStorage = (event: StorageEventLike) => {\n if (event.key === storageKey) listener();\n };\n browserGlobals.addEventListener?.(\"storage\", onStorage);\n return () => browserGlobals.removeEventListener?.(\"storage\", onStorage);\n },\n }),\n [sessionStorage, locks, storageKey, lockName, reportDegradation]\n );\n};\n\n/** Athena's browser session keys and retention policy. */\nexport const QuotaSafeLocalStorageSession = resource(\n useQuotaSafeLocalStorageSession\n);\n\nexport namespace QuotaSafeLocalStorageSession {\n export type Options = {\n threadId: string;\n key?: (threadId: string) => string;\n ttl?: number;\n max?: number;\n /**\n * Called when a storage operation failed and the session carried on in\n * memory. Defaults to one `console.warn` per operation and key.\n */\n onStorageDegraded?: (degradation: SessionStorageDegradation) => void;\n };\n}\n"],"names":["storage","failed","useRef","useMemo","useEffect","useCallback","useResource","KeyValueSessionStorage","DEEP_AGENT_STATEWIRE_PROTOCOL","_a","resource"],"mappings":"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"}
|