assistant-stream 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/dist/ai-sdk/index.d.ts +5 -0
  2. package/dist/ai-sdk/index.d.ts.map +1 -0
  3. package/dist/ai-sdk/index.js +4 -3
  4. package/dist/ai-sdk/index.js.map +1 -1
  5. package/dist/ai-sdk/language-model.d.ts +6 -0
  6. package/dist/ai-sdk/language-model.d.ts.map +1 -0
  7. package/dist/ai-sdk/language-model.js +4 -3
  8. package/dist/ai-sdk/language-model.js.map +1 -1
  9. package/dist/ai-sdk.d.ts +3 -0
  10. package/dist/ai-sdk.d.ts.map +1 -0
  11. package/dist/ai-sdk.js +3 -2
  12. package/dist/ai-sdk.js.map +1 -1
  13. package/dist/core/AssistantStream.d.ts +12 -0
  14. package/dist/core/AssistantStream.d.ts.map +1 -0
  15. package/dist/core/AssistantStream.js +2 -1
  16. package/dist/core/AssistantStream.js.map +1 -1
  17. package/dist/core/AssistantStreamChunk.d.ts +64 -0
  18. package/dist/core/AssistantStreamChunk.d.ts.map +1 -0
  19. package/dist/core/accumulators/AssistantMessageStream.d.ts +13 -0
  20. package/dist/core/accumulators/AssistantMessageStream.d.ts.map +1 -0
  21. package/dist/core/accumulators/AssistantMessageStream.js +7 -6
  22. package/dist/core/accumulators/AssistantMessageStream.js.map +1 -1
  23. package/dist/core/accumulators/assistant-message-accumulator.d.ts +8 -0
  24. package/dist/core/accumulators/assistant-message-accumulator.d.ts.map +1 -0
  25. package/dist/core/accumulators/assistant-message-accumulator.js +19 -18
  26. package/dist/core/accumulators/assistant-message-accumulator.js.map +1 -1
  27. package/dist/core/index.d.ts +11 -0
  28. package/dist/core/index.d.ts.map +1 -0
  29. package/dist/core/index.js +8 -7
  30. package/dist/core/index.js.map +1 -1
  31. package/dist/core/modules/assistant-stream.d.ts +31 -0
  32. package/dist/core/modules/assistant-stream.d.ts.map +1 -0
  33. package/dist/core/modules/assistant-stream.js +12 -11
  34. package/dist/core/modules/assistant-stream.js.map +1 -1
  35. package/dist/core/modules/text.d.ts +9 -0
  36. package/dist/core/modules/text.d.ts.map +1 -0
  37. package/dist/core/modules/text.js +5 -4
  38. package/dist/core/modules/text.js.map +1 -1
  39. package/dist/core/modules/tool-call.d.ts +13 -0
  40. package/dist/core/modules/tool-call.d.ts.map +1 -0
  41. package/dist/core/modules/tool-call.js +6 -5
  42. package/dist/core/modules/tool-call.js.map +1 -1
  43. package/dist/core/serialization/PlainText.d.ts +11 -0
  44. package/dist/core/serialization/PlainText.d.ts.map +1 -0
  45. package/dist/core/serialization/PlainText.js +7 -6
  46. package/dist/core/serialization/PlainText.js.map +1 -1
  47. package/dist/core/serialization/data-stream/DataStream.d.ts +11 -0
  48. package/dist/core/serialization/data-stream/DataStream.d.ts.map +1 -0
  49. package/dist/core/serialization/data-stream/DataStream.js +12 -11
  50. package/dist/core/serialization/data-stream/DataStream.js.map +1 -1
  51. package/dist/core/serialization/data-stream/chunk-types.d.ts +86 -0
  52. package/dist/core/serialization/data-stream/chunk-types.d.ts.map +1 -0
  53. package/dist/core/serialization/data-stream/chunk-types.js +1 -0
  54. package/dist/core/serialization/data-stream/chunk-types.js.map +1 -1
  55. package/dist/core/serialization/data-stream/serialization.d.ts +8 -0
  56. package/dist/core/serialization/data-stream/serialization.d.ts.map +1 -0
  57. package/dist/core/serialization/data-stream/serialization.js +5 -4
  58. package/dist/core/serialization/data-stream/serialization.js.map +1 -1
  59. package/dist/core/tool/ToolCallReader.d.ts +33 -0
  60. package/dist/core/tool/ToolCallReader.d.ts.map +1 -0
  61. package/dist/core/tool/ToolCallReader.js +17 -16
  62. package/dist/core/tool/ToolCallReader.js.map +1 -1
  63. package/dist/core/tool/ToolExecutionStream.d.ts +24 -0
  64. package/dist/core/tool/ToolExecutionStream.d.ts.map +1 -0
  65. package/dist/core/tool/ToolExecutionStream.js +8 -7
  66. package/dist/core/tool/ToolExecutionStream.js.map +1 -1
  67. package/dist/core/tool/ToolResponse.d.ts +17 -0
  68. package/dist/core/tool/ToolResponse.d.ts.map +1 -0
  69. package/dist/core/tool/ToolResponse.js +4 -3
  70. package/dist/core/tool/ToolResponse.js.map +1 -1
  71. package/dist/core/tool/index.d.ts +6 -0
  72. package/dist/core/tool/index.d.ts.map +1 -0
  73. package/dist/core/tool/index.js +4 -3
  74. package/dist/core/tool/index.js.map +1 -1
  75. package/dist/core/tool/tool-types.d.ts +76 -0
  76. package/dist/core/tool/tool-types.d.ts.map +1 -0
  77. package/dist/core/tool/toolResultStream.d.ts +6 -0
  78. package/dist/core/tool/toolResultStream.d.ts.map +1 -0
  79. package/dist/core/tool/toolResultStream.js +4 -3
  80. package/dist/core/tool/toolResultStream.js.map +1 -1
  81. package/dist/core/tool/type-path-utils.d.ts +23 -0
  82. package/dist/core/tool/type-path-utils.d.ts.map +1 -0
  83. package/dist/core/utils/Counter.d.ts +5 -0
  84. package/dist/core/utils/Counter.d.ts.map +1 -0
  85. package/dist/core/utils/Counter.js +3 -2
  86. package/dist/core/utils/Counter.js.map +1 -1
  87. package/dist/core/utils/generateId.d.ts +2 -0
  88. package/dist/core/utils/generateId.d.ts.map +1 -0
  89. package/dist/core/utils/generateId.js +2 -1
  90. package/dist/core/utils/generateId.js.map +1 -1
  91. package/dist/core/utils/stream/AssistantMetaTransformStream.d.ts +20 -0
  92. package/dist/core/utils/stream/AssistantMetaTransformStream.d.ts.map +1 -0
  93. package/dist/core/utils/stream/AssistantMetaTransformStream.js +3 -2
  94. package/dist/core/utils/stream/AssistantMetaTransformStream.js.map +1 -1
  95. package/dist/core/utils/stream/AssistantTransformStream.d.ts +15 -0
  96. package/dist/core/utils/stream/AssistantTransformStream.d.ts.map +1 -0
  97. package/dist/core/utils/stream/AssistantTransformStream.js +4 -3
  98. package/dist/core/utils/stream/AssistantTransformStream.js.map +1 -1
  99. package/dist/core/utils/stream/LineDecoderStream.d.ts +5 -0
  100. package/dist/core/utils/stream/LineDecoderStream.d.ts.map +1 -0
  101. package/dist/core/utils/stream/LineDecoderStream.js +3 -2
  102. package/dist/core/utils/stream/LineDecoderStream.js.map +1 -1
  103. package/dist/core/utils/stream/PipeableTransformStream.d.ts +4 -0
  104. package/dist/core/utils/stream/PipeableTransformStream.d.ts.map +1 -0
  105. package/dist/core/utils/stream/PipeableTransformStream.js +3 -2
  106. package/dist/core/utils/stream/PipeableTransformStream.js.map +1 -1
  107. package/dist/core/utils/stream/UnderlyingReadable.d.ts +6 -0
  108. package/dist/core/utils/stream/UnderlyingReadable.d.ts.map +1 -0
  109. package/dist/core/utils/stream/merge.d.ts +9 -0
  110. package/dist/core/utils/stream/merge.d.ts.map +1 -0
  111. package/dist/core/utils/stream/merge.js +3 -2
  112. package/dist/core/utils/stream/merge.js.map +1 -1
  113. package/dist/core/utils/stream/path-utils.d.ts +12 -0
  114. package/dist/core/utils/stream/path-utils.d.ts.map +1 -0
  115. package/dist/core/utils/stream/path-utils.js +8 -7
  116. package/dist/core/utils/stream/path-utils.js.map +1 -1
  117. package/dist/core/utils/types.d.ts +102 -0
  118. package/dist/core/utils/types.d.ts.map +1 -0
  119. package/dist/core/utils/withPromiseOrValue.d.ts +2 -0
  120. package/dist/core/utils/withPromiseOrValue.d.ts.map +1 -0
  121. package/dist/core/utils/withPromiseOrValue.js +1 -0
  122. package/dist/core/utils/withPromiseOrValue.js.map +1 -1
  123. package/dist/index.d.ts +2 -0
  124. package/dist/index.d.ts.map +1 -0
  125. package/dist/index.js +2 -1
  126. package/dist/index.js.map +1 -1
  127. package/dist/utils/AsyncIterableStream.d.ts +3 -0
  128. package/dist/utils/AsyncIterableStream.d.ts.map +1 -0
  129. package/dist/utils/AsyncIterableStream.js +1 -0
  130. package/dist/utils/AsyncIterableStream.js.map +1 -1
  131. package/dist/utils/json/fix-json.d.ts +2 -0
  132. package/dist/utils/json/fix-json.d.ts.map +1 -0
  133. package/dist/utils/json/fix-json.js +1 -0
  134. package/dist/utils/json/fix-json.js.map +1 -1
  135. package/dist/utils/json/is-json.d.ts +5 -0
  136. package/dist/utils/json/is-json.d.ts.map +1 -0
  137. package/dist/utils/json/is-json.js +1 -0
  138. package/dist/utils/json/is-json.js.map +1 -1
  139. package/dist/utils/json/json-value.d.ts +6 -0
  140. package/dist/utils/json/json-value.d.ts.map +1 -0
  141. package/dist/utils/json/parse-partial-json-object.d.ts +14 -0
  142. package/dist/utils/json/parse-partial-json-object.d.ts.map +1 -0
  143. package/dist/utils/json/parse-partial-json-object.js +7 -6
  144. package/dist/utils/json/parse-partial-json-object.js.map +1 -1
  145. package/dist/utils/json/parse-partial-json-object.test.d.ts +2 -0
  146. package/dist/utils/json/parse-partial-json-object.test.d.ts.map +1 -0
  147. package/dist/utils/promiseWithResolvers.d.ts +6 -0
  148. package/dist/utils/promiseWithResolvers.d.ts.map +1 -0
  149. package/dist/utils/promiseWithResolvers.js +2 -1
  150. package/dist/utils/promiseWithResolvers.js.map +1 -1
  151. package/dist/utils.d.ts +4 -0
  152. package/dist/utils.d.ts.map +1 -0
  153. package/dist/utils.js +3 -2
  154. package/dist/utils.js.map +1 -1
  155. package/package.json +3 -2
  156. package/src/ai-sdk/index.ts +204 -0
  157. package/src/ai-sdk/language-model.ts +122 -0
  158. package/src/ai-sdk.ts +2 -0
  159. package/src/core/AssistantStream.ts +39 -0
  160. package/src/core/AssistantStreamChunk.ts +93 -0
  161. package/src/core/accumulators/AssistantMessageStream.ts +56 -0
  162. package/src/core/accumulators/assistant-message-accumulator.ts +394 -0
  163. package/src/core/index.ts +17 -0
  164. package/src/core/modules/assistant-stream.ts +264 -0
  165. package/src/core/modules/text.ts +64 -0
  166. package/src/core/modules/tool-call.ts +105 -0
  167. package/src/core/serialization/PlainText.ts +68 -0
  168. package/src/core/serialization/data-stream/DataStream.ts +355 -0
  169. package/src/core/serialization/data-stream/chunk-types.ts +93 -0
  170. package/src/core/serialization/data-stream/serialization.ts +32 -0
  171. package/src/core/tool/ToolCallReader.ts +432 -0
  172. package/src/core/tool/ToolExecutionStream.ts +180 -0
  173. package/src/core/tool/ToolResponse.ts +31 -0
  174. package/src/core/tool/index.ts +8 -0
  175. package/src/core/tool/tool-types.ts +107 -0
  176. package/src/core/tool/toolResultStream.ts +119 -0
  177. package/src/core/tool/type-path-utils.ts +36 -0
  178. package/src/core/utils/Counter.ts +7 -0
  179. package/src/core/utils/generateId.tsx +6 -0
  180. package/src/core/utils/stream/AssistantMetaTransformStream.ts +74 -0
  181. package/src/core/utils/stream/AssistantTransformStream.ts +74 -0
  182. package/src/core/utils/stream/LineDecoderStream.ts +29 -0
  183. package/src/core/utils/stream/PipeableTransformStream.ts +10 -0
  184. package/src/core/utils/stream/UnderlyingReadable.ts +5 -0
  185. package/src/core/utils/stream/merge.ts +212 -0
  186. package/src/core/utils/stream/path-utils.ts +71 -0
  187. package/src/core/utils/types.ts +150 -0
  188. package/src/core/utils/withPromiseOrValue.ts +20 -0
  189. package/src/index.ts +1 -0
  190. package/src/utils/AsyncIterableStream.ts +24 -0
  191. package/src/utils/json/fix-json.ts +488 -0
  192. package/src/utils/json/is-json.ts +43 -0
  193. package/src/utils/json/json-value.ts +13 -0
  194. package/src/utils/json/parse-partial-json-object.test.ts +225 -0
  195. package/src/utils/json/parse-partial-json-object.ts +103 -0
  196. package/src/utils/promiseWithResolvers.ts +10 -0
  197. package/src/utils.ts +13 -0
  198. package/utils/README.md +0 -1
  199. package/utils/package.json +0 -5
