@theoremjs/react 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/README.md +728 -202
  2. package/client/composer-hints.js +1 -1
  3. package/client/decision-images.d.ts +76 -0
  4. package/client/decision-images.js +139 -0
  5. package/client/decision-transport.d.ts +26 -4
  6. package/client/decision-transport.js +79 -6
  7. package/client/embedding-input.d.ts +84 -0
  8. package/client/embedding-input.js +194 -0
  9. package/client/embedding-space.d.ts +82 -0
  10. package/client/embedding-space.js +389 -0
  11. package/client/embedding-transport.d.ts +40 -0
  12. package/client/embedding-transport.js +71 -0
  13. package/client/failure.d.ts +7 -10
  14. package/client/failure.js +9 -9
  15. package/client/host-call.d.ts +1 -1
  16. package/client/host-call.js +7 -2
  17. package/client/host-transport.d.ts +2 -0
  18. package/client/host-transport.js +3 -2
  19. package/client/index.d.ts +19 -11
  20. package/client/index.js +14 -10
  21. package/client/ink-waveform.d.ts +1 -1
  22. package/client/ink-waveform.js +1 -2
  23. package/client/live/apply-live-turn-tool-event.js +1 -1
  24. package/client/live/live-captions.d.ts +30 -0
  25. package/client/live/live-captions.js +105 -1
  26. package/client/live/live-mic-forward.d.ts +1 -0
  27. package/client/live/live-mic-forward.js +11 -2
  28. package/client/live/live-page-tool.d.ts +34 -0
  29. package/client/live/live-page-tool.js +11 -0
  30. package/client/live/live-state.d.ts +4 -2
  31. package/client/live/live-state.js +2 -0
  32. package/client/live/live-video.js +1 -1
  33. package/client/live/run-live-tool-call.d.ts +7 -2
  34. package/client/live/run-live-tool-call.js +48 -10
  35. package/client/live/use-live-caption-log.d.ts +17 -0
  36. package/client/live/use-live-caption-log.js +51 -0
  37. package/client/live-client.d.ts +52 -4
  38. package/client/live-client.js +250 -59
  39. package/client/live-messages.d.ts +51 -13
  40. package/client/live-messages.js +42 -9
  41. package/client/page-dispatch.d.ts +15 -0
  42. package/client/page-dispatch.js +63 -0
  43. package/client/run-commit.d.ts +16 -43
  44. package/client/run-commit.js +26 -101
  45. package/client/run-session.d.ts +40 -41
  46. package/client/run-session.js +104 -112
  47. package/client/schema-fields.js +27 -6
  48. package/client/shaped-data.js +37 -8
  49. package/client/source-citations.js +1 -1
  50. package/client/space-geometry.d.ts +39 -0
  51. package/client/space-geometry.js +183 -0
  52. package/client/state-fields.js +1 -1
  53. package/client/tool-result.js +30 -12
  54. package/client/tool-resume.d.ts +18 -1
  55. package/client/tool-resume.js +4 -1
  56. package/client/trace-feed.d.ts +1 -1
  57. package/client/trace-story.d.ts +2 -2
  58. package/client/trace-story.js +43 -19
  59. package/client/trace-view.d.ts +2 -2
  60. package/client/trace-view.js +56 -16
  61. package/client/transcript-groups.d.ts +28 -2
  62. package/client/transcript-groups.js +63 -13
  63. package/client/transcript-schema.d.ts +10 -0
  64. package/client/transcript-schema.js +120 -0
  65. package/client/transport.d.ts +114 -26
  66. package/client/transport.js +106 -32
  67. package/client/turn-client.d.ts +17 -8
  68. package/client/turn-client.js +26 -31
  69. package/client/voice-recorder.d.ts +1 -1
  70. package/client/voice-recorder.js +2 -5
  71. package/client/wire-line.js +5 -5
  72. package/client/worklets/mic-capture.js +9 -9
  73. package/components/InkWaveform.d.ts +12 -1
  74. package/components/InkWaveform.js +65 -42
  75. package/components/live/use-live-runner-controls.d.ts +3 -4
  76. package/components/live/use-live-runner-controls.js +6 -10
  77. package/components/live/use-live-runner-model.d.ts +10 -6
  78. package/components/live/use-live-runner-model.js +29 -14
  79. package/components/live/use-live-runner-ui.d.ts +10 -8
  80. package/components/live/use-live-runner-ui.js +19 -11
  81. package/components/live/use-live-session-client.d.ts +4 -4
  82. package/components/live/use-live-session-client.js +11 -8
  83. package/components/use-composer-voice.d.ts +4 -4
  84. package/components/use-composer-voice.js +8 -8
  85. package/components/use-editor-selection.js +4 -1
  86. package/components/use-voice-playback.d.ts +2 -1
  87. package/components/use-voice-playback.js +4 -4
  88. package/hooks/use-described.d.ts +5 -1
  89. package/hooks/use-described.js +11 -9
  90. package/hooks/use-embedding-index.d.ts +27 -0
  91. package/hooks/use-embedding-index.js +43 -0
  92. package/hooks/use-one-call.d.ts +31 -0
  93. package/hooks/use-one-call.js +76 -0
  94. package/hooks/use-page-tool-warnings.d.ts +6 -0
  95. package/hooks/use-page-tool-warnings.js +25 -0
  96. package/hooks/use-second-ticker.d.ts +2 -0
  97. package/hooks/use-second-ticker.js +13 -0
  98. package/hooks/use-theorem-chat-actions.d.ts +25 -21
  99. package/hooks/use-theorem-chat-actions.js +67 -45
  100. package/hooks/use-theorem-chat-state.d.ts +6 -6
  101. package/hooks/use-theorem-chat-state.js +13 -9
  102. package/hooks/use-theorem-chat.d.ts +48 -14
  103. package/hooks/use-theorem-chat.js +329 -141
  104. package/hooks/use-theorem-decision.d.ts +5 -10
  105. package/hooks/use-theorem-decision.js +4 -67
  106. package/hooks/use-theorem-embedding.d.ts +18 -0
  107. package/hooks/use-theorem-embedding.js +12 -0
  108. package/hooks/use-theorem-host.d.ts +5 -4
  109. package/hooks/use-theorem-host.js +26 -8
  110. package/hooks/use-theorem-interface.d.ts +6 -3
  111. package/hooks/use-theorem-interface.js +12 -22
  112. package/index.d.ts +6 -3
  113. package/index.js +5 -2
  114. package/live.d.ts +1 -0
  115. package/live.js +1 -1
  116. package/package.json +6 -5
  117. package/server/checkpoint-ownership.d.ts +6 -0
  118. package/server/checkpoint-ownership.js +42 -0
  119. package/server/conversation-store.d.ts +92 -0
  120. package/server/conversation-store.js +131 -0
  121. package/server/conversation.d.ts +193 -0
  122. package/server/conversation.js +552 -0
  123. package/server/decision-handler.d.ts +12 -4
  124. package/server/decision-handler.js +29 -38
  125. package/server/embedding-handler.d.ts +33 -0
  126. package/server/embedding-handler.js +45 -0
  127. package/server/handler.d.ts +58 -20
  128. package/server/handler.js +441 -238
  129. package/server/mod.d.ts +15 -6
  130. package/server/mod.js +13 -5
  131. package/server/page-dispatch.d.ts +22 -0
  132. package/server/page-dispatch.js +127 -0
  133. package/server/request-body.d.ts +2 -0
  134. package/server/request-body.js +42 -0
  135. package/server/request-check.d.ts +6 -1
  136. package/server/request-check.js +27 -12
  137. package/server/session-authority.d.ts +8 -0
  138. package/server/session-authority.js +78 -0
  139. package/server/session-boundary.d.ts +26 -0
  140. package/server/session-boundary.js +64 -0
  141. package/server/session-identity.d.ts +16 -0
  142. package/server/session-identity.js +41 -0
  143. package/server/session-limits.d.ts +12 -0
  144. package/server/session-limits.js +59 -0
  145. package/server/session-store.d.ts +69 -20
  146. package/server/session-store.js +220 -5
  147. package/server/single-action.d.ts +21 -0
  148. package/server/single-action.js +30 -0
  149. package/server/steer-inbox.js +1 -1
  150. package/server/tool-operations.d.ts +6 -0
  151. package/server/tool-operations.js +132 -0
  152. package/server/turn-input.d.ts +10 -0
  153. package/server/turn-input.js +29 -0
  154. package/styles/ink-controls.css +78 -78
  155. package/ui/CardWall.d.ts +10 -0
  156. package/ui/CardWall.js +61 -0
  157. package/ui/ChatComposerBar.d.ts +28 -5
  158. package/ui/ChatComposerBar.js +85 -30
  159. package/ui/ChatTranscript.d.ts +14 -1
  160. package/ui/ChatTranscript.js +110 -57
  161. package/ui/ConsoleFrame.d.ts +61 -4
  162. package/ui/ConsoleFrame.js +109 -9
  163. package/ui/DecisionAnswers.d.ts +40 -2
  164. package/ui/DecisionAnswers.js +166 -35
  165. package/ui/DecisionImages.d.ts +16 -0
  166. package/ui/DecisionImages.js +46 -0
  167. package/ui/EmbeddingDocuments.d.ts +78 -0
  168. package/ui/EmbeddingDocuments.js +278 -0
  169. package/ui/EmbeddingQuery.d.ts +27 -0
  170. package/ui/EmbeddingQuery.js +95 -0
  171. package/ui/EmbeddingResults.d.ts +41 -0
  172. package/ui/EmbeddingResults.js +165 -0
  173. package/ui/EmbeddingSpace.d.ts +26 -0
  174. package/ui/EmbeddingSpace.js +106 -0
  175. package/ui/LiveCaptionsPanel.d.ts +22 -0
  176. package/ui/LiveCaptionsPanel.js +27 -0
  177. package/ui/LiveRunner.d.ts +6 -4
  178. package/ui/LiveRunner.js +42 -55
  179. package/ui/PaneState.d.ts +33 -0
  180. package/ui/PaneState.js +50 -0
  181. package/ui/SchemaFields.d.ts +1 -1
  182. package/ui/SchemaFields.js +54 -26
  183. package/ui/ShapedData.js +73 -28
  184. package/ui/SidePanel.d.ts +74 -5
  185. package/ui/SidePanel.js +82 -9
  186. package/ui/StateFields.d.ts +10 -2
  187. package/ui/StateFields.js +56 -13
  188. package/ui/TheoremChat.d.ts +65 -14
  189. package/ui/TheoremChat.js +66 -78
  190. package/ui/TheoremDecision.d.ts +12 -20
  191. package/ui/TheoremDecision.js +78 -35
  192. package/ui/TheoremEmbedding.d.ts +27 -0
  193. package/ui/TheoremEmbedding.js +122 -0
  194. package/ui/TheoremHost.d.ts +22 -5
  195. package/ui/TheoremHost.js +127 -37
  196. package/ui/ToolGateCard.d.ts +5 -1
  197. package/ui/ToolGateCard.js +30 -23
  198. package/ui/ToolResult.js +12 -10
  199. package/ui/ToolResultCharts.js +29 -12
  200. package/ui/TraceCharts.d.ts +2 -2
  201. package/ui/TraceCharts.js +48 -17
  202. package/ui/TraceGuardrails.d.ts +3 -2
  203. package/ui/TraceGuardrails.js +24 -6
  204. package/ui/TraceInspector.d.ts +11 -1
  205. package/ui/TraceInspector.js +45 -28
  206. package/ui/TraceInspectorPanel.d.ts +31 -9
  207. package/ui/TraceInspectorPanel.js +77 -39
  208. package/ui/TraceOverview.d.ts +7 -3
  209. package/ui/TraceOverview.js +44 -14
  210. package/ui/TraceSpanDetail.js +24 -22
  211. package/ui/TraceStory.js +9 -8
  212. package/ui/TraceValues.js +19 -9
  213. package/ui/TraceWaterfall.js +12 -9
  214. package/ui/VoiceNote.js +4 -4
  215. package/ui/approval-heading.d.ts +4 -0
  216. package/ui/approval-heading.js +9 -0
  217. package/ui/arrive.d.ts +18 -0
  218. package/ui/arrive.js +46 -0
  219. package/ui/built/theme.css +86 -68
  220. package/ui/built/theme.css.d.ts +9 -0
  221. package/ui/built/theorem.d.ts +3 -3
  222. package/ui/built/theorem.js +34 -5
  223. package/ui/built/theorem.variants.d.ts +8 -2
  224. package/ui/code-language.d.ts +12 -0
  225. package/ui/code-language.js +50 -0
  226. package/ui/disclosure-motion.js +53 -9
  227. package/ui/embedding-space-gl.d.ts +37 -0
  228. package/ui/embedding-space-gl.js +663 -0
  229. package/ui/embedding-space-view.d.ts +8 -0
  230. package/ui/embedding-space-view.js +8 -0
  231. package/ui/glide.d.ts +7 -0
  232. package/ui/glide.js +44 -0
  233. package/ui/icons.js +2 -1
  234. package/ui/index.d.ts +20 -9
  235. package/ui/index.js +20 -10
  236. package/ui/labels-provider.js +5 -2
  237. package/ui/labels.d.ts +374 -85
  238. package/ui/labels.js +1028 -188
  239. package/ui/markdown-plugins.d.ts +9 -0
  240. package/ui/markdown-plugins.js +13 -0
  241. package/ui/mcp-icon.d.ts +6 -0
  242. package/ui/mcp-icon.js +8 -0
  243. package/ui/row-keys.d.ts +19 -0
  244. package/ui/row-keys.js +33 -0
  245. package/ui/theme.d.ts +1 -1
  246. package/ui/theme.js +2 -2
  247. package/ui/theorem-theme.js +32 -10
  248. package/ui/trace-palette.d.ts +2 -16
  249. package/ui/trace-palette.js +2 -16
  250. package/ui/trace-patterns.d.ts +11 -3
  251. package/ui/trace-patterns.js +16 -3
  252. package/ui/transcript-copy-text.js +1 -0
  253. package/ui/wall-columns.d.ts +8 -0
  254. package/ui/wall-columns.js +19 -0
  255. package/client/walk-away.d.ts +0 -34
  256. package/client/walk-away.js +0 -48
  257. package/server/walk-away.d.ts +0 -22
  258. package/server/walk-away.js +0 -41
