@raindrop-ai/opencode-plugin 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -87,7 +87,7 @@ function evalScopeAttributes() {
87
87
  return { [EVAL_CORRELATION_ID_ATTRIBUTE]: scope.correlationId };
88
88
  }
89
89
 
90
- // ../core/dist/chunk-WJOR6M7H.js
90
+ // ../core/dist/chunk-I6TVJQEC.js
91
91
  var STATE_KEY = /* @__PURE__ */ Symbol.for("raindrop.tracing.replayTraceRoutingState");
92
92
  function state() {
93
93
  const holder = globalThis;
@@ -2096,6 +2096,151 @@ var TraceShipper = class {
2096
2096
  }
2097
2097
  }
2098
2098
  };
2099
+ var PROMPT_TOOLS_ATTRIBUTE = "ai.prompt.tools";
2100
+ function isRecord(value) {
2101
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2102
+ }
2103
+ var JSON_SCHEMA_KEYS = [
2104
+ "type",
2105
+ "properties",
2106
+ "$schema",
2107
+ "$ref",
2108
+ "anyOf",
2109
+ "oneOf",
2110
+ "allOf",
2111
+ "enum",
2112
+ "const",
2113
+ "items",
2114
+ "additionalProperties",
2115
+ "required"
2116
+ ];
2117
+ function isJsonSchema(value) {
2118
+ if (!isRecord(value)) return false;
2119
+ if (typeof value["~standard"] === "object") return false;
2120
+ if (isRecord(value._def) || isRecord(value._zod)) return false;
2121
+ if (Object.keys(value).length === 0) return true;
2122
+ return JSON_SCHEMA_KEYS.some((key) => key in value);
2123
+ }
2124
+ function toJsonSchema(value, options) {
2125
+ if (value === void 0 || value === null) return void 0;
2126
+ if (isJsonSchema(value)) return value;
2127
+ if (isRecord(value) && isJsonSchema(value.jsonSchema)) return value.jsonSchema;
2128
+ if (isRecord(value) && isJsonSchema(value.json)) return value.json;
2129
+ if (options == null ? void 0 : options.schema) {
2130
+ try {
2131
+ return options.schema(value);
2132
+ } catch (e) {
2133
+ return void 0;
2134
+ }
2135
+ }
2136
+ return void 0;
2137
+ }
2138
+ function pickString(value) {
2139
+ return typeof value === "string" && value.length > 0 ? value : void 0;
2140
+ }
2141
+ function functionTool(name, description, schema, options) {
2142
+ const tool2 = { type: "function", name };
2143
+ const desc = pickString(description);
2144
+ if (desc !== void 0) tool2.description = desc;
2145
+ const inputSchema = toJsonSchema(schema, options);
2146
+ if (inputSchema !== void 0) tool2.inputSchema = inputSchema;
2147
+ return tool2;
2148
+ }
2149
+ function normalizeToolDeclaration(tool2, options, fallbackName) {
2150
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
2151
+ if (!isRecord(tool2)) return void 0;
2152
+ if (tool2.type === "provider-defined" || tool2.type === "provider") {
2153
+ const name2 = (_b = (_a = pickString(tool2.name)) != null ? _a : pickString(tool2.id)) != null ? _b : fallbackName;
2154
+ if (name2 === void 0) return void 0;
2155
+ const declaration = { type: "provider-defined", name: name2 };
2156
+ const id = pickString(tool2.id);
2157
+ if (id !== void 0) declaration.id = id;
2158
+ if (isRecord(tool2.args)) declaration.args = tool2.args;
2159
+ return declaration;
2160
+ }
2161
+ if (isRecord(tool2.toolSpec)) {
2162
+ const spec = tool2.toolSpec;
2163
+ const name2 = (_c = pickString(spec.name)) != null ? _c : fallbackName;
2164
+ if (name2 === void 0) return void 0;
2165
+ return functionTool(name2, spec.description, spec.inputSchema, options);
2166
+ }
2167
+ if (isRecord(tool2.function)) {
2168
+ const fn = tool2.function;
2169
+ const name2 = (_d = pickString(fn.name)) != null ? _d : fallbackName;
2170
+ if (name2 === void 0) return void 0;
2171
+ return functionTool(name2, fn.description, (_e = fn.parameters) != null ? _e : fn.input_schema, options);
2172
+ }
2173
+ const name = (_g = (_f = pickString(tool2.name)) != null ? _f : pickString(tool2.id)) != null ? _g : fallbackName;
2174
+ if (name === void 0) return void 0;
2175
+ const type = pickString(tool2.type);
2176
+ if (type !== void 0 && type !== "function" && type !== "dynamic" && type !== "custom") {
2177
+ if (!("input_schema" in tool2) && !("inputSchema" in tool2) && !("parameters" in tool2)) {
2178
+ return { type: "provider-defined", id: type, name };
2179
+ }
2180
+ }
2181
+ return functionTool(
2182
+ name,
2183
+ tool2.description,
2184
+ (_j = (_i = (_h = tool2.inputSchema) != null ? _h : tool2.input_schema) != null ? _i : tool2.parameters) != null ? _j : tool2.schema,
2185
+ options
2186
+ );
2187
+ }
2188
+ var NOT_A_GEMINI_GROUP_KEYS = ["name", "id", "type", "function", "toolSpec", "cachePoint"];
2189
+ function isGeminiToolGroup(tool2) {
2190
+ if (Array.isArray(tool2.functionDeclarations)) return true;
2191
+ const keys = Object.keys(tool2);
2192
+ if (keys.length === 0 || NOT_A_GEMINI_GROUP_KEYS.some((key) => key in tool2)) return false;
2193
+ return keys.every((key) => isRecord(tool2[key]));
2194
+ }
2195
+ function expandGeminiTool(tool2, options) {
2196
+ const declarations = [];
2197
+ for (const [key, value] of Object.entries(tool2)) {
2198
+ if (key === "functionDeclarations" && Array.isArray(value)) {
2199
+ for (const declaration of value) {
2200
+ const normalized = normalizeToolDeclaration(declaration, options);
2201
+ if (normalized) declarations.push(normalized);
2202
+ }
2203
+ } else if (isRecord(value)) {
2204
+ declarations.push({ type: "provider-defined", id: key, name: key, args: value });
2205
+ }
2206
+ }
2207
+ return declarations;
2208
+ }
2209
+ function normalizeToolDeclarations(tools, options) {
2210
+ if (tools === void 0 || tools === null) return void 0;
2211
+ if (Array.isArray(tools)) {
2212
+ return tools.flatMap((tool2) => {
2213
+ if (isRecord(tool2) && isGeminiToolGroup(tool2)) {
2214
+ return expandGeminiTool(tool2, options);
2215
+ }
2216
+ const declaration = normalizeToolDeclaration(tool2, options);
2217
+ return declaration ? [declaration] : [];
2218
+ });
2219
+ }
2220
+ if (!isRecord(tools)) return void 0;
2221
+ return Object.entries(tools).flatMap(([key, tool2]) => {
2222
+ const declaration = normalizeToolDeclaration(tool2, options, key);
2223
+ return declaration ? [declaration] : [];
2224
+ });
2225
+ }
2226
+ function promptToolsAttribute(tools) {
2227
+ const values = promptToolsAttributeValue(tools);
2228
+ if (values === void 0) return void 0;
2229
+ return {
2230
+ key: PROMPT_TOOLS_ATTRIBUTE,
2231
+ value: { arrayValue: { values: values.map((stringValue) => ({ stringValue })) } }
2232
+ };
2233
+ }
2234
+ function promptToolsAttributeValue(tools) {
2235
+ if (tools === void 0) return void 0;
2236
+ return tools.map((tool2) => {
2237
+ try {
2238
+ return JSON.stringify(tool2);
2239
+ } catch (e) {
2240
+ return JSON.stringify({ type: tool2.type, name: tool2.name });
2241
+ }
2242
+ });
2243
+ }
2099
2244
 
