@hebo-ai/gateway 0.4.0-beta.2 → 0.4.0-beta.4

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 (87) hide show
  1. package/README.md +13 -5
  2. package/dist/config.js +21 -7
  3. package/dist/endpoints/chat-completions/converters.js +2 -2
  4. package/dist/endpoints/chat-completions/handler.js +31 -25
  5. package/dist/endpoints/chat-completions/otel.d.ts +6 -0
  6. package/dist/endpoints/chat-completions/otel.js +121 -0
  7. package/dist/endpoints/embeddings/handler.js +19 -12
  8. package/dist/endpoints/embeddings/otel.d.ts +6 -0
  9. package/dist/endpoints/embeddings/otel.js +35 -0
  10. package/dist/endpoints/models/handler.js +3 -4
  11. package/dist/errors/gateway.js +1 -2
  12. package/dist/errors/openai.js +10 -12
  13. package/dist/errors/utils.d.ts +1 -3
  14. package/dist/errors/utils.js +5 -6
  15. package/dist/gateway.js +1 -1
  16. package/dist/lifecycle.js +62 -28
  17. package/dist/middleware/matcher.js +1 -1
  18. package/dist/models/amazon/presets.d.ts +37 -37
  19. package/dist/models/amazon/presets.js +1 -1
  20. package/dist/models/anthropic/presets.d.ts +56 -56
  21. package/dist/models/cohere/presets.d.ts +54 -54
  22. package/dist/models/cohere/presets.js +2 -2
  23. package/dist/models/google/presets.d.ts +31 -31
  24. package/dist/models/google/presets.js +1 -1
  25. package/dist/models/meta/presets.d.ts +42 -42
  26. package/dist/models/openai/presets.d.ts +96 -96
  27. package/dist/models/openai/presets.js +1 -1
  28. package/dist/models/types.d.ts +1 -1
  29. package/dist/models/voyage/presets.d.ts +92 -92
  30. package/dist/models/voyage/presets.js +1 -1
  31. package/dist/providers/registry.js +2 -2
  32. package/dist/telemetry/baggage.d.ts +1 -0
  33. package/dist/telemetry/baggage.js +24 -0
  34. package/dist/telemetry/fetch.d.ts +2 -1
  35. package/dist/telemetry/fetch.js +13 -3
  36. package/dist/telemetry/gen-ai.d.ts +4 -0
  37. package/dist/telemetry/gen-ai.js +42 -0
  38. package/dist/telemetry/http.d.ts +3 -0
  39. package/dist/telemetry/http.js +57 -0
  40. package/dist/telemetry/span.d.ts +6 -3
  41. package/dist/telemetry/span.js +23 -35
  42. package/dist/telemetry/stream.d.ts +3 -7
  43. package/dist/telemetry/stream.js +18 -18
  44. package/dist/types.d.ts +14 -12
  45. package/dist/utils/headers.d.ts +1 -1
  46. package/dist/utils/headers.js +7 -9
  47. package/dist/utils/request.d.ts +0 -4
  48. package/dist/utils/request.js +0 -9
  49. package/dist/utils/response.js +1 -1
  50. package/package.json +4 -2
  51. package/src/config.ts +28 -7
  52. package/src/endpoints/chat-completions/converters.ts +2 -2
  53. package/src/endpoints/chat-completions/handler.ts +39 -26
  54. package/src/endpoints/chat-completions/otel.ts +154 -0
  55. package/src/endpoints/embeddings/handler.test.ts +2 -2
  56. package/src/endpoints/embeddings/handler.ts +24 -12
  57. package/src/endpoints/embeddings/otel.ts +56 -0
  58. package/src/endpoints/models/handler.ts +3 -5
  59. package/src/errors/gateway.ts +1 -2
  60. package/src/errors/openai.ts +24 -17
  61. package/src/errors/utils.ts +5 -7
  62. package/src/gateway.ts +1 -1
  63. package/src/lifecycle.ts +73 -31
  64. package/src/middleware/matcher.ts +1 -1
  65. package/src/models/amazon/presets.ts +1 -1
  66. package/src/models/cohere/presets.ts +2 -2
  67. package/src/models/google/presets.ts +1 -1
  68. package/src/models/openai/presets.ts +1 -1
  69. package/src/models/types.ts +1 -1
  70. package/src/models/voyage/presets.ts +1 -1
  71. package/src/providers/registry.ts +2 -2
  72. package/src/telemetry/baggage.ts +27 -0
  73. package/src/telemetry/fetch.ts +15 -3
  74. package/src/telemetry/gen-ai.ts +60 -0
  75. package/src/telemetry/http.ts +65 -0
  76. package/src/telemetry/span.ts +28 -40
  77. package/src/telemetry/stream.ts +26 -30
  78. package/src/types.ts +15 -12
  79. package/src/utils/headers.ts +8 -19
  80. package/src/utils/request.ts +0 -11
  81. package/src/utils/response.ts +1 -1
  82. package/dist/telemetry/otel.d.ts +0 -2
  83. package/dist/telemetry/otel.js +0 -46
  84. package/dist/telemetry/utils.d.ts +0 -4
  85. package/dist/telemetry/utils.js +0 -223
  86. package/src/telemetry/otel.ts +0 -87
  87. package/src/telemetry/utils.ts +0 -273
