@tanstack/ai 0.43.1 → 0.44.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.
Files changed (69) hide show
  1. package/dist/esm/activities/chat/messages.js +21 -8
  2. package/dist/esm/activities/chat/messages.js.map +1 -1
  3. package/dist/esm/activities/embed/adapter.d.ts +69 -0
  4. package/dist/esm/activities/embed/adapter.js +23 -0
  5. package/dist/esm/activities/embed/adapter.js.map +1 -0
  6. package/dist/esm/activities/embed/index.d.ts +117 -0
  7. package/dist/esm/activities/embed/index.js +166 -0
  8. package/dist/esm/activities/embed/index.js.map +1 -0
  9. package/dist/esm/activities/error-payload.d.ts +8 -0
  10. package/dist/esm/activities/error-payload.js +29 -17
  11. package/dist/esm/activities/error-payload.js.map +1 -1
  12. package/dist/esm/activities/generateAudio/index.d.ts +12 -0
  13. package/dist/esm/activities/generateAudio/index.js +19 -6
  14. package/dist/esm/activities/generateAudio/index.js.map +1 -1
  15. package/dist/esm/activities/generateImage/index.d.ts +12 -0
  16. package/dist/esm/activities/generateImage/index.js +21 -7
  17. package/dist/esm/activities/generateImage/index.js.map +1 -1
  18. package/dist/esm/activities/generateSpeech/index.d.ts +17 -1
  19. package/dist/esm/activities/generateSpeech/index.js +19 -6
  20. package/dist/esm/activities/generateSpeech/index.js.map +1 -1
  21. package/dist/esm/activities/generateTranscription/index.d.ts +17 -1
  22. package/dist/esm/activities/generateTranscription/index.js +19 -6
  23. package/dist/esm/activities/generateTranscription/index.js.map +1 -1
  24. package/dist/esm/activities/generateVideo/index.d.ts +18 -0
  25. package/dist/esm/activities/generateVideo/index.js +54 -15
  26. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  27. package/dist/esm/activities/index.d.ts +8 -2
  28. package/dist/esm/activities/index.js +11 -7
  29. package/dist/esm/activities/middleware/types.d.ts +1 -1
  30. package/dist/esm/activities/rerank/adapter.d.ts +63 -0
  31. package/dist/esm/activities/rerank/adapter.js +23 -0
  32. package/dist/esm/activities/rerank/adapter.js.map +1 -0
  33. package/dist/esm/activities/rerank/index.d.ts +92 -0
  34. package/dist/esm/activities/rerank/index.js +163 -0
  35. package/dist/esm/activities/rerank/index.js.map +1 -0
  36. package/dist/esm/activities/summarize/index.d.ts +17 -1
  37. package/dist/esm/activities/summarize/index.js +19 -5
  38. package/dist/esm/activities/summarize/index.js.map +1 -1
  39. package/dist/esm/index.d.ts +7 -2
  40. package/dist/esm/index.js +5 -1
  41. package/dist/esm/middlewares/otel.js +20 -2
  42. package/dist/esm/middlewares/otel.js.map +1 -1
  43. package/dist/esm/types.d.ts +195 -0
  44. package/dist/esm/utilities/activity-abort.d.ts +53 -0
  45. package/dist/esm/utilities/activity-abort.js +150 -0
  46. package/dist/esm/utilities/activity-abort.js.map +1 -0
  47. package/dist/esm/utilities/embedding-input.d.ts +32 -0
  48. package/dist/esm/utilities/embedding-input.js +61 -0
  49. package/dist/esm/utilities/embedding-input.js.map +1 -0
  50. package/package.json +3 -3
  51. package/src/activities/chat/messages.ts +30 -1
  52. package/src/activities/embed/adapter.ts +112 -0
  53. package/src/activities/embed/index.ts +318 -0
  54. package/src/activities/error-payload.ts +41 -9
  55. package/src/activities/generateAudio/index.ts +47 -5
  56. package/src/activities/generateImage/index.ts +48 -5
  57. package/src/activities/generateSpeech/index.ts +52 -9
  58. package/src/activities/generateTranscription/index.ts +52 -9
  59. package/src/activities/generateVideo/index.ts +131 -33
  60. package/src/activities/index.ts +44 -0
  61. package/src/activities/middleware/types.ts +2 -0
  62. package/src/activities/rerank/adapter.ts +90 -0
  63. package/src/activities/rerank/index.ts +302 -0
  64. package/src/activities/summarize/index.ts +59 -19
  65. package/src/index.ts +19 -0
  66. package/src/middlewares/otel.ts +38 -3
  67. package/src/types.ts +219 -0
  68. package/src/utilities/activity-abort.ts +197 -0
  69. package/src/utilities/embedding-input.ts +83 -0
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Shared abort/timeout composition for media (and summarize) activities.
3
+ *
4
+ * Callers pass optional `timeout` and/or `abortSignal` on activity options.
5
+ * Core composes them into one effective signal, races the adapter call so a
6
+ * hung provider still rejects, clears timeout resources on settle, and
7
+ * classifies aborts so lifecycle middleware gets `onAbort` rather than
8
+ * `onError`.
9
+ */
10
+ /**
11
+ * Combine two optional AbortSignals into one that aborts when either does.
12
+ * Returns the other signal directly when one is absent or already aborted.
13
+ * First abort wins and preserves its reason.
14
+ *
15
+ * Manual implementation — `AbortSignal.any` requires Node >= 20.3.
16
+ */
17
+ export declare function combineAbortSignals(a: AbortSignal | undefined, b: AbortSignal | undefined): AbortSignal | undefined;
18
+ /** Normalize an abort reason into an Error the activity can reject with. */
19
+ export declare function toAbortError(reason: unknown): Error;
20
+ export interface ActivityAbortControls {
21
+ /** Effective signal, or `undefined` when neither timeout nor caller signal. */
22
+ signal: AbortSignal | undefined;
23
+ /** Clear the timeout timer if one was set. Idempotent. */
24
+ clear: () => void;
25
+ }
26
+ /**
27
+ * Compose an activity-level timeout with a caller AbortSignal.
28
+ *
29
+ * - No SDK-wide default timeout; omit both for unlimited wait.
30
+ * - First of caller cancellation or timeout wins and keeps its reason.
31
+ * - Call `clear()` when the activity settles (success or failure) so timers
32
+ * do not leak.
33
+ */
34
+ export declare function createActivityAbortControls(options: {
35
+ abortSignal?: AbortSignal;
36
+ timeout?: number;
37
+ }): ActivityAbortControls;
38
+ /**
39
+ * Reject when `signal` aborts, even if the underlying promise ignores it.
40
+ * Ensures activity-level timeouts work for adapters that do not yet forward
41
+ * the signal to the provider SDK.
42
+ *
43
+ * When the signal wins, the adapter promise is observed with an empty handler
44
+ * so a later settle cannot surface as an unhandled rejection.
45
+ */
46
+ export declare function raceWithAbort<T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T>;
47
+ /**
48
+ * Whether a thrown value (and optional effective signal) should route to
49
+ * middleware `onAbort` instead of `onError`.
50
+ */
51
+ export declare function isActivityAbortError(error: unknown, signal?: AbortSignal): boolean;
52
+ /** Best-effort string reason for {@link GenerationAbortInfo}. */
53
+ export declare function abortReasonMessage(error: unknown, signal?: AbortSignal): string | undefined;
@@ -0,0 +1,150 @@
1
+ //#region src/utilities/activity-abort.ts
2
+ /**
3
+ * Shared abort/timeout composition for media (and summarize) activities.
4
+ *
5
+ * Callers pass optional `timeout` and/or `abortSignal` on activity options.
6
+ * Core composes them into one effective signal, races the adapter call so a
7
+ * hung provider still rejects, clears timeout resources on settle, and
8
+ * classifies aborts so lifecycle middleware gets `onAbort` rather than
9
+ * `onError`.
10
+ */
11
+ var ABORT_ERROR_NAMES = /* @__PURE__ */ new Set([
12
+ "AbortError",
13
+ "TimeoutError",
14
+ "APIUserAbortError",
15
+ "RequestAbortedError"
16
+ ]);
17
+ /**
18
+ * Combine two optional AbortSignals into one that aborts when either does.
19
+ * Returns the other signal directly when one is absent or already aborted.
20
+ * First abort wins and preserves its reason.
21
+ *
22
+ * Manual implementation — `AbortSignal.any` requires Node >= 20.3.
23
+ */
24
+ function combineAbortSignals(a, b) {
25
+ if (!a) return b;
26
+ if (!b) return a;
27
+ if (a.aborted) return a;
28
+ if (b.aborted) return b;
29
+ const controller = new AbortController();
30
+ const onAbort = (source) => () => {
31
+ controller.abort(source.reason);
32
+ };
33
+ a.addEventListener("abort", onAbort(a), { once: true });
34
+ b.addEventListener("abort", onAbort(b), { once: true });
35
+ return controller.signal;
36
+ }
37
+ function createTimeoutReason(ms) {
38
+ if (typeof DOMException !== "undefined") return new DOMException(`Activity timed out after ${ms}ms`, "TimeoutError");
39
+ const err = /* @__PURE__ */ new Error(`Activity timed out after ${ms}ms`);
40
+ err.name = "TimeoutError";
41
+ return err;
42
+ }
43
+ /** Normalize an abort reason into an Error the activity can reject with. */
44
+ function toAbortError(reason) {
45
+ if (reason instanceof Error) return reason;
46
+ if (typeof reason === "string" && reason.length > 0) {
47
+ const err = new Error(reason);
48
+ err.name = "AbortError";
49
+ return err;
50
+ }
51
+ const err = /* @__PURE__ */ new Error("The operation was aborted");
52
+ err.name = "AbortError";
53
+ return err;
54
+ }
55
+ /**
56
+ * Compose an activity-level timeout with a caller AbortSignal.
57
+ *
58
+ * - No SDK-wide default timeout; omit both for unlimited wait.
59
+ * - First of caller cancellation or timeout wins and keeps its reason.
60
+ * - Call `clear()` when the activity settles (success or failure) so timers
61
+ * do not leak.
62
+ */
63
+ function createActivityAbortControls(options) {
64
+ let timeoutId;
65
+ let timeoutSignal;
66
+ if (options.timeout !== void 0) {
67
+ if (!Number.isFinite(options.timeout) || options.timeout < 0) throw new Error(`Invalid activity timeout: expected a non-negative finite number, got ${String(options.timeout)}`);
68
+ const controller = new AbortController();
69
+ timeoutSignal = controller.signal;
70
+ const ms = options.timeout;
71
+ timeoutId = setTimeout(() => {
72
+ controller.abort(createTimeoutReason(ms));
73
+ }, ms);
74
+ }
75
+ return {
76
+ signal: combineAbortSignals(options.abortSignal, timeoutSignal),
77
+ clear: () => {
78
+ if (timeoutId !== void 0) {
79
+ clearTimeout(timeoutId);
80
+ timeoutId = void 0;
81
+ }
82
+ }
83
+ };
84
+ }
85
+ /**
86
+ * Reject when `signal` aborts, even if the underlying promise ignores it.
87
+ * Ensures activity-level timeouts work for adapters that do not yet forward
88
+ * the signal to the provider SDK.
89
+ *
90
+ * When the signal wins, the adapter promise is observed with an empty handler
91
+ * so a later settle cannot surface as an unhandled rejection.
92
+ */
93
+ function raceWithAbort(promise, signal) {
94
+ if (!signal) return promise;
95
+ const swallow = () => {
96
+ promise.then(() => void 0, () => void 0);
97
+ };
98
+ if (signal.aborted) {
99
+ swallow();
100
+ return Promise.reject(toAbortError(signal.reason));
101
+ }
102
+ return new Promise((resolve, reject) => {
103
+ let settled = false;
104
+ const onAbort = () => {
105
+ if (settled) return;
106
+ settled = true;
107
+ cleanup();
108
+ swallow();
109
+ reject(toAbortError(signal.reason));
110
+ };
111
+ const cleanup = () => {
112
+ signal.removeEventListener("abort", onAbort);
113
+ };
114
+ signal.addEventListener("abort", onAbort, { once: true });
115
+ promise.then((value) => {
116
+ if (settled) return;
117
+ settled = true;
118
+ cleanup();
119
+ resolve(value);
120
+ }, (error) => {
121
+ if (settled) return;
122
+ settled = true;
123
+ cleanup();
124
+ reject(error);
125
+ });
126
+ });
127
+ }
128
+ /**
129
+ * Whether a thrown value (and optional effective signal) should route to
130
+ * middleware `onAbort` instead of `onError`.
131
+ */
132
+ function isActivityAbortError(error, signal) {
133
+ if (signal?.aborted) return true;
134
+ if (!error || typeof error !== "object") return false;
135
+ const name = error.name;
136
+ return typeof name === "string" && ABORT_ERROR_NAMES.has(name);
137
+ }
138
+ /** Best-effort string reason for {@link GenerationAbortInfo}. */
139
+ function abortReasonMessage(error, signal) {
140
+ if (signal?.reason !== void 0) {
141
+ if (typeof signal.reason === "string") return signal.reason;
142
+ if (signal.reason instanceof Error) return signal.reason.message;
143
+ }
144
+ if (error instanceof Error) return error.message;
145
+ if (typeof error === "string") return error;
146
+ }
147
+ //#endregion
148
+ export { abortReasonMessage, combineAbortSignals, createActivityAbortControls, isActivityAbortError, raceWithAbort, toAbortError };
149
+
150
+ //# sourceMappingURL=activity-abort.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity-abort.js","names":[],"sources":["../../../src/utilities/activity-abort.ts"],"sourcesContent":["/**\n * Shared abort/timeout composition for media (and summarize) activities.\n *\n * Callers pass optional `timeout` and/or `abortSignal` on activity options.\n * Core composes them into one effective signal, races the adapter call so a\n * hung provider still rejects, clears timeout resources on settle, and\n * classifies aborts so lifecycle middleware gets `onAbort` rather than\n * `onError`.\n */\n\nconst ABORT_ERROR_NAMES = new Set([\n 'AbortError',\n 'TimeoutError',\n 'APIUserAbortError',\n 'RequestAbortedError',\n])\n\n/**\n * Combine two optional AbortSignals into one that aborts when either does.\n * Returns the other signal directly when one is absent or already aborted.\n * First abort wins and preserves its reason.\n *\n * Manual implementation — `AbortSignal.any` requires Node >= 20.3.\n */\nexport function combineAbortSignals(\n a: AbortSignal | undefined,\n b: AbortSignal | undefined,\n): AbortSignal | undefined {\n if (!a) return b\n if (!b) return a\n if (a.aborted) return a\n if (b.aborted) return b\n const controller = new AbortController()\n const onAbort = (source: AbortSignal) => () => {\n controller.abort(source.reason)\n }\n a.addEventListener('abort', onAbort(a), { once: true })\n b.addEventListener('abort', onAbort(b), { once: true })\n return controller.signal\n}\n\nfunction createTimeoutReason(ms: number): Error {\n if (typeof DOMException !== 'undefined') {\n return new DOMException(`Activity timed out after ${ms}ms`, 'TimeoutError')\n }\n const err = new Error(`Activity timed out after ${ms}ms`)\n err.name = 'TimeoutError'\n return err\n}\n\n/** Normalize an abort reason into an Error the activity can reject with. */\nexport function toAbortError(reason: unknown): Error {\n if (reason instanceof Error) return reason\n if (typeof reason === 'string' && reason.length > 0) {\n const err = new Error(reason)\n err.name = 'AbortError'\n return err\n }\n const err = new Error('The operation was aborted')\n err.name = 'AbortError'\n return err\n}\n\nexport interface ActivityAbortControls {\n /** Effective signal, or `undefined` when neither timeout nor caller signal. */\n signal: AbortSignal | undefined\n /** Clear the timeout timer if one was set. Idempotent. */\n clear: () => void\n}\n\n/**\n * Compose an activity-level timeout with a caller AbortSignal.\n *\n * - No SDK-wide default timeout; omit both for unlimited wait.\n * - First of caller cancellation or timeout wins and keeps its reason.\n * - Call `clear()` when the activity settles (success or failure) so timers\n * do not leak.\n */\nexport function createActivityAbortControls(options: {\n abortSignal?: AbortSignal\n timeout?: number\n}): ActivityAbortControls {\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n let timeoutSignal: AbortSignal | undefined\n\n if (options.timeout !== undefined) {\n if (!Number.isFinite(options.timeout) || options.timeout < 0) {\n throw new Error(\n `Invalid activity timeout: expected a non-negative finite number, got ${String(options.timeout)}`,\n )\n }\n const controller = new AbortController()\n timeoutSignal = controller.signal\n const ms = options.timeout\n timeoutId = setTimeout(() => {\n controller.abort(createTimeoutReason(ms))\n }, ms)\n }\n\n const signal = combineAbortSignals(options.abortSignal, timeoutSignal)\n\n return {\n signal,\n clear: () => {\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId)\n timeoutId = undefined\n }\n },\n }\n}\n\n/**\n * Reject when `signal` aborts, even if the underlying promise ignores it.\n * Ensures activity-level timeouts work for adapters that do not yet forward\n * the signal to the provider SDK.\n *\n * When the signal wins, the adapter promise is observed with an empty handler\n * so a later settle cannot surface as an unhandled rejection.\n */\nexport function raceWithAbort<T>(\n promise: Promise<T>,\n signal: AbortSignal | undefined,\n): Promise<T> {\n if (!signal) return promise\n\n const swallow = () => {\n // Observe the adapter promise without acting on its outcome so a late\n // reject after we already aborted cannot become an unhandled rejection.\n promise.then(\n () => undefined,\n () => undefined,\n )\n }\n\n if (signal.aborted) {\n swallow()\n return Promise.reject(toAbortError(signal.reason))\n }\n\n return new Promise<T>((resolve, reject) => {\n let settled = false\n const onAbort = () => {\n if (settled) return\n settled = true\n cleanup()\n swallow()\n reject(toAbortError(signal.reason))\n }\n const cleanup = () => {\n signal.removeEventListener('abort', onAbort)\n }\n signal.addEventListener('abort', onAbort, { once: true })\n promise.then(\n (value) => {\n if (settled) return\n settled = true\n cleanup()\n resolve(value)\n },\n (error: unknown) => {\n if (settled) return\n settled = true\n cleanup()\n reject(error)\n },\n )\n })\n}\n\n/**\n * Whether a thrown value (and optional effective signal) should route to\n * middleware `onAbort` instead of `onError`.\n */\nexport function isActivityAbortError(\n error: unknown,\n signal?: AbortSignal,\n): boolean {\n if (signal?.aborted) return true\n if (!error || typeof error !== 'object') return false\n const name = (error as { name?: unknown }).name\n return typeof name === 'string' && ABORT_ERROR_NAMES.has(name)\n}\n\n/** Best-effort string reason for {@link GenerationAbortInfo}. */\nexport function abortReasonMessage(\n error: unknown,\n signal?: AbortSignal,\n): string | undefined {\n if (signal?.reason !== undefined) {\n if (typeof signal.reason === 'string') return signal.reason\n if (signal.reason instanceof Error) return signal.reason.message\n }\n if (error instanceof Error) return error.message\n if (typeof error === 'string') return error\n return undefined\n}\n"],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import { EmbeddingInputItem, ImagePart } from '../types.js';
2
+ /**
3
+ * One embedding input item resolved into its text and image constituents.
4
+ * Produced by {@link resolveEmbeddingInput}; adapters map each entry onto
5
+ * one provider-native input (one vector per entry).
6
+ */
7
+ export interface ResolvedEmbeddingItem {
8
+ /** Text contents of the item, in order (empty for image-only items) */
9
+ texts: Array<string>;
10
+ /** Image parts of the item, in order (empty for text-only items) */
11
+ images: Array<ImagePart>;
12
+ }
13
+ /**
14
+ * Resolve each embedding input item into its text and image constituents,
15
+ * preserving input order (result[i] corresponds to input[i] and to the
16
+ * vector at index i).
17
+ */
18
+ export declare function resolveEmbeddingInput(input: Array<EmbeddingInputItem>): Array<ResolvedEmbeddingItem>;
19
+ /**
20
+ * Extract plain text inputs for a text-only embedding model, throwing a
21
+ * uniform error if any item carries an image. The per-model modality typing
22
+ * rejects these at compile time; this guard covers untyped/dynamic callers.
23
+ */
24
+ export declare function requireTextOnlyEmbeddingInput(input: Array<EmbeddingInputItem>, provider: string, model: string): Array<string>;
25
+ /**
26
+ * Count text-only and image-carrying items for observability events. Never
27
+ * exposes input content.
28
+ */
29
+ export declare function countEmbeddingInputModalities(input: Array<EmbeddingInputItem>): {
30
+ textInputCount: number;
31
+ imageInputCount: number;
32
+ };
@@ -0,0 +1,61 @@
1
+ //#region src/utilities/embedding-input.ts
2
+ function resolveItem(item) {
3
+ if (typeof item === "string") return {
4
+ texts: [item],
5
+ images: []
6
+ };
7
+ if (Array.isArray(item)) {
8
+ const resolved = {
9
+ texts: [],
10
+ images: []
11
+ };
12
+ for (const part of item) if (part.type === "text") resolved.texts.push(part.content);
13
+ else resolved.images.push(part);
14
+ return resolved;
15
+ }
16
+ if (item.type === "text") return {
17
+ texts: [item.content],
18
+ images: []
19
+ };
20
+ return {
21
+ texts: [],
22
+ images: [item]
23
+ };
24
+ }
25
+ /**
26
+ * Resolve each embedding input item into its text and image constituents,
27
+ * preserving input order (result[i] corresponds to input[i] and to the
28
+ * vector at index i).
29
+ */
30
+ function resolveEmbeddingInput(input) {
31
+ return input.map(resolveItem);
32
+ }
33
+ /**
34
+ * Extract plain text inputs for a text-only embedding model, throwing a
35
+ * uniform error if any item carries an image. The per-model modality typing
36
+ * rejects these at compile time; this guard covers untyped/dynamic callers.
37
+ */
38
+ function requireTextOnlyEmbeddingInput(input, provider, model) {
39
+ return resolveEmbeddingInput(input).map((item, index) => {
40
+ if (item.images.length > 0) throw new Error(`${provider} model "${model}" only supports text embedding inputs; input item at index ${index} contains an image part`);
41
+ return item.texts.join("\n");
42
+ });
43
+ }
44
+ /**
45
+ * Count text-only and image-carrying items for observability events. Never
46
+ * exposes input content.
47
+ */
48
+ function countEmbeddingInputModalities(input) {
49
+ let textInputCount = 0;
50
+ let imageInputCount = 0;
51
+ for (const item of resolveEmbeddingInput(input)) if (item.images.length > 0) imageInputCount++;
52
+ else textInputCount++;
53
+ return {
54
+ textInputCount,
55
+ imageInputCount
56
+ };
57
+ }
58
+ //#endregion
59
+ export { countEmbeddingInputModalities, requireTextOnlyEmbeddingInput, resolveEmbeddingInput };
60
+
61
+ //# sourceMappingURL=embedding-input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embedding-input.js","names":[],"sources":["../../../src/utilities/embedding-input.ts"],"sourcesContent":["import type { EmbeddingInputItem, ImagePart } from '../types'\n\n/**\n * One embedding input item resolved into its text and image constituents.\n * Produced by {@link resolveEmbeddingInput}; adapters map each entry onto\n * one provider-native input (one vector per entry).\n */\nexport interface ResolvedEmbeddingItem {\n /** Text contents of the item, in order (empty for image-only items) */\n texts: Array<string>\n /** Image parts of the item, in order (empty for text-only items) */\n images: Array<ImagePart>\n}\n\nfunction resolveItem(item: EmbeddingInputItem): ResolvedEmbeddingItem {\n if (typeof item === 'string') {\n return { texts: [item], images: [] }\n }\n // A nested array is a fused item: its parts embed together into one vector.\n if (Array.isArray(item)) {\n const resolved: ResolvedEmbeddingItem = { texts: [], images: [] }\n for (const part of item) {\n if (part.type === 'text') {\n resolved.texts.push(part.content)\n } else {\n resolved.images.push(part)\n }\n }\n return resolved\n }\n if (item.type === 'text') {\n return { texts: [item.content], images: [] }\n }\n return { texts: [], images: [item] }\n}\n\n/**\n * Resolve each embedding input item into its text and image constituents,\n * preserving input order (result[i] corresponds to input[i] and to the\n * vector at index i).\n */\nexport function resolveEmbeddingInput(\n input: Array<EmbeddingInputItem>,\n): Array<ResolvedEmbeddingItem> {\n return input.map(resolveItem)\n}\n\n/**\n * Extract plain text inputs for a text-only embedding model, throwing a\n * uniform error if any item carries an image. The per-model modality typing\n * rejects these at compile time; this guard covers untyped/dynamic callers.\n */\nexport function requireTextOnlyEmbeddingInput(\n input: Array<EmbeddingInputItem>,\n provider: string,\n model: string,\n): Array<string> {\n return resolveEmbeddingInput(input).map((item, index) => {\n if (item.images.length > 0) {\n throw new Error(\n `${provider} model \"${model}\" only supports text embedding inputs; ` +\n `input item at index ${index} contains an image part`,\n )\n }\n return item.texts.join('\\n')\n })\n}\n\n/**\n * Count text-only and image-carrying items for observability events. Never\n * exposes input content.\n */\nexport function countEmbeddingInputModalities(\n input: Array<EmbeddingInputItem>,\n): { textInputCount: number; imageInputCount: number } {\n let textInputCount = 0\n let imageInputCount = 0\n for (const item of resolveEmbeddingInput(input)) {\n if (item.images.length > 0) imageInputCount++\n else textInputCount++\n }\n return { textInputCount, imageInputCount }\n}\n"],"mappings":";AAcA,SAAS,YAAY,MAAiD;CACpE,IAAI,OAAO,SAAS,UAClB,OAAO;EAAE,OAAO,CAAC,IAAI;EAAG,QAAQ,CAAC;CAAE;CAGrC,IAAI,MAAM,QAAQ,IAAI,GAAG;EACvB,MAAM,WAAkC;GAAE,OAAO,CAAC;GAAG,QAAQ,CAAC;EAAE;EAChE,KAAK,MAAM,QAAQ,MACjB,IAAI,KAAK,SAAS,QAChB,SAAS,MAAM,KAAK,KAAK,OAAO;OAEhC,SAAS,OAAO,KAAK,IAAI;EAG7B,OAAO;CACT;CACA,IAAI,KAAK,SAAS,QAChB,OAAO;EAAE,OAAO,CAAC,KAAK,OAAO;EAAG,QAAQ,CAAC;CAAE;CAE7C,OAAO;EAAE,OAAO,CAAC;EAAG,QAAQ,CAAC,IAAI;CAAE;AACrC;;;;;;AAOA,SAAgB,sBACd,OAC8B;CAC9B,OAAO,MAAM,IAAI,WAAW;AAC9B;;;;;;AAOA,SAAgB,8BACd,OACA,UACA,OACe;CACf,OAAO,sBAAsB,KAAK,CAAC,CAAC,KAAK,MAAM,UAAU;EACvD,IAAI,KAAK,OAAO,SAAS,GACvB,MAAM,IAAI,MACR,GAAG,SAAS,UAAU,MAAM,6DACH,MAAM,wBACjC;EAEF,OAAO,KAAK,MAAM,KAAK,IAAI;CAC7B,CAAC;AACH;;;;;AAMA,SAAgB,8BACd,OACqD;CACrD,IAAI,iBAAiB;CACrB,IAAI,kBAAkB;CACtB,KAAK,MAAM,QAAQ,sBAAsB,KAAK,GAC5C,IAAI,KAAK,OAAO,SAAS,GAAG;MACvB;CAEP,OAAO;EAAE;EAAgB;CAAgB;AAC3C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai",
3
- "version": "0.43.1",
3
+ "version": "0.44.0",
4
4
  "description": "Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -80,8 +80,8 @@
