@ably/ai-transport 0.1.0 → 0.3.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 (221) hide show
  1. package/README.md +93 -111
  2. package/dist/ably-ai-transport.js +2401 -1387
  3. package/dist/ably-ai-transport.js.map +1 -1
  4. package/dist/ably-ai-transport.umd.cjs +1 -1
  5. package/dist/ably-ai-transport.umd.cjs.map +1 -1
  6. package/dist/constants.d.ts +116 -42
  7. package/dist/core/agent.d.ts +44 -0
  8. package/dist/core/channel-options.d.ts +57 -0
  9. package/dist/core/codec/codec-event.d.ts +9 -0
  10. package/dist/core/codec/decoder.d.ts +24 -24
  11. package/dist/core/codec/define-codec.d.ts +100 -0
  12. package/dist/core/codec/encoder.d.ts +10 -12
  13. package/dist/core/codec/field-bag.d.ts +85 -0
  14. package/dist/core/codec/fields.d.ts +141 -0
  15. package/dist/core/codec/index.d.ts +8 -2
  16. package/dist/core/codec/input-descriptor-decoder.d.ts +19 -0
  17. package/dist/core/codec/input-descriptor-encoder.d.ts +22 -0
  18. package/dist/core/codec/input-descriptors.d.ts +281 -0
  19. package/dist/core/codec/lifecycle-tracker.d.ts +10 -9
  20. package/dist/core/codec/output-descriptor-decoder.d.ts +29 -0
  21. package/dist/core/codec/output-descriptor-encoder.d.ts +31 -0
  22. package/dist/core/codec/output-descriptors.d.ts +237 -0
  23. package/dist/core/codec/types.d.ts +470 -119
  24. package/dist/core/codec/well-known-inputs.d.ts +52 -0
  25. package/dist/core/transport/agent-session.d.ts +10 -0
  26. package/dist/core/transport/agent-view.d.ts +296 -0
  27. package/dist/core/transport/client-session.d.ts +13 -0
  28. package/dist/core/transport/decode-fold.d.ts +55 -0
  29. package/dist/core/transport/headers.d.ts +121 -14
  30. package/dist/core/transport/index.d.ts +5 -6
  31. package/dist/core/transport/internal/bounded-map.d.ts +20 -0
  32. package/dist/core/transport/invocation.d.ts +74 -0
  33. package/dist/core/transport/load-history-pages.d.ts +71 -0
  34. package/dist/core/transport/load-history.d.ts +44 -0
  35. package/dist/core/transport/pipe-stream.d.ts +9 -9
  36. package/dist/core/transport/run-manager.d.ts +76 -0
  37. package/dist/core/transport/session-support.d.ts +55 -0
  38. package/dist/core/transport/tree.d.ts +523 -109
  39. package/dist/core/transport/types/agent.d.ts +375 -0
  40. package/dist/core/transport/types/client.d.ts +201 -0
  41. package/dist/core/transport/types/shared.d.ts +24 -0
  42. package/dist/core/transport/types/tree.d.ts +357 -0
  43. package/dist/core/transport/types/view.d.ts +249 -0
  44. package/dist/core/transport/types.d.ts +13 -553
  45. package/dist/core/transport/view.d.ts +390 -84
  46. package/dist/core/transport/wire-log.d.ts +102 -0
  47. package/dist/errors.d.ts +27 -10
  48. package/dist/index.d.ts +8 -9
  49. package/dist/logger.d.ts +12 -0
  50. package/dist/react/ably-ai-transport-react.js +1365 -1010
  51. package/dist/react/ably-ai-transport-react.js.map +1 -1
  52. package/dist/react/ably-ai-transport-react.umd.cjs +1 -1
  53. package/dist/react/ably-ai-transport-react.umd.cjs.map +1 -1
  54. package/dist/react/contexts/client-session-context.d.ts +37 -0
  55. package/dist/react/contexts/client-session-provider.d.ts +56 -0
  56. package/dist/react/create-session-hooks.d.ts +116 -0
  57. package/dist/react/index.d.ts +13 -12
  58. package/dist/react/internal/skipped-session.d.ts +8 -0
  59. package/dist/react/internal/use-resolved-session.d.ts +36 -0
  60. package/dist/react/use-ably-messages.d.ts +17 -14
  61. package/dist/react/use-client-session.d.ts +81 -0
  62. package/dist/react/use-create-view.d.ts +14 -13
  63. package/dist/react/use-tree.d.ts +30 -15
  64. package/dist/react/use-view.d.ts +81 -50
  65. package/dist/utils.d.ts +48 -71
  66. package/dist/vercel/ably-ai-transport-vercel.js +3257 -2499
  67. package/dist/vercel/ably-ai-transport-vercel.js.map +1 -1
  68. package/dist/vercel/ably-ai-transport-vercel.umd.cjs +1 -1
  69. package/dist/vercel/ably-ai-transport-vercel.umd.cjs.map +1 -1
  70. package/dist/vercel/codec/decode-lifecycle.d.ts +9 -0
  71. package/dist/vercel/codec/events.d.ts +50 -0
  72. package/dist/vercel/codec/fields.d.ts +44 -0
  73. package/dist/vercel/codec/fold-content.d.ts +16 -0
  74. package/dist/vercel/codec/fold-data.d.ts +16 -0
  75. package/dist/vercel/codec/fold-input.d.ts +67 -0
  76. package/dist/vercel/codec/fold-lifecycle.d.ts +16 -0
  77. package/dist/vercel/codec/fold-text.d.ts +16 -0
  78. package/dist/vercel/codec/fold-tool-input.d.ts +17 -0
  79. package/dist/vercel/codec/fold-tool-output.d.ts +16 -0
  80. package/dist/vercel/codec/index.d.ts +7 -20
  81. package/dist/vercel/codec/inputs.d.ts +11 -0
  82. package/dist/vercel/codec/outputs.d.ts +11 -0
  83. package/dist/vercel/codec/reducer-state.d.ts +121 -0
  84. package/dist/vercel/codec/reducer.d.ts +62 -0
  85. package/dist/vercel/codec/tool-transitions.d.ts +2 -8
  86. package/dist/vercel/codec/wire-data.d.ts +34 -0
  87. package/dist/vercel/index.d.ts +5 -5
  88. package/dist/vercel/react/ably-ai-transport-vercel-react.js +2859 -9705
  89. package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
  90. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +1 -45
  91. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
  92. package/dist/vercel/react/contexts/chat-transport-context.d.ts +9 -7
  93. package/dist/vercel/react/contexts/chat-transport-provider.d.ts +53 -41
  94. package/dist/vercel/react/index.d.ts +1 -2
  95. package/dist/vercel/react/use-chat-transport.d.ts +30 -26
  96. package/dist/vercel/react/use-message-sync.d.ts +17 -30
  97. package/dist/vercel/run-end-reason.d.ts +84 -0
  98. package/dist/vercel/tool-part.d.ts +21 -0
  99. package/dist/vercel/transport/chat-transport.d.ts +41 -24
  100. package/dist/vercel/transport/index.d.ts +24 -20
  101. package/dist/vercel/transport/run-output-stream.d.ts +54 -0
  102. package/dist/version.d.ts +2 -0
  103. package/package.json +31 -24
  104. package/src/constants.ts +124 -51
  105. package/src/core/agent.ts +92 -0
  106. package/src/core/channel-options.ts +89 -0
  107. package/src/core/codec/codec-event.ts +27 -0
  108. package/src/core/codec/decoder.ts +202 -105
  109. package/src/core/codec/define-codec.ts +432 -0
  110. package/src/core/codec/encoder.ts +114 -107
  111. package/src/core/codec/field-bag.ts +142 -0
  112. package/src/core/codec/fields.ts +193 -0
  113. package/src/core/codec/index.ts +56 -6
  114. package/src/core/codec/input-descriptor-decoder.ts +97 -0
  115. package/src/core/codec/input-descriptor-encoder.ts +150 -0
  116. package/src/core/codec/input-descriptors.ts +373 -0
  117. package/src/core/codec/lifecycle-tracker.ts +10 -9
  118. package/src/core/codec/output-descriptor-decoder.ts +139 -0
  119. package/src/core/codec/output-descriptor-encoder.ts +101 -0
  120. package/src/core/codec/output-descriptors.ts +307 -0
  121. package/src/core/codec/types.ts +505 -126
  122. package/src/core/codec/well-known-inputs.ts +96 -0
  123. package/src/core/transport/agent-session.ts +1085 -0
  124. package/src/core/transport/agent-view.ts +738 -0
  125. package/src/core/transport/client-session.ts +780 -0
  126. package/src/core/transport/decode-fold.ts +101 -0
  127. package/src/core/transport/headers.ts +234 -22
  128. package/src/core/transport/index.ts +27 -27
  129. package/src/core/transport/internal/bounded-map.ts +27 -0
  130. package/src/core/transport/invocation.ts +98 -0
  131. package/src/core/transport/load-history-pages.ts +220 -0
  132. package/src/core/transport/load-history.ts +271 -0
  133. package/src/core/transport/pipe-stream.ts +63 -39
  134. package/src/core/transport/run-manager.ts +243 -0
  135. package/src/core/transport/session-support.ts +96 -0
  136. package/src/core/transport/tree.ts +1293 -308
  137. package/src/core/transport/types/agent.ts +434 -0
  138. package/src/core/transport/types/client.ts +247 -0
  139. package/src/core/transport/types/shared.ts +27 -0
  140. package/src/core/transport/types/tree.ts +393 -0
  141. package/src/core/transport/types/view.ts +288 -0
  142. package/src/core/transport/types.ts +13 -706
  143. package/src/core/transport/view.ts +1229 -450
  144. package/src/core/transport/wire-log.ts +189 -0
  145. package/src/errors.ts +29 -9
  146. package/src/event-emitter.ts +3 -2
  147. package/src/index.ts +86 -42
  148. package/src/logger.ts +14 -1
  149. package/src/react/contexts/client-session-context.ts +41 -0
  150. package/src/react/contexts/client-session-provider.tsx +222 -0
  151. package/src/react/create-session-hooks.ts +141 -0
  152. package/src/react/index.ts +24 -13
  153. package/src/react/internal/skipped-session.ts +62 -0
  154. package/src/react/internal/use-resolved-session.ts +63 -0
  155. package/src/react/use-ably-messages.ts +32 -22
  156. package/src/react/use-client-session.ts +178 -0
  157. package/src/react/use-create-view.ts +33 -29
  158. package/src/react/use-tree.ts +61 -30
  159. package/src/react/use-view.ts +138 -96
  160. package/src/utils.ts +83 -131
  161. package/src/vercel/codec/decode-lifecycle.ts +70 -0
  162. package/src/vercel/codec/events.ts +85 -0
  163. package/src/vercel/codec/fields.ts +58 -0
  164. package/src/vercel/codec/fold-content.ts +54 -0
  165. package/src/vercel/codec/fold-data.ts +46 -0
  166. package/src/vercel/codec/fold-input.ts +255 -0
  167. package/src/vercel/codec/fold-lifecycle.ts +85 -0
  168. package/src/vercel/codec/fold-text.ts +55 -0
  169. package/src/vercel/codec/fold-tool-input.ts +86 -0
  170. package/src/vercel/codec/fold-tool-output.ts +79 -0
  171. package/src/vercel/codec/index.ts +28 -21
  172. package/src/vercel/codec/inputs.ts +116 -0
  173. package/src/vercel/codec/outputs.ts +207 -0
  174. package/src/vercel/codec/reducer-state.ts +169 -0
  175. package/src/vercel/codec/reducer.ts +191 -0
  176. package/src/vercel/codec/tool-transitions.ts +3 -14
  177. package/src/vercel/codec/wire-data.ts +64 -0
  178. package/src/vercel/index.ts +7 -19
  179. package/src/vercel/react/contexts/chat-transport-context.ts +8 -7
  180. package/src/vercel/react/contexts/chat-transport-provider.tsx +87 -59
  181. package/src/vercel/react/index.ts +3 -5
  182. package/src/vercel/react/use-chat-transport.ts +44 -66
  183. package/src/vercel/react/use-message-sync.ts +75 -39
  184. package/src/vercel/run-end-reason.ts +157 -0
  185. package/src/vercel/tool-part.ts +25 -0
  186. package/src/vercel/transport/chat-transport.ts +380 -98
  187. package/src/vercel/transport/index.ts +38 -37
  188. package/src/vercel/transport/run-output-stream.ts +169 -0
  189. package/src/version.ts +2 -0
  190. package/dist/core/transport/client-transport.d.ts +0 -10
  191. package/dist/core/transport/decode-history.d.ts +0 -43
  192. package/dist/core/transport/server-transport.d.ts +0 -7
  193. package/dist/core/transport/stream-router.d.ts +0 -29
  194. package/dist/core/transport/turn-manager.d.ts +0 -37
  195. package/dist/react/contexts/transport-context.d.ts +0 -31
  196. package/dist/react/contexts/transport-provider.d.ts +0 -49
  197. package/dist/react/create-transport-hooks.d.ts +0 -124
  198. package/dist/react/use-active-turns.d.ts +0 -12
  199. package/dist/react/use-client-transport.d.ts +0 -80
  200. package/dist/vercel/codec/accumulator.d.ts +0 -21
  201. package/dist/vercel/codec/decoder.d.ts +0 -22
  202. package/dist/vercel/codec/encoder.d.ts +0 -41
  203. package/dist/vercel/react/use-staged-add-tool-approval-response.d.ts +0 -30
  204. package/dist/vercel/tool-approvals.d.ts +0 -124
  205. package/dist/vercel/tool-events.d.ts +0 -26
  206. package/src/core/transport/client-transport.ts +0 -977
  207. package/src/core/transport/decode-history.ts +0 -485
  208. package/src/core/transport/server-transport.ts +0 -612
  209. package/src/core/transport/stream-router.ts +0 -136
  210. package/src/core/transport/turn-manager.ts +0 -165
  211. package/src/react/contexts/transport-context.ts +0 -37
  212. package/src/react/contexts/transport-provider.tsx +0 -164
  213. package/src/react/create-transport-hooks.ts +0 -144
  214. package/src/react/use-active-turns.ts +0 -72
  215. package/src/react/use-client-transport.ts +0 -197
  216. package/src/vercel/codec/accumulator.ts +0 -588
  217. package/src/vercel/codec/decoder.ts +0 -618
  218. package/src/vercel/codec/encoder.ts +0 -410
  219. package/src/vercel/react/use-staged-add-tool-approval-response.ts +0 -87
  220. package/src/vercel/tool-approvals.ts +0 -380
  221. package/src/vercel/tool-events.ts +0 -53