package/server/mod.d.ts CHANGED
@@ -5,12 +5,21 @@
5
5
  * @module
6
6
  */
7
7
  export { theoremHostCallRequestSchema } from '../client/host-transport.ts';
8
- export type { LiveClientMessage } from '../client/live-messages.ts';
9
- export { theoremInvokeRequestSchema, theoremReplaySchema, theoremSteerRequestSchema, theoremTurnRequestSchema, } from '../client/transport.ts';
8
+ export type { LiveClientMessage, LiveOpenMessage } from '../client/live-messages.ts';
9
+ export { theoremConversationRequestSchema, theoremInvokeRequestSchema, theoremReplaySchema, theoremRewindRequestSchema, theoremSteerRequestSchema, theoremTurnRequestSchema, } from '../client/transport.ts';
10
+ export { answerReply, type ConversationHost, type ConversationUse, conversationView, type IssuedWalkAway, messageReply, type OpenedConversation, openConversation, type PostedMessage, postedMessage, type ReplyLine, type ReplyRunner, type ReplyTurn, readerConversationHost, rewindConversation, startConversation, statelessRefusal, type WalkedAwayCall, waitingCalls, } from './conversation.ts';
11
+ export { type ConversationSnapshot, type ConversationWriteResult, createMemoryConversationStore, createSealedConversationStore, emptyConversation, type PausedReply, type SealedConversationStoreOptions, type StoredConversation, storedConversationSchema, type TheoremConversationStore, unknownConversation, } from './conversation-store.ts';
10
12
  export { createMemoryCredentialStore, type TheoremCredentialStore, type TheoremCredentials, } from './credential-store.ts';
