@raindrop-ai/opencode-plugin 0.1.1 → 0.1.3

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 +233 -38
  2. package/dist/index.js +233 -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-3V4MIA75.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();
@@ -279,6 +293,11 @@ function attrInt(key, value) {
279
293
  if (!Number.isFinite(value)) return void 0;
280
294
  return { key, value: { intValue: String(Math.trunc(value)) } };
281
295
  }
296
+ function attrDouble(key, value) {
297
+ if (value === void 0) return void 0;
298
+ if (!Number.isFinite(value)) return void 0;
299
+ return { key, value: { doubleValue: value } };
300
+ }
282
301
  function buildOtlpSpan(args) {
283
302
  const attrs = args.attributes.filter((x) => x !== void 0);
284
303
  const span = {
@@ -800,6 +819,163 @@ var EventShipper = class {
800
819
  }
801
820
  }
802
821
  };
822
+ var MODEL_USAGE_ATTRIBUTES = {
823
+ usageSource: "raindrop.usage.source",
824
+ providerName: "gen_ai.provider.name",
825
+ requestModel: "gen_ai.request.model",
826
+ responseModel: "gen_ai.response.model",
827
+ inputTokens: "gen_ai.usage.input_tokens",
828
+ outputTokens: "gen_ai.usage.output_tokens",
829
+ reasoningTokens: "gen_ai.usage.reasoning_tokens",
830
+ cacheReadInputTokens: "gen_ai.usage.cache_read_input_tokens",
831
+ cacheWriteInputTokens: "gen_ai.usage.cache_write_input_tokens",
832
+ providerReportedCost: "gen_ai.usage.provider_reported_cost",
833
+ nonCachedInputTokens: "raindrop.usage.input_tokens.non_cached",
834
+ nonReasoningOutputTokens: "raindrop.usage.output_tokens.non_reasoning",
835
+ totalOutputTokens: "raindrop.usage.output_tokens.total",
836
+ reasoningOutputTokens: "raindrop.usage.output_tokens.reasoning",
837
+ cacheReadTokens: "raindrop.usage.input_tokens.cache_read",
838
+ cacheWriteTokens: "raindrop.usage.input_tokens.cache_write"
839
+ };
840
+ var MODEL_PROVIDER_NAMES = [
841
+ ["google.vertex.anthropic", "google-vertex"],
842
+ ["google.vertex_ai", "google-vertex"],
843
+ ["google.vertex", "google-vertex"],
844
+ ["gcp.vertex_ai", "google-vertex"],
845
+ ["gcp.gemini", "google"],
846
+ ["google_vertexai", "google-vertex"],
847
+ ["vertex.anthropic", "google-vertex"],
848
+ ["amazon-bedrock", "amazon-bedrock"],
849
+ ["aws.bedrock", "amazon-bedrock"],
850
+ ["bedrock", "amazon-bedrock"],
851
+ ["anthropic", "anthropic"],
852
+ ["openai", "openai"],
853
+ ["google", "google"],
854
+ ["az.ai.openai", "azure"],
855
+ ["azure", "azure"],
856
+ ["openrouter", "openrouter"]
857
+ ];
858
+ function canonicalModelProvider(provider) {
859
+ var _a;
860
+ const normalizedProvider = provider.trim().toLowerCase();
861
+ if (normalizedProvider === "gateway") return "vercel";
862
+ const match = MODEL_PROVIDER_NAMES.find(
863
+ ([family]) => normalizedProvider === family || normalizedProvider.startsWith(`${family}.`)
864
+ );
865
+ return (_a = match == null ? void 0 : match[1]) != null ? _a : normalizedProvider;
866
+ }
867
+ function tokenCount(value) {
868
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
869
+ }
870
+ function buildRawModelUsageAttributes(usage) {
871
+ const providerReportedCost = typeof usage.providerReportedCost === "number" && Number.isFinite(usage.providerReportedCost) && usage.providerReportedCost >= 0 ? usage.providerReportedCost : void 0;
872
+ return [
873
+ attrInt(MODEL_USAGE_ATTRIBUTES.inputTokens, tokenCount(usage.inputTokens)),
874
+ attrInt(MODEL_USAGE_ATTRIBUTES.outputTokens, tokenCount(usage.outputTokens)),
875
+ attrInt(MODEL_USAGE_ATTRIBUTES.reasoningTokens, tokenCount(usage.reasoningTokens)),
876
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheReadInputTokens, tokenCount(usage.cacheReadInputTokens)),
877
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheWriteInputTokens, tokenCount(usage.cacheWriteInputTokens)),
878
+ attrDouble(MODEL_USAGE_ATTRIBUTES.providerReportedCost, providerReportedCost)
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
+ }
803
979
  var DEFAULT_SECRET_KEY_NAMES = [
804
980
  "apikey",
805
981
  "apisecret",
@@ -1000,6 +1176,10 @@ var TraceShipper = class {
1000
1176
  return null;
1001
1177
  }
1002
1178
  }
