@paid-ai/paid-node 1.7.0 → 1.7.1

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.
@@ -35,6 +35,23 @@ const ATTRIBUTE_MAP = {
35
35
  function isAISDKSpan(span) {
36
36
  const attrs = span.attributes;
37
37
  const name = span.name.toLowerCase();
38
+ // The AI SDK emits two spans per call: an outer wrapper (e.g. ai.generateObject)
39
+ // and an inner span that actually hits the provider (e.g. ai.generateObject.doGenerate).
40
+ // Both carry identical token usage attributes, so processing both double-counts costs.
41
+ //
42
+ // We can't use attribute presence to distinguish them — outer and inner spans share
43
+ // the same base attributes (ai.model.id, ai.model.provider), and response-model
44
+ // attributes are only set on LLM inner spans, not embedding inner spans (doEmbed).
45
+ //
46
+ // The AI SDK's naming convention for inner (provider-call) spans is to include a
47
+ // ".do" segment in the operationId (doGenerate, doStream, doEmbed). This is the
48
+ // SDK's structural contract for separating orchestration from provider calls, and
49
+ // is the only reliable discriminator that works across all operation types (LLM,
50
+ // embedding) and telemetry formats (modern GenAI semconv, legacy LegacyOpenTelemetry).
51
+ const operationId = attrs["ai.operationId"];
52
+ if (typeof operationId === "string" && !operationId.includes(".do")) {
53
+ return false;
54
+ }
38
55
  // Check for AI SDK specific attributes (most reliable)
39
56
  // Also check span name - account for PaidSpanProcessor prefix (paid.trace.ai.xxx)
40
57
  return !!(attrs["ai.model.id"] ||
@@ -32,6 +32,23 @@ const ATTRIBUTE_MAP = {
32
32
  function isAISDKSpan(span) {
33
33
  const attrs = span.attributes;
34
34
  const name = span.name.toLowerCase();
35
+ // The AI SDK emits two spans per call: an outer wrapper (e.g. ai.generateObject)
36
+ // and an inner span that actually hits the provider (e.g. ai.generateObject.doGenerate).
37
+ // Both carry identical token usage attributes, so processing both double-counts costs.
38
+ //
39
+ // We can't use attribute presence to distinguish them — outer and inner spans share
40
+ // the same base attributes (ai.model.id, ai.model.provider), and response-model
41
+ // attributes are only set on LLM inner spans, not embedding inner spans (doEmbed).
42
+ //
43
+ // The AI SDK's naming convention for inner (provider-call) spans is to include a
44
+ // ".do" segment in the operationId (doGenerate, doStream, doEmbed). This is the
45
+ // SDK's structural contract for separating orchestration from provider calls, and
46
+ // is the only reliable discriminator that works across all operation types (LLM,
47
+ // embedding) and telemetry formats (modern GenAI semconv, legacy LegacyOpenTelemetry).
48
+ const operationId = attrs["ai.operationId"];
49
+ if (typeof operationId === "string" && !operationId.includes(".do")) {
50
+ return false;
51
+ }
35
52
  // Check for AI SDK specific attributes (most reliable)
36
53
  // Also check span name - account for PaidSpanProcessor prefix (paid.trace.ai.xxx)
37
54
  return !!(attrs["ai.model.id"] ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paid-ai/paid-node",
3
- "version": "1.7.0",
3
+ "version": "1.7.1",
4
4
  "private": false,
5
5
  "repository": "github:paid-ai/paid-node",
6
6
  "type": "commonjs",