docs: restructure docs into diataxis (#121)
This commit is contained in:
+11
-296
@@ -1,305 +1,20 @@
|
||||
# Plugins
|
||||
# Plugins (moved)
|
||||
|
||||
Takopi supports **entrypoint-based plugins** for:
|
||||
This page was split into smaller Diátaxis pages.
|
||||
|
||||
- **Engine backends** (new runner implementations)
|
||||
- **Transport backends** (new chat/command transports)
|
||||
- **Command backends** (custom `/command` handlers)
|
||||
## Start here
|
||||
|
||||
Plugins are **discovered lazily**: Takopi lists IDs without importing plugin code,
|
||||
and loads a plugin only when it is needed (or when you explicitly request it).
|
||||
- [Write a plugin](how-to/write-a-plugin.md)
|
||||
|
||||
This keeps `takopi --help` fast and prevents broken plugins from bricking the CLI.
|
||||
## Design
|
||||
|
||||
See `public-api.md` for the stable API surface you should depend on.
|
||||
- [Plugin system](explanation/plugin-system.md)
|
||||
|
||||
---
|
||||
## Reference
|
||||
|
||||
## Entrypoint groups
|
||||
- [Plugin API](reference/plugin-api.md)
|
||||
|
||||
Takopi uses three Python entrypoint groups:
|
||||
## Diagnostics
|
||||
|
||||
```toml
|
||||
[project.entry-points."takopi.engine_backends"]
|
||||
myengine = "myengine.backend:BACKEND"
|
||||
|
||||
[project.entry-points."takopi.transport_backends"]
|
||||
mytransport = "mytransport.backend:BACKEND"
|
||||
|
||||
[project.entry-points."takopi.command_backends"]
|
||||
mycommand = "mycommand.backend:BACKEND"
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
|
||||
- The entrypoint **name** is the plugin ID.
|
||||
- The entrypoint value must resolve to a **backend object**:
|
||||
- Engine backend -> `EngineBackend`
|
||||
- Transport backend -> `TransportBackend`
|
||||
- Command backend -> `CommandBackend`
|
||||
- The backend object **must** have `id == entrypoint name`.
|
||||
|
||||
Takopi validates this at load time and will report errors via `takopi plugins --load`.
|
||||
|
||||
---
|
||||
|
||||
## ID rules
|
||||
|
||||
Plugin IDs are used in the CLI and (for engines/projects) in Telegram commands.
|
||||
They must match:
|
||||
|
||||
```
|
||||
^[a-z0-9_]{1,32}$
|
||||
```
|
||||
|
||||
If an ID does not match, it is skipped and reported as an error.
|
||||
|
||||
**Reserved IDs (engines):**
|
||||
|
||||
- `cancel` (core chat command)
|
||||
- `init`, `plugins` (CLI commands)
|
||||
|
||||
Engines using these IDs are skipped and reported as errors.
|
||||
|
||||
**Reserved IDs (commands):**
|
||||
|
||||
- `cancel`, `init`, `plugins`
|
||||
- Any engine id or project alias (checked at runtime)
|
||||
|
||||
Command backends using reserved IDs are skipped and reported as errors.
|
||||
|
||||
---
|
||||
|
||||
## Enabling plugins
|
||||
|
||||
Takopi supports a simple enabled list to control which plugins are visible.
|
||||
|
||||
```toml
|
||||
[plugins]
|
||||
enabled = ["takopi-transport-slack", "takopi-engine-acme"]
|
||||
```
|
||||
|
||||
- `enabled = []` (default) -> load all installed plugins.
|
||||
- If `enabled` is non-empty, **only distributions with matching names** are visible.
|
||||
- Distribution names are taken from package metadata (case-insensitive).
|
||||
- If a plugin has no resolvable distribution name and an enabled list is set, it is hidden.
|
||||
This enabled list affects:
|
||||
|
||||
- Engine subcommands registered in the CLI
|
||||
- `takopi plugins` output
|
||||
- Runtime resolution of engines/transports/commands
|
||||
|
||||
---
|
||||
|
||||
## Discovering plugins
|
||||
|
||||
Use the CLI to inspect plugins:
|
||||
|
||||
```sh
|
||||
takopi plugins
|
||||
takopi plugins --load
|
||||
```
|
||||
|
||||
Behavior:
|
||||
|
||||
- `takopi plugins` lists discovered entrypoints **without loading them**.
|
||||
- `--load` loads each plugin to validate type and surface import errors.
|
||||
- Errors are shown at the end, grouped by engine/transport and distribution.
|
||||
- If `[plugins] enabled` is set, entries are still listed but marked `enabled`/`disabled`.
|
||||
|
||||
---
|
||||
|
||||
## Engine backend plugins
|
||||
|
||||
Engine plugins implement a runner for a new engine CLI and expose
|
||||
an `EngineBackend` object.
|
||||
|
||||
Minimal example:
|
||||
|
||||
```py
|
||||
# myengine/backend.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from takopi.api import EngineBackend, EngineConfig, Runner
|
||||
|
||||
def build_runner(config: EngineConfig, config_path: Path) -> Runner:
|
||||
_ = config_path
|
||||
# Parse config if needed; raise ConfigError for invalid config.
|
||||
return MyEngineRunner(config)
|
||||
|
||||
BACKEND = EngineBackend(
|
||||
id="myengine",
|
||||
build_runner=build_runner,
|
||||
cli_cmd="myengine",
|
||||
install_cmd="pip install myengine",
|
||||
)
|
||||
```
|
||||
|
||||
`EngineConfig` is the raw config table (dict) from `takopi.toml`:
|
||||
|
||||
```toml
|
||||
[myengine]
|
||||
model = "..."
|
||||
```
|
||||
|
||||
Read it with `settings.engine_config("myengine", config_path=...)` in Takopi,
|
||||
or just consume the dict directly in your runner builder.
|
||||
|
||||
See `public-api.md` for the runner contract and helper classes like
|
||||
`JsonlSubprocessRunner` and `EventFactory`.
|
||||
|
||||
---
|
||||
|
||||
## Transport backend plugins
|
||||
|
||||
Transport plugins connect Takopi to new messaging systems (Slack, Discord, etc).
|
||||
|
||||
You must provide a `TransportBackend` object with:
|
||||
|
||||
- `id` and `description`
|
||||
- `check_setup()` -> returns `SetupResult` (issues + config path)
|
||||
- `interactive_setup()` -> optional interactive setup flow
|
||||
- `lock_token()` -> token fingerprinting for config locks
|
||||
- `build_and_run()` -> build transport and start the main loop
|
||||
|
||||
Minimal skeleton:
|
||||
|
||||
```py
|
||||
# mytransport/backend.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from takopi.api import (
|
||||
EngineBackend,
|
||||
SetupResult,
|
||||
TransportBackend,
|
||||
TransportRuntime,
|
||||
)
|
||||
|
||||
class MyTransportBackend:
|
||||
id = "mytransport"
|
||||
description = "MyTransport bot"
|
||||
|
||||
def check_setup(
|
||||
self, engine_backend: EngineBackend, *, transport_override: str | None = None
|
||||
) -> SetupResult:
|
||||
_ = engine_backend, transport_override
|
||||
return SetupResult(issues=[], config_path=Path("takopi.toml"))
|
||||
|
||||
def interactive_setup(self, *, force: bool) -> bool:
|
||||
_ = force
|
||||
return True
|
||||
|
||||
def lock_token(
|
||||
self, *, transport_config: dict[str, object], config_path: Path
|
||||
) -> str | None:
|
||||
_ = transport_config, config_path
|
||||
return 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_config,
|
||||
config_path,
|
||||
runtime,
|
||||
final_notify,
|
||||
default_engine_override,
|
||||
)
|
||||
raise NotImplementedError
|
||||
|
||||
BACKEND = MyTransportBackend()
|
||||
```
|
||||
|
||||
For most transports, you will want to call `handle_message()` from `takopi.api`
|
||||
inside your message loop. That function implements progress updates, resume handling,
|
||||
and cancellation semantics.
|
||||
|
||||
---
|
||||
|
||||
## Command backend plugins
|
||||
|
||||
Command plugins add custom `/command` handlers. A command only runs when the
|
||||
message starts with `/command` and does **not** collide with engine ids,
|
||||
project aliases, or reserved command names.
|
||||
|
||||
Minimal example:
|
||||
|
||||
```py
|
||||
# mycommand/backend.py
|
||||
from __future__ import annotations
|
||||
|
||||
from takopi.api import CommandContext, CommandResult, RunRequest
|
||||
|
||||
class MultiCommand:
|
||||
id = "multi"
|
||||
description = "run the prompt on every engine"
|
||||
|
||||
async def handle(self, ctx: CommandContext) -> CommandResult | None:
|
||||
prompt = ctx.args_text.strip()
|
||||
if not prompt:
|
||||
return CommandResult(text="usage: /multi <prompt>")
|
||||
requests = [
|
||||
RunRequest(prompt=prompt, engine=engine)
|
||||
for engine in ctx.runtime.available_engine_ids()
|
||||
]
|
||||
results = await ctx.executor.run_many(
|
||||
requests,
|
||||
mode="capture",
|
||||
parallel=True,
|
||||
)
|
||||
blocks = []
|
||||
for result in results:
|
||||
text = result.message.text if result.message else "no output"
|
||||
blocks.append(f"## {result.engine}\n{text}")
|
||||
return CommandResult(text="\n\n".join(blocks))
|
||||
|
||||
BACKEND = MultiCommand()
|
||||
```
|
||||
|
||||
### Command plugin configuration
|
||||
|
||||
Configure command plugins under `[plugins.<id>]`:
|
||||
|
||||
```toml
|
||||
[plugins.multi]
|
||||
engines = ["codex", "claude"]
|
||||
```
|
||||
|
||||
The parsed dict is available as `ctx.plugin_config` inside `handle()`.
|
||||
|
||||
---
|
||||
|
||||
## Versioning & compatibility
|
||||
|
||||
Takopi exposes a **stable plugin API** via `takopi.api`.
|
||||
|
||||
- `TAKOPI_PLUGIN_API_VERSION = 1` is the current API version.
|
||||
- Depend on a compatible Takopi version range, for example:
|
||||
|
||||
```toml
|
||||
dependencies = ["takopi>=0.14,<0.15"]
|
||||
```
|
||||
|
||||
When the plugin API changes, Takopi will bump the API version and document
|
||||
any compatibility guidance.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Common issues:
|
||||
|
||||
- **Plugin missing from CLI**: check the enabled list in `[plugins] enabled`.
|
||||
- **Plugin not listed**: verify entrypoint group and ID regex.
|
||||
- **Load failures**: run `takopi plugins --load` and inspect errors.
|
||||
- **ID mismatch**: ensure `BACKEND.id == entrypoint name`.
|
||||
- `takopi plugins` lists discovered entrypoints without loading them.
|
||||
- `takopi plugins --load` loads each plugin to surface import errors.
|
||||
|
||||
Reference in New Issue
Block a user