@tanstack/ai
Version:
Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.
50 lines (49 loc) • 2.38 kB
JavaScript
import { firstNumber } from "../utilities/numbers.js";
//#region src/middlewares/usage-attributes.ts
/**
* Build the full set of `gen_ai.usage.*` span attributes from a `TokenUsage`.
*
* Beyond input/output tokens, this emits provider-reported cost, total tokens,
* cache and reasoning breakdowns, duration-based billing, and media unit counts
* — every field is guarded so spans stay clean when a provider doesn't report
* it. Cache and reasoning use the official GenAI semconv names;
* `gen_ai.usage.cost` and `gen_ai.usage.total_tokens` are de-facto extensions
* consumed by backends like PostHog (which otherwise re-derive cost from their
* own price tables, losing cache discounts and gateway markup). Fields with no
* semconv or de-facto convention (`costDetails`, `durationSeconds`,
* `unitsBilled`) are TanStack-namespaced.
*
* Shared by `otelMiddleware` across every activity (chat and the media
* activities) so usage lands identically whichever activity produced the span.
*
* Deliberately not emitted: `providerUsageDetails` (a provider-shaped bag,
* unsafe to spread onto spans) and the per-modality token breakdowns
* (`promptTokensDetails.audioTokens`, etc.) — those can balloon the attribute
* set and have no agreed convention yet.
*/
function usageAttributes(usage) {
const attrs = {
"gen_ai.usage.input_tokens": usage.promptTokens,
"gen_ai.usage.output_tokens": usage.completionTokens
};
const optional = [
["gen_ai.usage.total_tokens", usage.totalTokens],
["gen_ai.usage.cost", usage.cost],
["gen_ai.usage.cache_read.input_tokens", usage.promptTokensDetails?.cachedTokens],
["gen_ai.usage.cache_creation.input_tokens", usage.promptTokensDetails?.cacheWriteTokens],
["gen_ai.usage.reasoning.output_tokens", usage.completionTokensDetails?.reasoningTokens],
["tanstack.ai.usage.duration_seconds", usage.durationSeconds],
["tanstack.ai.usage.units_billed", usage.unitsBilled],
["tanstack.ai.usage.upstream_cost", usage.costDetails?.upstreamCost],
["tanstack.ai.usage.upstream_input_cost", usage.costDetails?.upstreamInputCost],
["tanstack.ai.usage.upstream_output_cost", usage.costDetails?.upstreamOutputCost]
];
for (const [key, value] of optional) {
const num = firstNumber(value);
if (num !== void 0) attrs[key] = num;
}
return attrs;
}
//#endregion
export { usageAttributes };
//# sourceMappingURL=usage-attributes.js.map