@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/README.md +94 -0
- package/dist/{chunk-4DAI6CYK.js → chunk-ZG47FVK7.js} +460 -3
- package/dist/index.cjs +534 -28
- package/dist/index.d.cts +12 -2
- package/dist/index.d.ts +12 -2
- package/dist/index.js +107 -27
- package/dist/{index.d-vgBcEZbX.d.cts → self-diagnostics-CLW6h4zB.d.cts} +60 -1
- package/dist/{index.d-vgBcEZbX.d.ts → self-diagnostics-CLW6h4zB.d.ts} +60 -1
- package/dist/v2.cjs +527 -14
- package/dist/v2.d.cts +10 -2
- package/dist/v2.d.ts +10 -2
- package/dist/v2.js +81 -13
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -33,6 +33,61 @@ rejected at the ingest size limit. Hook error logs are rate-limited to one
|
|
|
33
33
|
line per failure family per 30s so a persistent error can't flood the host's
|
|
34
34
|
output.
|
|
35
35
|
|
|
36
|
+
## Tool catalog capture (`ai.prompt.tools`)
|
|
37
|
+
|
|
38
|
+
OpenCode's step events carry no tool list, so the plugin cannot see the exact
|
|
39
|
+
tools each model request was given. Instead it records the host's tool catalog
|
|
40
|
+
for the step's model:
|
|
41
|
+
|
|
42
|
+
- **v1 plugin (`@raindrop-ai/opencode-plugin`)**: on the first step of a
|
|
43
|
+
session for a given provider/model, the plugin calls
|
|
44
|
+
`client.tool.list({ query: { provider, model } })` (OpenCode's experimental
|
|
45
|
+
`/experimental/tool` endpoint) once, caches the answer per session and
|
|
46
|
+
model, and stamps it as `ai.prompt.tools` on every step span. The lookup is
|
|
47
|
+
deduplicated while in flight, bounded by a 2s timeout, and never surfaces
|
|
48
|
+
errors to OpenCode: when it fails, the attribute is simply absent.
|
|
49
|
+
- **v2 plugin (`@raindrop-ai/opencode-plugin/v2`)**: the v2 host has no
|
|
50
|
+
per-model catalog endpoint; the plugin reads the registered tool list
|
|
51
|
+
(`ctx.tool.list()`, after every plugin's `tool.transform`) with the same
|
|
52
|
+
caching, timeout and no-throw rules.
|
|
53
|
+
|
|
54
|
+
Because this is OpenCode's catalog for that model, not the exact per-request
|
|
55
|
+
set, catalog-derived spans also carry `ai.prompt.tools.source = "opencode.tool.list"`.
|
|
56
|
+
An explicit override (below) is exact and carries no `source`. Each element of
|
|
57
|
+
`ai.prompt.tools` is a JSON document
|
|
58
|
+
`{"type":"function","name":…,"description"?:…,"inputSchema"?:<JSON Schema>}`;
|
|
59
|
+
a tool whose schema is not plain JSON Schema is recorded without `inputSchema`
|
|
60
|
+
rather than with an invented one. The plugin has no separate content switch
|
|
61
|
+
(prompts and responses are always recorded; only the system prompt is opt-in),
|
|
62
|
+
so the catalog is recorded whenever tracing is on. Absent attribute = catalog
|
|
63
|
+
unknown; `[]` = no tools.
|
|
64
|
+
|
|
65
|
+
### `tools` override
|
|
66
|
+
|
|
67
|
+
Overrides the tool catalog recorded as `ai.prompt.tools` on every model span.
|
|
68
|
+
Use when the integration cannot see the tools the model was given. The
|
|
69
|
+
override replaces the catalog entirely (`tools: []` records an empty catalog)
|
|
70
|
+
and skips the catalog lookup.
|
|
71
|
+
|
|
72
|
+
`raindrop.json` (user- or project-level):
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"tools": [
|
|
77
|
+
{
|
|
78
|
+
"name": "get_weather",
|
|
79
|
+
"description": "Get the current weather",
|
|
80
|
+
"parameters": { "type": "object", "properties": { "city": { "type": "string" } }, "required": ["city"] }
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Programmatic options (v1 `plugin(input, { tools })`, v2 `raindrop({ tools })`)
|
|
87
|
+
take precedence over the config file. Any recognized declaration shape is
|
|
88
|
+
accepted (OpenAI `{type:"function",function:{…}}`, Anthropic `input_schema`,
|
|
89
|
+
plain `{name, description, parameters}`, …).
|
|
90
|
+
|
|
36
91
|
## Correlating per-message feedback signals
|
|
37
92
|
|
|
38
93
|
Each Raindrop event uses a client-generated `event_id` that isn't predictable
|
|
@@ -75,6 +130,45 @@ The v2 entry exposes the plugin object plus a `raindrop` handle
|
|
|
75
130
|
same as the v1 entry. `@opencode/plugin` and `@opencode/sdk` are optional peer
|
|
76
131
|
dependencies — v1 users install neither.
|
|
77
132
|
|
|
133
|
+
## Self-diagnostics
|
|
134
|
+
|
|
135
|
+
The plugin can inject a `__raindrop_report` tool that lets the agent flag
|
|
136
|
+
serious, unrecoverable problems for developer review. When the agent calls it,
|
|
137
|
+
a `self diagnostics - <category>` signal is posted to `signals/track`, attached
|
|
138
|
+
to the session's currently-open turn event. The tool is invisible to the end
|
|
139
|
+
user — its output is a bare acknowledgement and the model is instructed never
|
|
140
|
+
to mention it.
|
|
141
|
+
|
|
142
|
+
Enable it via `raindrop.json` (`~/.config/opencode/raindrop.json` or
|
|
143
|
+
`.opencode/raindrop.json`), or set `RAINDROP_SELF_DIAGNOSTICS=true` to override
|
|
144
|
+
just the `enabled` flag:
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"self_diagnostics": { "enabled": true }
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
For the v2 host API, pass the option directly:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import raindrop from "@raindrop-ai/opencode-plugin/v2";
|
|
156
|
+
|
|
157
|
+
export default raindrop({ selfDiagnostics: { enabled: true } });
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Optional `signals` (custom category → `{ description, sentiment? }` map),
|
|
161
|
+
`guidance` (extra instructions appended to the tool prompt), and `tool_name` /
|
|
162
|
+
`toolName` overrides are supported. The built-in categories are:
|
|
163
|
+
|
|
164
|
+
- `missing_context` — blocked on information or access the user cannot provide
|
|
165
|
+
- `repeatedly_broken_tool` — a tool failed across multiple distinct attempts
|
|
166
|
+
- `capability_gap` — the task needs a tool/permission/capability the agent lacks
|
|
167
|
+
- `complete_task_failure` — the agent genuinely could not deliver what was asked
|
|
168
|
+
|
|
169
|
+
Signals only ship to the cloud destination, so the tool is not registered in
|
|
170
|
+
local-only mode (no `RAINDROP_WRITE_KEY`).
|
|
171
|
+
|
|
78
172
|
## Notes
|
|
79
173
|
|
|
80
174
|
- `@opencode-ai/plugin` is required for the default (v1) export; `@opencode/plugin` for `/v2`.
|
|
@@ -60,7 +60,7 @@ function evalScopeAttributes() {
|
|
|
60
60
|
return { [EVAL_CORRELATION_ID_ATTRIBUTE]: scope.correlationId };
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
// ../core/dist/chunk-
|
|
63
|
+
// ../core/dist/chunk-I6TVJQEC.js
|
|
64
64
|
var STATE_KEY = /* @__PURE__ */ Symbol.for("raindrop.tracing.replayTraceRoutingState");
|
|
65
65
|
function state() {
|
|
66
66
|
const holder = globalThis;
|
|
@@ -2069,6 +2069,151 @@ var TraceShipper = class {
|
|
|
2069
2069
|
}
|
|
2070
2070
|
}
|
|
2071
2071
|
};
|
|
2072
|
+
var PROMPT_TOOLS_ATTRIBUTE = "ai.prompt.tools";
|
|
2073
|
+
function isRecord(value) {
|
|
2074
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2075
|
+
}
|
|
2076
|
+
var JSON_SCHEMA_KEYS = [
|
|
2077
|
+
"type",
|
|
2078
|
+
"properties",
|
|
2079
|
+
"$schema",
|
|
2080
|
+
"$ref",
|
|
2081
|
+
"anyOf",
|
|
2082
|
+
"oneOf",
|
|
2083
|
+
"allOf",
|
|
2084
|
+
"enum",
|
|
2085
|
+
"const",
|
|
2086
|
+
"items",
|
|
2087
|
+
"additionalProperties",
|
|
2088
|
+
"required"
|
|
2089
|
+
];
|
|
2090
|
+
function isJsonSchema(value) {
|
|
2091
|
+
if (!isRecord(value)) return false;
|
|
2092
|
+
if (typeof value["~standard"] === "object") return false;
|
|
2093
|
+
if (isRecord(value._def) || isRecord(value._zod)) return false;
|
|
2094
|
+
if (Object.keys(value).length === 0) return true;
|
|
2095
|
+
return JSON_SCHEMA_KEYS.some((key) => key in value);
|
|
2096
|
+
}
|
|
2097
|
+
function toJsonSchema(value, options) {
|
|
2098
|
+
if (value === void 0 || value === null) return void 0;
|
|
2099
|
+
if (isJsonSchema(value)) return value;
|
|
2100
|
+
if (isRecord(value) && isJsonSchema(value.jsonSchema)) return value.jsonSchema;
|
|
2101
|
+
if (isRecord(value) && isJsonSchema(value.json)) return value.json;
|
|
2102
|
+
if (options == null ? void 0 : options.schema) {
|
|
2103
|
+
try {
|
|
2104
|
+
return options.schema(value);
|
|
2105
|
+
} catch (e) {
|
|
2106
|
+
return void 0;
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
return void 0;
|
|
2110
|
+
}
|
|
2111
|
+
function pickString(value) {
|
|
2112
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
2113
|
+
}
|
|
2114
|
+
function functionTool(name, description, schema, options) {
|
|
2115
|
+
const tool = { type: "function", name };
|
|
2116
|
+
const desc = pickString(description);
|
|
2117
|
+
if (desc !== void 0) tool.description = desc;
|
|
2118
|
+
const inputSchema = toJsonSchema(schema, options);
|
|
2119
|
+
if (inputSchema !== void 0) tool.inputSchema = inputSchema;
|
|
2120
|
+
return tool;
|
|
2121
|
+
}
|
|
2122
|
+
function normalizeToolDeclaration(tool, options, fallbackName) {
|
|
2123
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
2124
|
+
if (!isRecord(tool)) return void 0;
|
|
2125
|
+
if (tool.type === "provider-defined" || tool.type === "provider") {
|
|
2126
|
+
const name2 = (_b = (_a = pickString(tool.name)) != null ? _a : pickString(tool.id)) != null ? _b : fallbackName;
|
|
2127
|
+
if (name2 === void 0) return void 0;
|
|
2128
|
+
const declaration = { type: "provider-defined", name: name2 };
|
|
2129
|
+
const id = pickString(tool.id);
|
|
2130
|
+
if (id !== void 0) declaration.id = id;
|
|
2131
|
+
if (isRecord(tool.args)) declaration.args = tool.args;
|
|
2132
|
+
return declaration;
|
|
2133
|
+
}
|
|
2134
|
+
if (isRecord(tool.toolSpec)) {
|
|
2135
|
+
const spec = tool.toolSpec;
|
|
2136
|
+
const name2 = (_c = pickString(spec.name)) != null ? _c : fallbackName;
|
|
2137
|
+
if (name2 === void 0) return void 0;
|
|
2138
|
+
return functionTool(name2, spec.description, spec.inputSchema, options);
|
|
2139
|
+
}
|
|
2140
|
+
if (isRecord(tool.function)) {
|
|
2141
|
+
const fn = tool.function;
|
|
2142
|
+
const name2 = (_d = pickString(fn.name)) != null ? _d : fallbackName;
|
|
2143
|
+
if (name2 === void 0) return void 0;
|
|
2144
|
+
return functionTool(name2, fn.description, (_e = fn.parameters) != null ? _e : fn.input_schema, options);
|
|
2145
|
+
}
|
|
2146
|
+
const name = (_g = (_f = pickString(tool.name)) != null ? _f : pickString(tool.id)) != null ? _g : fallbackName;
|
|
2147
|
+
if (name === void 0) return void 0;
|
|
2148
|
+
const type = pickString(tool.type);
|
|
2149
|
+
if (type !== void 0 && type !== "function" && type !== "dynamic" && type !== "custom") {
|
|
2150
|
+
if (!("input_schema" in tool) && !("inputSchema" in tool) && !("parameters" in tool)) {
|
|
2151
|
+
return { type: "provider-defined", id: type, name };
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
return functionTool(
|
|
2155
|
+
name,
|
|
2156
|
+
tool.description,
|
|
2157
|
+
(_j = (_i = (_h = tool.inputSchema) != null ? _h : tool.input_schema) != null ? _i : tool.parameters) != null ? _j : tool.schema,
|
|
2158
|
+
options
|
|
2159
|
+
);
|
|
2160
|
+
}
|
|
2161
|
+
var NOT_A_GEMINI_GROUP_KEYS = ["name", "id", "type", "function", "toolSpec", "cachePoint"];
|
|
2162
|
+
function isGeminiToolGroup(tool) {
|
|
2163
|
+
if (Array.isArray(tool.functionDeclarations)) return true;
|
|
2164
|
+
const keys = Object.keys(tool);
|
|
2165
|
+
if (keys.length === 0 || NOT_A_GEMINI_GROUP_KEYS.some((key) => key in tool)) return false;
|
|
2166
|
+
return keys.every((key) => isRecord(tool[key]));
|
|
2167
|
+
}
|
|
2168
|
+
function expandGeminiTool(tool, options) {
|
|
2169
|
+
const declarations = [];
|
|
2170
|
+
for (const [key, value] of Object.entries(tool)) {
|
|
2171
|
+
if (key === "functionDeclarations" && Array.isArray(value)) {
|
|
2172
|
+
for (const declaration of value) {
|
|
2173
|
+
const normalized = normalizeToolDeclaration(declaration, options);
|
|
2174
|
+
if (normalized) declarations.push(normalized);
|
|
2175
|
+
}
|
|
2176
|
+
} else if (isRecord(value)) {
|
|
2177
|
+
declarations.push({ type: "provider-defined", id: key, name: key, args: value });
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
return declarations;
|
|
2181
|
+
}
|
|
2182
|
+
function normalizeToolDeclarations(tools, options) {
|
|
2183
|
+
if (tools === void 0 || tools === null) return void 0;
|
|
2184
|
+
if (Array.isArray(tools)) {
|
|
2185
|
+
return tools.flatMap((tool) => {
|
|
2186
|
+
if (isRecord(tool) && isGeminiToolGroup(tool)) {
|
|
2187
|
+
return expandGeminiTool(tool, options);
|
|
2188
|
+
}
|
|
2189
|
+
const declaration = normalizeToolDeclaration(tool, options);
|
|
2190
|
+
return declaration ? [declaration] : [];
|
|
2191
|
+
});
|
|
2192
|
+
}
|
|
2193
|
+
if (!isRecord(tools)) return void 0;
|
|
2194
|
+
return Object.entries(tools).flatMap(([key, tool]) => {
|
|
2195
|
+
const declaration = normalizeToolDeclaration(tool, options, key);
|
|
2196
|
+
return declaration ? [declaration] : [];
|
|
2197
|
+
});
|
|
2198
|
+
}
|
|
2199
|
+
function promptToolsAttribute(tools) {
|
|
2200
|
+
const values = promptToolsAttributeValue(tools);
|
|
2201
|
+
if (values === void 0) return void 0;
|
|
2202
|
+
return {
|
|
2203
|
+
key: PROMPT_TOOLS_ATTRIBUTE,
|
|
2204
|
+
value: { arrayValue: { values: values.map((stringValue) => ({ stringValue })) } }
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
function promptToolsAttributeValue(tools) {
|
|
2208
|
+
if (tools === void 0) return void 0;
|
|
2209
|
+
return tools.map((tool) => {
|
|
2210
|
+
try {
|
|
2211
|
+
return JSON.stringify(tool);
|
|
2212
|
+
} catch (e) {
|
|
2213
|
+
return JSON.stringify({ type: tool.type, name: tool.name });
|
|
2214
|
+
}
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2072
2217
|
|
|
2073
2218
|
// ../core/dist/index.node.js
|
|
2074
2219
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -2282,8 +2427,41 @@ function loadConfig(projectDirectory) {
|
|
|
2282
2427
|
captureSystemPrompt: process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] !== void 0 ? process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] === "true" : (_h = merged.capture_system_prompt) != null ? _h : false,
|
|
2283
2428
|
eventMetadata,
|
|
2284
2429
|
localWorkshopUrl: resolveLocalWorkshopUrl(merged.local_workshop_url),
|
|
2285
|
-
appGit: mapAppGit(merged.app_git)
|
|
2430
|
+
appGit: mapAppGit(merged.app_git),
|
|
2431
|
+
selfDiagnostics: mapSelfDiagnostics(merged.self_diagnostics),
|
|
2432
|
+
tools: Array.isArray(merged.tools) ? merged.tools : void 0
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
function isRecord2(value) {
|
|
2436
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2437
|
+
}
|
|
2438
|
+
function mapSelfDiagnostics(value) {
|
|
2439
|
+
const env = process.env["RAINDROP_SELF_DIAGNOSTICS"];
|
|
2440
|
+
const record = isRecord2(value) ? value : {};
|
|
2441
|
+
const enabled = env !== void 0 ? env === "true" : typeof record["enabled"] === "boolean" ? record["enabled"] : void 0;
|
|
2442
|
+
const signals = mapSelfDiagnosticsSignals(record["signals"]);
|
|
2443
|
+
const options = {
|
|
2444
|
+
...enabled !== void 0 ? { enabled } : {},
|
|
2445
|
+
...signals ? { signals } : {},
|
|
2446
|
+
...typeof record["guidance"] === "string" ? { guidance: record["guidance"] } : {},
|
|
2447
|
+
...typeof record["tool_name"] === "string" ? { toolName: record["tool_name"] } : {}
|
|
2286
2448
|
};
|
|
2449
|
+
return Object.keys(options).length > 0 ? options : void 0;
|
|
2450
|
+
}
|
|
2451
|
+
function mapSelfDiagnosticsSignals(value) {
|
|
2452
|
+
if (!isRecord2(value)) return void 0;
|
|
2453
|
+
const out = {};
|
|
2454
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
2455
|
+
if (!isRecord2(entry)) continue;
|
|
2456
|
+
const definition = entry;
|
|
2457
|
+
if (typeof definition["description"] !== "string") continue;
|
|
2458
|
+
const sentiment = definition["sentiment"];
|
|
2459
|
+
out[key] = {
|
|
2460
|
+
description: definition["description"],
|
|
2461
|
+
...sentiment === "POSITIVE" || sentiment === "NEGATIVE" ? { sentiment } : {}
|
|
2462
|
+
};
|
|
2463
|
+
}
|
|
2464
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
2287
2465
|
}
|
|
2288
2466
|
function mapAppGit(value) {
|
|
2289
2467
|
if (value === false) return false;
|
|
@@ -2311,7 +2489,7 @@ function resolveLocalWorkshopUrl(fileValue) {
|
|
|
2311
2489
|
// package.json
|
|
2312
2490
|
var package_default = {
|
|
2313
2491
|
name: "@raindrop-ai/opencode-plugin",
|
|
2314
|
-
version: "0.
|
|
2492
|
+
version: "0.3.1",
|
|
2315
2493
|
description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
|
|
2316
2494
|
type: "module",
|
|
2317
2495
|
main: "dist/index.js",
|
|
@@ -2400,6 +2578,185 @@ var package_default = {
|
|
|
2400
2578
|
var PLUGIN_NAME = package_default.name;
|
|
2401
2579
|
var PLUGIN_VERSION = package_default.version;
|
|
2402
2580
|
|
|
2581
|
+
// src/self-diagnostics.ts
|
|
2582
|
+
var SELF_DIAGNOSTICS_TOOL_NAME_DEFAULT = "__raindrop_report";
|
|
2583
|
+
var SELF_DIAGNOSTICS_SIGNALS_DEFAULT = {
|
|
2584
|
+
missing_context: {
|
|
2585
|
+
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.",
|
|
2586
|
+
sentiment: "NEGATIVE"
|
|
2587
|
+
},
|
|
2588
|
+
repeatedly_broken_tool: {
|
|
2589
|
+
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.",
|
|
2590
|
+
sentiment: "NEGATIVE"
|
|
2591
|
+
},
|
|
2592
|
+
capability_gap: {
|
|
2593
|
+
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.",
|
|
2594
|
+
sentiment: "NEGATIVE"
|
|
2595
|
+
},
|
|
2596
|
+
complete_task_failure: {
|
|
2597
|
+
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.",
|
|
2598
|
+
sentiment: "NEGATIVE"
|
|
2599
|
+
}
|
|
2600
|
+
};
|
|
2601
|
+
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.";
|
|
2602
|
+
var SELF_DIAGNOSTICS_NOTEWORTHY_SIGNAL_KEY = "noteworthy";
|
|
2603
|
+
function normalizeString(value) {
|
|
2604
|
+
if (typeof value !== "string") return void 0;
|
|
2605
|
+
const trimmed = value.trim();
|
|
2606
|
+
return trimmed || void 0;
|
|
2607
|
+
}
|
|
2608
|
+
function normalizeSelfDiagnosticsSignals(signals) {
|
|
2609
|
+
if (!signals) return SELF_DIAGNOSTICS_SIGNALS_DEFAULT;
|
|
2610
|
+
const normalizedEntries = Object.entries(signals).map(([key, value]) => {
|
|
2611
|
+
var _a;
|
|
2612
|
+
const signalKey = key.trim();
|
|
2613
|
+
if (!signalKey || !value || typeof value !== "object") return void 0;
|
|
2614
|
+
const description = (_a = value.description) == null ? void 0 : _a.trim();
|
|
2615
|
+
if (!description) return void 0;
|
|
2616
|
+
const sentiment = value.sentiment;
|
|
2617
|
+
return [
|
|
2618
|
+
signalKey,
|
|
2619
|
+
{
|
|
2620
|
+
description,
|
|
2621
|
+
...sentiment === "POSITIVE" || sentiment === "NEGATIVE" ? { sentiment } : {}
|
|
2622
|
+
}
|
|
2623
|
+
];
|
|
2624
|
+
}).filter(
|
|
2625
|
+
(entry) => entry !== void 0
|
|
2626
|
+
);
|
|
2627
|
+
if (normalizedEntries.length === 0) return SELF_DIAGNOSTICS_SIGNALS_DEFAULT;
|
|
2628
|
+
return Object.fromEntries(normalizedEntries);
|
|
2629
|
+
}
|
|
2630
|
+
function normalizeSelfDiagnosticsConfig(options) {
|
|
2631
|
+
var _a, _b;
|
|
2632
|
+
if (!(options == null ? void 0 : options.enabled)) return void 0;
|
|
2633
|
+
const signalDefinitions = normalizeSelfDiagnosticsSignals(options.signals);
|
|
2634
|
+
const signalKeys = Object.keys(signalDefinitions);
|
|
2635
|
+
const signalDescriptions = {};
|
|
2636
|
+
const signalSentiments = {};
|
|
2637
|
+
for (const signalKey of signalKeys) {
|
|
2638
|
+
const definition = signalDefinitions[signalKey];
|
|
2639
|
+
if (!definition) continue;
|
|
2640
|
+
signalDescriptions[signalKey] = definition.description;
|
|
2641
|
+
signalSentiments[signalKey] = definition.sentiment;
|
|
2642
|
+
}
|
|
2643
|
+
const customGuidance = ((_a = options.guidance) == null ? void 0 : _a.trim()) || "";
|
|
2644
|
+
const toolName = ((_b = options.toolName) == null ? void 0 : _b.trim()) || SELF_DIAGNOSTICS_TOOL_NAME_DEFAULT;
|
|
2645
|
+
const signalList = signalKeys.map((signalKey) => {
|
|
2646
|
+
const sentiment = signalSentiments[signalKey];
|
|
2647
|
+
const sentimentTag = sentiment ? ` [${sentiment.toLowerCase()}]` : "";
|
|
2648
|
+
return `- ${signalKey}: ${signalDescriptions[signalKey]}${sentimentTag}`;
|
|
2649
|
+
}).join("\n");
|
|
2650
|
+
const guidanceBlock = customGuidance ? `
|
|
2651
|
+
Additional guidance: ${customGuidance}
|
|
2652
|
+
` : "";
|
|
2653
|
+
const toolDescription = `${SELF_DIAGNOSTICS_TOOL_PREAMBLE}
|
|
2654
|
+
|
|
2655
|
+
When to call:
|
|
2656
|
+
- You are blocked from completing the task due to missing information or access that the user cannot provide.
|
|
2657
|
+
- A tool is persistently failing across multiple attempts, not just a single transient error.
|
|
2658
|
+
- The task requires a tool, permission, or capability you do not have.
|
|
2659
|
+
- You genuinely cannot deliver what the user asked for despite trying.
|
|
2660
|
+
|
|
2661
|
+
When NOT to call:
|
|
2662
|
+
- Normal clarifying questions or back-and-forth with the user.
|
|
2663
|
+
- A single tool error that you can recover from or retry.
|
|
2664
|
+
- You successfully completed the task, even if it was difficult.
|
|
2665
|
+
- Policy refusals or content filtering \u2014 those are working as intended.
|
|
2666
|
+
|
|
2667
|
+
Rules:
|
|
2668
|
+
1. Pick the single best category.
|
|
2669
|
+
2. Do not fabricate issues. Only report what is evident from the conversation.
|
|
2670
|
+
3. Err on the side of NOT calling this tool. When in doubt, help the user instead.
|
|
2671
|
+
${guidanceBlock}
|
|
2672
|
+
Categories:
|
|
2673
|
+
` + signalList;
|
|
2674
|
+
return {
|
|
2675
|
+
toolName,
|
|
2676
|
+
toolDescription,
|
|
2677
|
+
signalKeys,
|
|
2678
|
+
signalKeySet: new Set(signalKeys),
|
|
2679
|
+
signalDescriptions,
|
|
2680
|
+
signalSentiments
|
|
2681
|
+
};
|
|
2682
|
+
}
|
|
2683
|
+
var SELF_DIAGNOSTICS_ACK = '{"acknowledged":true}';
|
|
2684
|
+
function selfDiagnosticsInputJsonSchema(config) {
|
|
2685
|
+
return {
|
|
2686
|
+
type: "object",
|
|
2687
|
+
properties: {
|
|
2688
|
+
category: {
|
|
2689
|
+
type: "string",
|
|
2690
|
+
enum: config.signalKeys,
|
|
2691
|
+
description: "The single best-matching category from the list above."
|
|
2692
|
+
},
|
|
2693
|
+
detail: {
|
|
2694
|
+
type: "string",
|
|
2695
|
+
description: "One sentence of factual context: what happened and why it matters. Do not include PII or secrets."
|
|
2696
|
+
}
|
|
2697
|
+
},
|
|
2698
|
+
required: ["category", "detail"],
|
|
2699
|
+
additionalProperties: false
|
|
2700
|
+
};
|
|
2701
|
+
}
|
|
2702
|
+
function isRecord3(value) {
|
|
2703
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2704
|
+
}
|
|
2705
|
+
async function trackSelfDiagnosticsSignal(toolInput, config, eventShipper, eventId, debug) {
|
|
2706
|
+
var _a, _b;
|
|
2707
|
+
try {
|
|
2708
|
+
const input = isRecord3(toolInput) ? toolInput : void 0;
|
|
2709
|
+
const fallbackCategory = (_a = config.signalKeys[0]) != null ? _a : "unknown";
|
|
2710
|
+
const categoryCandidate = normalizeString(input == null ? void 0 : input["category"]);
|
|
2711
|
+
const category = categoryCandidate && config.signalKeySet.has(categoryCandidate) ? categoryCandidate : fallbackCategory;
|
|
2712
|
+
const detail = (_b = normalizeString(input == null ? void 0 : input["detail"])) != null ? _b : "";
|
|
2713
|
+
if (!eventId) {
|
|
2714
|
+
if (debug) {
|
|
2715
|
+
console.warn(
|
|
2716
|
+
"[raindrop-ai/opencode-plugin] self diagnostics signal skipped: no open event for this session."
|
|
2717
|
+
);
|
|
2718
|
+
}
|
|
2719
|
+
return;
|
|
2720
|
+
}
|
|
2721
|
+
if (category === SELF_DIAGNOSTICS_NOTEWORTHY_SIGNAL_KEY) {
|
|
2722
|
+
await eventShipper.trackSignal({
|
|
2723
|
+
eventId,
|
|
2724
|
+
name: "self diagnostics - noteworthy",
|
|
2725
|
+
type: "agent_internal",
|
|
2726
|
+
properties: {
|
|
2727
|
+
source: "agent_flag_event_tool",
|
|
2728
|
+
reason: detail,
|
|
2729
|
+
severity: "medium",
|
|
2730
|
+
sdk: "opencode-plugin",
|
|
2731
|
+
sdk_version: PLUGIN_VERSION
|
|
2732
|
+
}
|
|
2733
|
+
});
|
|
2734
|
+
return;
|
|
2735
|
+
}
|
|
2736
|
+
await eventShipper.trackSignal({
|
|
2737
|
+
eventId,
|
|
2738
|
+
name: `self diagnostics - ${category}`,
|
|
2739
|
+
type: "agent",
|
|
2740
|
+
sentiment: config.signalSentiments[category],
|
|
2741
|
+
properties: {
|
|
2742
|
+
source: "agent_reporting_tool",
|
|
2743
|
+
category,
|
|
2744
|
+
signal_description: config.signalDescriptions[category],
|
|
2745
|
+
sdk: "opencode-plugin",
|
|
2746
|
+
sdk_version: PLUGIN_VERSION,
|
|
2747
|
+
...detail ? { detail } : {}
|
|
2748
|
+
}
|
|
2749
|
+
});
|
|
2750
|
+
} catch (err) {
|
|
2751
|
+
if (debug) {
|
|
2752
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2753
|
+
console.warn(
|
|
2754
|
+
`[raindrop-ai/opencode-plugin] selfDiagnostics signal dispatch failed: ${message}`
|
|
2755
|
+
);
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2403
2760
|
// src/shipper.ts
|
|
2404
2761
|
var EventShipper3 = class extends EventShipper2 {
|
|
2405
2762
|
constructor(opts) {
|
|
@@ -2435,6 +2792,100 @@ var TraceShipper3 = class extends TraceShipper2 {
|
|
|
2435
2792
|
}
|
|
2436
2793
|
};
|
|
2437
2794
|
|
|
2795
|
+
// src/tool-catalog.ts
|
|
2796
|
+
var PROMPT_TOOLS_SOURCE_ATTRIBUTE = "ai.prompt.tools.source";
|
|
2797
|
+
var OPENCODE_TOOL_LIST_SOURCE = "opencode.tool.list";
|
|
2798
|
+
var TOOL_CATALOG_TIMEOUT_MS = 2e3;
|
|
2799
|
+
function isRecord4(value) {
|
|
2800
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2801
|
+
}
|
|
2802
|
+
function catalogItems(raw) {
|
|
2803
|
+
var _a;
|
|
2804
|
+
const list = Array.isArray(raw) ? raw : isRecord4(raw) && Array.isArray(raw.data) ? raw.data : void 0;
|
|
2805
|
+
if (!list) return void 0;
|
|
2806
|
+
const items = [];
|
|
2807
|
+
for (const item of list) {
|
|
2808
|
+
if (!isRecord4(item)) continue;
|
|
2809
|
+
const name = typeof item.id === "string" ? item.id : typeof item.name === "string" ? item.name : void 0;
|
|
2810
|
+
if (!name) continue;
|
|
2811
|
+
const schema = (_a = item.parameters) != null ? _a : item.input;
|
|
2812
|
+
items.push({
|
|
2813
|
+
type: "function",
|
|
2814
|
+
name,
|
|
2815
|
+
...typeof item.description === "string" ? { description: item.description } : {},
|
|
2816
|
+
...isJsonSchema(schema) ? { inputSchema: schema } : {}
|
|
2817
|
+
});
|
|
2818
|
+
}
|
|
2819
|
+
return items;
|
|
2820
|
+
}
|
|
2821
|
+
function withTimeout(promise, ms) {
|
|
2822
|
+
return new Promise((resolve2) => {
|
|
2823
|
+
const timer = setTimeout(() => resolve2(void 0), ms);
|
|
2824
|
+
if (typeof timer === "object" && "unref" in timer) timer.unref();
|
|
2825
|
+
promise.then(
|
|
2826
|
+
(value) => {
|
|
2827
|
+
clearTimeout(timer);
|
|
2828
|
+
resolve2(value);
|
|
2829
|
+
},
|
|
2830
|
+
() => {
|
|
2831
|
+
clearTimeout(timer);
|
|
2832
|
+
resolve2(void 0);
|
|
2833
|
+
}
|
|
2834
|
+
);
|
|
2835
|
+
});
|
|
2836
|
+
}
|
|
2837
|
+
function createToolCatalog(options = {}) {
|
|
2838
|
+
var _a, _b;
|
|
2839
|
+
const override = options.override !== void 0 ? { tools: (_a = normalizeToolDeclarations(options.override)) != null ? _a : [] } : void 0;
|
|
2840
|
+
const timeoutMs = (_b = options.timeoutMs) != null ? _b : TOOL_CATALOG_TIMEOUT_MS;
|
|
2841
|
+
const cache = /* @__PURE__ */ new Map();
|
|
2842
|
+
async function lookup(query) {
|
|
2843
|
+
const fetch2 = options.fetch;
|
|
2844
|
+
if (!fetch2) return void 0;
|
|
2845
|
+
let raw;
|
|
2846
|
+
try {
|
|
2847
|
+
raw = await withTimeout(Promise.resolve().then(() => fetch2(query)), timeoutMs);
|
|
2848
|
+
} catch (error) {
|
|
2849
|
+
if (options.debug) console.log(`[raindrop-ai/opencode-plugin] tool catalog lookup failed: ${String(error)}`);
|
|
2850
|
+
return void 0;
|
|
2851
|
+
}
|
|
2852
|
+
const tools = normalizeToolDeclarations(catalogItems(raw));
|
|
2853
|
+
if (!tools) {
|
|
2854
|
+
if (options.debug) console.log("[raindrop-ai/opencode-plugin] tool catalog lookup returned no tool list");
|
|
2855
|
+
return void 0;
|
|
2856
|
+
}
|
|
2857
|
+
return { tools, source: OPENCODE_TOOL_LIST_SOURCE };
|
|
2858
|
+
}
|
|
2859
|
+
return {
|
|
2860
|
+
resolve(sessionID, query) {
|
|
2861
|
+
if (override) return Promise.resolve(override);
|
|
2862
|
+
if (!options.fetch) return Promise.resolve(void 0);
|
|
2863
|
+
let perSession = cache.get(sessionID);
|
|
2864
|
+
if (!perSession) {
|
|
2865
|
+
perSession = /* @__PURE__ */ new Map();
|
|
2866
|
+
cache.set(sessionID, perSession);
|
|
2867
|
+
}
|
|
2868
|
+
const key = `${query.provider}\0${query.model}`;
|
|
2869
|
+
let pending = perSession.get(key);
|
|
2870
|
+
if (!pending) {
|
|
2871
|
+
pending = lookup(query).catch(() => void 0);
|
|
2872
|
+
perSession.set(key, pending);
|
|
2873
|
+
}
|
|
2874
|
+
return pending;
|
|
2875
|
+
},
|
|
2876
|
+
forget(sessionID) {
|
|
2877
|
+
cache.delete(sessionID);
|
|
2878
|
+
}
|
|
2879
|
+
};
|
|
2880
|
+
}
|
|
2881
|
+
function promptToolsAttributes(resolved) {
|
|
2882
|
+
if (!resolved) return [];
|
|
2883
|
+
return [
|
|
2884
|
+
promptToolsAttribute(resolved.tools),
|
|
2885
|
+
resolved.source !== void 0 ? attrString(PROMPT_TOOLS_SOURCE_ATTRIBUTE, resolved.source) : void 0
|
|
2886
|
+
];
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2438
2889
|
// src/bounded.ts
|
|
2439
2890
|
var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
|
|
2440
2891
|
var MAX_TEXT_FIELD_CHARS = 1e6;
|
|
@@ -2565,8 +3016,14 @@ export {
|
|
|
2565
3016
|
loadConfig,
|
|
2566
3017
|
PLUGIN_NAME,
|
|
2567
3018
|
PLUGIN_VERSION,
|
|
3019
|
+
normalizeSelfDiagnosticsConfig,
|
|
3020
|
+
SELF_DIAGNOSTICS_ACK,
|
|
3021
|
+
selfDiagnosticsInputJsonSchema,
|
|
3022
|
+
trackSelfDiagnosticsSignal,
|
|
2568
3023
|
EventShipper3 as EventShipper,
|
|
2569
3024
|
TraceShipper3 as TraceShipper,
|
|
3025
|
+
createToolCatalog,
|
|
3026
|
+
promptToolsAttributes,
|
|
2570
3027
|
capText2 as capText,
|
|
2571
3028
|
boundedStringify,
|
|
2572
3029
|
buildLlmGenAiAttributes
|