@raindrop-ai/opencode-plugin 0.2.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/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");
@@ -2309,8 +2454,41 @@ function loadConfig(projectDirectory) {
2309
2454
  captureSystemPrompt: process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] !== void 0 ? process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] === "true" : (_h = merged.capture_system_prompt) != null ? _h : false,
2310
2455
  eventMetadata,
2311
2456
  localWorkshopUrl: resolveLocalWorkshopUrl(merged.local_workshop_url),
2312
- appGit: mapAppGit(merged.app_git)
2457
+ appGit: mapAppGit(merged.app_git),
2458
+ selfDiagnostics: mapSelfDiagnostics(merged.self_diagnostics),
2459
+ tools: Array.isArray(merged.tools) ? merged.tools : void 0
2460
+ };
2461
+ }
2462
+ function isRecord2(value) {
2463
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2464
+ }
2465
+ function mapSelfDiagnostics(value) {
2466
+ const env = process.env["RAINDROP_SELF_DIAGNOSTICS"];
2467
+ const record = isRecord2(value) ? value : {};
2468
+ const enabled = env !== void 0 ? env === "true" : typeof record["enabled"] === "boolean" ? record["enabled"] : void 0;
2469
+ const signals = mapSelfDiagnosticsSignals(record["signals"]);
2470
+ const options = {
2471
+ ...enabled !== void 0 ? { enabled } : {},
2472
+ ...signals ? { signals } : {},
2473
+ ...typeof record["guidance"] === "string" ? { guidance: record["guidance"] } : {},
2474
+ ...typeof record["tool_name"] === "string" ? { toolName: record["tool_name"] } : {}
2313
2475
  };
2476
+ return Object.keys(options).length > 0 ? options : void 0;
2477
+ }
2478
+ function mapSelfDiagnosticsSignals(value) {
2479
+ if (!isRecord2(value)) return void 0;
2480
+ const out = {};
2481
+ for (const [key, entry] of Object.entries(value)) {
2482
+ if (!isRecord2(entry)) continue;
2483
+ const definition = entry;
2484
+ if (typeof definition["description"] !== "string") continue;
2485
+ const sentiment = definition["sentiment"];
2486
+ out[key] = {
2487
+ description: definition["description"],
2488
+ ...sentiment === "POSITIVE" || sentiment === "NEGATIVE" ? { sentiment } : {}
2489
+ };
2490
+ }
2491
+ return Object.keys(out).length > 0 ? out : void 0;
2314
2492
  }