80
80
  "@ag-ui/core": "0.1.1-canary.beta.0",
81
81
  "@standard-schema/spec": "^1.1.0",
82
82
  "partial-json": "^0.1.7",
83
- "@tanstack/ai-event-client": "0.7.0",
84
- "@tanstack/ai-utils": "0.4.0"
83
+ "@tanstack/ai-event-client": "^0.8.0",
84
+ "@tanstack/ai-utils": "^0.4.0"
85
85
  },
86
86
  "peerDependencies": {
87
87
  "@opentelemetry/api": ">=1.9.0"
@@ -184,8 +184,12 @@ function buildUserOrToolMessage(uiMessage: UIMessage): ModelMessage {
184
184
  }
185
185
 
186
186
  return {
187
+ id: uiMessage.id,
187
188
  role: uiMessage.role as 'user' | 'assistant' | 'tool',
188
189
  content: collapseContentParts(contentParts),
190
+ ...(uiMessage.createdAt !== undefined && {
191
+ createdAt: uiMessage.createdAt,
192
+ }),
189
193
  }
190
194
  }
191
195
 
@@ -228,6 +232,8 @@ function isToolCallIncluded(part: ToolCallPart): boolean {
228
232
  * result is emitted as a tool message.
229
233
  */
230
234
  function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
235
+ // A single UI message can fan out into several model messages. Keep the
236
+ // shared UI id on each one so persistence can retain the original identity.
231
237
  const messageList: Array<ModelMessage> = []
232
238
  let current = createSegment()
233
239
  let pendingThinking: Array<{ content: string; signature?: string }> = []
@@ -244,10 +250,14 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
244
250
 
245
251
  if (hasContent || hasToolCalls) {
246
252
  messageList.push({
253
+ id: uiMessage.id,
247
254
  role: 'assistant',
248
255
  content,
249
256
  ...(hasToolCalls && { toolCalls: current.toolCalls }),
250
257
  ...(pendingThinking.length > 0 && { thinking: pendingThinking }),
258
+ ...(uiMessage.createdAt !== undefined && {
259
+ createdAt: uiMessage.createdAt,
260
+ }),
251
261
  })
252
262
  pendingThinking = []
253
263
  }
@@ -288,9 +298,13 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
288
298
  !emittedToolResultIds.has(part.toolCallId)
289
299
  ) {
290
300
  messageList.push({
301
+ id: uiMessage.id,
291
302
  role: 'tool',
292
303
  content: part.content,
293
304
  toolCallId: part.toolCallId,
305
+ ...(uiMessage.createdAt !== undefined && {
306
+ createdAt: uiMessage.createdAt,
307
+ }),
294
308
  })
295
309
  emittedToolResultIds.add(part.toolCallId)
296
310
  }
