@theoremjs/react 0.3.0 → 0.3.2

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 (260) hide show
  1. package/README.md +746 -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 +64 -20
  128. package/server/handler.js +455 -248
  129. package/server/mcp-handler.d.ts +46 -0
  130. package/server/mcp-handler.js +312 -0
  131. package/server/mod.d.ts +17 -7
  132. package/server/mod.js +15 -6
  133. package/server/page-dispatch.d.ts +22 -0
  134. package/server/page-dispatch.js +127 -0
  135. package/server/request-body.d.ts +2 -0
  136. package/server/request-body.js +42 -0
  137. package/server/request-check.d.ts +6 -1
  138. package/server/request-check.js +27 -12
  139. package/server/session-authority.d.ts +8 -0
  140. package/server/session-authority.js +78 -0
  141. package/server/session-boundary.d.ts +26 -0
  142. package/server/session-boundary.js +64 -0
  143. package/server/session-identity.d.ts +16 -0
  144. package/server/session-identity.js +41 -0
  145. package/server/session-limits.d.ts +12 -0
  146. package/server/session-limits.js +59 -0
  147. package/server/session-store.d.ts +69 -20
  148. package/server/session-store.js +220 -5
  149. package/server/single-action.d.ts +21 -0
  150. package/server/single-action.js +30 -0
  151. package/server/steer-inbox.js +1 -1
  152. package/server/tool-operations.d.ts +6 -0
  153. package/server/tool-operations.js +132 -0
  154. package/server/turn-input.d.ts +10 -0
  155. package/server/turn-input.js +29 -0
  156. package/styles/ink-controls.css +78 -78
  157. package/ui/CardWall.d.ts +10 -0
  158. package/ui/CardWall.js +61 -0
  159. package/ui/ChatComposerBar.d.ts +28 -5
  160. package/ui/ChatComposerBar.js +85 -30
  161. package/ui/ChatTranscript.d.ts +14 -1
  162. package/ui/ChatTranscript.js +110 -57
  163. package/ui/ConsoleFrame.d.ts +61 -4
  164. package/ui/ConsoleFrame.js +109 -9
  165. package/ui/DecisionAnswers.d.ts +40 -2
  166. package/ui/DecisionAnswers.js +166 -35
  167. package/ui/DecisionImages.d.ts +16 -0
  168. package/ui/DecisionImages.js +46 -0
  169. package/ui/EmbeddingDocuments.d.ts +78 -0
  170. package/ui/EmbeddingDocuments.js +278 -0
  171. package/ui/EmbeddingQuery.d.ts +27 -0
  172. package/ui/EmbeddingQuery.js +95 -0
  173. package/ui/EmbeddingResults.d.ts +41 -0
  174. package/ui/EmbeddingResults.js +165 -0
  175. package/ui/EmbeddingSpace.d.ts +26 -0
  176. package/ui/EmbeddingSpace.js +106 -0
  177. package/ui/LiveCaptionsPanel.d.ts +22 -0
  178. package/ui/LiveCaptionsPanel.js +27 -0
  179. package/ui/LiveRunner.d.ts +6 -4
  180. package/ui/LiveRunner.js +42 -55
  181. package/ui/PaneState.d.ts +33 -0
  182. package/ui/PaneState.js +50 -0
  183. package/ui/SchemaFields.d.ts +1 -1
  184. package/ui/SchemaFields.js +54 -26
  185. package/ui/ShapedData.js +73 -28
  186. package/ui/SidePanel.d.ts +74 -5
  187. package/ui/SidePanel.js +82 -9
  188. package/ui/StateFields.d.ts +10 -2
  189. package/ui/StateFields.js +56 -13
  190. package/ui/TheoremChat.d.ts +65 -14
  191. package/ui/TheoremChat.js +66 -78
  192. package/ui/TheoremDecision.d.ts +12 -20
  193. package/ui/TheoremDecision.js +78 -35
  194. package/ui/TheoremEmbedding.d.ts +27 -0
  195. package/ui/TheoremEmbedding.js +122 -0
  196. package/ui/TheoremHost.d.ts +22 -5
  197. package/ui/TheoremHost.js +127 -37
  198. package/ui/ToolGateCard.d.ts +5 -1
  199. package/ui/ToolGateCard.js +30 -23
  200. package/ui/ToolResult.js +12 -10
  201. package/ui/ToolResultCharts.js +29 -12
  202. package/ui/TraceCharts.d.ts +2 -2
  203. package/ui/TraceCharts.js +48 -17
  204. package/ui/TraceGuardrails.d.ts +3 -2
  205. package/ui/TraceGuardrails.js +24 -6
  206. package/ui/TraceInspector.d.ts +11 -1
  207. package/ui/TraceInspector.js +45 -28
  208. package/ui/TraceInspectorPanel.d.ts +31 -9
  209. package/ui/TraceInspectorPanel.js +77 -39
  210. package/ui/TraceOverview.d.ts +7 -3
  211. package/ui/TraceOverview.js +44 -14
  212. package/ui/TraceSpanDetail.js +24 -22
  213. package/ui/TraceStory.js +9 -8
  214. package/ui/TraceValues.js +19 -9
  215. package/ui/TraceWaterfall.js +12 -9
  216. package/ui/VoiceNote.js +4 -4
  217. package/ui/approval-heading.d.ts +4 -0
  218. package/ui/approval-heading.js +9 -0
  219. package/ui/arrive.d.ts +18 -0
  220. package/ui/arrive.js +46 -0
  221. package/ui/built/theme.css +86 -68
  222. package/ui/built/theme.css.d.ts +9 -0
  223. package/ui/built/theorem.d.ts +3 -3
  224. package/ui/built/theorem.js +34 -5
  225. package/ui/built/theorem.variants.d.ts +8 -2
  226. package/ui/code-language.d.ts +12 -0
  227. package/ui/code-language.js +50 -0
  228. package/ui/disclosure-motion.js +53 -9
  229. package/ui/embedding-space-gl.d.ts +37 -0
  230. package/ui/embedding-space-gl.js +663 -0
  231. package/ui/embedding-space-view.d.ts +8 -0
  232. package/ui/embedding-space-view.js +8 -0
  233. package/ui/glide.d.ts +7 -0
  234. package/ui/glide.js +44 -0
  235. package/ui/icons.js +2 -1
  236. package/ui/index.d.ts +20 -9
  237. package/ui/index.js +20 -10
  238. package/ui/labels-provider.js +5 -2
  239. package/ui/labels.d.ts +374 -85
  240. package/ui/labels.js +1028 -188
  241. package/ui/markdown-plugins.d.ts +9 -0
  242. package/ui/markdown-plugins.js +13 -0
  243. package/ui/mcp-icon.d.ts +6 -0
  244. package/ui/mcp-icon.js +8 -0
  245. package/ui/row-keys.d.ts +19 -0
  246. package/ui/row-keys.js +33 -0
  247. package/ui/theme.d.ts +1 -1
  248. package/ui/theme.js +2 -2
  249. package/ui/theorem-theme.js +32 -10
  250. package/ui/trace-palette.d.ts +2 -16
  251. package/ui/trace-palette.js +2 -16
  252. package/ui/trace-patterns.d.ts +11 -3
  253. package/ui/trace-patterns.js +16 -3
  254. package/ui/transcript-copy-text.js +1 -0
  255. package/ui/wall-columns.d.ts +8 -0
  256. package/ui/wall-columns.js +19 -0
  257. package/client/walk-away.d.ts +0 -34
  258. package/client/walk-away.js +0 -48
  259. package/server/walk-away.d.ts +0 -22
  260. package/server/walk-away.js +0 -41