package/src/types.ts CHANGED
@@ -84,10 +84,6 @@ export type GatewayContext = {
84
84
  * Response object returned by the handler.
85
85
  */
86
86
  response?: Response;
87
- /**
88
- * Structured object result for streaming requests. Only available at the end of the stream.
89
- */
90
- streamResult?: ChatCompletions;
91
87
  };
92
88
 
93
89
  /**
@@ -166,6 +162,8 @@ export type GatewayHooks = {
166
162
  onResponse?: (ctx: OnResponseHookContext) => void | Response | Promise<void | Response>;
167
163
  };
168
164
 
165
+ export type TelemetrySignalLevel = "off" | "required" | "recommended" | "full";
166
+
169
167
  /**
170
168
  * Main configuration object for the gateway.
171
169
  */
@@ -186,6 +184,10 @@ export type GatewayConfig = {
186
184
  * Optional lifecycle hooks for routing, auth, and response shaping.
187
185
  */
188
186
  hooks?: GatewayHooks;
187
+ /**
188
+ * Preferred logger configuration: custom logger or default logger settings.
189
+ */
190
+ logger?: Logger | LoggerConfig | null;
189
191
  /**
190
192
  * Optional AI SDK telemetry configuration.
191
193
  */
@@ -200,17 +202,18 @@ export type GatewayConfig = {
200
202
  */
201
203
  tracer?: Tracer;
202
204
  /**
203
- * Controls how many telemetry attributes are attached to spans.
204
- * - required: minimal safe baseline
205
+ * Telemetry signal levels by namespace.
206
+ * - off: disable the namespace
207
+ * - required: minimal baseline
205
208
  * - recommended: practical defaults
206
- * - full: include all available attributes
209
+ * - full: include all available details
207
210
  */
208
- attributes?: "required" | "recommended" | "full";
211
+ signals?: {
212
+ gen_ai?: TelemetrySignalLevel;
213
+ http?: TelemetrySignalLevel;
214
+ hebo?: TelemetrySignalLevel;
215
+ };
209
216
  };
210
- /**
211
- * Preferred logger configuration: custom logger or default logger settings.
212
- */
213
- logger?: Logger | LoggerConfig | null;
214
217
  };
215
218
 
216
219
  export const kParsed = Symbol("hebo.gateway.parsed");
@@ -1,32 +1,21 @@
1
1
  export const REQUEST_ID_HEADER = "x-request-id";
2
2
 
3
- type HeaderSource =
4
- | string
5
- | URL
6
- | Headers
7
- | Request
8
- | Response
9
- | RequestInit
10
- | ResponseInit
11
- | HeadersInit
12
- | undefined;
3
+ type HeaderSource = Request | ResponseInit | undefined;
13
4
 
