@apo-ai/sdk 0.7.0 → 0.8.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.
@@ -4,7 +4,7 @@ import {
4
4
  emitGenerationAndTools,
5
5
  safeParse,
6
6
  startGeneration
7
- } from "./chunk-GDB6SDGN.js";
7
+ } from "./chunk-3OQ3JBFJ.js";
8
8
 
9
9
  // src/agent-task/integrations/openai.ts
10
10
  function createApoOpenAI(client, options) {
@@ -36,6 +36,9 @@ function createApoOpenAI(client, options) {
36
36
  text,
37
37
  promptTokens: response.usage?.prompt_tokens,
38
38
  completionTokens: response.usage?.completion_tokens,
39
+ // OpenAI-compatible providers report thinking tokens here (o-series,
40
+ // DeepSeek, GLM). Absent detail = unreported, not zero.
41
+ reasoningTokens: response.usage?.completion_tokens_details?.reasoning_tokens,
39
42
  toolCalls,
40
43
  taskId,
41
44
  turnNumber
@@ -77,4 +80,4 @@ function createApoOpenAI(client, options) {
77
80
  export {
78
81
  createApoOpenAI
79
82
  };
80
- //# sourceMappingURL=chunk-6ZRW5IW6.js.map
83
+ //# sourceMappingURL=chunk-3B3L2H4Y.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/agent-task/integrations/openai.ts"],"sourcesContent":["/**\n * OpenAI JS SDK tracing integration for apo.\n *\n * Wraps an `OpenAI` client so `chat.completions.create()` calls are\n * automatically traced. Tool calls and generations are captured without\n * any manual span code in the adapter.\n *\n * Uses a Proxy to intercept the `create` method, then emits GENERATION +\n * TOOL spans through the trace context, where they become the observations\n * that `t.calledTool` reads.\n *\n * @example\n * ```ts\n * import OpenAI from \"openai\";\n * import { createApoOpenAI } from \"@apo-ai/sdk/agent-task\";\n *\n * async sendUserTurn(turn, { trace, parentSpanId }) {\n * const client = createApoOpenAI(\n * new OpenAI({ apiKey, baseURL: \"https://openrouter.ai/api/v1\" }),\n * { trace, parentSpanId, taskId: ctx.task.id, turnNumber }\n * );\n * const response = await client.chat.completions.create({\n * model: \"deepseek/deepseek-v4.1-flash\",\n * messages: [{ role: \"user\", content: String(turn) }],\n * tools: [...],\n * });\n * return { response: response.choices[0].message.content ?? \"\" };\n * }\n * ```\n *\n * @module\n */\n\nimport type { AgentTaskTraceContext } from \"../tracing.ts\";\nimport { startGeneration, emitGenerationAndTools, safeParse } from \"./span-helpers.ts\";\n\nexport interface CreateApoOpenAIOptions {\n /** The tee'd trace context from `sendUserTurn`'s second argument. */\n trace: AgentTaskTraceContext;\n /** The parent span id for this turn (from `sendUserTurn`'s context). */\n parentSpanId?: string;\n /** Optional task id, surfaced in span metadata. */\n taskId?: string;\n /** Optional turn number, surfaced in span metadata. */\n turnNumber?: number;\n}\n\n/**\n * Minimal structural shape of the OpenAI client we need to intercept.\n * We avoid importing `openai` at the type level so this module doesn't\n * take a runtime dependency — the wrapper works with any client that has\n * `chat.completions.create`.\n *\n * The `create` signature uses a broad type so the OpenAI SDK's overloaded\n * `create` method (which has specific param types) is assignable to this.\n */\ninterface OpenAIClientLike {\n chat: {\n completions: {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n create: (...args: any[]) => Promise<any>;\n };\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [key: string]: any;\n}\n\n/**\n * The response shape we read from `chat.completions.create()`.\n */\ninterface OpenAIResponseLike {\n model?: string;\n choices?: Array<{\n message?: {\n content?: string | null;\n tool_calls?: Array<{\n function?: { name?: string; arguments?: string };\n }>;\n };\n }>;\n usage?: {\n prompt_tokens?: number;\n completion_tokens?: number;\n completion_tokens_details?: {\n /** Thinking tokens, reported by o-series/DeepSeek/GLM-style providers. */\n reasoning_tokens?: number;\n };\n };\n}\n\n/**\n * Wrap an OpenAI client so `chat.completions.create()` is traced.\n * Returns a Proxy-wrapped client — call it exactly as you would the\n * original. Non-streaming only; streaming responses pass through\n * untraced.\n *\n * For SDKs that emit OpenTelemetry natively (OpenAI Agents SDK, Vercel AI\n * SDK, Claude Agent SDK), prefer `configureApoTelemetry()` instead — no\n * wrapper needed. This manual wrapper is for the raw `openai` package or\n * custom clients that don't emit OTel on their own.\n */\nexport function createApoOpenAI<T extends OpenAIClientLike>(\n client: T,\n options: CreateApoOpenAIOptions,\n): T {\n const { trace, parentSpanId, taskId, turnNumber } = options;\n const realCreate = client.chat.completions.create.bind(client.chat.completions);\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const tracedCreate = async (...args: any[]): Promise<any> => {\n const params = (args[0] ?? {}) as Record<string, unknown>;\n const model = String(params.model ?? \"unknown\");\n\n // Non-streaming: trace fully. Streaming (stream: true): pass through.\n if (params.stream === true) {\n return realCreate(params);\n }\n\n const { spanId, startedAt } = startGeneration(trace, {\n model,\n system: typeof params.system === \"string\" ? params.system : undefined,\n messages: params.messages,\n parentSpanId,\n taskId,\n turnNumber,\n });\n\n try {\n const response = (await realCreate(params)) as OpenAIResponseLike;\n const message = response.choices?.[0]?.message;\n const text = message?.content ?? undefined;\n const toolCalls = (message?.tool_calls ?? []).map((tc) => ({\n name: tc.function?.name ?? \"unknown\",\n input: tc.function?.arguments ? safeParse(tc.function.arguments) : undefined,\n }));\n\n emitGenerationAndTools(trace, spanId, startedAt, {\n text,\n promptTokens: response.usage?.prompt_tokens,\n completionTokens: response.usage?.completion_tokens,\n // OpenAI-compatible providers report thinking tokens here (o-series,\n // DeepSeek, GLM). Absent detail = unreported, not zero.\n reasoningTokens:\n response.usage?.completion_tokens_details?.reasoning_tokens,\n toolCalls,\n taskId,\n turnNumber,\n });\n\n return response;\n } catch (error) {\n emitGenerationAndTools(trace, spanId, startedAt, {\n taskId,\n turnNumber,\n error: { message: error instanceof Error ? error.message : String(error) },\n });\n throw error;\n }\n };\n\n // Proxy that intercepts client.chat.completions.create but passes\n // everything else through unchanged.\n return new Proxy(client, {\n get(target, prop, receiver) {\n if (prop === \"chat\") {\n return new Proxy(target.chat, {\n get(chatTarget, chatProp) {\n if (chatProp === \"completions\") {\n return new Proxy(chatTarget.completions, {\n get(completionsTarget, completionsProp) {\n if (completionsProp === \"create\") {\n return tracedCreate;\n }\n return Reflect.get(completionsTarget, completionsProp);\n },\n });\n }\n return Reflect.get(chatTarget, chatProp);\n },\n });\n }\n return Reflect.get(target, prop, receiver);\n },\n }) as T;\n}\n"],"mappings":";;;;;;;;;AAqGO,SAAS,gBACd,QACA,SACG;AACH,QAAM,EAAE,OAAO,cAAc,QAAQ,WAAW,IAAI;AACpD,QAAM,aAAa,OAAO,KAAK,YAAY,OAAO,KAAK,OAAO,KAAK,WAAW;AAG9E,QAAM,eAAe,UAAU,SAA8B;AAC3D,UAAM,SAAU,KAAK,CAAC,KAAK,CAAC;AAC5B,UAAM,QAAQ,OAAO,OAAO,SAAS,SAAS;AAG9C,QAAI,OAAO,WAAW,MAAM;AAC1B,aAAO,WAAW,MAAM;AAAA,IAC1B;AAEA,UAAM,EAAE,QAAQ,UAAU,IAAI,gBAAgB,OAAO;AAAA,MACnD;AAAA,MACA,QAAQ,OAAO,OAAO,WAAW,WAAW,OAAO,SAAS;AAAA,MAC5D,UAAU,OAAO;AAAA,MACjB;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,QAAI;AACF,YAAM,WAAY,MAAM,WAAW,MAAM;AACzC,YAAM,UAAU,SAAS,UAAU,CAAC,GAAG;AACvC,YAAM,OAAO,SAAS,WAAW;AACjC,YAAM,aAAa,SAAS,cAAc,CAAC,GAAG,IAAI,CAAC,QAAQ;AAAA,QACzD,MAAM,GAAG,UAAU,QAAQ;AAAA,QAC3B,OAAO,GAAG,UAAU,YAAY,UAAU,GAAG,SAAS,SAAS,IAAI;AAAA,MACrE,EAAE;AAEF,6BAAuB,OAAO,QAAQ,WAAW;AAAA,QAC/C;AAAA,QACA,cAAc,SAAS,OAAO;AAAA,QAC9B,kBAAkB,SAAS,OAAO;AAAA;AAAA;AAAA,QAGlC,iBACE,SAAS,OAAO,2BAA2B;AAAA,QAC7C;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAED,aAAO;AAAA,IACT,SAAS,OAAO;AACd,6BAAuB,OAAO,QAAQ,WAAW;AAAA,QAC/C;AAAA,QACA;AAAA,QACA,OAAO,EAAE,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AAAA,MAC3E,CAAC;AACD,YAAM;AAAA,IACR;AAAA,EACF;AAIA,SAAO,IAAI,MAAM,QAAQ;AAAA,IACvB,IAAI,QAAQ,MAAM,UAAU;AAC1B,UAAI,SAAS,QAAQ;AACnB,eAAO,IAAI,MAAM,OAAO,MAAM;AAAA,UAC5B,IAAI,YAAY,UAAU;AACxB,gBAAI,aAAa,eAAe;AAC9B,qBAAO,IAAI,MAAM,WAAW,aAAa;AAAA,gBACvC,IAAI,mBAAmB,iBAAiB;AACtC,sBAAI,oBAAoB,UAAU;AAChC,2BAAO;AAAA,kBACT;AACA,yBAAO,QAAQ,IAAI,mBAAmB,eAAe;AAAA,gBACvD;AAAA,cACF,CAAC;AAAA,YACH;AACA,mBAAO,QAAQ,IAAI,YAAY,QAAQ;AAAA,UACzC;AAAA,QACF,CAAC;AAAA,MACH;AACA,aAAO,QAAQ,IAAI,QAAQ,MAAM,QAAQ;AAAA,IAC3C;AAAA,EACF,CAAC;AACH;","names":[]}
@@ -26,6 +26,7 @@ function emitGenerationAndTools(trace, genSpanId, genStartedAt, opts) {
26
26
  latency_ms: latency,
27
27
  prompt_tokens: opts.promptTokens,
28
28
  completion_tokens: opts.completionTokens,
29
+ reasoning_tokens: opts.reasoningTokens,
29
30
  output: {
30
31
  ...opts.text !== void 0 ? { text: opts.text } : {},
31
32
  ...opts.error ? { error: opts.error.message } : {}
@@ -72,4 +73,4 @@ export {
72
73
  emitGenerationAndTools,
73
74
  startGeneration
74
75
  };
75
- //# sourceMappingURL=chunk-GDB6SDGN.js.map
76
+ //# sourceMappingURL=chunk-3OQ3JBFJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/agent-task/integrations/span-helpers.ts"],"sourcesContent":["/**\n * Shared span-emission helpers for the tracing integrations.\n *\n * The OpenAI and Anthropic wrappers both need to:\n * 1. Create a GENERATION span before the LLM call\n * 2. End it with text/tokens/latency after the response\n * 3. Emit a TOOL span for each tool call in the response\n *\n * These helpers keep that logic in one place so the wrappers stay thin.\n *\n * @internal\n */\n\nimport type { AgentTaskTraceContext } from \"../tracing.ts\";\n\nfunction monotonicNowMs(): number {\n if (typeof performance !== \"undefined\" && typeof performance.now === \"function\") {\n return performance.now();\n }\n return Date.now();\n}\n\nfunction round3(n: number): number {\n return Math.round(n * 1000) / 1000;\n}\n\nfunction safeParse(raw: unknown): unknown {\n if (typeof raw !== \"string\") return raw;\n try {\n return JSON.parse(raw);\n } catch {\n return raw;\n }\n}\n\n/**\n * Emit a GENERATION span + TOOL spans for a completed LLM call.\n *\n * Both wrappers call this after the SDK returns the response. It:\n * 1. Ends the GENERATION span (created by {@link startGeneration}) with\n * text, token counts, and latency.\n * 2. Emits a TOOL span per tool call, so `t.calledTool(name)` works.\n */\nexport function emitGenerationAndTools(\n trace: AgentTaskTraceContext,\n genSpanId: string,\n genStartedAt: number,\n opts: {\n text?: string;\n promptTokens?: number;\n completionTokens?: number;\n /** Reasoning tokens, when the provider reports the dimension. */\n reasoningTokens?: number;\n toolCalls?: Array<{ name: string; input?: unknown }>;\n taskId?: string;\n turnNumber?: number;\n error?: { message: string };\n },\n): void {\n const latency = round3(monotonicNowMs() - genStartedAt);\n const isError = !!opts.error;\n\n // End the GENERATION span\n trace.endSpan(genSpanId, {\n latency_ms: latency,\n prompt_tokens: opts.promptTokens,\n completion_tokens: opts.completionTokens,\n reasoning_tokens: opts.reasoningTokens,\n output: {\n ...(opts.text !== undefined ? { text: opts.text } : {}),\n ...(opts.error ? { error: opts.error.message } : {}),\n },\n ...(isError\n ? { level: \"ERROR\" as const, status_message: opts.error!.message }\n : {}),\n });\n\n // Emit TOOL spans (only on success — errors are captured on the GENERATION span)\n if (!isError && opts.toolCalls) {\n for (const tc of opts.toolCalls) {\n const toolSpanId = trace.createSpan({\n task_id: opts.taskId ?? \"trace\",\n parent_call_id: genSpanId,\n step_name: tc.name,\n observation_type: \"TOOL\",\n ...(tc.input !== undefined\n ? {\n input:\n tc.input && typeof tc.input === \"object\"\n ? (tc.input as Record<string, unknown>)\n : { value: tc.input },\n }\n : {}),\n metadata: { toolName: tc.name },\n });\n trace.endSpan(toolSpanId, {});\n }\n }\n}\n\n/**\n * Create a GENERATION span before the LLM call.\n * Returns `{ spanId, startedAt }` to pass to {@link emitGenerationAndTools}.\n */\nexport function startGeneration(\n trace: AgentTaskTraceContext,\n opts: {\n model: string;\n system?: string;\n messages?: unknown;\n parentSpanId?: string;\n taskId?: string;\n turnNumber?: number;\n },\n): { spanId: string; startedAt: number } {\n const spanId = trace.createSpan({\n task_id: opts.taskId ?? \"trace\",\n parent_call_id: opts.parentSpanId ?? trace.rootSpanId,\n step_name: \"agent.generate\",\n model: opts.model,\n observation_type: \"GENERATION\",\n input: {\n ...(opts.system !== undefined ? { system: opts.system } : {}),\n ...(opts.messages !== undefined ? { messages: opts.messages } : {}),\n },\n metadata: {\n ...(opts.turnNumber !== undefined ? { turnNumber: opts.turnNumber } : {}),\n ...(opts.taskId ? { taskId: opts.taskId } : {}),\n },\n });\n return { spanId, startedAt: monotonicNowMs() };\n}\n\n// Re-export safeParse for the wrappers (OpenAI arguments is a JSON string)\nexport { safeParse };\n"],"mappings":";;;;AAeA,SAAS,iBAAyB;AAChC,MAAI,OAAO,gBAAgB,eAAe,OAAO,YAAY,QAAQ,YAAY;AAC/E,WAAO,YAAY,IAAI;AAAA,EACzB;AACA,SAAO,KAAK,IAAI;AAClB;AAEA,SAAS,OAAO,GAAmB;AACjC,SAAO,KAAK,MAAM,IAAI,GAAI,IAAI;AAChC;AAEA,SAAS,UAAU,KAAuB;AACxC,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,MAAI;AACF,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAUO,SAAS,uBACd,OACA,WACA,cACA,MAWM;AACN,QAAM,UAAU,OAAO,eAAe,IAAI,YAAY;AACtD,QAAM,UAAU,CAAC,CAAC,KAAK;AAGvB,QAAM,QAAQ,WAAW;AAAA,IACvB,YAAY;AAAA,IACZ,eAAe,KAAK;AAAA,IACpB,mBAAmB,KAAK;AAAA,IACxB,kBAAkB,KAAK;AAAA,IACvB,QAAQ;AAAA,MACN,GAAI,KAAK,SAAS,SAAY,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,MACrD,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAM,QAAQ,IAAI,CAAC;AAAA,IACpD;AAAA,IACA,GAAI,UACA,EAAE,OAAO,SAAkB,gBAAgB,KAAK,MAAO,QAAQ,IAC/D,CAAC;AAAA,EACP,CAAC;AAGD,MAAI,CAAC,WAAW,KAAK,WAAW;AAC9B,eAAW,MAAM,KAAK,WAAW;AAC/B,YAAM,aAAa,MAAM,WAAW;AAAA,QAClC,SAAS,KAAK,UAAU;AAAA,QACxB,gBAAgB;AAAA,QAChB,WAAW,GAAG;AAAA,QACd,kBAAkB;AAAA,QAClB,GAAI,GAAG,UAAU,SACb;AAAA,UACE,OACE,GAAG,SAAS,OAAO,GAAG,UAAU,WAC3B,GAAG,QACJ,EAAE,OAAO,GAAG,MAAM;AAAA,QAC1B,IACA,CAAC;AAAA,QACL,UAAU,EAAE,UAAU,GAAG,KAAK;AAAA,MAChC,CAAC;AACD,YAAM,QAAQ,YAAY,CAAC,CAAC;AAAA,IAC9B;AAAA,EACF;AACF;AAMO,SAAS,gBACd,OACA,MAQuC;AACvC,QAAM,SAAS,MAAM,WAAW;AAAA,IAC9B,SAAS,KAAK,UAAU;AAAA,IACxB,gBAAgB,KAAK,gBAAgB,MAAM;AAAA,IAC3C,WAAW;AAAA,IACX,OAAO,KAAK;AAAA,IACZ,kBAAkB;AAAA,IAClB,OAAO;AAAA,MACL,GAAI,KAAK,WAAW,SAAY,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;AAAA,MAC3D,GAAI,KAAK,aAAa,SAAY,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;AAAA,IACnE;AAAA,IACA,UAAU;AAAA,MACR,GAAI,KAAK,eAAe,SAAY,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;AAAA,MACvE,GAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AACD,SAAO,EAAE,QAAQ,WAAW,eAAe,EAAE;AAC/C;","names":[]}
@@ -3,7 +3,7 @@
3
3
  import {
4
4
  emitGenerationAndTools,
5
5
  startGeneration
6
- } from "./chunk-GDB6SDGN.js";
6
+ } from "./chunk-3OQ3JBFJ.js";
7
7
 
8
8
  // src/agent-task/integrations/anthropic.ts
9
9
  function createApoAnthropic(client, options) {
@@ -71,4 +71,4 @@ function createApoAnthropic(client, options) {
71
71
  export {
72
72
  createApoAnthropic
73
73
  };
74
- //# sourceMappingURL=chunk-SZDSIPXC.js.map
74
+ //# sourceMappingURL=chunk-43OI6GP7.js.map
@@ -2,7 +2,7 @@
2
2
  // Do not edit by hand — edit the src/ originals and rebuild.
3
3
  import {
4
4
  extractTokenUsage
5
- } from "./chunk-HWZVL6TS.js";
5
+ } from "./chunk-HLRXQAZ5.js";
6
6
 
7
7
  // src/agent-task/integrations/ai-sdk.ts
8
8
  function safeParse(raw) {
@@ -230,4 +230,4 @@ function extractTextFromAttrs(attrs) {
230
230
  export {
231
231
  createApoTracer
232
232
  };
233
- //# sourceMappingURL=chunk-QHRWPVPZ.js.map
233
+ //# sourceMappingURL=chunk-CKB3SUDO.js.map
@@ -9,9 +9,13 @@ function extractTokenUsage(attrs) {
9
9
  const completion_tokens = toNum(
10
10
  attrs["gen_ai.usage.output_tokens"] ?? attrs["gen_ai.usage.completion_tokens"] ?? attrs["ai.usage.completionTokens"]
11
11
  );
12
+ const reasoning_tokens = toNum(
13
+ attrs["gen_ai.usage.reasoning.output_tokens"] ?? attrs["ai.usage.reasoningTokens"] ?? attrs["llm.token_count.completion_details.reasoning"]
14
+ );
12
15
  const out = {};
13
16
  if (prompt_tokens !== void 0) out.prompt_tokens = prompt_tokens;
14
17
  if (completion_tokens !== void 0) out.completion_tokens = completion_tokens;
18
+ if (reasoning_tokens !== void 0) out.reasoning_tokens = reasoning_tokens;
15
19
  return out;
16
20
  }
17
21
  function toNum(val) {
@@ -23,15 +27,16 @@ function toNum(val) {
23
27
  return void 0;
24
28
  }
25
29
  function extractTokenCounts(attrs) {
26
- const { prompt_tokens, completion_tokens } = extractTokenUsage(attrs);
27
- return {
28
- ...prompt_tokens !== void 0 ? { promptTokens: prompt_tokens } : {},
29
- ...completion_tokens !== void 0 ? { completionTokens: completion_tokens } : {}
30
- };
30
+ const { prompt_tokens, completion_tokens, reasoning_tokens } = extractTokenUsage(attrs);
31
+ const out = {};
32
+ if (prompt_tokens !== void 0) out.promptTokens = prompt_tokens;
33
+ if (completion_tokens !== void 0) out.completionTokens = completion_tokens;
34
+ if (reasoning_tokens !== void 0) out.reasoningTokens = reasoning_tokens;
35
+ return out;
31
36
  }
32
37
 
33
38
  export {
34
39
  extractTokenUsage,
35
40
  extractTokenCounts
36
41
  };
37
- //# sourceMappingURL=chunk-HWZVL6TS.js.map
42
+ //# sourceMappingURL=chunk-HLRXQAZ5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/agent-task/integrations/token-usage.ts"],"sourcesContent":["/**\n * Shared token-usage extraction for the tracing integrations.\n *\n * Both the `createApoTracer` path (`ai-sdk.ts`) and the OTel-native path\n * (`otel-translate.ts` → `otel-processor.ts`) must extract token counts from\n * AI SDK span attributes using the same logic. This helper is the single\n * source of truth — it prevents the two paths from drifting apart (the bug\n * where one path reported tokens and the other silently dropped them).\n *\n * The AI SDK emits usage under `ai.usage.promptTokens` /\n * `ai.usage.completionTokens`. Standard OTel GenAI conventions use\n * `gen_ai.usage.input_tokens` / `gen_ai.usage.output_tokens`. Both are\n * recognized; standard `gen_ai.usage.*` takes precedence when both are present.\n *\n * @internal\n */\n\ntype SpanAttributes = Record<string, unknown>;\n\n/**\n * Extract `prompt_tokens` / `completion_tokens` / `reasoning_tokens` from a\n * span's attributes. Returns an object shaped to spread directly into\n * `trace.endSpan()` params. Omits keys when the corresponding attribute is\n * absent or non-numeric.\n *\n * Recognizes all known attribute-name conventions:\n * - Vercel AI SDK: `ai.usage.promptTokens` / `ai.usage.completionTokens` /\n * `ai.usage.reasoningTokens`\n * - OTel GenAI: `gen_ai.usage.input_tokens` / `gen_ai.usage.output_tokens` /\n * `gen_ai.usage.reasoning.output_tokens`\n * - OTel GenAI (alt): `gen_ai.usage.prompt_tokens` / `gen_ai.usage.completion_tokens`\n * - OpenInference: `llm.token_count.completion_details.reasoning`\n *\n * `gen_ai.usage.*` takes precedence over `ai.usage.*` when both are present.\n * Reasoning is omitted when unreported — a provider that never sends the\n * dimension must surface as unknown downstream, never as zero.\n */\nexport function extractTokenUsage(attrs: SpanAttributes): {\n prompt_tokens?: number;\n completion_tokens?: number;\n reasoning_tokens?: number;\n} {\n const prompt_tokens = toNum(\n attrs[\"gen_ai.usage.input_tokens\"] ??\n attrs[\"gen_ai.usage.prompt_tokens\"] ??\n attrs[\"ai.usage.promptTokens\"],\n );\n const completion_tokens = toNum(\n attrs[\"gen_ai.usage.output_tokens\"] ??\n attrs[\"gen_ai.usage.completion_tokens\"] ??\n attrs[\"ai.usage.completionTokens\"],\n );\n const reasoning_tokens = toNum(\n attrs[\"gen_ai.usage.reasoning.output_tokens\"] ??\n attrs[\"ai.usage.reasoningTokens\"] ??\n attrs[\"llm.token_count.completion_details.reasoning\"],\n );\n const out: {\n prompt_tokens?: number;\n completion_tokens?: number;\n reasoning_tokens?: number;\n } = {};\n if (prompt_tokens !== undefined) out.prompt_tokens = prompt_tokens;\n if (completion_tokens !== undefined) out.completion_tokens = completion_tokens;\n if (reasoning_tokens !== undefined) out.reasoning_tokens = reasoning_tokens;\n return out;\n}\n\n/**\n * Coerce a span attribute value to a number. Accepts numbers directly and\n * parses finite numeric strings; ignores everything else.\n */\nfunction toNum(val: unknown): number | undefined {\n if (typeof val === \"number\") return val;\n if (typeof val === \"string\") {\n const n = Number(val);\n return Number.isFinite(n) ? n : undefined;\n }\n return undefined;\n}\n\n/**\n * Re-exported so the integrations can read prompt/completion tokens as\n * separate values (the OTel translate path returns them individually, not\n * as an endSpan-ready object).\n */\nexport function extractTokenCounts(attrs: SpanAttributes): {\n promptTokens?: number;\n completionTokens?: number;\n reasoningTokens?: number;\n} {\n const { prompt_tokens, completion_tokens, reasoning_tokens } =\n extractTokenUsage(attrs);\n // Plain assignments, not conditional spreads: this runs once per span on\n // the translate hot path, and each spread allocates an intermediate object\n // (three of them measurably regressed the 300-span benchmark).\n const out: {\n promptTokens?: number;\n completionTokens?: number;\n reasoningTokens?: number;\n } = {};\n if (prompt_tokens !== undefined) out.promptTokens = prompt_tokens;\n if (completion_tokens !== undefined) out.completionTokens = completion_tokens;\n if (reasoning_tokens !== undefined) out.reasoningTokens = reasoning_tokens;\n return out;\n}\n"],"mappings":";;;;AAqCO,SAAS,kBAAkB,OAIhC;AACA,QAAM,gBAAgB;AAAA,IACpB,MAAM,2BAA2B,KAC/B,MAAM,4BAA4B,KAClC,MAAM,uBAAuB;AAAA,EACjC;AACA,QAAM,oBAAoB;AAAA,IACxB,MAAM,4BAA4B,KAChC,MAAM,gCAAgC,KACtC,MAAM,2BAA2B;AAAA,EACrC;AACA,QAAM,mBAAmB;AAAA,IACvB,MAAM,sCAAsC,KAC1C,MAAM,0BAA0B,KAChC,MAAM,8CAA8C;AAAA,EACxD;AACA,QAAM,MAIF,CAAC;AACL,MAAI,kBAAkB,OAAW,KAAI,gBAAgB;AACrD,MAAI,sBAAsB,OAAW,KAAI,oBAAoB;AAC7D,MAAI,qBAAqB,OAAW,KAAI,mBAAmB;AAC3D,SAAO;AACT;AAMA,SAAS,MAAM,KAAkC;AAC/C,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,MAAI,OAAO,QAAQ,UAAU;AAC3B,UAAM,IAAI,OAAO,GAAG;AACpB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AACT;AAOO,SAAS,mBAAmB,OAIjC;AACA,QAAM,EAAE,eAAe,mBAAmB,iBAAiB,IACzD,kBAAkB,KAAK;AAIzB,QAAM,MAIF,CAAC;AACL,MAAI,kBAAkB,OAAW,KAAI,eAAe;AACpD,MAAI,sBAAsB,OAAW,KAAI,mBAAmB;AAC5D,MAAI,qBAAqB,OAAW,KAAI,kBAAkB;AAC1D,SAAO;AACT;","names":[]}
@@ -2,7 +2,7 @@
2
2
  // Do not edit by hand — edit the src/ originals and rebuild.
3
3
  import {
4
4
  extractTokenCounts
5
- } from "./chunk-HWZVL6TS.js";
5
+ } from "./chunk-HLRXQAZ5.js";
6
6
 
7
7
  // src/otel/index.ts
8
8
  import {
@@ -205,6 +205,7 @@ function buildEndParams(t) {
205
205
  const params = {};
206
206
  if (t.promptTokens !== void 0) params.prompt_tokens = t.promptTokens;
207
207
  if (t.completionTokens !== void 0) params.completion_tokens = t.completionTokens;
208
+ if (t.reasoningTokens !== void 0) params.reasoning_tokens = t.reasoningTokens;
208
209
  if (t.observationType === "GENERATION") {
209
210
  if (t.text !== void 0) {
210
211
  params.output = { text: t.text };
@@ -492,4 +493,4 @@ export {
492
493
  injectTraceparent,
493
494
  extractTraceparent
494
495
  };
495
- //# sourceMappingURL=chunk-GETY34VI.js.map
496
+ //# sourceMappingURL=chunk-IPQ6IIEG.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/otel/index.ts","../src/agent-task/integrations/run-context.ts","../src/agent-task/integrations/otel-translate.ts","../src/agent-task/integrations/otel-processor.ts","../src/agent-task/integrations/register.ts"],"sourcesContent":["/**\n * OTel-native tracing setup for the apo SDK.\n *\n * This module provides the standard OpenTelemetry setup path for apo:\n * - ``configureApoTelemetry`` creates a tracer provider with an OTLP exporter\n * - ``withApoTrace`` creates root/child spans using standard OTel context\n * - Helper functions (``traceTool``, ``traceAgent``, etc.) emit normal OTel\n * spans with GenAI or apo vendor attributes\n *\n * This replaced the removed ``TraceTracker`` custom event protocol — apo\n * tracing is OTel-only now.\n *\n * @module @apo-ai/sdk/otel\n */\n\nimport {\n trace,\n context,\n defaultTextMapGetter,\n defaultTextMapSetter,\n type Span,\n type Tracer,\n type SpanOptions,\n type TracerProvider,\n} from \"@opentelemetry/api\";\nimport { W3CTraceContextPropagator } from \"@opentelemetry/core\";\n\n/**\n * Shared W3C Trace Context propagator. OTel's official implementation handles\n * `traceparent`/`tracestate`, edge cases (invalid traceId/spanId, versioning),\n * and stays correct as the spec evolves — so we delegate to it instead of\n * hand-rolling `00-<trace>-<span>-<flags>` parsing.\n */\nconst w3cPropagator = new W3CTraceContextPropagator();\nimport {\n BasicTracerProvider,\n BatchSpanProcessor,\n SimpleSpanProcessor,\n type SpanExporter,\n type SpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\nimport { AsyncHooksContextManager } from \"@opentelemetry/context-async-hooks\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport {\n SEMRESATTRS_SERVICE_NAME,\n SEMRESATTRS_SERVICE_VERSION,\n SEMRESATTRS_SERVICE_NAMESPACE,\n SEMRESATTRS_DEPLOYMENT_ENVIRONMENT,\n} from \"@opentelemetry/semantic-conventions\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport { getRegisteredApoProcessor } from \"../agent-task/integrations/register.ts\";\n\n// ── public types ───────────────────────────────────────────\n\nexport interface ConfigureApoTelemetryOptions {\n /** Explicit permission for this standalone helper to own OTel lifecycle. */\n takeOwnership: true;\n /**\n * Full OTLP traces endpoint URL. Falls back to the `APO_OTLP_ENDPOINT` env\n * var when omitted, then to `http://localhost:8000/api/public/otel/v1/traces`.\n */\n endpoint?: string;\n /**\n * Diagnostic resource attribute only. Auth owns tenancy — the project is\n * determined by the API key / service token, never by this field.\n * Falls back to the `APO_PROJECT` env var when omitted.\n */\n project?: string;\n /**\n * Service name for the OTel resource (required). Defaults to `\"apo-agent\"`.\n */\n serviceName?: string;\n /** Service version for the OTel resource. */\n serviceVersion?: string;\n /** Deployment environment (production, staging, etc.). */\n environment?: string;\n /**\n * Auth headers (Authorization: Basic/Bearer). Falls back to headers derived\n * from `APO_PUBLIC_KEY`+`APO_SECRET_KEY` (Basic) or `APO_AUTH_TOKEN` (Bearer)\n * env vars when omitted. Use {@link buildApoAuthHeaders} to build them\n * explicitly.\n */\n headers?: Record<string, string>;\n /**\n * Public key for Basic auth. Falls back to `APO_PUBLIC_KEY` env var.\n * Used with `secretKey` to build `Authorization: Basic base64(pk:sk)`.\n */\n publicKey?: string;\n /**\n * Secret key for Basic auth. Falls back to `APO_SECRET_KEY` env var.\n */\n secretKey?: string;\n /**\n * Bearer auth token. Falls back to `APO_AUTH_TOKEN` env var. Used only when\n * `publicKey`/`secretKey` are absent.\n */\n authToken?: string;\n /**\n * @deprecated OTel JS 2.x providers cannot accept processors after\n * construction. Use `createApoSpanProcessor()` in the host provider's\n * `spanProcessors` array instead.\n */\n provider?: TracerProvider;\n /** Batch for services; simple is useful for short-lived task subprocesses. */\n processor?: \"batch\" | \"simple\";\n /**\n * Whether to register apo's provider as the global tracer provider. Defaults\n * to false. When true, registration only happens if no global provider is\n * registered yet — apo never silently replaces an existing global provider.\n */\n registerGlobal?: boolean;\n}\n\nexport interface ApoTelemetryHandle {\n /** The OTel tracer for creating spans. */\n tracer: Tracer;\n /** The provider, for advanced use. */\n provider: TracerProvider;\n /** Export all ended spans without relinquishing provider ownership. */\n forceFlush(): Promise<void>;\n /** Gracefully shut down the provider and flush pending spans. */\n shutdown(): Promise<void>;\n}\n\nexport interface ApoTraceExporterOptions {\n /** Full standard OTLP/HTTP traces endpoint. */\n endpoint: string;\n /** Authentication headers sent by the official OTLP exporter. */\n headers: Record<string, string>;\n}\n\nexport interface ApoSpanProcessorOptions extends ApoTraceExporterOptions {\n /** Batch for long-lived services (default), simple for short-lived jobs. */\n processor?: \"batch\" | \"simple\";\n}\n\nexport interface ApoTraceOptions {\n /** Span name. */\n name: string;\n /** apo.observation.type override. */\n observationType?: string;\n /** Additional attributes. */\n attributes?: Record<string, unknown>;\n}\n\ninterface OwnedContextState {\n manager: AsyncHooksContextManager;\n references: number;\n}\n\nlet ownedContext: OwnedContextState | null = null;\n\n/** Create the official OTLP/HTTP exporter for a host-owned OTel provider. */\nexport function createApoTraceExporter(\n options: ApoTraceExporterOptions,\n): SpanExporter {\n return new OTLPTraceExporter({\n url: options.endpoint,\n headers: options.headers,\n });\n}\n\n/**\n * Create an apo OTLP processor for inclusion when constructing a host provider.\n * This factory has no global side effects and transfers lifecycle ownership to\n * the host provider.\n */\nexport function createApoSpanProcessor(\n options: ApoSpanProcessorOptions,\n): SpanProcessor {\n const exporter = createApoTraceExporter(options);\n return options.processor === \"simple\"\n ? new SimpleSpanProcessor(exporter)\n : new BatchSpanProcessor(exporter);\n}\n\n// ── auth + env-var helpers (mirror apo-otel-python) ─────────────────────\n\nconst DEFAULT_APO_OTLP_ENDPOINT = \"http://localhost:8000/api/public/otel/v1/traces\";\n\n/**\n * Build the `Authorization` header for apo's OTLP endpoint.\n *\n * - If `publicKey` + `secretKey` are given (or read from `APO_PUBLIC_KEY` /\n * `APO_SECRET_KEY`), returns HTTP Basic: `Basic base64(pk:sk)`. * both halves are required — a public identifier alone is not a\n * credential, so partial Basic is never synthesized.\n * - Else if `authToken` is given (or read from `APO_AUTH_TOKEN`), returns\n * `Bearer <token>`. (Covers short-lived task-run/attempt tokens and\n * secret-bearing legacy API keys.)\n * - Else returns an empty object (unauthenticated).\n *\n * Mirrors Python's `_build_auth_headers` in apo-otel-python.\n */\nexport function buildApoAuthHeaders(\n publicKey?: string,\n secretKey?: string,\n authToken?: string,\n): Record<string, string> {\n const pk = publicKey ?? process.env.APO_PUBLIC_KEY;\n const sk = secretKey ?? process.env.APO_SECRET_KEY;\n const token = authToken ?? process.env.APO_AUTH_TOKEN;\n if (pk && sk) {\n const credentials = typeof btoa === \"function\"\n ? btoa(`${pk}:${sk}`)\n : Buffer.from(`${pk}:${sk}`).toString(\"base64\");\n return { Authorization: `Basic ${credentials}` };\n }\n if (token) {\n return { Authorization: `Bearer ${token}` };\n }\n return {};\n}\n\n/**\n * Resolve telemetry config: explicit kwarg wins, else env var, else default.\n * Mirrors Python's `_resolve_standalone_configuration`.\n */\nfunction resolveTelemetryConfig(options: ConfigureApoTelemetryOptions): {\n endpoint: string;\n headers: Record<string, string>;\n project: string | undefined;\n serviceName: string;\n} {\n const endpoint = options.endpoint\n ?? process.env.APO_OTLP_ENDPOINT\n ?? DEFAULT_APO_OTLP_ENDPOINT;\n const project = options.project ?? process.env.APO_PROJECT;\n const serviceName = options.serviceName ?? \"apo-agent\";\n const headers = options.headers\n ?? buildApoAuthHeaders(options.publicKey, options.secretKey, options.authToken);\n return { endpoint, headers, project, serviceName };\n}\n\n/**\n * Set the GenAI content-capture env var so instrumentation libs emit prompt\n * and completion text into spans. Uses `setdefault`-equivalent semantics: an\n * explicit user setting is never overridden. Mirrors Python's\n * `_configure_instrumentation_environment`.\n */\nfunction configureInstrumentationEnvironment(): void {\n if (!process.env.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT) {\n process.env.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = \"span_only\";\n }\n}\n\n// ── configureApoTelemetry ────────────────────────────────────────────────\n\n/**\n * Configure apo's OpenTelemetry tracing.\n *\n * Builds apo's trace export from official OTel components: a real\n * {@link Resource} (service.name, service.version, deployment.environment)\n * and the official {@link OTLPTraceExporter} (standard OTLP/HTTP, no custom\n * wire format). This is the explicit standalone bootstrap. Host applications\n * that already own a provider should use `createApoSpanProcessor()` instead.\n *\n * @example\n * ```ts\n * import { configureApoTelemetry } from \"@apo-ai/sdk/otel\";\n *\n * const apo = await configureApoTelemetry({\n * takeOwnership: true,\n * endpoint: \"https://apo.example.com/api/public/otel/v1/traces\",\n * serviceName: \"my-agent\",\n * headers: { Authorization: `Basic ${btoa(\"pk-apo-...:sk-apo-...\")}` },\n * });\n *\n * const result = await withApoTrace({ name: \"chat\" }, apo.tracer, async (span) => {\n * span.setAttribute(\"gen_ai.request.model\", \"gpt-4o\");\n * // ... do work ...\n * return \"done\";\n * });\n *\n * await apo.shutdown();\n * ```\n */\nexport async function configureApoTelemetry(\n options: ConfigureApoTelemetryOptions,\n): Promise<ApoTelemetryHandle> {\n if (options.takeOwnership !== true) {\n throw new TypeError(\"standalone bootstrap requires takeOwnership: true\");\n }\n if (options.provider !== undefined) {\n throw new TypeError(\n \"A host provider cannot be mutated after construction in OTel JS 2.x; \" +\n \"construct it with createApoSpanProcessor() instead.\",\n );\n }\n\n // Resolve config from kwargs → env vars → defaults (mirror apo-otel-python).\n const { endpoint, headers, project, serviceName } = resolveTelemetryConfig(options);\n\n // Enable prompt/completion content capture for instrumentation libs.\n configureInstrumentationEnvironment();\n\n // Set up the async context manager so context.active() carries the active\n // span. Concurrent apo handles share one owned manager; a host-owned manager\n // is used as-is and is never disabled by apo.\n const releaseContext = acquireContextManager();\n\n // Build the official OTel Resource with standard semantic attributes.\n const resourceAttrs: Record<string, string> = {\n [SEMRESATTRS_SERVICE_NAME]: serviceName,\n };\n if (project) resourceAttrs[SEMRESATTRS_SERVICE_NAMESPACE] = project;\n if (options.serviceVersion) resourceAttrs[SEMRESATTRS_SERVICE_VERSION] = options.serviceVersion;\n if (options.environment) resourceAttrs[SEMRESATTRS_DEPLOYMENT_ENVIRONMENT] = options.environment;\n const resource = resourceFromAttributes(resourceAttrs);\n\n // Official OTLP/HTTP trace exporter — standard OTLP wire format, no custom\n // serializer. Headers carry the auth the apo receiver validates.\n // The standalone path owns this provider. Host-owned providers compose with\n // createApoSpanProcessor() at their own construction boundary.\n const spanProcessors: SpanProcessor[] = [createApoSpanProcessor({\n endpoint,\n headers,\n processor: options.processor,\n })];\n\n // If an ApoSpanProcessor was registered via registerApoTracing(), add it to\n // this provider so GenAI spans from SDKs (Vercel AI, OpenAI, etc.) are fed\n // into the projection-tee for local assertions AND exported via OTLP.\n const apoProcessor = getRegisteredApoProcessor();\n if (apoProcessor) {\n spanProcessors.push(apoProcessor as unknown as SpanProcessor);\n }\n\n const provider = new BasicTracerProvider({\n resource,\n spanProcessors,\n });\n\n const tracer = provider.getTracer(\"apo-sdk\", \"0.2.0\");\n\n // Register globally only when explicitly requested AND no host provider was\n // supplied AND no global provider is registered yet. apo never silently\n // replaces a host/global provider.\n let registeredGlobal = false;\n if (options.registerGlobal) {\n registeredGlobal = trace.setGlobalTracerProvider(provider);\n }\n\n let stopped = false;\n\n return {\n tracer,\n provider,\n async forceFlush() {\n await provider.forceFlush();\n },\n async shutdown() {\n if (stopped) return;\n stopped = true;\n // Force-flush + shut down the provider, but bound the whole teardown so\n // an unreachable export endpoint (e.g. no backend in unit tests) never\n // hangs the process. The span processors/exporters swallow their own\n // transport errors; this guards against the wait itself.\n await Promise.race([\n (async () => {\n await provider.forceFlush().catch(() => undefined);\n await provider.shutdown().catch(() => undefined);\n })(),\n new Promise<void>((resolve) => setTimeout(resolve, 2000)),\n ]);\n // Reset global state so a later configuration starts clean.\n if (registeredGlobal) {\n trace.disable();\n }\n releaseContext();\n },\n };\n}\n\nfunction acquireContextManager(): () => void {\n if (ownedContext !== null) {\n ownedContext.references += 1;\n const acquired = ownedContext;\n return () => releaseOwnedContext(acquired);\n }\n\n const manager = new AsyncHooksContextManager().enable();\n if (!context.setGlobalContextManager(manager)) {\n manager.disable();\n return () => undefined;\n }\n\n const acquired: OwnedContextState = { manager, references: 1 };\n ownedContext = acquired;\n return () => releaseOwnedContext(acquired);\n}\n\nfunction releaseOwnedContext(acquired: OwnedContextState): void {\n if (ownedContext !== acquired) return;\n acquired.references -= 1;\n if (acquired.references > 0) return;\n context.disable();\n ownedContext = null;\n}\n\n// ── withApoTrace ─────────────────────────────────────────────────────────\n\n/**\n * Run a function inside an OTel span.\n *\n * Creates a span using the active OTel context (standard context propagation,\n * not apo-specific span IDs passed by hand). The span automatically ends when\n * the function returns or throws.\n *\n * @example\n * ```ts\n * const result = await withApoTrace(\n * { name: \"agent-turn\", observationType: \"AGENT\" },\n * apo.tracer,\n * async (span) => {\n * span.setAttribute(\"gen_ai.request.model\", \"gpt-4o\");\n * return await llm.complete(\"hello\");\n * },\n * );\n * ```\n */\nexport async function withApoTrace<T>(\n options: ApoTraceOptions,\n tracer: Tracer,\n fn: (span: Span) => Promise<T>,\n): Promise<T> {\n const spanOptions: SpanOptions = {};\n if (options.attributes) {\n spanOptions.attributes = options.attributes as Record<string, string | number | boolean>;\n }\n\n // Use startActiveSpan which creates the span AND activates it in context,\n // so nested withApoTrace calls automatically inherit it as parent.\n return new Promise<T>((resolve, reject) => {\n tracer.startActiveSpan(options.name, spanOptions, async (span: Span) => {\n // Set apo vendor attributes\n if (options.observationType) {\n span.setAttribute(\"apo.observation.type\", options.observationType);\n }\n\n try {\n const result = await fn(span);\n span.end();\n resolve(result);\n } catch (err) {\n span.recordException(err as Error);\n span.setStatus({ code: 2, message: err instanceof Error ? err.message : String(err) });\n span.end();\n reject(err);\n }\n });\n });\n}\n\n// ── helper functions ─────────────────────────────────────────────────────\n\n/**\n * Trace a tool execution as an OTel span.\n *\n * Creates a span with ``gen_ai.tool.name``, ``gen_ai.tool.call.arguments``,\n * and ``gen_ai.tool.call.result`` attributes so the backend normalizer\n * classifies it as a TOOL observation.\n */\nexport async function traceTool<T>(\n tracer: Tracer,\n name: string,\n params: Record<string, unknown>,\n fn: () => Promise<T>,\n): Promise<T> {\n return withApoTrace(\n {\n name: `tool ${name}`,\n observationType: \"TOOL\",\n attributes: {\n \"gen_ai.tool.name\": name,\n \"gen_ai.tool.call.arguments\": JSON.stringify(params),\n },\n },\n tracer,\n async (span) => {\n const result = await fn();\n span.setAttribute(\"gen_ai.tool.call.result\", JSON.stringify(result));\n return result;\n },\n );\n}\n\n/**\n * Trace an agent invocation as an OTel span.\n */\nexport async function traceAgent<T>(\n tracer: Tracer,\n name: string,\n fn: () => Promise<T>,\n): Promise<T> {\n return withApoTrace(\n { name, observationType: \"AGENT\" },\n tracer,\n async () => fn(),\n );\n}\n\n/**\n * Trace a chain/step as an OTel span.\n */\nexport async function traceChain<T>(\n tracer: Tracer,\n name: string,\n fn: () => Promise<T>,\n): Promise<T> {\n return withApoTrace(\n { name, observationType: \"CHAIN\" },\n tracer,\n async () => fn(),\n );\n}\n\n/**\n * Trace a retrieval as an OTel span.\n */\nexport async function traceRetriever<T>(\n tracer: Tracer,\n query: string,\n fn: () => Promise<T>,\n): Promise<T> {\n return withApoTrace(\n { name: \"retrieve\", observationType: \"RETRIEVER\", attributes: { query } },\n tracer,\n async () => fn(),\n );\n}\n\n// ── Score API ─────────\n\nexport interface ScoreOptions {\n name: string;\n value: number | string | boolean;\n dataType?: \"NUMERIC\" | \"CATEGORICAL\" | \"BOOLEAN\";\n source?: \"API\" | \"EVAL\";\n comment?: string;\n observationId?: string;\n}\n\n/**\n * Record a score for a trace (or a specific observation within a trace).\n *\n * \"A Score is an apo domain record attached to a Trace or\n * Observation. It is not encoded as a fake span.\"\n *\n * This calls the native score API (`POST /api/v1/traces/{id}/scores` or\n * `POST /api/v1/observations/{id}/scores`) instead of creating a sentinel\n * span. The score is stored as a `RunMetricDB` or `CallMetricDB` row.\n *\n * @example\n * ```ts\n * await score({\n * traceId: \"abc123...\",\n * name: \"helpfulness\",\n * value: 0.85,\n * dataType: \"NUMERIC\",\n * source: \"EVAL\",\n * }, { endpoint: \"http://localhost:8000\", headers: authHeaders });\n * ```\n */\nexport async function score(\n params: ScoreOptions & { traceId: string },\n config: { endpoint: string; headers: Record<string, string> },\n): Promise<void> {\n const url = params.observationId\n ? `${config.endpoint}/api/v1/observations/${params.observationId}/scores`\n : `${config.endpoint}/api/v1/traces/${params.traceId}/scores`;\n\n const body: Record<string, unknown> = {\n name: params.name,\n value: params.value,\n data_type: params.dataType ?? \"NUMERIC\",\n source: params.source ?? \"API\",\n };\n if (params.comment) body.comment = params.comment;\n\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", ...config.headers },\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n console.error(`[apo score] HTTP ${response.status}: ${text.slice(0, 200)}`);\n }\n } catch (err) {\n console.error(\"[apo score] failed:\", err);\n }\n}\n\n// ── W3C trace context propagation ───────────────\n\n/**\n * Inject the current span's trace context into a headers dict for outbound\n * HTTP/queue calls. Produces a standard W3C `traceparent` header so the\n * downstream service can extract it and create a child span in the same trace.\n *\n * Delegates to OTel's official `W3CTraceContextPropagator`, which also emits\n * `tracestate` and handles the spec's edge cases.\n *\n * @example\n * ```ts\n * const headers = injectTraceparent();\n * await fetch(\"http://downstream-service/api\", { headers });\n * ```\n */\nexport function injectTraceparent(): Record<string, string> {\n const carrier: Record<string, string> = {};\n w3cPropagator.inject(context.active(), carrier, defaultTextMapSetter);\n return carrier;\n}\n\n/**\n * Extract a W3C trace context from inbound headers and run a function as a\n * child span within that trace. If no `traceparent` header is present (or it\n * is malformed), a new root span is created.\n *\n * Transport parsing is delegated to OTel's `W3CTraceContextPropagator`; the\n * span creation on top of the extracted remote parent is apo-specific and\n * kept here.\n *\n * @example\n * ```ts\n * // In a downstream HTTP handler:\n * app.post(\"/api\", (req, res) => {\n * const result = await extractTraceparent(req.headers, tracer, async (span) => {\n * return await doWork();\n * });\n * res.json(result);\n * });\n * ```\n */\nexport async function extractTraceparent<T>(\n headers: Record<string, string>,\n tracer: Tracer,\n fn: (span: Span) => Promise<T>,\n): Promise<T> {\n // Extract the upstream context. The propagator handles invalid/missing\n // traceparent by returning the input context unchanged (no extracted span).\n const extracted = w3cPropagator.extract(context.active(), headers, defaultTextMapGetter);\n const parent = trace.getSpan(extracted);\n if (!parent) {\n // No upstream context → new root span\n return withApoTrace({ name: \"inbound\" }, tracer, fn);\n }\n\n // Create a child span in the extracted context (shares the upstream traceId).\n return new Promise<T>((resolve, reject) => {\n context.with(extracted, () => {\n tracer.startActiveSpan(\"inbound\", async (span: Span) => {\n try {\n const result = await fn(span);\n span.end();\n resolve(result);\n } catch (err) {\n span.recordException(err as Error);\n span.end();\n reject(err);\n }\n });\n });\n });\n}\n","/**\n * Per-run context propagation via Node's AsyncLocalStorage.\n *\n * The OTel {@link ApoSpanProcessor} is global — it doesn't know which run a\n * span belongs to. This module solves that: {@link withApoRun} establishes\n * the run's trace context on AsyncLocalStorage, and any code called during\n * the run (including the processor's `onStart`/`onEnd`) can read it via\n * {@link getActiveApoRun}.\n *\n * This is the same propagation mechanism OTel's own\n * `AsyncLocalStorageContextManager` uses under the hood.\n *\n * @module\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { AgentTaskTraceContext } from \"../tracing.ts\";\n\nexport interface ApoRunContext {\n /** The tee'd trace context for this run. */\n trace: AgentTaskTraceContext;\n /** The parent span id (the run's root span or the current turn span). */\n parentSpanId?: string;\n /** The task id, surfaced in span metadata. */\n taskId?: string;\n /** The current turn number, surfaced in span metadata. */\n turnNumber?: number;\n}\n\nconst runStorage = new AsyncLocalStorage<ApoRunContext>();\n\n/**\n * Run `fn` with `ctx` as the active apo run context. Any code inside `fn`\n * (including across `await` boundaries) can call {@link getActiveApoRun}\n * to retrieve it.\n */\nexport function withApoRun<T>(ctx: ApoRunContext, fn: () => Promise<T>): Promise<T> {\n return runStorage.run(ctx, fn);\n}\n\n/**\n * Run `fn` synchronously with `ctx` as the active apo run context.\n */\nexport function withApoRunSync<T>(ctx: ApoRunContext, fn: () => T): T {\n return runStorage.run(ctx, fn);\n}\n\n/**\n * Get the active run context, or `undefined` if called outside a run.\n *\n * This is what the {@link ApoSpanProcessor} calls in `onStart`/`onEnd` to\n * know which run a span belongs to.\n */\nexport function getActiveApoRun(): ApoRunContext | undefined {\n return runStorage.getStore();\n}\n","/**\n * Shared span-translation helpers for the tracing integrations.\n *\n * Maps OpenTelemetry spans from both the Vercel AI SDK (`ai.*` attributes)\n * and the standard GenAI semantic conventions (`gen_ai.*` attributes) to\n * apo's observation types (`TOOL`, `GENERATION`, `AGENT`).\n *\n * Used by:\n * - {@link ApoSpanProcessor} (the OTel-native path)\n * - `createApoTracer` (the per-turn AI SDK tracer)\n *\n * @module\n */\n\n// ── shared utility functions ─────────────────────────────────────────────\n\nimport { extractTokenCounts } from \"./token-usage.ts\";\n\n/** Parse a JSON string, returning the original value if parsing fails. */\nexport function safeParse(raw: unknown): unknown {\n if (typeof raw !== \"string\") return raw;\n try {\n return JSON.parse(raw);\n } catch {\n return raw;\n }\n}\n\n/** High-resolution monotonic timestamp in milliseconds. */\nexport function monotonicNowMs(): number {\n if (typeof performance !== \"undefined\" && typeof performance.now === \"function\") {\n return performance.now();\n }\n return Date.now();\n}\n\n/** Round to 3 decimal places (for latency precision). */\nexport function round3(n: number): number {\n return Math.round(n * 1000) / 1000;\n}\n\n// ── the translation contract ─────────────────────────────────────────────\n\n/**\n * apo observation types the projection recognizes. Only spans with one\n * of these types become events that `t.*` assertions read.\n */\nexport type ApoObservationType = \"TOOL\" | \"GENERATION\" | \"AGENT\";\n\n/**\n * The result of translating an OTel span to apo's format.\n * `null` means the span is not load-bearing (housekeeping, internal, etc.)\n * and should be ignored.\n */\nexport interface TranslatedSpan {\n observationType: ApoObservationType;\n stepName: string;\n /** Model name (for GENERATION spans). */\n model?: string;\n /** Tool call input (for TOOL spans). */\n input?: Record<string, unknown>;\n /** Tool call result (set on end, not start). */\n output?: unknown;\n /** Generation text (for GENERATION spans). */\n text?: string;\n promptTokens?: number;\n completionTokens?: number;\n error?: boolean;\n errorMessage?: string;\n}\n\n/**\n * An OTel span's attributes and status, in a shape that doesn't require\n * importing `@opentelemetry/api` at the type level.\n */\nexport interface OtelSpanData {\n attributes: Record<string, unknown>;\n status: { code: number; message?: string };\n}\n\n/**\n * Translate an OTel span to apo's observation format.\n *\n * Handles both attribute conventions:\n * - **AI SDK** (`ai.*`): `ai.toolCall.*`, `ai.generateText`, `ai.streamText`\n * - **GenAI standard** (`gen_ai.*`): `gen_ai.tool.*`, `gen_ai.operation.name`\n *\n * Returns `null` for spans that aren't load-bearing (internal housekeeping,\n * non-GenAI operations, etc.).\n */\nexport function translateOtelSpan(\n spanName: string,\n data: OtelSpanData,\n): TranslatedSpan | null {\n const { attributes, status } = data;\n const isError = status.code === 2;\n const errorMessage = status.message;\n\n // ── AI SDK convention (ai.*) ────────────────────────────────────────\n\n if (spanName === \"ai.toolCall\") {\n return {\n observationType: \"TOOL\",\n stepName: String(attributes[\"ai.toolCall.name\"] ?? \"unknown\"),\n input: safeParse(attributes[\"ai.toolCall.args\"]) as Record<string, unknown>,\n output: safeParse(attributes[\"ai.toolCall.result\"]),\n error: isError,\n errorMessage,\n };\n }\n\n // The AI SDK emits a parent span (ai.generateText) and per-step child spans\n // (ai.generateText.doGenerate). The parent span carries:\n // - ai.model.id (at creation)\n // - ai.prompt.messages (the input, at creation)\n // - ai.response.text (the final assembled text, set at end)\n // - ai.usage.promptTokens/completionTokens (total usage, set at end)\n // The child doGenerate spans carry per-step data but NOT the final text.\n // We translate the parent span (which has the complete picture) and skip\n // the per-step child spans.\n if (\n spanName === \"ai.generateText\" ||\n spanName === \"ai.streamText\"\n ) {\n return {\n observationType: \"GENERATION\",\n stepName: \"agent.generate\",\n text: extractTextFromAttrs(attributes),\n model: String(\n attributes[\"gen_ai.request.model\"] ??\n attributes[\"ai.model.id\"] ??\n attributes[\"ai.response.model\"] ??\n \"unknown\",\n ),\n ...extractTokenCounts(attributes),\n error: isError,\n errorMessage,\n };\n }\n\n // ── Standard OTel GenAI conventions (gen_ai.*) ─────────────────────\n\n const operation = attributes[\"gen_ai.operation.name\"];\n\n // Tool execution span\n if (\n operation === \"execute_tool\" ||\n spanName.startsWith(\"gen_ai.tool\") ||\n (spanName.includes(\"tool\") && attributes[\"gen_ai.tool.name\"])\n ) {\n return {\n observationType: \"TOOL\",\n stepName: String(attributes[\"gen_ai.tool.name\"] ?? \"unknown\"),\n input: safeParse(attributes[\"gen_ai.tool.call.arguments\"]) as Record<string, unknown>,\n output: safeParse(attributes[\"gen_ai.tool.call.result\"]),\n error: isError,\n errorMessage,\n };\n }\n\n // Agent invocation span\n if (operation === \"invoke_agent\") {\n return {\n observationType: \"AGENT\",\n stepName: String(attributes[\"gen_ai.agent.name\"] ?? \"agent\"),\n output: safeParse(attributes[\"gen_ai.tool.call.result\"]),\n error: isError,\n errorMessage,\n };\n }\n\n // Chat / generation span\n if (\n operation === \"chat\" ||\n operation === \"generate_content\" ||\n operation === \"text_completion\"\n ) {\n return {\n observationType: \"GENERATION\",\n stepName: \"agent.generate\",\n text: extractTextFromAttrs(attributes, \"gen_ai.\"),\n ...extractTokenCounts(attributes),\n error: isError,\n errorMessage,\n };\n }\n\n // ── Not a load-bearing span ────────────────────────────────────────\n\n return null;\n}\n\n/**\n * Extract the model's text response from span attributes.\n * Tries both `ai.*` and `gen_ai.*` response keys.\n */\nexport function extractTextFromAttrs(\n attrs: Record<string, unknown>,\n prefix?: string,\n): string | undefined {\n const candidates = prefix === \"gen_ai.\"\n ? [\"gen_ai.completion\", \"gen_ai.response.text\"]\n : prefix === \"ai.\"\n ? [\"ai.response.text\", \"ai.response.output\", \"ai.generateText.result\"]\n : [\n \"gen_ai.completion\",\n \"gen_ai.response.text\",\n \"ai.response.text\",\n \"ai.response.output\",\n \"ai.generateText.result\",\n ];\n\n for (const key of candidates) {\n const val = attrs[key];\n if (typeof val === \"string\" && val.length > 0) {\n const parsed = safeParse(val);\n return typeof parsed === \"string\" ? parsed : val;\n }\n }\n return undefined;\n}\n","/**\n * ApoSpanProcessor — an OpenTelemetry SpanProcessor that routes GenAI spans\n * into apo's Flow.\n *\n * This is the OTel-native tracing path. After calling {@link registerApoTracing},\n * any SDK that emits OTel spans (Vercel AI SDK, OpenAI Agents SDK, etc.)\n * will be traced automatically — no per-SDK wrapper needed.\n *\n * The processor is global, but run isolation is maintained via\n * {@link AsyncLocalStorage}: {@link getActiveApoRun} returns the current\n * run's trace context, so each span is routed to the correct run's trace.\n *\n * **Design: eager creation, deferred completion.** The apo span is created\n * at `onStart` (when initial attributes like tool name and model are\n * available). The span is ended at `onEnd` with the final attributes\n * (results, tokens, text). This gives correct ordering: parent spans\n * are created before children, matching the real call hierarchy.\n *\n * @module\n */\n\nimport { getActiveApoRun } from \"./run-context.ts\";\nimport {\n translateOtelSpan,\n round3,\n monotonicNowMs,\n type TranslatedSpan,\n} from \"./otel-translate.ts\";\n\n// ── minimal OTel type shapes (no runtime dependency on @opentelemetry/api) ──\n\ninterface OTelSpanContext {\n spanId: string;\n traceId: string;\n traceFlags: number;\n}\n\ninterface OTelSpanStatus {\n code: number;\n message?: string;\n}\n\ninterface OTelReadableSpan {\n name: string;\n spanContext(): OTelSpanContext;\n parentSpanContext?: OTelSpanContext;\n attributes: Record<string, unknown>;\n status: OTelSpanStatus;\n duration: [number, number];\n startTime: [number, number];\n ended: boolean;\n}\n\ninterface OTelSpan extends OTelReadableSpan {\n setAttribute(key: string, value: unknown): this;\n setAttributes(attributes: Record<string, unknown>): this;\n setStatus(status: OTelSpanStatus): this;\n recordException(exception: unknown): void;\n updateName(name: string): this;\n end(endTime?: unknown): void;\n isRecording(): boolean;\n}\n\ninterface OTelContext {\n getValue(key: symbol): unknown;\n setValue(key: symbol, value: unknown): OTelContext;\n deleteValue(key: symbol): OTelContext;\n}\n\ninterface OTelSpanProcessorLifecycle {\n forceFlush(): Promise<void>;\n shutdown(): Promise<void>;\n}\n\n/**\n * Tracks an active span: the apo span ID, the OTel span name, and the\n * start timestamp for latency calculation.\n */\ninterface TrackedSpan {\n apoSpanId: string;\n otelSpanName: string;\n startedAt: number;\n}\n\n/**\n * A SpanProcessor that translates GenAI OTel spans into apo observations.\n *\n * Spans are created at `onStart` (eager) and completed at `onEnd`\n * (deferred). This gives correct parent/child ordering — parent spans\n * are created before their children, matching the real call hierarchy.\n *\n * If a span isn't load-bearing (not a GenAI tool/generation span), no\n * apo span is created.\n */\nexport class ApoSpanProcessor implements OTelSpanProcessorLifecycle {\n private spanMap = new Map<string, TrackedSpan>();\n\n onStart(span: OTelSpan, _parentContext: OTelContext): void {\n const run = getActiveApoRun();\n if (!run) return;\n\n // Quick check: is this span potentially load-bearing?\n // At onStart, the AI SDK has already set the initial attributes\n // (ai.toolCall.name, ai.toolCall.args for tool spans; ai.model.id\n // for generation spans). So we CAN translate now.\n const translated = translateOtelSpan(span.name, {\n attributes: span.attributes ?? {},\n status: { code: 0 }, // no error at start\n });\n if (!translated) return; // not load-bearing — skip\n\n // Resolve parent: check if the parent OTel span has an apo mapping\n const parentOtelId = span.parentSpanContext?.spanId;\n const parentTracked = parentOtelId ? this.spanMap.get(parentOtelId) : undefined;\n const parentApoId = parentTracked?.apoSpanId || run.parentSpanId || run.trace.rootSpanId;\n\n // Create the apo span eagerly (at start time)\n const apoSpanId = run.trace.createSpan({\n task_id: run.taskId ?? \"otel-trace\",\n parent_call_id: parentApoId,\n step_name: translated.stepName,\n model: translated.model ?? translated.stepName,\n observation_type: translated.observationType,\n ...(translated.input ? { input: translated.input } : {}),\n metadata: {\n ...(run.taskId ? { taskId: run.taskId } : {}),\n ...(run.turnNumber !== undefined ? { turnNumber: run.turnNumber } : {}),\n source: \"otel-processor\",\n },\n });\n\n this.spanMap.set(span.spanContext().spanId, {\n apoSpanId,\n otelSpanName: span.name,\n startedAt: monotonicNowMs(),\n });\n }\n\n onEnd(span: OTelReadableSpan): void {\n const otelId = span.spanContext().spanId;\n const tracked = this.spanMap.get(otelId);\n if (!tracked) return; // wasn't tracked (not load-bearing or outside run)\n this.spanMap.delete(otelId);\n\n const run = getActiveApoRun();\n if (!run) return;\n\n // Re-translate with final attributes (results, text, tokens set during execution)\n const translated = translateOtelSpan(tracked.otelSpanName, {\n attributes: span.attributes ?? {},\n status: span.status ?? { code: 0 },\n });\n\n const latency = round3(monotonicNowMs() - tracked.startedAt);\n\n // End the apo span with the final data\n run.trace.endSpan(tracked.apoSpanId, {\n latency_ms: latency,\n ...buildEndParams(translated),\n });\n }\n\n forceFlush(): Promise<void> {\n return Promise.resolve();\n }\n\n shutdown(): Promise<void> {\n this.spanMap.clear();\n return Promise.resolve();\n }\n\n /** Clear all state — for testing. */\n reset(): void {\n this.spanMap.clear();\n }\n}\n\n/**\n * Build the `endSpan` params from the translated span.\n * Handles observation-type-specific output shapes.\n */\nfunction buildEndParams(t: TranslatedSpan | null): Record<string, unknown> {\n if (!t) return {};\n\n const params: Record<string, unknown> = {};\n\n if (t.promptTokens !== undefined) params.prompt_tokens = t.promptTokens;\n if (t.completionTokens !== undefined) params.completion_tokens = t.completionTokens;\n\n if (t.observationType === \"GENERATION\") {\n if (t.text !== undefined) {\n params.output = { text: t.text };\n }\n } else if (t.observationType === \"TOOL\") {\n if (t.output !== undefined) {\n params.output =\n t.output && typeof t.output === \"object\"\n ? (t.output as Record<string, unknown>)\n : { value: t.output };\n }\n }\n\n if (t.error) {\n params.level = \"ERROR\";\n if (t.errorMessage) params.status_message = t.errorMessage;\n }\n\n return params;\n}\n","/**\n * Registration API for the OTel-native tracing integration.\n *\n * Call {@link registerApoTracing} once at startup (e.g., in `startSession`\n * or at module load). After this, any SDK that emits OTel spans during a run\n * will be traced automatically — no per-SDK wrapper needed.\n *\n * @example\n * ```ts\n * import { registerApoTracing } from \"@apo-ai/sdk/agent-task\";\n *\n * async startSession(ctx) {\n * registerApoTracing(); // one line, once\n * return { async sendUserTurn(turn) { ... } };\n * }\n * ```\n *\n * @module\n */\n\nimport { ApoSpanProcessor } from \"./otel-processor.ts\";\n\n// We use dynamic imports for @opentelemetry so the module is importable\n// without the packages installed. In practice they're transitive deps of `ai`.\n\nlet registered = false;\nlet currentProcessor: ApoSpanProcessor | null = null;\n\nexport interface RegisterApoTracingOptions {\n /**\n * Set as the global tracer provider (default: true).\n * Set to false if the host app has its own OTel setup and you want to\n * add apo's processor to an existing provider instead.\n */\n setGlobal?: boolean;\n}\n\n/**\n * Register apo's OTel SpanProcessor. Idempotent — calling it more than\n * once is a no-op.\n *\n * After registration, any OTel span created during an apo run (inside\n * `withApoRun`) that matches the GenAI conventions (`ai.*` or `gen_ai.*`)\n * is automatically translated and fed into the run's Flow.\n */\nexport async function registerApoTracing(\n _options?: RegisterApoTracingOptions,\n): Promise<void> {\n if (registered) return;\n registered = true;\n\n // Create the processor but DON'T register our own provider. The processor\n // is picked up by configureApoTelemetry (via getRegisteredApoProcessor)\n // and added to the OTLP-exporting provider. This ensures GenAI spans from\n // SDKs (Vercel AI, OpenAI, etc.) reach BOTH the projection-tee (for local\n // assertions) AND the OTLP exporter (for the backend), all through a single\n // global provider — no competing providers.\n currentProcessor = new ApoSpanProcessor();\n}\n\n/**\n * Alias used by configureApoTelemetry to pick up the processor and add it\n * to the OTLP-exporting provider.\n */\nexport function getRegisteredApoProcessor(): ApoSpanProcessor | null {\n return currentProcessor;\n}\n\n/**\n * Reset registration state — for testing only.\n */\nexport function resetApoTracing(): void {\n registered = false;\n currentProcessor = null;\n}\n"],"mappings":"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","names":["acquired"]}
1
+ {"version":3,"sources":["../src/otel/index.ts","../src/agent-task/integrations/run-context.ts","../src/agent-task/integrations/otel-translate.ts","../src/agent-task/integrations/otel-processor.ts","../src/agent-task/integrations/register.ts"],"sourcesContent":["/**\n * OTel-native tracing setup for the apo SDK.\n *\n * This module provides the standard OpenTelemetry setup path for apo:\n * - ``configureApoTelemetry`` creates a tracer provider with an OTLP exporter\n * - ``withApoTrace`` creates root/child spans using standard OTel context\n * - Helper functions (``traceTool``, ``traceAgent``, etc.) emit normal OTel\n * spans with GenAI or apo vendor attributes\n *\n * This replaced the removed ``TraceTracker`` custom event protocol — apo\n * tracing is OTel-only now.\n *\n * @module @apo-ai/sdk/otel\n */\n\nimport {\n trace,\n context,\n defaultTextMapGetter,\n defaultTextMapSetter,\n type Span,\n type Tracer,\n type SpanOptions,\n type TracerProvider,\n} from \"@opentelemetry/api\";\nimport { W3CTraceContextPropagator } from \"@opentelemetry/core\";\n\n/**\n * Shared W3C Trace Context propagator. OTel's official implementation handles\n * `traceparent`/`tracestate`, edge cases (invalid traceId/spanId, versioning),\n * and stays correct as the spec evolves — so we delegate to it instead of\n * hand-rolling `00-<trace>-<span>-<flags>` parsing.\n */\nconst w3cPropagator = new W3CTraceContextPropagator();\nimport {\n BasicTracerProvider,\n BatchSpanProcessor,\n SimpleSpanProcessor,\n type SpanExporter,\n type SpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\nimport { AsyncHooksContextManager } from \"@opentelemetry/context-async-hooks\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport {\n SEMRESATTRS_SERVICE_NAME,\n SEMRESATTRS_SERVICE_VERSION,\n SEMRESATTRS_SERVICE_NAMESPACE,\n SEMRESATTRS_DEPLOYMENT_ENVIRONMENT,\n} from \"@opentelemetry/semantic-conventions\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport { getRegisteredApoProcessor } from \"../agent-task/integrations/register.ts\";\n\n// ── public types ───────────────────────────────────────────\n\nexport interface ConfigureApoTelemetryOptions {\n /** Explicit permission for this standalone helper to own OTel lifecycle. */\n takeOwnership: true;\n /**\n * Full OTLP traces endpoint URL. Falls back to the `APO_OTLP_ENDPOINT` env\n * var when omitted, then to `http://localhost:8000/api/public/otel/v1/traces`.\n */\n endpoint?: string;\n /**\n * Diagnostic resource attribute only. Auth owns tenancy — the project is\n * determined by the API key / service token, never by this field.\n * Falls back to the `APO_PROJECT` env var when omitted.\n */\n project?: string;\n /**\n * Service name for the OTel resource (required). Defaults to `\"apo-agent\"`.\n */\n serviceName?: string;\n /** Service version for the OTel resource. */\n serviceVersion?: string;\n /** Deployment environment (production, staging, etc.). */\n environment?: string;\n /**\n * Auth headers (Authorization: Basic/Bearer). Falls back to headers derived\n * from `APO_PUBLIC_KEY`+`APO_SECRET_KEY` (Basic) or `APO_AUTH_TOKEN` (Bearer)\n * env vars when omitted. Use {@link buildApoAuthHeaders} to build them\n * explicitly.\n */\n headers?: Record<string, string>;\n /**\n * Public key for Basic auth. Falls back to `APO_PUBLIC_KEY` env var.\n * Used with `secretKey` to build `Authorization: Basic base64(pk:sk)`.\n */\n publicKey?: string;\n /**\n * Secret key for Basic auth. Falls back to `APO_SECRET_KEY` env var.\n */\n secretKey?: string;\n /**\n * Bearer auth token. Falls back to `APO_AUTH_TOKEN` env var. Used only when\n * `publicKey`/`secretKey` are absent.\n */\n authToken?: string;\n /**\n * @deprecated OTel JS 2.x providers cannot accept processors after\n * construction. Use `createApoSpanProcessor()` in the host provider's\n * `spanProcessors` array instead.\n */\n provider?: TracerProvider;\n /** Batch for services; simple is useful for short-lived task subprocesses. */\n processor?: \"batch\" | \"simple\";\n /**\n * Whether to register apo's provider as the global tracer provider. Defaults\n * to false. When true, registration only happens if no global provider is\n * registered yet — apo never silently replaces an existing global provider.\n */\n registerGlobal?: boolean;\n}\n\nexport interface ApoTelemetryHandle {\n /** The OTel tracer for creating spans. */\n tracer: Tracer;\n /** The provider, for advanced use. */\n provider: TracerProvider;\n /** Export all ended spans without relinquishing provider ownership. */\n forceFlush(): Promise<void>;\n /** Gracefully shut down the provider and flush pending spans. */\n shutdown(): Promise<void>;\n}\n\nexport interface ApoTraceExporterOptions {\n /** Full standard OTLP/HTTP traces endpoint. */\n endpoint: string;\n /** Authentication headers sent by the official OTLP exporter. */\n headers: Record<string, string>;\n}\n\nexport interface ApoSpanProcessorOptions extends ApoTraceExporterOptions {\n /** Batch for long-lived services (default), simple for short-lived jobs. */\n processor?: \"batch\" | \"simple\";\n}\n\nexport interface ApoTraceOptions {\n /** Span name. */\n name: string;\n /** apo.observation.type override. */\n observationType?: string;\n /** Additional attributes. */\n attributes?: Record<string, unknown>;\n}\n\ninterface OwnedContextState {\n manager: AsyncHooksContextManager;\n references: number;\n}\n\nlet ownedContext: OwnedContextState | null = null;\n\n/** Create the official OTLP/HTTP exporter for a host-owned OTel provider. */\nexport function createApoTraceExporter(\n options: ApoTraceExporterOptions,\n): SpanExporter {\n return new OTLPTraceExporter({\n url: options.endpoint,\n headers: options.headers,\n });\n}\n\n/**\n * Create an apo OTLP processor for inclusion when constructing a host provider.\n * This factory has no global side effects and transfers lifecycle ownership to\n * the host provider.\n */\nexport function createApoSpanProcessor(\n options: ApoSpanProcessorOptions,\n): SpanProcessor {\n const exporter = createApoTraceExporter(options);\n return options.processor === \"simple\"\n ? new SimpleSpanProcessor(exporter)\n : new BatchSpanProcessor(exporter);\n}\n\n// ── auth + env-var helpers (mirror apo-otel-python) ─────────────────────\n\nconst DEFAULT_APO_OTLP_ENDPOINT = \"http://localhost:8000/api/public/otel/v1/traces\";\n\n/**\n * Build the `Authorization` header for apo's OTLP endpoint.\n *\n * - If `publicKey` + `secretKey` are given (or read from `APO_PUBLIC_KEY` /\n * `APO_SECRET_KEY`), returns HTTP Basic: `Basic base64(pk:sk)`. * both halves are required — a public identifier alone is not a\n * credential, so partial Basic is never synthesized.\n * - Else if `authToken` is given (or read from `APO_AUTH_TOKEN`), returns\n * `Bearer <token>`. (Covers short-lived task-run/attempt tokens and\n * secret-bearing legacy API keys.)\n * - Else returns an empty object (unauthenticated).\n *\n * Mirrors Python's `_build_auth_headers` in apo-otel-python.\n */\nexport function buildApoAuthHeaders(\n publicKey?: string,\n secretKey?: string,\n authToken?: string,\n): Record<string, string> {\n const pk = publicKey ?? process.env.APO_PUBLIC_KEY;\n const sk = secretKey ?? process.env.APO_SECRET_KEY;\n const token = authToken ?? process.env.APO_AUTH_TOKEN;\n if (pk && sk) {\n const credentials = typeof btoa === \"function\"\n ? btoa(`${pk}:${sk}`)\n : Buffer.from(`${pk}:${sk}`).toString(\"base64\");\n return { Authorization: `Basic ${credentials}` };\n }\n if (token) {\n return { Authorization: `Bearer ${token}` };\n }\n return {};\n}\n\n/**\n * Resolve telemetry config: explicit kwarg wins, else env var, else default.\n * Mirrors Python's `_resolve_standalone_configuration`.\n */\nfunction resolveTelemetryConfig(options: ConfigureApoTelemetryOptions): {\n endpoint: string;\n headers: Record<string, string>;\n project: string | undefined;\n serviceName: string;\n} {\n const endpoint = options.endpoint\n ?? process.env.APO_OTLP_ENDPOINT\n ?? DEFAULT_APO_OTLP_ENDPOINT;\n const project = options.project ?? process.env.APO_PROJECT;\n const serviceName = options.serviceName ?? \"apo-agent\";\n const headers = options.headers\n ?? buildApoAuthHeaders(options.publicKey, options.secretKey, options.authToken);\n return { endpoint, headers, project, serviceName };\n}\n\n/**\n * Set the GenAI content-capture env var so instrumentation libs emit prompt\n * and completion text into spans. Uses `setdefault`-equivalent semantics: an\n * explicit user setting is never overridden. Mirrors Python's\n * `_configure_instrumentation_environment`.\n */\nfunction configureInstrumentationEnvironment(): void {\n if (!process.env.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT) {\n process.env.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = \"span_only\";\n }\n}\n\n// ── configureApoTelemetry ────────────────────────────────────────────────\n\n/**\n * Configure apo's OpenTelemetry tracing.\n *\n * Builds apo's trace export from official OTel components: a real\n * {@link Resource} (service.name, service.version, deployment.environment)\n * and the official {@link OTLPTraceExporter} (standard OTLP/HTTP, no custom\n * wire format). This is the explicit standalone bootstrap. Host applications\n * that already own a provider should use `createApoSpanProcessor()` instead.\n *\n * @example\n * ```ts\n * import { configureApoTelemetry } from \"@apo-ai/sdk/otel\";\n *\n * const apo = await configureApoTelemetry({\n * takeOwnership: true,\n * endpoint: \"https://apo.example.com/api/public/otel/v1/traces\",\n * serviceName: \"my-agent\",\n * headers: { Authorization: `Basic ${btoa(\"pk-apo-...:sk-apo-...\")}` },\n * });\n *\n * const result = await withApoTrace({ name: \"chat\" }, apo.tracer, async (span) => {\n * span.setAttribute(\"gen_ai.request.model\", \"gpt-4o\");\n * // ... do work ...\n * return \"done\";\n * });\n *\n * await apo.shutdown();\n * ```\n */\nexport async function configureApoTelemetry(\n options: ConfigureApoTelemetryOptions,\n): Promise<ApoTelemetryHandle> {\n if (options.takeOwnership !== true) {\n throw new TypeError(\"standalone bootstrap requires takeOwnership: true\");\n }\n if (options.provider !== undefined) {\n throw new TypeError(\n \"A host provider cannot be mutated after construction in OTel JS 2.x; \" +\n \"construct it with createApoSpanProcessor() instead.\",\n );\n }\n\n // Resolve config from kwargs → env vars → defaults (mirror apo-otel-python).\n const { endpoint, headers, project, serviceName } = resolveTelemetryConfig(options);\n\n // Enable prompt/completion content capture for instrumentation libs.\n configureInstrumentationEnvironment();\n\n // Set up the async context manager so context.active() carries the active\n // span. Concurrent apo handles share one owned manager; a host-owned manager\n // is used as-is and is never disabled by apo.\n const releaseContext = acquireContextManager();\n\n // Build the official OTel Resource with standard semantic attributes.\n const resourceAttrs: Record<string, string> = {\n [SEMRESATTRS_SERVICE_NAME]: serviceName,\n };\n if (project) resourceAttrs[SEMRESATTRS_SERVICE_NAMESPACE] = project;\n if (options.serviceVersion) resourceAttrs[SEMRESATTRS_SERVICE_VERSION] = options.serviceVersion;\n if (options.environment) resourceAttrs[SEMRESATTRS_DEPLOYMENT_ENVIRONMENT] = options.environment;\n const resource = resourceFromAttributes(resourceAttrs);\n\n // Official OTLP/HTTP trace exporter — standard OTLP wire format, no custom\n // serializer. Headers carry the auth the apo receiver validates.\n // The standalone path owns this provider. Host-owned providers compose with\n // createApoSpanProcessor() at their own construction boundary.\n const spanProcessors: SpanProcessor[] = [createApoSpanProcessor({\n endpoint,\n headers,\n processor: options.processor,\n })];\n\n // If an ApoSpanProcessor was registered via registerApoTracing(), add it to\n // this provider so GenAI spans from SDKs (Vercel AI, OpenAI, etc.) are fed\n // into the projection-tee for local assertions AND exported via OTLP.\n const apoProcessor = getRegisteredApoProcessor();\n if (apoProcessor) {\n spanProcessors.push(apoProcessor as unknown as SpanProcessor);\n }\n\n const provider = new BasicTracerProvider({\n resource,\n spanProcessors,\n });\n\n const tracer = provider.getTracer(\"apo-sdk\", \"0.2.0\");\n\n // Register globally only when explicitly requested AND no host provider was\n // supplied AND no global provider is registered yet. apo never silently\n // replaces a host/global provider.\n let registeredGlobal = false;\n if (options.registerGlobal) {\n registeredGlobal = trace.setGlobalTracerProvider(provider);\n }\n\n let stopped = false;\n\n return {\n tracer,\n provider,\n async forceFlush() {\n await provider.forceFlush();\n },\n async shutdown() {\n if (stopped) return;\n stopped = true;\n // Force-flush + shut down the provider, but bound the whole teardown so\n // an unreachable export endpoint (e.g. no backend in unit tests) never\n // hangs the process. The span processors/exporters swallow their own\n // transport errors; this guards against the wait itself.\n await Promise.race([\n (async () => {\n await provider.forceFlush().catch(() => undefined);\n await provider.shutdown().catch(() => undefined);\n })(),\n new Promise<void>((resolve) => setTimeout(resolve, 2000)),\n ]);\n // Reset global state so a later configuration starts clean.\n if (registeredGlobal) {\n trace.disable();\n }\n releaseContext();\n },\n };\n}\n\nfunction acquireContextManager(): () => void {\n if (ownedContext !== null) {\n ownedContext.references += 1;\n const acquired = ownedContext;\n return () => releaseOwnedContext(acquired);\n }\n\n const manager = new AsyncHooksContextManager().enable();\n if (!context.setGlobalContextManager(manager)) {\n manager.disable();\n return () => undefined;\n }\n\n const acquired: OwnedContextState = { manager, references: 1 };\n ownedContext = acquired;\n return () => releaseOwnedContext(acquired);\n}\n\nfunction releaseOwnedContext(acquired: OwnedContextState): void {\n if (ownedContext !== acquired) return;\n acquired.references -= 1;\n if (acquired.references > 0) return;\n context.disable();\n ownedContext = null;\n}\n\n// ── withApoTrace ─────────────────────────────────────────────────────────\n\n/**\n * Run a function inside an OTel span.\n *\n * Creates a span using the active OTel context (standard context propagation,\n * not apo-specific span IDs passed by hand). The span automatically ends when\n * the function returns or throws.\n *\n * @example\n * ```ts\n * const result = await withApoTrace(\n * { name: \"agent-turn\", observationType: \"AGENT\" },\n * apo.tracer,\n * async (span) => {\n * span.setAttribute(\"gen_ai.request.model\", \"gpt-4o\");\n * return await llm.complete(\"hello\");\n * },\n * );\n * ```\n */\nexport async function withApoTrace<T>(\n options: ApoTraceOptions,\n tracer: Tracer,\n fn: (span: Span) => Promise<T>,\n): Promise<T> {\n const spanOptions: SpanOptions = {};\n if (options.attributes) {\n spanOptions.attributes = options.attributes as Record<string, string | number | boolean>;\n }\n\n // Use startActiveSpan which creates the span AND activates it in context,\n // so nested withApoTrace calls automatically inherit it as parent.\n return new Promise<T>((resolve, reject) => {\n tracer.startActiveSpan(options.name, spanOptions, async (span: Span) => {\n // Set apo vendor attributes\n if (options.observationType) {\n span.setAttribute(\"apo.observation.type\", options.observationType);\n }\n\n try {\n const result = await fn(span);\n span.end();\n resolve(result);\n } catch (err) {\n span.recordException(err as Error);\n span.setStatus({ code: 2, message: err instanceof Error ? err.message : String(err) });\n span.end();\n reject(err);\n }\n });\n });\n}\n\n// ── helper functions ─────────────────────────────────────────────────────\n\n/**\n * Trace a tool execution as an OTel span.\n *\n * Creates a span with ``gen_ai.tool.name``, ``gen_ai.tool.call.arguments``,\n * and ``gen_ai.tool.call.result`` attributes so the backend normalizer\n * classifies it as a TOOL observation.\n */\nexport async function traceTool<T>(\n tracer: Tracer,\n name: string,\n params: Record<string, unknown>,\n fn: () => Promise<T>,\n): Promise<T> {\n return withApoTrace(\n {\n name: `tool ${name}`,\n observationType: \"TOOL\",\n attributes: {\n \"gen_ai.tool.name\": name,\n \"gen_ai.tool.call.arguments\": JSON.stringify(params),\n },\n },\n tracer,\n async (span) => {\n const result = await fn();\n span.setAttribute(\"gen_ai.tool.call.result\", JSON.stringify(result));\n return result;\n },\n );\n}\n\n/**\n * Trace an agent invocation as an OTel span.\n */\nexport async function traceAgent<T>(\n tracer: Tracer,\n name: string,\n fn: () => Promise<T>,\n): Promise<T> {\n return withApoTrace(\n { name, observationType: \"AGENT\" },\n tracer,\n async () => fn(),\n );\n}\n\n/**\n * Trace a chain/step as an OTel span.\n */\nexport async function traceChain<T>(\n tracer: Tracer,\n name: string,\n fn: () => Promise<T>,\n): Promise<T> {\n return withApoTrace(\n { name, observationType: \"CHAIN\" },\n tracer,\n async () => fn(),\n );\n}\n\n/**\n * Trace a retrieval as an OTel span.\n */\nexport async function traceRetriever<T>(\n tracer: Tracer,\n query: string,\n fn: () => Promise<T>,\n): Promise<T> {\n return withApoTrace(\n { name: \"retrieve\", observationType: \"RETRIEVER\", attributes: { query } },\n tracer,\n async () => fn(),\n );\n}\n\n// ── Score API ─────────\n\nexport interface ScoreOptions {\n name: string;\n value: number | string | boolean;\n dataType?: \"NUMERIC\" | \"CATEGORICAL\" | \"BOOLEAN\";\n source?: \"API\" | \"EVAL\";\n comment?: string;\n observationId?: string;\n}\n\n/**\n * Record a score for a trace (or a specific observation within a trace).\n *\n * \"A Score is an apo domain record attached to a Trace or\n * Observation. It is not encoded as a fake span.\"\n *\n * This calls the native score API (`POST /api/v1/traces/{id}/scores` or\n * `POST /api/v1/observations/{id}/scores`) instead of creating a sentinel\n * span. The score is stored as a `RunMetricDB` or `CallMetricDB` row.\n *\n * @example\n * ```ts\n * await score({\n * traceId: \"abc123...\",\n * name: \"helpfulness\",\n * value: 0.85,\n * dataType: \"NUMERIC\",\n * source: \"EVAL\",\n * }, { endpoint: \"http://localhost:8000\", headers: authHeaders });\n * ```\n */\nexport async function score(\n params: ScoreOptions & { traceId: string },\n config: { endpoint: string; headers: Record<string, string> },\n): Promise<void> {\n const url = params.observationId\n ? `${config.endpoint}/api/v1/observations/${params.observationId}/scores`\n : `${config.endpoint}/api/v1/traces/${params.traceId}/scores`;\n\n const body: Record<string, unknown> = {\n name: params.name,\n value: params.value,\n data_type: params.dataType ?? \"NUMERIC\",\n source: params.source ?? \"API\",\n };\n if (params.comment) body.comment = params.comment;\n\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", ...config.headers },\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n console.error(`[apo score] HTTP ${response.status}: ${text.slice(0, 200)}`);\n }\n } catch (err) {\n console.error(\"[apo score] failed:\", err);\n }\n}\n\n// ── W3C trace context propagation ───────────────\n\n/**\n * Inject the current span's trace context into a headers dict for outbound\n * HTTP/queue calls. Produces a standard W3C `traceparent` header so the\n * downstream service can extract it and create a child span in the same trace.\n *\n * Delegates to OTel's official `W3CTraceContextPropagator`, which also emits\n * `tracestate` and handles the spec's edge cases.\n *\n * @example\n * ```ts\n * const headers = injectTraceparent();\n * await fetch(\"http://downstream-service/api\", { headers });\n * ```\n */\nexport function injectTraceparent(): Record<string, string> {\n const carrier: Record<string, string> = {};\n w3cPropagator.inject(context.active(), carrier, defaultTextMapSetter);\n return carrier;\n}\n\n/**\n * Extract a W3C trace context from inbound headers and run a function as a\n * child span within that trace. If no `traceparent` header is present (or it\n * is malformed), a new root span is created.\n *\n * Transport parsing is delegated to OTel's `W3CTraceContextPropagator`; the\n * span creation on top of the extracted remote parent is apo-specific and\n * kept here.\n *\n * @example\n * ```ts\n * // In a downstream HTTP handler:\n * app.post(\"/api\", (req, res) => {\n * const result = await extractTraceparent(req.headers, tracer, async (span) => {\n * return await doWork();\n * });\n * res.json(result);\n * });\n * ```\n */\nexport async function extractTraceparent<T>(\n headers: Record<string, string>,\n tracer: Tracer,\n fn: (span: Span) => Promise<T>,\n): Promise<T> {\n // Extract the upstream context. The propagator handles invalid/missing\n // traceparent by returning the input context unchanged (no extracted span).\n const extracted = w3cPropagator.extract(context.active(), headers, defaultTextMapGetter);\n const parent = trace.getSpan(extracted);\n if (!parent) {\n // No upstream context → new root span\n return withApoTrace({ name: \"inbound\" }, tracer, fn);\n }\n\n // Create a child span in the extracted context (shares the upstream traceId).\n return new Promise<T>((resolve, reject) => {\n context.with(extracted, () => {\n tracer.startActiveSpan(\"inbound\", async (span: Span) => {\n try {\n const result = await fn(span);\n span.end();\n resolve(result);\n } catch (err) {\n span.recordException(err as Error);\n span.end();\n reject(err);\n }\n });\n });\n });\n}\n","/**\n * Per-run context propagation via Node's AsyncLocalStorage.\n *\n * The OTel {@link ApoSpanProcessor} is global — it doesn't know which run a\n * span belongs to. This module solves that: {@link withApoRun} establishes\n * the run's trace context on AsyncLocalStorage, and any code called during\n * the run (including the processor's `onStart`/`onEnd`) can read it via\n * {@link getActiveApoRun}.\n *\n * This is the same propagation mechanism OTel's own\n * `AsyncLocalStorageContextManager` uses under the hood.\n *\n * @module\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { AgentTaskTraceContext } from \"../tracing.ts\";\n\nexport interface ApoRunContext {\n /** The tee'd trace context for this run. */\n trace: AgentTaskTraceContext;\n /** The parent span id (the run's root span or the current turn span). */\n parentSpanId?: string;\n /** The task id, surfaced in span metadata. */\n taskId?: string;\n /** The current turn number, surfaced in span metadata. */\n turnNumber?: number;\n}\n\nconst runStorage = new AsyncLocalStorage<ApoRunContext>();\n\n/**\n * Run `fn` with `ctx` as the active apo run context. Any code inside `fn`\n * (including across `await` boundaries) can call {@link getActiveApoRun}\n * to retrieve it.\n */\nexport function withApoRun<T>(ctx: ApoRunContext, fn: () => Promise<T>): Promise<T> {\n return runStorage.run(ctx, fn);\n}\n\n/**\n * Run `fn` synchronously with `ctx` as the active apo run context.\n */\nexport function withApoRunSync<T>(ctx: ApoRunContext, fn: () => T): T {\n return runStorage.run(ctx, fn);\n}\n\n/**\n * Get the active run context, or `undefined` if called outside a run.\n *\n * This is what the {@link ApoSpanProcessor} calls in `onStart`/`onEnd` to\n * know which run a span belongs to.\n */\nexport function getActiveApoRun(): ApoRunContext | undefined {\n return runStorage.getStore();\n}\n","/**\n * Shared span-translation helpers for the tracing integrations.\n *\n * Maps OpenTelemetry spans from both the Vercel AI SDK (`ai.*` attributes)\n * and the standard GenAI semantic conventions (`gen_ai.*` attributes) to\n * apo's observation types (`TOOL`, `GENERATION`, `AGENT`).\n *\n * Used by:\n * - {@link ApoSpanProcessor} (the OTel-native path)\n * - `createApoTracer` (the per-turn AI SDK tracer)\n *\n * @module\n */\n\n// ── shared utility functions ─────────────────────────────────────────────\n\nimport { extractTokenCounts } from \"./token-usage.ts\";\n\n/** Parse a JSON string, returning the original value if parsing fails. */\nexport function safeParse(raw: unknown): unknown {\n if (typeof raw !== \"string\") return raw;\n try {\n return JSON.parse(raw);\n } catch {\n return raw;\n }\n}\n\n/** High-resolution monotonic timestamp in milliseconds. */\nexport function monotonicNowMs(): number {\n if (typeof performance !== \"undefined\" && typeof performance.now === \"function\") {\n return performance.now();\n }\n return Date.now();\n}\n\n/** Round to 3 decimal places (for latency precision). */\nexport function round3(n: number): number {\n return Math.round(n * 1000) / 1000;\n}\n\n// ── the translation contract ─────────────────────────────────────────────\n\n/**\n * apo observation types the projection recognizes. Only spans with one\n * of these types become events that `t.*` assertions read.\n */\nexport type ApoObservationType = \"TOOL\" | \"GENERATION\" | \"AGENT\";\n\n/**\n * The result of translating an OTel span to apo's format.\n * `null` means the span is not load-bearing (housekeeping, internal, etc.)\n * and should be ignored.\n */\nexport interface TranslatedSpan {\n observationType: ApoObservationType;\n stepName: string;\n /** Model name (for GENERATION spans). */\n model?: string;\n /** Tool call input (for TOOL spans). */\n input?: Record<string, unknown>;\n /** Tool call result (set on end, not start). */\n output?: unknown;\n /** Generation text (for GENERATION spans). */\n text?: string;\n promptTokens?: number;\n completionTokens?: number;\n /** Reasoning/thinking tokens, when the provider reports the dimension. */\n reasoningTokens?: number;\n error?: boolean;\n errorMessage?: string;\n}\n\n/**\n * An OTel span's attributes and status, in a shape that doesn't require\n * importing `@opentelemetry/api` at the type level.\n */\nexport interface OtelSpanData {\n attributes: Record<string, unknown>;\n status: { code: number; message?: string };\n}\n\n/**\n * Translate an OTel span to apo's observation format.\n *\n * Handles both attribute conventions:\n * - **AI SDK** (`ai.*`): `ai.toolCall.*`, `ai.generateText`, `ai.streamText`\n * - **GenAI standard** (`gen_ai.*`): `gen_ai.tool.*`, `gen_ai.operation.name`\n *\n * Returns `null` for spans that aren't load-bearing (internal housekeeping,\n * non-GenAI operations, etc.).\n */\nexport function translateOtelSpan(\n spanName: string,\n data: OtelSpanData,\n): TranslatedSpan | null {\n const { attributes, status } = data;\n const isError = status.code === 2;\n const errorMessage = status.message;\n\n // ── AI SDK convention (ai.*) ────────────────────────────────────────\n\n if (spanName === \"ai.toolCall\") {\n return {\n observationType: \"TOOL\",\n stepName: String(attributes[\"ai.toolCall.name\"] ?? \"unknown\"),\n input: safeParse(attributes[\"ai.toolCall.args\"]) as Record<string, unknown>,\n output: safeParse(attributes[\"ai.toolCall.result\"]),\n error: isError,\n errorMessage,\n };\n }\n\n // The AI SDK emits a parent span (ai.generateText) and per-step child spans\n // (ai.generateText.doGenerate). The parent span carries:\n // - ai.model.id (at creation)\n // - ai.prompt.messages (the input, at creation)\n // - ai.response.text (the final assembled text, set at end)\n // - ai.usage.promptTokens/completionTokens (total usage, set at end)\n // The child doGenerate spans carry per-step data but NOT the final text.\n // We translate the parent span (which has the complete picture) and skip\n // the per-step child spans.\n if (\n spanName === \"ai.generateText\" ||\n spanName === \"ai.streamText\"\n ) {\n return {\n observationType: \"GENERATION\",\n stepName: \"agent.generate\",\n text: extractTextFromAttrs(attributes),\n model: String(\n attributes[\"gen_ai.request.model\"] ??\n attributes[\"ai.model.id\"] ??\n attributes[\"ai.response.model\"] ??\n \"unknown\",\n ),\n ...extractTokenCounts(attributes),\n error: isError,\n errorMessage,\n };\n }\n\n // ── Standard OTel GenAI conventions (gen_ai.*) ─────────────────────\n\n const operation = attributes[\"gen_ai.operation.name\"];\n\n // Tool execution span\n if (\n operation === \"execute_tool\" ||\n spanName.startsWith(\"gen_ai.tool\") ||\n (spanName.includes(\"tool\") && attributes[\"gen_ai.tool.name\"])\n ) {\n return {\n observationType: \"TOOL\",\n stepName: String(attributes[\"gen_ai.tool.name\"] ?? \"unknown\"),\n input: safeParse(attributes[\"gen_ai.tool.call.arguments\"]) as Record<string, unknown>,\n output: safeParse(attributes[\"gen_ai.tool.call.result\"]),\n error: isError,\n errorMessage,\n };\n }\n\n // Agent invocation span\n if (operation === \"invoke_agent\") {\n return {\n observationType: \"AGENT\",\n stepName: String(attributes[\"gen_ai.agent.name\"] ?? \"agent\"),\n output: safeParse(attributes[\"gen_ai.tool.call.result\"]),\n error: isError,\n errorMessage,\n };\n }\n\n // Chat / generation span\n if (\n operation === \"chat\" ||\n operation === \"generate_content\" ||\n operation === \"text_completion\"\n ) {\n return {\n observationType: \"GENERATION\",\n stepName: \"agent.generate\",\n text: extractTextFromAttrs(attributes, \"gen_ai.\"),\n ...extractTokenCounts(attributes),\n error: isError,\n errorMessage,\n };\n }\n\n // ── Not a load-bearing span ────────────────────────────────────────\n\n return null;\n}\n\n/**\n * Extract the model's text response from span attributes.\n * Tries both `ai.*` and `gen_ai.*` response keys.\n */\nexport function extractTextFromAttrs(\n attrs: Record<string, unknown>,\n prefix?: string,\n): string | undefined {\n const candidates = prefix === \"gen_ai.\"\n ? [\"gen_ai.completion\", \"gen_ai.response.text\"]\n : prefix === \"ai.\"\n ? [\"ai.response.text\", \"ai.response.output\", \"ai.generateText.result\"]\n : [\n \"gen_ai.completion\",\n \"gen_ai.response.text\",\n \"ai.response.text\",\n \"ai.response.output\",\n \"ai.generateText.result\",\n ];\n\n for (const key of candidates) {\n const val = attrs[key];\n if (typeof val === \"string\" && val.length > 0) {\n const parsed = safeParse(val);\n return typeof parsed === \"string\" ? parsed : val;\n }\n }\n return undefined;\n}\n","/**\n * ApoSpanProcessor — an OpenTelemetry SpanProcessor that routes GenAI spans\n * into apo's Flow.\n *\n * This is the OTel-native tracing path. After calling {@link registerApoTracing},\n * any SDK that emits OTel spans (Vercel AI SDK, OpenAI Agents SDK, etc.)\n * will be traced automatically — no per-SDK wrapper needed.\n *\n * The processor is global, but run isolation is maintained via\n * {@link AsyncLocalStorage}: {@link getActiveApoRun} returns the current\n * run's trace context, so each span is routed to the correct run's trace.\n *\n * **Design: eager creation, deferred completion.** The apo span is created\n * at `onStart` (when initial attributes like tool name and model are\n * available). The span is ended at `onEnd` with the final attributes\n * (results, tokens, text). This gives correct ordering: parent spans\n * are created before children, matching the real call hierarchy.\n *\n * @module\n */\n\nimport { getActiveApoRun } from \"./run-context.ts\";\nimport {\n translateOtelSpan,\n round3,\n monotonicNowMs,\n type TranslatedSpan,\n} from \"./otel-translate.ts\";\n\n// ── minimal OTel type shapes (no runtime dependency on @opentelemetry/api) ──\n\ninterface OTelSpanContext {\n spanId: string;\n traceId: string;\n traceFlags: number;\n}\n\ninterface OTelSpanStatus {\n code: number;\n message?: string;\n}\n\ninterface OTelReadableSpan {\n name: string;\n spanContext(): OTelSpanContext;\n parentSpanContext?: OTelSpanContext;\n attributes: Record<string, unknown>;\n status: OTelSpanStatus;\n duration: [number, number];\n startTime: [number, number];\n ended: boolean;\n}\n\ninterface OTelSpan extends OTelReadableSpan {\n setAttribute(key: string, value: unknown): this;\n setAttributes(attributes: Record<string, unknown>): this;\n setStatus(status: OTelSpanStatus): this;\n recordException(exception: unknown): void;\n updateName(name: string): this;\n end(endTime?: unknown): void;\n isRecording(): boolean;\n}\n\ninterface OTelContext {\n getValue(key: symbol): unknown;\n setValue(key: symbol, value: unknown): OTelContext;\n deleteValue(key: symbol): OTelContext;\n}\n\ninterface OTelSpanProcessorLifecycle {\n forceFlush(): Promise<void>;\n shutdown(): Promise<void>;\n}\n\n/**\n * Tracks an active span: the apo span ID, the OTel span name, and the\n * start timestamp for latency calculation.\n */\ninterface TrackedSpan {\n apoSpanId: string;\n otelSpanName: string;\n startedAt: number;\n}\n\n/**\n * A SpanProcessor that translates GenAI OTel spans into apo observations.\n *\n * Spans are created at `onStart` (eager) and completed at `onEnd`\n * (deferred). This gives correct parent/child ordering — parent spans\n * are created before their children, matching the real call hierarchy.\n *\n * If a span isn't load-bearing (not a GenAI tool/generation span), no\n * apo span is created.\n */\nexport class ApoSpanProcessor implements OTelSpanProcessorLifecycle {\n private spanMap = new Map<string, TrackedSpan>();\n\n onStart(span: OTelSpan, _parentContext: OTelContext): void {\n const run = getActiveApoRun();\n if (!run) return;\n\n // Quick check: is this span potentially load-bearing?\n // At onStart, the AI SDK has already set the initial attributes\n // (ai.toolCall.name, ai.toolCall.args for tool spans; ai.model.id\n // for generation spans). So we CAN translate now.\n const translated = translateOtelSpan(span.name, {\n attributes: span.attributes ?? {},\n status: { code: 0 }, // no error at start\n });\n if (!translated) return; // not load-bearing — skip\n\n // Resolve parent: check if the parent OTel span has an apo mapping\n const parentOtelId = span.parentSpanContext?.spanId;\n const parentTracked = parentOtelId ? this.spanMap.get(parentOtelId) : undefined;\n const parentApoId = parentTracked?.apoSpanId || run.parentSpanId || run.trace.rootSpanId;\n\n // Create the apo span eagerly (at start time)\n const apoSpanId = run.trace.createSpan({\n task_id: run.taskId ?? \"otel-trace\",\n parent_call_id: parentApoId,\n step_name: translated.stepName,\n model: translated.model ?? translated.stepName,\n observation_type: translated.observationType,\n ...(translated.input ? { input: translated.input } : {}),\n metadata: {\n ...(run.taskId ? { taskId: run.taskId } : {}),\n ...(run.turnNumber !== undefined ? { turnNumber: run.turnNumber } : {}),\n source: \"otel-processor\",\n },\n });\n\n this.spanMap.set(span.spanContext().spanId, {\n apoSpanId,\n otelSpanName: span.name,\n startedAt: monotonicNowMs(),\n });\n }\n\n onEnd(span: OTelReadableSpan): void {\n const otelId = span.spanContext().spanId;\n const tracked = this.spanMap.get(otelId);\n if (!tracked) return; // wasn't tracked (not load-bearing or outside run)\n this.spanMap.delete(otelId);\n\n const run = getActiveApoRun();\n if (!run) return;\n\n // Re-translate with final attributes (results, text, tokens set during execution)\n const translated = translateOtelSpan(tracked.otelSpanName, {\n attributes: span.attributes ?? {},\n status: span.status ?? { code: 0 },\n });\n\n const latency = round3(monotonicNowMs() - tracked.startedAt);\n\n // End the apo span with the final data\n run.trace.endSpan(tracked.apoSpanId, {\n latency_ms: latency,\n ...buildEndParams(translated),\n });\n }\n\n forceFlush(): Promise<void> {\n return Promise.resolve();\n }\n\n shutdown(): Promise<void> {\n this.spanMap.clear();\n return Promise.resolve();\n }\n\n /** Clear all state — for testing. */\n reset(): void {\n this.spanMap.clear();\n }\n}\n\n/**\n * Build the `endSpan` params from the translated span.\n * Handles observation-type-specific output shapes.\n */\nfunction buildEndParams(t: TranslatedSpan | null): Record<string, unknown> {\n if (!t) return {};\n\n const params: Record<string, unknown> = {};\n\n if (t.promptTokens !== undefined) params.prompt_tokens = t.promptTokens;\n if (t.completionTokens !== undefined) params.completion_tokens = t.completionTokens;\n if (t.reasoningTokens !== undefined) params.reasoning_tokens = t.reasoningTokens;\n\n if (t.observationType === \"GENERATION\") {\n if (t.text !== undefined) {\n params.output = { text: t.text };\n }\n } else if (t.observationType === \"TOOL\") {\n if (t.output !== undefined) {\n params.output =\n t.output && typeof t.output === \"object\"\n ? (t.output as Record<string, unknown>)\n : { value: t.output };\n }\n }\n\n if (t.error) {\n params.level = \"ERROR\";\n if (t.errorMessage) params.status_message = t.errorMessage;\n }\n\n return params;\n}\n","/**\n * Registration API for the OTel-native tracing integration.\n *\n * Call {@link registerApoTracing} once at startup (e.g., in `startSession`\n * or at module load). After this, any SDK that emits OTel spans during a run\n * will be traced automatically — no per-SDK wrapper needed.\n *\n * @example\n * ```ts\n * import { registerApoTracing } from \"@apo-ai/sdk/agent-task\";\n *\n * async startSession(ctx) {\n * registerApoTracing(); // one line, once\n * return { async sendUserTurn(turn) { ... } };\n * }\n * ```\n *\n * @module\n */\n\nimport { ApoSpanProcessor } from \"./otel-processor.ts\";\n\n// We use dynamic imports for @opentelemetry so the module is importable\n// without the packages installed. In practice they're transitive deps of `ai`.\n\nlet registered = false;\nlet currentProcessor: ApoSpanProcessor | null = null;\n\nexport interface RegisterApoTracingOptions {\n /**\n * Set as the global tracer provider (default: true).\n * Set to false if the host app has its own OTel setup and you want to\n * add apo's processor to an existing provider instead.\n */\n setGlobal?: boolean;\n}\n\n/**\n * Register apo's OTel SpanProcessor. Idempotent — calling it more than\n * once is a no-op.\n *\n * After registration, any OTel span created during an apo run (inside\n * `withApoRun`) that matches the GenAI conventions (`ai.*` or `gen_ai.*`)\n * is automatically translated and fed into the run's Flow.\n */\nexport async function registerApoTracing(\n _options?: RegisterApoTracingOptions,\n): Promise<void> {\n if (registered) return;\n registered = true;\n\n // Create the processor but DON'T register our own provider. The processor\n // is picked up by configureApoTelemetry (via getRegisteredApoProcessor)\n // and added to the OTLP-exporting provider. This ensures GenAI spans from\n // SDKs (Vercel AI, OpenAI, etc.) reach BOTH the projection-tee (for local\n // assertions) AND the OTLP exporter (for the backend), all through a single\n // global provider — no competing providers.\n currentProcessor = new ApoSpanProcessor();\n}\n\n/**\n * Alias used by configureApoTelemetry to pick up the processor and add it\n * to the OTLP-exporting provider.\n */\nexport function getRegisteredApoProcessor(): ApoSpanProcessor | null {\n return currentProcessor;\n}\n\n/**\n * Reset registration state — for testing only.\n */\nexport function resetApoTracing(): void {\n registered = false;\n currentProcessor = null;\n}\n"],"mappings":"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","names":["acquired"]}
@@ -13,8 +13,8 @@ import {
13
13
  traceRetriever,
14
14
  traceTool,
15
15
  withApoTrace
16
- } from "../chunk-GETY34VI.js";
17
- import "../chunk-HWZVL6TS.js";
16
+ } from "../chunk-IPQ6IIEG.js";
17
+ import "../chunk-HLRXQAZ5.js";
18
18
  export {
19
19
  buildApoAuthHeaders,
20
20
  configureApoTelemetry,
@@ -38,6 +38,9 @@ interface EndSpanParams {
38
38
  latency_ms?: number;
39
39
  prompt_tokens?: number;
40
40
  completion_tokens?: number;
41
+ /** Reasoning/thinking tokens, when the provider reports them as a distinct
42
+ * dimension. Omit when unreported — unknown must stay unknown, not zero. */
43
+ reasoning_tokens?: number;
41
44
  status_message?: string;
42
45
  level?: "DEBUG" | "DEFAULT" | "WARNING" | "ERROR";
43
46
  metadata?: Record<string, unknown>;
@@ -51,6 +54,16 @@ interface TraceRunOptions extends Omit<CreateTraceParams, "project"> {
51
54
  interface TraceStepOptions extends Omit<CreateSpanParams, "project" | "task_id" | "run_id" | "output"> {
52
55
  task_id?: string;
53
56
  summarize?: (result: unknown) => Record<string, unknown> | undefined;
57
+ /**
58
+ * Post-hoc token-usage extractor — the usage sibling of `summarize`, for
59
+ * spans whose token counts are only known after the work (e.g. judge
60
+ * spans, whose usage rides the judged result). Applied when the span ends;
61
+ * the extracted counts become `gen_ai.usage.*` span attributes.
62
+ */
63
+ usage?: (result: unknown) => {
64
+ prompt_tokens?: number;
65
+ completion_tokens?: number;
66
+ } | undefined;
54
67
  }
55
68
  interface TraceEventOptions extends Omit<CreateSpanParams, "project" | "task_id" | "run_id"> {
56
69
  task_id?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apo-ai/sdk",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "TypeScript SDK for executable agent capability specifications and harness engineering with Apo",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/agent-task/integrations/openai.ts"],"sourcesContent":["/**\n * OpenAI JS SDK tracing integration for apo.\n *\n * Wraps an `OpenAI` client so `chat.completions.create()` calls are\n * automatically traced. Tool calls and generations are captured without\n * any manual span code in the adapter.\n *\n * Uses a Proxy to intercept the `create` method, then emits GENERATION +\n * TOOL spans through the trace context, where they become the observations\n * that `t.calledTool` reads.\n *\n * @example\n * ```ts\n * import OpenAI from \"openai\";\n * import { createApoOpenAI } from \"@apo-ai/sdk/agent-task\";\n *\n * async sendUserTurn(turn, { trace, parentSpanId }) {\n * const client = createApoOpenAI(\n * new OpenAI({ apiKey, baseURL: \"https://openrouter.ai/api/v1\" }),\n * { trace, parentSpanId, taskId: ctx.task.id, turnNumber }\n * );\n * const response = await client.chat.completions.create({\n * model: \"google/gemini-2.5-flash-lite\",\n * messages: [{ role: \"user\", content: String(turn) }],\n * tools: [...],\n * });\n * return { response: response.choices[0].message.content ?? \"\" };\n * }\n * ```\n *\n * @module\n */\n\nimport type { AgentTaskTraceContext } from \"../tracing.ts\";\nimport { startGeneration, emitGenerationAndTools, safeParse } from \"./span-helpers.ts\";\n\nexport interface CreateApoOpenAIOptions {\n /** The tee'd trace context from `sendUserTurn`'s second argument. */\n trace: AgentTaskTraceContext;\n /** The parent span id for this turn (from `sendUserTurn`'s context). */\n parentSpanId?: string;\n /** Optional task id, surfaced in span metadata. */\n taskId?: string;\n /** Optional turn number, surfaced in span metadata. */\n turnNumber?: number;\n}\n\n/**\n * Minimal structural shape of the OpenAI client we need to intercept.\n * We avoid importing `openai` at the type level so this module doesn't\n * take a runtime dependency — the wrapper works with any client that has\n * `chat.completions.create`.\n *\n * The `create` signature uses a broad type so the OpenAI SDK's overloaded\n * `create` method (which has specific param types) is assignable to this.\n */\ninterface OpenAIClientLike {\n chat: {\n completions: {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n create: (...args: any[]) => Promise<any>;\n };\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [key: string]: any;\n}\n\n/**\n * The response shape we read from `chat.completions.create()`.\n */\ninterface OpenAIResponseLike {\n model?: string;\n choices?: Array<{\n message?: {\n content?: string | null;\n tool_calls?: Array<{\n function?: { name?: string; arguments?: string };\n }>;\n };\n }>;\n usage?: {\n prompt_tokens?: number;\n completion_tokens?: number;\n };\n}\n\n/**\n * Wrap an OpenAI client so `chat.completions.create()` is traced.\n * Returns a Proxy-wrapped client — call it exactly as you would the\n * original. Non-streaming only; streaming responses pass through\n * untraced.\n *\n * For SDKs that emit OpenTelemetry natively (OpenAI Agents SDK, Vercel AI\n * SDK, Claude Agent SDK), prefer `configureApoTelemetry()` instead — no\n * wrapper needed. This manual wrapper is for the raw `openai` package or\n * custom clients that don't emit OTel on their own.\n */\nexport function createApoOpenAI<T extends OpenAIClientLike>(\n client: T,\n options: CreateApoOpenAIOptions,\n): T {\n const { trace, parentSpanId, taskId, turnNumber } = options;\n const realCreate = client.chat.completions.create.bind(client.chat.completions);\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const tracedCreate = async (...args: any[]): Promise<any> => {\n const params = (args[0] ?? {}) as Record<string, unknown>;\n const model = String(params.model ?? \"unknown\");\n\n // Non-streaming: trace fully. Streaming (stream: true): pass through.\n if (params.stream === true) {\n return realCreate(params);\n }\n\n const { spanId, startedAt } = startGeneration(trace, {\n model,\n system: typeof params.system === \"string\" ? params.system : undefined,\n messages: params.messages,\n parentSpanId,\n taskId,\n turnNumber,\n });\n\n try {\n const response = (await realCreate(params)) as OpenAIResponseLike;\n const message = response.choices?.[0]?.message;\n const text = message?.content ?? undefined;\n const toolCalls = (message?.tool_calls ?? []).map((tc) => ({\n name: tc.function?.name ?? \"unknown\",\n input: tc.function?.arguments ? safeParse(tc.function.arguments) : undefined,\n }));\n\n emitGenerationAndTools(trace, spanId, startedAt, {\n text,\n promptTokens: response.usage?.prompt_tokens,\n completionTokens: response.usage?.completion_tokens,\n toolCalls,\n taskId,\n turnNumber,\n });\n\n return response;\n } catch (error) {\n emitGenerationAndTools(trace, spanId, startedAt, {\n taskId,\n turnNumber,\n error: { message: error instanceof Error ? error.message : String(error) },\n });\n throw error;\n }\n };\n\n // Proxy that intercepts client.chat.completions.create but passes\n // everything else through unchanged.\n return new Proxy(client, {\n get(target, prop, receiver) {\n if (prop === \"chat\") {\n return new Proxy(target.chat, {\n get(chatTarget, chatProp) {\n if (chatProp === \"completions\") {\n return new Proxy(chatTarget.completions, {\n get(completionsTarget, completionsProp) {\n if (completionsProp === \"create\") {\n return tracedCreate;\n }\n return Reflect.get(completionsTarget, completionsProp);\n },\n });\n }\n return Reflect.get(chatTarget, chatProp);\n },\n });\n }\n return Reflect.get(target, prop, receiver);\n },\n }) as T;\n}\n"],"mappings":";;;;;;;;;AAiGO,SAAS,gBACd,QACA,SACG;AACH,QAAM,EAAE,OAAO,cAAc,QAAQ,WAAW,IAAI;AACpD,QAAM,aAAa,OAAO,KAAK,YAAY,OAAO,KAAK,OAAO,KAAK,WAAW;AAG9E,QAAM,eAAe,UAAU,SAA8B;AAC3D,UAAM,SAAU,KAAK,CAAC,KAAK,CAAC;AAC5B,UAAM,QAAQ,OAAO,OAAO,SAAS,SAAS;AAG9C,QAAI,OAAO,WAAW,MAAM;AAC1B,aAAO,WAAW,MAAM;AAAA,IAC1B;AAEA,UAAM,EAAE,QAAQ,UAAU,IAAI,gBAAgB,OAAO;AAAA,MACnD;AAAA,MACA,QAAQ,OAAO,OAAO,WAAW,WAAW,OAAO,SAAS;AAAA,MAC5D,UAAU,OAAO;AAAA,MACjB;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,QAAI;AACF,YAAM,WAAY,MAAM,WAAW,MAAM;AACzC,YAAM,UAAU,SAAS,UAAU,CAAC,GAAG;AACvC,YAAM,OAAO,SAAS,WAAW;AACjC,YAAM,aAAa,SAAS,cAAc,CAAC,GAAG,IAAI,CAAC,QAAQ;AAAA,QACzD,MAAM,GAAG,UAAU,QAAQ;AAAA,QAC3B,OAAO,GAAG,UAAU,YAAY,UAAU,GAAG,SAAS,SAAS,IAAI;AAAA,MACrE,EAAE;AAEF,6BAAuB,OAAO,QAAQ,WAAW;AAAA,QAC/C;AAAA,QACA,cAAc,SAAS,OAAO;AAAA,QAC9B,kBAAkB,SAAS,OAAO;AAAA,QAClC;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAED,aAAO;AAAA,IACT,SAAS,OAAO;AACd,6BAAuB,OAAO,QAAQ,WAAW;AAAA,QAC/C;AAAA,QACA;AAAA,QACA,OAAO,EAAE,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AAAA,MAC3E,CAAC;AACD,YAAM;AAAA,IACR;AAAA,EACF;AAIA,SAAO,IAAI,MAAM,QAAQ;AAAA,IACvB,IAAI,QAAQ,MAAM,UAAU;AAC1B,UAAI,SAAS,QAAQ;AACnB,eAAO,IAAI,MAAM,OAAO,MAAM;AAAA,UAC5B,IAAI,YAAY,UAAU;AACxB,gBAAI,aAAa,eAAe;AAC9B,qBAAO,IAAI,MAAM,WAAW,aAAa;AAAA,gBACvC,IAAI,mBAAmB,iBAAiB;AACtC,sBAAI,oBAAoB,UAAU;AAChC,2BAAO;AAAA,kBACT;AACA,yBAAO,QAAQ,IAAI,mBAAmB,eAAe;AAAA,gBACvD;AAAA,cACF,CAAC;AAAA,YACH;AACA,mBAAO,QAAQ,IAAI,YAAY,QAAQ;AAAA,UACzC;AAAA,QACF,CAAC;AAAA,MACH;AACA,aAAO,QAAQ,IAAI,QAAQ,MAAM,QAAQ;AAAA,IAC3C;AAAA,EACF,CAAC;AACH;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/agent-task/integrations/span-helpers.ts"],"sourcesContent":["/**\n * Shared span-emission helpers for the tracing integrations.\n *\n * The OpenAI and Anthropic wrappers both need to:\n * 1. Create a GENERATION span before the LLM call\n * 2. End it with text/tokens/latency after the response\n * 3. Emit a TOOL span for each tool call in the response\n *\n * These helpers keep that logic in one place so the wrappers stay thin.\n *\n * @internal\n */\n\nimport type { AgentTaskTraceContext } from \"../tracing.ts\";\n\nfunction monotonicNowMs(): number {\n if (typeof performance !== \"undefined\" && typeof performance.now === \"function\") {\n return performance.now();\n }\n return Date.now();\n}\n\nfunction round3(n: number): number {\n return Math.round(n * 1000) / 1000;\n}\n\nfunction safeParse(raw: unknown): unknown {\n if (typeof raw !== \"string\") return raw;\n try {\n return JSON.parse(raw);\n } catch {\n return raw;\n }\n}\n\n/**\n * Emit a GENERATION span + TOOL spans for a completed LLM call.\n *\n * Both wrappers call this after the SDK returns the response. It:\n * 1. Ends the GENERATION span (created by {@link startGeneration}) with\n * text, token counts, and latency.\n * 2. Emits a TOOL span per tool call, so `t.calledTool(name)` works.\n */\nexport function emitGenerationAndTools(\n trace: AgentTaskTraceContext,\n genSpanId: string,\n genStartedAt: number,\n opts: {\n text?: string;\n promptTokens?: number;\n completionTokens?: number;\n toolCalls?: Array<{ name: string; input?: unknown }>;\n taskId?: string;\n turnNumber?: number;\n error?: { message: string };\n },\n): void {\n const latency = round3(monotonicNowMs() - genStartedAt);\n const isError = !!opts.error;\n\n // End the GENERATION span\n trace.endSpan(genSpanId, {\n latency_ms: latency,\n prompt_tokens: opts.promptTokens,\n completion_tokens: opts.completionTokens,\n output: {\n ...(opts.text !== undefined ? { text: opts.text } : {}),\n ...(opts.error ? { error: opts.error.message } : {}),\n },\n ...(isError\n ? { level: \"ERROR\" as const, status_message: opts.error!.message }\n : {}),\n });\n\n // Emit TOOL spans (only on success — errors are captured on the GENERATION span)\n if (!isError && opts.toolCalls) {\n for (const tc of opts.toolCalls) {\n const toolSpanId = trace.createSpan({\n task_id: opts.taskId ?? \"trace\",\n parent_call_id: genSpanId,\n step_name: tc.name,\n observation_type: \"TOOL\",\n ...(tc.input !== undefined\n ? {\n input:\n tc.input && typeof tc.input === \"object\"\n ? (tc.input as Record<string, unknown>)\n : { value: tc.input },\n }\n : {}),\n metadata: { toolName: tc.name },\n });\n trace.endSpan(toolSpanId, {});\n }\n }\n}\n\n/**\n * Create a GENERATION span before the LLM call.\n * Returns `{ spanId, startedAt }` to pass to {@link emitGenerationAndTools}.\n */\nexport function startGeneration(\n trace: AgentTaskTraceContext,\n opts: {\n model: string;\n system?: string;\n messages?: unknown;\n parentSpanId?: string;\n taskId?: string;\n turnNumber?: number;\n },\n): { spanId: string; startedAt: number } {\n const spanId = trace.createSpan({\n task_id: opts.taskId ?? \"trace\",\n parent_call_id: opts.parentSpanId ?? trace.rootSpanId,\n step_name: \"agent.generate\",\n model: opts.model,\n observation_type: \"GENERATION\",\n input: {\n ...(opts.system !== undefined ? { system: opts.system } : {}),\n ...(opts.messages !== undefined ? { messages: opts.messages } : {}),\n },\n metadata: {\n ...(opts.turnNumber !== undefined ? { turnNumber: opts.turnNumber } : {}),\n ...(opts.taskId ? { taskId: opts.taskId } : {}),\n },\n });\n return { spanId, startedAt: monotonicNowMs() };\n}\n\n// Re-export safeParse for the wrappers (OpenAI arguments is a JSON string)\nexport { safeParse };\n"],"mappings":";;;;AAeA,SAAS,iBAAyB;AAChC,MAAI,OAAO,gBAAgB,eAAe,OAAO,YAAY,QAAQ,YAAY;AAC/E,WAAO,YAAY,IAAI;AAAA,EACzB;AACA,SAAO,KAAK,IAAI;AAClB;AAEA,SAAS,OAAO,GAAmB;AACjC,SAAO,KAAK,MAAM,IAAI,GAAI,IAAI;AAChC;AAEA,SAAS,UAAU,KAAuB;AACxC,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,MAAI;AACF,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAUO,SAAS,uBACd,OACA,WACA,cACA,MASM;AACN,QAAM,UAAU,OAAO,eAAe,IAAI,YAAY;AACtD,QAAM,UAAU,CAAC,CAAC,KAAK;AAGvB,QAAM,QAAQ,WAAW;AAAA,IACvB,YAAY;AAAA,IACZ,eAAe,KAAK;AAAA,IACpB,mBAAmB,KAAK;AAAA,IACxB,QAAQ;AAAA,MACN,GAAI,KAAK,SAAS,SAAY,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,MACrD,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAM,QAAQ,IAAI,CAAC;AAAA,IACpD;AAAA,IACA,GAAI,UACA,EAAE,OAAO,SAAkB,gBAAgB,KAAK,MAAO,QAAQ,IAC/D,CAAC;AAAA,EACP,CAAC;AAGD,MAAI,CAAC,WAAW,KAAK,WAAW;AAC9B,eAAW,MAAM,KAAK,WAAW;AAC/B,YAAM,aAAa,MAAM,WAAW;AAAA,QAClC,SAAS,KAAK,UAAU;AAAA,QACxB,gBAAgB;AAAA,QAChB,WAAW,GAAG;AAAA,QACd,kBAAkB;AAAA,QAClB,GAAI,GAAG,UAAU,SACb;AAAA,UACE,OACE,GAAG,SAAS,OAAO,GAAG,UAAU,WAC3B,GAAG,QACJ,EAAE,OAAO,GAAG,MAAM;AAAA,QAC1B,IACA,CAAC;AAAA,QACL,UAAU,EAAE,UAAU,GAAG,KAAK;AAAA,MAChC,CAAC;AACD,YAAM,QAAQ,YAAY,CAAC,CAAC;AAAA,IAC9B;AAAA,EACF;AACF;AAMO,SAAS,gBACd,OACA,MAQuC;AACvC,QAAM,SAAS,MAAM,WAAW;AAAA,IAC9B,SAAS,KAAK,UAAU;AAAA,IACxB,gBAAgB,KAAK,gBAAgB,MAAM;AAAA,IAC3C,WAAW;AAAA,IACX,OAAO,KAAK;AAAA,IACZ,kBAAkB;AAAA,IAClB,OAAO;AAAA,MACL,GAAI,KAAK,WAAW,SAAY,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;AAAA,MAC3D,GAAI,KAAK,aAAa,SAAY,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;AAAA,IACnE;AAAA,IACA,UAAU;AAAA,MACR,GAAI,KAAK,eAAe,SAAY,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;AAAA,MACvE,GAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AACD,SAAO,EAAE,QAAQ,WAAW,eAAe,EAAE;AAC/C;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/agent-task/integrations/token-usage.ts"],"sourcesContent":["/**\n * Shared token-usage extraction for the tracing integrations.\n *\n * Both the `createApoTracer` path (`ai-sdk.ts`) and the OTel-native path\n * (`otel-translate.ts` → `otel-processor.ts`) must extract token counts from\n * AI SDK span attributes using the same logic. This helper is the single\n * source of truth — it prevents the two paths from drifting apart (the bug\n * where one path reported tokens and the other silently dropped them).\n *\n * The AI SDK emits usage under `ai.usage.promptTokens` /\n * `ai.usage.completionTokens`. Standard OTel GenAI conventions use\n * `gen_ai.usage.input_tokens` / `gen_ai.usage.output_tokens`. Both are\n * recognized; standard `gen_ai.usage.*` takes precedence when both are present.\n *\n * @internal\n */\n\ntype SpanAttributes = Record<string, unknown>;\n\n/**\n * Extract `prompt_tokens` / `completion_tokens` from a span's attributes.\n * Returns an object shaped to spread directly into `trace.endSpan()` params.\n * Omits keys when the corresponding attribute is absent or non-numeric.\n *\n * Recognizes all known attribute-name conventions:\n * - Vercel AI SDK: `ai.usage.promptTokens` / `ai.usage.completionTokens`\n * - OTel GenAI: `gen_ai.usage.input_tokens` / `gen_ai.usage.output_tokens`\n * - OTel GenAI (alt): `gen_ai.usage.prompt_tokens` / `gen_ai.usage.completion_tokens`\n *\n * `gen_ai.usage.*` takes precedence over `ai.usage.*` when both are present.\n */\nexport function extractTokenUsage(attrs: SpanAttributes): {\n prompt_tokens?: number;\n completion_tokens?: number;\n} {\n const prompt_tokens = toNum(\n attrs[\"gen_ai.usage.input_tokens\"] ??\n attrs[\"gen_ai.usage.prompt_tokens\"] ??\n attrs[\"ai.usage.promptTokens\"],\n );\n const completion_tokens = toNum(\n attrs[\"gen_ai.usage.output_tokens\"] ??\n attrs[\"gen_ai.usage.completion_tokens\"] ??\n attrs[\"ai.usage.completionTokens\"],\n );\n const out: { prompt_tokens?: number; completion_tokens?: number } = {};\n if (prompt_tokens !== undefined) out.prompt_tokens = prompt_tokens;\n if (completion_tokens !== undefined) out.completion_tokens = completion_tokens;\n return out;\n}\n\n/**\n * Coerce a span attribute value to a number. Accepts numbers directly and\n * parses finite numeric strings; ignores everything else.\n */\nfunction toNum(val: unknown): number | undefined {\n if (typeof val === \"number\") return val;\n if (typeof val === \"string\") {\n const n = Number(val);\n return Number.isFinite(n) ? n : undefined;\n }\n return undefined;\n}\n\n/**\n * Re-exported so the integrations can read prompt/completion tokens as\n * separate values (the OTel translate path returns them individually, not\n * as an endSpan-ready object).\n */\nexport function extractTokenCounts(attrs: SpanAttributes): {\n promptTokens?: number;\n completionTokens?: number;\n} {\n const { prompt_tokens, completion_tokens } = extractTokenUsage(attrs);\n return {\n ...(prompt_tokens !== undefined ? { promptTokens: prompt_tokens } : {}),\n ...(completion_tokens !== undefined\n ? { completionTokens: completion_tokens }\n : {}),\n };\n}\n"],"mappings":";;;;AA+BO,SAAS,kBAAkB,OAGhC;AACA,QAAM,gBAAgB;AAAA,IACpB,MAAM,2BAA2B,KAC/B,MAAM,4BAA4B,KAClC,MAAM,uBAAuB;AAAA,EACjC;AACA,QAAM,oBAAoB;AAAA,IACxB,MAAM,4BAA4B,KAChC,MAAM,gCAAgC,KACtC,MAAM,2BAA2B;AAAA,EACrC;AACA,QAAM,MAA8D,CAAC;AACrE,MAAI,kBAAkB,OAAW,KAAI,gBAAgB;AACrD,MAAI,sBAAsB,OAAW,KAAI,oBAAoB;AAC7D,SAAO;AACT;AAMA,SAAS,MAAM,KAAkC;AAC/C,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,MAAI,OAAO,QAAQ,UAAU;AAC3B,UAAM,IAAI,OAAO,GAAG;AACpB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AACT;AAOO,SAAS,mBAAmB,OAGjC;AACA,QAAM,EAAE,eAAe,kBAAkB,IAAI,kBAAkB,KAAK;AACpE,SAAO;AAAA,IACL,GAAI,kBAAkB,SAAY,EAAE,cAAc,cAAc,IAAI,CAAC;AAAA,IACrE,GAAI,sBAAsB,SACtB,EAAE,kBAAkB,kBAAkB,IACtC,CAAC;AAAA,EACP;AACF;","names":[]}