2100
2245
  // ../core/dist/index.node.js
2101
2246
  var import_async_hooks = require("async_hooks");
@@ -2310,15 +2455,16 @@ function loadConfig(projectDirectory) {
2310
2455
  eventMetadata,
2311
2456
  localWorkshopUrl: resolveLocalWorkshopUrl(merged.local_workshop_url),
2312
2457
  appGit: mapAppGit(merged.app_git),
2313
- selfDiagnostics: mapSelfDiagnostics(merged.self_diagnostics)
2458
+ selfDiagnostics: mapSelfDiagnostics(merged.self_diagnostics),
2459
+ tools: Array.isArray(merged.tools) ? merged.tools : void 0
2314
2460
  };
2315
2461
  }
2316
- function isRecord(value) {
2462
+ function isRecord2(value) {
2317
2463
  return typeof value === "object" && value !== null && !Array.isArray(value);
2318
2464
  }
2319
2465
  function mapSelfDiagnostics(value) {
2320
2466
  const env = process.env["RAINDROP_SELF_DIAGNOSTICS"];
2321
- const record = isRecord(value) ? value : {};
2467
+ const record = isRecord2(value) ? value : {};
2322
2468
  const enabled = env !== void 0 ? env === "true" : typeof record["enabled"] === "boolean" ? record["enabled"] : void 0;
2323
2469
  const signals = mapSelfDiagnosticsSignals(record["signals"]);
2324
2470
  const options = {
@@ -2330,10 +2476,10 @@ function mapSelfDiagnostics(value) {
2330
2476
  return Object.keys(options).length > 0 ? options : void 0;
2331
2477
  }
2332
2478
  function mapSelfDiagnosticsSignals(value) {
2333
- if (!isRecord(value)) return void 0;
2479
+ if (!isRecord2(value)) return void 0;
2334
2480
  const out = {};
2335
2481
  for (const [key, entry] of Object.entries(value)) {
2336
- if (!isRecord(entry)) continue;
2482
+ if (!isRecord2(entry)) continue;
2337
2483
  const definition = entry;
2338
2484
  if (typeof definition["description"] !== "string") continue;
2339
2485
  const sentiment = definition["sentiment"];
@@ -2370,7 +2516,7 @@ function resolveLocalWorkshopUrl(fileValue) {
2370
2516
  // package.json
2371
2517
  var package_default = {
2372
2518
  name: "@raindrop-ai/opencode-plugin",
2373
- version: "0.3.0",
2519
+ version: "0.3.1",
2374
2520
  description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
2375
2521
  type: "module",
2376
2522
  main: "dist/index.js",
@@ -2562,13 +2708,13 @@ Categories:
2562
2708
  };
2563
2709
  }
2564
2710
  var SELF_DIAGNOSTICS_ACK = '{"acknowledged":true}';
2565
- function isRecord2(value) {
2711
+ function isRecord3(value) {
2566
2712
  return typeof value === "object" && value !== null && !Array.isArray(value);
2567
2713
  }
2568
2714
  async function trackSelfDiagnosticsSignal(toolInput, config, eventShipper, eventId, debug) {
2569
2715
  var _a, _b;
2570
2716
  try {
2571
- const input = isRecord2(toolInput) ? toolInput : void 0;
2717
+ const input = isRecord3(toolInput) ? toolInput : void 0;
2572
2718
  const fallbackCategory = (_a = config.signalKeys[0]) != null ? _a : "unknown";
2573
2719
  const categoryCandidate = normalizeString(input == null ? void 0 : input["category"]);
2574
2720
  const category = categoryCandidate && config.signalKeySet.has(categoryCandidate) ? categoryCandidate : fallbackCategory;
@@ -2655,6 +2801,100 @@ var TraceShipper3 = class extends TraceShipper2 {
2655
2801
  }
2656
2802
  };
2657
2803
 
2804
+ // src/tool-catalog.ts
2805
+ var PROMPT_TOOLS_SOURCE_ATTRIBUTE = "ai.prompt.tools.source";
2806
+ var OPENCODE_TOOL_LIST_SOURCE = "opencode.tool.list";
2807
+ var TOOL_CATALOG_TIMEOUT_MS = 2e3;
2808
+ function isRecord4(value) {
2809
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2810
+ }
2811
+ function catalogItems(raw) {
2812
+ var _a;
2813
+ const list = Array.isArray(raw) ? raw : isRecord4(raw) && Array.isArray(raw.data) ? raw.data : void 0;
2814
+ if (!list) return void 0;
2815
+ const items = [];
2816
+ for (const item of list) {
2817
+ if (!isRecord4(item)) continue;
2818
+ const name = typeof item.id === "string" ? item.id : typeof item.name === "string" ? item.name : void 0;
2819
+ if (!name) continue;
2820
+ const schema = (_a = item.parameters) != null ? _a : item.input;
2821
+ items.push({
2822
+ type: "function",
2823
+ name,
2824
+ ...typeof item.description === "string" ? { description: item.description } : {},
2825
+ ...isJsonSchema(schema) ? { inputSchema: schema } : {}
2826
+ });
2827
+ }
2828
+ return items;
2829
+ }
2830
+ function withTimeout(promise, ms) {
2831
+ return new Promise((resolve2) => {
2832
+ const timer = setTimeout(() => resolve2(void 0), ms);
2833
+ if (typeof timer === "object" && "unref" in timer) timer.unref();
2834
+ promise.then(
2835
+ (value) => {
2836
+ clearTimeout(timer);
2837
+ resolve2(value);
2838
+ },
2839
+ () => {
2840
+ clearTimeout(timer);
2841
+ resolve2(void 0);
2842
+ }
2843
+ );
2844
+ });
2845
+ }
2846
+ function createToolCatalog(options = {}) {
2847
+ var _a, _b;
2848
+ const override = options.override !== void 0 ? { tools: (_a = normalizeToolDeclarations(options.override)) != null ? _a : [] } : void 0;
2849
+ const timeoutMs = (_b = options.timeoutMs) != null ? _b : TOOL_CATALOG_TIMEOUT_MS;
2850
+ const cache = /* @__PURE__ */ new Map();
2851
+ async function lookup(query) {
2852
+ const fetch2 = options.fetch;
2853
+ if (!fetch2) return void 0;
2854
+ let raw;
2855
+ try {
2856
+ raw = await withTimeout(Promise.resolve().then(() => fetch2(query)), timeoutMs);
2857
+ } catch (error) {
2858
+ if (options.debug) console.log(`[raindrop-ai/opencode-plugin] tool catalog lookup failed: ${String(error)}`);
2859
+ return void 0;
2860
+ }
2861
+ const tools = normalizeToolDeclarations(catalogItems(raw));
2862
+ if (!tools) {
2863
+ if (options.debug) console.log("[raindrop-ai/opencode-plugin] tool catalog lookup returned no tool list");
2864
+ return void 0;
2865
+ }
2866
+ return { tools, source: OPENCODE_TOOL_LIST_SOURCE };
2867
+ }
2868
+ return {
2869
+ resolve(sessionID, query) {
2870
+ if (override) return Promise.resolve(override);
2871
+ if (!options.fetch) return Promise.resolve(void 0);
2872
+ let perSession = cache.get(sessionID);
2873
+ if (!perSession) {
2874
+ perSession = /* @__PURE__ */ new Map();
2875
+ cache.set(sessionID, perSession);
2876
+ }
2877
+ const key = `${query.provider}\0${query.model}`;
2878
+ let pending = perSession.get(key);
2879
+ if (!pending) {
2880
+ pending = lookup(query).catch(() => void 0);
2881
+ perSession.set(key, pending);
2882
+ }
2883
+ return pending;
2884
+ },
2885
+ forget(sessionID) {
2886
+ cache.delete(sessionID);
2887
+ }
2888
+ };
2889
+ }
2890
+ function promptToolsAttributes(resolved) {
2891
+ if (!resolved) return [];
2892
+ return [
2893
+ promptToolsAttribute(resolved.tools),
2894
+ resolved.source !== void 0 ? attrString(PROMPT_TOOLS_SOURCE_ATTRIBUTE, resolved.source) : void 0
2895
+ ];
2896
+ }
2897
+
2658
2898
  // src/tracing.ts
2659
2899
  var import_node_os2 = require("os");
2660
2900
 
@@ -2950,7 +3190,7 @@ function getHostname() {
2950
3190
  return h;
2951
3191
  }
2952
3192
  }
2953
- function createTracing(config, worktree, directory, eventShipper, traceShipper) {
3193
+ function createTracing(config, worktree, directory, eventShipper, traceShipper, toolCatalog) {
2954
3194
  function log(msg, data) {
2955
3195
  if (!config.debug) return;
2956
3196
  const prefix = `[raindrop-ai/opencode-plugin] [info] ${msg}`;
@@ -2977,6 +3217,14 @@ function createTracing(config, worktree, directory, eventShipper, traceShipper)
2977
3217
  log
2978
3218
  });
2979
3219
  const hooks = {
3220
+ // Fires before each model call with the model already resolved: start the
3221
+ // catalog lookup now so the span attributes rarely wait on it later.
3222
+ "chat.params": async (input) => {
3223
+ void (toolCatalog == null ? void 0 : toolCatalog.resolve(input.sessionID, {
3224
+ provider: input.model.providerID,
3225
+ model: input.model.id
3226
+ }));
3227
+ },
2980
3228
  // ------------------------------------------------------------------
2981
3229
  // event — session lifecycle + streaming parts
2982
3230
  // ------------------------------------------------------------------
@@ -3101,6 +3349,9 @@ function createTracing(config, worktree, directory, eventShipper, traceShipper)
3101
3349
  "message.updated: providerID and modelID are required non-empty strings"
3102
3350
  );
3103
3351
  const modelName = `${providerID}/${modelID}`;
3352
+ const promptTools = promptToolsAttributes(
3353
+ await (toolCatalog == null ? void 0 : toolCatalog.resolve(String(msgSessionID), { provider: providerID, model: modelID }))
3354
+ );
3104
3355
  const finishReason = info2["finish"];
3105
3356
  const outputText = state2.outputParts.get(messageId);
3106
3357
  const reasoningText = state2.reasoningParts.get(messageId);
@@ -3126,7 +3377,8 @@ type: ${name != null ? name : "UnknownError"}`;
3126
3377
  const llmAttrs = [
3127
3378
  attrString("ai.operationId", "generateText"),
3128
3379
  attrString("ai.response.text", responseText),
3129
- ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
3380
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj }),
3381
+ ...promptTools
3130
3382
  ];
3131
3383
  if (state2.currentSystemPrompt) {
3132
3384
  llmAttrs.push(
@@ -3162,7 +3414,8 @@ type: ${name != null ? name : "UnknownError"}`;
3162
3414
  buildPromptMessages(state2.currentSystemPrompt, state2.currentInput)
3163
3415
  ),
3164
3416
  attrString("ai.response.text", turnOutputText),
3165
- ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
3417
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj }),
3418
+ ...promptTools
3166
3419
  ];
3167
3420
  if (state2.currentSystemPrompt) {
3168
3421
  llmAttrs.push(
@@ -3207,7 +3460,8 @@ type: ${name != null ? name : "UnknownError"}`;
3207
3460
  buildPromptMessages(state2.currentSystemPrompt, state2.currentInput)
3208
3461
  ),
3209
3462
  attrString("ai.response.text", turnOutputText),
3210
- ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
3463
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj }),
3464
+ ...promptTools
3211
3465
  ];
3212
3466
  if (isCompaction) {
3213
3467
  rootAttrs.push(attrString("is_compaction", "true"));
@@ -3273,6 +3527,7 @@ type: ${name != null ? name : "UnknownError"}`;
3273
3527
  if (state2.parentId) {
3274
3528
  cleanupSessionParentMap(String(sessionID));
3275
3529
  sessions.delete(String(sessionID));
3530
+ toolCatalog == null ? void 0 : toolCatalog.forget(String(sessionID));
3276
3531
  } else {
3277
3532
  }
3278
3533
  } else if (event.type === "session.deleted") {
@@ -3289,6 +3544,7 @@ type: ${name != null ? name : "UnknownError"}`;
3289
3544
  cleanupSessionParentMap(String(sessionID));
3290
3545
  await Promise.all([eventShipper.flush(), traceShipper.flush()]);
3291
3546
  sessions.delete(String(sessionID));
3547
+ toolCatalog == null ? void 0 : toolCatalog.forget(String(sessionID));
3292
3548
  } else if (event.type === "session.error") {
3293
3549
  const errorSessionID = (_h = props["sessionID"]) != null ? _h : sessionID;
3294
3550
  if (errorSessionID === void 0 || errorSessionID === "")
@@ -3314,6 +3570,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
3314
3570
  cleanupSessionParentMap(String(errorSessionID));
3315
3571
  await Promise.all([eventShipper.flush(), traceShipper.flush()]);
3316
3572
  sessions.delete(String(errorSessionID));
3573
+ toolCatalog == null ? void 0 : toolCatalog.forget(String(errorSessionID));
3317
3574
  }
3318
3575
  } catch (err) {
3319
3576
  rateLimitedErrorLog(
@@ -3625,7 +3882,7 @@ function createRaindropHandle(eventShipper, traceShipper, appGitContext, current
3625
3882
  };
3626
3883
  }
3627
3884
  async function plugin(input, opts = {}) {
3628
- var _a, _b, _c, _d;
3885
+ var _a, _b, _c, _d, _e;
3629
3886
  const config = loadConfig(input.directory);
3630
3887
  function appLog(level, message) {
3631
3888
  console.log(`[raindrop-ai/opencode-plugin] [${level}] ${message}`);
@@ -3669,16 +3926,22 @@ async function plugin(input, opts = {}) {
3669
3926
  appGitContext
3670
3927
  });
3671
3928
  const worktree = (_c = input.worktree) != null ? _c : input.directory;
3929
+ const toolCatalog = createToolCatalog({
3930
+ override: (_d = opts.tools) != null ? _d : config.tools,
3931
+ fetch: (query) => input.client.tool.list({ query }),
3932
+ debug: config.debug
3933
+ });
3672
3934
  const tracing = createTracing(
3673
3935
  config,
3674
3936
  worktree,
3675
3937
  input.directory,
3676
3938
  eventShipper,
3677
- traceShipper
3939
+ traceShipper,
3940
+ toolCatalog
3678
3941
  );
3679
3942
  const hooks = tracing.hooks;
3680
3943
  const selfDiagnostics = normalizeSelfDiagnosticsConfig(
3681
- (_d = opts.selfDiagnostics) != null ? _d : config.selfDiagnostics
3944
+ (_e = opts.selfDiagnostics) != null ? _e : config.selfDiagnostics
3682
3945
  );
3683
3946
  if (selfDiagnostics && !config.writeKey.trim()) {
3684
3947
  appLog(
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { I as IdentifyInput, A as AppGitOptions, S as SelfDiagnosticsOptions } from './self-diagnostics-NiAq-SZ8.cjs';
2
- export { a as SelfDiagnosticsSignalDefinition, b as SelfDiagnosticsSignalDefinitions } from './self-diagnostics-NiAq-SZ8.cjs';
1
+ import { I as IdentifyInput, A as AppGitOptions, S as SelfDiagnosticsOptions, P as PromptToolsOverride } from './self-diagnostics-CLW6h4zB.cjs';
2
+ export { a as SelfDiagnosticsSignalDefinition, b as SelfDiagnosticsSignalDefinitions } from './self-diagnostics-CLW6h4zB.cjs';
3
3
  import { Hooks, PluginInput } from '@opencode-ai/plugin';
4
4
 
5
5
  interface RaindropOpencodeOptions {
@@ -7,6 +7,11 @@ interface RaindropOpencodeOptions {
7
7
  appGit?: AppGitOptions | false;
8
8
  /** Built-in self-diagnostics: injects a `__raindrop_report` tool when enabled. */
9
9
  selfDiagnostics?: SelfDiagnosticsOptions;
10
+ /**
11
+ * Overrides the tool catalog recorded as `ai.prompt.tools` on every model
12
+ * span; skips the OpenCode `client.tool.list` lookup entirely.
13
+ */
14
+ tools?: PromptToolsOverride;
10
15
  }
11
16
  /**
12
17
  * The Raindrop control handle exposed alongside the OpenCode hooks.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { I as IdentifyInput, A as AppGitOptions, S as SelfDiagnosticsOptions } from './self-diagnostics-NiAq-SZ8.js';
2
- export { a as SelfDiagnosticsSignalDefinition, b as SelfDiagnosticsSignalDefinitions } from './self-diagnostics-NiAq-SZ8.js';
1
+ import { I as IdentifyInput, A as AppGitOptions, S as SelfDiagnosticsOptions, P as PromptToolsOverride } from './self-diagnostics-CLW6h4zB.js';
2
+ export { a as SelfDiagnosticsSignalDefinition, b as SelfDiagnosticsSignalDefinitions } from './self-diagnostics-CLW6h4zB.js';
3
3
  import { Hooks, PluginInput } from '@opencode-ai/plugin';
4
4
 
5
5
  interface RaindropOpencodeOptions {
@@ -7,6 +7,11 @@ interface RaindropOpencodeOptions {
7
7
  appGit?: AppGitOptions | false;
8
8
  /** Built-in self-diagnostics: injects a `__raindrop_report` tool when enabled. */
9
9
  selfDiagnostics?: SelfDiagnosticsOptions;
10
+ /**
11
+ * Overrides the tool catalog recorded as `ai.prompt.tools` on every model
12
+ * span; skips the OpenCode `client.tool.list` lookup entirely.
13
+ */
14
+ tools?: PromptToolsOverride;
10
15
  }
11
16
  /**
12
17
  * The Raindrop control handle exposed alongside the OpenCode hooks.
package/dist/index.js CHANGED
@@ -9,13 +9,15 @@ import {
9
9
  buildLlmGenAiAttributes,
10
10
  capText,
11
11
  createAppGitContext,
12
+ createToolCatalog,
12
13
  generateId,
13
14
  loadConfig,
14
15
  normalizeSelfDiagnosticsConfig,
15
16
  nowUnixNanoString,
17
+ promptToolsAttributes,
16
18
  resolveLocalDebuggerBaseUrl,
17
19
  trackSelfDiagnosticsSignal
18
- } from "./chunk-FCORH4YS.js";
20
+ } from "./chunk-ZG47FVK7.js";
19
21
 
20
22
  // src/index.ts
21
23
  import { tool } from "@opencode-ai/plugin";
@@ -194,7 +196,7 @@ function getHostname() {
194
196
  return h;
195
197
  }
196
198
  }
197
- function createTracing(config, worktree, directory, eventShipper, traceShipper) {
199
+ function createTracing(config, worktree, directory, eventShipper, traceShipper, toolCatalog) {
198
200
  function log(msg, data) {
199
201
  if (!config.debug) return;
200
202
  const prefix = `[raindrop-ai/opencode-plugin] [info] ${msg}`;
@@ -221,6 +223,14 @@ function createTracing(config, worktree, directory, eventShipper, traceShipper)
221
223
  log
222
224
  });
223
225
  const hooks = {
226
+ // Fires before each model call with the model already resolved: start the
227
+ // catalog lookup now so the span attributes rarely wait on it later.
228
+ "chat.params": async (input) => {
229
+ void (toolCatalog == null ? void 0 : toolCatalog.resolve(input.sessionID, {
230
+ provider: input.model.providerID,
231
+ model: input.model.id
232
+ }));
233
+ },
224
234
  // ------------------------------------------------------------------
225
235
  // event — session lifecycle + streaming parts
226
236
  // ------------------------------------------------------------------
@@ -345,6 +355,9 @@ function createTracing(config, worktree, directory, eventShipper, traceShipper)
345
355
  "message.updated: providerID and modelID are required non-empty strings"
346
356
  );
347
357
  const modelName = `${providerID}/${modelID}`;
358
+ const promptTools = promptToolsAttributes(
359
+ await (toolCatalog == null ? void 0 : toolCatalog.resolve(String(msgSessionID), { provider: providerID, model: modelID }))
360
+ );
348
361
  const finishReason = info2["finish"];
349
362
  const outputText = state.outputParts.get(messageId);
350
363
  const reasoningText = state.reasoningParts.get(messageId);
@@ -370,7 +383,8 @@ type: ${name != null ? name : "UnknownError"}`;
370
383
  const llmAttrs = [
371
384
  attrString("ai.operationId", "generateText"),
372
385
  attrString("ai.response.text", responseText),
373
- ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
386
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj }),
387
+ ...promptTools
374
388
  ];
375
389
  if (state.currentSystemPrompt) {
376
390
  llmAttrs.push(
@@ -406,7 +420,8 @@ type: ${name != null ? name : "UnknownError"}`;
406
420
  buildPromptMessages(state.currentSystemPrompt, state.currentInput)
407
421
  ),
408
422
  attrString("ai.response.text", turnOutputText),
409
- ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
423
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj }),
424
+ ...promptTools
410
425
  ];
411
426
  if (state.currentSystemPrompt) {
412
427
  llmAttrs.push(
@@ -451,7 +466,8 @@ type: ${name != null ? name : "UnknownError"}`;
451
466
  buildPromptMessages(state.currentSystemPrompt, state.currentInput)
452
467
  ),
453
468
  attrString("ai.response.text", turnOutputText),
454
- ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj })
469
+ ...buildLlmGenAiAttributes({ providerID, modelID, tokens: tokensObj }),
470
+ ...promptTools
455
471
  ];
456
472
  if (isCompaction) {
457
473
  rootAttrs.push(attrString("is_compaction", "true"));
@@ -517,6 +533,7 @@ type: ${name != null ? name : "UnknownError"}`;
517
533
  if (state.parentId) {
518
534
  cleanupSessionParentMap(String(sessionID));
519
535
  sessions.delete(String(sessionID));
536
+ toolCatalog == null ? void 0 : toolCatalog.forget(String(sessionID));
520
537
  } else {
521
538
  }
522
539
  } else if (event.type === "session.deleted") {
@@ -533,6 +550,7 @@ type: ${name != null ? name : "UnknownError"}`;
533
550
  cleanupSessionParentMap(String(sessionID));
534
551
  await Promise.all([eventShipper.flush(), traceShipper.flush()]);
535
552
  sessions.delete(String(sessionID));
553
+ toolCatalog == null ? void 0 : toolCatalog.forget(String(sessionID));
536
554
  } else if (event.type === "session.error") {
537
555
  const errorSessionID = (_h = props["sessionID"]) != null ? _h : sessionID;
538
556
  if (errorSessionID === void 0 || errorSessionID === "")
@@ -558,6 +576,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
558
576
  cleanupSessionParentMap(String(errorSessionID));
559
577
  await Promise.all([eventShipper.flush(), traceShipper.flush()]);
560
578
  sessions.delete(String(errorSessionID));
579
+ toolCatalog == null ? void 0 : toolCatalog.forget(String(errorSessionID));
561
580
  }
562
581
  } catch (err) {
563
582
  rateLimitedErrorLog(
@@ -869,7 +888,7 @@ function createRaindropHandle(eventShipper, traceShipper, appGitContext, current
869
888
  };
870
889
  }
871
890
  async function plugin(input, opts = {}) {
872
- var _a, _b, _c, _d;
891
+ var _a, _b, _c, _d, _e;
873
892
  const config = loadConfig(input.directory);
874
893
  function appLog(level, message) {
875
894
  console.log(`[raindrop-ai/opencode-plugin] [${level}] ${message}`);
@@ -913,16 +932,22 @@ async function plugin(input, opts = {}) {
913
932
  appGitContext
914
933
  });
915
934
  const worktree = (_c = input.worktree) != null ? _c : input.directory;
935
+ const toolCatalog = createToolCatalog({
936
+ override: (_d = opts.tools) != null ? _d : config.tools,
937
+ fetch: (query) => input.client.tool.list({ query }),
938
+ debug: config.debug
939
+ });
916
940
  const tracing = createTracing(
917
941
  config,
918
942
  worktree,
919
943
  input.directory,
920
944
  eventShipper,
921
- traceShipper
945
+ traceShipper,
946
+ toolCatalog
922
947
  );
923
948
  const hooks = tracing.hooks;
924
949
  const selfDiagnostics = normalizeSelfDiagnosticsConfig(
925
- (_d = opts.selfDiagnostics) != null ? _d : config.selfDiagnostics
950
+ (_e = opts.selfDiagnostics) != null ? _e : config.selfDiagnostics
926
951
  );
927
952
  if (selfDiagnostics && !config.writeKey.trim()) {
928
953
  appLog(
@@ -85,6 +85,32 @@ declare global {
85
85
  var RAINDROP_CONTEXT_MANAGER: (new () => ContextManager) | undefined;
86
86
  var RAINDROP_ASYNC_LOCAL_STORAGE: (new <T>() => AsyncLocalStorageLike<T>) | undefined;
87
87
  }
88
+ type JsonSchema = Record<string, unknown>;
89
+ type FunctionToolDeclaration = {
90
+ type: "function";
91
+ name: string;
92
+ description?: string;
93
+ inputSchema?: JsonSchema;
94
+ };
95
+ type ProviderDefinedToolDeclaration = {
96
+ type: "provider-defined";
97
+ id?: string;
98
+ name: string;
99
+ args?: Record<string, unknown>;
100
+ };
101
+ type ToolDeclaration = FunctionToolDeclaration | ProviderDefinedToolDeclaration;
102
+ /**
103
+ * What callers hand to a `tools` override. The canonical `ToolDeclaration`
104
+ * shape is accepted as-is; so is any framework shape `normalizeToolDeclarations`
105
+ * recognizes (OpenAI, Anthropic, Bedrock, LangChain, Pi, Mastra, AI SDK).
106
+ */
107
+ type ToolDeclarationInput = ToolDeclaration | {
108
+ name: string;
109
+ description?: string;
110
+ inputSchema?: JsonSchema;
111
+ parameters?: JsonSchema;
112
+ };
113
+ type PromptToolsOverride = ReadonlyArray<ToolDeclarationInput | unknown>;
88
114
 
89
115
  type SelfDiagnosticsSignalDefinition = {
90
116
  /**
@@ -119,4 +145,4 @@ type SelfDiagnosticsOptions = {
119
145
  toolName?: string;
120
146
  };
121
147
 
122
- export type { AppGitOptions as A, IdentifyInput as I, SelfDiagnosticsOptions as S, SelfDiagnosticsSignalDefinition as a, SelfDiagnosticsSignalDefinitions as b, SignalInput as c };
148
+ export type { AppGitOptions as A, IdentifyInput as I, PromptToolsOverride as P, SelfDiagnosticsOptions as S, SelfDiagnosticsSignalDefinition as a, SelfDiagnosticsSignalDefinitions as b, SignalInput as c };