@tanstack/ai 0.36.0 → 0.37.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 (205) hide show
  1. package/dist/esm/activities/chat/adapter.d.ts +163 -0
  2. package/dist/esm/activities/chat/adapter.js +17 -0
  3. package/dist/esm/activities/chat/adapter.js.map +1 -0
  4. package/dist/esm/activities/chat/agent-loop-strategies.d.ts +59 -0
  5. package/dist/esm/activities/chat/agent-loop-strategies.js +23 -0
  6. package/dist/esm/activities/chat/agent-loop-strategies.js.map +1 -0
  7. package/dist/esm/activities/chat/index.d.ts +270 -0
  8. package/dist/esm/activities/chat/index.js +1724 -0
  9. package/dist/esm/activities/chat/index.js.map +1 -0
  10. package/dist/esm/activities/chat/mcp/manager.d.ts +25 -0
  11. package/dist/esm/activities/chat/mcp/manager.js +71 -0
  12. package/dist/esm/activities/chat/mcp/manager.js.map +1 -0
  13. package/dist/esm/activities/chat/mcp/types.d.ts +56 -0
  14. package/dist/esm/activities/chat/messages.d.ts +78 -0
  15. package/dist/esm/activities/chat/messages.js +374 -0
  16. package/dist/esm/activities/chat/messages.js.map +1 -0
  17. package/dist/esm/activities/chat/middleware/builder.d.ts +46 -0
  18. package/dist/esm/activities/chat/middleware/builder.js +17 -0
  19. package/dist/esm/activities/chat/middleware/builder.js.map +1 -0
  20. package/dist/esm/activities/chat/middleware/capabilities.d.ts +93 -0
  21. package/dist/esm/activities/chat/middleware/capabilities.js +45 -0
  22. package/dist/esm/activities/chat/middleware/capabilities.js.map +1 -0
  23. package/dist/esm/activities/chat/middleware/compose.d.ts +87 -0
  24. package/dist/esm/activities/chat/middleware/compose.js +510 -0
  25. package/dist/esm/activities/chat/middleware/compose.js.map +1 -0
  26. package/dist/esm/activities/chat/middleware/define.d.ts +20 -0
  27. package/dist/esm/activities/chat/middleware/define.js +7 -0
  28. package/dist/esm/activities/chat/middleware/define.js.map +1 -0
  29. package/dist/esm/activities/chat/middleware/index.d.ts +10 -0
  30. package/dist/esm/activities/chat/middleware/tool-cache-middleware.d.ts +89 -0
  31. package/dist/esm/activities/chat/middleware/tool-cache-middleware.js +76 -0
  32. package/dist/esm/activities/chat/middleware/tool-cache-middleware.js.map +1 -0
  33. package/dist/esm/activities/chat/middleware/types.d.ts +405 -0
  34. package/dist/esm/activities/chat/middleware/validate.d.ts +19 -0
  35. package/dist/esm/activities/chat/middleware/validate.js +30 -0
  36. package/dist/esm/activities/chat/middleware/validate.js.map +1 -0
  37. package/dist/esm/activities/chat/runtime-context-types.d.ts +43 -0
  38. package/dist/esm/activities/chat/stream/index.d.ts +11 -0
  39. package/dist/esm/activities/chat/stream/json-parser.d.ts +38 -0
  40. package/dist/esm/activities/chat/stream/json-parser.js +28 -0
  41. package/dist/esm/activities/chat/stream/json-parser.js.map +1 -0
  42. package/dist/esm/activities/chat/stream/message-updaters.d.ts +81 -0
  43. package/dist/esm/activities/chat/stream/message-updaters.js +253 -0
  44. package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -0
  45. package/dist/esm/activities/chat/stream/processor.d.ts +439 -0
  46. package/dist/esm/activities/chat/stream/processor.js +1426 -0
  47. package/dist/esm/activities/chat/stream/processor.js.map +1 -0
  48. package/dist/esm/activities/chat/stream/strategies.d.ts +43 -0
  49. package/dist/esm/activities/chat/stream/strategies.js +54 -0
  50. package/dist/esm/activities/chat/stream/strategies.js.map +1 -0
  51. package/dist/esm/activities/chat/stream/types.d.ts +90 -0
  52. package/dist/esm/activities/chat/tools/lazy-tool-manager.d.ts +82 -0
  53. package/dist/esm/activities/chat/tools/lazy-tool-manager.js +194 -0
  54. package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -0
  55. package/dist/esm/activities/chat/tools/lazy-tools.d.ts +15 -0
  56. package/dist/esm/activities/chat/tools/lazy-tools.js +16 -0
  57. package/dist/esm/activities/chat/tools/lazy-tools.js.map +1 -0
  58. package/dist/esm/activities/chat/tools/schema-converter.d.ts +140 -0
  59. package/dist/esm/activities/chat/tools/schema-converter.js +167 -0
  60. package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -0
  61. package/dist/esm/activities/chat/tools/tool-calls.d.ts +140 -0
  62. package/dist/esm/activities/chat/tools/tool-calls.js +512 -0
  63. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -0
  64. package/dist/esm/activities/chat/tools/tool-definition.d.ts +135 -0
  65. package/dist/esm/activities/chat/tools/tool-definition.js +25 -0
  66. package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -0
  67. package/dist/esm/activities/error-payload.d.ts +30 -0
  68. package/dist/esm/activities/error-payload.js +54 -0
  69. package/dist/esm/activities/error-payload.js.map +1 -0
  70. package/dist/esm/activities/generateAudio/adapter.d.ts +62 -0
  71. package/dist/esm/activities/generateAudio/adapter.js +16 -0
  72. package/dist/esm/activities/generateAudio/adapter.js.map +1 -0
  73. package/dist/esm/activities/generateAudio/index.d.ts +85 -0
  74. package/dist/esm/activities/generateAudio/index.js +114 -0
  75. package/dist/esm/activities/generateAudio/index.js.map +1 -0
  76. package/dist/esm/activities/generateImage/adapter.d.ts +78 -0
  77. package/dist/esm/activities/generateImage/adapter.js +16 -0
  78. package/dist/esm/activities/generateImage/adapter.js.map +1 -0
  79. package/dist/esm/activities/generateImage/index.d.ts +134 -0
  80. package/dist/esm/activities/generateImage/index.js +121 -0
  81. package/dist/esm/activities/generateImage/index.js.map +1 -0
  82. package/dist/esm/activities/generateSpeech/adapter.d.ts +62 -0
  83. package/dist/esm/activities/generateSpeech/adapter.js +16 -0
  84. package/dist/esm/activities/generateSpeech/adapter.js.map +1 -0
  85. package/dist/esm/activities/generateSpeech/index.d.ts +96 -0
  86. package/dist/esm/activities/generateSpeech/index.js +119 -0
  87. package/dist/esm/activities/generateSpeech/index.js.map +1 -0
  88. package/dist/esm/activities/generateTranscription/adapter.d.ts +62 -0
  89. package/dist/esm/activities/generateTranscription/adapter.js +16 -0
  90. package/dist/esm/activities/generateTranscription/adapter.js.map +1 -0
  91. package/dist/esm/activities/generateTranscription/index.d.ts +109 -0
  92. package/dist/esm/activities/generateTranscription/index.js +109 -0
  93. package/dist/esm/activities/generateTranscription/index.js.map +1 -0
  94. package/dist/esm/activities/generateVideo/adapter.d.ts +145 -0
  95. package/dist/esm/activities/generateVideo/adapter.js +30 -0
  96. package/dist/esm/activities/generateVideo/adapter.js.map +1 -0
  97. package/dist/esm/activities/generateVideo/index.d.ts +223 -0
  98. package/dist/esm/activities/generateVideo/index.js +295 -0
  99. package/dist/esm/activities/generateVideo/index.js.map +1 -0
  100. package/dist/esm/activities/generateVideo/snap.d.ts +14 -0
  101. package/dist/esm/activities/generateVideo/snap.js +54 -0
  102. package/dist/esm/activities/generateVideo/snap.js.map +1 -0
  103. package/dist/esm/activities/index.d.ts +27 -0
  104. package/dist/esm/activities/index.js +43 -0
  105. package/dist/esm/activities/index.js.map +1 -0
  106. package/dist/esm/activities/middleware/index.d.ts +2 -0
  107. package/dist/esm/activities/middleware/run.d.ts +20 -0
  108. package/dist/esm/activities/middleware/run.js +42 -0
  109. package/dist/esm/activities/middleware/run.js.map +1 -0
  110. package/dist/esm/activities/middleware/types.d.ts +118 -0
  111. package/dist/esm/activities/stream-generation-result.d.ts +15 -0
  112. package/dist/esm/activities/stream-generation-result.js +49 -0
  113. package/dist/esm/activities/stream-generation-result.js.map +1 -0
  114. package/dist/esm/activities/summarize/adapter.d.ts +74 -0
  115. package/dist/esm/activities/summarize/adapter.js +16 -0
  116. package/dist/esm/activities/summarize/adapter.js.map +1 -0
  117. package/dist/esm/activities/summarize/chat-stream-summarize.d.ts +45 -0
  118. package/dist/esm/activities/summarize/chat-stream-summarize.js +212 -0
  119. package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -0
  120. package/dist/esm/activities/summarize/index.d.ts +108 -0
  121. package/dist/esm/activities/summarize/index.js +113 -0
  122. package/dist/esm/activities/summarize/index.js.map +1 -0
  123. package/dist/esm/adapter-internals.d.ts +5 -0
  124. package/dist/esm/adapter-internals.js +10 -0
  125. package/dist/esm/adapter-internals.js.map +1 -0
  126. package/dist/esm/client.d.ts +44 -0
  127. package/dist/esm/client.js +67 -0
  128. package/dist/esm/client.js.map +1 -0
  129. package/dist/esm/extend-adapter.d.ts +152 -0
  130. package/dist/esm/extend-adapter.js +21 -0
  131. package/dist/esm/extend-adapter.js.map +1 -0
  132. package/dist/esm/index.d.ts +45 -0
  133. package/dist/esm/index.js +109 -0
  134. package/dist/esm/index.js.map +1 -0
  135. package/dist/esm/logger/console-logger.d.ts +29 -0
  136. package/dist/esm/logger/console-logger.js +83 -0
  137. package/dist/esm/logger/console-logger.js.map +1 -0
  138. package/dist/esm/logger/internal-logger.d.ts +41 -0
  139. package/dist/esm/logger/internal-logger.js +86 -0
  140. package/dist/esm/logger/internal-logger.js.map +1 -0
  141. package/dist/esm/logger/resolve.d.ts +14 -0
  142. package/dist/esm/logger/resolve.js +54 -0
  143. package/dist/esm/logger/resolve.js.map +1 -0
  144. package/dist/esm/logger/types.d.ts +75 -0
  145. package/dist/esm/middlewares/content-guard.d.ts +77 -0
  146. package/dist/esm/middlewares/content-guard.js +156 -0
  147. package/dist/esm/middlewares/content-guard.js.map +1 -0
  148. package/dist/esm/middlewares/index.d.ts +2 -0
  149. package/dist/esm/middlewares/index.js +7 -0
  150. package/dist/esm/middlewares/index.js.map +1 -0
  151. package/dist/esm/middlewares/otel.d.ts +81 -0
  152. package/dist/esm/middlewares/otel.js +745 -0
  153. package/dist/esm/middlewares/otel.js.map +1 -0
  154. package/dist/esm/middlewares/tool-cache.d.ts +1 -0
  155. package/dist/esm/middlewares/usage-attributes.d.ts +24 -0
  156. package/dist/esm/middlewares/usage-attributes.js +43 -0
  157. package/dist/esm/middlewares/usage-attributes.js.map +1 -0
  158. package/dist/esm/realtime/index.d.ts +28 -0
  159. package/dist/esm/realtime/index.js +8 -0
  160. package/dist/esm/realtime/index.js.map +1 -0
  161. package/dist/esm/realtime/types.d.ts +282 -0
  162. package/dist/esm/stream-to-response.d.ts +102 -0
  163. package/dist/esm/stream-to-response.js +121 -0
  164. package/dist/esm/stream-to-response.js.map +1 -0
  165. package/dist/esm/strip-to-spec-middleware.d.ts +18 -0
  166. package/dist/esm/strip-to-spec-middleware.js +20 -0
  167. package/dist/esm/strip-to-spec-middleware.js.map +1 -0
  168. package/dist/esm/system-prompts.d.ts +66 -0
  169. package/dist/esm/system-prompts.js +23 -0
  170. package/dist/esm/system-prompts.js.map +1 -0
  171. package/dist/esm/tool-registry.d.ts +81 -0
  172. package/dist/esm/tool-registry.js +49 -0
  173. package/dist/esm/tool-registry.js.map +1 -0
  174. package/dist/esm/tools/provider-tool.d.ts +30 -0
  175. package/dist/esm/tools/provider-tool.js +7 -0
  176. package/dist/esm/tools/provider-tool.js.map +1 -0
  177. package/dist/esm/types.d.ts +1594 -0
  178. package/dist/esm/utilities/ag-ui-wire.d.ts +44 -0
  179. package/dist/esm/utilities/ag-ui-wire.js +107 -0
  180. package/dist/esm/utilities/ag-ui-wire.js.map +1 -0
  181. package/dist/esm/utilities/chat-params.d.ts +85 -0
  182. package/dist/esm/utilities/chat-params.js +100 -0
  183. package/dist/esm/utilities/chat-params.js.map +1 -0
  184. package/dist/esm/utilities/errors.d.ts +13 -0
  185. package/dist/esm/utilities/errors.js +22 -0
  186. package/dist/esm/utilities/errors.js.map +1 -0
  187. package/dist/esm/utilities/media-prompt.d.ts +35 -0
  188. package/dist/esm/utilities/media-prompt.js +43 -0
  189. package/dist/esm/utilities/media-prompt.js.map +1 -0
  190. package/dist/esm/utilities/numbers.d.ts +8 -0
  191. package/dist/esm/utilities/numbers.js +12 -0
  192. package/dist/esm/utilities/numbers.js.map +1 -0
  193. package/dist/esm/utilities/sampling-keys.d.ts +20 -0
  194. package/dist/esm/utilities/sampling-keys.js +20 -0
  195. package/dist/esm/utilities/sampling-keys.js.map +1 -0
  196. package/dist/esm/utilities/tool-result.d.ts +21 -0
  197. package/dist/esm/utilities/tool-result.js +37 -0
  198. package/dist/esm/utilities/tool-result.js.map +1 -0
  199. package/dist/esm/utilities/usage.d.ts +31 -0
  200. package/dist/esm/utilities/usage.js +11 -0
  201. package/dist/esm/utilities/usage.js.map +1 -0
  202. package/dist/esm/utils.d.ts +17 -0
  203. package/dist/esm/utils.js +20 -0
  204. package/dist/esm/utils.js.map +1 -0
  205. package/package.json +3 -3
