# Public Plugin API Takopi's **public plugin API** is exported from: ``` takopi.api ``` Anything not imported from `takopi.api` should be considered **internal** and subject to change. The API version is tracked by `TAKOPI_PLUGIN_API_VERSION`. --- ## Versioning - Current API version: `TAKOPI_PLUGIN_API_VERSION = 1` - Plugins should pin to a compatible Takopi range, e.g.: ```toml dependencies = ["takopi>=0.14,<0.15"] ``` --- ## Exported symbols ### Engine backends and runners | Symbol | Purpose | |--------|---------| | `EngineBackend` | Declares an engine backend (id + runner builder) | | `EngineConfig` | Dict-based engine config table | | `Runner` | Runner protocol | | `BaseRunner` | Helper base class with resume locking | | `JsonlSubprocessRunner` | Helper for JSONL-streaming CLIs | | `EventFactory` | Helper for building takopi events | ### Transport backends | Symbol | Purpose | |--------|---------| | `TransportBackend` | Transport backend protocol | | `SetupIssue` | Setup issue for onboarding / validation | | `SetupResult` | Setup issues + config path | | `Transport` | Transport protocol (send/edit/delete) | | `Presenter` | Renders progress to `RenderedMessage` | | `RenderedMessage` | Rendered text + transport metadata | | `SendOptions` | Reply/notify/replace flags | | `MessageRef` | Transport-specific message reference | | `TransportRuntime` | Transport runtime facade (routers/projects hidden) | | `ResolvedMessage` | Parsed prompt + resume/context resolution | | `ResolvedRunner` | Runner selection result | ### Command backends | Symbol | Purpose | |--------|---------| | `CommandBackend` | Slash command plugin protocol | | `CommandContext` | Context passed to a command handler | | `CommandExecutor` | Helper to send messages or run engines | | `CommandResult` | Simple response payload for a command | | `RunRequest` | Engine run request used by commands | | `RunResult` | Engine run result (captured output) | | `RunMode` | `"emit"` (send) or `"capture"` (collect) | ### Core types and helpers | Symbol | Purpose | |--------|---------| | `EngineId` | Engine id type alias | | `ResumeToken` | Resume token (engine + value) | | `StartedEvent` / `ActionEvent` / `CompletedEvent` | Core event types | | `Action` | Action metadata for `ActionEvent` | | `RunContext` | Project/branch context | | `ConfigError` | Configuration error type | | `DirectiveError` | Error raised when parsing directives | | `RunnerUnavailableError` | Router error when a runner is unavailable | ### Bridge helpers (for transport plugins) | Symbol | Purpose | |--------|---------| | `ExecBridgeConfig` | Transport + presenter config | | `IncomingMessage` | Normalized incoming message | | `RunningTask` / `RunningTasks` | Per-message run coordination | | `handle_message()` | Core message handler used by transports | --- ## Runner contract (engine plugins) Runners emit events in a strict sequence (see `tests/test_runner_contract.py`): - Exactly **one** `StartedEvent` - Exactly **one** `CompletedEvent` - `CompletedEvent` is **last** - `CompletedEvent.resume == StartedEvent.resume` Action events are optional. The minimal valid run is: ``` StartedEvent -> CompletedEvent ``` ### Resume tokens Runners own the resume format: - `format_resume(token)` returns a command line users can paste - `extract_resume(text)` parses resume tokens from user text - `is_resume_line(line)` lets Takopi strip resume lines before running --- ## EngineBackend ```py EngineBackend( id: str, build_runner: Callable[[EngineConfig, Path], Runner], cli_cmd: str | None = None, install_cmd: str | None = None, ) ``` - `id` must match the entrypoint name and the ID regex. - `build_runner` should raise `ConfigError` for invalid config. - `cli_cmd` is used to check whether the engine CLI is on `PATH`. - `install_cmd` is surfaced in onboarding output. --- ## TransportBackend ```py class TransportBackend(Protocol): id: str description: str def check_setup(...) -> SetupResult: ... def interactive_setup(self, *, force: bool) -> bool: ... def lock_token( self, *, transport_config: dict[str, object], config_path: Path ) -> str | None: ... def build_and_run( self, *, transport_config: dict[str, object], config_path: Path, runtime: TransportRuntime, final_notify: bool, default_engine_override: str | None, ) -> None: ... ``` Transport backends are responsible for: - Validating config and onboarding users (`check_setup`, `interactive_setup`) - Providing a lock token so Takopi can prevent parallel runs - Starting the transport loop in `build_and_run` --- ## CommandBackend ```py class CommandBackend(Protocol): id: str description: str async def handle(self, ctx: CommandContext) -> CommandResult | None: ... ``` Command handlers receive a `CommandContext` with: - the raw command text and parsed args - the original message + reply metadata - `config_path` for the active `takopi.toml` (when known) - `plugin_config` from `[plugins.]` (dict, defaults to `{}`) - `runtime` (engine/project resolution) - `executor` (send messages or run engines) Use `ctx.executor.run_one(...)` or `ctx.executor.run_many(...)` to reuse Takopi's engine pipeline. Use `mode="capture"` to collect results and build a custom reply. --- ## TransportRuntime helpers `TransportRuntime` keeps transports away from internal router/project types. Key helpers: - `resolve_message(text, reply_text)` → `ResolvedMessage` (prompt, resume token, context) - `resolve_engine(engine_override, context)` → `EngineId` - `resolve_runner(resume_token, engine_override)` → `ResolvedRunner` (runner + availability info) - `resolve_run_cwd(context)` → `Path | None` (raises `ConfigError` for project/worktree issues) - `format_context_line(context)` → `str | None` - `available_engine_ids()` / `missing_engine_ids()` / `engine_ids` / `default_engine` - `project_aliases()` - `config_path` (active config path when available) - `plugin_config(plugin_id)` → `dict` from `[plugins.]` --- ## Bridge usage (transport plugins) Most transports can delegate message handling to `handle_message()`. Use `TransportRuntime` to resolve messages and select a runner: ```py from takopi.api import ( ExecBridgeConfig, IncomingMessage, RunningTask, RunningTasks, TransportRuntime, handle_message, ) async def on_message(...): resolved = runtime.resolve_message(text=text, reply_text=reply_text) entry = runtime.resolve_runner( resume_token=resolved.resume_token, engine_override=resolved.engine_override, ) context_line = runtime.format_context_line(resolved.context) incoming = IncomingMessage( channel_id=..., message_id=..., text=..., reply_to=..., ) await handle_message( exec_cfg, runner=entry.runner, incoming=incoming, resume_token=resolved.resume_token, context=resolved.context, context_line=context_line, strip_resume_line=runtime.is_resume_line, running_tasks=running_tasks, on_thread_known=on_thread_known, ) ``` `handle_message()` implements: - Progress updates and throttling - Resume handling - Cancellation propagation - Final rendering This keeps transport backends thin and consistent with core behavior.