basalt-ui 1.10.0 → 1.11.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.
Files changed (88) hide show
  1. package/AGENTS.md +2 -2
  2. package/CHANGELOG.md +10 -0
  3. package/dist/agent/ai-sdk-transport.d.ts +3 -52
  4. package/dist/agent/ai-sdk-transport.d.ts.map +1 -1
  5. package/dist/agent/ai-sdk-transport.js +108 -24
  6. package/dist/agent/coalesce.d.ts +26 -0
  7. package/dist/agent/coalesce.d.ts.map +1 -0
  8. package/dist/agent/coalesce.js +69 -0
  9. package/dist/agent/foreign.d.ts +97 -0
  10. package/dist/agent/foreign.d.ts.map +1 -0
  11. package/dist/agent/foreign.js +18 -0
  12. package/dist/agent/history.d.ts +2 -0
  13. package/dist/agent/history.d.ts.map +1 -1
  14. package/dist/agent/id.d.ts +35 -0
  15. package/dist/agent/id.d.ts.map +1 -0
  16. package/dist/agent/id.js +14 -0
  17. package/dist/agent/index.d.ts +9 -4
  18. package/dist/agent/index.d.ts.map +1 -1
  19. package/dist/agent/index.js +15 -3
  20. package/dist/agent/merge.d.ts +8 -0
  21. package/dist/agent/merge.d.ts.map +1 -0
  22. package/dist/agent/merge.js +23 -0
  23. package/dist/agent/part-list.d.ts.map +1 -1
  24. package/dist/agent/part-list.js +16 -6
  25. package/dist/agent/parts.d.ts +100 -21
  26. package/dist/agent/parts.d.ts.map +1 -1
  27. package/dist/agent/parts.js +128 -11
  28. package/dist/agent/transport.d.ts +39 -3
  29. package/dist/agent/transport.d.ts.map +1 -1
  30. package/dist/agent/transport.js +20 -17
  31. package/dist/agent/use-agent-stream.d.ts +2 -1
  32. package/dist/agent/use-agent-stream.d.ts.map +1 -1
  33. package/dist/agent/use-agent-stream.js +5 -2
  34. package/dist/agent/use-agent-thread-runs.d.ts +9 -3
  35. package/dist/agent/use-agent-thread-runs.d.ts.map +1 -1
  36. package/dist/agent/use-agent-thread-runs.js +75 -23
  37. package/dist/agent-chat/index.d.ts +2 -0
  38. package/dist/agent-chat/index.d.ts.map +1 -1
  39. package/dist/agent-chat/index.js +2 -0
  40. package/dist/agent-chat/thread-message.d.ts +24 -7
  41. package/dist/agent-chat/thread-message.d.ts.map +1 -1
  42. package/dist/agent-chat/thread-message.js +102 -43
  43. package/dist/agent-chat/tool-chip.d.ts +21 -0
  44. package/dist/agent-chat/tool-chip.d.ts.map +1 -0
  45. package/dist/agent-chat/tool-chip.js +120 -0
  46. package/dist/guard/index.d.ts.map +1 -1
  47. package/dist/guard/index.js +1 -3
  48. package/dist/index.d.ts +2 -2
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +3 -1
  51. package/dist/surfaces.d.ts +2 -2
  52. package/dist/surfaces.js +2 -2
  53. package/llms.txt +4 -4
  54. package/package.json +1 -1
  55. package/src/agent/ai-sdk-transport.test.ts +338 -40
  56. package/src/agent/ai-sdk-transport.ts +206 -53
  57. package/src/agent/coalesce.test.ts +194 -0
  58. package/src/agent/coalesce.ts +118 -0
  59. package/src/agent/foreign.test.ts +47 -0
  60. package/src/agent/foreign.ts +140 -0
  61. package/src/agent/history.ts +2 -0
  62. package/src/agent/id.test.ts +71 -0
  63. package/src/agent/id.ts +43 -0
  64. package/src/agent/index.ts +26 -3
  65. package/src/agent/merge.test.ts +119 -0
  66. package/src/agent/merge.ts +67 -0
  67. package/src/agent/part-list.test.tsx +82 -0
  68. package/src/agent/part-list.tsx +31 -6
  69. package/src/agent/parts.test.ts +401 -59
  70. package/src/agent/parts.ts +262 -32
  71. package/src/agent/transport.test.ts +70 -0
  72. package/src/agent/transport.ts +72 -20
  73. package/src/agent/use-agent-stream.test.tsx +47 -17
  74. package/src/agent/use-agent-stream.ts +13 -3
  75. package/src/agent/use-agent-thread-runs.foreign.test.tsx +133 -0
  76. package/src/agent/use-agent-thread-runs.resume.test.tsx +34 -6
  77. package/src/agent/use-agent-thread-runs.test.tsx +397 -14
  78. package/src/agent/use-agent-thread-runs.ts +170 -26
  79. package/src/agent/use-agent-thread-runs.wedge.test.tsx +4 -2
  80. package/src/agent-chat/index.ts +4 -0
  81. package/src/agent-chat/thread-message.test.tsx +211 -0
  82. package/src/agent-chat/thread-message.tsx +235 -62
  83. package/src/agent-chat/tool-chip.test.tsx +139 -0
  84. package/src/agent-chat/tool-chip.tsx +219 -0
  85. package/src/guard/check-source.test.ts +8 -4
  86. package/src/guard/index.ts +1 -3
  87. package/src/index.ts +2 -0
  88. package/src/surfaces.ts +2 -2