1179
+ try {
1180
+ current = normalizeModelUsageSpan(current);
1181
+ } catch (e) {
1182
+ }
1003
1183
  if (!this.disableDefaultRedaction) {
1004
1184
  current = defaultTransformSpan(current);
1005
1185
  }
@@ -1296,7 +1476,7 @@ function resolveLocalWorkshopUrl(fileValue) {
1296
1476
  // package.json
1297
1477
  var package_default = {
1298
1478
  name: "@raindrop-ai/opencode-plugin",
1299
- version: "0.1.1",
1479
+ version: "0.1.3",
1300
1480
  description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
1301
1481
  type: "module",
1302
1482
  main: "dist/index.js",
@@ -1401,6 +1581,9 @@ var TraceShipper2 = class extends TraceShipper {
1401
1581
  }
1402
1582
  };
1403
1583
 
1584
+ // src/tracing.ts
1585
+ var import_node_os2 = require("os");
1586
+
1404
1587
  // src/bounded.ts
1405
1588
  var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
1406
1589
  var MAX_TEXT_FIELD_CHARS = 1e6;
@@ -1481,6 +1664,47 @@ function boundedStringify(value, limit = MAX_TEXT_FIELD_CHARS) {
1481
1664
  }
1482
1665
  }
1483
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("gen_ai.provider.name", canonicalModelProvider(input.providerID));
1685
+ const usageAttrs = buildRawModelUsageAttributes({
1686
+ inputTokens: input.tokens.input,
1687
+ outputTokens: input.tokens.output,
1688
+ reasoningTokens: input.tokens.reasoning,
1689
+ cacheReadInputTokens: input.tokens.cacheRead,
1690
+ cacheWriteInputTokens: input.tokens.cacheWrite
1691
+ });
1692
+ return providerAttr ? [providerAttr, ...usageAttrs] : usageAttrs;
1693
+ }
1694
+ function buildLlmGenAiAttributes(input) {
1695
+ const parsed = parseOpenCodeAssistantTokens(input.tokens);
1696
+ if (!parsed) return [];
1697
+ return [
1698
+ // OpenCode has already normalized both token axes. Provenance lets Dawn
1699
+ // select that contract without the SDK or customer declaring semantics.
1700
+ attrString(MODEL_USAGE_ATTRIBUTES.usageSource, "opencode"),
1701
+ attrString("gen_ai.system", input.providerID),
1702
+ attrString("gen_ai.request.model", input.modelID),
1703
+ attrString("gen_ai.response.model", input.modelID),
1704
+ ...modelSpendSpanAttributes({ providerID: input.providerID, tokens: parsed })
1705
+ ].filter((attribute) => attribute !== void 0);
1706
+ }
1707
+
1484
1708
  // src/tracing-linkage-helpers.ts
