@athenaintel/react 0.48.0 → 0.50.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.
Files changed (179) hide show
  1. package/README.md +48 -10
  2. package/dist/agent-runtime/src/call-recap.cjs +319 -0
  3. package/dist/agent-runtime/src/call-recap.cjs.map +1 -0
  4. package/dist/agent-runtime/src/call-recap.d.ts +142 -0
  5. package/dist/agent-runtime/src/call-recap.js +319 -0
  6. package/dist/agent-runtime/src/call-recap.js.map +1 -0
  7. package/dist/agent-runtime/src/chat-outbox-store.cjs +1 -0
  8. package/dist/agent-runtime/src/chat-outbox-store.cjs.map +1 -1
  9. package/dist/agent-runtime/src/chat-outbox-store.d.ts +7 -0
  10. package/dist/agent-runtime/src/chat-outbox-store.js +1 -0
  11. package/dist/agent-runtime/src/chat-outbox-store.js.map +1 -1
  12. package/dist/agent-runtime/src/chat-outbox.cjs +46 -5
  13. package/dist/agent-runtime/src/chat-outbox.cjs.map +1 -1
  14. package/dist/agent-runtime/src/chat-outbox.d.ts +29 -0
  15. package/dist/agent-runtime/src/chat-outbox.js +46 -5
  16. package/dist/agent-runtime/src/chat-outbox.js.map +1 -1
  17. package/dist/agent-runtime/src/conversation-index.cjs.map +1 -1
  18. package/dist/agent-runtime/src/conversation-index.d.ts +7 -0
  19. package/dist/agent-runtime/src/conversation-index.js.map +1 -1
  20. package/dist/agent-runtime/src/conversation-list.cjs +2 -1
  21. package/dist/agent-runtime/src/conversation-list.cjs.map +1 -1
  22. package/dist/agent-runtime/src/conversation-list.d.ts +19 -3
  23. package/dist/agent-runtime/src/conversation-list.js +2 -1
  24. package/dist/agent-runtime/src/conversation-list.js.map +1 -1
  25. package/dist/agent-runtime/src/index.d.ts +6 -5
  26. package/dist/agent-runtime/src/interrupts.cjs +28 -1
  27. package/dist/agent-runtime/src/interrupts.cjs.map +1 -1
  28. package/dist/agent-runtime/src/interrupts.d.ts +21 -1
  29. package/dist/agent-runtime/src/interrupts.js +28 -1
  30. package/dist/agent-runtime/src/interrupts.js.map +1 -1
  31. package/dist/agent-runtime/src/model-context.cjs +3 -2
  32. package/dist/agent-runtime/src/model-context.cjs.map +1 -1
  33. package/dist/agent-runtime/src/model-context.d.ts +6 -1
  34. package/dist/agent-runtime/src/model-context.js +3 -2
  35. package/dist/agent-runtime/src/model-context.js.map +1 -1
  36. package/dist/agent-runtime/src/runtime.d.ts +3 -2
  37. package/dist/agent-runtime/src/send-recovery.cjs +37 -3
  38. package/dist/agent-runtime/src/send-recovery.cjs.map +1 -1
  39. package/dist/agent-runtime/src/send-recovery.d.ts +19 -0
  40. package/dist/agent-runtime/src/send-recovery.js +37 -3
  41. package/dist/agent-runtime/src/send-recovery.js.map +1 -1
  42. package/dist/agent-runtime/src/thread-handoff-flow-machine.d.ts +8 -8
  43. package/dist/agent-runtime/src/thread-handoff-protocol.d.ts +113 -1
  44. package/dist/agent-runtime/src/thread-handoff.d.ts +1 -1
  45. package/dist/agent-runtime/src/use-conversation-list.cjs +3 -2
  46. package/dist/agent-runtime/src/use-conversation-list.cjs.map +1 -1
  47. package/dist/agent-runtime/src/use-conversation-list.d.ts +6 -0
  48. package/dist/agent-runtime/src/use-conversation-list.js +3 -2
  49. package/dist/agent-runtime/src/use-conversation-list.js.map +1 -1
  50. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs +21 -1
  51. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs.map +1 -1
  52. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.d.ts +8 -2
  53. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js +21 -1
  54. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js.map +1 -1
  55. package/dist/agent-runtime/src/use-deep-agent-runtime.cjs +24 -6
  56. package/dist/agent-runtime/src/use-deep-agent-runtime.cjs.map +1 -1
  57. package/dist/agent-runtime/src/use-deep-agent-runtime.d.ts +17 -0
  58. package/dist/agent-runtime/src/use-deep-agent-runtime.js +24 -6
  59. package/dist/agent-runtime/src/use-deep-agent-runtime.js.map +1 -1
  60. package/dist/agent-runtime/src/use-deep-agent-voice.cjs +44 -9
  61. package/dist/agent-runtime/src/use-deep-agent-voice.cjs.map +1 -1
  62. package/dist/agent-runtime/src/use-deep-agent-voice.d.ts +34 -1
  63. package/dist/agent-runtime/src/use-deep-agent-voice.js +44 -9
  64. package/dist/agent-runtime/src/use-deep-agent-voice.js.map +1 -1
  65. package/dist/agent-runtime/src/use-thread-handoff-flow.d.ts +8 -2
  66. package/dist/agent-runtime/src/voice-earcon.cjs +139 -0
  67. package/dist/agent-runtime/src/voice-earcon.cjs.map +1 -0
  68. package/dist/agent-runtime/src/voice-earcon.d.ts +38 -0
  69. package/dist/agent-runtime/src/voice-earcon.js +139 -0
  70. package/dist/agent-runtime/src/voice-earcon.js.map +1 -0
  71. package/dist/agent-runtime/src/voice-media.cjs.map +1 -1
  72. package/dist/agent-runtime/src/voice-media.d.ts +14 -0
  73. package/dist/agent-runtime/src/voice-media.js.map +1 -1
  74. package/dist/agent-runtime/src/voice-presentation.cjs +2 -0
  75. package/dist/agent-runtime/src/voice-presentation.cjs.map +1 -1
  76. package/dist/agent-runtime/src/voice-presentation.js +2 -0
  77. package/dist/agent-runtime/src/voice-presentation.js.map +1 -1
  78. package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
  79. package/dist/assets/AthenaAssetEmbed.d.ts +11 -5
  80. package/dist/assets/AthenaAssetEmbed.js.map +1 -1
  81. package/dist/assets/use-asset-embed.cjs.map +1 -1
  82. package/dist/assets/use-asset-embed.d.ts +12 -5
  83. package/dist/assets/use-asset-embed.js.map +1 -1
  84. package/dist/chat/StatewireApprovalCard.cjs +41 -6
  85. package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
  86. package/dist/chat/StatewireApprovalCard.d.ts +18 -1
  87. package/dist/chat/StatewireApprovalCard.js +42 -7
  88. package/dist/chat/StatewireApprovalCard.js.map +1 -1
  89. package/dist/chat/StatewireQueuedMessages.cjs +56 -6
  90. package/dist/chat/StatewireQueuedMessages.cjs.map +1 -1
  91. package/dist/chat/StatewireQueuedMessages.d.ts +14 -0
  92. package/dist/chat/StatewireQueuedMessages.js +58 -8
  93. package/dist/chat/StatewireQueuedMessages.js.map +1 -1
  94. package/dist/chat/WaitingOnYouBanner.cjs +6 -2
  95. package/dist/chat/WaitingOnYouBanner.cjs.map +1 -1
  96. package/dist/chat/WaitingOnYouBanner.js +7 -3
  97. package/dist/chat/WaitingOnYouBanner.js.map +1 -1
  98. package/dist/chat/chat-voice-controls.cjs +3 -1
  99. package/dist/chat/chat-voice-controls.cjs.map +1 -1
  100. package/dist/chat/chat-voice-controls.d.ts +6 -1
  101. package/dist/chat/chat-voice-controls.js +3 -1
  102. package/dist/chat/chat-voice-controls.js.map +1 -1
  103. package/dist/chat-ui/index.cjs +3 -0
  104. package/dist/chat-ui/index.cjs.map +1 -1
  105. package/dist/chat-ui/index.d.ts +1 -0
  106. package/dist/chat-ui/index.js +3 -0
  107. package/dist/chat-ui/index.js.map +1 -1
  108. package/dist/chat-ui/messages/from-the-call-card.cjs +124 -0
  109. package/dist/chat-ui/messages/from-the-call-card.cjs.map +1 -0
  110. package/dist/chat-ui/messages/from-the-call-card.d.ts +17 -0
  111. package/dist/chat-ui/messages/from-the-call-card.js +124 -0
  112. package/dist/chat-ui/messages/from-the-call-card.js.map +1 -0
  113. package/dist/chat-ui/rich-text/chat-link.cjs +1 -0
  114. package/dist/chat-ui/rich-text/chat-link.cjs.map +1 -1
  115. package/dist/chat-ui/rich-text/chat-link.d.ts +4 -0
  116. package/dist/chat-ui/rich-text/chat-link.js +4 -3
  117. package/dist/chat-ui/rich-text/chat-link.js.map +1 -1
  118. package/dist/chat-ui/rich-text/chat-message-body.cjs +1 -0
  119. package/dist/chat-ui/rich-text/chat-message-body.cjs.map +1 -1
  120. package/dist/chat-ui/rich-text/chat-message-body.d.ts +12 -0
  121. package/dist/chat-ui/rich-text/chat-message-body.js +2 -1
  122. package/dist/chat-ui/rich-text/chat-message-body.js.map +1 -1
  123. package/dist/chat-ui/thread/chat-thread-shell.cjs +2 -0
  124. package/dist/chat-ui/thread/chat-thread-shell.cjs.map +1 -1
  125. package/dist/chat-ui/thread/chat-thread-shell.js +2 -0
  126. package/dist/chat-ui/thread/chat-thread-shell.js.map +1 -1
  127. package/dist/collab/client.cjs +15 -3
  128. package/dist/collab/client.cjs.map +1 -1
  129. package/dist/collab/client.d.ts +4 -2
  130. package/dist/collab/client.js +16 -4
  131. package/dist/collab/client.js.map +1 -1
  132. package/dist/composer/index.d.ts +1 -1
  133. package/dist/composer/useReadComposerDraft.cjs +26 -7
  134. package/dist/composer/useReadComposerDraft.cjs.map +1 -1
  135. package/dist/composer/useReadComposerDraft.d.ts +23 -1
  136. package/dist/composer/useReadComposerDraft.js +27 -8
  137. package/dist/composer/useReadComposerDraft.js.map +1 -1
  138. package/dist/index.cjs +3 -0
  139. package/dist/index.cjs.map +1 -1
  140. package/dist/index.d.ts +3 -1
  141. package/dist/index.js +4 -1
  142. package/dist/index.js.map +1 -1
  143. package/dist/keryx-http/src/unconfirmed-update-attributions.cjs +190 -0
  144. package/dist/keryx-http/src/unconfirmed-update-attributions.cjs.map +1 -0
  145. package/dist/keryx-http/src/unconfirmed-update-attributions.d.ts +132 -0
  146. package/dist/keryx-http/src/unconfirmed-update-attributions.js +190 -0
  147. package/dist/keryx-http/src/unconfirmed-update-attributions.js.map +1 -0
  148. package/dist/keryx-http/src/update-attributions.cjs +34 -4
  149. package/dist/keryx-http/src/update-attributions.cjs.map +1 -1
  150. package/dist/keryx-http/src/update-attributions.d.ts +36 -4
  151. package/dist/keryx-http/src/update-attributions.js +35 -5
  152. package/dist/keryx-http/src/update-attributions.js.map +1 -1
  153. package/dist/provider/AthenaProvider.cjs +4 -0
  154. package/dist/provider/AthenaProvider.cjs.map +1 -1
  155. package/dist/provider/AthenaProvider.d.ts +11 -1
  156. package/dist/provider/AthenaProvider.js +4 -0
  157. package/dist/provider/AthenaProvider.js.map +1 -1
  158. package/dist/react/package.json.cjs +1 -1
  159. package/dist/react/package.json.js +1 -1
  160. package/dist/threads/api.cjs.map +1 -1
  161. package/dist/threads/api.d.ts +15 -0
  162. package/dist/threads/api.js.map +1 -1
  163. package/dist/threads/conversation-list-fetcher.cjs +13 -5
  164. package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
  165. package/dist/threads/conversation-list-fetcher.d.ts +4 -1
  166. package/dist/threads/conversation-list-fetcher.js +13 -5
  167. package/dist/threads/conversation-list-fetcher.js.map +1 -1
  168. package/dist/threads/statewire-thread-list.cjs +28 -6
  169. package/dist/threads/statewire-thread-list.cjs.map +1 -1
  170. package/dist/threads/statewire-thread-list.d.ts +20 -8
  171. package/dist/threads/statewire-thread-list.js +28 -6
  172. package/dist/threads/statewire-thread-list.js.map +1 -1
  173. package/dist/threads/use-athena-sessions.cjs +27 -5
  174. package/dist/threads/use-athena-sessions.cjs.map +1 -1
  175. package/dist/threads/use-athena-sessions.d.ts +10 -5
  176. package/dist/threads/use-athena-sessions.js +28 -6
  177. package/dist/threads/use-athena-sessions.js.map +1 -1
  178. package/dist/threads/use-rest-conversation-history.d.ts +1 -0
  179. package/package.json +1 -1