@@ -8,18 +8,191 @@
8
8
  *
9
9
  * @module
10
10
  */
11
+ import { jsonObjectSchema, jsonValueSchema, labelText, savedToolInvocationSchema, TheoremError, TOOL_AUTH_TYPES, TOOL_GATE_KINDS, TOOL_PERMISSION, toolOperationOutcomeSchema, turnHistoryMessageSchema, turnToolSnapshotSchema, z, } from '@theoremjs/agents';
11
12
  import { gateExpired } from '@theoremjs/agents/kernel';
13
+ import { checkSessionLimits, resolveSessionLimits, } from "./session-limits.js";
14
+ export const DEFAULT_OPERATION_RETENTION_MS = 24 * 60 * 60 * 1000;
15
+ const blobSchema = z.strictObject({
16
+ data: z.string(),
17
+ mimeType: z.string(),
18
+ name: z.string().optional(),
19
+ });
20
+ const inputSchema = z.strictObject({
21
+ text: z.string().optional(),
22
+ role: z.string().optional(),
23
+ slots: z.record(z.string(), z.string()).optional(),
24
+ context: z
25
+ .strictObject({ client: jsonValueSchema.optional(), server: jsonValueSchema.optional() })
26
+ .optional(),
27
+ attachments: z
28
+ .array(z.union([
29
+ blobSchema,
30
+ z.strictObject({
31
+ uri: z.string(),
32
+ mimeType: z.string(),
33
+ name: z.string().optional(),
34
+ }),
35
+ ]))
36
+ .optional(),
37
+ voice: z.array(blobSchema).optional(),
38
+ history: z.array(turnHistoryMessageSchema).optional(),
39
+ repair: z
40
+ .strictObject({ previousOutput: z.string(), rejection: z.string(), guidance: z.string() })
41
+ .optional(),
42
+ historyTokens: z.number().nonnegative().optional(),
43
+ inputTokens: z.number().nonnegative().optional(),
44
+ });
45
+ const pendingSchema = z.strictObject({
46
+ profileId: z.string().min(1),
47
+ operationId: z.string().min(1).max(256),
48
+ callId: z.string().min(1),
49
+ name: z.string().min(1),
50
+ arguments: jsonObjectSchema,
51
+ gate: z
52
+ .strictObject({
53
+ kind: z.enum(TOOL_GATE_KINDS),
54
+ permission: z.enum(TOOL_PERMISSION).optional(),
55
+ auth: z
56
+ .strictObject({
57
+ slot: z.string().min(1),
58
+ authType: z.enum(TOOL_AUTH_TYPES),
59
+ service: z.string().min(1),
60
+ })
61
+ .optional(),
62
+ })
63
+ .superRefine((gate, ctx) => {
64
+ if ((gate.kind === 'auth') !== (gate.auth !== undefined)) {
65
+ ctx.addIssue({ code: 'custom', path: ['auth'] });
66
+ }
67
+ }),
68
+ snapshot: turnToolSnapshotSchema.optional(),
69
+ promoted: z.array(z.string().min(1)),
70
+ turnInput: inputSchema,
71
+ model: z.string().optional(),
72
+ createdAt: z.number().finite().nonnegative(),
73
+ expiresAt: z.number().finite().nonnegative().optional(),
74
+ });
75
+ const acceptedAnswerSchema = z.strictObject({
76
+ input: jsonObjectSchema,
77
+ resume: z.strictObject({
78
+ granted: z.boolean().optional(),
79
+ gateKind: z.enum(TOOL_GATE_KINDS).optional(),
80
+ cause: z.enum(['declined', 'abandoned', 'expired']).optional(),
81
+ signIn: z.boolean().optional(),
82
+ edited: z.strictObject({ from: jsonObjectSchema }).optional(),
83
+ }),
84
+ sessionPermissions: z.array(z.string().min(1)),
85
+ page: z
86
+ .union([
87
+ z.strictObject({ output: jsonValueSchema }),
88
+ z.strictObject({ timedOut: z.literal(true) }),
89
+ z.strictObject({ unanswered: z.literal(true) }),
90
+ ])
91
+ .optional(),
92
+ });
93
+ const identityFields = {
94
+ operationId: z.string().min(1).max(256),
95
+ callId: z.string().min(1),
96
+ profileId: z.string().min(1),
97
+ toolId: z.string().min(1),
98
+ invocationHash: z.string().min(1),
99
+ invocation: savedToolInvocationSchema.optional(),
100
+ };
101
+ const pageGrant = z.strictObject({
102
+ grantId: z.string().min(1),
103
+ expiresAt: z.number().finite().positive(),
104
+ resultHash: z.string().optional(),
105
+ });
106
+ const operationSchema = z.discriminatedUnion('status', [
107
+ z.strictObject({
108
+ status: z.literal('ready'),
109
+ profileId: z.string().min(1),
110
+ pending: pendingSchema,
111
+ answer: acceptedAnswerSchema,
112
+ readyAt: z.number().finite().nonnegative(),
113
+ }),
114
+ z.strictObject({
115
+ ...identityFields,
116
+ status: z.literal('running'),
117
+ pageGrant: pageGrant.optional(),
118
+ claimToken: z.string().min(1),
119
+ startedAt: z.number().finite().nonnegative(),
120
+ }),
121
+ z.strictObject({
122
+ ...identityFields,
123
+ status: z.literal('settled'),
124
+ pageGrant: pageGrant.optional(),
125
+ outcome: toolOperationOutcomeSchema,
126
+ settledAt: z.number().finite().nonnegative(),
127
+ retainUntil: z.number().finite().nonnegative().optional(),
128
+ }),
129
+ z.strictObject({
130
+ ...identityFields,
131
+ status: z.literal('uncertain'),
132
+ pageGrant: pageGrant.optional(),
133
+ claimToken: z.string().min(1),
134
+ reason: z.enum(['execution_interrupted', 'settlement_write_failed', 'worker_lost']),
135
+ detectedAt: z.number().finite().nonnegative(),
136
+ }),
137
+ ]);
138
+ /** Stored authority is checked before the handler reads or changes it. */
139
+ export const theoremSessionStateSchema = z
140
+ .strictObject({
141
+ version: z.literal(3),
142
+ operations: z.record(z.string().min(1), operationSchema),
143
+ permissions: z.array(z.string().min(1)),
144
+ gates: z.record(z.string().min(1), pendingSchema),
145
+ providerCheckpoints: z.array(z.string().min(1)),
146
+ })
147
+ .superRefine((state, ctx) => {
148
+ for (const [operationId, operation] of Object.entries(state.operations)) {
149
+ const reference = operation.status === 'ready' ? operation.pending : operation;
150
+ if (reference.operationId !== operationId ||
151
+ (operation.status === 'ready' && operation.profileId !== operation.pending.profileId) ||
152
+ operationId in state.gates ||
153
+ (operation.status === 'settled' &&
154
+ (operation.outcome.event.tool.operationId !== operationId ||
155
+ operation.outcome.event.tool.callId !== operation.callId ||
156
+ operation.outcome.event.tool.name !== operation.toolId))) {
157
+ ctx.addIssue({ code: 'custom', path: ['operations', operationId] });
158
+ }
159
+ }
160
+ for (const [operationId, gate] of Object.entries(state.gates)) {
161
+ if (operationId !== gate.operationId)
162
+ ctx.addIssue({ code: 'custom', path: ['gates', operationId] });
163
+ }
164
+ });
12
165
  export function emptySessionState() {
13
- return { permissions: [], gates: {}, settled: {}, interactions: [] };
166
+ return {
167
+ version: 3,
168
+ operations: {},
169
+ permissions: [],
170
+ gates: {},
171
+ providerCheckpoints: [],
172
+ };
14
173
  }