@@ -1,5 +1,9 @@
1
- import { parseAgentPart, isStartPart } from "./parts.js";
2
- import { edenTransport } from "./transport.js";
1
+ import { parseAgentPart, isStartPart, isToolCallSettled, TERMINAL_TOOL_STATES } from "./parts.js";
2
+ import { mergePart } from "./merge.js";
3
+ import { withPartIds } from "./id.js";
4
+ import { coalesceParts } from "./coalesce.js";
5
+ import { definePartRenderers, narrowAgentPart } from "./foreign.js";
6
+ import { edenTransport, isResumable } from "./transport.js";
3
7
  import { aiSdkTransport } from "./ai-sdk-transport.js";
4
8
  import { useAgentStream } from "./use-agent-stream.js";
5
9
  import { useAgentThreadRuns } from "./use-agent-thread-runs.js";
@@ -11,13 +15,21 @@ import { heuristicOutcome } from "./outcome.js";
11
15
  export {
12
16
  BasaltStickToBottom,
13
17
  PartList,
18
+ TERMINAL_TOOL_STATES,
14
19
  aiSdkTransport,
20
+ coalesceParts,
15
21
  createChatHistoryStore,
16
22
  createThreadsStore,
23
+ definePartRenderers,
17
24
  edenTransport,
18
25
  heuristicOutcome,
26
+ isResumable,
19
27
  isStartPart,
28
+ isToolCallSettled,
29
+ mergePart,
30
+ narrowAgentPart,
20
31
  parseAgentPart,
21
32
  useAgentStream,
22
- useAgentThreadRuns
33
+ useAgentThreadRuns,
34
+ withPartIds
23
35
  };
