Skip to content

fix: align OpenCode plugin with @opencode-ai/plugin SDK v1.2.x#1376

Open
runzexia wants to merge 1 commit intothedotmack:feature/npxfrom
runzexia:fix/opencode-plugin-sdk-compatibility
Open

fix: align OpenCode plugin with @opencode-ai/plugin SDK v1.2.x#1376
runzexia wants to merge 1 commit intothedotmack:feature/npxfrom
runzexia:fix/opencode-plugin-sdk-compatibility

Conversation

@runzexia
Copy link

Summary

Rewrites the OpenCode plugin (src/integrations/opencode-plugin/index.ts) to align with the actual @opencode-ai/plugin SDK v1.2.x API. The existing implementation uses API patterns that silently fail or crash — hooks never fire, events are never received, and the search tool crashes OpenCode on startup.

All fixes have been tested end-to-end against @opencode-ai/plugin@1.2.27 with a live OpenCode session, confirming the full pipeline: session.created → tool.execute.after → chat.message → session.idle → session.complete.

Problems Fixed

1. Hook API Mismatch (BREAKING — hooks silently fail)

The SDK expects flat string keys on the returned Hooks object:

// ✅ Correct (SDK v1.2.x)
"tool.execute.after": async (input, output) => { ... }

// ❌ Current code (silently ignored)
hooks: { tool: { execute: { after: (input, output) => { ... } } } }

Impact: No tool executions were being captured. Zero observations generated from tool use.

2. Event Handler Signature (BREAKING — events never received)

The SDK dispatches events as event({ event: Event }) where Event has .type and .properties:

// ✅ Correct (SDK v1.2.x)
async event({ event }) {
  switch (event.type) {
    case "session.created":
      const info = event.properties.info; // Session object
  }
}

// ❌ Current code (never called)
event: (eventName: string, payload: unknown) => { ... }

Impact: No session lifecycle events were received. Sessions never initialized, never completed.

3. Tool Definition Crash (BREAKING — crashes OpenCode on startup)

The SDK requires Zod schemas via the tool() helper:

// ✅ Correct
import { tool } from "@opencode-ai/plugin";
tool({ args: { query: tool.schema.string().describe("...") }, ... })

// ❌ Current code (crashes on load)
args: { query: { type: "string", description: "..." } }

Impact: OpenCode crashes during plugin initialization with a Zod validation error.

4. Missing Session Completion (sessions never clean up)

WORKAROUND: OpenCode does not emit a session.completed event. Added session.idle handler as the closest equivalent — it fires when a conversation turn finishes. This may fire multiple times per session, but the worker handles duplicate completion calls gracefully (idempotent).

5. Search Endpoint (wrong path)

Changed from /api/search/observations to /api/search — the worker's actual MCP-format endpoint. Added support for both MCP response format ({ content: [{ type: 'text', text }] }) and legacy format ({ items: [...] }).

Changes

src/integrations/opencode-plugin/index.ts

  • Replace manual type declarations with SDK imports (Plugin, PluginInput, Hooks)
  • Fix hook registration: nested objects → flat string keys
  • Fix event handler: (eventName, payload)({ event })
  • Fix tool definition: raw JSON schema → Zod via tool() helper
  • Add session.idle handler for session completion
  • Add chat.message hook for assistant response capture
  • Add truncate() helper to reduce duplication
  • Remove unused workerPost() (only fire-and-forget is needed)
  • Fix search endpoint and response format handling

scripts/build-hooks.js

  • Add @opencode-ai/plugin, @opencode-ai/plugin/tool, and zod to esbuild externals (provided by OpenCode runtime)

Workarounds & Notes

  1. session.idle as session completion — OpenCode SDK does not expose a session.completed event. session.idle fires when a conversation turn finishes, which is the closest available signal. Duplicate calls are safe (worker is idempotent).

  2. file.edited handler skipped — This event doesn't carry a sessionID in its properties, making it impossible to associate with a content session. File edits are already captured via the tool.execute.after hook when the agent uses file editing tools.

  3. message.updated performance note — Assistant message events (message.updated where role=assistant) can generate high-volume, low-signal observations. In heavy usage, these may account for ~85% of queued messages while producing near-zero useful observations. The chat.message hook captures the same content more efficiently. Consider disabling the message.updated handler if queue congestion is observed.

  4. claude_mem_search vs MCP server — The plugin's built-in search tool provides basic search capability for standalone usage. Users who additionally configure the claude-mem MCP server in their opencode.json get access to a richer toolset (search, timeline, observations, smart code search). Both approaches can coexist, but the MCP server provides a superior experience.

Test Results

✅ Plugin loads successfully
✅ session.created event fires → worker receives /api/sessions/init
✅ tool.execute.after hook fires → worker receives /api/sessions/observations  
✅ chat.message hook fires → worker receives assistant_response observation
✅ session.idle fires → worker receives /api/sessions/summarize + /api/sessions/complete
✅ Session lifecycle: CREATED → STORED → COMPLETED (confirmed in worker logs)
✅ Build: esbuild compiles cleanly (8.7KB output)
✅ 12/12 structural verification checks passed
✅ No personal information or API keys in the code

Tested with:

  • @opencode-ai/plugin@1.2.27
  • @opencode-ai/sdk@1.2.27
  • OpenCode v4
  • claude-mem worker on port 37777

- Fix hook API: use flat string keys ("tool.execute.after", "chat.message")
  instead of nested objects that silently fail in the current SDK
- Fix event handler: use SDK Event signature event({ event }) instead of
  string-based (eventName, payload) that never receives dispatched events
- Fix tool definition: use Zod schema via tool() helper instead of raw
  JSON schema that crashes OpenCode on startup
- Add session.idle handler for session completion (workaround: OpenCode
  has no session.completed event; session.idle is the closest equivalent)
- Add chat.message hook for richer assistant response capture
- Add @opencode-ai/plugin as external in esbuild config
- Use proper SDK types (Plugin, PluginInput, Hooks) instead of manual
  interface declarations
- Fix search endpoint to /api/search (MCP format) with legacy fallback

Tested against @opencode-ai/plugin@1.2.27 with full session lifecycle:
session.created → tool.execute.after → chat.message → session.idle → complete
@runzexia
Copy link
Author

Hey @thedotmack — this is the rewritten plugin code I mentioned in #1258 (issuecomment-4024344296).

Quick context on the approach:

The @opencode-ai/plugin SDK v1.2.x has different API conventions than what the original plugin used. The three breaking issues were:

  1. Hooks use flat string keys ("tool.execute.after") not nested objects (hooks.tool.execute.after) — the nested form is silently ignored
  2. Events use event({ event: Event }) signature not (eventName, payload) — the SDK dispatches Event objects with .type and .properties
  3. Tools require Zod schemas via tool() helper, not raw JSON schema objects — raw JSON crashes OpenCode on startup

All three issues cause the plugin to load without errors but capture zero data — which makes debugging tricky.

Two workarounds worth noting:

  • session.idle for session completion: OpenCode doesn't have session.completed. session.idle fires after each conversation turn, which is the closest signal. Duplicate calls are safe.
  • file.edited skipped: This event doesn't carry a sessionID, so we can't associate it with a content session. File edits are already captured via tool.execute.after.

Happy to iterate on this. The full test results are in the PR description.

@pratikbin
Copy link

+1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants