@tanstack/ai 0.2.0 → 0.2.1
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/README.md +0 -25
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +3 -2
- package/dist/esm/stream-to-response.d.ts +28 -21
- package/dist/esm/stream-to-response.js +24 -17
- package/dist/esm/stream-to-response.js.map +1 -1
- package/dist/esm/types.d.ts +1 -1
- package/package.json +1 -1
- package/src/index.ts +2 -1
- package/src/stream-to-response.ts +59 -44
- package/src/types.ts +1 -1
package/README.md
CHANGED
|
@@ -72,31 +72,6 @@ for await (const chunk of result) {
|
|
|
72
72
|
|
|
73
73
|
Available adapters: `openaiText`, `openaiEmbed`, `openaiSummarize`, `anthropicText`, `geminiText`, `ollamaText`, and more.
|
|
74
74
|
|
|
75
|
-
## Bonus: TanStack Start Integration
|
|
76
|
-
|
|
77
|
-
TanStack AI works with **any** framework (Next.js, Express, Remix, etc.).
|
|
78
|
-
|
|
79
|
-
**With TanStack Start**, you get a bonus: share implementations between AI tools and server functions with `createServerFnTool`:
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
import { createServerFnTool } from '@tanstack/ai-react'
|
|
83
|
-
|
|
84
|
-
// Define once, get AI tool AND server function (TanStack Start only)
|
|
85
|
-
const getProducts = createServerFnTool({
|
|
86
|
-
name: 'getProducts',
|
|
87
|
-
inputSchema: z.object({ query: z.string() }),
|
|
88
|
-
execute: async ({ query }) => db.products.search(query),
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
// Use in AI chat
|
|
92
|
-
chat({ tools: [getProducts.server] })
|
|
93
|
-
|
|
94
|
-
// Call directly from components (no API endpoint needed!)
|
|
95
|
-
const products = await getProducts.serverFn({ query: 'laptop' })
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
No duplicate logic, full type safety, automatic validation. The `serverFn` feature requires TanStack Start. See [docs](https://tanstack.com/ai) for details.
|
|
99
|
-
|
|
100
75
|
## Get Involved
|
|
101
76
|
|
|
102
77
|
- We welcome issues and pull requests!
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export { createTranscriptionOptions } from './activities/generateTranscription/i
|
|
|
8
8
|
export type { AIAdapter, ImageAdapter, AnyImageAdapter, TextAdapter, AnyTextAdapter, AnySummarizeAdapter, SummarizeAdapter, AnyTTSAdapter, TTSAdapter, AnyTranscriptionAdapter, TranscriptionAdapter, AnyVideoAdapter, VideoAdapter, } from './activities/index.js';
|
|
9
9
|
export { toolDefinition, type ToolDefinition, type ToolDefinitionInstance, type ToolDefinitionConfig, type ServerTool, type ClientTool, type AnyClientTool, type InferToolName, type InferToolInput, type InferToolOutput, } from './activities/chat/tools/tool-definition.js';
|
|
10
10
|
export { convertSchemaToJsonSchema } from './activities/chat/tools/schema-converter.js';
|
|
11
|
-
export { streamToText, toServerSentEventsStream,
|
|
11
|
+
export { streamToText, toServerSentEventsStream, toServerSentEventsResponse, toHttpStream, toHttpResponse, } from './stream-to-response.js';
|
|
12
12
|
export { ToolCallManager } from './activities/chat/tools/tool-calls.js';
|
|
13
13
|
export { maxIterations, untilFinishReason, combineStrategies, } from './activities/chat/agent-loop-strategies.js';
|
|
14
14
|
export * from './types.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { createSpeechOptions, generateSpeech } from "./activities/generateSpeech
|
|
|
6
6
|
import { createTranscriptionOptions, generateTranscription } from "./activities/generateTranscription/index.js";
|
|
7
7
|
import { toolDefinition } from "./activities/chat/tools/tool-definition.js";
|
|
8
8
|
import { convertSchemaToJsonSchema } from "./activities/chat/tools/schema-converter.js";
|
|
9
|
-
import { streamToText, toHttpStream,
|
|
9
|
+
import { streamToText, toHttpResponse, toHttpStream, toServerSentEventsResponse, toServerSentEventsStream } from "./stream-to-response.js";
|
|
10
10
|
import { ToolCallManager } from "./activities/chat/tools/tool-calls.js";
|
|
11
11
|
import { combineStrategies, maxIterations, untilFinishReason } from "./activities/chat/agent-loop-strategies.js";
|
|
12
12
|
import { aiEventClient } from "./event-client.js";
|
|
@@ -49,9 +49,10 @@ export {
|
|
|
49
49
|
parsePartialJSON,
|
|
50
50
|
streamToText,
|
|
51
51
|
summarize,
|
|
52
|
+
toHttpResponse,
|
|
52
53
|
toHttpStream,
|
|
54
|
+
toServerSentEventsResponse,
|
|
53
55
|
toServerSentEventsStream,
|
|
54
|
-
toStreamResponse,
|
|
55
56
|
toolDefinition,
|
|
56
57
|
uiMessageToModelMessages,
|
|
57
58
|
untilFinishReason
|
|
@@ -31,15 +31,29 @@ export declare function streamToText(stream: AsyncIterable<StreamChunk>): Promis
|
|
|
31
31
|
* @param stream - AsyncIterable of StreamChunks from chat()
|
|
32
32
|
* @param abortController - Optional AbortController to abort when stream is cancelled
|
|
33
33
|
* @returns ReadableStream in Server-Sent Events format
|
|
34
|
+
*/
|
|
35
|
+
export declare function toServerSentEventsStream(stream: AsyncIterable<StreamChunk>, abortController?: AbortController): ReadableStream<Uint8Array>;
|
|
36
|
+
/**
|
|
37
|
+
* Convert a StreamChunk async iterable to a Response in Server-Sent Events format
|
|
38
|
+
*
|
|
39
|
+
* This creates a Response that emits chunks in SSE format:
|
|
40
|
+
* - Each chunk is prefixed with "data: "
|
|
41
|
+
* - Each chunk is followed by "\n\n"
|
|
42
|
+
* - Stream ends with "data: [DONE]\n\n"
|
|
43
|
+
*
|
|
44
|
+
* @param stream - AsyncIterable of StreamChunks from chat()
|
|
45
|
+
* @param init - Optional Response initialization options (including `abortController`)
|
|
46
|
+
* @returns Response in Server-Sent Events format
|
|
34
47
|
*
|
|
35
48
|
* @example
|
|
36
49
|
* ```typescript
|
|
37
50
|
* const stream = chat({ adapter: openaiText(), model: "gpt-4o", messages: [...] });
|
|
38
|
-
*
|
|
39
|
-
* // Use with Response, or any API that accepts ReadableStream
|
|
51
|
+
* return toServerSentEventsResponse(stream, { abortController });
|
|
40
52
|
* ```
|
|
41
53
|
*/
|
|
42
|
-
export declare function
|
|
54
|
+
export declare function toServerSentEventsResponse(stream: AsyncIterable<StreamChunk>, init?: ResponseInit & {
|
|
55
|
+
abortController?: AbortController;
|
|
56
|
+
}): Response;
|
|
43
57
|
/**
|
|
44
58
|
* Convert a StreamChunk async iterable to a ReadableStream in HTTP stream format (newline-delimited JSON)
|
|
45
59
|
*
|
|
@@ -65,31 +79,24 @@ export declare function toServerSentEventsStream(stream: AsyncIterable<StreamChu
|
|
|
65
79
|
*/
|
|
66
80
|
export declare function toHttpStream(stream: AsyncIterable<StreamChunk>, abortController?: AbortController): ReadableStream<Uint8Array>;
|
|
67
81
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
82
|
+
* Convert a StreamChunk async iterable to a Response in HTTP stream format (newline-delimited JSON)
|
|
83
|
+
*
|
|
84
|
+
* This creates a Response that emits chunks in HTTP stream format:
|
|
85
|
+
* - Each chunk is JSON.stringify'd and followed by "\n"
|
|
86
|
+
* - No SSE formatting (no "data: " prefix)
|
|
70
87
|
*
|
|
71
|
-
*
|
|
88
|
+
* This format is compatible with `fetchHttpStream` connection adapter.
|
|
72
89
|
*
|
|
73
90
|
* @param stream - AsyncIterable of StreamChunks from chat()
|
|
74
|
-
* @param init - Optional Response initialization options
|
|
75
|
-
* @
|
|
76
|
-
* @returns Response object with SSE headers and streaming body
|
|
91
|
+
* @param init - Optional Response initialization options (including `abortController`)
|
|
92
|
+
* @returns Response in HTTP stream format (newline-delimited JSON)
|
|
77
93
|
*
|
|
78
94
|
* @example
|
|
79
95
|
* ```typescript
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* const abortController = new AbortController();
|
|
83
|
-
* const stream = chat({
|
|
84
|
-
* adapter: openaiText(),
|
|
85
|
-
* model: "gpt-4o",
|
|
86
|
-
* messages,
|
|
87
|
-
* options: { abortSignal: abortController.signal }
|
|
88
|
-
* });
|
|
89
|
-
* return toStreamResponse(stream, undefined, abortController);
|
|
90
|
-
* }
|
|
96
|
+
* const stream = chat({ adapter: openaiText(), model: "gpt-4o", messages: [...] });
|
|
97
|
+
* return toHttpResponse(stream, { abortController });
|
|
91
98
|
* ```
|
|
92
99
|
*/
|
|
93
|
-
export declare function
|
|
100
|
+
export declare function toHttpResponse(stream: AsyncIterable<StreamChunk>, init?: ResponseInit & {
|
|
94
101
|
abortController?: AbortController;
|
|
95
102
|
}): Response;
|
|
@@ -52,6 +52,24 @@ function toServerSentEventsStream(stream, abortController) {
|
|
|
52
52
|
}
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
|
+
function toServerSentEventsResponse(stream, init) {
|
|
56
|
+
const { headers, abortController, ...responseInit } = init ?? {};
|
|
57
|
+
const mergedHeaders = new Headers({
|
|
58
|
+
"Content-Type": "text/event-stream",
|
|
59
|
+
"Cache-Control": "no-cache",
|
|
60
|
+
Connection: "keep-alive"
|
|
61
|
+
});
|
|
62
|
+
if (headers) {
|
|
63
|
+
const userHeaders = new Headers(headers);
|
|
64
|
+
userHeaders.forEach((value, key) => {
|
|
65
|
+
mergedHeaders.set(key, value);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return new Response(toServerSentEventsStream(stream, abortController), {
|
|
69
|
+
...responseInit,
|
|
70
|
+
headers: mergedHeaders
|
|
71
|
+
});
|
|
72
|
+
}
|
|
55
73
|
function toHttpStream(stream, abortController) {
|
|
56
74
|
const encoder = new TextEncoder();
|
|
57
75
|
return new ReadableStream({
|
|
@@ -92,27 +110,16 @@ function toHttpStream(stream, abortController) {
|
|
|
92
110
|
}
|
|
93
111
|
});
|
|
94
112
|
}
|
|
95
|
-
function
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
"`toStreamResponse` is deprecated. Use `toServerSentEventsStream` instead. Example:\n const readableStream = toServerSentEventsStream(stream, abortController);\n return new Response(readableStream, {\n headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive' }\n });"
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
const { headers, abortController, ...responseInit } = init ?? {};
|
|
102
|
-
return new Response(toServerSentEventsStream(stream, abortController), {
|
|
103
|
-
...responseInit,
|
|
104
|
-
headers: {
|
|
105
|
-
"Content-Type": "text/event-stream",
|
|
106
|
-
"Cache-Control": "no-cache",
|
|
107
|
-
Connection: "keep-alive",
|
|
108
|
-
...headers || {}
|
|
109
|
-
}
|
|
113
|
+
function toHttpResponse(stream, init) {
|
|
114
|
+
return new Response(toHttpStream(stream, init?.abortController), {
|
|
115
|
+
...init
|
|
110
116
|
});
|
|
111
117
|
}
|
|
112
118
|
export {
|
|
113
119
|
streamToText,
|
|
120
|
+
toHttpResponse,
|
|
114
121
|
toHttpStream,
|
|
115
|
-
|
|
116
|
-
|
|
122
|
+
toServerSentEventsResponse,
|
|
123
|
+
toServerSentEventsStream
|
|
117
124
|
};
|
|
118
125
|
//# sourceMappingURL=stream-to-response.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream-to-response.js","sources":["../../src/stream-to-response.ts"],"sourcesContent":["import type { StreamChunk } from './types'\n\n/**\n * Collect all text content from a StreamChunk async iterable and return as a string.\n *\n * This function consumes the entire stream, accumulating content from 'content' type chunks,\n * and returns the final concatenated text.\n *\n * @param stream - AsyncIterable of StreamChunks from chat()\n * @returns Promise<string> - The accumulated text content\n *\n * @example\n * ```typescript\n * const stream = chat({\n * adapter: openaiText(),\n * model: 'gpt-4o',\n * messages: [{ role: 'user', content: 'Hello!' }]\n * });\n * const text = await streamToText(stream);\n * console.log(text); // \"Hello! How can I help you today?\"\n * ```\n */\nexport async function streamToText(\n stream: AsyncIterable<StreamChunk>,\n): Promise<string> {\n let accumulatedContent = ''\n\n for await (const chunk of stream) {\n if (chunk.type === 'content' && chunk.delta) {\n accumulatedContent += chunk.delta\n }\n }\n\n return accumulatedContent\n}\n\n/**\n * Convert a StreamChunk async iterable to a ReadableStream in Server-Sent Events format\n *\n * This creates a ReadableStream that emits chunks in SSE format:\n * - Each chunk is prefixed with \"data: \"\n * - Each chunk is followed by \"\\n\\n\"\n * - Stream ends with \"data: [DONE]\\n\\n\"\n *\n * @param stream - AsyncIterable of StreamChunks from chat()\n * @param abortController - Optional AbortController to abort when stream is cancelled\n * @returns ReadableStream in Server-Sent Events format\n *\n * @example\n * ```typescript\n * const stream = chat({ adapter: openaiText(), model: \"gpt-4o\", messages: [...] });\n * const readableStream = toServerSentEventsStream(stream);\n * // Use with Response, or any API that accepts ReadableStream\n * ```\n */\nexport function toServerSentEventsStream(\n stream: AsyncIterable<StreamChunk>,\n abortController?: AbortController,\n): ReadableStream<Uint8Array> {\n const encoder = new TextEncoder()\n\n return new ReadableStream({\n async start(controller) {\n try {\n for await (const chunk of stream) {\n // Check if stream was cancelled/aborted\n if (abortController?.signal.aborted) {\n break\n }\n\n // Send each chunk as Server-Sent Events format\n controller.enqueue(\n encoder.encode(`data: ${JSON.stringify(chunk)}\\n\\n`),\n )\n }\n\n // Send completion marker\n controller.enqueue(encoder.encode('data: [DONE]\\n\\n'))\n controller.close()\n } catch (error: any) {\n // Don't send error if aborted\n if (abortController?.signal.aborted) {\n controller.close()\n return\n }\n\n // Send error chunk\n controller.enqueue(\n encoder.encode(\n `data: ${JSON.stringify({\n type: 'error',\n error: {\n message: error.message || 'Unknown error occurred',\n code: error.code,\n },\n })}\\n\\n`,\n ),\n )\n controller.close()\n }\n },\n cancel() {\n // When the ReadableStream is cancelled (e.g., client disconnects),\n // abort the underlying stream\n if (abortController) {\n abortController.abort()\n }\n },\n })\n}\n\n/**\n * Convert a StreamChunk async iterable to a ReadableStream in HTTP stream format (newline-delimited JSON)\n *\n * This creates a ReadableStream that emits chunks as newline-delimited JSON:\n * - Each chunk is JSON.stringify'd and followed by \"\\n\"\n * - No SSE formatting (no \"data: \" prefix)\n *\n * This format is compatible with `fetchHttpStream` connection adapter.\n *\n * @param stream - AsyncIterable of StreamChunks from chat()\n * @param abortController - Optional AbortController to abort when stream is cancelled\n * @returns ReadableStream in HTTP stream format (newline-delimited JSON)\n *\n * @example\n * ```typescript\n * const stream = chat({ adapter: openaiText(), model: \"gpt-4o\", messages: [...] });\n * const readableStream = toHttpStream(stream);\n * // Use with Response for HTTP streaming (not SSE)\n * return new Response(readableStream, {\n * headers: { 'Content-Type': 'application/x-ndjson' }\n * });\n * ```\n */\nexport function toHttpStream(\n stream: AsyncIterable<StreamChunk>,\n abortController?: AbortController,\n): ReadableStream<Uint8Array> {\n const encoder = new TextEncoder()\n\n return new ReadableStream({\n async start(controller) {\n try {\n for await (const chunk of stream) {\n // Check if stream was cancelled/aborted\n if (abortController?.signal.aborted) {\n break\n }\n\n // Send each chunk as newline-delimited JSON\n controller.enqueue(encoder.encode(`${JSON.stringify(chunk)}\\n`))\n }\n\n controller.close()\n } catch (error: any) {\n // Don't send error if aborted\n if (abortController?.signal.aborted) {\n controller.close()\n return\n }\n\n // Send error chunk\n controller.enqueue(\n encoder.encode(\n `${JSON.stringify({\n type: 'error',\n error: {\n message: error.message || 'Unknown error occurred',\n code: error.code,\n },\n })}\\n`,\n ),\n )\n controller.close()\n }\n },\n cancel() {\n // When the ReadableStream is cancelled (e.g., client disconnects),\n // abort the underlying stream\n if (abortController) {\n abortController.abort()\n }\n },\n })\n}\n\n/**\n * Create a streaming HTTP response from a StreamChunk async iterable\n * Includes proper headers for Server-Sent Events\n *\n * @deprecated Use `toServerSentEventsStream` instead. This function will be removed in a future version.\n *\n * @param stream - AsyncIterable of StreamChunks from chat()\n * @param init - Optional Response initialization options\n * @param abortController - Optional AbortController to abort when client disconnects\n * @returns Response object with SSE headers and streaming body\n *\n * @example\n * ```typescript\n * export async function POST(request: Request) {\n * const { messages } = await request.json();\n * const abortController = new AbortController();\n * const stream = chat({\n * adapter: openaiText(),\n * model: \"gpt-4o\",\n * messages,\n * options: { abortSignal: abortController.signal }\n * });\n * return toStreamResponse(stream, undefined, abortController);\n * }\n * ```\n */\nexport function toStreamResponse(\n stream: AsyncIterable<StreamChunk>,\n init?: ResponseInit & { abortController?: AbortController },\n): Response {\n if (typeof console !== 'undefined') {\n console.warn(\n '`toStreamResponse` is deprecated. Use `toServerSentEventsStream` instead. Example:\\n' +\n ' const readableStream = toServerSentEventsStream(stream, abortController);\\n' +\n ' return new Response(readableStream, {\\n' +\n \" headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive' }\\n\" +\n ' });',\n )\n }\n\n const { headers, abortController, ...responseInit } = init ?? {}\n return new Response(toServerSentEventsStream(stream, abortController), {\n ...responseInit,\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n ...(headers || {}),\n },\n })\n}\n"],"names":[],"mappings":"AAsBA,eAAsB,aACpB,QACiB;AACjB,MAAI,qBAAqB;AAEzB,mBAAiB,SAAS,QAAQ;AAChC,QAAI,MAAM,SAAS,aAAa,MAAM,OAAO;AAC3C,4BAAsB,MAAM;AAAA,IAC9B;AAAA,EACF;AAEA,SAAO;AACT;AAqBO,SAAS,yBACd,QACA,iBAC4B;AAC5B,QAAM,UAAU,IAAI,YAAA;AAEpB,SAAO,IAAI,eAAe;AAAA,IACxB,MAAM,MAAM,YAAY;AACtB,UAAI;AACF,yBAAiB,SAAS,QAAQ;AAEhC,cAAI,iBAAiB,OAAO,SAAS;AACnC;AAAA,UACF;AAGA,qBAAW;AAAA,YACT,QAAQ,OAAO,SAAS,KAAK,UAAU,KAAK,CAAC;AAAA;AAAA,CAAM;AAAA,UAAA;AAAA,QAEvD;AAGA,mBAAW,QAAQ,QAAQ,OAAO,kBAAkB,CAAC;AACrD,mBAAW,MAAA;AAAA,MACb,SAAS,OAAY;AAEnB,YAAI,iBAAiB,OAAO,SAAS;AACnC,qBAAW,MAAA;AACX;AAAA,QACF;AAGA,mBAAW;AAAA,UACT,QAAQ;AAAA,YACN,SAAS,KAAK,UAAU;AAAA,cACtB,MAAM;AAAA,cACN,OAAO;AAAA,gBACL,SAAS,MAAM,WAAW;AAAA,gBAC1B,MAAM,MAAM;AAAA,cAAA;AAAA,YACd,CACD,CAAC;AAAA;AAAA;AAAA,UAAA;AAAA,QACJ;AAEF,mBAAW,MAAA;AAAA,MACb;AAAA,IACF;AAAA,IACA,SAAS;AAGP,UAAI,iBAAiB;AACnB,wBAAgB,MAAA;AAAA,MAClB;AAAA,IACF;AAAA,EAAA,CACD;AACH;AAyBO,SAAS,aACd,QACA,iBAC4B;AAC5B,QAAM,UAAU,IAAI,YAAA;AAEpB,SAAO,IAAI,eAAe;AAAA,IACxB,MAAM,MAAM,YAAY;AACtB,UAAI;AACF,yBAAiB,SAAS,QAAQ;AAEhC,cAAI,iBAAiB,OAAO,SAAS;AACnC;AAAA,UACF;AAGA,qBAAW,QAAQ,QAAQ,OAAO,GAAG,KAAK,UAAU,KAAK,CAAC;AAAA,CAAI,CAAC;AAAA,QACjE;AAEA,mBAAW,MAAA;AAAA,MACb,SAAS,OAAY;AAEnB,YAAI,iBAAiB,OAAO,SAAS;AACnC,qBAAW,MAAA;AACX;AAAA,QACF;AAGA,mBAAW;AAAA,UACT,QAAQ;AAAA,YACN,GAAG,KAAK,UAAU;AAAA,cAChB,MAAM;AAAA,cACN,OAAO;AAAA,gBACL,SAAS,MAAM,WAAW;AAAA,gBAC1B,MAAM,MAAM;AAAA,cAAA;AAAA,YACd,CACD,CAAC;AAAA;AAAA,UAAA;AAAA,QACJ;AAEF,mBAAW,MAAA;AAAA,MACb;AAAA,IACF;AAAA,IACA,SAAS;AAGP,UAAI,iBAAiB;AACnB,wBAAgB,MAAA;AAAA,MAClB;AAAA,IACF;AAAA,EAAA,CACD;AACH;AA4BO,SAAS,iBACd,QACA,MACU;AACV,MAAI,OAAO,YAAY,aAAa;AAClC,YAAQ;AAAA,MACN;AAAA,IAAA;AAAA,EAMJ;AAEA,QAAM,EAAE,SAAS,iBAAiB,GAAG,aAAA,IAAiB,QAAQ,CAAA;AAC9D,SAAO,IAAI,SAAS,yBAAyB,QAAQ,eAAe,GAAG;AAAA,IACrE,GAAG;AAAA,IACH,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,MACjB,YAAY;AAAA,MACZ,GAAI,WAAW,CAAA;AAAA,IAAC;AAAA,EAClB,CACD;AACH;"}
|
|
1
|
+
{"version":3,"file":"stream-to-response.js","sources":["../../src/stream-to-response.ts"],"sourcesContent":["import type { StreamChunk } from './types'\n\n/**\n * Collect all text content from a StreamChunk async iterable and return as a string.\n *\n * This function consumes the entire stream, accumulating content from 'content' type chunks,\n * and returns the final concatenated text.\n *\n * @param stream - AsyncIterable of StreamChunks from chat()\n * @returns Promise<string> - The accumulated text content\n *\n * @example\n * ```typescript\n * const stream = chat({\n * adapter: openaiText(),\n * model: 'gpt-4o',\n * messages: [{ role: 'user', content: 'Hello!' }]\n * });\n * const text = await streamToText(stream);\n * console.log(text); // \"Hello! How can I help you today?\"\n * ```\n */\nexport async function streamToText(\n stream: AsyncIterable<StreamChunk>,\n): Promise<string> {\n let accumulatedContent = ''\n\n for await (const chunk of stream) {\n if (chunk.type === 'content' && chunk.delta) {\n accumulatedContent += chunk.delta\n }\n }\n\n return accumulatedContent\n}\n\n/**\n * Convert a StreamChunk async iterable to a ReadableStream in Server-Sent Events format\n *\n * This creates a ReadableStream that emits chunks in SSE format:\n * - Each chunk is prefixed with \"data: \"\n * - Each chunk is followed by \"\\n\\n\"\n * - Stream ends with \"data: [DONE]\\n\\n\"\n *\n * @param stream - AsyncIterable of StreamChunks from chat()\n * @param abortController - Optional AbortController to abort when stream is cancelled\n * @returns ReadableStream in Server-Sent Events format\n */\nexport function toServerSentEventsStream(\n stream: AsyncIterable<StreamChunk>,\n abortController?: AbortController,\n): ReadableStream<Uint8Array> {\n const encoder = new TextEncoder()\n\n return new ReadableStream({\n async start(controller) {\n try {\n for await (const chunk of stream) {\n // Check if stream was cancelled/aborted\n if (abortController?.signal.aborted) {\n break\n }\n\n // Send each chunk as Server-Sent Events format\n controller.enqueue(\n encoder.encode(`data: ${JSON.stringify(chunk)}\\n\\n`),\n )\n }\n\n // Send completion marker\n controller.enqueue(encoder.encode('data: [DONE]\\n\\n'))\n controller.close()\n } catch (error: any) {\n // Don't send error if aborted\n if (abortController?.signal.aborted) {\n controller.close()\n return\n }\n\n // Send error chunk\n controller.enqueue(\n encoder.encode(\n `data: ${JSON.stringify({\n type: 'error',\n error: {\n message: error.message || 'Unknown error occurred',\n code: error.code,\n },\n })}\\n\\n`,\n ),\n )\n controller.close()\n }\n },\n cancel() {\n // When the ReadableStream is cancelled (e.g., client disconnects),\n // abort the underlying stream\n if (abortController) {\n abortController.abort()\n }\n },\n })\n}\n\n/**\n * Convert a StreamChunk async iterable to a Response in Server-Sent Events format\n *\n * This creates a Response that emits chunks in SSE format:\n * - Each chunk is prefixed with \"data: \"\n * - Each chunk is followed by \"\\n\\n\"\n * - Stream ends with \"data: [DONE]\\n\\n\"\n *\n * @param stream - AsyncIterable of StreamChunks from chat()\n * @param init - Optional Response initialization options (including `abortController`)\n * @returns Response in Server-Sent Events format\n *\n * @example\n * ```typescript\n * const stream = chat({ adapter: openaiText(), model: \"gpt-4o\", messages: [...] });\n * return toServerSentEventsResponse(stream, { abortController });\n * ```\n */\nexport function toServerSentEventsResponse(\n stream: AsyncIterable<StreamChunk>,\n init?: ResponseInit & { abortController?: AbortController },\n): Response {\n const { headers, abortController, ...responseInit } = init ?? {}\n\n // Start with default SSE headers\n const mergedHeaders = new Headers({\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n })\n\n // Override with user headers if provided, handling all HeadersInit forms:\n // Headers instance, string[][], or plain object\n if (headers) {\n const userHeaders = new Headers(headers)\n userHeaders.forEach((value, key) => {\n mergedHeaders.set(key, value)\n })\n }\n\n return new Response(toServerSentEventsStream(stream, abortController), {\n ...responseInit,\n headers: mergedHeaders,\n })\n}\n\n/**\n * Convert a StreamChunk async iterable to a ReadableStream in HTTP stream format (newline-delimited JSON)\n *\n * This creates a ReadableStream that emits chunks as newline-delimited JSON:\n * - Each chunk is JSON.stringify'd and followed by \"\\n\"\n * - No SSE formatting (no \"data: \" prefix)\n *\n * This format is compatible with `fetchHttpStream` connection adapter.\n *\n * @param stream - AsyncIterable of StreamChunks from chat()\n * @param abortController - Optional AbortController to abort when stream is cancelled\n * @returns ReadableStream in HTTP stream format (newline-delimited JSON)\n *\n * @example\n * ```typescript\n * const stream = chat({ adapter: openaiText(), model: \"gpt-4o\", messages: [...] });\n * const readableStream = toHttpStream(stream);\n * // Use with Response for HTTP streaming (not SSE)\n * return new Response(readableStream, {\n * headers: { 'Content-Type': 'application/x-ndjson' }\n * });\n * ```\n */\nexport function toHttpStream(\n stream: AsyncIterable<StreamChunk>,\n abortController?: AbortController,\n): ReadableStream<Uint8Array> {\n const encoder = new TextEncoder()\n\n return new ReadableStream({\n async start(controller) {\n try {\n for await (const chunk of stream) {\n // Check if stream was cancelled/aborted\n if (abortController?.signal.aborted) {\n break\n }\n\n // Send each chunk as newline-delimited JSON\n controller.enqueue(encoder.encode(`${JSON.stringify(chunk)}\\n`))\n }\n\n controller.close()\n } catch (error: any) {\n // Don't send error if aborted\n if (abortController?.signal.aborted) {\n controller.close()\n return\n }\n\n // Send error chunk\n controller.enqueue(\n encoder.encode(\n `${JSON.stringify({\n type: 'error',\n error: {\n message: error.message || 'Unknown error occurred',\n code: error.code,\n },\n })}\\n`,\n ),\n )\n controller.close()\n }\n },\n cancel() {\n // When the ReadableStream is cancelled (e.g., client disconnects),\n // abort the underlying stream\n if (abortController) {\n abortController.abort()\n }\n },\n })\n}\n\n/**\n * Convert a StreamChunk async iterable to a Response in HTTP stream format (newline-delimited JSON)\n *\n * This creates a Response that emits chunks in HTTP stream format:\n * - Each chunk is JSON.stringify'd and followed by \"\\n\"\n * - No SSE formatting (no \"data: \" prefix)\n *\n * This format is compatible with `fetchHttpStream` connection adapter.\n *\n * @param stream - AsyncIterable of StreamChunks from chat()\n * @param init - Optional Response initialization options (including `abortController`)\n * @returns Response in HTTP stream format (newline-delimited JSON)\n *\n * @example\n * ```typescript\n * const stream = chat({ adapter: openaiText(), model: \"gpt-4o\", messages: [...] });\n * return toHttpResponse(stream, { abortController });\n * ```\n */\nexport function toHttpResponse(\n stream: AsyncIterable<StreamChunk>,\n init?: ResponseInit & { abortController?: AbortController },\n): Response {\n return new Response(toHttpStream(stream, init?.abortController), {\n ...init,\n })\n}\n"],"names":[],"mappings":"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"}
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -49,7 +49,7 @@ export interface JSONSchema {
|
|
|
49
49
|
maxProperties?: number;
|
|
50
50
|
title?: string;
|
|
51
51
|
examples?: Array<unknown>;
|
|
52
|
-
[key: string]:
|
|
52
|
+
[key: string]: any;
|
|
53
53
|
}
|
|
54
54
|
/**
|
|
55
55
|
* Union type for schema input - can be any Standard JSON Schema compliant schema or a plain JSONSchema object.
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -55,8 +55,9 @@ export { convertSchemaToJsonSchema } from './activities/chat/tools/schema-conver
|
|
|
55
55
|
export {
|
|
56
56
|
streamToText,
|
|
57
57
|
toServerSentEventsStream,
|
|
58
|
-
|
|
58
|
+
toServerSentEventsResponse,
|
|
59
59
|
toHttpStream,
|
|
60
|
+
toHttpResponse,
|
|
60
61
|
} from './stream-to-response'
|
|
61
62
|
|
|
62
63
|
// Tool call management
|
|
@@ -45,13 +45,6 @@ export async function streamToText(
|
|
|
45
45
|
* @param stream - AsyncIterable of StreamChunks from chat()
|
|
46
46
|
* @param abortController - Optional AbortController to abort when stream is cancelled
|
|
47
47
|
* @returns ReadableStream in Server-Sent Events format
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* ```typescript
|
|
51
|
-
* const stream = chat({ adapter: openaiText(), model: "gpt-4o", messages: [...] });
|
|
52
|
-
* const readableStream = toServerSentEventsStream(stream);
|
|
53
|
-
* // Use with Response, or any API that accepts ReadableStream
|
|
54
|
-
* ```
|
|
55
48
|
*/
|
|
56
49
|
export function toServerSentEventsStream(
|
|
57
50
|
stream: AsyncIterable<StreamChunk>,
|
|
@@ -109,6 +102,52 @@ export function toServerSentEventsStream(
|
|
|
109
102
|
})
|
|
110
103
|
}
|
|
111
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Convert a StreamChunk async iterable to a Response in Server-Sent Events format
|
|
107
|
+
*
|
|
108
|
+
* This creates a Response that emits chunks in SSE format:
|
|
109
|
+
* - Each chunk is prefixed with "data: "
|
|
110
|
+
* - Each chunk is followed by "\n\n"
|
|
111
|
+
* - Stream ends with "data: [DONE]\n\n"
|
|
112
|
+
*
|
|
113
|
+
* @param stream - AsyncIterable of StreamChunks from chat()
|
|
114
|
+
* @param init - Optional Response initialization options (including `abortController`)
|
|
115
|
+
* @returns Response in Server-Sent Events format
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```typescript
|
|
119
|
+
* const stream = chat({ adapter: openaiText(), model: "gpt-4o", messages: [...] });
|
|
120
|
+
* return toServerSentEventsResponse(stream, { abortController });
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
export function toServerSentEventsResponse(
|
|
124
|
+
stream: AsyncIterable<StreamChunk>,
|
|
125
|
+
init?: ResponseInit & { abortController?: AbortController },
|
|
126
|
+
): Response {
|
|
127
|
+
const { headers, abortController, ...responseInit } = init ?? {}
|
|
128
|
+
|
|
129
|
+
// Start with default SSE headers
|
|
130
|
+
const mergedHeaders = new Headers({
|
|
131
|
+
'Content-Type': 'text/event-stream',
|
|
132
|
+
'Cache-Control': 'no-cache',
|
|
133
|
+
Connection: 'keep-alive',
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
// Override with user headers if provided, handling all HeadersInit forms:
|
|
137
|
+
// Headers instance, string[][], or plain object
|
|
138
|
+
if (headers) {
|
|
139
|
+
const userHeaders = new Headers(headers)
|
|
140
|
+
userHeaders.forEach((value, key) => {
|
|
141
|
+
mergedHeaders.set(key, value)
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return new Response(toServerSentEventsStream(stream, abortController), {
|
|
146
|
+
...responseInit,
|
|
147
|
+
headers: mergedHeaders,
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
112
151
|
/**
|
|
113
152
|
* Convert a StreamChunk async iterable to a ReadableStream in HTTP stream format (newline-delimited JSON)
|
|
114
153
|
*
|
|
@@ -185,53 +224,29 @@ export function toHttpStream(
|
|
|
185
224
|
}
|
|
186
225
|
|
|
187
226
|
/**
|
|
188
|
-
*
|
|
189
|
-
* Includes proper headers for Server-Sent Events
|
|
227
|
+
* Convert a StreamChunk async iterable to a Response in HTTP stream format (newline-delimited JSON)
|
|
190
228
|
*
|
|
191
|
-
*
|
|
229
|
+
* This creates a Response that emits chunks in HTTP stream format:
|
|
230
|
+
* - Each chunk is JSON.stringify'd and followed by "\n"
|
|
231
|
+
* - No SSE formatting (no "data: " prefix)
|
|
232
|
+
*
|
|
233
|
+
* This format is compatible with `fetchHttpStream` connection adapter.
|
|
192
234
|
*
|
|
193
235
|
* @param stream - AsyncIterable of StreamChunks from chat()
|
|
194
|
-
* @param init - Optional Response initialization options
|
|
195
|
-
* @
|
|
196
|
-
* @returns Response object with SSE headers and streaming body
|
|
236
|
+
* @param init - Optional Response initialization options (including `abortController`)
|
|
237
|
+
* @returns Response in HTTP stream format (newline-delimited JSON)
|
|
197
238
|
*
|
|
198
239
|
* @example
|
|
199
240
|
* ```typescript
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
* const abortController = new AbortController();
|
|
203
|
-
* const stream = chat({
|
|
204
|
-
* adapter: openaiText(),
|
|
205
|
-
* model: "gpt-4o",
|
|
206
|
-
* messages,
|
|
207
|
-
* options: { abortSignal: abortController.signal }
|
|
208
|
-
* });
|
|
209
|
-
* return toStreamResponse(stream, undefined, abortController);
|
|
210
|
-
* }
|
|
241
|
+
* const stream = chat({ adapter: openaiText(), model: "gpt-4o", messages: [...] });
|
|
242
|
+
* return toHttpResponse(stream, { abortController });
|
|
211
243
|
* ```
|
|
212
244
|
*/
|
|
213
|
-
export function
|
|
245
|
+
export function toHttpResponse(
|
|
214
246
|
stream: AsyncIterable<StreamChunk>,
|
|
215
247
|
init?: ResponseInit & { abortController?: AbortController },
|
|
216
248
|
): Response {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
'`toStreamResponse` is deprecated. Use `toServerSentEventsStream` instead. Example:\n' +
|
|
220
|
-
' const readableStream = toServerSentEventsStream(stream, abortController);\n' +
|
|
221
|
-
' return new Response(readableStream, {\n' +
|
|
222
|
-
" headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive' }\n" +
|
|
223
|
-
' });',
|
|
224
|
-
)
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const { headers, abortController, ...responseInit } = init ?? {}
|
|
228
|
-
return new Response(toServerSentEventsStream(stream, abortController), {
|
|
229
|
-
...responseInit,
|
|
230
|
-
headers: {
|
|
231
|
-
'Content-Type': 'text/event-stream',
|
|
232
|
-
'Cache-Control': 'no-cache',
|
|
233
|
-
Connection: 'keep-alive',
|
|
234
|
-
...(headers || {}),
|
|
235
|
-
},
|
|
249
|
+
return new Response(toHttpStream(stream, init?.abortController), {
|
|
250
|
+
...init,
|
|
236
251
|
})
|
|
237
252
|
}
|
package/src/types.ts
CHANGED
|
@@ -60,7 +60,7 @@ export interface JSONSchema {
|
|
|
60
60
|
maxProperties?: number
|
|
61
61
|
title?: string
|
|
62
62
|
examples?: Array<unknown>
|
|
63
|
-
[key: string]:
|
|
63
|
+
[key: string]: any // Allow additional properties for extensibility
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/**
|