From 2c298ddc2b30f2bfbab12dcb91c5d5d80a4f5210 Mon Sep 17 00:00:00 2001 From: Trevor Walker Date: Thu, 24 Sep 2026 01:43:16 -0600 Subject: [PATCH] fix: scrub OpenCode prompts routed through LiteLLM --- README.md | 2 +- src/__tests__/plugin.test.ts | 41 ++++++++++++++++++++++++++++++++++++ src/index.ts | 14 +++++++++--- 3 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 src/__tests__/plugin.test.ts diff --git a/README.md b/README.md index 50beb6b..ce64e66 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Verify at `http://localhost:3456/plugins` — you should see `opencode-scrub` li | OpenCode prompt + user CLAUDE.md additions | identity stripped, all user content preserved | | OpenCode environment preamble + `` | duplicate preamble and redundant fields removed; bare `Working directory` retained for Meridian's cwd extraction | -The plugin is scoped to `adapters: ["opencode"]`, so it has no effect on requests from pi, Crush, Droid, ForgeCode, or the passthrough adapter. +The plugin runs for the `opencode` adapter and for `passthrough` requests that still carry an OpenCode-specific identity or runtime marker. This covers OpenCode routed through LiteLLM when its client headers are removed. Other passthrough prompts, including genuine Claude Code prompts with an `` block, remain byte-for-byte unchanged. ## Rules diff --git a/src/__tests__/plugin.test.ts b/src/__tests__/plugin.test.ts new file mode 100644 index 0000000..2f11eae --- /dev/null +++ b/src/__tests__/plugin.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from "bun:test" +import plugin from "../index.js" +import type { RequestContext } from "../types.js" + +const opencodePrompt = `You are powered by the model named claude-haiku-4-5. The exact model ID is anthropic/claude-haiku-4-5 +Here is some useful information about the environment you are running in: + + Working directory: /client/project + Platform: darwin +` + +describe("opencode-scrub adapter boundary", () => { + it("includes LiteLLM's passthrough classification and scrubs OpenCode content", () => { + expect(plugin.adapters).toContain("passthrough") + const ctx: RequestContext = { adapter: "passthrough", systemContext: opencodePrompt, metadata: {} } + const result = plugin.onRequest?.(ctx) + expect(result?.systemContext).not.toContain("You are powered by the model named") + expect(result?.systemContext).not.toContain("Here is some useful information") + expect(result?.systemContext).toContain("Working directory: /client/project") + }) + + it("preserves genuine Claude Code passthrough prompts byte-for-byte", () => { + const systemContext = "You are Claude Code.\n\n\nHere is some useful information about the environment you are running in:\n\n Working directory: /client/project\n\n\n" + const ctx: RequestContext = { adapter: "passthrough", systemContext, metadata: {} } + expect(plugin.onRequest?.(ctx)).toBe(ctx) + expect(ctx.systemContext).toBe(systemContext) + }) + + it("preserves incidental OpenCode mentions in other passthrough prompts", () => { + const systemContext = "Compare OpenCode and other editors.\n\n\nKeep this formatting.\n" + const ctx: RequestContext = { adapter: "passthrough", systemContext, metadata: {} } + expect(plugin.onRequest?.(ctx)).toBe(ctx) + }) + + it("keeps the existing direct OpenCode adapter behavior", () => { + const ctx: RequestContext = { adapter: "opencode", systemContext: opencodePrompt, metadata: {} } + const result = plugin.onRequest?.(ctx) + expect(result?.systemContext).not.toContain("Here is some useful information") + expect(result?.systemContext).toContain("Working directory: /client/project") + }) +}) diff --git a/src/index.ts b/src/index.ts index fd669a3..db9eab0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,9 @@ /** * Meridian plugin: strip opencode-identifying fingerprints from the system prompt. * - * Scoped to the `opencode` adapter. Runs on every opencode request — the - * scrub is idempotent, so fork/subagent replays produce identical output. + * Runs on the `opencode` adapter and on `passthrough` requests carrying an + * OpenCode-specific marker. LiteLLM can remove the client headers that let + * Meridian identify OpenCode, so its requests arrive as `passthrough`. */ import type { Transform, RequestContext } from "./types.js" @@ -10,14 +11,21 @@ import { scrubOpencodeFingerprints } from "./scrub.js" export type { Transform, RequestContext } from "./types.js" +// The duplicate preamble is shared with genuine Claude Code traffic. +// Require an independent OpenCode/OMO marker before touching passthrough +// requests, so unrelated clients keep their original prompt bytes. +const PASSTHROUGH_OPENCODE_MARKER = + /You are OpenCode, the best coding agent on the planet\.|You are powered by the model named |||You are (?:"|\*\*)Sisyphus(?:"|\*\*)[^\n]*OhMyOpenCode/ + const plugin: Transform = { name: "opencode-scrub", version: "0.1.0", description: "Strip opencode-identifying fingerprints from the system prompt before it reaches Claude", - adapters: ["opencode"], + adapters: ["opencode", "passthrough"], onRequest(ctx: RequestContext): RequestContext { if (!ctx.systemContext) return ctx + if (ctx.adapter === "passthrough" && !PASSTHROUGH_OPENCODE_MARKER.test(ctx.systemContext)) return ctx const scrubbed = scrubOpencodeFingerprints(ctx.systemContext) if (scrubbed === ctx.systemContext) return ctx return { ...ctx, systemContext: scrubbed }