Files
takopi/docs/reference/plugin-api.md
T
2026-01-13 15:59:27 +04:00

263 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Plugin API
Takopis **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.<id>]` (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.
`ctx.message` and `ctx.reply_to` are `MessageRef` objects with:
- `channel_id` (`int | str`, chat/channel id)
- `message_id` (`int | str`, message id)
- `thread_id` (`int | str | None`; set when the transport supports threads, like Telegram topics)
- `raw` (transport-specific payload, may be `None`)
Example: key per-thread state by `(ctx.message.channel_id, ctx.message.thread_id)`.
---
## 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.<id>]`
---
## 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=...,
thread_id=...,
)
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.