basalt-ui 1.10.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +21 -21
- package/CHANGELOG.md +25 -0
- package/README.md +36 -27
- package/agent/rules/basalt-content.md +16 -3
- package/dist/agent/adapter.d.ts +146 -0
- package/dist/agent/adapter.d.ts.map +1 -0
- package/dist/agent/adapter.js +473 -0
- package/dist/agent/adapter.test.d.ts +2 -0
- package/dist/agent/adapter.test.d.ts.map +1 -0
- package/dist/agent/ai-sdk-transport.d.ts +3 -52
- package/dist/agent/ai-sdk-transport.d.ts.map +1 -1
- package/dist/agent/ai-sdk-transport.js +108 -24
- package/dist/agent/ai-sdk-transport.test.d.ts +2 -0
- package/dist/agent/ai-sdk-transport.test.d.ts.map +1 -0
- package/dist/agent/coalesce.d.ts +42 -0
- package/dist/agent/coalesce.d.ts.map +1 -0
- package/dist/agent/coalesce.js +137 -0
- package/dist/agent/coalesce.test.d.ts +2 -0
- package/dist/agent/coalesce.test.d.ts.map +1 -0
- package/dist/agent/foreign.d.ts +97 -0
- package/dist/agent/foreign.d.ts.map +1 -0
- package/dist/agent/foreign.js +18 -0
- package/dist/agent/foreign.test.d.ts +2 -0
- package/dist/agent/foreign.test.d.ts.map +1 -0
- package/dist/agent/history.d.ts +2 -0
- package/dist/agent/history.d.ts.map +1 -1
- package/dist/agent/id.d.ts +65 -0
- package/dist/agent/id.d.ts.map +1 -0
- package/dist/agent/id.js +30 -0
- package/dist/agent/id.test.d.ts +2 -0
- package/dist/agent/id.test.d.ts.map +1 -0
- package/dist/agent/index.d.ts +11 -4
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +18 -3
- package/dist/agent/merge.d.ts +8 -0
- package/dist/agent/merge.d.ts.map +1 -0
- package/dist/agent/merge.js +35 -0
- package/dist/agent/merge.test.d.ts +2 -0
- package/dist/agent/merge.test.d.ts.map +1 -0
- package/dist/agent/part-list.d.ts +49 -34
- package/dist/agent/part-list.d.ts.map +1 -1
- package/dist/agent/part-list.js +53 -7
- package/dist/agent/part-list.test.d.ts +2 -0
- package/dist/agent/part-list.test.d.ts.map +1 -0
- package/dist/agent/parts.d.ts +135 -20
- package/dist/agent/parts.d.ts.map +1 -1
- package/dist/agent/parts.js +138 -11
- package/dist/agent/parts.test.d.ts +2 -0
- package/dist/agent/parts.test.d.ts.map +1 -0
- package/dist/agent/thread.d.ts +14 -0
- package/dist/agent/thread.d.ts.map +1 -1
- package/dist/agent/thread.js +8 -2
- package/dist/agent/thread.test.d.ts +2 -0
- package/dist/agent/thread.test.d.ts.map +1 -0
- package/dist/agent/transport.d.ts +39 -3
- package/dist/agent/transport.d.ts.map +1 -1
- package/dist/agent/transport.js +20 -17
- package/dist/agent/transport.test.d.ts +2 -0
- package/dist/agent/transport.test.d.ts.map +1 -0
- package/dist/agent/use-agent-stream.d.ts +2 -1
- package/dist/agent/use-agent-stream.d.ts.map +1 -1
- package/dist/agent/use-agent-stream.js +5 -2
- package/dist/agent/use-agent-stream.test.d.ts +2 -0
- package/dist/agent/use-agent-stream.test.d.ts.map +1 -0
- package/dist/agent/use-agent-thread-runs.d.ts +9 -3
- package/dist/agent/use-agent-thread-runs.d.ts.map +1 -1
- package/dist/agent/use-agent-thread-runs.foreign.test.d.ts +2 -0
- package/dist/agent/use-agent-thread-runs.foreign.test.d.ts.map +1 -0
- package/dist/agent/use-agent-thread-runs.js +75 -23
- package/dist/agent/use-agent-thread-runs.resume.test.d.ts +2 -0
- package/dist/agent/use-agent-thread-runs.resume.test.d.ts.map +1 -0
- package/dist/agent/use-agent-thread-runs.test.d.ts +2 -0
- package/dist/agent/use-agent-thread-runs.test.d.ts.map +1 -0
- package/dist/agent/use-agent-thread-runs.wedge.test.d.ts +2 -0
- package/dist/agent/use-agent-thread-runs.wedge.test.d.ts.map +1 -0
- package/dist/agent-chat/composer.d.ts +130 -8
- package/dist/agent-chat/composer.d.ts.map +1 -1
- package/dist/agent-chat/composer.js +189 -23
- package/dist/agent-chat/composer.test.d.ts +2 -0
- package/dist/agent-chat/composer.test.d.ts.map +1 -0
- package/dist/agent-chat/index.d.ts +3 -1
- package/dist/agent-chat/index.d.ts.map +1 -1
- package/dist/agent-chat/index.js +2 -0
- package/dist/agent-chat/thread-detail-panel.d.ts.map +1 -1
- package/dist/agent-chat/thread-detail-panel.js +1 -1
- package/dist/agent-chat/thread-message.d.ts +24 -7
- package/dist/agent-chat/thread-message.d.ts.map +1 -1
- package/dist/agent-chat/thread-message.js +115 -45
- package/dist/agent-chat/thread-message.test.d.ts +2 -0
- package/dist/agent-chat/thread-message.test.d.ts.map +1 -0
- package/dist/agent-chat/thread-workspace.js +1 -1
- package/dist/agent-chat/tool-chip.d.ts +21 -0
- package/dist/agent-chat/tool-chip.d.ts.map +1 -0
- package/dist/agent-chat/tool-chip.js +120 -0
- package/dist/agent-chat/tool-chip.test.d.ts +2 -0
- package/dist/agent-chat/tool-chip.test.d.ts.map +1 -0
- package/dist/charts/primitives/ChartFrame.test.d.ts +2 -0
- package/dist/charts/primitives/ChartFrame.test.d.ts.map +1 -0
- package/dist/charts/utils/domain.test.d.ts +2 -0
- package/dist/charts/utils/domain.test.d.ts.map +1 -0
- package/dist/cli/check-theme.test.d.ts +2 -0
- package/dist/cli/check-theme.test.d.ts.map +1 -0
- package/dist/cli/help.test.d.ts +2 -0
- package/dist/cli/help.test.d.ts.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/init-sync.test.d.ts +2 -0
- package/dist/cli/init-sync.test.d.ts.map +1 -0
- package/dist/cli/placement-engine.test.d.ts +2 -0
- package/dist/cli/placement-engine.test.d.ts.map +1 -0
- package/dist/cli/tarball-parity.test.d.ts +2 -0
- package/dist/cli/tarball-parity.test.d.ts.map +1 -0
- package/dist/cli/template-format.test.d.ts +2 -0
- package/dist/cli/template-format.test.d.ts.map +1 -0
- package/dist/cli/template-ledger.test.d.ts +2 -0
- package/dist/cli/template-ledger.test.d.ts.map +1 -0
- package/dist/cli/tokens-css.test.d.ts +2 -0
- package/dist/cli/tokens-css.test.d.ts.map +1 -0
- package/dist/content/article-actions.test.d.ts +2 -0
- package/dist/content/article-actions.test.d.ts.map +1 -0
- package/dist/content/article-model.test.d.ts +2 -0
- package/dist/content/article-model.test.d.ts.map +1 -0
- package/dist/content/block-split.test.d.ts +2 -0
- package/dist/content/block-split.test.d.ts.map +1 -0
- package/dist/content/fence-block.d.ts +37 -10
- package/dist/content/fence-block.d.ts.map +1 -1
- package/dist/content/fence-block.js +80 -14
- package/dist/content/heading-components.d.ts.map +1 -1
- package/dist/content/heading-components.js +7 -4
- package/dist/content/heading-components.test.d.ts +2 -0
- package/dist/content/heading-components.test.d.ts.map +1 -0
- package/dist/content/index.d.ts +6 -3
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +5 -1
- package/dist/content/markdown.d.ts +101 -14
- package/dist/content/markdown.d.ts.map +1 -1
- package/dist/content/markdown.js +141 -28
- package/dist/content/markdown.test.d.ts +2 -0
- package/dist/content/markdown.test.d.ts.map +1 -0
- package/dist/content/prose.module.css.test.d.ts +2 -0
- package/dist/content/prose.module.css.test.d.ts.map +1 -0
- package/dist/content/sanitize.d.ts +170 -0
- package/dist/content/sanitize.d.ts.map +1 -0
- package/dist/content/sanitize.js +178 -0
- package/dist/content/sanitize.test.d.ts +2 -0
- package/dist/content/sanitize.test.d.ts.map +1 -0
- package/dist/content/slug.test.d.ts +2 -0
- package/dist/content/slug.test.d.ts.map +1 -0
- package/dist/dashboard/stat-card.test.d.ts +2 -0
- package/dist/dashboard/stat-card.test.d.ts.map +1 -0
- package/dist/guard/check-source.test.d.ts +2 -0
- package/dist/guard/check-source.test.d.ts.map +1 -0
- package/dist/guard/guard-hook.test.d.ts +2 -0
- package/dist/guard/guard-hook.test.d.ts.map +1 -0
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +1 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2
- package/dist/provider/index.test.d.ts +2 -0
- package/dist/provider/index.test.d.ts.map +1 -0
- package/dist/provider/lab-theme.test.d.ts +2 -0
- package/dist/provider/lab-theme.test.d.ts.map +1 -0
- package/dist/router-tanstack/multi-search-param-store.test.d.ts +2 -0
- package/dist/router-tanstack/multi-search-param-store.test.d.ts.map +1 -0
- package/dist/styles.floor.test.d.ts +2 -0
- package/dist/styles.floor.test.d.ts.map +1 -0
- package/dist/surfaces.d.ts +5 -5
- package/dist/surfaces.d.ts.map +1 -1
- package/dist/surfaces.js +13 -4
- package/dist/theme/border-coverage.test.d.ts +18 -0
- package/dist/theme/border-coverage.test.d.ts.map +1 -0
- package/dist/theme/contrast.test.d.ts +2 -0
- package/dist/theme/contrast.test.d.ts.map +1 -0
- package/dist/theme/density-option.test.d.ts +2 -0
- package/dist/theme/density-option.test.d.ts.map +1 -0
- package/dist/theme/density-relations.test.d.ts +2 -0
- package/dist/theme/density-relations.test.d.ts.map +1 -0
- package/dist/theme/derive-theme.test.d.ts +2 -0
- package/dist/theme/derive-theme.test.d.ts.map +1 -0
- package/dist/theme/fonts-option.test.d.ts +2 -0
- package/dist/theme/fonts-option.test.d.ts.map +1 -0
- package/dist/theme/radius-option.test.d.ts +2 -0
- package/dist/theme/radius-option.test.d.ts.map +1 -0
- package/dist/theme/radius.test.d.ts +2 -0
- package/dist/theme/radius.test.d.ts.map +1 -0
- package/dist/theme/shadow-surfaces.test.d.ts +26 -0
- package/dist/theme/shadow-surfaces.test.d.ts.map +1 -0
- package/dist/theme/spacing.test.d.ts +2 -0
- package/dist/theme/spacing.test.d.ts.map +1 -0
- package/dist/theme/use-basalt-spacing.test.d.ts +2 -0
- package/dist/theme/use-basalt-spacing.test.d.ts.map +1 -0
- package/dist/theme-lab/derive-controls.test.d.ts +2 -0
- package/dist/theme-lab/derive-controls.test.d.ts.map +1 -0
- package/dist/theme-lab/derive-state.test.d.ts +2 -0
- package/dist/theme-lab/derive-state.test.d.ts.map +1 -0
- package/dist/theme-lab/theme-lab.test.d.ts +2 -0
- package/dist/theme-lab/theme-lab.test.d.ts.map +1 -0
- package/dist/tokens/build-fonts-css.test.d.ts +2 -0
- package/dist/tokens/build-fonts-css.test.d.ts.map +1 -0
- package/dist/tokens/build-radius-css.test.d.ts +2 -0
- package/dist/tokens/build-radius-css.test.d.ts.map +1 -0
- package/dist/tokens/density.test.d.ts +2 -0
- package/dist/tokens/density.test.d.ts.map +1 -0
- package/dist/tokens/derive-radius.test.d.ts +2 -0
- package/dist/tokens/derive-radius.test.d.ts.map +1 -0
- package/dist/tokens/derive.test.d.ts +2 -0
- package/dist/tokens/derive.test.d.ts.map +1 -0
- package/dist/tokens/palette.test.d.ts +2 -0
- package/dist/tokens/palette.test.d.ts.map +1 -0
- package/dist/vite-color-mix.test.d.ts +2 -0
- package/dist/vite-color-mix.test.d.ts.map +1 -0
- package/dist/vite.test.d.ts +2 -0
- package/dist/vite.test.d.ts.map +1 -0
- package/llms.txt +7 -7
- package/package.json +6 -1
- package/src/agent/adapter.test.ts +1403 -0
- package/src/agent/adapter.ts +887 -0
- package/src/agent/ai-sdk-transport.test.ts +380 -55
- package/src/agent/ai-sdk-transport.ts +206 -53
- package/src/agent/coalesce.test.ts +387 -0
- package/src/agent/coalesce.ts +229 -0
- package/src/agent/foreign.test.ts +51 -0
- package/src/agent/foreign.ts +140 -0
- package/src/agent/history.ts +2 -0
- package/src/agent/id.test.ts +75 -0
- package/src/agent/id.ts +91 -0
- package/src/agent/index.ts +34 -3
- package/src/agent/merge.test.ts +206 -0
- package/src/agent/merge.ts +114 -0
- package/src/agent/part-list.test.tsx +112 -0
- package/src/agent/part-list.tsx +108 -35
- package/src/agent/parts.test.ts +516 -57
- package/src/agent/parts.ts +323 -32
- package/src/agent/thread.test.ts +75 -2
- package/src/agent/thread.ts +21 -1
- package/src/agent/transport.test.ts +70 -0
- package/src/agent/transport.ts +72 -20
- package/src/agent/use-agent-stream.test.tsx +53 -19
- package/src/agent/use-agent-stream.ts +13 -3
- package/src/agent/use-agent-thread-runs.foreign.test.tsx +138 -0
- package/src/agent/use-agent-thread-runs.resume.test.tsx +44 -11
- package/src/agent/use-agent-thread-runs.test.tsx +418 -22
- package/src/agent/use-agent-thread-runs.ts +170 -26
- package/src/agent/use-agent-thread-runs.wedge.test.tsx +15 -7
- package/src/agent-chat/composer.test.tsx +924 -0
- package/src/agent-chat/composer.tsx +521 -52
- package/src/agent-chat/index.ts +5 -1
- package/src/agent-chat/thread-detail-panel.tsx +3 -1
- package/src/agent-chat/thread-message.test.tsx +351 -0
- package/src/agent-chat/thread-message.tsx +262 -64
- package/src/agent-chat/thread-workspace.tsx +1 -1
- package/src/agent-chat/tool-chip.test.tsx +139 -0
- package/src/agent-chat/tool-chip.tsx +219 -0
- package/src/cli/index.ts +0 -1
- package/src/cli/template-format.test.ts +2 -2
- package/src/content/fence-block.tsx +242 -15
- package/src/content/heading-components.test.tsx +65 -0
- package/src/content/heading-components.tsx +26 -5
- package/src/content/index.ts +19 -3
- package/src/content/markdown.test.tsx +603 -0
- package/src/content/markdown.tsx +355 -35
- package/src/content/sanitize.test.ts +659 -0
- package/src/content/sanitize.ts +510 -0
- package/src/dashboard/stat-card.test.tsx +6 -1
- package/src/guard/check-source.test.ts +8 -4
- package/src/guard/index.ts +1 -3
- package/src/index.ts +15 -1
- package/src/surfaces.ts +13 -4
- package/src/theme/contrast.test.ts +1 -4
- package/src/theme/density-relations.test.ts +4 -2
- package/src/theme/fonts-option.test.ts +7 -1
- package/src/theme/shadow-surfaces.test.ts +12 -6
- package/src/theme/use-basalt-spacing.test.tsx +2 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { assertNever } from "../register.js";
|
|
2
|
+
import { TERMINAL_TOOL_STATES } from "./parts.js";
|
|
1
3
|
function createAiSdkResolver(options) {
|
|
2
4
|
let resolved;
|
|
3
5
|
return function resolveAiSdk() {
|
|
@@ -15,55 +17,133 @@ function createAiSdkResolver(options) {
|
|
|
15
17
|
return resolved;
|
|
16
18
|
};
|
|
17
19
|
}
|
|
18
|
-
function diffPart(prevPart, currPart) {
|
|
20
|
+
function diffPart(chatId, index, prevPart, currPart, toolStartTimes) {
|
|
19
21
|
switch (currPart.type) {
|
|
20
22
|
case "text": {
|
|
21
23
|
const prevText = prevPart?.type === "text" ? prevPart.text : "";
|
|
22
24
|
const delta = currPart.text.slice(prevText.length);
|
|
23
|
-
return delta.length === 0 ? [] : [{ type: "text", text: delta }];
|
|
25
|
+
return delta.length === 0 ? [] : [{ id: `${chatId}#${index}`, type: "text", text: delta, offset: prevText.length }];
|
|
24
26
|
}
|
|
25
27
|
case "reasoning": {
|
|
26
28
|
const prevText = prevPart?.type === "reasoning" ? prevPart.text : "";
|
|
27
29
|
const delta = currPart.text.slice(prevText.length);
|
|
28
|
-
return delta.length === 0 ? [] : [{ type: "reasoning", text: delta }];
|
|
30
|
+
return delta.length === 0 ? [] : [{ id: `${chatId}#${index}`, type: "reasoning", text: delta, offset: prevText.length }];
|
|
29
31
|
}
|
|
30
32
|
case "source-url": {
|
|
31
33
|
if (prevPart !== void 0) return [];
|
|
32
|
-
const source = { type: "source", url: currPart.url };
|
|
34
|
+
const source = { id: `${chatId}#${index}`, type: "source", url: currPart.url };
|
|
33
35
|
return [currPart.title !== void 0 ? { ...source, title: currPart.title } : source];
|
|
34
36
|
}
|
|
35
37
|
default:
|
|
36
|
-
return currPart
|
|
38
|
+
return isToolLikePart(currPart) ? diffToolPart(prevPart, currPart, toolStartTimes) : [];
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
|
-
function
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
function isToolLikePart(part) {
|
|
42
|
+
return part.type === "dynamic-tool" || part.type.startsWith("tool-");
|
|
43
|
+
}
|
|
44
|
+
function deriveToolName(part) {
|
|
45
|
+
return part.type === "dynamic-tool" ? part.toolName : part.type.split("-").slice(1).join("-");
|
|
46
|
+
}
|
|
47
|
+
function outputSnapshot(part) {
|
|
48
|
+
return part.state === "output-available" ? { output: part.output, preliminary: part.preliminary } : void 0;
|
|
49
|
+
}
|
|
50
|
+
function isSameToolState(prev, curr) {
|
|
51
|
+
if (prev.state !== curr.state) return false;
|
|
52
|
+
const prevOutput = outputSnapshot(prev);
|
|
53
|
+
const currOutput = outputSnapshot(curr);
|
|
54
|
+
if (prevOutput === void 0 || currOutput === void 0) return true;
|
|
55
|
+
return JSON.stringify(prevOutput.output) === JSON.stringify(currOutput.output) && prevOutput.preliminary === currOutput.preliminary;
|
|
56
|
+
}
|
|
57
|
+
function diffToolPart(prevPart, currPart, toolStartTimes) {
|
|
58
|
+
if (!toolStartTimes.has(currPart.toolCallId)) {
|
|
59
|
+
toolStartTimes.set(currPart.toolCallId, Date.now());
|
|
60
|
+
}
|
|
61
|
+
const prev = prevPart !== void 0 && isToolLikePart(prevPart) ? prevPart : void 0;
|
|
62
|
+
if (prev !== void 0 && isSameToolState(prev, currPart)) return [];
|
|
63
|
+
const settled = TERMINAL_TOOL_STATES.includes(currPart.state);
|
|
64
|
+
const durationMs = settled ? Date.now() - (toolStartTimes.get(currPart.toolCallId) ?? Date.now()) : void 0;
|
|
45
65
|
const base = {
|
|
66
|
+
id: `tool#${currPart.toolCallId}`,
|
|
46
67
|
type: "tool",
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
68
|
+
toolCallId: currPart.toolCallId,
|
|
69
|
+
toolName: deriveToolName(currPart),
|
|
70
|
+
...currPart.providerExecuted !== void 0 ? { providerExecuted: currPart.providerExecuted } : {},
|
|
71
|
+
...durationMs !== void 0 ? { durationMs } : {}
|
|
50
72
|
};
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
73
|
+
switch (currPart.state) {
|
|
74
|
+
case "input-streaming":
|
|
75
|
+
return [
|
|
76
|
+
{
|
|
77
|
+
...base,
|
|
78
|
+
state: "input-streaming",
|
|
79
|
+
...currPart.input !== void 0 ? { input: currPart.input } : {}
|
|
80
|
+
}
|
|
81
|
+
];
|
|
82
|
+
case "input-available":
|
|
83
|
+
return [{ ...base, state: "input-available", input: currPart.input }];
|
|
84
|
+
case "approval-requested":
|
|
85
|
+
return [
|
|
86
|
+
{
|
|
87
|
+
...base,
|
|
88
|
+
state: "approval-requested",
|
|
89
|
+
input: currPart.input,
|
|
90
|
+
approval: currPart.approval
|
|
91
|
+
}
|
|
92
|
+
];
|
|
93
|
+
case "approval-responded":
|
|
94
|
+
return [
|
|
95
|
+
{
|
|
96
|
+
...base,
|
|
97
|
+
state: "approval-responded",
|
|
98
|
+
input: currPart.input,
|
|
99
|
+
approval: currPart.approval
|
|
100
|
+
}
|
|
101
|
+
];
|
|
102
|
+
case "output-available":
|
|
103
|
+
return [
|
|
104
|
+
{
|
|
105
|
+
...base,
|
|
106
|
+
state: "output-available",
|
|
107
|
+
input: currPart.input,
|
|
108
|
+
output: currPart.output,
|
|
109
|
+
...currPart.preliminary !== void 0 ? { preliminary: currPart.preliminary } : {},
|
|
110
|
+
...currPart.approval !== void 0 ? { approval: currPart.approval } : {}
|
|
111
|
+
}
|
|
112
|
+
];
|
|
113
|
+
case "output-error": {
|
|
114
|
+
const rawInput = currPart.type === "dynamic-tool" ? void 0 : currPart.rawInput;
|
|
115
|
+
return [
|
|
116
|
+
{
|
|
117
|
+
...base,
|
|
118
|
+
state: "output-error",
|
|
119
|
+
...currPart.input !== void 0 ? { input: currPart.input } : {},
|
|
120
|
+
errorText: currPart.errorText,
|
|
121
|
+
...rawInput !== void 0 ? { rawInput } : {},
|
|
122
|
+
...currPart.approval !== void 0 ? { approval: currPart.approval } : {}
|
|
123
|
+
}
|
|
124
|
+
];
|
|
125
|
+
}
|
|
126
|
+
case "output-denied":
|
|
127
|
+
return [
|
|
128
|
+
{ ...base, state: "output-denied", input: currPart.input, approval: currPart.approval }
|
|
129
|
+
];
|
|
130
|
+
default:
|
|
131
|
+
return assertNever(currPart);
|
|
132
|
+
}
|
|
54
133
|
}
|
|
55
|
-
function diffMessage(prev, curr) {
|
|
134
|
+
function diffMessage(chatId, prev, curr, toolStartTimes) {
|
|
56
135
|
const deltas = [];
|
|
57
136
|
curr.parts.forEach((part, i) => {
|
|
58
|
-
deltas.push(...diffPart(prev?.parts[i], part));
|
|
137
|
+
deltas.push(...diffPart(chatId, i, prev?.parts[i], part, toolStartTimes));
|
|
59
138
|
});
|
|
60
139
|
return deltas;
|
|
61
140
|
}
|
|
62
|
-
async function* diffChunkStream(chunkStream, readStream, signal) {
|
|
141
|
+
async function* diffChunkStream(chatId, chunkStream, readStream, signal) {
|
|
63
142
|
let prev;
|
|
143
|
+
const toolStartTimes = /* @__PURE__ */ new Map();
|
|
64
144
|
for await (const curr of readStream({ stream: chunkStream })) {
|
|
65
145
|
if (signal?.aborted) return;
|
|
66
|
-
for (const part of diffMessage(prev, curr)) {
|
|
146
|
+
for (const part of diffMessage(chatId, prev, curr, toolStartTimes)) {
|
|
67
147
|
yield part;
|
|
68
148
|
}
|
|
69
149
|
prev = curr;
|
|
@@ -73,8 +153,12 @@ function aiSdkTransport(options) {
|
|
|
73
153
|
const resolveAiSdk = createAiSdkResolver(options);
|
|
74
154
|
function makeTransport(chatId) {
|
|
75
155
|
return {
|
|
156
|
+
// Every text/reasoning delta this transport emits carries an authoritative `offset` (see
|
|
157
|
+
// diffPart), so a replayed run rewrites in place instead of duplicating — the literal
|
|
158
|
+
// condition this assertion stands for.
|
|
159
|
+
idempotentReplay: true,
|
|
76
160
|
async *stream(input, signal) {
|
|
77
|
-
yield { type: "start", runId: chatId, resumeToken: chatId };
|
|
161
|
+
yield { id: `${chatId}#start`, type: "start", runId: chatId, resumeToken: chatId };
|
|
78
162
|
const { httpTransport, readUIMessageStream: readStream } = await resolveAiSdk();
|
|
79
163
|
const userMessage = {
|
|
80
164
|
id: crypto.randomUUID(),
|
|
@@ -88,13 +172,13 @@ function aiSdkTransport(options) {
|
|
|
88
172
|
messageId: void 0,
|
|
89
173
|
abortSignal: signal
|
|
90
174
|
});
|
|
91
|
-
yield* diffChunkStream(chunkStream, readStream, signal);
|
|
175
|
+
yield* diffChunkStream(chatId, chunkStream, readStream, signal);
|
|
92
176
|
},
|
|
93
177
|
async *resume(resumeToken, signal) {
|
|
94
178
|
const { httpTransport, readUIMessageStream: readStream } = await resolveAiSdk();
|
|
95
179
|
const chunkStream = await httpTransport.reconnectToStream({ chatId: resumeToken });
|
|
96
180
|
if (chunkStream === null) return;
|
|
97
|
-
yield* diffChunkStream(chunkStream, readStream, signal);
|
|
181
|
+
yield* diffChunkStream(resumeToken, chunkStream, readStream, signal);
|
|
98
182
|
}
|
|
99
183
|
};
|
|
100
184
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-sdk-transport.test.d.ts","sourceRoot":"","sources":["../../src/agent/ai-sdk-transport.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* coalesceParts — merges a rendered-turn's parts array for display.
|
|
3
|
+
*
|
|
4
|
+
* Merges consecutive `text`+`text` and `reasoning`+`reasoning` parts (streamed answers arrive as
|
|
5
|
+
* many small fragments; this renders a streaming reply as one flowing block instead of a stack of
|
|
6
|
+
* fragments). Merges `tool` parts BY `toolCallId` regardless of adjacency — the AI SDK re-emits a
|
|
7
|
+
* tool call's `input-available` then `output-available` as two separate parts, not necessarily
|
|
8
|
+
* adjacent once other parts interleave; without this rule a UI renders two stacked tool blocks for
|
|
9
|
+
* one call. The later state always wins; the merged part keeps the position of its FIRST
|
|
10
|
+
* occurrence (stable ordering); and an `input` (or `toolName`) supplied by an earlier state
|
|
11
|
+
* survives a later state that omits it — `tool-output-available` carries only `output` on the
|
|
12
|
+
* wire, the SDK re-supplies `input` by reading it back off the stored invocation, and a merge that
|
|
13
|
+
* replaces wholesale rather than accumulating would lose it outright.
|
|
14
|
+
*
|
|
15
|
+
* A settled tool part can arrive without a `toolCallId` at all — `approval-responded` plus the
|
|
16
|
+
* three terminal states all carry it as optional (see `MaybeIdentified` in `parts.ts`).
|
|
17
|
+
* `approval-responded` is where the WIRE itself omits it: the SDK's `tool-approval-response` chunk
|
|
18
|
+
* carries no `toolCallId`, only `approval.id`, so it is resolved by reverse lookup on `approval.id`
|
|
19
|
+
* instead — this maintains an `approvalId -> toolCallId` side index while folding and routes such a
|
|
20
|
+
* part through it. A part resolvable by neither key is kept as-is rather than dropped; never
|
|
21
|
+
* silently discard a part.
|
|
22
|
+
*
|
|
23
|
+
* The fold is strictly LEFT-TO-RIGHT with no backtracking: the side index only holds ids seen so
|
|
24
|
+
* far, so an unidentified `approval-responded` arriving BEFORE its `approval-requested` stays its
|
|
25
|
+
* own block and the later request opens a second one. That is deliberate, not an oversight —
|
|
26
|
+
* reconciling backwards would let the request (the later arrival) win under the "later state wins"
|
|
27
|
+
* rule and render an already-answered call as still pending, which is worse than two blocks in an
|
|
28
|
+
* ordering the wire never produces.
|
|
29
|
+
*
|
|
30
|
+
* **This function never throws.** It is public, it runs on the render path, and it folds data that
|
|
31
|
+
* came off a wire — so every anomaly (an unrecognized tool state, an unresolvable id) degrades to
|
|
32
|
+
* "keep the part as it arrived", announced in dev, rather than aborting the render. A throw here
|
|
33
|
+
* would turn one malformed chunk into a permanently dead transcript. Exhaustiveness over the tool
|
|
34
|
+
* states is still enforced, but at COMPILE time only (see `mergeToolPart`'s default branch).
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* import { coalesceParts } from 'basalt-ui/agent'
|
|
38
|
+
* <PartList parts={coalesceParts(parts)} />
|
|
39
|
+
*/
|
|
40
|
+
import type { AgentPart } from './parts.js';
|
|
41
|
+
export declare function coalesceParts<TPart extends AgentPart>(parts: readonly TPart[]): TPart[];
|
|
42
|
+
//# sourceMappingURL=coalesce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coalesce.d.ts","sourceRoot":"","sources":["../../src/agent/coalesce.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,OAAO,KAAK,EAAE,SAAS,EAAyC,MAAM,SAAS,CAAA;AA8I/E,wBAAgB,aAAa,CAAC,KAAK,SAAS,SAAS,EAAE,KAAK,EAAE,SAAS,KAAK,EAAE,GAAG,KAAK,EAAE,CA+CvF"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
function isTextLikeType(type) {
|
|
2
|
+
return type === "text" || type === "reasoning";
|
|
3
|
+
}
|
|
4
|
+
function resolveToolCallId(part, toolCallIdByApprovalId) {
|
|
5
|
+
if (part.toolCallId !== void 0) return part.toolCallId;
|
|
6
|
+
const approval = "approval" in part ? part.approval : void 0;
|
|
7
|
+
return approval !== void 0 ? toolCallIdByApprovalId.get(approval.id) : void 0;
|
|
8
|
+
}
|
|
9
|
+
function registerApprovalIndex(part, toolCallIdByApprovalId) {
|
|
10
|
+
const approval = "approval" in part ? part.approval : void 0;
|
|
11
|
+
if (approval === void 0) return;
|
|
12
|
+
if (part.toolCallId === void 0) return;
|
|
13
|
+
toolCallIdByApprovalId.set(approval.id, part.toolCallId);
|
|
14
|
+
}
|
|
15
|
+
function mergeToolPart(existing, next) {
|
|
16
|
+
const existingInput = "input" in existing ? existing.input : void 0;
|
|
17
|
+
const nextInput = "input" in next ? next.input : void 0;
|
|
18
|
+
const input = nextInput !== void 0 ? nextInput : existingInput;
|
|
19
|
+
const durationMs = next.durationMs ?? existing.durationMs;
|
|
20
|
+
const providerExecuted = next.providerExecuted ?? existing.providerExecuted;
|
|
21
|
+
const settledToolCallId = next.toolCallId ?? existing.toolCallId;
|
|
22
|
+
const common = {
|
|
23
|
+
id: next.id,
|
|
24
|
+
type: "tool",
|
|
25
|
+
toolName: next.toolName.length > 0 ? next.toolName : existing.toolName,
|
|
26
|
+
...durationMs !== void 0 ? { durationMs } : {},
|
|
27
|
+
...providerExecuted !== void 0 ? { providerExecuted } : {}
|
|
28
|
+
};
|
|
29
|
+
switch (next.state) {
|
|
30
|
+
case "input-streaming":
|
|
31
|
+
return {
|
|
32
|
+
...common,
|
|
33
|
+
toolCallId: next.toolCallId,
|
|
34
|
+
state: "input-streaming",
|
|
35
|
+
...input !== void 0 ? { input } : {}
|
|
36
|
+
};
|
|
37
|
+
case "input-available":
|
|
38
|
+
return { ...common, toolCallId: next.toolCallId, state: "input-available", input };
|
|
39
|
+
case "approval-requested":
|
|
40
|
+
return {
|
|
41
|
+
...common,
|
|
42
|
+
toolCallId: next.toolCallId,
|
|
43
|
+
state: "approval-requested",
|
|
44
|
+
input,
|
|
45
|
+
approval: next.approval
|
|
46
|
+
};
|
|
47
|
+
case "approval-responded":
|
|
48
|
+
return {
|
|
49
|
+
...common,
|
|
50
|
+
...settledToolCallId !== void 0 ? { toolCallId: settledToolCallId } : {},
|
|
51
|
+
state: "approval-responded",
|
|
52
|
+
input,
|
|
53
|
+
approval: next.approval
|
|
54
|
+
};
|
|
55
|
+
case "output-available":
|
|
56
|
+
return {
|
|
57
|
+
...common,
|
|
58
|
+
...settledToolCallId !== void 0 ? { toolCallId: settledToolCallId } : {},
|
|
59
|
+
state: "output-available",
|
|
60
|
+
input,
|
|
61
|
+
output: next.output,
|
|
62
|
+
...next.preliminary !== void 0 ? { preliminary: next.preliminary } : {},
|
|
63
|
+
...next.approval !== void 0 ? { approval: next.approval } : {}
|
|
64
|
+
};
|
|
65
|
+
case "output-error":
|
|
66
|
+
return {
|
|
67
|
+
...common,
|
|
68
|
+
...settledToolCallId !== void 0 ? { toolCallId: settledToolCallId } : {},
|
|
69
|
+
state: "output-error",
|
|
70
|
+
errorText: next.errorText,
|
|
71
|
+
...input !== void 0 ? { input } : {},
|
|
72
|
+
...next.rawInput !== void 0 ? { rawInput: next.rawInput } : {},
|
|
73
|
+
...next.approval !== void 0 ? { approval: next.approval } : {}
|
|
74
|
+
};
|
|
75
|
+
case "output-denied":
|
|
76
|
+
return {
|
|
77
|
+
...common,
|
|
78
|
+
...settledToolCallId !== void 0 ? { toolCallId: settledToolCallId } : {},
|
|
79
|
+
state: "output-denied",
|
|
80
|
+
input,
|
|
81
|
+
approval: next.approval
|
|
82
|
+
};
|
|
83
|
+
default: {
|
|
84
|
+
const unhandled = next;
|
|
85
|
+
const fallback = unhandled;
|
|
86
|
+
if (process.env["NODE_ENV"] !== "production") {
|
|
87
|
+
console.warn(
|
|
88
|
+
`[basalt] coalesceParts: unrecognized tool state ${JSON.stringify(fallback.state)} \u2014 kept, merged best-effort`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return { ...fallback, ...common };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function coalesceParts(parts) {
|
|
96
|
+
const out = [];
|
|
97
|
+
const toolIndexByCallId = /* @__PURE__ */ new Map();
|
|
98
|
+
const toolCallIdByApprovalId = /* @__PURE__ */ new Map();
|
|
99
|
+
for (const part of parts) {
|
|
100
|
+
if (isTextLikeType(part.type)) {
|
|
101
|
+
const last = out[out.length - 1];
|
|
102
|
+
if (last !== void 0 && last.type === part.type) {
|
|
103
|
+
const merged = {
|
|
104
|
+
...part,
|
|
105
|
+
text: last.text + part.text
|
|
106
|
+
};
|
|
107
|
+
out[out.length - 1] = merged;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
out.push(part);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (part.type === "tool") {
|
|
114
|
+
const toolPart = part;
|
|
115
|
+
registerApprovalIndex(toolPart, toolCallIdByApprovalId);
|
|
116
|
+
const resolvedCallId = resolveToolCallId(toolPart, toolCallIdByApprovalId);
|
|
117
|
+
if (resolvedCallId === void 0) {
|
|
118
|
+
out.push(part);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const existingIndex = toolIndexByCallId.get(resolvedCallId);
|
|
122
|
+
if (existingIndex === void 0) {
|
|
123
|
+
toolIndexByCallId.set(resolvedCallId, out.length);
|
|
124
|
+
out.push(part);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const existing = out[existingIndex];
|
|
128
|
+
out[existingIndex] = mergeToolPart(existing, toolPart);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
out.push(part);
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
export {
|
|
136
|
+
coalesceParts
|
|
137
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coalesce.test.d.ts","sourceRoot":"","sources":["../../src/agent/coalesce.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* foreign.ts — the open half of the transcript's part-rendering registry.
|
|
3
|
+
*
|
|
4
|
+
* `AgentPart` (`./parts`) is a closed, exhaustively-switched union — adding a variant without
|
|
5
|
+
* updating every consuming switch is a tsc error (`PartList`'s `assertNever` gate). A transport
|
|
6
|
+
* that streams app-specific structured data (a chart spec, a progress ping) needs a part type
|
|
7
|
+
* `AgentPart` will never carry — closing the union around it would defeat the whole point of the
|
|
8
|
+
* gate. This file is the seam: `ForeignPart` describes what basalt does NOT know, `ConsumerPart`
|
|
9
|
+
* resolves the augmented union a consumer registers via `declare module 'basalt-ui'`, and
|
|
10
|
+
* `definePartRenderers` types an exhaustive renderer map against it. See
|
|
11
|
+
* `agent-chat/thread-message.tsx`'s three-step resolution order for how this plugs into
|
|
12
|
+
* `ThreadTranscript`: a consumer renderer wins first, `narrowAgentPart` routes the built-in six to
|
|
13
|
+
* `PartList`'s own exhaustive switch second, and `fallbackRenderer` catches anything left.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* // app-side: src/features/chat/parts.ts
|
|
17
|
+
* type ChartPart = { type: 'data-chart'; id: string; spec: ChartSpec }
|
|
18
|
+
* declare module 'basalt-ui' {
|
|
19
|
+
* interface BasaltRegister { parts: ChartPart }
|
|
20
|
+
* }
|
|
21
|
+
* export const chatRenderers = definePartRenderers({
|
|
22
|
+
* 'data-chart': ({ part, settled }) => (settled ? <Chart spec={part.spec} /> : <ChartSkeleton />),
|
|
23
|
+
* })
|
|
24
|
+
*/
|
|
25
|
+
import type { ReactNode } from 'react';
|
|
26
|
+
import type { BasaltRegister } from '../register.js';
|
|
27
|
+
import type { ChatMessage } from './history.js';
|
|
28
|
+
import type { AgentPart } from './parts.js';
|
|
29
|
+
/** A part basalt does not know. NOT a member of AgentPart — the built-in union stays closed. */
|
|
30
|
+
export type ForeignPart = {
|
|
31
|
+
readonly type: string;
|
|
32
|
+
readonly id: string;
|
|
33
|
+
readonly [k: string]: unknown;
|
|
34
|
+
};
|
|
35
|
+
/** What a transcript actually holds — basalt's own six variants plus anything a consumer registers. */
|
|
36
|
+
export type TranscriptPart = AgentPart | ForeignPart;
|
|
37
|
+
/** The consumer's registered foreign-part union, or `never` when un-augmented. */
|
|
38
|
+
export type ConsumerPart = BasaltRegister extends {
|
|
39
|
+
parts: infer P extends ForeignPart;
|
|
40
|
+
} ? P : never;
|
|
41
|
+
/**
|
|
42
|
+
* Un-augmented: a loose string-keyed map (any renderer, any key — there is nothing to check
|
|
43
|
+
* against). Augmented: a MISSING key is a tsc error (`definePartRenderers` requires the full
|
|
44
|
+
* registered union), and each renderer's `part` is narrowed to exactly its variant. Same
|
|
45
|
+
* bound-check mechanism as `SeriesKey` (`register.ts:26-58`), applied to a union instead of a map.
|
|
46
|
+
*
|
|
47
|
+
* NOT caught: a STALE (extra, unregistered) key. `<const T extends Constraint>` inference does
|
|
48
|
+
* not excess-property-check an object-literal argument the way a direct assignment to a fixed
|
|
49
|
+
* target type would (verified against this repo's tsc — see
|
|
50
|
+
* `apps/playground/src/demo/agent-part-registry.type-guard.ts`'s Fixture 3) — an extra key
|
|
51
|
+
* compiles as dead code rather than erroring. Catching it would need an `Exact<T, Shape>`-style
|
|
52
|
+
* wrapper that departs from the `spec: T` "canonical token-factory contract" every other `defineX`
|
|
53
|
+
* in this codebase follows verbatim; that trade was left as a documented gap rather than a one-off
|
|
54
|
+
* factory shape.
|
|
55
|
+
*/
|
|
56
|
+
export type PartRenderers = [ConsumerPart] extends [never] ? Readonly<Record<string, PartRenderer<ForeignPart>>> : {
|
|
57
|
+
readonly [K in ConsumerPart['type']]: PartRenderer<Extract<ConsumerPart, {
|
|
58
|
+
type: K;
|
|
59
|
+
}>>;
|
|
60
|
+
};
|
|
61
|
+
/** Everything a foreign-part renderer needs beyond the part itself. */
|
|
62
|
+
export type PartRenderContext<TPart = ForeignPart> = {
|
|
63
|
+
readonly part: TPart;
|
|
64
|
+
readonly messageId: string;
|
|
65
|
+
readonly partId: string;
|
|
66
|
+
/** false while this part belongs to the in-flight tail of a streaming turn. */
|
|
67
|
+
readonly settled: boolean;
|
|
68
|
+
readonly role: ChatMessage['role'];
|
|
69
|
+
};
|
|
70
|
+
export type PartRenderer<TPart = ForeignPart> = (ctx: PartRenderContext<TPart>) => ReactNode;
|
|
71
|
+
/**
|
|
72
|
+
* Canonical const-generic factory (`packages/basalt-ui/CLAUDE.md`, "Canonical token-factory
|
|
73
|
+
* contract") — identity passthrough, exact-keyed return, no builder, no config bag. Mirrors
|
|
74
|
+
* `defineSeries` (`tokens/index.ts:648-650`) and `defineNotifications`
|
|
75
|
+
* (`notifications/define-notifications.ts:112-115`).
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* const chatRenderers = definePartRenderers({
|
|
79
|
+
* 'data-chart': ({ part }) => <Chart spec={part.spec} />,
|
|
80
|
+
* })
|
|
81
|
+
*/
|
|
82
|
+
export declare function definePartRenderers<const T extends PartRenderers>(map: T): T;
|
|
83
|
+
/**
|
|
84
|
+
* Returns `part` as an `AgentPart` when its `type` is one of the six built-in variants, else
|
|
85
|
+
* `null`. Deliberately NOT routed through `parseAgentPart`: that function is a VALIDATING parser
|
|
86
|
+
* for untrusted wire data — it checks every field a variant requires (`runId`, `toolCallId`,
|
|
87
|
+
* `approval`, ...) and would reject an already-well-typed in-memory `AgentPart` on shape grounds
|
|
88
|
+
* if it doesn't happen to match the wire representation byte-for-byte. `narrowAgentPart` is the
|
|
89
|
+
* cheap complement: a plain discriminator check on a value already known to be a `TranscriptPart`,
|
|
90
|
+
* used purely to route rendering — it trusts the caller's typing rather than re-validating it.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* const agentPart = narrowAgentPart(part)
|
|
94
|
+
* if (agentPart !== null) return <PartList parts={[agentPart]} />
|
|
95
|
+
*/
|
|
96
|
+
export declare function narrowAgentPart(part: TranscriptPart): AgentPart | null;
|
|
97
|
+
//# sourceMappingURL=foreign.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"foreign.d.ts","sourceRoot":"","sources":["../../src/agent/foreign.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAIxC,gGAAgG;AAChG,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CAC9B,CAAA;AAED,uGAAuG;AACvG,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,WAAW,CAAA;AAuBpD,kFAAkF;AAClF,MAAM,MAAM,YAAY,GAAG,cAAc,SAAS;IAAE,KAAK,EAAE,MAAM,CAAC,SAAS,WAAW,CAAA;CAAE,GAAG,CAAC,GAAG,KAAK,CAAA;AAIpG;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,KAAK,CAAC,GACtD,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC,GACnD;IAAE,QAAQ,EAAE,CAAC,IAAI,YAAY,CAAC,MAAM,CAAC,GAAG,YAAY,CAAC,OAAO,CAAC,YAAY,EAAE;QAAE,IAAI,EAAE,CAAC,CAAA;KAAE,CAAC,CAAC;CAAE,CAAA;AAE9F,uEAAuE;AACvE,MAAM,MAAM,iBAAiB,CAAC,KAAK,GAAG,WAAW,IAAI;IACnD,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;IACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CACnC,CAAA;AAED,MAAM,MAAM,YAAY,CAAC,KAAK,GAAG,WAAW,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,KAAK,CAAC,KAAK,SAAS,CAAA;AAE5F;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,CAAC,CAAC,SAAS,aAAa,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAE5E;AAaD;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI,CAEtE"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
function definePartRenderers(map) {
|
|
2
|
+
return map;
|
|
3
|
+
}
|
|
4
|
+
const AGENT_PART_TYPES = /* @__PURE__ */ new Set([
|
|
5
|
+
"start",
|
|
6
|
+
"text",
|
|
7
|
+
"reasoning",
|
|
8
|
+
"tool",
|
|
9
|
+
"source",
|
|
10
|
+
"error"
|
|
11
|
+
]);
|
|
12
|
+
function narrowAgentPart(part) {
|
|
13
|
+
return AGENT_PART_TYPES.has(part.type) ? part : null;
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
definePartRenderers,
|
|
17
|
+
narrowAgentPart
|
|
18
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"foreign.test.d.ts","sourceRoot":"","sources":["../../src/agent/foreign.test.ts"],"names":[],"mappings":""}
|
package/dist/agent/history.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export type ChatMessage<TPart = AgentPart> = {
|
|
|
16
16
|
readonly role: 'user' | 'assistant';
|
|
17
17
|
readonly parts: TPart[];
|
|
18
18
|
readonly createdAt: number;
|
|
19
|
+
/** How the turn ended. Absent on user messages and on pre-1.11 persisted assistant messages. */
|
|
20
|
+
readonly finish?: 'complete' | 'stopped' | 'error';
|
|
19
21
|
};
|
|
20
22
|
export type ChatHistoryStoreOptions = {
|
|
21
23
|
/** localStorage key (namespaced as `basalt:<key>`). Stable — changes key lose history. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../src/agent/history.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAIxC;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,WAAW,CAAC,KAAK,GAAG,SAAS,IAAI;IAC3C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;IACnC,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,CAAA;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../src/agent/history.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAIxC;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,WAAW,CAAC,KAAK,GAAG,SAAS,IAAI;IAC3C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;IACnC,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,CAAA;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,gGAAgG;IAChG,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,OAAO,CAAA;CACnD,CAAA;AAID,MAAM,MAAM,uBAAuB,GAAG;IACpC,0FAA0F;IAC1F,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,wFAAwF;IACxF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB;;;OAGG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAID,MAAM,MAAM,gBAAgB,CAAC,KAAK,GAAG,SAAS,IAAI;IAChD,2CAA2C;IAC3C,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,CAAA;IACvC,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC,KAAK,CAAC,KAAK,IAAI,CAAA;IACtD,0BAA0B;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAA;CAC3B,CAAA;AAID;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,GAAG,SAAS,EACtD,IAAI,EAAE,uBAAuB,GAC5B,MAAM,gBAAgB,CAAC,KAAK,CAAC,CAoC/B"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Id-minting helpers shared by the thread stores: `mintThreadId` (a client-side thread id, used by
|
|
3
|
+
* both `./thread`'s `createThreadsStore` and `./adapter`'s `createAdapterThreadsStore`) and
|
|
4
|
+
* `withPartIds` (stamps sequence ids onto streamed parts). Related in theme — both mint an id a
|
|
5
|
+
* store needs — unrelated in mechanism; grouped here because they're the two id-minting leaves the
|
|
6
|
+
* two thread stores both sit above.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Mints a client-side thread id. `crypto.randomUUID()` exists only in SECURE CONTEXTS (HTTPS or
|
|
10
|
+
* localhost) — a consumer served over plain HTTP on a LAN hostname (a homelab dashboard on a bare
|
|
11
|
+
* IP, a staging box) would otherwise have `create()`, about the most basic user action a thread
|
|
12
|
+
* store has, throw a plain TypeError. A store used from a browser must not throw on a user gesture
|
|
13
|
+
* — the same principle already applied twice this release (`spliceText` clamps rather than throws,
|
|
14
|
+
* `coalesceParts` degrades rather than throws).
|
|
15
|
+
*
|
|
16
|
+
* Fallback chain, each rung reached only when the one above it is unavailable:
|
|
17
|
+
* 1. `crypto.randomUUID()` — cryptographically random, RFC 4122 UUID. The normal path.
|
|
18
|
+
* 2. `crypto.getRandomValues()` — still cryptographically random; hand-assembled into a UUIDv4
|
|
19
|
+
* (the version/variant bits are set per RFC 4122, since this rung hands back raw bytes, not a
|
|
20
|
+
* formatted UUID).
|
|
21
|
+
* 3. No usable `crypto` at all (missing entirely, or missing both methods above — an old WebView,
|
|
22
|
+
* an SSR shim). NOT cryptographically random and NOT collision-resistant in general. This rung
|
|
23
|
+
* exists solely so `create()` never throws on such a host. It is acceptable ONLY because this
|
|
24
|
+
* id is minted a handful of times per client session and is not the idempotency key any write
|
|
25
|
+
* here depends on — `ThreadsStoreAdapter.appendMessage`'s idempotency key is the MESSAGE id,
|
|
26
|
+
* not this thread id, so a collision on this rung costs a locally merged/duplicated thread, not
|
|
27
|
+
* silent data loss on the append path. Do not reuse this rung for anything with a higher
|
|
28
|
+
* collision cost than that.
|
|
29
|
+
*/
|
|
30
|
+
export declare function mintThreadId(): string;
|
|
31
|
+
/**
|
|
32
|
+
* withPartIds — stamps a stable id onto every draft part arriving without one.
|
|
33
|
+
*
|
|
34
|
+
* A transport is allowed to yield a draft (`AgentPartDraft` — `id` optional); this wraps such a
|
|
35
|
+
* generator and normalizes it into a fully-identified stream, minting `${runId}#${n}` for any
|
|
36
|
+
* part that arrives without an `id`. A part that already carries an id passes through untouched
|
|
37
|
+
* (idempotent) and does not advance the counter.
|
|
38
|
+
*
|
|
39
|
+
* NOT a replay-convergence mechanism. The minted id is a bare per-call sequence number with no
|
|
40
|
+
* relationship to content or offset — it identifies "the nth id-less part THIS invocation saw",
|
|
41
|
+
* nothing more. Calling `withPartIds` again over a resumed/replayed stream restarts its counter at
|
|
42
|
+
* 0, so the resumed stream's first delta mints the SAME id as the original run's first delta
|
|
43
|
+
* (`${runId}#0`) regardless of whether it carries the same content — if the resumed transport
|
|
44
|
+
* re-sends the full text as one chunk while the original sent it as several deltas, mergePart's
|
|
45
|
+
* tail-splice will corrupt the accumulated text rather than converge it. A transport that needs
|
|
46
|
+
* genuine replay convergence (one asserting `ResumableAgentTransport`'s `idempotentReplay: true`)
|
|
47
|
+
* MUST mint its own content-stable ids — e.g. `aiSdkTransport`'s `${chatId}#${index}` /
|
|
48
|
+
* `tool#${toolCallId}` — and must not rely on this helper for that guarantee. Combining
|
|
49
|
+
* `withPartIds` with a transport that asserts `idempotentReplay: true` is only safe when that
|
|
50
|
+
* transport already mints stable ids itself, in which case every part already carries an `id` and
|
|
51
|
+
* `withPartIds` is a no-op passthrough (see the idempotent behavior above) — it is never the thing
|
|
52
|
+
* providing replay safety.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* const identified = withPartIds(runId, transport.stream(input, signal))
|
|
56
|
+
* for await (const part of identified) {
|
|
57
|
+
* // part.id is always defined here
|
|
58
|
+
* }
|
|
59
|
+
*/
|
|
60
|
+
export declare function withPartIds<TPart extends {
|
|
61
|
+
id?: string;
|
|
62
|
+
}>(runId: string, source: AsyncGenerator<TPart>): AsyncGenerator<TPart & {
|
|
63
|
+
id: string;
|
|
64
|
+
}>;
|
|
65
|
+
//# sourceMappingURL=id.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id.d.ts","sourceRoot":"","sources":["../../src/agent/id.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAgBrC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAuB,WAAW,CAAC,KAAK,SAAS;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,EAC9D,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,cAAc,CAAC,KAAK,CAAC,GAC5B,cAAc,CAAC,KAAK,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,CAUxC"}
|
package/dist/agent/id.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
function mintThreadId() {
|
|
2
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
3
|
+
return crypto.randomUUID();
|
|
4
|
+
}
|
|
5
|
+
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
|
6
|
+
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
7
|
+
const hex = Array.from({ length: 16 }, (_, i) => {
|
|
8
|
+
const raw = bytes.at(i) ?? 0;
|
|
9
|
+
const byte = i === 6 ? raw & 15 | 64 : i === 8 ? raw & 63 | 128 : raw;
|
|
10
|
+
return byte.toString(16).padStart(2, "0");
|
|
11
|
+
}).join("");
|
|
12
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
13
|
+
}
|
|
14
|
+
return `id-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
15
|
+
}
|
|
16
|
+
async function* withPartIds(runId, source) {
|
|
17
|
+
let n = 0;
|
|
18
|
+
for await (const part of source) {
|
|
19
|
+
if (part.id !== void 0) {
|
|
20
|
+
yield part;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
yield { ...part, id: `${runId}#${n}` };
|
|
24
|
+
n += 1;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
mintThreadId,
|
|
29
|
+
withPartIds
|
|
30
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id.test.d.ts","sourceRoot":"","sources":["../../src/agent/id.test.ts"],"names":[],"mappings":""}
|