@@ -0,0 +1,142 @@
1
+ /**
2
+ * "From the call": what a voice call produced or mentioned, gathered where the
3
+ * call's stretch of the transcript ends, so a person who talked to the agent
4
+ * and later reads the conversation as text (on the same app or another) finds
5
+ * the call's assets, links and open approvals in one place.
6
+ *
7
+ * Everything here is read from the transcript every app already renders, and
8
+ * nothing is invented: an asset item is an asset link or `@`-mention in a
9
+ * message, or a tool result that names the asset it made or read (`asset_id`
10
+ * with a `title`); a link item is a web link in a message; an approval item is
11
+ * a tool call of the call's that has no result yet, shown only while the
12
+ * host's live approval request still names it (the presenter's
13
+ * {@link callRecapItems}).
14
+ *
15
+ * **Where a call is in the transcript.** The host marks every turn a call
16
+ * commits (`metadata.custom.source`, see `voice-presentation.ts`): spoken turns
17
+ * (`voice`), messages typed into the call (`text`, id `voicetext_…`) and the
18
+ * tasks the live model handed the thread (`voice_delegation`, id
19
+ * `voicetask_…`). A typed message sent outside a call carries none of these. So
20
+ * a call's stretch starts at its first marked turn and runs, through the
21
+ * assistant work in between (the delegated runs), up to the next user message
22
+ * that is not a call turn, or the end of the transcript. A spoken assistant
23
+ * turn the converter joined onto the end of an earlier run's message starts
24
+ * the stretch at that part, so the earlier run's artifacts stay out. A
25
+ * delegated run's `send_voice_update` call (a tool only voice-delegated runs
26
+ * get) marks the call too, so a windowed transcript whose loaded tail starts
27
+ * inside a long delegated run still finds it.
28
+ *
29
+ * Two calls with no typed message between them read as one stretch: the
30
+ * transcript does not say where one ended and the next began (the host keeps
31
+ * no per-call marker), so their card collects both. A stretch that ends the
32
+ * transcript may still be live; the presenter shows it once the host reports
33
+ * no voice session (`state.voice` is null), see {@link mayHaveLiveCall}.
34
+ *
35
+ * **Cost.** Every message row asks {@link callRecapAfter} on every transcript
36
+ * update. A row that does not end a stretch answers after looking at the next
37
+ * message; a row that does walks back over its own stretch only. What a message
38
+ * and each of its parts contribute is memoized per object (statewire replaces a
39
+ * changed message and assistant-ui keeps every unchanged part), and an equal
40
+ * recap is the same frozen object, so a row selecting it re-renders only when
41
+ * the card's contents change. The transcript is never re-scanned per delta.
42
+ */
43
+ /** Id prefix of a message typed into a live call (`voice/user-text`). */
44
+ export declare const VOICE_TYPED_ID_PREFIX = "voicetext_";
45
+ /** An asset the call created, read or linked. */
46
+ export interface CallRecapAsset {
47
+ readonly kind: 'asset';
48
+ /** Unique within a recap. */
49
+ readonly key: string;
50
+ readonly assetId: string;
51
+ /** What the transcript calls it (the link's label, or the tool result's title); may be empty. */
52
+ readonly title: string;
53
+ /** The asset type the link or tool named (`document`, `spreadsheet`, …), when it named one. */
54
+ readonly assetType: string | null;
55
+ /** The link the transcript carried for it, when it came from a link. */
56
+ readonly href: string | null;
57
+ }
58
+ /** A web page the call linked. */
59
+ export interface CallRecapLink {
60
+ readonly kind: 'link';
61
+ readonly key: string;
62
+ readonly url: string;
63
+ /** The link's own label, else the page's host and path. */
64
+ readonly label: string;
65
+ }
66
+ /** A tool call of the call's that is waiting for the person's approval. */
67
+ export interface CallRecapApproval {
68
+ readonly kind: 'approval';
69
+ readonly key: string;
70
+ readonly toolCallId: string;
71
+ readonly toolName: string;
72
+ }
73
+ export type CallRecapItem = CallRecapAsset | CallRecapLink | CallRecapApproval;
74
+ /** A tool call in the stretch with no result yet: an approval while the host's request names it. */
75
+ export interface CallRecapOpenToolCall {
76
+ readonly toolCallId: string;
77
+ readonly toolName: string;
78
+ }
79
+ export interface CallRecap {
80
+ /** The message the stretch ends at; the card renders right after it. */
81
+ readonly endMessageId: string;
82
+ /** The stretch ends the transcript, so the call may still be live. */
83
+ readonly trailing: boolean;
84
+ /** Assets, in the order the stretch first mentioned them. */
85
+ readonly assets: readonly CallRecapAsset[];
86
+ /** Web links, in the order the stretch first mentioned them. */
87
+ readonly links: readonly CallRecapLink[];
88
+ /** Result-less tool calls, in transcript order. */
89
+ readonly openToolCalls: readonly CallRecapOpenToolCall[];
90
+ }
91
+ /** The fields of an assistant-ui thread message this reads (structural, so any runtime's message fits). */
92
+ export interface CallRecapMessage {
93
+ readonly id: string;
94
+ readonly role: string;
95
+ readonly content: readonly CallRecapPart[];
96
+ readonly metadata?: {
97
+ readonly custom?: Record<string, unknown>;
98
+ } | undefined;
99
+ }
100
+ /** The fields of a message part this reads. */
101
+ export interface CallRecapPart {
102
+ readonly type: string;
103
+ readonly text?: string;
104
+ readonly toolCallId?: string;
105
+ readonly toolName?: string;
106
+ readonly result?: unknown;
107
+ readonly isError?: boolean;
108
+ /** An interim result a running tool published (`markPreliminaryToolResults`); the real one has not arrived. */
109
+ readonly isPreliminary?: boolean;
110
+ readonly status?: unknown;
111
+ }
112
+ /**
113
+ * Whether a user message is one of a call's turns: spoken, typed into the call,
114
+ * or a task the live model delegated (by source, or by id on hosts that drop
115
+ * the command's metadata).
116
+ */
117
+ export declare function isCallUserMessage(message: Pick<CallRecapMessage, 'id' | 'role' | 'metadata'>): boolean;
118
+ /**
119
+ * Whether a stretch that ends the transcript may still be a live call: the
120
+ * host reports a voice session (`state.voice`), or its state has not loaded
121
+ * yet. A primitive read of the raw statewire state, safe as a selector.
122
+ */
123
+ export declare function mayHaveLiveCall(state: unknown): boolean;
124
+ /**
125
+ * The recap of the call stretch that ends at `messages[index]`, or `null` when
126
+ * no call stretch ends there. Cheap for every other row (it looks at the next
127
+ * message only); the same frozen object while the recap is unchanged, so it is
128
+ * safe as a `useAuiState` selector result.
129
+ */
130
+ export declare function callRecapAfter(messages: readonly CallRecapMessage[], index: number): CallRecap | null;
131
+ /** Whether a recap has anything to show once approvals are matched (see {@link callRecapItems}). */
132
+ export declare function callRecapHasItems(recap: CallRecap, awaitingApprovalToolCallIds?: ReadonlySet<string>): boolean;
133
+ /**
134
+ * The card's items: the call's tool calls still waiting for the person's
135
+ * approval (the host's live request names them), then its assets, then its
136
+ * web links. Pure; hosts memoize it on the recap and the id set.
137
+ */
138
+ export declare function callRecapItems(recap: CallRecap, awaitingApprovalToolCallIds?: ReadonlySet<string>): CallRecapItem[];
139
+ /** The card's heading, worded once for every app. */
140
+ export declare const CALL_RECAP_TITLE = "From the call";
141
+ /** An approval item's copy, worded once for every app. */
142
+ export declare const CALL_RECAP_APPROVAL_LABEL = "Waiting for your approval";
@@ -0,0 +1,319 @@
1
+ import { getExternalStoreMessages } from "@assistant-ui/core";
2
+ import { isDeepAgentStateHydrated, readDeepAgentStreamValues } from "./statewire-state.js";
3
+ import { resultRecord, stringField, classifyToolResult } from "./tool-result.js";
4
+ import { VOICE_DELEGATION_ID_PREFIX, isSpokenTurnTextPart, VOICE_UPDATE_TOOL_NAME, VOICE_SOURCE_SPOKEN, VOICE_SOURCE_TYPED, VOICE_SOURCE_DELEGATION } from "./voice-presentation.js";
5
+ const VOICE_TYPED_ID_PREFIX = "voicetext_";
6
+ const CALL_SOURCES = /* @__PURE__ */ new Set([
7
+ VOICE_SOURCE_SPOKEN,
8
+ VOICE_SOURCE_TYPED,
9
+ VOICE_SOURCE_DELEGATION
10
+ ]);
11
+ function isCallUserMessage(message) {
12
+ var _a, _b;
13
+ if (message.role !== "user") return false;
14
+ if (CALL_SOURCES.has((_b = (_a = message.metadata) == null ? void 0 : _a.custom) == null ? void 0 : _b.source)) return true;
15
+ return message.id.startsWith(VOICE_DELEGATION_ID_PREFIX) || message.id.startsWith(VOICE_TYPED_ID_PREFIX);
16
+ }
17
+ function mayHaveLiveCall(state) {
18
+ if (!isDeepAgentStateHydrated(state)) return true;
19
+ const voice = readDeepAgentStreamValues(state).voice;
20
+ return voice !== null && voice !== void 0;
21
+ }
22
+ const NO_ENTRIES = Object.freeze([]);
23
+ const MARKDOWN_LINK = /\[((?:[^[\]\n]|\[[^[\]\n]*\])*)\]\(\s*<?((?:[^()\s<>]|\([^()\s]*\))+)>?(?:\s+"[^"\n]*")?\s*\)/g;
24
+ const BARE_URL = /\bhttps?:\/\/[^\s<>()[\]"'`]+/g;
25
+ const TRAILING_PUNCTUATION = /[.,;:!?'"*_]+$/;
26
+ const SPACES_PATH = /\/dashboard\/spaces\/?$/;
27
+ const MAX_TOOL_RESULT_CHARS = 2e4;
28
+ function assetEntry(asset) {
29
+ return {
30
+ kind: "asset",
31
+ asset: { kind: "asset", key: `asset:${asset.assetId}`, ...asset }
32
+ };
33
+ }
34
+ function normalizeAssetType(value) {
35
+ if (!value) return null;
36
+ return value === "sheet" ? "spreadsheet" : value;
37
+ }
38
+ function labelOf(raw) {
39
+ return raw.trim().replace(/^@/, "").trim();
40
+ }
41
+ function displayUrl(url) {
42
+ const host = url.hostname.replace(/^www\./, "");
43
+ const path = url.pathname === "/" ? "" : url.pathname.replace(/\/$/, "");
44
+ return `${host}${path}`;
45
+ }
46
+ const RELATIVE_LINK_BASE = "https://app.athenaintel.com";
47
+ function entriesOfHref(href, rawLabel) {
48
+ const label = labelOf(rawLabel);
49
+ let url;
50
+ try {
51
+ url = new URL(href, href.startsWith("/") ? RELATIVE_LINK_BASE : void 0);
52
+ } catch {
53
+ return [];
54
+ }
55
+ if (url.protocol === "mention:") {
56
+ if (url.hostname !== "asset") return [];
57
+ const assetId = url.searchParams.get("id") ?? url.searchParams.get("asset_id");
58
+ if (!(assetId == null ? void 0 : assetId.startsWith("asset_"))) return [];
59
+ return [
60
+ assetEntry({
61
+ assetId,
62
+ title: label || (url.searchParams.get("name") ?? ""),
63
+ assetType: normalizeAssetType(
64
+ url.searchParams.get("asset_type") ?? url.searchParams.get("type")
65
+ ),
66
+ href
67
+ })
68
+ ];
69
+ }
70
+ if (url.protocol !== "https:" && url.protocol !== "http:") return [];
71
+ const assetIds = url.searchParams.get("asset_ids");
72
+ if (assetIds && SPACES_PATH.test(url.pathname)) {
73
+ const ids = [
74
+ ...new Set(
75
+ assetIds.split(",").map((id) => id.trim()).filter((id) => id.startsWith("asset_"))
76
+ )
77
+ ];
78
+ const assetType = normalizeAssetType(
79
+ url.searchParams.get("type") ?? url.searchParams.get("assetType") ?? url.searchParams.get("asset_type")
80
+ );
81
+ return ids.map((assetId) => {
82
+ if (ids.length === 1) return assetEntry({ assetId, title: label, assetType, href });
83
+ const single = new URL(url.toString());
84
+ single.searchParams.set("asset_ids", assetId);
85
+ return assetEntry({
86
+ assetId,
87
+ title: label,
88
+ assetType,
89
+ href: href.startsWith("/") ? `${single.pathname}${single.search}` : single.toString()
90
+ });
91
+ });
92
+ }
93
+ if (href.startsWith("/")) return [];
94
+ const shown = label && label !== href ? label : displayUrl(url);
95
+ const key = `${url.origin}${url.pathname.replace(/\/$/, "")}${url.search}`;
96
+ return [
97
+ {
98
+ kind: "link",
99
+ link: { kind: "link", key: `link:${key}`, url: url.toString(), label: shown }
100
+ }
101
+ ];
102
+ }
103
+ const CODE = /```[\s\S]*?(?:```|$)|`[^`\n]*`/g;
104
+ function textEntries(raw) {
105
+ if (!raw.includes("://") && !raw.includes("](/")) return NO_ENTRIES;
106
+ const text = raw.includes("`") ? raw.replace(CODE, (code) => " ".repeat(code.length)) : raw;
107
+ const found = [];
108
+ const covered = [];
109
+ for (const match of text.matchAll(MARKDOWN_LINK)) {
110
+ const [whole, label = "", href = ""] = match;
111
+ covered.push([match.index, match.index + whole.length]);
112
+ if (text[match.index - 1] === "!") continue;
113
+ for (const entry of entriesOfHref(href, label)) found.push({ index: match.index, entry });
114
+ }
115
+ for (const match of text.matchAll(BARE_URL)) {
116
+ const start = match.index;
117
+ if (covered.some(([from, to]) => start >= from && start < to)) continue;
118
+ const href = match[0].replace(TRAILING_PUNCTUATION, "");
119
+ for (const entry of entriesOfHref(href, "")) found.push({ index: start, entry });
120
+ }
121
+ if (found.length === 0) return NO_ENTRIES;
122
+ found.sort((a, b) => a.index - b.index);
123
+ return Object.freeze(found.map(({ entry }) => entry));
124
+ }
125
+ function toolCallEntries(part) {
126
+ const toolCallId = part.toolCallId;
127
+ const toolName = part.toolName ?? "tool";
128
+ if (!toolCallId) return NO_ENTRIES;
129
+ if (part.result === void 0) {
130
+ return Object.freeze([{ kind: "open-call", call: { toolCallId, toolName } }]);
131
+ }
132
+ if (part.isPreliminary === true) return NO_ENTRIES;
133
+ if (typeof part.result === "string" && part.result.length > MAX_TOOL_RESULT_CHARS)
134
+ return NO_ENTRIES;
135
+ const record = resultRecord(part.result);
136
+ if (!record) return NO_ENTRIES;
137
+ const assetId = stringField(record, ["asset_id", "assetId"]);
138
+ const title = stringField(record, ["title", "asset_title", "name"]);
139
+ if (!(assetId == null ? void 0 : assetId.startsWith("asset_")) || !title) return NO_ENTRIES;
140
+ const outcome = classifyToolResult({
141
+ status: { type: "complete" },
142
+ result: part.result,
143
+ isError: part.isError
144
+ });
145
+ if (outcome.state !== "complete") return NO_ENTRIES;
146
+ return Object.freeze([
147
+ assetEntry({
148
+ assetId,
149
+ title: title.trim(),
150
+ assetType: normalizeAssetType(stringField(record, ["asset_type", "assetType"])),
151
+ href: null
152
+ })
153
+ ]);
154
+ }
155
+ const partEntriesCache = /* @__PURE__ */ new WeakMap();
156
+ function partEntries(part) {
157
+ const cached = partEntriesCache.get(part);
158
+ if (cached !== void 0) return cached;
159
+ const entries = part.type === "text" && typeof part.text === "string" ? textEntries(part.text) : part.type === "tool-call" ? toolCallEntries(part) : NO_ENTRIES;
160
+ partEntriesCache.set(part, entries);
161
+ return entries;
162
+ }
163
+ const factsCache = /* @__PURE__ */ new WeakMap();
164
+ function isBoundPart(part) {
165
+ return getExternalStoreMessages(part).length > 0;
166
+ }
167
+ function messageFacts(message) {
168
+ var _a, _b;
169
+ const cached = factsCache.get(message);
170
+ if (cached !== void 0) return cached;
171
+ let facts;
172
+ if (message.role === "user") {
173
+ const call = isCallUserMessage(message);
174
+ facts = { breaksCall: !call, callFrom: call ? 0 : -1 };
175
+ } else if (message.role === "assistant") {
176
+ let callFrom = message.content.findIndex(
177
+ (part) => part.type === "text" && isSpokenTurnTextPart(part) || part.type === "tool-call" && part.toolName === VOICE_UPDATE_TOOL_NAME
178
+ );
179
+ if (callFrom < 0 && ((_b = (_a = message.metadata) == null ? void 0 : _a.custom) == null ? void 0 : _b.source) === VOICE_SOURCE_SPOKEN && !message.content.some(isBoundPart)) {
180
+ callFrom = 0;
181
+ }
182
+ facts = { breaksCall: false, callFrom };
183
+ } else {
184
+ facts = { breaksCall: false, callFrom: -1 };
185
+ }
186
+ factsCache.set(message, facts);
187
+ return facts;
188
+ }
189
+ const recapMemo = /* @__PURE__ */ new WeakMap();
190
+ const internedRecaps = /* @__PURE__ */ new Map();
191
+ const MAX_INTERNED_RECAPS = 200;
192
+ function recapSignature(recap) {
193
+ return JSON.stringify([
194
+ recap.endMessageId,
195
+ recap.trailing,
196
+ recap.assets.map((asset) => [asset.assetId, asset.title, asset.assetType, asset.href]),
197
+ recap.links.map((link) => [link.url, link.label]),
198
+ recap.openToolCalls.map((call) => [call.toolCallId, call.toolName])
199
+ ]);
200
+ }
201
+ function intern(recap) {
202
+ const signature = recapSignature(recap);
203
+ const existing = internedRecaps.get(signature);
204
+ if (existing !== void 0) {
205
+ internedRecaps.delete(signature);
206
+ internedRecaps.set(signature, existing);
207
+ return existing;
208
+ }
209
+ const frozen = Object.freeze({
210
+ ...recap,
211
+ assets: Object.freeze([...recap.assets]),
212
+ links: Object.freeze([...recap.links]),
213
+ openToolCalls: Object.freeze([...recap.openToolCalls])
214
+ });
215
+ internedRecaps.set(signature, frozen);
216
+ if (internedRecaps.size > MAX_INTERNED_RECAPS) {
217
+ const oldest = internedRecaps.keys().next().value;
218
+ if (oldest !== void 0) internedRecaps.delete(oldest);
219
+ }
220
+ return frozen;
221
+ }
222
+ function sameSpan(a, b) {
223
+ if (a.length !== b.length) return false;
224
+ for (let index = 0; index < a.length; index += 1) {
225
+ if (a[index] !== b[index]) return false;
226
+ }
227
+ return true;
228
+ }
229
+ function buildRecap({
230
+ span,
231
+ startPart,
232
+ trailing
233
+ }) {
234
+ const assets = /* @__PURE__ */ new Map();
235
+ const links = /* @__PURE__ */ new Map();
236
+ const openToolCalls = /* @__PURE__ */ new Map();
237
+ span.forEach((message, position) => {
238
+ const parts = position === 0 ? message.content.slice(startPart) : message.content;
239
+ for (const part of parts) {
240
+ for (const entry of partEntries(part)) {
241
+ if (entry.kind === "asset") {
242
+ const known = assets.get(entry.asset.assetId);
243
+ if (!known) {
244
+ assets.set(entry.asset.assetId, entry.asset);
245
+ } else if (!known.title && entry.asset.title || !known.assetType && entry.asset.assetType) {
246
+ assets.set(entry.asset.assetId, {
247
+ ...known,
248
+ title: known.title || entry.asset.title,
249
+ assetType: known.assetType ?? entry.asset.assetType,
250
+ href: known.href ?? entry.asset.href
251
+ });
252
+ }
253
+ } else if (entry.kind === "link") {
254
+ if (!links.has(entry.link.key)) links.set(entry.link.key, entry.link);
255
+ } else if (!openToolCalls.has(entry.call.toolCallId)) {
256
+ openToolCalls.set(entry.call.toolCallId, entry.call);
257
+ }
258
+ }
259
+ }
260
+ });
261
+ const end = span[span.length - 1];
262
+ return {
263
+ endMessageId: (end == null ? void 0 : end.id) ?? "",
264
+ trailing,
265
+ assets: [...assets.values()],
266
+ links: [...links.values()],
267
+ openToolCalls: [...openToolCalls.values()]
268
+ };
269
+ }
270
+ function callRecapAfter(messages, index) {
271
+ const message = messages[index];
272
+ if (message === void 0) return null;
273
+ const next = messages[index + 1];
274
+ if (next !== void 0 && (next.role !== "user" || isCallUserMessage(next))) return null;
275
+ let start = -1;
276
+ let startPart = -1;
277
+ for (let position = index; position >= 0; position -= 1) {
278
+ const candidate = messages[position];
279
+ if (candidate === void 0) break;
280
+ const facts = messageFacts(candidate);
281
+ if (facts.breaksCall) break;
282
+ if (facts.callFrom >= 0) {
283
+ start = position;
284
+ startPart = facts.callFrom;
285
+ }
286
+ }
287
+ if (start < 0) return null;
288
+ const span = messages.slice(start, index + 1);
289
+ const trailing = next === void 0;
290
+ const memo = recapMemo.get(message);
291
+ if (memo !== void 0 && memo.startPart === startPart && memo.trailing === trailing && sameSpan(memo.span, span)) {
292
+ return memo.recap;
293
+ }
294
+ const recap = intern(buildRecap({ span, startPart, trailing }));
295
+ recapMemo.set(message, { span, startPart, trailing, recap });
296
+ return recap;
297
+ }
298
+ const EMPTY_IDS = /* @__PURE__ */ new Set();
299
+ function callRecapItems(recap, awaitingApprovalToolCallIds = EMPTY_IDS) {
300
+ const approvals = recap.openToolCalls.filter((call) => awaitingApprovalToolCallIds.has(call.toolCallId)).map((call) => ({
301
+ kind: "approval",
302
+ key: `approval:${call.toolCallId}`,
303
+ toolCallId: call.toolCallId,
304
+ toolName: call.toolName
305
+ }));
306
+ return [...approvals, ...recap.assets, ...recap.links];
307
+ }
308
+ const CALL_RECAP_TITLE = "From the call";
309
+ const CALL_RECAP_APPROVAL_LABEL = "Waiting for your approval";
310
+ export {
311
+ CALL_RECAP_APPROVAL_LABEL,
312
+ CALL_RECAP_TITLE,
313
+ VOICE_TYPED_ID_PREFIX,
314
+ callRecapAfter,
315
+ callRecapItems,
316
+ isCallUserMessage,
317
+ mayHaveLiveCall
318
+ };
319
+ //# sourceMappingURL=call-recap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"call-recap.js","sources":["../../../../agent-runtime/src/call-recap.ts"],"sourcesContent":["/**\n * \"From the call\": what a voice call produced or mentioned, gathered where the\n * call's stretch of the transcript ends, so a person who talked to the agent\n * and later reads the conversation as text (on the same app or another) finds\n * the call's assets, links and open approvals in one place.\n *\n * Everything here is read from the transcript every app already renders, and\n * nothing is invented: an asset item is an asset link or `@`-mention in a\n * message, or a tool result that names the asset it made or read (`asset_id`\n * with a `title`); a link item is a web link in a message; an approval item is\n * a tool call of the call's that has no result yet, shown only while the\n * host's live approval request still names it (the presenter's\n * {@link callRecapItems}).\n *\n * **Where a call is in the transcript.** The host marks every turn a call\n * commits (`metadata.custom.source`, see `voice-presentation.ts`): spoken turns\n * (`voice`), messages typed into the call (`text`, id `voicetext_…`) and the\n * tasks the live model handed the thread (`voice_delegation`, id\n * `voicetask_…`). A typed message sent outside a call carries none of these. So\n * a call's stretch starts at its first marked turn and runs, through the\n * assistant work in between (the delegated runs), up to the next user message\n * that is not a call turn, or the end of the transcript. A spoken assistant\n * turn the converter joined onto the end of an earlier run's message starts\n * the stretch at that part, so the earlier run's artifacts stay out. A\n * delegated run's `send_voice_update` call (a tool only voice-delegated runs\n * get) marks the call too, so a windowed transcript whose loaded tail starts\n * inside a long delegated run still finds it.\n *\n * Two calls with no typed message between them read as one stretch: the\n * transcript does not say where one ended and the next began (the host keeps\n * no per-call marker), so their card collects both. A stretch that ends the\n * transcript may still be live; the presenter shows it once the host reports\n * no voice session (`state.voice` is null), see {@link mayHaveLiveCall}.\n *\n * **Cost.** Every message row asks {@link callRecapAfter} on every transcript\n * update. A row that does not end a stretch answers after looking at the next\n * message; a row that does walks back over its own stretch only. What a message\n * and each of its parts contribute is memoized per object (statewire replaces a\n * changed message and assistant-ui keeps every unchanged part), and an equal\n * recap is the same frozen object, so a row selecting it re-renders only when\n * the card's contents change. The transcript is never re-scanned per delta.\n */\n\nimport { getExternalStoreMessages, type ThreadMessage } from '@assistant-ui/core';\nimport { isDeepAgentStateHydrated, readDeepAgentStreamValues } from './statewire-state';\nimport {\n classifyToolResult,\n resultRecord,\n stringField,\n type ToolCallStatusLike,\n} from './tool-result';\nimport {\n isSpokenTurnTextPart,\n VOICE_DELEGATION_ID_PREFIX,\n VOICE_UPDATE_TOOL_NAME,\n VOICE_SOURCE_DELEGATION,\n VOICE_SOURCE_SPOKEN,\n VOICE_SOURCE_TYPED,\n} from './voice-presentation';\n\n/** Id prefix of a message typed into a live call (`voice/user-text`). */\nexport const VOICE_TYPED_ID_PREFIX = 'voicetext_';\n\n/** An asset the call created, read or linked. */\nexport interface CallRecapAsset {\n readonly kind: 'asset';\n /** Unique within a recap. */\n readonly key: string;\n readonly assetId: string;\n /** What the transcript calls it (the link's label, or the tool result's title); may be empty. */\n readonly title: string;\n /** The asset type the link or tool named (`document`, `spreadsheet`, …), when it named one. */\n readonly assetType: string | null;\n /** The link the transcript carried for it, when it came from a link. */\n readonly href: string | null;\n}\n\n/** A web page the call linked. */\nexport interface CallRecapLink {\n readonly kind: 'link';\n readonly key: string;\n readonly url: string;\n /** The link's own label, else the page's host and path. */\n readonly label: string;\n}\n\n/** A tool call of the call's that is waiting for the person's approval. */\nexport interface CallRecapApproval {\n readonly kind: 'approval';\n readonly key: string;\n readonly toolCallId: string;\n readonly toolName: string;\n}\n\nexport type CallRecapItem = CallRecapAsset | CallRecapLink | CallRecapApproval;\n\n/** A tool call in the stretch with no result yet: an approval while the host's request names it. */\nexport interface CallRecapOpenToolCall {\n readonly toolCallId: string;\n readonly toolName: string;\n}\n\nexport interface CallRecap {\n /** The message the stretch ends at; the card renders right after it. */\n readonly endMessageId: string;\n /** The stretch ends the transcript, so the call may still be live. */\n readonly trailing: boolean;\n /** Assets, in the order the stretch first mentioned them. */\n readonly assets: readonly CallRecapAsset[];\n /** Web links, in the order the stretch first mentioned them. */\n readonly links: readonly CallRecapLink[];\n /** Result-less tool calls, in transcript order. */\n readonly openToolCalls: readonly CallRecapOpenToolCall[];\n}\n\n/** The fields of an assistant-ui thread message this reads (structural, so any runtime's message fits). */\nexport interface CallRecapMessage {\n readonly id: string;\n readonly role: string;\n readonly content: readonly CallRecapPart[];\n readonly metadata?: { readonly custom?: Record<string, unknown> } | undefined;\n}\n\n/** The fields of a message part this reads. */\nexport interface CallRecapPart {\n readonly type: string;\n readonly text?: string;\n readonly toolCallId?: string;\n readonly toolName?: string;\n readonly result?: unknown;\n readonly isError?: boolean;\n /** An interim result a running tool published (`markPreliminaryToolResults`); the real one has not arrived. */\n readonly isPreliminary?: boolean;\n readonly status?: unknown;\n}\n\nconst CALL_SOURCES: ReadonlySet<unknown> = new Set([\n VOICE_SOURCE_SPOKEN,\n VOICE_SOURCE_TYPED,\n VOICE_SOURCE_DELEGATION,\n]);\n\n/**\n * Whether a user message is one of a call's turns: spoken, typed into the call,\n * or a task the live model delegated (by source, or by id on hosts that drop\n * the command's metadata).\n */\nexport function isCallUserMessage(\n message: Pick<CallRecapMessage, 'id' | 'role' | 'metadata'>,\n): boolean {\n if (message.role !== 'user') return false;\n if (CALL_SOURCES.has(message.metadata?.custom?.source)) return true;\n return (\n message.id.startsWith(VOICE_DELEGATION_ID_PREFIX) ||\n message.id.startsWith(VOICE_TYPED_ID_PREFIX)\n );\n}\n\n/**\n * Whether a stretch that ends the transcript may still be a live call: the\n * host reports a voice session (`state.voice`), or its state has not loaded\n * yet. A primitive read of the raw statewire state, safe as a selector.\n */\nexport function mayHaveLiveCall(state: unknown): boolean {\n if (!isDeepAgentStateHydrated(state)) return true;\n const voice: unknown = readDeepAgentStreamValues(state).voice;\n return voice !== null && voice !== undefined;\n}\n\n// ── Per-part entries ─────────────────────────────────────────────────────────\n\ntype Entry =\n | { readonly kind: 'asset'; readonly asset: CallRecapAsset }\n | { readonly kind: 'link'; readonly link: CallRecapLink }\n | { readonly kind: 'open-call'; readonly call: CallRecapOpenToolCall };\n\nconst NO_ENTRIES: readonly Entry[] = Object.freeze([]);\n\n/** `[label](href)` with an optional `\"title\"`; one level of brackets in the label, one of parens in the href. */\nconst MARKDOWN_LINK =\n /\\[((?:[^[\\]\\n]|\\[[^[\\]\\n]*\\])*)\\]\\(\\s*<?((?:[^()\\s<>]|\\([^()\\s]*\\))+)>?(?:\\s+\"[^\"\\n]*\")?\\s*\\)/g;\n/** A bare web address in prose. */\nconst BARE_URL = /\\bhttps?:\\/\\/[^\\s<>()[\\]\"'`]+/g;\n/** Sentence punctuation that follows an address rather than belonging to it. */\nconst TRAILING_PUNCTUATION = /[.,;:!?'\"*_]+$/;\n/** The Spaces route an asset link opens (`/dashboard/spaces?asset_ids=…`). */\nconst SPACES_PATH = /\\/dashboard\\/spaces\\/?$/;\n/** Past this a tool result is not read for an asset: a created asset's answer is small. */\nconst MAX_TOOL_RESULT_CHARS = 20_000;\n\nfunction assetEntry(asset: Omit<CallRecapAsset, 'kind' | 'key'>): Entry {\n return {\n kind: 'asset',\n asset: { kind: 'asset', key: `asset:${asset.assetId}`, ...asset },\n };\n}\n\n/** `sheet` is the old spelling of `spreadsheet` in Spaces links. */\nfunction normalizeAssetType(value: string | null): string | null {\n if (!value) return null;\n return value === 'sheet' ? 'spreadsheet' : value;\n}\n\nfunction labelOf(raw: string): string {\n return raw.trim().replace(/^@/, '').trim();\n}\n\n/** The page's host and path, for a link that carries no label of its own. */\nfunction displayUrl(url: URL): string {\n const host = url.hostname.replace(/^www\\./, '');\n const path = url.pathname === '/' ? '' : url.pathname.replace(/\\/$/, '');\n return `${host}${path}`;\n}\n\n/** The base a relative Spaces link (`/dashboard/spaces?asset_ids=…`) resolves against; only its query is read. */\nconst RELATIVE_LINK_BASE = 'https://app.athenaintel.com';\n\n/** The assets an Athena link names, the web page a link names, or nothing. */\nfunction entriesOfHref(href: string, rawLabel: string): readonly Entry[] {\n const label = labelOf(rawLabel);\n let url: URL;\n try {\n url = new URL(href, href.startsWith('/') ? RELATIVE_LINK_BASE : undefined);\n } catch {\n return [];\n }\n if (url.protocol === 'mention:') {\n if (url.hostname !== 'asset') return [];\n const assetId = url.searchParams.get('id') ?? url.searchParams.get('asset_id');\n if (!assetId?.startsWith('asset_')) return [];\n return [\n assetEntry({\n assetId,\n title: label || (url.searchParams.get('name') ?? ''),\n assetType: normalizeAssetType(\n url.searchParams.get('asset_type') ?? url.searchParams.get('type'),\n ),\n href,\n }),\n ];\n }\n if (url.protocol !== 'https:' && url.protocol !== 'http:') return [];\n const assetIds = url.searchParams.get('asset_ids');\n if (assetIds && SPACES_PATH.test(url.pathname)) {\n const ids = [\n ...new Set(\n assetIds\n .split(',')\n .map((id) => id.trim())\n .filter((id) => id.startsWith('asset_')),\n ),\n ];\n const assetType = normalizeAssetType(\n url.searchParams.get('type') ??\n url.searchParams.get('assetType') ??\n url.searchParams.get('asset_type'),\n );\n // A link that opens several assets side by side names each of them: one\n // item per asset, each opening only its own asset.\n return ids.map((assetId) => {\n if (ids.length === 1) return assetEntry({ assetId, title: label, assetType, href });\n const single = new URL(url.toString());\n single.searchParams.set('asset_ids', assetId);\n return assetEntry({\n assetId,\n title: label,\n assetType,\n href: href.startsWith('/') ? `${single.pathname}${single.search}` : single.toString(),\n });\n });\n }\n // Any other relative link is a route inside the app, not a page the call named.\n if (href.startsWith('/')) return [];\n const shown = label && label !== href ? label : displayUrl(url);\n const key = `${url.origin}${url.pathname.replace(/\\/$/, '')}${url.search}`;\n return [\n {\n kind: 'link',\n link: { kind: 'link', key: `link:${key}`, url: url.toString(), label: shown },\n },\n ];\n}\n\n/** Fenced code blocks and inline code spans: an address shown as code is not a link the message names. */\nconst CODE = /```[\\s\\S]*?(?:```|$)|`[^`\\n]*`/g;\n\n/** The asset and web links a text part carries, in the order it carries them. */\nfunction textEntries(raw: string): readonly Entry[] {\n // An address (`https://`, `mention://`) or a relative Spaces link (`](/dashboard/…`).\n if (!raw.includes('://') && !raw.includes('](/')) return NO_ENTRIES;\n // Blanked to the same length, so the rest keeps its offsets (the order the part names things in).\n const text = raw.includes('`') ? raw.replace(CODE, (code) => ' '.repeat(code.length)) : raw;\n const found: { index: number; entry: Entry }[] = [];\n const covered: [number, number][] = [];\n for (const match of text.matchAll(MARKDOWN_LINK)) {\n const [whole, label = '', href = ''] = match;\n covered.push([match.index, match.index + whole.length]);\n // `![alt](src)` is an image the message shows, not a link it names.\n if (text[match.index - 1] === '!') continue;\n for (const entry of entriesOfHref(href, label)) found.push({ index: match.index, entry });\n }\n for (const match of text.matchAll(BARE_URL)) {\n const start = match.index;\n if (covered.some(([from, to]) => start >= from && start < to)) continue;\n const href = match[0].replace(TRAILING_PUNCTUATION, '');\n for (const entry of entriesOfHref(href, '')) found.push({ index: start, entry });\n }\n if (found.length === 0) return NO_ENTRIES;\n found.sort((a, b) => a.index - b.index);\n return Object.freeze(found.map(({ entry }) => entry));\n}\n\n/** The asset a finished tool call reports (`asset_id` with a `title`), the open call, or nothing. */\nfunction toolCallEntries(part: CallRecapPart): readonly Entry[] {\n const toolCallId = part.toolCallId;\n const toolName = part.toolName ?? 'tool';\n if (!toolCallId) return NO_ENTRIES;\n if (part.result === undefined) {\n return Object.freeze([{ kind: 'open-call', call: { toolCallId, toolName } }]);\n }\n // A running tool's interim payload is not its outcome: the asset counts once the tool finished.\n if (part.isPreliminary === true) return NO_ENTRIES;\n if (typeof part.result === 'string' && part.result.length > MAX_TOOL_RESULT_CHARS)\n return NO_ENTRIES;\n const record = resultRecord(part.result);\n if (!record) return NO_ENTRIES;\n const assetId = stringField(record, ['asset_id', 'assetId']);\n const title = stringField(record, ['title', 'asset_title', 'name']);\n if (!assetId?.startsWith('asset_') || !title) return NO_ENTRIES;\n const outcome = classifyToolResult({\n status: { type: 'complete' } satisfies ToolCallStatusLike,\n result: part.result,\n isError: part.isError,\n });\n if (outcome.state !== 'complete') return NO_ENTRIES;\n return Object.freeze([\n assetEntry({\n assetId,\n title: title.trim(),\n assetType: normalizeAssetType(stringField(record, ['asset_type', 'assetType'])),\n href: null,\n }),\n ]);\n}\n\nconst partEntriesCache = new WeakMap<object, readonly Entry[]>();\n\nfunction partEntries(part: CallRecapPart): readonly Entry[] {\n const cached = partEntriesCache.get(part);\n if (cached !== undefined) return cached;\n const entries =\n part.type === 'text' && typeof part.text === 'string'\n ? textEntries(part.text)\n : part.type === 'tool-call'\n ? toolCallEntries(part)\n : NO_ENTRIES;\n partEntriesCache.set(part, entries);\n return entries;\n}\n\n// ── Per-message facts ────────────────────────────────────────────────────────\n\ninterface MessageFacts {\n /** A user message outside any call: it ends the stretch before it. */\n readonly breaksCall: boolean;\n /** The first part that belongs to a call (a call turn's first part, a spoken assistant part, a voice update), or -1. */\n readonly callFrom: number;\n}\n\nconst factsCache = new WeakMap<object, MessageFacts>();\n\n/** Whether the converter bound a part to its wire entry (`isSpokenTurnTextPart` reads that entry). */\nfunction isBoundPart(part: CallRecapPart): boolean {\n return getExternalStoreMessages<unknown>(part as ThreadMessage['content'][number]).length > 0;\n}\n\nfunction messageFacts(message: CallRecapMessage): MessageFacts {\n const cached = factsCache.get(message);\n if (cached !== undefined) return cached;\n let facts: MessageFacts;\n if (message.role === 'user') {\n const call = isCallUserMessage(message);\n facts = { breaksCall: !call, callFrom: call ? 0 : -1 };\n } else if (message.role === 'assistant') {\n // A spoken turn, or the interim-update tool only a run the live model\n // delegated is given (`send_voice_update`): a delegated run still marks its\n // call when a windowed transcript has not loaded the turns before it.\n let callFrom = message.content.findIndex(\n (part) =>\n (part.type === 'text' && isSpokenTurnTextPart(part)) ||\n (part.type === 'tool-call' && part.toolName === VOICE_UPDATE_TOOL_NAME),\n );\n // A message no converter bound reads by its own source (one spoken turn).\n if (\n callFrom < 0 &&\n message.metadata?.custom?.source === VOICE_SOURCE_SPOKEN &&\n !message.content.some(isBoundPart)\n ) {\n callFrom = 0;\n }\n facts = { breaksCall: false, callFrom };\n } else {\n facts = { breaksCall: false, callFrom: -1 };\n }\n factsCache.set(message, facts);\n return facts;\n}\n\n// ── The recap at a row ───────────────────────────────────────────────────────\n\ninterface RecapMemo {\n /** The stretch's messages, start first, as last read. */\n readonly span: readonly CallRecapMessage[];\n readonly startPart: number;\n readonly trailing: boolean;\n readonly recap: CallRecap;\n}\n\n/** Keyed by the stretch's last message: a settled stretch answers from here without rebuilding. */\nconst recapMemo = new WeakMap<object, RecapMemo>();\n\n/** Equal recaps share one frozen object (bounded; a miss only costs a rebuild). */\nconst internedRecaps = new Map<string, CallRecap>();\nconst MAX_INTERNED_RECAPS = 200;\n\nfunction recapSignature(recap: CallRecap): string {\n return JSON.stringify([\n recap.endMessageId,\n recap.trailing,\n recap.assets.map((asset) => [asset.assetId, asset.title, asset.assetType, asset.href]),\n recap.links.map((link) => [link.url, link.label]),\n recap.openToolCalls.map((call) => [call.toolCallId, call.toolName]),\n ]);\n}\n\nfunction intern(recap: CallRecap): CallRecap {\n const signature = recapSignature(recap);\n const existing = internedRecaps.get(signature);\n if (existing !== undefined) {\n // Most recently used last, so the bound evicts the stalest.\n internedRecaps.delete(signature);\n internedRecaps.set(signature, existing);\n return existing;\n }\n const frozen: CallRecap = Object.freeze({\n ...recap,\n assets: Object.freeze([...recap.assets]),\n links: Object.freeze([...recap.links]),\n openToolCalls: Object.freeze([...recap.openToolCalls]),\n });\n internedRecaps.set(signature, frozen);\n if (internedRecaps.size > MAX_INTERNED_RECAPS) {\n const oldest = internedRecaps.keys().next().value;\n if (oldest !== undefined) internedRecaps.delete(oldest);\n }\n return frozen;\n}\n\nfunction sameSpan(a: readonly CallRecapMessage[], b: readonly CallRecapMessage[]): boolean {\n if (a.length !== b.length) return false;\n for (let index = 0; index < a.length; index += 1) {\n if (a[index] !== b[index]) return false;\n }\n return true;\n}\n\nfunction buildRecap({\n span,\n startPart,\n trailing,\n}: {\n span: readonly CallRecapMessage[];\n startPart: number;\n trailing: boolean;\n}): CallRecap {\n const assets = new Map<string, CallRecapAsset>();\n const links = new Map<string, CallRecapLink>();\n const openToolCalls = new Map<string, CallRecapOpenToolCall>();\n span.forEach((message, position) => {\n const parts = position === 0 ? message.content.slice(startPart) : message.content;\n for (const part of parts) {\n for (const entry of partEntries(part)) {\n if (entry.kind === 'asset') {\n const known = assets.get(entry.asset.assetId);\n if (!known) {\n assets.set(entry.asset.assetId, entry.asset);\n } else if (\n (!known.title && entry.asset.title) ||\n (!known.assetType && entry.asset.assetType)\n ) {\n // A later mention fills what the first one left out; the first keeps its place.\n assets.set(entry.asset.assetId, {\n ...known,\n title: known.title || entry.asset.title,\n assetType: known.assetType ?? entry.asset.assetType,\n href: known.href ?? entry.asset.href,\n });\n }\n } else if (entry.kind === 'link') {\n if (!links.has(entry.link.key)) links.set(entry.link.key, entry.link);\n } else if (!openToolCalls.has(entry.call.toolCallId)) {\n openToolCalls.set(entry.call.toolCallId, entry.call);\n }\n }\n }\n });\n const end = span[span.length - 1];\n return {\n endMessageId: end?.id ?? '',\n trailing,\n assets: [...assets.values()],\n links: [...links.values()],\n openToolCalls: [...openToolCalls.values()],\n };\n}\n\n/**\n * The recap of the call stretch that ends at `messages[index]`, or `null` when\n * no call stretch ends there. Cheap for every other row (it looks at the next\n * message only); the same frozen object while the recap is unchanged, so it is\n * safe as a `useAuiState` selector result.\n */\nexport function callRecapAfter(\n messages: readonly CallRecapMessage[],\n index: number,\n): CallRecap | null {\n const message = messages[index];\n if (message === undefined) return null;\n const next = messages[index + 1];\n // Only a user message outside a call ends a stretch; an assistant message never does.\n if (next !== undefined && (next.role !== 'user' || isCallUserMessage(next))) return null;\n\n // Walk back over this stretch only: to the user message outside a call that opened it.\n let start = -1;\n let startPart = -1;\n for (let position = index; position >= 0; position -= 1) {\n const candidate = messages[position];\n if (candidate === undefined) break;\n const facts = messageFacts(candidate);\n if (facts.breaksCall) break;\n if (facts.callFrom >= 0) {\n start = position;\n startPart = facts.callFrom;\n }\n }\n if (start < 0) return null;\n\n const span = messages.slice(start, index + 1);\n const trailing = next === undefined;\n const memo = recapMemo.get(message);\n if (\n memo !== undefined &&\n memo.startPart === startPart &&\n memo.trailing === trailing &&\n sameSpan(memo.span, span)\n ) {\n return memo.recap;\n }\n const recap = intern(buildRecap({ span, startPart, trailing }));\n recapMemo.set(message, { span, startPart, trailing, recap });\n return recap;\n}\n\n/** Whether a recap has anything to show once approvals are matched (see {@link callRecapItems}). */\nexport function callRecapHasItems(\n recap: CallRecap,\n awaitingApprovalToolCallIds: ReadonlySet<string> = EMPTY_IDS,\n): boolean {\n return (\n recap.assets.length > 0 ||\n recap.links.length > 0 ||\n recap.openToolCalls.some((call) => awaitingApprovalToolCallIds.has(call.toolCallId))\n );\n}\n\nconst EMPTY_IDS: ReadonlySet<string> = new Set();\n\n/**\n * The card's items: the call's tool calls still waiting for the person's\n * approval (the host's live request names them), then its assets, then its\n * web links. Pure; hosts memoize it on the recap and the id set.\n */\nexport function callRecapItems(\n recap: CallRecap,\n awaitingApprovalToolCallIds: ReadonlySet<string> = EMPTY_IDS,\n): CallRecapItem[] {\n const approvals: CallRecapApproval[] = recap.openToolCalls\n .filter((call) => awaitingApprovalToolCallIds.has(call.toolCallId))\n .map((call) => ({\n kind: 'approval',\n key: `approval:${call.toolCallId}`,\n toolCallId: call.toolCallId,\n toolName: call.toolName,\n }));\n return [...approvals, ...recap.assets, ...recap.links];\n}\n\n/** The card's heading, worded once for every app. */\nexport const CALL_RECAP_TITLE = 'From the call';\n\n/** An approval item's copy, worded once for every app. */\nexport const CALL_RECAP_APPROVAL_LABEL = 'Waiting for your approval';\n"],"names":[],"mappings":"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"}
@@ -82,6 +82,7 @@ function parseChatOutboxEntry(value) {
82
82
  ...typeof value.composerMarkdown === "string" && { composerMarkdown: value.composerMarkdown },
83
83
  ...typeof value.composerHtml === "string" && { composerHtml: value.composerHtml },
84
84
  ...isRecord(value.metadata) && { metadata: value.metadata },
85
+ ...isRecord(value.sendContext) && !Array.isArray(value.sendContext) && { sendContext: value.sendContext },
85
86
  ...files && { files },
86
87
  ...attachments && { attachments }
87
88
  };