@@ -0,0 +1,8 @@
1
+ /** The structural bound `mergePart` (and the hooks that call it) actually need — an `AgentPart` or
2
+ * a `ForeignPart` both satisfy it, so this is the widened constraint, not a new closed union. */
3
+ export type PartLike = {
4
+ readonly id: string;
5
+ readonly type: string;
6
+ };
7
+ export declare function mergePart<TPart extends PartLike>(parts: readonly TPart[], next: TPart): TPart[];
8
+ //# sourceMappingURL=merge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../../src/agent/merge.ts"],"names":[],"mappings":"AA+BA;iGACiG;AACjG,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAgBrE,wBAAgB,SAAS,CAAC,KAAK,SAAS,QAAQ,EAAE,KAAK,EAAE,SAAS,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,GAAG,KAAK,EAAE,CAiB/F"}
@@ -0,0 +1,23 @@
1
+ function isTextLikeType(type) {
2
+ return type === "text" || type === "reasoning";
3
+ }
4
+ function spliceText(existing, next) {
5
+ const offset = next.offset ?? existing.text.length;
6
+ return existing.text.slice(0, offset) + next.text + existing.text.slice(offset + next.text.length);
7
+ }
8
+ function mergePart(parts, next) {
9
+ const index = parts.findIndex((part) => part.id === next.id);
10
+ if (index === -1) return [...parts, next];
11
+ const existing = parts[index];
12
+ const spliceable = existing.type === next.type && isTextLikeType(next.type);
13
+ const merged = spliceable ? {
14
+ ...next,
15
+ text: spliceText(existing, next)
16
+ } : next;
17
+ const out = parts.slice();
18
+ out[index] = merged;
19
+ return out;
20
+ }
21
+ export {
22
+ mergePart
23
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"part-list.d.ts","sourceRoot":"","sources":["../../src/agent/part-list.tsx"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAGhC,OAAO,KAAK,EACV,SAAS,EAMV,MAAM,SAAS,CAAA;AAIhB,MAAM,MAAM,gBAAgB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI,CAAC,KAAK,EAAE;IAC1E,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACtC,KAAK,EAAE,MAAM,CAAA;CACd,KAAK,GAAG,CAAC,OAAO,GAAG,IAAI,CAAA;AAExB,MAAM,MAAM,qBAAqB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI,CAAC,KAAK,EAAE;IAC/E,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,WAAW,CAAA;KAAE,CAAC,CAAA;IAC3C,KAAK,EAAE,MAAM,CAAA;CACd,KAAK,GAAG,CAAC,OAAO,GAAG,IAAI,CAAA;AAExB,MAAM,MAAM,oBAAoB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI,CAAC,KAAK,EAAE;IAC9E,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACtC,KAAK,EAAE,MAAM,CAAA;CACd,KAAK,GAAG,CAAC,OAAO,GAAG,IAAI,CAAA;AAExB,MAAM,MAAM,kBAAkB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI,CAAC,KAAK,EAAE;IAC5E,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAA;IACxC,KAAK,EAAE,MAAM,CAAA;CACd,KAAK,GAAG,CAAC,OAAO,GAAG,IAAI,CAAA;AAExB,MAAM,MAAM,iBAAiB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI,CAAC,KAAK,EAAE;IAC3E,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IACvC,KAAK,EAAE,MAAM,CAAA;CACd,KAAK,GAAG,CAAC,OAAO,GAAG,IAAI,CAAA;AAExB;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI;IACpE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC,KAAK,CAAC,CAAA;IACtC,QAAQ,CAAC,SAAS,EAAE,qBAAqB,CAAC,KAAK,CAAC,CAAA;IAChD,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC,KAAK,CAAC,CAAA;IAC1C,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC,KAAK,CAAC,CAAA;IAC1C,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC,KAAK,CAAC,CAAA;CACzC,CAAA;AA4HD,MAAM,MAAM,aAAa,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI;IAC/D,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,CAAA;IACvB;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAA;CACzD,CAAA;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,QAAQ,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,EAAE,EAC5D,KAAK,EACL,UAAU,GACX,EAAE,aAAa,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,OAAO,CAgEpC"}
1
+ {"version":3,"file":"part-list.d.ts","sourceRoot":"","sources":["../../src/agent/part-list.tsx"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAGhC,OAAO,KAAK,EACV,SAAS,EAMV,MAAM,SAAS,CAAA;AAIhB,MAAM,MAAM,gBAAgB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI,CAAC,KAAK,EAAE;IAC1E,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACtC,KAAK,EAAE,MAAM,CAAA;CACd,KAAK,GAAG,CAAC,OAAO,GAAG,IAAI,CAAA;AAExB,MAAM,MAAM,qBAAqB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI,CAAC,KAAK,EAAE;IAC/E,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,WAAW,CAAA;KAAE,CAAC,CAAA;IAC3C,KAAK,EAAE,MAAM,CAAA;CACd,KAAK,GAAG,CAAC,OAAO,GAAG,IAAI,CAAA;AAExB,MAAM,MAAM,oBAAoB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI,CAAC,KAAK,EAAE;IAC9E,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACtC,KAAK,EAAE,MAAM,CAAA;CACd,KAAK,GAAG,CAAC,OAAO,GAAG,IAAI,CAAA;AAExB,MAAM,MAAM,kBAAkB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI,CAAC,KAAK,EAAE;IAC5E,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAA;IACxC,KAAK,EAAE,MAAM,CAAA;CACd,KAAK,GAAG,CAAC,OAAO,GAAG,IAAI,CAAA;AAExB,MAAM,MAAM,iBAAiB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI,CAAC,KAAK,EAAE;IAC3E,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IACvC,KAAK,EAAE,MAAM,CAAA;CACd,KAAK,GAAG,CAAC,OAAO,GAAG,IAAI,CAAA;AAExB;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI;IACpE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC,KAAK,CAAC,CAAA;IACtC,QAAQ,CAAC,SAAS,EAAE,qBAAqB,CAAC,KAAK,CAAC,CAAA;IAChD,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC,KAAK,CAAC,CAAA;IAC1C,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC,KAAK,CAAC,CAAA;IAC1C,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC,KAAK,CAAC,CAAA;CACzC,CAAA;AAqJD,MAAM,MAAM,aAAa,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI;IAC/D,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,CAAA;IACvB;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAA;CACzD,CAAA;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,QAAQ,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,EAAE,EAC5D,KAAK,EACL,UAAU,GACX,EAAE,aAAa,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,OAAO,CAgEpC"}
@@ -22,6 +22,10 @@ const CODE_BLOCK_STYLE = {
22
22
  padding: "var(--vx-space-agent-code-inset)",
23
23
  overflowX: "auto"
24
24
  };
25
+ const STATUS_LINE_STYLE = {
26
+ marginTop: "var(--vx-space-agent-part-gap-top)",
27
+ color: VX.status.bad
28
+ };
25
29
  function DefaultText({ part }) {
26
30
  return /* @__PURE__ */ jsx(
27
31
  "div",
@@ -56,7 +60,13 @@ function DefaultToolCall({ part }) {
56
60
  return /* @__PURE__ */ jsxs("div", { className: "basalt-agent-tool", style: RAIL_STYLE, children: [
57
61
  /* @__PURE__ */ jsx("span", { className: "basalt-agent-tool-name", style: MICRO_LABEL_STYLE, children: part.toolName }),
58
62
  /* @__PURE__ */ jsx("pre", { className: "basalt-agent-tool-input", style: CODE_BLOCK_STYLE, children: JSON.stringify(part.input, null, 2) }),
59
- part.output !== void 0 && /* @__PURE__ */ jsx("pre", { className: "basalt-agent-tool-output", style: CODE_BLOCK_STYLE, children: JSON.stringify(part.output, null, 2) })
63
+ part.state === "output-error" && part.input === void 0 && part.rawInput !== void 0 && /* @__PURE__ */ jsx("pre", { className: "basalt-agent-tool-raw-input", style: CODE_BLOCK_STYLE, children: JSON.stringify(part.rawInput, null, 2) }),
64
+ part.state === "output-available" && /* @__PURE__ */ jsx("pre", { className: "basalt-agent-tool-output", style: CODE_BLOCK_STYLE, children: JSON.stringify(part.output, null, 2) }),
65
+ part.state === "output-error" && /* @__PURE__ */ jsx("div", { className: "basalt-agent-tool-error", role: "alert", style: STATUS_LINE_STYLE, children: part.errorText }),
66
+ part.state === "output-denied" && /* @__PURE__ */ jsxs("div", { className: "basalt-agent-tool-denied", style: STATUS_LINE_STYLE, children: [
67
+ "Denied",
68
+ part.approval.reason !== void 0 ? `: ${part.approval.reason}` : ""
69
+ ] })
60
70
  ] });
61
71
  }
62
72
  function DefaultSource({ part }) {
@@ -109,23 +119,23 @@ function PartList({
109
119
  return null;
110
120
  case "text": {
111
121
  const Render = renderers.text;
112
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Render, { part, index }) }, index);
122
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Render, { part, index }) }, part.id);
113
123
  }
114
124
  case "reasoning": {
115
125
  const Render = renderers.reasoning;
116
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Render, { part, index }) }, index);
126
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Render, { part, index }) }, part.id);
117
127
  }
