@xsai/stream-text 0.3.0-beta.6 → 0.3.0-beta.7

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.
@@ -0,0 +1,4 @@
1
+ const strCamelToSnake = (str) => str.replace(/[A-Z]/g, (s) => `_${s.toLowerCase()}`);
2
+ const objCamelToSnake = (obj) => Object.fromEntries(Object.entries(obj).map(([k, v]) => [strCamelToSnake(k), v]));
3
+
4
+ export { objCamelToSnake as o };
@@ -0,0 +1,55 @@
1
+ import { CompletionToolCall, CompletionToolResult, FinishReason, Usage, ChatOptions, CompletionStep, Message } from '@xsai/shared-chat';
2
+
3
+ type StreamTextEvent = (CompletionToolCall & {
4
+ type: 'tool-call';
5
+ }) | (CompletionToolResult & {
6
+ type: 'tool-result';
7
+ }) | {
8
+ argsTextDelta: string;
9
+ toolCallId: string;
10
+ toolName: string;
11
+ type: 'tool-call-delta';
12
+ } | {
13
+ error: unknown;
14
+ type: 'error';
15
+ } | {
16
+ finishReason: FinishReason;
17
+ type: 'finish';
18
+ usage?: Usage;
19
+ } | {
20
+ text: string;
21
+ type: 'text-delta';
22
+ } | {
23
+ toolCallId: string;
24
+ toolName: string;
25
+ type: 'tool-call-streaming-start';
26
+ };
27
+
28
+ interface StreamTextOptions extends ChatOptions {
29
+ /** @default 1 */
30
+ maxSteps?: number;
31
+ onEvent?: (event: StreamTextEvent) => Promise<unknown> | unknown;
32
+ onFinish?: (step?: CompletionStep) => Promise<unknown> | unknown;
33
+ onStepFinish?: (step: CompletionStep) => Promise<unknown> | unknown;
34
+ /**
35
+ * If you want to disable stream, use `@xsai/generate-{text,object}`.
36
+ */
37
+ stream?: never;
38
+ streamOptions?: {
39
+ /**
40
+ * Return usage.
41
+ * @default `undefined`
42
+ */
43
+ includeUsage?: boolean;
44
+ };
45
+ }
46
+ interface StreamTextResult {
47
+ fullStream: ReadableStream<StreamTextEvent>;
48
+ messages: Promise<Message[]>;
49
+ steps: Promise<CompletionStep[]>;
50
+ textStream: ReadableStream<string>;
51
+ usage: Promise<undefined | Usage>;
52
+ }
53
+ declare const streamText: (options: StreamTextOptions) => Promise<StreamTextResult>;
54
+
55
+ export { type StreamTextOptions, type StreamTextResult, streamText };
@@ -0,0 +1,215 @@
1
+ import { chat, executeTool, determineStepType } from '@xsai/shared-chat';
2
+ import { o as objCamelToSnake } from '../case-PdS00lUs.js';
3
+
4
+ const trampoline = async (fn) => {
5
+ let result = await fn();
6
+ while (result instanceof Function)
7
+ result = await result();
8
+ return result;
9
+ };
10
+
11
+ const parseChunk = (text) => {
12
+ if (!text || !text.startsWith("data:"))
13
+ return [void 0, false];
14
+ const content = text.slice("data:".length);
15
+ const data = content.startsWith(" ") ? content.slice(1) : content;
16
+ if (data === "[DONE]") {
17
+ return [void 0, true];
18
+ }
19
+ if (data.startsWith("{") && data.includes('"error":')) {
20
+ throw new Error(`Error from server: ${data}`);
21
+ }
22
+ const chunk = JSON.parse(data);
23
+ return [chunk, false];
24
+ };
25
+ const transformChunk = () => {
26
+ const decoder = new TextDecoder();
27
+ let buffer = "";
28
+ return new TransformStream({
29
+ transform: async (chunk, controller) => {
30
+ const text = decoder.decode(chunk, { stream: true });
31
+ buffer += text;
32
+ const lines = buffer.split("\n");
33
+ buffer = lines.pop() ?? "";
34
+ for (const line of lines) {
35
+ try {
36
+ const [chunk2, isEnd] = parseChunk(line);
37
+ if (isEnd)
38
+ break;
39
+ if (chunk2) {
40
+ controller.enqueue(chunk2);
41
+ }
42
+ } catch (error) {
43
+ controller.error(error);
44
+ }
45
+ }
46
+ }
47
+ });
48
+ };
49
+
50
+ class DelayedPromise {
51
+ get promise() {
52
+ if (this._promise == null) {
53
+ this._promise = new Promise((resolve, reject) => {
54
+ if (this.status.type === "resolved") {
55
+ resolve(this.status.value);
56
+ } else if (this.status.type === "rejected") {
57
+ reject(this.status.error);
58
+ }
59
+ this._resolve = resolve;
60
+ this._reject = reject;
61
+ });
62
+ }
63
+ return this._promise;
64
+ }
65
+ _promise;
66
+ _reject;
67
+ _resolve;
68
+ status = { type: "pending" };
69
+ reject(error) {
70
+ this.status = { error, type: "rejected" };
71
+ if (this._promise) {
72
+ this._reject?.(error);
73
+ }
74
+ }
75
+ resolve(value) {
76
+ this.status = { type: "resolved", value };
77
+ if (this._promise) {
78
+ this._resolve?.(value);
79
+ }
80
+ }
81
+ }
82
+
83
+ const streamText = async (options) => {
84
+ const steps = [];
85
+ const messages = structuredClone(options.messages);
86
+ const maxSteps = options.maxSteps ?? 1;
87
+ let usage;
88
+ const resultSteps = new DelayedPromise();
89
+ const resultMessages = new DelayedPromise();
90
+ const resultUsage = new DelayedPromise();
91
+ let eventCtrl;
92
+ let textCtrl;
93
+ const eventStream = new ReadableStream({ start: (controller) => eventCtrl = controller });
94
+ const textStream = new ReadableStream({ start: (controller) => textCtrl = controller });
95
+ const pushEvent = (stepEvent) => {
96
+ eventCtrl?.enqueue(stepEvent);
97
+ void options.onEvent?.(stepEvent);
98
+ };
99
+ const pushStep = (step) => {
100
+ steps.push(step);
101
+ void options.onStepFinish?.(step);
102
+ };
103
+ const startStream = async () => {
104
+ const pushUsage = (u) => {
105
+ usage = u;
106
+ };
107
+ let text = "";
108
+ const pushText = (content) => {
109
+ textCtrl?.enqueue(content);
110
+ text += content;
111
+ };
112
+ const tool_calls = [];
113
+ const toolCalls = [];
114
+ const toolResults = [];
115
+ let finishReason = "other";
116
+ await chat({
117
+ ...options,
118
+ maxSteps: void 0,
119
+ messages,
120
+ stream: true,
121
+ streamOptions: options.streamOptions != null ? objCamelToSnake(options.streamOptions) : void 0
122
+ }).then(
123
+ async (res) => res.body.pipeThrough(transformChunk()).pipeTo(new WritableStream({
124
+ abort: (reason) => {
125
+ eventCtrl?.error(reason);
126
+ textCtrl?.error(reason);
127
+ },
128
+ close: () => {
129
+ },
130
+ write: (chunk) => {
131
+ if (chunk.usage)
132
+ pushUsage(chunk.usage);
133
+ if (chunk.choices == null || chunk.choices.length === 0)
134
+ return;
135
+ const choice = chunk.choices[0];
136
+ if (choice.finish_reason != null)
137
+ finishReason = choice.finish_reason;
138
+ if (choice.delta.tool_calls?.length === 0 || choice.delta.tool_calls == null) {
139
+ if (choice.delta.content != null) {
140
+ pushEvent({ text: choice.delta.content, type: "text-delta" });
141
+ pushText(choice.delta.content);
142
+ } else if (choice.delta.refusal != null) {
143
+ pushEvent({ error: choice.delta.refusal, type: "error" });
144
+ } else if (choice.finish_reason != null) {
145
+ pushEvent({ finishReason: choice.finish_reason, type: "finish", usage });
146
+ }
147
+ } else {
148
+ for (const toolCall of choice.delta.tool_calls) {
149
+ const { index } = toolCall;
150
+ if (!tool_calls.at(index)) {
151
+ tool_calls[index] = toolCall;
152
+ pushEvent({ toolCallId: toolCall.id, toolName: toolCall.function.name, type: "tool-call-streaming-start" });
153
+ } else {
154
+ tool_calls[index].function.arguments += toolCall.function.arguments;
155
+ pushEvent({ argsTextDelta: toolCall.function.arguments, toolCallId: toolCall.id, toolName: toolCall.function.name, type: "tool-call-delta" });
156
+ }
157
+ }
158
+ }
159
+ }
160
+ }))
161
+ );
162
+ if (tool_calls.length !== 0) {
163
+ for (const toolCall of tool_calls) {
164
+ const { completionToolCall, completionToolResult, message } = await executeTool({
165
+ abortSignal: options.abortSignal,
166
+ messages,
167
+ toolCall,
168
+ tools: options.tools
169
+ });
170
+ pushEvent({ ...completionToolCall, type: "tool-call" });
171
+ pushEvent({ ...completionToolResult, type: "tool-result" });
172
+ toolCalls.push(completionToolCall);
173
+ toolResults.push(completionToolResult);
174
+ messages.push(message);
175
+ }
176
+ } else {
177
+ messages.push({ content: text, role: "assistant" });
178
+ }
179
+ pushStep({
180
+ finishReason,
181
+ stepType: determineStepType({ finishReason, maxSteps, stepsLength: steps.length, toolCallsLength: toolCalls.length }),
182
+ text,
183
+ toolCalls,
184
+ toolResults,
185
+ usage
186
+ });
187
+ if (toolCalls.length !== 0 && steps.length < maxSteps)
188
+ return async () => startStream();
189
+ };
190
+ try {
191
+ await trampoline(async () => startStream());
192
+ } catch (err) {
193
+ eventCtrl?.error(err);
194
+ textCtrl?.error(err);
195
+ resultSteps.reject(err);
196
+ resultMessages.reject(err);
197
+ resultUsage.reject(err);
198
+ } finally {
199
+ eventCtrl?.close();
200
+ textCtrl?.close();
201
+ resultSteps.resolve(steps);
202
+ resultMessages.resolve(messages);
203
+ resultUsage.resolve(usage);
204
+ void options.onFinish?.(steps.at(-1));
205
+ }
206
+ return {
207
+ fullStream: eventStream,
208
+ messages: resultMessages.promise,
209
+ steps: resultSteps.promise,
210
+ textStream,
211
+ usage: resultUsage.promise
212
+ };
213
+ };
214
+
215
+ export { streamText };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ToolMessagePart, FinishReason, Usage, ToolCall, ChatOptions, AssistantMessage, Message, StepType, CompletionToolCall, CompletionToolResult, Tool } from '@xsai/shared-chat';
1
+ import { ToolMessagePart, FinishReason, Usage, ToolCall, ChatOptions, AssistantMessage, Message, CompletionStepType, CompletionToolCall, CompletionToolResult } from '@xsai/shared-chat';
2
2
 