2315
2493
  function mapAppGit(value) {
2316
2494
  if (value === false) return false;
@@ -2338,7 +2516,7 @@ function resolveLocalWorkshopUrl(fileValue) {
2338
2516
  // package.json
2339
2517
  var package_default = {
2340
2518
  name: "@raindrop-ai/opencode-plugin",
2341
- version: "0.2.0",
2519
+ version: "0.3.1",
2342
2520
  description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
2343
2521
  type: "module",
2344
2522
  main: "dist/index.js",
@@ -2427,6 +2605,185 @@ var package_default = {
2427
2605
  var PLUGIN_NAME = package_default.name;
2428
2606
  var PLUGIN_VERSION = package_default.version;
2429
2607
 
2608
+ // src/self-diagnostics.ts
2609
+ var SELF_DIAGNOSTICS_TOOL_NAME_DEFAULT = "__raindrop_report";
2610
+ var SELF_DIAGNOSTICS_SIGNALS_DEFAULT = {
2611
+ missing_context: {
2612
+ description: "You cannot complete the task because critical information, credentials, or access is missing and the user cannot provide it. Do NOT report this for normal clarifying questions \u2014 only when you are blocked.",
2613
+ sentiment: "NEGATIVE"
2614
+ },
2615
+ repeatedly_broken_tool: {
2616
+ description: "A tool has failed or not returned the expected response on multiple distinct attempts in this conversation, preventing task completion. A single tool error is NOT enough \u2014 the tool must be persistently broken or aberrantly behaving across retries.",
2617
+ sentiment: "NEGATIVE"
2618
+ },
2619
+ capability_gap: {
2620
+ description: "The task requires a tool, permission, or capability that you do not have. For example, the user asks you to perform an action but no suitable tool exists, or you lack the necessary access. Do NOT report this if you simply need more information from the user \u2014 only when the gap is in your own capabilities.",
2621
+ sentiment: "NEGATIVE"
2622
+ },
2623
+ complete_task_failure: {
2624
+ description: "You were unable to accomplish what the user asked despite making genuine attempts. This might be things like, you genuinely do not have the capabilities the user is asking for. You have tried but run into a persistent bug in the environment etc. This is NOT a refusal or policy block \u2014 you tried and failed to deliver the result.",
2625
+ sentiment: "NEGATIVE"
2626
+ }
2627
+ };
2628
+ var SELF_DIAGNOSTICS_TOOL_PREAMBLE = "You have a diagnostic tool for flagging serious failures for developer review. Only call this when you hit a significant, unrecoverable problem \u2014 not for minor hiccups or routine clarifications. If you can still help the user, do that instead of calling this tool. This tool is invisible to the user \u2014 never mention it or its output.";
2629
+ var SELF_DIAGNOSTICS_NOTEWORTHY_SIGNAL_KEY = "noteworthy";
2630
+ function normalizeString(value) {
2631
+ if (typeof value !== "string") return void 0;
2632
+ const trimmed = value.trim();
2633
+ return trimmed || void 0;
2634
+ }
2635
+ function normalizeSelfDiagnosticsSignals(signals) {
2636
+ if (!signals) return SELF_DIAGNOSTICS_SIGNALS_DEFAULT;
2637
+ const normalizedEntries = Object.entries(signals).map(([key, value]) => {
2638
+ var _a;
2639
+ const signalKey = key.trim();
2640
+ if (!signalKey || !value || typeof value !== "object") return void 0;
2641
+ const description = (_a = value.description) == null ? void 0 : _a.trim();
2642
+ if (!description) return void 0;
2643
+ const sentiment = value.sentiment;
2644
+ return [
2645
+ signalKey,
2646
+ {
2647
+ description,
2648
+ ...sentiment === "POSITIVE" || sentiment === "NEGATIVE" ? { sentiment } : {}
2649
+ }
2650
+ ];
2651
+ }).filter(
2652
+ (entry) => entry !== void 0
2653
+ );
2654
+ if (normalizedEntries.length === 0) return SELF_DIAGNOSTICS_SIGNALS_DEFAULT;
2655
+ return Object.fromEntries(normalizedEntries);
2656
+ }
2657
+ function normalizeSelfDiagnosticsConfig(options) {
2658
+ var _a, _b;
2659
+ if (!(options == null ? void 0 : options.enabled)) return void 0;
2660
+ const signalDefinitions = normalizeSelfDiagnosticsSignals(options.signals);
2661
+ const signalKeys = Object.keys(signalDefinitions);
2662
+ const signalDescriptions = {};
2663
+ const signalSentiments = {};
2664
+ for (const signalKey of signalKeys) {
2665
+ const definition = signalDefinitions[signalKey];
2666
+ if (!definition) continue;
2667
+ signalDescriptions[signalKey] = definition.description;
2668
+ signalSentiments[signalKey] = definition.sentiment;
2669
+ }
2670
+ const customGuidance = ((_a = options.guidance) == null ? void 0 : _a.trim()) || "";
2671
+ const toolName = ((_b = options.toolName) == null ? void 0 : _b.trim()) || SELF_DIAGNOSTICS_TOOL_NAME_DEFAULT;
2672
+ const signalList = signalKeys.map((signalKey) => {
2673
+ const sentiment = signalSentiments[signalKey];
2674
+ const sentimentTag = sentiment ? ` [${sentiment.toLowerCase()}]` : "";
2675
+ return `- ${signalKey}: ${signalDescriptions[signalKey]}${sentimentTag}`;
2676
+ }).join("\n");
2677
+ const guidanceBlock = customGuidance ? `
2678
+ Additional guidance: ${customGuidance}
2679
+ ` : "";
2680
+ const toolDescription = `${SELF_DIAGNOSTICS_TOOL_PREAMBLE}
2681
+
2682
+ When to call:
2683
+ - You are blocked from completing the task due to missing information or access that the user cannot provide.
2684
+ - A tool is persistently failing across multiple attempts, not just a single transient error.
2685
+ - The task requires a tool, permission, or capability you do not have.
2686
+ - You genuinely cannot deliver what the user asked for despite trying.
2687
+
2688
+ When NOT to call:
2689
+ - Normal clarifying questions or back-and-forth with the user.
2690
+ - A single tool error that you can recover from or retry.
2691
+ - You successfully completed the task, even if it was difficult.
2692
+ - Policy refusals or content filtering \u2014 those are working as intended.
2693
+
2694
+ Rules:
2695
+ 1. Pick the single best category.
2696
+ 2. Do not fabricate issues. Only report what is evident from the conversation.
2697
+ 3. Err on the side of NOT calling this tool. When in doubt, help the user instead.
2698
+ ${guidanceBlock}
2699
+ Categories:
2700
+ ` + signalList;
2701
+ return {
2702
+ toolName,
2703
+ toolDescription,
2704
+ signalKeys,
2705
+ signalKeySet: new Set(signalKeys),
2706
+ signalDescriptions,
2707
+ signalSentiments
2708
+ };
2709
+ }
2710
+ var SELF_DIAGNOSTICS_ACK = '{"acknowledged":true}';
2711
+ function selfDiagnosticsInputJsonSchema(config) {
2712
+ return {
2713
+ type: "object",
2714
+ properties: {
2715
+ category: {
2716
+ type: "string",
2717
+ enum: config.signalKeys,
2718
+ description: "The single best-matching category from the list above."
2719
+ },
2720
+ detail: {
2721
+ type: "string",
2722
+ description: "One sentence of factual context: what happened and why it matters. Do not include PII or secrets."
2723
+ }
2724
+ },
2725
+ required: ["category", "detail"],
2726
+ additionalProperties: false
2727
+ };
2728
+ }
2729
+ function isRecord3(value) {
2730
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2731
+ }
2732
+ async function trackSelfDiagnosticsSignal(toolInput, config, eventShipper, eventId, debug) {
2733
+ var _a, _b;
2734
+ try {
2735
+ const input = isRecord3(toolInput) ? toolInput : void 0;
2736
+ const fallbackCategory = (_a = config.signalKeys[0]) != null ? _a : "unknown";
2737
+ const categoryCandidate = normalizeString(input == null ? void 0 : input["category"]);
2738
+ const category = categoryCandidate && config.signalKeySet.has(categoryCandidate) ? categoryCandidate : fallbackCategory;
2739
+ const detail = (_b = normalizeString(input == null ? void 0 : input["detail"])) != null ? _b : "";
2740
+ if (!eventId) {
2741
+ if (debug) {
2742
+ console.warn(
2743
+ "[raindrop-ai/opencode-plugin] self diagnostics signal skipped: no open event for this session."
2744
+ );
2745
+ }
2746
+ return;
2747
+ }
2748
+ if (category === SELF_DIAGNOSTICS_NOTEWORTHY_SIGNAL_KEY) {
2749
+ await eventShipper.trackSignal({
2750
+ eventId,
2751
+ name: "self diagnostics - noteworthy",
2752
+ type: "agent_internal",
2753
+ properties: {
2754
+ source: "agent_flag_event_tool",
2755
+ reason: detail,
2756
+ severity: "medium",
2757
+ sdk: "opencode-plugin",
2758
+ sdk_version: PLUGIN_VERSION
2759
+ }
2760
+ });
2761
+ return;
2762
+ }
2763
+ await eventShipper.trackSignal({
2764
+ eventId,
2765
+ name: `self diagnostics - ${category}`,
2766
+ type: "agent",
2767
+ sentiment: config.signalSentiments[category],
2768
+ properties: {
2769
+ source: "agent_reporting_tool",
2770
+ category,
2771
+ signal_description: config.signalDescriptions[category],
2772
+ sdk: "opencode-plugin",
2773
+ sdk_version: PLUGIN_VERSION,
2774
+ ...detail ? { detail } : {}
2775
+ }
2776
+ });
2777
+ } catch (err) {
2778
+ if (debug) {
2779
+ const message = err instanceof Error ? err.message : String(err);
2780
+ console.warn(
2781
+ `[raindrop-ai/opencode-plugin] selfDiagnostics signal dispatch failed: ${message}`
2782
+ );
2783
+ }
2784
+ }
2785
+ }
2786
+
2430
2787
  // src/shipper.ts
2431
2788
  var EventShipper3 = class extends EventShipper2 {
2432
2789
  constructor(opts) {
@@ -2462,6 +2819,100 @@ var TraceShipper3 = class extends TraceShipper2 {
2462
2819
  }
2463
2820
  };
2464
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
+
2465
2916
  // src/tracing-v2.ts
2466
2917
  var import_node_os2 = require("os");
2467
2918
 
@@ -2635,7 +3086,7 @@ function errorString(error) {
2635
3086
 
2636
3087
  type: ${error.type}`;
2637
3088
  }
2638
- function createV2Tracing(config, eventShipper, traceShipper, sessionLookup) {
3089
+ function createV2Tracing(config, eventShipper, traceShipper, sessionLookup, toolCatalog) {
2639
3090
  const sessions = /* @__PURE__ */ new Map();
2640
3091
  const admittedPrompts = /* @__PURE__ */ new Map();
2641
3092
  const subagentLinks = /* @__PURE__ */ new Map();
@@ -2675,7 +3126,7 @@ function createV2Tracing(config, eventShipper, traceShipper, sessionLookup) {
2675
3126
  async function finalizeTurn(state2, error) {
2676
3127
  var _a, _b, _c;
2677
3128
  for (const step of state2.stepSpans.values()) {
2678
- traceShipper.endSpan(step.span);
3129
+ traceShipper.endSpan(step.span, { attributes: promptToolsAttributes(await step.promptTools) });
2679
3130
  }
2680
3131
  state2.stepSpans.clear();
2681
3132
  if (state2.compactionSpan) {
@@ -2930,7 +3381,13 @@ function createV2Tracing(config, eventShipper, traceShipper, sessionLookup) {
2930
3381
  state2.stepSpans.set(data.assistantMessageID, {
2931
3382
  span,
2932
3383
  providerID: data.model.providerID,
2933
- 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)
2934
3391
  });
2935
3392
  break;
2936
3393
  }
@@ -2961,6 +3418,7 @@ function createV2Tracing(config, eventShipper, traceShipper, sessionLookup) {
2961
3418
  if (reasoning !== void 0) {
2962
3419
  attrs.push(attrString("ai.reasoning.text", reasoning));
2963
3420
  }
3421
+ attrs.push(...promptToolsAttributes(await step.promptTools));
2964
3422
  traceShipper.endSpan(step.span, { attributes: attrs });
2965
3423
  break;
2966
3424
  }
@@ -2970,7 +3428,10 @@ function createV2Tracing(config, eventShipper, traceShipper, sessionLookup) {
2970
3428
  const step = state2 == null ? void 0 : state2.stepSpans.get(data.assistantMessageID);
2971
3429
  if (!state2 || !step) return;
2972
3430
  state2.stepSpans.delete(data.assistantMessageID);
2973
- 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
+ });
2974
3435
  break;
2975
3436
  }
2976
3437
  case "session.text.ended": {
@@ -3146,6 +3607,7 @@ function createV2Tracing(config, eventShipper, traceShipper, sessionLookup) {
3146
3607
  }
3147
3608
  }
3148
3609
  sessions.delete(event.data.sessionID);
3610
+ toolCatalog == null ? void 0 : toolCatalog.forget(event.data.sessionID);
3149
3611
  break;
3150
3612
  }
3151
3613
  }
@@ -3223,7 +3685,7 @@ function raindrop(opts = {}) {
3223
3685
  }
3224
3686
  }
3225
3687
  function createShared(ctx) {
3226
- var _a, _b, _c;
3688
+ var _a, _b, _c, _d, _e;
3227
3689
  const config = loadConfig((_a = opts.directory) != null ? _a : ctx.location.directory);
3228
3690
  const resolvedLocalUrl = resolveLocalDebuggerBaseUrl(config.localWorkshopUrl);
3229
3691
  const hasLocalDestination = resolvedLocalUrl !== null;
@@ -3256,18 +3718,41 @@ function raindrop(opts = {}) {
3256
3718
  };
3257
3719
  const eventShipper = new EventShipper3(shipperOptions);
3258
3720
  const traceShipper = new TraceShipper3(shipperOptions);
3721
+ const selfDiagnostics = normalizeSelfDiagnosticsConfig(
3722
+ (_d = opts.selfDiagnostics) != null ? _d : config.selfDiagnostics
3723
+ );
3724
+ if (selfDiagnostics && !config.writeKey.trim()) {
3725
+ appLog(
3726
+ "warn",
3727
+ `selfDiagnostics enabled but RAINDROP_WRITE_KEY is not set \u2014 the ${selfDiagnostics.toolName} tool is not registered (signals require the cloud destination).`
3728
+ );
3729
+ }
3259
3730
  const s = {
3260
3731
  eventShipper,
3261
3732
  traceShipper,
3733
+ config,
3734
+ selfDiagnostics: config.writeKey.trim() ? selfDiagnostics : void 0,
3262
3735
  appGitContext,
3263
3736
  abort: new AbortController(),
3264
3737
  instances: /* @__PURE__ */ new Set(),
3265
- runtime: createV2Tracing(config, eventShipper, traceShipper, async (sessionID) => {
3266
- const instance = firstInstance(s);
3267
- if (!instance) return void 0;
3268
- const info = await instance.ctx.session.get({ sessionID });
3269
- return { parentID: info.parentID };
3270
- })
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
+ )
3271
3756
  };
3272
3757
  return s;
3273
3758
  }
@@ -3310,6 +3795,34 @@ function raindrop(opts = {}) {
3310
3795
  const directory = ctx.location.directory;
3311
3796
  instance.disposables.push(await ctx.session.hook("prompt", (e) => runtime.onPrompt(e, directory)));
3312
3797
  instance.disposables.push(await ctx.session.hook("context", (e) => runtime.onContext(e)));
3798
+ const selfDiagnostics = s.selfDiagnostics;
3799
+ if (selfDiagnostics) {
3800
+ const cfg = selfDiagnostics;
3801
+ const eventShipper = s.eventShipper;
3802
+ const debug = s.config.debug;
3803
+ instance.disposables.push(
3804
+ await ctx.tool.transform(
3805
+ (editor) => editor.add({
3806
+ name: cfg.toolName,
3807
+ description: cfg.toolDescription,
3808
+ input: selfDiagnosticsInputJsonSchema(cfg),
3809
+ // Direct tool — the host routes everything else through the
3810
+ // codemode `execute` wrapper, where this name isn't callable.
3811
+ options: { codemode: false },
3812
+ async execute(input, context) {
3813
+ await trackSelfDiagnosticsSignal(
3814
+ input,
3815
+ cfg,
3816
+ eventShipper,
3817
+ runtime.currentEventId(context.sessionID),
3818
+ debug
3819
+ );
3820
+ return { content: SELF_DIAGNOSTICS_ACK };
3821
+ }
3822
+ })
3823
+ )
3824
+ );
3825
+ }
3313
3826
  if (isFirst) void runEventLoop(s);
3314
3827
  const owner = s;
3315
3828
  return async () => {
package/dist/v2.d.cts CHANGED
@@ -1,11 +1,19 @@
1
1
  import { Plugin } from '@opencode/plugin';
2
- import { I as IdentifyInput, S as SignalInput, A as AppGitOptions } from './index.d-vgBcEZbX.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';
3
4
 
4
5
  interface RaindropOpencodeV2Options {
5
6
  /** Application Git identity override. false disables enrichment. */
6
7
  appGit?: AppGitOptions | false;
7
8
  /** Config directory override; defaults to the first instance's ctx.location.directory. */
8
9
  directory?: string;
10
+ /** Built-in self-diagnostics: injects a `__raindrop_report` tool when enabled. */
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;
9
17
  }
10
18
  /**
11
19
  * The Raindrop control handle exposed on the v2 plugin. Unlike v1 (where the
@@ -38,4 +46,4 @@ interface RaindropOpencodePluginV2 extends Plugin.Plugin {
38
46
  */
39
47
  declare function raindrop(opts?: RaindropOpencodeV2Options): RaindropOpencodePluginV2;
40
48
 
41
- export { IdentifyInput, type RaindropOpencodePluginV2, type RaindropOpencodeV2Handle, type RaindropOpencodeV2Options, SignalInput, raindrop as default };
49
+ export { IdentifyInput, type RaindropOpencodePluginV2, type RaindropOpencodeV2Handle, type RaindropOpencodeV2Options, SelfDiagnosticsOptions, SignalInput, raindrop as default };