@tanstack/ai-client 0.11.8 → 0.13.0
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/esm/chat-client.d.ts +17 -2
- package/dist/esm/chat-client.js +223 -37
- package/dist/esm/chat-client.js.map +1 -1
- package/dist/esm/connection-adapters.d.ts +20 -2
- package/dist/esm/connection-adapters.js +154 -48
- package/dist/esm/connection-adapters.js.map +1 -1
- package/dist/esm/devtools-noop.d.ts +60 -0
- package/dist/esm/devtools-noop.js +135 -0
- package/dist/esm/devtools-noop.js.map +1 -0
- package/dist/esm/devtools.d.ts +276 -0
- package/dist/esm/devtools.js +1178 -0
- package/dist/esm/devtools.js.map +1 -0
- package/dist/esm/events.d.ts +50 -7
- package/dist/esm/events.js +65 -20
- package/dist/esm/events.js.map +1 -1
- package/dist/esm/generation-client.d.ts +14 -0
- package/dist/esm/generation-client.js +135 -5
- package/dist/esm/generation-client.js.map +1 -1
- package/dist/esm/generation-types.d.ts +15 -1
- package/dist/esm/generation-types.js.map +1 -1
- package/dist/esm/index.d.ts +3 -2
- package/dist/esm/index.js +4 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types.d.ts +78 -10
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/video-generation-client.d.ts +15 -0
- package/dist/esm/video-generation-client.js +147 -8
- package/dist/esm/video-generation-client.js.map +1 -1
- package/package.json +8 -4
- package/src/chat-client.ts +285 -39
- package/src/connection-adapters.ts +226 -61
- package/src/devtools-noop.ts +205 -0
- package/src/devtools.ts +1866 -0
- package/src/events.ts +142 -11
- package/src/generation-client.ts +176 -9
- package/src/generation-types.ts +23 -4
- package/src/index.ts +15 -1
- package/src/types.ts +98 -12
- package/src/video-generation-client.ts +186 -11
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
import { GENERATION_EVENTS } from "./generation-types.js";
|
|
2
|
+
import { createNoOpGenerationDevtoolsBridge } from "./devtools-noop.js";
|
|
2
3
|
import { parseSSEResponse } from "./sse-parser.js";
|
|
3
4
|
class GenerationClient {
|
|
4
5
|
connection;
|
|
5
6
|
fetcher;
|
|
7
|
+
uniqueId;
|
|
8
|
+
devtoolsMetadata;
|
|
9
|
+
devtoolsBridge;
|
|
10
|
+
threadId;
|
|
6
11
|
body;
|
|
7
12
|
result = null;
|
|
13
|
+
input = null;
|
|
14
|
+
progress = null;
|
|
8
15
|
isLoading = false;
|
|
9
16
|
error = void 0;
|
|
10
17
|
status = "idle";
|
|
11
18
|
abortController = null;
|
|
12
19
|
callbacksRef;
|
|
20
|
+
devtoolsMounted = false;
|
|
13
21
|
constructor(options) {
|
|
22
|
+
this.uniqueId = options.id ?? this.generateUniqueId("generation");
|
|
23
|
+
this.threadId = this.uniqueId;
|
|
14
24
|
this.connection = options.connection;
|
|
15
25
|
this.fetcher = options.fetcher;
|
|
16
26
|
this.body = options.body ?? {};
|
|
@@ -24,6 +34,32 @@ class GenerationClient {
|
|
|
24
34
|
onErrorChange: options.onErrorChange,
|
|
25
35
|
onStatusChange: options.onStatusChange
|
|
26
36
|
};
|
|
37
|
+
this.devtoolsMetadata = this.createDevtoolsMetadata(options.devtools);
|
|
38
|
+
this.devtoolsBridge = (options.devtoolsBridgeFactory ?? createNoOpGenerationDevtoolsBridge)(this.buildDevtoolsBridgeOptions());
|
|
39
|
+
}
|
|
40
|
+
buildDevtoolsBridgeOptions() {
|
|
41
|
+
return {
|
|
42
|
+
hookId: this.uniqueId,
|
|
43
|
+
clientId: this.uniqueId,
|
|
44
|
+
threadId: this.threadId,
|
|
45
|
+
metadata: this.devtoolsMetadata,
|
|
46
|
+
getCoreState: () => ({
|
|
47
|
+
input: this.input,
|
|
48
|
+
result: this.result,
|
|
49
|
+
progress: this.progress,
|
|
50
|
+
status: this.status,
|
|
51
|
+
isLoading: this.isLoading,
|
|
52
|
+
...this.error ? { error: this.error.message } : {}
|
|
53
|
+
})
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
mountDevtools() {
|
|
57
|
+
if (this.devtoolsMounted) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
this.devtoolsMounted = true;
|
|
61
|
+
this.devtoolsBridge.emitRegistered();
|
|
62
|
+
this.devtoolsBridge.emitSnapshot();
|
|
27
63
|
}
|
|
28
64
|
/**
|
|
29
65
|
* Trigger a generation request.
|
|
@@ -31,7 +67,11 @@ class GenerationClient {
|
|
|
31
67
|
* while already generating will be a no-op.
|
|
32
68
|
*/
|
|
33
69
|
async generate(input) {
|
|
70
|
+
this.mountDevtools();
|
|
34
71
|
if (this.isLoading) return;
|
|
72
|
+
this.input = input;
|
|
73
|
+
this.progress = null;
|
|
74
|
+
const runId = this.devtoolsBridge.beginRun(input);
|
|
35
75
|
this.setIsLoading(true);
|
|
36
76
|
this.setStatus("generating");
|
|
37
77
|
this.setError(void 0);
|
|
@@ -43,25 +83,45 @@ class GenerationClient {
|
|
|
43
83
|
const result = await this.fetcher(input, { signal });
|
|
44
84
|
if (signal.aborted) return;
|
|
45
85
|
if (result instanceof Response) {
|
|
46
|
-
await this.processStream(parseSSEResponse(result, signal));
|
|
86
|
+
await this.processStream(parseSSEResponse(result, signal), runId);
|
|
47
87
|
} else {
|
|
88
|
+
this.devtoolsBridge.ensureRunStarted(runId);
|
|
48
89
|
this.setResult(result);
|
|
49
90
|
this.setStatus("success");
|
|
50
91
|
}
|
|
51
92
|
} else if (this.connection) {
|
|
52
93
|
const mergedData = { ...this.body, ...input };
|
|
53
|
-
const stream = this.connection.connect(
|
|
54
|
-
|
|
94
|
+
const stream = this.connection.connect(
|
|
95
|
+
[],
|
|
96
|
+
mergedData,
|
|
97
|
+
signal,
|
|
98
|
+
this.createRunContext(runId)
|
|
99
|
+
);
|
|
100
|
+
await this.processStream(stream, runId);
|
|
55
101
|
} else {
|
|
56
102
|
throw new Error(
|
|
57
103
|
"GenerationClient requires either a connection or fetcher option"
|
|
58
104
|
);
|
|
59
105
|
}
|
|
106
|
+
if (!signal.aborted && this.status === "success") {
|
|
107
|
+
this.progress = completeProgressValue(this.progress);
|
|
108
|
+
this.devtoolsBridge.finishRun(
|
|
109
|
+
this.devtoolsBridge.getActiveRunId() ?? runId,
|
|
110
|
+
"run:completed",
|
|
111
|
+
"completed"
|
|
112
|
+
);
|
|
113
|
+
}
|
|
60
114
|
} catch (err) {
|
|
61
115
|
if (signal.aborted) return;
|
|
62
116
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
63
117
|
this.setError(error);
|
|
64
118
|
this.setStatus("error");
|
|
119
|
+
this.devtoolsBridge.finishRun(
|
|
120
|
+
this.devtoolsBridge.getActiveRunId() ?? runId,
|
|
121
|
+
"run:errored",
|
|
122
|
+
"errored",
|
|
123
|
+
error.message
|
|
124
|
+
);
|
|
65
125
|
this.callbacksRef.onError?.(error);
|
|
66
126
|
} finally {
|
|
67
127
|
this.abortController = null;
|
|
@@ -71,25 +131,38 @@ class GenerationClient {
|
|
|
71
131
|
/**
|
|
72
132
|
* Process a stream of AG-UI events from the streaming connection adapter.
|
|
73
133
|
*/
|
|
74
|
-
async processStream(source) {
|
|
134
|
+
async processStream(source, fallbackRunId) {
|
|
135
|
+
let streamRunId;
|
|
75
136
|
for await (const chunk of source) {
|
|
76
137
|
if (this.abortController?.signal.aborted) break;
|
|
77
138
|
this.callbacksRef.onChunk?.(chunk);
|
|
139
|
+
const chunkRunId = "runId" in chunk && typeof chunk.runId === "string" ? chunk.runId : void 0;
|
|
78
140
|
switch (chunk.type) {
|
|
141
|
+
case "RUN_STARTED": {
|
|
142
|
+
streamRunId = chunk.runId;
|
|
143
|
+
this.devtoolsBridge.ensureRunStarted(chunk.runId);
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
79
146
|
case "CUSTOM": {
|
|
147
|
+
this.devtoolsBridge.ensureRunStarted(streamRunId ?? fallbackRunId);
|
|
80
148
|
if (chunk.name === GENERATION_EVENTS.RESULT) {
|
|
81
149
|
this.setResult(chunk.value);
|
|
82
150
|
} else if (chunk.name === GENERATION_EVENTS.PROGRESS) {
|
|
83
151
|
const { progress, message } = chunk.value;
|
|
84
|
-
this.
|
|
152
|
+
this.setProgress(progress, message);
|
|
85
153
|
}
|
|
86
154
|
break;
|
|
87
155
|
}
|
|
88
156
|
case "RUN_FINISHED": {
|
|
157
|
+
streamRunId = chunk.runId;
|
|
158
|
+
this.devtoolsBridge.ensureRunStarted(chunk.runId);
|
|
89
159
|
this.setStatus("success");
|
|
90
160
|
break;
|
|
91
161
|
}
|
|
92
162
|
case "RUN_ERROR": {
|
|
163
|
+
this.devtoolsBridge.ensureRunStarted(
|
|
164
|
+
chunkRunId ?? streamRunId ?? fallbackRunId
|
|
165
|
+
);
|
|
93
166
|
const msg = chunk.message || chunk.error?.message || "An error occurred";
|
|
94
167
|
throw new Error(msg);
|
|
95
168
|
}
|
|
@@ -100,6 +173,7 @@ class GenerationClient {
|
|
|
100
173
|
* Abort any in-flight generation request.
|
|
101
174
|
*/
|
|
102
175
|
stop() {
|
|
176
|
+
const runId = this.devtoolsBridge.getActiveRunId();
|
|
103
177
|
if (this.abortController) {
|
|
104
178
|
this.abortController.abort();
|
|
105
179
|
this.abortController = null;
|
|
@@ -107,6 +181,9 @@ class GenerationClient {
|
|
|
107
181
|
this.setIsLoading(false);
|
|
108
182
|
if (this.status === "generating") {
|
|
109
183
|
this.setStatus("idle");
|
|
184
|
+
if (runId) {
|
|
185
|
+
this.devtoolsBridge.finishRun(runId, "run:cancelled", "cancelled");
|
|
186
|
+
}
|
|
110
187
|
}
|
|
111
188
|
}
|
|
112
189
|
/**
|
|
@@ -115,8 +192,12 @@ class GenerationClient {
|
|
|
115
192
|
reset() {
|
|
116
193
|
this.stop();
|
|
117
194
|
this.setResult(null);
|
|
195
|
+
this.input = null;
|
|
196
|
+
this.progress = null;
|
|
197
|
+
this.devtoolsBridge.resetRuns();
|
|
118
198
|
this.setError(void 0);
|
|
119
199
|
this.setStatus("idle");
|
|
200
|
+
this.devtoolsBridge.emitState();
|
|
120
201
|
}
|
|
121
202
|
/**
|
|
122
203
|
* Update options without recreating the client.
|
|
@@ -138,6 +219,11 @@ class GenerationClient {
|
|
|
138
219
|
this.callbacksRef.onChunk = options.onChunk;
|
|
139
220
|
}
|
|
140
221
|
}
|
|
222
|
+
dispose() {
|
|
223
|
+
this.stop();
|
|
224
|
+
this.devtoolsBridge.dispose();
|
|
225
|
+
this.devtoolsMounted = false;
|
|
226
|
+
}
|
|
141
227
|
// ===========================
|
|
142
228
|
// Getters
|
|
143
229
|
// ===========================
|
|
@@ -160,34 +246,78 @@ class GenerationClient {
|
|
|
160
246
|
if (rawResult === null) {
|
|
161
247
|
this.result = null;
|
|
162
248
|
this.callbacksRef.onResultChange?.(null);
|
|
249
|
+
this.devtoolsBridge.recordResultChange();
|
|
163
250
|
return;
|
|
164
251
|
}
|
|
165
252
|
if (this.callbacksRef.onResult) {
|
|
166
253
|
const transformed = this.callbacksRef.onResult(rawResult);
|
|
167
254
|
if (transformed === null) {
|
|
255
|
+
this.devtoolsBridge.emitState();
|
|
168
256
|
return;
|
|
169
257
|
}
|
|
170
258
|
if (transformed !== void 0) {
|
|
171
259
|
this.result = transformed;
|
|
172
260
|
this.callbacksRef.onResultChange?.(this.result);
|
|
261
|
+
this.devtoolsBridge.recordResultChange();
|
|
173
262
|
return;
|
|
174
263
|
}
|
|
175
264
|
}
|
|
176
265
|
this.result = rawResult;
|
|
177
266
|
this.callbacksRef.onResultChange?.(this.result);
|
|
267
|
+
this.devtoolsBridge.recordResultChange();
|
|
178
268
|
}
|
|
179
269
|
setIsLoading(isLoading) {
|
|
180
270
|
this.isLoading = isLoading;
|
|
181
271
|
this.callbacksRef.onLoadingChange?.(isLoading);
|
|
272
|
+
this.devtoolsBridge.recordLoadingChange();
|
|
182
273
|
}
|
|
183
274
|
setError(error) {
|
|
184
275
|
this.error = error;
|
|
185
276
|
this.callbacksRef.onErrorChange?.(error);
|
|
277
|
+
this.devtoolsBridge.recordErrorChange(error);
|
|
186
278
|
}
|
|
187
279
|
setStatus(status) {
|
|
188
280
|
this.status = status;
|
|
189
281
|
this.callbacksRef.onStatusChange?.(status);
|
|
282
|
+
this.devtoolsBridge.recordStatusChange(status);
|
|
283
|
+
}
|
|
284
|
+
setProgress(value, message) {
|
|
285
|
+
this.progress = {
|
|
286
|
+
value,
|
|
287
|
+
...message ? { message } : {}
|
|
288
|
+
};
|
|
289
|
+
if (message === void 0) {
|
|
290
|
+
this.callbacksRef.onProgress?.(value);
|
|
291
|
+
} else {
|
|
292
|
+
this.callbacksRef.onProgress?.(value, message);
|
|
293
|
+
}
|
|
294
|
+
this.devtoolsBridge.recordProgressChange();
|
|
190
295
|
}
|
|
296
|
+
createDevtoolsMetadata(metadata) {
|
|
297
|
+
return {
|
|
298
|
+
hookName: metadata?.hookName ?? "useGeneration",
|
|
299
|
+
...metadata?.framework ? { framework: metadata.framework } : {},
|
|
300
|
+
...metadata?.outputKind ? { outputKind: metadata.outputKind } : {},
|
|
301
|
+
...metadata?.name ? { name: metadata.name } : {}
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
generateUniqueId(prefix) {
|
|
305
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
306
|
+
}
|
|
307
|
+
createRunContext(runId) {
|
|
308
|
+
return {
|
|
309
|
+
threadId: this.threadId,
|
|
310
|
+
runId
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function completeProgressValue(progress) {
|
|
315
|
+
if (!progress) return null;
|
|
316
|
+
const message = progress.message;
|
|
317
|
+
return {
|
|
318
|
+
value: 100,
|
|
319
|
+
...message ? { message } : {}
|
|
320
|
+
};
|
|
191
321
|
}
|
|
192
322
|
export {
|
|
193
323
|
GenerationClient
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generation-client.js","sources":["../../src/generation-client.ts"],"sourcesContent":["import { GENERATION_EVENTS } from './generation-types'\nimport { parseSSEResponse } from './sse-parser'\nimport type { StreamChunk } from '@tanstack/ai'\nimport type { ConnectConnectionAdapter } from './connection-adapters'\nimport type {\n GenerationClientOptions,\n GenerationClientState,\n GenerationFetcher,\n} from './generation-types'\n\n/**\n * Callbacks stored in a ref so hooks can update them without recreating the client.\n */\n// All optional fields explicitly allow `| undefined` so callers can spread\n// option bags (where each callback may be `undefined`) into the callbacks\n// ref under `exactOptionalPropertyTypes`.\ninterface GenerationCallbacks<TResult, TOutput> {\n onResult?: ((result: TResult) => TOutput | null | void) | undefined\n onError?: ((error: Error) => void) | undefined\n onProgress?: ((progress: number, message?: string) => void) | undefined\n onChunk?: ((chunk: StreamChunk) => void) | undefined\n onResultChange?: ((result: TOutput | null) => void) | undefined\n onLoadingChange?: ((isLoading: boolean) => void) | undefined\n onErrorChange?: ((error: Error | undefined) => void) | undefined\n onStatusChange?: ((status: GenerationClientState) => void) | undefined\n}\n\n/**\n * A lightweight, generic client for one-shot generation tasks\n * (image, speech, transcription, summarize).\n *\n * Supports two transport modes:\n * - **ConnectConnectionAdapter** — Streaming transport (SSE, HTTP stream, custom).\n * Server wraps results in StreamChunk events with CUSTOM event names.\n * - **Fetcher** — Direct async function call. No streaming protocol needed.\n *\n * @template TInput - The input type for the generation request\n * @template TResult - The result type returned by the generation\n *\n * @example\n * ```typescript\n * // With streaming connection adapter\n * const client = new GenerationClient<ImageGenerateInput, ImageGenerationResult>({\n * connection: fetchServerSentEvents('/api/generate/image'),\n * onResultChange: setResult,\n * onLoadingChange: setIsLoading,\n * })\n *\n * // With fetcher (direct)\n * const client = new GenerationClient<ImageGenerateInput, ImageGenerationResult>({\n * fetcher: async (input) => {\n * const res = await fetch('/api/generate/image', {\n * method: 'POST',\n * body: JSON.stringify(input),\n * })\n * return res.json()\n * },\n * })\n *\n * await client.generate({ prompt: 'A sunset over mountains' })\n * ```\n */\nexport class GenerationClient<\n TInput extends Record<string, any>,\n TResult,\n TOutput = TResult,\n> {\n private readonly connection: ConnectConnectionAdapter | undefined\n private readonly fetcher: GenerationFetcher<TInput, TResult> | undefined\n private body: Record<string, any>\n private result: TOutput | null = null\n private isLoading = false\n private error: Error | undefined = undefined\n private status: GenerationClientState = 'idle'\n private abortController: AbortController | null = null\n private readonly callbacksRef: GenerationCallbacks<TResult, TOutput>\n\n constructor(\n options: GenerationClientOptions<TInput, TResult, TOutput> &\n (\n | { connection: ConnectConnectionAdapter; fetcher?: never }\n | {\n fetcher: GenerationFetcher<TInput, TResult>\n connection?: never\n }\n ),\n ) {\n this.connection = options.connection\n this.fetcher = options.fetcher\n this.body = options.body ?? {}\n\n this.callbacksRef = {\n onResult: options.onResult,\n onError: options.onError,\n onProgress: options.onProgress,\n onChunk: options.onChunk,\n onResultChange: options.onResultChange,\n onLoadingChange: options.onLoadingChange,\n onErrorChange: options.onErrorChange,\n onStatusChange: options.onStatusChange,\n }\n }\n\n /**\n * Trigger a generation request.\n * Only one generation can be in-flight at a time; calling generate()\n * while already generating will be a no-op.\n */\n async generate(input: TInput): Promise<void> {\n if (this.isLoading) return\n\n this.setIsLoading(true)\n this.setStatus('generating')\n this.setError(undefined)\n\n const abortController = new AbortController()\n this.abortController = abortController\n const { signal } = abortController\n\n try {\n if (this.fetcher) {\n // Direct fetch path\n const result = await this.fetcher(input, { signal })\n if (signal.aborted) return\n if (result instanceof Response) {\n // Server function returned SSE Response — parse stream\n await this.processStream(parseSSEResponse(result, signal))\n } else {\n this.setResult(result)\n this.setStatus('success')\n }\n } else if (this.connection) {\n // Streaming adapter path\n const mergedData = { ...this.body, ...input }\n const stream = this.connection.connect([], mergedData, signal)\n await this.processStream(stream)\n } else {\n throw new Error(\n 'GenerationClient requires either a connection or fetcher option',\n )\n }\n } catch (err: any) {\n if (signal.aborted) return\n const error = err instanceof Error ? err : new Error(String(err))\n this.setError(error)\n this.setStatus('error')\n this.callbacksRef.onError?.(error)\n } finally {\n this.abortController = null\n this.setIsLoading(false)\n }\n }\n\n /**\n * Process a stream of AG-UI events from the streaming connection adapter.\n */\n private async processStream(\n source: AsyncIterable<StreamChunk>,\n ): Promise<void> {\n for await (const chunk of source) {\n if (this.abortController?.signal.aborted) break\n\n this.callbacksRef.onChunk?.(chunk)\n\n // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- AG-UI EventType has ~22 variants; this consumer only handles the subset relevant to generation lifecycle.\n switch (chunk.type) {\n case 'CUSTOM': {\n if (chunk.name === GENERATION_EVENTS.RESULT) {\n this.setResult(chunk.value as TResult)\n } else if (chunk.name === GENERATION_EVENTS.PROGRESS) {\n const { progress, message } = chunk.value as {\n progress: number\n message?: string\n }\n this.callbacksRef.onProgress?.(progress, message)\n }\n break\n }\n case 'RUN_FINISHED': {\n this.setStatus('success')\n break\n }\n case 'RUN_ERROR': {\n // Prefer spec `message`; fall back to deprecated `error.message`\n const msg =\n (chunk.message as string | undefined) ||\n chunk.error?.message ||\n 'An error occurred'\n throw new Error(msg)\n }\n default:\n break\n }\n }\n }\n\n /**\n * Abort any in-flight generation request.\n */\n stop(): void {\n if (this.abortController) {\n this.abortController.abort()\n this.abortController = null\n }\n this.setIsLoading(false)\n if (this.status === 'generating') {\n this.setStatus('idle')\n }\n }\n\n /**\n * Clear the result, error, and return to idle state.\n */\n reset(): void {\n this.stop()\n this.setResult(null)\n this.setError(undefined)\n this.setStatus('idle')\n }\n\n /**\n * Update options without recreating the client.\n */\n updateOptions(\n options: Partial<\n Pick<\n GenerationClientOptions<TInput, TResult, TOutput>,\n 'body' | 'onResult' | 'onError' | 'onProgress' | 'onChunk'\n >\n >,\n ): void {\n if (options.body !== undefined) {\n this.body = options.body ?? {}\n }\n if (options.onResult !== undefined) {\n this.callbacksRef.onResult = options.onResult\n }\n if (options.onError !== undefined) {\n this.callbacksRef.onError = options.onError\n }\n if (options.onProgress !== undefined) {\n this.callbacksRef.onProgress = options.onProgress\n }\n if (options.onChunk !== undefined) {\n this.callbacksRef.onChunk = options.onChunk\n }\n }\n\n // ===========================\n // Getters\n // ===========================\n\n getResult(): TOutput | null {\n return this.result\n }\n\n getIsLoading(): boolean {\n return this.isLoading\n }\n\n getError(): Error | undefined {\n return this.error\n }\n\n getStatus(): GenerationClientState {\n return this.status\n }\n\n // ===========================\n // Private state setters\n // ===========================\n\n private setResult(rawResult: TResult | null): void {\n if (rawResult === null) {\n this.result = null\n this.callbacksRef.onResultChange?.(null)\n return\n }\n\n if (this.callbacksRef.onResult) {\n const transformed = this.callbacksRef.onResult(rawResult)\n if (transformed === null) {\n // null return → keep previous result unchanged\n return\n }\n if (transformed !== undefined) {\n // Non-null, non-undefined → use transformed value\n this.result = transformed\n this.callbacksRef.onResultChange?.(this.result)\n return\n }\n }\n\n // No onResult callback, or callback returned void → use raw value\n this.result = rawResult as TOutput\n this.callbacksRef.onResultChange?.(this.result)\n }\n\n private setIsLoading(isLoading: boolean): void {\n this.isLoading = isLoading\n this.callbacksRef.onLoadingChange?.(isLoading)\n }\n\n private setError(error: Error | undefined): void {\n this.error = error\n this.callbacksRef.onErrorChange?.(error)\n }\n\n private setStatus(status: GenerationClientState): void {\n this.status = status\n this.callbacksRef.onStatusChange?.(status)\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"generation-client.js","sources":["../../src/generation-client.ts"],"sourcesContent":["import { GENERATION_EVENTS } from './generation-types'\nimport { createNoOpGenerationDevtoolsBridge } from './devtools-noop'\nimport { parseSSEResponse } from './sse-parser'\nimport type { StreamChunk } from '@tanstack/ai'\nimport type {\n ConnectConnectionAdapter,\n RunAgentInputContext,\n} from './connection-adapters'\nimport type {\n AIDevtoolsClientMetadata,\n AIDevtoolsGenerationProgress,\n GenerationDevtoolsBridge,\n GenerationDevtoolsBridgeOptions,\n} from './devtools'\nimport type {\n GenerationClientOptions,\n GenerationClientState,\n GenerationFetcher,\n} from './generation-types'\n\n/**\n * Callbacks stored in a ref so hooks can update them without recreating the client.\n */\n// All optional fields explicitly allow `| undefined` so callers can spread\n// option bags (where each callback may be `undefined`) into the callbacks\n// ref under `exactOptionalPropertyTypes`.\ninterface GenerationCallbacks<TResult, TOutput> {\n onResult?: ((result: TResult) => TOutput | null | void) | undefined\n onError?: ((error: Error) => void) | undefined\n onProgress?: ((progress: number, message?: string) => void) | undefined\n onChunk?: ((chunk: StreamChunk) => void) | undefined\n onResultChange?: ((result: TOutput | null) => void) | undefined\n onLoadingChange?: ((isLoading: boolean) => void) | undefined\n onErrorChange?: ((error: Error | undefined) => void) | undefined\n onStatusChange?: ((status: GenerationClientState) => void) | undefined\n}\n\n/**\n * A lightweight, generic client for one-shot generation tasks\n * (image, speech, transcription, summarize).\n *\n * Supports two transport modes:\n * - **ConnectConnectionAdapter** — Streaming transport (SSE, HTTP stream, custom).\n * Server wraps results in StreamChunk events with CUSTOM event names.\n * - **Fetcher** — Direct async function call. No streaming protocol needed.\n *\n * @template TInput - The input type for the generation request\n * @template TResult - The result type returned by the generation\n *\n * @example\n * ```typescript\n * // With streaming connection adapter\n * const client = new GenerationClient<ImageGenerateInput, ImageGenerationResult>({\n * connection: fetchServerSentEvents('/api/generate/image'),\n * onResultChange: setResult,\n * onLoadingChange: setIsLoading,\n * })\n *\n * // With fetcher (direct)\n * const client = new GenerationClient<ImageGenerateInput, ImageGenerationResult>({\n * fetcher: async (input) => {\n * const res = await fetch('/api/generate/image', {\n * method: 'POST',\n * body: JSON.stringify(input),\n * })\n * return res.json()\n * },\n * })\n *\n * await client.generate({ prompt: 'A sunset over mountains' })\n * ```\n */\nexport class GenerationClient<\n TInput extends Record<string, any>,\n TResult,\n TOutput = TResult,\n> {\n private readonly connection: ConnectConnectionAdapter | undefined\n private readonly fetcher: GenerationFetcher<TInput, TResult> | undefined\n private readonly uniqueId: string\n private readonly devtoolsMetadata: AIDevtoolsClientMetadata\n private readonly devtoolsBridge: GenerationDevtoolsBridge<TOutput>\n private readonly threadId: string\n private body: Record<string, any>\n private result: TOutput | null = null\n private input: TInput | null = null\n private progress: AIDevtoolsGenerationProgress | null = null\n private isLoading = false\n private error: Error | undefined = undefined\n private status: GenerationClientState = 'idle'\n private abortController: AbortController | null = null\n private readonly callbacksRef: GenerationCallbacks<TResult, TOutput>\n private devtoolsMounted = false\n\n constructor(\n options: GenerationClientOptions<TInput, TResult, TOutput> &\n (\n | { connection: ConnectConnectionAdapter; fetcher?: never }\n | {\n fetcher: GenerationFetcher<TInput, TResult>\n connection?: never\n }\n ),\n ) {\n this.uniqueId = options.id ?? this.generateUniqueId('generation')\n this.threadId = this.uniqueId\n this.connection = options.connection\n this.fetcher = options.fetcher\n this.body = options.body ?? {}\n\n this.callbacksRef = {\n onResult: options.onResult,\n onError: options.onError,\n onProgress: options.onProgress,\n onChunk: options.onChunk,\n onResultChange: options.onResultChange,\n onLoadingChange: options.onLoadingChange,\n onErrorChange: options.onErrorChange,\n onStatusChange: options.onStatusChange,\n }\n\n this.devtoolsMetadata = this.createDevtoolsMetadata(options.devtools)\n this.devtoolsBridge = (\n options.devtoolsBridgeFactory ?? createNoOpGenerationDevtoolsBridge\n )<TOutput>(this.buildDevtoolsBridgeOptions())\n }\n\n private buildDevtoolsBridgeOptions(): GenerationDevtoolsBridgeOptions<TOutput> {\n return {\n hookId: this.uniqueId,\n clientId: this.uniqueId,\n threadId: this.threadId,\n metadata: this.devtoolsMetadata,\n getCoreState: () => ({\n input: this.input,\n result: this.result,\n progress: this.progress,\n status: this.status,\n isLoading: this.isLoading,\n ...(this.error ? { error: this.error.message } : {}),\n }),\n }\n }\n\n mountDevtools(): void {\n if (this.devtoolsMounted) {\n return\n }\n\n this.devtoolsMounted = true\n this.devtoolsBridge.emitRegistered()\n this.devtoolsBridge.emitSnapshot()\n }\n\n /**\n * Trigger a generation request.\n * Only one generation can be in-flight at a time; calling generate()\n * while already generating will be a no-op.\n */\n async generate(input: TInput): Promise<void> {\n this.mountDevtools()\n if (this.isLoading) return\n\n this.input = input\n this.progress = null\n const runId = this.devtoolsBridge.beginRun(input)\n this.setIsLoading(true)\n this.setStatus('generating')\n this.setError(undefined)\n\n const abortController = new AbortController()\n this.abortController = abortController\n const { signal } = abortController\n\n try {\n if (this.fetcher) {\n // Direct fetch path\n const result = await this.fetcher(input, { signal })\n if (signal.aborted) return\n if (result instanceof Response) {\n // Server function returned SSE Response — parse stream\n await this.processStream(parseSSEResponse(result, signal), runId)\n } else {\n this.devtoolsBridge.ensureRunStarted(runId)\n this.setResult(result)\n this.setStatus('success')\n }\n } else if (this.connection) {\n // Streaming adapter path\n const mergedData = { ...this.body, ...input }\n const stream = this.connection.connect(\n [],\n mergedData,\n signal,\n this.createRunContext(runId),\n )\n await this.processStream(stream, runId)\n } else {\n throw new Error(\n 'GenerationClient requires either a connection or fetcher option',\n )\n }\n if (!signal.aborted && this.status === 'success') {\n // Bump progress to 100 on successful completion so devtools\n // snapshots reflect the final state. The bridge mirrors this in\n // the run's recorded progress, but the snapshot reads `progress`\n // from the client's core state.\n this.progress = completeProgressValue(this.progress)\n this.devtoolsBridge.finishRun(\n this.devtoolsBridge.getActiveRunId() ?? runId,\n 'run:completed',\n 'completed',\n )\n }\n } catch (err: unknown) {\n if (signal.aborted) return\n const error = err instanceof Error ? err : new Error(String(err))\n this.setError(error)\n this.setStatus('error')\n this.devtoolsBridge.finishRun(\n this.devtoolsBridge.getActiveRunId() ?? runId,\n 'run:errored',\n 'errored',\n error.message,\n )\n this.callbacksRef.onError?.(error)\n } finally {\n this.abortController = null\n this.setIsLoading(false)\n }\n }\n\n /**\n * Process a stream of AG-UI events from the streaming connection adapter.\n */\n private async processStream(\n source: AsyncIterable<StreamChunk>,\n fallbackRunId: string,\n ): Promise<void> {\n let streamRunId: string | undefined\n\n for await (const chunk of source) {\n if (this.abortController?.signal.aborted) break\n\n this.callbacksRef.onChunk?.(chunk)\n const chunkRunId =\n 'runId' in chunk && typeof chunk.runId === 'string'\n ? chunk.runId\n : undefined\n\n // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- AG-UI EventType has ~22 variants; this consumer only handles the subset relevant to generation lifecycle.\n switch (chunk.type) {\n case 'RUN_STARTED': {\n streamRunId = chunk.runId\n this.devtoolsBridge.ensureRunStarted(chunk.runId)\n break\n }\n case 'CUSTOM': {\n this.devtoolsBridge.ensureRunStarted(streamRunId ?? fallbackRunId)\n if (chunk.name === GENERATION_EVENTS.RESULT) {\n this.setResult(chunk.value as TResult)\n } else if (chunk.name === GENERATION_EVENTS.PROGRESS) {\n const { progress, message } = chunk.value as {\n progress: number\n message?: string\n }\n this.setProgress(progress, message)\n }\n break\n }\n case 'RUN_FINISHED': {\n streamRunId = chunk.runId\n this.devtoolsBridge.ensureRunStarted(chunk.runId)\n this.setStatus('success')\n break\n }\n case 'RUN_ERROR': {\n this.devtoolsBridge.ensureRunStarted(\n chunkRunId ?? streamRunId ?? fallbackRunId,\n )\n // Prefer spec `message`; fall back to deprecated `error.message`\n const msg =\n (chunk.message as string | undefined) ||\n chunk.error?.message ||\n 'An error occurred'\n throw new Error(msg)\n }\n default:\n break\n }\n }\n }\n\n /**\n * Abort any in-flight generation request.\n */\n stop(): void {\n const runId = this.devtoolsBridge.getActiveRunId()\n if (this.abortController) {\n this.abortController.abort()\n this.abortController = null\n }\n this.setIsLoading(false)\n if (this.status === 'generating') {\n this.setStatus('idle')\n if (runId) {\n this.devtoolsBridge.finishRun(runId, 'run:cancelled', 'cancelled')\n }\n }\n }\n\n /**\n * Clear the result, error, and return to idle state.\n */\n reset(): void {\n this.stop()\n this.setResult(null)\n this.input = null\n this.progress = null\n this.devtoolsBridge.resetRuns()\n this.setError(undefined)\n this.setStatus('idle')\n this.devtoolsBridge.emitState()\n }\n\n /**\n * Update options without recreating the client.\n */\n updateOptions(\n options: Partial<\n Pick<\n GenerationClientOptions<TInput, TResult, TOutput>,\n 'body' | 'onResult' | 'onError' | 'onProgress' | 'onChunk'\n >\n >,\n ): void {\n if (options.body !== undefined) {\n this.body = options.body ?? {}\n }\n if (options.onResult !== undefined) {\n this.callbacksRef.onResult = options.onResult\n }\n if (options.onError !== undefined) {\n this.callbacksRef.onError = options.onError\n }\n if (options.onProgress !== undefined) {\n this.callbacksRef.onProgress = options.onProgress\n }\n if (options.onChunk !== undefined) {\n this.callbacksRef.onChunk = options.onChunk\n }\n }\n\n dispose(): void {\n this.stop()\n this.devtoolsBridge.dispose()\n this.devtoolsMounted = false\n }\n\n // ===========================\n // Getters\n // ===========================\n\n getResult(): TOutput | null {\n return this.result\n }\n\n getIsLoading(): boolean {\n return this.isLoading\n }\n\n getError(): Error | undefined {\n return this.error\n }\n\n getStatus(): GenerationClientState {\n return this.status\n }\n\n // ===========================\n // Private state setters\n // ===========================\n\n private setResult(rawResult: TResult | null): void {\n if (rawResult === null) {\n this.result = null\n this.callbacksRef.onResultChange?.(null)\n this.devtoolsBridge.recordResultChange()\n return\n }\n\n if (this.callbacksRef.onResult) {\n const transformed = this.callbacksRef.onResult(rawResult)\n if (transformed === null) {\n // null return → keep previous result unchanged, just re-emit\n this.devtoolsBridge.emitState()\n return\n }\n if (transformed !== undefined) {\n // Non-null, non-undefined → use transformed value\n this.result = transformed\n this.callbacksRef.onResultChange?.(this.result)\n this.devtoolsBridge.recordResultChange()\n return\n }\n }\n\n // No onResult callback, or callback returned void → use raw value as\n // TOutput. When the caller did not supply an onResult transform,\n // `TOutput` defaults to `TResult`, so the runtime cast is sound.\n // eslint-disable-next-line no-restricted-syntax -- TOutput defaults to TResult when no onResult transform is supplied\n this.result = rawResult as unknown as TOutput\n this.callbacksRef.onResultChange?.(this.result)\n this.devtoolsBridge.recordResultChange()\n }\n\n private setIsLoading(isLoading: boolean): void {\n this.isLoading = isLoading\n this.callbacksRef.onLoadingChange?.(isLoading)\n this.devtoolsBridge.recordLoadingChange()\n }\n\n private setError(error: Error | undefined): void {\n this.error = error\n this.callbacksRef.onErrorChange?.(error)\n this.devtoolsBridge.recordErrorChange(error)\n }\n\n private setStatus(status: GenerationClientState): void {\n this.status = status\n this.callbacksRef.onStatusChange?.(status)\n this.devtoolsBridge.recordStatusChange(status)\n }\n\n private setProgress(value: number, message?: string): void {\n this.progress = {\n value,\n ...(message ? { message } : {}),\n }\n if (message === undefined) {\n this.callbacksRef.onProgress?.(value)\n } else {\n this.callbacksRef.onProgress?.(value, message)\n }\n this.devtoolsBridge.recordProgressChange()\n }\n\n private createDevtoolsMetadata(\n metadata?: Partial<AIDevtoolsClientMetadata>,\n ): AIDevtoolsClientMetadata {\n return {\n hookName: metadata?.hookName ?? 'useGeneration',\n ...(metadata?.framework ? { framework: metadata.framework } : {}),\n ...(metadata?.outputKind ? { outputKind: metadata.outputKind } : {}),\n ...(metadata?.name ? { name: metadata.name } : {}),\n }\n }\n\n private generateUniqueId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`\n }\n\n private createRunContext(runId: string): RunAgentInputContext {\n return {\n threadId: this.threadId,\n runId,\n }\n }\n}\n\nfunction completeProgressValue(\n progress: AIDevtoolsGenerationProgress | null,\n): AIDevtoolsGenerationProgress | null {\n if (!progress) return null\n const message = progress.message\n return {\n value: 100,\n ...(message ? { message } : {}),\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { StreamChunk } from '@tanstack/ai';
|
|
2
2
|
import { ConnectConnectionAdapter } from './connection-adapters.js';
|
|
3
|
+
import { AIDevtoolsClientMetadata } from './devtools.js';
|
|
4
|
+
import { GenerationDevtoolsBridgeFactory, VideoDevtoolsBridgeFactory } from './devtools-noop.js';
|
|
3
5
|
/**
|
|
4
6
|
* Infers the output type from an `onResult` callback's return type.
|
|
5
7
|
*
|
|
@@ -71,6 +73,13 @@ export interface GenerationClientOptions<_TInput, TResult, TOutput = TResult> {
|
|
|
71
73
|
id?: string;
|
|
72
74
|
/** Additional body parameters to send with connect-based adapter requests */
|
|
73
75
|
body?: Record<string, any>;
|
|
76
|
+
/** Metadata used to register this generation hook with TanStack AI Devtools */
|
|
77
|
+
devtools?: Partial<AIDevtoolsClientMetadata>;
|
|
78
|
+
/**
|
|
79
|
+
* Factory that constructs the devtools bridge. Default is a no-op
|
|
80
|
+
* factory; the real implementation lives in `@tanstack/ai-client/devtools`.
|
|
81
|
+
*/
|
|
82
|
+
devtoolsBridgeFactory?: GenerationDevtoolsBridgeFactory;
|
|
74
83
|
/**
|
|
75
84
|
* Callback when a result is received. Can optionally return a transformed value
|
|
76
85
|
* that replaces the stored result.
|
|
@@ -126,7 +135,12 @@ export interface VideoGenerateResult {
|
|
|
126
135
|
/**
|
|
127
136
|
* Options for the VideoGenerationClient.
|
|
128
137
|
*/
|
|
129
|
-
export interface VideoGenerationClientOptions<TOutput = VideoGenerateResult> extends GenerationClientOptions<VideoGenerateInput, VideoGenerateResult, TOutput> {
|
|
138
|
+
export interface VideoGenerationClientOptions<TOutput = VideoGenerateResult> extends Omit<GenerationClientOptions<VideoGenerateInput, VideoGenerateResult, TOutput>, 'devtoolsBridgeFactory'> {
|
|
139
|
+
/**
|
|
140
|
+
* Factory that constructs the video devtools bridge. Default is a no-op
|
|
141
|
+
* factory; the real implementation lives in `@tanstack/ai-client/devtools`.
|
|
142
|
+
*/
|
|
143
|
+
devtoolsBridgeFactory?: VideoDevtoolsBridgeFactory;
|
|
130
144
|
/** Callback when a video job is created */
|
|
131
145
|
onJobCreated?: (jobId: string) => void;
|
|
132
146
|
/** Callback on each status update */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generation-types.js","sources":["../../src/generation-types.ts"],"sourcesContent":["import type { StreamChunk } from '@tanstack/ai'\nimport type { ConnectConnectionAdapter } from './connection-adapters'\n\n// ===========================\n// Inference Utilities\n// ===========================\n\n/**\n * Infers the output type from an `onResult` callback's return type.\n *\n * - If the callback returns a concrete type (excluding null/void/undefined), uses that type.\n * - If the callback only returns null/void/undefined, or is not provided, falls back to TResult.\n *\n * @template TResult - The raw result type from the generation\n * @template TFn - The onResult callback type (or undefined if not provided)\n */\nexport type InferGenerationOutput<TResult, TFn> = TFn extends (\n result: any,\n) => infer R\n ? [Exclude<R, null | void | undefined>] extends [never]\n ? TResult\n : Exclude<R, null | void | undefined>\n : TResult\n\n// ===========================\n// State\n// ===========================\n\n/**\n * State machine for generation clients.\n * Simpler than ChatClientState since generation is a single request/response cycle.\n */\nexport type GenerationClientState = 'idle' | 'generating' | 'success' | 'error'\n\n// ===========================\n// Event Constants\n// ===========================\n\n/**\n * Well-known CUSTOM event names used by generation clients.\n * These events are emitted by the server-side streaming helpers\n * and consumed by the client-side GenerationClient.\n */\nexport const GENERATION_EVENTS = {\n /** The generation result payload */\n RESULT: 'generation:result',\n /** Progress update (0-100) with optional message */\n PROGRESS: 'generation:progress',\n /** Video job created with jobId */\n VIDEO_JOB_CREATED: 'video:job:created',\n /** Video job status update */\n VIDEO_STATUS: 'video:status',\n} as const\n\n// ===========================\n// Transport Types\n// ===========================\n\n/**\n * Options passed to a fetcher function by the generation client.\n */\nexport interface GenerationFetcherOptions {\n /** AbortSignal that is triggered when the user calls `stop()` */\n signal: AbortSignal\n}\n\n/**\n * A direct async function that performs a generation request.\n *\n * Can return the result directly, or return a `Response` with an SSE body\n * (e.g., from a TanStack Start server function using `toServerSentEventsResponse()`).\n * When a `Response` is returned, the client will parse it as an SSE stream.\n *\n * @template TInput - The input type for the generation request\n * @template TResult - The result type returned by the generation\n */\nexport type GenerationFetcher<TInput, TResult> = (\n input: TInput,\n options?: GenerationFetcherOptions,\n) => Promise<TResult | Response>\n\n/**\n * Transport configuration for generation clients.\n * Supports either a connect-based streaming adapter or a direct fetcher function.\n */\nexport type GenerationTransport<TInput, TResult> =\n | { connection: ConnectConnectionAdapter; fetcher?: never }\n | { fetcher: GenerationFetcher<TInput, TResult>; connection?: never }\n\n// ===========================\n// Client Options\n// ===========================\n\n/**\n * Options for the GenerationClient.\n *\n * @template TInput - The input type for the generation request (used by consuming code)\n * @template TResult - The result type returned by the generation\n * @template TOutput - The output type after optional transform (defaults to TResult)\n */\n// eslint-disable-next-line @typescript-eslint/naming-convention -- _TInput is unused in the interface body but part of the public positional generic API (callers supply it for inference)\nexport interface GenerationClientOptions<_TInput, TResult, TOutput = TResult> {\n /** Unique identifier for this generation client instance */\n id?: string\n\n /** Additional body parameters to send with connect-based adapter requests */\n body?: Record<string, any>\n\n /**\n * Callback when a result is received. Can optionally return a transformed value\n * that replaces the stored result.\n *\n * - Return a non-null value to transform and store it as the result\n * - Return `null` to keep the previous result unchanged\n * - Return nothing (`void`) to store the raw result as-is\n */\n onResult?: (result: TResult) => TOutput | null | void\n /** Callback when an error occurs */\n onError?: (error: Error) => void\n /** Callback when progress is reported (0-100) */\n onProgress?: (progress: number, message?: string) => void\n /** Callback for each stream chunk (connect-based adapter mode only) */\n onChunk?: (chunk: StreamChunk) => void\n\n // Framework state callbacks (set by hooks, not users)\n /** @internal Called when result changes */\n onResultChange?: (result: TOutput | null) => void\n /** @internal Called when loading state changes */\n onLoadingChange?: (isLoading: boolean) => void\n /** @internal Called when error state changes */\n onErrorChange?: (error: Error | undefined) => void\n /** @internal Called when generation status changes */\n onStatusChange?: (status: GenerationClientState) => void\n}\n\n// ===========================\n// Video-Specific Options\n// ===========================\n\n/**\n * Video status information returned during job polling.\n */\nexport interface VideoStatusInfo {\n /** Job identifier */\n jobId: string\n /** Current status of the video generation job */\n status: 'pending' | 'processing' | 'completed' | 'failed'\n /** Progress percentage (0-100), if available */\n progress?: number\n /** URL to the generated video (when completed) */\n url?: string\n /** Error message if status is 'failed' */\n error?: string\n}\n\n/**\n * Composite result for video generation (job completion).\n */\nexport interface VideoGenerateResult {\n /** Job identifier */\n jobId: string\n /** Final status */\n status: 'completed'\n /** URL to the generated video */\n url: string\n /** When the URL expires, if applicable */\n expiresAt?: Date\n}\n\n/**\n * Options for the VideoGenerationClient.\n */\nexport interface VideoGenerationClientOptions<\n TOutput = VideoGenerateResult,\n> extends GenerationClientOptions<\n VideoGenerateInput,\n VideoGenerateResult,\n TOutput\n> {\n /** Callback when a video job is created */\n onJobCreated?: (jobId: string) => void\n /** Callback on each status update */\n onStatusUpdate?: (status: VideoStatusInfo) => void\n\n // Framework state callbacks\n /** @internal Called when jobId changes */\n onJobIdChange?: (jobId: string | null) => void\n /** @internal Called when video status changes */\n onVideoStatusChange?: (status: VideoStatusInfo | null) => void\n}\n\n// ===========================\n// Input Types\n// ===========================\n\n/**\n * Input for image generation.\n */\nexport interface ImageGenerateInput {\n /** Text description of the desired image(s) */\n prompt: string\n /** Number of images to generate (default: 1) */\n numberOfImages?: number\n /** Image size in WIDTHxHEIGHT format (e.g., \"1024x1024\") */\n size?: string\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n\n/**\n * Input for audio generation (music, sound effects).\n */\nexport interface AudioGenerateInput {\n /** Text description of the desired audio */\n prompt: string\n /** Desired duration in seconds */\n duration?: number\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n\n/**\n * Input for text-to-speech generation.\n */\nexport interface SpeechGenerateInput {\n /** The text to convert to speech */\n text: string\n /** The voice to use for generation */\n voice?: string\n /** The output audio format */\n format?: 'mp3' | 'opus' | 'aac' | 'flac' | 'wav' | 'pcm'\n /** The speed of the generated audio (0.25 to 4.0) */\n speed?: number\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n\n/**\n * Input for audio transcription.\n */\nexport interface TranscriptionGenerateInput {\n /** The audio data to transcribe - can be base64 string, File, Blob, or ArrayBuffer */\n audio: string | File | Blob | ArrayBuffer\n /** The language of the audio in ISO-639-1 format (e.g., 'en') */\n language?: string\n /** An optional prompt to guide the transcription */\n prompt?: string\n /** The format of the transcription output */\n responseFormat?: 'json' | 'text' | 'srt' | 'verbose_json' | 'vtt'\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n\n/**\n * Input for text summarization.\n */\nexport interface SummarizeGenerateInput {\n /** The text to summarize */\n text: string\n /** Maximum length of the summary */\n maxLength?: number\n /** Style of the summary */\n style?: 'bullet-points' | 'paragraph' | 'concise'\n /** Topics to focus on */\n focus?: Array<string>\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n\n/**\n * Input for video generation.\n */\nexport interface VideoGenerateInput {\n /** Text description of the desired video */\n prompt: string\n /** Video size — format depends on provider (e.g., \"16:9\", \"1280x720\") */\n size?: string\n /** Video duration in seconds */\n duration?: number\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n"],"names":[],"mappings":"AA2CO,MAAM,oBAAoB;AAAA;AAAA,EAE/B,QAAQ;AAAA;AAAA,EAER,UAAU;AAAA;AAAA,EAEV,mBAAmB;AAAA;AAAA,EAEnB,cAAc;AAChB;"}
|
|
1
|
+
{"version":3,"file":"generation-types.js","sources":["../../src/generation-types.ts"],"sourcesContent":["import type { StreamChunk } from '@tanstack/ai'\nimport type { ConnectConnectionAdapter } from './connection-adapters'\nimport type { AIDevtoolsClientMetadata } from './devtools'\nimport type {\n GenerationDevtoolsBridgeFactory,\n VideoDevtoolsBridgeFactory,\n} from './devtools-noop'\n\n// ===========================\n// Inference Utilities\n// ===========================\n\n/**\n * Infers the output type from an `onResult` callback's return type.\n *\n * - If the callback returns a concrete type (excluding null/void/undefined), uses that type.\n * - If the callback only returns null/void/undefined, or is not provided, falls back to TResult.\n *\n * @template TResult - The raw result type from the generation\n * @template TFn - The onResult callback type (or undefined if not provided)\n */\nexport type InferGenerationOutput<TResult, TFn> = TFn extends (\n result: any,\n) => infer R\n ? [Exclude<R, null | void | undefined>] extends [never]\n ? TResult\n : Exclude<R, null | void | undefined>\n : TResult\n\n// ===========================\n// State\n// ===========================\n\n/**\n * State machine for generation clients.\n * Simpler than ChatClientState since generation is a single request/response cycle.\n */\nexport type GenerationClientState = 'idle' | 'generating' | 'success' | 'error'\n\n// ===========================\n// Event Constants\n// ===========================\n\n/**\n * Well-known CUSTOM event names used by generation clients.\n * These events are emitted by the server-side streaming helpers\n * and consumed by the client-side GenerationClient.\n */\nexport const GENERATION_EVENTS = {\n /** The generation result payload */\n RESULT: 'generation:result',\n /** Progress update (0-100) with optional message */\n PROGRESS: 'generation:progress',\n /** Video job created with jobId */\n VIDEO_JOB_CREATED: 'video:job:created',\n /** Video job status update */\n VIDEO_STATUS: 'video:status',\n} as const\n\n// ===========================\n// Transport Types\n// ===========================\n\n/**\n * Options passed to a fetcher function by the generation client.\n */\nexport interface GenerationFetcherOptions {\n /** AbortSignal that is triggered when the user calls `stop()` */\n signal: AbortSignal\n}\n\n/**\n * A direct async function that performs a generation request.\n *\n * Can return the result directly, or return a `Response` with an SSE body\n * (e.g., from a TanStack Start server function using `toServerSentEventsResponse()`).\n * When a `Response` is returned, the client will parse it as an SSE stream.\n *\n * @template TInput - The input type for the generation request\n * @template TResult - The result type returned by the generation\n */\nexport type GenerationFetcher<TInput, TResult> = (\n input: TInput,\n options?: GenerationFetcherOptions,\n) => Promise<TResult | Response>\n\n/**\n * Transport configuration for generation clients.\n * Supports either a connect-based streaming adapter or a direct fetcher function.\n */\nexport type GenerationTransport<TInput, TResult> =\n | { connection: ConnectConnectionAdapter; fetcher?: never }\n | { fetcher: GenerationFetcher<TInput, TResult>; connection?: never }\n\n// ===========================\n// Client Options\n// ===========================\n\n/**\n * Options for the GenerationClient.\n *\n * @template TInput - The input type for the generation request (used by consuming code)\n * @template TResult - The result type returned by the generation\n * @template TOutput - The output type after optional transform (defaults to TResult)\n */\n// eslint-disable-next-line @typescript-eslint/naming-convention -- _TInput is unused in the interface body but part of the public positional generic API (callers supply it for inference)\nexport interface GenerationClientOptions<_TInput, TResult, TOutput = TResult> {\n /** Unique identifier for this generation client instance */\n id?: string\n\n /** Additional body parameters to send with connect-based adapter requests */\n body?: Record<string, any>\n\n /** Metadata used to register this generation hook with TanStack AI Devtools */\n devtools?: Partial<AIDevtoolsClientMetadata>\n\n /**\n * Factory that constructs the devtools bridge. Default is a no-op\n * factory; the real implementation lives in `@tanstack/ai-client/devtools`.\n */\n devtoolsBridgeFactory?: GenerationDevtoolsBridgeFactory\n\n /**\n * Callback when a result is received. Can optionally return a transformed value\n * that replaces the stored result.\n *\n * - Return a non-null value to transform and store it as the result\n * - Return `null` to keep the previous result unchanged\n * - Return nothing (`void`) to store the raw result as-is\n */\n onResult?: (result: TResult) => TOutput | null | void\n /** Callback when an error occurs */\n onError?: (error: Error) => void\n /** Callback when progress is reported (0-100) */\n onProgress?: (progress: number, message?: string) => void\n /** Callback for each stream chunk (connect-based adapter mode only) */\n onChunk?: (chunk: StreamChunk) => void\n\n // Framework state callbacks (set by hooks, not users)\n /** @internal Called when result changes */\n onResultChange?: (result: TOutput | null) => void\n /** @internal Called when loading state changes */\n onLoadingChange?: (isLoading: boolean) => void\n /** @internal Called when error state changes */\n onErrorChange?: (error: Error | undefined) => void\n /** @internal Called when generation status changes */\n onStatusChange?: (status: GenerationClientState) => void\n}\n\n// ===========================\n// Video-Specific Options\n// ===========================\n\n/**\n * Video status information returned during job polling.\n */\nexport interface VideoStatusInfo {\n /** Job identifier */\n jobId: string\n /** Current status of the video generation job */\n status: 'pending' | 'processing' | 'completed' | 'failed'\n /** Progress percentage (0-100), if available */\n progress?: number\n /** URL to the generated video (when completed) */\n url?: string\n /** Error message if status is 'failed' */\n error?: string\n}\n\n/**\n * Composite result for video generation (job completion).\n */\nexport interface VideoGenerateResult {\n /** Job identifier */\n jobId: string\n /** Final status */\n status: 'completed'\n /** URL to the generated video */\n url: string\n /** When the URL expires, if applicable */\n expiresAt?: Date\n}\n\n/**\n * Options for the VideoGenerationClient.\n */\nexport interface VideoGenerationClientOptions<\n TOutput = VideoGenerateResult,\n> extends Omit<\n GenerationClientOptions<VideoGenerateInput, VideoGenerateResult, TOutput>,\n 'devtoolsBridgeFactory'\n> {\n /**\n * Factory that constructs the video devtools bridge. Default is a no-op\n * factory; the real implementation lives in `@tanstack/ai-client/devtools`.\n */\n devtoolsBridgeFactory?: VideoDevtoolsBridgeFactory\n\n /** Callback when a video job is created */\n onJobCreated?: (jobId: string) => void\n /** Callback on each status update */\n onStatusUpdate?: (status: VideoStatusInfo) => void\n\n // Framework state callbacks\n /** @internal Called when jobId changes */\n onJobIdChange?: (jobId: string | null) => void\n /** @internal Called when video status changes */\n onVideoStatusChange?: (status: VideoStatusInfo | null) => void\n}\n\n// ===========================\n// Input Types\n// ===========================\n\n/**\n * Input for image generation.\n */\nexport interface ImageGenerateInput {\n /** Text description of the desired image(s) */\n prompt: string\n /** Number of images to generate (default: 1) */\n numberOfImages?: number\n /** Image size in WIDTHxHEIGHT format (e.g., \"1024x1024\") */\n size?: string\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n\n/**\n * Input for audio generation (music, sound effects).\n */\nexport interface AudioGenerateInput {\n /** Text description of the desired audio */\n prompt: string\n /** Desired duration in seconds */\n duration?: number\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n\n/**\n * Input for text-to-speech generation.\n */\nexport interface SpeechGenerateInput {\n /** The text to convert to speech */\n text: string\n /** The voice to use for generation */\n voice?: string\n /** The output audio format */\n format?: 'mp3' | 'opus' | 'aac' | 'flac' | 'wav' | 'pcm'\n /** The speed of the generated audio (0.25 to 4.0) */\n speed?: number\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n\n/**\n * Input for audio transcription.\n */\nexport interface TranscriptionGenerateInput {\n /** The audio data to transcribe - can be base64 string, File, Blob, or ArrayBuffer */\n audio: string | File | Blob | ArrayBuffer\n /** The language of the audio in ISO-639-1 format (e.g., 'en') */\n language?: string\n /** An optional prompt to guide the transcription */\n prompt?: string\n /** The format of the transcription output */\n responseFormat?: 'json' | 'text' | 'srt' | 'verbose_json' | 'vtt'\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n\n/**\n * Input for text summarization.\n */\nexport interface SummarizeGenerateInput {\n /** The text to summarize */\n text: string\n /** Maximum length of the summary */\n maxLength?: number\n /** Style of the summary */\n style?: 'bullet-points' | 'paragraph' | 'concise'\n /** Topics to focus on */\n focus?: Array<string>\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n\n/**\n * Input for video generation.\n */\nexport interface VideoGenerateInput {\n /** Text description of the desired video */\n prompt: string\n /** Video size — format depends on provider (e.g., \"16:9\", \"1280x720\") */\n size?: string\n /** Video duration in seconds */\n duration?: number\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n"],"names":[],"mappings":"AAgDO,MAAM,oBAAoB;AAAA;AAAA,EAE/B,QAAQ;AAAA;AAAA,EAER,UAAU;AAAA;AAAA,EAEV,mBAAmB;AAAA;AAAA,EAEnB,cAAc;AAChB;"}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -2,13 +2,14 @@ export { ChatClient } from './chat-client.js';
|
|
|
2
2
|
export { RealtimeClient } from './realtime-client.js';
|
|
3
3
|
export { GenerationClient } from './generation-client.js';
|
|
4
4
|
export { VideoGenerationClient } from './video-generation-client.js';
|
|
5
|
-
export type { UIMessage, MessagePart, TextPart, ToolCallPart, ToolResultPart, ThinkingPart, StructuredOutputPart, ChatClientOptions, ChatRequestBody, InferChatMessages, ChatClientState, ConnectionStatus, MultimodalContent, } from './types.js';
|
|
5
|
+
export type { UIMessage, MessagePart, TextPart, ToolCallPart, ToolResultPart, ThinkingPart, StructuredOutputPart, ChatClientOptions, ChatRequestBody, InferChatMessages, ChatClientState, ConnectionStatus, ChatFetcher, ChatFetcherInput, ChatFetcherOptions, ChatTransport, DistributedOmit, MultimodalContent, } from './types.js';
|
|
6
6
|
export type { InferGenerationOutput, GenerationClientState, GenerationClientOptions, GenerationFetcher, GenerationFetcherOptions, GenerationTransport, VideoGenerationClientOptions, VideoStatusInfo, VideoGenerateResult, ImageGenerateInput, AudioGenerateInput, SpeechGenerateInput, TranscriptionGenerateInput, SummarizeGenerateInput, VideoGenerateInput, } from './generation-types.js';
|
|
7
7
|
export { GENERATION_EVENTS } from './generation-types.js';
|
|
8
8
|
export { clientTools, createChatClientOptions } from './types.js';
|
|
9
|
+
export { createAIDevtoolsGenerationPreview, type AIDevtoolsClientMetadata, type AIDevtoolsDisplayOptions, type AIDevtoolsGenerationMediaItem, type AIDevtoolsGenerationPreview, type AIDevtoolsGenerationProgress, type AIDevtoolsGenerationVideoJob, } from './devtools.js';
|
|
9
10
|
export type { ExtractToolNames, ExtractToolInput, ExtractToolOutput, } from './tool-types.js';
|
|
10
11
|
export type { AnyClientTool } from '@tanstack/ai';
|
|
11
12
|
export type { RealtimeAdapter, RealtimeConnection, RealtimeClientOptions, RealtimeClientState, RealtimeStateChangeCallback, } from './realtime-types.js';
|
|
12
|
-
export { fetchServerSentEvents, fetchHttpStream, stream, rpcStream, type ConnectConnectionAdapter, type ConnectionAdapter, type FetchConnectionOptions, type RunAgentInputContext, type SubscribeConnectionAdapter, } from './connection-adapters.js';
|
|
13
|
+
export { fetchServerSentEvents, fetchHttpStream, stream, rpcStream, StreamTruncatedError, type ConnectConnectionAdapter, type ConnectionAdapter, type FetchConnectionOptions, type RunAgentInputContext, type SubscribeConnectionAdapter, } from './connection-adapters.js';
|
|
13
14
|
export { uiMessageToModelMessages, modelMessageToUIMessage, modelMessagesToUIMessages, convertMessagesToModelMessages, normalizeToUIMessage, generateMessageId, } from '@tanstack/ai';
|
|
14
15
|
export { StreamProcessor, ImmediateStrategy, PunctuationStrategy, BatchStrategy, WordBoundaryStrategy, CompositeStrategy, parsePartialJSON, PartialJSONParser, defaultJSONParser, type ChunkStrategy, type StreamProcessorOptions, type StreamProcessorEvents, type InternalToolCallState, type ToolCallState, type ToolResultState, type JSONParser, type ChunkRecording, type ProcessorResult, type ProcessorState, } from '@tanstack/ai';
|
package/dist/esm/index.js
CHANGED
|
@@ -4,7 +4,8 @@ import { GenerationClient } from "./generation-client.js";
|
|
|
4
4
|
import { VideoGenerationClient } from "./video-generation-client.js";
|
|
5
5
|
import { GENERATION_EVENTS } from "./generation-types.js";
|
|
6
6
|
import { clientTools, createChatClientOptions } from "./types.js";
|
|
7
|
-
import {
|
|
7
|
+
import { createAIDevtoolsGenerationPreview } from "./devtools.js";
|
|
8
|
+
import { StreamTruncatedError, fetchHttpStream, fetchServerSentEvents, rpcStream, stream } from "./connection-adapters.js";
|
|
8
9
|
import { BatchStrategy, CompositeStrategy, ImmediateStrategy, PartialJSONParser, PunctuationStrategy, StreamProcessor, WordBoundaryStrategy, convertMessagesToModelMessages, defaultJSONParser, generateMessageId, modelMessageToUIMessage, modelMessagesToUIMessages, normalizeToUIMessage, parsePartialJSON, uiMessageToModelMessages } from "@tanstack/ai";
|
|
9
10
|
export {
|
|
10
11
|
BatchStrategy,
|
|
@@ -17,10 +18,12 @@ export {
|
|
|
17
18
|
PunctuationStrategy,
|
|
18
19
|
RealtimeClient,
|
|
19
20
|
StreamProcessor,
|
|
21
|
+
StreamTruncatedError,
|
|
20
22
|
VideoGenerationClient,
|
|
21
23
|
WordBoundaryStrategy,
|
|
22
24
|
clientTools,
|
|
23
25
|
convertMessagesToModelMessages,
|
|
26
|
+
createAIDevtoolsGenerationPreview,
|
|
24
27
|
createChatClientOptions,
|
|
25
28
|
defaultJSONParser,
|
|
26
29
|
fetchHttpStream,
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;"}
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -1,6 +1,60 @@
|
|
|
1
1
|
import { AnyClientTool, AudioPart, ChunkStrategy, ContentPart, DocumentPart, ImagePart, InferToolInput, InferToolOutput, ModelMessage, StreamChunk, StructuredOutputPart, VideoPart } from '@tanstack/ai';
|
|
2
2
|
import { ConnectionAdapter } from './connection-adapters.js';
|
|
3
|
+
import { AIDevtoolsClientMetadata } from './devtools.js';
|
|
4
|
+
import { ChatDevtoolsBridgeFactory } from './devtools-noop.js';
|
|
3
5
|
export type { StructuredOutputPart } from '@tanstack/ai';
|
|
6
|
+
/**
|
|
7
|
+
* `messages` is the full UIMessage history (not a delta). `data` is the
|
|
8
|
+
* merged body — `ChatClientOptions.body` plus any per-call data passed to
|
|
9
|
+
* `sendMessage(...)`. `threadId` / `runId` are the AG-UI correlation ids
|
|
10
|
+
* the chat client uses to track this turn — forward them to your server
|
|
11
|
+
* if it needs to correlate requests.
|
|
12
|
+
*/
|
|
13
|
+
export interface ChatFetcherInput {
|
|
14
|
+
messages: Array<UIMessage>;
|
|
15
|
+
data?: Record<string, unknown>;
|
|
16
|
+
threadId: string;
|
|
17
|
+
runId: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ChatFetcherOptions {
|
|
20
|
+
/** Fires when `stop()` is called or the request is superseded. */
|
|
21
|
+
signal: AbortSignal;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Direct function that performs a chat request. Mirrors
|
|
25
|
+
* `GenerationFetcher`. Returns either a `Response` (SSE body parsed by the
|
|
26
|
+
* chat client) or an `AsyncIterable<StreamChunk>` (yielded directly). May
|
|
27
|
+
* return the value synchronously, as a `Promise`, or as an async generator
|
|
28
|
+
* (`async function*`) — the chat client awaits whichever shape is returned.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* useChat({
|
|
33
|
+
* fetcher: ({ messages }, { signal }) =>
|
|
34
|
+
* chatFn({ data: { messages }, signal }),
|
|
35
|
+
* })
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export type ChatFetcher = (input: ChatFetcherInput, options: ChatFetcherOptions) => Response | AsyncIterable<StreamChunk> | Promise<Response | AsyncIterable<StreamChunk>>;
|
|
39
|
+
/**
|
|
40
|
+
* Distributive `Omit` — applies `Omit<O, K>` per branch of a union so
|
|
41
|
+
* discriminated unions survive omission. Plain `Omit` collapses unions
|
|
42
|
+
* into a single object shape, which would erase the `ChatTransport` XOR
|
|
43
|
+
* when framework hooks omit React-managed callbacks from
|
|
44
|
+
* `ChatClientOptions`.
|
|
45
|
+
*/
|
|
46
|
+
export type DistributedOmit<TObject, TKeys extends keyof any> = TObject extends unknown ? Omit<TObject, TKeys> : never;
|
|
47
|
+
/**
|
|
48
|
+
* Discriminated union enforcing that exactly one of `connection` or
|
|
49
|
+
* `fetcher` is provided. Mirrors `GenerationTransport`.
|
|
50
|
+
*/
|
|
51
|
+
export type ChatTransport = {
|
|
52
|
+
connection: ConnectionAdapter;
|
|
53
|
+
fetcher?: never;
|
|
54
|
+
} | {
|
|
55
|
+
fetcher: ChatFetcher;
|
|
56
|
+
connection?: never;
|
|
57
|
+
};
|
|
4
58
|
/**
|
|
5
59
|
* Tool call states - track the lifecycle of a tool call
|
|
6
60
|
*/
|
|
@@ -136,13 +190,11 @@ export interface UIMessage<TTools extends ReadonlyArray<AnyClientTool> = any, TD
|
|
|
136
190
|
parts: Array<MessagePart<TTools, TData>>;
|
|
137
191
|
createdAt?: Date;
|
|
138
192
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
*/
|
|
145
|
-
connection: ConnectionAdapter;
|
|
193
|
+
/**
|
|
194
|
+
* Options for `ChatClient`. Exactly one of `connection` or `fetcher` must be
|
|
195
|
+
* provided — the type-level XOR is enforced via `ChatTransport`.
|
|
196
|
+
*/
|
|
197
|
+
export type ChatClientOptions<TTools extends ReadonlyArray<AnyClientTool> = any, TContext = unknown> = {
|
|
146
198
|
/**
|
|
147
199
|
* Initial messages to populate the chat
|
|
148
200
|
*/
|
|
@@ -239,6 +291,22 @@ export interface ChatClientOptions<TTools extends ReadonlyArray<AnyClientTool> =
|
|
|
239
291
|
* When provided, tools with execute functions will be called automatically
|
|
240
292
|
*/
|
|
241
293
|
tools?: TTools;
|
|
294
|
+
/**
|
|
295
|
+
* Client-local context passed to client-side tool execution.
|
|
296
|
+
*/
|
|
297
|
+
context?: TContext;
|
|
298
|
+
/**
|
|
299
|
+
* Devtools hook metadata for this client instance.
|
|
300
|
+
*/
|
|
301
|
+
devtools?: Partial<AIDevtoolsClientMetadata>;
|
|
302
|
+
/**
|
|
303
|
+
* Factory that constructs the devtools bridge. Default is a no-op
|
|
304
|
+
* factory, which keeps `@tanstack/ai-client/devtools` (the heavy
|
|
305
|
+
* bridge implementation) out of the main entry's bundle. Frameworks
|
|
306
|
+
* that need live devtools should pass the real factory from
|
|
307
|
+
* `@tanstack/ai-client/devtools`.
|
|
308
|
+
*/
|
|
309
|
+
devtoolsBridgeFactory?: ChatDevtoolsBridgeFactory;
|
|
242
310
|
/**
|
|
243
311
|
* Stream processing options (optional)
|
|
244
312
|
* Configure chunking strategy
|
|
@@ -250,7 +318,7 @@ export interface ChatClientOptions<TTools extends ReadonlyArray<AnyClientTool> =
|
|
|
250
318
|
*/
|
|
251
319
|
chunkStrategy?: ChunkStrategy;
|
|
252
320
|
};
|
|
253
|
-
}
|
|
321
|
+
} & ChatTransport;
|
|
254
322
|
export interface ChatRequestBody {
|
|
255
323
|
messages: Array<ModelMessage>;
|
|
256
324
|
data?: Record<string, any>;
|
|
@@ -287,7 +355,7 @@ export declare function clientTools<const T extends Array<AnyClientTool>>(...too
|
|
|
287
355
|
* type MyMessages = InferChatMessages<typeof chatOptions>
|
|
288
356
|
* ```
|
|
289
357
|
*/
|
|
290
|
-
export declare function createChatClientOptions<const TTools extends ReadonlyArray<AnyClientTool
|
|
358
|
+
export declare function createChatClientOptions<const TTools extends ReadonlyArray<AnyClientTool>, TContext = unknown>(options: ChatClientOptions<TTools, TContext>): ChatClientOptions<TTools, TContext>;
|
|
291
359
|
/**
|
|
292
360
|
* Extract the message type from chat options
|
|
293
361
|
*
|
|
@@ -302,4 +370,4 @@ export declare function createChatClientOptions<const TTools extends ReadonlyArr
|
|
|
302
370
|
* // MyMessages is now Array<UIMessage<[typeof myTool1, typeof myTool2]>>
|
|
303
371
|
* ```
|
|
304
372
|
*/
|
|
305
|
-
export type InferChatMessages<T> = T extends ChatClientOptions<infer TTools> ? Array<UIMessage<TTools>> : never;
|
|
373
|
+
export type InferChatMessages<T> = T extends ChatClientOptions<infer TTools, any> ? Array<UIMessage<TTools>> : never;
|