Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,197 changes: 1,197 additions & 0 deletions docs/tool-bridge-design.md

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions packages/tool-bridge-opencode/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "@opencode-ai/tool-bridge-opencode",
"version": "1.0.0",
"type": "module",
"private": true,
"exports": {
"./*": "./src/*.ts"
},
"dependencies": {
"@opencode-ai/tool-bridge": "workspace:*",
"@opencode-ai/tool-bridge-protocol": "workspace:*",
"opencode": "workspace:*",
"zod": "catalog:",
"zod-to-json-schema": "3.24.5"
},
"devDependencies": {
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:",
"typescript": "catalog:"
}
}
173 changes: 173 additions & 0 deletions packages/tool-bridge-opencode/src/adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import { ToolBridge, type ToolContext, type ToolResult } from "@opencode-ai/tool-bridge/index"
import { zodToJsonSchema } from "zod-to-json-schema"
import type { Tool } from "opencode/tool/tool"
import { BashTool } from "opencode/tool/bash"
import { EditTool } from "opencode/tool/edit"
import { GlobTool } from "opencode/tool/glob"
import { GrepTool } from "opencode/tool/grep"
import { ListTool } from "opencode/tool/ls"
import { ReadTool } from "opencode/tool/read"
import { WriteTool } from "opencode/tool/write"
import { WebFetchTool } from "opencode/tool/webfetch"
import type z from "zod"

/**
* Configuration for the OpenCode tool adapter
*/
export interface OpencodeAdapterConfig {
/**
* Working directory for file operations
*/
workingDirectory: string

/**
* Session ID for tool context
*/
sessionId?: string

/**
* Agent name for tool context
*/
agent?: string

/**
* Additional tools to register (beyond the default set)
*/
additionalTools?: Tool.Info[]

/**
* Tools to exclude from registration
*/
excludeTools?: string[]
}

/**
* Default tools to expose via the bridge
*/
const DEFAULT_TOOLS: Tool.Info[] = [
BashTool,
EditTool,
GlobTool,
GrepTool,
ListTool,
ReadTool,
WriteTool,
WebFetchTool,
]

/**
* Convert a zod schema to JSON schema
*/
function schemaToJson(schema: z.ZodType): Record<string, unknown> {
try {
return zodToJsonSchema(schema) as Record<string, unknown>
} catch {
return { type: "object" }
}
}

/**
* Create a tool executor that wraps an opencode tool
*/
function createExecutor(
toolInfo: Awaited<ReturnType<Tool.Info["init"]>>,
config: OpencodeAdapterConfig
): (args: Record<string, unknown>, ctx: ToolContext) => Promise<ToolResult> {
return async (args: Record<string, unknown>, ctx: ToolContext): Promise<ToolResult> => {
// Create a tool context compatible with opencode
const toolCtx: Tool.Context = {
sessionID: ctx.sessionId || config.sessionId || "bridge-session",
messageID: ctx.callId,
agent: config.agent || "bridge",
abort: ctx.abort,
callID: ctx.callId,
metadata: (input) => {
// Send metadata updates as chunks
if (input.metadata) {
ctx.sendChunk(JSON.stringify(input.metadata), { type: "metadata" })
}
},
}

try {
const result = await toolInfo.execute(args, toolCtx)
return {
output: result.output,
metadata: result.metadata as Record<string, unknown>,
title: result.title,
}
} catch (error) {
throw error
}
}
}

/**
* Register opencode tools with a tool bridge
*/
export async function registerOpencodeTools(
bridge: ToolBridge,
config: OpencodeAdapterConfig
): Promise<void> {
const excludeSet = new Set(config.excludeTools ?? [])
const toolsToRegister = [...DEFAULT_TOOLS, ...(config.additionalTools ?? [])]

// Set up process.cwd() to return the working directory
const originalCwd = process.cwd
const cwd = () => config.workingDirectory

for (const tool of toolsToRegister) {
if (excludeSet.has(tool.id)) continue

try {
// Temporarily override cwd for tool initialization
process.cwd = cwd

const toolInfo = await tool.init()
const jsonSchema = schemaToJson(toolInfo.parameters)

bridge.registerTool(
tool.id,
toolInfo.description,
jsonSchema,
createExecutor(toolInfo, config)
)
} catch (error) {
console.error(`Failed to register tool ${tool.id}:`, error)
} finally {
process.cwd = originalCwd
}
}
}

/**
* Create a pre-configured tool bridge with opencode tools
*/
export async function createOpencodeBridge(
relayUrl: string,
config: OpencodeAdapterConfig & {
bridgeId?: string
credentials?: { type: "bearer" | "none"; token?: string }
onConnect?: (sessionId: string) => void
onDisconnect?: (reason: string) => void
onError?: (error: Error) => void
}
): Promise<ToolBridge> {
const bridge = new ToolBridge({
url: relayUrl,
bridgeId: config.bridgeId,
credentials: config.credentials,
onConnect: config.onConnect,
onDisconnect: config.onDisconnect,
onError: config.onError,
})

await registerOpencodeTools(bridge, config)

return bridge
}

/**
* Export tool definitions for reference
*/
export const TOOL_IDS = DEFAULT_TOOLS.map((t) => t.id)
16 changes: 16 additions & 0 deletions packages/tool-bridge-opencode/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* OpenCode Tool Bridge Adapter
*
* Provides integration between the Tool Bridge and OpenCode's
* built-in tools (bash, read, write, edit, glob, grep, etc.)
*/

export {
registerOpencodeTools,
createOpencodeBridge,
TOOL_IDS,
type OpencodeAdapterConfig,
} from "./adapter"

// Re-export bridge types for convenience
export { ToolBridge, type BridgeConfig, type ToolContext, type ToolResult } from "@opencode-ai/tool-bridge/index"
9 changes: 9 additions & 0 deletions packages/tool-bridge-opencode/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/bun/tsconfig.json",
"compilerOptions": {
"lib": ["ESNext", "DOM"],
"types": [],
"noUncheckedIndexedAccess": false
}
}
17 changes: 17 additions & 0 deletions packages/tool-bridge-protocol/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@opencode-ai/tool-bridge-protocol",
"version": "1.0.0",
"type": "module",
"private": true,
"exports": {
"./*": "./src/*.ts"
},
"dependencies": {
"zod": "catalog:"
},
"devDependencies": {
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:",
"typescript": "catalog:"
}
}
9 changes: 9 additions & 0 deletions packages/tool-bridge-protocol/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Tool Bridge Protocol
*
* A protocol for reverse RPC over WebSocket, enabling server-side LLM agents
* to invoke tools on client machines behind NAT/firewall.
*/

export * from "./schema"
export * from "./utils"
Loading
Loading