The src/tools/ directory provides the core tool implementations for the oh-my-opencode-slim plugin. It exposes three main categories of tools:
These tools are consumed by the OpenCode plugin system and exposed to AI agents for code navigation, analysis, and modification tasks.
src/tools/
├── index.ts # Central export point
├── background.ts # Background task tools (3 tools)
├── ast-grep/
│ ├── cli.ts # CLI execution, path resolution, binary download
│ ├── index.ts # Module re-exports
│ ├── types.ts # TypeScript interfaces (CliLanguage, CliMatch, SgResult)
│ ├── utils.ts # Output formatting (formatSearchResult, formatReplaceResult)
│ ├── constants.ts # CLI path resolution, safety limits
│ └── downloader.ts # Binary auto-download for missing ast-grep
└── lsp/
├── client.ts # LSP client & connection pooling (LSPServerManager singleton)
├── config.ts # Server discovery & language mapping
├── constants.ts # Built-in server configs (45+ servers), extensions, install hints
├── index.ts # Module re-exports
├── types.ts # LSP type re-exports (Diagnostic, Location, WorkspaceEdit, etc.)
├── utils.ts # Formatters & workspace edit application
├── config-store.ts # User LSP config runtime storage
└── tools.ts # 4 tool definitions
All tools follow the OpenCode plugin tool schema:
export const toolName: ToolDefinition = tool({
description: string,
args: { /* Zod schema */ },
execute: async (args, context) => { /* implementation */ }
});
The ast-grep module uses a CLI execution pattern:
The LSP module implements a singleton LSPServerManager with:
root::serverId)refCount, increment on acquire, decrement on releaseinitPromiseAll tools enforce strict safety limits:
User Request (ast_grep_search or ast_grep_replace)
↓
Tool definition (ast-grep/tools.ts)
↓
runSg() (cli.ts)
├─→ getAstGrepPath()
│ ├─→ Check cached path
│ ├─→ findSgCliPathSync()
│ │ ├─→ Cached binary in ~/.cache
│ │ ├─→ @ast-grep/cli package
│ │ ├─→ Platform-specific package (@ast-grep/cli-*)
│ │ └─→ Homebrew (macOS)
│ └─→ ensureAstGrepBinary() → download if missing
└─→ Build args: pattern, lang, rewrite, globs, paths
↓
spawn([sg, 'run', '-p', pattern, '--lang', lang, ...])
↓
Parse JSON output → CliMatch[]
↓
Handle truncation (max_output_bytes, max_matches, timeout)
↓
formatSearchResult() / formatReplaceResult() (utils.ts)
├─→ Group by file
├─→ Truncate long text
└─→ Add summary
↓
Add empty result hints (getEmptyResultHint)
↓
Return formatted output
User Request (e.g., lsp_goto_definition)
↓
Tool definition (lsp/tools.ts)
↓
withLspClient() (utils.ts)
├─→ findServerForExtension() (config.ts)
│ ├─→ Match extension to BUILTIN_SERVERS
│ ├─→ Merge with user config from config-store
│ └─→ isServerInstalled() → PATH check
├─→ findServerProjectRoot() → server-specific root patterns
└─→ lspManager.getClient() (client.ts)
├─→ Check cache (root::serverId)
├─→ If cached: increment refCount, return
└─→ If new:
├─→ new LSPClient(root, server)
├─→ client.start() → spawn server
├─→ client.initialize() → LSP handshake
└─→ Store in pool with refCount=1
↓
client.definition() / references() / diagnostics() / rename()
├─→ openFile() → textDocument/didOpen
└─→ Send LSP request
↓
Format result (formatLocation, formatDiagnostic, etc.)
↓
lspManager.releaseClient() → decrement refCount
↓
Return formatted output
LSP Client Lifecycle:
start()
├─→ spawn(command)
├─→ Create JSON-RPC connection (vscode-jsonrpc)
├─→ Register handlers (diagnostics, configuration, window)
└─→ Wait for process to stabilize
↓
initialize()
├─→ sendRequest('initialize', capabilities)
└─→ sendNotification('initialized')
↓
[Operational phase]
├─→ openFile() → textDocument/didOpen
├─→ definition() / references() / diagnostics() / rename()
└─→ Receive notifications (diagnostics)
↓
stop()
├─→ sendRequest('shutdown')
├─→ sendNotification('exit')
└─→ kill process
User Request (background_task)
↓
Tool definition (background.ts)
↓
manager.launch()
├─→ Validate agent against delegation rules
├─→ Create task with unique ID
├─→ Store in BackgroundTaskManager
└─→ Return task_id immediately (~1ms)
↓
[Background execution]
├─→ Agent runs independently
├─→ Completes with result/error
└─→ Auto-notify parent session
↓
User Request (background_output)
↓
manager.getResult(task_id)
├─→ If timeout > 0: waitForCompletion()
└─→ Return status/result/error/duration
↓
User Request (background_cancel)
↓
manager.cancel(task_id) or manager.cancel(all)
└─→ Cancel pending/starting/running tasks only
tool, ToolDefinition)spawn), file operations (Bun.write)BackgroundTaskManager for background task toolsSUBAGENT_NAMES, PluginConfig, TmuxConfigextractZip for binary extractionAll tools are exported from src/tools/index.ts:
export { ast_grep_replace, ast_grep_search } from './ast-grep';
export { createBackgroundTools } from './background';
export {
lsp_diagnostics,
lsp_find_references,
lsp_goto_definition,
lsp_rename,
lspManager,
setUserLspConfig,
} from './lsp';
"disabled": true~/.cache/oh-my-opencode-slim/bin/sg (Linux/macOS), %LOCALAPPDATA%\oh-my-opencode-slim\bin\sg.exe (Windows)runSg(), getAstGrepPath(), startBackgroundInit(), isCliAvailable(), ensureCliAvailable() - CLI execution layerCliLanguage, CliMatch, SgResult, CLI_LANGUAGES - TypeScript interfacesformatSearchResult(), formatReplaceResult(), getEmptyResultHint() - Output formattingfindSgCliPathSync(), getSgCliPath(), setSgCliPath(), checkEnvironment(), formatEnvironmentCheck(), safety limitsdownloadAstGrep(), ensureAstGrepBinary(), getCacheDir(), getCachedBinaryPath() - Binary managementLSPServerManager (singleton), LSPClient class - full connection lifecycle managementlsp_goto_definition, lsp_find_references, lsp_diagnostics, lsp_renameDiagnostic, Location, WorkspaceEdit, etc.)withLspClient(), findServerProjectRoot(), formatters, applyWorkspaceEdit(), formatApplyResult()findServerForExtension(), getLanguageId(), isServerInstalled(), buildMergedServers()setUserLspConfig(), getUserLspConfig(), getAllUserLspConfigs(), hasUserLspConfig()BUILTIN_SERVERS (45+ servers), LANGUAGE_EXTENSIONS, LSP_INSTALL_HINTS, NearestRoot(), safety limits