-
Notifications
You must be signed in to change notification settings - Fork 6.3k
feat(plugin)!: migrate plugin system to SDK v2 #7639
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Conversation
…v2 plugin subpath exports (@opencode-ai/plugin/v2)\n- Import types from @opencode-ai/sdk/v2 with flattened API params\n- Update permission hook to use PermissionRequest with reply format\n- Add v2/tool.ts importing FilePart from SDK v2
|
Thanks for your contribution! This PR doesn't have a linked issue. All PRs must reference an existing issue. Please:
See CONTRIBUTING.md for details. |
|
The following comment was made by an LLM, it may be inaccurate: No duplicate PRs found |
…e separate client instances for v1 and v2 SDKs to maintain backward compatibility.\n- Update plugin input handling to accommodate both versions.\n- Implement version detection for plugins to ensure correct initialization based on SDK version.
- Replace separate v1/v2 inputs with unified PluginInput - Create UnifiedClient with .v2 accessor for backward compatibility - Remove plugin version detection logic - Simplify plugin loading to use single unified input
- Remove packages/plugin/src/v2/index.ts and v2/tool.ts - No longer needed after unifying plugin API with single input
- Remove unified client proxy, use v2 SDK client directly - Update CodexAuthPlugin to use v2 auth.set() API format - Update plugin types to use v2 SDK types (OpencodeClient, PermissionRequest) - Add 'reject' status option to permission.ask hook - Remove v1 client imports and creation
|
Hey! Your PR title Please update it to start with one of:
Where See CONTRIBUTING.md for details. |
## Summary
- Migrates plugin system to use SDK v2 types and client exclusively
- Removes v1 SDK compatibility layer - this is now a **full breaking change**
- **Key context**: Many hooks already had type drift between v1 contract and v2 runtime, so this breaking change is less severe than it appears
## Why This Breaking Change Is Acceptable
### Hook Types Were Already Broken
Analysis revealed significant type drift between what the plugin package defined and what the OpenCode core actually passed at runtime:
| Hook | Plugin Expected | Core Passed | Impact |
|------|----------------|-------------|--------|
| `permission.ask` | `Permission` with `title` | `Permission.Info` with `message` | Field name mismatch - `input.title` returns `undefined` |
| `chat.params` | `Provider` | `Promise<Provider.Info>` | Promise wrapper not in type |
| `chat.message` | `UserMessage` | `MessageV2.Info` | Different type structure |
The core was already using `@ts-expect-error` workarounds to suppress these mismatches. Plugins accessing `input.title` in `permission.ask` were already getting `undefined` at runtime.
### This Change Fixes the Type Mismatch
Instead of maintaining broken v1 types, we:
1. Update the plugin contract to match the actual v2 runtime behavior
2. Provide clean, consistent types that match what OpenCode core actually passes
3. Remove the `UnifiedClient` complexity (no more `.v2` accessor needed)
## Changes
### Plugin Package (`packages/plugin`)
- Updated all imports from `@opencode-ai/sdk` → `@opencode-ai/sdk/v2`
- Changed hook types to use v2 SDK types (`PermissionRequest` instead of `Permission`, etc.)
- Simplified `PluginInput.client` to be v2 `OpencodeClient` only
- Added `ToolContext.metadata()` and `ToolResult` types
### Plugin Loader (`packages/opencode`)
- Removed v1 SDK client creation
- Removed unified client proxy (`.v2` accessor)
- Now creates and passes v2 SDK client only
## Breaking Changes
| What | Before | After |
|------|--------|-------|
| SDK client | `input.client` (v1) + `input.client.v2` (v2) | `input.client` (v2 only) |
| Permission type | `Permission` with `title` | `PermissionRequest` with `permission` |
| Permission reply | `status: ask/deny/allow` | `reply: once/always/reject` |
| SDK calls | Nested `{ path, body, query }` | Flattened parameters |
## Migration for Plugin Authors
1. **Change imports**: `@opencode-ai/plugin` (no `/v2`)
2. **Update permission hook**:
```typescript
// Before
(input: Permission, output: { status: "ask" | "deny" | "allow" }) => { ... }
// After
(input: PermissionRequest, output: { reply: "once" | "always" | "reject" }) => { ... }
```
3. **Update SDK calls** to use flattened parameters
## Why Not Maintain v1 Compatibility?
- The SDK client had **massive breaking changes** (every method call changed from nested to flat)
- Hook types already had **significant drift** (not just style changes, but actual field mismatches)
- Maintaining both v1 and v2 code paths added complexity
- Clean break forces plugin authors to update, resulting in better type safety
Closes anomalyco#7641
Patchwork-Source: plugin-v2
Patchwork-PR: anomalyco#7639
Patchwork-Squashed: 2026-01-12T21:07:39.017Z
- Migrates plugin system to use SDK v2 types and client exclusively
- Removes v1 SDK compatibility layer - this is now a **full breaking change**
- **Key context**: Many hooks already had type drift between v1 contract and v2 runtime, so this breaking change is less severe than it appears
Analysis revealed significant type drift between what the plugin package defined and what the OpenCode core actually passed at runtime:
| Hook | Plugin Expected | Core Passed | Impact |
|------|----------------|-------------|--------|
| `permission.ask` | `Permission` with `title` | `Permission.Info` with `message` | Field name mismatch - `input.title` returns `undefined` |
| `chat.params` | `Provider` | `Promise<Provider.Info>` | Promise wrapper not in type |
| `chat.message` | `UserMessage` | `MessageV2.Info` | Different type structure |
The core was already using `@ts-expect-error` workarounds to suppress these mismatches. Plugins accessing `input.title` in `permission.ask` were already getting `undefined` at runtime.
Instead of maintaining broken v1 types, we:
1. Update the plugin contract to match the actual v2 runtime behavior
2. Provide clean, consistent types that match what OpenCode core actually passes
3. Remove the `UnifiedClient` complexity (no more `.v2` accessor needed)
- Updated all imports from `@opencode-ai/sdk` → `@opencode-ai/sdk/v2`
- Changed hook types to use v2 SDK types (`PermissionRequest` instead of `Permission`, etc.)
- Simplified `PluginInput.client` to be v2 `OpencodeClient` only
- Added `ToolContext.metadata()` and `ToolResult` types
- Removed v1 SDK client creation
- Removed unified client proxy (`.v2` accessor)
- Now creates and passes v2 SDK client only
| What | Before | After |
|------|--------|-------|
| SDK client | `input.client` (v1) + `input.client.v2` (v2) | `input.client` (v2 only) |
| Permission type | `Permission` with `title` | `PermissionRequest` with `permission` |
| Permission reply | `status: ask/deny/allow` | `reply: once/always/reject` |
| SDK calls | Nested `{ path, body, query }` | Flattened parameters |
1. **Change imports**: `@opencode-ai/plugin` (no `/v2`)
2. **Update permission hook**:
```typescript
// Before
(input: Permission, output: { status: "ask" | "deny" | "allow" }) => { ... }
// After
(input: PermissionRequest, output: { reply: "once" | "always" | "reject" }) => { ... }
```
3. **Update SDK calls** to use flattened parameters
- The SDK client had **massive breaking changes** (every method call changed from nested to flat)
- Hook types already had **significant drift** (not just style changes, but actual field mismatches)
- Maintaining both v1 and v2 code paths added complexity
- Clean break forces plugin authors to update, resulting in better type safety
Closes anomalyco#7641
Patchwork-Source: plugin-v2
Patchwork-PR: anomalyco#7639
Patchwork-Squashed: 2026-01-12T21:07:39.017Z
- Migrates plugin system to use SDK v2 types and client exclusively
- Removes v1 SDK compatibility layer - this is now a **full breaking change**
- **Key context**: Many hooks already had type drift between v1 contract and v2 runtime, so this breaking change is less severe than it appears
Analysis revealed significant type drift between what the plugin package defined and what the OpenCode core actually passed at runtime:
| Hook | Plugin Expected | Core Passed | Impact |
|------|----------------|-------------|--------|
| `permission.ask` | `Permission` with `title` | `Permission.Info` with `message` | Field name mismatch - `input.title` returns `undefined` |
| `chat.params` | `Provider` | `Promise<Provider.Info>` | Promise wrapper not in type |
| `chat.message` | `UserMessage` | `MessageV2.Info` | Different type structure |
The core was already using `@ts-expect-error` workarounds to suppress these mismatches. Plugins accessing `input.title` in `permission.ask` were already getting `undefined` at runtime.
Instead of maintaining broken v1 types, we:
1. Update the plugin contract to match the actual v2 runtime behavior
2. Provide clean, consistent types that match what OpenCode core actually passes
3. Remove the `UnifiedClient` complexity (no more `.v2` accessor needed)
- Updated all imports from `@opencode-ai/sdk` → `@opencode-ai/sdk/v2`
- Changed hook types to use v2 SDK types (`PermissionRequest` instead of `Permission`, etc.)
- Simplified `PluginInput.client` to be v2 `OpencodeClient` only
- Added `ToolContext.metadata()` and `ToolResult` types
- Removed v1 SDK client creation
- Removed unified client proxy (`.v2` accessor)
- Now creates and passes v2 SDK client only
| What | Before | After |
|------|--------|-------|
| SDK client | `input.client` (v1) + `input.client.v2` (v2) | `input.client` (v2 only) |
| Permission type | `Permission` with `title` | `PermissionRequest` with `permission` |
| Permission reply | `status: ask/deny/allow` | `reply: once/always/reject` |
| SDK calls | Nested `{ path, body, query }` | Flattened parameters |
1. **Change imports**: `@opencode-ai/plugin` (no `/v2`)
2. **Update permission hook**:
```typescript
// Before
(input: Permission, output: { status: "ask" | "deny" | "allow" }) => { ... }
// After
(input: PermissionRequest, output: { reply: "once" | "always" | "reject" }) => { ... }
```
3. **Update SDK calls** to use flattened parameters
- The SDK client had **massive breaking changes** (every method call changed from nested to flat)
- Hook types already had **significant drift** (not just style changes, but actual field mismatches)
- Maintaining both v1 and v2 code paths added complexity
- Clean break forces plugin authors to update, resulting in better type safety
Closes anomalyco#7641
Patchwork-Source: plugin-v2
Patchwork-PR: anomalyco#7639
Patchwork-Squashed: 2026-01-12T21:07:39.017Z
- Migrates plugin system to use SDK v2 types and client exclusively
- Removes v1 SDK compatibility layer - this is now a **full breaking change**
- **Key context**: Many hooks already had type drift between v1 contract and v2 runtime, so this breaking change is less severe than it appears
Analysis revealed significant type drift between what the plugin package defined and what the OpenCode core actually passed at runtime:
| Hook | Plugin Expected | Core Passed | Impact |
|------|----------------|-------------|--------|
| `permission.ask` | `Permission` with `title` | `Permission.Info` with `message` | Field name mismatch - `input.title` returns `undefined` |
| `chat.params` | `Provider` | `Promise<Provider.Info>` | Promise wrapper not in type |
| `chat.message` | `UserMessage` | `MessageV2.Info` | Different type structure |
The core was already using `@ts-expect-error` workarounds to suppress these mismatches. Plugins accessing `input.title` in `permission.ask` were already getting `undefined` at runtime.
Instead of maintaining broken v1 types, we:
1. Update the plugin contract to match the actual v2 runtime behavior
2. Provide clean, consistent types that match what OpenCode core actually passes
3. Remove the `UnifiedClient` complexity (no more `.v2` accessor needed)
- Updated all imports from `@opencode-ai/sdk` → `@opencode-ai/sdk/v2`
- Changed hook types to use v2 SDK types (`PermissionRequest` instead of `Permission`, etc.)
- Simplified `PluginInput.client` to be v2 `OpencodeClient` only
- Added `ToolContext.metadata()` and `ToolResult` types
- Removed v1 SDK client creation
- Removed unified client proxy (`.v2` accessor)
- Now creates and passes v2 SDK client only
| What | Before | After |
|------|--------|-------|
| SDK client | `input.client` (v1) + `input.client.v2` (v2) | `input.client` (v2 only) |
| Permission type | `Permission` with `title` | `PermissionRequest` with `permission` |
| Permission reply | `status: ask/deny/allow` | `reply: once/always/reject` |
| SDK calls | Nested `{ path, body, query }` | Flattened parameters |
1. **Change imports**: `@opencode-ai/plugin` (no `/v2`)
2. **Update permission hook**:
```typescript
// Before
(input: Permission, output: { status: "ask" | "deny" | "allow" }) => { ... }
// After
(input: PermissionRequest, output: { reply: "once" | "always" | "reject" }) => { ... }
```
3. **Update SDK calls** to use flattened parameters
- The SDK client had **massive breaking changes** (every method call changed from nested to flat)
- Hook types already had **significant drift** (not just style changes, but actual field mismatches)
- Maintaining both v1 and v2 code paths added complexity
- Clean break forces plugin authors to update, resulting in better type safety
Closes anomalyco#7641
Patchwork-Source: plugin-v2
Patchwork-PR: anomalyco#7639
Patchwork-Squashed: 2026-01-12T21:07:39.017Z
|
@aryasaatvik I made another PR that is very similar to this one, but takes a different approach that maintains backwards compatibility. I didn't even see your PR at first, as I just happened to work myself into the SDK v2 upgrade path. Not trying to step on any toes, but the idea of upgrading is super valuable and I'm down to take any path that gets us there. |
Summary
Why This Breaking Change Is Acceptable
Hook Types Were Already Broken
Analysis revealed significant type drift between what the plugin package defined and what the OpenCode core actually passed at runtime:
permission.askPermissionwithtitlePermission.Infowithmessageinput.titlereturnsundefinedchat.paramsProviderPromise<Provider.Info>chat.messageUserMessageMessageV2.InfoThe core was already using
@ts-expect-errorworkarounds to suppress these mismatches. Plugins accessinginput.titleinpermission.askwere already gettingundefinedat runtime.This Change Fixes the Type Mismatch
Instead of maintaining broken v1 types, we:
UnifiedClientcomplexity (no more.v2accessor needed)Changes
Plugin Package (
packages/plugin)@opencode-ai/sdk→@opencode-ai/sdk/v2PermissionRequestinstead ofPermission, etc.)PluginInput.clientto be v2OpencodeClientonlyToolContext.metadata()andToolResulttypesPlugin Loader (
packages/opencode).v2accessor)Breaking Changes
input.client(v1) +input.client.v2(v2)input.client(v2 only)PermissionwithtitlePermissionRequestwithpermissionstatus: ask/deny/allowreply: once/always/reject{ path, body, query }Migration for Plugin Authors
@opencode-ai/plugin(no/v2)Why Not Maintain v1 Compatibility?
Closes #7641