118
128
  case "tool": {
119
129
  const Render = renderers.tool;
120
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Render, { part, index }) }, index);
130
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Render, { part, index }) }, part.id);
121
131
  }
122
132
  case "source": {
123
133
  const Render = renderers.source;
124
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Render, { part, index }) }, index);
134
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Render, { part, index }) }, part.id);
125
135
  }
126
136
  case "error": {
127
137
  const Render = renderers.error;
128
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Render, { part, index }) }, index);
138
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Render, { part, index }) }, part.id);
129
139
  }
130
140
  default:
131
141
  return assertNever(part);
@@ -9,6 +9,11 @@
9
9
  * explicitly and carry NO t.Object/t.Union response schema. Validate at yield-time. See
10
10
  * agent/rules/basalt-agent.md for the full doctrine.
11
11
  *
12
+ * Part identity: every variant carries a stable `id` (see `PartBase`). Transports are permitted to
13
+ * yield a DRAFT — `id` omitted — via `AgentPartDraft`; the consuming hooks (or `withPartIds`)
14
+ * normalize a draft into a fully-identified part before it is ever accumulated. `parseAgentPart`
15
+ * validates the FULL (identified) shape — a draft is a separate, narrower concern.
16
+ *
12
17
  * @example
13
18
  * import type { AgentPart } from 'basalt-ui/agent'
14
19
  * function describe(p: AgentPart): string {
@@ -23,49 +28,110 @@
23
28
  * }
24
29
  * }
25
30
  */
31
+ /** Every AgentPart variant carries a stable id — the addressing key for `mergePart`/`coalesceParts`. */
32
+ type PartBase = {
33
+ readonly id: string;
34
+ };
26
35
  /**
27
36
  * Emitted once at stream start, carrying the run id and an opaque token a transport can use to
28
37
  * resume this run after a disconnect. Not renderable content — UI code should treat it as a
29
38
  * no-op signal, not conversation content.
30
39
  */
