From ba38fe1bd37cc028e129ba9cc905bc7aca299bcd Mon Sep 17 00:00:00 2001 From: Saatvik Arya Date: Sat, 10 Jan 2026 18:39:13 +0530 Subject: [PATCH 1/5] feat(plugin): add v2 subpath exports with flattened SDK API\n\n- Add 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 --- packages/plugin/package.json | 4 +- packages/plugin/src/v2/index.ts | 228 ++++++++++++++++++++++++++++++++ packages/plugin/src/v2/tool.ts | 38 ++++++ 3 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 packages/plugin/src/v2/index.ts create mode 100644 packages/plugin/src/v2/tool.ts diff --git a/packages/plugin/package.json b/packages/plugin/package.json index ffd083cbea6..ce2973d04cd 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -10,7 +10,9 @@ }, "exports": { ".": "./src/index.ts", - "./tool": "./src/tool.ts" + "./tool": "./src/tool.ts", + "./v2": "./src/v2/index.ts", + "./v2/tool": "./src/v2/tool.ts" }, "files": [ "dist" diff --git a/packages/plugin/src/v2/index.ts b/packages/plugin/src/v2/index.ts new file mode 100644 index 00000000000..cbfb4dc62a2 --- /dev/null +++ b/packages/plugin/src/v2/index.ts @@ -0,0 +1,228 @@ +import type { + Event, + createOpencodeClient, + Project, + Model, + Provider, + PermissionRequest, + UserMessage, + Message, + Part, + Auth, + Config, +} from "@opencode-ai/sdk/v2" + +import type { BunShell } from "../shell" +import { type ToolDefinition } from "./tool" + +export * from "./tool" + +export type ProviderContext = { + source: "env" | "config" | "custom" | "api" + info: Provider + options: Record +} + +export type PluginInput = { + client: ReturnType + project: Project + directory: string + worktree: string + serverUrl: URL + $: BunShell +} + +export type Plugin = (input: PluginInput) => Promise + +export type AuthHook = { + provider: string + loader?: (auth: () => Promise, provider: Provider) => Promise> + methods: ( + | { + type: "oauth" + label: string + prompts?: Array< + | { + type: "text" + key: string + message: string + placeholder?: string + validate?: (value: string) => string | undefined + condition?: (inputs: Record) => boolean + } + | { + type: "select" + key: string + message: string + options: Array<{ + label: string + value: string + hint?: string + }> + condition?: (inputs: Record) => boolean + } + > + authorize(inputs?: Record): Promise + } + | { + type: "api" + label: string + prompts?: Array< + | { + type: "text" + key: string + message: string + placeholder?: string + validate?: (value: string) => string | undefined + condition?: (inputs: Record) => boolean + } + | { + type: "select" + key: string + message: string + options: Array<{ + label: string + value: string + hint?: string + }> + condition?: (inputs: Record) => boolean + } + > + authorize?(inputs?: Record): Promise< + | { + type: "success" + key: string + provider?: string + } + | { + type: "failed" + } + > + } + )[] +} + +export type AuthOuathResult = { url: string; instructions: string } & ( + | { + method: "auto" + callback(): Promise< + | ({ + type: "success" + provider?: string + } & ( + | { + refresh: string + access: string + expires: number + } + | { key: string } + )) + | { + type: "failed" + } + > + } + | { + method: "code" + callback(code: string): Promise< + | ({ + type: "success" + provider?: string + } & ( + | { + refresh: string + access: string + expires: number + } + | { key: string } + )) + | { + type: "failed" + } + > + } +) + +export interface Hooks { + event?: (input: { event: Event }) => Promise + config?: (input: Config) => Promise + tool?: { + [key: string]: ToolDefinition + } + auth?: AuthHook + /** + * Called when a new message is received + */ + "chat.message"?: ( + input: { + sessionID: string + agent?: string + model?: { providerID: string; modelID: string } + messageID?: string + variant?: string + }, + output: { message: UserMessage; parts: Part[] }, + ) => Promise + /** + * Modify parameters sent to LLM + */ + "chat.params"?: ( + input: { sessionID: string; agent: string; model: Model; provider: ProviderContext; message: UserMessage }, + output: { temperature: number; topP: number; topK: number; options: Record }, + ) => Promise + /** + * Called when a permission is requested. Uses v2 permission system. + * + * @param input - The permission request from the v2 SDK + * @param output - Set reply to control the permission decision: + * - "once": Allow this specific request + * - "always": Allow and remember for future requests matching the pattern + * - "reject": Deny the permission request + */ + "permission.ask"?: ( + input: PermissionRequest, + output: { reply: "once" | "always" | "reject" } + ) => Promise + "tool.execute.before"?: ( + input: { tool: string; sessionID: string; callID: string }, + output: { args: any }, + ) => Promise + "tool.execute.after"?: ( + input: { tool: string; sessionID: string; callID: string }, + output: { + title: string + output: string + metadata: any + }, + ) => Promise + "experimental.chat.messages.transform"?: ( + input: {}, + output: { + messages: { + info: Message + parts: Part[] + }[] + }, + ) => Promise + "experimental.chat.system.transform"?: ( + input: {}, + output: { + system: string[] + }, + ) => Promise + /** + * Called before session compaction starts. Allows plugins to customize + * the compaction prompt. + * + * - `context`: Additional context strings appended to the default prompt + * - `prompt`: If set, replaces the default compaction prompt entirely + */ + "experimental.session.compacting"?: ( + input: { sessionID: string }, + output: { context: string[]; prompt?: string }, + ) => Promise + "experimental.text.complete"?: ( + input: { sessionID: string; messageID: string; partID: string }, + output: { text: string }, + ) => Promise +} diff --git a/packages/plugin/src/v2/tool.ts b/packages/plugin/src/v2/tool.ts new file mode 100644 index 00000000000..1c1c4c92924 --- /dev/null +++ b/packages/plugin/src/v2/tool.ts @@ -0,0 +1,38 @@ +import { z } from "zod" +import type { FilePart } from "@opencode-ai/sdk/v2" + +export type ToolContext = { + sessionID: string + messageID: string + agent: string + abort: AbortSignal + metadata(input: { title?: string; metadata?: Record }): void +} + +/** + * Structured result for plugin tools. + * + * Return this instead of a plain string to provide rich metadata + * that integrates with streaming updates. + */ +export interface ToolResult { + /** Title displayed in the UI */ + title: string + /** Arbitrary metadata passed to tool.execute.after hooks */ + metadata: Record + /** The text output returned to the model */ + output: string + /** Optional file attachments to include with the result */ + attachments?: FilePart[] +} + +export function tool(input: { + description: string + args: Args + execute(args: z.infer>, context: ToolContext): Promise +}) { + return input +} +tool.schema = z + +export type ToolDefinition = ReturnType From edffb363654df7fb900e5165dd22ddc7b4a71fca Mon Sep 17 00:00:00 2001 From: Saatvik Arya Date: Sun, 11 Jan 2026 09:30:37 +0530 Subject: [PATCH 2/5] feat(plugin): enhance support for v1 and v2 SDK clients\n\n- Introduce 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. --- packages/opencode/src/plugin/index.ts | 52 ++++++++++++++++++++------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index 4912b8f74ba..97cc6e32255 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -1,14 +1,18 @@ -import type { Hooks, PluginInput, Plugin as PluginInstance } from "@opencode-ai/plugin" +import type { Hooks as HooksV1, PluginInput as PluginInputV1, Plugin as PluginInstance } from "@opencode-ai/plugin" +import type { Hooks as HooksV2, PluginInput as PluginInputV2 } from "@opencode-ai/plugin/v2" import { Config } from "../config/config" import { Bus } from "../bus" import { Log } from "../util/log" -import { createOpencodeClient } from "@opencode-ai/sdk" +import { createOpencodeClient as createOpencodeClientV1 } from "@opencode-ai/sdk" +import { createOpencodeClient as createOpencodeClientV2 } from "@opencode-ai/sdk/v2" import { Server } from "../server/server" import { BunProc } from "../bun" import { Instance } from "../project/instance" import { Flag } from "../flag/flag" import { CodexAuthPlugin } from "./codex" +type Hooks = HooksV1 | HooksV2 + export namespace Plugin { const log = Log.create({ service: "plugin" }) @@ -18,15 +22,23 @@ export namespace Plugin { const INTERNAL_PLUGINS: PluginInstance[] = [CodexAuthPlugin] const state = Instance.state(async () => { - const client = createOpencodeClient({ + // Create both v1 and v2 clients for backward compatibility + const clientV1 = createOpencodeClientV1({ + baseUrl: "http://localhost:4096", + // @ts-ignore - fetch type incompatibility + fetch: async (...args) => Server.App().fetch(...args), + }) + const clientV2 = createOpencodeClientV2({ baseUrl: "http://localhost:4096", // @ts-ignore - fetch type incompatibility fetch: async (...args) => Server.App().fetch(...args), }) + const config = await Config.get() const hooks: Hooks[] = [] - const input: PluginInput = { - client, + + // Base input shared between v1 and v2 + const baseInput = { project: Instance.project, worktree: Instance.worktree, directory: Instance.directory, @@ -34,11 +46,14 @@ export namespace Plugin { $: Bun.$, } - // Load internal plugins first + const inputV1: PluginInputV1 = { client: clientV1, ...baseInput } + const inputV2: PluginInputV2 = { client: clientV2, ...baseInput } + + // Load internal plugins first (use v2 client) if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) { for (const plugin of INTERNAL_PLUGINS) { log.info("loading internal plugin", { name: plugin.name }) - const init = await plugin(input) + const init = await plugin(inputV1) hooks.push(init) } } @@ -54,30 +69,41 @@ export namespace Plugin { if (!plugin.startsWith("file://")) { const lastAtIndex = plugin.lastIndexOf("@") const pkg = lastAtIndex > 0 ? plugin.substring(0, lastAtIndex) : plugin - const version = lastAtIndex > 0 ? plugin.substring(lastAtIndex + 1) : "latest" + const pkgVersion = lastAtIndex > 0 ? plugin.substring(lastAtIndex + 1) : "latest" const builtin = BUILTIN.some((x) => x.startsWith(pkg + "@")) - plugin = await BunProc.install(pkg, version).catch((err) => { + plugin = await BunProc.install(pkg, pkgVersion).catch((err) => { if (builtin) return "" throw err }) if (!plugin) continue } const mod = await import(plugin) + + // Check if plugin exports version = 2 for v2 SDK + const isV2Plugin = mod.version === 2 + + log.info("detected plugin version", { path: plugin, version: isV2Plugin ? 2 : 1 }) + // Prevent duplicate initialization when plugins export the same function // as both a named export and default export (e.g., `export const X` and `export default X`). // Object.entries(mod) would return both entries pointing to the same function reference. - const seen = new Set() - for (const [_name, fn] of Object.entries(mod)) { + const seen = new Set() + for (const [_name, fn] of Object.entries(mod)) { + if (typeof fn !== "function") continue if (seen.has(fn)) continue seen.add(fn) - const init = await fn(input) + // Call with appropriate input based on plugin version + const init = isV2Plugin + ? await (fn as (input: PluginInputV2) => Promise)(inputV2) + : await (fn as (input: PluginInputV1) => Promise)(inputV1) hooks.push(init) } } return { hooks, - input, + inputV1, + inputV2, } }) From f9adbf26a49e485b193682d807e81824ba8560ba Mon Sep 17 00:00:00 2001 From: Saatvik Arya Date: Sun, 11 Jan 2026 10:12:12 +0530 Subject: [PATCH 3/5] refactor(plugin): unify plugin API with single input and unified client - 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 --- packages/opencode/src/plugin/index.ts | 57 +++++++++++++++------------ 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index 97cc6e32255..87b6a1ac5c8 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -1,28 +1,41 @@ -import type { Hooks as HooksV1, PluginInput as PluginInputV1, Plugin as PluginInstance } from "@opencode-ai/plugin" -import type { Hooks as HooksV2, PluginInput as PluginInputV2 } from "@opencode-ai/plugin/v2" +import type { PluginInput, Hooks } from "@opencode-ai/plugin" +import type { Plugin as PluginFn } from "@opencode-ai/plugin" import { Config } from "../config/config" import { Bus } from "../bus" import { Log } from "../util/log" import { createOpencodeClient as createOpencodeClientV1 } from "@opencode-ai/sdk" import { createOpencodeClient as createOpencodeClientV2 } from "@opencode-ai/sdk/v2" +import type { OpencodeClient as OpencodeClientV2 } from "@opencode-ai/sdk/v2" import { Server } from "../server/server" import { BunProc } from "../bun" import { Instance } from "../project/instance" import { Flag } from "../flag/flag" import { CodexAuthPlugin } from "./codex" -type Hooks = HooksV1 | HooksV2 - export namespace Plugin { const log = Log.create({ service: "plugin" }) const BUILTIN = ["opencode-copilot-auth@0.0.11", "opencode-anthropic-auth@0.0.8"] // Built-in plugins that are directly imported (not installed from npm) - const INTERNAL_PLUGINS: PluginInstance[] = [CodexAuthPlugin] + const INTERNAL_PLUGINS = [CodexAuthPlugin] + + type UnifiedClient = PluginInput["client"] & { v2: OpencodeClientV2 } + + function createUnifiedClient(v1: PluginInput["client"], v2: OpencodeClientV2): UnifiedClient { + return new Proxy(v1, { + get(target, prop) { + if (prop === "v2") { + return v2 + } + // Forward all other properties to v1 client + return (target as unknown as Record)[prop] + }, + }) as UnifiedClient + } const state = Instance.state(async () => { - // Create both v1 and v2 clients for backward compatibility + // Create both v1 and v2 clients const clientV1 = createOpencodeClientV1({ baseUrl: "http://localhost:4096", // @ts-ignore - fetch type incompatibility @@ -37,7 +50,10 @@ export namespace Plugin { const config = await Config.get() const hooks: Hooks[] = [] - // Base input shared between v1 and v2 + // Create unified client with .v2 accessor + const unifiedClient = createUnifiedClient(clientV1, clientV2) + + // Base input shared between v1 and v2 plugins const baseInput = { project: Instance.project, worktree: Instance.worktree, @@ -46,14 +62,13 @@ export namespace Plugin { $: Bun.$, } - const inputV1: PluginInputV1 = { client: clientV1, ...baseInput } - const inputV2: PluginInputV2 = { client: clientV2, ...baseInput } + const input: PluginInput = { client: unifiedClient, ...baseInput } - // Load internal plugins first (use v2 client) + // Load internal plugins first if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) { for (const plugin of INTERNAL_PLUGINS) { log.info("loading internal plugin", { name: plugin.name }) - const init = await plugin(inputV1) + const init = await plugin(input) hooks.push(init) } } @@ -79,31 +94,23 @@ export namespace Plugin { } const mod = await import(plugin) - // Check if plugin exports version = 2 for v2 SDK - const isV2Plugin = mod.version === 2 - - log.info("detected plugin version", { path: plugin, version: isV2Plugin ? 2 : 1 }) - // Prevent duplicate initialization when plugins export the same function // as both a named export and default export (e.g., `export const X` and `export default X`). // Object.entries(mod) would return both entries pointing to the same function reference. - const seen = new Set() + const seen = new Set() for (const [_name, fn] of Object.entries(mod)) { if (typeof fn !== "function") continue - if (seen.has(fn)) continue - seen.add(fn) - // Call with appropriate input based on plugin version - const init = isV2Plugin - ? await (fn as (input: PluginInputV2) => Promise)(inputV2) - : await (fn as (input: PluginInputV1) => Promise)(inputV1) + const pluginFn = fn as PluginFn + if (seen.has(pluginFn)) continue + seen.add(pluginFn) + const init = await pluginFn(input) hooks.push(init) } } return { hooks, - inputV1, - inputV2, + input, } }) From 166651197e40b21a031474be3ef139136ab7b39d Mon Sep 17 00:00:00 2001 From: Saatvik Arya Date: Sun, 11 Jan 2026 10:16:43 +0530 Subject: [PATCH 4/5] refactor(plugin): remove v2 subpath exports - Remove packages/plugin/src/v2/index.ts and v2/tool.ts - No longer needed after unifying plugin API with single input --- packages/plugin/package.json | 4 +- packages/plugin/src/v2/index.ts | 228 -------------------------------- packages/plugin/src/v2/tool.ts | 38 ------ 3 files changed, 1 insertion(+), 269 deletions(-) delete mode 100644 packages/plugin/src/v2/index.ts delete mode 100644 packages/plugin/src/v2/tool.ts diff --git a/packages/plugin/package.json b/packages/plugin/package.json index ce2973d04cd..ffd083cbea6 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -10,9 +10,7 @@ }, "exports": { ".": "./src/index.ts", - "./tool": "./src/tool.ts", - "./v2": "./src/v2/index.ts", - "./v2/tool": "./src/v2/tool.ts" + "./tool": "./src/tool.ts" }, "files": [ "dist" diff --git a/packages/plugin/src/v2/index.ts b/packages/plugin/src/v2/index.ts deleted file mode 100644 index cbfb4dc62a2..00000000000 --- a/packages/plugin/src/v2/index.ts +++ /dev/null @@ -1,228 +0,0 @@ -import type { - Event, - createOpencodeClient, - Project, - Model, - Provider, - PermissionRequest, - UserMessage, - Message, - Part, - Auth, - Config, -} from "@opencode-ai/sdk/v2" - -import type { BunShell } from "../shell" -import { type ToolDefinition } from "./tool" - -export * from "./tool" - -export type ProviderContext = { - source: "env" | "config" | "custom" | "api" - info: Provider - options: Record -} - -export type PluginInput = { - client: ReturnType - project: Project - directory: string - worktree: string - serverUrl: URL - $: BunShell -} - -export type Plugin = (input: PluginInput) => Promise - -export type AuthHook = { - provider: string - loader?: (auth: () => Promise, provider: Provider) => Promise> - methods: ( - | { - type: "oauth" - label: string - prompts?: Array< - | { - type: "text" - key: string - message: string - placeholder?: string - validate?: (value: string) => string | undefined - condition?: (inputs: Record) => boolean - } - | { - type: "select" - key: string - message: string - options: Array<{ - label: string - value: string - hint?: string - }> - condition?: (inputs: Record) => boolean - } - > - authorize(inputs?: Record): Promise - } - | { - type: "api" - label: string - prompts?: Array< - | { - type: "text" - key: string - message: string - placeholder?: string - validate?: (value: string) => string | undefined - condition?: (inputs: Record) => boolean - } - | { - type: "select" - key: string - message: string - options: Array<{ - label: string - value: string - hint?: string - }> - condition?: (inputs: Record) => boolean - } - > - authorize?(inputs?: Record): Promise< - | { - type: "success" - key: string - provider?: string - } - | { - type: "failed" - } - > - } - )[] -} - -export type AuthOuathResult = { url: string; instructions: string } & ( - | { - method: "auto" - callback(): Promise< - | ({ - type: "success" - provider?: string - } & ( - | { - refresh: string - access: string - expires: number - } - | { key: string } - )) - | { - type: "failed" - } - > - } - | { - method: "code" - callback(code: string): Promise< - | ({ - type: "success" - provider?: string - } & ( - | { - refresh: string - access: string - expires: number - } - | { key: string } - )) - | { - type: "failed" - } - > - } -) - -export interface Hooks { - event?: (input: { event: Event }) => Promise - config?: (input: Config) => Promise - tool?: { - [key: string]: ToolDefinition - } - auth?: AuthHook - /** - * Called when a new message is received - */ - "chat.message"?: ( - input: { - sessionID: string - agent?: string - model?: { providerID: string; modelID: string } - messageID?: string - variant?: string - }, - output: { message: UserMessage; parts: Part[] }, - ) => Promise - /** - * Modify parameters sent to LLM - */ - "chat.params"?: ( - input: { sessionID: string; agent: string; model: Model; provider: ProviderContext; message: UserMessage }, - output: { temperature: number; topP: number; topK: number; options: Record }, - ) => Promise - /** - * Called when a permission is requested. Uses v2 permission system. - * - * @param input - The permission request from the v2 SDK - * @param output - Set reply to control the permission decision: - * - "once": Allow this specific request - * - "always": Allow and remember for future requests matching the pattern - * - "reject": Deny the permission request - */ - "permission.ask"?: ( - input: PermissionRequest, - output: { reply: "once" | "always" | "reject" } - ) => Promise - "tool.execute.before"?: ( - input: { tool: string; sessionID: string; callID: string }, - output: { args: any }, - ) => Promise - "tool.execute.after"?: ( - input: { tool: string; sessionID: string; callID: string }, - output: { - title: string - output: string - metadata: any - }, - ) => Promise - "experimental.chat.messages.transform"?: ( - input: {}, - output: { - messages: { - info: Message - parts: Part[] - }[] - }, - ) => Promise - "experimental.chat.system.transform"?: ( - input: {}, - output: { - system: string[] - }, - ) => Promise - /** - * Called before session compaction starts. Allows plugins to customize - * the compaction prompt. - * - * - `context`: Additional context strings appended to the default prompt - * - `prompt`: If set, replaces the default compaction prompt entirely - */ - "experimental.session.compacting"?: ( - input: { sessionID: string }, - output: { context: string[]; prompt?: string }, - ) => Promise - "experimental.text.complete"?: ( - input: { sessionID: string; messageID: string; partID: string }, - output: { text: string }, - ) => Promise -} diff --git a/packages/plugin/src/v2/tool.ts b/packages/plugin/src/v2/tool.ts deleted file mode 100644 index 1c1c4c92924..00000000000 --- a/packages/plugin/src/v2/tool.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { z } from "zod" -import type { FilePart } from "@opencode-ai/sdk/v2" - -export type ToolContext = { - sessionID: string - messageID: string - agent: string - abort: AbortSignal - metadata(input: { title?: string; metadata?: Record }): void -} - -/** - * Structured result for plugin tools. - * - * Return this instead of a plain string to provide rich metadata - * that integrates with streaming updates. - */ -export interface ToolResult { - /** Title displayed in the UI */ - title: string - /** Arbitrary metadata passed to tool.execute.after hooks */ - metadata: Record - /** The text output returned to the model */ - output: string - /** Optional file attachments to include with the result */ - attachments?: FilePart[] -} - -export function tool(input: { - description: string - args: Args - execute(args: z.infer>, context: ToolContext): Promise -}) { - return input -} -tool.schema = z - -export type ToolDefinition = ReturnType From 7ffb962b81f56bc431ce1252740bb7370723c6ff Mon Sep 17 00:00:00 2001 From: Saatvik Arya Date: Sun, 11 Jan 2026 11:22:31 +0530 Subject: [PATCH 5/5] refactor(plugin): migrate to v2 SDK exclusively - 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 --- packages/opencode/src/plugin/codex.ts | 8 +++--- packages/opencode/src/plugin/index.ts | 36 +++------------------------ packages/plugin/src/index.ts | 10 ++++---- 3 files changed, 14 insertions(+), 40 deletions(-) diff --git a/packages/opencode/src/plugin/codex.ts b/packages/opencode/src/plugin/codex.ts index d0f025b0614..5c9d73107ce 100644 --- a/packages/opencode/src/plugin/codex.ts +++ b/packages/opencode/src/plugin/codex.ts @@ -385,8 +385,8 @@ export async function CodexAuthPlugin(input: PluginInput): Promise { log.info("refreshing codex access token") const tokens = await refreshAccessToken(currentAuth.refresh) await input.client.auth.set({ - path: { id: "codex" }, - body: { + providerID: "codex", + auth: { type: "oauth", refresh: tokens.refresh_token, access: tokens.access_token, @@ -400,7 +400,9 @@ export async function CodexAuthPlugin(input: PluginInput): Promise { const headers = new Headers() if (init?.headers) { if (init.headers instanceof Headers) { - init.headers.forEach((value, key) => headers.set(key, value)) + init.headers.forEach((value, key) => { + headers.set(key, value) + }) } else if (Array.isArray(init.headers)) { for (const [key, value] of init.headers) { if (value !== undefined) headers.set(key, String(value)) diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index 87b6a1ac5c8..42fa1936f51 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -3,9 +3,7 @@ import type { Plugin as PluginFn } from "@opencode-ai/plugin" import { Config } from "../config/config" import { Bus } from "../bus" import { Log } from "../util/log" -import { createOpencodeClient as createOpencodeClientV1 } from "@opencode-ai/sdk" -import { createOpencodeClient as createOpencodeClientV2 } from "@opencode-ai/sdk/v2" -import type { OpencodeClient as OpencodeClientV2 } from "@opencode-ai/sdk/v2" +import { createOpencodeClient } from "@opencode-ai/sdk/v2" import { Server } from "../server/server" import { BunProc } from "../bun" import { Instance } from "../project/instance" @@ -20,28 +18,8 @@ export namespace Plugin { // Built-in plugins that are directly imported (not installed from npm) const INTERNAL_PLUGINS = [CodexAuthPlugin] - type UnifiedClient = PluginInput["client"] & { v2: OpencodeClientV2 } - - function createUnifiedClient(v1: PluginInput["client"], v2: OpencodeClientV2): UnifiedClient { - return new Proxy(v1, { - get(target, prop) { - if (prop === "v2") { - return v2 - } - // Forward all other properties to v1 client - return (target as unknown as Record)[prop] - }, - }) as UnifiedClient - } - const state = Instance.state(async () => { - // Create both v1 and v2 clients - const clientV1 = createOpencodeClientV1({ - baseUrl: "http://localhost:4096", - // @ts-ignore - fetch type incompatibility - fetch: async (...args) => Server.App().fetch(...args), - }) - const clientV2 = createOpencodeClientV2({ + const client = createOpencodeClient({ baseUrl: "http://localhost:4096", // @ts-ignore - fetch type incompatibility fetch: async (...args) => Server.App().fetch(...args), @@ -50,11 +28,8 @@ export namespace Plugin { const config = await Config.get() const hooks: Hooks[] = [] - // Create unified client with .v2 accessor - const unifiedClient = createUnifiedClient(clientV1, clientV2) - - // Base input shared between v1 and v2 plugins - const baseInput = { + const input: PluginInput = { + client, project: Instance.project, worktree: Instance.worktree, directory: Instance.directory, @@ -62,8 +37,6 @@ export namespace Plugin { $: Bun.$, } - const input: PluginInput = { client: unifiedClient, ...baseInput } - // Load internal plugins first if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) { for (const plugin of INTERNAL_PLUGINS) { @@ -139,7 +112,6 @@ export namespace Plugin { const hooks = await state().then((x) => x.hooks) const config = await Config.get() for (const hook of hooks) { - // @ts-expect-error this is because we haven't moved plugin to sdk v2 await hook.config?.(config) } Bus.subscribeAll(async (input) => { diff --git a/packages/plugin/src/index.ts b/packages/plugin/src/index.ts index bf9b6e8c2d8..6fc37c76c64 100644 --- a/packages/plugin/src/index.ts +++ b/packages/plugin/src/index.ts @@ -1,16 +1,16 @@ import type { Event, - createOpencodeClient, Project, Model, Provider, - Permission, + PermissionRequest, UserMessage, Message, Part, Auth, Config, -} from "@opencode-ai/sdk" + OpencodeClient, +} from "@opencode-ai/sdk/v2" import type { BunShell } from "./shell" import { type ToolDefinition } from "./tool" @@ -24,7 +24,7 @@ export type ProviderContext = { } export type PluginInput = { - client: ReturnType + client: OpencodeClient project: Project directory: string worktree: string @@ -170,7 +170,7 @@ export interface Hooks { input: { sessionID: string; agent: string; model: Model; provider: ProviderContext; message: UserMessage }, output: { temperature: number; topP: number; topK: number; options: Record }, ) => Promise - "permission.ask"?: (input: Permission, output: { status: "ask" | "deny" | "allow" }) => Promise + "permission.ask"?: (input: PermissionRequest, output: { status: "ask" | "deny" | "allow" | "reject" }) => Promise "tool.execute.before"?: ( input: { tool: string; sessionID: string; callID: string }, output: { args: any },