@xsai-ext/telemetry 0.4.0-beta.9 → 0.4.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.
package/dist/index.d.ts CHANGED
@@ -1,10 +1,9 @@
1
- import { AttributeValue } from '@opentelemetry/api';
1
+ import { Attributes } from '@opentelemetry/api';
2
2
  import { WithUnknown, GenerateTextOptions, GenerateTextResult, StreamTextOptions, StreamTextResult } from 'xsai';
3
3
  export * from 'xsai';
4
4
 
5
- type TelemetryMetadata = Record<string, AttributeValue>;
6
5
  interface TelemetryOptions {
7
- metadata?: TelemetryMetadata;
6
+ attributes?: Attributes;
8
7
  }
9
8
  type WithTelemetry<T> = T & {
10
9
  telemetry?: TelemetryOptions;
@@ -23,4 +22,4 @@ declare const generateText: (options: WithUnknown<WithTelemetry<GenerateTextOpti
23
22
  declare const streamText: (options: WithUnknown<WithTelemetry<StreamTextOptions>>) => StreamTextResult;
24
23
 
25
24
  export { generateText, streamText };
26
- export type { TelemetryMetadata, TelemetryOptions, WithTelemetry };
25
+ export type { TelemetryOptions, WithTelemetry };
package/dist/index.js CHANGED
@@ -1,91 +1,37 @@
1
- import { determineStepType, clean, executeTool, trampoline, chat, responseJSON, DelayedPromise, objCamelToSnake } from 'xsai';
1
+ import { trampoline, chat, responseJSON, determineStepType, executeTool, DelayedPromise, objCamelToSnake } from 'xsai';
2
2
  export * from 'xsai';
3
3
  import { trace, SpanStatusCode } from '@opentelemetry/api';
4
4
 
5
- const metadataAttributes = (metadata = {}) => Object.fromEntries(
6
- Object.entries(metadata).map(([key, value]) => [`ai.telemetry.metadata.${key}`, value])
7
- );
8
- const idAttributes = () => {
9
- const id = crypto.randomUUID();
10
- return {
11
- "ai.response.id": id,
12
- "ai.response.timestamp": (/* @__PURE__ */ new Date()).toISOString(),
13
- "gen_ai.response.id": id
14
- };
15
- };
16
- const commonAttributes = (operationId, model) => ({
17
- "ai.model.id": model,
18
- // TODO: provider name
19
- "ai.model.provider": "xsai",
20
- "ai.operationId": operationId,
21
- "ai.response.providerMetadata": "{}",
22
- "operation.name": operationId
5
+ const chatSpan = (options, tracer) => ({
6
+ attributes: {
7
+ "gen_ai.input.messages": JSON.stringify(options.messages),
8
+ "gen_ai.operation.name": "chat",
9
+ "gen_ai.provider.name": "openai",
10
+ "gen_ai.request.choice.count": 1,
11
+ "gen_ai.request.frequency_penalty": options.frequencyPenalty,
12
+ "gen_ai.request.model": options.model,
13
+ "gen_ai.request.presence_penalty": options.presencePenalty,
14
+ "gen_ai.request.seed": options.seed,
15
+ "gen_ai.request.stop_sequences": options.stop == null ? void 0 : Array.isArray(options.stop) ? options.stop : [options.stop],
16
+ "gen_ai.request.temperature": options.temperature,
17
+ "gen_ai.request.top_k": options.topK,
18
+ "gen_ai.request.top_p": options.topP,
19
+ "gen_ai.response.id": crypto.randomUUID(),
20
+ "gen_ai.response.model": options.model,
21
+ "gen_ai.tool.definitions": JSON.stringify(options.tools?.map((tool) => ({ function: tool.function, type: tool.type }))),
22
+ "server.address": new URL(options.baseURL).host,
23
+ ...options.telemetry?.attributes
24
+ // TODO: gen_ai.output.type
25
+ },
26
+ name: `chat ${options.model}`,
27
+ tracer
23
28
  });
24
29
 
25
- const extractGenerateTextStep = async (options, res) => {
26
- const { choices, usage } = res;
27
- if (!choices?.length)
28
- throw new Error(`No choices returned, response body: ${JSON.stringify(res)}`);
29
- const messages = [];
30
- const toolCalls = [];
31
- const { finish_reason: finishReason, message } = choices[0];
32
- const msgToolCalls = message?.tool_calls ?? [];
33
- const stepType = determineStepType({
34
- finishReason,
35
- maxSteps: options.maxSteps ?? 1,
36
- stepsLength: options.steps?.length ?? 0,
37
- toolCallsLength: msgToolCalls.length
38
- });
39
- messages.push(clean({
40
- ...message,
41
- reasoning_content: void 0
42
- }));
43
- if (finishReason !== "stop" || stepType !== "done") {
44
- for (const toolCall of msgToolCalls) {
45
- toolCalls.push({
46
- args: toolCall.function.arguments,
47
- toolCallId: toolCall.id,
48
- toolCallType: toolCall.type,
49
- toolName: toolCall.function.name
50
- });
51
- }
52
- }
53
- return [
54
- {
55
- finishReason,
56
- stepType,
57
- text: message.content,
58
- toolCalls,
59
- usage
60
- },
61
- {
62
- messages,
63
- msgToolCalls,
64
- reasoningText: message.reasoning_content
65
- }
66
- ];
67
- };
68
- const extractGenerateTextStepPost = async (options, msgToolCalls) => {
69
- const inputMessages = structuredClone(options.messages);
70
- const outputMessages = [];
71
- const toolResults = [];
72
- for (const toolCall of msgToolCalls) {
73
- const { completionToolResult, message } = await executeTool({
74
- abortSignal: options.abortSignal,
75
- messages: inputMessages,
76
- toolCall,
77
- tools: options.tools
78
- });
79
- toolResults.push(completionToolResult);
80
- outputMessages.push(message);
81
- }
82
- return [
83
- toolResults,
84
- outputMessages
85
- ];
86
- };
30
+ var version = "0.4.0";
31
+ var pkg = {
32
+ version: version};
87
33
 
88
- const getTracer = () => trace.getTracer("@xsai-ext/telemetry");
34
+ const getTracer = () => trace.getTracer("@xsai-ext/telemetry", pkg.version);
89
35
 
90
36
  const recordErrorOnSpan = (span, error) => {
91
37
  if (error instanceof Error) {
@@ -122,44 +68,28 @@ const recordSpan = async ({
122
68
  throw error;
123
69
  }
124
70
  });
125
- const recordSpanSync = ({
126
- attributes,
127
- endWhenDone = true,
128
- name,
129
- tracer
130
- }, fn) => tracer.startActiveSpan(name, { attributes }, (span) => {
131
- try {
132
- const result = fn(span);
133
- if (endWhenDone)
134
- span.end();
135
- return result;
136
- } catch (error) {
137
- try {
138
- recordErrorOnSpan(span, error);
139
- } finally {
140
- span.end();
141
- }
142
- throw error;
143
- }
144
- });
145
-
146
- const stringifyTool = ({ function: func, type }) => JSON.stringify({ description: func.description, inputSchema: func.parameters, name: func.name, type });
147
71
 
148
72
  const wrapTool = (tool, tracer) => ({
149
73
  execute: async (input, options) => recordSpan({
150
74
  attributes: {
151
- "ai.operationId": "ai.toolCall",
152
- "ai.toolCall.args": JSON.stringify(input),
153
- "ai.toolCall.id": options.toolCallId,
154
- "ai.toolCall.name": tool.function.name,
155
- "operation.name": "ai.toolCall"
75
+ "gen_ai.operation.name": "execute_tool",
76
+ "gen_ai.tool.call.arguments": JSON.stringify(input),
77
+ "gen_ai.tool.call.description": tool.function.description,
78
+ "gen_ai.tool.call.id": options.toolCallId,
79
+ "gen_ai.tool.call.name": tool.function.name
156
80
  },
157
- name: "ai.toolCall",
81
+ name: `execute_tool ${tool.function.name}`,
158
82
  tracer
159
83
  }, async (span) => {
160
- const result = await tool.execute(input, options);
161
- span.setAttribute("ai.toolCall.result", JSON.stringify(result));
162
- return result;
84
+ try {
85
+ const result = await tool.execute(input, options);
86
+ span.setAttribute("gen_ai.tool.call.result", JSON.stringify(result));
87
+ return result;
88
+ } catch (err) {
89
+ if (err instanceof Error)
90
+ span.setAttribute("error.type", err.message);
91
+ throw err;
92
+ }
163
93
  }),
164
94
  function: tool.function,
165
95
  type: tool.type
@@ -167,65 +97,63 @@ const wrapTool = (tool, tracer) => ({
167
97
 
168
98
  const generateText = async (options) => {
169
99
  const tracer = getTracer();
170
- const rawGenerateText = async (options2) => {
100
+ const rawGenerateText = async (options2) => recordSpan(chatSpan(options2, tracer), async (span) => chat({
101
+ ...options2,
102
+ maxSteps: void 0,
103
+ steps: void 0,
104
+ stream: false
105
+ }).then(responseJSON).then(async (res) => {
106
+ const { choices, usage } = res;
107
+ if (!choices?.length)
108
+ throw new Error(`No choices returned, response body: ${JSON.stringify(res)}`);
171
109
  const messages = structuredClone(options2.messages);
172
110
  const steps = options2.steps ? structuredClone(options2.steps) : [];
173
- const [stepWithoutToolCalls, { messages: msgs1, msgToolCalls, reasoningText }] = await recordSpan({
174
- attributes: {
175
- ...idAttributes(),
176
- ...commonAttributes("ai.generateText.doGenerate", options2.model),
177
- ...metadataAttributes(options2.telemetry?.metadata),
178
- ...options2.tools != null && options2.tools.length > 0 ? {
179
- "ai.prompt.toolChoice": JSON.stringify(options2.toolChoice ?? { type: "auto" }),
180
- "ai.prompt.tools": options2.tools.map(stringifyTool)
181
- } : {},
182
- "ai.prompt.messages": JSON.stringify(messages),
183
- "ai.response.model": options2.model,
184
- "gen_ai.request.model": options2.model,
185
- "gen_ai.response.id": crypto.randomUUID(),
186
- "gen_ai.response.model": options2.model,
187
- "gen_ai.system": "xsai"
188
- },
189
- name: "ai.generateText.doGenerate",
190
- tracer
191
- }, async (span) => {
192
- const res = await chat({
193
- ...options2,
194
- maxSteps: void 0,
195
- steps: void 0,
196
- stream: false,
197
- telemetry: void 0
198
- }).then(responseJSON);
199
- const [step2, { messages: msgs, msgToolCalls: msgToolCalls2, reasoningText: reasoningText2 }] = await extractGenerateTextStep({
200
- ...options2,
201
- messages,
202
- steps
203
- }, res);
204
- span.setAttributes({
205
- ...step2.text != null && step2.toolCalls.length === 0 ? { "ai.response.text": step2.text } : {},
206
- ...step2.toolCalls.length > 0 ? { "ai.response.toolCalls": JSON.stringify(step2.toolCalls) } : {},
207
- "ai.response.finishReason": step2.finishReason,
208
- "ai.usage.completionTokens": step2.usage.completion_tokens,
209
- "ai.usage.promptTokens": step2.usage.prompt_tokens,
210
- "gen_ai.response.finish_reasons": [step2.finishReason],
211
- "gen_ai.usage.input_tokens": step2.usage.prompt_tokens,
212
- "gen_ai.usage.output_tokens": step2.usage.completion_tokens
213
- });
214
- return [step2, { messages: msgs, msgToolCalls: msgToolCalls2, reasoningText: reasoningText2 }];
111
+ const toolCalls = [];
112
+ const toolResults = [];
113
+ const { finish_reason: finishReason, message } = choices[0];
114
+ const msgToolCalls = message?.tool_calls ?? [];
115
+ const stepType = determineStepType({
116
+ finishReason,
117
+ maxSteps: options2.maxSteps ?? 1,
118
+ stepsLength: steps.length,
119
+ toolCallsLength: msgToolCalls.length
215
120
  });
216
- const [toolResults, msgs2] = await extractGenerateTextStepPost({
217
- ...options2,
218
- messages}, msgToolCalls);
219
- const step = { ...stepWithoutToolCalls, toolResults };
121
+ messages.push(message);
122
+ span.setAttribute("gen_ai.output.messages", JSON.stringify([message]));
123
+ if (finishReason !== "stop" && stepType !== "done") {
124
+ for (const toolCall of msgToolCalls) {
125
+ const { completionToolCall, completionToolResult, message: message2 } = await executeTool({
126
+ abortSignal: options2.abortSignal,
127
+ messages,
128
+ toolCall,
129
+ tools: options2.tools
130
+ });
131
+ toolCalls.push(completionToolCall);
132
+ toolResults.push(completionToolResult);
133
+ messages.push(message2);
134
+ }
135
+ }
136
+ const step = {
137
+ finishReason,
138
+ stepType,
139
+ text: Array.isArray(message.content) ? message.content.filter((m) => m.type === "text").map((m) => m.text).join("\n") : message.content,
140
+ toolCalls,
141
+ toolResults,
142
+ usage
143
+ };
220
144
  steps.push(step);
221
- messages.push(...msgs1, ...msgs2);
145
+ span.setAttributes({
146
+ "gen_ai.response.finish_reasons": [step.finishReason],
147
+ "gen_ai.usage.input_tokens": step.usage.prompt_tokens,
148
+ "gen_ai.usage.output_tokens": step.usage.completion_tokens
149
+ });
222
150
  if (options2.onStepFinish)
223
151
  await options2.onStepFinish(step);
224
152
  if (step.finishReason === "stop" || step.stepType === "done") {
225
153
  return {
226
154
  finishReason: step.finishReason,
227
155
  messages,
228
- reasoningText,
156
+ reasoningText: message.reasoning_content,
229
157
  steps,
230
158
  text: step.text,
231
159
  toolCalls: step.toolCalls,
@@ -239,33 +167,13 @@ const generateText = async (options) => {
239
167
  steps
240
168
  });
241
169
  }
242
- };
243
- return recordSpan({
244
- attributes: {
245
- ...commonAttributes("ai.generateText", options.model),
246
- ...metadataAttributes(options.telemetry?.metadata),
247
- "ai.prompt": JSON.stringify({ messages: options.messages })
248
- },
249
- name: "ai.generateText",
250
- tracer
251
- }, async (span) => {
252
- const result = await trampoline(async () => rawGenerateText({
253
- ...options,
254
- tools: options.tools?.map((tool) => wrapTool(tool, tracer))
255
- }));
256
- span.setAttributes({
257
- ...result.toolCalls.length > 0 ? { "ai.response.toolCalls": JSON.stringify(result.toolCalls) } : {},
258
- ...result.text != null ? { "ai.response.text": result.text } : {},
259
- "ai.response.finishReason": result.finishReason,
260
- "ai.usage.completionTokens": result.usage.completion_tokens,
261
- "ai.usage.promptTokens": result.usage.prompt_tokens
262
- });
263
- return result;
264
- });
170
+ }));
171
+ return trampoline(async () => rawGenerateText({
172
+ ...options,
173
+ tools: options.tools?.map((tool) => wrapTool(tool, tracer))
174
+ }));
265
175
  };
266
176
 
267
- const now = () => globalThis?.performance?.now() ?? Date.now();
268
-
269
177
  const parseChunk = (text) => {
270
178
  if (!text || !text.startsWith("data:"))
271
179
  return [void 0, false];
@@ -318,8 +226,10 @@ const streamText = (options) => {
318
226
  const resultTotalUsage = new DelayedPromise();
319
227
  let eventCtrl;
320
228
  let textCtrl;
229
+ let reasoningTextCtrl;
321
230
  const eventStream = new ReadableStream({ start: (controller) => eventCtrl = controller });
322
231
  const textStream = new ReadableStream({ start: (controller) => textCtrl = controller });
232
+ const reasoningTextStream = new ReadableStream({ start: (controller) => reasoningTextCtrl = controller });
323
233
  const pushEvent = (stepEvent) => {
324
234
  eventCtrl?.enqueue(stepEvent);
325
235
  void options.onEvent?.(stepEvent);
@@ -329,26 +239,7 @@ const streamText = (options) => {
329
239
  void options.onStepFinish?.(step);
330
240
  };
331
241
  const tools = options.tools != null && options.tools.length > 0 ? options.tools.map((tool) => wrapTool(tool, tracer)) : void 0;
332
- const doStream = async () => recordSpan({
333
- attributes: {
334
- ...idAttributes(),
335
- ...commonAttributes("ai.streamText.doStream", options.model),
336
- ...metadataAttributes(options.telemetry?.metadata),
337
- ...tools != null && tools.length > 0 && {
338
- "ai.prompt.toolChoice": JSON.stringify(options.toolChoice ?? { type: "auto" }),
339
- "ai.prompt.tools": tools.map(stringifyTool)
340
- },
341
- "ai.prompt.messages": JSON.stringify(options.messages),
342
- "ai.response.model": options.model,
343
- "gen_ai.request.model": options.model,
344
- "gen_ai.response.id": crypto.randomUUID(),
345
- "gen_ai.response.model": options.model,
346
- "gen_ai.system": "xsai"
347
- },
348
- name: "ai.streamText.doStream",
349
- tracer
350
- }, async (span) => {
351
- const startMs = now();
242
+ const doStream = async () => recordSpan(chatSpan({ ...options, messages }, tracer), async (span) => {
352
243
  const { body: stream } = await chat({
353
244
  ...options,
354
245
  maxSteps: void 0,
@@ -366,15 +257,21 @@ const streamText = (options) => {
366
257
  } : { ...u };
367
258
  };
368
259
  let text = "";
260
+ let reasoningText;
369
261
  const pushText = (content) => {
370
262
  textCtrl?.enqueue(content);
371
263
  text += content;
372
264
  };
265
+ const pushReasoningText = (reasoningContent) => {
266
+ if (reasoningText == null)
267
+ reasoningText = "";
268
+ reasoningTextCtrl?.enqueue(reasoningContent);
269
+ reasoningText += reasoningContent;
270
+ };
373
271
  const tool_calls = [];
374
272
  const toolCalls = [];
375
273
  const toolResults = [];
376
274
  let finishReason = "other";
377
- let firstChunk = true;
378
275
  await stream.pipeThrough(transformChunk()).pipeTo(new WritableStream({
379
276
  abort: (reason) => {
380
277
  eventCtrl?.error(reason);
@@ -384,23 +281,15 @@ const streamText = (options) => {
384
281
  },
385
282
  // eslint-disable-next-line sonarjs/cognitive-complexity
386
283
  write: (chunk) => {
387
- if (firstChunk) {
388
- const msToFirstChunk = now() - startMs;
389
- span.addEvent("ai.stream.firstChunk", {
390
- "ai.response.msToFirstChunk": msToFirstChunk
391
- });
392
- span.setAttributes({
393
- "ai.response.msToFirstChunk": msToFirstChunk
394
- });
395
- firstChunk = false;
396
- }
397
284
  if (chunk.usage)
398
285
  pushUsage(chunk.usage);
399
286
  if (chunk.choices == null || chunk.choices.length === 0)
400
287
  return;
401
288
  const choice = chunk.choices[0];
402
- if (choice.delta.reasoning_content != null)
289
+ if (choice.delta.reasoning_content != null) {
403
290
  pushEvent({ text: choice.delta.reasoning_content, type: "reasoning-delta" });
291
+ pushReasoningText(choice.delta.reasoning_content);
292
+ }
404
293
  if (choice.finish_reason != null)
405
294
  finishReason = choice.finish_reason;
406
295
  if (choice.delta.tool_calls?.length === 0 || choice.delta.tool_calls == null) {
@@ -420,24 +309,40 @@ const streamText = (options) => {
420
309
  ...toolCall,
421
310
  function: {
422
311
  ...toolCall.function,
423
- arguments: toolCall.function.arguments
312
+ arguments: toolCall.function.arguments ?? ""
424
313
  }
425
314
  };
426
- pushEvent({ toolCallId: toolCall.id, toolName: toolCall.function.name, type: "tool-call-streaming-start" });
315
+ pushEvent({
316
+ toolCallId: toolCall.id,
317
+ toolName: toolCall.function.name,
318
+ type: "tool-call-streaming-start"
319
+ });
427
320
  } else {
428
321
  tool_calls[index].function.arguments += toolCall.function.arguments;
429
- pushEvent({ argsTextDelta: toolCall.function.arguments, toolCallId: toolCall.id, toolName: toolCall.function.name ?? tool_calls[index].function.name, type: "tool-call-delta" });
322
+ pushEvent({
323
+ argsTextDelta: toolCall.function.arguments,
324
+ toolCallId: toolCall.id,
325
+ toolName: toolCall.function.name ?? tool_calls[index].function.name,
326
+ type: "tool-call-delta"
327
+ });
430
328
  }
431
329
  }
432
330
  }
433
331
  }
434
332
  }));
435
- messages.push({ content: text, role: "assistant", tool_calls });
333
+ const message = {
334
+ content: text,
335
+ reasoning_content: reasoningText,
336
+ role: "assistant",
337
+ tool_calls: tool_calls.length > 0 ? tool_calls : void 0
338
+ };
339
+ messages.push(message);
340
+ span.setAttribute("gen_ai.output.messages", JSON.stringify([message]));
436
341
  if (tool_calls.length !== 0) {
437
342
  for (const toolCall of tool_calls) {
438
343
  if (toolCall == null)
439
344
  continue;
440
- const { completionToolCall, completionToolResult, message } = await executeTool({
345
+ const { completionToolCall, completionToolResult, message: message2 } = await executeTool({
441
346
  abortSignal: options.abortSignal,
442
347
  messages,
443
348
  toolCall,
@@ -445,7 +350,7 @@ const streamText = (options) => {
445
350
  });
446
351
  toolCalls.push(completionToolCall);
447
352
  toolResults.push(completionToolResult);
448
- messages.push(message);
353
+ messages.push(message2);
449
354
  pushEvent({ ...completionToolCall, type: "tool-call" });
450
355
  pushEvent({ ...completionToolResult, type: "tool-result" });
451
356
  }
@@ -465,19 +370,9 @@ const streamText = (options) => {
465
370
  usage
466
371
  };
467
372
  pushStep(step);
468
- const msToFinish = now() - startMs;
469
- span.addEvent("ai.stream.finish");
470
373
  span.setAttributes({
471
- "ai.response.msToFinish": msToFinish,
472
- ...step.toolCalls.length > 0 && { "ai.response.toolCalls": JSON.stringify(step.toolCalls) },
473
- "ai.response.finishReason": step.finishReason,
474
- "ai.response.text": step.text != null ? step.text : "",
475
374
  "gen_ai.response.finish_reasons": [step.finishReason],
476
375
  ...step.usage && {
477
- "ai.response.avgOutputTokensPerSecond": 1e3 * (step.usage.completion_tokens ?? 0) / msToFinish,
478
- "ai.usage.inputTokens": step.usage.prompt_tokens,
479
- "ai.usage.outputTokens": step.usage.completion_tokens,
480
- "ai.usage.totalTokens": step.usage.total_tokens,
481
376
  "gen_ai.usage.input_tokens": step.usage.prompt_tokens,
482
377
  "gen_ai.usage.output_tokens": step.usage.completion_tokens
483
378
  }
@@ -485,61 +380,38 @@ const streamText = (options) => {
485
380
  if (toolCalls.length !== 0 && steps.length < maxSteps)
486
381
  return async () => doStream();
487
382
  });
488
- return recordSpanSync({
489
- attributes: {
490
- ...commonAttributes("ai.streamText", options.model),
491
- ...metadataAttributes(options.telemetry?.metadata),
492
- "ai.prompt": JSON.stringify({ messages: options.messages })
493
- },
494
- endWhenDone: false,
495
- name: "ai.streamText",
496
- tracer
497
- }, (rootSpan) => {
498
- void (async () => {
499
- try {
500
- await trampoline(async () => doStream());
501
- eventCtrl?.close();
502
- textCtrl?.close();
503
- } catch (err) {
504
- eventCtrl?.error(err);
505
- textCtrl?.error(err);
506
- resultSteps.reject(err);
507
- resultMessages.reject(err);
508
- resultUsage.reject(err);
509
- resultTotalUsage.reject(err);
510
- } finally {
511
- resultSteps.resolve(steps);
512
- resultMessages.resolve(messages);
513
- resultUsage.resolve(usage);
514
- resultTotalUsage.resolve(totalUsage);
515
- const finishStep = steps.at(-1);
516
- if (finishStep) {
517
- rootSpan.setAttributes({
518
- ...finishStep.toolCalls.length > 0 && { "ai.response.toolCalls": JSON.stringify(finishStep.toolCalls) },
519
- "ai.response.finishReason": finishStep.finishReason,
520
- "ai.response.text": finishStep.text != null ? finishStep.text : ""
521
- });
522
- }
523
- if (totalUsage) {
524
- rootSpan.setAttributes({
525
- "ai.usage.inputTokens": totalUsage.prompt_tokens,
526
- "ai.usage.outputTokens": totalUsage.completion_tokens,
527
- "ai.usage.totalTokens": totalUsage.total_tokens
528
- });
529
- }
530
- void options.onFinish?.(finishStep);
531
- rootSpan.end();
532
- }
533
- })();
534
- return {
535
- fullStream: eventStream,
536
- messages: resultMessages.promise,
537
- steps: resultSteps.promise,
538
- textStream,
539
- totalUsage: resultTotalUsage.promise,
540
- usage: resultUsage.promise
541
- };
542
- });
383
+ void (async () => {
384
+ try {
385
+ await trampoline(async () => doStream());
386
+ eventCtrl?.close();
387
+ textCtrl?.close();
388
+ reasoningTextCtrl?.close();
389
+ } catch (err) {
390
+ eventCtrl?.error(err);
391
+ textCtrl?.error(err);
392
+ reasoningTextCtrl?.error(err);
393
+ resultSteps.reject(err);
394
+ resultMessages.reject(err);
395
+ resultUsage.reject(err);
396
+ resultTotalUsage.reject(err);
397
+ } finally {
398
+ resultSteps.resolve(steps);
399
+ resultMessages.resolve(messages);
400
+ resultUsage.resolve(usage);
401
+ resultTotalUsage.resolve(totalUsage);
402
+ const finishStep = steps.at(-1);
403
+ void options.onFinish?.(finishStep);
404
+ }
405
+ })();
406
+ return {
407
+ fullStream: eventStream,
408
+ messages: resultMessages.promise,
409
+ reasoningTextStream,
410
+ steps: resultSteps.promise,
411
+ textStream,
412
+ totalUsage: resultTotalUsage.promise,
413
+ usage: resultUsage.promise
414
+ };
543
415
  };
544
416
 
545
417
  export { generateText, streamText };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xsai-ext/telemetry",
3
3
  "type": "module",
4
- "version": "0.4.0-beta.9",
4
+ "version": "0.4.0",
5
5
  "description": "extra-small AI SDK.",
6
6
  "author": "Moeru AI",
7
7
  "license": "MIT",
@@ -30,18 +30,17 @@
30
30
  ],
31
31
  "dependencies": {
32
32
  "@opentelemetry/api": "^1.9.0",
33
- "xsai": "~0.4.0-beta.9"
33
+ "xsai": "~0.4.0"
34
34
  },
35
35
  "devDependencies": {
36
- "@opentelemetry/sdk-trace-base": "^2.0.1",
37
- "@opentelemetry/sdk-trace-node": "^2.0.1",
38
- "ai": "^5.0.28",
39
- "ollama-ai-provider-v2": "^1.2.1",
40
- "zod": "^4.1.5"
36
+ "@langfuse/otel": "^4.5.1",
37
+ "@opentelemetry/sdk-trace-base": "^2.2.0",
38
+ "@opentelemetry/sdk-trace-node": "^2.2.0",
39
+ "zod": "^4.2.1"
41
40
  },
42
41
  "scripts": {
43
42
  "build": "pkgroll",
44
- "test": "vitest run --update"
43
+ "test": "vitest run"
45
44
  },
46
45
  "main": "./dist/index.js",
47
46
  "types": "./dist/index.d.ts"