1485
1709
  function createSessionParentMapHelpers({
1486
1710
  sessions: sessions2,
@@ -1645,7 +1869,7 @@ function getHostname() {
1645
1869
  try {
1646
1870
  const bunGlobal = globalThis;
1647
1871
  if ((_a = bunGlobal.Bun) == null ? void 0 : _a.hostname) return bunGlobal.Bun.hostname;
1648
- return require("os").hostname();
1872
+ return (0, import_node_os2.hostname)();
1649
1873
  } catch (e) {
1650
1874
  const h = process.env["HOSTNAME"];
1651
1875
  if (h === void 0 || h === "") throw new Error("HOSTNAME env is not set");
@@ -1794,7 +2018,6 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
1794
2018
  const tokensObj = tokens;
1795
2019
  const inputTokens = tokensObj["input"];
1796
2020
  const outputTokens = tokensObj["output"];
1797
- const reasoningTokens = tokensObj["reasoning"];
1798
2021
  if (typeof inputTokens !== "number" || typeof outputTokens !== "number")
1799
2022
  throw new Error("message.updated: tokens.input and tokens.output must be numbers");
1800
2023
  const providerID = info2["providerID"];
@@ -1812,13 +2035,7 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
1812
2035
  state.turnOutputParts.push(outputText);
1813
2036
  }
1814
2037
  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
- }
2038
+ if (outputText === void 0 && !isToolCallsCompletion) return;
1822
2039
  const msgError = info2["error"];
1823
2040
  const errorForSpan = msgError ? (() => {
1824
2041
  var _a2, _b2;
@@ -1831,20 +2048,12 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
1831
2048
  type: ${name != null ? name : "UnknownError"}`;
1832
2049
  })() : void 0;
1833
2050
  if (isToolCallsCompletion) {
1834
- if (reasoningText === void 0)
1835
- throw new Error("message.updated: reasoningParts missing for tool-call continuation");
2051
+ const responseText = reasoningText !== void 0 && reasoningText.trim().length > 0 ? reasoningText.trim() : outputText !== void 0 && outputText.trim().length > 0 ? outputText.trim() : "";
1836
2052
  const llmAttrs = [
1837
2053
  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)
2054
+ attrString("ai.response.text", responseText),
2055
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
1844
2056
  ];
1845
- if (reasoningTokens != null && typeof reasoningTokens === "number" && reasoningTokens > 0) {
1846
- llmAttrs.push(attrInt("gen_ai.usage.reasoning_tokens", reasoningTokens));
1847
- }
1848
2057
  if (state.currentSystemPrompt) {
1849
2058
  llmAttrs.push(
1850
2059
  attrString("gen_ai.prompt.0.role", "system"),
@@ -1879,15 +2088,8 @@ type: ${name != null ? name : "UnknownError"}`;
1879
2088
  buildPromptMessages(state.currentSystemPrompt, state.currentInput)
1880
2089
  ),
1881
2090
  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)
2091
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
1887
2092
  ];
1888
- if (reasoningTokens != null && typeof reasoningTokens === "number" && reasoningTokens > 0) {
1889
- llmAttrs.push(attrInt("gen_ai.usage.reasoning_tokens", reasoningTokens));
1890
- }
1891
2093
  if (state.currentSystemPrompt) {
1892
2094
  llmAttrs.push(
1893
2095
  attrString("gen_ai.prompt.0.role", "system"),
@@ -1931,19 +2133,12 @@ type: ${name != null ? name : "UnknownError"}`;
1931
2133
  buildPromptMessages(state.currentSystemPrompt, state.currentInput)
1932
2134
  ),
1933
2135
  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)
2136
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
1939
2137
  ];
1940
2138
  if (isCompaction) {
1941
2139
  rootAttrs.push(attrString("is_compaction", "true"));
1942
2140
  }
1943
2141
  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
2142
  if (state.currentSystemPrompt) {
1948
2143
  rootAttrs.push(
1949
2144
  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-3V4MIA75.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();
@@ -260,6 +267,11 @@ function attrInt(key, value) {
260
267
  if (!Number.isFinite(value)) return void 0;
261
268
  return { key, value: { intValue: String(Math.trunc(value)) } };
262
269
  }
270
+ function attrDouble(key, value) {
271
+ if (value === void 0) return void 0;
272
+ if (!Number.isFinite(value)) return void 0;
273
+ return { key, value: { doubleValue: value } };
274
+ }
263
275
  function buildOtlpSpan(args) {
264
276
  const attrs = args.attributes.filter((x) => x !== void 0);
265
277
  const span = {
@@ -781,6 +793,163 @@ var EventShipper = class {
781
793
  }
782
794
  }
783
795
  };
796
+ var MODEL_USAGE_ATTRIBUTES = {
797
+ usageSource: "raindrop.usage.source",
798
+ providerName: "gen_ai.provider.name",
799
+ requestModel: "gen_ai.request.model",
800
+ responseModel: "gen_ai.response.model",
801
+ inputTokens: "gen_ai.usage.input_tokens",
802
+ outputTokens: "gen_ai.usage.output_tokens",
803
+ reasoningTokens: "gen_ai.usage.reasoning_tokens",
804
+ cacheReadInputTokens: "gen_ai.usage.cache_read_input_tokens",
805
+ cacheWriteInputTokens: "gen_ai.usage.cache_write_input_tokens",
806
+ providerReportedCost: "gen_ai.usage.provider_reported_cost",
807
+ nonCachedInputTokens: "raindrop.usage.input_tokens.non_cached",
808
+ nonReasoningOutputTokens: "raindrop.usage.output_tokens.non_reasoning",
809
+ totalOutputTokens: "raindrop.usage.output_tokens.total",
810
+ reasoningOutputTokens: "raindrop.usage.output_tokens.reasoning",
811
+ cacheReadTokens: "raindrop.usage.input_tokens.cache_read",
812
+ cacheWriteTokens: "raindrop.usage.input_tokens.cache_write"
813
+ };
814
+ var MODEL_PROVIDER_NAMES = [
815
+ ["google.vertex.anthropic", "google-vertex"],
816
+ ["google.vertex_ai", "google-vertex"],
817
+ ["google.vertex", "google-vertex"],
818
+ ["gcp.vertex_ai", "google-vertex"],
819
+ ["gcp.gemini", "google"],
820
+ ["google_vertexai", "google-vertex"],
821
+ ["vertex.anthropic", "google-vertex"],
822
+ ["amazon-bedrock", "amazon-bedrock"],
823
+ ["aws.bedrock", "amazon-bedrock"],
824
+ ["bedrock", "amazon-bedrock"],
825
+ ["anthropic", "anthropic"],
826
+ ["openai", "openai"],
827
+ ["google", "google"],
828
+ ["az.ai.openai", "azure"],
829
+ ["azure", "azure"],
830
+ ["openrouter", "openrouter"]
831
+ ];
832
+ function canonicalModelProvider(provider) {
833
+ var _a;
834
+ const normalizedProvider = provider.trim().toLowerCase();
835
+ if (normalizedProvider === "gateway") return "vercel";
836
+ const match = MODEL_PROVIDER_NAMES.find(
837
+ ([family]) => normalizedProvider === family || normalizedProvider.startsWith(`${family}.`)
838
+ );
839
+ return (_a = match == null ? void 0 : match[1]) != null ? _a : normalizedProvider;
840
+ }
841
+ function tokenCount(value) {
842
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
843
+ }
844
+ function buildRawModelUsageAttributes(usage) {
845
+ const providerReportedCost = typeof usage.providerReportedCost === "number" && Number.isFinite(usage.providerReportedCost) && usage.providerReportedCost >= 0 ? usage.providerReportedCost : void 0;
846
+ return [
847
+ attrInt(MODEL_USAGE_ATTRIBUTES.inputTokens, tokenCount(usage.inputTokens)),
848
+ attrInt(MODEL_USAGE_ATTRIBUTES.outputTokens, tokenCount(usage.outputTokens)),
849
+ attrInt(MODEL_USAGE_ATTRIBUTES.reasoningTokens, tokenCount(usage.reasoningTokens)),
850
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheReadInputTokens, tokenCount(usage.cacheReadInputTokens)),
851
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheWriteInputTokens, tokenCount(usage.cacheWriteInputTokens)),
852
+ attrDouble(MODEL_USAGE_ATTRIBUTES.providerReportedCost, providerReportedCost)
853
+ ].filter((attribute) => attribute !== void 0);
854
+ }
855
+ var STRING_ALIASES = {
856
+ [MODEL_USAGE_ATTRIBUTES.requestModel]: ["ai.model.id", "ai.model"]
857
+ };
858
+ var NUMBER_ALIASES = {
859
+ [MODEL_USAGE_ATTRIBUTES.inputTokens]: [
860
+ "gen_ai.usage.prompt_tokens",
861
+ "ai.usage.prompt_tokens",
862
+ "ai.usage.promptTokens",
863
+ "ai.usage.input_tokens",
864
+ "ai.usage.inputTokens"
865
+ ],
866
+ [MODEL_USAGE_ATTRIBUTES.outputTokens]: [
867
+ "gen_ai.usage.completion_tokens",
868
+ "ai.usage.completion_tokens",
869
+ "ai.usage.completionTokens",
870
+ "ai.usage.output_tokens",
871
+ "ai.usage.outputTokens"
872
+ ],
873
+ [MODEL_USAGE_ATTRIBUTES.reasoningTokens]: [
874
+ "gen_ai.usage.reasoning.output_tokens",
875
+ "ai.usage.reasoningTokens",
876
+ "ai.usage.thoughts_tokens"
877
+ ],
878
+ [MODEL_USAGE_ATTRIBUTES.cacheReadInputTokens]: [
879
+ "gen_ai.usage.cache_read_tokens",
880
+ "gen_ai.usage.cache_read.input_tokens",
881
+ "ai.usage.cachedInputTokens",
882
+ "ai.usage.cached_tokens",
883
+ "ai.usage.cache_read_tokens",
884
+ "ai.usage.cache_read_input_tokens",
885
+ "ai.usage.cacheReadInputTokens"
886
+ ],
887
+ [MODEL_USAGE_ATTRIBUTES.cacheWriteInputTokens]: [
888
+ "gen_ai.usage.cache_creation_input_tokens",
889
+ "gen_ai.usage.cache_creation.input_tokens",
890
+ "ai.usage.cacheWriteInputTokens",
891
+ "ai.usage.cache_creation_input_tokens",
892
+ "ai.usage.cacheCreationInputTokens"
893
+ ]
894
+ };
895
+ function hasAttribute(attributes, key) {
896
+ return attributes.some((attribute) => attribute.key === key);
897
+ }
898
+ function findStringAttribute(attributes, keys) {
899
+ return attributes.find(
900
+ (attribute) => keys.includes(attribute.key) && typeof attribute.value.stringValue === "string" && attribute.value.stringValue.length > 0
901
+ );
902
+ }
903
+ function findNumberAttribute(attributes, keys) {
904
+ return attributes.find((attribute) => {
905
+ if (!keys.includes(attribute.key)) return false;
906
+ if (typeof attribute.value.doubleValue === "number" && Number.isSafeInteger(attribute.value.doubleValue) && attribute.value.doubleValue >= 0) {
907
+ return true;
908
+ }
909
+ const intValue = attribute.value.intValue;
910
+ return integerValue(intValue) !== void 0;
911
+ });
912
+ }
913
+ function integerValue(value) {
914
+ if (typeof value === "number") {
915
+ return Number.isSafeInteger(value) && value >= 0 ? value : void 0;
916
+ }
917
+ if (typeof value !== "string") return void 0;
918
+ const parsed = Number(value);
919
+ return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : void 0;
920
+ }
921
+ function appendAlias(attributes, target, source) {
922
+ if (!source || hasAttribute(attributes, target)) return attributes;
923
+ return [...attributes, { key: target, value: { ...source.value } }];
924
+ }
925
+ function appendProviderAlias(attributes) {
926
+ if (hasAttribute(attributes, MODEL_USAGE_ATTRIBUTES.providerName)) return attributes;
927
+ const source = findStringAttribute(attributes, ["gen_ai.system", "ai.model.provider"]);
928
+ if (!(source == null ? void 0 : source.value.stringValue)) return attributes;
929
+ const attribute = attrString(
930
+ MODEL_USAGE_ATTRIBUTES.providerName,
931
+ canonicalModelProvider(source.value.stringValue)
932
+ );
933
+ return attribute ? [...attributes, attribute] : attributes;
934
+ }
935
+ function normalizeModelUsageSpan(span) {
936
+ const original = span.attributes;
937
+ if (!original || original.length === 0) return span;
938
+ const responseModel = findStringAttribute(original, [
939
+ MODEL_USAGE_ATTRIBUTES.responseModel,
940
+ "ai.response.model"
941
+ ]);
942
+ if (!responseModel) return span;
943
+ let attributes = appendAlias(original, MODEL_USAGE_ATTRIBUTES.responseModel, responseModel);
944
+ attributes = appendProviderAlias(attributes);
945
+ for (const [target, aliases] of Object.entries(STRING_ALIASES)) {
946
+ attributes = appendAlias(attributes, target, findStringAttribute(attributes, aliases));
947
+ }
948
+ for (const [target, aliases] of Object.entries(NUMBER_ALIASES)) {
949
+ attributes = appendAlias(attributes, target, findNumberAttribute(attributes, aliases));
950
+ }
951
+ return attributes === original ? span : { ...span, attributes };
952
+ }
784
953
  var DEFAULT_SECRET_KEY_NAMES = [
785
954
  "apikey",
786
955
  "apisecret",
@@ -981,6 +1150,10 @@ var TraceShipper = class {
981
1150
  return null;
982
1151
  }
983
1152
  }
1153
+ try {
1154
+ current = normalizeModelUsageSpan(current);
1155
+ } catch (e) {
1156
+ }
984
1157
  if (!this.disableDefaultRedaction) {
985
1158
  current = defaultTransformSpan(current);
986
1159
  }
@@ -1277,7 +1450,7 @@ function resolveLocalWorkshopUrl(fileValue) {
1277
1450
  // package.json
1278
1451
  var package_default = {
1279
1452
  name: "@raindrop-ai/opencode-plugin",
1280
- version: "0.1.1",
1453
+ version: "0.1.3",
1281
1454
  description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
1282
1455
  type: "module",
1283
1456
  main: "dist/index.js",
@@ -1382,6 +1555,9 @@ var TraceShipper2 = class extends TraceShipper {
1382
1555
  }
1383
1556
  };
1384
1557
 
1558
+ // src/tracing.ts
1559
+ import { hostname as osHostname } from "os";
1560
+
1385
1561
  // src/bounded.ts
1386
1562
  var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
1387
1563
  var MAX_TEXT_FIELD_CHARS = 1e6;
@@ -1462,6 +1638,47 @@ function boundedStringify(value, limit = MAX_TEXT_FIELD_CHARS) {
1462
1638
  }
1463
1639
  }
1464
1640
 
1641
+ // src/model-spend.ts
1642
+ function parseOpenCodeAssistantTokens(tokens) {
1643
+ const input = tokens["input"];
1644
+ const output = tokens["output"];
1645
+ if (typeof input !== "number" || typeof output !== "number") return void 0;
1646
+ const reasoning = tokens["reasoning"];
1647
+ const cache = tokens["cache"];
1648
+ const cacheObj = cache && typeof cache === "object" && cache !== null ? cache : void 0;
1649
+ return {
1650
+ input,
1651
+ output,
1652
+ reasoning: typeof reasoning === "number" ? reasoning : 0,
1653
+ cacheRead: typeof (cacheObj == null ? void 0 : cacheObj["read"]) === "number" ? cacheObj["read"] : 0,
1654
+ cacheWrite: typeof (cacheObj == null ? void 0 : cacheObj["write"]) === "number" ? cacheObj["write"] : 0
1655
+ };
1656
+ }
1657
+ function modelSpendSpanAttributes(input) {
1658
+ const providerAttr = attrString("gen_ai.provider.name", canonicalModelProvider(input.providerID));
1659
+ const usageAttrs = buildRawModelUsageAttributes({
1660
+ inputTokens: input.tokens.input,
1661
+ outputTokens: input.tokens.output,
1662
+ reasoningTokens: input.tokens.reasoning,
1663
+ cacheReadInputTokens: input.tokens.cacheRead,
1664
+ cacheWriteInputTokens: input.tokens.cacheWrite
1665
+ });
1666
+ return providerAttr ? [providerAttr, ...usageAttrs] : usageAttrs;
1667
+ }
1668
+ function buildLlmGenAiAttributes(input) {
1669
+ const parsed = parseOpenCodeAssistantTokens(input.tokens);
1670
+ if (!parsed) return [];
1671
+ return [
1672
+ // OpenCode has already normalized both token axes. Provenance lets Dawn
1673
+ // select that contract without the SDK or customer declaring semantics.
1674
+ attrString(MODEL_USAGE_ATTRIBUTES.usageSource, "opencode"),
1675
+ attrString("gen_ai.system", input.providerID),
1676
+ attrString("gen_ai.request.model", input.modelID),
1677
+ attrString("gen_ai.response.model", input.modelID),
1678
+ ...modelSpendSpanAttributes({ providerID: input.providerID, tokens: parsed })
1679
+ ].filter((attribute) => attribute !== void 0);
1680
+ }
1681
+
1465
1682
  // src/tracing-linkage-helpers.ts
1466
1683
  function createSessionParentMapHelpers({
1467
1684
  sessions: sessions2,
@@ -1626,7 +1843,7 @@ function getHostname() {
1626
1843
  try {
1627
1844
  const bunGlobal = globalThis;
1628
1845
  if ((_a = bunGlobal.Bun) == null ? void 0 : _a.hostname) return bunGlobal.Bun.hostname;
1629
- return __require("os").hostname();
1846
+ return osHostname();
1630
1847
  } catch (e) {
1631
1848
  const h = process.env["HOSTNAME"];
1632
1849
  if (h === void 0 || h === "") throw new Error("HOSTNAME env is not set");
@@ -1775,7 +1992,6 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
1775
1992
  const tokensObj = tokens;
1776
1993
  const inputTokens = tokensObj["input"];
1777
1994
  const outputTokens = tokensObj["output"];
1778
- const reasoningTokens = tokensObj["reasoning"];
1779
1995
  if (typeof inputTokens !== "number" || typeof outputTokens !== "number")
1780
1996
  throw new Error("message.updated: tokens.input and tokens.output must be numbers");
1781
1997
  const providerID = info2["providerID"];
@@ -1793,13 +2009,7 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
1793
2009
  state.turnOutputParts.push(outputText);
1794
2010
  }
1795
2011
  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
- }
2012
+ if (outputText === void 0 && !isToolCallsCompletion) return;
1803
2013
  const msgError = info2["error"];
1804
2014
  const errorForSpan = msgError ? (() => {
1805
2015
  var _a2, _b2;
@@ -1812,20 +2022,12 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
1812
2022
  type: ${name != null ? name : "UnknownError"}`;
1813
2023
  })() : void 0;
1814
2024
  if (isToolCallsCompletion) {
1815
- if (reasoningText === void 0)
1816
- throw new Error("message.updated: reasoningParts missing for tool-call continuation");
2025
+ const responseText = reasoningText !== void 0 && reasoningText.trim().length > 0 ? reasoningText.trim() : outputText !== void 0 && outputText.trim().length > 0 ? outputText.trim() : "";
1817
2026
  const llmAttrs = [
1818
2027
  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)
2028
+ attrString("ai.response.text", responseText),
2029
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
1825
2030
  ];
1826
- if (reasoningTokens != null && typeof reasoningTokens === "number" && reasoningTokens > 0) {
1827
- llmAttrs.push(attrInt("gen_ai.usage.reasoning_tokens", reasoningTokens));
1828
- }
1829
2031
  if (state.currentSystemPrompt) {
1830
2032
  llmAttrs.push(
1831
2033
  attrString("gen_ai.prompt.0.role", "system"),
@@ -1860,15 +2062,8 @@ type: ${name != null ? name : "UnknownError"}`;
1860
2062
  buildPromptMessages(state.currentSystemPrompt, state.currentInput)
1861
2063
  ),
1862
2064
  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)
2065
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
1868
2066
  ];
1869
- if (reasoningTokens != null && typeof reasoningTokens === "number" && reasoningTokens > 0) {
1870
- llmAttrs.push(attrInt("gen_ai.usage.reasoning_tokens", reasoningTokens));
1871
- }
1872
2067
  if (state.currentSystemPrompt) {
1873
2068
  llmAttrs.push(
1874
2069
  attrString("gen_ai.prompt.0.role", "system"),
@@ -1912,19 +2107,12 @@ type: ${name != null ? name : "UnknownError"}`;
1912
2107
  buildPromptMessages(state.currentSystemPrompt, state.currentInput)
1913
2108
  ),
1914
2109
  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)
2110
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
1920
2111
  ];
1921
2112
  if (isCompaction) {
1922
2113
  rootAttrs.push(attrString("is_compaction", "true"));
1923
2114
  }
1924
2115
  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
2116
  if (state.currentSystemPrompt) {
1929
2117
  rootAttrs.push(
1930
2118
  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.3",
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.1"
41
41
  },
42
42
  "tsup": {
43
43
  "entry": [