Local OpenCode plugin scaffold for the /intercept command family and safe, session-scoped provider dump capture.
This repository now ships the hardened S03 runtime contract without changing the user-facing command forms:
/intercept/intercept on/intercept off
The plugin installs a single long-lived globalThis.fetch wrapper when it loads, refreshes active session context from OpenCode chat hooks, and keeps capture fail-open. If matching capture, serialization, dump writing, or startup cleanup fails, provider traffic still proceeds.
When interception is enabled, LLM provider traffic is detected by request body shape rather than URL. Any POST request with a JSON body containing a messages array is captured. The plugin distinguishes between Anthropic, OpenAI, and generic LLM request shapes.
Matching captures are written under temp storage only:
/tmp/opencode-interceptor/<session-id>
Each captured request writes an ordered trio with a shared basename:
001-anthropic-<timestamp>.request.json001-anthropic-<timestamp>.response.json001-anthropic-<timestamp>.meta.json
Persisted artifacts are intentionally safe-by-default:
- request JSON bodies are recursively scrubbed for obvious secret-bearing keys such as
api_key,token,password,authorization, andsecret_* - response JSON bodies, including HTTP-error payloads, are scrubbed through the same recursive redactor
- streamed SSE responses are persisted only as replay-friendly assistant text, not raw
event:/data:frame payloads - unsafe plain-text bodies are omitted instead of being written raw
- request headers are persisted with best-effort redaction (
authorization,x-api-key,cookie, etc. are replaced with[REDACTED])
On plugin startup, the interceptor runs one best-effort cleanup pass over the fixed temp root:
/tmp/opencode-interceptor
Cleanup behavior:
- only direct child session directories under that root are eligible for deletion
- only expired session directories are pruned (current retention window: 24 hours)
- fresh session directories stay in place
- unknown files, nested paths, and anything outside the interceptor root are not deleted
- cleanup failures remain non-fatal and surface as anomalies through
/intercept
/intercept is the authoritative operator surface. The summary reports:
EnabledDump rootCapturesTotal bytesAnomaliesLatest anomaly phaseLatest anomaly message
The anomaly surface is shared by capture-time and startup-cleanup failures. Example phases include:
capture/response-readcapture/response-parsecapture/dump-writecleanup/root-readcleanup/entry-statcleanup/entry-deletecleanup/path-safety
*.meta.json records truthful byte accounting and request metadata:
timestampurlmethodstatuscontentTypedurationMsrequestBytesresponseBytescapturedBytes
*.response.json records one of the following safe body states:
emptyjsonreplay-textomittedread-error
When the body is omitted or unreadable, the dump tells the truth through bodyOmittedReason or bodyReadError instead of inventing success.
-
Install dependencies:
bun install
-
Add this plugin to your local OpenCode config as a file URL that points at this repo's entrypoint.
Either load the TypeScript source directly (Bun compiles it at runtime — no build step needed for local dev):
{ "plugin": [ "file:///absolute/path/to/opencode-interceptor/src/index.ts" ] }Or, after running
bun run build, load the compiled ESM bundle:{ "plugin": [ "file:///absolute/path/to/opencode-interceptor/dist/index.js" ] }Example from this checkout:
{ "plugin": [ "file:///Users/ufukaltinok/Work/OSS/opencode-interceptor/src/index.ts" ] } -
Start or restart OpenCode after updating the config.
Expected replies:
/intercept→## Interceptor Status/intercept on→## Interceptor Enabled/intercept off→## Interceptor Disabled- invalid forms such as
/intercept bogus→## Interceptor Usage
Use these commands as the authoritative verification flow:
bun test tests/e2e/serve-toggle-proof.test.ts
bun run checkFocused suites remain available when you need to localize a failure before rerunning the root gate:
bun test tests/e2e/serve-command.test.ts
bun test tests/e2e/serve-capture.test.tsWhat each suite proves:
tests/e2e/serve-toggle-proof.test.tsis the milestone-level assembly proof: one realopencode servesession stays inert while disabled, captures truthfully while enabled, and stops again after disable.tests/e2e/serve-command.test.tsisolates the/interceptcommand seam: status, toggles, repeated forms, malformed input handling, restart behavior, and missing-plugin/startup diagnostics.tests/e2e/serve-capture.test.tsis the hardening and negative-regression suite: startup cleanup, replay-text persistence, scrubbed HTTP-error capture, stalled providers, malformed event streams, and failure diagnostics.- the unit suites keep the deny-by-default matcher, redaction, retention, and command-state contracts covered underneath the real-runtime proofs.
bun run checkremains the authoritative repo gate because it reruns lint, typecheck, and the entire test suite with the milestone proof included.
If /intercept or dump capture does not behave truthfully:
- Run the canonical milestone proof first:
bun test tests/e2e/serve-toggle-proof.test.ts. - Inspect the failure output for:
latestInterceptSummary=dumpInspection=mockRequests=--- stdout ------ stderr ---
- Parse the reported
Dump rootfrom/interceptand inspect the request/response/meta trio files for that session. - Check
Enabled,Captures,Total bytes,Anomalies,Latest anomaly phase, andLatest anomaly messagein/interceptbefore assuming capture is silently broken. - If the failure looks command-specific, rerun
bun test tests/e2e/serve-command.test.ts; if it looks like capture hardening or malformed-provider behavior, rerunbun test tests/e2e/serve-capture.test.ts. - After the focused proof is green again, rerun
bun run checkto confirm the repository-wide gate still passes. - Inspect the shared temp root (
/tmp/opencode-interceptor) if you suspect stale artifacts or startup cleanup behavior. - Confirm the plugin path in OpenCode config still points at
src/index.tsin this repo. - Confirm the
opencodeCLI is available onPATH, because the real-runtime proofs startopencode servedirectly.
S03 establishes the hardened dump/runtime baseline: curated provider matching, scrubbed request/response artifacts, replay-text stream persistence, startup retention cleanup, and shared anomaly visibility through /intercept. S04 closes the milestone proof path by making tests/e2e/serve-toggle-proof.test.ts the canonical end-to-end verification surface while keeping serve-command.test.ts and serve-capture.test.ts as narrower diagnostic suites.