@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.
@@ -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 };
package/dist/v2.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 tool = { type: "function", name };
2143
+ const desc = pickString(description);
2144
+ if (desc !== void 0) tool.description = desc;
2145
+ const inputSchema = toJsonSchema(schema, options);
2146
+ if (inputSchema !== void 0) tool.inputSchema = inputSchema;
2147
+ return tool;
2148
+ }
2149
+ function normalizeToolDeclaration(tool, options, fallbackName) {
2150
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
2151
+ if (!isRecord(tool)) return void 0;
2152
+ if (tool.type === "provider-defined" || tool.type === "provider") {
2153
+ const name2 = (_b = (_a = pickString(tool.name)) != null ? _a : pickString(tool.id)) != null ? _b : fallbackName;
2154
+ if (name2 === void 0) return void 0;
2155
+ const declaration = { type: "provider-defined", name: name2 };
2156
+ const id = pickString(tool.id);
2157
+ if (id !== void 0) declaration.id = id;
2158
+ if (isRecord(tool.args)) declaration.args = tool.args;
2159
+ return declaration;
2160
+ }
2161
+ if (isRecord(tool.toolSpec)) {
2162
+ const spec = tool.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(tool.function)) {
2168
+ const fn = tool.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(tool.name)) != null ? _f : pickString(tool.id)) != null ? _g : fallbackName;
2174
+ if (name === void 0) return void 0;
2175
+ const type = pickString(tool.type);
2176
+ if (type !== void 0 && type !== "function" && type !== "dynamic" && type !== "custom") {
2177
+ if (!("input_schema" in tool) && !("inputSchema" in tool) && !("parameters" in tool)) {
2178
+ return { type: "provider-defined", id: type, name };
2179
+ }
2180
+ }
2181
+ return functionTool(
2182
+ name,
2183
+ tool.description,
2184
+ (_j = (_i = (_h = tool.inputSchema) != null ? _h : tool.input_schema) != null ? _i : tool.parameters) != null ? _j : tool.schema,
2185
+ options
2186
+ );
2187
+ }
2188
+ var NOT_A_GEMINI_GROUP_KEYS = ["name", "id", "type", "function", "toolSpec", "cachePoint"];
2189
+ function isGeminiToolGroup(tool) {
2190
+ if (Array.isArray(tool.functionDeclarations)) return true;
2191
+ const keys = Object.keys(tool);
2192
+ if (keys.length === 0 || NOT_A_GEMINI_GROUP_KEYS.some((key) => key in tool)) return false;
2193
+ return keys.every((key) => isRecord(tool[key]));
2194
+ }
2195
+ function expandGeminiTool(tool, options) {
2196
+ const declarations = [];
2197
+ for (const [key, value] of Object.entries(tool)) {
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((tool) => {
2213
+ if (isRecord(tool) && isGeminiToolGroup(tool)) {
2214
+ return expandGeminiTool(tool, options);
2215
+ }
2216
+ const declaration = normalizeToolDeclaration(tool, options);
2217
+ return declaration ? [declaration] : [];
2218
+ });
2219
+ }
2220
+ if (!isRecord(tools)) return void 0;
2221
+ return Object.entries(tools).flatMap(([key, tool]) => {
2222
+ const declaration = normalizeToolDeclaration(tool, 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((tool) => {
2237
+ try {
2238
+ return JSON.stringify(tool);
2239
+ } catch (e) {
2240
+ return JSON.stringify({ type: tool.type, name: tool.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",
@@ -2580,13 +2726,13 @@ function selfDiagnosticsInputJsonSchema(config) {
2580
2726
  additionalProperties: false
2581
2727
  };
2582
2728
  }
2583
- function isRecord2(value) {
2729
+ function isRecord3(value) {
2584
2730
  return typeof value === "object" && value !== null && !Array.isArray(value);
2585
2731
  }
2586
2732
  async function trackSelfDiagnosticsSignal(toolInput, config, eventShipper, eventId, debug) {
2587
2733
  var _a, _b;
2588
2734
  try {
2589
- const input = isRecord2(toolInput) ? toolInput : void 0;
2735
+ const input = isRecord3(toolInput) ? toolInput : void 0;
2590
2736
  const fallbackCategory = (_a = config.signalKeys[0]) != null ? _a : "unknown";
2591
2737
  const categoryCandidate = normalizeString(input == null ? void 0 : input["category"]);
2592
2738
  const category = categoryCandidate && config.signalKeySet.has(categoryCandidate) ? categoryCandidate : fallbackCategory;
@@ -2673,6 +2819,100 @@ var TraceShipper3 = class extends TraceShipper2 {
2673
2819
  }
2674
2820
  };
2675
2821
 
2822
+ // src/tool-catalog.ts
2823
+ var PROMPT_TOOLS_SOURCE_ATTRIBUTE = "ai.prompt.tools.source";
2824
+ var OPENCODE_TOOL_LIST_SOURCE = "opencode.tool.list";
2825
+ var TOOL_CATALOG_TIMEOUT_MS = 2e3;
2826
+ function isRecord4(value) {
2827
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2828
+ }
2829
+ function catalogItems(raw) {
2830
+ var _a;
2831
+ const list = Array.isArray(raw) ? raw : isRecord4(raw) && Array.isArray(raw.data) ? raw.data : void 0;
2832
+ if (!list) return void 0;
2833
+ const items = [];
2834
+ for (const item of list) {
2835
+ if (!isRecord4(item)) continue;
2836
+ const name = typeof item.id === "string" ? item.id : typeof item.name === "string" ? item.name : void 0;
2837
+ if (!name) continue;
2838
+ const schema = (_a = item.parameters) != null ? _a : item.input;
2839
+ items.push({
2840
+ type: "function",
2841
+ name,
2842
+ ...typeof item.description === "string" ? { description: item.description } : {},
2843
+ ...isJsonSchema(schema) ? { inputSchema: schema } : {}
2844
+ });
2845
+ }
2846
+ return items;
2847
+ }
2848
+ function withTimeout(promise, ms) {
2849
+ return new Promise((resolve2) => {
2850
+ const timer = setTimeout(() => resolve2(void 0), ms);
2851
+ if (typeof timer === "object" && "unref" in timer) timer.unref();
2852
+ promise.then(
2853
+ (value) => {
2854
+ clearTimeout(timer);
2855
+ resolve2(value);
2856
+ },
2857
+ () => {
2858
+ clearTimeout(timer);
2859
+ resolve2(void 0);
2860
+ }
2861
+ );
2862
+ });
2863
+ }
2864
+ function createToolCatalog(options = {}) {
2865
+ var _a, _b;
2866
+ const override = options.override !== void 0 ? { tools: (_a = normalizeToolDeclarations(options.override)) != null ? _a : [] } : void 0;
2867
+ const timeoutMs = (_b = options.timeoutMs) != null ? _b : TOOL_CATALOG_TIMEOUT_MS;
2868
+ const cache = /* @__PURE__ */ new Map();
2869
+ async function lookup(query) {
2870
+ const fetch2 = options.fetch;
2871
+ if (!fetch2) return void 0;
2872
+ let raw;
2873
+ try {
2874
+ raw = await withTimeout(Promise.resolve().then(() => fetch2(query)), timeoutMs);
2875
+ } catch (error) {
2876
+ if (options.debug) console.log(`[raindrop-ai/opencode-plugin] tool catalog lookup failed: ${String(error)}`);
2877
+ return void 0;
2878
+ }
2879
+ const tools = normalizeToolDeclarations(catalogItems(raw));
2880
+ if (!tools) {
2881
+ if (options.debug) console.log("[raindrop-ai/opencode-plugin] tool catalog lookup returned no tool list");
2882
+ return void 0;
2883
+ }
2884
+ return { tools, source: OPENCODE_TOOL_LIST_SOURCE };
2885
+ }
2886
+ return {
2887
+ resolve(sessionID, query) {
2888
+ if (override) return Promise.resolve(override);
2889
+ if (!options.fetch) return Promise.resolve(void 0);
2890
+ let perSession = cache.get(sessionID);
2891
+ if (!perSession) {
2892
+ perSession = /* @__PURE__ */ new Map();
2893
+ cache.set(sessionID, perSession);
2894
+ }
2895
+ const key = `${query.provider}\0${query.model}`;
2896
+ let pending = perSession.get(key);
2897
+ if (!pending) {
2898
+ pending = lookup(query).catch(() => void 0);
2899
+ perSession.set(key, pending);
2900
+ }
2901
+ return pending;
2902
+ },
2903
+ forget(sessionID) {
2904
+ cache.delete(sessionID);
2905
+ }
2906
+ };
2907
+ }
2908
+ function promptToolsAttributes(resolved) {
2909
+ if (!resolved) return [];
2910
+ return [
2911
+ promptToolsAttribute(resolved.tools),
2912
+ resolved.source !== void 0 ? attrString(PROMPT_TOOLS_SOURCE_ATTRIBUTE, resolved.source) : void 0
2913
+ ];
2914
+ }
2915
+
2676
2916
  // src/tracing-v2.ts
2677
2917
  var import_node_os2 = require("os");
2678
2918
 
@@ -2846,7 +3086,7 @@ function errorString(error) {
2846
3086
 
2847
3087
  type: ${error.type}`;
2848
3088
  }
2849
- function createV2Tracing(config, eventShipper, traceShipper, sessionLookup) {
3089
+ function createV2Tracing(config, eventShipper, traceShipper, sessionLookup, toolCatalog) {
2850
3090
  const sessions = /* @__PURE__ */ new Map();
2851
3091
  const admittedPrompts = /* @__PURE__ */ new Map();
2852
3092
  const subagentLinks = /* @__PURE__ */ new Map();
@@ -2886,7 +3126,7 @@ function createV2Tracing(config, eventShipper, traceShipper, sessionLookup) {
2886
3126
  async function finalizeTurn(state2, error) {
2887
3127
  var _a, _b, _c;
2888
3128
  for (const step of state2.stepSpans.values()) {
2889
- traceShipper.endSpan(step.span);
3129
+ traceShipper.endSpan(step.span, { attributes: promptToolsAttributes(await step.promptTools) });
2890
3130
  }
2891
3131
  state2.stepSpans.clear();
2892
3132
  if (state2.compactionSpan) {
@@ -3141,7 +3381,13 @@ function createV2Tracing(config, eventShipper, traceShipper, sessionLookup) {
3141
3381
  state2.stepSpans.set(data.assistantMessageID, {
3142
3382
  span,
3143
3383
  providerID: data.model.providerID,
3144
- modelID: data.model.id
3384
+ modelID: data.model.id,
3385
+ // Kicked off here, awaited when the step closes: the lookup (bounded
3386
+ // by its timeout) never delays the events streamed during the step.
3387
+ promptTools: toolCatalog ? toolCatalog.resolve(data.sessionID, {
3388
+ provider: data.model.providerID,
3389
+ model: data.model.id
3390
+ }) : Promise.resolve(void 0)
3145
3391
  });
3146
3392
  break;
3147
3393
  }
@@ -3172,6 +3418,7 @@ function createV2Tracing(config, eventShipper, traceShipper, sessionLookup) {
3172
3418
  if (reasoning !== void 0) {
3173
3419
  attrs.push(attrString("ai.reasoning.text", reasoning));
3174
3420
  }
3421
+ attrs.push(...promptToolsAttributes(await step.promptTools));
3175
3422
  traceShipper.endSpan(step.span, { attributes: attrs });
3176
3423
  break;
3177
3424
  }
@@ -3181,7 +3428,10 @@ function createV2Tracing(config, eventShipper, traceShipper, sessionLookup) {
3181
3428
  const step = state2 == null ? void 0 : state2.stepSpans.get(data.assistantMessageID);
3182
3429
  if (!state2 || !step) return;
3183
3430
  state2.stepSpans.delete(data.assistantMessageID);
3184
- traceShipper.endSpan(step.span, { error: errorString(data.error) });
3431
+ traceShipper.endSpan(step.span, {
3432
+ error: errorString(data.error),
3433
+ attributes: promptToolsAttributes(await step.promptTools)
3434
+ });
3185
3435
  break;
3186
3436
  }
3187
3437
  case "session.text.ended": {
@@ -3357,6 +3607,7 @@ function createV2Tracing(config, eventShipper, traceShipper, sessionLookup) {
3357
3607
  }
3358
3608
  }
3359
3609
  sessions.delete(event.data.sessionID);
3610
+ toolCatalog == null ? void 0 : toolCatalog.forget(event.data.sessionID);
3360
3611
  break;
3361
3612
  }
3362
3613
  }
@@ -3434,7 +3685,7 @@ function raindrop(opts = {}) {
3434
3685
  }
3435
3686
  }
3436
3687
  function createShared(ctx) {
3437
- var _a, _b, _c, _d;
3688
+ var _a, _b, _c, _d, _e;
3438
3689
  const config = loadConfig((_a = opts.directory) != null ? _a : ctx.location.directory);
3439
3690
  const resolvedLocalUrl = resolveLocalDebuggerBaseUrl(config.localWorkshopUrl);
3440
3691
  const hasLocalDestination = resolvedLocalUrl !== null;
@@ -3484,12 +3735,24 @@ function raindrop(opts = {}) {
3484
3735
  appGitContext,
3485
3736
  abort: new AbortController(),
3486
3737
  instances: /* @__PURE__ */ new Set(),
3487
- runtime: createV2Tracing(config, eventShipper, traceShipper, async (sessionID) => {
3488
- const instance = firstInstance(s);
3489
- if (!instance) return void 0;
3490
- const info = await instance.ctx.session.get({ sessionID });
3491
- return { parentID: info.parentID };
3492
- })
3738
+ runtime: createV2Tracing(
3739
+ config,
3740
+ eventShipper,
3741
+ traceShipper,
3742
+ async (sessionID) => {
3743
+ const instance = firstInstance(s);
3744
+ if (!instance) return void 0;
3745
+ const info = await instance.ctx.session.get({ sessionID });
3746
+ return { parentID: info.parentID };
3747
+ },
3748
+ createToolCatalog({
3749
+ override: (_e = opts.tools) != null ? _e : config.tools,
3750
+ // v2 exposes no per-model catalog endpoint; the registered tool list
3751
+ // (after every plugin transform) is the closest observable catalog.
3752
+ fetch: () => ctx.tool.list(),
3753
+ debug: config.debug
3754
+ })
3755
+ )
3493
3756
  };
3494
3757
  return s;
3495
3758
  }
package/dist/v2.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Plugin } from '@opencode/plugin';
2
- import { I as IdentifyInput, c as SignalInput, A as AppGitOptions, S as SelfDiagnosticsOptions } from './self-diagnostics-NiAq-SZ8.cjs';
3
- export { a as SelfDiagnosticsSignalDefinition, b as SelfDiagnosticsSignalDefinitions } from './self-diagnostics-NiAq-SZ8.cjs';
2
+ import { I as IdentifyInput, c as SignalInput, A as AppGitOptions, S as SelfDiagnosticsOptions, P as PromptToolsOverride } from './self-diagnostics-CLW6h4zB.cjs';
3
+ export { a as SelfDiagnosticsSignalDefinition, b as SelfDiagnosticsSignalDefinitions } from './self-diagnostics-CLW6h4zB.cjs';
4
4
 
5
5
  interface RaindropOpencodeV2Options {
6
6
  /** Application Git identity override. false disables enrichment. */
@@ -9,6 +9,11 @@ interface RaindropOpencodeV2Options {
9
9
  directory?: string;
10
10
  /** Built-in self-diagnostics: injects a `__raindrop_report` tool when enabled. */
11
11
  selfDiagnostics?: SelfDiagnosticsOptions;
12
+ /**
13
+ * Overrides the tool catalog recorded as `ai.prompt.tools` on every model
14
+ * span; skips the OpenCode `ctx.tool.list()` lookup entirely.
15
+ */
16
+ tools?: PromptToolsOverride;
12
17
  }
13
18
  /**
14
19
  * The Raindrop control handle exposed on the v2 plugin. Unlike v1 (where the
package/dist/v2.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Plugin } from '@opencode/plugin';
2
- import { I as IdentifyInput, c as SignalInput, A as AppGitOptions, S as SelfDiagnosticsOptions } from './self-diagnostics-NiAq-SZ8.js';
3
- export { a as SelfDiagnosticsSignalDefinition, b as SelfDiagnosticsSignalDefinitions } from './self-diagnostics-NiAq-SZ8.js';
2
+ import { I as IdentifyInput, c as SignalInput, A as AppGitOptions, S as SelfDiagnosticsOptions, P as PromptToolsOverride } from './self-diagnostics-CLW6h4zB.js';
3
+ export { a as SelfDiagnosticsSignalDefinition, b as SelfDiagnosticsSignalDefinitions } from './self-diagnostics-CLW6h4zB.js';
4
4
 
5
5
  interface RaindropOpencodeV2Options {
6
6
  /** Application Git identity override. false disables enrichment. */
@@ -9,6 +9,11 @@ interface RaindropOpencodeV2Options {
9
9
  directory?: string;
10
10
  /** Built-in self-diagnostics: injects a `__raindrop_report` tool when enabled. */
11
11
  selfDiagnostics?: SelfDiagnosticsOptions;
12
+ /**
13
+ * Overrides the tool catalog recorded as `ai.prompt.tools` on every model
14
+ * span; skips the OpenCode `ctx.tool.list()` lookup entirely.
15
+ */
16
+ tools?: PromptToolsOverride;
12
17
  }
13
18
  /**
14
19
  * The Raindrop control handle exposed on the v2 plugin. Unlike v1 (where the
package/dist/v2.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,
16
+ promptToolsAttributes,
15
17
  resolveLocalDebuggerBaseUrl,
16
18
  selfDiagnosticsInputJsonSchema,
17
19
  trackSelfDiagnosticsSignal
18
- } from "./chunk-FCORH4YS.js";
20
+ } from "./chunk-ZG47FVK7.js";
19
21
 
20
22
  // src/v2.ts
21
23
  import { Plugin } from "@opencode/plugin";
@@ -70,7 +72,7 @@ function errorString(error) {
70
72
 
71
73
  type: ${error.type}`;
72
74
  }
73
- function createV2Tracing(config, eventShipper, traceShipper, sessionLookup) {
75
+ function createV2Tracing(config, eventShipper, traceShipper, sessionLookup, toolCatalog) {
74
76
  const sessions = /* @__PURE__ */ new Map();
75
77
  const admittedPrompts = /* @__PURE__ */ new Map();
76
78
  const subagentLinks = /* @__PURE__ */ new Map();
@@ -110,7 +112,7 @@ function createV2Tracing(config, eventShipper, traceShipper, sessionLookup) {
110
112
  async function finalizeTurn(state, error) {
111
113
  var _a, _b, _c;
112
114
  for (const step of state.stepSpans.values()) {
113
- traceShipper.endSpan(step.span);
115
+ traceShipper.endSpan(step.span, { attributes: promptToolsAttributes(await step.promptTools) });
114
116
  }
115
117
  state.stepSpans.clear();
116
118
  if (state.compactionSpan) {
@@ -365,7 +367,13 @@ function createV2Tracing(config, eventShipper, traceShipper, sessionLookup) {
365
367
  state.stepSpans.set(data.assistantMessageID, {
366
368
  span,
367
369
  providerID: data.model.providerID,
368
- modelID: data.model.id
370
+ modelID: data.model.id,
371
+ // Kicked off here, awaited when the step closes: the lookup (bounded
372
+ // by its timeout) never delays the events streamed during the step.
373
+ promptTools: toolCatalog ? toolCatalog.resolve(data.sessionID, {
374
+ provider: data.model.providerID,
375
+ model: data.model.id
376
+ }) : Promise.resolve(void 0)
369
377
  });
370
378
  break;
371
379
  }
@@ -396,6 +404,7 @@ function createV2Tracing(config, eventShipper, traceShipper, sessionLookup) {
396
404
  if (reasoning !== void 0) {
397
405
  attrs.push(attrString("ai.reasoning.text", reasoning));
398
406
  }
407
+ attrs.push(...promptToolsAttributes(await step.promptTools));
399
408
  traceShipper.endSpan(step.span, { attributes: attrs });
400
409
  break;
401
410
  }
@@ -405,7 +414,10 @@ function createV2Tracing(config, eventShipper, traceShipper, sessionLookup) {
405
414
  const step = state == null ? void 0 : state.stepSpans.get(data.assistantMessageID);
406
415
  if (!state || !step) return;
407
416
  state.stepSpans.delete(data.assistantMessageID);
408
- traceShipper.endSpan(step.span, { error: errorString(data.error) });
417
+ traceShipper.endSpan(step.span, {
418
+ error: errorString(data.error),
419
+ attributes: promptToolsAttributes(await step.promptTools)
420
+ });
409
421
  break;
410
422
  }
411
423
  case "session.text.ended": {
@@ -581,6 +593,7 @@ function createV2Tracing(config, eventShipper, traceShipper, sessionLookup) {
581
593
  }
582
594
  }
583
595
  sessions.delete(event.data.sessionID);
596
+ toolCatalog == null ? void 0 : toolCatalog.forget(event.data.sessionID);
584
597
  break;
585
598
  }
586
599
  }
@@ -658,7 +671,7 @@ function raindrop(opts = {}) {
658
671
  }
659
672
  }
660
673
  function createShared(ctx) {
661
- var _a, _b, _c, _d;
674
+ var _a, _b, _c, _d, _e;
662
675
  const config = loadConfig((_a = opts.directory) != null ? _a : ctx.location.directory);
663
676
  const resolvedLocalUrl = resolveLocalDebuggerBaseUrl(config.localWorkshopUrl);
664
677
  const hasLocalDestination = resolvedLocalUrl !== null;
@@ -708,12 +721,24 @@ function raindrop(opts = {}) {
708
721
  appGitContext,
709
722
  abort: new AbortController(),
710
723
  instances: /* @__PURE__ */ new Set(),
711
- runtime: createV2Tracing(config, eventShipper, traceShipper, async (sessionID) => {
712
- const instance = firstInstance(s);
713
- if (!instance) return void 0;
714
- const info = await instance.ctx.session.get({ sessionID });
715
- return { parentID: info.parentID };
716
- })
724
+ runtime: createV2Tracing(
725
+ config,
726
+ eventShipper,
727
+ traceShipper,
728
+ async (sessionID) => {
729
+ const instance = firstInstance(s);
730
+ if (!instance) return void 0;
731
+ const info = await instance.ctx.session.get({ sessionID });
732
+ return { parentID: info.parentID };
733
+ },
734
+ createToolCatalog({
735
+ override: (_e = opts.tools) != null ? _e : config.tools,
736
+ // v2 exposes no per-model catalog endpoint; the registered tool list
737
+ // (after every plugin transform) is the closest observable catalog.
738
+ fetch: () => ctx.tool.list(),
739
+ debug: config.debug
740
+ })
741
+ )
717
742
  };
718
743
  return s;
719
744
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raindrop-ai/opencode-plugin",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Raindrop observability plugin for OpenCode — automatic session/event/span tracing",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -55,7 +55,7 @@
55
55
  "@types/node": "^20.11.17",
56
56
  "tsup": "^8.4.0",
57
57
  "typescript": "^5.3.3",
58
- "@raindrop-ai/core": "0.3.0"
58
+ "@raindrop-ai/core": "0.3.1"
59
59
  },
60
60
  "tsup": {
61
61
  "entry": [