/** * Shared helpers for plugin tool handlers. * * Every tool that talks to the Rust binary should use `callBridge()` instead * of calling `ctx.pool.getBridge(...).send(...)` directly. The helper: * * 1. Resolves the project root from `context.worktree ?? context.directory` * (canonical path), so two tool calls in the same project always reach * the same bridge even if the agent's cwd momentarily differs. * 2. Injects `session_id` from `context.sessionID` into every request so the * Rust side can partition undo/checkpoint state per OpenCode session * (issue #14 — one shared bridge per project, N sessions per bridge). * * Tools that specifically need the raw `BinaryBridge` (for example to call * `bridge.send()` multiple times with shared state) should use `bridgeFor()` * and still pass `session_id` explicitly. */ import type { AftProjectTransport, BridgeRequestOptions, ToolCallOptions, ToolCallResult } from "@cortexkit/aft-bridge"; import type { PluginContext } from "../types.js"; /** * Optional integer with bounds. * * MUST be JSON-Schema-representable for OpenCode tool registration to * succeed: OpenCode wraps plugin args in a host `z.object()` and runs * `z.toJSONSchema(args, { io: "input" })` at session start. Any node * the host's Zod can't convert (e.g. `.transform()`, `.preprocess()`) * throws "Transforms cannot be represented in JSON Schema" and the * entire plugin fails to load. Keep this a plain schema — no * transforms. Empty-sentinel coercion (null/""/0 → undefined) belongs * in tool handlers via `coerceOptionalInt`, not in the schema. * * Regression guard: `tool-schemas-json-convertible.test.ts` runs * `z.toJSONSchema(z.object(args), { io: "input" })` on every plugin * tool. If anyone reintroduces a `.transform()` here it fails before * shipping. * * Return type is `any` to suppress TS2742 — Zod's inferred type leaks * `.bun/zod@...` paths that aren't portable across the host SDK and * our zod version. The type annotation has no runtime effect; the * contract test is the real invariant. */ export declare const optionalInt: (min: number, max: number) => any; export declare const BASH_TRANSPORT_TIMEOUT_MS = 30000; export { coerceOptionalInt, formatBridgeErrorMessage, isEmptyParam, LONG_RUNNING_COMMAND_TIMEOUT_MS, timeoutForCommand, } from "@cortexkit/aft-bridge"; /** * Minimum shape of the per-tool-call context provided by the OpenCode SDK. * * We only depend on a few fields so any similar context (including the Pi * plugin's `ExtensionContext`) can be passed through the same helpers once * they adopt session-aware calls. */ export interface ToolRuntime { /** Worktree root (preferred); falls back to `directory` when absent. */ worktree?: string; /** Agent's working directory for this tool call. */ directory: string; /** Opaque OpenCode session identifier. Missing in CLI tests / some hosts. */ sessionID?: string; } /** * Resolve the canonical project root for a runtime. * * Prefers `worktree` because that stays stable across OpenCode sessions in * the same project; falls back to `directory` when unavailable (standalone * CLI use, older hosts). Normalizes trailing slashes and resolves symlinks * so `/repo` and `/repo/` and `/Users/.../repo -> /Volumes/...` collapse to * the same key. * * NOTE: When the runtime carries a `sessionID` and we have a cached * session-stored directory for it (see `shared/session-directory.ts`), the * stored directory wins. This is the workaround for OpenCode's bug where * `ctx.directory` is set to `process.cwd()` rather than the resumed * session's actual project directory. */ export declare function projectRootFor(runtime: ToolRuntime): string; /** * Warm the session-directory cache, then return the same project root that * callBridge()/bridgeFor() will use for this tool call. Permission checks must * resolve paths through this helper before dispatch so the path the user * approves is byte-for-byte the path the Rust bridge acts on. */ export declare function resolveProjectRoot(ctx: PluginContext, runtime: ToolRuntime): Promise; /** * Expand a leading `~` to the user's home directory. Node's `path.resolve` * treats `~` as a literal segment, so `~/foo` would otherwise resolve to * `/~/foo`. Applied before any absolute/relative decision so all * file tools (read/write/edit/outline/zoom/delete/refactor/imports/safety) * accept `~/...` the same way the search tools already do. */ export declare function expandTilde(input: string): string; export { decodeFileUrl } from "@cortexkit/aft-bridge"; /** Resolve a user path exactly as a bridge request will: file: URLs decode * to local paths, `~` expands to home, absolute paths are preserved; * relative paths are rooted at the session/project root. */ export declare function resolvePathFromProjectRoot(projectRoot: string, target: string): string; export declare function resolvePathArg(ctx: PluginContext, runtime: ToolRuntime, target: string): Promise; /** * Get the BinaryBridge for the runtime's project root. * * Prefer `callBridge()` unless you need to send multiple requests yourself. * * This is synchronous and uses only the cached session directory. If the * cache is cold, it falls back to `runtime.directory` — `callBridge()` * eagerly warms the cache before calling this so the cache is hot for * subsequent calls in the same session. */ export declare function bridgeFor(ctx: PluginContext, runtime: ToolRuntime): AftProjectTransport; /** * Send a single command to the Rust binary with `session_id` injected. * * This is the canonical way for a tool handler to call AFT: the helper picks * the right bridge (project-keyed), attaches the session namespace from * `context.sessionID`, and returns whatever the binary responds. * * Before routing, it ensures the session-directory cache is warm so the * very first tool call on a resumed-from-wrong-cwd session still reaches * the correct project bridge. Subsequent calls hit the cache synchronously. * * The Rust side falls back to a shared default namespace when `session_id` * is absent (see `RawRequest::session()`), so hosts that don't expose a * session identifier still work — they just share undo/checkpoint state. */ export declare function callBridge(ctx: PluginContext, runtime: ToolRuntime, command: string, params?: Record, options?: BridgeRequestOptions): Promise>; /** * Dispatch one hoisted agent tool through the server-side `tool_call` command. * * The helper mirrors `callBridge()`: it warms the session-directory cache, * routes to the project-keyed bridge, applies the bare tool's timeout budget, * records the same perf marks, and ingests background-completion sidecars from * the raw response exactly once. The returned object is the full Rust response * plus the server-rendered `text` field. */ export declare function callToolCall(ctx: PluginContext, runtime: ToolRuntime, name: string, rawArgs?: Record, options?: ToolCallOptions): Promise; /** * Send a bash-family command without restarting the shared bridge on transport * timeout. Bash has its own child-process timeout/watchdog handling; a late * transport response must not sacrifice the warm shared bridge and reject * unrelated sibling requests. */ export declare function callBashBridge(ctx: PluginContext, runtime: ToolRuntime, command: string, params?: Record, options?: BridgeRequestOptions): Promise>; //# sourceMappingURL=_shared.d.ts.map