11
13
  export { createTheoremDecisionHandler, type TheoremDecisionHandlerOptions, } from './decision-handler.ts';
12
- export { createTheoremHandler, createTheoremHostHandler, readBody, type TheoremHandlerOptions, type TheoremHostHandlerOptions, type TheoremRequestContext, theoremSessionId, } from './handler.ts';
13
- export { checkRequest, parseLiveClientMessage } from './request-check.ts';
14
- export { createMemorySessionStore, type MemorySessionStoreOptions, type PendingToolGate, type SettledToolGate, type TheoremSessionState, type TheoremSessionStore, } from './session-store.ts';
14
+ export { createTheoremEmbeddingHandler, embedBodySchema, embedRequest, type TheoremEmbeddingHandlerOptions, } from './embedding-handler.ts';
15
+ export { createTheoremHandler, createTheoremHostHandler, readBody, type TheoremHandlerOptions, type TheoremHostHandlerOptions, type TheoremRequestContext, theoremSession, theoremSessionId, } from './handler.ts';
16
+ export { createMemoryPageToolBroker, type PageToolBroker, type PageToolResult, pageToolResultSchema, sessionPageDispatch, } from './page-dispatch.ts';
17
+ export { checkRequest, parseLiveClientMessage, parseLiveOpenMessage } from './request-check.ts';
18
+ export { sessionStateStore } from './session-authority.ts';
19
+ export { createTheoremSessionBoundary, type PreparedLiveOpen, type SessionBoundaryOptions, type TheoremSessionBoundary, } from './session-boundary.ts';
20
+ export type { TheoremSessionLimits } from './session-limits.ts';
21
+ export { resolveSessionLimits } from './session-limits.ts';
22
+ export { createMemorySessionStore, DEFAULT_OPERATION_RETENTION_MS, type MemorySessionStoreOptions, type PendingToolGate, type SessionSnapshot, type SessionToolOperation, type SessionWriteResult, type TheoremSessionState, type TheoremSessionStore, theoremSessionStateSchema, } from './session-store.ts';
15
23
  export { createMemorySteerInbox, type SteerInbox, type SteerUnit, steerStage, steerUnitOf, } from './steer-inbox.ts';
16
- export { checkWalkAway, type WalkedAwayCall, walkAway } from './walk-away.ts';
24
+ export { sessionToolCoordinator } from './tool-operations.ts';
25
+ export { kernelTurnInput, liveSessionOpen } from './turn-input.ts';
package/server/mod.js CHANGED
@@ -5,11 +5,19 @@
5
5
  * @module
6
6
  */
7
7
  export { theoremHostCallRequestSchema } from "../client/host-transport.js";
