@raindrop-ai/opencode-plugin 0.1.1 → 0.1.2

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.
Files changed (3) hide show
  1. package/dist/index.cjs +261 -38
  2. package/dist/index.js +261 -45
  3. package/package.json +2 -2
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;
@@ -82,6 +82,20 @@ function base64Encode(bytes) {
82
82
  function generateId() {
83
83
  return randomUUID();
84
84
  }
85
+ var HANDOFF_ATTRIBUTE_SUFFIXES = {
86
+ mode: "raindrop.handoff.mode",
87
+ childEventId: "raindrop.handoff.childEventId",
88
+ parentEventId: "raindrop.handoff.parentEventId",
89
+ parentSpanId: "raindrop.handoff.parentSpanId",
90
+ name: "raindrop.handoff.name",
91
+ terminal: "raindrop.handoff.terminal",
92
+ agentRole: "raindrop.agent.role"
93
+ };
94
+ var HANDOFF_TERMINAL_PROPERTY_KEY = HANDOFF_ATTRIBUTE_SUFFIXES.terminal;
95
+ var TRACEPARENT_HEADER = "traceparent";
96
+ var BAGGAGE_HEADER = "baggage";
97
+ var HANDOFF_HEADER = "x-raindrop-handoff";
98
+ 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.`;
85
99
  function runWithTracingSuppressed(fn) {
86
100
  const hook = globalThis.RAINDROP_SUPPRESS_TRACING;
87
101
  if (typeof hook !== "function") return fn();
@@ -800,6 +814,194 @@ var EventShipper = class {
800
814
  }
801
815
  }
802
816
  };
817
+ var MODEL_USAGE_ATTRIBUTES = {
818
+ providerName: "gen_ai.provider.name",
819
+ requestModel: "gen_ai.request.model",
820
+ responseModel: "gen_ai.response.model",
821
+ inputTokens: "gen_ai.usage.input_tokens",
822
+ outputTokens: "gen_ai.usage.output_tokens",
823
+ reasoningTokens: "gen_ai.usage.reasoning_tokens",
824
+ cacheReadInputTokens: "gen_ai.usage.cache_read_input_tokens",
825
+ cacheWriteInputTokens: "gen_ai.usage.cache_write_input_tokens",
826
+ nonCachedInputTokens: "raindrop.usage.input_tokens.non_cached",
827
+ nonReasoningOutputTokens: "raindrop.usage.output_tokens.non_reasoning",
828
+ totalOutputTokens: "raindrop.usage.output_tokens.total",
829
+ reasoningOutputTokens: "raindrop.usage.output_tokens.reasoning",
830
+ cacheReadTokens: "raindrop.usage.input_tokens.cache_read",
831
+ cacheWriteTokens: "raindrop.usage.input_tokens.cache_write"
832
+ };
833
+ var MODEL_PROVIDER_NAMES = [
834
+ ["google.vertex", "google-vertex"],
835
+ ["vertex.anthropic", "google-vertex"],
836
+ ["amazon-bedrock", "amazon-bedrock"],
837
+ ["bedrock", "amazon-bedrock"],
838
+ ["anthropic", "anthropic"],
839
+ ["openai", "openai"],
840
+ ["google", "google"],
841
+ ["azure", "azure"],
842
+ ["openrouter", "openrouter"]
843
+ ];
844
+ function canonicalModelProvider(provider) {
845
+ var _a;
846
+ const normalizedProvider = provider.trim().toLowerCase();
847
+ if (normalizedProvider === "gateway") return "vercel";
848
+ const match = MODEL_PROVIDER_NAMES.find(
849
+ ([family]) => normalizedProvider === family || normalizedProvider.startsWith(`${family}.`)
850
+ );
851
+ return (_a = match == null ? void 0 : match[1]) != null ? _a : provider;
852
+ }
853
+ function tokenCount(value) {
854
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
855
+ }
856
+ function exclusiveTokens({
857
+ explicit,
858
+ total,
859
+ parts,
860
+ semantics
861
+ }) {
862
+ if (explicit !== void 0) return explicit;
863
+ if (total === void 0) return void 0;
864
+ if (semantics === "exclusive") return total;
865
+ const observedParts = parts.filter((part) => part !== void 0 && part > 0);
866
+ if (semantics === void 0 && observedParts.length > 0) return void 0;
867
+ const exclusive = total - observedParts.reduce((sum, part) => sum + part, 0);
868
+ return exclusive >= 0 ? exclusive : void 0;
869
+ }
870
+ function buildModelUsageAttributes(usage) {
871
+ const inputTokens = tokenCount(usage.inputTokens);
872
+ const outputTokens = tokenCount(usage.outputTokens);
873
+ const explicitNonCachedInputTokens = tokenCount(usage.nonCachedInputTokens);
874
+ const explicitNonReasoningOutputTokens = tokenCount(usage.nonReasoningOutputTokens);
875
+ const reasoningTokens = tokenCount(usage.reasoningTokens);
876
+ const cacheReadInputTokens = tokenCount(usage.cacheReadInputTokens);
877
+ const cacheWriteInputTokens = tokenCount(usage.cacheWriteInputTokens);
878
+ const nonCachedInputTokens = exclusiveTokens({
879
+ explicit: explicitNonCachedInputTokens,
880
+ total: inputTokens,
881
+ parts: [cacheReadInputTokens, cacheWriteInputTokens],
882
+ semantics: usage.inputTokenSemantics
883
+ });
884
+ const nonReasoningOutputTokens = exclusiveTokens({
885
+ explicit: explicitNonReasoningOutputTokens,
886
+ total: outputTokens,
887
+ parts: [reasoningTokens],
888
+ semantics: usage.outputTokenSemantics
889
+ });
890
+ const exclusivePoolAttributes = nonCachedInputTokens !== void 0 && nonReasoningOutputTokens !== void 0 ? [
891
+ // backend consumes this complete raindrop.* set as one atomic billing-pool contract.
892
+ attrInt(MODEL_USAGE_ATTRIBUTES.nonCachedInputTokens, nonCachedInputTokens),
893
+ attrInt(MODEL_USAGE_ATTRIBUTES.nonReasoningOutputTokens, nonReasoningOutputTokens),
894
+ attrInt(MODEL_USAGE_ATTRIBUTES.reasoningOutputTokens, reasoningTokens),
895
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheReadTokens, cacheReadInputTokens),
896
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheWriteTokens, cacheWriteInputTokens)
897
+ ] : [];
898
+ return [
899
+ attrInt(MODEL_USAGE_ATTRIBUTES.inputTokens, inputTokens),
900
+ attrInt(MODEL_USAGE_ATTRIBUTES.outputTokens, outputTokens),
901
+ attrInt(MODEL_USAGE_ATTRIBUTES.reasoningTokens, reasoningTokens),
902
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheReadInputTokens, cacheReadInputTokens),
903
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheWriteInputTokens, cacheWriteInputTokens),
904
+ ...exclusivePoolAttributes
905
+ ].filter((attribute) => attribute !== void 0);
906
+ }
907
+ var STRING_ALIASES = {
908
+ [MODEL_USAGE_ATTRIBUTES.requestModel]: ["ai.model.id", "ai.model"]
909
+ };
910
+ var NUMBER_ALIASES = {
911
+ [MODEL_USAGE_ATTRIBUTES.inputTokens]: [
912
+ "gen_ai.usage.prompt_tokens",
913
+ "ai.usage.prompt_tokens",
914
+ "ai.usage.promptTokens",
915
+ "ai.usage.input_tokens",
916
+ "ai.usage.inputTokens"
917
+ ],
918
+ [MODEL_USAGE_ATTRIBUTES.outputTokens]: [
919
+ "gen_ai.usage.completion_tokens",
920
+ "ai.usage.completion_tokens",
921
+ "ai.usage.completionTokens",
922
+ "ai.usage.output_tokens",
923
+ "ai.usage.outputTokens"
924
+ ],
925
+ [MODEL_USAGE_ATTRIBUTES.reasoningTokens]: [
926
+ "gen_ai.usage.reasoning.output_tokens",
927
+ "ai.usage.reasoningTokens",
928
+ "ai.usage.thoughts_tokens"
929
+ ],
930
+ [MODEL_USAGE_ATTRIBUTES.cacheReadInputTokens]: [
931
+ "gen_ai.usage.cache_read_tokens",
932
+ "gen_ai.usage.cache_read.input_tokens",
933
+ "ai.usage.cachedInputTokens",
934
+ "ai.usage.cached_tokens",
935
+ "ai.usage.cache_read_tokens",
936
+ "ai.usage.cache_read_input_tokens",
937
+ "ai.usage.cacheReadInputTokens"
938
+ ],
939
+ [MODEL_USAGE_ATTRIBUTES.cacheWriteInputTokens]: [
940
+ "gen_ai.usage.cache_creation_input_tokens",
941
+ "gen_ai.usage.cache_creation.input_tokens",
942
+ "ai.usage.cacheWriteInputTokens",
943
+ "ai.usage.cache_creation_input_tokens",
944
+ "ai.usage.cacheCreationInputTokens"
945
+ ]
946
+ };
947
+ function hasAttribute(attributes, key) {
948
+ return attributes.some((attribute) => attribute.key === key);
949
+ }
950
+ function findStringAttribute(attributes, keys) {
951
+ return attributes.find(
952
+ (attribute) => keys.includes(attribute.key) && typeof attribute.value.stringValue === "string" && attribute.value.stringValue.length > 0
953
+ );
954
+ }
955
+ function findNumberAttribute(attributes, keys) {
956
+ return attributes.find((attribute) => {
957
+ if (!keys.includes(attribute.key)) return false;
958
+ if (typeof attribute.value.doubleValue === "number" && Number.isSafeInteger(attribute.value.doubleValue) && attribute.value.doubleValue >= 0) {
959
+ return true;
960
+ }
961
+ const intValue = attribute.value.intValue;
962
+ return integerValue(intValue) !== void 0;
963
+ });
964
+ }
965
+ function integerValue(value) {
966
+ if (typeof value === "number") {
967
+ return Number.isSafeInteger(value) && value >= 0 ? value : void 0;
968
+ }
969
+ if (typeof value !== "string") return void 0;
970
+ const parsed = Number(value);
971
+ return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : void 0;
972
+ }
973
+ function appendAlias(attributes, target, source) {
974
+ if (!source || hasAttribute(attributes, target)) return attributes;
975
+ return [...attributes, { key: target, value: { ...source.value } }];
976
+ }
977
+ function appendProviderAlias(attributes) {
978
+ if (hasAttribute(attributes, MODEL_USAGE_ATTRIBUTES.providerName)) return attributes;
979
+ const source = findStringAttribute(attributes, ["gen_ai.system", "ai.model.provider"]);
980
+ if (!(source == null ? void 0 : source.value.stringValue)) return attributes;
981
+ const attribute = attrString(
982
+ MODEL_USAGE_ATTRIBUTES.providerName,
983
+ canonicalModelProvider(source.value.stringValue)
984
+ );
985
+ return attribute ? [...attributes, attribute] : attributes;
986
+ }
987
+ function normalizeModelUsageSpan(span) {
988
+ const original = span.attributes;
989
+ if (!original || original.length === 0) return span;
990
+ const responseModel = findStringAttribute(original, [
991
+ MODEL_USAGE_ATTRIBUTES.responseModel,
992
+ "ai.response.model"
993
+ ]);
994
+ if (!responseModel) return span;
995
+ let attributes = appendAlias(original, MODEL_USAGE_ATTRIBUTES.responseModel, responseModel);
996
+ attributes = appendProviderAlias(attributes);
997
+ for (const [target, aliases] of Object.entries(STRING_ALIASES)) {
998
+ attributes = appendAlias(attributes, target, findStringAttribute(attributes, aliases));
999
+ }
1000
+ for (const [target, aliases] of Object.entries(NUMBER_ALIASES)) {
1001
+ attributes = appendAlias(attributes, target, findNumberAttribute(attributes, aliases));
1002
+ }
1003
+ return attributes === original ? span : { ...span, attributes };
1004
+ }
803
1005
  var DEFAULT_SECRET_KEY_NAMES = [
804
1006
  "apikey",
805
1007
  "apisecret",
@@ -1000,6 +1202,10 @@ var TraceShipper = class {
1000
1202
  return null;
1001
1203
  }
1002
1204
  }
1205
+ try {
1206
+ current = normalizeModelUsageSpan(current);
1207
+ } catch (e) {
1208
+ }
1003
1209
  if (!this.disableDefaultRedaction) {
1004
1210
  current = defaultTransformSpan(current);
1005
1211
  }
@@ -1296,7 +1502,7 @@ function resolveLocalWorkshopUrl(fileValue) {
1296
1502
  // package.json
1297
1503
  var package_default = {
1298
1504
  name: "@raindrop-ai/opencode-plugin",
1299
- version: "0.1.1",
1505
+ version: "0.1.2",
1300
1506
  description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
1301
1507
  type: "module",
1302
1508
  main: "dist/index.js",
@@ -1401,6 +1607,9 @@ var TraceShipper2 = class extends TraceShipper {
1401
1607
  }
1402
1608
  };
1403
1609
 
1610
+ // src/tracing.ts
1611
+ var import_node_os2 = require("os");
1612
+
1404
1613
  // src/bounded.ts
1405
1614
  var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
1406
1615
  var MAX_TEXT_FIELD_CHARS = 1e6;
@@ -1481,6 +1690,49 @@ function boundedStringify(value, limit = MAX_TEXT_FIELD_CHARS) {
1481
1690
  }
1482
1691
  }
1483
1692
 
1693
+ // src/model-spend.ts
1694
+ function parseOpenCodeAssistantTokens(tokens) {
1695
+ const input = tokens["input"];
1696
+ const output = tokens["output"];
1697
+ if (typeof input !== "number" || typeof output !== "number") return void 0;
1698
+ const reasoning = tokens["reasoning"];
1699
+ const cache = tokens["cache"];
1700
+ const cacheObj = cache && typeof cache === "object" && cache !== null ? cache : void 0;
1701
+ return {
1702
+ input,
1703
+ output,
1704
+ reasoning: typeof reasoning === "number" ? reasoning : 0,
1705
+ cacheRead: typeof (cacheObj == null ? void 0 : cacheObj["read"]) === "number" ? cacheObj["read"] : 0,
1706
+ cacheWrite: typeof (cacheObj == null ? void 0 : cacheObj["write"]) === "number" ? cacheObj["write"] : 0
1707
+ };
1708
+ }
1709
+ function modelSpendSpanAttributes(input) {
1710
+ const providerAttr = attrString(
1711
+ "gen_ai.provider.name",
1712
+ canonicalModelProvider(input.providerID)
1713
+ );
1714
+ const usageAttrs = buildModelUsageAttributes({
1715
+ inputTokens: input.tokens.input,
1716
+ outputTokens: input.tokens.output,
1717
+ reasoningTokens: input.tokens.reasoning,
1718
+ cacheReadInputTokens: input.tokens.cacheRead,
1719
+ cacheWriteInputTokens: input.tokens.cacheWrite,
1720
+ inputTokenSemantics: "exclusive",
1721
+ outputTokenSemantics: "exclusive"
1722
+ });
1723
+ return providerAttr ? [providerAttr, ...usageAttrs] : usageAttrs;
1724
+ }
1725
+ function buildLlmGenAiAttributes(input) {
1726
+ const parsed = parseOpenCodeAssistantTokens(input.tokens);
1727
+ if (!parsed) return [];
1728
+ return [
1729
+ attrString("gen_ai.system", input.providerID),
1730
+ attrString("gen_ai.request.model", input.modelID),
1731
+ attrString("gen_ai.response.model", input.modelID),
1732
+ ...modelSpendSpanAttributes({ providerID: input.providerID, tokens: parsed })
1733
+ ].filter((attribute) => attribute !== void 0);
1734
+ }
1735
+
1484
1736
  // src/tracing-linkage-helpers.ts
1485
1737
  function createSessionParentMapHelpers({
1486
1738
  sessions: sessions2,
@@ -1645,7 +1897,7 @@ function getHostname() {
1645
1897
  try {
1646
1898
  const bunGlobal = globalThis;
1647
1899
  if ((_a = bunGlobal.Bun) == null ? void 0 : _a.hostname) return bunGlobal.Bun.hostname;
1648
- return require("os").hostname();
1900
+ return (0, import_node_os2.hostname)();
1649
1901
  } catch (e) {
1650
1902
  const h = process.env["HOSTNAME"];
1651
1903
  if (h === void 0 || h === "") throw new Error("HOSTNAME env is not set");
@@ -1794,7 +2046,6 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
1794
2046
  const tokensObj = tokens;
1795
2047
  const inputTokens = tokensObj["input"];
1796
2048
  const outputTokens = tokensObj["output"];
1797
- const reasoningTokens = tokensObj["reasoning"];
1798
2049
  if (typeof inputTokens !== "number" || typeof outputTokens !== "number")
1799
2050
  throw new Error("message.updated: tokens.input and tokens.output must be numbers");
1800
2051
  const providerID = info2["providerID"];
@@ -1812,13 +2063,7 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
1812
2063
  state.turnOutputParts.push(outputText);
1813
2064
  }
1814
2065
  state.turnOutputTokens += outputTokens;
1815
- const hasIntermediateReasoning = reasoningText !== void 0 && reasoningText.trim().length > 0;
1816
- if (outputText === void 0 && !(isToolCallsCompletion && hasIntermediateReasoning))
1817
- return;
1818
- if (isToolCallsCompletion && !hasIntermediateReasoning) {
1819
- await traceShipper.flush();
1820
- return;
1821
- }
2066
+ if (outputText === void 0 && !isToolCallsCompletion) return;
1822
2067
  const msgError = info2["error"];
1823
2068
  const errorForSpan = msgError ? (() => {
1824
2069
  var _a2, _b2;
@@ -1831,20 +2076,12 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
1831
2076
  type: ${name != null ? name : "UnknownError"}`;
1832
2077
  })() : void 0;
