@theoremjs/react 0.0.0-stage → 0.3.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 (231) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +246 -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-transport.d.ts +48 -0
  16. package/client/decision-transport.js +102 -0
  17. package/client/decode-composer-draft.d.ts +11 -0
  18. package/client/decode-composer-draft.js +12 -0
  19. package/client/encode-composer-draft.d.ts +9 -0
  20. package/client/encode-composer-draft.js +34 -0
  21. package/client/encode-files.d.ts +18 -0
  22. package/client/encode-files.js +54 -0
  23. package/client/failure.d.ts +32 -0
  24. package/client/failure.js +34 -0
  25. package/client/generation-selection.d.ts +15 -0
  26. package/client/generation-selection.js +24 -0
  27. package/client/host-call.d.ts +51 -0
  28. package/client/host-call.js +42 -0
  29. package/client/host-transport.d.ts +68 -0
  30. package/client/host-transport.js +74 -0
  31. package/client/image-output.d.ts +6 -0
  32. package/client/image-output.js +13 -0
  33. package/client/index.d.ts +20 -0
  34. package/client/index.js +18 -0
  35. package/client/ink-waveform.d.ts +17 -0
  36. package/client/ink-waveform.js +69 -0
  37. package/client/live/apply-live-turn-tool-event.d.ts +14 -0
  38. package/client/live/apply-live-turn-tool-event.js +39 -0
  39. package/client/live/live-captions.d.ts +23 -0
  40. package/client/live/live-captions.js +70 -0
  41. package/client/live/live-mic-forward.d.ts +29 -0
  42. package/client/live/live-mic-forward.js +37 -0
  43. package/client/live/live-state.d.ts +11 -0
  44. package/client/live/live-state.js +20 -0
  45. package/client/live/live-tool.d.ts +11 -0
  46. package/client/live/live-tool.js +1 -0
  47. package/client/live/live-video.d.ts +10 -0
  48. package/client/live/live-video.js +71 -0
  49. package/client/live/run-live-tool-call.d.ts +17 -0
  50. package/client/live/run-live-tool-call.js +32 -0
  51. package/client/live-client.d.ts +141 -0
  52. package/client/live-client.js +638 -0
  53. package/client/live-errors.d.ts +2 -0
  54. package/client/live-errors.js +9 -0
  55. package/client/live-messages.d.ts +110 -0
  56. package/client/live-messages.js +67 -0
  57. package/client/mic-capture.d.ts +4 -0
  58. package/client/mic-capture.js +6 -0
  59. package/client/oauth-popup.d.ts +26 -0
  60. package/client/oauth-popup.js +30 -0
  61. package/client/pcm-downsample.d.ts +4 -0
  62. package/client/pcm-downsample.js +45 -0
  63. package/client/run-commit.d.ts +61 -0
  64. package/client/run-commit.js +124 -0
  65. package/client/run-session.d.ts +80 -0
  66. package/client/run-session.js +215 -0
  67. package/client/schema-fields.d.ts +63 -0
  68. package/client/schema-fields.js +178 -0
  69. package/client/shaped-data.d.ts +118 -0
  70. package/client/shaped-data.js +314 -0
  71. package/client/source-citations.d.ts +13 -0
  72. package/client/source-citations.js +56 -0
  73. package/client/state-fields.d.ts +16 -0
  74. package/client/state-fields.js +44 -0
  75. package/client/tool-result.d.ts +78 -0
  76. package/client/tool-result.js +275 -0
  77. package/client/tool-resume.d.ts +33 -0
  78. package/client/tool-resume.js +11 -0
  79. package/client/trace-feed.d.ts +13 -0
  80. package/client/trace-feed.js +18 -0
  81. package/client/trace-story.d.ts +163 -0
  82. package/client/trace-story.js +416 -0
  83. package/client/trace-view.d.ts +130 -0
  84. package/client/trace-view.js +293 -0
  85. package/client/transcript-groups.d.ts +114 -0
  86. package/client/transcript-groups.js +167 -0
  87. package/client/transcript-scroll.d.ts +7 -0
  88. package/client/transcript-scroll.js +6 -0
  89. package/client/transport.d.ts +176 -0
  90. package/client/transport.js +256 -0
  91. package/client/turn-client.d.ts +35 -0
  92. package/client/turn-client.js +93 -0
  93. package/client/voice-label.d.ts +2 -0
  94. package/client/voice-label.js +15 -0
  95. package/client/voice-recorder.d.ts +27 -0
  96. package/client/voice-recorder.js +165 -0
  97. package/client/walk-away.d.ts +34 -0
  98. package/client/walk-away.js +48 -0
  99. package/client/wire-line.d.ts +56 -0
  100. package/client/wire-line.js +90 -0
  101. package/client/worklets/mic-capture.js +16 -0
  102. package/components/InkWaveform.d.ts +10 -0
  103. package/components/InkWaveform.js +108 -0
  104. package/components/live/use-live-runner-controls.d.ts +48 -0
  105. package/components/live/use-live-runner-controls.js +100 -0
  106. package/components/live/use-live-runner-model.d.ts +45 -0
  107. package/components/live/use-live-runner-model.js +175 -0
  108. package/components/live/use-live-runner-ui.d.ts +65 -0
  109. package/components/live/use-live-runner-ui.js +159 -0
  110. package/components/live/use-live-session-client.d.ts +41 -0
  111. package/components/live/use-live-session-client.js +123 -0
  112. package/components/use-composer-voice.d.ts +28 -0
  113. package/components/use-composer-voice.js +90 -0
  114. package/components/use-editor-selection.d.ts +3 -0
  115. package/components/use-editor-selection.js +16 -0
  116. package/components/use-voice-playback.d.ts +16 -0
  117. package/components/use-voice-playback.js +68 -0
  118. package/hooks/use-described.d.ts +8 -0
  119. package/hooks/use-described.js +23 -0
  120. package/hooks/use-theorem-chat-actions.d.ts +75 -0
  121. package/hooks/use-theorem-chat-actions.js +265 -0
  122. package/hooks/use-theorem-chat-state.d.ts +66 -0
  123. package/hooks/use-theorem-chat-state.js +118 -0
  124. package/hooks/use-theorem-chat.d.ts +63 -0
  125. package/hooks/use-theorem-chat.js +340 -0
  126. package/hooks/use-theorem-decision.d.ts +23 -0
  127. package/hooks/use-theorem-decision.js +75 -0
  128. package/hooks/use-theorem-host.d.ts +44 -0
  129. package/hooks/use-theorem-host.js +117 -0
  130. package/hooks/use-theorem-interface.d.ts +19 -0
  131. package/hooks/use-theorem-interface.js +25 -0
  132. package/hooks/use-trace-records.d.ts +4 -0
  133. package/hooks/use-trace-records.js +7 -0
  134. package/index.d.ts +14 -0
  135. package/index.js +14 -0
  136. package/live.d.ts +8 -0
  137. package/live.js +10 -0
  138. package/package.json +73 -4
  139. package/server/credential-store.d.ts +24 -0
  140. package/server/credential-store.js +14 -0
  141. package/server/decision-handler.d.ts +34 -0
  142. package/server/decision-handler.js +55 -0
  143. package/server/handler.d.ts +114 -0
  144. package/server/handler.js +568 -0
  145. package/server/json-response.d.ts +2 -0
  146. package/server/json-response.js +7 -0
  147. package/server/mod.d.ts +16 -0
  148. package/server/mod.js +15 -0
  149. package/server/request-check.d.ts +16 -0
  150. package/server/request-check.js +34 -0
  151. package/server/session-store.d.ts +74 -0
  152. package/server/session-store.js +49 -0
  153. package/server/steer-inbox.d.ts +35 -0
  154. package/server/steer-inbox.js +52 -0
  155. package/server/walk-away.d.ts +22 -0
  156. package/server/walk-away.js +41 -0
  157. package/styles/ink-controls.css +134 -0
  158. package/ui/ChatComposerBar.d.ts +39 -0
  159. package/ui/ChatComposerBar.js +251 -0
  160. package/ui/ChatTranscript.d.ts +30 -0
  161. package/ui/ChatTranscript.js +474 -0
  162. package/ui/ConsoleFrame.d.ts +47 -0
  163. package/ui/ConsoleFrame.js +46 -0
  164. package/ui/DecisionAnswers.d.ts +15 -0
  165. package/ui/DecisionAnswers.js +102 -0
  166. package/ui/LiveRunner.d.ts +28 -0
  167. package/ui/LiveRunner.js +193 -0
  168. package/ui/SchemaFields.d.ts +10 -0
  169. package/ui/SchemaFields.js +122 -0
  170. package/ui/ShapedData.d.ts +11 -0
  171. package/ui/ShapedData.js +151 -0
  172. package/ui/SidePanel.d.ts +57 -0
  173. package/ui/SidePanel.js +50 -0
  174. package/ui/StateFields.d.ts +36 -0
  175. package/ui/StateFields.js +96 -0
  176. package/ui/TheoremChat.d.ts +71 -0
  177. package/ui/TheoremChat.js +146 -0
  178. package/ui/TheoremDecision.d.ts +38 -0
  179. package/ui/TheoremDecision.js +87 -0
  180. package/ui/TheoremHost.d.ts +38 -0
  181. package/ui/TheoremHost.js +150 -0
  182. package/ui/ToolGateCard.d.ts +28 -0
  183. package/ui/ToolGateCard.js +100 -0
  184. package/ui/ToolResult.d.ts +10 -0
  185. package/ui/ToolResult.js +60 -0
  186. package/ui/ToolResultCharts.d.ts +5 -0
  187. package/ui/ToolResultCharts.js +86 -0
  188. package/ui/TraceCharts.d.ts +13 -0
  189. package/ui/TraceCharts.js +235 -0
  190. package/ui/TraceGuardrails.d.ts +10 -0
  191. package/ui/TraceGuardrails.js +56 -0
  192. package/ui/TraceInspector.d.ts +6 -0
  193. package/ui/TraceInspector.js +189 -0
  194. package/ui/TraceInspectorPanel.d.ts +24 -0
  195. package/ui/TraceInspectorPanel.js +57 -0
  196. package/ui/TraceOverview.d.ts +34 -0
  197. package/ui/TraceOverview.js +96 -0
  198. package/ui/TraceSpanDetail.d.ts +10 -0
  199. package/ui/TraceSpanDetail.js +188 -0
  200. package/ui/TraceStory.d.ts +20 -0
  201. package/ui/TraceStory.js +107 -0
  202. package/ui/TraceValues.d.ts +36 -0
  203. package/ui/TraceValues.js +113 -0
  204. package/ui/TraceWaterfall.d.ts +13 -0
  205. package/ui/TraceWaterfall.js +132 -0
  206. package/ui/VoiceNote.d.ts +12 -0
  207. package/ui/VoiceNote.js +28 -0
  208. package/ui/built/theme.css +774 -0
  209. package/ui/built/theorem.d.ts +13 -0
  210. package/ui/built/theorem.js +631 -0
  211. package/ui/built/theorem.variants.d.ts +16 -0
  212. package/ui/disclosure-motion.d.ts +6 -0
  213. package/ui/disclosure-motion.js +163 -0
  214. package/ui/icons.d.ts +4 -0
  215. package/ui/icons.js +37 -0
  216. package/ui/index.d.ts +25 -0
  217. package/ui/index.js +29 -0
  218. package/ui/labels-provider.d.ts +16 -0
  219. package/ui/labels-provider.js +61 -0
  220. package/ui/labels.d.ts +1153 -0
  221. package/ui/labels.js +603 -0
  222. package/ui/theme.d.ts +10 -0
  223. package/ui/theme.js +13 -0
  224. package/ui/theorem-theme.d.ts +10 -0
  225. package/ui/theorem-theme.js +83 -0
  226. package/ui/trace-palette.d.ts +24 -0
  227. package/ui/trace-palette.js +24 -0
  228. package/ui/trace-patterns.d.ts +32 -0
  229. package/ui/trace-patterns.js +44 -0
  230. package/ui/transcript-copy-text.d.ts +4 -0
  231. package/ui/transcript-copy-text.js +39 -0
