@raindrop-ai/pi-agent 0.1.0 → 0.1.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.
@@ -1,4 +1,4 @@
1
- // ../core/dist/chunk-QTUH3BHZ.js
1
+ // ../core/dist/chunk-YUIXRQWN.js
2
2
  function getCrypto() {
3
3
  const c = globalThis.crypto;
4
4
  return c;
@@ -56,6 +56,20 @@ function base64Encode(bytes) {
56
56
  function generateId() {
57
57
  return randomUUID();
58
58
  }
59
+ var HANDOFF_ATTRIBUTE_SUFFIXES = {
60
+ mode: "raindrop.handoff.mode",
61
+ childEventId: "raindrop.handoff.childEventId",
62
+ parentEventId: "raindrop.handoff.parentEventId",
63
+ parentSpanId: "raindrop.handoff.parentSpanId",
64
+ name: "raindrop.handoff.name",
65
+ terminal: "raindrop.handoff.terminal",
66
+ agentRole: "raindrop.agent.role"
67
+ };
68
+ var HANDOFF_TERMINAL_PROPERTY_KEY = HANDOFF_ATTRIBUTE_SUFFIXES.terminal;
69
+ var TRACEPARENT_HEADER = "traceparent";
70
+ var BAGGAGE_HEADER = "baggage";
71
+ var HANDOFF_HEADER = "x-raindrop-handoff";
72
+ var UNRESOLVED_CARRIER_WARNING = `[raindrop] resume: headers were supplied but no hand-off carrier was found on them (searched ${HANDOFF_HEADER}, ${TRACEPARENT_HEADER}, ${BAGGAGE_HEADER}). This run reports as an UNLINKED event, and the launcher that dispatched it will stay on "queued" forever because nothing ever references its child. Passing headers means a parent handed off, so no carrier on them is almost certainly a defect: a gateway or proxy stripping ${HANDOFF_HEADER}, a middleware overwriting ${BAGGAGE_HEADER}, a request forwarded without its headers, or a hand-built carrier whose field names do not match. Send every header the launcher's dispatch returned. Logged once per process; pass no headers for a directly-invoked sub-agent to silence it.`;
59
73
  function runWithTracingSuppressed(fn) {
60
74
  const hook = globalThis.RAINDROP_SUPPRESS_TRACING;
61
75
  if (typeof hook !== "function") return fn();
@@ -774,6 +788,194 @@ var EventShipper = class {
774
788
  }
775
789
  }
776
790
  };
791
+ var MODEL_USAGE_ATTRIBUTES = {
792
+ providerName: "gen_ai.provider.name",
793
+ requestModel: "gen_ai.request.model",
794
+ responseModel: "gen_ai.response.model",
795
+ inputTokens: "gen_ai.usage.input_tokens",
796
+ outputTokens: "gen_ai.usage.output_tokens",
797
+ reasoningTokens: "gen_ai.usage.reasoning_tokens",
798
+ cacheReadInputTokens: "gen_ai.usage.cache_read_input_tokens",
799
+ cacheWriteInputTokens: "gen_ai.usage.cache_write_input_tokens",
800
+ nonCachedInputTokens: "raindrop.usage.input_tokens.non_cached",
801
+ nonReasoningOutputTokens: "raindrop.usage.output_tokens.non_reasoning",
802
+ totalOutputTokens: "raindrop.usage.output_tokens.total",
803
+ reasoningOutputTokens: "raindrop.usage.output_tokens.reasoning",
804
+ cacheReadTokens: "raindrop.usage.input_tokens.cache_read",
805
+ cacheWriteTokens: "raindrop.usage.input_tokens.cache_write"
806
+ };
807
+ var MODEL_PROVIDER_NAMES = [
808
+ ["google.vertex", "google-vertex"],
809
+ ["vertex.anthropic", "google-vertex"],
810
+ ["amazon-bedrock", "amazon-bedrock"],
811
+ ["bedrock", "amazon-bedrock"],
812
+ ["anthropic", "anthropic"],
813
+ ["openai", "openai"],
814
+ ["google", "google"],
815
+ ["azure", "azure"],
816
+ ["openrouter", "openrouter"]
817
+ ];
818
+ function canonicalModelProvider(provider) {
819
+ var _a;
820
+ const normalizedProvider = provider.trim().toLowerCase();
821
+ if (normalizedProvider === "gateway") return "vercel";
822
+ const match = MODEL_PROVIDER_NAMES.find(
823
+ ([family]) => normalizedProvider === family || normalizedProvider.startsWith(`${family}.`)
824
+ );
825
+ return (_a = match == null ? void 0 : match[1]) != null ? _a : provider;
826
+ }
827
+ function tokenCount(value) {
828
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
829
+ }
830
+ function exclusiveTokens({
831
+ explicit,
832
+ total,
833
+ parts,
834
+ semantics
835
+ }) {
836
+ if (explicit !== void 0) return explicit;
837
+ if (total === void 0) return void 0;
838
+ if (semantics === "exclusive") return total;
839
+ const observedParts = parts.filter((part) => part !== void 0 && part > 0);
840
+ if (semantics === void 0 && observedParts.length > 0) return void 0;
841
+ const exclusive = total - observedParts.reduce((sum, part) => sum + part, 0);
842
+ return exclusive >= 0 ? exclusive : void 0;
843
+ }
844
+ function buildModelUsageAttributes(usage) {
845
+ const inputTokens = tokenCount(usage.inputTokens);
846
+ const outputTokens = tokenCount(usage.outputTokens);
847
+ const explicitNonCachedInputTokens = tokenCount(usage.nonCachedInputTokens);
848
+ const explicitNonReasoningOutputTokens = tokenCount(usage.nonReasoningOutputTokens);
849
+ const reasoningTokens = tokenCount(usage.reasoningTokens);
850
+ const cacheReadInputTokens = tokenCount(usage.cacheReadInputTokens);
851
+ const cacheWriteInputTokens = tokenCount(usage.cacheWriteInputTokens);
852
+ const nonCachedInputTokens = exclusiveTokens({
853
+ explicit: explicitNonCachedInputTokens,
854
+ total: inputTokens,
855
+ parts: [cacheReadInputTokens, cacheWriteInputTokens],
856
+ semantics: usage.inputTokenSemantics
857
+ });
858
+ const nonReasoningOutputTokens = exclusiveTokens({
859
+ explicit: explicitNonReasoningOutputTokens,
860
+ total: outputTokens,
861
+ parts: [reasoningTokens],
862
+ semantics: usage.outputTokenSemantics
863
+ });
864
+ const exclusivePoolAttributes = nonCachedInputTokens !== void 0 && nonReasoningOutputTokens !== void 0 ? [
865
+ // backend consumes this complete raindrop.* set as one atomic billing-pool contract.
866
+ attrInt(MODEL_USAGE_ATTRIBUTES.nonCachedInputTokens, nonCachedInputTokens),
867
+ attrInt(MODEL_USAGE_ATTRIBUTES.nonReasoningOutputTokens, nonReasoningOutputTokens),
868
+ attrInt(MODEL_USAGE_ATTRIBUTES.reasoningOutputTokens, reasoningTokens),
869
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheReadTokens, cacheReadInputTokens),
870
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheWriteTokens, cacheWriteInputTokens)
871
+ ] : [];
872
+ return [
873
+ attrInt(MODEL_USAGE_ATTRIBUTES.inputTokens, inputTokens),
874
+ attrInt(MODEL_USAGE_ATTRIBUTES.outputTokens, outputTokens),
875
+ attrInt(MODEL_USAGE_ATTRIBUTES.reasoningTokens, reasoningTokens),
876
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheReadInputTokens, cacheReadInputTokens),
877
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheWriteInputTokens, cacheWriteInputTokens),
878
+ ...exclusivePoolAttributes
879
+ ].filter((attribute) => attribute !== void 0);
880
+ }
881
+ var STRING_ALIASES = {
882
+ [MODEL_USAGE_ATTRIBUTES.requestModel]: ["ai.model.id", "ai.model"]
883
+ };
884
+ var NUMBER_ALIASES = {
885
+ [MODEL_USAGE_ATTRIBUTES.inputTokens]: [
886
+ "gen_ai.usage.prompt_tokens",
887
+ "ai.usage.prompt_tokens",
888
+ "ai.usage.promptTokens",
889
+ "ai.usage.input_tokens",
890
+ "ai.usage.inputTokens"
891
+ ],
892
+ [MODEL_USAGE_ATTRIBUTES.outputTokens]: [
893
+ "gen_ai.usage.completion_tokens",
894
+ "ai.usage.completion_tokens",
895
+ "ai.usage.completionTokens",
896
+ "ai.usage.output_tokens",
897
+ "ai.usage.outputTokens"
898
+ ],
899
+ [MODEL_USAGE_ATTRIBUTES.reasoningTokens]: [
900
+ "gen_ai.usage.reasoning.output_tokens",
901
+ "ai.usage.reasoningTokens",
902
+ "ai.usage.thoughts_tokens"
903
+ ],
904
+ [MODEL_USAGE_ATTRIBUTES.cacheReadInputTokens]: [
905
+ "gen_ai.usage.cache_read_tokens",
906
+ "gen_ai.usage.cache_read.input_tokens",
907
+ "ai.usage.cachedInputTokens",
908
+ "ai.usage.cached_tokens",
909
+ "ai.usage.cache_read_tokens",
910
+ "ai.usage.cache_read_input_tokens",
911
+ "ai.usage.cacheReadInputTokens"
912
+ ],
913
+ [MODEL_USAGE_ATTRIBUTES.cacheWriteInputTokens]: [
914
+ "gen_ai.usage.cache_creation_input_tokens",
915
+ "gen_ai.usage.cache_creation.input_tokens",
916
+ "ai.usage.cacheWriteInputTokens",
917
+ "ai.usage.cache_creation_input_tokens",
918
+ "ai.usage.cacheCreationInputTokens"
919
+ ]
920
+ };
921
+ function hasAttribute(attributes, key) {
922
+ return attributes.some((attribute) => attribute.key === key);
923
+ }
924
+ function findStringAttribute(attributes, keys) {
925
+ return attributes.find(
926
+ (attribute) => keys.includes(attribute.key) && typeof attribute.value.stringValue === "string" && attribute.value.stringValue.length > 0
927
+ );
928
+ }
929
+ function findNumberAttribute(attributes, keys) {
930
+ return attributes.find((attribute) => {
931
+ if (!keys.includes(attribute.key)) return false;
932
+ if (typeof attribute.value.doubleValue === "number" && Number.isSafeInteger(attribute.value.doubleValue) && attribute.value.doubleValue >= 0) {
933
+ return true;
934
+ }
935
+ const intValue = attribute.value.intValue;
936
+ return integerValue(intValue) !== void 0;
937
+ });
938
+ }
939
+ function integerValue(value) {
940
+ if (typeof value === "number") {
941
+ return Number.isSafeInteger(value) && value >= 0 ? value : void 0;
942
+ }
943
+ if (typeof value !== "string") return void 0;
944
+ const parsed = Number(value);
945
+ return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : void 0;
946
+ }
947
+ function appendAlias(attributes, target, source) {
948
+ if (!source || hasAttribute(attributes, target)) return attributes;
949
+ return [...attributes, { key: target, value: { ...source.value } }];
950
+ }
951
+ function appendProviderAlias(attributes) {
952
+ if (hasAttribute(attributes, MODEL_USAGE_ATTRIBUTES.providerName)) return attributes;
953
+ const source = findStringAttribute(attributes, ["gen_ai.system", "ai.model.provider"]);
954
+ if (!(source == null ? void 0 : source.value.stringValue)) return attributes;
955
+ const attribute = attrString(
956
+ MODEL_USAGE_ATTRIBUTES.providerName,
957
+ canonicalModelProvider(source.value.stringValue)
958
+ );
959
+ return attribute ? [...attributes, attribute] : attributes;
960
+ }
961
+ function normalizeModelUsageSpan(span) {
962
+ const original = span.attributes;
963
+ if (!original || original.length === 0) return span;
964
+ const responseModel = findStringAttribute(original, [
965
+ MODEL_USAGE_ATTRIBUTES.responseModel,
966
+ "ai.response.model"
967
+ ]);
968
+ if (!responseModel) return span;
969
+ let attributes = appendAlias(original, MODEL_USAGE_ATTRIBUTES.responseModel, responseModel);
970
+ attributes = appendProviderAlias(attributes);
971
+ for (const [target, aliases] of Object.entries(STRING_ALIASES)) {
972
+ attributes = appendAlias(attributes, target, findStringAttribute(attributes, aliases));
973
+ }
974
+ for (const [target, aliases] of Object.entries(NUMBER_ALIASES)) {
975
+ attributes = appendAlias(attributes, target, findNumberAttribute(attributes, aliases));
976
+ }
977
+ return attributes === original ? span : { ...span, attributes };
978
+ }
777
979
  var DEFAULT_SECRET_KEY_NAMES = [
778
980
  "apikey",
779
981
  "apisecret",
@@ -974,6 +1176,10 @@ var TraceShipper = class {
974
1176
  return null;
975
1177
  }
976
1178
  }
