@hebo-ai/gateway 0.6.2 → 0.8.0-rc0

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 (168) hide show
  1. package/README.md +134 -7
  2. package/package.json +46 -1
  3. package/dist/config.d.ts +0 -2
  4. package/dist/config.js +0 -81
  5. package/dist/endpoints/chat-completions/converters.d.ts +0 -43
  6. package/dist/endpoints/chat-completions/converters.js +0 -625
  7. package/dist/endpoints/chat-completions/handler.d.ts +0 -2
  8. package/dist/endpoints/chat-completions/handler.js +0 -149
  9. package/dist/endpoints/chat-completions/index.d.ts +0 -4
  10. package/dist/endpoints/chat-completions/index.js +0 -4
  11. package/dist/endpoints/chat-completions/otel.d.ts +0 -5
  12. package/dist/endpoints/chat-completions/otel.js +0 -171
  13. package/dist/endpoints/chat-completions/schema.d.ts +0 -1188
  14. package/dist/endpoints/chat-completions/schema.js +0 -298
  15. package/dist/endpoints/embeddings/converters.d.ts +0 -10
  16. package/dist/endpoints/embeddings/converters.js +0 -31
  17. package/dist/endpoints/embeddings/handler.d.ts +0 -2
  18. package/dist/endpoints/embeddings/handler.js +0 -104
  19. package/dist/endpoints/embeddings/index.d.ts +0 -4
  20. package/dist/endpoints/embeddings/index.js +0 -4
  21. package/dist/endpoints/embeddings/otel.d.ts +0 -5
  22. package/dist/endpoints/embeddings/otel.js +0 -29
  23. package/dist/endpoints/embeddings/schema.d.ts +0 -44
  24. package/dist/endpoints/embeddings/schema.js +0 -29
  25. package/dist/endpoints/models/converters.d.ts +0 -6
  26. package/dist/endpoints/models/converters.js +0 -42
  27. package/dist/endpoints/models/handler.d.ts +0 -2
  28. package/dist/endpoints/models/handler.js +0 -29
  29. package/dist/endpoints/models/index.d.ts +0 -3
  30. package/dist/endpoints/models/index.js +0 -3
  31. package/dist/endpoints/models/schema.d.ts +0 -42
  32. package/dist/endpoints/models/schema.js +0 -31
  33. package/dist/errors/ai-sdk.d.ts +0 -2
  34. package/dist/errors/ai-sdk.js +0 -52
  35. package/dist/errors/gateway.d.ts +0 -5
  36. package/dist/errors/gateway.js +0 -13
  37. package/dist/errors/openai.d.ts +0 -20
  38. package/dist/errors/openai.js +0 -40
  39. package/dist/errors/utils.d.ts +0 -22
  40. package/dist/errors/utils.js +0 -44
  41. package/dist/gateway.d.ts +0 -9
  42. package/dist/gateway.js +0 -40
  43. package/dist/index.d.ts +0 -14
  44. package/dist/index.js +0 -13
  45. package/dist/lifecycle.d.ts +0 -2
  46. package/dist/lifecycle.js +0 -98
  47. package/dist/logger/default.d.ts +0 -4
  48. package/dist/logger/default.js +0 -81
  49. package/dist/logger/index.d.ts +0 -11
  50. package/dist/logger/index.js +0 -25
  51. package/dist/middleware/common.d.ts +0 -12
  52. package/dist/middleware/common.js +0 -146
  53. package/dist/middleware/debug.d.ts +0 -3
  54. package/dist/middleware/debug.js +0 -27
  55. package/dist/middleware/matcher.d.ts +0 -28
  56. package/dist/middleware/matcher.js +0 -118
  57. package/dist/middleware/utils.d.ts +0 -2
  58. package/dist/middleware/utils.js +0 -24
  59. package/dist/models/amazon/index.d.ts +0 -2
  60. package/dist/models/amazon/index.js +0 -2
  61. package/dist/models/amazon/middleware.d.ts +0 -3
  62. package/dist/models/amazon/middleware.js +0 -68
  63. package/dist/models/amazon/presets.d.ts +0 -345
  64. package/dist/models/amazon/presets.js +0 -80
  65. package/dist/models/anthropic/index.d.ts +0 -2
  66. package/dist/models/anthropic/index.js +0 -2
  67. package/dist/models/anthropic/middleware.d.ts +0 -5
  68. package/dist/models/anthropic/middleware.js +0 -127
  69. package/dist/models/anthropic/presets.d.ts +0 -711
  70. package/dist/models/anthropic/presets.js +0 -135
  71. package/dist/models/catalog.d.ts +0 -4
  72. package/dist/models/catalog.js +0 -8
  73. package/dist/models/cohere/index.d.ts +0 -2
  74. package/dist/models/cohere/index.js +0 -2
  75. package/dist/models/cohere/middleware.d.ts +0 -3
  76. package/dist/models/cohere/middleware.js +0 -62
  77. package/dist/models/cohere/presets.d.ts +0 -411
  78. package/dist/models/cohere/presets.js +0 -134
  79. package/dist/models/google/index.d.ts +0 -2
  80. package/dist/models/google/index.js +0 -2
  81. package/dist/models/google/middleware.d.ts +0 -8
  82. package/dist/models/google/middleware.js +0 -111
  83. package/dist/models/google/presets.d.ts +0 -375
  84. package/dist/models/google/presets.js +0 -82
  85. package/dist/models/meta/index.d.ts +0 -1
  86. package/dist/models/meta/index.js +0 -1
  87. package/dist/models/meta/presets.d.ts +0 -483
  88. package/dist/models/meta/presets.js +0 -95
  89. package/dist/models/openai/index.d.ts +0 -2
  90. package/dist/models/openai/index.js +0 -2
  91. package/dist/models/openai/middleware.d.ts +0 -4
  92. package/dist/models/openai/middleware.js +0 -88
  93. package/dist/models/openai/presets.d.ts +0 -959
  94. package/dist/models/openai/presets.js +0 -213
  95. package/dist/models/types.d.ts +0 -20
  96. package/dist/models/types.js +0 -84
  97. package/dist/models/voyage/index.d.ts +0 -2
  98. package/dist/models/voyage/index.js +0 -2
  99. package/dist/models/voyage/middleware.d.ts +0 -2
  100. package/dist/models/voyage/middleware.js +0 -19
  101. package/dist/models/voyage/presets.d.ts +0 -436
  102. package/dist/models/voyage/presets.js +0 -85
  103. package/dist/providers/anthropic/canonical.d.ts +0 -3
  104. package/dist/providers/anthropic/canonical.js +0 -9
  105. package/dist/providers/anthropic/index.d.ts +0 -1
  106. package/dist/providers/anthropic/index.js +0 -1
  107. package/dist/providers/bedrock/canonical.d.ts +0 -17
  108. package/dist/providers/bedrock/canonical.js +0 -61
  109. package/dist/providers/bedrock/index.d.ts +0 -2
  110. package/dist/providers/bedrock/index.js +0 -2
  111. package/dist/providers/bedrock/middleware.d.ts +0 -4
  112. package/dist/providers/bedrock/middleware.js +0 -104
  113. package/dist/providers/cohere/canonical.d.ts +0 -3
  114. package/dist/providers/cohere/canonical.js +0 -17
  115. package/dist/providers/cohere/index.d.ts +0 -1
  116. package/dist/providers/cohere/index.js +0 -1
  117. package/dist/providers/groq/canonical.d.ts +0 -3
  118. package/dist/providers/groq/canonical.js +0 -12
  119. package/dist/providers/groq/index.d.ts +0 -1
  120. package/dist/providers/groq/index.js +0 -1
  121. package/dist/providers/openai/canonical.d.ts +0 -3
  122. package/dist/providers/openai/canonical.js +0 -8
  123. package/dist/providers/openai/index.d.ts +0 -1
  124. package/dist/providers/openai/index.js +0 -1
  125. package/dist/providers/registry.d.ts +0 -24
  126. package/dist/providers/registry.js +0 -103
  127. package/dist/providers/types.d.ts +0 -7
  128. package/dist/providers/types.js +0 -11
  129. package/dist/providers/vertex/canonical.d.ts +0 -3
  130. package/dist/providers/vertex/canonical.js +0 -8
  131. package/dist/providers/vertex/index.d.ts +0 -1
  132. package/dist/providers/vertex/index.js +0 -1
  133. package/dist/providers/voyage/canonical.d.ts +0 -3
  134. package/dist/providers/voyage/canonical.js +0 -7
  135. package/dist/providers/voyage/index.d.ts +0 -1
  136. package/dist/providers/voyage/index.js +0 -1
  137. package/dist/telemetry/ai-sdk.d.ts +0 -2
  138. package/dist/telemetry/ai-sdk.js +0 -31
  139. package/dist/telemetry/baggage.d.ts +0 -1
  140. package/dist/telemetry/baggage.js +0 -24
  141. package/dist/telemetry/fetch.d.ts +0 -2
  142. package/dist/telemetry/fetch.js +0 -49
  143. package/dist/telemetry/gen-ai.d.ts +0 -6
  144. package/dist/telemetry/gen-ai.js +0 -78
  145. package/dist/telemetry/http.d.ts +0 -3
  146. package/dist/telemetry/http.js +0 -54
  147. package/dist/telemetry/index.d.ts +0 -1
  148. package/dist/telemetry/index.js +0 -1
  149. package/dist/telemetry/memory.d.ts +0 -2
  150. package/dist/telemetry/memory.js +0 -43
  151. package/dist/telemetry/span.d.ts +0 -13
  152. package/dist/telemetry/span.js +0 -60
  153. package/dist/telemetry/stream.d.ts +0 -3
  154. package/dist/telemetry/stream.js +0 -58
  155. package/dist/types.d.ts +0 -176
  156. package/dist/types.js +0 -1
  157. package/dist/utils/env.d.ts +0 -2
  158. package/dist/utils/env.js +0 -7
  159. package/dist/utils/headers.d.ts +0 -4
  160. package/dist/utils/headers.js +0 -22
  161. package/dist/utils/preset.d.ts +0 -10
  162. package/dist/utils/preset.js +0 -42
  163. package/dist/utils/request.d.ts +0 -2
  164. package/dist/utils/request.js +0 -43
  165. package/dist/utils/response.d.ts +0 -3
  166. package/dist/utils/response.js +0 -70
  167. package/dist/utils/url.d.ts +0 -4
  168. package/dist/utils/url.js +0 -21
