@theoremjs/react 0.0.0-stage → 0.3.1

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 (320) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +772 -2
  3. package/client/attachment-hover-preview.d.ts +11 -0
  4. package/client/attachment-hover-preview.js +36 -0
  5. package/client/audio-level.d.ts +5 -0
  6. package/client/audio-level.js +24 -0
  7. package/client/composer-attachments.d.ts +30 -0
  8. package/client/composer-attachments.js +33 -0
  9. package/client/composer-drawer.d.ts +19 -0
  10. package/client/composer-drawer.js +15 -0
  11. package/client/composer-hints.d.ts +25 -0
  12. package/client/composer-hints.js +20 -0
  13. package/client/composer-primary.d.ts +16 -0
  14. package/client/composer-primary.js +25 -0
  15. package/client/decision-images.d.ts +76 -0
  16. package/client/decision-images.js +139 -0
  17. package/client/decision-transport.d.ts +70 -0
  18. package/client/decision-transport.js +175 -0
  19. package/client/decode-composer-draft.d.ts +11 -0
  20. package/client/decode-composer-draft.js +12 -0
  21. package/client/embedding-input.d.ts +84 -0
  22. package/client/embedding-input.js +194 -0
  23. package/client/embedding-space.d.ts +82 -0
  24. package/client/embedding-space.js +389 -0
  25. package/client/embedding-transport.d.ts +40 -0
  26. package/client/embedding-transport.js +71 -0
  27. package/client/encode-composer-draft.d.ts +9 -0
  28. package/client/encode-composer-draft.js +34 -0
  29. package/client/encode-files.d.ts +18 -0
  30. package/client/encode-files.js +54 -0
  31. package/client/failure.d.ts +29 -0
  32. package/client/failure.js +34 -0
  33. package/client/generation-selection.d.ts +15 -0
  34. package/client/generation-selection.js +24 -0
  35. package/client/host-call.d.ts +51 -0
  36. package/client/host-call.js +47 -0
  37. package/client/host-transport.d.ts +70 -0
  38. package/client/host-transport.js +75 -0
  39. package/client/image-output.d.ts +6 -0
  40. package/client/image-output.js +13 -0
  41. package/client/index.d.ts +28 -0
  42. package/client/index.js +22 -0
  43. package/client/ink-waveform.d.ts +17 -0
  44. package/client/ink-waveform.js +68 -0
  45. package/client/live/apply-live-turn-tool-event.d.ts +14 -0
  46. package/client/live/apply-live-turn-tool-event.js +39 -0
  47. package/client/live/live-captions.d.ts +53 -0
  48. package/client/live/live-captions.js +174 -0
  49. package/client/live/live-mic-forward.d.ts +30 -0
  50. package/client/live/live-mic-forward.js +46 -0
  51. package/client/live/live-page-tool.d.ts +34 -0
  52. package/client/live/live-page-tool.js +11 -0
  53. package/client/live/live-state.d.ts +13 -0
  54. package/client/live/live-state.js +22 -0
  55. package/client/live/live-tool.d.ts +11 -0
  56. package/client/live/live-tool.js +1 -0
  57. package/client/live/live-video.d.ts +10 -0
  58. package/client/live/live-video.js +71 -0
  59. package/client/live/run-live-tool-call.d.ts +22 -0
  60. package/client/live/run-live-tool-call.js +70 -0
  61. package/client/live/use-live-caption-log.d.ts +17 -0
  62. package/client/live/use-live-caption-log.js +51 -0
  63. package/client/live-client.d.ts +189 -0
  64. package/client/live-client.js +829 -0
  65. package/client/live-errors.d.ts +2 -0
  66. package/client/live-errors.js +9 -0
  67. package/client/live-messages.d.ts +148 -0
  68. package/client/live-messages.js +100 -0
  69. package/client/mic-capture.d.ts +4 -0
  70. package/client/mic-capture.js +6 -0
  71. package/client/oauth-popup.d.ts +26 -0
  72. package/client/oauth-popup.js +30 -0
  73. package/client/page-dispatch.d.ts +15 -0
  74. package/client/page-dispatch.js +63 -0
  75. package/client/pcm-downsample.d.ts +4 -0
  76. package/client/pcm-downsample.js +45 -0
  77. package/client/run-commit.d.ts +34 -0
  78. package/client/run-commit.js +49 -0
  79. package/client/run-session.d.ts +79 -0
  80. package/client/run-session.js +207 -0
  81. package/client/schema-fields.d.ts +63 -0
  82. package/client/schema-fields.js +199 -0
  83. package/client/shaped-data.d.ts +118 -0
  84. package/client/shaped-data.js +343 -0
  85. package/client/source-citations.d.ts +13 -0
  86. package/client/source-citations.js +56 -0
  87. package/client/space-geometry.d.ts +39 -0
  88. package/client/space-geometry.js +183 -0
  89. package/client/state-fields.d.ts +16 -0
  90. package/client/state-fields.js +44 -0
  91. package/client/tool-result.d.ts +78 -0
  92. package/client/tool-result.js +293 -0
  93. package/client/tool-resume.d.ts +50 -0
  94. package/client/tool-resume.js +14 -0
  95. package/client/trace-feed.d.ts +13 -0
  96. package/client/trace-feed.js +18 -0
  97. package/client/trace-story.d.ts +163 -0
  98. package/client/trace-story.js +440 -0
  99. package/client/trace-view.d.ts +130 -0
  100. package/client/trace-view.js +333 -0
  101. package/client/transcript-groups.d.ts +140 -0
  102. package/client/transcript-groups.js +217 -0
  103. package/client/transcript-schema.d.ts +10 -0
  104. package/client/transcript-schema.js +120 -0
  105. package/client/transcript-scroll.d.ts +7 -0
  106. package/client/transcript-scroll.js +6 -0
  107. package/client/transport.d.ts +264 -0
  108. package/client/transport.js +330 -0
  109. package/client/turn-client.d.ts +44 -0
  110. package/client/turn-client.js +88 -0
  111. package/client/voice-label.d.ts +2 -0
  112. package/client/voice-label.js +15 -0
  113. package/client/voice-recorder.d.ts +27 -0
  114. package/client/voice-recorder.js +162 -0
  115. package/client/wire-line.d.ts +56 -0
  116. package/client/wire-line.js +90 -0
  117. package/client/worklets/mic-capture.js +16 -0
  118. package/components/InkWaveform.d.ts +21 -0
  119. package/components/InkWaveform.js +131 -0
  120. package/components/live/use-live-runner-controls.d.ts +47 -0
  121. package/components/live/use-live-runner-controls.js +96 -0
  122. package/components/live/use-live-runner-model.d.ts +49 -0
  123. package/components/live/use-live-runner-model.js +190 -0
  124. package/components/live/use-live-runner-ui.d.ts +67 -0
  125. package/components/live/use-live-runner-ui.js +167 -0
  126. package/components/live/use-live-session-client.d.ts +41 -0
  127. package/components/live/use-live-session-client.js +126 -0
  128. package/components/use-composer-voice.d.ts +28 -0
  129. package/components/use-composer-voice.js +90 -0
  130. package/components/use-editor-selection.d.ts +3 -0
  131. package/components/use-editor-selection.js +19 -0
  132. package/components/use-voice-playback.d.ts +17 -0
  133. package/components/use-voice-playback.js +68 -0
  134. package/hooks/use-described.d.ts +12 -0
  135. package/hooks/use-described.js +25 -0
  136. package/hooks/use-embedding-index.d.ts +27 -0
  137. package/hooks/use-embedding-index.js +43 -0
  138. package/hooks/use-one-call.d.ts +31 -0
  139. package/hooks/use-one-call.js +76 -0
  140. package/hooks/use-page-tool-warnings.d.ts +6 -0
  141. package/hooks/use-page-tool-warnings.js +25 -0
  142. package/hooks/use-second-ticker.d.ts +2 -0
  143. package/hooks/use-second-ticker.js +13 -0
  144. package/hooks/use-theorem-chat-actions.d.ts +79 -0
  145. package/hooks/use-theorem-chat-actions.js +287 -0
  146. package/hooks/use-theorem-chat-state.d.ts +66 -0
  147. package/hooks/use-theorem-chat-state.js +122 -0
  148. package/hooks/use-theorem-chat.d.ts +97 -0
  149. package/hooks/use-theorem-chat.js +528 -0
  150. package/hooks/use-theorem-decision.d.ts +18 -0
  151. package/hooks/use-theorem-decision.js +12 -0
  152. package/hooks/use-theorem-embedding.d.ts +18 -0
  153. package/hooks/use-theorem-embedding.js +12 -0
  154. package/hooks/use-theorem-host.d.ts +45 -0
  155. package/hooks/use-theorem-host.js +135 -0
  156. package/hooks/use-theorem-interface.d.ts +22 -0
  157. package/hooks/use-theorem-interface.js +15 -0
  158. package/hooks/use-trace-records.d.ts +4 -0
  159. package/hooks/use-trace-records.js +7 -0
  160. package/index.d.ts +17 -0
  161. package/index.js +17 -0
  162. package/live.d.ts +9 -0
  163. package/live.js +10 -0
  164. package/package.json +74 -4
  165. package/server/checkpoint-ownership.d.ts +6 -0
  166. package/server/checkpoint-ownership.js +42 -0
  167. package/server/conversation-store.d.ts +92 -0
  168. package/server/conversation-store.js +131 -0
  169. package/server/conversation.d.ts +193 -0
  170. package/server/conversation.js +552 -0
  171. package/server/credential-store.d.ts +24 -0
  172. package/server/credential-store.js +14 -0
  173. package/server/decision-handler.d.ts +42 -0
  174. package/server/decision-handler.js +46 -0
  175. package/server/embedding-handler.d.ts +33 -0
  176. package/server/embedding-handler.js +45 -0
  177. package/server/handler.d.ts +152 -0
  178. package/server/handler.js +771 -0
  179. package/server/json-response.d.ts +2 -0
  180. package/server/json-response.js +7 -0
  181. package/server/mod.d.ts +25 -0
  182. package/server/mod.js +23 -0
  183. package/server/page-dispatch.d.ts +22 -0
  184. package/server/page-dispatch.js +127 -0
  185. package/server/request-body.d.ts +2 -0
  186. package/server/request-body.js +42 -0
  187. package/server/request-check.d.ts +21 -0
  188. package/server/request-check.js +49 -0
  189. package/server/session-authority.d.ts +8 -0
  190. package/server/session-authority.js +78 -0
  191. package/server/session-boundary.d.ts +26 -0
  192. package/server/session-boundary.js +64 -0
  193. package/server/session-identity.d.ts +16 -0
  194. package/server/session-identity.js +41 -0
  195. package/server/session-limits.d.ts +12 -0
  196. package/server/session-limits.js +59 -0
  197. package/server/session-store.d.ts +123 -0
  198. package/server/session-store.js +264 -0
  199. package/server/single-action.d.ts +21 -0
  200. package/server/single-action.js +30 -0
  201. package/server/steer-inbox.d.ts +35 -0
  202. package/server/steer-inbox.js +52 -0
  203. package/server/tool-operations.d.ts +6 -0
  204. package/server/tool-operations.js +132 -0
  205. package/server/turn-input.d.ts +10 -0
  206. package/server/turn-input.js +29 -0
  207. package/styles/ink-controls.css +134 -0
  208. package/ui/CardWall.d.ts +10 -0
  209. package/ui/CardWall.js +61 -0
  210. package/ui/ChatComposerBar.d.ts +62 -0
  211. package/ui/ChatComposerBar.js +306 -0
  212. package/ui/ChatTranscript.d.ts +43 -0
  213. package/ui/ChatTranscript.js +527 -0
  214. package/ui/ConsoleFrame.d.ts +104 -0
  215. package/ui/ConsoleFrame.js +146 -0
  216. package/ui/DecisionAnswers.d.ts +53 -0
  217. package/ui/DecisionAnswers.js +233 -0
  218. package/ui/DecisionImages.d.ts +16 -0
  219. package/ui/DecisionImages.js +46 -0
  220. package/ui/EmbeddingDocuments.d.ts +78 -0
  221. package/ui/EmbeddingDocuments.js +278 -0
  222. package/ui/EmbeddingQuery.d.ts +27 -0
  223. package/ui/EmbeddingQuery.js +95 -0
  224. package/ui/EmbeddingResults.d.ts +41 -0
  225. package/ui/EmbeddingResults.js +165 -0
  226. package/ui/EmbeddingSpace.d.ts +26 -0
  227. package/ui/EmbeddingSpace.js +106 -0
  228. package/ui/LiveCaptionsPanel.d.ts +22 -0
  229. package/ui/LiveCaptionsPanel.js +27 -0
  230. package/ui/LiveRunner.d.ts +30 -0
  231. package/ui/LiveRunner.js +180 -0
  232. package/ui/PaneState.d.ts +33 -0
  233. package/ui/PaneState.js +50 -0
  234. package/ui/SchemaFields.d.ts +10 -0
  235. package/ui/SchemaFields.js +150 -0
  236. package/ui/ShapedData.d.ts +11 -0
  237. package/ui/ShapedData.js +196 -0
  238. package/ui/SidePanel.d.ts +126 -0
  239. package/ui/SidePanel.js +123 -0
  240. package/ui/StateFields.d.ts +44 -0
  241. package/ui/StateFields.js +139 -0
  242. package/ui/TheoremChat.d.ts +122 -0
  243. package/ui/TheoremChat.js +134 -0
  244. package/ui/TheoremDecision.d.ts +30 -0
  245. package/ui/TheoremDecision.js +130 -0
  246. package/ui/TheoremEmbedding.d.ts +27 -0
  247. package/ui/TheoremEmbedding.js +122 -0
  248. package/ui/TheoremHost.d.ts +55 -0
  249. package/ui/TheoremHost.js +240 -0
  250. package/ui/ToolGateCard.d.ts +32 -0
  251. package/ui/ToolGateCard.js +107 -0
  252. package/ui/ToolResult.d.ts +10 -0
  253. package/ui/ToolResult.js +62 -0
  254. package/ui/ToolResultCharts.d.ts +5 -0
  255. package/ui/ToolResultCharts.js +103 -0
  256. package/ui/TraceCharts.d.ts +13 -0
  257. package/ui/TraceCharts.js +266 -0
  258. package/ui/TraceGuardrails.d.ts +11 -0
  259. package/ui/TraceGuardrails.js +74 -0
  260. package/ui/TraceInspector.d.ts +16 -0
  261. package/ui/TraceInspector.js +206 -0
  262. package/ui/TraceInspectorPanel.d.ts +46 -0
  263. package/ui/TraceInspectorPanel.js +95 -0
  264. package/ui/TraceOverview.d.ts +38 -0
  265. package/ui/TraceOverview.js +126 -0
  266. package/ui/TraceSpanDetail.d.ts +10 -0
  267. package/ui/TraceSpanDetail.js +190 -0
  268. package/ui/TraceStory.d.ts +20 -0
  269. package/ui/TraceStory.js +108 -0
  270. package/ui/TraceValues.d.ts +36 -0
  271. package/ui/TraceValues.js +123 -0
  272. package/ui/TraceWaterfall.d.ts +13 -0
  273. package/ui/TraceWaterfall.js +135 -0
  274. package/ui/VoiceNote.d.ts +12 -0
  275. package/ui/VoiceNote.js +28 -0
  276. package/ui/approval-heading.d.ts +4 -0
  277. package/ui/approval-heading.js +9 -0
  278. package/ui/arrive.d.ts +18 -0
  279. package/ui/arrive.js +46 -0
  280. package/ui/built/theme.css +792 -0
  281. package/ui/built/theme.css.d.ts +9 -0
  282. package/ui/built/theorem.d.ts +13 -0
  283. package/ui/built/theorem.js +660 -0
  284. package/ui/built/theorem.variants.d.ts +22 -0
  285. package/ui/code-language.d.ts +12 -0
  286. package/ui/code-language.js +50 -0
  287. package/ui/disclosure-motion.d.ts +6 -0
  288. package/ui/disclosure-motion.js +207 -0
  289. package/ui/embedding-space-gl.d.ts +37 -0
  290. package/ui/embedding-space-gl.js +663 -0
  291. package/ui/embedding-space-view.d.ts +8 -0
  292. package/ui/embedding-space-view.js +8 -0
  293. package/ui/glide.d.ts +7 -0
  294. package/ui/glide.js +44 -0
  295. package/ui/icons.d.ts +4 -0
  296. package/ui/icons.js +38 -0
  297. package/ui/index.d.ts +36 -0
  298. package/ui/index.js +39 -0
  299. package/ui/labels-provider.d.ts +16 -0
  300. package/ui/labels-provider.js +64 -0
  301. package/ui/labels.d.ts +1442 -0
  302. package/ui/labels.js +1443 -0
  303. package/ui/markdown-plugins.d.ts +9 -0
  304. package/ui/markdown-plugins.js +13 -0
  305. package/ui/mcp-icon.d.ts +6 -0
  306. package/ui/mcp-icon.js +8 -0
  307. package/ui/row-keys.d.ts +19 -0
  308. package/ui/row-keys.js +33 -0
  309. package/ui/theme.d.ts +10 -0
  310. package/ui/theme.js +13 -0
  311. package/ui/theorem-theme.d.ts +10 -0
  312. package/ui/theorem-theme.js +105 -0
  313. package/ui/trace-palette.d.ts +10 -0
  314. package/ui/trace-palette.js +10 -0
  315. package/ui/trace-patterns.d.ts +40 -0
  316. package/ui/trace-patterns.js +57 -0
  317. package/ui/transcript-copy-text.d.ts +4 -0
  318. package/ui/transcript-copy-text.js +40 -0
  319. package/ui/wall-columns.d.ts +8 -0
  320. package/ui/wall-columns.js +19 -0
