@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.
- package/LICENSE +21 -0
- package/README.md +246 -2
- package/client/attachment-hover-preview.d.ts +11 -0
- package/client/attachment-hover-preview.js +36 -0
- package/client/audio-level.d.ts +5 -0
- package/client/audio-level.js +24 -0
- package/client/composer-attachments.d.ts +30 -0
- package/client/composer-attachments.js +33 -0
- package/client/composer-drawer.d.ts +19 -0
- package/client/composer-drawer.js +15 -0
- package/client/composer-hints.d.ts +25 -0
- package/client/composer-hints.js +20 -0
- package/client/composer-primary.d.ts +16 -0
- package/client/composer-primary.js +25 -0
- package/client/decision-transport.d.ts +48 -0
- package/client/decision-transport.js +102 -0
- package/client/decode-composer-draft.d.ts +11 -0
- package/client/decode-composer-draft.js +12 -0
- package/client/encode-composer-draft.d.ts +9 -0
- package/client/encode-composer-draft.js +34 -0
- package/client/encode-files.d.ts +18 -0
- package/client/encode-files.js +54 -0
- package/client/failure.d.ts +32 -0
- package/client/failure.js +34 -0
- package/client/generation-selection.d.ts +15 -0
- package/client/generation-selection.js +24 -0
- package/client/host-call.d.ts +51 -0
- package/client/host-call.js +42 -0
- package/client/host-transport.d.ts +68 -0
- package/client/host-transport.js +74 -0
- package/client/image-output.d.ts +6 -0
- package/client/image-output.js +13 -0
- package/client/index.d.ts +20 -0
- package/client/index.js +18 -0
- package/client/ink-waveform.d.ts +17 -0
- package/client/ink-waveform.js +69 -0
- package/client/live/apply-live-turn-tool-event.d.ts +14 -0
- package/client/live/apply-live-turn-tool-event.js +39 -0
- package/client/live/live-captions.d.ts +23 -0
- package/client/live/live-captions.js +70 -0
- package/client/live/live-mic-forward.d.ts +29 -0
- package/client/live/live-mic-forward.js +37 -0
- package/client/live/live-state.d.ts +11 -0
- package/client/live/live-state.js +20 -0
- package/client/live/live-tool.d.ts +11 -0
- package/client/live/live-tool.js +1 -0
- package/client/live/live-video.d.ts +10 -0
- package/client/live/live-video.js +71 -0
- package/client/live/run-live-tool-call.d.ts +17 -0
- package/client/live/run-live-tool-call.js +32 -0
- package/client/live-client.d.ts +141 -0
- package/client/live-client.js +638 -0
- package/client/live-errors.d.ts +2 -0
- package/client/live-errors.js +9 -0
- package/client/live-messages.d.ts +110 -0
- package/client/live-messages.js +67 -0
- package/client/mic-capture.d.ts +4 -0
- package/client/mic-capture.js +6 -0
- package/client/oauth-popup.d.ts +26 -0
- package/client/oauth-popup.js +30 -0
- package/client/pcm-downsample.d.ts +4 -0
- package/client/pcm-downsample.js +45 -0
- package/client/run-commit.d.ts +61 -0
- package/client/run-commit.js +124 -0
- package/client/run-session.d.ts +80 -0
- package/client/run-session.js +215 -0
- package/client/schema-fields.d.ts +63 -0
- package/client/schema-fields.js +178 -0
- package/client/shaped-data.d.ts +118 -0
- package/client/shaped-data.js +314 -0
- package/client/source-citations.d.ts +13 -0
- package/client/source-citations.js +56 -0
- package/client/state-fields.d.ts +16 -0
- package/client/state-fields.js +44 -0
- package/client/tool-result.d.ts +78 -0
- package/client/tool-result.js +275 -0
- package/client/tool-resume.d.ts +33 -0
- package/client/tool-resume.js +11 -0
- package/client/trace-feed.d.ts +13 -0
- package/client/trace-feed.js +18 -0
- package/client/trace-story.d.ts +163 -0
- package/client/trace-story.js +416 -0
- package/client/trace-view.d.ts +130 -0
- package/client/trace-view.js +293 -0
- package/client/transcript-groups.d.ts +114 -0
- package/client/transcript-groups.js +167 -0
- package/client/transcript-scroll.d.ts +7 -0
- package/client/transcript-scroll.js +6 -0
- package/client/transport.d.ts +176 -0
- package/client/transport.js +256 -0
- package/client/turn-client.d.ts +35 -0
- package/client/turn-client.js +93 -0
- package/client/voice-label.d.ts +2 -0
- package/client/voice-label.js +15 -0
- package/client/voice-recorder.d.ts +27 -0
- package/client/voice-recorder.js +165 -0
- package/client/walk-away.d.ts +34 -0
- package/client/walk-away.js +48 -0
- package/client/wire-line.d.ts +56 -0
- package/client/wire-line.js +90 -0
- package/client/worklets/mic-capture.js +16 -0
- package/components/InkWaveform.d.ts +10 -0
- package/components/InkWaveform.js +108 -0
- package/components/live/use-live-runner-controls.d.ts +48 -0
- package/components/live/use-live-runner-controls.js +100 -0
- package/components/live/use-live-runner-model.d.ts +45 -0
- package/components/live/use-live-runner-model.js +175 -0
- package/components/live/use-live-runner-ui.d.ts +65 -0
- package/components/live/use-live-runner-ui.js +159 -0
- package/components/live/use-live-session-client.d.ts +41 -0
- package/components/live/use-live-session-client.js +123 -0
- package/components/use-composer-voice.d.ts +28 -0
- package/components/use-composer-voice.js +90 -0
- package/components/use-editor-selection.d.ts +3 -0
- package/components/use-editor-selection.js +16 -0
- package/components/use-voice-playback.d.ts +16 -0
- package/components/use-voice-playback.js +68 -0
- package/hooks/use-described.d.ts +8 -0
- package/hooks/use-described.js +23 -0
- package/hooks/use-theorem-chat-actions.d.ts +75 -0
- package/hooks/use-theorem-chat-actions.js +265 -0
- package/hooks/use-theorem-chat-state.d.ts +66 -0
- package/hooks/use-theorem-chat-state.js +118 -0
- package/hooks/use-theorem-chat.d.ts +63 -0
- package/hooks/use-theorem-chat.js +340 -0
- package/hooks/use-theorem-decision.d.ts +23 -0
- package/hooks/use-theorem-decision.js +75 -0
- package/hooks/use-theorem-host.d.ts +44 -0
- package/hooks/use-theorem-host.js +117 -0
- package/hooks/use-theorem-interface.d.ts +19 -0
- package/hooks/use-theorem-interface.js +25 -0
- package/hooks/use-trace-records.d.ts +4 -0
- package/hooks/use-trace-records.js +7 -0
- package/index.d.ts +14 -0
- package/index.js +14 -0
- package/live.d.ts +8 -0
- package/live.js +10 -0
- package/package.json +73 -4
- package/server/credential-store.d.ts +24 -0
- package/server/credential-store.js +14 -0
- package/server/decision-handler.d.ts +34 -0
- package/server/decision-handler.js +55 -0
- package/server/handler.d.ts +114 -0
- package/server/handler.js +568 -0
- package/server/json-response.d.ts +2 -0
- package/server/json-response.js +7 -0
- package/server/mod.d.ts +16 -0
- package/server/mod.js +15 -0
- package/server/request-check.d.ts +16 -0
- package/server/request-check.js +34 -0
- package/server/session-store.d.ts +74 -0
- package/server/session-store.js +49 -0
- package/server/steer-inbox.d.ts +35 -0
- package/server/steer-inbox.js +52 -0
- package/server/walk-away.d.ts +22 -0
- package/server/walk-away.js +41 -0
- package/styles/ink-controls.css +134 -0
- package/ui/ChatComposerBar.d.ts +39 -0
- package/ui/ChatComposerBar.js +251 -0
- package/ui/ChatTranscript.d.ts +30 -0
- package/ui/ChatTranscript.js +474 -0
- package/ui/ConsoleFrame.d.ts +47 -0
- package/ui/ConsoleFrame.js +46 -0
- package/ui/DecisionAnswers.d.ts +15 -0
- package/ui/DecisionAnswers.js +102 -0
- package/ui/LiveRunner.d.ts +28 -0
- package/ui/LiveRunner.js +193 -0
- package/ui/SchemaFields.d.ts +10 -0
- package/ui/SchemaFields.js +122 -0
- package/ui/ShapedData.d.ts +11 -0
- package/ui/ShapedData.js +151 -0
- package/ui/SidePanel.d.ts +57 -0
- package/ui/SidePanel.js +50 -0
- package/ui/StateFields.d.ts +36 -0
- package/ui/StateFields.js +96 -0
- package/ui/TheoremChat.d.ts +71 -0
- package/ui/TheoremChat.js +146 -0
- package/ui/TheoremDecision.d.ts +38 -0
- package/ui/TheoremDecision.js +87 -0
- package/ui/TheoremHost.d.ts +38 -0
- package/ui/TheoremHost.js +150 -0
- package/ui/ToolGateCard.d.ts +28 -0
- package/ui/ToolGateCard.js +100 -0
- package/ui/ToolResult.d.ts +10 -0
- package/ui/ToolResult.js +60 -0
- package/ui/ToolResultCharts.d.ts +5 -0
- package/ui/ToolResultCharts.js +86 -0
- package/ui/TraceCharts.d.ts +13 -0
- package/ui/TraceCharts.js +235 -0
- package/ui/TraceGuardrails.d.ts +10 -0
- package/ui/TraceGuardrails.js +56 -0
- package/ui/TraceInspector.d.ts +6 -0
- package/ui/TraceInspector.js +189 -0
- package/ui/TraceInspectorPanel.d.ts +24 -0
- package/ui/TraceInspectorPanel.js +57 -0
- package/ui/TraceOverview.d.ts +34 -0
- package/ui/TraceOverview.js +96 -0
- package/ui/TraceSpanDetail.d.ts +10 -0
- package/ui/TraceSpanDetail.js +188 -0
- package/ui/TraceStory.d.ts +20 -0
- package/ui/TraceStory.js +107 -0
- package/ui/TraceValues.d.ts +36 -0
- package/ui/TraceValues.js +113 -0
- package/ui/TraceWaterfall.d.ts +13 -0
- package/ui/TraceWaterfall.js +132 -0
- package/ui/VoiceNote.d.ts +12 -0
- package/ui/VoiceNote.js +28 -0
- package/ui/built/theme.css +774 -0
- package/ui/built/theorem.d.ts +13 -0
- package/ui/built/theorem.js +631 -0
- package/ui/built/theorem.variants.d.ts +16 -0
- package/ui/disclosure-motion.d.ts +6 -0
- package/ui/disclosure-motion.js +163 -0
- package/ui/icons.d.ts +4 -0
- package/ui/icons.js +37 -0
- package/ui/index.d.ts +25 -0
- package/ui/index.js +29 -0
- package/ui/labels-provider.d.ts +16 -0
- package/ui/labels-provider.js +61 -0
- package/ui/labels.d.ts +1153 -0
- package/ui/labels.js +603 -0
- package/ui/theme.d.ts +10 -0
- package/ui/theme.js +13 -0
- package/ui/theorem-theme.d.ts +10 -0
- package/ui/theorem-theme.js +83 -0
- package/ui/trace-palette.d.ts +24 -0
- package/ui/trace-palette.js +24 -0
- package/ui/trace-patterns.d.ts +32 -0
- package/ui/trace-patterns.js +44 -0
- package/ui/transcript-copy-text.d.ts +4 -0
- package/ui/transcript-copy-text.js +39 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { useCallback, useRef, useState } from 'react';
|
|
2
|
+
import { emptyInterfaceTurnSession, } from '@theoremjs/agents/interface';
|
|
3
|
+
/** Coalesces streamed partial blocks to one state update per animation frame. */
|
|
4
|
+
function useStreamFrames(setStreamBlocks) {
|
|
5
|
+
const streamRafRef = useRef(null);
|
|
6
|
+
const pendingStreamRef = useRef(null);
|
|
7
|
+
const cancelPendingStreamFrame = useCallback(() => {
|
|
8
|
+
if (streamRafRef.current != null) {
|
|
9
|
+
cancelAnimationFrame(streamRafRef.current);
|
|
10
|
+
streamRafRef.current = null;
|
|
11
|
+
}
|
|
12
|
+
pendingStreamRef.current = null;
|
|
13
|
+
}, []);
|
|
14
|
+
const scheduleStreamBlocks = useCallback((partial) => {
|
|
15
|
+
pendingStreamRef.current = partial;
|
|
16
|
+
if (streamRafRef.current != null)
|
|
17
|
+
return;
|
|
18
|
+
streamRafRef.current = requestAnimationFrame(() => {
|
|
19
|
+
streamRafRef.current = null;
|
|
20
|
+
const next = pendingStreamRef.current;
|
|
21
|
+
pendingStreamRef.current = null;
|
|
22
|
+
if (next)
|
|
23
|
+
setStreamBlocks(next);
|
|
24
|
+
});
|
|
25
|
+
}, []);
|
|
26
|
+
return { cancelPendingStreamFrame, scheduleStreamBlocks };
|
|
27
|
+
}
|
|
28
|
+
/** Composer / transcript / session state behind {@link useTheoremChat}. */
|
|
29
|
+
export function useTheoremChatState(initial) {
|
|
30
|
+
const [blocks, setBlocksState] = useState(initial?.blocks ?? []);
|
|
31
|
+
const [streamBlocks, setStreamBlocks] = useState([]);
|
|
32
|
+
const [draftText, setDraftText] = useState('');
|
|
33
|
+
const [pendingFiles, setPendingFiles] = useState([]);
|
|
34
|
+
const [pendingVoice, setPendingVoice] = useState([]);
|
|
35
|
+
const [pendingMessages, setPendingMessages] = useState([]);
|
|
36
|
+
const [issues, setIssues] = useState([]);
|
|
37
|
+
const [failure, setFailure] = useState(null);
|
|
38
|
+
const [answering, setAnswering] = useState(null);
|
|
39
|
+
const [busy, setBusy] = useState(false);
|
|
40
|
+
const [chatStarted, setChatStarted] = useState((initial?.blocks.length ?? 0) > 0);
|
|
41
|
+
const [streaming, setStreaming] = useState(false);
|
|
42
|
+
const [session, setSession] = useState(initial?.session ?? emptyInterfaceTurnSession());
|
|
43
|
+
// One object per posted message, so a run can tell whether it posted one.
|
|
44
|
+
const [delivery, setDeliveryState] = useState(null);
|
|
45
|
+
const blocksRef = useRef(blocks);
|
|
46
|
+
// Set as it is written, so a caller awaiting a turn reads the transcript the turn left.
|
|
47
|
+
const setBlocks = useCallback((value) => {
|
|
48
|
+
blocksRef.current = typeof value === 'function' ? value(blocksRef.current) : value;
|
|
49
|
+
setBlocksState(blocksRef.current);
|
|
50
|
+
}, []);
|
|
51
|
+
const streamBlocksRef = useRef(streamBlocks);
|
|
52
|
+
streamBlocksRef.current = streamBlocks;
|
|
53
|
+
const busyRef = useRef(false);
|
|
54
|
+
const abortRef = useRef(null);
|
|
55
|
+
const turnIdRef = useRef(null);
|
|
56
|
+
const pendingRef = useRef(pendingMessages);
|
|
57
|
+
pendingRef.current = pendingMessages;
|
|
58
|
+
const sessionRef = useRef(session);
|
|
59
|
+
sessionRef.current = session;
|
|
60
|
+
const drainLockRef = useRef(false);
|
|
61
|
+
const allowQueueDrainRef = useRef(false);
|
|
62
|
+
const runPromiseRef = useRef(null);
|
|
63
|
+
const deliveryRef = useRef(delivery);
|
|
64
|
+
const setDelivery = useCallback((next) => {
|
|
65
|
+
deliveryRef.current = next;
|
|
66
|
+
setDeliveryState(next);
|
|
67
|
+
}, []);
|
|
68
|
+
const { cancelPendingStreamFrame, scheduleStreamBlocks } = useStreamFrames(setStreamBlocks);
|
|
69
|
+
const clearComposer = useCallback(() => {
|
|
70
|
+
setDraftText('');
|
|
71
|
+
setPendingFiles([]);
|
|
72
|
+
setPendingVoice([]);
|
|
73
|
+
}, []);
|
|
74
|
+
return {
|
|
75
|
+
blocks,
|
|
76
|
+
setBlocks,
|
|
77
|
+
streamBlocks,
|
|
78
|
+
setStreamBlocks,
|
|
79
|
+
streamBlocksRef,
|
|
80
|
+
draftText,
|
|
81
|
+
setDraftText,
|
|
82
|
+
pendingFiles,
|
|
83
|
+
setPendingFiles,
|
|
84
|
+
pendingVoice,
|
|
85
|
+
setPendingVoice,
|
|
86
|
+
pendingMessages,
|
|
87
|
+
setPendingMessages,
|
|
88
|
+
issues,
|
|
89
|
+
setIssues,
|
|
90
|
+
failure,
|
|
91
|
+
setFailure,
|
|
92
|
+
answering,
|
|
93
|
+
setAnswering,
|
|
94
|
+
busy,
|
|
95
|
+
setBusy,
|
|
96
|
+
chatStarted,
|
|
97
|
+
setChatStarted,
|
|
98
|
+
streaming,
|
|
99
|
+
setStreaming,
|
|
100
|
+
session,
|
|
101
|
+
setSession,
|
|
102
|
+
delivery,
|
|
103
|
+
setDelivery,
|
|
104
|
+
deliveryRef,
|
|
105
|
+
blocksRef,
|
|
106
|
+
busyRef,
|
|
107
|
+
abortRef,
|
|
108
|
+
turnIdRef,
|
|
109
|
+
pendingRef,
|
|
110
|
+
sessionRef,
|
|
111
|
+
drainLockRef,
|
|
112
|
+
allowQueueDrainRef,
|
|
113
|
+
runPromiseRef,
|
|
114
|
+
cancelPendingStreamFrame,
|
|
115
|
+
scheduleStreamBlocks,
|
|
116
|
+
clearComposer,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type ComposerPendingMessage, type ComposerProfileInterface, type ComposerRunPhase, type InterfaceTurnSession, type TranscriptBlock } from '@theoremjs/agents/interface';
|
|
2
|
+
import type { TheoremTransport } from '../client/transport.ts';
|
|
3
|
+
import { type ChatSnapshot, type MessageDelivery } from './use-theorem-chat-state.ts';
|
|
4
|
+
export type { ChatSnapshot };
|
|
5
|
+
export type UseTheoremChatOptions = {
|
|
6
|
+
transport: TheoremTransport;
|
|
7
|
+
/** Composer interface for the host profile — see `useTheoremInterface`. `null` while loading. */
|
|
8
|
+
iface: ComposerProfileInterface | null;
|
|
9
|
+
/** A conversation to resume: its transcript, and the session the next turn continues from. */
|
|
10
|
+
initial?: ChatSnapshot;
|
|
11
|
+
/**
|
|
12
|
+
* The conversation each time it comes to rest: a turn finished, or a message was added or
|
|
13
|
+
* removed. Never while a reply streams or waits on a gate.
|
|
14
|
+
*/
|
|
15
|
+
onChange?: (snapshot: ChatSnapshot) => void;
|
|
16
|
+
};
|
|
17
|
+
/** What `sendText` hands back: the blocks the turn added, its user message and the reply. */
|
|
18
|
+
export type SentTurn = {
|
|
19
|
+
blocks: TranscriptBlock[];
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Headless chat model: transcript, streaming, composer drafts, pending
|
|
23
|
+
* queue / steer / stash, tool gates. Render it with `@theoremjs/react/ui` or
|
|
24
|
+
* your own components.
|
|
25
|
+
*/
|
|
26
|
+
export declare function useTheoremChat({ transport, iface, initial, onChange }: UseTheoremChatOptions): {
|
|
27
|
+
iface: ComposerProfileInterface | null;
|
|
28
|
+
blocks: TranscriptBlock[];
|
|
29
|
+
chatStarted: boolean;
|
|
30
|
+
draftText: string;
|
|
31
|
+
failure: import("../index.ts").ClientFailure | null;
|
|
32
|
+
/** The answer on its way to a gate, shown on it until the answer settles or fails. */
|
|
33
|
+
answering: import("../index.ts").AnsweringGate | null;
|
|
34
|
+
issues: import("@theoremjs/agents/interface").AttachmentValidationIssue[];
|
|
35
|
+
pendingFiles: File[];
|
|
36
|
+
pendingMessages: ComposerPendingMessage[];
|
|
37
|
+
pendingVoice: File[];
|
|
38
|
+
phase: ComposerRunPhase;
|
|
39
|
+
session: InterfaceTurnSession;
|
|
40
|
+
/** The latest message's delivery; `null` before the first and once its turn failed. */
|
|
41
|
+
delivery: MessageDelivery | null;
|
|
42
|
+
streamBlocks: TranscriptBlock[];
|
|
43
|
+
streaming: boolean;
|
|
44
|
+
setDraftText: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
45
|
+
setPendingFiles: import("react").Dispatch<import("react").SetStateAction<File[]>>;
|
|
46
|
+
setPendingVoice: import("react").Dispatch<import("react").SetStateAction<File[]>>;
|
|
47
|
+
setPendingMessages: import("react").Dispatch<import("react").SetStateAction<ComposerPendingMessage[]>>;
|
|
48
|
+
setIssues: import("react").Dispatch<import("react").SetStateAction<import("@theoremjs/agents/interface").AttachmentValidationIssue[]>>;
|
|
49
|
+
handleAuthenticated: (_index: number, secret?: string) => Promise<void>;
|
|
50
|
+
handleBranch: (index: number) => void;
|
|
51
|
+
handleSubmit: () => Promise<void>;
|
|
52
|
+
handleStop: () => void;
|
|
53
|
+
handleMenuAction: (action: import("@theoremjs/agents/interface").ComposerMenuAction) => void;
|
|
54
|
+
handlePendingQueue: (id: string) => void;
|
|
55
|
+
handlePendingRestore: (id: string) => Promise<void>;
|
|
56
|
+
handleSendNow: (draftSource?: ComposerPendingMessage) => Promise<void>;
|
|
57
|
+
handleToolDecision: (_index: number, action: import("../index.ts").ToolDecisionAction) => Promise<void>;
|
|
58
|
+
handleGenerationChange: (next: {
|
|
59
|
+
modelId: string;
|
|
60
|
+
effort?: string;
|
|
61
|
+
}) => void;
|
|
62
|
+
sendText: (text: string) => Promise<SentTurn | null>;
|
|
63
|
+
};
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { branchInterfaceTurnSession, consumeNextComposerQueue, convertSteersToFrontQueued, defaultInterfaceEffort, orderComposerPendingMessages, promoteComposerPendingKind, removeLandedSteers, } from '@theoremjs/agents/interface';
|
|
3
|
+
import { clientFailure } from "../client/failure.js";
|
|
4
|
+
import { followGenerationDefaults } from "../client/generation-selection.js";
|
|
5
|
+
import { applyTurnResultToTranscript } from "../client/index.js";
|
|
6
|
+
import { useTheoremChatActions } from "./use-theorem-chat-actions.js";
|
|
7
|
+
import { useTheoremChatState, } from "./use-theorem-chat-state.js";
|
|
8
|
+
/**
|
|
9
|
+
* Seed the session with the profile's default model / effort once the interface loads, and keep
|
|
10
|
+
* it valid as the interface changes: a pick still on the old defaults follows the new ones, and a
|
|
11
|
+
* pick the profile no longer has falls back to them (see `followGenerationDefaults`).
|
|
12
|
+
*/
|
|
13
|
+
function useDefaultGeneration(iface, session, setSession) {
|
|
14
|
+
const previous = useRef(undefined);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!iface)
|
|
17
|
+
return;
|
|
18
|
+
const next = followGenerationDefaults(iface, { model: session.selectedModel, effort: session.selectedEffort }, previous.current);
|
|
19
|
+
previous.current = iface;
|
|
20
|
+
if (next.model === session.selectedModel && next.effort === session.selectedEffort)
|
|
21
|
+
return;
|
|
22
|
+
setSession((prev) => ({ ...prev, selectedModel: next.model, selectedEffort: next.effort }));
|
|
23
|
+
}, [session.selectedEffort, session.selectedModel, setSession, iface]);
|
|
24
|
+
}
|
|
25
|
+
/** The streamed blocks, ending on the turn's error unless the stream already carried it. */
|
|
26
|
+
function withTurnError(blocks, error) {
|
|
27
|
+
if (blocks.some((block) => block.kind === 'error'))
|
|
28
|
+
return blocks;
|
|
29
|
+
return [...blocks, { id: crypto.randomUUID(), kind: 'error', message: error }];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Shows a failed turn's error. Once the message is in the transcript, the
|
|
33
|
+
* failure is too, closing the turn (the message, or the reply as far as it
|
|
34
|
+
* got, shows it failed); a message that never went out (refused attachments)
|
|
35
|
+
* is still in the composer, and a reply that still waits on a gate stays open,
|
|
36
|
+
* so the failure shows in the composer.
|
|
37
|
+
*/
|
|
38
|
+
function showTurnFailure(state, result, streamed, open) {
|
|
39
|
+
const { error, errorKind, errorInternal } = result;
|
|
40
|
+
if (open) {
|
|
41
|
+
state.setFailure({ error, errorKind, ...(errorInternal ? { errorInternal } : {}) });
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
const failed = withTurnError(streamed, error);
|
|
45
|
+
state.setBlocks((prev) => [...prev, ...failed]);
|
|
46
|
+
state.setDelivery(null);
|
|
47
|
+
}
|
|
48
|
+
if (result.issues)
|
|
49
|
+
state.setIssues(result.issues);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The run begins: busy, and streaming unless its message goes live when it
|
|
53
|
+
* posts (onUserBlocks), so the previous reply never renders as streaming in
|
|
54
|
+
* between. Returns the delivery before it, which a new message replaces.
|
|
55
|
+
*/
|
|
56
|
+
function beginRun(state, userBlocksAlreadyApplied) {
|
|
57
|
+
state.setFailure(null);
|
|
58
|
+
const priorDelivery = state.deliveryRef.current;
|
|
59
|
+
state.busyRef.current = true;
|
|
60
|
+
state.setBusy(true);
|
|
61
|
+
if (!userBlocksAlreadyApplied)
|
|
62
|
+
state.setStreaming(true);
|
|
63
|
+
state.allowQueueDrainRef.current = false;
|
|
64
|
+
return priorDelivery;
|
|
65
|
+
}
|
|
66
|
+
/** The run is over: nothing streams, and nothing can stop or steer it. */
|
|
67
|
+
function endRun(state) {
|
|
68
|
+
state.cancelPendingStreamFrame();
|
|
69
|
+
state.busyRef.current = false;
|
|
70
|
+
state.setBusy(false);
|
|
71
|
+
state.setStreaming(false);
|
|
72
|
+
state.abortRef.current = null;
|
|
73
|
+
state.turnIdRef.current = null;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* A failed run: its session (when it has one) and its error. A message that
|
|
77
|
+
* never posted, or an answer that left its reply paused (as it was, or on its
|
|
78
|
+
* next gate), keeps the transcript as it was, the paused reply waiting in it.
|
|
79
|
+
*/
|
|
80
|
+
function failRun(state, result, stream, unposted) {
|
|
81
|
+
const waits = (result.session ?? state.sessionRef.current).gatedTool !== null;
|
|
82
|
+
const open = unposted || waits;
|
|
83
|
+
if (result.session)
|
|
84
|
+
state.setSession(result.session);
|
|
85
|
+
if (!result.aborted)
|
|
86
|
+
showTurnFailure(state, result, stream.streamed, open);
|
|
87
|
+
if (!open)
|
|
88
|
+
state.setStreamBlocks([]);
|
|
89
|
+
else
|
|
90
|
+
state.setStreamBlocks(result.session ? stream.streamed : stream.before);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Runs one turn's stream into the transcript: live partials while it streams,
|
|
94
|
+
* then the committed result (or the error) and the pending queue's next step.
|
|
95
|
+
*/
|
|
96
|
+
function useRunTurnStream(iface, state) {
|
|
97
|
+
// The current reply's work across its runs: a gate splits a reply into runs, and the wait between doesn't count.
|
|
98
|
+
const replyWorkedMs = useRef(0);
|
|
99
|
+
const onRunEnded = useCallback((nextPending, drain) => {
|
|
100
|
+
const converted = convertSteersToFrontQueued(nextPending);
|
|
101
|
+
state.setPendingMessages(orderComposerPendingMessages(converted));
|
|
102
|
+
state.allowQueueDrainRef.current = drain;
|
|
103
|
+
return converted;
|
|
104
|
+
}, [state]);
|
|
105
|
+
return useCallback(async (run, options = {}) => {
|
|
106
|
+
if (!iface || state.busyRef.current)
|
|
107
|
+
return;
|
|
108
|
+
const applied = options.userBlocksAlreadyApplied === true;
|
|
109
|
+
const priorDelivery = beginRun(state, applied);
|
|
110
|
+
const streamBefore = state.streamBlocksRef.current;
|
|
111
|
+
const paused = { workedMs: state.sessionRef.current.gatedTool ? replyWorkedMs.current : 0 };
|
|
112
|
+
// A run that answers a gate (a decision, sign-in) continues its reply; any other, walking away included, starts one.
|
|
113
|
+
if (!state.sessionRef.current.gatedTool || options.walksAway)
|
|
114
|
+
replyWorkedMs.current = 0;
|
|
115
|
+
const runStartedAt = Date.now();
|
|
116
|
+
const work = (async () => {
|
|
117
|
+
let latestStream = [];
|
|
118
|
+
const view = {
|
|
119
|
+
blocks: (partial) => {
|
|
120
|
+
latestStream = partial;
|
|
121
|
+
state.scheduleStreamBlocks(partial);
|
|
122
|
+
},
|
|
123
|
+
// The reply goes on; the composer names what it left out.
|
|
124
|
+
skipped: (error) => state.setFailure(clientFailure(error, iface.lexicon)),
|
|
125
|
+
};
|
|
126
|
+
const result = await run(view, paused);
|
|
127
|
+
const endedAt = Date.now();
|
|
128
|
+
replyWorkedMs.current += endedAt - runStartedAt;
|
|
129
|
+
endRun(state);
|
|
130
|
+
if (!result.ok) {
|
|
131
|
+
// A new message posts itself (onUserBlocks) with a fresh delivery.
|
|
132
|
+
const unposted = applied && state.deliveryRef.current === priorDelivery;
|
|
133
|
+
failRun(state, result, { streamed: latestStream, before: streamBefore }, unposted);
|
|
134
|
+
onRunEnded(state.pendingRef.current, false);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const merged = applyTurnResultToTranscript({
|
|
138
|
+
blocks: state.blocksRef.current,
|
|
139
|
+
streamBlocks: latestStream,
|
|
140
|
+
session: result.session,
|
|
141
|
+
userBlocks: applied ? undefined : result.userBlocks,
|
|
142
|
+
assistantBlocks: result.assistantBlocks,
|
|
143
|
+
worked: { workedMs: replyWorkedMs.current, endedAt },
|
|
144
|
+
});
|
|
145
|
+
state.setBlocks(merged.blocks);
|
|
146
|
+
state.setStreamBlocks(merged.streamBlocks);
|
|
147
|
+
state.setSession(merged.session);
|
|
148
|
+
if (merged.session.gatedTool !== null)
|
|
149
|
+
return;
|
|
150
|
+
onRunEnded(state.pendingRef.current, true);
|
|
151
|
+
})();
|
|
152
|
+
state.runPromiseRef.current = work;
|
|
153
|
+
try {
|
|
154
|
+
await work;
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
if (state.runPromiseRef.current === work)
|
|
158
|
+
state.runPromiseRef.current = null;
|
|
159
|
+
}
|
|
160
|
+
}, [iface, onRunEnded, state]);
|
|
161
|
+
}
|
|
162
|
+
/** Start the next queued message once the run goes idle, when the run that ended allows it. */
|
|
163
|
+
function useQueueDrain(phase, state, startTurnFromDraft) {
|
|
164
|
+
const drainQueue = useCallback(async () => {
|
|
165
|
+
if (state.drainLockRef.current ||
|
|
166
|
+
state.busyRef.current ||
|
|
167
|
+
state.sessionRef.current.gatedTool) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const { message, remaining } = consumeNextComposerQueue(state.pendingRef.current);
|
|
171
|
+
if (!message)
|
|
172
|
+
return;
|
|
173
|
+
state.drainLockRef.current = true;
|
|
174
|
+
state.setPendingMessages(remaining);
|
|
175
|
+
try {
|
|
176
|
+
await startTurnFromDraft(message.draft);
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
state.drainLockRef.current = false;
|
|
180
|
+
}
|
|
181
|
+
}, [startTurnFromDraft, state]);
|
|
182
|
+
useEffect(() => {
|
|
183
|
+
if (phase !== 'idle' || !state.allowQueueDrainRef.current)
|
|
184
|
+
return;
|
|
185
|
+
if (!state.pendingMessages.some((m) => m.kind === 'queue')) {
|
|
186
|
+
state.allowQueueDrainRef.current = false;
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
state.allowQueueDrainRef.current = false;
|
|
190
|
+
void drainQueue();
|
|
191
|
+
}, [drainQueue, phase, state]);
|
|
192
|
+
}
|
|
193
|
+
const DELIVERY_ORDER = ['sending', 'sent', 'delivered', 'read'];
|
|
194
|
+
/** The server's own reports (bookkeeping, a failure, the end), which say nothing of the model having the message. */
|
|
195
|
+
const SERVER_EVENTS = new Set(['stage', 'guardrail', 'session', 'error', 'done']);
|
|
196
|
+
/** The reply itself (its thinking and tool calls come before it). */
|
|
197
|
+
const REPLY_EVENTS = new Set(['text', 'structured', 'media']);
|
|
198
|
+
/**
|
|
199
|
+
* How far an event shows the message got: any event means the server took it,
|
|
200
|
+
* one from the model (thinking, a tool call) means the model has it, and the
|
|
201
|
+
* reply means it was read.
|
|
202
|
+
*/
|
|
203
|
+
function deliveryOf(event) {
|
|
204
|
+
if (REPLY_EVENTS.has(event.type))
|
|
205
|
+
return 'read';
|
|
206
|
+
return SERVER_EVENTS.has(event.type) ? 'sent' : 'delivered';
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* The transport, with each turn's events also clearing the steers they report
|
|
210
|
+
* as landed (so the run's end requeues only the steers the agent never saw),
|
|
211
|
+
* and moving the posted message's delivery forward.
|
|
212
|
+
*/
|
|
213
|
+
function useTappedTransport(transport, state) {
|
|
214
|
+
return useMemo(() => {
|
|
215
|
+
const tap = (onEvent) => (event) => {
|
|
216
|
+
if (event.type === 'stage' && event.injected?.length) {
|
|
217
|
+
state.pendingRef.current = removeLandedSteers(state.pendingRef.current, event);
|
|
218
|
+
state.setPendingMessages((prev) => removeLandedSteers(prev, event));
|
|
219
|
+
}
|
|
220
|
+
const delivery = state.deliveryRef.current;
|
|
221
|
+
const reached = deliveryOf(event);
|
|
222
|
+
if (delivery && DELIVERY_ORDER.indexOf(reached) > DELIVERY_ORDER.indexOf(delivery.status)) {
|
|
223
|
+
state.setDelivery({ status: reached });
|
|
224
|
+
}
|
|
225
|
+
onEvent(event);
|
|
226
|
+
};
|
|
227
|
+
return {
|
|
228
|
+
...transport,
|
|
229
|
+
turn: (request, onEvent, signal) => transport.turn(request, tap(onEvent), signal),
|
|
230
|
+
invoke: (request, onEvent, signal) => transport.invoke(request, tap(onEvent), signal),
|
|
231
|
+
};
|
|
232
|
+
}, [transport, state.pendingRef, state.setPendingMessages, state.deliveryRef, state.setDelivery]);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Headless chat model: transcript, streaming, composer drafts, pending
|
|
236
|
+
* queue / steer / stash, tool gates. Render it with `@theoremjs/react/ui` or
|
|
237
|
+
* your own components.
|
|
238
|
+
*/
|
|
239
|
+
export function useTheoremChat({ transport, iface, initial, onChange }) {
|
|
240
|
+
const state = useTheoremChatState(initial);
|
|
241
|
+
useDefaultGeneration(iface, state.session, state.setSession);
|
|
242
|
+
const gated = state.session.gatedTool !== null;
|
|
243
|
+
const phase = state.busy ? 'streaming' : gated ? 'gated' : 'idle';
|
|
244
|
+
const handleGenerationChange = useCallback((next) => {
|
|
245
|
+
const effort = next.effort ?? (iface ? defaultInterfaceEffort(iface, next.modelId) : undefined);
|
|
246
|
+
state.setSession((prev) => ({
|
|
247
|
+
...prev,
|
|
248
|
+
selectedModel: next.modelId,
|
|
249
|
+
...(effort ? { selectedEffort: effort } : { selectedEffort: undefined }),
|
|
250
|
+
}));
|
|
251
|
+
}, [iface, state]);
|
|
252
|
+
const runTurnStream = useRunTurnStream(iface, state);
|
|
253
|
+
const steerTransport = useTappedTransport(transport, state);
|
|
254
|
+
const actions = useTheoremChatActions({
|
|
255
|
+
...state,
|
|
256
|
+
iface,
|
|
257
|
+
transport: steerTransport,
|
|
258
|
+
phase,
|
|
259
|
+
gated,
|
|
260
|
+
runTurnStream,
|
|
261
|
+
});
|
|
262
|
+
useQueueDrain(phase, state, actions.startTurnFromDraft);
|
|
263
|
+
const onChangeRef = useRef(onChange);
|
|
264
|
+
onChangeRef.current = onChange;
|
|
265
|
+
const restedOn = useRef(initial);
|
|
266
|
+
useEffect(() => {
|
|
267
|
+
if (state.busy || gated)
|
|
268
|
+
return;
|
|
269
|
+
const last = restedOn.current;
|
|
270
|
+
if (last && last.blocks === state.blocks && last.session === state.session)
|
|
271
|
+
return;
|
|
272
|
+
const snapshot = { blocks: state.blocks, session: state.session };
|
|
273
|
+
restedOn.current = snapshot;
|
|
274
|
+
onChangeRef.current?.(snapshot);
|
|
275
|
+
}, [state.busy, gated, state.blocks, state.session]);
|
|
276
|
+
/** Sends `text` as the composer would, and resolves once the reply is done; `null` when it can't go now. */
|
|
277
|
+
const sendText = useCallback(async (text) => {
|
|
278
|
+
if (!iface || state.busyRef.current || state.sessionRef.current.gatedTool !== null)
|
|
279
|
+
return null;
|
|
280
|
+
const before = state.blocksRef.current.length;
|
|
281
|
+
await actions.startTurnFromFields({ text, files: [], voice: [] });
|
|
282
|
+
return { blocks: state.blocksRef.current.slice(before) };
|
|
283
|
+
}, [actions, iface, state]);
|
|
284
|
+
const handleBranch = useCallback((index) => {
|
|
285
|
+
const kept = [...state.blocks, ...state.streamBlocks].slice(0, index + 1);
|
|
286
|
+
state.setBlocks(kept);
|
|
287
|
+
state.setStreamBlocks([]);
|
|
288
|
+
state.setStreaming(false);
|
|
289
|
+
state.busyRef.current = false;
|
|
290
|
+
state.setBusy(false);
|
|
291
|
+
state.setChatStarted(kept.length > 0);
|
|
292
|
+
// The kept transcript's last message isn't the one whose delivery was tracked.
|
|
293
|
+
state.setDelivery(null);
|
|
294
|
+
state.setSession((prevSession) => branchInterfaceTurnSession(prevSession, kept));
|
|
295
|
+
}, [iface, state]);
|
|
296
|
+
const handlePendingQueue = useCallback((id) => {
|
|
297
|
+
const message = state.pendingRef.current.find((m) => m.id === id);
|
|
298
|
+
if (!message || message.kind !== 'stash')
|
|
299
|
+
return;
|
|
300
|
+
if (phase === 'idle') {
|
|
301
|
+
state.allowQueueDrainRef.current = true;
|
|
302
|
+
}
|
|
303
|
+
state.setPendingMessages((prev) => promoteComposerPendingKind(prev, id, 'queue'));
|
|
304
|
+
}, [phase, state]);
|
|
305
|
+
return {
|
|
306
|
+
iface,
|
|
307
|
+
blocks: state.blocks,
|
|
308
|
+
chatStarted: state.chatStarted,
|
|
309
|
+
draftText: state.draftText,
|
|
310
|
+
failure: state.failure,
|
|
311
|
+
/** The answer on its way to a gate, shown on it until the answer settles or fails. */
|
|
312
|
+
answering: state.answering,
|
|
313
|
+
issues: state.issues,
|
|
314
|
+
pendingFiles: state.pendingFiles,
|
|
315
|
+
pendingMessages: state.pendingMessages,
|
|
316
|
+
pendingVoice: state.pendingVoice,
|
|
317
|
+
phase,
|
|
318
|
+
session: state.session,
|
|
319
|
+
/** The latest message's delivery; `null` before the first and once its turn failed. */
|
|
320
|
+
delivery: state.delivery?.status ?? null,
|
|
321
|
+
streamBlocks: state.streamBlocks,
|
|
322
|
+
streaming: state.streaming,
|
|
323
|
+
setDraftText: state.setDraftText,
|
|
324
|
+
setPendingFiles: state.setPendingFiles,
|
|
325
|
+
setPendingVoice: state.setPendingVoice,
|
|
326
|
+
setPendingMessages: state.setPendingMessages,
|
|
327
|
+
setIssues: state.setIssues,
|
|
328
|
+
handleAuthenticated: actions.handleAuthenticated,
|
|
329
|
+
handleBranch,
|
|
330
|
+
handleSubmit: actions.handleSubmit,
|
|
331
|
+
handleStop: actions.handleStop,
|
|
332
|
+
handleMenuAction: actions.handleMenuAction,
|
|
333
|
+
handlePendingQueue,
|
|
334
|
+
handlePendingRestore: actions.handlePendingRestore,
|
|
335
|
+
handleSendNow: actions.handleSendNow,
|
|
336
|
+
handleToolDecision: actions.handleToolDecision,
|
|
337
|
+
handleGenerationChange,
|
|
338
|
+
sendText,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DecisionJson, DecisionResult } from '@theoremjs/agents';
|
|
2
|
+
import type { DecisionInterface, DecisionTransport } from '../client/decision-transport.ts';
|
|
3
|
+
import { type ClientFailure } from '../client/failure.ts';
|
|
4
|
+
export type TheoremDecisionState = {
|
|
5
|
+
/** The profile as the page sees it; null until the host describes it. */
|
|
6
|
+
iface: DecisionInterface | null;
|
|
7
|
+
/** Why the host could not describe the profile. */
|
|
8
|
+
describeFailure: ClientFailure | null;
|
|
9
|
+
status: 'idle' | 'deciding' | 'done' | 'error';
|
|
10
|
+
/** The last answers; kept while the next decision runs and when it fails. */
|
|
11
|
+
result: DecisionResult | null;
|
|
12
|
+
/** How long the last decision took, round trip, in milliseconds. */
|
|
13
|
+
elapsedMs: number | null;
|
|
14
|
+
failure: ClientFailure | null;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* One decision profile: describe it once, then `decide(state)` as often as
|
|
18
|
+
* needed. A new decision aborts the one still running.
|
|
19
|
+
*/
|
|
20
|
+
export declare function useTheoremDecision(transport: DecisionTransport): TheoremDecisionState & {
|
|
21
|
+
decide: (state: Exclude<DecisionJson, null>) => Promise<void>;
|
|
22
|
+
cancel: () => void;
|
|
23
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { clientFailure } from "../client/failure.js";
|
|
3
|
+
import { useDescribed } from "./use-described.js";
|
|
4
|
+
/**
|
|
5
|
+
* One decision profile: describe it once, then `decide(state)` as often as
|
|
6
|
+
* needed. A new decision aborts the one still running.
|
|
7
|
+
*/
|
|
8
|
+
export function useTheoremDecision(transport) {
|
|
9
|
+
const { iface, describeFailure } = useDescribed(transport);
|
|
10
|
+
const [run, setRun] = useState({
|
|
11
|
+
transport,
|
|
12
|
+
status: 'idle',
|
|
13
|
+
result: null,
|
|
14
|
+
elapsedMs: null,
|
|
15
|
+
failure: null,
|
|
16
|
+
});
|
|
17
|
+
const running = useRef(null);
|
|
18
|
+
useEffect(() => () => running.current?.abort(), [transport]);
|
|
19
|
+
const cancel = useCallback(() => {
|
|
20
|
+
running.current?.abort();
|
|
21
|
+
running.current = null;
|
|
22
|
+
setRun((previous) => previous.status === 'deciding'
|
|
23
|
+
? { ...previous, status: previous.result ? 'done' : 'idle' }
|
|
24
|
+
: previous);
|
|
25
|
+
}, []);
|
|
26
|
+
const decide = useCallback(async (state) => {
|
|
27
|
+
running.current?.abort();
|
|
28
|
+
const controller = new AbortController();
|
|
29
|
+
running.current = controller;
|
|
30
|
+
setRun((previous) => ({
|
|
31
|
+
transport,
|
|
32
|
+
status: 'deciding',
|
|
33
|
+
result: previous.transport === transport ? previous.result : null,
|
|
34
|
+
elapsedMs: previous.transport === transport ? previous.elapsedMs : null,
|
|
35
|
+
failure: null,
|
|
36
|
+
}));
|
|
37
|
+
const started = performance.now();
|
|
38
|
+
try {
|
|
39
|
+
const reply = await transport.decide(state, controller.signal);
|
|
40
|
+
if (controller.signal.aborted)
|
|
41
|
+
return;
|
|
42
|
+
for (const record of reply.traces ?? [])
|
|
43
|
+
transport.traces?.push(record);
|
|
44
|
+
setRun({
|
|
45
|
+
transport,
|
|
46
|
+
status: 'done',
|
|
47
|
+
result: reply.result,
|
|
48
|
+
elapsedMs: performance.now() - started,
|
|
49
|
+
failure: null,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
if (controller.signal.aborted)
|
|
54
|
+
return;
|
|
55
|
+
setRun((previous) => ({ ...previous, status: 'error', failure: clientFailure(err) }));
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
if (running.current === controller)
|
|
59
|
+
running.current = null;
|
|
60
|
+
}
|
|
61
|
+
}, [transport]);
|
|
62
|
+
const visible = run.transport === transport
|
|
63
|
+
? run
|
|
64
|
+
: { status: 'idle', result: null, elapsedMs: null, failure: null };
|
|
65
|
+
return {
|
|
66
|
+
iface,
|
|
67
|
+
describeFailure,
|
|
68
|
+
status: visible.status,
|
|
69
|
+
result: visible.result,
|
|
70
|
+
elapsedMs: visible.elapsedMs,
|
|
71
|
+
failure: visible.failure,
|
|
72
|
+
decide,
|
|
73
|
+
cancel,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { TurnEvent } from '@theoremjs/agents';
|
|
2
|
+
import { type ClientFailure } from '../client/failure.ts';
|
|
3
|
+
import type { ToolCall } from '../client/host-call.ts';
|
|
4
|
+
import type { HostInterface, HostTransport } from '../client/host-transport.ts';
|
|
5
|
+
import { type ToolGateResolution } from '../client/tool-resume.ts';
|
|
6
|
+
export type { ToolCall };
|
|
7
|
+
/** One call of one of the host's tools, as it streams. */
|
|
8
|
+
export type TheoremHostCall = {
|
|
9
|
+
/** The page's id for the call, before the host names it. */
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
input: unknown;
|
|
13
|
+
/** Everything the host streamed for the call, answers included. */
|
|
14
|
+
events: TurnEvent[];
|
|
15
|
+
/** The call as its events fold: its state is the phase it is in. */
|
|
16
|
+
call: ToolCall | null;
|
|
17
|
+
/** The tool's latest progress report while it runs. */
|
|
18
|
+
progress: unknown;
|
|
19
|
+
isRunning: boolean;
|
|
20
|
+
/** The call never reached its tool, or its stream broke. */
|
|
21
|
+
failure: ClientFailure | null;
|
|
22
|
+
/** The answer on its way to the gate the call paused on. */
|
|
23
|
+
answering: ToolGateResolution['action'] | null;
|
|
24
|
+
startedAt: number;
|
|
25
|
+
/** Round trip, once it settles or pauses. */
|
|
26
|
+
elapsedMs: number | null;
|
|
27
|
+
};
|
|
28
|
+
export type TheoremHostState = {
|
|
29
|
+
/** The host as the page sees it; null until it describes itself. */
|
|
30
|
+
iface: HostInterface | null;
|
|
31
|
+
describeFailure: ClientFailure | null;
|
|
32
|
+
/** This page's calls, newest first. */
|
|
33
|
+
calls: TheoremHostCall[];
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* One host profile: describe it once, then `run(name, input)` any of its tools
|
|
37
|
+
* as often as needed, and answer the gates a call pauses on. `run` returns the
|
|
38
|
+
* call's id at once; calls run side by side, and `cancel` stops one.
|
|
39
|
+
*/
|
|
40
|
+
export declare function useTheoremHost(transport: HostTransport): TheoremHostState & {
|
|
41
|
+
run: (name: string, input: unknown) => string;
|
|
42
|
+
answer: (id: string, resolution: ToolGateResolution) => Promise<void>;
|
|
43
|
+
cancel: (id: string) => void;
|
|
44
|
+
};
|