@theoremjs/react 0.0.0-stage → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +772 -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-images.d.ts +76 -0
- package/client/decision-images.js +139 -0
- package/client/decision-transport.d.ts +70 -0
- package/client/decision-transport.js +175 -0
- package/client/decode-composer-draft.d.ts +11 -0
- package/client/decode-composer-draft.js +12 -0
- package/client/embedding-input.d.ts +84 -0
- package/client/embedding-input.js +194 -0
- package/client/embedding-space.d.ts +82 -0
- package/client/embedding-space.js +389 -0
- package/client/embedding-transport.d.ts +40 -0
- package/client/embedding-transport.js +71 -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 +29 -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 +47 -0
- package/client/host-transport.d.ts +70 -0
- package/client/host-transport.js +75 -0
- package/client/image-output.d.ts +6 -0
- package/client/image-output.js +13 -0
- package/client/index.d.ts +28 -0
- package/client/index.js +22 -0
- package/client/ink-waveform.d.ts +17 -0
- package/client/ink-waveform.js +68 -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 +53 -0
- package/client/live/live-captions.js +174 -0
- package/client/live/live-mic-forward.d.ts +30 -0
- package/client/live/live-mic-forward.js +46 -0
- package/client/live/live-page-tool.d.ts +34 -0
- package/client/live/live-page-tool.js +11 -0
- package/client/live/live-state.d.ts +13 -0
- package/client/live/live-state.js +22 -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 +22 -0
- package/client/live/run-live-tool-call.js +70 -0
- package/client/live/use-live-caption-log.d.ts +17 -0
- package/client/live/use-live-caption-log.js +51 -0
- package/client/live-client.d.ts +189 -0
- package/client/live-client.js +829 -0
- package/client/live-errors.d.ts +2 -0
- package/client/live-errors.js +9 -0
- package/client/live-messages.d.ts +148 -0
- package/client/live-messages.js +100 -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/page-dispatch.d.ts +15 -0
- package/client/page-dispatch.js +63 -0
- package/client/pcm-downsample.d.ts +4 -0
- package/client/pcm-downsample.js +45 -0
- package/client/run-commit.d.ts +34 -0
- package/client/run-commit.js +49 -0
- package/client/run-session.d.ts +79 -0
- package/client/run-session.js +207 -0
- package/client/schema-fields.d.ts +63 -0
- package/client/schema-fields.js +199 -0
- package/client/shaped-data.d.ts +118 -0
- package/client/shaped-data.js +343 -0
- package/client/source-citations.d.ts +13 -0
- package/client/source-citations.js +56 -0
- package/client/space-geometry.d.ts +39 -0
- package/client/space-geometry.js +183 -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 +293 -0
- package/client/tool-resume.d.ts +50 -0
- package/client/tool-resume.js +14 -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 +440 -0
- package/client/trace-view.d.ts +130 -0
- package/client/trace-view.js +333 -0
- package/client/transcript-groups.d.ts +140 -0
- package/client/transcript-groups.js +217 -0
- package/client/transcript-schema.d.ts +10 -0
- package/client/transcript-schema.js +120 -0
- package/client/transcript-scroll.d.ts +7 -0
- package/client/transcript-scroll.js +6 -0
- package/client/transport.d.ts +264 -0
- package/client/transport.js +330 -0
- package/client/turn-client.d.ts +44 -0
- package/client/turn-client.js +88 -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 +162 -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 +21 -0
- package/components/InkWaveform.js +131 -0
- package/components/live/use-live-runner-controls.d.ts +47 -0
- package/components/live/use-live-runner-controls.js +96 -0
- package/components/live/use-live-runner-model.d.ts +49 -0
- package/components/live/use-live-runner-model.js +190 -0
- package/components/live/use-live-runner-ui.d.ts +67 -0
- package/components/live/use-live-runner-ui.js +167 -0
- package/components/live/use-live-session-client.d.ts +41 -0
- package/components/live/use-live-session-client.js +126 -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 +19 -0
- package/components/use-voice-playback.d.ts +17 -0
- package/components/use-voice-playback.js +68 -0
- package/hooks/use-described.d.ts +12 -0
- package/hooks/use-described.js +25 -0
- package/hooks/use-embedding-index.d.ts +27 -0
- package/hooks/use-embedding-index.js +43 -0
- package/hooks/use-one-call.d.ts +31 -0
- package/hooks/use-one-call.js +76 -0
- package/hooks/use-page-tool-warnings.d.ts +6 -0
- package/hooks/use-page-tool-warnings.js +25 -0
- package/hooks/use-second-ticker.d.ts +2 -0
- package/hooks/use-second-ticker.js +13 -0
- package/hooks/use-theorem-chat-actions.d.ts +79 -0
- package/hooks/use-theorem-chat-actions.js +287 -0
- package/hooks/use-theorem-chat-state.d.ts +66 -0
- package/hooks/use-theorem-chat-state.js +122 -0
- package/hooks/use-theorem-chat.d.ts +97 -0
- package/hooks/use-theorem-chat.js +528 -0
- package/hooks/use-theorem-decision.d.ts +18 -0
- package/hooks/use-theorem-decision.js +12 -0
- package/hooks/use-theorem-embedding.d.ts +18 -0
- package/hooks/use-theorem-embedding.js +12 -0
- package/hooks/use-theorem-host.d.ts +45 -0
- package/hooks/use-theorem-host.js +135 -0
- package/hooks/use-theorem-interface.d.ts +22 -0
- package/hooks/use-theorem-interface.js +15 -0
- package/hooks/use-trace-records.d.ts +4 -0
- package/hooks/use-trace-records.js +7 -0
- package/index.d.ts +17 -0
- package/index.js +17 -0
- package/live.d.ts +9 -0
- package/live.js +10 -0
- package/package.json +74 -4
- package/server/checkpoint-ownership.d.ts +6 -0
- package/server/checkpoint-ownership.js +42 -0
- package/server/conversation-store.d.ts +92 -0
- package/server/conversation-store.js +131 -0
- package/server/conversation.d.ts +193 -0
- package/server/conversation.js +552 -0
- package/server/credential-store.d.ts +24 -0
- package/server/credential-store.js +14 -0
- package/server/decision-handler.d.ts +42 -0
- package/server/decision-handler.js +46 -0
- package/server/embedding-handler.d.ts +33 -0
- package/server/embedding-handler.js +45 -0
- package/server/handler.d.ts +152 -0
- package/server/handler.js +771 -0
- package/server/json-response.d.ts +2 -0
- package/server/json-response.js +7 -0
- package/server/mod.d.ts +25 -0
- package/server/mod.js +23 -0
- package/server/page-dispatch.d.ts +22 -0
- package/server/page-dispatch.js +127 -0
- package/server/request-body.d.ts +2 -0
- package/server/request-body.js +42 -0
- package/server/request-check.d.ts +21 -0
- package/server/request-check.js +49 -0
- package/server/session-authority.d.ts +8 -0
- package/server/session-authority.js +78 -0
- package/server/session-boundary.d.ts +26 -0
- package/server/session-boundary.js +64 -0
- package/server/session-identity.d.ts +16 -0
- package/server/session-identity.js +41 -0
- package/server/session-limits.d.ts +12 -0
- package/server/session-limits.js +59 -0
- package/server/session-store.d.ts +123 -0
- package/server/session-store.js +264 -0
- package/server/single-action.d.ts +21 -0
- package/server/single-action.js +30 -0
- package/server/steer-inbox.d.ts +35 -0
- package/server/steer-inbox.js +52 -0
- package/server/tool-operations.d.ts +6 -0
- package/server/tool-operations.js +132 -0
- package/server/turn-input.d.ts +10 -0
- package/server/turn-input.js +29 -0
- package/styles/ink-controls.css +134 -0
- package/ui/CardWall.d.ts +10 -0
- package/ui/CardWall.js +61 -0
- package/ui/ChatComposerBar.d.ts +62 -0
- package/ui/ChatComposerBar.js +306 -0
- package/ui/ChatTranscript.d.ts +43 -0
- package/ui/ChatTranscript.js +527 -0
- package/ui/ConsoleFrame.d.ts +104 -0
- package/ui/ConsoleFrame.js +146 -0
- package/ui/DecisionAnswers.d.ts +53 -0
- package/ui/DecisionAnswers.js +233 -0
- package/ui/DecisionImages.d.ts +16 -0
- package/ui/DecisionImages.js +46 -0
- package/ui/EmbeddingDocuments.d.ts +78 -0
- package/ui/EmbeddingDocuments.js +278 -0
- package/ui/EmbeddingQuery.d.ts +27 -0
- package/ui/EmbeddingQuery.js +95 -0
- package/ui/EmbeddingResults.d.ts +41 -0
- package/ui/EmbeddingResults.js +165 -0
- package/ui/EmbeddingSpace.d.ts +26 -0
- package/ui/EmbeddingSpace.js +106 -0
- package/ui/LiveCaptionsPanel.d.ts +22 -0
- package/ui/LiveCaptionsPanel.js +27 -0
- package/ui/LiveRunner.d.ts +30 -0
- package/ui/LiveRunner.js +180 -0
- package/ui/PaneState.d.ts +33 -0
- package/ui/PaneState.js +50 -0
- package/ui/SchemaFields.d.ts +10 -0
- package/ui/SchemaFields.js +150 -0
- package/ui/ShapedData.d.ts +11 -0
- package/ui/ShapedData.js +196 -0
- package/ui/SidePanel.d.ts +126 -0
- package/ui/SidePanel.js +123 -0
- package/ui/StateFields.d.ts +44 -0
- package/ui/StateFields.js +139 -0
- package/ui/TheoremChat.d.ts +122 -0
- package/ui/TheoremChat.js +134 -0
- package/ui/TheoremDecision.d.ts +30 -0
- package/ui/TheoremDecision.js +130 -0
- package/ui/TheoremEmbedding.d.ts +27 -0
- package/ui/TheoremEmbedding.js +122 -0
- package/ui/TheoremHost.d.ts +55 -0
- package/ui/TheoremHost.js +240 -0
- package/ui/ToolGateCard.d.ts +32 -0
- package/ui/ToolGateCard.js +107 -0
- package/ui/ToolResult.d.ts +10 -0
- package/ui/ToolResult.js +62 -0
- package/ui/ToolResultCharts.d.ts +5 -0
- package/ui/ToolResultCharts.js +103 -0
- package/ui/TraceCharts.d.ts +13 -0
- package/ui/TraceCharts.js +266 -0
- package/ui/TraceGuardrails.d.ts +11 -0
- package/ui/TraceGuardrails.js +74 -0
- package/ui/TraceInspector.d.ts +16 -0
- package/ui/TraceInspector.js +206 -0
- package/ui/TraceInspectorPanel.d.ts +46 -0
- package/ui/TraceInspectorPanel.js +95 -0
- package/ui/TraceOverview.d.ts +38 -0
- package/ui/TraceOverview.js +126 -0
- package/ui/TraceSpanDetail.d.ts +10 -0
- package/ui/TraceSpanDetail.js +190 -0
- package/ui/TraceStory.d.ts +20 -0
- package/ui/TraceStory.js +108 -0
- package/ui/TraceValues.d.ts +36 -0
- package/ui/TraceValues.js +123 -0
- package/ui/TraceWaterfall.d.ts +13 -0
- package/ui/TraceWaterfall.js +135 -0
- package/ui/VoiceNote.d.ts +12 -0
- package/ui/VoiceNote.js +28 -0
- package/ui/approval-heading.d.ts +4 -0
- package/ui/approval-heading.js +9 -0
- package/ui/arrive.d.ts +18 -0
- package/ui/arrive.js +46 -0
- package/ui/built/theme.css +792 -0
- package/ui/built/theme.css.d.ts +9 -0
- package/ui/built/theorem.d.ts +13 -0
- package/ui/built/theorem.js +660 -0
- package/ui/built/theorem.variants.d.ts +22 -0
- package/ui/code-language.d.ts +12 -0
- package/ui/code-language.js +50 -0
- package/ui/disclosure-motion.d.ts +6 -0
- package/ui/disclosure-motion.js +207 -0
- package/ui/embedding-space-gl.d.ts +37 -0
- package/ui/embedding-space-gl.js +663 -0
- package/ui/embedding-space-view.d.ts +8 -0
- package/ui/embedding-space-view.js +8 -0
- package/ui/glide.d.ts +7 -0
- package/ui/glide.js +44 -0
- package/ui/icons.d.ts +4 -0
- package/ui/icons.js +38 -0
- package/ui/index.d.ts +36 -0
- package/ui/index.js +39 -0
- package/ui/labels-provider.d.ts +16 -0
- package/ui/labels-provider.js +64 -0
- package/ui/labels.d.ts +1442 -0
- package/ui/labels.js +1443 -0
- package/ui/markdown-plugins.d.ts +9 -0
- package/ui/markdown-plugins.js +13 -0
- package/ui/mcp-icon.d.ts +6 -0
- package/ui/mcp-icon.js +8 -0
- package/ui/row-keys.d.ts +19 -0
- package/ui/row-keys.js +33 -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 +105 -0
- package/ui/trace-palette.d.ts +10 -0
- package/ui/trace-palette.js +10 -0
- package/ui/trace-patterns.d.ts +40 -0
- package/ui/trace-patterns.js +57 -0
- package/ui/transcript-copy-text.d.ts +4 -0
- package/ui/transcript-copy-text.js +40 -0
- package/ui/wall-columns.d.ts +8 -0
- package/ui/wall-columns.js +19 -0
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
import { branchInterfaceTurnSession, consumeNextComposerQueue, convertSteersToFrontQueued, defaultInterfaceEffort, orderComposerPendingMessages, promoteComposerPendingKind, removeComposerPendingMessage, removeLandedSteers, } from '@theoremjs/agents/interface';
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { clientFailure, turnFailure } from "../client/failure.js";
|
|
4
|
+
import { followGenerationDefaults } from "../client/generation-selection.js";
|
|
5
|
+
import { withPageToolDispatch } from "../client/page-dispatch.js";
|
|
6
|
+
import { sessionAfter, stampWorked } from "../client/run-commit.js";
|
|
7
|
+
import { keepsConversation } from "../client/run-session.js";
|
|
8
|
+
import { foldAssistantTurn } from "../client/turn-client.js";
|
|
9
|
+
import { usePageToolWarnings } from "./use-page-tool-warnings.js";
|
|
10
|
+
import { useTheoremChatActions } from "./use-theorem-chat-actions.js";
|
|
11
|
+
import { useTheoremChatState, } from "./use-theorem-chat-state.js";
|
|
12
|
+
/**
|
|
13
|
+
* The slots the profile declares and the value of each: the page's where it names one, else what
|
|
14
|
+
* the visitor chose, else the first allowed value.
|
|
15
|
+
*/
|
|
16
|
+
function useSlotChoices(iface, fixed, onSlotsChange) {
|
|
17
|
+
const [picked, setPicked] = useState({});
|
|
18
|
+
const declared = iface?.inputs.slots;
|
|
19
|
+
const values = useMemo(() => {
|
|
20
|
+
const out = { ...fixed };
|
|
21
|
+
for (const [name, allowed] of Object.entries(declared ?? {})) {
|
|
22
|
+
const value = fixed?.[name] ?? picked[name] ?? allowed[0];
|
|
23
|
+
if (value !== undefined)
|
|
24
|
+
out[name] = value;
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}, [declared, fixed, picked]);
|
|
28
|
+
const choose = useCallback((name, value) => {
|
|
29
|
+
setPicked((prev) => ({ ...prev, [name]: value }));
|
|
30
|
+
onSlotsChange?.({ ...values, [name]: value });
|
|
31
|
+
}, [values, onSlotsChange]);
|
|
32
|
+
const isChosen = declared !== undefined && Object.keys(declared).length > 0;
|
|
33
|
+
return {
|
|
34
|
+
values: isChosen || fixed ? values : undefined,
|
|
35
|
+
choices: isChosen ? { declared, values, onChange: choose } : undefined,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Settles legacy page gates as unanswered. Browser callbacks require an active dispatch grant.
|
|
40
|
+
*/
|
|
41
|
+
function usePageAnswers(gated, pageTools, answer) {
|
|
42
|
+
const answered = useRef(null);
|
|
43
|
+
const latest = useRef({ pageTools, answer });
|
|
44
|
+
latest.current = { pageTools, answer };
|
|
45
|
+
const waiting = gated?.gateKind === 'page' ? gated : null;
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (!waiting?.operationId || answered.current === waiting.operationId)
|
|
48
|
+
return;
|
|
49
|
+
const operationId = waiting.operationId;
|
|
50
|
+
answered.current = operationId;
|
|
51
|
+
// invariant: A restored page gate cannot authorize callback execution. Only active dispatch grants do.
|
|
52
|
+
void latest.current.answer({ action: 'page', page: { unanswered: true } });
|
|
53
|
+
}, [waiting]);
|
|
54
|
+
}
|
|
55
|
+
/** The page's side of a chat: answers the calls that wait on it, and warns of a tool it cannot answer. */
|
|
56
|
+
function usePageTools(iface, waitingOn, pageTools, answer) {
|
|
57
|
+
usePageAnswers(waitingOn, pageTools, answer);
|
|
58
|
+
usePageToolWarnings(iface && 'tools' in iface ? iface.tools.page : undefined, pageTools);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Seed the session with the profile's default model / effort once the interface loads, and keep
|
|
62
|
+
* it valid as the interface changes: a pick still on the old defaults follows the new ones, and a
|
|
63
|
+
* pick the profile no longer has falls back to them (see `followGenerationDefaults`).
|
|
64
|
+
*/
|
|
65
|
+
function useDefaultGeneration(iface, session, setSession) {
|
|
66
|
+
const previous = useRef(undefined);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (!iface)
|
|
69
|
+
return;
|
|
70
|
+
const next = followGenerationDefaults(iface, { model: session.selectedModel, effort: session.selectedEffort }, previous.current);
|
|
71
|
+
previous.current = iface;
|
|
72
|
+
if (next.model === session.selectedModel && next.effort === session.selectedEffort)
|
|
73
|
+
return;
|
|
74
|
+
setSession((prev) => ({ ...prev, selectedModel: next.model, selectedEffort: next.effort }));
|
|
75
|
+
}, [session.selectedEffort, session.selectedModel, setSession, iface]);
|
|
76
|
+
}
|
|
77
|
+
function useConversationRef(initial, onChange) {
|
|
78
|
+
const ref = useRef(initial);
|
|
79
|
+
const onChangeRef = useRef(onChange);
|
|
80
|
+
onChangeRef.current = onChange;
|
|
81
|
+
const adopt = useCallback((next) => {
|
|
82
|
+
if (ref.current === next)
|
|
83
|
+
return;
|
|
84
|
+
ref.current = next;
|
|
85
|
+
onChangeRef.current?.(next);
|
|
86
|
+
}, []);
|
|
87
|
+
return useMemo(() => ({ ref, adopt }), [adopt]);
|
|
88
|
+
}
|
|
89
|
+
/** Draws the conversation as the host shows it: its transcript, and the reply that waits on gates. */
|
|
90
|
+
function drawConversation(state, view, session, events) {
|
|
91
|
+
state.setBlocks(view.transcript);
|
|
92
|
+
state.setStreamBlocks(view.paused?.blocks ?? []);
|
|
93
|
+
// why: A reply at rest leaves the run's own events (tokens, an ask); a paused one, the host's.
|
|
94
|
+
state.setSession(sessionAfter(session, view.paused?.events ?? events));
|
|
95
|
+
state.setChatStarted(view.transcript.length > 0 || view.paused !== undefined);
|
|
96
|
+
}
|
|
97
|
+
/** The streamed blocks, ending on the turn's error unless the stream already carried it. */
|
|
98
|
+
function withTurnError(blocks, error) {
|
|
99
|
+
if (blocks.some((block) => block.kind === 'error'))
|
|
100
|
+
return blocks;
|
|
101
|
+
return [...blocks, { id: crypto.randomUUID(), kind: 'error', message: error }];
|
|
102
|
+
}
|
|
103
|
+
/** A failure the composer shows: none when the person stopped the run. */
|
|
104
|
+
function showComposerFailure(state, failure) {
|
|
105
|
+
if (!failure || failure.aborted)
|
|
106
|
+
return;
|
|
107
|
+
const { error, errorKind, errorInternal } = failure;
|
|
108
|
+
state.setFailure({ error, errorKind, ...(errorInternal ? { errorInternal } : {}) });
|
|
109
|
+
if (failure.issues)
|
|
110
|
+
state.setIssues(failure.issues);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The run begins: busy, and streaming unless its message goes live when it
|
|
114
|
+
* posts (onUserBlocks), so the previous reply never renders as streaming in
|
|
115
|
+
* between. Returns the delivery before it, which a new message replaces.
|
|
116
|
+
*/
|
|
117
|
+
function beginRun(state, message) {
|
|
118
|
+
state.setFailure(null);
|
|
119
|
+
const priorDelivery = state.deliveryRef.current;
|
|
120
|
+
state.busyRef.current = true;
|
|
121
|
+
state.setBusy(true);
|
|
122
|
+
if (!message)
|
|
123
|
+
state.setStreaming(true);
|
|
124
|
+
state.allowQueueDrainRef.current = false;
|
|
125
|
+
return priorDelivery;
|
|
126
|
+
}
|
|
127
|
+
/** The run is over: nothing streams, and nothing can stop or steer it. */
|
|
128
|
+
function endRun(state) {
|
|
129
|
+
state.cancelPendingStreamFrame();
|
|
130
|
+
state.busyRef.current = false;
|
|
131
|
+
state.setBusy(false);
|
|
132
|
+
state.setStreaming(false);
|
|
133
|
+
state.abortRef.current = null;
|
|
134
|
+
state.turnIdRef.current = null;
|
|
135
|
+
}
|
|
136
|
+
/** A message that never reached the conversation leaves the transcript and goes back in the composer. */
|
|
137
|
+
async function takeBackMessage(state, start, restore) {
|
|
138
|
+
state.setBlocks(start.blocks);
|
|
139
|
+
state.setStreamBlocks(start.streamBlocks);
|
|
140
|
+
state.setChatStarted(start.blocks.length > 0 || start.streamBlocks.length > 0);
|
|
141
|
+
state.setDelivery(null);
|
|
142
|
+
await restore();
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* A host that keeps the conversation is the truth: the chat draws it again
|
|
146
|
+
* from its ref, even after a message that never posted (a walk can settle
|
|
147
|
+
* calls before it fails). A failure shows in the composer unless the host's
|
|
148
|
+
* transcript closed on it. Returns whether the reply waits on gates.
|
|
149
|
+
*/
|
|
150
|
+
async function settleHosted(state, transport, conversation, outcome, start) {
|
|
151
|
+
const { failure } = outcome;
|
|
152
|
+
if (failure)
|
|
153
|
+
state.setDelivery(null);
|
|
154
|
+
if (conversation === undefined) {
|
|
155
|
+
state.setStreamBlocks(start.streamBlocks);
|
|
156
|
+
showComposerFailure(state, failure);
|
|
157
|
+
return state.sessionRef.current.gatedTool !== null;
|
|
158
|
+
}
|
|
159
|
+
const view = await transport.conversation({ conversation });
|
|
160
|
+
drawConversation(state, view, outcome.session, outcome.events);
|
|
161
|
+
if (!view.transcript.slice(start.blocks.length).some((b) => b.kind === 'error')) {
|
|
162
|
+
showComposerFailure(state, failure);
|
|
163
|
+
}
|
|
164
|
+
return view.paused !== undefined;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* A stateless chat keeps the reply as the run streamed it: in the transcript
|
|
168
|
+
* once it ends (closed by its failure, or by the person's stop), or beside it
|
|
169
|
+
* while it waits on a gate. Returns whether it waits.
|
|
170
|
+
*/
|
|
171
|
+
function settleLocal(state, iface, outcome, worked) {
|
|
172
|
+
const session = sessionAfter(outcome.session, outcome.events);
|
|
173
|
+
const { failure } = outcome;
|
|
174
|
+
const reply = stampWorked(foldAssistantTurn(iface, outcome.events), worked);
|
|
175
|
+
state.setSession(session);
|
|
176
|
+
if (session.gatedTool !== null) {
|
|
177
|
+
state.setStreamBlocks(reply);
|
|
178
|
+
showComposerFailure(state, failure);
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
state.setStreamBlocks([]);
|
|
182
|
+
if (!failure) {
|
|
183
|
+
state.setBlocks((prev) => [...prev, ...reply]);
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
state.setDelivery(null);
|
|
187
|
+
const closing = failure.aborted
|
|
188
|
+
? [{ id: crypto.randomUUID(), kind: 'turn-done', stop: { kind: 'cancelled' } }]
|
|
189
|
+
: withTurnError(reply, failure.error);
|
|
190
|
+
state.setBlocks((prev) => [...prev, ...closing]);
|
|
191
|
+
if (failure.issues)
|
|
192
|
+
state.setIssues(failure.issues);
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Draws the conversation the chat resumes, once the interface says the host
|
|
197
|
+
* keeps one. A run that starts first draws it itself.
|
|
198
|
+
*/
|
|
199
|
+
function useResumedConversation(iface, transport, state, conversation, shown) {
|
|
200
|
+
const loaded = useRef(false);
|
|
201
|
+
const shownRef = useRef(shown);
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
if (!iface || loaded.current)
|
|
204
|
+
return;
|
|
205
|
+
loaded.current = true;
|
|
206
|
+
const ref = conversation.ref.current;
|
|
207
|
+
if (ref === undefined || !keepsConversation(iface))
|
|
208
|
+
return;
|
|
209
|
+
if (shownRef.current) {
|
|
210
|
+
drawConversation(state, shownRef.current, state.sessionRef.current, []);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
void (async () => {
|
|
214
|
+
const before = state.blocksRef.current;
|
|
215
|
+
try {
|
|
216
|
+
const view = await transport.conversation({ conversation: ref });
|
|
217
|
+
// why: A message sent while it loaded posted onto the host's conversation; its run draws it.
|
|
218
|
+
if (state.blocksRef.current !== before || state.busyRef.current)
|
|
219
|
+
return;
|
|
220
|
+
drawConversation(state, view, state.sessionRef.current, []);
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
showComposerFailure(state, turnFailure(err, iface.labels));
|
|
224
|
+
}
|
|
225
|
+
})();
|
|
226
|
+
}, [conversation, iface, state, transport]);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Runs one turn's stream into the transcript: live partials while it streams,
|
|
230
|
+
* then what the run left (or its failure) and the pending queue's next step.
|
|
231
|
+
*/
|
|
232
|
+
function useRunTurnStream(iface, transport, state, conversation) {
|
|
233
|
+
// why: A stateless reply's work across its runs: a gate splits a reply into runs, and the wait between doesn't count.
|
|
234
|
+
const replyWorkedMs = useRef(0);
|
|
235
|
+
// why: Steers the reply never took wait at the front of the queue.
|
|
236
|
+
const onRunEnded = useCallback((drain) => {
|
|
237
|
+
state.setPendingMessages((prev) => orderComposerPendingMessages(convertSteersToFrontQueued(prev)));
|
|
238
|
+
state.allowQueueDrainRef.current = drain;
|
|
239
|
+
}, [state]);
|
|
240
|
+
return useCallback(async (run, options = {}) => {
|
|
241
|
+
if (!iface || state.busyRef.current)
|
|
242
|
+
return;
|
|
243
|
+
const { message } = options;
|
|
244
|
+
const priorDelivery = beginRun(state, message !== undefined);
|
|
245
|
+
const start = {
|
|
246
|
+
blocks: state.blocksRef.current,
|
|
247
|
+
streamBlocks: state.streamBlocksRef.current,
|
|
248
|
+
};
|
|
249
|
+
// why: A run that answers a gate continues its reply; any other starts one.
|
|
250
|
+
if (!state.sessionRef.current.gatedTool)
|
|
251
|
+
replyWorkedMs.current = 0;
|
|
252
|
+
const runStartedAt = Date.now();
|
|
253
|
+
const work = (async () => {
|
|
254
|
+
const view = {
|
|
255
|
+
blocks: (partial) => state.scheduleStreamBlocks(partial),
|
|
256
|
+
// why: The reply goes on; the composer names what it left out.
|
|
257
|
+
skipped: (error) => state.setFailure(clientFailure(error, iface.labels)),
|
|
258
|
+
};
|
|
259
|
+
const outcome = await run(view);
|
|
260
|
+
const endedAt = Date.now();
|
|
261
|
+
replyWorkedMs.current += endedAt - runStartedAt;
|
|
262
|
+
endRun(state);
|
|
263
|
+
const unposted = message !== undefined && !outcome.posted;
|
|
264
|
+
// why: A message that posts itself (onUserBlocks) gets a fresh delivery.
|
|
265
|
+
if (unposted && state.deliveryRef.current !== priorDelivery) {
|
|
266
|
+
await takeBackMessage(state, start, message.restore);
|
|
267
|
+
}
|
|
268
|
+
let waits;
|
|
269
|
+
if (keepsConversation(iface)) {
|
|
270
|
+
waits = await settleHosted(state, transport, conversation.ref.current, outcome, start);
|
|
271
|
+
}
|
|
272
|
+
else if (unposted) {
|
|
273
|
+
showComposerFailure(state, outcome.failure);
|
|
274
|
+
waits = state.sessionRef.current.gatedTool !== null;
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
waits = settleLocal(state, iface, outcome, { workedMs: replyWorkedMs.current, endedAt });
|
|
278
|
+
}
|
|
279
|
+
onRunEnded(!outcome.failure && !waits);
|
|
280
|
+
})();
|
|
281
|
+
state.runPromiseRef.current = work;
|
|
282
|
+
try {
|
|
283
|
+
await work;
|
|
284
|
+
}
|
|
285
|
+
catch (err) {
|
|
286
|
+
// why: The run left no view to draw; the composer says so, and the chat stays as it stood.
|
|
287
|
+
showComposerFailure(state, turnFailure(err, iface.labels));
|
|
288
|
+
onRunEnded(false);
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
if (state.runPromiseRef.current === work)
|
|
292
|
+
state.runPromiseRef.current = null;
|
|
293
|
+
}
|
|
294
|
+
}, [conversation, iface, onRunEnded, state, transport]);
|
|
295
|
+
}
|
|
296
|
+
/** Start the next queued message once the run goes idle, when the run that ended allows it. */
|
|
297
|
+
function useQueueDrain(phase, state, startTurnFromDraft) {
|
|
298
|
+
const drainQueue = useCallback(async () => {
|
|
299
|
+
if (state.drainLockRef.current || state.busyRef.current || state.sessionRef.current.gatedTool) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const { message } = consumeNextComposerQueue(state.pendingRef.current);
|
|
303
|
+
if (!message)
|
|
304
|
+
return;
|
|
305
|
+
state.drainLockRef.current = true;
|
|
306
|
+
try {
|
|
307
|
+
// why: The message leaves the queue once it posts; one that never starts stays queued.
|
|
308
|
+
await startTurnFromDraft(message.draft, {
|
|
309
|
+
onPosted: () => state.setPendingMessages((prev) => removeComposerPendingMessage(prev, message.id)),
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
finally {
|
|
313
|
+
state.drainLockRef.current = false;
|
|
314
|
+
}
|
|
315
|
+
}, [startTurnFromDraft, state]);
|
|
316
|
+
useEffect(() => {
|
|
317
|
+
if (phase !== 'idle' || !state.allowQueueDrainRef.current)
|
|
318
|
+
return;
|
|
319
|
+
if (!state.pendingMessages.some((m) => m.kind === 'queue')) {
|
|
320
|
+
state.allowQueueDrainRef.current = false;
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
state.allowQueueDrainRef.current = false;
|
|
324
|
+
void drainQueue();
|
|
325
|
+
}, [drainQueue, phase, state]);
|
|
326
|
+
}
|
|
327
|
+
const DELIVERY_ORDER = ['sending', 'sent', 'delivered', 'read'];
|
|
328
|
+
/** The server's own reports (bookkeeping, a failure, the end), which say nothing of the model having the message. */
|
|
329
|
+
const SERVER_EVENTS = new Set(['stage', 'guardrail', 'session', 'error', 'done']);
|
|
330
|
+
/** The reply itself (its thinking and tool calls come before it). */
|
|
331
|
+
const REPLY_EVENTS = new Set(['text', 'structured', 'media']);
|
|
332
|
+
/**
|
|
333
|
+
* How far a line shows the message got: the host saving it, or any event,
|
|
334
|
+
* means the server took it; one from the model (thinking, a tool call) means
|
|
335
|
+
* the model has it, and the reply means it was read. A save without the
|
|
336
|
+
* message (a walk settling calls) says nothing of it.
|
|
337
|
+
*/
|
|
338
|
+
function deliveryOf(line) {
|
|
339
|
+
if (line.type === 'conversation')
|
|
340
|
+
return line.posted ? 'sent' : undefined;
|
|
341
|
+
if (REPLY_EVENTS.has(line.type))
|
|
342
|
+
return 'read';
|
|
343
|
+
return SERVER_EVENTS.has(line.type) ? 'sent' : 'delivered';
|
|
344
|
+
}
|
|
345
|
+
/** Moves the message's delivery on to what `line` shows it reached, never back. */
|
|
346
|
+
function advanceDelivery(deliveryRef, setDelivery, line) {
|
|
347
|
+
const delivery = deliveryRef.current;
|
|
348
|
+
const reached = deliveryOf(line);
|
|
349
|
+
if (!delivery || !reached)
|
|
350
|
+
return;
|
|
351
|
+
if (DELIVERY_ORDER.indexOf(reached) > DELIVERY_ORDER.indexOf(delivery.status)) {
|
|
352
|
+
setDelivery({ status: reached });
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* The transport, with each turn's events also clearing the steers they report
|
|
357
|
+
* as landed (so the run's end requeues only the steers the agent never saw),
|
|
358
|
+
* and moving the posted message's delivery forward. The chat adopts each
|
|
359
|
+
* conversation ref as it arrives, so a page that reloads mid-reply keeps the
|
|
360
|
+
* posted message. Each turn carries the page's slots and context as they are
|
|
361
|
+
* when it is sent.
|
|
362
|
+
*/
|
|
363
|
+
function useTappedTransport(transport, state, conversation, values, onTurnEvent) {
|
|
364
|
+
const valuesRef = useRef(values);
|
|
365
|
+
valuesRef.current = values;
|
|
366
|
+
const onTurnEventRef = useRef(onTurnEvent);
|
|
367
|
+
onTurnEventRef.current = onTurnEvent;
|
|
368
|
+
return useMemo(() => {
|
|
369
|
+
const withValues = (request) => {
|
|
370
|
+
const { slots, context } = valuesRef.current;
|
|
371
|
+
if (slots === undefined && context === undefined)
|
|
372
|
+
return request;
|
|
373
|
+
return {
|
|
374
|
+
...request,
|
|
375
|
+
input: {
|
|
376
|
+
...request.input,
|
|
377
|
+
...(slots === undefined ? {} : { slots }),
|
|
378
|
+
...(context === undefined ? {} : { context }),
|
|
379
|
+
},
|
|
380
|
+
};
|
|
381
|
+
};
|
|
382
|
+
const tap = (onLine) => (line) => {
|
|
383
|
+
if (line.type === 'stage' && line.injected?.length) {
|
|
384
|
+
state.pendingRef.current = removeLandedSteers(state.pendingRef.current, line);
|
|
385
|
+
state.setPendingMessages((prev) => removeLandedSteers(prev, line));
|
|
386
|
+
}
|
|
387
|
+
advanceDelivery(state.deliveryRef, state.setDelivery, line);
|
|
388
|
+
if (line.type === 'conversation')
|
|
389
|
+
conversation.adopt(line.ref);
|
|
390
|
+
else
|
|
391
|
+
onTurnEventRef.current?.(line);
|
|
392
|
+
onLine(line);
|
|
393
|
+
};
|
|
394
|
+
return {
|
|
395
|
+
...transport,
|
|
396
|
+
turn: (request, onLine, signal) => transport.turn(withValues(request), tap(onLine), signal),
|
|
397
|
+
invoke: (request, onLine, signal) => transport.invoke(request, tap(onLine), signal),
|
|
398
|
+
};
|
|
399
|
+
}, [
|
|
400
|
+
transport,
|
|
401
|
+
conversation,
|
|
402
|
+
state.pendingRef,
|
|
403
|
+
state.setPendingMessages,
|
|
404
|
+
state.deliveryRef,
|
|
405
|
+
state.setDelivery,
|
|
406
|
+
]);
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Headless chat model: transcript, streaming, composer drafts, pending
|
|
410
|
+
* queue / steer / stash, tool gates. Render it with `@theoremjs/react/ui` or
|
|
411
|
+
* your own components.
|
|
412
|
+
*/
|
|
413
|
+
export function useTheoremChat({ transport, iface, conversation: initialConversation, conversationView, initialText, onConversationChange, slots, onSlotsChange, context, pageTools, onTurnEvent, }) {
|
|
414
|
+
const slotState = useSlotChoices(iface, slots, onSlotsChange);
|
|
415
|
+
const state = useTheoremChatState(initialText);
|
|
416
|
+
const conversation = useConversationRef(initialConversation, onConversationChange);
|
|
417
|
+
useResumedConversation(iface, transport, state, conversation, conversationView);
|
|
418
|
+
useDefaultGeneration(iface, state.session, state.setSession);
|
|
419
|
+
const gated = state.session.gatedTool !== null;
|
|
420
|
+
const phase = state.busy ? 'streaming' : gated ? 'gated' : 'idle';
|
|
421
|
+
const handleGenerationChange = useCallback((next) => {
|
|
422
|
+
const effort = next.effort ?? (iface ? defaultInterfaceEffort(iface, next.modelId) : undefined);
|
|
423
|
+
state.setSession((prev) => ({
|
|
424
|
+
...prev,
|
|
425
|
+
selectedModel: next.modelId,
|
|
426
|
+
...(effort ? { selectedEffort: effort } : { selectedEffort: undefined }),
|
|
427
|
+
}));
|
|
428
|
+
}, [iface, state]);
|
|
429
|
+
const runTurnStream = useRunTurnStream(iface, transport, state, conversation);
|
|
430
|
+
const pageTransport = useMemo(() => withPageToolDispatch(transport, pageTools), [transport, pageTools]);
|
|
431
|
+
const steerTransport = useTappedTransport(pageTransport, state, conversation, { slots: slotState.values, context }, onTurnEvent);
|
|
432
|
+
const actions = useTheoremChatActions({
|
|
433
|
+
...state,
|
|
434
|
+
iface,
|
|
435
|
+
transport: steerTransport,
|
|
436
|
+
conversationRef: conversation.ref,
|
|
437
|
+
phase,
|
|
438
|
+
gated,
|
|
439
|
+
runTurnStream,
|
|
440
|
+
});
|
|
441
|
+
useQueueDrain(phase, state, actions.startTurnFromDraft);
|
|
442
|
+
usePageTools(iface, phase === 'gated' ? state.session.gatedTool : null, pageTools, actions.resumeGatedTool);
|
|
443
|
+
/** Sends `text` and optional files as the composer would, and resolves once the reply is done; `null` when it can't go now. */
|
|
444
|
+
const sendText = useCallback(async (text, files = []) => {
|
|
445
|
+
if (!iface || state.busyRef.current || state.sessionRef.current.gatedTool !== null)
|
|
446
|
+
return null;
|
|
447
|
+
const before = state.blocksRef.current.length;
|
|
448
|
+
await actions.startTurnFromFields({ text, files: [...files], voice: [] });
|
|
449
|
+
return { blocks: state.blocksRef.current.slice(before) };
|
|
450
|
+
}, [actions, iface, state]);
|
|
451
|
+
/**
|
|
452
|
+
* Cut the conversation after transcript block `index`. A host that keeps
|
|
453
|
+
* the conversation cuts it (walking away from a reply that waits on gates)
|
|
454
|
+
* and the chat draws what it returns; a stateless chat cuts its own.
|
|
455
|
+
*/
|
|
456
|
+
const handleBranch = useCallback(async (index) => {
|
|
457
|
+
if (!iface || state.busyRef.current)
|
|
458
|
+
return;
|
|
459
|
+
const ref = conversation.ref.current;
|
|
460
|
+
if (!keepsConversation(iface) || ref === undefined) {
|
|
461
|
+
const kept = [...state.blocksRef.current, ...state.streamBlocksRef.current].slice(0, index + 1);
|
|
462
|
+
state.setBlocks(kept);
|
|
463
|
+
state.setStreamBlocks([]);
|
|
464
|
+
state.setChatStarted(kept.length > 0);
|
|
465
|
+
// why: The kept transcript's last message isn't the one whose delivery was tracked.
|
|
466
|
+
state.setDelivery(null);
|
|
467
|
+
state.setSession((prev) => branchInterfaceTurnSession(prev, kept));
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
state.setFailure(null);
|
|
471
|
+
try {
|
|
472
|
+
// why: The paused reply is not in the host's transcript; a cut inside it keeps the transcript whole.
|
|
473
|
+
const at = Math.min(index, state.blocksRef.current.length - 1);
|
|
474
|
+
const rewound = await transport.rewind({ conversation: ref, at });
|
|
475
|
+
conversation.adopt(rewound.conversation);
|
|
476
|
+
drawConversation(state, rewound.view, state.sessionRef.current, []);
|
|
477
|
+
state.setDelivery(null);
|
|
478
|
+
}
|
|
479
|
+
catch (err) {
|
|
480
|
+
showComposerFailure(state, turnFailure(err, iface.labels));
|
|
481
|
+
}
|
|
482
|
+
}, [conversation, iface, state, transport]);
|
|
483
|
+
const handlePendingQueue = useCallback((id) => {
|
|
484
|
+
const message = state.pendingRef.current.find((m) => m.id === id);
|
|
485
|
+
if (message?.kind !== 'stash')
|
|
486
|
+
return;
|
|
487
|
+
if (phase === 'idle') {
|
|
488
|
+
state.allowQueueDrainRef.current = true;
|
|
489
|
+
}
|
|
490
|
+
state.setPendingMessages((prev) => promoteComposerPendingKind(prev, id, 'queue'));
|
|
491
|
+
}, [phase, state]);
|
|
492
|
+
return {
|
|
493
|
+
iface,
|
|
494
|
+
blocks: state.blocks,
|
|
495
|
+
chatStarted: state.chatStarted,
|
|
496
|
+
draftText: state.draftText,
|
|
497
|
+
failure: state.failure,
|
|
498
|
+
/** The answer on its way to a gate, shown on it until the answer settles or fails. */
|
|
499
|
+
answering: state.answering,
|
|
500
|
+
issues: state.issues,
|
|
501
|
+
pendingFiles: state.pendingFiles,
|
|
502
|
+
pendingMessages: state.pendingMessages,
|
|
503
|
+
pendingVoice: state.pendingVoice,
|
|
504
|
+
phase,
|
|
505
|
+
session: state.session,
|
|
506
|
+
/** The latest message's delivery; `null` before the first and once its turn failed. */
|
|
507
|
+
delivery: state.delivery?.status ?? null,
|
|
508
|
+
streamBlocks: state.streamBlocks,
|
|
509
|
+
streaming: state.streaming,
|
|
510
|
+
setDraftText: state.setDraftText,
|
|
511
|
+
setPendingFiles: state.setPendingFiles,
|
|
512
|
+
setPendingVoice: state.setPendingVoice,
|
|
513
|
+
setPendingMessages: state.setPendingMessages,
|
|
514
|
+
setIssues: state.setIssues,
|
|
515
|
+
handleAuthenticated: actions.handleAuthenticated,
|
|
516
|
+
handleBranch,
|
|
517
|
+
handleSubmit: actions.handleSubmit,
|
|
518
|
+
handleStop: actions.handleStop,
|
|
519
|
+
handleMenuAction: actions.handleMenuAction,
|
|
520
|
+
handlePendingQueue,
|
|
521
|
+
handlePendingRestore: actions.handlePendingRestore,
|
|
522
|
+
handleSendNow: actions.handleSendNow,
|
|
523
|
+
handleToolDecision: actions.handleToolDecision,
|
|
524
|
+
handleGenerationChange,
|
|
525
|
+
slotChoices: slotState.choices,
|
|
526
|
+
sendText,
|
|
527
|
+
};
|
|
528
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { DecisionAsset, DecisionJson, DecisionResult } from '@theoremjs/agents';
|
|
2
|
+
import type { DecisionInterface, DecisionTransport } from '../client/decision-transport.ts';
|
|
3
|
+
import type { ClientFailure } from '../client/failure.ts';
|
|
4
|
+
import { type OneCallState } from './use-one-call.ts';
|
|
5
|
+
export type TheoremDecisionState = {
|
|
6
|
+
/** The profile as the page sees it; null until the host describes it. */
|
|
7
|
+
iface: DecisionInterface | null;
|
|
8
|
+
/** Why the host could not describe the profile. */
|
|
9
|
+
describeFailure: ClientFailure | null;
|
|
10
|
+
} & OneCallState<'deciding', DecisionResult>;
|
|
11
|
+
/**
|
|
12
|
+
* One decision profile: describe it once, then `decide(state)` as often as
|
|
13
|
+
* needed. A new decision aborts the one still running.
|
|
14
|
+
*/
|
|
15
|
+
export declare function useTheoremDecision(transport: DecisionTransport): TheoremDecisionState & {
|
|
16
|
+
decide: (state: Exclude<DecisionJson, null>, assets?: Record<string, DecisionAsset>) => Promise<void>;
|
|
17
|
+
cancel: () => void;
|
|
18
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useDescribed } from "./use-described.js";
|
|
2
|
+
import { useOneCall } from "./use-one-call.js";
|
|
3
|
+
const decideWith = (transport, signal, state, assets) => transport.decide(state, signal, assets);
|
|
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 { call, ...run } = useOneCall(transport, 'deciding', decideWith);
|
|
11
|
+
return { iface, describeFailure, ...run, decide: call };
|
|
12
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { EmbeddingInput, EmbeddingResult } from '@theoremjs/agents';
|
|
2
|
+
import type { EmbeddingInterface, EmbeddingTransport, EmbedOptions } from '../client/embedding-transport.ts';
|
|
3
|
+
import type { ClientFailure } from '../client/failure.ts';
|
|
4
|
+
import { type OneCallState } from './use-one-call.ts';
|
|
5
|
+
export type TheoremEmbeddingState = {
|
|
6
|
+
/** The profile as the page sees it; null until the host describes it. */
|
|
7
|
+
iface: EmbeddingInterface | null;
|
|
8
|
+
/** Why the host could not describe the profile. */
|
|
9
|
+
describeFailure: ClientFailure | null;
|
|
10
|
+
} & OneCallState<'embedding', EmbeddingResult>;
|
|
11
|
+
/**
|
|
12
|
+
* One embedding profile: describe it once, then `embed(inputs)` as often as
|
|
13
|
+
* needed. A new embedding aborts the one still running.
|
|
14
|
+
*/
|
|
15
|
+
export declare function useTheoremEmbedding(transport: EmbeddingTransport): TheoremEmbeddingState & {
|
|
16
|
+
embed: (inputs: readonly EmbeddingInput[], options?: EmbedOptions) => Promise<void>;
|
|
17
|
+
cancel: () => void;
|
|
18
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useDescribed } from "./use-described.js";
|
|
2
|
+
import { useOneCall } from "./use-one-call.js";
|
|
3
|
+
const embedWith = (transport, signal, inputs, options) => transport.embed(inputs, options, signal);
|
|
4
|
+
/**
|
|
5
|
+
* One embedding profile: describe it once, then `embed(inputs)` as often as
|
|
6
|
+
* needed. A new embedding aborts the one still running.
|
|
7
|
+
*/
|
|
8
|
+
export function useTheoremEmbedding(transport) {
|
|
9
|
+
const { iface, describeFailure } = useDescribed(transport);
|
|
10
|
+
const { call, ...run } = useOneCall(transport, 'embedding', embedWith);
|
|
11
|
+
return { iface, describeFailure, ...run, embed: call };
|
|
12
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
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 { PageTools } from '../client/live/live-page-tool.ts';
|
|
6
|
+
import { type PersonGateResolution } from '../client/tool-resume.ts';
|
|
7
|
+
export type { ToolCall };
|
|
8
|
+
/** One call of one of the host's tools, as it streams. */
|
|
9
|
+
export type TheoremHostCall = {
|
|
10
|
+
/** The page's id for the call, before the host names it. */
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
input: unknown;
|
|
14
|
+
/** Everything the host streamed for the call, answers included. */
|
|
15
|
+
events: TurnEvent[];
|
|
16
|
+
/** The call as its events fold: its state is the phase it is in. */
|
|
17
|
+
call: ToolCall | null;
|
|
18
|
+
/** The tool's latest progress report while it runs. */
|
|
19
|
+
progress: unknown;
|
|
20
|
+
isRunning: boolean;
|
|
21
|
+
/** The call never reached its tool, or its stream broke. */
|
|
22
|
+
failure: ClientFailure | null;
|
|
23
|
+
/** The answer on its way to the gate the call paused on. */
|
|
24
|
+
answering: PersonGateResolution['action'] | null;
|
|
25
|
+
startedAt: number;
|
|
26
|
+
/** Round trip, once it settles or pauses. */
|
|
27
|
+
elapsedMs: number | null;
|
|
28
|
+
};
|
|
29
|
+
export type TheoremHostState = {
|
|
30
|
+
/** The host as the page sees it; null until it describes itself. */
|
|
31
|
+
iface: HostInterface | null;
|
|
32
|
+
describeFailure: ClientFailure | null;
|
|
33
|
+
/** This page's calls, newest first. */
|
|
34
|
+
calls: TheoremHostCall[];
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* One host profile: describe it once, then `run(name, input)` any of its tools
|
|
38
|
+
* as often as needed, and answer the gates a call pauses on. `run` returns the
|
|
39
|
+
* call's id at once; calls run side by side, and `cancel` stops one.
|
|
40
|
+
*/
|
|
41
|
+
export declare function useTheoremHost(source: HostTransport, pageTools?: PageTools): TheoremHostState & {
|
|
42
|
+
run: (name: string, input: unknown) => string;
|
|
43
|
+
answer: (id: string, resolution: PersonGateResolution) => Promise<void>;
|
|
44
|
+
cancel: (id: string) => void;
|
|
45
|
+
};
|