@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 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!
@@ -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, toStreamResponse, toHttpStream, } from './stream-to-response.js';
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, toServerSentEventsStream, toStreamResponse } from "./stream-to-response.js";
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
- * const readableStream = toServerSentEventsStream(stream);
39
- * // Use with Response, or any API that accepts ReadableStream
51
+ * return toServerSentEventsResponse(stream, { abortController });
40
52
  * ```
41
53
  */
42
- export declare function toServerSentEventsStream(stream: AsyncIterable<StreamChunk>, abortController?: AbortController): ReadableStream<Uint8Array>;
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
- * Create a streaming HTTP response from a StreamChunk async iterable
69
- * Includes proper headers for Server-Sent Events
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
- * @deprecated Use `toServerSentEventsStream` instead. This function will be removed in a future version.
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
- * @param abortController - Optional AbortController to abort when client disconnects
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
- * export async function POST(request: Request) {
81
- * const { messages } = await request.json();
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 toStreamResponse(stream: AsyncIterable<StreamChunk>, init?: ResponseInit & {
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 toStreamResponse(stream, init) {
96
- if (typeof console !== "undefined") {
97
- console.warn(
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
- toServerSentEventsStream,
116
- toStreamResponse
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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"}
@@ -49,7 +49,7 @@ export interface JSONSchema {
49
49
  maxProperties?: number;
50
50
  title?: string;
51
51
  examples?: Array<unknown>;
52
- [key: string]: unknown;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Core TanStack AI library - Open source AI SDK",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
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
- toStreamResponse,
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
- * Create a streaming HTTP response from a StreamChunk async iterable
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
- * @deprecated Use `toServerSentEventsStream` instead. This function will be removed in a future version.
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
- * @param abortController - Optional AbortController to abort when client disconnects
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
- * export async function POST(request: Request) {
201
- * const { messages } = await request.json();
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 toStreamResponse(
245
+ export function toHttpResponse(
214
246
  stream: AsyncIterable<StreamChunk>,
215
247
  init?: ResponseInit & { abortController?: AbortController },
216
248
  ): Response {
217
- if (typeof console !== 'undefined') {
218
- console.warn(
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]: unknown // Allow additional properties for extensibility
63
+ [key: string]: any // Allow additional properties for extensibility
64
64
  }
65
65
 
66
66
  /**