@@ -347,9 +361,13 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
347
361
  // emit the concrete output regardless of approval metadata.
348
362
  if (part.output !== undefined && !emittedToolResultIds.has(part.id)) {
349
363
  messageList.push({
364
+ id: uiMessage.id,
350
365
  role: 'tool',
351
366
  content: normalizeToolResult(part.output),
352
367
  toolCallId: part.id,
368
+ ...(uiMessage.createdAt !== undefined && {
369
+ createdAt: uiMessage.createdAt,
370
+ }),
353
371
  })
354
372
  emittedToolResultIds.add(part.id)
355
373
  }
@@ -363,6 +381,7 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
363
381
  ) {
364
382
  const approved = part.approval.approved
365
383
  messageList.push({
384
+ id: uiMessage.id,
366
385
  role: 'tool',
367
386
  content: JSON.stringify({
368
387
  approved,
@@ -372,6 +391,9 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
372
391
  : 'User denied this action',
373
392
  }),
374
393
  toolCallId: part.id,
394
+ ...(uiMessage.createdAt !== undefined && {
395
+ createdAt: uiMessage.createdAt,
396
+ }),
375
397
  })
376
398
  emittedToolResultIds.add(part.id)
377
399
  }
@@ -380,8 +402,12 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
380
402
  // If no messages were produced (e.g., empty parts), emit a minimal assistant message