8
- export { theoremInvokeRequestSchema, theoremReplaySchema, theoremSteerRequestSchema, theoremTurnRequestSchema, } from "../client/transport.js";
8
+ export { theoremConversationRequestSchema, theoremInvokeRequestSchema, theoremReplaySchema, theoremRewindRequestSchema, theoremSteerRequestSchema, theoremTurnRequestSchema, } from "../client/transport.js";
9
+ export { answerReply, conversationView, messageReply, openConversation, postedMessage, readerConversationHost, rewindConversation, startConversation, statelessRefusal, waitingCalls, } from "./conversation.js";
10
+ export { createMemoryConversationStore, createSealedConversationStore, emptyConversation, storedConversationSchema, unknownConversation, } from "./conversation-store.js";
9
11
  export { createMemoryCredentialStore, } from "./credential-store.js";
10
12
  export { createTheoremDecisionHandler, } from "./decision-handler.js";
11
- export { createTheoremHandler, createTheoremHostHandler, readBody, theoremSessionId, } from "./handler.js";
12
- export { checkRequest, parseLiveClientMessage } from "./request-check.js";
13
- export { createMemorySessionStore, } from "./session-store.js";
13
+ export { createTheoremEmbeddingHandler, embedBodySchema, embedRequest, } from "./embedding-handler.js";
14
+ export { createTheoremHandler, createTheoremHostHandler, readBody, theoremSession, theoremSessionId, } from "./handler.js";
15
+ export { createMemoryPageToolBroker, pageToolResultSchema, sessionPageDispatch, } from "./page-dispatch.js";
16
+ export { checkRequest, parseLiveClientMessage, parseLiveOpenMessage } from "./request-check.js";
17
+ export { sessionStateStore } from "./session-authority.js";
18
+ export { createTheoremSessionBoundary, } from "./session-boundary.js";
19
+ export { resolveSessionLimits } from "./session-limits.js";
20
+ export { createMemorySessionStore, DEFAULT_OPERATION_RETENTION_MS, theoremSessionStateSchema, } from "./session-store.js";
14
21
  export { createMemorySteerInbox, steerStage, steerUnitOf, } from "./steer-inbox.js";