3
3
  type StreamTextEvent = {
4
4
  error: unknown;
@@ -87,10 +87,6 @@ interface StreamTextOptions extends ChatOptions {
87
87
  */
88
88
  includeUsage?: boolean;
89
89
  };
90
- /**
91
- * List of tools to be used in the stream.
92
- */
93
- tools?: Tool[];
94
90
  }
95
91
  interface StreamTextResult {
96
92
  chunkStream: ReadableStream<StreamTextChunkResult>;
@@ -101,7 +97,7 @@ interface StreamTextStep {
101
97
  choices: StreamTextChoice[];
102
98
  finishReason: FinishReason;
103
99
  messages: Message[];
104
- stepType: StepType;
100
+ stepType: CompletionStepType;
105
101
  toolCalls: CompletionToolCall[];
106
102
  toolResults: CompletionToolResult[];
107
103
  usage?: Usage;
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { chat, executeTool, determineStepType } from '@xsai/shared-chat';
2
+ import { o as objCamelToSnake } from './case-PdS00lUs.js';
2
3
 
3
4
  class XSAIError extends Error {
4
5
  response;
@@ -9,9 +10,6 @@ class XSAIError extends Error {
9
10
  }
10
11
  }
11
12
 
12
- const strCamelToSnake = (str) => str.replace(/[A-Z]/g, (s) => `_${s.toLowerCase()}`);
13
- const objCamelToSnake = (obj) => Object.fromEntries(Object.entries(obj).map(([k, v]) => [strCamelToSnake(k), v]));
14
-
15
13
  const CHUNK_HEADER_PREFIX = "data:";
16
14
  const parseChunk = (text) => {
17
15
  if (!text || !text.startsWith(CHUNK_HEADER_PREFIX))
@@ -226,7 +224,7 @@ const streamText = async (options) => {
226
224
  type: "tool-call"
227
225
  });
228
226
  try {
229
- const { parsedArgs, result, toolName } = await executeTool({
227
+ const { completionToolResult, message, parsedArgs, result } = await executeTool({
230
228
  abortSignal: options2.abortSignal,
231
229
  messages: options2.messages,
232
230
  toolCall,
@@ -234,17 +232,8 @@ const streamText = async (options) => {
234
232
  });
235
233
  toolCall.function.parsedArguments = parsedArgs;
236
234
  state.toolCallResults[toolCall.id] = result;
237
- step.messages.push({
238
- content: result,
239
- role: "tool",
240
- tool_call_id: toolCall.id
241
- });
242
- step.toolResults.push({
243
- args: parsedArgs,
244
- result,
245
- toolCallId: toolCall.id,
246
- toolName
247
- });
235
+ step.messages.push(message);
236
+ step.toolResults.push(completionToolResult);
248
237
  options2.onEvent?.({
249
238
  id: toolCall.id,
250
239
  result,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xsai/stream-text",
3
3
  "type": "module",
4
- "version": "0.3.0-beta.6",
4
+ "version": "0.3.0-beta.7",
5
5
  "description": "extra-small AI SDK.",
6
6
  "author": "Moeru AI",
7
7
  "license": "MIT",
@@ -23,18 +23,22 @@
23
23
  "types": "./dist/index.d.ts",
24
24
  "default": "./dist/index.js"
25
25
  },
26
+ "./experimental": {
27
+ "types": "./dist/experimental/index.d.ts",
28
+ "default": "./dist/experimental/index.js"
29
+ },
26
30
  "./package.json": "./package.json"
27
31
  },
28
32
  "files": [
29
33
  "dist"
30
34
  ],
31
35
  "dependencies": {
32
- "@xsai/shared-chat": "~0.3.0-beta.6"
36
+ "@xsai/shared-chat": "~0.3.0-beta.7"
33
37
  },
34
38
  "devDependencies": {
35
39
  "valibot": "^1.0.0",
36
- "@xsai/shared": "~0.3.0-beta.6",
37
- "@xsai/tool": "~0.3.0-beta.6"
40
+ "@xsai/shared": "~0.3.0-beta.7",
41
+ "@xsai/tool": "~0.3.0-beta.7"
38
42
  },
39
43
  "scripts": {
40
44
  "build": "pkgroll",