381
403
  if (messageList.length === 0) {
382
404
  messageList.push({
405
+ id: uiMessage.id,
383
406
  role: 'assistant',
384
407
  content: null,
408
+ ...(uiMessage.createdAt !== undefined && {
409
+ createdAt: uiMessage.createdAt,
410
+ }),
385
411
  })
386
412
  }
387
413
 
@@ -469,6 +495,9 @@ export function modelMessageToUIMessage(
469
495
  id: id || generateMessageId(),
470
496
  role: modelMessage.role === 'tool' ? 'assistant' : modelMessage.role,
471
497
  parts,
498
+ ...(modelMessage.createdAt !== undefined && {
499
+ createdAt: modelMessage.createdAt,
500
+ }),
472
501
  }
473
502
  }
474
503
 
@@ -659,7 +688,7 @@ export function normalizeToUIMessage(
659
688
  // ModelMessage - convert to UIMessage
660
689
  return {
661
690
  ...modelMessageToUIMessage(message, generateId()),
662
- createdAt: new Date(),
691
+ createdAt: message.createdAt ?? new Date(),
663
692
  }
664
693
  }
665
694
  }
@@ -0,0 +1,112 @@
1
+ import type {
2
+ EmbeddingModelInputModalitiesByName,
3
+ EmbeddingOptions,
4
+ EmbeddingResult,
5
+ } from '../../types'
6
+
7
+ /**
8
+ * Configuration for embedding adapter instances
9
+ */
10
+ export interface EmbeddingAdapterConfig {
11
+ apiKey?: string
12
+ baseUrl?: string
13
+ timeout?: number
14
+ maxRetries?: number
15
+ headers?: Record<string, string>
16
+ }
17
+
18
+ /**
19
+ * Embedding adapter interface with pre-resolved generics.
20
+ *
21
+ * An adapter is created by a provider function: `provider('model')` → `adapter`
22
+ * All type resolution happens at the provider call site, not in this interface.
23
+ *
24
+ * Generic parameters:
25
+ * - TModel: The specific model name (e.g., 'text-embedding-3-small')
26
+ * - TProviderOptions: Base provider-specific options (already resolved)
27
+ * - TModelProviderOptionsByName: Map from model name to its specific provider options
28
+ * - TModelInputModalitiesByName: Map from model name to the input modalities it
29
+ * accepts (constrains the `input` item types at compile time)
30
+ */
31
+ export interface EmbeddingAdapter<
32
+ TModel extends string = string,
33
+ TProviderOptions extends object = Record<string, unknown>,
34
+ TModelProviderOptionsByName extends Record<string, any> = Record<string, any>,
35
+ TModelInputModalitiesByName extends EmbeddingModelInputModalitiesByName =
36
+ EmbeddingModelInputModalitiesByName,
37
+ > {
38
+ /** Discriminator for adapter kind */
39
+ readonly kind: 'embedding'
40
+ /** Adapter name identifier */
41
+ readonly name: string
42
+ /** The model this adapter is configured for */
43
+ readonly model: TModel
44
+
45
+ /**
46
+ * @internal Type-only properties for inference. Not assigned at runtime.
47
+ */
48
+ '~types': {
49
+ providerOptions: TProviderOptions
50
+ modelProviderOptionsByName: TModelProviderOptionsByName
51
+ modelInputModalitiesByName: TModelInputModalitiesByName
52
+ }
53
+
54
+ /**
55
+ * Generate embeddings for the input items (one vector per item)
56
+ */
57
+ createEmbeddings: (
58
+ options: EmbeddingOptions<TProviderOptions>,
59
+ ) => Promise<EmbeddingResult>
60
+ }
61
+
62
+ /**
63
+ * An EmbeddingAdapter with any/unknown type parameters.
64
+ * Useful as a constraint in generic functions and interfaces.
65
+ */
66
+ export type AnyEmbeddingAdapter = EmbeddingAdapter<any, any, any, any>
67
+
68
+ /**
69
+ * Abstract base class for embedding adapters.
70
+ * Extend this class to implement an embedding adapter for a specific provider.
71
+ *
72
+ * Generic parameters match EmbeddingAdapter - all pre-resolved by the provider function.
73
+ */
74
+ export abstract class BaseEmbeddingAdapter<
75
+ TModel extends string = string,
76
+ TProviderOptions extends object = Record<string, unknown>,
77
+ TModelProviderOptionsByName extends Record<string, any> = Record<string, any>,
78
+ TModelInputModalitiesByName extends EmbeddingModelInputModalitiesByName =
79
+ EmbeddingModelInputModalitiesByName,
80
+ > implements EmbeddingAdapter<
81
+ TModel,
82
+ TProviderOptions,
83
+ TModelProviderOptionsByName,
84
+ TModelInputModalitiesByName
85
+ > {
86
+ readonly kind = 'embedding' as const
87
+ abstract readonly name: string
88
+ readonly model: TModel
89
+
90
+ // Type-only property - never assigned at runtime
91
+ declare '~types': {
92
+ providerOptions: TProviderOptions
93
+ modelProviderOptionsByName: TModelProviderOptionsByName
94
+ modelInputModalitiesByName: TModelInputModalitiesByName
95
+ }
96
+
97
+ protected config: EmbeddingAdapterConfig
98
+
99
+ constructor(model: TModel, config: EmbeddingAdapterConfig = {}) {
100
+ this.config = config
101
+ this.model = model
102
+ }
103
+
104
+ abstract createEmbeddings(
105
+ options: EmbeddingOptions<TProviderOptions>,
106
+ ): Promise<EmbeddingResult>
107
+
108
+ protected generateId(prefix?: string): string {
109
+ const p = prefix ?? this.name
110
+ return `${p}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
111
+ }
112
+ }