@@ -0,0 +1,39 @@
1
+ import { AssistantStreamChunk } from "./AssistantStreamChunk";
2
+
3
+ export type AssistantStream = ReadableStream<AssistantStreamChunk>;
4
+
5
+ export type AssistantStreamEncoder = ReadableWritablePair<
6
+ Uint8Array,
7
+ AssistantStreamChunk
8
+ > & {
9
+ headers?: Headers;
10
+ };
11
+
12
+ export const AssistantStream = {
13
+ toResponse(stream: AssistantStream, transformer: AssistantStreamEncoder) {
14
+ return new Response(AssistantStream.toByteStream(stream, transformer), {
15
+ headers: transformer.headers ?? {},
16
+ });
17
+ },
18
+
19
+ fromResponse(
20
+ response: Response,
21
+ transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array>,
22
+ ) {
23
+ return AssistantStream.fromByteStream(response.body!, transformer);
24
+ },
25
+
26
+ toByteStream(
27
+ stream: AssistantStream,
28
+ transformer: ReadableWritablePair<Uint8Array, AssistantStreamChunk>,
29
+ ) {
30
+ return stream.pipeThrough(transformer);
31
+ },
32
+
33
+ fromByteStream(
34
+ readable: ReadableStream<Uint8Array>,
35
+ transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array>,
36
+ ) {
37
+ return readable.pipeThrough(transformer);
38
+ },
39
+ };
@@ -0,0 +1,93 @@
1
+ import { ReadonlyJSONValue } from "../utils/json/json-value";
2
+
3
+ export type PartInit =
4
+ | {
5
+ readonly type: "text" | "reasoning";
6
+ }
7
+ | {
8
+ readonly type: "tool-call";
9
+ readonly toolCallId: string;
10
+ readonly toolName: string;
11
+ }
12
+ | {
13
+ readonly type: "source";
14
+ readonly sourceType: "url";
15
+ readonly id: string;
16
+ readonly url: string;
17
+ readonly title?: string;
18
+ }
19
+ | {
20
+ readonly type: "file";
21
+ readonly data: string;
22
+ readonly mimeType: string;
23
+ };
24
+
25
+ export type AssistantStreamChunk = { readonly path: readonly number[] } & (
26
+ | {
27
+ readonly type: "part-start";
28
+ readonly part: PartInit;
29
+ }
30
+ | {
31
+ readonly type: "part-finish";
32
+ }
33
+ | {
34
+ readonly type: "tool-call-args-text-finish";
35
+ }
36
+ | {
37
+ readonly type: "text-delta";
38
+ readonly textDelta: string;
39
+ }
40
+ | {
41
+ readonly type: "annotations";
42
+ readonly annotations: ReadonlyJSONValue[];
43
+ }
44
+ | {
45
+ readonly type: "data";
46
+ readonly data: ReadonlyJSONValue[];
47
+ }
48
+ | {
49
+ readonly type: "step-start";
50
+ readonly messageId: string;
51
+ }
52
+ | {
53
+ readonly type: "step-finish";
54
+ readonly finishReason:
55
+ | "stop"
56
+ | "length"
57
+ | "content-filter"
58
+ | "tool-calls"
59
+ | "error"
60
+ | "other"
61
+ | "unknown";
62
+ readonly usage: {
63
+ readonly promptTokens: number;
64
+ readonly completionTokens: number;
65
+ };
66
+ readonly isContinued: boolean;
67
+ }
68
+ | {
69
+ readonly type: "message-finish";
70
+ readonly finishReason:
71
+ | "stop"
72
+ | "length"
73
+ | "content-filter"
74
+ | "tool-calls"
75
+ | "error"
76
+ | "other"
77
+ | "unknown";
78
+ readonly usage: {
79
+ readonly promptTokens: number;
80
+ readonly completionTokens: number;
81
+ };
82
+ }
83
+ | {
84
+ readonly type: "result";
85
+ readonly artifact?: ReadonlyJSONValue | undefined;
86
+ readonly result: ReadonlyJSONValue;
87
+ readonly isError: boolean;
88
+ }
89
+ | {
90
+ readonly type: "error";
91
+ readonly error: string;
92
+ }
93
+ );
@@ -0,0 +1,56 @@
1
+ import { AssistantStream } from "../AssistantStream";
2
+ import { AssistantMessage } from "../utils/types";
3
+ import { AssistantMessageAccumulator } from "./assistant-message-accumulator";
4
+
5
+ export class AssistantMessageStream {
6
+ constructor(public readonly readable: ReadableStream<AssistantMessage>) {
7
+ this.readable = readable;
8
+ }
9
+
10
+ static fromAssistantStream(stream: AssistantStream) {
11
+ return new AssistantMessageStream(
12
+ stream.pipeThrough(new AssistantMessageAccumulator()),
13
+ );
14
+ }
15
+
16
+ async unstable_result(): Promise<AssistantMessage> {
17
+ let last: AssistantMessage | undefined;
18
+ for await (const chunk of this) {
19
+ last = chunk;
20
+ }
21
+
22
+ if (!last) {
23
+ return {
24
+ role: "assistant",
25
+ status: { type: "complete", reason: "unknown" },
26
+ parts: [],
27
+ content: [],
28
+ metadata: {
29
+ unstable_data: [],
30
+ unstable_annotations: [],
31
+ steps: [],
32
+ custom: {},
33
+ },
34
+ };
35
+ }
36
+ return last;
37
+ }
38
+
39
+ [Symbol.asyncIterator]() {
40
+ const reader = this.readable.getReader();
41
+ return {
42
+ async next(): Promise<IteratorResult<AssistantMessage, undefined>> {
43
+ const { done, value } = await reader.read();
44
+ return done ? { done: true, value: undefined } : { done: false, value };
45
+ },
46
+ };
47
+ }
48
+
49
+ tee(): [AssistantMessageStream, AssistantMessageStream] {
50
+ const [readable1, readable2] = this.readable.tee();
51
+ return [
52
+ new AssistantMessageStream(readable1),
53
+ new AssistantMessageStream(readable2),
54
+ ];
55
+ }
56
+ }
@@ -0,0 +1,394 @@
1
+ import { AssistantStreamChunk } from "../AssistantStreamChunk";
2
+ import { generateId } from "../utils/generateId";
3
+ import { parsePartialJsonObject } from "../../utils/json/parse-partial-json-object";
4
+ import {
5
+ AssistantMessage,
6
+ AssistantMessageStatus,
7
+ TextPart,
8
+ ToolCallPart,
9
+ SourcePart,
10
+ AssistantMessagePart,
11
+ ReasoningPart,
12
+ FilePart,
13
+ } from "../utils/types";
14
+
15
+ const createInitialMessage = (): AssistantMessage => ({
16
+ role: "assistant",
17
+ status: { type: "running" },
18
+ parts: [],
19
+ get content() {
20
+ return this.parts;
21
+ },
22
+ metadata: {
23
+ unstable_data: [],
24
+ unstable_annotations: [],
25
+ steps: [],
26
+ custom: {},
27
+ },
28
+ });
29
+
30
+ const updatePartForPath = (
31
+ message: AssistantMessage,
32
+ chunk: AssistantStreamChunk,
33
+ updater: (part: AssistantMessagePart) => AssistantMessagePart,
34
+ ): AssistantMessage => {
35
+ if (message.parts.length === 0) {
36
+ throw new Error("No parts available to update.");
37
+ }
38
+
39
+ if (chunk.path.length !== 1)
40
+ throw new Error("Nested paths are not supported yet.");
41
+
42
+ const partIndex = chunk.path[0]!;
43
+ const updatedPart = updater(message.parts[partIndex]!);
44
+ return {
45
+ ...message,
46
+ parts: [
47
+ ...message.parts.slice(0, partIndex),
48
+ updatedPart,
49
+ ...message.parts.slice(partIndex + 1),
50
+ ],
51
+ get content() {
52
+ return this.parts;
53
+ },
54
+ };
55
+ };
56
+
57
+ const handlePartStart = (
58
+ message: AssistantMessage,
59
+ chunk: AssistantStreamChunk & { readonly type: "part-start" },
60
+ ): AssistantMessage => {
61
+ const partInit = chunk.part;
62
+ if (partInit.type === "text" || partInit.type === "reasoning") {
63
+ const newTextPart: TextPart | ReasoningPart = {
64
+ type: partInit.type,
65
+ text: "",
66
+ status: { type: "running" },
67
+ };
68
+ return {
69
+ ...message,
70
+ parts: [...message.parts, newTextPart],
71
+ get content() {
72
+ return this.parts;
73
+ },
74
+ };
75
+ } else if (partInit.type === "tool-call") {
76
+ const newToolCallPart: ToolCallPart = {
77
+ type: "tool-call",
78
+ state: "partial-call",
79
+ status: { type: "running", isArgsComplete: false },
80
+ toolCallId: partInit.toolCallId,
81
+ toolName: partInit.toolName,
82
+ argsText: "",
83
+ args: {},
84
+ };
85
+ return {
86
+ ...message,
87
+ parts: [...message.parts, newToolCallPart],
88
+ get content() {
89
+ return this.parts;
90
+ },
91
+ };
92
+ } else if (partInit.type === "source") {
93
+ const newSourcePart: SourcePart = {
94
+ type: "source",
95
+ sourceType: partInit.sourceType,
96
+ id: partInit.id,
97
+ url: partInit.url,
98
+ ...(partInit.title ? { title: partInit.title } : undefined),
99
+ };
100
+ return {
101
+ ...message,
102
+ parts: [...message.parts, newSourcePart],
103
+ get content() {
104
+ return this.parts;
105
+ },
106
+ };
107
+ } else if (partInit.type === "file") {
108
+ const newFilePart: FilePart = {
109
+ type: "file",
110
+ mimeType: partInit.mimeType,
111
+ data: partInit.data,
112
+ };
113
+ return {
114
+ ...message,
115
+ parts: [...message.parts, newFilePart],
116
+ get content() {
117
+ return this.parts;
118
+ },
119
+ };
120
+ } else {
121
+ throw new Error(`Unsupported part type: ${partInit.type}`);
122
+ }
123
+ };
124
+
125
+ const handleToolCallArgsTextFinish = (
126
+ message: AssistantMessage,
127
+ chunk: AssistantStreamChunk & {
128
+ readonly type: "tool-call-args-text-finish";
129
+ },
130
+ ): AssistantMessage => {
131
+ return updatePartForPath(message, chunk, (part) => {
132
+ if (part.type !== "tool-call") {
133
+ throw new Error("Last is not a tool call");
134
+ }
135
+ return {
136
+ ...part,
137
+ state: "call",
138
+ };
139
+ });
140
+ };
141
+
142
+ const handlePartFinish = (
143
+ message: AssistantMessage,
144
+ chunk: AssistantStreamChunk & { readonly type: "part-finish" },
145
+ ): AssistantMessage => {
146
+ return updatePartForPath(message, chunk, (part) => ({
147
+ ...part,
148
+ status: { type: "complete", reason: "unknown" },
149
+ }));
150
+ };
151
+
152
+ const handleTextDelta = (
153
+ message: AssistantMessage,
154
+ chunk: AssistantStreamChunk & { type: "text-delta" },
155
+ ): AssistantMessage => {
156
+ return updatePartForPath(message, chunk, (part) => {
157
+ if (part.type === "text" || part.type === "reasoning") {
158
+ return { ...part, text: part.text + chunk.textDelta };
159
+ } else if (part.type === "tool-call") {
160
+ const newArgsText = part.argsText + chunk.textDelta;
161
+
162
+ // Fall back to existing args if parsing fails
163
+ const newArgs = parsePartialJsonObject(newArgsText) ?? part.args;
164
+
165
+ return { ...part, argsText: newArgsText, args: newArgs };
166
+ } else {
167
+ throw new Error(
168
+ "text-delta received but part is neither text nor tool-call",
169
+ );
170
+ }
171
+ });
172
+ };
173
+
174
+ const handleResult = (
175
+ message: AssistantMessage,
176
+ chunk: AssistantStreamChunk & { type: "result" },
177
+ ): AssistantMessage => {
178
+ return updatePartForPath(message, chunk, (part) => {
179
+ if (part.type === "tool-call") {
180
+ return {
181
+ ...part,
182
+ state: "result",
183
+ artifact: chunk.artifact,
184
+ result: chunk.result,
185
+ isError: chunk.isError ?? false,
186
+ status: { type: "complete", reason: "stop" },
187
+ };
188
+ } else {
189
+ throw new Error("Result chunk received but part is not a tool-call");
190
+ }
191
+ });
192
+ };
193
+
194
+ const handleMessageFinish = (
195
+ message: AssistantMessage,
196
+ chunk: AssistantStreamChunk & { type: "message-finish" },
197
+ ): AssistantMessage => {
198
+ const newStatus = getStatus(chunk);
199
+ return { ...message, status: newStatus };
200
+ };
201
+
202
+ const getStatus = (
203
+ chunk:
204
+ | (AssistantStreamChunk & { type: "message-finish" })
205
+ | (AssistantStreamChunk & { type: "step-finish" }),
206
+ ): AssistantMessageStatus => {
207
+ if (chunk.finishReason === "tool-calls") {
208
+ return {
209
+ type: "requires-action",
210
+ reason: "tool-calls",
211
+ };
212
+ } else if (
213
+ chunk.finishReason === "stop" ||
214
+ chunk.finishReason === "unknown"
215
+ ) {
216
+ return {
217
+ type: "complete",
218
+ reason: chunk.finishReason,
219
+ };
220
+ } else {
221
+ return {
222
+ type: "incomplete",
223
+ reason: chunk.finishReason,
224
+ };
225
+ }
226
+ };
227
+
228
+ const handleAnnotations = (
229
+ message: AssistantMessage,
230
+ chunk: AssistantStreamChunk & { type: "annotations" },
231
+ ): AssistantMessage => {
232
+ return {
233
+ ...message,
234
+ metadata: {
235
+ ...message.metadata,
236
+ unstable_annotations: [
237
+ ...message.metadata.unstable_annotations,
238
+ ...chunk.annotations,
239
+ ],
240
+ },
241
+ };
242
+ };
243
+
244
+ const handleData = (
245
+ message: AssistantMessage,
246
+ chunk: AssistantStreamChunk & { type: "data" },
247
+ ): AssistantMessage => {
248
+ return {
249
+ ...message,
250
+ metadata: {
251
+ ...message.metadata,
252
+ unstable_data: [...message.metadata.unstable_data, ...chunk.data],
253
+ },
254
+ };
255
+ };
256
+
257
+ const handleStepStart = (
258
+ message: AssistantMessage,
259
+ chunk: AssistantStreamChunk & { type: "step-start" },
260
+ ): AssistantMessage => {
261
+ return {
262
+ ...message,
263
+ metadata: {
264
+ ...message.metadata,
265
+ steps: [
266
+ ...message.metadata.steps,
267
+ { state: "started", messageId: chunk.messageId },
268
+ ],
269
+ },
270
+ };
271
+ };
272
+
273
+ const handleStepFinish = (
274
+ message: AssistantMessage,
275
+ chunk: AssistantStreamChunk & { type: "step-finish" },
276
+ ): AssistantMessage => {
277
+ const steps = message.metadata.steps.slice();
278
+ const lastIndex = steps.length - 1;
279
+
280
+ // Check if the previous step is a step-start (has state "started")
281
+ if (steps.length > 0 && steps[lastIndex]?.state === "started") {
282
+ steps[lastIndex] = {
283
+ ...steps[lastIndex],
284
+ state: "finished",
285
+ finishReason: chunk.finishReason,
286
+ usage: chunk.usage,
287
+ isContinued: chunk.isContinued,
288
+ };
289
+ } else {
290
+ // If no previous step-start exists, append a finished step
291
+ steps.push({
292
+ state: "finished",
293
+ messageId: generateId(),
294
+ finishReason: chunk.finishReason,
295
+ usage: chunk.usage,
296
+ isContinued: chunk.isContinued,
297
+ });
298
+ }
299
+
300
+ return {
301
+ ...message,
302
+ metadata: {
303
+ ...message.metadata,
304
+ steps,
305
+ },
306
+ };
307
+ };
308
+
309
+ const handleErrorChunk = (
310
+ message: AssistantMessage,
311
+ chunk: AssistantStreamChunk & { type: "error" },
312
+ ): AssistantMessage => {
313
+ return {
314
+ ...message,
315
+ status: { type: "incomplete", reason: "error", error: chunk.error },
316
+ };
317
+ };
318
+
319
+ export class AssistantMessageAccumulator extends TransformStream<
320
+ AssistantStreamChunk,
321
+ AssistantMessage
322
+ > {
323
+ constructor({
324
+ initialMessage,
325
+ }: {
326
+ initialMessage?: AssistantMessage;
327
+ } = {}) {
328
+ let message = initialMessage ?? createInitialMessage();
329
+ super({
330
+ transform(chunk, controller) {
331
+ const type = chunk.type;
332
+ switch (type) {
333
+ case "part-start":
334
+ message = handlePartStart(message, chunk);
335
+ break;
336
+
337
+ case "tool-call-args-text-finish":
338
+ message = handleToolCallArgsTextFinish(message, chunk);
339
+ break;
340
+
341
+ case "part-finish":
342
+ message = handlePartFinish(message, chunk);
343
+ break;
344
+
345
+ case "text-delta":
346
+ message = handleTextDelta(message, chunk);
347
+ break;
348
+ case "result":
349
+ message = handleResult(message, chunk);
350
+ break;
351
+ case "message-finish":
352
+ message = handleMessageFinish(message, chunk);
353
+ break;
354
+ case "annotations":
355
+ message = handleAnnotations(message, chunk);
356
+ break;
357
+ case "data":
358
+ message = handleData(message, chunk);
359
+ break;
360
+ case "step-start":
361
+ message = handleStepStart(message, chunk);
362
+ break;
363
+ case "step-finish":
364
+ message = handleStepFinish(message, chunk);
365
+ break;
366
+ case "error":
367
+ message = handleErrorChunk(message, chunk);
368
+ break;
369
+ default: {
370
+ const unhandledType: never = type;
371
+ throw new Error(`Unsupported chunk type: ${unhandledType}`);
372
+ }
373
+ }
374
+ controller.enqueue(message);
375
+ },
376
+ flush(controller) {
377
+ if (message.status?.type === "running") {
378
+ // TODO this test isn't sound
379
+ const requiresAction = message.parts?.at(-1)?.type === "tool-call";
380
+ message = handleMessageFinish(message, {
381
+ type: "message-finish",
382
+ path: [],
383
+ finishReason: requiresAction ? "tool-calls" : "unknown",
384
+ usage: {
385
+ promptTokens: 0,
386
+ completionTokens: 0,
387
+ },
388
+ });
389
+ controller.enqueue(message);
390
+ }
391
+ },
392
+ });
393
+ }
394
+ }
@@ -0,0 +1,17 @@
1
+ export {
2
+ createAssistantStream,
3
+ createAssistantStreamResponse,
4
+ } from "./modules/assistant-stream";
5
+ export { AssistantMessageAccumulator } from "./accumulators/assistant-message-accumulator";
6
+ export { AssistantStream } from "./AssistantStream";
7
+ export type { AssistantStreamController } from "./modules/assistant-stream";
8
+ export type { AssistantStreamChunk } from "./AssistantStreamChunk";
9
+ export {
10
+ DataStreamDecoder,
11
+ DataStreamEncoder,
12
+ } from "./serialization/data-stream/DataStream";
13
+ export { PlainTextDecoder, PlainTextEncoder } from "./serialization/PlainText";
14
+ export { AssistantMessageStream } from "./accumulators/AssistantMessageStream";
15
+ export type { AssistantMessage } from "./utils/types";
16
+
17
+ export * from "./tool";