1833
2078
  if (isToolCallsCompletion) {
1834
- if (reasoningText === void 0)
1835
- throw new Error("message.updated: reasoningParts missing for tool-call continuation");
2079
+ const responseText = reasoningText !== void 0 && reasoningText.trim().length > 0 ? reasoningText.trim() : outputText !== void 0 && outputText.trim().length > 0 ? outputText.trim() : "";
1836
2080
  const llmAttrs = [
1837
2081
  attrString("ai.operationId", "generateText"),
1838
- attrString("ai.response.text", reasoningText),
1839
- attrString("gen_ai.system", providerID),
1840
- attrString("gen_ai.request.model", modelID),
1841
- attrString("gen_ai.response.model", modelID),
1842
- attrInt("gen_ai.usage.input_tokens", inputTokens),
1843
- attrInt("gen_ai.usage.output_tokens", outputTokens)
2082
+ attrString("ai.response.text", responseText),
2083
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
1844
2084
  ];
1845
- if (reasoningTokens != null && typeof reasoningTokens === "number" && reasoningTokens > 0) {
1846
- llmAttrs.push(attrInt("gen_ai.usage.reasoning_tokens", reasoningTokens));
1847
- }
1848
2085
  if (state.currentSystemPrompt) {
1849
2086
  llmAttrs.push(
1850
2087
  attrString("gen_ai.prompt.0.role", "system"),
@@ -1879,15 +2116,8 @@ type: ${name != null ? name : "UnknownError"}`;
1879
2116
  buildPromptMessages(state.currentSystemPrompt, state.currentInput)
1880
2117
  ),
1881
2118
  attrString("ai.response.text", turnOutputText),
1882
- attrString("gen_ai.system", providerID),
1883
- attrString("gen_ai.request.model", modelID),
1884
- attrString("gen_ai.response.model", modelID),
1885
- attrInt("gen_ai.usage.input_tokens", inputTokens),
1886
- attrInt("gen_ai.usage.output_tokens", outputTokens)
2119
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
1887
2120
  ];
1888
- if (reasoningTokens != null && typeof reasoningTokens === "number" && reasoningTokens > 0) {
1889
- llmAttrs.push(attrInt("gen_ai.usage.reasoning_tokens", reasoningTokens));
1890
- }
1891
2121
  if (state.currentSystemPrompt) {
1892
2122
  llmAttrs.push(
1893
2123
  attrString("gen_ai.prompt.0.role", "system"),
@@ -1931,19 +2161,12 @@ type: ${name != null ? name : "UnknownError"}`;
1931
2161
  buildPromptMessages(state.currentSystemPrompt, state.currentInput)
1932
2162
  ),
1933
2163
  attrString("ai.response.text", turnOutputText),
1934
- attrString("gen_ai.system", providerID),
1935
- attrString("gen_ai.request.model", modelID),
1936
- attrString("gen_ai.response.model", modelID),
1937
- attrInt("gen_ai.usage.input_tokens", inputTokens),
1938
- attrInt("gen_ai.usage.output_tokens", outputTokens)
2164
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
1939
2165
  ];
1940
2166
  if (isCompaction) {
1941
2167
  rootAttrs.push(attrString("is_compaction", "true"));
1942
2168
  }
1943
2169
  rootAttrs.push(attrString("message_id", messageId));
1944
- if (reasoningTokens != null && typeof reasoningTokens === "number" && reasoningTokens > 0) {
1945
- rootAttrs.push(attrInt("gen_ai.usage.reasoning_tokens", reasoningTokens));
1946
- }
1947
2170
  if (state.currentSystemPrompt) {
1948
2171
  rootAttrs.push(
1949
2172
  attrString("gen_ai.prompt.0.role", "system"),
package/dist/index.js CHANGED
@@ -1,11 +1,4 @@
1
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
- }) : x)(function(x) {
4
- if (typeof require !== "undefined") return require.apply(this, arguments);
5
- throw Error('Dynamic require of "' + x + '" is not supported');
6
- });
7
-
8
- // ../core/dist/chunk-QTUH3BHZ.js
1
+ // ../core/dist/chunk-YUIXRQWN.js
9
2
  function getCrypto() {
10
3
  const c = globalThis.crypto;
11
4
  return c;
@@ -63,6 +56,20 @@ function base64Encode(bytes) {
63
56
  function generateId() {
64
57
  return randomUUID();
65
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.`;
66
73
  function runWithTracingSuppressed(fn) {
67
74
  const hook = globalThis.RAINDROP_SUPPRESS_TRACING;
68
75
  if (typeof hook !== "function") return fn();
@@ -781,6 +788,194 @@ var EventShipper = class {
781
788
  }
782
789
  }
783
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
+ }
784
979
  var DEFAULT_SECRET_KEY_NAMES = [
785
980
  "apikey",
786
981
  "apisecret",
@@ -981,6 +1176,10 @@ var TraceShipper = class {
981
1176
  return null;
982
1177
  }
983
1178
  }
1179
+ try {
1180
+ current = normalizeModelUsageSpan(current);
1181
+ } catch (e) {
1182
+ }
984
1183
  if (!this.disableDefaultRedaction) {
985
1184
  current = defaultTransformSpan(current);
986
1185
  }
@@ -1277,7 +1476,7 @@ function resolveLocalWorkshopUrl(fileValue) {
1277
1476
  // package.json
1278
1477
  var package_default = {
1279
1478
  name: "@raindrop-ai/opencode-plugin",
1280
- version: "0.1.1",
1479
+ version: "0.1.2",
1281
1480
  description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
1282
1481
  type: "module",
1283
1482
  main: "dist/index.js",
@@ -1382,6 +1581,9 @@ var TraceShipper2 = class extends TraceShipper {
1382
1581
  }
1383
1582
  };
1384
1583
 
1584
+ // src/tracing.ts
1585
+ import { hostname as osHostname } from "os";
1586
+
1385
1587
  // src/bounded.ts
1386
1588
  var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
1387
1589
  var MAX_TEXT_FIELD_CHARS = 1e6;
@@ -1462,6 +1664,49 @@ function boundedStringify(value, limit = MAX_TEXT_FIELD_CHARS) {
1462
1664
  }
1463
1665
  }
1464
1666
 
1667
+ // src/model-spend.ts
1668
+ function parseOpenCodeAssistantTokens(tokens) {
1669
+ const input = tokens["input"];
1670
+ const output = tokens["output"];
1671
+ if (typeof input !== "number" || typeof output !== "number") return void 0;
1672
+ const reasoning = tokens["reasoning"];
1673
+ const cache = tokens["cache"];
1674
+ const cacheObj = cache && typeof cache === "object" && cache !== null ? cache : void 0;
1675
+ return {
1676
+ input,
1677
+ output,
1678
+ reasoning: typeof reasoning === "number" ? reasoning : 0,
1679
+ cacheRead: typeof (cacheObj == null ? void 0 : cacheObj["read"]) === "number" ? cacheObj["read"] : 0,
1680
+ cacheWrite: typeof (cacheObj == null ? void 0 : cacheObj["write"]) === "number" ? cacheObj["write"] : 0
1681
+ };
1682
+ }
1683
+ function modelSpendSpanAttributes(input) {
1684
+ const providerAttr = attrString(
1685
+ "gen_ai.provider.name",
1686
+ canonicalModelProvider(input.providerID)
1687
+ );
1688
+ const usageAttrs = buildModelUsageAttributes({
1689
+ inputTokens: input.tokens.input,
1690
+ outputTokens: input.tokens.output,
1691
+ reasoningTokens: input.tokens.reasoning,
1692
+ cacheReadInputTokens: input.tokens.cacheRead,
1693
+ cacheWriteInputTokens: input.tokens.cacheWrite,
1694
+ inputTokenSemantics: "exclusive",
1695
+ outputTokenSemantics: "exclusive"
1696
+ });
1697
+ return providerAttr ? [providerAttr, ...usageAttrs] : usageAttrs;
1698
+ }
1699
+ function buildLlmGenAiAttributes(input) {
1700
+ const parsed = parseOpenCodeAssistantTokens(input.tokens);
1701
+ if (!parsed) return [];
1702
+ return [
1703
+ attrString("gen_ai.system", input.providerID),
1704
+ attrString("gen_ai.request.model", input.modelID),
1705
+ attrString("gen_ai.response.model", input.modelID),
1706
+ ...modelSpendSpanAttributes({ providerID: input.providerID, tokens: parsed })
1707
+ ].filter((attribute) => attribute !== void 0);
1708
+ }
1709
+
1465
1710
  // src/tracing-linkage-helpers.ts
1466
1711
  function createSessionParentMapHelpers({
1467
1712
  sessions: sessions2,
@@ -1626,7 +1871,7 @@ function getHostname() {
1626
1871
  try {
1627
1872
  const bunGlobal = globalThis;
1628
1873
  if ((_a = bunGlobal.Bun) == null ? void 0 : _a.hostname) return bunGlobal.Bun.hostname;
1629
- return __require("os").hostname();
1874
+ return osHostname();
1630
1875
  } catch (e) {
1631
1876
  const h = process.env["HOSTNAME"];
1632
1877
  if (h === void 0 || h === "") throw new Error("HOSTNAME env is not set");
@@ -1775,7 +2020,6 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
1775
2020
  const tokensObj = tokens;
1776
2021
  const inputTokens = tokensObj["input"];
1777
2022
  const outputTokens = tokensObj["output"];
1778
- const reasoningTokens = tokensObj["reasoning"];
1779
2023
  if (typeof inputTokens !== "number" || typeof outputTokens !== "number")
1780
2024
  throw new Error("message.updated: tokens.input and tokens.output must be numbers");
1781
2025
  const providerID = info2["providerID"];
@@ -1793,13 +2037,7 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
1793
2037
  state.turnOutputParts.push(outputText);
1794
2038
  }
1795
2039
  state.turnOutputTokens += outputTokens;
1796
- const hasIntermediateReasoning = reasoningText !== void 0 && reasoningText.trim().length > 0;
1797
- if (outputText === void 0 && !(isToolCallsCompletion && hasIntermediateReasoning))
1798
- return;
1799
- if (isToolCallsCompletion && !hasIntermediateReasoning) {
1800
- await traceShipper.flush();
1801
- return;
1802
- }
2040
+ if (outputText === void 0 && !isToolCallsCompletion) return;
1803
2041
  const msgError = info2["error"];
1804
2042
  const errorForSpan = msgError ? (() => {
1805
2043
  var _a2, _b2;
@@ -1812,20 +2050,12 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
1812
2050
  type: ${name != null ? name : "UnknownError"}`;
1813
2051
  })() : void 0;
1814
2052
  if (isToolCallsCompletion) {
1815
- if (reasoningText === void 0)
1816
- throw new Error("message.updated: reasoningParts missing for tool-call continuation");
2053
+ const responseText = reasoningText !== void 0 && reasoningText.trim().length > 0 ? reasoningText.trim() : outputText !== void 0 && outputText.trim().length > 0 ? outputText.trim() : "";
1817
2054
  const llmAttrs = [
1818
2055
  attrString("ai.operationId", "generateText"),
1819
- attrString("ai.response.text", reasoningText),
1820
- attrString("gen_ai.system", providerID),
1821
- attrString("gen_ai.request.model", modelID),
1822
- attrString("gen_ai.response.model", modelID),
1823
- attrInt("gen_ai.usage.input_tokens", inputTokens),
1824
- attrInt("gen_ai.usage.output_tokens", outputTokens)
2056
+ attrString("ai.response.text", responseText),
2057
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
1825
2058
  ];
1826
- if (reasoningTokens != null && typeof reasoningTokens === "number" && reasoningTokens > 0) {
1827
- llmAttrs.push(attrInt("gen_ai.usage.reasoning_tokens", reasoningTokens));
1828
- }
1829
2059
  if (state.currentSystemPrompt) {
1830
2060
  llmAttrs.push(
1831
2061
  attrString("gen_ai.prompt.0.role", "system"),
@@ -1860,15 +2090,8 @@ type: ${name != null ? name : "UnknownError"}`;
1860
2090
  buildPromptMessages(state.currentSystemPrompt, state.currentInput)
1861
2091
  ),
1862
2092
  attrString("ai.response.text", turnOutputText),
1863
- attrString("gen_ai.system", providerID),
1864
- attrString("gen_ai.request.model", modelID),
1865
- attrString("gen_ai.response.model", modelID),
1866
- attrInt("gen_ai.usage.input_tokens", inputTokens),
1867
- attrInt("gen_ai.usage.output_tokens", outputTokens)
2093
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
1868
2094
  ];
1869
- if (reasoningTokens != null && typeof reasoningTokens === "number" && reasoningTokens > 0) {
1870
- llmAttrs.push(attrInt("gen_ai.usage.reasoning_tokens", reasoningTokens));
1871
- }
1872
2095
  if (state.currentSystemPrompt) {
1873
2096
  llmAttrs.push(
1874
2097
  attrString("gen_ai.prompt.0.role", "system"),
@@ -1912,19 +2135,12 @@ type: ${name != null ? name : "UnknownError"}`;
1912
2135
  buildPromptMessages(state.currentSystemPrompt, state.currentInput)
1913
2136
  ),
1914
2137
  attrString("ai.response.text", turnOutputText),
1915
- attrString("gen_ai.system", providerID),
1916
- attrString("gen_ai.request.model", modelID),
1917
- attrString("gen_ai.response.model", modelID),
1918
- attrInt("gen_ai.usage.input_tokens", inputTokens),
1919
- attrInt("gen_ai.usage.output_tokens", outputTokens)
2138
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
1920
2139
  ];
1921
2140
  if (isCompaction) {
1922
2141
  rootAttrs.push(attrString("is_compaction", "true"));
1923
2142
  }
1924
2143
  rootAttrs.push(attrString("message_id", messageId));
1925
- if (reasoningTokens != null && typeof reasoningTokens === "number" && reasoningTokens > 0) {
1926
- rootAttrs.push(attrInt("gen_ai.usage.reasoning_tokens", reasoningTokens));
1927
- }
1928
2144
  if (state.currentSystemPrompt) {
1929
2145
  rootAttrs.push(
1930
2146
  attrString("gen_ai.prompt.0.role", "system"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raindrop-ai/opencode-plugin",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Raindrop observability plugin for OpenCode — automatic session/event/span tracing",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -37,7 +37,7 @@
37
37
  "@types/node": "^20.11.17",
38
38
  "tsup": "^8.4.0",
39
39
  "typescript": "^5.3.3",
40
- "@raindrop-ai/core": "0.1.3"
40
+ "@raindrop-ai/core": "0.2.0"
41
41
  },
42
42
  "tsup": {
43
43
  "entry": [