@hebo-ai/gateway 0.6.2-rc0 → 0.6.2

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 (134) hide show
  1. package/README.md +3 -3
  2. package/dist/endpoints/chat-completions/converters.js +26 -21
  3. package/dist/endpoints/chat-completions/handler.js +2 -0
  4. package/dist/endpoints/chat-completions/otel.js +1 -1
  5. package/dist/endpoints/chat-completions/schema.d.ts +4 -18
  6. package/dist/endpoints/chat-completions/schema.js +14 -17
  7. package/dist/endpoints/embeddings/handler.js +2 -0
  8. package/dist/endpoints/embeddings/otel.js +5 -0
  9. package/dist/endpoints/embeddings/schema.d.ts +6 -0
  10. package/dist/endpoints/embeddings/schema.js +4 -1
  11. package/dist/endpoints/models/converters.js +3 -3
  12. package/dist/lifecycle.js +2 -2
  13. package/dist/logger/default.js +3 -3
  14. package/dist/logger/index.d.ts +2 -5
  15. package/dist/middleware/common.js +1 -0
  16. package/dist/middleware/utils.js +0 -3
  17. package/dist/models/amazon/middleware.js +8 -5
  18. package/dist/models/anthropic/middleware.js +13 -13
  19. package/dist/models/catalog.js +5 -1
  20. package/dist/models/cohere/middleware.js +7 -5
  21. package/dist/models/google/middleware.d.ts +1 -1
  22. package/dist/models/google/middleware.js +29 -25
  23. package/dist/models/openai/middleware.js +13 -9
  24. package/dist/models/voyage/middleware.js +2 -1
  25. package/dist/providers/bedrock/middleware.js +21 -23
  26. package/dist/providers/registry.js +3 -0
  27. package/dist/telemetry/fetch.js +7 -2
  28. package/dist/telemetry/gen-ai.js +15 -12
  29. package/dist/telemetry/memory.d.ts +1 -1
  30. package/dist/telemetry/memory.js +30 -14
  31. package/dist/telemetry/span.js +1 -1
  32. package/dist/telemetry/stream.js +30 -23
  33. package/dist/utils/env.js +4 -2
  34. package/dist/utils/preset.js +1 -0
  35. package/dist/utils/response.js +3 -1
  36. package/package.json +36 -50
  37. package/src/config.ts +0 -98
  38. package/src/endpoints/chat-completions/converters.test.ts +0 -631
  39. package/src/endpoints/chat-completions/converters.ts +0 -899
  40. package/src/endpoints/chat-completions/handler.test.ts +0 -391
  41. package/src/endpoints/chat-completions/handler.ts +0 -201
  42. package/src/endpoints/chat-completions/index.ts +0 -4
  43. package/src/endpoints/chat-completions/otel.test.ts +0 -315
  44. package/src/endpoints/chat-completions/otel.ts +0 -214
  45. package/src/endpoints/chat-completions/schema.ts +0 -364
  46. package/src/endpoints/embeddings/converters.ts +0 -51
  47. package/src/endpoints/embeddings/handler.test.ts +0 -133
  48. package/src/endpoints/embeddings/handler.ts +0 -137
  49. package/src/endpoints/embeddings/index.ts +0 -4
  50. package/src/endpoints/embeddings/otel.ts +0 -40
  51. package/src/endpoints/embeddings/schema.ts +0 -36
  52. package/src/endpoints/models/converters.ts +0 -56
  53. package/src/endpoints/models/handler.test.ts +0 -122
  54. package/src/endpoints/models/handler.ts +0 -37
  55. package/src/endpoints/models/index.ts +0 -3
  56. package/src/endpoints/models/schema.ts +0 -37
  57. package/src/errors/ai-sdk.ts +0 -99
  58. package/src/errors/gateway.ts +0 -17
  59. package/src/errors/openai.ts +0 -57
  60. package/src/errors/utils.ts +0 -47
  61. package/src/gateway.ts +0 -50
  62. package/src/index.ts +0 -19
  63. package/src/lifecycle.ts +0 -135
  64. package/src/logger/default.ts +0 -105
  65. package/src/logger/index.ts +0 -42
  66. package/src/middleware/common.test.ts +0 -215
  67. package/src/middleware/common.ts +0 -163
  68. package/src/middleware/debug.ts +0 -37
  69. package/src/middleware/matcher.ts +0 -161
  70. package/src/middleware/utils.ts +0 -34
  71. package/src/models/amazon/index.ts +0 -2
  72. package/src/models/amazon/middleware.test.ts +0 -133
  73. package/src/models/amazon/middleware.ts +0 -79
  74. package/src/models/amazon/presets.ts +0 -104
  75. package/src/models/anthropic/index.ts +0 -2
  76. package/src/models/anthropic/middleware.test.ts +0 -643
  77. package/src/models/anthropic/middleware.ts +0 -148
  78. package/src/models/anthropic/presets.ts +0 -191
  79. package/src/models/catalog.ts +0 -13
  80. package/src/models/cohere/index.ts +0 -2
  81. package/src/models/cohere/middleware.test.ts +0 -138
  82. package/src/models/cohere/middleware.ts +0 -76
  83. package/src/models/cohere/presets.ts +0 -186
  84. package/src/models/google/index.ts +0 -2
  85. package/src/models/google/middleware.test.ts +0 -298
  86. package/src/models/google/middleware.ts +0 -137
  87. package/src/models/google/presets.ts +0 -118
  88. package/src/models/meta/index.ts +0 -1
  89. package/src/models/meta/presets.ts +0 -143
  90. package/src/models/openai/index.ts +0 -2
  91. package/src/models/openai/middleware.test.ts +0 -189
  92. package/src/models/openai/middleware.ts +0 -103
  93. package/src/models/openai/presets.ts +0 -280
  94. package/src/models/types.ts +0 -114
  95. package/src/models/voyage/index.ts +0 -2
  96. package/src/models/voyage/middleware.test.ts +0 -28
  97. package/src/models/voyage/middleware.ts +0 -23
  98. package/src/models/voyage/presets.ts +0 -126
  99. package/src/providers/anthropic/canonical.ts +0 -17
  100. package/src/providers/anthropic/index.ts +0 -1
  101. package/src/providers/bedrock/canonical.ts +0 -87
  102. package/src/providers/bedrock/index.ts +0 -2
  103. package/src/providers/bedrock/middleware.test.ts +0 -303
  104. package/src/providers/bedrock/middleware.ts +0 -128
  105. package/src/providers/cohere/canonical.ts +0 -26
  106. package/src/providers/cohere/index.ts +0 -1
  107. package/src/providers/groq/canonical.ts +0 -21
  108. package/src/providers/groq/index.ts +0 -1
  109. package/src/providers/openai/canonical.ts +0 -16
  110. package/src/providers/openai/index.ts +0 -1
  111. package/src/providers/registry.test.ts +0 -44
  112. package/src/providers/registry.ts +0 -165
  113. package/src/providers/types.ts +0 -20
  114. package/src/providers/vertex/canonical.ts +0 -17
  115. package/src/providers/vertex/index.ts +0 -1
  116. package/src/providers/voyage/canonical.ts +0 -16
  117. package/src/providers/voyage/index.ts +0 -1
  118. package/src/telemetry/ai-sdk.ts +0 -46
  119. package/src/telemetry/baggage.ts +0 -27
  120. package/src/telemetry/fetch.ts +0 -62
  121. package/src/telemetry/gen-ai.ts +0 -113
  122. package/src/telemetry/http.ts +0 -62
  123. package/src/telemetry/index.ts +0 -1
  124. package/src/telemetry/memory.ts +0 -36
  125. package/src/telemetry/span.ts +0 -85
  126. package/src/telemetry/stream.ts +0 -64
  127. package/src/types.ts +0 -223
  128. package/src/utils/env.ts +0 -7
  129. package/src/utils/headers.ts +0 -27
  130. package/src/utils/preset.ts +0 -65
  131. package/src/utils/request.test.ts +0 -75
  132. package/src/utils/request.ts +0 -52
  133. package/src/utils/response.ts +0 -84
  134. package/src/utils/url.ts +0 -26
