@raindrop-ai/claude-code 0.0.20 → 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "raindrop",
3
- "version": "0.0.20",
3
+ "version": "0.0.21",
4
4
  "description": "Observability for Claude Code \u2014 automatic session, tool call, and prompt tracing",
5
5
  "author": {
6
6
  "name": "Raindrop AI",
package/README.md CHANGED
@@ -45,6 +45,28 @@ Tag events with product names or custom metadata via `.claude/settings.json`:
45
45
  }
46
46
  ```
47
47
 
48
+ ## Tool catalog capture (`ai.prompt.tools`)
49
+
50
+ Claude Code hooks and transcripts never expose the request Claude Code sends to the model, so this
51
+ integration cannot observe the tool catalog and records nothing by default (the attribute stays
52
+ absent, meaning "unknown"). Supply the catalog yourself with `tools` — an array of tool
53
+ declarations in any shape `@raindrop-ai/core` recognizes (`{ name, description?, parameters }`,
54
+ `{ type: "function", name, inputSchema }`, OpenAI/Anthropic wrappers, ...):
55
+
56
+ ```json
57
+ {
58
+ "tools": [
59
+ { "name": "read_file", "description": "Read a file", "parameters": { "type": "object", "properties": { "path": { "type": "string" } }, "required": ["path"] } }
60
+ ]
61
+ }
62
+ ```
63
+
64
+ Set it in `~/.config/raindrop/config.json` (`tools`) or as JSON in the `RAINDROP_TOOLS` environment
65
+ variable (the variable wins). The override is recorded verbatim as `ai.prompt.tools` on every LLM
66
+ span reconstructed from the transcript (`gen_ai.response.model` spans), never on tool-call,
67
+ subagent, or permission spans, and `tools: []` records an explicitly empty catalog. Nothing is
68
+ inferred from hook payloads: implicit capture is not possible here.
69
+
48
70
  ## Custom Self-Diagnostics Signals
49
71
 
50
72
  Replace the built-in signal categories with your own via `~/.config/raindrop/config.json`:
package/dist/cli.js CHANGED
@@ -69,7 +69,7 @@ function evalScopeAttributes() {
69
69
  return { [EVAL_CORRELATION_ID_ATTRIBUTE]: scope.correlationId };
70
70
  }
71
71
 
72
- // ../core/dist/chunk-WJOR6M7H.js
72
+ // ../core/dist/chunk-I6TVJQEC.js
73
73
  var STATE_KEY = /* @__PURE__ */ Symbol.for("raindrop.tracing.replayTraceRoutingState");
74
74
  function state() {
75
75
  const holder = globalThis;
@@ -2056,6 +2056,158 @@ var TraceShipper = class {
2056
2056
  }
2057
2057
  }
2058
2058
  };
2059
+ var PROMPT_TOOLS_ATTRIBUTE = "ai.prompt.tools";
2060
+ function isRecord(value) {
2061
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2062
+ }
2063
+ var JSON_SCHEMA_KEYS = [
2064
+ "type",
2065
+ "properties",
2066
+ "$schema",
2067
+ "$ref",
2068
+ "anyOf",
2069
+ "oneOf",
2070
+ "allOf",
2071
+ "enum",
2072
+ "const",
2073
+ "items",
2074
+ "additionalProperties",
2075
+ "required"
2076
+ ];
2077
+ function isJsonSchema(value) {
2078
+ if (!isRecord(value)) return false;
2079
+ if (typeof value["~standard"] === "object") return false;
2080
+ if (isRecord(value._def) || isRecord(value._zod)) return false;
2081
+ if (Object.keys(value).length === 0) return true;
2082
+ return JSON_SCHEMA_KEYS.some((key) => key in value);
2083
+ }
2084
+ function toJsonSchema(value, options) {
2085
+ if (value === void 0 || value === null) return void 0;
2086
+ if (isJsonSchema(value)) return value;
2087
+ if (isRecord(value) && isJsonSchema(value.jsonSchema)) return value.jsonSchema;
2088
+ if (isRecord(value) && isJsonSchema(value.json)) return value.json;
2089
+ if (options == null ? void 0 : options.schema) {
2090
+ try {
2091
+ return options.schema(value);
2092
+ } catch (e) {
2093
+ return void 0;
2094
+ }
2095
+ }
2096
+ return void 0;
2097
+ }
2098
+ function pickString(value) {
2099
+ return typeof value === "string" && value.length > 0 ? value : void 0;
2100
+ }
2101
+ function functionTool(name, description, schema, options) {
2102
+ const tool = { type: "function", name };
2103
+ const desc = pickString(description);
2104
+ if (desc !== void 0) tool.description = desc;
2105
+ const inputSchema = toJsonSchema(schema, options);
2106
+ if (inputSchema !== void 0) tool.inputSchema = inputSchema;
2107
+ return tool;
2108
+ }
2109
+ function normalizeToolDeclaration(tool, options, fallbackName) {
2110
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
2111
+ if (!isRecord(tool)) return void 0;
2112
+ if (tool.type === "provider-defined" || tool.type === "provider") {
2113
+ const name2 = (_b = (_a = pickString(tool.name)) != null ? _a : pickString(tool.id)) != null ? _b : fallbackName;
2114
+ if (name2 === void 0) return void 0;
2115
+ const declaration = { type: "provider-defined", name: name2 };
2116
+ const id = pickString(tool.id);
2117
+ if (id !== void 0) declaration.id = id;
2118
+ if (isRecord(tool.args)) declaration.args = tool.args;
2119
+ return declaration;
2120
+ }
2121
+ if (isRecord(tool.toolSpec)) {
2122
+ const spec = tool.toolSpec;
2123
+ const name2 = (_c = pickString(spec.name)) != null ? _c : fallbackName;
2124
+ if (name2 === void 0) return void 0;
2125
+ return functionTool(name2, spec.description, spec.inputSchema, options);
2126
+ }
2127
+ if (isRecord(tool.function)) {
2128
+ const fn = tool.function;
2129
+ const name2 = (_d = pickString(fn.name)) != null ? _d : fallbackName;
2130
+ if (name2 === void 0) return void 0;
2131
+ return functionTool(name2, fn.description, (_e = fn.parameters) != null ? _e : fn.input_schema, options);
2132
+ }
2133
+ const name = (_g = (_f = pickString(tool.name)) != null ? _f : pickString(tool.id)) != null ? _g : fallbackName;
2134
+ if (name === void 0) return void 0;
2135
+ const type = pickString(tool.type);
2136
+ if (type !== void 0 && type !== "function" && type !== "dynamic" && type !== "custom") {
2137
+ if (!("input_schema" in tool) && !("inputSchema" in tool) && !("parameters" in tool)) {
2138
+ return { type: "provider-defined", id: type, name };
2139
+ }
2140
+ }
2141
+ return functionTool(
2142
+ name,
2143
+ tool.description,
2144
+ (_j = (_i = (_h = tool.inputSchema) != null ? _h : tool.input_schema) != null ? _i : tool.parameters) != null ? _j : tool.schema,
2145
+ options
2146
+ );
2147
+ }
2148
+ var NOT_A_GEMINI_GROUP_KEYS = ["name", "id", "type", "function", "toolSpec", "cachePoint"];
2149
+ function isGeminiToolGroup(tool) {
2150
+ if (Array.isArray(tool.functionDeclarations)) return true;
2151
+ const keys = Object.keys(tool);
2152
+ if (keys.length === 0 || NOT_A_GEMINI_GROUP_KEYS.some((key) => key in tool)) return false;
2153
+ return keys.every((key) => isRecord(tool[key]));
2154
+ }
2155
+ function expandGeminiTool(tool, options) {
2156
+ const declarations = [];
2157
+ for (const [key, value] of Object.entries(tool)) {
2158
+ if (key === "functionDeclarations" && Array.isArray(value)) {
2159
+ for (const declaration of value) {
2160
+ const normalized = normalizeToolDeclaration(declaration, options);
2161
+ if (normalized) declarations.push(normalized);
2162
+ }
2163
+ } else if (isRecord(value)) {
2164
+ declarations.push({ type: "provider-defined", id: key, name: key, args: value });
2165
+ }
2166
+ }
2167
+ return declarations;
2168
+ }
2169
+ function normalizeToolDeclarations(tools, options) {
2170
+ if (tools === void 0 || tools === null) return void 0;
2171
+ if (Array.isArray(tools)) {
2172
+ return tools.flatMap((tool) => {
2173
+ if (isRecord(tool) && isGeminiToolGroup(tool)) {
2174
+ return expandGeminiTool(tool, options);
2175
+ }
2176
+ const declaration = normalizeToolDeclaration(tool, options);
2177
+ return declaration ? [declaration] : [];
2178
+ });
2179
+ }
2180
+ if (!isRecord(tools)) return void 0;
2181
+ return Object.entries(tools).flatMap(([key, tool]) => {
2182
+ const declaration = normalizeToolDeclaration(tool, options, key);
2183
+ return declaration ? [declaration] : [];
2184
+ });
2185
+ }
2186
+ function resolvePromptTools(args2) {
2187
+ var _a;
2188
+ if (args2.override !== void 0) {
2189
+ return (_a = normalizeToolDeclarations(args2.override, args2.options)) != null ? _a : [];
2190
+ }
2191
+ return normalizeToolDeclarations(args2.inferred, args2.options);
2192
+ }
2193
+ function promptToolsAttribute(tools) {
2194
+ const values = promptToolsAttributeValue(tools);
2195
+ if (values === void 0) return void 0;
2196
+ return {
2197
+ key: PROMPT_TOOLS_ATTRIBUTE,
2198
+ value: { arrayValue: { values: values.map((stringValue) => ({ stringValue })) } }
2199
+ };
2200
+ }
2201
+ function promptToolsAttributeValue(tools) {
2202
+ if (tools === void 0) return void 0;
2203
+ return tools.map((tool) => {
2204
+ try {
2205
+ return JSON.stringify(tool);
2206
+ } catch (e) {
2207
+ return JSON.stringify({ type: tool.type, name: tool.name });
2208
+ }
2209
+ });
2210
+ }
2059
2211
 
2060
2212
  // ../core/dist/index.node.js
2061
2213
  import { AsyncLocalStorage } from "async_hooks";
@@ -2234,7 +2386,7 @@ installTracingSuppressionHook();
2234
2386
  // package.json
2235
2387
  var package_default = {
2236
2388
  name: "@raindrop-ai/claude-code",
2237
- version: "0.0.20",
2389
+ version: "0.0.21",
2238
2390
  description: "Raindrop observability for Claude Code CLI \u2014 automatic session, tool call, and prompt tracing via hooks",
2239
2391
  license: "MIT",
2240
2392
  homepage: "https://www.raindrop.ai/docs/integrations/claude-code/",
@@ -3143,7 +3295,7 @@ function isoToNanoString(iso, fallback) {
3143
3295
  return fallback;
3144
3296
  }
3145
3297
  }
3146
- function enrichFromTranscript(payload, eventId, traceShipper) {
3298
+ function enrichFromTranscript(payload, eventId, traceShipper, promptTools) {
3147
3299
  var _a, _b, _c, _d;
3148
3300
  if (!payload.transcript_path) {
3149
3301
  return { summary: void 0, props: {} };
@@ -3182,7 +3334,8 @@ function enrichFromTranscript(payload, eventId, traceShipper) {
3182
3334
  attrString("ai.response.text", truncate(outputText)),
3183
3335
  ...toolCallNames ? [attrString("ai.toolCall.names", toolCallNames)] : [],
3184
3336
  ...phase.hasThinking ? [attrString("ai.has_thinking", "true")] : [],
3185
- attrInt("ai.llm_call.index", emittedPhaseIndex)
3337
+ attrInt("ai.llm_call.index", emittedPhaseIndex),
3338
+ promptTools
3186
3339
  ]
3187
3340
  });
3188
3341
  emittedPhaseIndex++;
@@ -3204,7 +3357,8 @@ function enrichFromTranscript(payload, eventId, traceShipper) {
3204
3357
  attrString("gen_ai.system", "anthropic"),
3205
3358
  attrInt("gen_ai.usage.prompt_tokens", spanInputTokens),
3206
3359
  attrInt("gen_ai.usage.completion_tokens", spanOutputTokens),
3207
- ...summary.lastTurnCacheReadTokens != null && summary.lastTurnCacheReadTokens > 0 ? [attrInt("gen_ai.usage.cache_read_tokens", summary.lastTurnCacheReadTokens)] : []
3360
+ ...summary.lastTurnCacheReadTokens != null && summary.lastTurnCacheReadTokens > 0 ? [attrInt("gen_ai.usage.cache_read_tokens", summary.lastTurnCacheReadTokens)] : [],
3361
+ promptTools
3208
3362
  ]
3209
3363
  });
3210
3364
  }
@@ -3230,7 +3384,12 @@ function buildStopOutput(summary, lastAssistantMessage) {
3230
3384
  return finalMessage;
3231
3385
  }
3232
3386
  async function handleStopOrFailure(payload, eventId, convoId, config, properties, eventShipper, traceShipper, extraProperties) {
3233
- const { summary, props: transcriptProps } = enrichFromTranscript(payload, eventId, traceShipper);
3387
+ const { summary, props: transcriptProps } = enrichFromTranscript(
3388
+ payload,
3389
+ eventId,
3390
+ traceShipper,
3391
+ promptToolsAttribute(resolvePromptTools({ override: config.tools }))
3392
+ );
3234
3393
  const instructions = gatherInstructions(payload.session_id);
3235
3394
  const appendSysPrompt = readAppendSystemPrompt(payload.session_id);
3236
3395
  const output = buildStopOutput(summary, payload.last_assistant_message);
@@ -3295,6 +3454,15 @@ function loadConfig() {
3295
3454
  } catch (e) {
3296
3455
  }
3297
3456
  }
3457
+ let tools = Array.isArray(file.tools) ? file.tools : void 0;
3458
+ const envTools = process.env["RAINDROP_TOOLS"];
3459
+ if (envTools) {
3460
+ try {
3461
+ const parsed = JSON.parse(envTools);
3462
+ if (Array.isArray(parsed)) tools = parsed;
3463
+ } catch (e) {
3464
+ }
3465
+ }
3298
3466
  let selfDiagnostics = file.self_diagnostics;
3299
3467
  const envDiag = process.env["RAINDROP_SELF_DIAGNOSTICS"];
3300
3468
  if (envDiag) {
@@ -3323,7 +3491,8 @@ function loadConfig() {
3323
3491
  eventName: (_l = (_k = process.env["RAINDROP_EVENT_NAME"]) != null ? _k : file.event_name) != null ? _l : "claude_code_session",
3324
3492
  customProperties,
3325
3493
  selfDiagnostics,
3326
- appGit: mapAppGit(file.app_git)
3494
+ appGit: mapAppGit(file.app_git),
3495
+ tools
3327
3496
  };
3328
3497
  }
3329
3498
  function mapAppGit(value) {
@@ -3545,7 +3714,8 @@ async function handleHook() {
3545
3714
  convoId: config.convoId,
3546
3715
  debug: config.debug,
3547
3716
  eventName: config.eventName,
3548
- customProperties: config.customProperties
3717
+ customProperties: config.customProperties,
3718
+ tools: config.tools
3549
3719
  };
3550
3720
  const appGitContext = createAppGitContext2(
3551
3721
  ((_a = config.writeKey) == null ? void 0 : _a.trim()) || hasDebugger ? config.appGit : false,
package/dist/index.cjs CHANGED
@@ -106,7 +106,7 @@ function evalScopeAttributes() {
106
106
  return { [EVAL_CORRELATION_ID_ATTRIBUTE]: scope.correlationId };
107
107
  }
108
108
 
109
- // ../core/dist/chunk-WJOR6M7H.js
109
+ // ../core/dist/chunk-I6TVJQEC.js
110
110
  var STATE_KEY = /* @__PURE__ */ Symbol.for("raindrop.tracing.replayTraceRoutingState");
111
111
  function state() {
112
112
  const holder = globalThis;
@@ -2093,6 +2093,158 @@ var TraceShipper = class {
2093
2093
  }
2094
2094
  }
2095
2095
  };
2096
+ var PROMPT_TOOLS_ATTRIBUTE = "ai.prompt.tools";
2097
+ function isRecord(value) {
2098
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2099
+ }
2100
+ var JSON_SCHEMA_KEYS = [
2101
+ "type",
2102
+ "properties",
2103
+ "$schema",
2104
+ "$ref",
2105
+ "anyOf",
2106
+ "oneOf",
2107
+ "allOf",
2108
+ "enum",
2109
+ "const",
2110
+ "items",
2111
+ "additionalProperties",
2112
+ "required"
2113
+ ];
2114
+ function isJsonSchema(value) {
2115
+ if (!isRecord(value)) return false;
2116
+ if (typeof value["~standard"] === "object") return false;
2117
+ if (isRecord(value._def) || isRecord(value._zod)) return false;
2118
+ if (Object.keys(value).length === 0) return true;
2119
+ return JSON_SCHEMA_KEYS.some((key) => key in value);
2120
+ }
2121
+ function toJsonSchema(value, options) {
2122
+ if (value === void 0 || value === null) return void 0;
2123
+ if (isJsonSchema(value)) return value;
2124
+ if (isRecord(value) && isJsonSchema(value.jsonSchema)) return value.jsonSchema;
2125
+ if (isRecord(value) && isJsonSchema(value.json)) return value.json;
2126
+ if (options == null ? void 0 : options.schema) {
2127
+ try {
2128
+ return options.schema(value);
2129
+ } catch (e) {
2130
+ return void 0;
2131
+ }
2132
+ }
2133
+ return void 0;
2134
+ }
2135
+ function pickString(value) {
2136
+ return typeof value === "string" && value.length > 0 ? value : void 0;
2137
+ }
2138
+ function functionTool(name, description, schema, options) {
2139
+ const tool = { type: "function", name };
2140
+ const desc = pickString(description);
2141
+ if (desc !== void 0) tool.description = desc;
2142
+ const inputSchema = toJsonSchema(schema, options);
2143
+ if (inputSchema !== void 0) tool.inputSchema = inputSchema;
2144
+ return tool;
2145
+ }
2146
+ function normalizeToolDeclaration(tool, options, fallbackName) {
2147
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
2148
+ if (!isRecord(tool)) return void 0;
2149
+ if (tool.type === "provider-defined" || tool.type === "provider") {
2150
+ const name2 = (_b = (_a = pickString(tool.name)) != null ? _a : pickString(tool.id)) != null ? _b : fallbackName;
2151
+ if (name2 === void 0) return void 0;
2152
+ const declaration = { type: "provider-defined", name: name2 };
2153
+ const id = pickString(tool.id);
2154
+ if (id !== void 0) declaration.id = id;
2155
+ if (isRecord(tool.args)) declaration.args = tool.args;
2156
+ return declaration;
2157
+ }
2158
+ if (isRecord(tool.toolSpec)) {
2159
+ const spec = tool.toolSpec;
2160
+ const name2 = (_c = pickString(spec.name)) != null ? _c : fallbackName;
2161
+ if (name2 === void 0) return void 0;
2162
+ return functionTool(name2, spec.description, spec.inputSchema, options);
2163
+ }
2164
+ if (isRecord(tool.function)) {
2165
+ const fn = tool.function;
2166
+ const name2 = (_d = pickString(fn.name)) != null ? _d : fallbackName;
2167
+ if (name2 === void 0) return void 0;
2168
+ return functionTool(name2, fn.description, (_e = fn.parameters) != null ? _e : fn.input_schema, options);
2169
+ }
2170
+ const name = (_g = (_f = pickString(tool.name)) != null ? _f : pickString(tool.id)) != null ? _g : fallbackName;
2171
+ if (name === void 0) return void 0;
2172
+ const type = pickString(tool.type);
2173
+ if (type !== void 0 && type !== "function" && type !== "dynamic" && type !== "custom") {
2174
+ if (!("input_schema" in tool) && !("inputSchema" in tool) && !("parameters" in tool)) {
2175
+ return { type: "provider-defined", id: type, name };
2176
+ }
2177
+ }
2178
+ return functionTool(
2179
+ name,
2180
+ tool.description,
2181
+ (_j = (_i = (_h = tool.inputSchema) != null ? _h : tool.input_schema) != null ? _i : tool.parameters) != null ? _j : tool.schema,
2182
+ options
2183
+ );
2184
+ }
2185
+ var NOT_A_GEMINI_GROUP_KEYS = ["name", "id", "type", "function", "toolSpec", "cachePoint"];
2186
+ function isGeminiToolGroup(tool) {
2187
+ if (Array.isArray(tool.functionDeclarations)) return true;
2188
+ const keys = Object.keys(tool);
2189
+ if (keys.length === 0 || NOT_A_GEMINI_GROUP_KEYS.some((key) => key in tool)) return false;
2190
+ return keys.every((key) => isRecord(tool[key]));
2191
+ }
2192
+ function expandGeminiTool(tool, options) {
2193
+ const declarations = [];
2194
+ for (const [key, value] of Object.entries(tool)) {
2195
+ if (key === "functionDeclarations" && Array.isArray(value)) {
2196
+ for (const declaration of value) {
2197
+ const normalized = normalizeToolDeclaration(declaration, options);
2198
+ if (normalized) declarations.push(normalized);
2199
+ }
2200
+ } else if (isRecord(value)) {
2201
+ declarations.push({ type: "provider-defined", id: key, name: key, args: value });
2202
+ }
2203
+ }
2204
+ return declarations;
2205
+ }
2206
+ function normalizeToolDeclarations(tools, options) {
2207
+ if (tools === void 0 || tools === null) return void 0;
2208
+ if (Array.isArray(tools)) {
2209
+ return tools.flatMap((tool) => {
2210
+ if (isRecord(tool) && isGeminiToolGroup(tool)) {
2211
+ return expandGeminiTool(tool, options);
2212
+ }
2213
+ const declaration = normalizeToolDeclaration(tool, options);
2214
+ return declaration ? [declaration] : [];
2215
+ });
2216
+ }
2217
+ if (!isRecord(tools)) return void 0;
2218
+ return Object.entries(tools).flatMap(([key, tool]) => {
2219
+ const declaration = normalizeToolDeclaration(tool, options, key);
2220
+ return declaration ? [declaration] : [];
2221
+ });
2222
+ }
2223
+ function resolvePromptTools(args) {
2224
+ var _a;
2225
+ if (args.override !== void 0) {
2226
+ return (_a = normalizeToolDeclarations(args.override, args.options)) != null ? _a : [];
2227
+ }
2228
+ return normalizeToolDeclarations(args.inferred, args.options);
2229
+ }
2230
+ function promptToolsAttribute(tools) {
2231
+ const values = promptToolsAttributeValue(tools);
2232
+ if (values === void 0) return void 0;
2233
+ return {
2234
+ key: PROMPT_TOOLS_ATTRIBUTE,
2235
+ value: { arrayValue: { values: values.map((stringValue) => ({ stringValue })) } }
2236
+ };
2237
+ }
2238
+ function promptToolsAttributeValue(tools) {
2239
+ if (tools === void 0) return void 0;
2240
+ return tools.map((tool) => {
2241
+ try {
2242
+ return JSON.stringify(tool);
2243
+ } catch (e) {
2244
+ return JSON.stringify({ type: tool.type, name: tool.name });
2245
+ }
2246
+ });
2247
+ }
2096
2248
 
2097
2249
  // ../core/dist/index.node.js
2098
2250
  var import_async_hooks = require("async_hooks");
@@ -2271,7 +2423,7 @@ installTracingSuppressionHook();
2271
2423
  // package.json
2272
2424
  var package_default = {
2273
2425
  name: "@raindrop-ai/claude-code",
2274
- version: "0.0.20",
2426
+ version: "0.0.21",
2275
2427
  description: "Raindrop observability for Claude Code CLI \u2014 automatic session, tool call, and prompt tracing via hooks",
2276
2428
  license: "MIT",
2277
2429
  homepage: "https://www.raindrop.ai/docs/integrations/claude-code/",
@@ -2411,6 +2563,15 @@ function loadConfig() {
2411
2563
  } catch (e) {
2412
2564
  }
2413
2565
  }
2566
+ let tools = Array.isArray(file.tools) ? file.tools : void 0;
2567
+ const envTools = process.env["RAINDROP_TOOLS"];
2568
+ if (envTools) {
2569
+ try {
2570
+ const parsed = JSON.parse(envTools);
2571
+ if (Array.isArray(parsed)) tools = parsed;
2572
+ } catch (e) {
2573
+ }
2574
+ }
2414
2575
  let selfDiagnostics = file.self_diagnostics;
2415
2576
  const envDiag = process.env["RAINDROP_SELF_DIAGNOSTICS"];
2416
2577
  if (envDiag) {
@@ -2439,7 +2600,8 @@ function loadConfig() {
2439
2600
  eventName: (_l = (_k = process.env["RAINDROP_EVENT_NAME"]) != null ? _k : file.event_name) != null ? _l : "claude_code_session",
2440
2601
  customProperties,
2441
2602
  selfDiagnostics,
2442
- appGit: mapAppGit(file.app_git)
2603
+ appGit: mapAppGit(file.app_git),
2604
+ tools
2443
2605
  };
2444
2606
  }
2445
2607
  function mapAppGit(value) {
@@ -3278,7 +3440,7 @@ function isoToNanoString(iso, fallback) {
3278
3440
  return fallback;
3279
3441
  }
3280
3442
  }
3281
- function enrichFromTranscript(payload, eventId, traceShipper) {
3443
+ function enrichFromTranscript(payload, eventId, traceShipper, promptTools) {
3282
3444
  var _a, _b, _c, _d;
3283
3445
  if (!payload.transcript_path) {
3284
3446
  return { summary: void 0, props: {} };
@@ -3317,7 +3479,8 @@ function enrichFromTranscript(payload, eventId, traceShipper) {
3317
3479
  attrString("ai.response.text", truncate(outputText)),
3318
3480
  ...toolCallNames ? [attrString("ai.toolCall.names", toolCallNames)] : [],
3319
3481
  ...phase.hasThinking ? [attrString("ai.has_thinking", "true")] : [],
3320
- attrInt("ai.llm_call.index", emittedPhaseIndex)
3482
+ attrInt("ai.llm_call.index", emittedPhaseIndex),
3483
+ promptTools
3321
3484
  ]
3322
3485
  });
3323
3486
  emittedPhaseIndex++;
@@ -3339,7 +3502,8 @@ function enrichFromTranscript(payload, eventId, traceShipper) {
3339
3502
  attrString("gen_ai.system", "anthropic"),
3340
3503
  attrInt("gen_ai.usage.prompt_tokens", spanInputTokens),
3341
3504
  attrInt("gen_ai.usage.completion_tokens", spanOutputTokens),
3342
- ...summary.lastTurnCacheReadTokens != null && summary.lastTurnCacheReadTokens > 0 ? [attrInt("gen_ai.usage.cache_read_tokens", summary.lastTurnCacheReadTokens)] : []
3505
+ ...summary.lastTurnCacheReadTokens != null && summary.lastTurnCacheReadTokens > 0 ? [attrInt("gen_ai.usage.cache_read_tokens", summary.lastTurnCacheReadTokens)] : [],
3506
+ promptTools
3343
3507
  ]
3344
3508
  });
3345
3509
  }
@@ -3365,7 +3529,12 @@ function buildStopOutput(summary, lastAssistantMessage) {
3365
3529
  return finalMessage;
3366
3530
  }
3367
3531
  async function handleStopOrFailure(payload, eventId, convoId, config, properties, eventShipper, traceShipper, extraProperties) {
3368
- const { summary, props: transcriptProps } = enrichFromTranscript(payload, eventId, traceShipper);
3532
+ const { summary, props: transcriptProps } = enrichFromTranscript(
3533
+ payload,
3534
+ eventId,
3535
+ traceShipper,
3536
+ promptToolsAttribute(resolvePromptTools({ override: config.tools }))
3537
+ );
3369
3538
  const instructions = gatherInstructions(payload.session_id);
3370
3539
  const appendSysPrompt = readAppendSystemPrompt(payload.session_id);
3371
3540
  const output = buildStopOutput(summary, payload.last_assistant_message);
package/dist/index.d.cts CHANGED
@@ -506,6 +506,32 @@ declare global {
506
506
  var RAINDROP_CONTEXT_MANAGER: (new () => ContextManager) | undefined;
507
507
  var RAINDROP_ASYNC_LOCAL_STORAGE: (new <T>() => AsyncLocalStorageLike<T>) | undefined;
508
508
  }
509
+ type JsonSchema = Record<string, unknown>;
510
+ type FunctionToolDeclaration = {
511
+ type: "function";
512
+ name: string;
513
+ description?: string;
514
+ inputSchema?: JsonSchema;
515
+ };
516
+ type ProviderDefinedToolDeclaration = {
517
+ type: "provider-defined";
518
+ id?: string;
519
+ name: string;
520
+ args?: Record<string, unknown>;
521
+ };
522
+ type ToolDeclaration = FunctionToolDeclaration | ProviderDefinedToolDeclaration;
523
+ /**
524
+ * What callers hand to a `tools` override. The canonical `ToolDeclaration`
525
+ * shape is accepted as-is; so is any framework shape `normalizeToolDeclarations`
526
+ * recognizes (OpenAI, Anthropic, Bedrock, LangChain, Pi, Mastra, AI SDK).
527
+ */
528
+ type ToolDeclarationInput = ToolDeclaration | {
529
+ name: string;
530
+ description?: string;
531
+ inputSchema?: JsonSchema;
532
+ parameters?: JsonSchema;
533
+ };
534
+ type PromptToolsOverride = ReadonlyArray<ToolDeclarationInput | unknown>;
509
535
 
510
536
  declare class EventShipper extends EventShipper$1 {
511
537
  constructor(opts: ConstructorParameters<typeof EventShipper$1>[0]);
@@ -543,6 +569,7 @@ interface ConfigFile {
543
569
  custom_properties?: Record<string, unknown>;
544
570
  self_diagnostics?: SelfDiagnosticsConfig;
545
571
  app_git?: AppGitFileOptions | false;
572
+ tools?: unknown[];
546
573
  }
547
574
  interface RaindropConfig {
548
575
  writeKey: string;
@@ -563,6 +590,11 @@ interface RaindropConfig {
563
590
  selfDiagnostics?: SelfDiagnosticsConfig;
564
591
  /** Application Git identity override. false disables enrichment. */
565
592
  appGit?: AppGitOptions | false;
593
+ /**
594
+ * Tool catalog recorded as `ai.prompt.tools` on every LLM span. Hooks never
595
+ * see the model request, so this is the only way to record it.
596
+ */
597
+ tools?: PromptToolsOverride;
566
598
  }
567
599
  /**
568
600
  * Load config with precedence (low -> high):
@@ -610,6 +642,7 @@ interface MapperConfig {
610
642
  debug: boolean;
611
643
  eventName: string;
612
644
  customProperties: Record<string, unknown>;
645
+ tools?: PromptToolsOverride;
613
646
  }
614
647
  declare function mapHookToRaindrop(payload: HookPayload, config: MapperConfig, eventShipper: EventShipper, traceShipper: TraceShipper): Promise<void>;
615
648
  /**
package/dist/index.d.ts CHANGED
@@ -506,6 +506,32 @@ declare global {
506
506
  var RAINDROP_CONTEXT_MANAGER: (new () => ContextManager) | undefined;
507
507
  var RAINDROP_ASYNC_LOCAL_STORAGE: (new <T>() => AsyncLocalStorageLike<T>) | undefined;
508
508
  }
509
+ type JsonSchema = Record<string, unknown>;
510
+ type FunctionToolDeclaration = {
511
+ type: "function";
512
+ name: string;
513
+ description?: string;
514
+ inputSchema?: JsonSchema;
515
+ };
516
+ type ProviderDefinedToolDeclaration = {
517
+ type: "provider-defined";
518
+ id?: string;
519
+ name: string;
520
+ args?: Record<string, unknown>;
521
+ };
522
+ type ToolDeclaration = FunctionToolDeclaration | ProviderDefinedToolDeclaration;
523
+ /**
524
+ * What callers hand to a `tools` override. The canonical `ToolDeclaration`
525
+ * shape is accepted as-is; so is any framework shape `normalizeToolDeclarations`
526
+ * recognizes (OpenAI, Anthropic, Bedrock, LangChain, Pi, Mastra, AI SDK).
527
+ */
528
+ type ToolDeclarationInput = ToolDeclaration | {
529
+ name: string;
530
+ description?: string;
531
+ inputSchema?: JsonSchema;
532
+ parameters?: JsonSchema;
533
+ };
534
+ type PromptToolsOverride = ReadonlyArray<ToolDeclarationInput | unknown>;
509
535
 
510
536
  declare class EventShipper extends EventShipper$1 {
511
537
  constructor(opts: ConstructorParameters<typeof EventShipper$1>[0]);
@@ -543,6 +569,7 @@ interface ConfigFile {
543
569
  custom_properties?: Record<string, unknown>;
544
570
  self_diagnostics?: SelfDiagnosticsConfig;
545
571
  app_git?: AppGitFileOptions | false;
572
+ tools?: unknown[];
546
573
  }
547
574
  interface RaindropConfig {
548
575
  writeKey: string;
@@ -563,6 +590,11 @@ interface RaindropConfig {
563
590
  selfDiagnostics?: SelfDiagnosticsConfig;
564
591
  /** Application Git identity override. false disables enrichment. */
565
592
  appGit?: AppGitOptions | false;
593
+ /**
594
+ * Tool catalog recorded as `ai.prompt.tools` on every LLM span. Hooks never
595
+ * see the model request, so this is the only way to record it.
596
+ */
597
+ tools?: PromptToolsOverride;
566
598
  }
567
599
  /**
568
600
  * Load config with precedence (low -> high):
@@ -610,6 +642,7 @@ interface MapperConfig {
610
642
  debug: boolean;
611
643
  eventName: string;
612
644
  customProperties: Record<string, unknown>;
645
+ tools?: PromptToolsOverride;
613
646
  }
614
647
  declare function mapHookToRaindrop(payload: HookPayload, config: MapperConfig, eventShipper: EventShipper, traceShipper: TraceShipper): Promise<void>;
615
648
  /**
package/dist/index.js CHANGED
@@ -60,7 +60,7 @@ function evalScopeAttributes() {
60
60
  return { [EVAL_CORRELATION_ID_ATTRIBUTE]: scope.correlationId };
61
61
  }
62
62
 
63
- // ../core/dist/chunk-WJOR6M7H.js
63
+ // ../core/dist/chunk-I6TVJQEC.js
64
64
  var STATE_KEY = /* @__PURE__ */ Symbol.for("raindrop.tracing.replayTraceRoutingState");
65
65
  function state() {
66
66
  const holder = globalThis;
@@ -2047,6 +2047,158 @@ var TraceShipper = class {
2047
2047
  }
2048
2048
  }
2049
2049
  };
2050
+ var PROMPT_TOOLS_ATTRIBUTE = "ai.prompt.tools";
2051
+ function isRecord(value) {
2052
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2053
+ }
2054
+ var JSON_SCHEMA_KEYS = [
2055
+ "type",
2056
+ "properties",
2057
+ "$schema",
2058
+ "$ref",
2059
+ "anyOf",
2060
+ "oneOf",
2061
+ "allOf",
2062
+ "enum",
2063
+ "const",
2064
+ "items",
2065
+ "additionalProperties",
2066
+ "required"
2067
+ ];
2068
+ function isJsonSchema(value) {
2069
+ if (!isRecord(value)) return false;
2070
+ if (typeof value["~standard"] === "object") return false;
2071
+ if (isRecord(value._def) || isRecord(value._zod)) return false;
2072
+ if (Object.keys(value).length === 0) return true;
2073
+ return JSON_SCHEMA_KEYS.some((key) => key in value);
2074
+ }
2075
+ function toJsonSchema(value, options) {
2076
+ if (value === void 0 || value === null) return void 0;
2077
+ if (isJsonSchema(value)) return value;
2078
+ if (isRecord(value) && isJsonSchema(value.jsonSchema)) return value.jsonSchema;
2079
+ if (isRecord(value) && isJsonSchema(value.json)) return value.json;
2080
+ if (options == null ? void 0 : options.schema) {
2081
+ try {
2082
+ return options.schema(value);
2083
+ } catch (e) {
2084
+ return void 0;
2085
+ }
2086
+ }
2087
+ return void 0;
2088
+ }
2089
+ function pickString(value) {
2090
+ return typeof value === "string" && value.length > 0 ? value : void 0;
2091
+ }
2092
+ function functionTool(name, description, schema, options) {
2093
+ const tool = { type: "function", name };
2094
+ const desc = pickString(description);
2095
+ if (desc !== void 0) tool.description = desc;
2096
+ const inputSchema = toJsonSchema(schema, options);
2097
+ if (inputSchema !== void 0) tool.inputSchema = inputSchema;
2098
+ return tool;
2099
+ }
2100
+ function normalizeToolDeclaration(tool, options, fallbackName) {
2101
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
2102
+ if (!isRecord(tool)) return void 0;
2103
+ if (tool.type === "provider-defined" || tool.type === "provider") {
2104
+ const name2 = (_b = (_a = pickString(tool.name)) != null ? _a : pickString(tool.id)) != null ? _b : fallbackName;
2105
+ if (name2 === void 0) return void 0;
2106
+ const declaration = { type: "provider-defined", name: name2 };
2107
+ const id = pickString(tool.id);
2108
+ if (id !== void 0) declaration.id = id;
2109
+ if (isRecord(tool.args)) declaration.args = tool.args;
2110
+ return declaration;
2111
+ }
2112
+ if (isRecord(tool.toolSpec)) {
2113
+ const spec = tool.toolSpec;
2114
+ const name2 = (_c = pickString(spec.name)) != null ? _c : fallbackName;
2115
+ if (name2 === void 0) return void 0;
2116
+ return functionTool(name2, spec.description, spec.inputSchema, options);
2117
+ }
2118
+ if (isRecord(tool.function)) {
2119
+ const fn = tool.function;
2120
+ const name2 = (_d = pickString(fn.name)) != null ? _d : fallbackName;
2121
+ if (name2 === void 0) return void 0;
2122
+ return functionTool(name2, fn.description, (_e = fn.parameters) != null ? _e : fn.input_schema, options);
2123
+ }
2124
+ const name = (_g = (_f = pickString(tool.name)) != null ? _f : pickString(tool.id)) != null ? _g : fallbackName;
2125
+ if (name === void 0) return void 0;
2126
+ const type = pickString(tool.type);
2127
+ if (type !== void 0 && type !== "function" && type !== "dynamic" && type !== "custom") {
2128
+ if (!("input_schema" in tool) && !("inputSchema" in tool) && !("parameters" in tool)) {
2129
+ return { type: "provider-defined", id: type, name };
2130
+ }
2131
+ }
2132
+ return functionTool(
2133
+ name,
2134
+ tool.description,
2135
+ (_j = (_i = (_h = tool.inputSchema) != null ? _h : tool.input_schema) != null ? _i : tool.parameters) != null ? _j : tool.schema,
2136
+ options
2137
+ );
2138
+ }
2139
+ var NOT_A_GEMINI_GROUP_KEYS = ["name", "id", "type", "function", "toolSpec", "cachePoint"];
2140
+ function isGeminiToolGroup(tool) {
2141
+ if (Array.isArray(tool.functionDeclarations)) return true;
2142
+ const keys = Object.keys(tool);
2143
+ if (keys.length === 0 || NOT_A_GEMINI_GROUP_KEYS.some((key) => key in tool)) return false;
2144
+ return keys.every((key) => isRecord(tool[key]));
2145
+ }
2146
+ function expandGeminiTool(tool, options) {
2147
+ const declarations = [];
2148
+ for (const [key, value] of Object.entries(tool)) {
2149
+ if (key === "functionDeclarations" && Array.isArray(value)) {
2150
+ for (const declaration of value) {
2151
+ const normalized = normalizeToolDeclaration(declaration, options);
2152
+ if (normalized) declarations.push(normalized);
2153
+ }
2154
+ } else if (isRecord(value)) {
2155
+ declarations.push({ type: "provider-defined", id: key, name: key, args: value });
2156
+ }
2157
+ }
2158
+ return declarations;
2159
+ }
2160
+ function normalizeToolDeclarations(tools, options) {
2161
+ if (tools === void 0 || tools === null) return void 0;
2162
+ if (Array.isArray(tools)) {
2163
+ return tools.flatMap((tool) => {
2164
+ if (isRecord(tool) && isGeminiToolGroup(tool)) {
2165
+ return expandGeminiTool(tool, options);
2166
+ }
2167
+ const declaration = normalizeToolDeclaration(tool, options);
2168
+ return declaration ? [declaration] : [];
2169
+ });
2170
+ }
2171
+ if (!isRecord(tools)) return void 0;
2172
+ return Object.entries(tools).flatMap(([key, tool]) => {
2173
+ const declaration = normalizeToolDeclaration(tool, options, key);
2174
+ return declaration ? [declaration] : [];
2175
+ });
2176
+ }
2177
+ function resolvePromptTools(args) {
2178
+ var _a;
2179
+ if (args.override !== void 0) {
2180
+ return (_a = normalizeToolDeclarations(args.override, args.options)) != null ? _a : [];
2181
+ }
2182
+ return normalizeToolDeclarations(args.inferred, args.options);
2183
+ }
2184
+ function promptToolsAttribute(tools) {
2185
+ const values = promptToolsAttributeValue(tools);
2186
+ if (values === void 0) return void 0;
2187
+ return {
2188
+ key: PROMPT_TOOLS_ATTRIBUTE,
2189
+ value: { arrayValue: { values: values.map((stringValue) => ({ stringValue })) } }
2190
+ };
2191
+ }
2192
+ function promptToolsAttributeValue(tools) {
2193
+ if (tools === void 0) return void 0;
2194
+ return tools.map((tool) => {
2195
+ try {
2196
+ return JSON.stringify(tool);
2197
+ } catch (e) {
2198
+ return JSON.stringify({ type: tool.type, name: tool.name });
2199
+ }
2200
+ });
2201
+ }
2050
2202
 
2051
2203
  // ../core/dist/index.node.js
2052
2204
  import { AsyncLocalStorage } from "async_hooks";
@@ -2225,7 +2377,7 @@ installTracingSuppressionHook();
2225
2377
  // package.json
2226
2378
  var package_default = {
2227
2379
  name: "@raindrop-ai/claude-code",
2228
- version: "0.0.20",
2380
+ version: "0.0.21",
2229
2381
  description: "Raindrop observability for Claude Code CLI \u2014 automatic session, tool call, and prompt tracing via hooks",
2230
2382
  license: "MIT",
2231
2383
  homepage: "https://www.raindrop.ai/docs/integrations/claude-code/",
@@ -2365,6 +2517,15 @@ function loadConfig() {
2365
2517
  } catch (e) {
2366
2518
  }
2367
2519
  }
2520
+ let tools = Array.isArray(file.tools) ? file.tools : void 0;
2521
+ const envTools = process.env["RAINDROP_TOOLS"];
2522
+ if (envTools) {
2523
+ try {
2524
+ const parsed = JSON.parse(envTools);
2525
+ if (Array.isArray(parsed)) tools = parsed;
2526
+ } catch (e) {
2527
+ }
2528
+ }
2368
2529
  let selfDiagnostics = file.self_diagnostics;
2369
2530
  const envDiag = process.env["RAINDROP_SELF_DIAGNOSTICS"];
2370
2531
  if (envDiag) {
@@ -2393,7 +2554,8 @@ function loadConfig() {
2393
2554
  eventName: (_l = (_k = process.env["RAINDROP_EVENT_NAME"]) != null ? _k : file.event_name) != null ? _l : "claude_code_session",
2394
2555
  customProperties,
2395
2556
  selfDiagnostics,
2396
- appGit: mapAppGit(file.app_git)
2557
+ appGit: mapAppGit(file.app_git),
2558
+ tools
2397
2559
  };
2398
2560
  }
2399
2561
  function mapAppGit(value) {
@@ -3232,7 +3394,7 @@ function isoToNanoString(iso, fallback) {
3232
3394
  return fallback;
3233
3395
  }
3234
3396
  }
3235
- function enrichFromTranscript(payload, eventId, traceShipper) {
3397
+ function enrichFromTranscript(payload, eventId, traceShipper, promptTools) {
3236
3398
  var _a, _b, _c, _d;
3237
3399
  if (!payload.transcript_path) {
3238
3400
  return { summary: void 0, props: {} };
@@ -3271,7 +3433,8 @@ function enrichFromTranscript(payload, eventId, traceShipper) {
3271
3433
  attrString("ai.response.text", truncate(outputText)),
3272
3434
  ...toolCallNames ? [attrString("ai.toolCall.names", toolCallNames)] : [],
3273
3435
  ...phase.hasThinking ? [attrString("ai.has_thinking", "true")] : [],
3274
- attrInt("ai.llm_call.index", emittedPhaseIndex)
3436
+ attrInt("ai.llm_call.index", emittedPhaseIndex),
3437
+ promptTools
3275
3438
  ]
3276
3439
  });
3277
3440
  emittedPhaseIndex++;
@@ -3293,7 +3456,8 @@ function enrichFromTranscript(payload, eventId, traceShipper) {
3293
3456
  attrString("gen_ai.system", "anthropic"),
3294
3457
  attrInt("gen_ai.usage.prompt_tokens", spanInputTokens),
3295
3458
  attrInt("gen_ai.usage.completion_tokens", spanOutputTokens),
3296
- ...summary.lastTurnCacheReadTokens != null && summary.lastTurnCacheReadTokens > 0 ? [attrInt("gen_ai.usage.cache_read_tokens", summary.lastTurnCacheReadTokens)] : []
3459
+ ...summary.lastTurnCacheReadTokens != null && summary.lastTurnCacheReadTokens > 0 ? [attrInt("gen_ai.usage.cache_read_tokens", summary.lastTurnCacheReadTokens)] : [],
3460
+ promptTools
3297
3461
  ]
3298
3462
  });
3299
3463
  }
@@ -3319,7 +3483,12 @@ function buildStopOutput(summary, lastAssistantMessage) {
3319
3483
  return finalMessage;
3320
3484
  }
3321
3485
  async function handleStopOrFailure(payload, eventId, convoId, config, properties, eventShipper, traceShipper, extraProperties) {
3322
- const { summary, props: transcriptProps } = enrichFromTranscript(payload, eventId, traceShipper);
3486
+ const { summary, props: transcriptProps } = enrichFromTranscript(
3487
+ payload,
3488
+ eventId,
3489
+ traceShipper,
3490
+ promptToolsAttribute(resolvePromptTools({ override: config.tools }))
3491
+ );
3323
3492
  const instructions = gatherInstructions(payload.session_id);
3324
3493
  const appendSysPrompt = readAppendSystemPrompt(payload.session_id);
3325
3494
  const output = buildStopOutput(summary, payload.last_assistant_message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raindrop-ai/claude-code",
3
- "version": "0.0.20",
3
+ "version": "0.0.21",
4
4
  "description": "Raindrop observability for Claude Code CLI — automatic session, tool call, and prompt tracing via hooks",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.raindrop.ai/docs/integrations/claude-code/",
@@ -53,7 +53,7 @@
53
53
  "tsup": "^8.4.0",
54
54
  "typescript": "^5.3.3",
55
55
  "vitest": "^2.1.0",
56
- "@raindrop-ai/core": "0.3.0"
56
+ "@raindrop-ai/core": "0.3.1"
57
57
  },
58
58
  "publishConfig": {
59
59
  "access": "public"