15
- export { checkWalkAway, walkAway } from "./walk-away.js";
22
+ export { sessionToolCoordinator } from "./tool-operations.js";
23
+ export { kernelTurnInput, liveSessionOpen } from "./turn-input.js";
@@ -0,0 +1,22 @@
1
+ import { type PageAnswer, type PageToolExecution, z } from '@theoremjs/agents';
2
+ import type { MutableStore } from './session-authority.ts';
3
+ import type { TheoremSessionState } from './session-store.ts';
4
+ export type PageToolResult = {
5
+ callId: string;
6
+ operationId: string;
7
+ grantId: string;
8
+ page: PageAnswer;
9
+ };
10
+ export declare const pageToolResultSchema: z.ZodType<PageToolResult>;
11
+ export interface PageToolBroker {
12
+ reserve(sessionId: string, grantId: string, signal?: AbortSignal): {
13
+ result: Promise<PageAnswer>;
14
+ cancel(): void;
15
+ };
16
+ deliver(sessionId: string, grantId: string, page: PageAnswer): Promise<void>;
17
+ }
18
+ export declare function createMemoryPageToolBroker(maxPending?: number): PageToolBroker;
19
+ export declare function sessionPageDispatch(store: MutableStore<TheoremSessionState>, sessionId: string, broker: PageToolBroker, timeoutMs?: number): {
20
+ execution: PageToolExecution;
21
+ answer(result: PageToolResult): Promise<boolean>;
22
+ };
@@ -0,0 +1,127 @@
1
+ import { jsonValueSchema, labelText, TheoremError, toolInvocationHash, z, } from '@theoremjs/agents';
2
+ export const pageToolResultSchema = z.strictObject({
3
+ callId: z.string().min(1),
4
+ operationId: z.string().min(1).max(256),
5
+ grantId: z.string().min(1),
6
+ page: z.union([
7
+ z.strictObject({ output: jsonValueSchema }),
8
+ z.strictObject({ unanswered: z.literal(true) }),
9
+ z.strictObject({ timedOut: z.literal(true) }),
10
+ ]),
11
+ });
12
+ function unavailable() {
13
+ throw new TheoremError('unavailable', labelText('tool.execution_uncertain'), {
14
+ copy: { key: 'tool.execution_uncertain' },
15
+ });
16
+ }
17
+ export function createMemoryPageToolBroker(maxPending = 128) {
18
+ if (!Number.isSafeInteger(maxPending) || maxPending <= 0)
19
+ throw new TheoremError('config', labelText('error.config'));
20
+ const waiting = new Map();
21
+ const key = (sessionId, grantId) => JSON.stringify([sessionId, grantId]);
22
+ return {
23
+ reserve(sessionId, grantId, signal) {
24
+ const id = key(sessionId, grantId);
25
+ if (signal?.aborted || waiting.has(id) || waiting.size >= maxPending)
26
+ unavailable();
27
+ let finish = () => { };
28
+ const result = new Promise((resolve) => {
29
+ finish = resolve;
30
+ });
31
+ const cancel = () => {
32
+ waiting.delete(id);
33
+ signal?.removeEventListener('abort', cancel);
34
+ finish({ timedOut: true });
35
+ };
36
+ waiting.set(id, (answer) => {
37
+ waiting.delete(id);
38
+ signal?.removeEventListener('abort', cancel);
39
+ finish(answer);
40
+ });
41
+ signal?.addEventListener('abort', cancel, { once: true });
42
+ return { result, cancel };
43
+ },
44
+ deliver(sessionId, grantId, page) {
45
+ const finish = waiting.get(key(sessionId, grantId));
46
+ if (!finish)
47
+ return Promise.reject(new TheoremError('unavailable', labelText('tool.execution_uncertain')));
48
+ finish(page);
49
+ return Promise.resolve();
50
+ },
51
+ };
52
+ }
53
+ export function sessionPageDispatch(store, sessionId, broker, timeoutMs = 20_000) {
54
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2147483647)
55
+ throw new TheoremError('config', labelText('error.config'));
56
+ return {
57
+ execution: {
58
+ async dispatch(claim, signal) {
59
+ if (!claim.invocation)
60
+ unavailable();
61
+ const grantId = crypto.randomUUID();
62
+ const expiresAt = Date.now() + timeoutMs;
63
+ const pending = broker.reserve(sessionId, grantId, signal);
64
+ try {
65
+ await store.mutate(sessionId, (state) => {
66
+ const record = state.operations[claim.operationId];
67
+ if (record?.status !== 'running' ||
68
+ record.claimToken !== claim.claimToken ||
69
+ record.invocationHash !== claim.invocationHash ||
70
+ record.pageGrant)
71
+ unavailable();
72
+ record.pageGrant = { grantId, expiresAt };
73
+ });
74
+ }
75
+ catch (error) {
76
+ pending.cancel();
77
+ throw error;
78
+ }
79
+ const remaining = expiresAt - Date.now();
80
+ if (remaining <= 0 || signal?.aborted) {
81
+ pending.cancel();
82
+ unavailable();
83
+ }
84
+ let expire = () => { };
85
+ const expired = new Promise((resolve) => {
86
+ expire = () => resolve({ timedOut: true });
87
+ });
88
+ const timer = setTimeout(expire, remaining);
89
+ signal?.addEventListener('abort', expire, { once: true });
90
+ return {
91
+ grantId,
92
+ result: Promise.race([pending.result, expired]).finally(() => {
93
+ clearTimeout(timer);
94
+ signal?.removeEventListener('abort', expire);
95
+ pending.cancel();
96
+ }),
97
+ };
98
+ },
99
+ },
100
+ async answer(raw) {
101
+ const result = pageToolResultSchema.parse(raw);
102
+ const hash = await toolInvocationHash(result.page);
103
+ const duplicate = await store.mutate(sessionId, (state) => {
104
+ const record = state.operations[result.operationId];
105
+ if (!record ||
106
+ record.status === 'ready' ||
107
+ record.callId !== result.callId ||
108
+ record.pageGrant?.grantId !== result.grantId)
109
+ unavailable();
110
+ if (record.pageGrant.resultHash !== undefined) {
111
+ if (record.pageGrant.resultHash !== hash)
112
+ unavailable();
113
+ if (record.status === 'uncertain')
114
+ unavailable();
115
+ return { duplicate: true, settled: record.status === 'settled' };
116
+ }
117
+ if (record.status !== 'running' || Date.now() >= record.pageGrant.expiresAt)
118
+ unavailable();
119
+ record.pageGrant.resultHash = hash;
120
+ return { duplicate: false, settled: false };
121
+ });
122
+ if (!duplicate.duplicate)
123
+ await broker.deliver(sessionId, result.grantId, result.page);
124
+ return duplicate.settled;
125
+ },
126
+ };
127
+ }
@@ -0,0 +1,2 @@
1
+ export declare function requestByteLimit(bytes?: number): number;
2
+ export declare function boundedRequestJson(request: Request, maxBytes: number): Promise<unknown>;
@@ -0,0 +1,42 @@
1
+ import { labelText, TheoremError, throwIfAborted } from '@theoremjs/agents';
2
+ const DEFAULT_REQUEST_BYTES = 16 * 1024 * 1024;
3
+ export function requestByteLimit(bytes = DEFAULT_REQUEST_BYTES) {
4
+ if (!Number.isSafeInteger(bytes) || bytes <= 0) {
5
+ throw new TheoremError('config', labelText('error.config'));
6
+ }
7
+ return bytes;
8
+ }
9
+ export async function boundedRequestJson(request, maxBytes) {
10
+ if (Number(request.headers.get('content-length')) > maxBytes || !request.body) {
11
+ throw new TheoremError('request', labelText('error.request'));
12
+ }
13
+ const reader = request.body.getReader();
14
+ const abort = () => {
15
+ void reader.cancel(request.signal.reason).catch(() => undefined);
16
+ };
17
+ request.signal.addEventListener('abort', abort, { once: true });
18
+ const decoder = new TextDecoder();
19
+ const text = [];
20
+ let bytes = 0;
21
+ try {
22
+ for (;;) {
23
+ throwIfAborted(request.signal);
24
+ const next = await reader.read();
25
+ throwIfAborted(request.signal);
26
+ if (next.done)
27
+ break;
28
+ bytes += next.value.byteLength;
29
+ if (bytes > maxBytes)
30
+ throw new TheoremError('request', labelText('error.request'));
31
+ if (next.value.byteLength)
32
+ text.push(decoder.decode(next.value, { stream: true }));
33
+ }
34
+ text.push(decoder.decode());
35
+ return JSON.parse(text.join(''));
36
+ }
37
+ finally {
38
+ request.signal.removeEventListener('abort', abort);
39
+ void reader.cancel().catch(() => undefined);
40
+ reader.releaseLock();
41
+ }
42
+ }
@@ -6,7 +6,7 @@
6
6
  * @module
7
7
  */
8
8
  import { type z } from '@theoremjs/agents';
9
- import { type LiveClientMessage } from '../client/live-messages.ts';
9
+ import { type LiveClientMessage, type LiveOpenMessage } from '../client/live-messages.ts';
10
10
  /** `raw` checked against `schema`; a missing or malformed field is a `request` error. */
11
11
  export declare function checkRequest<T>(schema: z.ZodType<T>, raw: unknown, what: string): T;
12
12
  /**
@@ -14,3 +14,8 @@ export declare function checkRequest<T>(schema: z.ZodType<T>, raw: unknown, what
14
14
  * `liveClientMessageSchema`, else a `request` error.
15
15
  */
16
16
  export declare function parseLiveClientMessage(text: string): LiveClientMessage;
17
+ /**
18
+ * The live client's first frame, as a relay reads it before it opens the
19
+ * session: JSON that passes `liveOpenMessageSchema`, else a `request` error.
20
+ */
21
+ export declare function parseLiveOpenMessage(text: string): LiveOpenMessage;
@@ -5,30 +5,45 @@
5
5
  *
6
6
  * @module
7
7
  */
8
- import { TheoremError } from '@theoremjs/agents';
9
- import { liveClientMessageSchema } from "../client/live-messages.js";
8
+ import { labelText, TheoremError } from '@theoremjs/agents';
9
+ import { liveClientMessageSchema, liveOpenMessageSchema, } from "../client/live-messages.js";
10
10
  import { issueSummary } from "../client/wire-line.js";
11
+ import { requestByteLimit } from "./request-body.js";
11
12
  /** `raw` checked against `schema`; a missing or malformed field is a `request` error. */