1179
+ try {
1180
+ current = normalizeModelUsageSpan(current);
1181
+ } catch (e) {
1182
+ }
977
1183
  if (!this.disableDefaultRedaction) {
978
1184
  current = defaultTransformSpan(current);
979
1185
  }
@@ -1219,7 +1425,7 @@ installTracingSuppressionHook();
1219
1425
  // package.json
1220
1426
  var package_default = {
1221
1427
  name: "@raindrop-ai/pi-agent",
1222
- version: "0.1.0",
1428
+ version: "0.1.1",
1223
1429
  description: "Raindrop observability for Pi Agent \u2014 automatic tracing via subscriber or pi-coding-agent extension",
1224
1430
  type: "module",
1225
1431
  license: "MIT",
@@ -1374,9 +1580,22 @@ function extractTokenUsage(message) {
1374
1580
  return {
1375
1581
  input: usage.input,
1376
1582
  output: usage.output,
1377
- ...typeof usage.cacheRead === "number" && usage.cacheRead > 0 ? { cacheRead: usage.cacheRead } : {}
1583
+ cacheRead: typeof usage.cacheRead === "number" ? usage.cacheRead : 0,
1584
+ cacheWrite: typeof usage.cacheWrite === "number" ? usage.cacheWrite : 0
1378
1585
  };
1379
1586
  }
1587
+ function modelSpendSpanAttributes(input) {
1588
+ const providerAttr = input.provider ? attrString("gen_ai.provider.name", canonicalModelProvider(input.provider)) : void 0;
1589
+ const usageAttrs = input.usage ? buildModelUsageAttributes({
1590
+ inputTokens: input.usage.input,
1591
+ outputTokens: input.usage.output,
1592
+ cacheReadInputTokens: input.usage.cacheRead,
1593
+ cacheWriteInputTokens: input.usage.cacheWrite,
1594
+ reasoningTokens: 0,
1595
+ inputTokenSemantics: "exclusive"
1596
+ }) : [];
1597
+ return providerAttr ? [providerAttr, ...usageAttrs] : usageAttrs;
1598
+ }
1380
1599
  function extractAssistantText(message) {
1381
1600
  if (!("role" in message) || message.role !== "assistant") return void 0;
1382
1601
  if (!Array.isArray(message.content)) return void 0;
@@ -1530,6 +1749,7 @@ export {
1530
1749
  extractUserText,
1531
1750
  extractModelName,
1532
1751
  extractTokenUsage,
1752
+ modelSpendSpanAttributes,
1533
1753
  extractAssistantText,
1534
1754
  extractToolCallIds,
1535
1755
  formatToolSpanName,
@@ -25,7 +25,7 @@ __export(extension_exports, {
25
25
  });
26
26
  module.exports = __toCommonJS(extension_exports);
27
27
 
28
- // ../core/dist/chunk-QTUH3BHZ.js
28
+ // ../core/dist/chunk-YUIXRQWN.js
29
29
  function getCrypto() {
30
30
  const c = globalThis.crypto;
31
31
  return c;
@@ -83,6 +83,20 @@ function base64Encode(bytes) {
83
83
  function generateId() {
84
84
  return randomUUID();
85
85
  }
86
+ var HANDOFF_ATTRIBUTE_SUFFIXES = {
87
+ mode: "raindrop.handoff.mode",
88
+ childEventId: "raindrop.handoff.childEventId",
89
+ parentEventId: "raindrop.handoff.parentEventId",
90
+ parentSpanId: "raindrop.handoff.parentSpanId",
91
+ name: "raindrop.handoff.name",
92
+ terminal: "raindrop.handoff.terminal",
93
+ agentRole: "raindrop.agent.role"
94
+ };
95
+ var HANDOFF_TERMINAL_PROPERTY_KEY = HANDOFF_ATTRIBUTE_SUFFIXES.terminal;
96
+ var TRACEPARENT_HEADER = "traceparent";
97
+ var BAGGAGE_HEADER = "baggage";
98
+ var HANDOFF_HEADER = "x-raindrop-handoff";
99
+ var UNRESOLVED_CARRIER_WARNING = `[raindrop] resume: headers were supplied but no hand-off carrier was found on them (searched ${HANDOFF_HEADER}, ${TRACEPARENT_HEADER}, ${BAGGAGE_HEADER}). This run reports as an UNLINKED event, and the launcher that dispatched it will stay on "queued" forever because nothing ever references its child. Passing headers means a parent handed off, so no carrier on them is almost certainly a defect: a gateway or proxy stripping ${HANDOFF_HEADER}, a middleware overwriting ${BAGGAGE_HEADER}, a request forwarded without its headers, or a hand-built carrier whose field names do not match. Send every header the launcher's dispatch returned. Logged once per process; pass no headers for a directly-invoked sub-agent to silence it.`;
86
100
  function runWithTracingSuppressed(fn) {
87
101
  const hook = globalThis.RAINDROP_SUPPRESS_TRACING;
88
102
  if (typeof hook !== "function") return fn();
@@ -801,6 +815,194 @@ var EventShipper = class {
801
815
  }
802
816
  }
803
817
  };
818
+ var MODEL_USAGE_ATTRIBUTES = {
819
+ providerName: "gen_ai.provider.name",
820
+ requestModel: "gen_ai.request.model",
821
+ responseModel: "gen_ai.response.model",
822
+ inputTokens: "gen_ai.usage.input_tokens",
823
+ outputTokens: "gen_ai.usage.output_tokens",
824
+ reasoningTokens: "gen_ai.usage.reasoning_tokens",
825
+ cacheReadInputTokens: "gen_ai.usage.cache_read_input_tokens",
826
+ cacheWriteInputTokens: "gen_ai.usage.cache_write_input_tokens",
827
+ nonCachedInputTokens: "raindrop.usage.input_tokens.non_cached",
828
+ nonReasoningOutputTokens: "raindrop.usage.output_tokens.non_reasoning",
829
+ totalOutputTokens: "raindrop.usage.output_tokens.total",
830
+ reasoningOutputTokens: "raindrop.usage.output_tokens.reasoning",
831
+ cacheReadTokens: "raindrop.usage.input_tokens.cache_read",
832
+ cacheWriteTokens: "raindrop.usage.input_tokens.cache_write"
833
+ };
834
+ var MODEL_PROVIDER_NAMES = [
835
+ ["google.vertex", "google-vertex"],
836
+ ["vertex.anthropic", "google-vertex"],
837
+ ["amazon-bedrock", "amazon-bedrock"],
838
+ ["bedrock", "amazon-bedrock"],
839
+ ["anthropic", "anthropic"],
840
+ ["openai", "openai"],
841
+ ["google", "google"],
842
+ ["azure", "azure"],
843
+ ["openrouter", "openrouter"]
844
+ ];
845
+ function canonicalModelProvider(provider) {
846
+ var _a;
847
+ const normalizedProvider = provider.trim().toLowerCase();
848
+ if (normalizedProvider === "gateway") return "vercel";
849
+ const match = MODEL_PROVIDER_NAMES.find(
850
+ ([family]) => normalizedProvider === family || normalizedProvider.startsWith(`${family}.`)
851
+ );
852
+ return (_a = match == null ? void 0 : match[1]) != null ? _a : provider;
853
+ }
854
+ function tokenCount(value) {
855
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
856
+ }
857
+ function exclusiveTokens({
858
+ explicit,
859
+ total,
860
+ parts,
861
+ semantics
862
+ }) {
863
+ if (explicit !== void 0) return explicit;
864
+ if (total === void 0) return void 0;
865
+ if (semantics === "exclusive") return total;
866
+ const observedParts = parts.filter((part) => part !== void 0 && part > 0);
867
+ if (semantics === void 0 && observedParts.length > 0) return void 0;
868
+ const exclusive = total - observedParts.reduce((sum, part) => sum + part, 0);
869
+ return exclusive >= 0 ? exclusive : void 0;
870
+ }
871
+ function buildModelUsageAttributes(usage) {
872
+ const inputTokens = tokenCount(usage.inputTokens);
873
+ const outputTokens = tokenCount(usage.outputTokens);
874
+ const explicitNonCachedInputTokens = tokenCount(usage.nonCachedInputTokens);
875
+ const explicitNonReasoningOutputTokens = tokenCount(usage.nonReasoningOutputTokens);
876
+ const reasoningTokens = tokenCount(usage.reasoningTokens);
877
+ const cacheReadInputTokens = tokenCount(usage.cacheReadInputTokens);
878
+ const cacheWriteInputTokens = tokenCount(usage.cacheWriteInputTokens);
879
+ const nonCachedInputTokens = exclusiveTokens({
880
+ explicit: explicitNonCachedInputTokens,
881
+ total: inputTokens,
882
+ parts: [cacheReadInputTokens, cacheWriteInputTokens],
883
+ semantics: usage.inputTokenSemantics
884
+ });
885
+ const nonReasoningOutputTokens = exclusiveTokens({
886
+ explicit: explicitNonReasoningOutputTokens,
887
+ total: outputTokens,
888
+ parts: [reasoningTokens],
889
+ semantics: usage.outputTokenSemantics
890
+ });
891
+ const exclusivePoolAttributes = nonCachedInputTokens !== void 0 && nonReasoningOutputTokens !== void 0 ? [
892
+ // backend consumes this complete raindrop.* set as one atomic billing-pool contract.
893
+ attrInt(MODEL_USAGE_ATTRIBUTES.nonCachedInputTokens, nonCachedInputTokens),
894
+ attrInt(MODEL_USAGE_ATTRIBUTES.nonReasoningOutputTokens, nonReasoningOutputTokens),
895
+ attrInt(MODEL_USAGE_ATTRIBUTES.reasoningOutputTokens, reasoningTokens),
896
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheReadTokens, cacheReadInputTokens),
897
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheWriteTokens, cacheWriteInputTokens)
898
+ ] : [];
899
+ return [
900
+ attrInt(MODEL_USAGE_ATTRIBUTES.inputTokens, inputTokens),
901
+ attrInt(MODEL_USAGE_ATTRIBUTES.outputTokens, outputTokens),
902
+ attrInt(MODEL_USAGE_ATTRIBUTES.reasoningTokens, reasoningTokens),
903
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheReadInputTokens, cacheReadInputTokens),
904
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheWriteInputTokens, cacheWriteInputTokens),
905
+ ...exclusivePoolAttributes
906
+ ].filter((attribute) => attribute !== void 0);
907
+ }
908
+ var STRING_ALIASES = {
909
+ [MODEL_USAGE_ATTRIBUTES.requestModel]: ["ai.model.id", "ai.model"]
910
+ };
911
+ var NUMBER_ALIASES = {
912
+ [MODEL_USAGE_ATTRIBUTES.inputTokens]: [
913
+ "gen_ai.usage.prompt_tokens",
914
+ "ai.usage.prompt_tokens",
915
+ "ai.usage.promptTokens",
916
+ "ai.usage.input_tokens",
917
+ "ai.usage.inputTokens"
918
+ ],
919
+ [MODEL_USAGE_ATTRIBUTES.outputTokens]: [
920
+ "gen_ai.usage.completion_tokens",
921
+ "ai.usage.completion_tokens",
922
+ "ai.usage.completionTokens",
923
+ "ai.usage.output_tokens",
924
+ "ai.usage.outputTokens"
925
+ ],
926
+ [MODEL_USAGE_ATTRIBUTES.reasoningTokens]: [
927
+ "gen_ai.usage.reasoning.output_tokens",
928
+ "ai.usage.reasoningTokens",
929
+ "ai.usage.thoughts_tokens"
930
+ ],
931
+ [MODEL_USAGE_ATTRIBUTES.cacheReadInputTokens]: [
932
+ "gen_ai.usage.cache_read_tokens",
933
+ "gen_ai.usage.cache_read.input_tokens",
934
+ "ai.usage.cachedInputTokens",
935
+ "ai.usage.cached_tokens",
936
+ "ai.usage.cache_read_tokens",
937
+ "ai.usage.cache_read_input_tokens",
938
+ "ai.usage.cacheReadInputTokens"
939
+ ],
940
+ [MODEL_USAGE_ATTRIBUTES.cacheWriteInputTokens]: [
941
+ "gen_ai.usage.cache_creation_input_tokens",
942
+ "gen_ai.usage.cache_creation.input_tokens",
943
+ "ai.usage.cacheWriteInputTokens",
944
+ "ai.usage.cache_creation_input_tokens",
945
+ "ai.usage.cacheCreationInputTokens"
946
+ ]
947
+ };
948
+ function hasAttribute(attributes, key) {
949
+ return attributes.some((attribute) => attribute.key === key);
950
+ }
951
+ function findStringAttribute(attributes, keys) {
952
+ return attributes.find(
953
+ (attribute) => keys.includes(attribute.key) && typeof attribute.value.stringValue === "string" && attribute.value.stringValue.length > 0
954
+ );
955
+ }
956
+ function findNumberAttribute(attributes, keys) {
957
+ return attributes.find((attribute) => {
958
+ if (!keys.includes(attribute.key)) return false;
959
+ if (typeof attribute.value.doubleValue === "number" && Number.isSafeInteger(attribute.value.doubleValue) && attribute.value.doubleValue >= 0) {
960
+ return true;
961
+ }
962
+ const intValue = attribute.value.intValue;
963
+ return integerValue(intValue) !== void 0;
964
+ });
965
+ }
966
+ function integerValue(value) {
967
+ if (typeof value === "number") {
968
+ return Number.isSafeInteger(value) && value >= 0 ? value : void 0;
969
+ }
970
+ if (typeof value !== "string") return void 0;
971
+ const parsed = Number(value);
972
+ return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : void 0;
973
+ }
974
+ function appendAlias(attributes, target, source) {
975
+ if (!source || hasAttribute(attributes, target)) return attributes;
976
+ return [...attributes, { key: target, value: { ...source.value } }];
977
+ }
978
+ function appendProviderAlias(attributes) {
979
+ if (hasAttribute(attributes, MODEL_USAGE_ATTRIBUTES.providerName)) return attributes;
980
+ const source = findStringAttribute(attributes, ["gen_ai.system", "ai.model.provider"]);
981
+ if (!(source == null ? void 0 : source.value.stringValue)) return attributes;
982
+ const attribute = attrString(
983
+ MODEL_USAGE_ATTRIBUTES.providerName,
984
+ canonicalModelProvider(source.value.stringValue)
985
+ );
986
+ return attribute ? [...attributes, attribute] : attributes;
987
+ }
988
+ function normalizeModelUsageSpan(span) {
989
+ const original = span.attributes;
990
+ if (!original || original.length === 0) return span;
991
+ const responseModel = findStringAttribute(original, [
992
+ MODEL_USAGE_ATTRIBUTES.responseModel,
993
+ "ai.response.model"
994
+ ]);
995
+ if (!responseModel) return span;
996
+ let attributes = appendAlias(original, MODEL_USAGE_ATTRIBUTES.responseModel, responseModel);
997
+ attributes = appendProviderAlias(attributes);
998
+ for (const [target, aliases] of Object.entries(STRING_ALIASES)) {
999
+ attributes = appendAlias(attributes, target, findStringAttribute(attributes, aliases));
1000
+ }
1001
+ for (const [target, aliases] of Object.entries(NUMBER_ALIASES)) {
1002
+ attributes = appendAlias(attributes, target, findNumberAttribute(attributes, aliases));
1003
+ }
1004
+ return attributes === original ? span : { ...span, attributes };
1005
+ }
804
1006
  var DEFAULT_SECRET_KEY_NAMES = [
805
1007
  "apikey",
806
1008
  "apisecret",
@@ -1001,6 +1203,10 @@ var TraceShipper = class {
1001
1203
  return null;
1002
1204
  }
1003
1205
  }
