@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,389 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The index as a space: each document a point in a ball, placed by the
|
|
3
|
+
* three directions its vectors differ most along, each measured in its own
|
|
4
|
+
* spread so the space is as deep as it is wide. Its direction from the
|
|
5
|
+
* centre is its direction along those three; its distance is how far it sits
|
|
6
|
+
* from the index's mean along them, as `r / (r + median)`, scaled so the
|
|
7
|
+
* farthest document is on the rim: the order of distances is kept, and no
|
|
8
|
+
* one far document squeezes the rest into the centre. A pair is alike when
|
|
9
|
+
* its cosine similarity stands out against every other pair in the index:
|
|
10
|
+
* more than {@link OUTLIER} robust deviations above the median pair. Most
|
|
11
|
+
* pairs in an index are unrelated, so the median and its deviation say what
|
|
12
|
+
* unrelated looks like for this model and these documents. Each document is
|
|
13
|
+
* linked to its nearest neighbour, the one its cosine similarity is highest
|
|
14
|
+
* with, when that pair is alike. A cluster is documents alike pairs connect,
|
|
15
|
+
* directly or through each other. Positions are a projection and only
|
|
16
|
+
* approximate; links and clusters are the vectors' own cosine similarity.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
/** Power-iteration rounds per axis; the Gram matrix is small, so this converges well inside it. */
|
|
21
|
+
const ROUNDS = 200;
|
|
22
|
+
/** Where a point lands when the projection says nothing about it. */
|
|
23
|
+
const CENTRE = [0, 0, 0];
|
|
24
|
+
/** How many robust deviations above the median pair a pair must be to count as alike: the usual cut for an outlier. */
|
|
25
|
+
const OUTLIER = 3;
|
|
26
|
+
/** Scales the median absolute deviation to a standard deviation, for normally spread values. */
|
|
27
|
+
const MAD_SCALE = 1.4826;
|
|
28
|
+
function dot(a, b, length) {
|
|
29
|
+
let sum = 0;
|
|
30
|
+
for (let i = 0; i < length; i += 1)
|
|
31
|
+
sum += a[i] * b[i];
|
|
32
|
+
return sum;
|
|
33
|
+
}
|
|
34
|
+
/** A point's offset from the mean along each axis, in that axis's own spread; 0 along an axis with none. */
|
|
35
|
+
function along(axes, mean, spreads, vector) {
|
|
36
|
+
const offset = Array.from(vector, (v, d) => v - mean[d]);
|
|
37
|
+
return [0, 1, 2].map((k) => axes[k] && spreads[k] > 1e-12 ? dot(axes[k], offset, offset.length) / spreads[k] : 0);
|
|
38
|
+
}
|
|
39
|
+
/** Each axis's spread: the root mean square of the documents' offsets along it. */
|
|
40
|
+
function spreadsOf(axes, mean, vectors) {
|
|
41
|
+
return [0, 1, 2].map((k) => {
|
|
42
|
+
const axis = axes[k];
|
|
43
|
+
if (!axis || vectors.length === 0)
|
|
44
|
+
return 0;
|
|
45
|
+
const squares = vectors.reduce((sum, v) => {
|
|
46
|
+
const at = dot(axis, Array.from(v, (x, d) => x - mean[d]), v.length);
|
|
47
|
+
return sum + at * at;
|
|
48
|
+
}, 0);
|
|
49
|
+
return Math.sqrt(squares / vectors.length);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/** Coordinates in the axes, drawn into the ball: same direction, distance `r / (r + scale) / reach`. */
|
|
53
|
+
function inBall(point, scale, reach) {
|
|
54
|
+
const length = Math.hypot(point[0], point[1], point[2]);
|
|
55
|
+
if (length < 1e-9 || scale <= 0 || reach <= 0)
|
|
56
|
+
return CENTRE;
|
|
57
|
+
const radius = length / (length + scale) / reach / length;
|
|
58
|
+
return [point[0] * radius, point[1] * radius, point[2] * radius];
|
|
59
|
+
}
|
|
60
|
+
/** The documents packed row after row, so the loops over them stay on one typed array. */
|
|
61
|
+
function packed(vectors, dimensions) {
|
|
62
|
+
const rows = new Float64Array(vectors.length * dimensions);
|
|
63
|
+
vectors.forEach((vector, i) => {
|
|
64
|
+
rows.set(vector, i * dimensions);
|
|
65
|
+
});
|
|
66
|
+
return rows;
|
|
67
|
+
}
|
|
68
|
+
/** Every document against every other: the raw dot products, once. Yields after each row, so a large index can share the thread. */
|
|
69
|
+
function* gramOf(rows, n, dimensions) {
|
|
70
|
+
const gram = new Float64Array(n * n);
|
|
71
|
+
const tail = dimensions - (dimensions % 4);
|
|
72
|
+
for (let i = 0; i < n; i += 1) {
|
|
73
|
+
const a = i * dimensions;
|
|
74
|
+
for (let j = i; j < n; j += 1) {
|
|
75
|
+
const b = j * dimensions;
|
|
76
|
+
let s0 = 0;
|
|
77
|
+
let s1 = 0;
|
|
78
|
+
let s2 = 0;
|
|
79
|
+
let s3 = 0;
|
|
80
|
+
// why: four running sums let the engine pipeline the multiplies; this loop is most of the layout's time.
|
|
81
|
+
for (let d = 0; d < tail; d += 4) {
|
|
82
|
+
s0 += rows[a + d] * rows[b + d];
|
|
83
|
+
s1 += rows[a + d + 1] * rows[b + d + 1];
|
|
84
|
+
s2 += rows[a + d + 2] * rows[b + d + 2];
|
|
85
|
+
s3 += rows[a + d + 3] * rows[b + d + 3];
|
|
86
|
+
}
|
|
87
|
+
for (let d = tail; d < dimensions; d += 1)
|
|
88
|
+
s0 += rows[a + d] * rows[b + d];
|
|
89
|
+
const sum = s0 + s1 + s2 + s3;
|
|
90
|
+
gram[i * n + j] = sum;
|
|
91
|
+
gram[j * n + i] = sum;
|
|
92
|
+
}
|
|
93
|
+
yield;
|
|
94
|
+
}
|
|
95
|
+
return gram;
|
|
96
|
+
}
|
|
97
|
+
/** The same products about the documents' mean, from the raw ones. */
|
|
98
|
+
function centred(gram, n) {
|
|
99
|
+
const rows = new Float64Array(n);
|
|
100
|
+
let all = 0;
|
|
101
|
+
for (let i = 0; i < n; i += 1) {
|
|
102
|
+
for (let j = 0; j < n; j += 1)
|
|
103
|
+
rows[i] += gram[i * n + j];
|
|
104
|
+
rows[i] /= n;
|
|
105
|
+
all += rows[i];
|
|
106
|
+
}
|
|
107
|
+
all /= n;
|
|
108
|
+
const out = new Float64Array(n * n);
|
|
109
|
+
for (let i = 0; i < n; i += 1)
|
|
110
|
+
for (let j = 0; j < n; j += 1)
|
|
111
|
+
out[i * n + j] = gram[i * n + j] - rows[i] - rows[j] + all;
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
/** One power-iteration round: `matrix · vector`, normalised, with its length and how far it moved. */
|
|
115
|
+
function powerRound(matrix, n, vector) {
|
|
116
|
+
const next = new Float64Array(n);
|
|
117
|
+
let length = 0;
|
|
118
|
+
for (let i = 0; i < n; i += 1) {
|
|
119
|
+
let sum = 0;
|
|
120
|
+
for (let j = 0; j < n; j += 1)
|
|
121
|
+
sum += matrix[i * n + j] * vector[j];
|
|
122
|
+
next[i] = sum;
|
|
123
|
+
length += sum * sum;
|
|
124
|
+
}
|
|
125
|
+
length = Math.sqrt(length);
|
|
126
|
+
if (length < 1e-12)
|
|
127
|
+
return { next: vector, length: 0, moved: 0 };
|
|
128
|
+
let moved = 0;
|
|
129
|
+
for (let i = 0; i < n; i += 1) {
|
|
130
|
+
next[i] /= length;
|
|
131
|
+
moved = Math.max(moved, Math.abs(next[i] - vector[i]));
|
|
132
|
+
}
|
|
133
|
+
return { next, length, moved };
|
|
134
|
+
}
|
|
135
|
+
/** Flips a vector so its largest entry is positive. */
|
|
136
|
+
function signed(vector) {
|
|
137
|
+
let lead = 0;
|
|
138
|
+
for (let i = 1; i < vector.length; i += 1)
|
|
139
|
+
if (Math.abs(vector[i]) > Math.abs(vector[lead]))
|
|
140
|
+
lead = i;
|
|
141
|
+
return vector[lead] < 0 ? vector.map((v) => -v) : vector;
|
|
142
|
+
}
|
|
143
|
+
/** The largest eigenvector left in a matrix, by power iteration. */
|
|
144
|
+
function* largestEigen(matrix, n, k) {
|
|
145
|
+
// invariant: a fixed start, so the same documents always draw the same space.
|
|
146
|
+
let vector = Float64Array.from({ length: n }, (_, i) => 1 + ((i * 7919 + k * 104729) % 97) / 97);
|
|
147
|
+
let value = 0;
|
|
148
|
+
for (let round = 0; round < ROUNDS; round += 1) {
|
|
149
|
+
const step = powerRound(matrix, n, vector);
|
|
150
|
+
if (step.length === 0)
|
|
151
|
+
break;
|
|
152
|
+
value = step.length;
|
|
153
|
+
vector = step.next;
|
|
154
|
+
if (step.moved < 1e-12)
|
|
155
|
+
break;
|
|
156
|
+
yield;
|
|
157
|
+
}
|
|
158
|
+
return { vector: signed(vector), value };
|
|
159
|
+
}
|
|
160
|
+
/** The largest eigenvectors of a symmetric matrix, by power iteration with deflation, each signed so its largest entry is positive. */
|
|
161
|
+
function* topEigen(matrix, n, count) {
|
|
162
|
+
const work = Float64Array.from(matrix);
|
|
163
|
+
const found = [];
|
|
164
|
+
const scale = Math.max(...Array.from({ length: n }, (_, i) => Math.abs(work[i * n + i])), 0);
|
|
165
|
+
for (let k = 0; k < Math.min(count, n); k += 1) {
|
|
166
|
+
const { vector, value } = yield* largestEigen(work, n, k);
|
|
167
|
+
if (value <= scale * 1e-9)
|
|
168
|
+
break;
|
|
169
|
+
found.push({ vector, value });
|
|
170
|
+
for (let i = 0; i < n; i += 1)
|
|
171
|
+
for (let j = 0; j < n; j += 1)
|
|
172
|
+
work[i * n + j] -= value * vector[i] * vector[j];
|
|
173
|
+
}
|
|
174
|
+
return found;
|
|
175
|
+
}
|
|
176
|
+
function meanOf(rows, n, dimensions) {
|
|
177
|
+
const mean = new Float64Array(dimensions);
|
|
178
|
+
for (let i = 0; i < n; i += 1)
|
|
179
|
+
for (let d = 0; d < dimensions; d += 1)
|
|
180
|
+
mean[d] += rows[i * dimensions + d];
|
|
181
|
+
for (let d = 0; d < dimensions; d += 1)
|
|
182
|
+
mean[d] /= Math.max(n, 1);
|
|
183
|
+
return mean;
|
|
184
|
+
}
|
|
185
|
+
/** One axis in the vectors' own space: the documents weighted by an eigenvector of their Gram matrix. */
|
|
186
|
+
function axisOf(rows, mean, weights) {
|
|
187
|
+
const dimensions = mean.length;
|
|
188
|
+
const axis = new Float64Array(dimensions);
|
|
189
|
+
for (let i = 0; i < weights.length; i += 1)
|
|
190
|
+
for (let d = 0; d < dimensions; d += 1)
|
|
191
|
+
axis[d] += weights[i] * (rows[i * dimensions + d] - mean[d]);
|
|
192
|
+
let length = 0;
|
|
193
|
+
for (let d = 0; d < dimensions; d += 1)
|
|
194
|
+
length += axis[d] * axis[d];
|
|
195
|
+
length = Math.sqrt(length);
|
|
196
|
+
if (length > 0)
|
|
197
|
+
for (let d = 0; d < dimensions; d += 1)
|
|
198
|
+
axis[d] /= length;
|
|
199
|
+
return axis;
|
|
200
|
+
}
|
|
201
|
+
/** Every pair's cosine similarity, from the raw products. */
|
|
202
|
+
function cosineOf(gram, n) {
|
|
203
|
+
const norms = Array.from({ length: n }, (_, i) => Math.sqrt(Math.max(gram[i * n + i], 0)));
|
|
204
|
+
const cosine = new Float64Array(n * n);
|
|
205
|
+
for (let i = 0; i < n; i += 1)
|
|
206
|
+
for (let j = 0; j < n; j += 1)
|
|
207
|
+
cosine[i * n + j] =
|
|
208
|
+
norms[i] === 0 || norms[j] === 0 ? 0 : gram[i * n + j] / (norms[i] * norms[j]);
|
|
209
|
+
return cosine;
|
|
210
|
+
}
|
|
211
|
+
/** The median of some values; it sorts them. */
|
|
212
|
+
function medianOf(values) {
|
|
213
|
+
values.sort();
|
|
214
|
+
const middle = values.length >> 1;
|
|
215
|
+
return values.length % 2 ? values[middle] : (values[middle - 1] + values[middle]) / 2;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* The similarity one must pass to stand out among `similarities`: their
|
|
219
|
+
* median plus {@link OUTLIER} robust deviations. `Infinity` when they have no
|
|
220
|
+
* spread, since then nothing stands out.
|
|
221
|
+
*/
|
|
222
|
+
export function outlierThreshold(similarities) {
|
|
223
|
+
const values = Float64Array.from(similarities);
|
|
224
|
+
if (values.length === 0)
|
|
225
|
+
return Infinity;
|
|
226
|
+
const median = medianOf(values.slice());
|
|
227
|
+
const deviation = MAD_SCALE * medianOf(values.map((v) => Math.abs(v - median)));
|
|
228
|
+
return deviation > 0 ? median + OUTLIER * deviation : Infinity;
|
|
229
|
+
}
|
|
230
|
+
/** Every pair's similarity, once each. */
|
|
231
|
+
function pairsOf(cosine, n) {
|
|
232
|
+
const pairs = new Float64Array((n * (n - 1)) / 2);
|
|
233
|
+
let at = 0;
|
|
234
|
+
for (let i = 0; i < n; i += 1)
|
|
235
|
+
for (let j = i + 1; j < n; j += 1)
|
|
236
|
+
pairs[at++] = cosine[i * n + j];
|
|
237
|
+
return pairs;
|
|
238
|
+
}
|
|
239
|
+
/** Every pair above the threshold, a row at a time: what clusters are made of. */
|
|
240
|
+
function* linksOf(cosine, n, threshold) {
|
|
241
|
+
const links = [];
|
|
242
|
+
for (let i = 0; i < n; i += 1) {
|
|
243
|
+
for (let j = i + 1; j < n; j += 1)
|
|
244
|
+
if (cosine[i * n + j] > threshold)
|
|
245
|
+
links.push({ a: i, b: j, similarity: cosine[i * n + j] });
|
|
246
|
+
yield;
|
|
247
|
+
}
|
|
248
|
+
return links;
|
|
249
|
+
}
|
|
250
|
+
/** The document whose cosine similarity with `i` is highest, the first on a tie; -1 when there is no other. */
|
|
251
|
+
function nearestTo(cosine, n, i) {
|
|
252
|
+
let best = -1;
|
|
253
|
+
for (let j = 0; j < n; j += 1)
|
|
254
|
+
if (j !== i && (best < 0 || cosine[i * n + j] > cosine[i * n + best]))
|
|
255
|
+
best = j;
|
|
256
|
+
return best;
|
|
257
|
+
}
|
|
258
|
+
/** Each document's nearest neighbour where that pair passes the threshold, each pair once, a row at a time. */
|
|
259
|
+
function* nearestOf(cosine, n, threshold) {
|
|
260
|
+
const seen = new Set();
|
|
261
|
+
const links = [];
|
|
262
|
+
for (let i = 0; i < n; i += 1) {
|
|
263
|
+
const best = nearestTo(cosine, n, i);
|
|
264
|
+
const [a, b] = i < best ? [i, best] : [best, i];
|
|
265
|
+
if (best >= 0 && cosine[i * n + best] > threshold && !seen.has(a * n + b)) {
|
|
266
|
+
seen.add(a * n + b);
|
|
267
|
+
links.push({ a, b, similarity: cosine[i * n + best] });
|
|
268
|
+
}
|
|
269
|
+
yield;
|
|
270
|
+
}
|
|
271
|
+
return links.sort((x, y) => x.a - y.a || x.b - y.b);
|
|
272
|
+
}
|
|
273
|
+
/** The groups the links connect, each in document order. */
|
|
274
|
+
function groupsOf(n, links) {
|
|
275
|
+
const root = Array.from({ length: n }, (_, i) => i);
|
|
276
|
+
const find = (i) => {
|
|
277
|
+
while (root[i] !== i) {
|
|
278
|
+
root[i] = root[root[i]];
|
|
279
|
+
i = root[i];
|
|
280
|
+
}
|
|
281
|
+
return i;
|
|
282
|
+
};
|
|
283
|
+
for (const link of links)
|
|
284
|
+
root[find(link.a)] = find(link.b);
|
|
285
|
+
const groups = new Map();
|
|
286
|
+
for (let i = 0; i < n; i += 1) {
|
|
287
|
+
const top = find(i);
|
|
288
|
+
const group = groups.get(top);
|
|
289
|
+
if (group)
|
|
290
|
+
group.push(i);
|
|
291
|
+
else
|
|
292
|
+
groups.set(top, [i]);
|
|
293
|
+
}
|
|
294
|
+
return [...groups.values()];
|
|
295
|
+
}
|
|
296
|
+
/** The mean of some rows. */
|
|
297
|
+
function meanOfRows(rows, members, dimensions) {
|
|
298
|
+
const mean = new Array(dimensions).fill(0);
|
|
299
|
+
for (const i of members)
|
|
300
|
+
for (let d = 0; d < dimensions; d += 1)
|
|
301
|
+
mean[d] += rows[i * dimensions + d];
|
|
302
|
+
return mean.map((v) => v / Math.max(members.length, 1));
|
|
303
|
+
}
|
|
304
|
+
/** A position's hue: its angle around the space's two widest directions, from 0 to 1. */
|
|
305
|
+
function toneOf(position) {
|
|
306
|
+
return (Math.atan2(position[1], position[0]) / (2 * Math.PI) + 1) % 1;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* The layout, a slice at a time. Vectors whose width differs from the first
|
|
310
|
+
* are placed at the centre, each a cluster of its own.
|
|
311
|
+
*/
|
|
312
|
+
function* layoutSteps(vectors) {
|
|
313
|
+
const dimensions = vectors[0]?.length ?? 0;
|
|
314
|
+
const original = vectors.flatMap((vector, i) => (vector.length === dimensions ? [i] : []));
|
|
315
|
+
const n = original.length;
|
|
316
|
+
const rows = packed(original.map((i) => vectors[i]), dimensions);
|
|
317
|
+
const mean = meanOf(rows, n, dimensions);
|
|
318
|
+
const gram = yield* gramOf(rows, n, dimensions);
|
|
319
|
+
const axes = (yield* topEigen(centred(gram, n), n, 3)).map(({ vector }) => axisOf(rows, mean, vector));
|
|
320
|
+
const kept = original.map((i) => vectors[i]);
|
|
321
|
+
const spreads = spreadsOf(axes, mean, kept);
|
|
322
|
+
const coordinates = kept.map((vector) => along(axes, mean, spreads, vector));
|
|
323
|
+
const lengths = Float64Array.from(coordinates, (p) => Math.hypot(p[0], p[1], p[2]));
|
|
324
|
+
// why: when more than half sit at the mean, the farthest sets the scale, so the rest still spread out.
|
|
325
|
+
const median = n > 0 ? medianOf(lengths.slice()) : 0;
|
|
326
|
+
const scale = median > 1e-9 ? median : lengths.reduce((most, v) => Math.max(most, v), 0);
|
|
327
|
+
const reach = lengths.reduce((most, r) => Math.max(most, scale > 0 ? r / (r + scale) : 0), 0);
|
|
328
|
+
const basis = { mean, axes, spreads, scale, reach };
|
|
329
|
+
const cosine = cosineOf(gram, n);
|
|
330
|
+
const threshold = outlierThreshold(pairsOf(cosine, n));
|
|
331
|
+
const found = yield* linksOf(cosine, n, threshold);
|
|
332
|
+
const groups = groupsOf(n, found);
|
|
333
|
+
const nearest = yield* nearestOf(cosine, n, threshold);
|
|
334
|
+
const clusters = groups.map((group) => ({
|
|
335
|
+
members: group.map((i) => original[i]),
|
|
336
|
+
tone: toneOf(project(basis, meanOfRows(rows, group, dimensions))),
|
|
337
|
+
}));
|
|
338
|
+
for (const [i, vector] of vectors.entries())
|
|
339
|
+
if (vector.length !== dimensions)
|
|
340
|
+
clusters.push({ members: [i], tone: toneOf(CENTRE) });
|
|
341
|
+
const clusterOf = new Map(clusters.flatMap((c, k) => c.members.map((i) => [i, k])));
|
|
342
|
+
const documents = vectors.map((vector, index) => ({
|
|
343
|
+
index,
|
|
344
|
+
// invariant: documents and queries go through the one projection, so a query lands on its own document.
|
|
345
|
+
position: vector.length === dimensions ? project(basis, vector) : CENTRE,
|
|
346
|
+
cluster: clusterOf.get(index) ?? 0,
|
|
347
|
+
}));
|
|
348
|
+
const links = nearest.map((link) => ({ ...link, a: original[link.a], b: original[link.b] }));
|
|
349
|
+
return { documents, clusters, links, threshold, basis, dimensions };
|
|
350
|
+
}
|
|
351
|
+
/** The space for a set of document vectors, all at once. */
|
|
352
|
+
export function layoutSpace(vectors) {
|
|
353
|
+
const steps = layoutSteps(vectors);
|
|
354
|
+
for (;;) {
|
|
355
|
+
const step = steps.next();
|
|
356
|
+
if (step.done)
|
|
357
|
+
return step.value;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
/** Time the layout works before it lets the page go on, in milliseconds. */
|
|
361
|
+
const SLICE_MS = 8;
|
|
362
|
+
/**
|
|
363
|
+
* The same space, laid out in slices between which the page goes on, so a
|
|
364
|
+
* large index never freezes it. Rejects with the signal's reason once aborted.
|
|
365
|
+
*/
|
|
366
|
+
export async function layoutSpaceSoon(vectors, signal) {
|
|
367
|
+
const steps = layoutSteps(vectors);
|
|
368
|
+
for (;;) {
|
|
369
|
+
const until = performance.now() + SLICE_MS;
|
|
370
|
+
for (;;) {
|
|
371
|
+
const step = steps.next();
|
|
372
|
+
if (step.done)
|
|
373
|
+
return step.value;
|
|
374
|
+
if (performance.now() >= until)
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
378
|
+
signal?.throwIfAborted();
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/** Where a query lands in the space: projected along the same directions as the documents. `null` when its width differs. */
|
|
382
|
+
export function placeQuery(space, query) {
|
|
383
|
+
if (query.length !== space.dimensions || space.dimensions === 0)
|
|
384
|
+
return null;
|
|
385
|
+
return project(space.basis, query);
|
|
386
|
+
}
|
|
387
|
+
function project(basis, vector) {
|
|
388
|
+
return inBall(along(basis.axes, basis.mean, basis.spreads, vector), basis.scale, basis.reach);
|
|
389
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type EmbeddingInput, type EmbeddingProfile, type EmbeddingResult, type EmbeddingRules, type EmbeddingTask, type Protocol, type TraceRecord } from '@theoremjs/agents';
|
|
2
|
+
import type { TraceFeed } from './trace-feed.ts';
|
|
3
|
+
import { type HttpOptions } from './transport.ts';
|
|
4
|
+
export type EmbeddingInterface = {
|
|
5
|
+
type: 'embedding';
|
|
6
|
+
identity: {
|
|
7
|
+
handle: string;
|
|
8
|
+
};
|
|
9
|
+
/** The profile's inputs held to what its default model takes. */
|
|
10
|
+
inputs: EmbeddingRules;
|
|
11
|
+
observability?: {
|
|
12
|
+
record: boolean;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The interface a client reads: the profile's inputs held to its default model's limits. Pass
|
|
17
|
+
* `protocols` when a model speaks a protocol of your own.
|
|
18
|
+
*/
|
|
19
|
+
export declare function embeddingInterface(profile: Pick<EmbeddingProfile, 'identity' | 'inputs' | 'observability' | 'models' | 'defaultModel'>, protocols?: readonly Protocol[]): EmbeddingInterface;
|
|
20
|
+
export type EmbeddingReply = {
|
|
21
|
+
result: EmbeddingResult;
|
|
22
|
+
traces?: TraceRecord[];
|
|
23
|
+
};
|
|
24
|
+
/** What an embedding asks beyond its inputs: a named model, and what the inputs are for. */
|
|
25
|
+
export type EmbedOptions = {
|
|
26
|
+
model?: string;
|
|
27
|
+
task?: EmbeddingTask;
|
|
28
|
+
};
|
|
29
|
+
export interface EmbeddingTransport {
|
|
30
|
+
describe(signal?: AbortSignal): Promise<EmbeddingInterface>;
|
|
31
|
+
embed(input: EmbeddingInput | readonly EmbeddingInput[], options?: EmbedOptions, signal?: AbortSignal): Promise<EmbeddingReply>;
|
|
32
|
+
traces?: TraceFeed;
|
|
33
|
+
}
|
|
34
|
+
export declare function readEmbeddingReply(raw: unknown): EmbeddingReply;
|
|
35
|
+
/** POSTs an embedding and reads its reply; a failure keeps the trace records its body carried. */
|
|
36
|
+
export declare function postEmbedding(url: string, body: unknown, signal: AbortSignal | undefined, options?: HttpOptions): Promise<EmbeddingReply>;
|
|
37
|
+
/** Transport for a host mounted with `createTheoremEmbeddingHandler`. Default endpoint `/api/embedding`. */
|
|
38
|
+
export declare function createEmbeddingTransport(options?: HttpOptions & {
|
|
39
|
+
endpoint?: string;
|
|
40
|
+
}): EmbeddingTransport;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { embeddingModelLimits, embeddingRules, resolveObservabilityPolicy, traceRecordSchema, z, } from '@theoremjs/agents';
|
|
2
|
+
import { postWithTraces } from "./decision-transport.js";
|
|
3
|
+
import { fetchJson } from "./transport.js";
|
|
4
|
+
import { checkWire } from "./wire-line.js";
|
|
5
|
+
/**
|
|
6
|
+
* The interface a client reads: the profile's inputs held to its default model's limits. Pass
|
|
7
|
+
* `protocols` when a model speaks a protocol of your own.
|
|
8
|
+
*/
|
|
9
|
+
export function embeddingInterface(profile, protocols = []) {
|
|
10
|
+
const binding = profile.models[profile.defaultModel];
|
|
11
|
+
return {
|
|
12
|
+
type: 'embedding',
|
|
13
|
+
identity: { handle: profile.identity.handle },
|
|
14
|
+
inputs: embeddingRules(profile.inputs, binding ? embeddingModelLimits(binding, protocols) : undefined),
|
|
15
|
+
...(profile.observability
|
|
16
|
+
? { observability: { record: resolveObservabilityPolicy(profile.observability).record } }
|
|
17
|
+
: {}),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const embeddingReplySchema = z.object({
|
|
21
|
+
result: z.object({
|
|
22
|
+
model: z.string().min(1),
|
|
23
|
+
embeddings: z.array(z
|
|
24
|
+
.object({
|
|
25
|
+
index: z.number().int().nonnegative(),
|
|
26
|
+
values: z.array(z.number()),
|
|
27
|
+
dimensions: z.number().int().positive(),
|
|
28
|
+
})
|
|
29
|
+
.refine((embedding) => embedding.values.length === embedding.dimensions, 'vector length must match dimensions')),
|
|
30
|
+
usage: z
|
|
31
|
+
.object({
|
|
32
|
+
inputTokens: z.number().int().nonnegative().optional(),
|
|
33
|
+
costUsd: z.number().nonnegative().optional(),
|
|
34
|
+
estimated: z.literal(true).optional(),
|
|
35
|
+
})
|
|
36
|
+
.optional(),
|
|
37
|
+
}),
|
|
38
|
+
traces: z.array(traceRecordSchema).optional(),
|
|
39
|
+
});
|
|
40
|
+
const describedSchema = z.object({
|
|
41
|
+
interface: z.object({
|
|
42
|
+
type: z.literal('embedding'),
|
|
43
|
+
identity: z.object({ handle: z.string() }),
|
|
44
|
+
inputs: z.object({
|
|
45
|
+
text: z.boolean(),
|
|
46
|
+
attachments: z.object({ accept: z.array(z.string()) }).optional(),
|
|
47
|
+
maxFiles: z.number().optional(),
|
|
48
|
+
maxBytes: z.number().optional(),
|
|
49
|
+
maxTurnBytes: z.number().optional(),
|
|
50
|
+
limitsByMime: z.record(z.string(), z.number()).optional(),
|
|
51
|
+
}),
|
|
52
|
+
observability: z.object({ record: z.boolean() }).optional(),
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
export function readEmbeddingReply(raw) {
|
|
56
|
+
return checkWire(embeddingReplySchema, raw, 'the embedding');
|
|
57
|
+
}
|
|
58
|
+
/** POSTs an embedding and reads its reply; a failure keeps the trace records its body carried. */
|
|
59
|
+
export function postEmbedding(url, body, signal, options = {}) {
|
|
60
|
+
return postWithTraces(url, body, signal, options, readEmbeddingReply);
|
|
61
|
+
}
|
|
62
|
+
/** Transport for a host mounted with `createTheoremEmbeddingHandler`. Default endpoint `/api/embedding`. */
|
|
63
|
+
export function createEmbeddingTransport(options = {}) {
|
|
64
|
+
const base = (options.endpoint ?? '/api/embedding').replace(/\/$/, '');
|
|
65
|
+
return {
|
|
66
|
+
async describe(signal) {
|
|
67
|
+
return checkWire(describedSchema, await fetchJson(base, { signal }, options), 'the embedding description').interface;
|
|
68
|
+
},
|
|
69
|
+
embed: (input, embed, signal) => postEmbedding(`${base}/embed`, { input, ...embed }, signal, options),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encode composer fields into a serializable `UserTurnDraft` for pending intents.
|
|
3
|
+
*/
|
|
4
|
+
import type { UserTurnDraft } from '@theoremjs/agents/interface';
|
|
5
|
+
export declare function encodeComposerDraft(args: {
|
|
6
|
+
text: string;
|
|
7
|
+
pendingFiles?: readonly File[];
|
|
8
|
+
pendingVoice?: readonly File[];
|
|
9
|
+
}): Promise<UserTurnDraft>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encode composer fields into a serializable `UserTurnDraft` for pending intents.
|
|
3
|
+
*/
|
|
4
|
+
import { encodeFiles } from "./encode-files.js";
|
|
5
|
+
export async function encodeComposerDraft(args) {
|
|
6
|
+
const text = args.text.trim();
|
|
7
|
+
const files = args.pendingFiles ?? [];
|
|
8
|
+
const voice = args.pendingVoice ?? [];
|
|
9
|
+
const encodedFiles = files.length ? await encodeFiles(files) : [];
|
|
10
|
+
const encodedVoice = voice.length ? await encodeFiles(voice) : [];
|
|
11
|
+
return {
|
|
12
|
+
...(text ? { text } : {}),
|
|
13
|
+
...(encodedFiles.length
|
|
14
|
+
? {
|
|
15
|
+
attachments: encodedFiles.map((blob, index) => ({
|
|
16
|
+
name: blob.name,
|
|
17
|
+
mimeType: blob.mimeType,
|
|
18
|
+
sizeBytes: files[index]?.size ?? 0,
|
|
19
|
+
data: blob.data,
|
|
20
|
+
})),
|
|
21
|
+
}
|
|
22
|
+
: {}),
|
|
23
|
+
...(encodedVoice.length
|
|
24
|
+
? {
|
|
25
|
+
voice: encodedVoice.map((blob, index) => ({
|
|
26
|
+
name: blob.name,
|
|
27
|
+
mimeType: blob.mimeType,
|
|
28
|
+
sizeBytes: voice[index]?.size ?? 0,
|
|
29
|
+
data: blob.data,
|
|
30
|
+
})),
|
|
31
|
+
}
|
|
32
|
+
: {}),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PendingAttachment, TranscriptBlock } from '@theoremjs/agents/interface';
|
|
2
|
+
export declare function filesToPending(files: readonly File[]): PendingAttachment[];
|
|
3
|
+
/**
|
|
4
|
+
* Rebuild `File`s from encoded pending attachments (stash / queue restore).
|
|
5
|
+
* Attachments without `data` cannot be restored and are skipped.
|
|
6
|
+
*/
|
|
7
|
+
export declare function pendingAttachmentsToFiles(attachments: readonly PendingAttachment[]): File[];
|
|
8
|
+
export declare function encodeFiles(files: readonly File[]): Promise<Array<{
|
|
9
|
+
name: string;
|
|
10
|
+
mimeType: string;
|
|
11
|
+
data: string;
|
|
12
|
+
}>>;
|
|
13
|
+
/** Copy encoded base64 onto matching user attachment/voice transcript blocks for UI preview. */
|
|
14
|
+
export declare function attachPreviewData(blocks: readonly TranscriptBlock[], attachments?: ReadonlyArray<{
|
|
15
|
+
data: string;
|
|
16
|
+
}>, voice?: ReadonlyArray<{
|
|
17
|
+
data: string;
|
|
18
|
+
}>): TranscriptBlock[];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { base64ToBytes, bytesToBase64 } from '@theoremjs/agents/kernel';
|
|
2
|
+
export function filesToPending(files) {
|
|
3
|
+
return files.map((file) => ({
|
|
4
|
+
name: file.name,
|
|
5
|
+
mimeType: file.type || 'application/octet-stream',
|
|
6
|
+
sizeBytes: file.size,
|
|
7
|
+
}));
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Rebuild `File`s from encoded pending attachments (stash / queue restore).
|
|
11
|
+
* Attachments without `data` cannot be restored and are skipped.
|
|
12
|
+
*/
|
|
13
|
+
export function pendingAttachmentsToFiles(attachments) {
|
|
14
|
+
const files = [];
|
|
15
|
+
for (const attachment of attachments) {
|
|
16
|
+
if (typeof attachment.data !== 'string' || attachment.data.length === 0)
|
|
17
|
+
continue;
|
|
18
|
+
const bytes = base64ToBytes(attachment.data);
|
|
19
|
+
const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
20
|
+
files.push(new File([buffer], attachment.name, {
|
|
21
|
+
type: attachment.mimeType || 'application/octet-stream',
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
return files;
|
|
25
|
+
}
|
|
26
|
+
export async function encodeFiles(files) {
|
|
27
|
+
const out = [];
|
|
28
|
+
for (const file of files) {
|
|
29
|
+
out.push({
|
|
30
|
+
name: file.name,
|
|
31
|
+
mimeType: file.type || 'application/octet-stream',
|
|
32
|
+
data: bytesToBase64(new Uint8Array(await file.arrayBuffer())),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
/** Copy encoded base64 onto matching user attachment/voice transcript blocks for UI preview. */
|
|
38
|
+
export function attachPreviewData(blocks, attachments, voice) {
|
|
39
|
+
let attachmentIndex = 0;
|
|
40
|
+
let voiceIndex = 0;
|
|
41
|
+
return blocks.map((block) => {
|
|
42
|
+
if (block.kind === 'user-attachment') {
|
|
43
|
+
const data = attachments?.[attachmentIndex]?.data;
|
|
44
|
+
attachmentIndex += 1;
|
|
45
|
+
return data !== undefined ? { ...block, data } : block;
|
|
46
|
+
}
|
|
47
|
+
if (block.kind === 'user-voice') {
|
|
48
|
+
const data = voice?.[voiceIndex]?.data;
|
|
49
|
+
voiceIndex += 1;
|
|
50
|
+
return data !== undefined ? { ...block, data } : block;
|
|
51
|
+
}
|
|
52
|
+
return block;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One failure, two worlds, in the browser: the builder reads `errorKind` and
|
|
3
|
+
* `errorInternal`; the user reads `error`, the profile's wording for it.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
import { type ErrorKind, type LabelOverrides } from '@theoremjs/agents';
|
|
8
|
+
import type { AttachmentValidationIssue } from '@theoremjs/agents/interface';
|
|
9
|
+
export type ClientFailure = {
|
|
10
|
+
/** What the user reads: the host's wording, else the profile labels' for the failure. */
|
|
11
|
+
error: string;
|
|
12
|
+
errorKind: ErrorKind;
|
|
13
|
+
/** Raw detail for the builder; never shown to the user. */
|
|
14
|
+
errorInternal?: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Word a caught value for the user with the profile's `labels` (the interface's
|
|
18
|
+
* `labels`). A host-reported failure keeps the host's wording.
|
|
19
|
+
*/
|
|
20
|
+
export declare function clientFailure(err: unknown, labels?: LabelOverrides): ClientFailure;
|
|
21
|
+
/** A turn that did not run or did not finish. */
|
|
22
|
+
export type TurnFailure = ClientFailure & {
|
|
23
|
+
/** The files the profile refused, one entry per reason (the kernel's check). */
|
|
24
|
+
issues?: AttachmentValidationIssue[];
|
|
25
|
+
/** The user stopped it; nothing to show. */
|
|
26
|
+
aborted?: boolean;
|
|
27
|
+
};
|
|
28
|
+
/** A caught value as a turn failure; an aborted `signal` marks it the user's stop. */
|
|
29
|
+
export declare function turnFailure(err: unknown, labels?: LabelOverrides, signal?: AbortSignal): TurnFailure;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One failure, two worlds, in the browser: the builder reads `errorKind` and
|
|
3
|
+
* `errorInternal`; the user reads `error`, the profile's wording for it.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
import { describeError, errorKind, labelText, publicError, } from '@theoremjs/agents';
|
|
8
|
+
import { isTheoremStreamError } from "./transport.js";
|
|
9
|
+
/**
|
|
10
|
+
* Word a caught value for the user with the profile's `labels` (the interface's
|
|
11
|
+
* `labels`). A host-reported failure keeps the host's wording.
|
|
12
|
+
*/
|
|
13
|
+
export function clientFailure(err, labels) {
|
|
14
|
+
if (isTheoremStreamError(err)) {
|
|
15
|
+
return {
|
|
16
|
+
error: err.publicMessage ?? labelText(`error.${err.kind}`, {}, labels),
|
|
17
|
+
errorKind: err.kind,
|
|
18
|
+
...(err.internalMessage ? { errorInternal: err.internalMessage } : {}),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const error = publicError(err, labels);
|
|
22
|
+
const internal = describeError(err);
|
|
23
|
+
return {
|
|
24
|
+
error,
|
|
25
|
+
errorKind: errorKind(err),
|
|
26
|
+
...(internal && internal !== error ? { errorInternal: internal } : {}),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** A caught value as a turn failure; an aborted `signal` marks it the user's stop. */
|
|
30
|
+
export function turnFailure(err, labels, signal) {
|
|
31
|
+
const failure = clientFailure(err, labels);
|
|
32
|
+
const aborted = failure.errorKind === 'cancelled' || signal?.aborted === true;
|
|
33
|
+
return { ...failure, ...(aborted ? { aborted: true } : {}) };
|
|
34
|
+
}
|