12
13
  export function checkRequest(schema, raw, what) {
13
14
  const parsed = schema.safeParse(raw);
14
15
  if (!parsed.success) {
15
- // lexicon-exempt: internal diagnostic; the user reads the error kind's (or copy key's) wording
16
+ // labels-exempt: internal diagnostic; the user reads the error kind's (or copy key's) wording
16
17
  throw new TheoremError('request', `${what} failed its check: ${issueSummary(parsed.error)}`);
17
18
  }
18
19
  return parsed.data;
19
20
  }
21
+ function liveFrame(text) {
22
+ const limit = requestByteLimit();
23
+ if (text.length > limit || new TextEncoder().encode(text).byteLength > limit)
24
+ throw new TheoremError('request', labelText('error.request'));
25
+ try {
26
+ return JSON.parse(text);
27
+ }
28
+ catch {
29
+ // invariant: JSON parser diagnostics can contain browser values.
30
+ throw new TheoremError('request', labelText('error.request'), {
31
+ cause: { code: 'request_json' },
32
+ });
33
+ }
34
+ }
20
35
  /**
21
36
  * One text frame from the live client, as a relay reads it: JSON that passes
22
37
  * `liveClientMessageSchema`, else a `request` error.
23
38
  */
24
39
  export function parseLiveClientMessage(text) {
25
- let raw;
26
- try {
27
- raw = JSON.parse(text);
28
- }
29
- catch (cause) {
30
- // lexicon-exempt: internal diagnostic; the user reads the error kind's (or copy key's) wording
31
- throw new TheoremError('request', 'live message must be JSON', { cause });
32
- }
33
- return checkRequest(liveClientMessageSchema, raw, 'live message');
40
+ return checkRequest(liveClientMessageSchema, liveFrame(text), 'live message');
41
+ }
42
+ /**
43
+ * The live client's first frame, as a relay reads it before it opens the
44
+ * session: JSON that passes `liveOpenMessageSchema`, else a `request` error.
45
+ */
46
+ export function parseLiveOpenMessage(text) {
47
+ // labels-exempt: internal diagnostic; the user reads the error kind's wording
48
+ return checkRequest(liveOpenMessageSchema, liveFrame(text), 'live open message');
34
49
  }
