@apo-ai/sdk 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +314 -0
- package/dist/agent-task/integrations/ai-sdk.d.ts +121 -0
- package/dist/agent-task/integrations/ai-sdk.js +10 -0
- package/dist/agent-task/integrations/ai-sdk.js.map +1 -0
- package/dist/agent-task/integrations/anthropic.d.ts +67 -0
- package/dist/agent-task/integrations/anthropic.js +10 -0
- package/dist/agent-task/integrations/anthropic.js.map +1 -0
- package/dist/agent-task/integrations/openai.d.ts +77 -0
- package/dist/agent-task/integrations/openai.js +10 -0
- package/dist/agent-task/integrations/openai.js.map +1 -0
- package/dist/agent-task/public.d.ts +1326 -0
- package/dist/agent-task/public.js +3026 -0
- package/dist/agent-task/public.js.map +1 -0
- package/dist/chunk-DT2LMB7O.js +80 -0
- package/dist/chunk-DT2LMB7O.js.map +1 -0
- package/dist/chunk-GDB6SDGN.js +75 -0
- package/dist/chunk-GDB6SDGN.js.map +1 -0
- package/dist/chunk-HKLBPIB4.js +232 -0
- package/dist/chunk-HKLBPIB4.js.map +1 -0
- package/dist/chunk-HWZVL6TS.js +37 -0
- package/dist/chunk-HWZVL6TS.js.map +1 -0
- package/dist/chunk-PMYLL2H4.js +495 -0
- package/dist/chunk-PMYLL2H4.js.map +1 -0
- package/dist/chunk-SZDSIPXC.js +74 -0
- package/dist/chunk-SZDSIPXC.js.map +1 -0
- package/dist/index.d.ts +95 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/otel/index.d.ts +270 -0
- package/dist/otel/index.js +32 -0
- package/dist/otel/index.js.map +1 -0
- package/dist/tracing-DtbhYQV6.d.ts +26 -0
- package/dist/types-C-nrDEVf.d.ts +140 -0
- package/package.json +92 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Auto-generated by `pnpm --filter @apo-ai/sdk build` (tsup.config.ts).
|
|
2
|
+
// Do not edit by hand — edit the src/ originals and rebuild.
|
|
3
|
+
import {
|
|
4
|
+
emitGenerationAndTools,
|
|
5
|
+
safeParse,
|
|
6
|
+
startGeneration
|
|
7
|
+
} from "./chunk-GDB6SDGN.js";
|
|
8
|
+
|
|
9
|
+
// src/agent-task/integrations/openai.ts
|
|
10
|
+
function createApoOpenAI(client, options) {
|
|
11
|
+
const { trace, parentSpanId, taskId, turnNumber } = options;
|
|
12
|
+
const realCreate = client.chat.completions.create.bind(client.chat.completions);
|
|
13
|
+
const tracedCreate = async (...args) => {
|
|
14
|
+
const params = args[0] ?? {};
|
|
15
|
+
const model = String(params.model ?? "unknown");
|
|
16
|
+
if (params.stream === true) {
|
|
17
|
+
return realCreate(params);
|
|
18
|
+
}
|
|
19
|
+
const { spanId, startedAt } = startGeneration(trace, {
|
|
20
|
+
model,
|
|
21
|
+
system: typeof params.system === "string" ? params.system : void 0,
|
|
22
|
+
messages: params.messages,
|
|
23
|
+
parentSpanId,
|
|
24
|
+
taskId,
|
|
25
|
+
turnNumber
|
|
26
|
+
});
|
|
27
|
+
try {
|
|
28
|
+
const response = await realCreate(params);
|
|
29
|
+
const message = response.choices?.[0]?.message;
|
|
30
|
+
const text = message?.content ?? void 0;
|
|
31
|
+
const toolCalls = (message?.tool_calls ?? []).map((tc) => ({
|
|
32
|
+
name: tc.function?.name ?? "unknown",
|
|
33
|
+
input: tc.function?.arguments ? safeParse(tc.function.arguments) : void 0
|
|
34
|
+
}));
|
|
35
|
+
emitGenerationAndTools(trace, spanId, startedAt, {
|
|
36
|
+
text,
|
|
37
|
+
promptTokens: response.usage?.prompt_tokens,
|
|
38
|
+
completionTokens: response.usage?.completion_tokens,
|
|
39
|
+
toolCalls,
|
|
40
|
+
taskId,
|
|
41
|
+
turnNumber
|
|
42
|
+
});
|
|
43
|
+
return response;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
emitGenerationAndTools(trace, spanId, startedAt, {
|
|
46
|
+
taskId,
|
|
47
|
+
turnNumber,
|
|
48
|
+
error: { message: error instanceof Error ? error.message : String(error) }
|
|
49
|
+
});
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
return new Proxy(client, {
|
|
54
|
+
get(target, prop, receiver) {
|
|
55
|
+
if (prop === "chat") {
|
|
56
|
+
return new Proxy(target.chat, {
|
|
57
|
+
get(chatTarget, chatProp) {
|
|
58
|
+
if (chatProp === "completions") {
|
|
59
|
+
return new Proxy(chatTarget.completions, {
|
|
60
|
+
get(completionsTarget, completionsProp) {
|
|
61
|
+
if (completionsProp === "create") {
|
|
62
|
+
return tracedCreate;
|
|
63
|
+
}
|
|
64
|
+
return Reflect.get(completionsTarget, completionsProp);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return Reflect.get(chatTarget, chatProp);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return Reflect.get(target, prop, receiver);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export {
|
|
78
|
+
createApoOpenAI
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=chunk-DT2LMB7O.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. The FlowTee picks these up and\n * builds the Flow 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":[]}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Auto-generated by `pnpm --filter @apo-ai/sdk build` (tsup.config.ts).
|
|
2
|
+
// Do not edit by hand — edit the src/ originals and rebuild.
|
|
3
|
+
|
|
4
|
+
// src/agent-task/integrations/span-helpers.ts
|
|
5
|
+
function monotonicNowMs() {
|
|
6
|
+
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
7
|
+
return performance.now();
|
|
8
|
+
}
|
|
9
|
+
return Date.now();
|
|
10
|
+
}
|
|
11
|
+
function round3(n) {
|
|
12
|
+
return Math.round(n * 1e3) / 1e3;
|
|
13
|
+
}
|
|
14
|
+
function safeParse(raw) {
|
|
15
|
+
if (typeof raw !== "string") return raw;
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(raw);
|
|
18
|
+
} catch {
|
|
19
|
+
return raw;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function emitGenerationAndTools(trace, genSpanId, genStartedAt, opts) {
|
|
23
|
+
const latency = round3(monotonicNowMs() - genStartedAt);
|
|
24
|
+
const isError = !!opts.error;
|
|
25
|
+
trace.endSpan(genSpanId, {
|
|
26
|
+
latency_ms: latency,
|
|
27
|
+
prompt_tokens: opts.promptTokens,
|
|
28
|
+
completion_tokens: opts.completionTokens,
|
|
29
|
+
output: {
|
|
30
|
+
...opts.text !== void 0 ? { text: opts.text } : {},
|
|
31
|
+
...opts.error ? { error: opts.error.message } : {}
|
|
32
|
+
},
|
|
33
|
+
...isError ? { level: "ERROR", status_message: opts.error.message } : {}
|
|
34
|
+
});
|
|
35
|
+
if (!isError && opts.toolCalls) {
|
|
36
|
+
for (const tc of opts.toolCalls) {
|
|
37
|
+
const toolSpanId = trace.createSpan({
|
|
38
|
+
task_id: opts.taskId ?? "trace",
|
|
39
|
+
parent_call_id: genSpanId,
|
|
40
|
+
step_name: tc.name,
|
|
41
|
+
observation_type: "TOOL",
|
|
42
|
+
...tc.input !== void 0 ? {
|
|
43
|
+
input: tc.input && typeof tc.input === "object" ? tc.input : { value: tc.input }
|
|
44
|
+
} : {},
|
|
45
|
+
metadata: { toolName: tc.name }
|
|
46
|
+
});
|
|
47
|
+
trace.endSpan(toolSpanId, {});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function startGeneration(trace, opts) {
|
|
52
|
+
const spanId = trace.createSpan({
|
|
53
|
+
task_id: opts.taskId ?? "trace",
|
|
54
|
+
parent_call_id: opts.parentSpanId ?? trace.rootSpanId,
|
|
55
|
+
step_name: "agent.generate",
|
|
56
|
+
model: opts.model,
|
|
57
|
+
observation_type: "GENERATION",
|
|
58
|
+
input: {
|
|
59
|
+
...opts.system !== void 0 ? { system: opts.system } : {},
|
|
60
|
+
...opts.messages !== void 0 ? { messages: opts.messages } : {}
|
|
61
|
+
},
|
|
62
|
+
metadata: {
|
|
63
|
+
...opts.turnNumber !== void 0 ? { turnNumber: opts.turnNumber } : {},
|
|
64
|
+
...opts.taskId ? { taskId: opts.taskId } : {}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
return { spanId, startedAt: monotonicNowMs() };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export {
|
|
71
|
+
safeParse,
|
|
72
|
+
emitGenerationAndTools,
|
|
73
|
+
startGeneration
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=chunk-GDB6SDGN.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 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":[]}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// Auto-generated by `pnpm --filter @apo-ai/sdk build` (tsup.config.ts).
|
|
2
|
+
// Do not edit by hand — edit the src/ originals and rebuild.
|
|
3
|
+
import {
|
|
4
|
+
extractTokenUsage
|
|
5
|
+
} from "./chunk-HWZVL6TS.js";
|
|
6
|
+
|
|
7
|
+
// src/agent-task/integrations/ai-sdk.ts
|
|
8
|
+
function safeParse(raw) {
|
|
9
|
+
if (typeof raw !== "string") return raw;
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(raw);
|
|
12
|
+
} catch {
|
|
13
|
+
return raw;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function monotonicNowMs() {
|
|
17
|
+
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
18
|
+
return performance.now();
|
|
19
|
+
}
|
|
20
|
+
return Date.now();
|
|
21
|
+
}
|
|
22
|
+
function round3(n) {
|
|
23
|
+
return Math.round(n * 1e3) / 1e3;
|
|
24
|
+
}
|
|
25
|
+
var SPAN_ID_PREFIX = "ai-sdk-";
|
|
26
|
+
var spanCounter = 0;
|
|
27
|
+
function nextSpanId() {
|
|
28
|
+
spanCounter += 1;
|
|
29
|
+
return `${SPAN_ID_PREFIX}${spanCounter}`;
|
|
30
|
+
}
|
|
31
|
+
function createApoTracer(options) {
|
|
32
|
+
const { trace, parentSpanId, taskId, turnNumber } = options;
|
|
33
|
+
let currentGenerationSpanId;
|
|
34
|
+
function makeLiveSpan(name, observationType, initialAttrs) {
|
|
35
|
+
const stepName = observationType === "TOOL" ? String(initialAttrs["ai.toolCall.name"] ?? "unknown") : "agent.generate";
|
|
36
|
+
const input = observationType === "TOOL" ? safeParse(initialAttrs["ai.toolCall.args"]) : void 0;
|
|
37
|
+
const spanId = trace.createSpan({
|
|
38
|
+
task_id: taskId ?? "ai-sdk",
|
|
39
|
+
parent_call_id: observationType === "TOOL" ? currentGenerationSpanId ?? parentSpanId ?? trace.rootSpanId : parentSpanId ?? trace.rootSpanId,
|
|
40
|
+
step_name: stepName,
|
|
41
|
+
model: String(initialAttrs["ai.model.id"] ?? "ai-sdk"),
|
|
42
|
+
observation_type: observationType,
|
|
43
|
+
...input !== void 0 ? { input } : {},
|
|
44
|
+
metadata: {
|
|
45
|
+
...turnNumber !== void 0 ? { turnNumber } : {},
|
|
46
|
+
...taskId ? { taskId } : {}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
if (observationType === "GENERATION") {
|
|
50
|
+
currentGenerationSpanId = spanId;
|
|
51
|
+
}
|
|
52
|
+
const startedAt = monotonicNowMs();
|
|
53
|
+
let pendingAttrs = { ...initialAttrs };
|
|
54
|
+
let status = { code: 0 };
|
|
55
|
+
let errored = false;
|
|
56
|
+
let ended = false;
|
|
57
|
+
const span = {
|
|
58
|
+
spanContext() {
|
|
59
|
+
return { traceId: trace.runId, spanId, traceFlags: 0 };
|
|
60
|
+
},
|
|
61
|
+
setAttribute(key, value) {
|
|
62
|
+
pendingAttrs[key] = value;
|
|
63
|
+
return span;
|
|
64
|
+
},
|
|
65
|
+
setAttributes(attrs) {
|
|
66
|
+
Object.assign(pendingAttrs, attrs);
|
|
67
|
+
return span;
|
|
68
|
+
},
|
|
69
|
+
addEvent(_name, _attributesOrStartTime, _startTime) {
|
|
70
|
+
return span;
|
|
71
|
+
},
|
|
72
|
+
addLink(_link) {
|
|
73
|
+
return span;
|
|
74
|
+
},
|
|
75
|
+
addLinks(_links) {
|
|
76
|
+
return span;
|
|
77
|
+
},
|
|
78
|
+
setStatus(s) {
|
|
79
|
+
status = s;
|
|
80
|
+
if (s.code === 2) errored = true;
|
|
81
|
+
return span;
|
|
82
|
+
},
|
|
83
|
+
updateName(_name) {
|
|
84
|
+
return span;
|
|
85
|
+
},
|
|
86
|
+
end(_endTime) {
|
|
87
|
+
if (ended) return;
|
|
88
|
+
ended = true;
|
|
89
|
+
const latency = round3(monotonicNowMs() - startedAt);
|
|
90
|
+
if (observationType === "TOOL") {
|
|
91
|
+
const resultRaw = pendingAttrs["ai.toolCall.result"];
|
|
92
|
+
const output = resultRaw !== void 0 ? safeParse(resultRaw) : void 0;
|
|
93
|
+
trace.endSpan(spanId, {
|
|
94
|
+
...output !== void 0 ? {
|
|
95
|
+
output: output && typeof output === "object" ? output : { value: output }
|
|
96
|
+
} : {},
|
|
97
|
+
latency_ms: latency,
|
|
98
|
+
...errored ? { level: "ERROR" } : {}
|
|
99
|
+
});
|
|
100
|
+
} else {
|
|
101
|
+
const text = extractTextFromAttrs(pendingAttrs);
|
|
102
|
+
const tokens = extractTokenUsage(pendingAttrs);
|
|
103
|
+
trace.endSpan(spanId, {
|
|
104
|
+
output: text !== void 0 ? { text } : {},
|
|
105
|
+
latency_ms: latency,
|
|
106
|
+
...tokens,
|
|
107
|
+
...errored ? { level: "ERROR", status_message: status.message } : {}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
isRecording() {
|
|
112
|
+
return !ended;
|
|
113
|
+
},
|
|
114
|
+
recordException(exception, _time) {
|
|
115
|
+
errored = true;
|
|
116
|
+
const msg = typeof exception === "string" ? exception : exception.message;
|
|
117
|
+
status = { code: 2, message: msg };
|
|
118
|
+
return span;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
return span;
|
|
122
|
+
}
|
|
123
|
+
function makeNoopSpan() {
|
|
124
|
+
const noop = {
|
|
125
|
+
spanContext: () => ({ traceId: "", spanId: nextSpanId(), traceFlags: 0 }),
|
|
126
|
+
setAttribute() {
|
|
127
|
+
return noop;
|
|
128
|
+
},
|
|
129
|
+
setAttributes() {
|
|
130
|
+
return noop;
|
|
131
|
+
},
|
|
132
|
+
addEvent() {
|
|
133
|
+
return noop;
|
|
134
|
+
},
|
|
135
|
+
addLink() {
|
|
136
|
+
return noop;
|
|
137
|
+
},
|
|
138
|
+
addLinks() {
|
|
139
|
+
return noop;
|
|
140
|
+
},
|
|
141
|
+
setStatus() {
|
|
142
|
+
return noop;
|
|
143
|
+
},
|
|
144
|
+
updateName() {
|
|
145
|
+
return noop;
|
|
146
|
+
},
|
|
147
|
+
end() {
|
|
148
|
+
},
|
|
149
|
+
isRecording: () => false,
|
|
150
|
+
recordException() {
|
|
151
|
+
return noop;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
return noop;
|
|
155
|
+
}
|
|
156
|
+
const tracer = {
|
|
157
|
+
startSpan(name, opts) {
|
|
158
|
+
const attrs = opts?.attributes ?? {};
|
|
159
|
+
const isToolCall = name === "ai.toolCall";
|
|
160
|
+
const isGeneration = name === "ai.generateText" || name === "ai.streamText";
|
|
161
|
+
if (!isToolCall && !isGeneration) return makeNoopSpan();
|
|
162
|
+
return makeLiveSpan(name, isToolCall ? "TOOL" : "GENERATION", attrs);
|
|
163
|
+
},
|
|
164
|
+
startActiveSpan(name, optionsOrFn, contextOrFn, maybeFn) {
|
|
165
|
+
let opts;
|
|
166
|
+
let fn;
|
|
167
|
+
if (typeof optionsOrFn === "function") {
|
|
168
|
+
opts = {};
|
|
169
|
+
fn = optionsOrFn;
|
|
170
|
+
} else if (typeof contextOrFn === "function") {
|
|
171
|
+
opts = optionsOrFn;
|
|
172
|
+
fn = contextOrFn;
|
|
173
|
+
} else {
|
|
174
|
+
opts = optionsOrFn;
|
|
175
|
+
fn = maybeFn;
|
|
176
|
+
}
|
|
177
|
+
const attrs = opts.attributes ?? {};
|
|
178
|
+
const isToolCall = name === "ai.toolCall";
|
|
179
|
+
const isGeneration = name === "ai.generateText" || name === "ai.streamText";
|
|
180
|
+
if (!isToolCall && !isGeneration) {
|
|
181
|
+
const noopSpan = makeNoopSpan();
|
|
182
|
+
return Promise.resolve(fn(noopSpan)).then((result) => {
|
|
183
|
+
noopSpan.end();
|
|
184
|
+
return result;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
const span = makeLiveSpan(
|
|
188
|
+
name,
|
|
189
|
+
isToolCall ? "TOOL" : "GENERATION",
|
|
190
|
+
attrs
|
|
191
|
+
);
|
|
192
|
+
return Promise.resolve(fn(span)).then((result) => {
|
|
193
|
+
span.end();
|
|
194
|
+
return result;
|
|
195
|
+
}).catch((error) => {
|
|
196
|
+
span.setStatus({
|
|
197
|
+
code: 2,
|
|
198
|
+
message: error instanceof Error ? error.message : String(error)
|
|
199
|
+
});
|
|
200
|
+
span.recordException({
|
|
201
|
+
name: error instanceof Error ? error.name : "Error",
|
|
202
|
+
message: error instanceof Error ? error.message : String(error),
|
|
203
|
+
...error instanceof Error && error.stack ? { stack: error.stack } : {}
|
|
204
|
+
});
|
|
205
|
+
span.end();
|
|
206
|
+
throw error;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
return { isEnabled: true, tracer };
|
|
211
|
+
}
|
|
212
|
+
function extractTextFromAttrs(attrs) {
|
|
213
|
+
const candidates = [
|
|
214
|
+
"ai.response.text",
|
|
215
|
+
"ai.response.output",
|
|
216
|
+
"ai.generateText.result"
|
|
217
|
+
];
|
|
218
|
+
for (const key of candidates) {
|
|
219
|
+
const val = attrs[key];
|
|
220
|
+
if (typeof val === "string" && val.length > 0) {
|
|
221
|
+
const parsed = safeParse(val);
|
|
222
|
+
if (typeof parsed === "string") return parsed;
|
|
223
|
+
return val;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return void 0;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export {
|
|
230
|
+
createApoTracer
|
|
231
|
+
};
|
|
232
|
+
//# sourceMappingURL=chunk-HKLBPIB4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/agent-task/integrations/ai-sdk.ts"],"sourcesContent":["/**\n * Vercel AI SDK tracing integration for apo.\n *\n * Pass the result of {@link createApoTracer} to `generateText`'s\n * `experimental_telemetry` option. The AI SDK already emits OpenTelemetry\n * spans natively (e.g. `ai.toolCall`, `ai.generateText`); this tracer\n * intercepts them and translates them into apo trace spans. The FlowTee\n * picks up TOOL/GENERATION observations and builds the Flow that\n * `t.calledTool`, `t.noFailedActions`, and `t.messageIncludes` read.\n *\n * The adapter author writes zero manual span code.\n *\n * @example\n * ```ts\n * import { generateText } from \"ai\";\n * import { createApoTracer } from \"@apo-ai/sdk/agent-task\";\n *\n * async sendUserTurn(turn, { trace, parentSpanId }) {\n * const result = await generateText({\n * model, system, messages, tools,\n * experimental_telemetry: createApoTracer({ trace, parentSpanId }),\n * });\n * return { response: result.text };\n * }\n * ```\n *\n * @module\n */\n\nimport type { AgentTaskTraceContext } from \"../tracing.ts\";\nimport { extractTokenUsage } from \"./token-usage.ts\";\n\n// ── OpenTelemetry-compatible types ───────────────────────────────────────\n// We implement the subset of the OTel `Tracer` and `Span` interfaces that the\n// Vercel AI SDK touches (verified from `ai@6` source: `recordSpan` uses\n// `startActiveSpan`; the AI SDK also calls `startSpan` on the default tracer\n// for some operations). We re-declare the structural shape here so the\n// `experimental_telemetry: { tracer }` option type-checks against the AI\n// SDK's `TelemetrySettings` without taking a runtime dep on `@opentelemetry/api`.\n\ntype OTelAttributeValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | Array<string | number | boolean | null | undefined>;\ntype OTelAttributes = Record<string, OTelAttributeValue>;\n\ninterface OTelSpanContext {\n traceId: string;\n spanId: string;\n traceFlags: number;\n}\n\ninterface OTelSpanStatus {\n code: number; // 0 = UNSET, 1 = OK, 2 = ERROR\n message?: string;\n}\n\ninterface OTelException {\n name: string;\n message: string;\n stack?: string;\n}\n\n/**\n * Minimal `Span` implementation. The AI SDK calls:\n * - `setAttributes` / `setAttribute` (tool result, response text)\n * - `setStatus` / `recordException` (errors)\n * - `end()` (finalises the span)\n *\n * Other methods (`addEvent`, `addLink`, `updateName`) are no-ops but must\n * exist on the object for structural type compatibility with the OTel `Span`\n * interface the AI SDK expects.\n */\ninterface ApoSpan {\n spanContext(): OTelSpanContext;\n setAttribute(key: string, value: OTelAttributeValue): this;\n setAttributes(attrs: OTelAttributes): this;\n addEvent(name: string, attributesOrStartTime?: unknown, startTime?: unknown): this;\n addLink(link: unknown): this;\n addLinks(links: unknown[]): this;\n setStatus(status: OTelSpanStatus): this;\n updateName(name: string): this;\n end(endTime?: unknown): void;\n isRecording(): boolean;\n recordException(exception: OTelException | string, time?: unknown): this;\n}\n\n/**\n * Minimal `Tracer` implementation. The AI SDK's `recordSpan` uses\n * `startActiveSpan(name, { attributes }, fn)`. We also implement\n * `startSpan(name, opts)` for structural compatibility — it returns a span\n * without invoking a callback, and the caller is responsible for `.end()`.\n */\ninterface ApoTracer {\n startSpan(name: string, options?: { attributes?: OTelAttributes }): ApoSpan;\n startActiveSpan<T>(\n name: string,\n options: { attributes?: OTelAttributes },\n fn: (span: ApoSpan) => Promise<T> | T,\n ): Promise<T>;\n startActiveSpan<T>(\n name: string,\n options: { attributes?: OTelAttributes },\n context: unknown,\n fn: (span: ApoSpan) => Promise<T> | T,\n ): Promise<T>;\n startActiveSpan<T>(\n name: string,\n fn: (span: ApoSpan) => Promise<T> | T,\n ): Promise<T>;\n}\n\n// ── helpers ──────────────────────────────────────────────────────────────\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\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\nconst SPAN_ID_PREFIX = \"ai-sdk-\";\nlet spanCounter = 0;\n\nfunction nextSpanId(): string {\n spanCounter += 1;\n return `${SPAN_ID_PREFIX}${spanCounter}`;\n}\n\n// ── createApoTracer ──────────────────────────────────────────────────────\n\nexport interface CreateApoTracerOptions {\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 * Build a `Tracer` compatible with the Vercel AI SDK's\n * `experimental_telemetry.tracer` option. The returned object satisfies the\n * shape `{ isEnabled: true, tracer }` so it can be passed directly to\n * `experimental_telemetry`.\n *\n * The AI SDK emits spans named `ai.generateText`, `ai.generateText.doGenerate`,\n * and `ai.toolCall`. We translate the load-bearing ones:\n *\n * - `ai.toolCall` → a TOOL span (so `t.calledTool(name)` works)\n * - `ai.generateText` → a GENERATION span (so `t.messageIncludes(text)` works)\n * and error status → `t.noFailedActions()` catches it\n *\n * Other spans (`ai.generateText.doGenerate`, etc.) are ignored — the FlowTee\n * only cares about TOOL/AGENT/GENERATION observation types.\n */\nexport function createApoTracer(\n options: CreateApoTracerOptions,\n): { isEnabled: true; tracer: ApoTracer } {\n const { trace, parentSpanId, taskId, turnNumber } = options;\n\n // The generation span id for the current turn. Once `ai.generateText`\n // creates it, subsequent `ai.toolCall` spans should nest under it (the\n // agent calls tools *during* generation), not under the turn span.\n // Without this, every tool renders as a sibling of `agent.generate`\n // instead of a child, flattening the trace hierarchy.\n let currentGenerationSpanId: string | undefined;\n\n /**\n * Build a live span that accumulates attributes and, on `.end()`,\n * calls `trace.createSpan` + `trace.endSpan` with the right observation type.\n */\n function makeLiveSpan(\n name: string,\n observationType: \"TOOL\" | \"GENERATION\",\n initialAttrs: OTelAttributes,\n ): ApoSpan {\n const stepName =\n observationType === \"TOOL\"\n ? String(initialAttrs[\"ai.toolCall.name\"] ?? \"unknown\")\n : \"agent.generate\";\n\n const input =\n observationType === \"TOOL\"\n ? (safeParse(initialAttrs[\"ai.toolCall.args\"]) as Record<string, unknown>)\n : undefined;\n\n const spanId = trace.createSpan({\n task_id: taskId ?? \"ai-sdk\",\n parent_call_id:\n observationType === \"TOOL\"\n ? (currentGenerationSpanId ?? parentSpanId ?? trace.rootSpanId)\n : (parentSpanId ?? trace.rootSpanId),\n step_name: stepName,\n model: String(initialAttrs[\"ai.model.id\"] ?? \"ai-sdk\"),\n observation_type: observationType,\n ...(input !== undefined ? { input } : {}),\n metadata: {\n ...(turnNumber !== undefined ? { turnNumber } : {}),\n ...(taskId ? { taskId } : {}),\n },\n });\n\n // Record the generation span id so tool spans created after it nest\n // under it (tools are called during generation).\n if (observationType === \"GENERATION\") {\n currentGenerationSpanId = spanId;\n }\n\n const startedAt = monotonicNowMs();\n let pendingAttrs: OTelAttributes = { ...initialAttrs };\n let status: OTelSpanStatus = { code: 0 };\n let errored = false;\n let ended = false;\n\n const span: ApoSpan = {\n spanContext(): OTelSpanContext {\n return { traceId: trace.runId, spanId, traceFlags: 0 };\n },\n setAttribute(key: string, value: OTelAttributeValue): ApoSpan {\n pendingAttrs[key] = value;\n return span;\n },\n setAttributes(attrs: OTelAttributes): ApoSpan {\n Object.assign(pendingAttrs, attrs);\n return span;\n },\n addEvent(_name: string, _attributesOrStartTime?: unknown, _startTime?: unknown): ApoSpan {\n return span; // no-op — events aren't load-bearing for Flow assertions\n },\n addLink(_link: unknown): ApoSpan {\n return span;\n },\n addLinks(_links: unknown[]): ApoSpan {\n return span;\n },\n setStatus(s: OTelSpanStatus): ApoSpan {\n status = s;\n if (s.code === 2) errored = true;\n return span;\n },\n updateName(_name: string): ApoSpan {\n return span; // name was set at creation time\n },\n end(_endTime?: unknown): void {\n if (ended) return;\n ended = true;\n const latency = round3(monotonicNowMs() - startedAt);\n\n if (observationType === \"TOOL\") {\n const resultRaw = pendingAttrs[\"ai.toolCall.result\"];\n const output = resultRaw !== undefined ? safeParse(resultRaw) : undefined;\n trace.endSpan(spanId, {\n ...(output !== undefined\n ? {\n output:\n output && typeof output === \"object\"\n ? (output as Record<string, unknown>)\n : { value: output },\n }\n : {}),\n latency_ms: latency,\n ...(errored ? { level: \"ERROR\" as const } : {}),\n });\n } else {\n const text = extractTextFromAttrs(pendingAttrs);\n const tokens = extractTokenUsage(pendingAttrs);\n trace.endSpan(spanId, {\n output: text !== undefined ? { text } : {},\n latency_ms: latency,\n ...tokens,\n ...(errored\n ? { level: \"ERROR\" as const, status_message: status.message }\n : {}),\n });\n }\n },\n isRecording(): boolean {\n return !ended;\n },\n recordException(exception: OTelException | string, _time?: unknown): ApoSpan {\n errored = true;\n const msg = typeof exception === \"string\" ? exception : exception.message;\n status = { code: 2, message: msg };\n return span;\n },\n };\n\n return span;\n }\n\n function makeNoopSpan(): ApoSpan {\n const noop: ApoSpan = {\n spanContext: () => ({ traceId: \"\", spanId: nextSpanId(), traceFlags: 0 }),\n setAttribute() {\n return noop;\n },\n setAttributes() {\n return noop;\n },\n addEvent() {\n return noop;\n },\n addLink() {\n return noop;\n },\n addLinks() {\n return noop;\n },\n setStatus() {\n return noop;\n },\n updateName() {\n return noop;\n },\n end() {},\n isRecording: () => false,\n recordException() {\n return noop;\n },\n };\n return noop;\n }\n\n const tracer: ApoTracer = {\n startSpan(name: string, opts?: { attributes?: OTelAttributes }): ApoSpan {\n const attrs = opts?.attributes ?? {};\n const isToolCall = name === \"ai.toolCall\";\n const isGeneration = name === \"ai.generateText\" || name === \"ai.streamText\";\n if (!isToolCall && !isGeneration) return makeNoopSpan();\n return makeLiveSpan(name, isToolCall ? \"TOOL\" : \"GENERATION\", attrs);\n },\n\n startActiveSpan<T>(\n name: string,\n optionsOrFn:\n | { attributes?: OTelAttributes }\n | ((span: ApoSpan) => Promise<T> | T),\n contextOrFn?: ((span: ApoSpan) => Promise<T> | T) | unknown,\n maybeFn?: (span: ApoSpan) => Promise<T> | T,\n ): Promise<T> {\n // Three overload shapes:\n // (name, fn) → optionsOrFn is the fn\n // (name, options, fn) → contextOrFn is the fn\n // (name, options, context, fn) → maybeFn is the fn (context ignored)\n let opts: { attributes?: OTelAttributes };\n let fn: (span: ApoSpan) => Promise<T> | T;\n\n if (typeof optionsOrFn === \"function\") {\n opts = {};\n fn = optionsOrFn;\n } else if (typeof contextOrFn === \"function\") {\n opts = optionsOrFn;\n fn = contextOrFn as (span: ApoSpan) => Promise<T> | T;\n } else {\n opts = optionsOrFn;\n fn = maybeFn!;\n }\n const attrs = opts.attributes ?? {};\n const isToolCall = name === \"ai.toolCall\";\n const isGeneration = name === \"ai.generateText\" || name === \"ai.streamText\";\n\n if (!isToolCall && !isGeneration) {\n const noopSpan = makeNoopSpan();\n return Promise.resolve(fn(noopSpan)).then((result) => {\n noopSpan.end();\n return result;\n });\n }\n\n const span = makeLiveSpan(\n name,\n isToolCall ? \"TOOL\" : \"GENERATION\",\n attrs,\n );\n\n return Promise.resolve(fn(span))\n .then((result) => {\n span.end();\n return result;\n })\n .catch((error) => {\n span.setStatus({\n code: 2,\n message: error instanceof Error ? error.message : String(error),\n });\n span.recordException({\n name: error instanceof Error ? error.name : \"Error\",\n message: error instanceof Error ? error.message : String(error),\n ...(error instanceof Error && error.stack ? { stack: error.stack } : {}),\n });\n span.end();\n throw error;\n });\n },\n };\n\n return { isEnabled: true, tracer };\n}\n\n// ── internals ────────────────────────────────────────────────────────────\n\n/**\n * The AI SDK sets the model's response text under various attribute keys\n * depending on the operation. Try the known ones.\n */\nfunction extractTextFromAttrs(attrs: OTelAttributes): string | undefined {\n const candidates = [\n \"ai.response.text\",\n \"ai.response.output\",\n \"ai.generateText.result\",\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 if (typeof parsed === \"string\") return parsed;\n return val;\n }\n }\n return undefined;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Auto-generated by `pnpm --filter @apo-ai/sdk build` (tsup.config.ts).
|
|
2
|
+
// Do not edit by hand — edit the src/ originals and rebuild.
|
|
3
|
+
|
|
4
|
+
// src/agent-task/integrations/token-usage.ts
|
|
5
|
+
function extractTokenUsage(attrs) {
|
|
6
|
+
const prompt_tokens = toNum(
|
|
7
|
+
attrs["gen_ai.usage.input_tokens"] ?? attrs["gen_ai.usage.prompt_tokens"] ?? attrs["ai.usage.promptTokens"]
|
|
8
|
+
);
|
|
9
|
+
const completion_tokens = toNum(
|
|
10
|
+
attrs["gen_ai.usage.output_tokens"] ?? attrs["gen_ai.usage.completion_tokens"] ?? attrs["ai.usage.completionTokens"]
|
|
11
|
+
);
|
|
12
|
+
const out = {};
|
|
13
|
+
if (prompt_tokens !== void 0) out.prompt_tokens = prompt_tokens;
|
|
14
|
+
if (completion_tokens !== void 0) out.completion_tokens = completion_tokens;
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
function toNum(val) {
|
|
18
|
+
if (typeof val === "number") return val;
|
|
19
|
+
if (typeof val === "string") {
|
|
20
|
+
const n = Number(val);
|
|
21
|
+
return Number.isFinite(n) ? n : void 0;
|
|
22
|
+
}
|
|
23
|
+
return void 0;
|
|
24
|
+
}
|
|
25
|
+
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
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
extractTokenUsage,
|
|
35
|
+
extractTokenCounts
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=chunk-HWZVL6TS.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` 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":[]}
|