15
174
  /** Drop gates (waiting or settled) older than `ttlMs`. */
16
175
  export function pruneGates(gates, now, ttlMs) {
17
- return Object.fromEntries(Object.entries(gates).filter(([, gate]) => !gateExpired(gate.createdAt, now, ttlMs)));
176
+ return Object.fromEntries(Object.entries(gates).filter(([, gate]) => gate.expiresAt !== undefined
177
+ ? now < gate.expiresAt
178
+ : !gateExpired(gate.createdAt, now, ttlMs)));
18
179
  }
19
- /** A per-session value kept in process memory: forgotten when idle, oldest evicted first. */
20
- export function createMemorySessionMap(options = {}) {
180
+ export function memoryStoreLimits(options) {
21
181
  const ttlMs = options.ttlMs ?? 24 * 60 * 60 * 1000;
22
182
  const maxSessions = options.maxSessions ?? 10_000;
183
+ if (!Number.isFinite(ttlMs) ||
184
+ ttlMs <= 0 ||
185
+ !Number.isSafeInteger(maxSessions) ||
186
+ maxSessions <= 0) {
187
+ throw new TheoremError('config', labelText('error.config'), {
188
+ cause: { code: 'session_store_options', ttlMs, maxSessions },
189
+ });
190
+ }
191
+ return { ttlMs, maxSessions };
192
+ }
193
+ /** A per-session value kept in process memory: forgotten when idle, oldest evicted first. */
194
+ export function createMemorySessionMap(options = {}) {
195
+ const { ttlMs, maxSessions } = memoryStoreLimits(options);
23
196
  const sessions = new Map();
24
197
  return {
25
198
  load(sessionId) {
@@ -45,5 +218,47 @@ export function createMemorySessionMap(options = {}) {
45
218
  };
46
219
  }
47
220
  export function createMemorySessionStore(options = {}) {
48
- return createMemorySessionMap(options);
221
+ const { ttlMs, maxSessions } = memoryStoreLimits(options);
222
+ const limits = resolveSessionLimits(options.sessionLimits);
223
+ const sessions = new Map();
224
+ const held = (entry) => Object.values(entry.snapshot.state.gates).some((gate) => gate.expiresAt === undefined || Date.now() < gate.expiresAt) ||
225
+ Object.values(entry.snapshot.state.operations).some((operation) => operation.status !== 'settled' ||
226
+ Date.now() <
227
+ (operation.retainUntil ?? operation.settledAt + DEFAULT_OPERATION_RETENTION_MS));
228
+ const current = (sessionId) => {
229
+ const entry = sessions.get(sessionId);
230
+ if (entry && !held(entry) && Date.now() - entry.touched >= ttlMs) {
231
+ sessions.delete(sessionId);
232
+ return undefined;
233
+ }
234
+ return entry?.snapshot;
235
+ };
236
+ return {
237
+ load(sessionId) {
238
+ return Promise.resolve(structuredClone(current(sessionId)));
239
+ },
240
+ compareAndSwap(sessionId, expectedRevision, next) {
241
+ const snapshot = current(sessionId);
242
+ if ((snapshot?.revision ?? null) !== expectedRevision)
243
+ return Promise.resolve({ written: false });
244
+ const state = theoremSessionStateSchema.parse(next);
245
+ checkSessionLimits(state, limits);
246
+ if (!snapshot && sessions.size >= maxSessions) {
247
+ const removable = [...sessions].find(([, entry]) => !held(entry));
248
+ if (!removable) {
249
+ throw new TheoremError('unavailable', labelText('error.unavailable'), {
250
+ cause: { code: 'session_store_capacity' },
251
+ });
252
+ }
253
+ sessions.delete(removable[0]);
254
+ }
255
+ const revision = crypto.randomUUID();
256
+ sessions.delete(sessionId);
257
+ sessions.set(sessionId, {
258
+ snapshot: { revision, state: structuredClone(state) },
259
+ touched: Date.now(),
260
+ });
261
+ return Promise.resolve({ written: true, revision });
262
+ },
263
+ };
49
264
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The shape the decision and embedding handlers share: `GET <base>` answers the profile's
3
+ * interface, `POST <base>/<action>` answers `{ result }`, and a failure is worded with the
4
+ * profile's labels.
5
+ *
6
+ * @module
7
+ */
8
+ import { type ProfileDefinition } from '@theoremjs/agents';
9
+ import { type ClientTurnOptions } from '@theoremjs/agents/host';
10
+ export type SingleActionOptions = {
11
+ /** The last path segment the POST is sent to, such as `decide`. */
12
+ action: string;
13
+ /** What `GET <base>` answers, as `{ interface }`. */
14
+ iface: unknown;
15
+ labels: ProfileDefinition['labels'];
16
+ clientEvents?: ClientTurnOptions;
17
+ onError?: (err: unknown, ctx: {
18
+ request: Request;
19
+ }) => void;
20
+ };
21
+ export declare function singleActionHandler({ action, iface, labels, clientEvents, onError }: SingleActionOptions, run: (request: Request) => Promise<unknown>): (request: Request) => Promise<Response>;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The shape the decision and embedding handlers share: `GET <base>` answers the profile's
3
+ * interface, `POST <base>/<action>` answers `{ result }`, and a failure is worded with the
4
+ * profile's labels.
5
+ *
6
+ * @module
7
+ */
8
+ import { labelText } from '@theoremjs/agents';
9
+ import { caughtStatus, clientError, HTTP_METHOD, } from '@theoremjs/agents/host';
10
+ import { jsonResponse } from "./json-response.js";
11
+ export function singleActionHandler({ action, iface, labels, clientEvents, onError }, run) {
12
+ const isAction = (request) => new URL(request.url).pathname.split('/').filter(Boolean).at(-1) === action;
13
+ return async (request) => {
14
+ try {
15
+ if (request.method === 'GET' && !isAction(request))
16
+ return jsonResponse(200, { interface: iface });
17
+ if (request.method !== 'POST' || !isAction(request)) {
18
+ return jsonResponse(HTTP_METHOD, {
19
+ error: labelText('error.request', {}, labels),
20
+ errorKind: 'request',
21
+ });
22
+ }
23
+ return jsonResponse(200, { result: await run(request) });
24
+ }
25
+ catch (err) {
26
+ onError?.(err, { request });
27
+ return jsonResponse(caughtStatus(err), clientError(err, labels, clientEvents));
28
+ }
29
+ };
30
+ }
@@ -14,7 +14,7 @@ const STEER_STAGES = new Set(['pre_turn', 'post_tool', 'before_end']);
14
14
  */
15
15
  export function steerUnitOf(steer) {
16
16
  const messages = steer.inject.filter((message) => message.role === 'user');
17
- // lexicon-exempt: internal diagnostic; the user reads the error kind's (or copy key's) wording
17
+ // labels-exempt: internal diagnostic; the user reads the error kind's (or copy key's) wording
18
18
  if (!messages.length)
19
19
  throw new TheoremError('request', 'inject must contain user messages');
20
20
  return { id: steer.id, messages };
@@ -0,0 +1,6 @@
1
+ import { type ToolExecutionCoordinator } from '@theoremjs/agents';
2
+ import type { TheoremSessionState } from './session-store.ts';
3
+ export declare function sessionToolCoordinator(store: {
4
+ mutate<T>(id: string, change: (state: TheoremSessionState) => T): Promise<T>;
5
+ }, sessionId: string): ToolExecutionCoordinator;
6
+ export declare function sameAcceptedAnswer(left: unknown, right: unknown): boolean;
@@ -0,0 +1,132 @@
1
+ import { labelText, TheoremError, } from '@theoremjs/agents';
2
+ function unavailable() {
3
+ throw new TheoremError('unavailable', labelText('error.unavailable'));
4
+ }
5
+ function sameOperation(record, identity) {
6
+ return (record.operationId === identity.operationId &&
7
+ record.callId === identity.callId &&
8
+ record.profileId === identity.profileId &&
9
+ record.toolId === identity.toolId &&
10
+ record.invocationHash === identity.invocationHash);
11
+ }
12
+ function checkedRecord(state, identity) {
13
+ if (identity.operationId === '__proto__')
14
+ unavailable();
15
+ const record = Object.hasOwn(state.operations, identity.operationId)
16
+ ? state.operations[identity.operationId]
17
+ : undefined;
18
+ if (record?.status === 'ready') {
19
+ if (record.pending.callId !== identity.callId ||
20
+ record.pending.profileId !== identity.profileId ||
21
+ record.pending.name !== identity.toolId ||
22
+ record.profileId !== identity.profileId)
23
+ unavailable();
24
+ }
25
+ else if (record && !sameOperation(record, identity))
26
+ unavailable();
27
+ return record;
28
+ }
29
+ function checkedClaim(state, claim) {
30
+ const record = checkedRecord(state, claim);
31
+ if (!record ||
32
+ (record.status !== 'running' && record.status !== 'uncertain') ||
33
+ record.claimToken !== claim.claimToken)
34
+ unavailable();
35
+ return record;
36
+ }
37
+ function existingResult(record) {
38
+ if (record?.status === 'settled')
39
+ return { kind: 'settled', outcome: record.outcome };
40
+ if (record?.status === 'running')
41
+ return { kind: 'running' };
42
+ if (record?.status === 'uncertain')
43
+ return { kind: 'uncertain' };
44
+ return undefined;
45
+ }
46
+ function saveOutcome(state, identity, outcome) {
47
+ const before = state.operations[identity.operationId];
48
+ const pageGrant = before && before.status !== 'ready' ? before.pageGrant : undefined;
49
+ const settledAt = Date.now();
50
+ const { operationId, callId, profileId, toolId, invocationHash, invocation } = identity;
51
+ state.operations[identity.operationId] = {
52
+ operationId,
53
+ callId,
54
+ profileId,
55
+ toolId,
56
+ invocationHash,
57
+ ...(invocation ? { invocation } : {}),
58
+ status: 'settled',
59
+ ...(pageGrant ? { pageGrant } : {}),
60
+ outcome,
61
+ settledAt,
62
+ };
63
+ }
64
+ export function sessionToolCoordinator(store, sessionId) {
65
+ return {
66
+ lookup(identity) {
67
+ return store.mutate(sessionId, (state) => {
68
+ const record = checkedRecord(state, identity);
69
+ return existingResult(record);
70
+ });
71
+ },
72
+ begin(identity) {
73
+ const claimToken = crypto.randomUUID();
74
+ const startedAt = Date.now();
75
+ return store.mutate(sessionId, (state) => {
76
+ const record = checkedRecord(state, identity);
77
+ const existing = existingResult(record);
78
+ if (existing)
79
+ return existing;
80
+ state.operations[identity.operationId] = {
81
+ ...identity,
82
+ status: 'running',
83
+ claimToken,
84
+ startedAt,
85
+ };
86
+ return { kind: 'acquired', claimToken };
87
+ });
88
+ },
89
+ settle(claim, outcome) {
90
+ return store.mutate(sessionId, (state) => {
91
+ const record = checkedClaim(state, claim);
92
+ saveOutcome(state, { ...claim, invocation: record.invocation }, outcome);
93
+ });
94
+ },
95
+ settleNotStarted(identity, outcome) {
96
+ return store.mutate(sessionId, (state) => {
97
+ const record = checkedRecord(state, identity);
98
+ if (record && record.status !== 'ready')
99
+ unavailable();
100
+ if (outcome.effects !== 'not_started')
101
+ unavailable();
102
+ saveOutcome(state, identity, outcome);
103
+ });
104
+ },
105
+ markUncertain(claim, reason) {
106
+ return store.mutate(sessionId, (state) => {
107
+ const record = checkedClaim(state, claim);
108
+ state.operations[claim.operationId] = {
109
+ ...claim,
110
+ invocation: record.invocation,
111
+ ...(record.pageGrant ? { pageGrant: record.pageGrant } : {}),
112
+ status: 'uncertain',
113
+ reason,
114
+ detectedAt: Date.now(),
115
+ };
116
+ });
117
+ },
118
+ };
119
+ }
120
+ export function sameAcceptedAnswer(left, right) {
121
+ const canonical = (value) => {
122
+ if (Array.isArray(value))
123
+ return value.map(canonical);
124
+ if (value && typeof value === 'object')
125
+ return Object.fromEntries(Object.entries(value)
126
+ .filter(([, item]) => item !== undefined)
127
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
128
+ .map(([key, item]) => [key, canonical(item)]));
129
+ return value;
130
+ };
131
+ return JSON.stringify(canonical(left)) === JSON.stringify(canonical(right));
132
+ }
@@ -0,0 +1,10 @@
1
+ import type { SessionRequest, TurnInput } from '@theoremjs/agents';
2
+ import type { LiveOpenMessage } from '../client/live-messages.ts';
3
+ import type { TheoremTurnInput } from '../client/transport.ts';
4
+ /** A turn input off the wire as the kernel takes it: the page's context is the client's. */
5
+ export declare function kernelTurnInput(input: TheoremTurnInput, server?: unknown): TurnInput;
6
+ /**
7
+ * What a live call's first message asks of the session, as `runSession` takes
8
+ * it: the page's context is the client's, and `server` is the host's own.
9
+ */
10
+ export declare function liveSessionOpen(open: LiveOpenMessage, server?: unknown): Pick<SessionRequest, 'slots' | 'context' | 'providerState' | 'awayMs' | 'history'>;
@@ -0,0 +1,29 @@
1
+ /** The page's context as the client's and the host's as the server's; nothing when neither gave any. */
2
+ function turnContext(client, server) {
3
+ const context = {
4
+ ...(client === undefined ? {} : { client }),
5
+ ...(server === undefined ? {} : { server }),
6
+ };
7
+ return Object.keys(context).length > 0 ? context : undefined;
8
+ }
9
+ /** A turn input off the wire as the kernel takes it: the page's context is the client's. */
10
+ export function kernelTurnInput(input, server) {
11
+ const { context: client, ...rest } = input;
12
+ const context = turnContext(client, server);
13
+ return context ? { ...rest, context } : rest;
14
+ }
15
+ /**
16
+ * What a live call's first message asks of the session, as `runSession` takes
17
+ * it: the page's context is the client's, and `server` is the host's own.
18
+ */
19
+ export function liveSessionOpen(open, server) {
20
+ const context = turnContext(open.context, server);
21
+ return {
22
+ ...(open.history ? { history: open.history } : {}),
23
+ ...(open.slots ? { slots: open.slots } : {}),
24
+ ...(context ? { context } : {}),
25
+ ...(open.resume
26
+ ? { providerState: open.resume.providerState, awayMs: open.resume.awayMs }
27
+ : {}),
28
+ };
29
+ }