@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.
- package/dist/case-PdS00lUs.js +4 -0
- package/dist/experimental/index.d.ts +55 -0
- package/dist/experimental/index.js +215 -0
- package/dist/index.d.ts +2 -6
- package/dist/index.js +4 -15
- package/package.json +8 -4
|
@@ -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,
|
|
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:
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
37
|
-
"@xsai/tool": "~0.3.0-beta.
|
|
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",
|