@@ -0,0 +1,174 @@
1
+ import { foldTurnEvents } from '@theoremjs/agents/interface';
2
+ export function emptyLiveCaptionState() {
3
+ return { turns: [], interimUser: '', interimAgent: '', working: false };
4
+ }
5
+ /** Keep this call's lines so the next call starts below a divider. A call with no lines adds nothing. */
6
+ export function stashLiveCaptionCall(past, turns) {
7
+ if (turns.length === 0)
8
+ return past;
9
+ return [...past, [...turns]];
10
+ }
11
+ function mergeInterimWithFinal(interim, final) {
12
+ if (!interim)
13
+ return final;
14
+ if (!final)
15
+ return interim;
16
+ if (final.startsWith(interim) || interim.startsWith(final)) {
17
+ return final.length >= interim.length ? final : interim;
18
+ }
19
+ return appendTurnText(interim, final);
20
+ }
21
+ function appendTurnText(existing, chunk) {
22
+ if (!existing)
23
+ return chunk;
24
+ if (!chunk)
25
+ return existing;
26
+ const needsSpace = !existing.endsWith(' ') &&
27
+ !chunk.startsWith(' ') &&
28
+ !/^\s/.test(chunk) &&
29
+ !/^[\s.,!?;:]/.test(chunk);
30
+ return needsSpace ? `${existing} ${chunk}` : `${existing}${chunk}`;
31
+ }
32
+ function nextTurnId(state) {
33
+ return `${String(Date.now())}-${String(state.turns.length)}`;
34
+ }
35
+ /**
36
+ * Fold streaming ASR deltas into turn-by-turn caption lines.
37
+ * Final chunks for the same role append to the open turn; role switches start a new turn.
38
+ */
39
+ export function applyLiveTranscript(state, text, isUser, interim, options) {
40
+ if (!text)
41
+ return state;
42
+ if (interim) {
43
+ return isUser ? { ...state, interimUser: text } : { ...state, interimAgent: text };
44
+ }
45
+ const role = isUser ? 'user' : 'agent';
46
+ const turns = [...state.turns];
47
+ const last = turns.at(-1);
48
+ const interimText = isUser ? state.interimUser : state.interimAgent;
49
+ if (!options?.forceNew && last?.role === role) {
50
+ turns[turns.length - 1] = {
51
+ ...last,
52
+ text: appendTurnText(last.text, text),
53
+ };
54
+ }
55
+ else {
56
+ const committed = options?.forceNew ? text : mergeInterimWithFinal(interimText, text);
57
+ turns.push({
58
+ id: nextTurnId(state),
59
+ role,
60
+ text: committed,
61
+ });
62
+ }
63
+ return {
64
+ ...state,
65
+ turns,
66
+ interimUser: isUser ? '' : state.interimUser,
67
+ interimAgent: isUser ? state.interimAgent : '',
68
+ };
69
+ }
70
+ function isWorkEvent(event) {
71
+ return event.type === 'thought' || event.type === 'tool' || event.type === 'citation';
72
+ }
73
+ /** The work that made the call `callId`: where the call's later events go. -1 when no work made it. */
74
+ function callWorkIndex(turns, callId) {
75
+ return turns.findIndex((turn) => turn.role === 'work' &&
76
+ turn.events.some((made) => made.type === 'tool' && made.tool.callId === callId));
77
+ }
78
+ /** Which line takes `event`: `turns.length` starts a new one, -1 leaves the event out. */
79
+ function workIndex(turns, event) {
80
+ if (event.type === 'tool' && event.tool.phase !== undefined) {
81
+ // why: The call view's dialog asks a gate's question; the transcript's card would ask it twice.
82
+ return event.tool.phase === 'gate' ? -1 : callWorkIndex(turns, event.tool.callId);
83
+ }
84
+ const open = turns.at(-1)?.role === 'work';
85
+ // why: The thought guard releases a held blank tail after the speech; it is not work of its own.
86
+ if (event.type === 'thought' && !(open ? event.text : event.text.trim()))
87
+ return -1;
88
+ return open ? turns.length - 1 : turns.length;
89
+ }
90
+ /**
91
+ * Fold a live turn event into the captions. A thought, a tool call, or a citation joins the open
92
+ * work, else starts it; a tool call's later events go to the work that made the call. `done` ends
93
+ * the reply.
94
+ */
95
+ export function applyLiveTurnEvent(state, event) {
96
+ if (event.type === 'done')
97
+ return state.working ? { ...state, working: false } : state;
98
+ if (!isWorkEvent(event))
99
+ return state;
100
+ const at = workIndex(state.turns, event);
101
+ if (at === -1)
102
+ return state;
103
+ const turns = [...state.turns];
104
+ const work = turns[at];
105
+ turns[at] =
106
+ work?.role === 'work'
107
+ ? { ...work, events: [...work.events, event] }
108
+ : { id: nextTurnId(state), role: 'work', events: [event] };
109
+ return { ...state, turns, working: true };
110
+ }
111
+ /** A line as the chat's blocks: speech is one, work is what the chat folds its events into. */
112
+ function turnBlocks(turn) {
113
+ if (turn.role === 'work')
114
+ return foldTurnEvents(turn.events, { idPrefix: turn.id });
115
+ return [{ id: turn.id, kind: turn.role === 'user' ? 'user-text' : 'text', text: turn.text }];
116
+ }
117
+ /** How much of a broken-off line the next one must repeat to count as starting it again. */
118
+ const RESTART_MATCH = 24;
119
+ function sharedStart(a, b) {
120
+ let length = 0;
121
+ while (length < a.length && length < b.length && a[length] === b[length])
122
+ length += 1;
123
+ return length;
124
+ }
125
+ /** The later line opens with the same words as the earlier one, or with all of it when it is short. */
126
+ function restarts(earlier, later) {
127
+ const start = earlier.trim();
128
+ return sharedStart(start, later.trim()) >= Math.min(start.length, RESTART_MATCH);
129
+ }
130
+ /**
131
+ * Drops speech the agent broke off and began again in the same reply, which a
132
+ * tool call mid-sentence makes it do: the line that starts over replaces it.
133
+ */
134
+ function withoutRestarts(lines) {
135
+ return lines.filter((line, index) => {
136
+ if (line.role !== 'agent')
137
+ return true;
138
+ const after = lines.slice(index + 1);
139
+ const nextUser = after.findIndex((later) => later.role === 'user');
140
+ const reply = nextUser === -1 ? after : after.slice(0, nextUser);
141
+ return !reply.some((later) => later.role === 'agent' && restarts(line.text, later.text));
142
+ });
143
+ }
144
+ /** Captions as the chat's transcript blocks: past calls, this call, then the lines still being heard. */
145
+ export function liveCaptionTranscript(pastCalls, { turns, interimUser, interimAgent }) {
146
+ const current = [...turns];
147
+ if (interimUser)
148
+ current.push({ id: 'interim-user', role: 'user', text: interimUser });
149
+ if (interimAgent)
150
+ current.push({ id: 'interim-agent', role: 'agent', text: interimAgent });
151
+ const calls = [
152
+ ...pastCalls.map((call, index) => call.map((turn) => ({ ...turn, id: `${String(index)}:${turn.id}` }))),
153
+ current,
154
+ ]
155
+ .map((call) => withoutRestarts(call).flatMap(turnBlocks))
156
+ .filter((call) => call.length > 0);
157
+ return {
158
+ blocks: calls.flat(),
159
+ callStarts: calls.slice(1).map((call) => call[0].id),
160
+ };
161
+ }
162
+ /** The agent is mid-reply: a line of its speech is still arriving, or it is working. */
163
+ export function liveCaptionStreaming({ working, interimAgent }) {
164
+ return interimAgent !== '' || working;
165
+ }
166
+ /** Clear streaming partials when a live turn completes or is interrupted. */
167
+ export function clearLiveCaptionInterim(state) {
168
+ if (!state.interimUser && !state.interimAgent)
169
+ return state;
170
+ return { ...state, interimUser: '', interimAgent: '' };
171
+ }
172
+ export function latestLiveCaptionTurnId(state) {
173
+ return state.turns.at(-1)?.id ?? null;
174
+ }
@@ -0,0 +1,30 @@
1
+ import type { ToolCallEvent, TurnEventOf } from '@theoremjs/agents';
2
+ /** Whether a mic frame should be sent upstream given mute / socket / barge-in gates. */
3
+ export declare function shouldForwardMicFrame(args: {
4
+ isMuted: boolean;
5
+ socketOpen: boolean;
6
+ modelPlaying: boolean;
7
+ rms: number;
8
+ bargeInRmsWhileSpeaking: number;
9
+ }): boolean;
10
+ /** Classify live evidence transcription into a UI transcript callback payload. */
11
+ export declare function liveTranscriptFromEvidence(event: TurnEventOf<'evidence'>): {
12
+ text: string;
13
+ isUser: boolean;
14
+ interim: boolean;
15
+ } | null;
16
+ export type LiveToolCallDraft = {
17
+ operationId?: string;
18
+ id: string;
19
+ name: string;
20
+ arguments: Record<string, unknown>;
21
+ };
22
+ /**
23
+ * Fold a tool turn event into cancelled-id / runnable-call lists. The model's
24
+ * call is runnable; a failure in the same batch (a name or arguments the
25
+ * provider could not use) is not: the session answered the model with it.
26
+ */
27
+ export declare function applyLiveToolTurnEvent(tool: ToolCallEvent, accum: {
28
+ cancelledToolIds: Set<string>;
29
+ toolCalls: LiveToolCallDraft[];
30
+ }): void;
@@ -0,0 +1,46 @@
1
+ /** Whether a mic frame should be sent upstream given mute / socket / barge-in gates. */
2
+ export function shouldForwardMicFrame(args) {
3
+ if (args.isMuted || !args.socketOpen)
4
+ return false;
5
+ if (args.modelPlaying && args.rms < args.bargeInRmsWhileSpeaking)
6
+ return false;
7
+ return true;
8
+ }
9
+ /** Classify live evidence transcription into a UI transcript callback payload. */
10
+ export function liveTranscriptFromEvidence(event) {
11
+ const { evidence, text } = event;
12
+ if (!text)
13
+ return null;
14
+ if (evidence.kind !== 'input_transcription' && evidence.kind !== 'output_transcription')
15
+ return null;
16
+ return {
17
+ text,
18
+ isUser: evidence.kind === 'input_transcription',
19
+ interim: evidence.interim === true,
20
+ };
21
+ }
22
+ /**
23
+ * Fold a tool turn event into cancelled-id / runnable-call lists. The model's
24
+ * call is runnable; a failure in the same batch (a name or arguments the
25
+ * provider could not use) is not: the session answered the model with it.
26
+ */
27
+ export function applyLiveToolTurnEvent(tool, accum) {
28
+ if (tool.phase === undefined) {
29
+ accum.toolCalls.push({
30
+ id: tool.callId,
31
+ name: tool.name,
32
+ arguments: tool.arguments,
33
+ ...(tool.operationId ? { operationId: tool.operationId } : {}),
34
+ });
35
+ return;
36
+ }
37
+ if (tool.phase === 'cancel') {
38
+ accum.cancelledToolIds.add(tool.callId);
39
+ return;
40
+ }
41
+ if (tool.phase !== 'error')
42
+ return;
43
+ const failed = accum.toolCalls.findIndex((draft) => draft.id === tool.callId);
44
+ if (failed !== -1)
45
+ accum.toolCalls.splice(failed, 1);
46
+ }
@@ -0,0 +1,34 @@
1
+ /** The page's result for a call: the tool's output schema checks it, then the model reads it. */
2
+ export type PageToolAnswer = {
3
+ output: unknown;
4
+ };
5
+ /** One page tool: what the page does for a call the model made, and what it tells the model. */
6
+ export type PageTool = (args: Record<string, unknown>, call: {
7
+ callId: string;
8
+ operationId: string;
9
+ }) => PageToolAnswer | Promise<PageToolAnswer>;
10
+ /**
11
+ * The page's tools, by name: each answers a tool the profile declares with
12
+ * `answeredBy: 'page'`. Any other tool runs on the relay, gates and all.
13
+ */
14
+ export type PageTools = Record<string, PageTool>;
15
+ /** What a host gives a live call. */
16
+ export type LiveCallOptions = {
17
+ /** The value chosen for each of the profile's `inputs.slots`, read as the call starts. */
18
+ slots?: Record<string, string>;
19
+ /**
20
+ * What the page tells the agent: any JSON. It goes with the call's opening, and
21
+ * again whenever it changes, as background the agent reads without replying.
22
+ */
23
+ context?: unknown;
24
+ pageTools?: PageTools;
25
+ };
26
+ /**
27
+ * Where the page's tools and the profile's disagree, for the builder: a page
28
+ * tool with no handler here leaves the model unanswered, and a handler for a
29
+ * tool the profile does not give the page never runs.
30
+ */
31
+ export declare function pageToolMismatch(declared: readonly string[], handled: readonly string[]): {
32
+ unanswered: string[];
33
+ unused: string[];
34
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Where the page's tools and the profile's disagree, for the builder: a page
3
+ * tool with no handler here leaves the model unanswered, and a handler for a
4
+ * tool the profile does not give the page never runs.
5
+ */
6
+ export function pageToolMismatch(declared, handled) {
7
+ return {
8
+ unanswered: declared.filter((name) => !handled.includes(name)),
9
+ unused: handled.filter((name) => !declared.includes(name)),
10
+ };
11
+ }
@@ -0,0 +1,13 @@
1
+ export type LiveSessionStatus = 'disconnected' | 'connecting'
2
+ /** The call dropped and the client is taking it up again. */
3
+ | 'reconnecting' | 'ready' | 'listening' | 'speaking' | 'working' | 'error';
4
+ export type LiveConnectPhase = 'socket' | 'microphone';
5
+ /** What a live call is doing, for its status line; the UI words it. */
6
+ export type LiveState = 'calling_tool' | 'connecting' | 'reconnecting' | 'requesting_mic' | 'speaking' | 'connected' | 'muted' | 'listening' | 'error' | 'ended';
7
+ export declare function liveState(args: {
8
+ status: LiveSessionStatus;
9
+ connectPhase: LiveConnectPhase | null;
10
+ toolName: string | null;
11
+ isMuted: boolean;
12
+ voiceEnabled?: boolean;
13
+ }): LiveState;
@@ -0,0 +1,22 @@
1
+ export function liveState(args) {
2
+ if (args.status === 'reconnecting')
3
+ return 'reconnecting';
4
+ if (args.toolName)
5
+ return 'calling_tool';
6
+ if (args.connectPhase === 'socket')
7
+ return 'connecting';
8
+ if (args.connectPhase === 'microphone')
9
+ return 'requesting_mic';
10
+ if (args.status === 'speaking')
11
+ return 'speaking';
12
+ if (args.status === 'listening' || args.status === 'ready') {
13
+ if (args.voiceEnabled === false)
14
+ return 'connected';
15
+ return args.isMuted ? 'muted' : 'listening';
16
+ }
17
+ if (args.status === 'connecting')
18
+ return 'connecting';
19
+ if (args.status === 'error')
20
+ return 'error';
21
+ return 'ended';
22
+ }
@@ -0,0 +1,11 @@
1
+ import type { ToolGate } from '@theoremjs/agents/kernel';
2
+ import type { ToolGateResolution } from '../tool-resume.ts';
3
+ /** A gate the user is asked to answer, for the model's call `callId`. */
4
+ export type LiveToolGatePrompt = {
5
+ callId: string;
6
+ toolName: string;
7
+ input: Record<string, unknown>;
8
+ gate: ToolGate;
9
+ };
10
+ /** The user's answer to a live gate, or `withdrawn`: the model cancelled the call, so nothing is asked. */
11
+ export type LiveGateAnswer = ToolGateResolution | 'withdrawn';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ export type LiveFacingMode = 'user' | 'environment';
2
+ export type LiveVideoCapture = {
3
+ stop: () => void;
4
+ readonly video: HTMLVideoElement;
5
+ facingMode: () => LiveFacingMode;
6
+ flip: () => Promise<LiveFacingMode>;
7
+ };
8
+ export declare function startLiveVideoCapture(onFrame: (base64: string) => void, options?: {
9
+ facingMode?: LiveFacingMode;
10
+ }): Promise<LiveVideoCapture>;
@@ -0,0 +1,71 @@
1
+ import { TheoremError } from '@theoremjs/agents';
2
+ const JPEG_QUALITY = 0.62;
3
+ const FRAME_INTERVAL_MS = 400;
4
+ function openCamera(facing) {
5
+ return navigator.mediaDevices.getUserMedia({
6
+ video: { facingMode: { ideal: facing }, width: { ideal: 640 }, height: { ideal: 480 } },
7
+ audio: false,
8
+ });
9
+ }
10
+ export function startLiveVideoCapture(onFrame, options) {
11
+ const initialFacing = options?.facingMode ?? 'user';
12
+ return openCamera(initialFacing).then((stream) => {
13
+ const video = document.createElement('video');
14
+ video.srcObject = stream;
15
+ video.muted = true;
16
+ video.playsInline = true;
17
+ video.setAttribute('playsinline', '');
18
+ const canvas = document.createElement('canvas');
19
+ const context = canvas.getContext('2d');
20
+ if (!context) {
21
+ for (const track of stream.getTracks())
22
+ track.stop();
23
+ throw new TheoremError('unsupported', 'no 2d canvas for live video'); // labels-exempt: internal diagnostic
24
+ }
25
+ let timer = null;
26
+ let stopped = false;
27
+ let state = { stream, facing: initialFacing };
28
+ const capture = () => {
29
+ if (stopped || video.videoWidth === 0)
30
+ return;
31
+ canvas.width = video.videoWidth;
32
+ canvas.height = video.videoHeight;
33
+ context.drawImage(video, 0, 0);
34
+ const dataUrl = canvas.toDataURL('image/jpeg', JPEG_QUALITY);
35
+ const base64 = dataUrl.split(',')[1];
36
+ if (base64)
37
+ onFrame(base64);
38
+ };
39
+ const startTimer = () => {
40
+ if (timer || stopped)
41
+ return;
42
+ timer = setInterval(capture, FRAME_INTERVAL_MS);
43
+ };
44
+ void video.play().then(startTimer);
45
+ return {
46
+ video,
47
+ facingMode: () => state.facing,
48
+ flip: async () => {
49
+ if (stopped)
50
+ return state.facing;
51
+ const nextFacing = state.facing === 'user' ? 'environment' : 'user';
52
+ const nextStream = await openCamera(nextFacing);
53
+ for (const track of state.stream.getTracks())
54
+ track.stop();
55
+ state = { stream: nextStream, facing: nextFacing };
56
+ video.srcObject = nextStream;
57
+ await video.play();
58
+ return nextFacing;
59
+ },
60
+ stop: () => {
61
+ stopped = true;
62
+ if (timer)
63
+ clearInterval(timer);
64
+ timer = null;
65
+ video.srcObject = null;
66
+ for (const track of state.stream.getTracks())
67
+ track.stop();
68
+ },
69
+ };
70
+ });
71
+ }
@@ -0,0 +1,22 @@
1
+ import type { ExecuteToolOnRelay } from '../live-messages.ts';
2
+ import type { PageTools } from './live-page-tool.ts';
3
+ import type { LiveGateAnswer, LiveToolGatePrompt } from './live-tool.ts';
4
+ /**
5
+ * Live provider tool-call handler: the page answers the call if it is the
6
+ * page's; else run the model's call on the relay; at a gate, ask the user and
7
+ * send their decision. The session answers the model
8
+ * however the call ends; its tool events tell the user. A call the model
9
+ * cancels while its gate is open is gone: nothing is sent.
10
+ */
11
+ export declare function runLiveToolCall(args: {
12
+ executeToolOnRelay: ExecuteToolOnRelay;
13
+ /** The host's page tools, if it has any. */
14
+ pageTools?: PageTools;
15
+ name: string;
16
+ toolArgs: Record<string, unknown>;
17
+ callId: string;
18
+ operationId: string;
19
+ sessionPermissions: string[];
20
+ setSessionPermissions: (next: string[]) => void;
21
+ waitForGateDecision: (prompt: LiveToolGatePrompt) => Promise<LiveGateAnswer>;
22
+ }): Promise<void>;
@@ -0,0 +1,70 @@
1
+ import { continueGatedToolInvocation } from "../tool-resume.js";
2
+ /**
3
+ * Live provider tool-call handler: the page answers the call if it is the
4
+ * page's; else run the model's call on the relay; at a gate, ask the user and
5
+ * send their decision. The session answers the model
6
+ * however the call ends; its tool events tell the user. A call the model
7
+ * cancels while its gate is open is gone: nothing is sent.
8
+ */
9
+ export async function runLiveToolCall(args) {
10
+ const { executeToolOnRelay, name, toolArgs, callId, operationId } = args;
11
+ let sessionPermissions = args.sessionPermissions;
12
+ // why: A name from the model is looked up among the host's own keys only.
13
+ const pageTool = args.pageTools && Object.hasOwn(args.pageTools, name) ? args.pageTools[name] : undefined;
14
+ let step = await executeToolOnRelay({ callId, operationId });
15
+ const dispatched = new Set();
16
+ while (step.status !== 'settled') {
17
+ if (step.status === 'dispatched') {
18
+ if (dispatched.has(step.grantId))
19
+ return;
20
+ dispatched.add(step.grantId);
21
+ const grantId = step.grantId;
22
+ const input = step.arguments;
23
+ const answer = pageTool
24
+ ? await Promise.resolve()
25
+ .then(() => pageTool(input, { callId, operationId }))
26
+ .catch(() => undefined)
27
+ : undefined;
28
+ step = await executeToolOnRelay({
29
+ callId,
30
+ operationId,
31
+ grantId,
32
+ ...(answer ? { output: answer.output } : { unanswered: true }),
33
+ });
34
+ continue;
35
+ }
36
+ const { gate } = step;
37
+ if (gate.kind === 'page') {
38
+ await executeToolOnRelay({ callId, operationId, decision: 'deny' });
39
+ return;
40
+ }
41
+ const resolution = await args.waitForGateDecision({
42
+ callId,
43
+ toolName: name,
44
+ input: gate.preparedArguments ?? toolArgs,
45
+ gate,
46
+ });
47
+ if (resolution === 'withdrawn')
48
+ return;
49
+ const reply = continueGatedToolInvocation({
50
+ toolName: name,
51
+ gate,
52
+ sessionPermissions,
53
+ resolution,
54
+ });
55
+ if (reply.decision === 'deny') {
56
+ // why: The session settles the refusal; its tool event tells the user.
57
+ await executeToolOnRelay({ callId, operationId, decision: 'deny' });
58
+ return;
59
+ }
60
+ sessionPermissions = reply.sessionPermissions;
61
+ args.setSessionPermissions(reply.sessionPermissions);
62
+ // why: Signed in: a typed key goes once, with the approval; after an OAuth callback there is none.
63
+ step = await executeToolOnRelay({
64
+ callId,
65
+ decision: 'approve',
66
+ operationId,
67
+ ...(reply.secret !== undefined ? { secret: reply.secret } : {}),
68
+ });
69
+ }
70
+ }
@@ -0,0 +1,17 @@
1
+ import type { TurnEvent } from '@theoremjs/agents';
2
+ import { type LiveCaptionState, type LiveCaptionTurn } from './live-captions.ts';
3
+ /**
4
+ * The captions a live call shows: this call's lines, and earlier calls kept
5
+ * when it is started again. Speech arrives through `applyTranscript`, and the
6
+ * call's turn events (thoughts, tool calls, the reply's end) through
7
+ * `applyTurnEvent`. A typed send is `noteSentText`, which always starts its own line.
8
+ */
9
+ export declare function useLiveCaptionLog(): {
10
+ captions: LiveCaptionState;
11
+ pastCalls: LiveCaptionTurn[][];
12
+ applyTranscript: (text: string, isUser: boolean, interim?: boolean) => void;
13
+ applyTurnEvent: (event: TurnEvent) => void;
14
+ noteSentText: (text: string) => void;
15
+ beginNextCall: () => void;
16
+ clear: () => void;
17
+ };
@@ -0,0 +1,51 @@
1
+ import { useCallback, useRef, useState } from 'react';
2
+ import { applyLiveTranscript, applyLiveTurnEvent, emptyLiveCaptionState, stashLiveCaptionCall, } from "./live-captions.js";
3
+ /**
4
+ * The captions a live call shows: this call's lines, and earlier calls kept
5
+ * when it is started again. Speech arrives through `applyTranscript`, and the
6
+ * call's turn events (thoughts, tool calls, the reply's end) through
7
+ * `applyTurnEvent`. A typed send is `noteSentText`, which always starts its own line.
8
+ */
9
+ export function useLiveCaptionLog() {
10
+ const [captions, setCaptions] = useState(emptyLiveCaptionState);
11
+ const [pastCalls, setPastCalls] = useState([]);
12
+ const captionsRef = useRef(captions);
13
+ captionsRef.current = captions;
14
+ const applyTranscript = useCallback((text, isUser, interim) => {
15
+ const next = applyLiveTranscript(captionsRef.current, text, isUser, interim);
16
+ captionsRef.current = next;
17
+ setCaptions(next);
18
+ }, []);
19
+ const applyTurnEvent = useCallback((event) => {
20
+ const next = applyLiveTurnEvent(captionsRef.current, event);
21
+ captionsRef.current = next;
22
+ setCaptions(next);
23
+ }, []);
24
+ const noteSentText = useCallback((text) => {
25
+ const next = applyLiveTranscript(captionsRef.current, text, true, false, { forceNew: true });
26
+ captionsRef.current = next;
27
+ setCaptions(next);
28
+ }, []);
29
+ const beginNextCall = useCallback(() => {
30
+ const previous = captionsRef.current.turns;
31
+ setPastCalls((calls) => stashLiveCaptionCall(calls, previous));
32
+ const next = emptyLiveCaptionState();
33
+ captionsRef.current = next;
34
+ setCaptions(next);
35
+ }, []);
36
+ const clear = useCallback(() => {
37
+ setPastCalls([]);
38
+ const next = emptyLiveCaptionState();
39
+ captionsRef.current = next;
40
+ setCaptions(next);
41
+ }, []);
42
+ return {
43
+ captions,
44
+ pastCalls,
45
+ applyTranscript,
46
+ applyTurnEvent,
47
+ noteSentText,
48
+ beginNextCall,
49
+ clear,
50
+ };
51
+ }