31
- export type StartPart = {
40
+ export type StartPart = PartBase & {
32
41
  readonly type: 'start';
33
42
  readonly runId: string;
34
43
  readonly resumeToken?: string;
35
44
  };
36
- /** A streamed text fragment from the assistant. */
37
- export type TextPart = {
45
+ /**
46
+ * A streamed text fragment from the assistant. `offset` is the character position of this delta
47
+ * inside its part (undefined = append at the tail) — the mechanism `mergePart` uses to splice a
48
+ * replayed delta in place rather than duplicating it.
49
+ */
50
+ export type TextPart = PartBase & {
38
51
  readonly type: 'text';
39
52
  readonly text: string;
53
+ readonly offset?: number;
40
54
  };
41
- /** An internal reasoning/thinking fragment (may be hidden in the UI). */
42
- export type ReasoningPart = {
55
+ /** An internal reasoning/thinking fragment (may be hidden in the UI). Same `offset` mechanism as TextPart. */
56
+ export type ReasoningPart = PartBase & {
43
57
  readonly type: 'reasoning';
44
58
  readonly text: string;
59
+ readonly offset?: number;
45
60
  };
46
- /**
47
- * A tool invocation — input is the parameters sent, output (optional) is the result once
48
- * the tool has completed. Streams arrive with output undefined; it may be appended later.
49
- *
50
- * `toolCallId` (optional) identifies the specific invocation when a transport's wire format
51
- * carries one (e.g. AI SDK's `ToolUIPart`) — useful for consumer-side coalescing of a tool call's
52
- * input/output pair by id rather than by array position.
53
- */
54
- export type ToolCallPart = {
61
+ /** The nested approval envelope — mirrors the SDK's shape verbatim, never flattened. */
62
+ type ToolApproval = {
63
+ readonly id: string;
64
+ readonly approved?: boolean;
65
+ readonly reason?: string;
66
+ readonly isAutomatic?: boolean;
67
+ readonly signature?: string;
68
+ };
69
+ type ToolCallBase = PartBase & {
55
70
  readonly type: 'tool';
71
+ /** Required on all seven states (was optional — see parts.ts history). */
72
+ readonly toolCallId: string;
56
73
  readonly toolName: string;
57
- readonly input: unknown;
58
- readonly output?: unknown;
59
- readonly toolCallId?: string;
74
+ /** Wall-clock time from first sighting of this toolCallId to its terminal state. */
75
+ readonly durationMs?: number;
76
+ readonly providerExecuted?: boolean;
60
77
  };
78
+ export type ToolCallPart = (ToolCallBase & {
79
+ readonly state: 'input-streaming';
80
+ readonly input?: unknown;
81
+ }) | (ToolCallBase & {
82
+ readonly state: 'input-available';
83
+ readonly input: unknown;
84
+ }) | (ToolCallBase & {
85
+ readonly state: 'approval-requested';
86
+ readonly input: unknown;
87
+ readonly approval: ToolApproval & {
88
+ readonly approved?: never;
89
+ readonly reason?: never;
90
+ };
91
+ }) | (ToolCallBase & {
92
+ readonly state: 'approval-responded';
93
+ readonly input: unknown;
94
+ readonly approval: ToolApproval & {
95
+ readonly approved: boolean;
96
+ };
97
+ }) | (ToolCallBase & {
98
+ readonly state: 'output-available';
99
+ readonly input: unknown;
100
+ readonly output: unknown;
101
+ readonly preliminary?: boolean;
102
+ readonly approval?: ToolApproval & {
103
+ readonly approved?: true;
104
+ };
105
+ }) | (ToolCallBase & {
106
+ readonly state: 'output-error';
107
+ readonly input?: unknown;
108
+ /** The SDK's field name — there is no field named `error` anywhere in the union. */
109
+ readonly errorText: string;
110
+ /** The only place the raw, un-parsed input survives — what lets a UI render a call whose input never validated. */
111
+ readonly rawInput?: unknown;
112
+ readonly approval?: ToolApproval & {
113
+ readonly approved?: true;
114
+ };
115
+ }) | (ToolCallBase & {
116
+ readonly state: 'output-denied';
117
+ readonly input: unknown;
118
+ readonly approval: ToolApproval & {
119
+ readonly approved: false;
120
+ };
121
+ });
122
+ export type ToolCallState = ToolCallPart['state'];
123
+ /** The three states a tool call can never leave — see `isToolCallSettled`. */
124
+ export declare const TERMINAL_TOOL_STATES: readonly ["output-available", "output-error", "output-denied"];
125
+ /** True once a tool call has reached one of `TERMINAL_TOOL_STATES` — no further state transition. */
126
+ export declare function isToolCallSettled(part: ToolCallPart): boolean;
61
127
  /** A cited source URL referenced by the assistant. */
62
- export type SourcePart = {
128
+ export type SourcePart = PartBase & {
63
129
  readonly type: 'source';
64
130
  readonly url: string;
65
131
  readonly title?: string;
66
132
  };
67
133
  /** A fatal or recoverable error emitted in-stream by the agent. */
68
- export type ErrorPart = {
134
+ export type ErrorPart = PartBase & {
69
135
  readonly type: 'error';
70
136
  readonly message: string;
71
137
  };
@@ -73,12 +139,24 @@ export type ErrorPart = {
73
139
  * The complete discriminated union of all structured agent stream parts.
74
140
  *
75
141
  * @example
76
- * const part: AgentPart = { type: 'text', text: 'Hello' }
142
+ * const part: AgentPart = { id: 'p1', type: 'text', text: 'Hello' }
77
143
  */
78
144
  export type AgentPart = StartPart | TextPart | ReasoningPart | ToolCallPart | SourcePart | ErrorPart;
145
+ /**
146
+ * An AgentPart with `id` optional — the shape a transport is allowed to yield. The naked type
147
+ * parameter (not `AgentPart extends unknown ? ... : never` written some other way) is deliberate:
148
+ * it makes the conditional type distribute over the AgentPart union member-by-member, so
149
+ * `Drafted<AgentPart>` is `Drafted<StartPart> | Drafted<TextPart> | ...` rather than one flattened
150
+ * (and wrong) shape.
151
+ */
152
+ export type Drafted<T> = T extends unknown ? Omit<T, 'id'> & {
153
+ readonly id?: string;
154
+ } : never;
155
+ export type AgentPartDraft = Drafted<AgentPart>;
79
156
  /**
80
157
  * Runtime type-narrowing guard: validates an unknown value against the AgentPart discriminated
81
- * union and returns the narrowed type or null when validation fails.
158
+ * union and returns the narrowed type or null when validation fails. Validates the FULL
159
+ * (identified) shape — an `id`-less draft is a separate concern handled by `withPartIds`.
82
160
  *
83
161
  * @example
84
162
  * const raw = JSON.parse(line)
@@ -88,4 +166,5 @@ export type AgentPart = StartPart | TextPart | ReasoningPart | ToolCallPart | So
88
166
  export declare function parseAgentPart(raw: unknown): AgentPart | null;
89
167
  /** Type guard: narrows an unknown value to StartPart. */
90
168
  export declare function isStartPart(part: unknown): part is StartPart;
169
+ export {};
91
170
  //# sourceMappingURL=parts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"parts.d.ts","sourceRoot":"","sources":["../../src/agent/parts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAC9B,CAAA;AAED,mDAAmD;AACnD,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvE,yEAAyE;AACzE,MAAM,MAAM,aAAa,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAEjF;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B,CAAA;AAED,sDAAsD;AACtD,MAAM,MAAM,UAAU,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAEnG,mEAAmE;AACnE,MAAM,MAAM,SAAS,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAE5E;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,aAAa,GAAG,YAAY,GAAG,UAAU,GAAG,SAAS,CAAA;AAIpG;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,SAAS,GAAG,IAAI,CA0C7D;AAID,yDAAyD;AACzD,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,SAAS,CAE5D"}
1
+ {"version":3,"file":"parts.d.ts","sourceRoot":"","sources":["../../src/agent/parts.ts"],"names":[],"mappings":"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"}
@@ -1,49 +1,166 @@
1
+ const TERMINAL_TOOL_STATES = ["output-available", "output-error", "output-denied"];
2
+ function isToolCallSettled(part) {
3
+ return TERMINAL_TOOL_STATES.includes(part.state);
4
+ }
1
5
  function parseAgentPart(raw) {
2
6
  if (raw === null || typeof raw !== "object") return null;
3
7
  const obj = raw;
4
8
  if (typeof obj["type"] !== "string") return null;
9
+ if (typeof obj["id"] !== "string") return null;
10
+ const id = obj["id"];
5
11
  switch (obj["type"]) {
6
12
  case "start":
7
13
  if (typeof obj["runId"] !== "string") return null;
8
14
  return {
15
+ id,
9
16
  type: "start",
10
17
  runId: obj["runId"],
11
18
  ...typeof obj["resumeToken"] === "string" ? { resumeToken: obj["resumeToken"] } : {}
12
19
  };
13
20
  case "text":
14
21
  if (typeof obj["text"] !== "string") return null;
15
- return { type: "text", text: obj["text"] };
22
+ return {
23
+ id,
24
+ type: "text",
25
+ text: obj["text"],
26
+ ...typeof obj["offset"] === "number" ? { offset: obj["offset"] } : {}
27
+ };
16
28
  case "reasoning":
17
29
  if (typeof obj["text"] !== "string") return null;
18
- return { type: "reasoning", text: obj["text"] };
19
- case "tool":
20
- if (typeof obj["toolName"] !== "string") return null;
21
- if (!("input" in obj)) return null;
22
30
  return {
23
- type: "tool",
24
- toolName: obj["toolName"],
25
- input: obj["input"],
26
- ...obj["output"] !== void 0 ? { output: obj["output"] } : {},
27
- ...typeof obj["toolCallId"] === "string" ? { toolCallId: obj["toolCallId"] } : {}
31
+ id,
32
+ type: "reasoning",
33
+ text: obj["text"],
34
+ ...typeof obj["offset"] === "number" ? { offset: obj["offset"] } : {}
28
35
  };
36
+ case "tool":
37
+ return parseToolCallPart(id, obj);
29
38
  case "source":
30
39
  if (typeof obj["url"] !== "string") return null;
31
40
  return {
41
+ id,
32
42
  type: "source",
33
43
  url: obj["url"],
34
44
  ...typeof obj["title"] === "string" ? { title: obj["title"] } : {}
35
45
  };
36
46
  case "error":
37
47
  if (typeof obj["message"] !== "string") return null;
38
- return { type: "error", message: obj["message"] };
48
+ return { id, type: "error", message: obj["message"] };
39
49
  default:
40
50
  return null;
41
51
  }
42
52
  }
53
+ function parseToolCallPart(id, obj) {
54
+ if (typeof obj["toolCallId"] !== "string") return null;
55
+ if (typeof obj["toolName"] !== "string") return null;
56
+ if (typeof obj["state"] !== "string") return null;
57
+ const { toolCallId, toolName } = obj;
58
+ const common = {
59
+ id,
60
+ type: "tool",
61
+ toolCallId,
62
+ toolName,
63
+ ...typeof obj["durationMs"] === "number" ? { durationMs: obj["durationMs"] } : {},
64
+ ...typeof obj["providerExecuted"] === "boolean" ? { providerExecuted: obj["providerExecuted"] } : {}
65
+ };
66
+ switch (obj["state"]) {
67
+ case "input-streaming":
68
+ return {
69
+ ...common,
70
+ state: "input-streaming",
71
+ ..."input" in obj ? { input: obj["input"] } : {}
72
+ };
73
+ case "input-available":
74
+ if (!("input" in obj)) return null;
75
+ return { ...common, state: "input-available", input: obj["input"] };
76
+ case "approval-requested": {
77
+ if (!("input" in obj)) return null;
78
+ const approval = parseApproval(obj["approval"]);
79
+ if (approval === null || approval.approved !== void 0 || approval.reason !== void 0) {
80
+ return null;
81
+ }
82
+ return {
83
+ ...common,
84
+ state: "approval-requested",
85
+ input: obj["input"],
86
+ approval
87
+ };
88
+ }
89
+ case "approval-responded": {
90
+ if (!("input" in obj)) return null;
91
+ const approval = parseApproval(obj["approval"]);
92
+ if (approval === null || typeof approval.approved !== "boolean") return null;
93
+ return {
94
+ ...common,
95
+ state: "approval-responded",
96
+ input: obj["input"],
97
+ approval
98
+ };
99
+ }
100
+ case "output-denied": {
101
+ if (!("input" in obj)) return null;
102
+ const approval = parseApproval(obj["approval"]);
103
+ if (approval === null || approval.approved !== false) return null;
104
+ return {
105
+ ...common,
106
+ state: "output-denied",
107
+ input: obj["input"],
108
+ approval
109
+ };
110
+ }
111
+ case "output-available": {
112
+ if (!("input" in obj)) return null;
113
+ if (!("output" in obj)) return null;
114
+ const approval = obj["approval"] === void 0 ? null : parseApproval(obj["approval"]);
115
+ if (obj["approval"] !== void 0 && (approval === null || approval.approved === false)) {
116
+ return null;
117
+ }
118
+ return {
119
+ ...common,
120
+ state: "output-available",
121
+ input: obj["input"],
122
+ output: obj["output"],
123
+ ...typeof obj["preliminary"] === "boolean" ? { preliminary: obj["preliminary"] } : {},
124
+ ...approval !== null ? { approval } : {}
125
+ };
126
+ }
127
+ case "output-error": {
128
+ if (typeof obj["errorText"] !== "string") return null;
129
+ const approval = obj["approval"] === void 0 ? null : parseApproval(obj["approval"]);
130
+ if (obj["approval"] !== void 0 && (approval === null || approval.approved === false)) {
131
+ return null;
132
+ }
133
+ return {
134
+ ...common,
135
+ state: "output-error",
136
+ errorText: obj["errorText"],
137
+ ..."input" in obj ? { input: obj["input"] } : {},
138
+ ..."rawInput" in obj ? { rawInput: obj["rawInput"] } : {},
139
+ ...approval !== null ? { approval } : {}
140
+ };
141
+ }
142
+ default:
143
+ return null;
144
+ }
145
+ }
146
+ function parseApproval(raw) {
147
+ if (raw === null || typeof raw !== "object") return null;
148
+ const obj = raw;
149
+ if (typeof obj["id"] !== "string") return null;
150
+ return {
151
+ id: obj["id"],
152
+ ...typeof obj["approved"] === "boolean" ? { approved: obj["approved"] } : {},
153
+ ...typeof obj["reason"] === "string" ? { reason: obj["reason"] } : {},
154
+ ...typeof obj["isAutomatic"] === "boolean" ? { isAutomatic: obj["isAutomatic"] } : {},
155
+ ...typeof obj["signature"] === "string" ? { signature: obj["signature"] } : {}
156
+ };
157
+ }
43
158
  function isStartPart(part) {
44
159
  return typeof part === "object" && part !== null && part.type === "start";
45
160
  }
46
161
  export {
162
+ TERMINAL_TOOL_STATES,
47
163
  isStartPart,
164
+ isToolCallSettled,
48
165
  parseAgentPart
49
166
  };
@@ -29,11 +29,16 @@
29
29
  * api.chat.post({ body: { message: input }, fetch: { signal } }),
30
30
  * )
31
31
  */
32
- import type { AgentPart } from './parts.js';
32
+ import type { AgentPart, AgentPartDraft } from './parts.js';
33
33
  /**
34
34
  * The injected transport seam. `stream(input, signal)` returns an async generator of parts.
35
35
  * Implement this interface for any backend — Eden, fetch-based SSE, AI SDK, or a mock.
36
36
  *
37
+ * The default `TPart` is `AgentPartDraft` (not `AgentPart`): transports yield DRAFTS — `id`
38
+ * optional — and the consuming hooks (or `withPartIds`) normalize a draft into a fully-identified
39
+ * part before it is ever accumulated. Pass an explicit `AgentPart` generic when a transport mints
40
+ * its own ids directly (as `aiSdkTransport` does).
41
+ *
37
42
  * @example
38
43
  * // Minimal mock transport for tests / playground:
39
44
  * const mockTransport: AgentTransport = {
@@ -42,11 +47,33 @@ import type { AgentPart } from './parts.js';
42
47
  * },
43
48
  * }
44
49
  */
45
- export type AgentTransport<TPart = AgentPart, TInput = string> = {
50
+ export type AgentTransport<TPart = AgentPartDraft, TInput = string> = {
46
51
  stream: (input: TInput, signal?: AbortSignal) => AsyncGenerator<TPart>;
47
52
  /** Optional: resume a previously-started run from its resumeToken (see StartPart). Transports that don't support resumption simply omit this. */
48
53
  resume?: (resumeToken: string, signal?: AbortSignal) => AsyncGenerator<TPart>;
49
54
  };
55
+ /**
56
+ * A transport whose replay is safe to run over an already-rendered turn. Implementing `resume`
57
+ * alone is no longer enough: `idempotentReplay: true` is a literal-typed assertion that every
58
+ * text/reasoning part this transport emits carries an authoritative `offset`, so mergePart
59
+ * rewrites instead of appending.
60
+ */
61
+ export type ResumableAgentTransport<TPart = AgentPartDraft, TInput = string> = AgentTransport<TPart, TInput> & {
62
+ readonly resume: (resumeToken: string, signal?: AbortSignal) => AsyncGenerator<TPart>;
63
+ readonly idempotentReplay: true;
64
+ };
65
+ /**
66
+ * Type guard: true when `t` both implements `resume` AND has explicitly asserted
67
+ * `idempotentReplay: true`. A transport with `resume` but no `idempotentReplay` is NOT resumable
68
+ * — the mount-time reconcile in `useAgentThreadRuns` falls back to `'interrupted'` for it rather
69
+ * than risk duplicating already-rendered content.
70
+ *
71
+ * @example
72
+ * if (isResumable(transport)) {
73
+ * for await (const part of transport.resume(resumeToken)) { ... }
74
+ * }
75
+ */
76
+ export declare function isResumable<TPart, TInput>(t: AgentTransport<TPart, TInput>): t is ResumableAgentTransport<TPart, TInput>;
50
77
  /**
51
78
  * Wraps a consumer's Eden async-generator call into an AgentTransport.
52
79
  *
@@ -64,6 +91,12 @@ export type AgentTransport<TPart = AgentPart, TInput = string> = {
64
91
  * object has NO `resume` key at all (not `resume: undefined`) — consumers that don't wire a
65
92
  * resumable backend get a transport that behaves exactly as before.
66
93
  *
94
+ * `resumeCall` alone does NOT make the transport resumable per `isResumable` — pass
95
+ * `{ idempotentReplay: true }` as the third argument to opt in EXPLICITLY once the server-side
96
+ * `resumeCall` route is verified to re-emit every text/reasoning delta with an authoritative
97
+ * `offset` (never a bare append). This is an opt-in, not an inference: a `resumeCall` whose deltas
98
+ * aren't offset-accurate would double-render on replay if treated as resumable.
99
+ *
67
100
  * @example
68
101
  * import { edenTransport, type AgentPart } from 'basalt-ui/agent'
69
102
  *
@@ -72,6 +105,7 @@ export type AgentTransport<TPart = AgentPart, TInput = string> = {
72
105
  * const transport = edenTransport<AgentPart>(
73
106
  * (input, signal) => api.chat.post({ body: { message: input }, fetch: { signal } }),
74
107
  * (resumeToken, signal) => api.chat.resume.post({ body: { resumeToken }, fetch: { signal } }),
108
+ * { idempotentReplay: true },
75
109
  * )
76
110
  *
77
111
  * // Wire into useAgentStream:
@@ -83,5 +117,7 @@ export declare function edenTransport<TPart = AgentPart, TInput = string>(call:
83
117
  }>, resumeCall?: (resumeToken: string, signal?: AbortSignal) => Promise<{
84
118
  data: AsyncGenerator<TPart> | null;
85
119
  error: unknown;
86
- }>): AgentTransport<TPart, TInput>;
120
+ }>, options?: {
121
+ readonly idempotentReplay?: true;
122
+ }): AgentTransport<TPart, TInput> | ResumableAgentTransport<TPart, TInput>;
87
123
  //# sourceMappingURL=transport.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/agent/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAIxC;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,cAAc,CAAC,KAAK,GAAG,SAAS,EAAE,MAAM,GAAG,MAAM,IAAI;IAC/D,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,cAAc,CAAC,KAAK,CAAC,CAAA;IACtE,iJAAiJ;IACjJ,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,cAAc,CAAC,KAAK,CAAC,CAAA;CAC9E,CAAA;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,aAAa,CAAC,KAAK,GAAG,SAAS,EAAE,MAAM,GAAG,MAAM,EAC9D,IAAI,EAAE,CACJ,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,WAAW,KACjB,OAAO,CAAC;IAAE,IAAI,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,EACpE,UAAU,CAAC,EAAE,CACX,WAAW,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,WAAW,KACjB,OAAO,CAAC;IAAE,IAAI,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,GACnE,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,CAmB/B"}
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/agent/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAIxD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,cAAc,CAAC,KAAK,GAAG,cAAc,EAAE,MAAM,GAAG,MAAM,IAAI;IACpE,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,cAAc,CAAC,KAAK,CAAC,CAAA;IACtE,iJAAiJ;IACjJ,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,cAAc,CAAC,KAAK,CAAC,CAAA;CAC9E,CAAA;AAID;;;;;GAKG;AACH,MAAM,MAAM,uBAAuB,CAAC,KAAK,GAAG,cAAc,EAAE,MAAM,GAAG,MAAM,IAAI,cAAc,CAC3F,KAAK,EACL,MAAM,CACP,GAAG;IACF,QAAQ,CAAC,MAAM,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,cAAc,CAAC,KAAK,CAAC,CAAA;IACrF,QAAQ,CAAC,gBAAgB,EAAE,IAAI,CAAA;CAChC,CAAA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EACvC,CAAC,EAAE,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,GAC/B,CAAC,IAAI,uBAAuB,CAAC,KAAK,EAAE,MAAM,CAAC,CAK7C;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,aAAa,CAAC,KAAK,GAAG,SAAS,EAAE,MAAM,GAAG,MAAM,EAC9D,IAAI,EAAE,CACJ,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,WAAW,KACjB,OAAO,CAAC;IAAE,IAAI,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,EACpE,UAAU,CAAC,EAAE,CACX,WAAW,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,WAAW,KACjB,OAAO,CAAC;IAAE,IAAI,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,EACpE,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,IAAI,CAAA;CAAE,GAC7C,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,uBAAuB,CAAC,KAAK,EAAE,MAAM,CAAC,CAsBxE"}
@@ -1,21 +1,24 @@
1
- function edenTransport(call, resumeCall) {
2
- return {
3
- async *stream(input, signal) {
4
- const { data, error } = await call(input, signal);
5
- if (error) throw error;
6
- if (data === null) return;
7
- yield* data;
8
- },
9
- ...resumeCall !== void 0 ? {
10
- async *resume(resumeToken, signal) {
11
- const { data, error } = await resumeCall(resumeToken, signal);
12
- if (error) throw error;
13
- if (data === null) return;
14
- yield* data;
15
- }
16
- } : {}
1
+ function isResumable(t) {
2
+ return typeof t.resume === "function" && t.idempotentReplay === true;
3
+ }
4
+ function edenTransport(call, resumeCall, options) {
5
+ const stream = async function* (input, signal) {
6
+ const { data, error } = await call(input, signal);
7
+ if (error) throw error;
8
+ if (data === null) return;
9
+ yield* data;
10
+ };
11
+ if (resumeCall === void 0) return { stream };
12
+ const resume = async function* (resumeToken, signal) {
13
+ const { data, error } = await resumeCall(resumeToken, signal);
14
+ if (error) throw error;
15
+ if (data === null) return;
16
+ yield* data;
17
17
  };
18
+ if (options?.idempotentReplay !== true) return { stream, resume };
19
+ return { stream, resume, idempotentReplay: true };
18
20
  }
19
21
  export {
20
- edenTransport
22
+ edenTransport,
23
+ isResumable
21
24
  };
@@ -1,3 +1,4 @@
1
+ import type { PartLike } from './merge.js';
1
2
  import type { AgentPart } from './parts.js';
2
3
  import type { AgentTransport } from './transport.js';
3
4
  /** The lifecycle state of the current stream. */
@@ -28,7 +29,7 @@ export type UseAgentStreamReturn<TPart, TInput> = {
28
29
  * // parts accumulates as the stream arrives
29
30
  * // status: 'idle' → 'streaming' → 'done' (or 'error')
30
31
  */
31
- export declare function useAgentStream<TPart = AgentPart, TInput = string>({ transport, }: {
32
+ export declare function useAgentStream<TPart extends PartLike = AgentPart, TInput = string>({ transport, }: {
32
33
  transport: AgentTransport<TPart, TInput>;
33
34
  }): UseAgentStreamReturn<TPart, TInput>;
34
35
  //# sourceMappingURL=use-agent-stream.d.ts.map