@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,330 @@
1
+ import { gateReferenceSchema, TOOL_GATE_KINDS, } from '@theoremjs/agents';
2
+ /**
3
+ * Transport contract between a Theorem chat UI and the host that runs turns.
4
+ *
5
+ * The browser never holds the profile: it asks the transport to `describe` the
6
+ * profile's client-safe interface, then streams turns / tool invokes through it.
7
+ * `createHttpTransport` speaks to `createTheoremHandler` (`@theoremjs/react/server`);
8
+ * hosts with their own wire format implement {@link TheoremTransport} directly.
9
+ *
10
+ * @module
11
+ */
12
+ import { describeError, errorKindSchema, isAbortError, TheoremError, TURN_EVENT_SCHEMAS, throwIfAborted, turnEventSchema, turnHistoryMessageSchema, turnToolSnapshotSchema, z, } from '@theoremjs/agents';
13
+ import { errorCopiesSchema, kindOfHttpStatus, } from '@theoremjs/agents/guardrails';
14
+ import { profileInterfaceSchema, } from '@theoremjs/agents/interface';
15
+ import { transcriptBlockSchema } from "./transcript-schema.js";
16
+ import { checkWire, parseWireJson, readWireLine, } from "./wire-line.js";
17
+ const encodedBlob = z.object({ name: z.string(), mimeType: z.string(), data: z.string() });
18
+ true;
19
+ const theoremTurnInput = z.object({
20
+ slots: z.record(z.string(), z.string()).optional(),
21
+ context: z.unknown().optional(),
22
+ text: z.string().optional(),
23
+ attachments: z.array(encodedBlob).optional(),
24
+ voice: z.array(encodedBlob).optional(),
25
+ });
26
+ true;
27
+ const theoremReplay = z.object({
28
+ gateKind: z.enum(TOOL_GATE_KINDS).optional(),
29
+ sessionPermissions: z.array(z.string()).optional(),
30
+ name: z.string().optional(),
31
+ input: z.unknown().optional(),
32
+ snapshot: turnToolSnapshotSchema.optional(),
33
+ promoted: z.array(z.string()).optional(),
34
+ model: z.string().optional(),
35
+ effort: z.string().optional(),
36
+ });
37
+ true;
38
+ /** For a host that reads the studio's client-held state. */
39
+ export const theoremReplaySchema = theoremReplay;
40
+ /** An id the client made: trimmed, never blank. */
41
+ const clientId = z.string().trim().min(1);
42
+ const theoremTurnRequest = z.object({
43
+ input: theoremTurnInput,
44
+ conversation: z.string().min(1).optional(),
45
+ model: z.string().optional(),
46
+ effort: z.string().optional(),
47
+ turnId: clientId.optional(),
48
+ abandon: z.array(gateReferenceSchema).min(1).optional(),
49
+ replay: theoremReplay.pick({ sessionPermissions: true }).optional(),
50
+ transcript: z.array(transcriptBlockSchema).optional(),
51
+ });
52
+ true;
53
+ /** A `/turn` body as `createTheoremHandler` reads it. */
54
+ export const theoremTurnRequestSchema = theoremTurnRequest;
55
+ /** What the user answers a gate with; walking away rides on the next message (`TheoremTurnRequest.abandon`). */
56
+ const INVOKE_DECISIONS = ['approve', 'deny'];
57
+ const pagePart = z.union([
58
+ z.strictObject({ unanswered: z.literal(true) }),
59
+ z
60
+ .strictObject({ output: z.unknown() })
61
+ .refine((part) => 'output' in part)
62
+ .transform((part) => ({ output: part.output })),
63
+ ]);
64
+ const theoremInvokeRequest = z.object({
65
+ gateId: z.string().min(1),
66
+ operationId: z.string().min(1).max(256),
67
+ decision: z.enum(INVOKE_DECISIONS),
68
+ input: z.unknown().optional(),
69
+ secret: z.string().optional(),
70
+ page: pagePart.optional(),
71
+ conversation: z.string().min(1).optional(),
72
+ turnId: clientId.optional(),
73
+ replay: theoremReplay.optional(),
74
+ });
75
+ true;
76
+ /** An `/invoke` body as `createTheoremHandler` reads it. */
77
+ export const theoremInvokeRequestSchema = theoremInvokeRequest;
78
+ const theoremSteerRequest = z.object({
79
+ turnId: clientId,
80
+ id: clientId,
81
+ inject: z.array(turnHistoryMessageSchema).min(1),
82
+ });
83
+ true;
84
+ /** A `/steer` body as `createTheoremHandler` reads it. */
85
+ export const theoremSteerRequestSchema = theoremSteerRequest;
86
+ const conversationLine = z.object({
87
+ type: z.literal('conversation'),
88
+ ref: z.string().min(1),
89
+ posted: z.literal(true).optional(),
90
+ });
91
+ true;
92
+ /** The lines a chat's reply streams: turn events, and the conversation's ref. */
93
+ export const CHAT_LINES = {
94
+ ...TURN_EVENT_SCHEMAS,
95
+ conversation: conversationLine,
96
+ };
97
+ const conversationView = z.object({
98
+ transcript: z.array(transcriptBlockSchema),
99
+ paused: z
100
+ .object({ events: z.array(turnEventSchema), blocks: z.array(transcriptBlockSchema) })
101
+ .optional(),
102
+ });
103
+ true;
104
+ const theoremConversationRequest = z.object({ conversation: z.string().min(1) });
105
+ true;
106
+ /** A `/conversation` body as `createTheoremHandler` reads it. */
107
+ export const theoremConversationRequestSchema = theoremConversationRequest;
108
+ const theoremRewindRequest = z.object({
109
+ conversation: z.string().min(1),
110
+ at: z.number().int().min(-1),
111
+ });
112
+ true;
113
+ /** A `/rewind` body as `createTheoremHandler` reads it. */
114
+ export const theoremRewindRequestSchema = theoremRewindRequest;
115
+ const theoremRewound = z.object({ conversation: z.string().min(1), view: conversationView });
116
+ true;
117
+ /**
118
+ * A failure the host reported: its kind, the host's wording when it sent one
119
+ * (the profile's labels, applied on the host), and raw detail when the host
120
+ * exposes it. `clientFailure` words it for the user.
121
+ */
122
+ export class TheoremStreamError extends Error {
123
+ kind;
124
+ publicMessage;
125
+ internalMessage;
126
+ /** The labels key behind `publicMessage`, when the host gave one: what failed, more exactly than `kind`. */
127
+ copy;
128
+ constructor(kind, publicMessage, internalMessage, copy) {
129
+ super(internalMessage ?? publicMessage ?? kind);
130
+ this.name = 'TheoremStreamError';
131
+ this.kind = kind;
132
+ if (publicMessage)
133
+ this.publicMessage = publicMessage;
134
+ if (internalMessage)
135
+ this.internalMessage = internalMessage;
136
+ if (copy)
137
+ this.copy = copy;
138
+ }
139
+ }
140
+ export function isTheoremStreamError(err) {
141
+ return err instanceof TheoremStreamError;
142
+ }
143
+ /** The trimmed string, or undefined when absent or blank. */
144
+ function textOf(value) {
145
+ return value?.trim() || undefined;
146
+ }
147
+ const hostErrorBody = z.object({
148
+ error: z.string().optional(),
149
+ errorKind: errorKindSchema.optional(),
150
+ errorCopy: errorCopiesSchema.optional(),
151
+ errorInternal: z.string().optional(),
152
+ });
153
+ true;
154
+ /** A host's error body, as the transport and the live client read it. */
155
+ export const hostErrorBodySchema = hostErrorBody;
156
+ /** A host's error body as a failure: its kind (else `fallbackKind`), wording, and detail. */
157
+ export function hostError(body, fallbackKind) {
158
+ const publicMessage = textOf(body.error);
159
+ const internal = textOf(body.errorInternal);
160
+ return new TheoremStreamError(body.errorKind ?? fallbackKind, publicMessage, internal && internal !== publicMessage ? internal : undefined, body.errorCopy);
161
+ }
162
+ /** The host could not open the message's conversation (`session.conversation_unknown`). */
163
+ export function isUnknownConversation(err) {
164
+ if (!isTheoremStreamError(err) || err.copy === undefined)
165
+ return false;
166
+ const copies = Array.isArray(err.copy) ? err.copy : [err.copy];
167
+ return copies.some((copy) => copy.key === 'session.conversation_unknown');
168
+ }
169
+ /** One NDJSON line checked against `lines`; an `error` line ends the stream as a {@link TheoremStreamError}. */
170
+ function parseStreamLine(lines, text) {
171
+ const line = readWireLine(lines, text);
172
+ // why: An `error` line passed the error event's schema, whose fields include the body's.
173
+ if (line.type === 'error')
174
+ throw hostError(hostErrorBody.parse(line), 'internal');
175
+ return line;
176
+ }
177
+ function flushNdjsonChunk(buffer, lines, onLine) {
178
+ const chunks = buffer.split('\n');
179
+ const rest = chunks.pop() ?? '';
180
+ for (const chunk of chunks) {
181
+ if (!chunk.trim())
182
+ continue;
183
+ onLine(parseStreamLine(lines, chunk));
184
+ }
185
+ return rest;
186
+ }
187
+ /**
188
+ * Read an NDJSON reply, checking each line against `lines`: a kind it doesn't
189
+ * list reaches `onLine` as `unsupported`, and a line that fails its check as
190
+ * `malformed`; the stream goes on. An `error` line ends it.
191
+ */
192
+ export async function readNdjsonStream(response, lines, onLine, signal) {
193
+ if (!response.body)
194
+ throw new TheoremError('bad_response', 'stream reply has no body'); // labels-exempt: internal diagnostic
195
+ const reader = response.body.getReader();
196
+ const decoder = new TextDecoder();
197
+ let buffer = '';
198
+ const onAbort = () => {
199
+ void reader.cancel();
200
+ };
201
+ signal?.addEventListener('abort', onAbort, { once: true });
202
+ try {
203
+ for (;;) {
204
+ throwIfAborted(signal);
205
+ const { done, value } = await reader.read();
206
+ if (done)
207
+ break;
208
+ buffer = flushNdjsonChunk(buffer + decoder.decode(value, { stream: true }), lines, onLine);
209
+ }
210
+ const tail = buffer.trim();
211
+ if (tail)
212
+ onLine(parseStreamLine(lines, tail));
213
+ }
214
+ finally {
215
+ signal?.removeEventListener('abort', onAbort);
216
+ }
217
+ }
218
+ async function resolveHeaders(options) {
219
+ const extra = typeof options.headers === 'function' ? await options.headers() : options.headers;
220
+ const headers = new Headers(extra);
221
+ headers.set('content-type', 'application/json');
222
+ return headers;
223
+ }
224
+ /**
225
+ * A non-OK reply as a failure: the host's kind and wording, else the status's
226
+ * kind. A body that is not JSON (a proxy's page) leaves the status to speak; a
227
+ * JSON body that fails the error body's schema is `bad_response`.
228
+ */
229
+ async function failureFromResponse(response, failed) {
230
+ const text = await response.text();
231
+ let raw;
232
+ try {
233
+ raw = JSON.parse(text);
234
+ failed?.(raw);
235
+ }
236
+ catch {
237
+ return hostError({ errorInternal: `HTTP ${String(response.status)}` }, kindOfHttpStatus(response.status));
238
+ }
239
+ const body = hostErrorBody.safeParse(raw);
240
+ if (!body.success) {
241
+ // labels-exempt: internal diagnostic; the user reads error.bad_response
242
+ return new TheoremError('bad_response', `HTTP ${String(response.status)} error body failed its wire check`);
243
+ }
244
+ return hostError({ ...body.data, errorInternal: body.data.errorInternal ?? `HTTP ${String(response.status)}` }, kindOfHttpStatus(response.status));
245
+ }
246
+ async function request(url, init, options) {
247
+ const doFetch = options.fetch ?? globalThis.fetch.bind(globalThis);
248
+ try {
249
+ return await doFetch(url, {
250
+ ...init,
251
+ headers: await resolveHeaders(options),
252
+ credentials: options.credentials ?? 'same-origin',
253
+ });
254
+ }
255
+ catch (err) {
256
+ if (isAbortError(err))
257
+ throw err;
258
+ throw new TheoremError('network', describeError(err), { cause: err });
259
+ }
260
+ }
261
+ /**
262
+ * POST JSON and stream NDJSON lines back, each checked against `lines`: turn
263
+ * events ({@link TURN_EVENT_SCHEMAS}), or a host's own schema beside them.
264
+ */
265
+ export async function postNdjson(url, body, lines, onLine, options = {}) {
266
+ const response = await jsonPostResponse(url, body, options);
267
+ await readNdjsonStream(response, lines, onLine, options.signal);
268
+ }
269
+ async function jsonPostResponse(url, body, options) {
270
+ const response = await request(url, { method: 'POST', body: JSON.stringify(body), signal: options.signal }, options);
271
+ if (!response.ok)
272
+ throw await failureFromResponse(response);
273
+ return response;
274
+ }
275
+ /** POST JSON; a non-OK reply throws its failure. The OK reply's body is not read. */
276
+ export async function postJson(url, body, options = {}) {
277
+ await jsonPostResponse(url, body, options);
278
+ }
279
+ /**
280
+ * GET, or POST `body` as JSON, and read the JSON reply; a non-OK reply throws its failure, after
281
+ * `failed` has read whatever else its JSON body carries.
282
+ */
283
+ export async function fetchJson(url, init, options = {}) {
284
+ const method = init.body === undefined
285
+ ? { method: 'GET' }
286
+ : { method: 'POST', body: JSON.stringify(init.body) };
287
+ const response = await request(url, { ...method, signal: init.signal }, options);
288
+ if (!response.ok)
289
+ throw await failureFromResponse(response, init.failed);
290
+ return parseWireJson(await response.text());
291
+ }
292
+ /** POST a conversation's read to `url`, and check the view the host answers. */
293
+ export async function fetchConversation(url, body, options = {}) {
294
+ const { signal, ...http } = options;
295
+ // labels-exempt: internal diagnostic; the user reads error.bad_response
296
+ return checkWire(conversationView, await fetchJson(url, { body, signal }, http), 'the conversation');
297
+ }
298
+ /** POST a rewind to `url`, and check the rewound conversation the host answers. */
299
+ export async function fetchRewound(url, body, options = {}) {
300
+ const { signal, ...http } = options;
301
+ return checkWire(theoremRewound, await fetchJson(url, { body, signal }, http), 'the rewound conversation');
302
+ }
303
+ /** Drops trailing '/'s by scanning back, not with a regex (a `/\/+$/` scan is quadratic on long runs of '/'). */
304
+ function withoutTrailingSlashes(url) {
305
+ let end = url.length;
306
+ while (end > 0 && url[end - 1] === '/')
307
+ end -= 1;
308
+ return url.slice(0, end);
309
+ }
310
+ /** The host's `describe` reply. */
311
+ const describedSchema = z.object({ interface: profileInterfaceSchema });
312
+ /** Transport for a host mounted with `createTheoremHandler`. */
313
+ export function createHttpTransport(options = {}) {
314
+ const base = withoutTrailingSlashes(options.endpoint ?? '/api/theorem');
315
+ return {
316
+ async describe(signal) {
317
+ const response = await request(base, { method: 'GET', signal }, options);
318
+ if (!response.ok)
319
+ throw await failureFromResponse(response);
320
+ // labels-exempt: internal diagnostic; the user reads error.bad_response
321
+ return checkWire(describedSchema, parseWireJson(await response.text()), 'the profile description').interface;
322
+ },
323
+ turn: ({ replay: _replay, ...body }, onEvent, signal) => postNdjson(`${base}/turn`, body, CHAT_LINES, onEvent, { ...options, signal }),
324
+ invoke: ({ replay: _replay, ...body }, onEvent, signal) => postNdjson(`${base}/invoke`, body, CHAT_LINES, onEvent, { ...options, signal }),
325
+ conversation: (body, signal) => fetchConversation(`${base}/conversation`, body, { ...options, signal }),
326
+ rewind: (body, signal) => fetchRewound(`${base}/rewind`, body, { ...options, signal }),
327
+ pageResult: (body, signal) => postJson(`${base}/page-result`, body, { ...options, signal }),
328
+ steer: (body) => postJson(`${base}/steer`, body, options),
329
+ };
330
+ }
@@ -0,0 +1,44 @@
1
+ import { type TurnEvent } from '@theoremjs/agents';
2
+ import { type AttachmentValidationIssue, type ComposerProfileInterface, type GatedToolContext, type InterfaceTurnSession, type TranscriptBlock, type UserTurnDraft } from '@theoremjs/agents/interface';
3
+ import type { PagePart } from './tool-resume.ts';
4
+ import type { TheoremInvokeRequest, TheoremReplay, TheoremTurnInput, TheoremTurnRequest } from './transport.ts';
5
+ export declare function issuedOperationId(call: {
6
+ operationId?: string;
7
+ }): string;
8
+ /** A gated call as the client holds it, for a host without a session (the studio). */
9
+ export type HeldCall = {
10
+ name: string;
11
+ /** The model's input to the call. */
12
+ input: unknown;
13
+ gateKind?: TheoremReplay['gateKind'];
14
+ sessionPermissions?: string[];
15
+ };
16
+ /** The calls a paused reply waits on that a message walks away from. */
17
+ export type WalkAway = {
18
+ calls: readonly GatedToolContext[];
19
+ };
20
+ /** Where a request goes on from: the ref of the conversation the host keeps, and the run's id for steers. */
21
+ export type RunScope = {
22
+ conversation?: string;
23
+ turnId?: string;
24
+ };
25
+ export declare function buildTurnRequest(iface: ComposerProfileInterface, session: InterfaceTurnSession, input: TheoremTurnInput, options?: RunScope & {
26
+ walkAway?: WalkAway;
27
+ }): TheoremTurnRequest;
28
+ export declare function buildInvokeRequest(iface: ComposerProfileInterface, session: InterfaceTurnSession, args: HeldCall & {
29
+ gateId: string;
30
+ operationId: string;
31
+ decision: TheoremInvokeRequest['decision'];
32
+ secret?: string;
33
+ page?: PagePart;
34
+ }, scope?: RunScope): TheoremInvokeRequest;
35
+ export declare function projectUserTurn(iface: ComposerProfileInterface, draft: UserTurnDraft): {
36
+ ok: true;
37
+ blocks: TranscriptBlock[];
38
+ draft: UserTurnDraft;
39
+ } | {
40
+ ok: false;
41
+ issues: AttachmentValidationIssue[];
42
+ };
43
+ export declare function prepareComposerTurn(iface: ComposerProfileInterface, text: string, pendingFiles: readonly File[], pendingVoice?: readonly File[]): ReturnType<typeof projectUserTurn>;
44
+ export declare function foldAssistantTurn(iface: ComposerProfileInterface, events: readonly TurnEvent[]): TranscriptBlock[];
@@ -0,0 +1,88 @@
1
+ import { labelText, TheoremError } from '@theoremjs/agents';
2
+ import { foldTurnEvents, prepareUserTurn, streamThoughtsEnabled, } from '@theoremjs/agents/interface';
3
+ import { filesToPending } from "./encode-files.js";
4
+ import { defaultModel } from "./generation-selection.js";
5
+ export function issuedOperationId(call) {
6
+ if (!call.operationId)
7
+ throw new TheoremError('request', labelText('session.gate_expired'));
8
+ return call.operationId;
9
+ }
10
+ function resolveModelId(iface, session) {
11
+ return session.selectedModel ?? defaultModel(iface);
12
+ }
13
+ function resolveEffort(iface, session, modelId) {
14
+ if (!modelId || !Object.hasOwn(iface.models, modelId))
15
+ return undefined;
16
+ const binding = iface.models[modelId];
17
+ if (!binding.allowEffortSelect)
18
+ return undefined;
19
+ return session.selectedEffort ?? binding.defaultEffort;
20
+ }
21
+ /** Model / effort selection the host should honour for this session. */
22
+ function generationFields(iface, session) {
23
+ const modelId = resolveModelId(iface, session);
24
+ const model = iface.allowModelSelect ? modelId : undefined;
25
+ const effort = resolveEffort(iface, session, modelId);
26
+ return { ...(model ? { model } : {}), ...(effort ? { effort } : {}) };
27
+ }
28
+ function gateReplay(iface, session, call) {
29
+ return {
30
+ name: call.name,
31
+ input: call.input,
32
+ ...(call.gateKind ? { gateKind: call.gateKind } : {}),
33
+ sessionPermissions: call.sessionPermissions ?? session.sessionPermissions,
34
+ ...(session.toolSnapshot ? { snapshot: session.toolSnapshot } : {}),
35
+ ...(session.promotedToolIds.length ? { promoted: [...session.promotedToolIds] } : {}),
36
+ ...generationFields(iface, session),
37
+ };
38
+ }
39
+ function scopeFields(scope) {
40
+ return {
41
+ ...(scope.conversation ? { conversation: scope.conversation } : {}),
42
+ ...(scope.turnId ? { turnId: scope.turnId } : {}),
43
+ };
44
+ }
45
+ export function buildTurnRequest(iface, session, input, options = {}) {
46
+ const abandon = (options.walkAway?.calls ?? []).map((call) => ({
47
+ callId: call.callId,
48
+ operationId: issuedOperationId(call),
49
+ }));
50
+ return {
51
+ ...scopeFields(options),
52
+ ...generationFields(iface, session),
53
+ input,
54
+ ...(abandon.length ? { abandon } : {}),
55
+ replay: { sessionPermissions: session.sessionPermissions },
56
+ };
57
+ }
58
+ export function buildInvokeRequest(iface, session, args, scope = {}) {
59
+ return {
60
+ ...scopeFields(scope),
61
+ gateId: args.gateId,
62
+ operationId: args.operationId,
63
+ decision: args.decision,
64
+ ...(args.secret === undefined ? {} : { secret: args.secret }),
65
+ ...(args.page === undefined ? {} : { page: args.page }),
66
+ replay: gateReplay(iface, session, {
67
+ ...args,
68
+ gateKind: args.gateKind ??
69
+ (session.gatedTool?.callId === args.gateId ? session.gatedTool.gateKind : undefined),
70
+ }),
71
+ };
72
+ }
73
+ export function projectUserTurn(iface, draft) {
74
+ return prepareUserTurn(iface.inputs, draft, iface.guardrails);
75
+ }
76
+ export function prepareComposerTurn(iface, text, pendingFiles, pendingVoice = []) {
77
+ return projectUserTurn(iface, {
78
+ ...(text.trim() ? { text } : {}),
79
+ ...(pendingFiles.length ? { attachments: filesToPending(pendingFiles) } : {}),
80
+ ...(pendingVoice.length ? { voice: filesToPending(pendingVoice) } : {}),
81
+ });
82
+ }
83
+ export function foldAssistantTurn(iface, events) {
84
+ // why: Its `turn-done` blocks stay: hidden in the view, they carry the reply's worked time.
85
+ return foldTurnEvents(events, {
86
+ showThoughts: streamThoughtsEnabled(iface.outputs),
87
+ });
88
+ }
@@ -0,0 +1,2 @@
1
+ /** A voice note's audio format (`webm`, `wav`, `mp3`, `m4a`, `ogg`) from its MIME type; undefined when unknown. */
2
+ export declare function voiceFormatFromMime(mime: string): string | undefined;
@@ -0,0 +1,15 @@
1
+ /** A voice note's audio format (`webm`, `wav`, `mp3`, `m4a`, `ogg`) from its MIME type; undefined when unknown. */
2
+ export function voiceFormatFromMime(mime) {
3
+ const lower = mime.toLowerCase();
4
+ if (lower.includes('webm'))
5
+ return 'webm';
6
+ if (lower.includes('wav'))
7
+ return 'wav';
8
+ if (lower.includes('mpeg') || lower.includes('mp3'))
9
+ return 'mp3';
10
+ if (lower.includes('mp4') || lower.includes('m4a') || lower.includes('aac'))
11
+ return 'm4a';
12
+ if (lower.includes('ogg'))
13
+ return 'ogg';
14
+ return undefined;
15
+ }
@@ -0,0 +1,27 @@
1
+ /** Why recording failed; the user reads labels `voice.<code>`. */
2
+ export type VoiceRecorderFailureCode = 'unsupported' | 'permission' | 'unavailable' | 'failed' | 'empty';
3
+ /** Headless MediaRecorder session for turn-composer voice notes. */
4
+ export declare class ComposerVoiceRecorder {
5
+ private readonly accept;
6
+ private readonly onLevel?;
7
+ private stream;
8
+ private recorder;
9
+ private chunks;
10
+ private audioContext;
11
+ private analyser;
12
+ private source;
13
+ private rafId;
14
+ private mime;
15
+ private recording;
16
+ constructor(accept: string[], onLevel?: ((level: number) => void) | undefined);
17
+ start(): Promise<void>;
18
+ stop(): Promise<File>;
19
+ cancel(): void;
20
+ dispose(): void;
21
+ private startMeter;
22
+ private stopMeter;
23
+ private disposeTracks;
24
+ }
25
+ export declare function isVoiceRecorderFailure(value: unknown): value is Error & {
26
+ readonly code: VoiceRecorderFailureCode;
27
+ };
@@ -0,0 +1,162 @@
1
+ import { describeError } from '@theoremjs/agents';
2
+ import { pickMediaRecorderMime } from '@theoremjs/agents/interface';
3
+ import { timeDomainBytesToLevel } from "./audio-level.js";
4
+ class VoiceRecorderFailure extends Error {
5
+ code;
6
+ /** `detail` is raw diagnostic text for the builder, never shown to the user. */
7
+ constructor(code, detail) {
8
+ super(detail ?? code);
9
+ this.name = 'VoiceRecorderFailure';
10
+ this.code = code;
11
+ }
12
+ }
13
+ function extensionForMime(mime) {
14
+ if (mime.includes('webm'))
15
+ return 'webm';
16
+ if (mime.includes('mp4'))
17
+ return 'm4a';
18
+ if (mime.includes('wav'))
19
+ return 'wav';
20
+ if (mime.includes('mpeg'))
21
+ return 'mp3';
22
+ return 'bin';
23
+ }
24
+ function isPermissionDenied(err) {
25
+ return err instanceof DOMException && err.name === 'NotAllowedError';
26
+ }
27
+ /** Headless MediaRecorder session for turn-composer voice notes. */
28
+ export class ComposerVoiceRecorder {
29
+ accept;
30
+ onLevel;
31
+ stream = null;
32
+ recorder = null;
33
+ chunks = [];
34
+ audioContext = null;
35
+ analyser = null;
36
+ source = null;
37
+ rafId = 0;
38
+ mime = '';
39
+ recording = false;
40
+ constructor(accept, onLevel) {
41
+ this.accept = accept;
42
+ this.onLevel = onLevel;
43
+ }
44
+ async start() {
45
+ this.disposeTracks();
46
+ const mime = pickMediaRecorderMime(this.accept);
47
+ if (!mime) {
48
+ throw new VoiceRecorderFailure('unsupported');
49
+ }
50
+ try {
51
+ this.stream = await navigator.mediaDevices.getUserMedia({
52
+ audio: {
53
+ channelCount: 1,
54
+ echoCancellation: true,
55
+ noiseSuppression: true,
56
+ autoGainControl: true,
57
+ },
58
+ });
59
+ }
60
+ catch (err) {
61
+ throw new VoiceRecorderFailure(isPermissionDenied(err) ? 'permission' : 'unavailable', describeError(err));
62
+ }
63
+ this.mime = mime;
64
+ this.chunks = [];
65
+ this.recorder = new MediaRecorder(this.stream, { mimeType: mime });
66
+ this.recorder.ondataavailable = (event) => {
67
+ if (event.data.size > 0)
68
+ this.chunks.push(event.data);
69
+ };
70
+ this.recorder.start(250);
71
+ this.recording = true;
72
+ this.startMeter();
73
+ }
74
+ async stop() {
75
+ if (!this.recorder || !this.recording) {
76
+ // labels-exempt: internal diagnostic; the user reads voice.empty
77
+ throw new VoiceRecorderFailure('empty', 'stop without a recording');
78
+ }
79
+ const recorder = this.recorder;
80
+ const blob = await new Promise((resolve, reject) => {
81
+ recorder.onerror = () => {
82
+ reject(new VoiceRecorderFailure('failed'));
83
+ };
84
+ recorder.onstop = () => {
85
+ const parts = this.chunks;
86
+ if (parts.length === 0) {
87
+ reject(new VoiceRecorderFailure('empty'));
88
+ return;
89
+ }
90
+ resolve(new Blob(parts, { type: this.mime }));
91
+ };
92
+ recorder.stop();
93
+ });
94
+ this.recording = false;
95
+ this.stopMeter();
96
+ this.disposeTracks();
97
+ if (blob.size === 0) {
98
+ throw new VoiceRecorderFailure('empty');
99
+ }
100
+ const ext = extensionForMime(this.mime);
101
+ return new File([blob], `voice-note.${ext}`, { type: this.mime });
102
+ }
103
+ cancel() {
104
+ this.recording = false;
105
+ this.stopMeter();
106
+ if (this.recorder && this.recorder.state !== 'inactive') {
107
+ try {
108
+ this.recorder.stop();
109
+ }
110
+ catch {
111
+ // why: stop() throws when the recorder stopped between the check and the call; it is stopped either way.
112
+ }
113
+ }
114
+ this.disposeTracks();
115
+ this.chunks = [];
116
+ this.recorder = null;
117
+ }
118
+ dispose() {
119
+ this.cancel();
120
+ }
121
+ startMeter() {
122
+ if (!this.stream)
123
+ return;
124
+ const AudioContextClass = globalThis.AudioContext;
125
+ this.audioContext = new AudioContextClass();
126
+ this.analyser = this.audioContext.createAnalyser();
127
+ this.analyser.fftSize = 2048;
128
+ this.source = this.audioContext.createMediaStreamSource(this.stream);
129
+ this.source.connect(this.analyser);
130
+ const tick = () => {
131
+ if (!this.analyser || !this.recording)
132
+ return;
133
+ const data = new Uint8Array(this.analyser.fftSize);
134
+ this.analyser.getByteTimeDomainData(data);
135
+ this.onLevel?.(timeDomainBytesToLevel(data));
136
+ this.rafId = requestAnimationFrame(tick);
137
+ };
138
+ this.rafId = requestAnimationFrame(tick);
139
+ }
140
+ stopMeter() {
141
+ cancelAnimationFrame(this.rafId);
142
+ this.rafId = 0;
143
+ this.onLevel?.(0);
144
+ this.source?.disconnect();
145
+ this.analyser?.disconnect();
146
+ this.source = null;
147
+ this.analyser = null;
148
+ if (this.audioContext && this.audioContext.state !== 'closed') {
149
+ void this.audioContext.close();
150
+ }
151
+ this.audioContext = null;
152
+ }
153
+ disposeTracks() {
154
+ for (const track of this.stream?.getTracks() ?? []) {
155
+ track.stop();
156
+ }
157
+ this.stream = null;
158
+ }
159
+ }
160
+ export function isVoiceRecorderFailure(value) {
161
+ return value instanceof VoiceRecorderFailure;
162
+ }