@@ -0,0 +1,8 @@
1
+ import { type TheoremSessionLimits } from './session-limits.ts';
2
+ import { type TheoremSessionState, type TheoremSessionStore } from './session-store.ts';
3
+ export type MutableStore<T> = {
4
+ read(sessionId: string): Promise<T>;
5
+ mutate<R>(sessionId: string, change: (value: T) => R | Promise<R>): Promise<R>;
6
+ };
7
+ /** Session state, with expired gates dropped as it loads so they can't be answered. */
8
+ export declare function sessionStateStore(store: TheoremSessionStore, gateTtlMs: number, operationRetentionMs: number, limits: TheoremSessionLimits): MutableStore<TheoremSessionState>;
@@ -0,0 +1,78 @@
1
+ import { labelText, TheoremError, z } from '@theoremjs/agents';
2
+ import { checkSessionLimits } from "./session-limits.js";
3
+ import { emptySessionState, pruneGates, theoremSessionStateSchema, } from "./session-store.js";
4
+ /** Session state, with expired gates dropped as it loads so they can't be answered. */
5
+ export function sessionStateStore(store, gateTtlMs, operationRetentionMs, limits) {
6
+ if (typeof store.compareAndSwap !== 'function') {
7
+ throw new TheoremError('config', labelText('error.config'), {
8
+ cause: { code: 'session_store_contract', required: 'compareAndSwap' },
9
+ });
10
+ }
11
+ const writeResult = z.discriminatedUnion('written', [
12
+ z.strictObject({ written: z.literal(false) }),
13
+ z.strictObject({ written: z.literal(true), revision: z.string().min(1) }),
14
+ ]);
15
+ const load = async (sessionId, now) => {
16
+ const snapshot = await store.load(sessionId);
17
+ const parsed = theoremSessionStateSchema.safeParse(snapshot === undefined ? emptySessionState() : snapshot.state);
18
+ if (!parsed.success ||
19
+ (snapshot !== undefined && (typeof snapshot.revision !== 'string' || !snapshot.revision))) {
20
+ throw new TheoremError('config', labelText('error.config'), {
21
+ cause: { code: 'session_store_state' },
22
+ });
23
+ }
24
+ const state = structuredClone(parsed.data);
25
+ for (const gate of Object.values(state.gates))
26
+ gate.expiresAt ??= gate.createdAt + gateTtlMs;
27
+ state.gates = pruneGates(state.gates, now, gateTtlMs);
28
+ for (const operation of Object.values(state.operations)) {
29
+ if (operation.status === 'ready' && operation.answer.resume.granted === true)
30
+ operation.answer.resume.gateKind ??= operation.pending.gate.kind;
31
+ }
32
+ state.operations = Object.fromEntries(Object.entries(state.operations).filter(([, operation]) => operation.status !== 'settled' ||
33
+ now < (operation.retainUntil ?? operation.settledAt + operationRetentionMs)));
34
+ return { revision: snapshot?.revision ?? null, state };
35
+ };
36
+ return {
37
+ async read(sessionId) {
38
+ return (await load(sessionId, Date.now())).state;
39
+ },
40
+ async mutate(sessionId, change) {
41
+ const now = Date.now();
42
+ for (let conflict = 0; conflict < 8; conflict++) {
43
+ const { revision, state } = await load(sessionId, now);
44
+ const result = await change(state);
45
+ for (const gate of Object.values(state.gates))
46
+ gate.expiresAt ??= gate.createdAt + gateTtlMs;
47
+ for (const operation of Object.values(state.operations)) {
48
+ if (operation.status === 'settled')
49
+ operation.retainUntil ??= operation.settledAt + operationRetentionMs;
50
+ }
51
+ checkSessionLimits(state, limits);
52
+ const next = theoremSessionStateSchema.safeParse(state);
53
+ if (!next.success) {
54
+ throw new TheoremError('config', labelText('error.config'), {
55
+ cause: { code: 'session_store_transition' },
56
+ });
57
+ }
58
+ const written = writeResult.safeParse(await store.compareAndSwap(sessionId, revision, next.data));
59
+ if (!written.success) {
60
+ throw new TheoremError('config', labelText('error.config'), {
61
+ cause: { code: 'session_store_write_result' },
62
+ });
63
+ }
64
+ if (written.data.written) {
65
+ if (written.data.revision === revision) {
66
+ throw new TheoremError('config', labelText('error.config'), {
67
+ cause: { code: 'session_store_revision' },
68
+ });
69
+ }
70
+ return result;
71
+ }
72
+ }
73
+ throw new TheoremError('unavailable', labelText('error.unavailable'), {
74
+ cause: { code: 'session_store_contention' },
75
+ });
76
+ },
77
+ };
78
+ }
@@ -0,0 +1,26 @@
1
+ import { type PageToolExecution, type ProviderCheckpoint, type SessionRequest, type ToolExecutionCoordinator, type TurnHistoryMessage } from '@theoremjs/agents';
2
+ import { type LiveOpenMessage } from '../client/live-messages.ts';
3
+ import { type PageToolBroker, type PageToolResult } from './page-dispatch.ts';
4
+ import { type TheoremSessionLimits } from './session-limits.ts';
5
+ import { type TheoremSessionStore } from './session-store.ts';
6
+ export interface SessionBoundaryOptions {
7
+ request: Request;
8
+ session?: (request: Request) => string | undefined | Promise<string | undefined>;
9
+ sessionStore: TheoremSessionStore;
10
+ gateTtlMs?: number;
11
+ operationRetentionMs?: number;
12
+ sessionLimits?: Partial<TheoremSessionLimits>;
13
+ pageToolBroker?: PageToolBroker;
14
+ }
15
+ export type PreparedLiveOpen = Pick<SessionRequest, 'history' | 'slots' | 'context' | 'providerState' | 'awayMs'>;
16
+ export interface TheoremSessionBoundary {
17
+ sessionId: string;
18
+ setCookie?: string;
19
+ prepareLiveOpen(open: LiveOpenMessage, serverContext?: unknown): Promise<PreparedLiveOpen>;
20
+ prepareProviderState(checkpoint: ProviderCheckpoint | undefined, history?: readonly TurnHistoryMessage[]): Promise<ProviderCheckpoint | undefined>;
21
+ recordCheckpoint(checkpoint: ProviderCheckpoint): Promise<void>;
22
+ toolExecution: ToolExecutionCoordinator;
23
+ pageExecution: PageToolExecution;
24
+ answerPageResult(result: PageToolResult): Promise<boolean>;
25
+ }
26
+ export declare function createTheoremSessionBoundary(options: SessionBoundaryOptions): Promise<TheoremSessionBoundary>;
@@ -0,0 +1,64 @@
1
+ import { labelText, TheoremError, } from '@theoremjs/agents';
2
+ import { resolveGateTtlMs } from '@theoremjs/agents/kernel';
3
+ import { liveOpenMessageSchema } from "../client/live-messages.js";
4
+ import { checkpointIdentity, checkpointOwnershipKey, requireOwnedCheckpoint, retainedCheckpointIdentities, } from "./checkpoint-ownership.js";
5
+ import { createMemoryPageToolBroker, sessionPageDispatch, } from "./page-dispatch.js";
6
+ import { checkRequest } from "./request-check.js";
7
+ import { sessionStateStore } from "./session-authority.js";
8
+ import { theoremSession } from "./session-identity.js";
9
+ import { resolveSessionLimits } from "./session-limits.js";
10
+ import { DEFAULT_OPERATION_RETENTION_MS } from "./session-store.js";
11
+ import { sessionToolCoordinator } from "./tool-operations.js";
12
+ import { liveSessionOpen } from "./turn-input.js";
13
+ export async function createTheoremSessionBoundary(options) {
14
+ const cookie = options.session ? undefined : theoremSession(options.request);
15
+ const id = options.session ? (await options.session(options.request))?.trim() : cookie?.id;
16
+ if (!id)
17
+ throw new TheoremError('auth', labelText('session.sign_in'), {
18
+ copy: { key: 'session.sign_in' },
19
+ });
20
+ const retention = options.operationRetentionMs ?? DEFAULT_OPERATION_RETENTION_MS;
21
+ if (!Number.isSafeInteger(retention) || retention <= 0)
22
+ throw new TheoremError('config', labelText('error.config'));
23
+ const limits = resolveSessionLimits(options.sessionLimits);
24
+ const store = sessionStateStore(options.sessionStore, resolveGateTtlMs('session boundary', options.gateTtlMs), retention, limits);
25
+ // invariant: Refuse unavailable or malformed authority before an upstream connection opens.
26
+ await store.read(id);
27
+ const page = sessionPageDispatch(store, id, options.pageToolBroker ?? createMemoryPageToolBroker());
28
+ return {
29
+ pageExecution: page.execution,
30
+ answerPageResult: page.answer,
31
+ sessionId: id,
32
+ ...(cookie?.setCookie ? { setCookie: cookie.setCookie } : {}),
33
+ toolExecution: sessionToolCoordinator(store, id),
34
+ async prepareLiveOpen(raw, server) {
35
+ const open = checkRequest(liveOpenMessageSchema, raw, 'live open');
36
+ const prepared = liveSessionOpen(open, server);
37
+ const state = await store.read(id);
38
+ const providerState = await requireOwnedCheckpoint(state.providerCheckpoints, prepared.providerState, state.operations);
39
+ if (providerState &&
40
+ Object.values(state.operations).some((operation) => operation.status !== 'settled')) {
41
+ throw new TheoremError('unavailable', labelText('tool.execution_uncertain'), {
42
+ copy: { key: 'tool.execution_uncertain' },
43
+ });
44
+ }
45
+ return {
46
+ ...prepared,
47
+ providerState,
48
+ ...(prepared.history
49
+ ? { history: prepared.history.filter((message) => message.role !== 'system') }
50
+ : {}),
51
+ };
52
+ },
53
+ async prepareProviderState(checkpoint, history) {
54
+ const state = await store.read(id);
55
+ return requireOwnedCheckpoint(state.providerCheckpoints, checkpoint, state.operations, history);
56
+ },
57
+ async recordCheckpoint(checkpoint) {
58
+ const identity = await checkpointIdentity(checkpoint);
59
+ await store.mutate(id, async (state) => {
60
+ state.providerCheckpoints = retainedCheckpointIdentities(state.providerCheckpoints, [await checkpointOwnershipKey(identity, state.operations)], limits.maxCheckpoints);
61
+ });
62
+ },
63
+ };
64
+ }
@@ -0,0 +1,16 @@
1
+ export type Session = {
2
+ id: string;
3
+ setCookie?: string;
4
+ };
5
+ /**
6
+ * The session id the handler issued in its cookie, for a host route outside the
7
+ * handler (an OAuth callback) that saves credentials under it. Hosts that pass
8
+ * a `session` resolver use their own id instead.
9
+ */
10
+ export declare function theoremSessionId(request: Request): string | undefined;
11
+ /**
12
+ * The handler's cookie session for `request`: its id, and the `Set-Cookie` header to send when
13
+ * the request had none. A page that starts a conversation before the chat's first request uses
14
+ * it, so the chat and the conversation share one session.
15
+ */
16
+ export declare function theoremSession(request: Request): Session;
@@ -0,0 +1,41 @@
1
+ import { toBase64Url } from '@theoremjs/agents/kernel';
2
+ const SESSION_COOKIE = 'theorem_session';
3
+ const SESSION_ID_PATTERN = /^[A-Za-z0-9_-]{32,128}$/;
4
+ /** How long a visitor keeps their session, so a chat they come back to opens as theirs: 30 days. */
5
+ const SESSION_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;
6
+ function readCookie(request, name) {
7
+ for (const part of (request.headers.get('cookie') ?? '').split(';')) {
8
+ const [key, ...rest] = part.trim().split('=');
9
+ if (key === name)
10
+ return rest.join('=');
11
+ }
12
+ return undefined;
13
+ }
14
+ function newSessionId() {
15
+ return toBase64Url(crypto.getRandomValues(new Uint8Array(32)));
16
+ }
17
+ /**
18
+ * The session id the handler issued in its cookie, for a host route outside the
19
+ * handler (an OAuth callback) that saves credentials under it. Hosts that pass
20
+ * a `session` resolver use their own id instead.
21
+ */
22
+ export function theoremSessionId(request) {
23
+ const existing = readCookie(request, SESSION_COOKIE);
24
+ return existing && SESSION_ID_PATTERN.test(existing) ? existing : undefined;
25
+ }
26
+ /**
27
+ * The handler's cookie session for `request`: its id, and the `Set-Cookie` header to send when
28
+ * the request had none. A page that starts a conversation before the chat's first request uses
29
+ * it, so the chat and the conversation share one session.
30
+ */
31
+ export function theoremSession(request) {
32
+ const existing = theoremSessionId(request);
33
+ if (existing)
34
+ return { id: existing };
35
+ const id = newSessionId();
36
+ const secure = new URL(request.url).protocol === 'https:' ? '; Secure' : '';
37
+ return {
38
+ id,
39
+ setCookie: `${SESSION_COOKIE}=${id}; Path=/; Max-Age=${SESSION_MAX_AGE_SECONDS}; HttpOnly; SameSite=Lax${secure}`,
40
+ };
41
+ }
@@ -0,0 +1,12 @@
1
+ import type { TheoremSessionState } from './session-store.ts';
2
+ export interface TheoremSessionLimits {
3
+ maxGates: number;
4
+ maxOperations: number;
5
+ maxCheckpoints: number;
6
+ maxPermissions: number;
7
+ maxStateBytes: number;
8
+ maxInvocationBytes: number;
9
+ maxOutcomeBytes: number;
10
+ }
11
+ export declare function resolveSessionLimits(options?: Partial<TheoremSessionLimits>): TheoremSessionLimits;
12
+ export declare function checkSessionLimits(state: TheoremSessionState, limits: TheoremSessionLimits): void;
@@ -0,0 +1,59 @@
1
+ import { labelText, TheoremError } from '@theoremjs/agents';
2
+ const DEFAULTS = {
3
+ maxGates: 32,
4
+ maxOperations: 128,
5
+ maxCheckpoints: 256,
6
+ maxPermissions: 2048,
7
+ maxStateBytes: 32 * 1024 * 1024,
8
+ maxInvocationBytes: 256 * 1024,
9
+ maxOutcomeBytes: 256 * 1024,
10
+ };
11
+ export function resolveSessionLimits(options = {}) {
12
+ const limits = { ...DEFAULTS, ...options };
13
+ if (Object.keys(options).some((key) => !Object.hasOwn(DEFAULTS, key)) ||
14
+ Object.values(limits).some((value) => !Number.isSafeInteger(value) || value <= 0)) {
15
+ throw new TheoremError('config', labelText('error.config'), {
16
+ cause: { code: 'session_limits' },
17
+ });
18
+ }
19
+ return limits;
20
+ }
21
+ function bytes(value) {
22
+ return new TextEncoder().encode(JSON.stringify(value)).byteLength;
23
+ }
24
+ function capacity() {
25
+ throw new TheoremError('unavailable', labelText('error.unavailable'), {
26
+ cause: { code: 'session_state_capacity' },
27
+ });
28
+ }
29
+ export function checkSessionLimits(state, limits) {
30
+ const gates = Object.values(state.gates);
31
+ const operations = Object.values(state.operations);
32
+ if (gates.length > limits.maxGates ||
33
+ operations.length > limits.maxOperations ||
34
+ state.providerCheckpoints.length > limits.maxCheckpoints ||
35
+ state.permissions.length > limits.maxPermissions)
36
+ capacity();
37
+ for (const gate of gates)
38
+ if (bytes(gate) > limits.maxInvocationBytes)
39
+ capacity();
40
+ let reserved = gates.length * limits.maxOutcomeBytes;
41
+ for (const operation of operations) {
42
+ if (operation.status !== 'ready' &&
43
+ operation.invocation &&
44
+ bytes(operation.invocation) > limits.maxInvocationBytes)
45
+ capacity();
46
+ if (operation.status === 'settled') {
47
+ if (bytes(operation.outcome) > limits.maxOutcomeBytes)
48
+ capacity();
49
+ }
50
+ else {
51
+ reserved += limits.maxOutcomeBytes;
52
+ if (operation.status === 'ready' && bytes(operation) > limits.maxInvocationBytes)
53
+ capacity();
54
+ }
55
+ }
56
+ // invariant: Reserve terminal evidence before dispatch; unresolved records are never evicted to make room.
57
+ if (bytes(state) + reserved > limits.maxStateBytes)
58
+ capacity();
59
+ }