1206
+ try {
1207
+ current = normalizeModelUsageSpan(current);
1208
+ } catch (e) {
1209
+ }
1004
1210
  if (!this.disableDefaultRedaction) {
1005
1211
  current = defaultTransformSpan(current);
1006
1212
  }
@@ -1301,7 +1507,7 @@ function resolveLocalWorkshopUrl(fileValue) {
1301
1507
  // package.json
1302
1508
  var package_default = {
1303
1509
  name: "@raindrop-ai/pi-agent",
1304
- version: "0.1.0",
1510
+ version: "0.1.1",
1305
1511
  description: "Raindrop observability for Pi Agent \u2014 automatic tracing via subscriber or pi-coding-agent extension",
1306
1512
  type: "module",
1307
1513
  license: "MIT",
@@ -1427,6 +1633,18 @@ var TraceShipper2 = class extends TraceShipper {
1427
1633
 
1428
1634
  // src/internal/helpers.ts
1429
1635
  var import_node_os2 = require("os");
1636
+ function modelSpendSpanAttributes(input) {
1637
+ const providerAttr = input.provider ? attrString("gen_ai.provider.name", canonicalModelProvider(input.provider)) : void 0;
1638
+ const usageAttrs = input.usage ? buildModelUsageAttributes({
1639
+ inputTokens: input.usage.input,
1640
+ outputTokens: input.usage.output,
1641
+ cacheReadInputTokens: input.usage.cacheRead,
1642
+ cacheWriteInputTokens: input.usage.cacheWrite,
1643
+ reasoningTokens: 0,
1644
+ inputTokenSemantics: "exclusive"
1645
+ }) : [];
1646
+ return providerAttr ? [providerAttr, ...usageAttrs] : usageAttrs;
1647
+ }
1430
1648
  function extractAssistantText(message) {
1431
1649
  if (!("role" in message) || message.role !== "assistant") return void 0;
1432
1650
  if (!Array.isArray(message.content)) return void 0;
@@ -1755,7 +1973,7 @@ function registerTracing(pi, config, eventShipper, traceShipper) {
1755
1973
  }
1756
1974
  });
1757
1975
  pi.on("message_end", async (event, ctx) => {
1758
- var _a, _b, _c, _d, _e, _f, _g, _h;
1976
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
1759
1977
  try {
1760
1978
  if (((_a = event.message) == null ? void 0 : _a.role) !== "assistant") return;
1761
1979
  const state = getState(stateRef, ctx);
@@ -1769,19 +1987,26 @@ function registerTracing(pi, config, eventShipper, traceShipper) {
1769
1987
  const outputText = capText2(getAssistantText(message));
1770
1988
  const inputTokens = (_d = message.usage) == null ? void 0 : _d.input;
1771
1989
  const outputTokens = (_e = message.usage) == null ? void 0 : _e.output;
1772
- const cacheReadTokens = (_f = message.usage) == null ? void 0 : _f.cacheRead;
1990
+ const cacheReadTokens = typeof ((_f = message.usage) == null ? void 0 : _f.cacheRead) === "number" ? message.usage.cacheRead : 0;
1991
+ const cacheWriteTokens = typeof ((_g = message.usage) == null ? void 0 : _g.cacheWrite) === "number" ? message.usage.cacheWrite : 0;
1773
1992
  if (typeof inputTokens === "number") state.totalInputTokens += inputTokens;
1774
1993
  if (typeof outputTokens === "number") state.totalOutputTokens += outputTokens;
1775
- if (typeof cacheReadTokens === "number" && cacheReadTokens > 0) state.totalCacheReadTokens += cacheReadTokens;
1776
- const turnParent = (_g = state.currentTurnSpan) != null ? _g : state.currentRootSpan;
1994
+ if (cacheReadTokens > 0) state.totalCacheReadTokens += cacheReadTokens;
1995
+ const turnParent = (_h = state.currentTurnSpan) != null ? _h : state.currentRootSpan;
1777
1996
  const llmAttrs = [
1778
1997
  attrString("ai.operationId", "generateText"),
1779
1998
  provider ? attrString("gen_ai.system", provider) : void 0,
1780
1999
  modelId ? attrString("gen_ai.request.model", modelId) : void 0,
1781
2000
  modelId ? attrString("gen_ai.response.model", modelId) : void 0,
1782
- typeof inputTokens === "number" ? attrInt("gen_ai.usage.input_tokens", inputTokens) : void 0,
1783
- typeof outputTokens === "number" ? attrInt("gen_ai.usage.output_tokens", outputTokens) : void 0,
1784
- typeof cacheReadTokens === "number" && cacheReadTokens > 0 ? attrInt("gen_ai.usage.cache_read_tokens", cacheReadTokens) : void 0,
2001
+ ...modelSpendSpanAttributes({
2002
+ provider: provider || void 0,
2003
+ usage: typeof inputTokens === "number" && typeof outputTokens === "number" ? {
2004
+ input: inputTokens,
2005
+ output: outputTokens,
2006
+ cacheRead: cacheReadTokens,
2007
+ cacheWrite: cacheWriteTokens
2008
+ } : void 0
2009
+ }),
1785
2010
  outputText ? attrString("ai.response.text", truncate(outputText)) : void 0,
1786
2011
  state.currentInput ? attrString("ai.prompt", truncate(state.currentInput)) : void 0,
1787
2012
  message.stopReason ? attrString("ai.stop_reason", message.stopReason) : void 0,
@@ -1819,7 +2044,7 @@ function registerTracing(pi, config, eventShipper, traceShipper) {
1819
2044
  ...errorForSpan ? { error: errorForSpan } : {}
1820
2045
  });
1821
2046
  state.currentRootSpan = void 0;
1822
- await eventShipper.finish((_h = state.currentEventRequestId) != null ? _h : state.currentEventId, {
2047
+ await eventShipper.finish((_i = state.currentEventRequestId) != null ? _i : state.currentEventId, {
1823
2048
  userId: getUserId(state, config.eventMetadata),
1824
2049
  ...modelId ? { model: modelId } : {},
1825
2050
  ...!errorForSpan && outputText.trim() ? { output: outputText } : {},
package/dist/extension.js CHANGED
@@ -10,11 +10,12 @@ import {
10
10
  getHostname,
11
11
  getUsername,
12
12
  libraryVersion,
13
+ modelSpendSpanAttributes,
13
14
  nowUnixNanoString,
14
15
  resolveLocalDebuggerBaseUrl,
15
16
  safeStringify,
16
17
  truncate
17
- } from "./chunk-XQJ6G4YA.js";
18
+ } from "./chunk-GTKXT2J5.js";
18
19
 
19
20
  // src/internal/config.ts
20
21
  import { existsSync, readFileSync } from "fs";
@@ -270,7 +271,7 @@ function registerTracing(pi, config, eventShipper, traceShipper) {
270
271
  }
271
272
  });
272
273
  pi.on("message_end", async (event, ctx) => {
273
- var _a, _b, _c, _d, _e, _f, _g, _h;
274
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
274
275
  try {
275
276
  if (((_a = event.message) == null ? void 0 : _a.role) !== "assistant") return;
276
277
  const state = getState(stateRef, ctx);
@@ -284,19 +285,26 @@ function registerTracing(pi, config, eventShipper, traceShipper) {
284
285
  const outputText = capText(getAssistantText(message));
285
286
  const inputTokens = (_d = message.usage) == null ? void 0 : _d.input;
286
287
  const outputTokens = (_e = message.usage) == null ? void 0 : _e.output;
287
- const cacheReadTokens = (_f = message.usage) == null ? void 0 : _f.cacheRead;
288
+ const cacheReadTokens = typeof ((_f = message.usage) == null ? void 0 : _f.cacheRead) === "number" ? message.usage.cacheRead : 0;
289
+ const cacheWriteTokens = typeof ((_g = message.usage) == null ? void 0 : _g.cacheWrite) === "number" ? message.usage.cacheWrite : 0;
288
290
  if (typeof inputTokens === "number") state.totalInputTokens += inputTokens;
289
291
  if (typeof outputTokens === "number") state.totalOutputTokens += outputTokens;
290
- if (typeof cacheReadTokens === "number" && cacheReadTokens > 0) state.totalCacheReadTokens += cacheReadTokens;
291
- const turnParent = (_g = state.currentTurnSpan) != null ? _g : state.currentRootSpan;
292
+ if (cacheReadTokens > 0) state.totalCacheReadTokens += cacheReadTokens;
293
+ const turnParent = (_h = state.currentTurnSpan) != null ? _h : state.currentRootSpan;
292
294
  const llmAttrs = [
293
295
  attrString("ai.operationId", "generateText"),
294
296
  provider ? attrString("gen_ai.system", provider) : void 0,
295
297
  modelId ? attrString("gen_ai.request.model", modelId) : void 0,
296
298
  modelId ? attrString("gen_ai.response.model", modelId) : void 0,
297
- typeof inputTokens === "number" ? attrInt("gen_ai.usage.input_tokens", inputTokens) : void 0,
298
- typeof outputTokens === "number" ? attrInt("gen_ai.usage.output_tokens", outputTokens) : void 0,
299
- typeof cacheReadTokens === "number" && cacheReadTokens > 0 ? attrInt("gen_ai.usage.cache_read_tokens", cacheReadTokens) : void 0,
299
+ ...modelSpendSpanAttributes({
300
+ provider: provider || void 0,
301
+ usage: typeof inputTokens === "number" && typeof outputTokens === "number" ? {
302
+ input: inputTokens,
303
+ output: outputTokens,
304
+ cacheRead: cacheReadTokens,
305
+ cacheWrite: cacheWriteTokens
306
+ } : void 0
307
+ }),
300
308
  outputText ? attrString("ai.response.text", truncate(outputText)) : void 0,
301
309
  state.currentInput ? attrString("ai.prompt", truncate(state.currentInput)) : void 0,
302
310
  message.stopReason ? attrString("ai.stop_reason", message.stopReason) : void 0,
@@ -334,7 +342,7 @@ function registerTracing(pi, config, eventShipper, traceShipper) {
334
342
  ...errorForSpan ? { error: errorForSpan } : {}
335
343
  });
336
344
  state.currentRootSpan = void 0;
337
- await eventShipper.finish((_h = state.currentEventRequestId) != null ? _h : state.currentEventId, {
345
+ await eventShipper.finish((_i = state.currentEventRequestId) != null ? _i : state.currentEventId, {
338
346
  userId: getUserId(state, config.eventMetadata),
339
347
  ...modelId ? { model: modelId } : {},
340
348
  ...!errorForSpan && outputText.trim() ? { output: outputText } : {},
package/dist/index.cjs CHANGED
@@ -24,7 +24,7 @@ __export(index_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(index_exports);
26
26
 
27
- // ../core/dist/chunk-QTUH3BHZ.js
27
+ // ../core/dist/chunk-YUIXRQWN.js
28
28
  function getCrypto() {
29
29
  const c = globalThis.crypto;
30
30
  return c;
@@ -79,6 +79,20 @@ function base64Encode(bytes) {
79
79
  }
80
80
  return out;
81
81
  }
82
+ var HANDOFF_ATTRIBUTE_SUFFIXES = {
83
+ mode: "raindrop.handoff.mode",
84
+ childEventId: "raindrop.handoff.childEventId",
85
+ parentEventId: "raindrop.handoff.parentEventId",
86
+ parentSpanId: "raindrop.handoff.parentSpanId",
87
+ name: "raindrop.handoff.name",
88
+ terminal: "raindrop.handoff.terminal",
89
+ agentRole: "raindrop.agent.role"
90
+ };
91
+ var HANDOFF_TERMINAL_PROPERTY_KEY = HANDOFF_ATTRIBUTE_SUFFIXES.terminal;
92
+ var TRACEPARENT_HEADER = "traceparent";
93
+ var BAGGAGE_HEADER = "baggage";
94
+ var HANDOFF_HEADER = "x-raindrop-handoff";
95
+ var UNRESOLVED_CARRIER_WARNING = `[raindrop] resume: headers were supplied but no hand-off carrier was found on them (searched ${HANDOFF_HEADER}, ${TRACEPARENT_HEADER}, ${BAGGAGE_HEADER}). This run reports as an UNLINKED event, and the launcher that dispatched it will stay on "queued" forever because nothing ever references its child. Passing headers means a parent handed off, so no carrier on them is almost certainly a defect: a gateway or proxy stripping ${HANDOFF_HEADER}, a middleware overwriting ${BAGGAGE_HEADER}, a request forwarded without its headers, or a hand-built carrier whose field names do not match. Send every header the launcher's dispatch returned. Logged once per process; pass no headers for a directly-invoked sub-agent to silence it.`;
82
96
  function runWithTracingSuppressed(fn) {
83
97
  const hook = globalThis.RAINDROP_SUPPRESS_TRACING;
84
98
  if (typeof hook !== "function") return fn();
@@ -797,6 +811,194 @@ var EventShipper = class {
797
811
  }
798
812
  }
799
813
  };
814
+ var MODEL_USAGE_ATTRIBUTES = {
815
+ providerName: "gen_ai.provider.name",
816
+ requestModel: "gen_ai.request.model",
817
+ responseModel: "gen_ai.response.model",
818
+ inputTokens: "gen_ai.usage.input_tokens",
819
+ outputTokens: "gen_ai.usage.output_tokens",
820
+ reasoningTokens: "gen_ai.usage.reasoning_tokens",
821
+ cacheReadInputTokens: "gen_ai.usage.cache_read_input_tokens",
822
+ cacheWriteInputTokens: "gen_ai.usage.cache_write_input_tokens",
823
+ nonCachedInputTokens: "raindrop.usage.input_tokens.non_cached",
824
+ nonReasoningOutputTokens: "raindrop.usage.output_tokens.non_reasoning",
825
+ totalOutputTokens: "raindrop.usage.output_tokens.total",
826
+ reasoningOutputTokens: "raindrop.usage.output_tokens.reasoning",
827
+ cacheReadTokens: "raindrop.usage.input_tokens.cache_read",
828
+ cacheWriteTokens: "raindrop.usage.input_tokens.cache_write"
829
+ };
830
+ var MODEL_PROVIDER_NAMES = [
831
+ ["google.vertex", "google-vertex"],
832
+ ["vertex.anthropic", "google-vertex"],
833
+ ["amazon-bedrock", "amazon-bedrock"],
834
+ ["bedrock", "amazon-bedrock"],
835
+ ["anthropic", "anthropic"],
836
+ ["openai", "openai"],
837
+ ["google", "google"],
838
+ ["azure", "azure"],
839
+ ["openrouter", "openrouter"]
840
+ ];
841
+ function canonicalModelProvider(provider) {
842
+ var _a;
843
+ const normalizedProvider = provider.trim().toLowerCase();
844
+ if (normalizedProvider === "gateway") return "vercel";
845
+ const match = MODEL_PROVIDER_NAMES.find(
846
+ ([family]) => normalizedProvider === family || normalizedProvider.startsWith(`${family}.`)
847
+ );
848
+ return (_a = match == null ? void 0 : match[1]) != null ? _a : provider;
849
+ }
850
+ function tokenCount(value) {
851
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
852
+ }
853
+ function exclusiveTokens({
854
+ explicit,
855
+ total,
856
+ parts,
857
+ semantics
858
+ }) {
859
+ if (explicit !== void 0) return explicit;
860
+ if (total === void 0) return void 0;
861
+ if (semantics === "exclusive") return total;
862
+ const observedParts = parts.filter((part) => part !== void 0 && part > 0);
863
+ if (semantics === void 0 && observedParts.length > 0) return void 0;
864
+ const exclusive = total - observedParts.reduce((sum, part) => sum + part, 0);
865
+ return exclusive >= 0 ? exclusive : void 0;
866
+ }
867
+ function buildModelUsageAttributes(usage) {
868
+ const inputTokens = tokenCount(usage.inputTokens);
869
+ const outputTokens = tokenCount(usage.outputTokens);
870
+ const explicitNonCachedInputTokens = tokenCount(usage.nonCachedInputTokens);
871
+ const explicitNonReasoningOutputTokens = tokenCount(usage.nonReasoningOutputTokens);
872
+ const reasoningTokens = tokenCount(usage.reasoningTokens);
873
+ const cacheReadInputTokens = tokenCount(usage.cacheReadInputTokens);
874
+ const cacheWriteInputTokens = tokenCount(usage.cacheWriteInputTokens);
875
+ const nonCachedInputTokens = exclusiveTokens({
876
+ explicit: explicitNonCachedInputTokens,
877
+ total: inputTokens,
878
+ parts: [cacheReadInputTokens, cacheWriteInputTokens],
879
+ semantics: usage.inputTokenSemantics
880
+ });
881
+ const nonReasoningOutputTokens = exclusiveTokens({
882
+ explicit: explicitNonReasoningOutputTokens,
883
+ total: outputTokens,
884
+ parts: [reasoningTokens],
885
+ semantics: usage.outputTokenSemantics
886
+ });
887
+ const exclusivePoolAttributes = nonCachedInputTokens !== void 0 && nonReasoningOutputTokens !== void 0 ? [
888
+ // backend consumes this complete raindrop.* set as one atomic billing-pool contract.
889
+ attrInt(MODEL_USAGE_ATTRIBUTES.nonCachedInputTokens, nonCachedInputTokens),
890
+ attrInt(MODEL_USAGE_ATTRIBUTES.nonReasoningOutputTokens, nonReasoningOutputTokens),
891
+ attrInt(MODEL_USAGE_ATTRIBUTES.reasoningOutputTokens, reasoningTokens),
892
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheReadTokens, cacheReadInputTokens),
893
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheWriteTokens, cacheWriteInputTokens)
894
+ ] : [];
895
+ return [
896
+ attrInt(MODEL_USAGE_ATTRIBUTES.inputTokens, inputTokens),
897
+ attrInt(MODEL_USAGE_ATTRIBUTES.outputTokens, outputTokens),
898
+ attrInt(MODEL_USAGE_ATTRIBUTES.reasoningTokens, reasoningTokens),
899
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheReadInputTokens, cacheReadInputTokens),
900
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheWriteInputTokens, cacheWriteInputTokens),
901
+ ...exclusivePoolAttributes
902
+ ].filter((attribute) => attribute !== void 0);
903
+ }
904
+ var STRING_ALIASES = {
905
+ [MODEL_USAGE_ATTRIBUTES.requestModel]: ["ai.model.id", "ai.model"]
906
+ };
907
+ var NUMBER_ALIASES = {
908
+ [MODEL_USAGE_ATTRIBUTES.inputTokens]: [
909
+ "gen_ai.usage.prompt_tokens",
910
+ "ai.usage.prompt_tokens",
911
+ "ai.usage.promptTokens",
912
+ "ai.usage.input_tokens",
913
+ "ai.usage.inputTokens"
914
+ ],
915
+ [MODEL_USAGE_ATTRIBUTES.outputTokens]: [
916
+ "gen_ai.usage.completion_tokens",
917
+ "ai.usage.completion_tokens",
918
+ "ai.usage.completionTokens",
919
+ "ai.usage.output_tokens",
920
+ "ai.usage.outputTokens"
921
+ ],
922
+ [MODEL_USAGE_ATTRIBUTES.reasoningTokens]: [
923
+ "gen_ai.usage.reasoning.output_tokens",
924
+ "ai.usage.reasoningTokens",
925
+ "ai.usage.thoughts_tokens"
926
+ ],
927
+ [MODEL_USAGE_ATTRIBUTES.cacheReadInputTokens]: [
928
+ "gen_ai.usage.cache_read_tokens",
929
+ "gen_ai.usage.cache_read.input_tokens",
930
+ "ai.usage.cachedInputTokens",
931
+ "ai.usage.cached_tokens",
932
+ "ai.usage.cache_read_tokens",
933
+ "ai.usage.cache_read_input_tokens",
934
+ "ai.usage.cacheReadInputTokens"
935
+ ],
936
+ [MODEL_USAGE_ATTRIBUTES.cacheWriteInputTokens]: [
937
+ "gen_ai.usage.cache_creation_input_tokens",
938
+ "gen_ai.usage.cache_creation.input_tokens",
939
+ "ai.usage.cacheWriteInputTokens",
940
+ "ai.usage.cache_creation_input_tokens",
941
+ "ai.usage.cacheCreationInputTokens"
942
+ ]
943
+ };
944
+ function hasAttribute(attributes, key) {
945
+ return attributes.some((attribute) => attribute.key === key);
946
+ }
947
+ function findStringAttribute(attributes, keys) {
948
+ return attributes.find(
949
+ (attribute) => keys.includes(attribute.key) && typeof attribute.value.stringValue === "string" && attribute.value.stringValue.length > 0
950
+ );
951
+ }
952
+ function findNumberAttribute(attributes, keys) {
953
+ return attributes.find((attribute) => {
954
+ if (!keys.includes(attribute.key)) return false;
955
+ if (typeof attribute.value.doubleValue === "number" && Number.isSafeInteger(attribute.value.doubleValue) && attribute.value.doubleValue >= 0) {
956
+ return true;
957
+ }
958
+ const intValue = attribute.value.intValue;
959
+ return integerValue(intValue) !== void 0;
960
+ });
961
+ }
962
+ function integerValue(value) {
963
+ if (typeof value === "number") {
964
+ return Number.isSafeInteger(value) && value >= 0 ? value : void 0;
965
+ }
966
+ if (typeof value !== "string") return void 0;
967
+ const parsed = Number(value);
968
+ return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : void 0;
969
+ }
970
+ function appendAlias(attributes, target, source) {
971
+ if (!source || hasAttribute(attributes, target)) return attributes;
972
+ return [...attributes, { key: target, value: { ...source.value } }];
973
+ }
974
+ function appendProviderAlias(attributes) {
975
+ if (hasAttribute(attributes, MODEL_USAGE_ATTRIBUTES.providerName)) return attributes;
976
+ const source = findStringAttribute(attributes, ["gen_ai.system", "ai.model.provider"]);
977
+ if (!(source == null ? void 0 : source.value.stringValue)) return attributes;
978
+ const attribute = attrString(
979
+ MODEL_USAGE_ATTRIBUTES.providerName,
980
+ canonicalModelProvider(source.value.stringValue)
981
+ );
982
+ return attribute ? [...attributes, attribute] : attributes;
983
+ }
984
+ function normalizeModelUsageSpan(span) {
985
+ const original = span.attributes;
986
+ if (!original || original.length === 0) return span;
987
+ const responseModel = findStringAttribute(original, [
988
+ MODEL_USAGE_ATTRIBUTES.responseModel,
989
+ "ai.response.model"
990
+ ]);
991
+ if (!responseModel) return span;
992
+ let attributes = appendAlias(original, MODEL_USAGE_ATTRIBUTES.responseModel, responseModel);
993
+ attributes = appendProviderAlias(attributes);
994
+ for (const [target, aliases] of Object.entries(STRING_ALIASES)) {
995
+ attributes = appendAlias(attributes, target, findStringAttribute(attributes, aliases));
996
+ }
997
+ for (const [target, aliases] of Object.entries(NUMBER_ALIASES)) {
998
+ attributes = appendAlias(attributes, target, findNumberAttribute(attributes, aliases));
999
+ }
1000
+ return attributes === original ? span : { ...span, attributes };
1001
+ }
800
1002
  var DEFAULT_SECRET_KEY_NAMES = [
801
1003
  "apikey",
802
1004
  "apisecret",
@@ -997,6 +1199,10 @@ var TraceShipper = class {
997
1199
  return null;
998
1200
  }
999
1201
  }
1202
+ try {
1203
+ current = normalizeModelUsageSpan(current);
1204
+ } catch (e) {
1205
+ }
1000
1206
  if (!this.disableDefaultRedaction) {
1001
1207
  current = defaultTransformSpan(current);
1002
1208
  }
@@ -1242,7 +1448,7 @@ installTracingSuppressionHook();
1242
1448
  // package.json
1243
1449
  var package_default = {
1244
1450
  name: "@raindrop-ai/pi-agent",
1245
- version: "0.1.0",
1451
+ version: "0.1.1",
1246
1452
  description: "Raindrop observability for Pi Agent \u2014 automatic tracing via subscriber or pi-coding-agent extension",
1247
1453
  type: "module",
1248
1454
  license: "MIT",
@@ -1397,9 +1603,22 @@ function extractTokenUsage(message) {
1397
1603
  return {
1398
1604
  input: usage.input,
1399
1605
  output: usage.output,
1400
- ...typeof usage.cacheRead === "number" && usage.cacheRead > 0 ? { cacheRead: usage.cacheRead } : {}
1606
+ cacheRead: typeof usage.cacheRead === "number" ? usage.cacheRead : 0,
1607
+ cacheWrite: typeof usage.cacheWrite === "number" ? usage.cacheWrite : 0
1401
1608
  };
1402
1609
  }
1610
+ function modelSpendSpanAttributes(input) {
1611
+ const providerAttr = input.provider ? attrString("gen_ai.provider.name", canonicalModelProvider(input.provider)) : void 0;
1612
+ const usageAttrs = input.usage ? buildModelUsageAttributes({
1613
+ inputTokens: input.usage.input,
1614
+ outputTokens: input.usage.output,
1615
+ cacheReadInputTokens: input.usage.cacheRead,
1616
+ cacheWriteInputTokens: input.usage.cacheWrite,
1617
+ reasoningTokens: 0,
1618
+ inputTokenSemantics: "exclusive"
1619
+ }) : [];
1620
+ return providerAttr ? [providerAttr, ...usageAttrs] : usageAttrs;
1621
+ }
1403
1622
  function extractAssistantText(message) {
1404
1623
  if (!("role" in message) || message.role !== "assistant") return void 0;
1405
1624
  if (!Array.isArray(message.content)) return void 0;
@@ -1541,6 +1760,9 @@ function getUsername() {
1541
1760
  }
1542
1761
 
1543
1762
  // src/internal/subscriber.ts
1763
+ function pushAttr(attrs, attr) {
1764
+ if (attr) attrs.push(attr);
1765
+ }
1544
1766
  function resolveEventId(source) {
1545
1767
  if (typeof source !== "function") return randomUUID();
1546
1768
  try {
@@ -1658,41 +1880,30 @@ function createSubscriber(agent, eventShipper, traceShipper, defaultOptions, opt
1658
1880
  if (usage) {
1659
1881
  currentRun.totalInputTokens += usage.input;
1660
1882
  currentRun.totalOutputTokens += usage.output;
1661
- if (usage.cacheRead) currentRun.totalCacheReadTokens += usage.cacheRead;
1883
+ if (usage.cacheRead > 0) currentRun.totalCacheReadTokens += usage.cacheRead;
1662
1884
  }
1663
1885
  if (!traceShipper || !currentRun.currentTurnSpan) return;
1664
1886
  const rawProvider = provider;
1665
1887
  const rawModelId = bareModel;
1666
1888
  const stopReason = "stopReason" in message && typeof message.stopReason === "string" ? message.stopReason : void 0;
1667
- const llmAttrs = [
1668
- attrString("ai.operationId", "generateText")
1669
- ];
1889
+ const llmAttrs = [];
1890
+ pushAttr(llmAttrs, attrString("ai.operationId", "generateText"));
1670
1891
  if (rawProvider) {
1671
- llmAttrs.push(attrString("gen_ai.system", rawProvider));
1892
+ pushAttr(llmAttrs, attrString("gen_ai.system", rawProvider));
1672
1893
  }
1673
1894
  if (rawModelId) {
1674
- llmAttrs.push(
1675
- attrString("gen_ai.response.model", rawModelId),
1676
- attrString("gen_ai.request.model", rawModelId)
1677
- );
1678
- }
1679
- if (usage) {
1680
- llmAttrs.push(
1681
- attrInt("gen_ai.usage.input_tokens", usage.input),
1682
- attrInt("gen_ai.usage.output_tokens", usage.output)
1683
- );
1684
- if (usage.cacheRead) {
1685
- llmAttrs.push(attrInt("gen_ai.usage.cache_read_tokens", usage.cacheRead));
1686
- }
1895
+ pushAttr(llmAttrs, attrString("gen_ai.response.model", rawModelId));
1896
+ pushAttr(llmAttrs, attrString("gen_ai.request.model", rawModelId));
1687
1897
  }
1898
+ llmAttrs.push(...modelSpendSpanAttributes({ provider: rawProvider, usage }));
1688
1899
  if (assistantText) {
1689
- llmAttrs.push(attrString("ai.response.text", truncate(assistantText)));
1900
+ pushAttr(llmAttrs, attrString("ai.response.text", truncate(assistantText)));
1690
1901
  }
1691
1902
  if (currentRun.currentInput) {
1692
- llmAttrs.push(attrString("ai.prompt", truncate(currentRun.currentInput)));
1903
+ pushAttr(llmAttrs, attrString("ai.prompt", truncate(currentRun.currentInput)));
1693
1904
  }
1694
1905
  if (stopReason) {
1695
- llmAttrs.push(attrString("ai.stop_reason", stopReason));
1906
+ pushAttr(llmAttrs, attrString("ai.stop_reason", stopReason));
1696
1907
  }
1697
1908
  const llmSpan = traceShipper.startSpan({
1698
1909
  name: model != null ? model : "llm",
@@ -1794,9 +2005,9 @@ function createSubscriber(agent, eventShipper, traceShipper, defaultOptions, opt
1794
2005
  if (!traceShipper || !toolSpan) return;
1795
2006
  const endAttrs = [];
1796
2007
  const argsStr = truncate(safeStringify(args));
1797
- if (argsStr) endAttrs.push(attrString("ai.toolCall.args", argsStr));
2008
+ if (argsStr) pushAttr(endAttrs, attrString("ai.toolCall.args", argsStr));
1798
2009
  const resultStr = truncate(safeStringify(result));
1799
- if (resultStr) endAttrs.push(attrString("ai.toolCall.result", resultStr));
2010
+ if (resultStr) pushAttr(endAttrs, attrString("ai.toolCall.result", resultStr));
1800
2011
  traceShipper.endSpan(toolSpan, {
1801
2012
  attributes: endAttrs,
1802
2013
  ...isError ? { error: `Tool "${toolName}" failed` } : {}
@@ -1823,29 +2034,28 @@ function createSubscriber(agent, eventShipper, traceShipper, defaultOptions, opt
1823
2034
  }
1824
2035
  const outputText = capText2(run.outputParts.join(""));
1825
2036
  if (traceShipper && run.rootSpan) {
1826
- const rootAttrs = [
1827
- attrString("ai.operationId", "generateText")
1828
- ];
2037
+ const rootAttrs = [];
2038
+ pushAttr(rootAttrs, attrString("ai.operationId", "generateText"));
1829
2039
  if (run.lastModel) {
1830
2040
  run.rootSpan.name = run.lastProvider ? `${run.lastProvider}/${run.lastModel}` : run.lastModel;
1831
- rootAttrs.push(attrString("gen_ai.response.model", run.lastModel));
2041
+ pushAttr(rootAttrs, attrString("gen_ai.response.model", run.lastModel));
1832
2042
  }
1833
2043
  if (run.currentInput) {
1834
- rootAttrs.push(attrString("ai.prompt", truncate(run.currentInput)));
2044
+ pushAttr(rootAttrs, attrString("ai.prompt", truncate(run.currentInput)));
1835
2045
  }
1836
2046
  if (outputText) {
1837
- rootAttrs.push(attrString("ai.response.text", truncate(outputText)));
2047
+ pushAttr(rootAttrs, attrString("ai.response.text", truncate(outputText)));
1838
2048
  }
1839
2049
  if (run.totalInputTokens > 0) {
1840
- rootAttrs.push(attrInt("gen_ai.usage.input_tokens", run.totalInputTokens));
2050
+ pushAttr(rootAttrs, attrInt("gen_ai.usage.input_tokens", run.totalInputTokens));
1841
2051
  }
1842
2052
  if (run.totalOutputTokens > 0) {
1843
- rootAttrs.push(attrInt("gen_ai.usage.output_tokens", run.totalOutputTokens));
2053
+ pushAttr(rootAttrs, attrInt("gen_ai.usage.output_tokens", run.totalOutputTokens));
1844
2054
  }
1845
2055
  if (run.totalCacheReadTokens > 0) {
1846
- rootAttrs.push(attrInt("gen_ai.usage.cache_read_tokens", run.totalCacheReadTokens));
2056
+ pushAttr(rootAttrs, attrInt("gen_ai.usage.cache_read_tokens", run.totalCacheReadTokens));
1847
2057
  }
1848
- rootAttrs.push(attrInt("ai.total_turns", run.turnNumber));
2058
+ pushAttr(rootAttrs, attrInt("ai.total_turns", run.turnNumber));
1849
2059
  traceShipper.endSpan(run.rootSpan, { attributes: rootAttrs });
1850
2060
  }
1851
2061
  if (eventShipper) {
package/dist/index.js CHANGED
@@ -13,13 +13,17 @@ import {
13
13
  getHostname,
14
14
  getUsername,
15
15
  libraryVersion,
16
+ modelSpendSpanAttributes,
16
17
  randomUUID,
17
18
  resolveLocalDebuggerBaseUrl,
18
19
  safeStringify,
19
20
  truncate
20
- } from "./chunk-XQJ6G4YA.js";
21
+ } from "./chunk-GTKXT2J5.js";
21
22
 
22
23
  // src/internal/subscriber.ts
24
+ function pushAttr(attrs, attr) {
25
+ if (attr) attrs.push(attr);
26
+ }
23
27
  function resolveEventId(source) {
24
28
  if (typeof source !== "function") return randomUUID();
25
29
  try {
@@ -137,41 +141,30 @@ function createSubscriber(agent, eventShipper, traceShipper, defaultOptions, opt
137
141
  if (usage) {
138
142
  currentRun.totalInputTokens += usage.input;
139
143
  currentRun.totalOutputTokens += usage.output;
140
- if (usage.cacheRead) currentRun.totalCacheReadTokens += usage.cacheRead;
144
+ if (usage.cacheRead > 0) currentRun.totalCacheReadTokens += usage.cacheRead;
141
145
  }
142
146
  if (!traceShipper || !currentRun.currentTurnSpan) return;
143
147
  const rawProvider = provider;
144
148
  const rawModelId = bareModel;
145
149
  const stopReason = "stopReason" in message && typeof message.stopReason === "string" ? message.stopReason : void 0;
146
- const llmAttrs = [
147
- attrString("ai.operationId", "generateText")
148
- ];
150
+ const llmAttrs = [];
151
+ pushAttr(llmAttrs, attrString("ai.operationId", "generateText"));
149
152
  if (rawProvider) {
150
- llmAttrs.push(attrString("gen_ai.system", rawProvider));
153
+ pushAttr(llmAttrs, attrString("gen_ai.system", rawProvider));
151
154
  }
152
155
  if (rawModelId) {
153
- llmAttrs.push(
154
- attrString("gen_ai.response.model", rawModelId),
155
- attrString("gen_ai.request.model", rawModelId)
156
- );
157
- }
158
- if (usage) {
159
- llmAttrs.push(
160
- attrInt("gen_ai.usage.input_tokens", usage.input),
161
- attrInt("gen_ai.usage.output_tokens", usage.output)
162
- );
163
- if (usage.cacheRead) {
164
- llmAttrs.push(attrInt("gen_ai.usage.cache_read_tokens", usage.cacheRead));
165
- }
156
+ pushAttr(llmAttrs, attrString("gen_ai.response.model", rawModelId));
157
+ pushAttr(llmAttrs, attrString("gen_ai.request.model", rawModelId));
166
158
  }
159
+ llmAttrs.push(...modelSpendSpanAttributes({ provider: rawProvider, usage }));
167
160
  if (assistantText) {
168
- llmAttrs.push(attrString("ai.response.text", truncate(assistantText)));
161
+ pushAttr(llmAttrs, attrString("ai.response.text", truncate(assistantText)));
169
162
  }
170
163
  if (currentRun.currentInput) {
171
- llmAttrs.push(attrString("ai.prompt", truncate(currentRun.currentInput)));
164
+ pushAttr(llmAttrs, attrString("ai.prompt", truncate(currentRun.currentInput)));
172
165
  }
173
166
  if (stopReason) {
174
- llmAttrs.push(attrString("ai.stop_reason", stopReason));
167
+ pushAttr(llmAttrs, attrString("ai.stop_reason", stopReason));
175
168
  }
176
169
  const llmSpan = traceShipper.startSpan({
177
170
  name: model != null ? model : "llm",
@@ -273,9 +266,9 @@ function createSubscriber(agent, eventShipper, traceShipper, defaultOptions, opt
273
266
  if (!traceShipper || !toolSpan) return;
274
267
  const endAttrs = [];
275
268
  const argsStr = truncate(safeStringify(args));
276
- if (argsStr) endAttrs.push(attrString("ai.toolCall.args", argsStr));
269
+ if (argsStr) pushAttr(endAttrs, attrString("ai.toolCall.args", argsStr));
277
270
  const resultStr = truncate(safeStringify(result));
278
- if (resultStr) endAttrs.push(attrString("ai.toolCall.result", resultStr));
271
+ if (resultStr) pushAttr(endAttrs, attrString("ai.toolCall.result", resultStr));
279
272
  traceShipper.endSpan(toolSpan, {
280
273
  attributes: endAttrs,
281
274
  ...isError ? { error: `Tool "${toolName}" failed` } : {}
@@ -302,29 +295,28 @@ function createSubscriber(agent, eventShipper, traceShipper, defaultOptions, opt
302
295
  }
303
296
  const outputText = capText(run.outputParts.join(""));
304
297
  if (traceShipper && run.rootSpan) {
305
- const rootAttrs = [
306
- attrString("ai.operationId", "generateText")
307
- ];
298
+ const rootAttrs = [];
299
+ pushAttr(rootAttrs, attrString("ai.operationId", "generateText"));
308
300
  if (run.lastModel) {
309
301
  run.rootSpan.name = run.lastProvider ? `${run.lastProvider}/${run.lastModel}` : run.lastModel;
310
- rootAttrs.push(attrString("gen_ai.response.model", run.lastModel));
302
+ pushAttr(rootAttrs, attrString("gen_ai.response.model", run.lastModel));
311
303
  }
312
304
  if (run.currentInput) {
313
- rootAttrs.push(attrString("ai.prompt", truncate(run.currentInput)));
305
+ pushAttr(rootAttrs, attrString("ai.prompt", truncate(run.currentInput)));
314
306
  }
315
307
  if (outputText) {
316
- rootAttrs.push(attrString("ai.response.text", truncate(outputText)));
308
+ pushAttr(rootAttrs, attrString("ai.response.text", truncate(outputText)));
317
309
  }
318
310
  if (run.totalInputTokens > 0) {
319
- rootAttrs.push(attrInt("gen_ai.usage.input_tokens", run.totalInputTokens));
311
+ pushAttr(rootAttrs, attrInt("gen_ai.usage.input_tokens", run.totalInputTokens));
320
312
  }
321
313
  if (run.totalOutputTokens > 0) {
322
- rootAttrs.push(attrInt("gen_ai.usage.output_tokens", run.totalOutputTokens));
314
+ pushAttr(rootAttrs, attrInt("gen_ai.usage.output_tokens", run.totalOutputTokens));
323
315
  }
324
316
  if (run.totalCacheReadTokens > 0) {
325
- rootAttrs.push(attrInt("gen_ai.usage.cache_read_tokens", run.totalCacheReadTokens));
317
+ pushAttr(rootAttrs, attrInt("gen_ai.usage.cache_read_tokens", run.totalCacheReadTokens));
326
318
  }
327
- rootAttrs.push(attrInt("ai.total_turns", run.turnNumber));
319
+ pushAttr(rootAttrs, attrInt("ai.total_turns", run.turnNumber));
328
320
  traceShipper.endSpan(run.rootSpan, { attributes: rootAttrs });
329
321
  }
330
322
  if (eventShipper) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raindrop-ai/pi-agent",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Raindrop observability for Pi Agent — automatic tracing via subscriber or pi-coding-agent extension",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -54,7 +54,7 @@
54
54
  "tsup": "^8.5.1",
55
55
  "typescript": "^5.7.3",
56
56
  "vitest": "^2.1.9",
57
- "@raindrop-ai/core": "0.1.3"
57
+ "@raindrop-ai/core": "0.2.0"
58
58
  },
59
59
  "tsup": {
60
60
  "entry": [