@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,90 @@
1
+ import { useCallback, useRef, useState } from 'react';
2
+ import { attachmentIssueText, describeError, lexiconText, } from '@theoremjs/agents';
3
+ import { canStageVoice } from "../client/composer-attachments.js";
4
+ import { ComposerVoiceRecorder, isVoiceRecorderFailure, } from "../client/voice-recorder.js";
5
+ function voiceFailure(err, fallback, lexicon) {
6
+ const code = isVoiceRecorderFailure(err) ? err.code : fallback;
7
+ const internal = describeError(err);
8
+ return {
9
+ code,
10
+ error: lexiconText(`voice.${code}`, {}, lexicon),
11
+ ...(internal && internal !== code ? { errorInternal: internal } : {}),
12
+ };
13
+ }
14
+ export function useComposerVoice(args) {
15
+ const [recording, setRecording] = useState(false);
16
+ const [inputLevel, setInputLevel] = useState(0);
17
+ const [failure, setFailure] = useState(null);
18
+ const recorderRef = useRef(null);
19
+ const onVoiceStagedRef = useRef(args.onVoiceStaged);
20
+ const onVoiceClearRef = useRef(args.onVoiceClear);
21
+ onVoiceStagedRef.current = args.onVoiceStaged;
22
+ onVoiceClearRef.current = args.onVoiceClear;
23
+ const ensureRecorder = useCallback(() => {
24
+ recorderRef.current ??= new ComposerVoiceRecorder(args.inputs.voice?.accept ?? [], (level) => {
25
+ setInputLevel(level);
26
+ });
27
+ return recorderRef.current;
28
+ }, [args.inputs.voice?.accept]);
29
+ const startRecording = useCallback(async () => {
30
+ setFailure(null);
31
+ const maxFiles = args.inputs.maxFiles;
32
+ if (!canStageVoice({ fileCount: args.pendingFiles.length, maxFiles })) {
33
+ setFailure({
34
+ code: 'too_many_files',
35
+ error: attachmentIssueText({ code: 'too_many_files', params: { maxFiles } }, args.lexicon),
36
+ });
37
+ return;
38
+ }
39
+ onVoiceClearRef.current?.();
40
+ try {
41
+ await ensureRecorder().start();
42
+ setRecording(true);
43
+ }
44
+ catch (err) {
45
+ setRecording(false);
46
+ setFailure(voiceFailure(err, 'unavailable', args.lexicon));
47
+ }
48
+ }, [args.inputs.maxFiles, args.lexicon, args.pendingFiles.length, ensureRecorder]);
49
+ const stopRecording = useCallback(async () => {
50
+ setFailure(null);
51
+ try {
52
+ const file = await ensureRecorder().stop();
53
+ setRecording(false);
54
+ setInputLevel(0);
55
+ onVoiceStagedRef.current?.(file);
56
+ }
57
+ catch (err) {
58
+ setRecording(false);
59
+ setInputLevel(0);
60
+ setFailure(voiceFailure(err, 'failed', args.lexicon));
61
+ }
62
+ }, [args.lexicon, ensureRecorder]);
63
+ const toggleRecording = useCallback(async () => {
64
+ if (recording) {
65
+ await stopRecording();
66
+ return;
67
+ }
68
+ await startRecording();
69
+ }, [recording, startRecording, stopRecording]);
70
+ const discardRecordingOrVoice = useCallback(() => {
71
+ if (recording) {
72
+ ensureRecorder().cancel();
73
+ setRecording(false);
74
+ setInputLevel(0);
75
+ }
76
+ onVoiceClearRef.current?.();
77
+ }, [ensureRecorder, recording]);
78
+ const disposeRecorder = useCallback(() => {
79
+ recorderRef.current?.dispose();
80
+ }, []);
81
+ return {
82
+ recording,
83
+ inputLevel,
84
+ failure,
85
+ setFailure,
86
+ toggleRecording,
87
+ discardRecordingOrVoice,
88
+ disposeRecorder,
89
+ };
90
+ }
@@ -0,0 +1,3 @@
1
+ import { type RefObject } from 'react';
2
+ /** Text currently selected inside `ref` (a contenteditable), or '' when the selection is elsewhere. */
3
+ export declare function useEditorSelection(ref: RefObject<HTMLElement | null>): string;
@@ -0,0 +1,16 @@
1
+ import { useEffect, useState } from 'react';
2
+ /** Text currently selected inside `ref` (a contenteditable), or '' when the selection is elsewhere. */
3
+ export function useEditorSelection(ref) {
4
+ const [selected, setSelected] = useState('');
5
+ useEffect(() => {
6
+ const read = () => {
7
+ const root = ref.current;
8
+ const selection = document.getSelection();
9
+ const inside = root && selection && selection.rangeCount > 0 && root.contains(selection.getRangeAt(0).commonAncestorContainer);
10
+ setSelected(inside ? selection.toString() : '');
11
+ };
12
+ document.addEventListener('selectionchange', read);
13
+ return () => document.removeEventListener('selectionchange', read);
14
+ }, [ref]);
15
+ return selected;
16
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Click-to-play state for a voice note: an `<audio>` ref, whether it plays,
3
+ * and a gentle output level that drives the ink waveform while it does.
4
+ */
5
+ export declare function useVoicePlayback(): {
6
+ playing: boolean;
7
+ outputLevel: number;
8
+ toggle: () => Promise<void>;
9
+ audioProps: {
10
+ ref: import("react").RefObject<HTMLAudioElement | null>;
11
+ preload: "metadata";
12
+ onPlay: () => void;
13
+ onPause: () => void;
14
+ onEnded: () => void;
15
+ };
16
+ };
@@ -0,0 +1,68 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ /**
3
+ * Click-to-play state for a voice note: an `<audio>` ref, whether it plays,
4
+ * and a gentle output level that drives the ink waveform while it does.
5
+ */
6
+ export function useVoicePlayback() {
7
+ const audioRef = useRef(null);
8
+ const [playing, setPlaying] = useState(false);
9
+ const playingRef = useRef(false);
10
+ const [outputLevel, setOutputLevel] = useState(0);
11
+ const rafIdRef = useRef(0);
12
+ function stopMeter() {
13
+ cancelAnimationFrame(rafIdRef.current);
14
+ rafIdRef.current = 0;
15
+ setOutputLevel(0);
16
+ }
17
+ function startMeter() {
18
+ stopMeter();
19
+ const tick = (time) => {
20
+ if (!playingRef.current)
21
+ return;
22
+ setOutputLevel(0.28 + 0.22 * (0.5 + 0.5 * Math.sin(time * 0.008)));
23
+ rafIdRef.current = requestAnimationFrame(tick);
24
+ };
25
+ rafIdRef.current = requestAnimationFrame(tick);
26
+ }
27
+ useEffect(() => {
28
+ return () => {
29
+ cancelAnimationFrame(rafIdRef.current);
30
+ };
31
+ }, []);
32
+ function settle(isPlaying) {
33
+ playingRef.current = isPlaying;
34
+ setPlaying(isPlaying);
35
+ if (isPlaying)
36
+ startMeter();
37
+ else
38
+ stopMeter();
39
+ }
40
+ async function toggle() {
41
+ const audio = audioRef.current;
42
+ if (!audio)
43
+ return;
44
+ if (!audio.paused) {
45
+ audio.pause();
46
+ return;
47
+ }
48
+ try {
49
+ await audio.play();
50
+ }
51
+ catch {
52
+ settle(false);
53
+ }
54
+ }
55
+ /** Spread onto the `<audio>` element. */
56
+ const audioProps = {
57
+ ref: audioRef,
58
+ preload: 'metadata',
59
+ onPlay: () => settle(true),
60
+ onPause: () => settle(false),
61
+ onEnded: () => {
62
+ settle(false);
63
+ if (audioRef.current)
64
+ audioRef.current.currentTime = 0;
65
+ },
66
+ };
67
+ return { playing, outputLevel, toggle, audioProps };
68
+ }
@@ -0,0 +1,8 @@
1
+ import { type ClientFailure } from '../client/failure.ts';
2
+ /** What a transport describes, read once per transport; a failure when it can't be read. */
3
+ export declare function useDescribed<T>(transport: {
4
+ describe: (signal?: AbortSignal) => Promise<T>;
5
+ }): {
6
+ iface: T | null;
7
+ describeFailure: ClientFailure | null;
8
+ };
@@ -0,0 +1,23 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { clientFailure } from "../client/failure.js";
3
+ /** What a transport describes, read once per transport; a failure when it can't be read. */
4
+ export function useDescribed(transport) {
5
+ const [description, setDescription] = useState(null);
6
+ useEffect(() => {
7
+ const controller = new AbortController();
8
+ transport.describe(controller.signal).then((described) => {
9
+ if (controller.signal.aborted)
10
+ return;
11
+ setDescription({ transport, iface: described, describeFailure: null });
12
+ }, (err) => {
13
+ if (!controller.signal.aborted)
14
+ setDescription({ transport, iface: null, describeFailure: clientFailure(err) });
15
+ });
16
+ return () => {
17
+ controller.abort();
18
+ };
19
+ }, [transport]);
20
+ return description?.transport === transport
21
+ ? { iface: description.iface, describeFailure: description.describeFailure }
22
+ : { iface: null, describeFailure: null };
23
+ }
@@ -0,0 +1,75 @@
1
+ import { type MutableRefObject } from 'react';
2
+ import type { AttachmentValidationIssue, ComposerMenuAction, ComposerPendingMessage, ComposerProfileInterface, ComposerRunPhase, InterfaceTurnSession, TranscriptBlock } from '@theoremjs/agents/interface';
3
+ import { type AnsweringGate, type StreamView, type ToolDecisionAction } from '../client/index.ts';
4
+ import { type ClientFailure, type TurnFailure } from '../client/failure.ts';
5
+ import type { TheoremTransport } from '../client/transport.ts';
6
+ import type { MessageDelivery } from './use-theorem-chat-state.ts';
7
+ export type RunTurnStream = (run: (view: StreamView,
8
+ /** The work so far of the reply the session waits on (none when it waits on nothing). */
9
+ paused: {
10
+ workedMs: number;
11
+ }) => Promise<{
12
+ ok: true;
13
+ session: InterfaceTurnSession;
14
+ userBlocks?: TranscriptBlock[];
15
+ assistantBlocks: TranscriptBlock[];
16
+ } | TurnFailure>, options?: {
17
+ userBlocksAlreadyApplied?: boolean;
18
+ /** The run's message walks away from the gates the reply waits on, ending that reply. */
19
+ walksAway?: boolean;
20
+ }) => Promise<void>;
21
+ export type TheoremChatActionArgs = {
22
+ iface: ComposerProfileInterface | null;
23
+ transport: TheoremTransport;
24
+ phase: ComposerRunPhase;
25
+ gated: boolean;
26
+ draftText: string;
27
+ pendingFiles: File[];
28
+ pendingVoice: File[];
29
+ clearComposer: () => void;
30
+ runTurnStream: RunTurnStream;
31
+ sessionRef: MutableRefObject<InterfaceTurnSession>;
32
+ blocksRef: MutableRefObject<TranscriptBlock[]>;
33
+ abortRef: MutableRefObject<AbortController | null>;
34
+ turnIdRef: MutableRefObject<string | null>;
35
+ busyRef: MutableRefObject<boolean>;
36
+ runPromiseRef: MutableRefObject<Promise<void> | null>;
37
+ allowQueueDrainRef: MutableRefObject<boolean>;
38
+ setBlocks: (value: TranscriptBlock[] | ((prev: TranscriptBlock[]) => TranscriptBlock[])) => void;
39
+ setStreamBlocks: (value: TranscriptBlock[]) => void;
40
+ setSession: (value: InterfaceTurnSession) => void;
41
+ setChatStarted: (value: boolean) => void;
42
+ setStreaming: (value: boolean) => void;
43
+ setPendingMessages: (value: ComposerPendingMessage[] | ((prev: ComposerPendingMessage[]) => ComposerPendingMessage[])) => void;
44
+ setDraftText: (value: string) => void;
45
+ setPendingFiles: (value: File[]) => void;
46
+ setPendingVoice: (value: File[]) => void;
47
+ setIssues: (value: AttachmentValidationIssue[]) => void;
48
+ setFailure: (value: ClientFailure | null) => void;
49
+ setAnswering: (value: AnsweringGate | null) => void;
50
+ setDelivery: (value: {
51
+ status: MessageDelivery;
52
+ } | null) => void;
53
+ pendingRef: MutableRefObject<ComposerPendingMessage[]>;
54
+ };
55
+ export declare function useTheoremChatActions(args: TheoremChatActionArgs): {
56
+ startTurnFromFields: (fields: {
57
+ text: string;
58
+ files: File[];
59
+ voice: File[];
60
+ }) => Promise<void>;
61
+ startTurnFromDraft: (draft: ComposerPendingMessage["draft"], options?: {
62
+ /** Send while the reply waits on gates: the message walks away from them. */
63
+ walkAway?: boolean;
64
+ /** Once the message posts: it leaves wherever it was waiting to be sent. */
65
+ onPosted?: () => void;
66
+ }) => Promise<void>;
67
+ handleStop: () => void;
68
+ handleSubmit: () => Promise<void>;
69
+ handleSendNow: (draftSource?: ComposerPendingMessage) => Promise<void>;
70
+ handleMenuAction: (action: ComposerMenuAction) => void;
71
+ handleToolDecision: (_index: number, action: ToolDecisionAction) => Promise<void>;
72
+ handleAuthenticated: (_index: number, secret?: string) => Promise<void>;
73
+ handlePendingRestore: (id: string) => Promise<void>;
74
+ enqueuePending: (kind: "queue" | "steer" | "stash") => Promise<void>;
75
+ };
@@ -0,0 +1,265 @@
1
+ import { useCallback } from 'react';
2
+ import { TheoremError } from '@theoremjs/agents';
3
+ import { convertSteersToFrontQueued, createComposerPendingMessage, orderComposerPendingMessages, removeComposerPendingMessage, userDraftHasPayload, userDraftToSteerInject, } from '@theoremjs/agents/interface';
4
+ import { composerFieldsFromDraft, encodeComposerDraft, resumeInterfaceTool, streamInterfaceDraftTurn, streamInterfaceTurn, } from "../client/index.js";
5
+ import { clientFailure } from "../client/failure.js";
6
+ function composerFieldsPayload(text, pendingFiles, pendingVoice) {
7
+ const meta = (files) => files.map((f) => ({
8
+ name: f.name,
9
+ mimeType: f.type || 'application/octet-stream',
10
+ sizeBytes: f.size,
11
+ }));
12
+ return {
13
+ ...(text.trim() ? { text } : {}),
14
+ ...(pendingFiles.length ? { attachments: meta(pendingFiles) } : {}),
15
+ ...(pendingVoice.length ? { voice: meta(pendingVoice) } : {}),
16
+ };
17
+ }
18
+ function newTurnId() {
19
+ return typeof crypto !== 'undefined' && 'randomUUID' in crypto
20
+ ? crypto.randomUUID()
21
+ : `turn-${Date.now()}`;
22
+ }
23
+ function beginAbortableTurn(args) {
24
+ if (!args.iface)
25
+ return null;
26
+ const controller = new AbortController();
27
+ args.abortRef.current = controller;
28
+ const turnId = newTurnId();
29
+ args.turnIdRef.current = turnId;
30
+ return {
31
+ composer: args.iface,
32
+ turnId,
33
+ signal: controller.signal,
34
+ };
35
+ }
36
+ /** Starts a turn from the composer's fields, or from an encoded draft (queued or send-now). */
37
+ function useTurnStarters(args) {
38
+ const startTurnFromFields = useCallback(async (fields) => {
39
+ const started = beginAbortableTurn(args);
40
+ if (!started)
41
+ return;
42
+ await args.runTurnStream((view) => streamInterfaceTurn({
43
+ iface: started.composer,
44
+ transport: args.transport,
45
+ session: args.sessionRef.current,
46
+ text: fields.text,
47
+ pendingFiles: fields.files,
48
+ pendingVoice: fields.voice,
49
+ signal: started.signal,
50
+ turnId: started.turnId,
51
+ view,
52
+ onUserBlocks: (userBlocks) => {
53
+ args.setBlocks((prev) => [...prev, ...userBlocks]);
54
+ args.setDelivery({ status: 'sending' });
55
+ args.setChatStarted(true);
56
+ args.setStreaming(true);
57
+ args.clearComposer();
58
+ },
59
+ }), { userBlocksAlreadyApplied: true });
60
+ }, [args]);
61
+ const startTurnFromDraft = useCallback(async (draft, options = {}) => {
62
+ const started = beginAbortableTurn(args);
63
+ if (!started)
64
+ return;
65
+ await args.runTurnStream((view, paused) => streamInterfaceDraftTurn({
66
+ iface: started.composer,
67
+ transport: args.transport,
68
+ session: args.sessionRef.current,
69
+ draft,
70
+ signal: started.signal,
71
+ turnId: started.turnId,
72
+ view,
73
+ ...(options.walkAway ? { walkAway: paused } : {}),
74
+ onUserBlocks: (posted) => {
75
+ args.setBlocks((prev) => [...prev, ...posted]);
76
+ // A walked-away reply's blocks were streaming; they are posted now.
77
+ args.setStreamBlocks([]);
78
+ args.setDelivery({ status: 'sending' });
79
+ args.setChatStarted(true);
80
+ args.setStreaming(true);
81
+ options.onPosted?.();
82
+ },
83
+ }), { userBlocksAlreadyApplied: true, walksAway: options.walkAway === true });
84
+ }, [args]);
85
+ return { startTurnFromFields, startTurnFromDraft };
86
+ }
87
+ /** A steer with no running turn: the user reads `session.turn_ended`; queued steers move to the front. */
88
+ function steerAfterTurnEnded(args, messageId) {
89
+ args.setFailure(clientFailure(
90
+ // lexicon-exempt: internal diagnostic; the user reads session.turn_ended
91
+ new TheoremError('request', 'steer: no active turn', { copy: { key: 'session.turn_ended' } }), args.iface?.lexicon));
92
+ args.setPendingMessages((prev) => orderComposerPendingMessages(convertSteersToFrontQueued(removeComposerPendingMessage(prev, messageId))));
93
+ }
94
+ /** Queue / steer / stash the composer draft, and restore a pending message into the composer. */
95
+ function usePendingActions(args) {
96
+ const enqueuePending = useCallback(async (kind) => {
97
+ if (!userDraftHasPayload(composerFieldsPayload(args.draftText, args.pendingFiles, args.pendingVoice))) {
98
+ return;
99
+ }
100
+ args.setIssues([]);
101
+ try {
102
+ const draft = await encodeComposerDraft({
103
+ text: args.draftText,
104
+ pendingFiles: args.pendingFiles,
105
+ pendingVoice: args.pendingVoice,
106
+ });
107
+ const message = createComposerPendingMessage({ kind, draft });
108
+ args.setPendingMessages((prev) => orderComposerPendingMessages([...prev, message]));
109
+ args.clearComposer();
110
+ if (kind !== 'steer')
111
+ return;
112
+ const turnId = args.turnIdRef.current;
113
+ if (!turnId) {
114
+ steerAfterTurnEnded(args, message.id);
115
+ return;
116
+ }
117
+ const inject = userDraftToSteerInject(draft);
118
+ if (inject.length === 0)
119
+ return;
120
+ await args.transport.steer({ turnId, id: message.id, inject });
121
+ }
122
+ catch (err) {
123
+ args.setFailure(clientFailure(err, args.iface?.lexicon));
124
+ }
125
+ }, [args]);
126
+ const handlePendingRestore = useCallback(async (id) => {
127
+ const message = args.pendingRef.current.find((m) => m.id === id);
128
+ if (!message)
129
+ return;
130
+ const currentDraft = composerFieldsPayload(args.draftText, args.pendingFiles, args.pendingVoice);
131
+ try {
132
+ let nextPending = removeComposerPendingMessage(args.pendingRef.current, id);
133
+ if (userDraftHasPayload(currentDraft)) {
134
+ const stashDraft = await encodeComposerDraft({
135
+ text: args.draftText,
136
+ pendingFiles: args.pendingFiles,
137
+ pendingVoice: args.pendingVoice,
138
+ });
139
+ const stash = createComposerPendingMessage({ kind: 'stash', draft: stashDraft });
140
+ nextPending = orderComposerPendingMessages([...nextPending, stash]);
141
+ }
142
+ const restored = composerFieldsFromDraft(message.draft);
143
+ args.setPendingMessages(nextPending);
144
+ args.setDraftText(restored.text);
145
+ args.setPendingFiles(restored.files);
146
+ args.setPendingVoice(restored.voice);
147
+ args.setIssues([]);
148
+ }
149
+ catch (err) {
150
+ args.setFailure(clientFailure(err, args.iface?.lexicon));
151
+ }
152
+ }, [args]);
153
+ return { enqueuePending, handlePendingRestore };
154
+ }
155
+ /** Answer the gate the session waits on: a decision, or a sign-in. */
156
+ function useGateActions(args) {
157
+ const resumeGatedTool = useCallback(async (resolution) => {
158
+ const composer = args.iface;
159
+ if (!composer)
160
+ return;
161
+ // Set once the run starts (not while another runs), and cleared once it has ended.
162
+ let started = false;
163
+ try {
164
+ await args.runTurnStream((view) => {
165
+ started = true;
166
+ const session = args.sessionRef.current;
167
+ if (session.gatedTool)
168
+ args.setAnswering({ callId: session.gatedTool.callId, action: resolution.action });
169
+ return resumeInterfaceTool({ iface: composer, transport: args.transport, session, resolution, view });
170
+ });
171
+ }
172
+ finally {
173
+ if (started)
174
+ args.setAnswering(null);
175
+ }
176
+ }, [args]);
177
+ const handleToolDecision = useCallback(async (_index, action) => {
178
+ await resumeGatedTool({ action });
179
+ }, [resumeGatedTool]);
180
+ const handleAuthenticated = useCallback(async (_index, secret) => {
181
+ await resumeGatedTool({ action: 'auth', ...(secret === undefined ? {} : { secret }) });
182
+ }, [resumeGatedTool]);
183
+ return { handleToolDecision, handleAuthenticated };
184
+ }
185
+ export function useTheoremChatActions(args) {
186
+ const { startTurnFromFields, startTurnFromDraft } = useTurnStarters(args);
187
+ const { enqueuePending, handlePendingRestore } = usePendingActions(args);
188
+ const { handleToolDecision, handleAuthenticated } = useGateActions(args);
189
+ const handleStop = useCallback(() => {
190
+ args.abortRef.current?.abort();
191
+ }, [args]);
192
+ const handleSubmit = useCallback(async () => {
193
+ if (args.phase === 'streaming' || args.phase === 'gated') {
194
+ await enqueuePending('queue');
195
+ return;
196
+ }
197
+ if (args.gated)
198
+ return;
199
+ args.setIssues([]);
200
+ await startTurnFromFields({
201
+ text: args.draftText,
202
+ files: [...args.pendingFiles],
203
+ voice: [...args.pendingVoice],
204
+ });
205
+ }, [args, enqueuePending, startTurnFromFields]);
206
+ /** The queued message's draft, or the composer's when it holds something to send. */
207
+ const draftToSend = useCallback(async (draftSource) => {
208
+ if (draftSource)
209
+ return draftSource.draft;
210
+ const draft = await encodeComposerDraft({
211
+ text: args.draftText,
212
+ pendingFiles: args.pendingFiles,
213
+ pendingVoice: args.pendingVoice,
214
+ });
215
+ return userDraftHasPayload(draft) ? draft : undefined;
216
+ }, [args]);
217
+ /** The sent message leaves the queue, or the composer it came from. */
218
+ const releaseDraft = useCallback((draftSource) => {
219
+ if (!draftSource) {
220
+ args.clearComposer();
221
+ return;
222
+ }
223
+ const pendingId = draftSource.id;
224
+ args.setPendingMessages((prev) => removeComposerPendingMessage(prev, pendingId));
225
+ }, [args]);
226
+ const handleSendNow = useCallback(async (draftSource) => {
227
+ if (!args.iface)
228
+ return;
229
+ const draft = await draftToSend(draftSource);
230
+ if (!draft)
231
+ return;
232
+ if (args.busyRef.current) {
233
+ args.abortRef.current?.abort();
234
+ await args.runPromiseRef.current;
235
+ }
236
+ args.setPendingMessages((prev) => convertSteersToFrontQueued(prev));
237
+ args.allowQueueDrainRef.current = false;
238
+ // Sent while the reply waits on gates, the message walks away from them in its own request.
239
+ await startTurnFromDraft(draft, {
240
+ walkAway: args.sessionRef.current.gatedTool !== null,
241
+ // The message leaves the composer (or the queue) once it posts.
242
+ onPosted: () => releaseDraft(draftSource),
243
+ });
244
+ }, [args, draftToSend, releaseDraft, startTurnFromDraft]);
245
+ const handleMenuAction = useCallback((action) => {
246
+ if (action === 'queue' || action === 'steer' || action === 'stash') {
247
+ void enqueuePending(action);
248
+ return;
249
+ }
250
+ if (action === 'send_now')
251
+ void handleSendNow();
252
+ }, [enqueuePending, handleSendNow]);
253
+ return {
254
+ startTurnFromFields,
255
+ startTurnFromDraft,
256
+ handleStop,
257
+ handleSubmit,
258
+ handleSendNow,
259
+ handleMenuAction,
260
+ handleToolDecision,
261
+ handleAuthenticated,
262
+ handlePendingRestore,
263
+ enqueuePending,
264
+ };
265
+ }
@@ -0,0 +1,66 @@
1
+ import { type AttachmentValidationIssue, type ComposerPendingMessage, type InterfaceTurnSession, type TranscriptBlock } from '@theoremjs/agents/interface';
2
+ import type { ClientFailure } from '../client/failure.ts';
3
+ import type { AnsweringGate } from '../client/tool-resume.ts';
4
+ type SetSession = (value: InterfaceTurnSession | ((prev: InterfaceTurnSession) => InterfaceTurnSession)) => void;
5
+ export type { SetSession };
6
+ /**
7
+ * How far the latest message has got: posted (`sending`), taken by the server
8
+ * (`sent`), reached the model (`delivered`), answered (`read`).
9
+ */
10
+ export type MessageDelivery = 'sending' | 'sent' | 'delivered' | 'read';
11
+ /** A conversation at rest: what the transcript shows and what the next turn is sent with. */
12
+ export type ChatSnapshot = {
13
+ blocks: TranscriptBlock[];
14
+ session: InterfaceTurnSession;
15
+ };
16
+ /** Composer / transcript / session state behind {@link useTheoremChat}. */
17
+ export declare function useTheoremChatState(initial?: ChatSnapshot): {
18
+ blocks: TranscriptBlock[];
19
+ setBlocks: (value: TranscriptBlock[] | ((prev: TranscriptBlock[]) => TranscriptBlock[])) => void;
20
+ streamBlocks: TranscriptBlock[];
21
+ setStreamBlocks: import("react").Dispatch<import("react").SetStateAction<TranscriptBlock[]>>;
22
+ streamBlocksRef: import("react").RefObject<TranscriptBlock[]>;
23
+ draftText: string;
24
+ setDraftText: import("react").Dispatch<import("react").SetStateAction<string>>;
25
+ pendingFiles: File[];
26
+ setPendingFiles: import("react").Dispatch<import("react").SetStateAction<File[]>>;
27
+ pendingVoice: File[];
28
+ setPendingVoice: import("react").Dispatch<import("react").SetStateAction<File[]>>;
29
+ pendingMessages: ComposerPendingMessage[];
30
+ setPendingMessages: import("react").Dispatch<import("react").SetStateAction<ComposerPendingMessage[]>>;
31
+ issues: AttachmentValidationIssue[];
32
+ setIssues: import("react").Dispatch<import("react").SetStateAction<AttachmentValidationIssue[]>>;
33
+ failure: ClientFailure | null;
34
+ setFailure: import("react").Dispatch<import("react").SetStateAction<ClientFailure | null>>;
35
+ answering: AnsweringGate | null;
36
+ setAnswering: import("react").Dispatch<import("react").SetStateAction<AnsweringGate | null>>;
37
+ busy: boolean;
38
+ setBusy: import("react").Dispatch<import("react").SetStateAction<boolean>>;
39
+ chatStarted: boolean;
40
+ setChatStarted: import("react").Dispatch<import("react").SetStateAction<boolean>>;
41
+ streaming: boolean;
42
+ setStreaming: import("react").Dispatch<import("react").SetStateAction<boolean>>;
43
+ session: InterfaceTurnSession;
44
+ setSession: import("react").Dispatch<import("react").SetStateAction<InterfaceTurnSession>>;
45
+ delivery: {
46
+ status: MessageDelivery;
47
+ } | null;
48
+ setDelivery: (next: {
49
+ status: MessageDelivery;
50
+ } | null) => void;
51
+ deliveryRef: import("react").RefObject<{
52
+ status: MessageDelivery;
53
+ } | null>;
54
+ blocksRef: import("react").RefObject<TranscriptBlock[]>;
55
+ busyRef: import("react").RefObject<boolean>;
56
+ abortRef: import("react").RefObject<AbortController | null>;
57
+ turnIdRef: import("react").RefObject<string | null>;
58
+ pendingRef: import("react").RefObject<ComposerPendingMessage[]>;
59
+ sessionRef: import("react").RefObject<InterfaceTurnSession>;
60
+ drainLockRef: import("react").RefObject<boolean>;
61
+ allowQueueDrainRef: import("react").RefObject<boolean>;
62
+ runPromiseRef: import("react").RefObject<Promise<void> | null>;
63
+ cancelPendingStreamFrame: () => void;
64
+ scheduleStreamBlocks: (partial: TranscriptBlock[]) => void;
65
+ clearComposer: () => void;
66
+ };