@@ -0,0 +1,165 @@
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
+ // fallow-ignore-next-line unused-class-member -- called from useComposerVoice via recorder refs
45
+ async start() {
46
+ this.disposeTracks();
47
+ const mime = pickMediaRecorderMime(this.accept);
48
+ if (!mime) {
49
+ throw new VoiceRecorderFailure('unsupported');
50
+ }
51
+ try {
52
+ this.stream = await navigator.mediaDevices.getUserMedia({
53
+ audio: {
54
+ channelCount: 1,
55
+ echoCancellation: true,
56
+ noiseSuppression: true,
57
+ autoGainControl: true,
58
+ },
59
+ });
60
+ }
61
+ catch (err) {
62
+ throw new VoiceRecorderFailure(isPermissionDenied(err) ? 'permission' : 'unavailable', describeError(err));
63
+ }
64
+ this.mime = mime;
65
+ this.chunks = [];
66
+ this.recorder = new MediaRecorder(this.stream, { mimeType: mime });
67
+ this.recorder.ondataavailable = (event) => {
68
+ if (event.data.size > 0)
69
+ this.chunks.push(event.data);
70
+ };
71
+ this.recorder.start(250);
72
+ this.recording = true;
73
+ this.startMeter();
74
+ }
75
+ // fallow-ignore-next-line unused-class-member -- called from useComposerVoice via recorder refs
76
+ async stop() {
77
+ if (!this.recorder || !this.recording) {
78
+ // lexicon-exempt: internal diagnostic; the user reads voice.empty
79
+ throw new VoiceRecorderFailure('empty', 'stop without a recording');
80
+ }
81
+ const recorder = this.recorder;
82
+ const blob = await new Promise((resolve, reject) => {
83
+ recorder.onerror = () => {
84
+ reject(new VoiceRecorderFailure('failed'));
85
+ };
86
+ recorder.onstop = () => {
87
+ const parts = this.chunks;
88
+ if (parts.length === 0) {
89
+ reject(new VoiceRecorderFailure('empty'));
90
+ return;
91
+ }
92
+ resolve(new Blob(parts, { type: this.mime }));
93
+ };
94
+ recorder.stop();
95
+ });
96
+ this.recording = false;
97
+ this.stopMeter();
98
+ this.disposeTracks();
99
+ if (blob.size === 0) {
100
+ throw new VoiceRecorderFailure('empty');
101
+ }
102
+ const ext = extensionForMime(this.mime);
103
+ return new File([blob], `voice-note.${ext}`, { type: this.mime });
104
+ }
105
+ cancel() {
106
+ this.recording = false;
107
+ this.stopMeter();
108
+ if (this.recorder && this.recorder.state !== 'inactive') {
109
+ try {
110
+ this.recorder.stop();
111
+ }
112
+ catch {
113
+ // ignore
114
+ }
115
+ }
116
+ this.disposeTracks();
117
+ this.chunks = [];
118
+ this.recorder = null;
119
+ }
120
+ // fallow-ignore-next-line unused-class-member -- called from useComposerVoice cleanup
121
+ dispose() {
122
+ this.cancel();
123
+ }
124
+ startMeter() {
125
+ if (!this.stream)
126
+ return;
127
+ const AudioContextClass = globalThis.AudioContext;
128
+ this.audioContext = new AudioContextClass();
129
+ this.analyser = this.audioContext.createAnalyser();
130
+ this.analyser.fftSize = 2048;
131
+ this.source = this.audioContext.createMediaStreamSource(this.stream);
132
+ this.source.connect(this.analyser);
133
+ const tick = () => {
134
+ if (!this.analyser || !this.recording)
135
+ return;
136
+ const data = new Uint8Array(this.analyser.fftSize);
137
+ this.analyser.getByteTimeDomainData(data);
138
+ this.onLevel?.(timeDomainBytesToLevel(data));
139
+ this.rafId = requestAnimationFrame(tick);
140
+ };
141
+ this.rafId = requestAnimationFrame(tick);
142
+ }
143
+ stopMeter() {
144
+ cancelAnimationFrame(this.rafId);
145
+ this.rafId = 0;
146
+ this.onLevel?.(0);
147
+ this.source?.disconnect();
148
+ this.analyser?.disconnect();
149
+ this.source = null;
150
+ this.analyser = null;
151
+ if (this.audioContext && this.audioContext.state !== 'closed') {
152
+ void this.audioContext.close();
153
+ }
154
+ this.audioContext = null;
155
+ }
156
+ disposeTracks() {
157
+ for (const track of this.stream?.getTracks() ?? []) {
158
+ track.stop();
159
+ }
160
+ this.stream = null;
161
+ }
162
+ }
163
+ export function isVoiceRecorderFailure(value) {
164
+ return value instanceof VoiceRecorderFailure;
165
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * A message sent while its reply waits on gates walks away from them in the
3
+ * same request: `abandon` names the waiting calls, and the history carries the
4
+ * paused reply with those calls open. The host settles each one cancelled and
5
+ * streams those events ahead of the message's reply; once the last one
6
+ * settles, the paused reply commits whole, and the message's reply begins.
7
+ *
8
+ * @module
9
+ */
10
+ import { type TurnHistoryMessage } from '@theoremjs/agents';
11
+ import { type ComposerProfileInterface, type InterfaceTurnSession, type TranscriptBlock } from '@theoremjs/agents/interface';
12
+ import type { TurnEventSink } from './transport.ts';
13
+ import { type WalkAway } from './turn-client.ts';
14
+ /** The paused reply, committed with its settled calls, and its blocks as the transcript keeps them. */
15
+ export type WalkedAway = {
16
+ session: InterfaceTurnSession;
17
+ blocks: TranscriptBlock[];
18
+ };
19
+ export type WalkingAway = {
20
+ /** For `buildTurnRequest`: the paused session and the calls it waits on. */
21
+ request: WalkAway;
22
+ /** The paused reply as the model reads it, its waiting calls open. */
23
+ history: TurnHistoryMessage[];
24
+ /**
25
+ * The sink for the request's stream: each waiting call's events settle the
26
+ * paused reply, and `onSettled` fires once the last one has; every event
27
+ * after that is the message's reply, and goes on to `onEvent`.
28
+ */
29
+ sink(onEvent: TurnEventSink, onSettled: (walked: WalkedAway) => void): TurnEventSink;
30
+ /** Whether every waiting call has settled. */
31
+ settled(): boolean;
32
+ };
33
+ /** Walk away from every call `paused` waits on; `workedMs` is the paused reply's work so far. */
34
+ export declare function walkAwayFrom(iface: ComposerProfileInterface, paused: InterfaceTurnSession, workedMs: number): WalkingAway;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * A message sent while its reply waits on gates walks away from them in the
3
+ * same request: `abandon` names the waiting calls, and the history carries the
4
+ * paused reply with those calls open. The host settles each one cancelled and
5
+ * streams those events ahead of the message's reply; once the last one
6
+ * settles, the paused reply commits whole, and the message's reply begins.
7
+ *
8
+ * @module
9
+ */
10
+ import { TheoremError } from '@theoremjs/agents';
11
+ import { appendPausedTurnToHistory, gatedToolsFromEvents, settlesToolCall, } from '@theoremjs/agents/interface';
12
+ import { commitCompletedTurn, stampWorked } from "./run-commit.js";
13
+ import { foldAssistantTurn } from "./turn-client.js";
14
+ /** Walk away from every call `paused` waits on; `workedMs` is the paused reply's work so far. */
15
+ export function walkAwayFrom(iface, paused, workedMs) {
16
+ const calls = gatedToolsFromEvents(paused.assistantEvents);
17
+ const waiting = new Set(calls.map((call) => call.callId));
18
+ const events = [...paused.assistantEvents];
19
+ const settle = (event) => {
20
+ events.push(event);
21
+ if (event.type === 'tool' && settlesToolCall(event, event.tool.callId))
22
+ waiting.delete(event.tool.callId);
23
+ if (waiting.size)
24
+ return undefined;
25
+ return {
26
+ session: commitCompletedTurn(paused, events),
27
+ blocks: stampWorked(foldAssistantTurn(iface, events), { workedMs, endedAt: Date.now() }),
28
+ };
29
+ };
30
+ return {
31
+ request: { paused, calls },
32
+ history: appendPausedTurnToHistory(paused.history, paused.assistantEvents),
33
+ sink: (onEvent, onSettled) => (event) => {
34
+ if (!waiting.size || event.type === 'unsupported' || event.type === 'malformed') {
35
+ onEvent(event);
36
+ return;
37
+ }
38
+ if (event.type !== 'tool' || !waiting.has(event.tool.callId)) {
39
+ // lexicon-exempt: internal diagnostic; the user reads the error kind's wording
40
+ throw new TheoremError('bad_response', 'the reply began before the calls it walked away from settled');
41
+ }
42
+ const walked = settle(event);
43
+ if (walked)
44
+ onSettled(walked);
45
+ },
46
+ settled: () => waiting.size === 0,
47
+ };
48
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Every value that reaches the browser over a wire (NDJSON lines, live
3
+ * envelopes) is checked against its schema here. A line or event of a kind the
4
+ * client does not know is reported as `unsupported`, and one that fails its
5
+ * check (not JSON, no kind, or a known kind that fails its schema) as
6
+ * `malformed`; either way the reply goes on. A whole reply that fails its check
7
+ * (the profile description) is `bad_response`.
8
+ *
9
+ * @module
10
+ */
11
+ import { TheoremError, z } from '@theoremjs/agents';
12
+ /**
13
+ * A line or envelope of a kind the client does not know. The server never
14
+ * sends it; the client's parser makes it so the host sees what arrived. Theorem
15
+ * shows nothing for it.
16
+ */
17
+ export type UnsupportedEvent = {
18
+ type: 'unsupported';
19
+ received: string;
20
+ raw: unknown;
21
+ };
22
+ /**
23
+ * A line or event that failed its wire check. The server never sends it; the
24
+ * client's reader makes it, leaves the line out, and the reply goes on. `error`
25
+ * is `bad_response`: its message names what broke, never the value, and the
26
+ * user reads `session.part_skipped`.
27
+ */
28
+ export type MalformedEvent = {
29
+ type: 'malformed';
30
+ error: TheoremError;
31
+ };
32
+ /** Every kind a wire carries, and the schema a value of that kind must pass. */
33
+ export type WireLines<Line extends {
34
+ type: string;
35
+ }> = {
36
+ readonly [K in Line['type']]: z.ZodType<Extract<Line, {
37
+ type: K;
38
+ }>>;
39
+ };
40
+ /**
41
+ * Issue paths and codes only: a failure names what broke, never the value. A
42
+ * union that matched no branch lists each branch's issues.
43
+ */
44
+ export declare function issueSummary(error: z.ZodError): string;
45
+ /** `raw` as `schema` reads it; one that fails is `bad_response`, naming `what`. */
46
+ export declare function checkWire<T>(schema: z.ZodType<T>, raw: unknown, what: string): T;
47
+ /** One wire value: a known kind that passed its schema, `unsupported`, or `malformed`. */
48
+ export declare function readWireValue<Line extends {
49
+ type: string;
50
+ }>(lines: WireLines<Line>, raw: unknown): Line | UnsupportedEvent | MalformedEvent;
51
+ /** One wire line's text: {@link readWireValue} of its JSON, or `malformed` when it is not JSON. */
52
+ export declare function readWireLine<Line extends {
53
+ type: string;
54
+ }>(lines: WireLines<Line>, text: string): Line | UnsupportedEvent | MalformedEvent;
55
+ /** A whole reply's JSON text as a value; text that is not JSON is `bad_response`. */
56
+ export declare function parseWireJson(text: string): unknown;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Every value that reaches the browser over a wire (NDJSON lines, live
3
+ * envelopes) is checked against its schema here. A line or event of a kind the
4
+ * client does not know is reported as `unsupported`, and one that fails its
5
+ * check (not JSON, no kind, or a known kind that fails its schema) as
6
+ * `malformed`; either way the reply goes on. A whole reply that fails its check
7
+ * (the profile description) is `bad_response`.
8
+ *
9
+ * @module
10
+ */
11
+ import { TheoremError, z } from '@theoremjs/agents';
12
+ const wireKind = z.object({ type: z.string() });
13
+ function isKnown(lines, type) {
14
+ return Object.hasOwn(lines, type);
15
+ }
16
+ function issuesSummary(issues, at) {
17
+ return issues
18
+ .map((issue) => {
19
+ const path = [...at, ...issue.path];
20
+ const head = `${path.map(String).join('.') || '(root)'} ${issue.code}`;
21
+ if (issue.code !== 'invalid_union' || issue.errors.length === 0)
22
+ return head;
23
+ return `${head} [${issue.errors.map((branch) => issuesSummary(branch, path)).join(' | ')}]`;
24
+ })
25
+ .join('; ');
26
+ }
27
+ /**
28
+ * Issue paths and codes only: a failure names what broke, never the value. A
29
+ * union that matched no branch lists each branch's issues.
30
+ */
31
+ export function issueSummary(error) {
32
+ return issuesSummary(error.issues, []);
33
+ }
34
+ function badResponse(what, error) {
35
+ // lexicon-exempt: internal diagnostic; the user reads error.bad_response
36
+ return new TheoremError('bad_response', `${what} failed its wire check: ${issueSummary(error)}`);
37
+ }
38
+ /** `raw` as `schema` reads it; one that fails is `bad_response`, naming `what`. */
39
+ export function checkWire(schema, raw, what) {
40
+ const parsed = schema.safeParse(raw);
41
+ if (!parsed.success)
42
+ throw badResponse(what, parsed.error);
43
+ return parsed.data;
44
+ }
45
+ function malformed(message) {
46
+ return {
47
+ type: 'malformed',
48
+ error: new TheoremError('bad_response', message, { copy: { key: 'session.part_skipped' } }),
49
+ };
50
+ }
51
+ /** One wire value: a known kind that passed its schema, `unsupported`, or `malformed`. */
52
+ export function readWireValue(lines, raw) {
53
+ const kind = wireKind.safeParse(raw);
54
+ // lexicon-exempt: internal diagnostic; the user reads session.part_skipped
55
+ if (!kind.success)
56
+ return malformed(`a line without a kind failed its wire check: ${issueSummary(kind.error)}`);
57
+ const { type } = kind.data;
58
+ if (!isKnown(lines, type))
59
+ return { type: 'unsupported', received: type, raw };
60
+ const line = lines[type].safeParse(raw);
61
+ if (line.success)
62
+ return line.data;
63
+ // lexicon-exempt: internal diagnostic; the user reads session.part_skipped
64
+ return malformed(`a '${type}' line failed its wire check: ${issueSummary(line.error)}`);
65
+ }
66
+ /** JSON text as a value, or undefined when it is not JSON. The parser's own error quotes the text, so it is not kept. */
67
+ function jsonValue(text) {
68
+ try {
69
+ return { value: JSON.parse(text) };
70
+ }
71
+ catch (err) {
72
+ if (err instanceof SyntaxError)
73
+ return undefined;
74
+ throw err;
75
+ }
76
+ }
77
+ /** One wire line's text: {@link readWireValue} of its JSON, or `malformed` when it is not JSON. */
78
+ export function readWireLine(lines, text) {
79
+ const json = jsonValue(text);
80
+ // lexicon-exempt: internal diagnostic; the user reads session.part_skipped
81
+ return json ? readWireValue(lines, json.value) : malformed('a line is not JSON');
82
+ }
83
+ /** A whole reply's JSON text as a value; text that is not JSON is `bad_response`. */
84
+ export function parseWireJson(text) {
85
+ const json = jsonValue(text);
86
+ // lexicon-exempt: internal diagnostic; the user reads error.bad_response
87
+ if (!json)
88
+ throw new TheoremError('bad_response', 'a line is not JSON');
89
+ return json.value;
90
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * AudioWorklet processor that forwards mono mic frames to the main thread.
3
+ * Plain JS: it runs in the AudioWorkletGlobalScope as its own module, loaded
4
+ * by URL (see `../mic-capture.ts`), so it imports nothing and ships as is.
5
+ */
6
+ registerProcessor(
7
+ 'mic-capture-processor',
8
+ class MicCaptureProcessor extends AudioWorkletProcessor {
9
+ process(inputs) {
10
+ const channel = inputs[0][0];
11
+ const copy = new Float32Array(channel);
12
+ this.port.postMessage(copy, [copy.buffer]);
13
+ return true;
14
+ }
15
+ },
16
+ );
@@ -0,0 +1,10 @@
1
+ import { type InkWaveStatus } from '../client/ink-waveform.ts';
2
+ export type InkWaveformProps = {
3
+ status?: InkWaveStatus;
4
+ inputLevel?: number;
5
+ outputLevel?: number;
6
+ toolActive?: boolean;
7
+ frozen?: boolean;
8
+ variant?: 'default' | 'hero' | 'pill' | 'strip';
9
+ };
10
+ export declare function InkWaveform({ status, inputLevel, outputLevel, toolActive, frozen, variant, }: InkWaveformProps): import("react").JSX.Element;
@@ -0,0 +1,108 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef, useState } from 'react';
3
+ import { computeInkBarTargets, INK_WAVE_BAR_COUNT, INK_WAVE_HERO_BAR_COUNT, inkWaveDriver, inkWavePhases, stepInkBarHeights, } from "../client/ink-waveform.js";
4
+ const WAVE_CONFIG = {
5
+ hero: {
6
+ viewWidth: 960,
7
+ viewHeight: 720,
8
+ preserveAspect: 'xMidYMax slice',
9
+ strokeWidth: 2,
10
+ barCount: INK_WAVE_HERO_BAR_COUNT,
11
+ },
12
+ pill: {
13
+ viewWidth: 480,
14
+ viewHeight: 120,
15
+ preserveAspect: 'none',
16
+ strokeWidth: 3,
17
+ barCount: INK_WAVE_BAR_COUNT,
18
+ },
19
+ // A long, low band (a call bar along the foot of a page): many fine bars.
20
+ strip: {
21
+ viewWidth: 1600,
22
+ viewHeight: 48,
23
+ preserveAspect: 'none',
24
+ strokeWidth: 2,
25
+ barCount: 120,
26
+ },
27
+ default: {
28
+ viewWidth: 480,
29
+ viewHeight: 320,
30
+ preserveAspect: 'xMidYMax meet',
31
+ strokeWidth: 2,
32
+ barCount: INK_WAVE_BAR_COUNT,
33
+ },
34
+ };
35
+ function resolveWaveDimensions(variant) {
36
+ const cfg = WAVE_CONFIG[variant] ?? WAVE_CONFIG.default;
37
+ const gap = (cfg.viewWidth - cfg.strokeWidth * cfg.barCount) / (cfg.barCount + 1);
38
+ return { ...cfg, gap };
39
+ }
40
+ function useAnimatedWaveHeights(args) {
41
+ const { barCount, phases, status, inputLevel, outputLevel, toolActive, frozen } = args;
42
+ const [heights, setHeights] = useState(() => Array.from({ length: barCount }, () => 0.06));
43
+ const snapRef = useRef({
44
+ status,
45
+ inputLevel,
46
+ outputLevel,
47
+ toolActive,
48
+ frozen,
49
+ });
50
+ useEffect(() => {
51
+ snapRef.current = { status, inputLevel, outputLevel, toolActive, frozen };
52
+ }, [status, inputLevel, outputLevel, toolActive, frozen]);
53
+ useEffect(() => {
54
+ let frame = 0;
55
+ setHeights(Array.from({ length: barCount }, () => 0.06));
56
+ const tick = (time) => {
57
+ const snap = snapRef.current;
58
+ setHeights((prev) => {
59
+ const current = prev.length === barCount ? prev : Array.from({ length: barCount }, () => 0.06);
60
+ const targets = computeInkBarTargets({
61
+ phases,
62
+ timeMs: time,
63
+ driver: inkWaveDriver(snap.status, snap.toolActive, snap.inputLevel, snap.outputLevel, snap.frozen),
64
+ inputLevel: snap.frozen ? 0 : snap.inputLevel,
65
+ outputLevel: snap.frozen ? 0 : snap.outputLevel,
66
+ frozen: snap.frozen,
67
+ });
68
+ return stepInkBarHeights(current, targets, snap.frozen ? 0.2 : 0.14);
69
+ });
70
+ frame = requestAnimationFrame(tick);
71
+ };
72
+ frame = requestAnimationFrame(tick);
73
+ return () => {
74
+ cancelAnimationFrame(frame);
75
+ };
76
+ }, [barCount, phases]);
77
+ return heights;
78
+ }
79
+ export function InkWaveform({ status = 'disconnected', inputLevel = 0, outputLevel = 0, toolActive = false, frozen = false, variant = 'default', }) {
80
+ const { viewWidth, viewHeight, preserveAspect, strokeWidth, barCount, gap } = resolveWaveDimensions(variant);
81
+ const phases = useMemo(() => inkWavePhases(barCount), [barCount]);
82
+ const heights = useAnimatedWaveHeights({
83
+ barCount,
84
+ phases,
85
+ status,
86
+ inputLevel,
87
+ outputLevel,
88
+ toolActive,
89
+ frozen,
90
+ });
91
+ const bars = heights.map((height, index) => {
92
+ const x = gap + index * (strokeWidth + gap) + strokeWidth / 2;
93
+ const barHeight = height * (viewHeight - 4);
94
+ return { x, y2: viewHeight - barHeight };
95
+ });
96
+ const className = [
97
+ 'ink-wave',
98
+ variant === 'hero' ? 'ink-wave--hero' : '',
99
+ variant === 'pill' ? 'ink-wave--pill' : '',
100
+ variant === 'strip' ? 'ink-wave--strip' : '',
101
+ ]
102
+ .filter(Boolean)
103
+ .join(' ');
104
+ return (_jsx("svg", { className: className, "aria-hidden": "true", viewBox: `0 0 ${String(viewWidth)} ${String(viewHeight)}`, preserveAspectRatio: preserveAspect,
105
+ // Attribute defaults for hosts without ink-controls.css (the Astryx UI);
106
+ // the live stylesheet overrides them where it loads.
107
+ width: "100%", height: "100%", children: bars.map((bar) => (_jsx("line", { className: "ink-wave__bar", x1: bar.x, x2: bar.x, y1: viewHeight, y2: bar.y2, strokeWidth: strokeWidth, stroke: "currentColor", strokeLinecap: "square" }, `${String(bar.x)}:${String(bar.y2)}`))) }));
108
+ }
@@ -0,0 +1,48 @@
1
+ import { type MutableRefObject, type Dispatch, type SetStateAction } from 'react';
2
+ import { type LiveCaptionState } from '../../client/live/live-captions.ts';
3
+ import { type LiveFacingMode, type LiveVideoCapture } from '../../client/live/live-video.ts';
4
+ import type { LiveConnectPhase, LiveSessionStatus } from '../../client/live/live-state.ts';
5
+ import type { LiveConnection, LiveSessionClient } from '../../client/live-client.ts';
6
+ /** Media + session lifecycle handlers for LiveRunner. */
7
+ export declare function useLiveRunnerControls(args: {
8
+ clientRef: MutableRefObject<LiveSessionClient | null>;
9
+ videoCaptureRef: MutableRefObject<LiveVideoCapture | null>;
10
+ captionsRef: MutableRefObject<LiveCaptionState>;
11
+ connectionRef: MutableRefObject<() => LiveConnection | Promise<LiveConnection>>;
12
+ statusRef: MutableRefObject<LiveSessionStatus>;
13
+ isMutedRef: MutableRefObject<boolean>;
14
+ sessionPermissionsRef: MutableRefObject<string[]>;
15
+ ensureClient: (connection: LiveConnection) => LiveSessionClient;
16
+ clearClient: () => void;
17
+ cancelGateDecision: (reason?: string) => void;
18
+ stopVideo: () => void;
19
+ resetCaptions: () => void;
20
+ sessionActive: boolean;
21
+ textAvailable: boolean;
22
+ videoAvailable: boolean;
23
+ voiceAvailable: boolean;
24
+ isVideoOn: boolean;
25
+ textDraft: string;
26
+ setTextDraft: Dispatch<SetStateAction<string>>;
27
+ setCaptions: Dispatch<SetStateAction<LiveCaptionState>>;
28
+ reportFailure: (err: unknown) => void;
29
+ clearFailure: () => void;
30
+ setIsMuted: Dispatch<SetStateAction<boolean>>;
31
+ setSessionActive: Dispatch<SetStateAction<boolean>>;
32
+ setSessionPermissions: Dispatch<SetStateAction<string[]>>;
33
+ setStatus: Dispatch<SetStateAction<LiveSessionStatus>>;
34
+ setConnectPhase: Dispatch<SetStateAction<LiveConnectPhase | null>>;
35
+ setInputLevel: Dispatch<SetStateAction<number>>;
36
+ setOutputLevel: Dispatch<SetStateAction<number>>;
37
+ setVideoPreview: Dispatch<SetStateAction<HTMLVideoElement | null>>;
38
+ setVideoFacingMode: Dispatch<SetStateAction<LiveFacingMode>>;
39
+ setIsVideoOn: Dispatch<SetStateAction<boolean>>;
40
+ }): {
41
+ teardownSession: () => void;
42
+ startSession: () => Promise<void>;
43
+ handleSendText: () => void;
44
+ handleToggleVideo: () => Promise<void>;
45
+ handleFlipCamera: () => Promise<void>;
46
+ handleToggleMic: () => void;
47
+ handleRestart: () => Promise<void>;
48
+ };
@@ -0,0 +1,100 @@
1
+ import { useCallback } from 'react';
2
+ import { applyLiveTranscript, } from "../../client/live/live-captions.js";
3
+ import { startLiveVideoCapture, } from "../../client/live/live-video.js";
4
+ /** Media + session lifecycle handlers for LiveRunner. */
5
+ export function useLiveRunnerControls(args) {
6
+ const teardownSession = useCallback(() => {
7
+ args.cancelGateDecision();
8
+ args.clearClient();
9
+ args.stopVideo();
10
+ args.setIsMuted(false);
11
+ args.isMutedRef.current = false;
12
+ args.setSessionActive(false);
13
+ args.setSessionPermissions([]);
14
+ args.sessionPermissionsRef.current = [];
15
+ args.statusRef.current = 'disconnected';
16
+ args.setStatus('disconnected');
17
+ args.setConnectPhase(null);
18
+ args.setInputLevel(0);
19
+ args.setOutputLevel(0);
20
+ }, [args]);
21
+ const startSession = useCallback(async () => {
22
+ args.clearFailure();
23
+ args.resetCaptions();
24
+ try {
25
+ const liveClient = args.ensureClient(await args.connectionRef.current());
26
+ if (args.statusRef.current === 'disconnected' || args.statusRef.current === 'error') {
27
+ await liveClient.connect();
28
+ }
29
+ }
30
+ catch (err) {
31
+ args.reportFailure(err);
32
+ }
33
+ }, [args]);
34
+ const handleSendText = useCallback(() => {
35
+ const text = args.textDraft.trim();
36
+ if (!text || !args.clientRef.current || !args.sessionActive || !args.textAvailable)
37
+ return;
38
+ args.clientRef.current.sendText(text);
39
+ args.setTextDraft('');
40
+ const next = applyLiveTranscript(args.captionsRef.current, text, true, false, {
41
+ forceNew: true,
42
+ });
43
+ args.captionsRef.current = next;
44
+ args.setCaptions(next);
45
+ }, [args]);
46
+ const handleToggleVideo = useCallback(async () => {
47
+ if (!args.clientRef.current || !args.sessionActive || !args.videoAvailable)
48
+ return;
49
+ if (args.isVideoOn) {
50
+ args.stopVideo();
51
+ return;
52
+ }
53
+ try {
54
+ const capture = await startLiveVideoCapture((base64) => {
55
+ args.clientRef.current?.sendVideo(base64);
56
+ });
57
+ args.videoCaptureRef.current = capture;
58
+ args.setVideoPreview(capture.video);
59
+ args.setVideoFacingMode(capture.facingMode());
60
+ args.setIsVideoOn(true);
61
+ }
62
+ catch (err) {
63
+ args.reportFailure(err);
64
+ args.stopVideo();
65
+ }
66
+ }, [args]);
67
+ const handleFlipCamera = useCallback(async () => {
68
+ const capture = args.videoCaptureRef.current;
69
+ if (!capture || !args.sessionActive || !args.isVideoOn)
70
+ return;
71
+ try {
72
+ const facing = await capture.flip();
73
+ args.setVideoFacingMode(facing);
74
+ args.setVideoPreview(capture.video);
75
+ }
76
+ catch (err) {
77
+ args.reportFailure(err);
78
+ }
79
+ }, [args]);
80
+ const handleToggleMic = useCallback(() => {
81
+ if (!args.clientRef.current || !args.sessionActive || !args.voiceAvailable)
82
+ return;
83
+ const muted = args.clientRef.current.toggleMute();
84
+ args.isMutedRef.current = muted;
85
+ args.setIsMuted(muted);
86
+ }, [args]);
87
+ const handleRestart = useCallback(async () => {
88
+ teardownSession();
89
+ await startSession();
90
+ }, [startSession, teardownSession]);
91
+ return {
92
+ teardownSession,
93
+ startSession,
94
+ handleSendText,
95
+ handleToggleVideo,
96
+ handleFlipCamera,
97
+ handleToggleMic,
98
+ handleRestart,
99
+ };
100
+ }