@@ -1,315 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import type { ChatCompletions, ChatCompletionsBody } from "./schema";
4
-
5
- import { getChatRequestAttributes, getChatResponseAttributes } from "./otel";
6
-
7
- describe("Chat Completions OTEL", () => {
8
- test("should map request metadata into per-key attributes", () => {
9
- const metadata = {
10
- tenant: "acme",
11
- "Org ID": "o-123",
12
- };
13
-
14
- const inputs: ChatCompletionsBody = {
15
- model: "openai/gpt-oss-20b",
16
- messages: [{ role: "user", content: "hi" }],
17
- metadata,
18
- };
19
-
20
- const attrs = getChatRequestAttributes(inputs, "recommended");
21
-
22
- expect(attrs["gen_ai.request.metadata"]).toBeUndefined();
23
- expect(attrs["gen_ai.request.metadata.tenant"]).toBe("acme");
24
- expect(attrs["gen_ai.request.metadata.Org ID"]).toBe("o-123");
25
- });
26
-
27
- test("should stringify each tool definition individually", () => {
28
- const tool1 = {
29
- type: "function" as const,
30
- function: {
31
- name: "get_weather",
32
- parameters: {
33
- type: "object",
34
- properties: {
35
- location: { type: "string" },
36
- },
37
- required: ["location"],
38
- },
39
- },
40
- };
41
-
42
- const tool2 = {
43
- type: "function" as const,
44
- function: {
45
- name: "get_time",
46
- parameters: {
47
- type: "object",
48
- properties: {
49
- timezone: { type: "string" },
50
- },
51
- },
52
- },
53
- };
54
-
55
- const inputs: ChatCompletionsBody = {
56
- model: "openai/gpt-oss-20b",
57
- messages: [{ role: "user", content: "hi" }],
58
- tools: [tool1, tool2],
59
- };
60
-
61
- const attrs = getChatRequestAttributes(inputs, "full");
62
-
63
- expect(attrs["gen_ai.tool.definitions"]).toEqual([
64
- JSON.stringify(tool1),
65
- JSON.stringify(tool2),
66
- ]);
67
- });
68
-
69
- test("should map assistant text content part arrays in request attributes", () => {
70
- const inputs: ChatCompletionsBody = {
71
- model: "openai/gpt-oss-20b",
72
- messages: [
73
- {
74
- role: "assistant",
75
- content: [
76
- { type: "text", text: "hello " },
77
- { type: "text", text: "world" },
78
- ],
79
- },
80
- ],
81
- };
82
-
83
- const attrs = getChatRequestAttributes(inputs, "full");
84
-
85
- expect(attrs["gen_ai.input.messages"]).toEqual([
86
- JSON.stringify({
87
- role: "assistant",
88
- parts: [
89
- { type: "text", content: "hello " },
90
- { type: "text", content: "world" },
91
- ],
92
- }),
93
- ]);
94
- });
95
-
96
- test("should map assistant reasoning string in request attributes", () => {
97
- const inputs: ChatCompletionsBody = {
98
- model: "openai/gpt-oss-20b",
99
- messages: [
100
- {
101
- role: "assistant",
102
- content: "final answer",
103
- reasoning: "fallback reasoning",
104
- reasoning_details: [
105
- {
106
- type: "reasoning.text",
107
- index: 0,
108
- text: "step-by-step",
109
- },
110
- {
111
- type: "reasoning.encrypted",
112
- index: 1,
113
- data: "encrypted",
114
- },
115
- ],
116
- },
117
- ],
118
- };
119
-
120
- const attrs = getChatRequestAttributes(inputs, "full");
121
-
122
- expect(attrs["gen_ai.input.messages"]).toEqual([
123
- JSON.stringify({
124
- role: "assistant",
125
- parts: [
126
- { type: "reasoning", content: "fallback reasoning" },
127
- { type: "text", content: "final answer" },
128
- ],
129
- }),
130
- ]);
131
- });
132
-
133
- test("should flatten tool content part arrays in request attributes", () => {
134
- const inputs: ChatCompletionsBody = {
135
- model: "openai/gpt-oss-20b",
136
- messages: [
137
- {
138
- role: "tool",
139
- tool_call_id: "call_1",
140
- content: [
141
- { type: "text", text: "line-1 " },
142
- { type: "text", text: "line-2" },
143
- ],
144
- },
145
- ],
146
- };
147
-
148
- const attrs = getChatRequestAttributes(inputs, "full");
149
-
150
- expect(attrs["gen_ai.input.messages"]).toEqual([
151
- JSON.stringify({
152
- role: "tool",
153
- parts: [{ type: "tool_call_response", id: "call_1", response: "line-1 line-2" }],
154
- }),
155
- ]);
156
- });
157
-
158
- test("should map user media content parts to schema-compatible uri/blob parts", () => {
159
- const inputs: ChatCompletionsBody = {
160
- model: "openai/gpt-oss-20b",
161
- messages: [
162
- {
163
- role: "user",
164
- content: [
165
- { type: "image_url", image_url: { url: "https://example.com/cat.png" } },
166
- { type: "image_url", image_url: { url: "data:image/png;base64,AAAA" } },
167
- { type: "input_audio", input_audio: { data: "AAAA", format: "wav" } },
168
- {
169
- type: "file",
170
- file: { data: "AAAA", media_type: "application/pdf", filename: "brochure.pdf" },
171
- },
172
- ],
173
- },
174
- ],
175
- };
176
-
177
- const attrs = getChatRequestAttributes(inputs, "full");
178
-
179
- expect(attrs["gen_ai.input.messages"]).toEqual([
180
- JSON.stringify({
181
- role: "user",
182
- parts: [
183
- { type: "uri", modality: "image", uri: "https://example.com/cat.png" },
184
- {
185
- type: "blob",
186
- modality: "image",
187
- content: "[REDACTED_BINARY_DATA]",
188
- mime_type: "image/png",
189
- },
190
- {
191
- type: "blob",
192
- modality: "audio",
193
- content: "[REDACTED_BINARY_DATA]",
194
- mime_type: "audio/wav",
195
- },
196
- {
197
- type: "blob",
198
- modality: "file",
199
- content: "[REDACTED_BINARY_DATA]",
200
- mime_type: "application/pdf",
201
- file_name: "brochure.pdf",
202
- },
203
- ],
204
- }),
205
- ]);
206
- });
207
-
208
- test("should map assistant text content part arrays in response attributes", () => {
209
- const completions: ChatCompletions = {
210
- id: "chatcmpl_123",
211
- object: "chat.completion",
212
- created: 1700000000,
213
- model: "openai/gpt-oss-20b",
214
- choices: [
215
- {
216
- index: 0,
217
- message: {
218
- role: "assistant",
219
- content: [
220
- { type: "text", text: "hello " },
221
- { type: "text", text: "world" },
222
- ],
223
- },
224
- finish_reason: "stop",
225
- },
226
- ],
227
- usage: null,
228
- };
229
-
230
- const attrs = getChatResponseAttributes(completions, "full");
231
-
232
- expect(attrs["gen_ai.output.messages"]).toEqual([
233
- JSON.stringify({
234
- role: "assistant",
235
- parts: [
236
- { type: "text", content: "hello " },
237
- { type: "text", content: "world" },
238
- ],
239
- finish_reason: "stop",
240
- }),
241
- ]);
242
- });
243
-
244
- test("should map assistant reasoning string to reasoning parts in response attributes", () => {
245
- const completions: ChatCompletions = {
246
- id: "chatcmpl_123",
247
- object: "chat.completion",
248
- created: 1700000000,
249
- model: "openai/gpt-oss-20b",
250
- choices: [
251
- {
252
- index: 0,
253
- message: {
254
- role: "assistant",
255
- content: "final answer",
256
- reasoning: "chain-of-thought",
257
- },
258
- finish_reason: "stop",
259
- },
260
- ],
261
- usage: null,
262
- };
263
-
264
- const attrs = getChatResponseAttributes(completions, "full");
265
-
266
- expect(attrs["gen_ai.output.messages"]).toEqual([
267
- JSON.stringify({
268
- role: "assistant",
269
- parts: [
270
- { type: "reasoning", content: "chain-of-thought" },
271
- { type: "text", content: "final answer" },
272
- ],
273
- finish_reason: "stop",
274
- }),
275
- ]);
276
- });
277
-
278
- test("should map usage token attributes with cache_read_input/reasoning_output names", () => {
279
- const completions: ChatCompletions = {
280
- id: "chatcmpl_123",
281
- object: "chat.completion",
282
- created: 1700000000,
283
- model: "openai/gpt-oss-20b",
284
- choices: [
285
- {
286
- index: 0,
287
- message: {
288
- role: "assistant",
289
- content: "done",
290
- },
291
- finish_reason: "stop",
292
- },
293
- ],
294
- usage: {
295
- prompt_tokens: 10,
296
- completion_tokens: 20,
297
- total_tokens: 30,
298
- prompt_tokens_details: {
299
- cached_tokens: 4,
300
- },
301
- completion_tokens_details: {
302
- reasoning_tokens: 6,
303
- },
304
- },
305
- };
306
-
307
- const attrs = getChatResponseAttributes(completions, "recommended");
308
-
309
- expect(attrs["gen_ai.usage.input_tokens"]).toBe(10);
310
- expect(attrs["gen_ai.usage.output_tokens"]).toBe(20);
311
- expect(attrs["gen_ai.usage.total_tokens"]).toBe(30);
312
- expect(attrs["gen_ai.usage.cache_read.input_tokens"]).toBe(4);
313
- expect(attrs["gen_ai.usage.reasoning.output_tokens"]).toBe(6);
314
- });
315
- });
@@ -1,214 +0,0 @@
1
- import type { Attributes } from "@opentelemetry/api";
2
-
3
- import type {
4
- ChatCompletionsAssistantMessage,
5
- ChatCompletions,
6
- ChatCompletionsBody,
7
- ChatCompletionsContentPart,
8
- ChatCompletionsContentPartText,
9
- ChatCompletionsMessage,
10
- } from "./schema";
11
-
12
- import { type TelemetrySignalLevel } from "../../types";
13
- import { parseDataUrl } from "../../utils/url";
14
-
15
- const toTextParts = (content: string | ChatCompletionsContentPart[] | null | undefined) => {
16
- if (typeof content === "string") {
17
- return [{ type: "text", content }];
18
- }
19
-
20
- const result = [];
21
- if (Array.isArray(content)) {
22
- for (const part of content) {
23
- if (part.type === "text") {
24
- result.push({ type: "text", content: part.text });
25
- }
26
- }
27
- }
28
- return result;
29
- };
30
-
31
- const toBlobPart = (modality: string, mimeType?: string) => {
32
- const part: Record<string, unknown> = {
33
- type: "blob",
34
- modality,
35
- content: "[REDACTED_BINARY_DATA]",
36
- };
37
- if (mimeType) part["mime_type"] = mimeType;
38
- return part;
39
- };
40
-
41
- const toToolResponsePart = (id: string, content: string | ChatCompletionsContentPartText[]) => ({
42
- type: "tool_call_response" as const,
43
- id,
44
- response: typeof content === "string" ? content : content.map((p) => p.text).join(""),
45
- });
46
-
47
- const toAssistantParts = (message: ChatCompletionsAssistantMessage) => {
48
- const parts: Record<string, unknown>[] = [];
49
-
50
- if (typeof message.reasoning === "string") {
51
- parts.push({ type: "reasoning", content: message.reasoning });
52
- }
53
-
54
- for (const part of toTextParts(message.content)) {
55
- parts.push(part);
56
- }
57
-
58
- if (Array.isArray(message.tool_calls)) {
59
- for (const call of message.tool_calls) {
60
- parts.push({
61
- type: "tool_call",
62
- id: call.id,
63
- name: call.function.name,
64
- arguments: call.function.arguments,
65
- });
66
- }
67
- }
68
-
69
- return parts;
70
- };
71
-
72
- const toUserParts = (content: string | ChatCompletionsContentPart[]) => {
73
- if (typeof content === "string") return [{ type: "text", content }];
74
-
75
- const parts: Record<string, unknown>[] = [];
76
-
77
- for (const part of content) {
78
- switch (part.type) {
79
- case "text":
80
- parts.push({ type: "text", content: part.text });
81
- break;
82
- case "image_url": {
83
- const url = part.image_url.url;
84
- if (url.slice(0, 5).toLowerCase() === "data:") {
85
- const { mimeType } = parseDataUrl(url);
86
- parts.push(toBlobPart("image", mimeType || undefined));
87
- } else {
88
- parts.push({ type: "uri", modality: "image", uri: url });
89
- }
90
- break;
91
- }
92
- case "input_audio":
93
- parts.push(toBlobPart("audio", `audio/${part.input_audio.format}`));
94
- break;
95
- case "file": {
96
- const filePart = toBlobPart("file", part.file.media_type);
97
- if (part.file.filename) filePart["file_name"] = part.file.filename;
98
- parts.push(filePart);
99
- break;
100
- }
101
- }
102
- }
103
-
104
- return parts;
105
- };
106
-
107
- const toMessageParts = (message: ChatCompletionsMessage) => {
108
- switch (message.role) {
109
- case "assistant":
110
- return toAssistantParts(message);
111
- case "tool":
112
- return [toToolResponsePart(message.tool_call_id, message.content)];
113
- case "user":
114
- return toUserParts(message.content);
115
- // FUTURE: remove once Langfuse supports gen_ai.system_instructions
116
- // https://github.com/langfuse/langfuse/issues/11607
117
- case "system":
118
- return toTextParts(message.content);
119
- default:
120
- return [];
121
- }
122
- };
123
-
124
- export const getChatRequestAttributes = (
125
- inputs: ChatCompletionsBody,
126
- signalLevel?: TelemetrySignalLevel,
127
- ): Attributes => {
128
- if (!signalLevel || signalLevel === "off") return {};
129
-
130
- const attrs: Attributes = {};
131
-
132
- if (inputs.seed !== undefined) {
133
- Object.assign(attrs, { "gen_ai.request.seed": inputs.seed });
134
- }
135
-
136
- if (signalLevel !== "required") {
137
- Object.assign(attrs, {
138
- // FUTURE: add reasoning info
139
- "gen_ai.request.stream": inputs.stream,
140
- "gen_ai.request.frequency_penalty": inputs.frequency_penalty,
141
- "gen_ai.request.max_tokens": inputs.max_completion_tokens,
142
- "gen_ai.request.presence_penalty": inputs.presence_penalty,
143
- "gen_ai.request.stop_sequences": inputs.stop
144
- ? Array.isArray(inputs.stop)
145
- ? inputs.stop
146
- : [inputs.stop]
147
- : undefined,
148
- "gen_ai.request.temperature": inputs.temperature,
149
- "gen_ai.request.top_p": inputs.top_p,
150
- });
151
-
152
- if (inputs.metadata) {
153
- for (const key in inputs.metadata) {
154
- attrs[`gen_ai.request.metadata.${key}`] = inputs.metadata[key];
155
- }
156
- }
157
- }
158
-
159
- if (signalLevel === "full") {
160
- Object.assign(attrs, {
161
- // FUTURE: move system instructions from messages to here
162
- // blocker: https://github.com/langfuse/langfuse/issues/11607
163
- // "gen_ai.system_instructions": inputs.messages
164
- // .filter((m) => m.role === "system")
165
- // .map((m) => JSON.stringify(toTextPart(m.content))),
166
- "gen_ai.input.messages": inputs.messages
167
- //.filter((m) => m.role !== "system")
168
- .map((m) => JSON.stringify({ role: m.role, parts: toMessageParts(m) })),
169
- "gen_ai.tool.definitions": inputs.tools?.map((toolDefinition) =>
170
- JSON.stringify(toolDefinition),
171
- ),
172
- });
173
- }
174
-
175
- return attrs;
176
- };
177
-
178
- export const getChatResponseAttributes = (
179
- completions: ChatCompletions,
180
- signalLevel?: TelemetrySignalLevel,
181
- ): Attributes => {
182
- if (!signalLevel || signalLevel === "off") return {};
183
-
184
- const attrs: Attributes = {
185
- "gen_ai.response.id": completions.id,
186
- };
187
-
188
- if (signalLevel !== "required") {
189
- Object.assign(attrs, {
190
- "gen_ai.response.finish_reasons": completions.choices?.map((c) => c.finish_reason),
191
- "gen_ai.usage.total_tokens": completions.usage?.total_tokens,
192
- "gen_ai.usage.input_tokens": completions.usage?.prompt_tokens,
193
- "gen_ai.usage.cache_read.input_tokens":
194
- completions.usage?.prompt_tokens_details?.cached_tokens,
195
- "gen_ai.usage.output_tokens": completions.usage?.completion_tokens,
196
- "gen_ai.usage.reasoning.output_tokens":
197
- completions.usage?.completion_tokens_details?.reasoning_tokens,
198
- });
199
- }
200
-
201
- if (signalLevel === "full") {
202
- Object.assign(attrs, {
203
- "gen_ai.output.messages": completions.choices?.map((c) =>
204
- JSON.stringify({
205
- role: c.message.role,
206
- parts: toMessageParts(c.message),
207
- finish_reason: c.finish_reason,
208
- }),
209
- ),
210
- });
211
- }
212
-
213
- return attrs;
214
- };