@@ -0,0 +1,745 @@
1
+ import { SpanKind, SpanStatusCode, trace, context } from "@opentelemetry/api";
2
+ import { MAX_TOKENS_KEYS, NESTED_MAX_TOKENS_KEY } from "../utilities/sampling-keys.js";
3
+ import { firstNumber } from "../utilities/numbers.js";
4
+ import { errorMessage, errorTypeName } from "../utilities/errors.js";
5
+ import { usageAttributes } from "./usage-attributes.js";
6
+ const OPERATION_NAME = {
7
+ chat: "chat",
8
+ image: "image_generation",
9
+ video: "video_generation",
10
+ audio: "audio_generation",
11
+ tts: "text_to_speech",
12
+ transcription: "transcription"
13
+ };
14
+ const stateByCtx = /* @__PURE__ */ new WeakMap();
15
+ const DEFAULT_MAX_CONTENT_LENGTH = 1e5;
16
+ const REDACTION_FAILED_SENTINEL = "[redaction_failed]";
17
+ function serializeContent(content) {
18
+ if (typeof content === "string") return content;
19
+ if (!Array.isArray(content)) return "";
20
+ const parts = [];
21
+ for (const part of content) {
22
+ if (!part || typeof part !== "object") continue;
23
+ const type = part.type;
24
+ switch (type) {
25
+ case "text":
26
+ parts.push(
27
+ (part.text ?? part.content ?? "").toString()
28
+ );
29
+ break;
30
+ case "image":
31
+ parts.push("[image]");
32
+ break;
33
+ case "audio":
34
+ parts.push("[audio]");
35
+ break;
36
+ case "video":
37
+ parts.push("[video]");
38
+ break;
39
+ case "document":
40
+ parts.push("[document]");
41
+ break;
42
+ case void 0:
43
+ parts.push("[unknown]");
44
+ break;
45
+ default:
46
+ parts.push(`[${type}]`);
47
+ }
48
+ }
49
+ return parts.join(" ");
50
+ }
51
+ function messageEventName(role) {
52
+ switch (role) {
53
+ case "user":
54
+ return "gen_ai.user.message";
55
+ case "assistant":
56
+ return "gen_ai.assistant.message";
57
+ case "tool":
58
+ return "gen_ai.tool.message";
59
+ case "system":
60
+ return "gen_ai.system.message";
61
+ default:
62
+ return `gen_ai.${role}.message`;
63
+ }
64
+ }
65
+ function safeCall(label, fn) {
66
+ try {
67
+ return fn();
68
+ } catch (err) {
69
+ console.warn(`[otelMiddleware] ${label} failed`, err);
70
+ return void 0;
71
+ }
72
+ }
73
+ function otelMiddleware(options) {
74
+ const {
75
+ tracer,
76
+ meter,
77
+ captureContent = false,
78
+ redact = (s) => s,
79
+ maxContentLength = DEFAULT_MAX_CONTENT_LENGTH,
80
+ spanNameFormatter,
81
+ attributeEnricher,
82
+ onBeforeSpanStart,
83
+ onSpanEnd
84
+ } = options;
85
+ const durationHistogram = meter?.createHistogram(
86
+ "gen_ai.client.operation.duration",
87
+ {
88
+ description: "GenAI client operation duration",
89
+ unit: "s"
90
+ }
91
+ );
92
+ const tokenHistogram = meter?.createHistogram("gen_ai.client.token.usage", {
93
+ description: "GenAI client token usage",
94
+ unit: "{token}"
95
+ });
96
+ const redactContent = (text) => {
97
+ try {
98
+ return redact(text);
99
+ } catch (err) {
100
+ console.warn("[otelMiddleware] otel.redact failed", err);
101
+ return REDACTION_FAILED_SENTINEL;
102
+ }
103
+ };
104
+ const appendAssistantText = (state, delta) => {
105
+ if (maxContentLength > 0) {
106
+ if (state.assistantTextBufferTruncated) return;
107
+ const remaining = maxContentLength - state.assistantTextBuffer.length;
108
+ if (remaining <= 0) {
109
+ state.assistantTextBufferTruncated = true;
110
+ state.assistantTextBuffer += "…";
111
+ return;
112
+ }
113
+ if (delta.length > remaining) {
114
+ state.assistantTextBuffer += delta.slice(0, remaining) + "…";
115
+ state.assistantTextBufferTruncated = true;
116
+ return;
117
+ }
118
+ }
119
+ state.assistantTextBuffer += delta;
120
+ };
121
+ const closeIterationSpan = (state, ctx) => {
122
+ if (!state.currentIterationSpan) return;
123
+ const span = state.currentIterationSpan;
124
+ const iteration = state.iterationCount - 1;
125
+ safeCall(
126
+ "otel.onSpanEnd",
127
+ () => onSpanEnd?.({ kind: "iteration", ctx, iteration }, span)
128
+ );
129
+ span.end();
130
+ state.currentIterationSpan = null;
131
+ };
132
+ const mediaSpans = /* @__PURE__ */ new WeakMap();
133
+ const recordMediaDuration = (ctx, durationMs, errorType) => {
134
+ if (!durationHistogram) return;
135
+ durationHistogram.record(durationMs / 1e3, {
136
+ "gen_ai.system": ctx.provider,
137
+ "gen_ai.operation.name": OPERATION_NAME[ctx.activity],
138
+ "gen_ai.request.model": ctx.model,
139
+ ...errorType ? { "error.type": errorType } : {}
140
+ });
141
+ };
142
+ const startMediaSpan = (ctx) => {
143
+ safeCall("otel.onStart", () => {
144
+ const operationName = OPERATION_NAME[ctx.activity];
145
+ const info = { kind: "generation", ctx };
146
+ const name = safeCall("otel.spanNameFormatter", () => spanNameFormatter?.(info)) ?? `${operationName} ${ctx.model}`;
147
+ const baseOptions = {
148
+ kind: SpanKind.CLIENT,
149
+ attributes: {
150
+ "gen_ai.system": ctx.provider,
151
+ "gen_ai.operation.name": operationName,
152
+ "gen_ai.request.model": ctx.model
153
+ }
154
+ };
155
+ const spanOptions = safeCall(
156
+ "otel.onBeforeSpanStart",
157
+ () => onBeforeSpanStart?.(info, baseOptions)
158
+ ) ?? baseOptions;
159
+ const span = tracer.startSpan(name, spanOptions);
160
+ const enriched = safeCall(
161
+ "otel.attributeEnricher",
162
+ () => attributeEnricher?.(info)
163
+ );
164
+ if (enriched) span.setAttributes(enriched);
165
+ mediaSpans.set(ctx, span);
166
+ });
167
+ };
168
+ const endMediaSpan = (ctx, finalize) => {
169
+ const span = mediaSpans.get(ctx);
170
+ mediaSpans.delete(ctx);
171
+ if (!span) return;
172
+ finalize(span);
173
+ safeCall(
174
+ "otel.onSpanEnd",
175
+ () => onSpanEnd?.({ kind: "generation", ctx }, span)
176
+ );
177
+ span.end();
178
+ };
179
+ return {
180
+ name: "otel",
181
+ onStart(ctx) {
182
+ if (ctx.activity !== "chat") {
183
+ startMediaSpan(ctx);
184
+ return;
185
+ }
186
+ const chatCtx = ctx;
187
+ safeCall("otel.onStart", () => {
188
+ const info = { kind: "chat", ctx: chatCtx };
189
+ const name = safeCall("otel.spanNameFormatter", () => spanNameFormatter?.(info)) ?? `chat ${chatCtx.model}`;
190
+ const baseOptions = {
191
+ kind: SpanKind.INTERNAL,
192
+ attributes: {
193
+ "gen_ai.system": chatCtx.provider,
194
+ "gen_ai.request.model": chatCtx.model
195
+ // NOTE: `gen_ai.operation.name` is deliberately NOT set on the
196
+ // root span. The root represents a `chat()` invocation that may
197
+ // span multiple model calls; only iteration spans correspond to
198
+ // a single chat operation. Backends that map `operation.name=chat`
199
+ // to a "generation" event (e.g. PostHog LLM Analytics) would
200
+ // otherwise emit a duplicate generation for the wrapper span.
201
+ }
202
+ };
203
+ const spanOptions = safeCall(
204
+ "otel.onBeforeSpanStart",
205
+ () => onBeforeSpanStart?.(info, baseOptions)
206
+ ) ?? baseOptions;
207
+ const rootSpan = tracer.startSpan(name, spanOptions);
208
+ const enriched = safeCall(
209
+ "otel.attributeEnricher",
210
+ () => attributeEnricher?.(info)
211
+ );
212
+ if (enriched) rootSpan.setAttributes(enriched);
213
+ stateByCtx.set(chatCtx, {
214
+ rootSpan,
215
+ currentIterationSpan: null,
216
+ toolSpans: /* @__PURE__ */ new Map(),
217
+ iterationCount: 0,
218
+ assistantTextBuffer: "",
219
+ assistantTextBufferTruncated: false,
220
+ startTime: Date.now(),
221
+ lastFinishReason: null
222
+ });
223
+ });
224
+ },
225
+ onConfig(ctx, config) {
226
+ if (ctx.phase !== "beforeModel") return;
227
+ safeCall("otel.onConfig", () => {
228
+ const state = stateByCtx.get(ctx);
229
+ if (!state) return;
230
+ closeIterationSpan(state, ctx);
231
+ const info = {
232
+ kind: "iteration",
233
+ ctx,
234
+ iteration: ctx.iteration
235
+ };
236
+ const name = safeCall("otel.spanNameFormatter", () => spanNameFormatter?.(info)) ?? `chat ${ctx.model} #${ctx.iteration}`;
237
+ const baseAttrs = {
238
+ "gen_ai.system": ctx.provider,
239
+ "gen_ai.operation.name": "chat",
240
+ "gen_ai.request.model": ctx.model,
241
+ "tanstack.ai.iteration": ctx.iteration
242
+ };
243
+ const sampling = config.modelOptions ?? {};
244
+ const nestedOptions = sampling["options"] && typeof sampling["options"] === "object" ? sampling["options"] : void 0;
245
+ const samplingTemperature = firstNumber(
246
+ sampling["temperature"],
247
+ nestedOptions?.["temperature"]
248
+ );
249
+ const samplingTopP = firstNumber(
250
+ sampling["top_p"],
251
+ sampling["topP"],
252
+ nestedOptions?.["top_p"]
253
+ );
254
+ const samplingMaxTokens = firstNumber(
255
+ ...MAX_TOKENS_KEYS.map((k) => sampling[k]),
256
+ nestedOptions?.[NESTED_MAX_TOKENS_KEY]
257
+ );
258
+ if (samplingTemperature !== void 0)
259
+ baseAttrs["gen_ai.request.temperature"] = samplingTemperature;
260
+ if (samplingTopP !== void 0)
261
+ baseAttrs["gen_ai.request.top_p"] = samplingTopP;
262
+ if (samplingMaxTokens !== void 0)
263
+ baseAttrs["gen_ai.request.max_tokens"] = samplingMaxTokens;
264
+ const baseOptions = {
265
+ kind: SpanKind.CLIENT,
266
+ attributes: baseAttrs
267
+ };
268
+ const spanOptions = safeCall(
269
+ "otel.onBeforeSpanStart",
270
+ () => onBeforeSpanStart?.(info, baseOptions)
271
+ ) ?? baseOptions;
272
+ const parentCtx = trace.setSpan(
273
+ context.active(),
274
+ state.rootSpan
275
+ );
276
+ let iterSpan;
277
+ context.with(parentCtx, () => {
278
+ iterSpan = tracer.startSpan(name, spanOptions, parentCtx);
279
+ });
280
+ const enriched = safeCall(
281
+ "otel.attributeEnricher",
282
+ () => attributeEnricher?.(info)
283
+ );
284
+ if (enriched) iterSpan.setAttributes(enriched);
285
+ state.currentIterationSpan = iterSpan;
286
+ state.assistantTextBuffer = "";
287
+ state.assistantTextBufferTruncated = false;
288
+ if (captureContent) {
289
+ const systemPromptContents = config.systemPrompts.map(
290
+ (p) => typeof p === "string" ? p : p.content
291
+ );
292
+ const systemPromptMetadata = config.systemPrompts.map(
293
+ (p) => typeof p === "string" || p.metadata === void 0 ? null : p.metadata
294
+ );
295
+ if (systemPromptMetadata.some((m) => m !== null)) {
296
+ iterSpan.setAttribute(
297
+ "tanstack.ai.system_prompt.metadata",
298
+ JSON.stringify(systemPromptMetadata)
299
+ );
300
+ }
301
+ for (const sys of systemPromptContents) {
302
+ iterSpan.addEvent("gen_ai.system.message", {
303
+ content: redactContent(sys)
304
+ });
305
+ }
306
+ for (const m of config.messages) {
307
+ const body = serializeContent(m.content);
308
+ if (body.length === 0) continue;
309
+ iterSpan.addEvent(messageEventName(m.role), {
310
+ content: redactContent(body)
311
+ });
312
+ }
313
+ const inputMessages = [];
314
+ for (const sys of systemPromptContents) {
315
+ inputMessages.push({
316
+ role: "system",
317
+ content: redactContent(sys)
318
+ });
319
+ }
320
+ for (const m of config.messages) {
321
+ const body = serializeContent(m.content);
322
+ if (body.length === 0) continue;
323
+ inputMessages.push({
324
+ role: m.role,
325
+ content: redactContent(body)
326
+ });
327
+ }
328
+ if (inputMessages.length > 0) {
329
+ const inputJson = JSON.stringify(inputMessages);
330
+ iterSpan.setAttribute("gen_ai.input.messages", inputJson);
331
+ iterSpan.setAttribute("langfuse.observation.input", inputJson);
332
+ if (state.iterationCount === 0) {
333
+ state.rootSpan.setAttribute(
334
+ "langfuse.observation.input",
335
+ inputJson
336
+ );
337
+ state.rootSpan.setAttribute("langfuse.trace.input", inputJson);
338
+ }
339
+ }
340
+ }
341
+ state.iterationCount += 1;
342
+ });
343
+ return void 0;
344
+ },
345
+ onChunk(ctx, chunk) {
346
+ safeCall("otel.onChunk", () => {
347
+ const state = stateByCtx.get(ctx);
348
+ if (!state) return;
349
+ if (captureContent && chunk.type === "TEXT_MESSAGE_CONTENT") {
350
+ appendAssistantText(state, chunk.delta);
351
+ }
352
+ if (chunk.type !== "RUN_FINISHED") return;
353
+ if (chunk.finishReason) state.lastFinishReason = chunk.finishReason;
354
+ const span = state.currentIterationSpan;
355
+ if (!span) return;
356
+ if (chunk.finishReason) {
357
+ span.setAttribute("gen_ai.response.finish_reasons", [
358
+ chunk.finishReason
359
+ ]);
360
+ }
361
+ if (chunk.model) span.setAttribute("gen_ai.response.model", chunk.model);
362
+ if (chunk.usage) {
363
+ span.setAttributes(usageAttributes(chunk.usage));
364
+ }
365
+ if (captureContent && state.assistantTextBuffer.length > 0) {
366
+ const completion = redactContent(state.assistantTextBuffer);
367
+ const outputJson = JSON.stringify([
368
+ { role: "assistant", content: completion }
369
+ ]);
370
+ span.addEvent("gen_ai.choice", { content: completion });
371
+ span.setAttribute("gen_ai.output.messages", outputJson);
372
+ span.setAttribute("langfuse.observation.output", outputJson);
373
+ state.rootSpan.setAttribute("langfuse.observation.output", outputJson);
374
+ state.rootSpan.setAttribute("langfuse.trace.output", outputJson);
375
+ state.assistantTextBuffer = "";
376
+ state.assistantTextBufferTruncated = false;
377
+ }
378
+ });
379
+ return void 0;
380
+ },
381
+ onUsage(ctx, usage) {
382
+ if (ctx.activity !== "chat") {
383
+ safeCall("otel.onUsage", () => {
384
+ const span = mediaSpans.get(ctx);
385
+ if (span) span.setAttributes(usageAttributes(usage));
386
+ });
387
+ return;
388
+ }
389
+ const chatCtx = ctx;
390
+ safeCall("otel.onUsage", () => {
391
+ const state = stateByCtx.get(chatCtx);
392
+ if (!state) return;
393
+ if (tokenHistogram) {
394
+ const metricAttrs = {
395
+ "gen_ai.system": chatCtx.provider,
396
+ "gen_ai.operation.name": "chat",
397
+ "gen_ai.request.model": chatCtx.model
398
+ };
399
+ tokenHistogram.record(usage.promptTokens, {
400
+ ...metricAttrs,
401
+ "gen_ai.token.type": "input"
402
+ });
403
+ tokenHistogram.record(usage.completionTokens, {
404
+ ...metricAttrs,
405
+ "gen_ai.token.type": "output"
406
+ });
407
+ }
408
+ const span = state.currentIterationSpan ?? state.rootSpan;
409
+ span.setAttributes(usageAttributes(usage));
410
+ });
411
+ },
412
+ onBeforeToolCall(ctx, hookCtx) {
413
+ safeCall("otel.onBeforeToolCall", () => {
414
+ const state = stateByCtx.get(ctx);
415
+ if (!state) return;
416
+ const parent = state.currentIterationSpan ?? state.rootSpan;
417
+ const info = {
418
+ kind: "tool",
419
+ ctx,
420
+ toolName: hookCtx.toolName,
421
+ toolCallId: hookCtx.toolCallId,
422
+ iteration: state.iterationCount - 1
423
+ };
424
+ const name = safeCall("otel.spanNameFormatter", () => spanNameFormatter?.(info)) ?? `execute_tool ${hookCtx.toolName}`;
425
+ const baseAttrs = {
426
+ "gen_ai.tool.name": hookCtx.toolName,
427
+ "gen_ai.tool.call.id": hookCtx.toolCallId,
428
+ "gen_ai.tool.type": "function"
429
+ };
430
+ const baseOptions = {
431
+ kind: SpanKind.INTERNAL,
432
+ attributes: baseAttrs
433
+ };
434
+ const spanOptions = safeCall(
435
+ "otel.onBeforeSpanStart",
436
+ () => onBeforeSpanStart?.(info, baseOptions)
437
+ ) ?? baseOptions;
438
+ const parentCtx = trace.setSpan(context.active(), parent);
439
+ let toolSpan;
440
+ context.with(parentCtx, () => {
441
+ toolSpan = tracer.startSpan(name, spanOptions, parentCtx);
442
+ });
443
+ const enriched = safeCall(
444
+ "otel.attributeEnricher",
445
+ () => attributeEnricher?.(info)
446
+ );
447
+ if (enriched) toolSpan.setAttributes(enriched);
448
+ if (captureContent) {
449
+ const argsBody = typeof hookCtx.args === "string" ? hookCtx.args : safeCall(
450
+ "otel.serializeToolArgs",
451
+ () => JSON.stringify(hookCtx.args ?? null)
452
+ ) ?? "[unserializable_tool_args]";
453
+ const redactedArgs = redactContent(argsBody);
454
+ const toolInputJson = JSON.stringify([
455
+ { role: "tool", content: redactedArgs }
456
+ ]);
457
+ toolSpan.setAttribute("gen_ai.input.messages", toolInputJson);
458
+ toolSpan.setAttribute("langfuse.observation.input", toolInputJson);
459
+ }
460
+ state.toolSpans.set(hookCtx.toolCallId, {
461
+ span: toolSpan,
462
+ toolName: hookCtx.toolName
463
+ });
464
+ });
465
+ return void 0;
466
+ },
467
+ onAfterToolCall(ctx, info) {
468
+ safeCall("otel.onAfterToolCall", () => {
469
+ const state = stateByCtx.get(ctx);
470
+ if (!state) return;
471
+ const entry = state.toolSpans.get(info.toolCallId);
472
+ if (!entry) return;
473
+ const { span: toolSpan } = entry;
474
+ const outcome = info.ok ? "success" : "error";
475
+ toolSpan.setAttribute("tanstack.ai.tool.outcome", outcome);
476
+ if (!info.ok && info.error !== void 0) {
477
+ toolSpan.recordException(info.error);
478
+ const msg = errorMessage(info.error);
479
+ toolSpan.setStatus({
480
+ code: SpanStatusCode.ERROR,
481
+ ...msg !== void 0 && { message: msg }
482
+ });
483
+ }
484
+ if (captureContent) {
485
+ const body = typeof info.result === "string" ? info.result : safeCall(
486
+ "otel.serializeToolResult",
487
+ () => JSON.stringify(info.result ?? null)
488
+ ) ?? "[unserializable_tool_result]";
489
+ const redactedBody = redactContent(body);
490
+ if (state.currentIterationSpan) {
491
+ state.currentIterationSpan.addEvent("gen_ai.tool.message", {
492
+ content: redactedBody,
493
+ tool_call_id: info.toolCallId
494
+ });
495
+ }
496
+ const toolOutputJson = JSON.stringify([
497
+ { role: "tool", content: redactedBody }
498
+ ]);
499
+ toolSpan.setAttribute("gen_ai.output.messages", toolOutputJson);
500
+ toolSpan.setAttribute("langfuse.observation.output", toolOutputJson);
501
+ }
502
+ safeCall(
503
+ "otel.onSpanEnd",
504
+ () => onSpanEnd?.(
505
+ {
506
+ kind: "tool",
507
+ ctx,
508
+ toolName: info.toolName,
509
+ toolCallId: info.toolCallId,
510
+ iteration: state.iterationCount - 1
511
+ },
512
+ toolSpan
513
+ )
514
+ );
515
+ toolSpan.end();
516
+ state.toolSpans.delete(info.toolCallId);
517
+ });
518
+ },
519
+ onError(ctx, info) {
520
+ if (ctx.activity !== "chat") {
521
+ safeCall("otel.onError", () => {
522
+ const message = errorMessage(info.error);
523
+ endMediaSpan(ctx, (span) => {
524
+ span.recordException(info.error);
525
+ span.setStatus({
526
+ code: SpanStatusCode.ERROR,
527
+ ...message !== void 0 ? { message } : {}
528
+ });
529
+ });
530
+ recordMediaDuration(ctx, info.duration, errorTypeName(info.error));
531
+ });
532
+ return;
533
+ }
534
+ const chatCtx = ctx;
535
+ safeCall("otel.onError", () => {
536
+ const state = stateByCtx.get(chatCtx);
537
+ if (!state) return;
538
+ const errType = errorTypeName(info.error);
539
+ const message = errorMessage(info.error);
540
+ const statusMessage = message !== void 0 ? { message } : {};
541
+ const exception = info.error;
542
+ const iterationSpan = state.currentIterationSpan;
543
+ if (iterationSpan) {
544
+ iterationSpan.recordException(exception);
545
+ iterationSpan.setStatus({
546
+ code: SpanStatusCode.ERROR,
547
+ ...statusMessage
548
+ });
549
+ safeCall(
550
+ "otel.onSpanEnd",
551
+ () => onSpanEnd?.(
552
+ {
553
+ kind: "iteration",
554
+ ctx: chatCtx,
555
+ iteration: state.iterationCount - 1
556
+ },
557
+ iterationSpan
558
+ )
559
+ );
560
+ iterationSpan.end();
561
+ state.currentIterationSpan = null;
562
+ }
563
+ for (const [id, entry] of state.toolSpans) {
564
+ const { span, toolName } = entry;
565
+ span.recordException(exception);
566
+ span.setStatus({ code: SpanStatusCode.ERROR, ...statusMessage });
567
+ safeCall(
568
+ "otel.onSpanEnd",
569
+ () => onSpanEnd?.(
570
+ {
571
+ kind: "tool",
572
+ ctx: chatCtx,
573
+ toolCallId: id,
574
+ toolName,
575
+ iteration: state.iterationCount - 1
576
+ },
577
+ span
578
+ )
579
+ );
580
+ span.end();
581
+ state.toolSpans.delete(id);
582
+ }
583
+ state.rootSpan.recordException(exception);
584
+ state.rootSpan.setStatus({
585
+ code: SpanStatusCode.ERROR,
586
+ ...statusMessage
587
+ });
588
+ if (durationHistogram) {
589
+ durationHistogram.record(info.duration / 1e3, {
590
+ "gen_ai.system": chatCtx.provider,
591
+ "gen_ai.operation.name": "chat",
592
+ "gen_ai.request.model": chatCtx.model,
593
+ "error.type": errType
594
+ });
595
+ }
596
+ safeCall(
597
+ "otel.onSpanEnd",
598
+ () => onSpanEnd?.({ kind: "chat", ctx: chatCtx }, state.rootSpan)
599
+ );
600
+ state.rootSpan.end();
601
+ stateByCtx.delete(chatCtx);
602
+ });
603
+ },
604
+ onAbort(ctx, info) {
605
+ if (ctx.activity !== "chat") {
606
+ safeCall("otel.onAbort", () => {
607
+ endMediaSpan(ctx, (span) => {
608
+ span.setAttribute("tanstack.ai.completion.reason", "cancelled");
609
+ span.setStatus({
610
+ code: SpanStatusCode.ERROR,
611
+ message: info.reason ?? "cancelled"
612
+ });
613
+ });
614
+ recordMediaDuration(ctx, info.duration, "cancelled");
615
+ });
616
+ return;
617
+ }
618
+ const chatCtx = ctx;
619
+ safeCall("otel.onAbort", () => {
620
+ const state = stateByCtx.get(chatCtx);
621
+ if (!state) return;
622
+ const closeCancelled = (span) => {
623
+ span.setAttribute("tanstack.ai.completion.reason", "cancelled");
624
+ span.setStatus({ code: SpanStatusCode.ERROR, message: "cancelled" });
625
+ };
626
+ const iterationSpan = state.currentIterationSpan;
627
+ if (iterationSpan) {
628
+ closeCancelled(iterationSpan);
629
+ safeCall(
630
+ "otel.onSpanEnd",
631
+ () => onSpanEnd?.(
632
+ {
633
+ kind: "iteration",
634
+ ctx: chatCtx,
635
+ iteration: state.iterationCount - 1
636
+ },
637
+ iterationSpan
638
+ )
639
+ );
640
+ iterationSpan.end();
641
+ state.currentIterationSpan = null;
642
+ }
643
+ for (const [id, entry] of state.toolSpans) {
644
+ const { span, toolName } = entry;
645
+ closeCancelled(span);
646
+ safeCall(
647
+ "otel.onSpanEnd",
648
+ () => onSpanEnd?.(
649
+ {
650
+ kind: "tool",
651
+ ctx: chatCtx,
652
+ toolCallId: id,
653
+ toolName,
654
+ iteration: state.iterationCount - 1
655
+ },
656
+ span
657
+ )
658
+ );
659
+ span.end();
660
+ state.toolSpans.delete(id);
661
+ }
662
+ closeCancelled(state.rootSpan);
663
+ if (durationHistogram) {
664
+ durationHistogram.record(info.duration / 1e3, {
665
+ "gen_ai.system": chatCtx.provider,
666
+ "gen_ai.operation.name": "chat",
667
+ "gen_ai.request.model": chatCtx.model,
668
+ "error.type": "cancelled"
669
+ });
670
+ }
671
+ safeCall(
672
+ "otel.onSpanEnd",
673
+ () => onSpanEnd?.({ kind: "chat", ctx: chatCtx }, state.rootSpan)
674
+ );
675
+ state.rootSpan.end();
676
+ stateByCtx.delete(chatCtx);
677
+ });
678
+ },
679
+ onFinish(ctx, info) {
680
+ if (ctx.activity !== "chat") {
681
+ safeCall("otel.onFinish", () => {
682
+ endMediaSpan(ctx, (span) => {
683
+ if (info.usage) span.setAttributes(usageAttributes(info.usage));
684
+ });
685
+ recordMediaDuration(ctx, info.duration);
686
+ });
687
+ return;
688
+ }
689
+ const chatCtx = ctx;
690
+ safeCall("otel.onFinish", () => {
691
+ const state = stateByCtx.get(chatCtx);
692
+ if (!state) return;
693
+ for (const [id, entry] of state.toolSpans) {
694
+ const { span, toolName } = entry;
695
+ span.setAttribute("tanstack.ai.tool.outcome", "unknown");
696
+ safeCall(
697
+ "otel.onSpanEnd",
698
+ () => onSpanEnd?.(
699
+ {
700
+ kind: "tool",
701
+ ctx: chatCtx,
702
+ toolCallId: id,
703
+ toolName,
704
+ iteration: state.iterationCount - 1
705
+ },
706
+ span
707
+ )
708
+ );
709
+ span.end();
710
+ state.toolSpans.delete(id);
711
+ }
712
+ closeIterationSpan(state, chatCtx);
713
+ if (durationHistogram) {
714
+ durationHistogram.record(info.duration / 1e3, {
715
+ "gen_ai.system": chatCtx.provider,
716
+ "gen_ai.operation.name": "chat",
717
+ "gen_ai.request.model": chatCtx.model
718
+ });
719
+ }
720
+ if (info.usage) {
721
+ state.rootSpan.setAttributes(usageAttributes(info.usage));
722
+ }
723
+ if (state.lastFinishReason) {
724
+ state.rootSpan.setAttribute("gen_ai.response.finish_reasons", [
725
+ state.lastFinishReason
726
+ ]);
727
+ }
728
+ state.rootSpan.setAttribute(
729
+ "tanstack.ai.iterations",
730
+ state.iterationCount
731
+ );
732
+ safeCall(
733
+ "otel.onSpanEnd",
734
+ () => onSpanEnd?.({ kind: "chat", ctx: chatCtx }, state.rootSpan)
735
+ );
736
+ state.rootSpan.end();
737
+ stateByCtx.delete(chatCtx);
738
+ });
739
+ }
740
+ };
741
+ }
742
+ export {
743
+ otelMiddleware
744
+ };
745
+ //# sourceMappingURL=otel.js.map