14
5
  export const resolveRequestId = (source: HeaderSource): string | undefined => {
15
- if (!source || typeof source === "string" || source instanceof URL) return undefined;
6
+ if (!source) return undefined;
16
7
 
17
- if (source instanceof Request || source instanceof Response) {
8
+ if (source instanceof Request) {
18
9
  return source.headers.get(REQUEST_ID_HEADER) ?? undefined;
19
10
  }
20
11
 
21
- const headers = "headers" in source ? source.headers : source;
22
- if (!headers || typeof headers === "string") return undefined;
12
+ const headers = source.headers;
13
+ if (!headers) return undefined;
23
14
 
24
- if (Object.getPrototypeOf(headers) === Object.prototype) {
25
- return (headers as Record<string, string>)[REQUEST_ID_HEADER] ?? undefined;
15
+ if (headers instanceof Headers) {
16
+ return headers.get(REQUEST_ID_HEADER) ?? undefined;
26
17
  }
27
18
 
28
- if (headers instanceof Headers) return headers.get(REQUEST_ID_HEADER) ?? undefined;
29
-
30
19
  if (Array.isArray(headers)) {
31
20
  for (const [key, value] of headers) {
32
21
  if (key.toLowerCase() === REQUEST_ID_HEADER) return value;
@@ -34,5 +23,5 @@ export const resolveRequestId = (source: HeaderSource): string | undefined => {
34
23
  return undefined;
35
24
  }
36
25
 
37
- return undefined;
26
+ return headers[REQUEST_ID_HEADER];
38
27
  };
@@ -18,17 +18,6 @@ export const prepareRequestHeaders = (request: Request) => {
18
18
  return headers;
19
19
  };
20
20
 
21
- export const prepareRequestBody = async (request: Request) => {
22
- let requestBytes = 0;
23
- let body: ArrayBuffer | undefined;
24
- if (request.body) {
25
- body = await request.arrayBuffer();
26
- requestBytes = body.byteLength;
27
- }
28
-
29
- return { body, requestBytes };
30
- };
31
-
32
21
  export const prepareForwardHeaders = (request: Request): Record<string, string> => {
33
22
  const userAgent = request.headers.get("user-agent");
34
23
  const appendedUserAgent = userAgent
@@ -16,7 +16,7 @@ class JsonToSseTransformStream extends TransformStream<unknown, string> {
16
16
  }
17
17
 
18
18
  export const prepareResponseInit = (request: Request): ResponseInit => ({
19
- headers: { [REQUEST_ID_HEADER]: resolveRequestId(request.headers)! },
19
+ headers: { [REQUEST_ID_HEADER]: resolveRequestId(request)! },
20
20
  });
21
21
 
22
22
  export const mergeResponseInit = (
@@ -1,2 +0,0 @@
1
- import type { GatewayConfigParsed, GatewayContext } from "../types";
2
- export declare const withOtel: (run: (ctx: GatewayContext) => Promise<void>, config: GatewayConfigParsed) => (ctx: GatewayContext) => Promise<void>;
@@ -1,46 +0,0 @@
1
- import { SpanStatusCode } from "@opentelemetry/api";
2
- import { initFetch } from "./fetch";
3
- import { startSpan } from "./span";
4
- import { instrumentStream } from "./stream";
5
- import { getAIAttributes, getBaggageAttributes, getRequestAttributes, getResponseAttributes, } from "./utils";
6
- export const withOtel = (run, config) => async (ctx) => {
7
- const requestStart = performance.now();
8
- const aiSpan = startSpan(ctx.request.url, undefined, config.telemetry?.tracer);
9
- initFetch();
10
- const endAiSpan = (status, stats) => {
11
- const attrs = getAIAttributes(ctx.body, ctx.streamResult ?? ctx.result, config.telemetry?.attributes, ctx.resolvedProviderId);
12
- attrs["gen_ai.server.request.duration"] = Number(((performance.now() - requestStart) / 1000).toFixed(4));
13
- if (!aiSpan.isExisting) {
14
- Object.assign(attrs, getRequestAttributes(ctx.request, config.telemetry?.attributes), getResponseAttributes(ctx.response, config.telemetry?.attributes));
15
- }
16
- Object.assign(attrs, getBaggageAttributes(ctx.request));
17
- if (config.telemetry?.attributes === "full") {
18
- attrs["http.request.body.size"] = Number(ctx.request.headers.get("content-length") || 0);
19
- attrs["http.response.body.size"] =
20
- stats?.bytes ?? Number(attrs["http.response.header.content-length"] || 0);
21
- }
22
- attrs["http.response.status_code_effective"] = status;
23
- aiSpan.setStatus({ code: status >= 500 ? SpanStatusCode.ERROR : SpanStatusCode.OK });
24
- if (ctx.operation && ctx.modelId) {
25
- aiSpan.updateName(`${ctx.operation} ${ctx.modelId}`);
26
- }
27
- else if (ctx.operation) {
28
- aiSpan.updateName(`${ctx.operation}`);
29
- }
30
- aiSpan.setAttributes(attrs);
31
- aiSpan.finish();
32
- };
33
- await aiSpan.runWithContext(() => run(ctx));
34
- if (ctx.response.body instanceof ReadableStream) {
35
- const instrumented = instrumentStream(ctx.response.body, {
36
- onComplete: (status, params) => endAiSpan(status, params),
37
- }, ctx.request.signal);
38
- ctx.response = new Response(instrumented, {
39
- status: ctx.response.status,
40
- statusText: ctx.response.statusText,
41
- headers: ctx.response.headers,
42
- });
43
- return;
44
- }
45
- endAiSpan(ctx.response.status);
46
- };
@@ -1,4 +0,0 @@
1
- export declare const getRequestAttributes: (request?: Request, attributesLevel?: string) => {};
2
- export declare const getAIAttributes: (body?: object, result?: object, attributesLevel?: string, providerName?: string) => {};
3
- export declare const getResponseAttributes: (response?: Response, attributesLevel?: string) => {};
4
- export declare const getBaggageAttributes: (request?: Request) => Record<string, string>;
@@ -1,223 +0,0 @@
1
- import { resolveRequestId } from "../utils/headers";
2
- const DEFAULT_ATTRIBUTES_LEVEL = "recommended";
3
- const HEBO_BAGGAGE_PREFIX = "hebo.";
4
- const toTextPart = (content) => ({ type: "text", content });
5
- const toMessageParts = (message) => {
6
- if (message.role === "assistant") {
7
- const parts = [];
8
- if (typeof message.content === "string")
9
- parts.push(toTextPart(message.content));
10
- if (Array.isArray(message.tool_calls)) {
11
- for (const call of message.tool_calls) {
12
- parts.push({
13
- type: "tool_call",
14
- id: call.id,
15
- name: call.function.name,
16
- arguments: call.function.arguments,
17
- });
18
- }
19
- }
20
- return parts;
21
- }
22
- if (message.role === "tool") {
23
- return [{ type: "tool_call_response", id: message.tool_call_id, content: message.content }];
24
- }
25
- if (message.role === "user") {
26
- const parts = [];
27
- if (typeof message.content === "string")
28
- parts.push(toTextPart(message.content));
29
- if (Array.isArray(message.content)) {
30
- for (const part of message.content) {
31
- if (part.type === "text") {
32
- parts.push(toTextPart(part.text));
33
- }
34
- else if (part.type === "image_url") {
35
- parts.push({ type: "image", content: part.image_url.url });
36
- }
37
- else {
38
- parts.push({
39
- type: "file",
40
- // FUTURE: optionally expose safe metadata without raw binary payloads.
41
- content: part.file.filename ?? "[REDACTED_BINARY_DATA]",
42
- media_type: part.file.media_type,
43
- });
44
- }
45
- }
46
- }
47
- return parts;
48
- }
49
- return [];
50
- };
51
- export const getRequestAttributes = (request, attributesLevel = DEFAULT_ATTRIBUTES_LEVEL) => {
52
- if (!request)
53
- return {};
54
- let url;
55
- try {
56
- // FUTURE: use URL from lifecycle
57
- url = new URL(request.url);
58
- }
59
- catch { }
60
- const attrs = {
61
- "http.request.method": request.method,
62
- "url.full": request.url,
63
- "url.path": url?.pathname,
64
- "url.scheme": url?.protocol.replace(":", ""),
65
- "server.address": url?.hostname,
66
- "server.port": url
67
- ? url.port
68
- ? Number(url.port)
69
- : url.protocol === "https:"
70
- ? 443
71
- : 80
72
- : undefined,
73
- };
74
- if (attributesLevel !== "required") {
75
- Object.assign(attrs, {
76
- "http.request.id": resolveRequestId(request),
77
- "user_agent.original": request.headers.get("user-agent") ?? undefined,
78
- });
79
- }
80
- if (attributesLevel === "full") {
81
- Object.assign(attrs, {
82
- // FUTURE: "url.query"
83
- "http.request.header.content-type": [request.headers.get("content-type") ?? undefined],
84
- "http.request.header.content-length": [request.headers.get("content-length") ?? undefined],
85
- // FUTURE: "client.address"
86
- });
87
- }
88
- return attrs;
89
- };
90
- export const getAIAttributes = (body, result, attributesLevel = DEFAULT_ATTRIBUTES_LEVEL, providerName) => {
91
- if (!body && !result)
92
- return {};
93
- const isChat = !!body && "messages" in body;
94
- const isEmbeddings = !!body && "input" in body;
95
- const attrs = {
96
- "gen_ai.operation.name": isEmbeddings ? "embeddings" : isChat ? "chat" : undefined,
97
- "gen_ai.output.type": isEmbeddings ? "embedding" : isChat ? "text" : undefined,
98
- "gen_ai.request.model": body && "model" in body ? body.model : undefined,
99
- "gen_ai.provider.name": providerName,
100
- };
101
- if (isChat) {
102
- if (body) {
103
- const inputs = body;
104
- if (inputs.seed !== undefined) {
105
- Object.assign(attrs, { "gen_ai.request.seed": inputs.seed });
106
- }
107
- if (attributesLevel !== "required") {
108
- Object.assign(attrs, {
109
- "gen_ai.request.stream": inputs.stream,
110
- "gen_ai.request.frequency_penalty": inputs.frequency_penalty,
111
- "gen_ai.request.max_tokens": inputs.max_completion_tokens,
112
- "gen_ai.request.presence_penalty": inputs.presence_penalty,
113
- "gen_ai.request.stop_sequences": inputs.stop
114
- ? Array.isArray(inputs.stop)
115
- ? inputs.stop
116
- : [inputs.stop]
117
- : undefined,
118
- "gen_ai.request.temperature": inputs.temperature,
119
- "gen_ai.request.top_p": inputs.top_p,
120
- });
121
- }
122
- if (attributesLevel === "full") {
123
- Object.assign(attrs, {
124
- // FUTURE: only construct once
125
- "gen_ai.system_instructions": inputs.messages
126
- .filter((m) => m.role === "system")
127
- .map((m) => JSON.stringify({ parts: [toTextPart(m.content)] })),
128
- "gen_ai.input.messages": inputs.messages
129
- .filter((m) => m.role !== "system")
130
- .map((m) => JSON.stringify({ role: m.role, parts: toMessageParts(m) })),
131
- "gen_ai.tool.definitions": JSON.stringify(inputs.tools),
132
- });
133
- }
134
- }
135
- // FUTURE: implement streaming
136
- if (result && !(result instanceof ReadableStream)) {
137
- const completions = result;
138
- Object.assign(attrs, {
139
- "gen_ai.response.model": completions.model,
140
- "gen_ai.response.id": completions.id,
141
- });
142
- if (attributesLevel !== "required") {
143
- Object.assign(attrs, {
144
- "gen_ai.response.finish_reasons": completions.choices?.map((c) => c.finish_reason),
145
- "gen_ai.usage.total_tokens": completions.usage?.total_tokens,
146
- "gen_ai.usage.input_tokens": completions.usage?.prompt_tokens,
147
- "gen_ai.usage.cached_tokens": completions.usage?.prompt_tokens_details?.cached_tokens,
148
- "gen_ai.usage.output_tokens": completions.usage?.completion_tokens,
149
- "gen_ai.usage.reasoning_tokens": completions.usage?.completion_tokens_details?.reasoning_tokens,
150
- });
151
- }
152
- if (attributesLevel === "full") {
153
- Object.assign(attrs, {
154
- "gen_ai.output.messages": completions.choices?.map((c) => JSON.stringify({
155
- role: c.message.role,
156
- parts: toMessageParts(c.message),
157
- finish_reason: c.finish_reason,
158
- })),
159
- });
160
- }
161
- }
162
- }
163
- if (isEmbeddings) {
164
- if (body) {
165
- const inputs = body;
166
- if (attributesLevel !== "required") {
167
- Object.assign(attrs, {
168
- "gen_ai.embeddings.dimension.count": inputs.dimensions,
169
- });
170
- }
171
- }
172
- if (result) {
173
- const embeddings = result;
174
- Object.assign(attrs, {
175
- "gen_ai.response.model": embeddings.model,
176
- });
177
- if (attributesLevel !== "required") {
178
- Object.assign(attrs, {
179
- "gen_ai.usage.input_tokens": embeddings.usage?.prompt_tokens,
180
- "gen_ai.usage.total_tokens": embeddings.usage?.total_tokens,
181
- });
182
- }
183
- }
184
- }
185
- return attrs;
186
- };
187
- export const getResponseAttributes = (response, attributesLevel = DEFAULT_ATTRIBUTES_LEVEL) => {
188
- if (!response)
189
- return {};
190
- const attrs = {
191
- "http.response.status_code": response.status,
192
- };
193
- if (attributesLevel === "full") {
194
- Object.assign(attrs, {
195
- "http.response.header.content-type": [response.headers.get("content-type") ?? undefined],
196
- "http.response.header.content-length": [response.headers.get("content-length") ?? undefined],
197
- });
198
- }
199
- return attrs;
200
- };
201
- export const getBaggageAttributes = (request) => {
202
- const h = request?.headers.get("baggage");
203
- if (!h)
204
- return {};
205
- const attrs = {};
206
- for (const part of h.split(",")) {
207
- const [k, v] = part.trim().split("=", 2);
208
- if (!k || !v)
209
- continue;
210
- const [rawValue] = v.split(";", 1);
211
- if (!rawValue)
212
- continue;
213
- let value = rawValue;
214
- try {
215
- value = decodeURIComponent(rawValue);
216
- }
217
- catch { }
218
- if (k.startsWith(HEBO_BAGGAGE_PREFIX)) {
219
- attrs[k.slice(HEBO_BAGGAGE_PREFIX.length)] = value;
220
- }
221
- }
222
- return attrs;
223
- };
@@ -1,87 +0,0 @@
1
- import type { Attributes } from "@opentelemetry/api";
2
-
3
- import { SpanStatusCode } from "@opentelemetry/api";
4
-
5
- import type { GatewayConfigParsed, GatewayContext } from "../types";
6
-
7
- import { initFetch } from "./fetch";
8
- import { startSpan } from "./span";
9
- import { instrumentStream } from "./stream";
10
- import {
11
- getAIAttributes,
12
- getBaggageAttributes,
13
- getRequestAttributes,
14
- getResponseAttributes,
15
- } from "./utils";
16
-
17
- export const withOtel =
18
- (run: (ctx: GatewayContext) => Promise<void>, config: GatewayConfigParsed) =>
19
- async (ctx: GatewayContext) => {
20
- const requestStart = performance.now();
21
- const aiSpan = startSpan(ctx.request.url, undefined, config.telemetry?.tracer);
22
- initFetch();
23
-
24
- const endAiSpan = (status: number, stats?: { bytes: number }) => {
25
- const attrs: Attributes = getAIAttributes(
26
- ctx.body,
27
- ctx.streamResult ?? ctx.result,
28
- config.telemetry?.attributes,
29
- ctx.resolvedProviderId,
30
- );
31
-
32
- attrs["gen_ai.server.request.duration"] = Number(
33
- ((performance.now() - requestStart) / 1000).toFixed(4),
34
- );
35
-
36
- if (!aiSpan.isExisting) {
37
- Object.assign(
38
- attrs,
39
- getRequestAttributes(ctx.request, config.telemetry?.attributes),
40
- getResponseAttributes(ctx.response, config.telemetry?.attributes),
41
- );
42
- }
43
-
44
- Object.assign(attrs, getBaggageAttributes(ctx.request));
45
-
46
- if (config.telemetry?.attributes === "full") {
47
- attrs["http.request.body.size"] = Number(ctx.request.headers.get("content-length") || 0);
48
- attrs["http.response.body.size"] =
49
- stats?.bytes ?? Number(attrs["http.response.header.content-length"] || 0);
50
- }
51
-
52
- attrs["http.response.status_code_effective"] = status;
53
-
54
- aiSpan.setStatus({ code: status >= 500 ? SpanStatusCode.ERROR : SpanStatusCode.OK });
55
-
56
- if (ctx.operation && ctx.modelId) {
57
- aiSpan.updateName(`${ctx.operation} ${ctx.modelId}`);
58
- } else if (ctx.operation) {
59
- aiSpan.updateName(`${ctx.operation}`);
60
- }
61
-
62
- aiSpan.setAttributes(attrs);
63
-
64
- aiSpan.finish();
65
- };
66
-
67
- await aiSpan.runWithContext(() => run(ctx));
68
-
69
- if (ctx.response!.body instanceof ReadableStream) {
70
- const instrumented = instrumentStream(
71
- ctx.response!.body,
72
- {
73
- onComplete: (status, params) => endAiSpan(status, params),
74
- },
75
- ctx.request.signal,
76
- );
77
-
78
- ctx.response = new Response(instrumented, {
79
- status: ctx.response!.status,
80
- statusText: ctx.response!.statusText,
81
- headers: ctx.response!.headers,
82
- });
83
- return;
84
- }
85
-
86
- endAiSpan(ctx.response!.status);
87
- };