@raindrop-ai/opencode-plugin 0.1.0 → 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 +274 -42
  2. package/dist/index.js +274 -49
  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.0",
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,
@@ -1576,6 +1828,8 @@ function createSessionState(sessionId) {
1576
1828
  return {
1577
1829
  sessionId,
1578
1830
  currentInput: "",
1831
+ turnOutputParts: [],
1832
+ turnOutputTokens: 0,
1579
1833
  outputParts: /* @__PURE__ */ new Map(),
1580
1834
  reasoningParts: /* @__PURE__ */ new Map(),
1581
1835
  toolSpanStarts: /* @__PURE__ */ new Map(),
@@ -1643,7 +1897,7 @@ function getHostname() {
1643
1897
  try {
1644
1898
  const bunGlobal = globalThis;
1645
1899
  if ((_a = bunGlobal.Bun) == null ? void 0 : _a.hostname) return bunGlobal.Bun.hostname;
1646
- return require("os").hostname();
1900
+ return (0, import_node_os2.hostname)();
1647
1901
  } catch (e) {
1648
1902
  const h = process.env["HOSTNAME"];
1649
1903
  if (h === void 0 || h === "") throw new Error("HOSTNAME env is not set");
@@ -1792,7 +2046,6 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
1792
2046
  const tokensObj = tokens;
1793
2047
  const inputTokens = tokensObj["input"];
1794
2048
  const outputTokens = tokensObj["output"];
1795
- const reasoningTokens = tokensObj["reasoning"];
1796
2049
  if (typeof inputTokens !== "number" || typeof outputTokens !== "number")
1797
2050
  throw new Error("message.updated: tokens.input and tokens.output must be numbers");
1798
2051
  const providerID = info2["providerID"];
@@ -1806,13 +2059,11 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
1806
2059
  const outputText = state.outputParts.get(messageId);
1807
2060
  const reasoningText = state.reasoningParts.get(messageId);
1808
2061
  const isToolCallsCompletion = finishReason === "tool-calls";
1809
- const hasIntermediateReasoning = reasoningText !== void 0 && reasoningText.trim().length > 0;
1810
- if (outputText === void 0 && !(isToolCallsCompletion && hasIntermediateReasoning))
1811
- return;
1812
- if (isToolCallsCompletion && !hasIntermediateReasoning) {
1813
- await traceShipper.flush();
1814
- return;
2062
+ if (outputText !== void 0 && outputText.trim().length > 0) {
2063
+ state.turnOutputParts.push(outputText);
1815
2064
  }
2065
+ state.turnOutputTokens += outputTokens;
2066
+ if (outputText === void 0 && !isToolCallsCompletion) return;
1816
2067
  const msgError = info2["error"];
1817
2068
  const errorForSpan = msgError ? (() => {
1818
2069
  var _a2, _b2;
@@ -1825,20 +2076,12 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
1825
2076
  type: ${name != null ? name : "UnknownError"}`;
1826
2077
  })() : void 0;
1827
2078
  if (isToolCallsCompletion) {
1828
- if (reasoningText === void 0)
1829
- 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() : "";
1830
2080
  const llmAttrs = [
1831
2081
  attrString("ai.operationId", "generateText"),
1832
- attrString("ai.response.text", reasoningText),
1833
- attrString("gen_ai.system", providerID),
1834
- attrString("gen_ai.request.model", modelID),
1835
- attrString("gen_ai.response.model", modelID),
1836
- attrInt("gen_ai.usage.input_tokens", inputTokens),
1837
- attrInt("gen_ai.usage.output_tokens", outputTokens)
2082
+ attrString("ai.response.text", responseText),
2083
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
1838
2084
  ];
1839
- if (reasoningTokens != null && typeof reasoningTokens === "number" && reasoningTokens > 0) {
1840
- llmAttrs.push(attrInt("gen_ai.usage.reasoning_tokens", reasoningTokens));
1841
- }
1842
2085
  if (state.currentSystemPrompt) {
1843
2086
  llmAttrs.push(
1844
2087
  attrString("gen_ai.prompt.0.role", "system"),
@@ -1860,6 +2103,7 @@ type: ${name != null ? name : "UnknownError"}`;
1860
2103
  return;
1861
2104
  }
1862
2105
  if (outputText === void 0) return;
2106
+ const turnOutputText = capText2(state.turnOutputParts.join("\n\n"));
1863
2107
  const rootSpan = state.currentRootSpan;
1864
2108
  const isCompaction = state.isCompacting === true;
1865
2109
  if (state.parentId) {
@@ -1871,16 +2115,9 @@ type: ${name != null ? name : "UnknownError"}`;
1871
2115
  "ai.prompt.messages",
1872
2116
  buildPromptMessages(state.currentSystemPrompt, state.currentInput)
1873
2117
  ),
1874
- attrString("ai.response.text", outputText),
1875
- attrString("gen_ai.system", providerID),
1876
- attrString("gen_ai.request.model", modelID),
1877
- attrString("gen_ai.response.model", modelID),
1878
- attrInt("gen_ai.usage.input_tokens", inputTokens),
1879
- attrInt("gen_ai.usage.output_tokens", outputTokens)
2118
+ attrString("ai.response.text", turnOutputText),
2119
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
1880
2120
  ];
1881
- if (reasoningTokens != null && typeof reasoningTokens === "number" && reasoningTokens > 0) {
1882
- llmAttrs.push(attrInt("gen_ai.usage.reasoning_tokens", reasoningTokens));
1883
- }
1884
2121
  if (state.currentSystemPrompt) {
1885
2122
  llmAttrs.push(
1886
2123
  attrString("gen_ai.prompt.0.role", "system"),
@@ -1923,20 +2160,13 @@ type: ${name != null ? name : "UnknownError"}`;
1923
2160
  "ai.prompt.messages",
1924
2161
  buildPromptMessages(state.currentSystemPrompt, state.currentInput)
1925
2162
  ),
1926
- attrString("ai.response.text", outputText),
1927
- attrString("gen_ai.system", providerID),
1928
- attrString("gen_ai.request.model", modelID),
1929
- attrString("gen_ai.response.model", modelID),
1930
- attrInt("gen_ai.usage.input_tokens", inputTokens),
1931
- attrInt("gen_ai.usage.output_tokens", outputTokens)
2163
+ attrString("ai.response.text", turnOutputText),
2164
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
1932
2165
  ];
1933
2166
  if (isCompaction) {
1934
2167
  rootAttrs.push(attrString("is_compaction", "true"));
1935
2168
  }
1936
2169
  rootAttrs.push(attrString("message_id", messageId));
1937
- if (reasoningTokens != null && typeof reasoningTokens === "number" && reasoningTokens > 0) {
1938
- rootAttrs.push(attrInt("gen_ai.usage.reasoning_tokens", reasoningTokens));
1939
- }
1940
2170
  if (state.currentSystemPrompt) {
1941
2171
  rootAttrs.push(
1942
2172
  attrString("gen_ai.prompt.0.role", "system"),
@@ -1952,7 +2182,7 @@ type: ${name != null ? name : "UnknownError"}`;
1952
2182
  }
1953
2183
  }
1954
2184
  state.currentRootSpan = void 0;
1955
- const hasAssistantResponse = outputText.trim().length > 0;
2185
+ const hasAssistantResponse = turnOutputText.trim().length > 0;
1956
2186
  if (!hasAssistantResponse && !msgError) {
1957
2187
  await traceShipper.flush();
1958
2188
  state.currentEventId = void 0;
@@ -1962,10 +2192,10 @@ type: ${name != null ? name : "UnknownError"}`;
1962
2192
  await eventShipper.finish(state.currentEventId, {
1963
2193
  userId: (_d = (_c = state.eventMetadata) == null ? void 0 : _c.userId) != null ? _d : state.sessionId,
1964
2194
  model: modelID,
1965
- output: msgError ? void 0 : outputText,
2195
+ output: msgError ? void 0 : turnOutputText,
1966
2196
  usage: {
1967
2197
  promptTokens: inputTokens,
1968
- completionTokens: outputTokens
2198
+ completionTokens: state.turnOutputTokens
1969
2199
  },
1970
2200
  error: msgError ? {
1971
2201
  name: msgError.name,
@@ -2099,6 +2329,8 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
2099
2329
  };
2100
2330
  const userText = capText2(textParts.join("\n"));
2101
2331
  state.currentInput = userText;
2332
+ state.turnOutputParts = [];
2333
+ state.turnOutputTokens = 0;
2102
2334
  if (state.parentId) {
2103
2335
  if (!state.parentTaskSpanIds || !state.parentEventContext) {
2104
2336
  return;
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.0",
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,
@@ -1557,6 +1802,8 @@ function createSessionState(sessionId) {
1557
1802
  return {
1558
1803
  sessionId,
1559
1804
  currentInput: "",
1805
+ turnOutputParts: [],
1806
+ turnOutputTokens: 0,
1560
1807
  outputParts: /* @__PURE__ */ new Map(),
1561
1808
  reasoningParts: /* @__PURE__ */ new Map(),
1562
1809
  toolSpanStarts: /* @__PURE__ */ new Map(),
@@ -1624,7 +1871,7 @@ function getHostname() {
1624
1871
  try {
1625
1872
  const bunGlobal = globalThis;
1626
1873
  if ((_a = bunGlobal.Bun) == null ? void 0 : _a.hostname) return bunGlobal.Bun.hostname;
1627
- return __require("os").hostname();
1874
+ return osHostname();
1628
1875
  } catch (e) {
1629
1876
  const h = process.env["HOSTNAME"];
1630
1877
  if (h === void 0 || h === "") throw new Error("HOSTNAME env is not set");
@@ -1773,7 +2020,6 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
1773
2020
  const tokensObj = tokens;
1774
2021
  const inputTokens = tokensObj["input"];
1775
2022
  const outputTokens = tokensObj["output"];
1776
- const reasoningTokens = tokensObj["reasoning"];
1777
2023
  if (typeof inputTokens !== "number" || typeof outputTokens !== "number")
1778
2024
  throw new Error("message.updated: tokens.input and tokens.output must be numbers");
1779
2025
  const providerID = info2["providerID"];
@@ -1787,13 +2033,11 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
1787
2033
  const outputText = state.outputParts.get(messageId);
1788
2034
  const reasoningText = state.reasoningParts.get(messageId);
1789
2035
  const isToolCallsCompletion = finishReason === "tool-calls";
1790
- const hasIntermediateReasoning = reasoningText !== void 0 && reasoningText.trim().length > 0;
1791
- if (outputText === void 0 && !(isToolCallsCompletion && hasIntermediateReasoning))
1792
- return;
1793
- if (isToolCallsCompletion && !hasIntermediateReasoning) {
1794
- await traceShipper.flush();
1795
- return;
2036
+ if (outputText !== void 0 && outputText.trim().length > 0) {
2037
+ state.turnOutputParts.push(outputText);
1796
2038
  }
2039
+ state.turnOutputTokens += outputTokens;
2040
+ if (outputText === void 0 && !isToolCallsCompletion) return;
1797
2041
  const msgError = info2["error"];
1798
2042
  const errorForSpan = msgError ? (() => {
1799
2043
  var _a2, _b2;
@@ -1806,20 +2050,12 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
1806
2050
  type: ${name != null ? name : "UnknownError"}`;
1807
2051
  })() : void 0;
1808
2052
  if (isToolCallsCompletion) {
1809
- if (reasoningText === void 0)
1810
- 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() : "";
1811
2054
  const llmAttrs = [
1812
2055
  attrString("ai.operationId", "generateText"),
1813
- attrString("ai.response.text", reasoningText),
1814
- attrString("gen_ai.system", providerID),
1815
- attrString("gen_ai.request.model", modelID),
1816
- attrString("gen_ai.response.model", modelID),
1817
- attrInt("gen_ai.usage.input_tokens", inputTokens),
1818
- attrInt("gen_ai.usage.output_tokens", outputTokens)
2056
+ attrString("ai.response.text", responseText),
2057
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
1819
2058
  ];
1820
- if (reasoningTokens != null && typeof reasoningTokens === "number" && reasoningTokens > 0) {
1821
- llmAttrs.push(attrInt("gen_ai.usage.reasoning_tokens", reasoningTokens));
1822
- }
1823
2059
  if (state.currentSystemPrompt) {
1824
2060
  llmAttrs.push(
1825
2061
  attrString("gen_ai.prompt.0.role", "system"),
@@ -1841,6 +2077,7 @@ type: ${name != null ? name : "UnknownError"}`;
1841
2077
  return;
1842
2078
  }
1843
2079
  if (outputText === void 0) return;
2080
+ const turnOutputText = capText2(state.turnOutputParts.join("\n\n"));
1844
2081
  const rootSpan = state.currentRootSpan;
1845
2082
  const isCompaction = state.isCompacting === true;
1846
2083
  if (state.parentId) {
@@ -1852,16 +2089,9 @@ type: ${name != null ? name : "UnknownError"}`;
1852
2089
  "ai.prompt.messages",
1853
2090
  buildPromptMessages(state.currentSystemPrompt, state.currentInput)
1854
2091
  ),
1855
- attrString("ai.response.text", outputText),
1856
- attrString("gen_ai.system", providerID),
1857
- attrString("gen_ai.request.model", modelID),
1858
- attrString("gen_ai.response.model", modelID),
1859
- attrInt("gen_ai.usage.input_tokens", inputTokens),
1860
- attrInt("gen_ai.usage.output_tokens", outputTokens)
2092
+ attrString("ai.response.text", turnOutputText),
2093
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
1861
2094
  ];
1862
- if (reasoningTokens != null && typeof reasoningTokens === "number" && reasoningTokens > 0) {
1863
- llmAttrs.push(attrInt("gen_ai.usage.reasoning_tokens", reasoningTokens));
1864
- }
1865
2095
  if (state.currentSystemPrompt) {
1866
2096
  llmAttrs.push(
1867
2097
  attrString("gen_ai.prompt.0.role", "system"),
@@ -1904,20 +2134,13 @@ type: ${name != null ? name : "UnknownError"}`;
1904
2134
  "ai.prompt.messages",
1905
2135
  buildPromptMessages(state.currentSystemPrompt, state.currentInput)
1906
2136
  ),
1907
- attrString("ai.response.text", outputText),
1908
- attrString("gen_ai.system", providerID),
1909
- attrString("gen_ai.request.model", modelID),
1910
- attrString("gen_ai.response.model", modelID),
1911
- attrInt("gen_ai.usage.input_tokens", inputTokens),
1912
- attrInt("gen_ai.usage.output_tokens", outputTokens)
2137
+ attrString("ai.response.text", turnOutputText),
2138
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
1913
2139
  ];
1914
2140
  if (isCompaction) {
1915
2141
  rootAttrs.push(attrString("is_compaction", "true"));
1916
2142
  }
1917
2143
  rootAttrs.push(attrString("message_id", messageId));
1918
- if (reasoningTokens != null && typeof reasoningTokens === "number" && reasoningTokens > 0) {
1919
- rootAttrs.push(attrInt("gen_ai.usage.reasoning_tokens", reasoningTokens));
1920
- }
1921
2144
  if (state.currentSystemPrompt) {
1922
2145
  rootAttrs.push(
1923
2146
  attrString("gen_ai.prompt.0.role", "system"),
@@ -1933,7 +2156,7 @@ type: ${name != null ? name : "UnknownError"}`;
1933
2156
  }
1934
2157
  }
1935
2158
  state.currentRootSpan = void 0;
1936
- const hasAssistantResponse = outputText.trim().length > 0;
2159
+ const hasAssistantResponse = turnOutputText.trim().length > 0;
1937
2160
  if (!hasAssistantResponse && !msgError) {
1938
2161
  await traceShipper.flush();
1939
2162
  state.currentEventId = void 0;
@@ -1943,10 +2166,10 @@ type: ${name != null ? name : "UnknownError"}`;
1943
2166
  await eventShipper.finish(state.currentEventId, {
1944
2167
  userId: (_d = (_c = state.eventMetadata) == null ? void 0 : _c.userId) != null ? _d : state.sessionId,
1945
2168
  model: modelID,
1946
- output: msgError ? void 0 : outputText,
2169
+ output: msgError ? void 0 : turnOutputText,
1947
2170
  usage: {
1948
2171
  promptTokens: inputTokens,
1949
- completionTokens: outputTokens
2172
+ completionTokens: state.turnOutputTokens
1950
2173
  },
1951
2174
  error: msgError ? {
1952
2175
  name: msgError.name,
@@ -2080,6 +2303,8 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
2080
2303
  };
2081
2304
  const userText = capText2(textParts.join("\n"));
2082
2305
  state.currentInput = userText;
2306
+ state.turnOutputParts = [];
2307
+ state.turnOutputTokens = 0;
2083
2308
  if (state.parentId) {
2084
2309
  if (!state.parentTaskSpanIds || !state.parentEventContext) {
2085
2310
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raindrop-ai/opencode-plugin",
3
- "version": "0.1.0",
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": [