@@ -1,149 +0,0 @@
1
- import { generateText, Output, streamText, wrapLanguageModel, } from "ai";
2
- import * as z from "zod/mini";
3
- import { GatewayError } from "../../errors/gateway";
4
- import { winterCgHandler } from "../../lifecycle";
5
- import { logger } from "../../logger";
6
- import { modelMiddlewareMatcher } from "../../middleware/matcher";
7
- import { resolveProvider } from "../../providers/registry";
8
- import { getGenAiGeneralAttributes, recordTimePerOutputToken, recordTokenUsage, } from "../../telemetry/gen-ai";
9
- import { addSpanEvent, setSpanAttributes } from "../../telemetry/span";
10
- import { prepareForwardHeaders } from "../../utils/request";
11
- import { convertToTextCallOptions, toChatCompletions, toChatCompletionsStream } from "./converters";
12
- import { getChatRequestAttributes, getChatResponseAttributes } from "./otel";
13
- import { ChatCompletionsBodySchema } from "./schema";
14
- export const chatCompletions = (config) => {
15
- const hooks = config.hooks;
16
- const handler = async (ctx) => {
17
- const start = performance.now();
18
- ctx.operation = "chat";
19
- addSpanEvent("hebo.handler.started");
20
- // Guard: enforce HTTP method early.
21
- if (!ctx.request || ctx.request.method !== "POST") {
22
- throw new GatewayError("Method Not Allowed", 405);
23
- }
24
- // Parse + validate input.
25
- try {
26
- // oxlint-disable-next-line no-unsafe-assignment
27
- ctx.body = await ctx.request.json();
28
- }
29
- catch {
30
- throw new GatewayError("Invalid JSON", 400);
31
- }
32
- logger.trace({ requestId: ctx.requestId, body: ctx.body }, "[chat] ChatCompletionsBody");
33
- addSpanEvent("hebo.request.deserialized");
34
- const parsed = ChatCompletionsBodySchema.safeParse(ctx.body);
35
- if (!parsed.success) {
36
- // FUTURE: consider adding body shape to metadata
37
- throw new GatewayError(z.prettifyError(parsed.error), 400, undefined, parsed.error);
38
- }
39
- ctx.body = parsed.data;
40
- addSpanEvent("hebo.request.parsed");
41
- if (hooks?.before) {
42
- ctx.body =
43
- (await hooks.before(ctx)) ?? ctx.body;
44
- addSpanEvent("hebo.hooks.before.completed");
45
- }
46
- // Resolve model + provider (hooks may override defaults).
47
- let inputs, stream;
48
- ({ model: ctx.modelId, stream, ...inputs } = ctx.body);
49
- ctx.resolvedModelId =
50
- (await hooks?.resolveModelId?.(ctx)) ?? ctx.modelId;
51
- logger.debug(`[chat] resolved ${ctx.modelId} to ${ctx.resolvedModelId}`);
52
- addSpanEvent("hebo.model.resolved");
53
- const override = await hooks?.resolveProvider?.(ctx);
54
- ctx.provider =
55
- override ??
56
- resolveProvider({
57
- providers: ctx.providers,
58
- models: ctx.models,
59
- modelId: ctx.resolvedModelId,
60
- operation: ctx.operation,
61
- });
62
- const languageModel = ctx.provider.languageModel(ctx.resolvedModelId);
63
- ctx.resolvedProviderId = languageModel.provider;
64
- logger.debug(`[chat] using ${languageModel.provider} for ${ctx.resolvedModelId}`);
65
- addSpanEvent("hebo.provider.resolved");
66
- const genAiSignalLevel = config.telemetry?.signals?.gen_ai;
67
- const genAiGeneralAttrs = getGenAiGeneralAttributes(ctx, genAiSignalLevel);
68
- setSpanAttributes(genAiGeneralAttrs);
69
- // Convert inputs to AI SDK call options.
70
- // oxlint-disable-next-line no-unsafe-argument
71
- const textOptions = convertToTextCallOptions(inputs);
72
- logger.trace({
73
- requestId: ctx.requestId,
74
- options: textOptions,
75
- }, "[chat] AI SDK options");
76
- addSpanEvent("hebo.options.prepared");
77
- setSpanAttributes(getChatRequestAttributes(ctx.body, genAiSignalLevel));
78
- // Build middleware chain (model -> forward params -> provider).
79
- const languageModelWithMiddleware = wrapLanguageModel({
80
- model: languageModel,
81
- middleware: modelMiddlewareMatcher.for(ctx.resolvedModelId, languageModel.provider),
82
- });
83
- // Execute request (streaming vs. non-streaming).
84
- if (stream) {
85
- addSpanEvent("hebo.ai-sdk.started");
86
- const result = streamText({
87
- model: languageModelWithMiddleware,
88
- headers: prepareForwardHeaders(ctx.request),
89
- abortSignal: ctx.request.signal,
90
- timeout: {
91
- totalMs: 5 * 60 * 1000,
92
- },
93
- onAbort: () => {
94
- throw new DOMException("The operation was aborted.", "AbortError");
95
- },
96
- onError: () => { },
97
- onFinish: (res) => {
98
- addSpanEvent("hebo.ai-sdk.completed");
99
- const streamResult = toChatCompletions(res, ctx.resolvedModelId);
100
- logger.trace({ requestId: ctx.requestId, result: streamResult }, "[chat] ChatCompletions");
101
- addSpanEvent("hebo.result.transformed");
102
- const genAiResponseAttrs = getChatResponseAttributes(streamResult, genAiSignalLevel);
103
- setSpanAttributes(genAiResponseAttrs);
104
- recordTokenUsage(genAiResponseAttrs, genAiGeneralAttrs, genAiSignalLevel);
105
- recordTimePerOutputToken(start, genAiResponseAttrs, genAiGeneralAttrs, genAiSignalLevel);
106
- },
107
- experimental_include: {
108
- requestBody: false,
109
- },
110
- includeRawChunks: false,
111
- ...textOptions,
112
- });
113
- ctx.result = toChatCompletionsStream(result, ctx.resolvedModelId);
114
- if (hooks?.after) {
115
- ctx.result = (await hooks.after(ctx)) ?? ctx.result;
116
- addSpanEvent("hebo.hooks.after.completed");
117
- }
118
- return ctx.result;
119
- }
120
- addSpanEvent("hebo.ai-sdk.started");
121
- const result = await generateText({
122
- model: languageModelWithMiddleware,
123
- headers: prepareForwardHeaders(ctx.request),
124
- abortSignal: ctx.request.signal,
125
- timeout: 5 * 60 * 1000,
126
- experimental_include: {
127
- requestBody: false,
128
- responseBody: false,
129
- },
130
- ...textOptions,
131
- });
132
- logger.trace({ requestId: ctx.requestId, result }, "[chat] AI SDK result");
133
- addSpanEvent("hebo.ai-sdk.completed");
134
- // Transform result.
135
- ctx.result = toChatCompletions(result, ctx.resolvedModelId);
136
- logger.trace({ requestId: ctx.requestId, result: ctx.result }, "[chat] ChatCompletions");
137
- addSpanEvent("hebo.result.transformed");
138
- const genAiResponseAttrs = getChatResponseAttributes(ctx.result, genAiSignalLevel);
139
- setSpanAttributes(genAiResponseAttrs);
140
- recordTokenUsage(genAiResponseAttrs, genAiGeneralAttrs, genAiSignalLevel);
141
- if (hooks?.after) {
142
- ctx.result = (await hooks.after(ctx)) ?? ctx.result;
143
- addSpanEvent("hebo.hooks.after.completed");
144
- }
145
- recordTimePerOutputToken(start, genAiResponseAttrs, genAiGeneralAttrs, genAiSignalLevel);
146
- return ctx.result;
147
- };
148
- return { handler: winterCgHandler(handler, config) };
149
- };
@@ -1,4 +0,0 @@
1
- export * from "./converters";
2
- export * from "./handler";
3
- export * from "./schema";
4
- export * from "./otel";
@@ -1,4 +0,0 @@
1
- export * from "./converters";
2
- export * from "./handler";
3
- export * from "./schema";
4
- export * from "./otel";
@@ -1,5 +0,0 @@
1
- import type { Attributes } from "@opentelemetry/api";
2
- import type { ChatCompletions, ChatCompletionsBody } from "./schema";
3
- import { type TelemetrySignalLevel } from "../../types";
4
- export declare const getChatRequestAttributes: (inputs: ChatCompletionsBody, signalLevel?: TelemetrySignalLevel) => Attributes;
5
- export declare const getChatResponseAttributes: (completions: ChatCompletions, signalLevel?: TelemetrySignalLevel) => Attributes;
@@ -1,171 +0,0 @@
1
- import {} from "../../types";
2
- import { parseDataUrl } from "../../utils/url";
3
- const toTextParts = (content) => {
4
- if (typeof content === "string") {
5
- return [{ type: "text", content }];
6
- }
7
- const result = [];
8
- if (Array.isArray(content)) {
9
- for (const part of content) {
10
- if (part.type === "text") {
11
- result.push({ type: "text", content: part.text });
12
- }
13
- }
14
- }
15
- return result;
16
- };
17
- const toBlobPart = (modality, mimeType) => {
18
- const part = {
19
- type: "blob",
20
- modality,
21
- content: "[REDACTED_BINARY_DATA]",
22
- };
23
- if (mimeType)
24
- part["mime_type"] = mimeType;
25
- return part;
26
- };
27
- const toToolResponsePart = (id, content) => ({
28
- type: "tool_call_response",
29
- id,
30
- response: typeof content === "string" ? content : content.map((p) => p.text).join(""),
31
- });
32
- const toAssistantParts = (message) => {
33
- const parts = [];
34
- if (typeof message.reasoning === "string") {
35
- parts.push({ type: "reasoning", content: message.reasoning });
36
- }
37
- for (const part of toTextParts(message.content)) {
38
- parts.push(part);
39
- }
40
- if (Array.isArray(message.tool_calls)) {
41
- for (const call of message.tool_calls) {
42
- parts.push({
43
- type: "tool_call",
44
- id: call.id,
45
- name: call.function.name,
46
- arguments: call.function.arguments,
47
- });
48
- }
49
- }
50
- return parts;
51
- };
52
- const toUserParts = (content) => {
53
- if (typeof content === "string")
54
- return [{ type: "text", content }];
55
- const parts = [];
56
- for (const part of content) {
57
- switch (part.type) {
58
- case "text":
59
- parts.push({ type: "text", content: part.text });
60
- break;
61
- case "image_url": {
62
- const url = part.image_url.url;
63
- if (url.slice(0, 5).toLowerCase() === "data:") {
64
- const { mimeType } = parseDataUrl(url);
65
- parts.push(toBlobPart("image", mimeType || undefined));
66
- }
67
- else {
68
- parts.push({ type: "uri", modality: "image", uri: url });
69
- }
70
- break;
71
- }
72
- case "input_audio":
73
- parts.push(toBlobPart("audio", `audio/${part.input_audio.format}`));
74
- break;
75
- case "file": {
76
- const filePart = toBlobPart("file", part.file.media_type);
77
- if (part.file.filename)
78
- filePart["file_name"] = part.file.filename;
79
- parts.push(filePart);
80
- break;
81
- }
82
- }
83
- }
84
- return parts;
85
- };
86
- const toMessageParts = (message) => {
87
- switch (message.role) {
88
- case "assistant":
89
- return toAssistantParts(message);
90
- case "tool":
91
- return [toToolResponsePart(message.tool_call_id, message.content)];
92
- case "user":
93
- return toUserParts(message.content);
94
- // FUTURE: remove once Langfuse supports gen_ai.system_instructions
95
- // https://github.com/langfuse/langfuse/issues/11607
96
- case "system":
97
- return toTextParts(message.content);
98
- default:
99
- throw new Error(`Unhandled content part type: ${message.role}`);
100
- }
101
- };
102
- export const getChatRequestAttributes = (inputs, signalLevel) => {
103
- if (!signalLevel || signalLevel === "off")
104
- return {};
105
- const attrs = {};
106
- if (inputs.seed !== undefined) {
107
- Object.assign(attrs, { "gen_ai.request.seed": inputs.seed });
108
- }
109
- if (signalLevel !== "required") {
110
- Object.assign(attrs, {
111
- // FUTURE: add reasoning info
112
- "gen_ai.request.stream": inputs.stream,
113
- "gen_ai.request.frequency_penalty": inputs.frequency_penalty,
114
- "gen_ai.request.max_tokens": inputs.max_completion_tokens,
115
- "gen_ai.request.presence_penalty": inputs.presence_penalty,
116
- "gen_ai.request.stop_sequences": inputs.stop
117
- ? Array.isArray(inputs.stop)
118
- ? inputs.stop
119
- : [inputs.stop]
120
- : undefined,
121
- "gen_ai.request.temperature": inputs.temperature,
122
- "gen_ai.request.top_p": inputs.top_p,
123
- });
124
- if (inputs.metadata) {
125
- for (const key in inputs.metadata) {
126
- attrs[`gen_ai.request.metadata.${key}`] = inputs.metadata[key];
127
- }
128
- }
129
- }
130
- if (signalLevel === "full") {
131
- Object.assign(attrs, {
132
- // FUTURE: move system instructions from messages to here
133
- // blocker: https://github.com/langfuse/langfuse/issues/11607
134
- // "gen_ai.system_instructions": inputs.messages
135
- // .filter((m) => m.role === "system")
136
- // .map((m) => JSON.stringify(toTextPart(m.content))),
137
- "gen_ai.input.messages": inputs.messages
138
- //.filter((m) => m.role !== "system")
139
- .map((m) => JSON.stringify({ role: m.role, parts: toMessageParts(m) })),
140
- "gen_ai.tool.definitions": inputs.tools?.map((toolDefinition) => JSON.stringify(toolDefinition)),
141
- });
142
- }
143
- return attrs;
144
- };
145
- export const getChatResponseAttributes = (completions, signalLevel) => {
146
- if (!signalLevel || signalLevel === "off")
147
- return {};
148
- const attrs = {
149
- "gen_ai.response.id": completions.id,
150
- };
151
- if (signalLevel !== "required") {
152
- Object.assign(attrs, {
153
- "gen_ai.response.finish_reasons": completions.choices?.map((c) => c.finish_reason),
154
- "gen_ai.usage.total_tokens": completions.usage?.total_tokens,
155
- "gen_ai.usage.input_tokens": completions.usage?.prompt_tokens,
156
- "gen_ai.usage.cache_read.input_tokens": completions.usage?.prompt_tokens_details?.cached_tokens,
157
- "gen_ai.usage.output_tokens": completions.usage?.completion_tokens,
158
- "gen_ai.usage.reasoning.output_tokens": completions.usage?.completion_tokens_details?.reasoning_tokens,
159
- });
160
- }
161
- if (signalLevel === "full") {
162
- Object.assign(attrs, {
163
- "gen_ai.output.messages": completions.choices?.map((c) => JSON.stringify({
164
- role: c.message.role,
165
- parts: toMessageParts(c.message),
166
- finish_reason: c.finish_reason,
167
- })),
168
- });
169
- }
170
- return attrs;
171
- };