@raindrop-ai/opencode-plugin 0.1.4 → 0.3.0

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
@@ -23,6 +23,7 @@ __export(index_exports, {
23
23
  default: () => plugin
24
24
  });
25
25
  module.exports = __toCommonJS(index_exports);
26
+ var import_plugin = require("@opencode-ai/plugin");
26
27
 
27
28
  // ../core/dist/chunk-BYBMRLZM.js
28
29
  var EVAL_CORRELATION_ID_ATTRIBUTE = "raindrop.eval_correlation_id";
@@ -2308,9 +2309,41 @@ function loadConfig(projectDirectory) {
2308
2309
  captureSystemPrompt: process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] !== void 0 ? process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] === "true" : (_h = merged.capture_system_prompt) != null ? _h : false,
2309
2310
  eventMetadata,
2310
2311
  localWorkshopUrl: resolveLocalWorkshopUrl(merged.local_workshop_url),
2311
- appGit: mapAppGit(merged.app_git)
2312
+ appGit: mapAppGit(merged.app_git),
2313
+ selfDiagnostics: mapSelfDiagnostics(merged.self_diagnostics)
2312
2314
  };
2313
2315
  }
2316
+ function isRecord(value) {
2317
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2318
+ }
2319
+ function mapSelfDiagnostics(value) {
2320
+ const env = process.env["RAINDROP_SELF_DIAGNOSTICS"];
2321
+ const record = isRecord(value) ? value : {};
2322
+ const enabled = env !== void 0 ? env === "true" : typeof record["enabled"] === "boolean" ? record["enabled"] : void 0;
2323
+ const signals = mapSelfDiagnosticsSignals(record["signals"]);
2324
+ const options = {
2325
+ ...enabled !== void 0 ? { enabled } : {},
2326
+ ...signals ? { signals } : {},
2327
+ ...typeof record["guidance"] === "string" ? { guidance: record["guidance"] } : {},
2328
+ ...typeof record["tool_name"] === "string" ? { toolName: record["tool_name"] } : {}
2329
+ };
2330
+ return Object.keys(options).length > 0 ? options : void 0;
2331
+ }
2332
+ function mapSelfDiagnosticsSignals(value) {
2333
+ if (!isRecord(value)) return void 0;
2334
+ const out = {};
2335
+ for (const [key, entry] of Object.entries(value)) {
2336
+ if (!isRecord(entry)) continue;
2337
+ const definition = entry;
2338
+ if (typeof definition["description"] !== "string") continue;
2339
+ const sentiment = definition["sentiment"];
2340
+ out[key] = {
2341
+ description: definition["description"],
2342
+ ...sentiment === "POSITIVE" || sentiment === "NEGATIVE" ? { sentiment } : {}
2343
+ };
2344
+ }
2345
+ return Object.keys(out).length > 0 ? out : void 0;
2346
+ }
2314
2347
  function mapAppGit(value) {
2315
2348
  if (value === false) return false;
2316
2349
  if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
@@ -2337,7 +2370,7 @@ function resolveLocalWorkshopUrl(fileValue) {
2337
2370
  // package.json
2338
2371
  var package_default = {
2339
2372
  name: "@raindrop-ai/opencode-plugin",
2340
- version: "0.1.4",
2373
+ version: "0.3.0",
2341
2374
  description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
2342
2375
  type: "module",
2343
2376
  main: "dist/index.js",
@@ -2353,6 +2386,11 @@ var package_default = {
2353
2386
  types: "./dist/index.d.ts",
2354
2387
  import: "./dist/index.js",
2355
2388
  require: "./dist/index.cjs"
2389
+ },
2390
+ "./v2": {
2391
+ types: "./dist/v2.d.ts",
2392
+ import: "./dist/v2.js",
2393
+ require: "./dist/v2.cjs"
2356
2394
  }
2357
2395
  },
2358
2396
  sideEffects: false,
@@ -2367,24 +2405,38 @@ var package_default = {
2367
2405
  },
2368
2406
  peerDependencies: {
2369
2407
  "@opencode-ai/plugin": ">=1.3.0",
2370
- "@opencode-ai/sdk": ">=1.3.0"
2408
+ "@opencode-ai/sdk": ">=1.3.0",
2409
+ "@opencode/plugin": ">=2.0.0",
2410
+ "@opencode/sdk": ">=2.0.0"
2371
2411
  },
2372
2412
  peerDependenciesMeta: {
2413
+ "@opencode-ai/plugin": {
2414
+ optional: true
2415
+ },
2373
2416
  "@opencode-ai/sdk": {
2374
2417
  optional: true
2418
+ },
2419
+ "@opencode/plugin": {
2420
+ optional: true
2421
+ },
2422
+ "@opencode/sdk": {
2423
+ optional: true
2375
2424
  }
2376
2425
  },
2377
2426
  devDependencies: {
2378
2427
  "@raindrop-ai/core": "workspace:*",
2379
2428
  "@opencode-ai/plugin": "^1.3.3",
2380
2429
  "@opencode-ai/sdk": "^1.3.3",
2430
+ "@opencode/plugin": "^2.0.10",
2431
+ "@opencode/sdk": "^2.0.10",
2381
2432
  "@types/node": "^20.11.17",
2382
2433
  tsup: "^8.4.0",
2383
2434
  typescript: "^5.3.3"
2384
2435
  },
2385
2436
  tsup: {
2386
2437
  entry: [
2387
- "src/index.ts"
2438
+ "src/index.ts",
2439
+ "src/v2.ts"
2388
2440
  ],
2389
2441
  format: [
2390
2442
  "cjs",
@@ -2407,6 +2459,167 @@ var package_default = {
2407
2459
  var PLUGIN_NAME = package_default.name;
2408
2460
  var PLUGIN_VERSION = package_default.version;
2409
2461
 
2462
+ // src/self-diagnostics.ts
2463
+ var SELF_DIAGNOSTICS_TOOL_NAME_DEFAULT = "__raindrop_report";
2464
+ var SELF_DIAGNOSTICS_SIGNALS_DEFAULT = {
2465
+ missing_context: {
2466
+ 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.",
2467
+ sentiment: "NEGATIVE"
2468
+ },
2469
+ repeatedly_broken_tool: {
2470
+ 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.",
2471
+ sentiment: "NEGATIVE"
2472
+ },
2473
+ capability_gap: {
2474
+ 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.",
2475
+ sentiment: "NEGATIVE"
2476
+ },
2477
+ complete_task_failure: {
2478
+ 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.",
2479
+ sentiment: "NEGATIVE"
2480
+ }
2481
+ };
2482
+ 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.";
2483
+ var SELF_DIAGNOSTICS_NOTEWORTHY_SIGNAL_KEY = "noteworthy";
2484
+ function normalizeString(value) {
2485
+ if (typeof value !== "string") return void 0;
2486
+ const trimmed = value.trim();
2487
+ return trimmed || void 0;
2488
+ }
2489
+ function normalizeSelfDiagnosticsSignals(signals) {
2490
+ if (!signals) return SELF_DIAGNOSTICS_SIGNALS_DEFAULT;
2491
+ const normalizedEntries = Object.entries(signals).map(([key, value]) => {
2492
+ var _a;
2493
+ const signalKey = key.trim();
2494
+ if (!signalKey || !value || typeof value !== "object") return void 0;
2495
+ const description = (_a = value.description) == null ? void 0 : _a.trim();
2496
+ if (!description) return void 0;
2497
+ const sentiment = value.sentiment;
2498
+ return [
2499
+ signalKey,
2500
+ {
2501
+ description,
2502
+ ...sentiment === "POSITIVE" || sentiment === "NEGATIVE" ? { sentiment } : {}
2503
+ }
2504
+ ];
2505
+ }).filter(
2506
+ (entry) => entry !== void 0
2507
+ );
2508
+ if (normalizedEntries.length === 0) return SELF_DIAGNOSTICS_SIGNALS_DEFAULT;
2509
+ return Object.fromEntries(normalizedEntries);
2510
+ }
2511
+ function normalizeSelfDiagnosticsConfig(options) {
2512
+ var _a, _b;
2513
+ if (!(options == null ? void 0 : options.enabled)) return void 0;
2514
+ const signalDefinitions = normalizeSelfDiagnosticsSignals(options.signals);
2515
+ const signalKeys = Object.keys(signalDefinitions);
2516
+ const signalDescriptions = {};
2517
+ const signalSentiments = {};
2518
+ for (const signalKey of signalKeys) {
2519
+ const definition = signalDefinitions[signalKey];
2520
+ if (!definition) continue;
2521
+ signalDescriptions[signalKey] = definition.description;
2522
+ signalSentiments[signalKey] = definition.sentiment;
2523
+ }
2524
+ const customGuidance = ((_a = options.guidance) == null ? void 0 : _a.trim()) || "";
2525
+ const toolName = ((_b = options.toolName) == null ? void 0 : _b.trim()) || SELF_DIAGNOSTICS_TOOL_NAME_DEFAULT;
2526
+ const signalList = signalKeys.map((signalKey) => {
2527
+ const sentiment = signalSentiments[signalKey];
2528
+ const sentimentTag = sentiment ? ` [${sentiment.toLowerCase()}]` : "";
2529
+ return `- ${signalKey}: ${signalDescriptions[signalKey]}${sentimentTag}`;
2530
+ }).join("\n");
2531
+ const guidanceBlock = customGuidance ? `
2532
+ Additional guidance: ${customGuidance}
2533
+ ` : "";
2534
+ const toolDescription = `${SELF_DIAGNOSTICS_TOOL_PREAMBLE}
2535
+
2536
+ When to call:
2537
+ - You are blocked from completing the task due to missing information or access that the user cannot provide.
2538
+ - A tool is persistently failing across multiple attempts, not just a single transient error.
2539
+ - The task requires a tool, permission, or capability you do not have.
2540
+ - You genuinely cannot deliver what the user asked for despite trying.
2541
+
2542
+ When NOT to call:
2543
+ - Normal clarifying questions or back-and-forth with the user.
2544
+ - A single tool error that you can recover from or retry.
2545
+ - You successfully completed the task, even if it was difficult.
2546
+ - Policy refusals or content filtering \u2014 those are working as intended.
2547
+
2548
+ Rules:
2549
+ 1. Pick the single best category.
2550
+ 2. Do not fabricate issues. Only report what is evident from the conversation.
2551
+ 3. Err on the side of NOT calling this tool. When in doubt, help the user instead.
2552
+ ${guidanceBlock}
2553
+ Categories:
2554
+ ` + signalList;
2555
+ return {
2556
+ toolName,
2557
+ toolDescription,
2558
+ signalKeys,
2559
+ signalKeySet: new Set(signalKeys),
2560
+ signalDescriptions,
2561
+ signalSentiments
2562
+ };
2563
+ }
2564
+ var SELF_DIAGNOSTICS_ACK = '{"acknowledged":true}';
2565
+ function isRecord2(value) {
2566
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2567
+ }
2568
+ async function trackSelfDiagnosticsSignal(toolInput, config, eventShipper, eventId, debug) {
2569
+ var _a, _b;
2570
+ try {
2571
+ const input = isRecord2(toolInput) ? toolInput : void 0;
2572
+ const fallbackCategory = (_a = config.signalKeys[0]) != null ? _a : "unknown";
2573
+ const categoryCandidate = normalizeString(input == null ? void 0 : input["category"]);
2574
+ const category = categoryCandidate && config.signalKeySet.has(categoryCandidate) ? categoryCandidate : fallbackCategory;
2575
+ const detail = (_b = normalizeString(input == null ? void 0 : input["detail"])) != null ? _b : "";
2576
+ if (!eventId) {
2577
+ if (debug) {
2578
+ console.warn(
2579
+ "[raindrop-ai/opencode-plugin] self diagnostics signal skipped: no open event for this session."
2580
+ );
2581
+ }
2582
+ return;
2583
+ }
2584
+ if (category === SELF_DIAGNOSTICS_NOTEWORTHY_SIGNAL_KEY) {
2585
+ await eventShipper.trackSignal({
2586
+ eventId,
2587
+ name: "self diagnostics - noteworthy",
2588
+ type: "agent_internal",
2589
+ properties: {
2590
+ source: "agent_flag_event_tool",
2591
+ reason: detail,
2592
+ severity: "medium",
2593
+ sdk: "opencode-plugin",
2594
+ sdk_version: PLUGIN_VERSION
2595
+ }
2596
+ });
2597
+ return;
2598
+ }
2599
+ await eventShipper.trackSignal({
2600
+ eventId,
2601
+ name: `self diagnostics - ${category}`,
2602
+ type: "agent",
2603
+ sentiment: config.signalSentiments[category],
2604
+ properties: {
2605
+ source: "agent_reporting_tool",
2606
+ category,
2607
+ signal_description: config.signalDescriptions[category],
2608
+ sdk: "opencode-plugin",
2609
+ sdk_version: PLUGIN_VERSION,
2610
+ ...detail ? { detail } : {}
2611
+ }
2612
+ });
2613
+ } catch (err) {
2614
+ if (debug) {
2615
+ const message = err instanceof Error ? err.message : String(err);
2616
+ console.warn(
2617
+ `[raindrop-ai/opencode-plugin] selfDiagnostics signal dispatch failed: ${message}`
2618
+ );
2619
+ }
2620
+ }
2621
+ }
2622
+
2410
2623
  // src/shipper.ts
2411
2624
  var EventShipper3 = class extends EventShipper2 {
2412
2625
  constructor(opts) {
@@ -2678,7 +2891,7 @@ function markTaskCallFinished(sessionID, callID) {
2678
2891
  running.delete(callID);
2679
2892
  if (running.size === 0) runningTaskCallsBySession.delete(sessionID);
2680
2893
  }
2681
- function endPendingToolSpanWithError(state2, callID, tool, toolCallArgs, errorMessage, traceShipper) {
2894
+ function endPendingToolSpanWithError(state2, callID, tool2, toolCallArgs, errorMessage, traceShipper) {
2682
2895
  const startInfo = state2.toolSpanStarts.get(callID);
2683
2896
  if (!startInfo) return false;
2684
2897
  state2.toolSpanStarts.delete(callID);
@@ -2700,7 +2913,7 @@ function endPendingToolSpanWithError(state2, callID, tool, toolCallArgs, errorMe
2700
2913
  startTimeUnixNano: startInfo.startTimeUnixNano,
2701
2914
  attributes: [
2702
2915
  attrString("ai.operationId", "ai.toolCall"),
2703
- attrString("ai.toolCall.name", tool),
2916
+ attrString("ai.toolCall.name", tool2),
2704
2917
  attrString("ai.toolCall.id", callID)
2705
2918
  ]
2706
2919
  });
@@ -2737,7 +2950,7 @@ function getHostname() {
2737
2950
  return h;
2738
2951
  }
2739
2952
  }
2740
- function createHooks(config, worktree, directory, eventShipper, traceShipper) {
2953
+ function createTracing(config, worktree, directory, eventShipper, traceShipper) {
2741
2954
  function log(msg, data) {
2742
2955
  if (!config.debug) return;
2743
2956
  const prefix = `[raindrop-ai/opencode-plugin] [info] ${msg}`;
@@ -2763,7 +2976,7 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
2763
2976
  callKey,
2764
2977
  log
2765
2978
  });
2766
- return {
2979
+ const hooks = {
2767
2980
  // ------------------------------------------------------------------
2768
2981
  // event — session lifecycle + streaming parts
2769
2982
  // ------------------------------------------------------------------
@@ -2808,14 +3021,14 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
2808
3021
  if (partObj["type"] === "text" && typeof partObj["text"] === "string") {
2809
3022
  state2.outputParts.set(messageId, capText2(partObj["text"]));
2810
3023
  } else if (partObj["type"] === "tool" && messageId) {
2811
- const tool = partObj["tool"];
3024
+ const tool2 = partObj["tool"];
2812
3025
  const callID = partObj["callID"];
2813
3026
  const partState = partObj["state"];
2814
3027
  if (callID == null || callID === "")
2815
3028
  throw new Error("message.part.updated: part.callID is required for tool part");
2816
3029
  if (partState == null || typeof partState !== "object")
2817
3030
  throw new Error("message.part.updated: part.state is required for tool part");
2818
- if (tool === "task") {
3031
+ if (tool2 === "task") {
2819
3032
  const metadata = partState["metadata"];
2820
3033
  const childSessionId = metadata != null && typeof metadata === "object" ? metadata["sessionId"] : void 0;
2821
3034
  if (childSessionId != null && childSessionId !== "") {
@@ -2840,11 +3053,11 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
2840
3053
  if (endPendingToolSpanWithError(
2841
3054
  state2,
2842
3055
  callID,
2843
- tool,
3056
+ tool2,
2844
3057
  toolCallArgs,
2845
3058
  errorMessage,
2846
3059
  traceShipper
2847
- ) && tool === "task") {
3060
+ ) && tool2 === "task") {
2848
3061
  markTaskCallFinished(partSessionID, callID);
2849
3062
  }
2850
3063
  }
@@ -3221,20 +3434,20 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
3221
3434
  "tool.execute.before": async (toolInput, _output) => {
3222
3435
  var _a, _b, _c;
3223
3436
  try {
3224
- const { tool, sessionID, callID } = toolInput;
3437
+ const { tool: tool2, sessionID, callID } = toolInput;
3225
3438
  const state2 = sessions.get(sessionID);
3226
3439
  if (!state2 || !state2.currentEventId) return;
3227
3440
  const spanParent = state2.parentId && state2.parentTaskSpanIds ? state2.parentTaskSpanIds : (_a = state2.currentRootSpan) == null ? void 0 : _a.ids;
3228
3441
  if (!spanParent) return;
3229
3442
  const startTimeUnixNano = nowUnixNanoString();
3230
- if (tool === "task") {
3443
+ if (tool2 === "task") {
3231
3444
  const liveTaskSpan = traceShipper.startSpan({
3232
3445
  name: "ai.toolCall",
3233
3446
  parent: spanParent,
3234
3447
  eventId: state2.currentEventId,
3235
3448
  attributes: [
3236
3449
  attrString("ai.operationId", "ai.toolCall"),
3237
- attrString("ai.toolCall.name", tool),
3450
+ attrString("ai.toolCall.name", tool2),
3238
3451
  attrString("ai.toolCall.id", callID)
3239
3452
  ]
3240
3453
  });
@@ -3259,7 +3472,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
3259
3472
  startTimeUnixNano,
3260
3473
  parent: spanParent,
3261
3474
  eventId: state2.currentEventId,
3262
- name: tool,
3475
+ name: tool2,
3263
3476
  liveSpan: liveTaskSpan
3264
3477
  });
3265
3478
  } else {
@@ -3267,7 +3480,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
3267
3480
  startTimeUnixNano,
3268
3481
  parent: spanParent,
3269
3482
  eventId: state2.currentEventId,
3270
- name: tool
3483
+ name: tool2
3271
3484
  });
3272
3485
  }
3273
3486
  } catch (err) {
@@ -3282,7 +3495,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
3282
3495
  // ------------------------------------------------------------------
3283
3496
  "tool.execute.after": async (toolInput, result) => {
3284
3497
  try {
3285
- const { tool, sessionID, callID, args } = toolInput;
3498
+ const { tool: tool2, sessionID, callID, args } = toolInput;
3286
3499
  const state2 = sessions.get(sessionID);
3287
3500
  if (!state2 || !state2.currentEventId) return;
3288
3501
  if (args === void 0 || args === null)
@@ -3292,7 +3505,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
3292
3505
  state2.toolSpanStarts.delete(callID);
3293
3506
  const resultMetadata = result.metadata;
3294
3507
  const childSessionId = resultMetadata == null ? void 0 : resultMetadata["sessionId"];
3295
- if (tool === "task" && childSessionId) {
3508
+ if (tool2 === "task" && childSessionId) {
3296
3509
  mapChildSessionToParent.set(childSessionId, { parentId: sessionID });
3297
3510
  attachChildSessionToParentTask(childSessionId, sessionID, callID, "tool.execute.after");
3298
3511
  }
@@ -3317,7 +3530,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
3317
3530
  endTimeUnixNano,
3318
3531
  attributes: [
3319
3532
  attrString("ai.operationId", "ai.toolCall"),
3320
- attrString("ai.toolCall.name", tool),
3533
+ attrString("ai.toolCall.name", tool2),
3321
3534
  attrString("ai.toolCall.id", callID),
3322
3535
  attrString("ai.toolCall.args", toolCallArgs),
3323
3536
  attrString("ai.toolCall.result", toolResult)
@@ -3325,7 +3538,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
3325
3538
  });
3326
3539
  }
3327
3540
  }
3328
- if (tool === "task") {
3541
+ if (tool2 === "task") {
3329
3542
  markTaskCallFinished(sessionID, callID);
3330
3543
  }
3331
3544
  } catch (err) {
@@ -3378,16 +3591,24 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
3378
3591
  }
3379
3592
  }
3380
3593
  };
3594
+ return {
3595
+ hooks,
3596
+ currentEventId(sessionID) {
3597
+ var _a;
3598
+ return (_a = sessions.get(sessionID)) == null ? void 0 : _a.currentEventId;
3599
+ }
3600
+ };
3381
3601
  }
3382
3602
 
3383
3603
  // src/index.ts
3384
- function createRaindropHandle(eventShipper, traceShipper, appGitContext) {
3604
+ function createRaindropHandle(eventShipper, traceShipper, appGitContext, currentEventId) {
3385
3605
  return {
3386
3606
  users: {
3387
3607
  async identify(users) {
3388
3608
  await eventShipper.identify(users);
3389
3609
  }
3390
3610
  },
3611
+ currentEventId,
3391
3612
  async flush() {
3392
3613
  await Promise.all([eventShipper.flush(), traceShipper.flush()]);
3393
3614
  },
@@ -3404,7 +3625,7 @@ function createRaindropHandle(eventShipper, traceShipper, appGitContext) {
3404
3625
  };
3405
3626
  }
3406
3627
  async function plugin(input, opts = {}) {
3407
- var _a, _b, _c;
3628
+ var _a, _b, _c, _d;
3408
3629
  const config = loadConfig(input.directory);
3409
3630
  function appLog(level, message) {
3410
3631
  console.log(`[raindrop-ai/opencode-plugin] [${level}] ${message}`);
@@ -3448,12 +3669,53 @@ async function plugin(input, opts = {}) {
3448
3669
  appGitContext
3449
3670
  });
3450
3671
  const worktree = (_c = input.worktree) != null ? _c : input.directory;
3451
- const hooks = createHooks(
3672
+ const tracing = createTracing(
3452
3673
  config,
3453
3674
  worktree,
3454
3675
  input.directory,
3455
3676
  eventShipper,
3456
3677
  traceShipper
3457
3678
  );
3458
- return { ...hooks, raindrop: createRaindropHandle(eventShipper, traceShipper, appGitContext) };
3679
+ const hooks = tracing.hooks;
3680
+ const selfDiagnostics = normalizeSelfDiagnosticsConfig(
3681
+ (_d = opts.selfDiagnostics) != null ? _d : config.selfDiagnostics
3682
+ );
3683
+ if (selfDiagnostics && !config.writeKey.trim()) {
3684
+ appLog(
3685
+ "warn",
3686
+ `selfDiagnostics enabled but RAINDROP_WRITE_KEY is not set \u2014 the ${selfDiagnostics.toolName} tool is not registered (signals require the cloud destination).`
3687
+ );
3688
+ }
3689
+ const [firstSignalKey, ...restSignalKeys] = config.writeKey.trim() && selfDiagnostics ? selfDiagnostics.signalKeys : [];
3690
+ if (selfDiagnostics && firstSignalKey !== void 0) {
3691
+ const cfg = selfDiagnostics;
3692
+ hooks.tool = {
3693
+ [cfg.toolName]: (0, import_plugin.tool)({
3694
+ description: cfg.toolDescription,
3695
+ args: {
3696
+ category: import_plugin.tool.schema.enum([firstSignalKey, ...restSignalKeys]),
3697
+ detail: import_plugin.tool.schema.string()
3698
+ },
3699
+ async execute(args, ctx) {
3700
+ await trackSelfDiagnosticsSignal(
3701
+ args,
3702
+ cfg,
3703
+ eventShipper,
3704
+ tracing.currentEventId(ctx.sessionID),
3705
+ config.debug
3706
+ );
3707
+ return SELF_DIAGNOSTICS_ACK;
3708
+ }
3709
+ })
3710
+ };
3711
+ }
3712
+ return {
3713
+ ...hooks,
3714
+ raindrop: createRaindropHandle(
3715
+ eventShipper,
3716
+ traceShipper,
3717
+ appGitContext,
3718
+ tracing.currentEventId
3719
+ )
3720
+ };
3459
3721
  }
package/dist/index.d.cts CHANGED
@@ -1,85 +1,12 @@
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
3
  import { Hooks, PluginInput } from '@opencode-ai/plugin';
2
4
 
3
- /** Kept structural so public integration declarations do not depend on private schemas. */
4
- type AppGitOptions = {
5
- commitSha?: string | null;
6
- commitDirty?: boolean | null;
7
- branch?: string | null;
8
- detectBranch?: boolean;
9
- sourceDirectory?: string;
10
- autoDetect?: boolean;
11
- };
12
- type IdentifyInput = {
13
- userId: string;
14
- traits?: Record<string, unknown>;
15
- };
16
-
17
- /**
18
- * Run telemetry egress with OpenTelemetry tracing suppressed.
19
- *
20
- * Why this exists
21
- * ---------------
22
- * Raindrop integrations ship spans/events over HTTP with the global `fetch`
23
- * (see {@link ../http.ts `postJson`}). When the host app also runs an OTel
24
- * fetch/undici instrumentation — e.g. `@vercel/otel`'s `registerOTel`, which
25
- * every Eve agent installs — that instrumentation wraps *our own* telemetry
26
- * POSTs in a `fetch POST <endpoint>` span. Those spans are then handed to the
27
- * very exporter that issued the request, so they get shipped right back to
28
- * Raindrop and Workshop as standalone "runs" (and, because each export issues
29
- * another fetch, they feed back on themselves). The result is a run list
30
- * flooded with `fetch POST .../v1/traces`, `.../events/track_partial` and
31
- * `.../live` entries that drown out the real agent turns — especially with
32
- * sub-agents, where every sandbox runs its own instrumentation.
33
- *
34
- * The OTel-blessed fix is to mark the active context as "tracing suppressed"
35
- * around the request; instrumentations check `isTracingSuppressed` and return
36
- * a no-op span instead of recording one. We do this through a hook stashed on
37
- * `globalThis` by the Node entrypoint ({@link ../index.node.ts}) so that:
38
- * - `@opentelemetry/api` / `@opentelemetry/core` stay *optional* — core never
39
- * hard-depends on them, and the hook is simply absent when they (and thus
40
- * any instrumentation to suppress) are not installed; and
41
- * - the browser bundle never pulls in `node:module`, mirroring how core
42
- * injects `AsyncLocalStorage` via `RAINDROP_ASYNC_LOCAL_STORAGE`.
43
- *
44
- * When no hook is present the callback runs unchanged, so suppression is a
45
- * best-effort no-op rather than a hard requirement.
46
- */
47
- /** Hook signature: run `fn` with OTel tracing suppressed, returning its value. */
48
- type SuppressTracingHook = <T>(fn: () => T) => T;
49
- declare global {
50
- var RAINDROP_SUPPRESS_TRACING: SuppressTracingHook | undefined;
51
- }
52
-
53
- type ParentSpanContext = {
54
- traceIdB64: string;
55
- spanIdB64: string;
56
- eventId: string;
57
- };
58
- interface ContextSpan {
59
- readonly traceIdB64: string;
60
- readonly spanIdB64: string;
61
- readonly eventId: string;
62
- log?(data: Record<string, unknown>): void;
63
- }
64
- interface AsyncLocalStorageLike<T> {
65
- getStore(): T | undefined;
66
- run<R>(store: T, callback: () => R): R;
67
- enterWith?(store: T): void;
68
- }
69
- declare abstract class ContextManager {
70
- abstract getParentSpanIds(): ParentSpanContext | undefined;
71
- abstract runInContext<R>(span: ContextSpan, callback: () => R): R;
72
- abstract getCurrentSpan(): ContextSpan | undefined;
73
- abstract isReady(): boolean;
74
- }
75
- declare global {
76
- var RAINDROP_CONTEXT_MANAGER: (new () => ContextManager) | undefined;
77
- var RAINDROP_ASYNC_LOCAL_STORAGE: (new <T>() => AsyncLocalStorageLike<T>) | undefined;
78
- }
79
-
80
5
  interface RaindropOpencodeOptions {
81
6
  /** Application Git identity override. false disables enrichment. */
82
7
  appGit?: AppGitOptions | false;
8
+ /** Built-in self-diagnostics: injects a `__raindrop_report` tool when enabled. */
9
+ selfDiagnostics?: SelfDiagnosticsOptions;
83
10
  }
84
11
  /**
85
12
  * The Raindrop control handle exposed alongside the OpenCode hooks.
@@ -101,6 +28,8 @@ interface RaindropOpencodeHandle {
101
28
  */
102
29
  identify(users: IdentifyInput | IdentifyInput[]): Promise<void>;
103
30
  };
31
+ /** Raindrop eventId of the turn currently open for this OpenCode session, if any. */
32
+ currentEventId(sessionID: string): string | undefined;
104
33
  /** Drain the buffered event + trace shippers. */
105
34
  flush(): Promise<void>;
106
35
  /** Flush and tear down the shippers (also wired into session teardown). */
@@ -129,4 +58,4 @@ type RaindropOpencodeHooks = Hooks & {
129
58
  */
130
59
  declare function plugin(input: PluginInput, opts?: RaindropOpencodeOptions): Promise<RaindropOpencodeHooks>;
131
60
 
132
- export { type IdentifyInput, type RaindropOpencodeHandle, type RaindropOpencodeHooks, type RaindropOpencodeOptions, plugin as default };
61
+ export { IdentifyInput, type RaindropOpencodeHandle, type RaindropOpencodeHooks, type RaindropOpencodeOptions, SelfDiagnosticsOptions, plugin as default };