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