@raindrop-ai/opencode-plugin
v0.3.1
Published
Raindrop observability plugin for OpenCode — automatic session/event/span tracing
Maintainers
Keywords
Readme
@raindrop-ai/opencode-plugin
Raindrop observability plugin for OpenCode with automatic session, event, and span tracing.
Install
pnpm add @raindrop-ai/opencode-plugin @opencode-ai/pluginIf your integration also uses the OpenCode SDK directly, install @opencode-ai/sdk as well.
Projects
If your org has multiple projects, route telemetry to a specific one by setting its slug via the RAINDROP_PROJECT_ID env var or the project_id key in ~/.config/opencode/raindrop.json (or project-level .opencode/raindrop.json):
{
"project_id": "support-prod"
}This sets the X-Raindrop-Project-Id header on every outbound request. Leave it unset (or use "default") to use your org's default Production project — the existing behavior. Single-project orgs need nothing new.
Payload size limits
Text fields (event input/output, span prompt/response and tool payloads) are
capped at 1,000,000 characters per field and truncated with a
...[truncated by raindrop] marker. The cap is enforced before (or during)
serialization, so oversized payloads cost the cap — not the payload — on the
OpenCode host's event loop, and large events land truncated instead of being
rejected at the ingest size limit. Hook error logs are rate-limited to one
line per failure family per 30s so a persistent error can't flood the host's
output.
Tool catalog capture (ai.prompt.tools)
OpenCode's step events carry no tool list, so the plugin cannot see the exact tools each model request was given. Instead it records the host's tool catalog for the step's model:
- v1 plugin (
@raindrop-ai/opencode-plugin): on the first step of a session for a given provider/model, the plugin callsclient.tool.list({ query: { provider, model } })(OpenCode's experimental/experimental/toolendpoint) once, caches the answer per session and model, and stamps it asai.prompt.toolson every step span. The lookup is deduplicated while in flight, bounded by a 2s timeout, and never surfaces errors to OpenCode: when it fails, the attribute is simply absent. - v2 plugin (
@raindrop-ai/opencode-plugin/v2): the v2 host has no per-model catalog endpoint; the plugin reads the registered tool list (ctx.tool.list(), after every plugin'stool.transform) with the same caching, timeout and no-throw rules.
Because this is OpenCode's catalog for that model, not the exact per-request
set, catalog-derived spans also carry ai.prompt.tools.source = "opencode.tool.list".
An explicit override (below) is exact and carries no source. Each element of
ai.prompt.tools is a JSON document
{"type":"function","name":…,"description"?:…,"inputSchema"?:<JSON Schema>};
a tool whose schema is not plain JSON Schema is recorded without inputSchema
rather than with an invented one. The plugin has no separate content switch
(prompts and responses are always recorded; only the system prompt is opt-in),
so the catalog is recorded whenever tracing is on. Absent attribute = catalog
unknown; [] = no tools.
tools override
Overrides the tool catalog recorded as ai.prompt.tools on every model span.
Use when the integration cannot see the tools the model was given. The
override replaces the catalog entirely (tools: [] records an empty catalog)
and skips the catalog lookup.
raindrop.json (user- or project-level):
{
"tools": [
{
"name": "get_weather",
"description": "Get the current weather",
"parameters": { "type": "object", "properties": { "city": { "type": "string" } }, "required": ["city"] }
}
]
}Programmatic options (v1 plugin(input, { tools }), v2 raindrop({ tools }))
take precedence over the config file. Any recognized declaration shape is
accepted (OpenAI {type:"function",function:{…}}, Anthropic input_schema,
plain {name, description, parameters}, …).
Correlating per-message feedback signals
Each Raindrop event uses a client-generated event_id that isn't predictable
from the outside. To let you attach a signal (e.g. a per-message thumbs
up/down) to the right event, every finalized event carries the OpenCode
assistant message id as properties.message_id (the msg_... of the turn's
final assistant message). The same id is set as the message_id attribute on
the turn's root span.
Given a msg_..., look up the event whose properties.message_id matches it,
then send its event_id to /v1/signals:
// finalized event payload (track_partial)
{
"event_id": "…", // random, generated client-side
"ai_data": { "convo_id": "ses_…" },
"properties": { "message_id": "msg_…" } // ← correlate on this
}OpenCode v2 plugin API
OpenCode's newer plugin API (@opencode/plugin, @opencode/sdk v2) is served
by the @raindrop-ai/opencode-plugin/v2 subpath. Use it instead of the default
export when running against the v2 plugin host:
// opencode.json
{ "plugin": ["@raindrop-ai/opencode-plugin/v2"] }npm install @raindrop-ai/opencode-plugin @opencode/pluginThe v2 entry exposes the plugin object plus a raindrop handle
(users.identify, signals.track, currentEventId(sessionID), flush,
shutdown). Configuration, event/tracing semantics, and payload bounds are the
same as the v1 entry. @opencode/plugin and @opencode/sdk are optional peer
dependencies — v1 users install neither.
Self-diagnostics
The plugin can inject a __raindrop_report tool that lets the agent flag
serious, unrecoverable problems for developer review. When the agent calls it,
a self diagnostics - <category> signal is posted to signals/track, attached
to the session's currently-open turn event. The tool is invisible to the end
user — its output is a bare acknowledgement and the model is instructed never
to mention it.
Enable it via raindrop.json (~/.config/opencode/raindrop.json or
.opencode/raindrop.json), or set RAINDROP_SELF_DIAGNOSTICS=true to override
just the enabled flag:
{
"self_diagnostics": { "enabled": true }
}For the v2 host API, pass the option directly:
import raindrop from "@raindrop-ai/opencode-plugin/v2";
export default raindrop({ selfDiagnostics: { enabled: true } });Optional signals (custom category → { description, sentiment? } map),
guidance (extra instructions appended to the tool prompt), and tool_name /
toolName overrides are supported. The built-in categories are:
missing_context— blocked on information or access the user cannot providerepeatedly_broken_tool— a tool failed across multiple distinct attemptscapability_gap— the task needs a tool/permission/capability the agent lackscomplete_task_failure— the agent genuinely could not deliver what was asked
Signals only ship to the cloud destination, so the tool is not registered in
local-only mode (no RAINDROP_WRITE_KEY).
Notes
@opencode-ai/pluginis required for the default (v1) export;@opencode/pluginfor/v2.@opencode-ai/sdkand@opencode/sdkare optional peer dependencies.- Tests live in the nested
tests/package and validate HTTP payloads and tracing behavior.
Application Git identity
OpenCode does not treat the edited project directory or ambient deploy/CI metadata in the plugin process as the agent-under-test revision. By default the new canonical application revision is unknown. Configure an app_git object in raindrop.json using commit_sha, commit_dirty, and ordinary branch, set RAINDROP_COMMIT_SHA / RAINDROP_COMMIT_DIRTY / RAINDROP_BRANCH, or set an explicit source_directory (or RAINDROP_GIT_SOURCE_DIRECTORY) that identifies the agent-under-test checkout. Set app_git to false to opt out. Automatic canonical branch discovery is off unless detect_branch is true.