@@ -0,0 +1,157 @@
1
+ import * as Ably from 'ably';
2
+ import type * as AI from 'ai';
3
+
4
+ import type { RunEndReason, StreamResult } from '../core/transport/types.js';
5
+ import { ErrorCode } from '../errors.js';
6
+
7
+ /**
8
+ * The outcome of a Vercel `streamText` response piped through `Run.pipe`.
9
+ * Discriminated on `reason`: `'suspend'` means the run should pause; the
10
+ * non-`'suspend'` arms describe how it terminated, and an `'error'` outcome
11
+ * always carries `error`.
12
+ *
13
+ * This is a *description of what the Vercel run resulted in*, not a command to
14
+ * the SDK. The common case maps cleanly onto one transport action — `'suspend'`
15
+ * → `Run.suspend()`, everything else → `Run.end()` — and to make that case a
16
+ * one-liner the non-`'suspend'` arms are deliberately assignable to
17
+ * {@link RunEndParams}, so after a `suspend` guard the whole object passes
18
+ * straight to `Run.end(outcome)`. That assignability is a convenience for this
19
+ * adapter, not a constraint on what an outcome can mean: responding to an
20
+ * outcome may also involve work outside this SDK (persisting a result,
21
+ * notifying a human, triggering a downstream workflow), and the developer is
22
+ * free to do that around the terminal call.
23
+ *
24
+ * The type is Vercel-specific by design. Outcomes are the layer where agent
25
+ * SDKs diverge most — both in what they report (the `'suspend'` arm exists only
26
+ * because Vercel surfaces unexecuted tool calls as a non-terminal finish) and
27
+ * in what a developer must do in response. A different SDK's outcome type would
28
+ * have different arms; hence each adapter names its own rather than sharing a
29
+ * single core `RunOutcome`. The vocabulary it bottoms out in
30
+ * ({@link RunEndParams}, `Run.suspend`/`Run.end`) is the shared, codec-agnostic
31
+ * part that does live in core.
32
+ */
33
+ export type VercelRunOutcome =
34
+ | {
35
+ /**
36
+ * The LLM requested tools the SDK did not auto-execute, so the run
37
+ * pauses rather than ending — call `Run.suspend()`.
38
+ */
39
+ reason: 'suspend';
40
+ /** Never present for a suspend outcome. */
41
+ error?: never;
42
+ }
43
+ | {
44
+ /** A non-error terminal reason; pass the outcome to `Run.end()`. */
45
+ reason: Exclude<RunEndReason, 'error'>;
46
+ /** Never present for a non-error outcome. */
47
+ error?: never;
48
+ }
49
+ | {
50
+ /** The run ended in error; pass the outcome to `Run.end()`. */
51
+ reason: Extract<RunEndReason, 'error'>;
52
+ /**
53
+ * The terminal error: the underlying stream / `finishReason` failure
54
+ * wrapped as an `Ably.ErrorInfo` (code `StreamError`).
55
+ */
56
+ error: Ably.ErrorInfo;
57
+ };
58
+
59
+ /**
60
+ * Derive the {@link VercelRunOutcome} for a Vercel `streamText` response that
61
+ * was piped through `Run.pipe`. Preserves transport-level outcomes
62
+ * (`'cancelled'`, `'error'`) from the pipe result; when the pipe completed
63
+ * naturally, awaits Vercel's `finishReason` and returns `'suspend'` for
64
+ * `'tool-calls'` (the LLM requested tools the SDK did not auto-execute, so the
65
+ * run should suspend rather than end), or `'complete'` otherwise.
66
+ *
67
+ * Surfaces the failure for both error shapes so the caller can forward it to
68
+ * `Run.end(reason, error)`: a stream that threw (`pipeResult.error`) and a
69
+ * `finishReason` that rejected with a non-abort error (e.g.
70
+ * `NoOutputGeneratedError`, network blow-ups). The error is wrapped as an
71
+ * `Ably.ErrorInfo` (code `StreamError`). A stream that already produced a
72
+ * codec-level error chunk is unaffected — stamping run-end is the
73
+ * codec-agnostic baseline that any consumer can read.
74
+ *
75
+ * Tolerates `finishReason` rejection. Vercel AI SDK v6 rejects
76
+ * `streamText().finishReason` with the abort signal's reason when the stream
77
+ * is aborted before any step completes, and rejects with
78
+ * `NoOutputGeneratedError` when the model produced nothing at all. Without
79
+ * this guard the rejection would bubble out of the route handler's `after()`
80
+ * block, skip the developer's `Run.end(...)` call, and leave the run with no
81
+ * `ai-run-end` event on the channel — so observers' UIs stay stuck on
82
+ * `streaming` indefinitely.
83
+ *
84
+ * Saves callers from interpreting Vercel domain semantics inline at the end
85
+ * of every route handler.
86
+ * @param pipeResult - The result returned by `Run.pipe`.
87
+ * @param finishReason - The `finishReason` promise from a `streamText` result.
88
+ * @returns The {@link VercelRunOutcome}: the terminal `reason` (or `'suspend'`)
89
+ * and, when `reason` is `'error'`, the wrapped `error` to pass to `Run.end`.
90
+ */
91
+ export const vercelRunOutcome = async (
92
+ pipeResult: StreamResult,
93
+ finishReason: PromiseLike<AI.FinishReason>,
94
+ ): Promise<VercelRunOutcome> => {
95
+ if (pipeResult.reason !== 'complete') {
96
+ // Vercel's `result.finishReason` getter creates the underlying Promise
97
+ // eagerly, before the caller hands it to us. When `streamText` is
98
+ // aborted before any step completes, Vercel rejects that Promise with
99
+ // the abort signal's reason — typically a DOMException whose
100
+ // `.message` is a read-only getter. Returning early without ever
101
+ // attaching a handler lets Node report it as an unhandled rejection;
102
+ // Next.js' dev bundler then tries to mutate `.message` for logging
103
+ // and crashes with a confusing TypeError. Attach a silent handler so
104
+ // the rejection is observed and discarded — the transport-level
105
+ // `pipeResult.reason` is already what we return.
106
+ Promise.resolve(finishReason).catch(() => {
107
+ /* intentionally discarded; reason already known from pipeResult */
108
+ });
109
+ if (pipeResult.reason === 'error') {
110
+ return { reason: 'error', error: _toErrorInfo(pipeResult.error) };
111
+ }
112
+ return { reason: pipeResult.reason };
113
+ }
114
+ try {
115
+ const finish = await finishReason;
116
+ if (finish === 'tool-calls') return { reason: 'suspend' };
117
+ return { reason: 'complete' };
118
+ } catch (error) {
119
+ // Abort-shaped rejections are surfaced from streamText when the run was
120
+ // cancelled before any step finished — treat the run as cancelled so the
121
+ // observable lifecycle matches the cancel that triggered it. Everything
122
+ // else is a real error (e.g. NoOutputGeneratedError, network blow-ups);
123
+ // surface it as such — wrapped so the caller can stamp it on run-end — so
124
+ // the developer sees the failure rather than a silent cancel.
125
+ if (_isAbortLikeError(error)) return { reason: 'cancelled' };
126
+ return { reason: 'error', error: _toErrorInfo(error) };
127
+ }
128
+ };
129
+
130
+ /**
131
+ * Wrap a caught stream / `finishReason` failure as an `Ably.ErrorInfo` so it
132
+ * can be passed to `Run.end(reason, error)`. An error that is already an
133
+ * `Ably.ErrorInfo` is returned unchanged; anything else is wrapped with code
134
+ * `StreamError`, mirroring how `Run.pipe` wraps stream errors for `onError`.
135
+ * @param error - The caught error (or `undefined` when the stream reported none).
136
+ * @returns The error as an `Ably.ErrorInfo`.
137
+ */
138
+ const _toErrorInfo = (error: unknown): Ably.ErrorInfo => {
139
+ if (error instanceof Ably.ErrorInfo) return error;
140
+ const message = error instanceof Error ? error.message : String(error);
141
+ return new Ably.ErrorInfo(`unable to complete run; ${message}`, ErrorCode.StreamError, 500);
142
+ };
143
+
144
+ /**
145
+ * Heuristic for "this error came from an AbortSignal aborting".
146
+ * Covers `DOMException` aborts (browser / Node 20+ `streamText`),
147
+ * plain `Error` objects whose `name` is `'AbortError'`, and anything
148
+ * else carrying that conventional name. Avoids importing
149
+ * `@ai-sdk/provider-utils` just for `isAbortError`.
150
+ * @param error - The error to test.
151
+ * @returns `true` if the error looks like an abort.
152
+ */
153
+ const _isAbortLikeError = (error: unknown): boolean => {
154
+ if (typeof error !== 'object' || error === null) return false;
155
+ const name = (error as { name?: unknown }).name;
156
+ return name === 'AbortError';
157
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Shared tool-part type guard for the Vercel layer.
3
+ *
4
+ * The codec normalises every tool part to the `dynamic-tool` shape, but the AI
5
+ * SDK emits `tool-${name}` parts for statically-declared tools. Both shapes
6
+ * carry `toolCallId` and `state`. The guard accepts either representation so
7
+ * the transport's unresolved-tool detection and the React overlay merge can
8
+ * match tool parts uniformly — and so the cross-representation rule lives in
9
+ * one place rather than being re-spelled per call site.
10
+ */
11
+
12
+ import type * as AI from 'ai';
13
+
14
+ /** A UIMessage tool part in either the `dynamic-tool` or `tool-${name}` representation. */
15
+ export type ToolPart = AI.DynamicToolUIPart | AI.ToolUIPart;
16
+
17
+ /**
18
+ * Whether a UIMessage part is a tool part of either representation. The
19
+ * `toolCallId`/`state` shape check is defensive against a future AI SDK release
20
+ * introducing a non-tool variant under the `tool-` prefix (none exists today).
21
+ * @param part - The UIMessage part to inspect.
22
+ * @returns True when the part is a tool part.
23
+ */
24
+ export const isToolPart = (part: AI.UIMessage['parts'][number]): part is ToolPart =>
25
+ (part.type === 'dynamic-tool' || part.type.startsWith('tool-')) && 'toolCallId' in part && 'state' in part;