# Extending Tau Agent

Tau Agent is a Pi extension harness and a programmatic API for trusted Pi extensions.

Install `@shanepadgett/tau-agent` as a dependency when calling its API. Import only from the package root. Files under `extensions/` and other package paths are private.

## Image generation

`generateImage()` runs the same image-generation pipeline as Tau's `image_gen` tool without opening UI, injecting messages, or starting an agent turn.

```ts
import { generateImage } from "@shanepadgett/tau-agent";

const result = await generateImage(ctx, {
  prompt: "A stone archway at sunrise",
  provider: "openai",
  path: candidatePath,
  referencedImagePaths: [perspectivePath, palettePath],
  signal,
});
```

The context must provide `cwd`, `model`, and `modelRegistry`; Pi's `ExtensionContext` and `ExtensionCommandContext` already do. Relative destination and reference paths resolve from `ctx.cwd`.

Fields:

- `prompt`: complete non-empty image prompt.
- `provider`: optional `"openai"` or `"xai"` override. Without one, Tau follows the active model and falls back when authentication is unavailable.
- `path`: optional destination. Tau uses its external image store when omitted and never overwrites an explicit destination.
- `referencedImagePaths`: optional list of up to three local PNG, JPEG, or WebP files. Supplying references selects image editing or composition.
- `signal`: optional cancellation signal.

The promise resolves after publication with the generated bytes, absolute path, provider, model, operation, MIME type, width, and height. The bytes are the same bytes written to `result.path`, so callers can attach the image without reopening the file. Failures reject with a bounded, sanitized error. Cancellation before publication rejects with the signal's abort reason and removes staged files.

## File injection

`prepareFileInjection(request)` reads files in full and builds visible `tau.file` messages, in request order, without sending them. The caller sends or appends the prepared rows.

```ts
import { prepareFileInjection } from "@shanepadgett/tau-agent";

const messages = await prepareFileInjection({
  cwd: ctx.cwd,
  source: "my-extension",
  batchId,
  paths: ["src/server.ts", "src/router.ts"],
});
```

Fields:

- `cwd`: root used to resolve file paths.
- `source`: caller identifier recorded on each message.
- `batchId`: groups the visible rows produced by one call.
- `paths`: file paths relative to `cwd`.

Behavior:

- Messages are returned in request order.
- Missing, unreadable, or oversized files produce visible failed rows instead of rejecting.

## Tool groups

Extensions can register tools as deferred or codemode tools, keeping their schemas out of the active tool set. Deferred tools load with Pi's built-in `tool_search`; codemode tools are listed by the codemode tool and called from code mode scripts. Tau keeps `tool_search` active whenever deferred tools exist and `codemode` active whenever codemode tools exist.

```ts
import { defineTool, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { registerToolGroup } from "@shanepadgett/tau-agent";
import { Type } from "typebox";

const confluenceSearch = defineTool({
    name: "confluence_search",
    label: "Confluence Search",
    description: "Search and read Confluence pages.",
    parameters: Type.Object({ query: Type.String() }),
    async execute(_toolCallId, params) {
        return {
            content: [{ type: "text", text: await searchConfluence(params.query) }],
            details: {},
        };
    },
});

export default function confluenceExtension(pi: ExtensionAPI): void {
    registerToolGroup(pi, {
        id: "confluence",
        exposure: "deferred",
        description: "Search and read Confluence pages",
        tools: [confluenceSearch],
    });
}
```

`registerToolGroup()` registers each tool with the given `exposure` and a namespace made from `id` and `description`. Call it during extension initialization. Project-local and global package extensions use the same API. Tool names must be unique within the group.

Group registration affects model-visible tool schemas, not JavaScript package loading. Initialize expensive clients, authentication, and network connections inside tool execution when possible. Pi handles provider-specific deferred-tool behavior when `tool_search` loads a tool. On models that cannot take tool changes without replacing the cached prefix, Tau blocks `tool_search`.

## Events

External event integration uses Pi's native `pi.events` bus. The caller and Tau Agent must be loaded in the same Pi runtime. Event callers use string channel names and documented payloads. They do not import Tau Agent internals.

Only events documented in this file are public. Extensions run trusted in-process; event emitters can ask Tau Agent to do work.

Related:

- [TUI components](./tui.md)

## `tau:editor-slot`

Embed text in one of five slots in Tau's editor border.

```ts
pi.events.emit("tau:editor-slot", {
  slot: "top-left",
  id: "my-extension.work-order",
  text: "work order 4521",
});
```

Fields:

- `slot`: `"top-left"`, `"top-center"`, `"top-right"`, `"bottom-left"`, or `"bottom-right"`.
- `id`: stable item id. Re-emitting the same id in the same slot replaces the previous item.
- `text`: display text. Omit or leave empty to remove the item.
- `priority`: optional sort priority. Higher shows first when a slot has several items; they are joined with ` · `.

Slots truncate to fit: the right slot gives way before the left slot, and the center slot shifts or disappears when space is tight. Working, compaction, and retry indicators stay in Pi's status area above the editor, not in the border.

## `tau:agent.blocked`

Notify that Tau is blocked waiting on the user (confirmations, custom UI, attention).

```ts
pi.events.emit("tau:agent.blocked", {
  source: "my-extension",
  title: "Needs input",
  body: "Answer the open question to continue.",
});
```

Fields:

- `source`: optional caller id.
- `title`: optional short title.
- `body`: optional detail text.

Tau's attention extension listens for this event. Other packages can listen too for custom notifications.

## `tau:commit.before`

Register a check that runs once when the user runs `/commit`, before Tau generates a commit plan. If any check fails, `/commit` stops without committing and sends the failure text to the agent to fix.

```ts
pi.events.on("tau:commit.before", (event) => {
  event.waitFor("my-extension", runMyCheck(event.cwd, event.signal));
});

async function runMyCheck(cwd: string, signal: AbortSignal) {
  const result = await pi.exec("npm", ["test"], { cwd, signal });
  return { passed: result.code === 0, text: result.stdout + result.stderr };
}
```

Fields:

- `cwd`: session working directory.
- `signal`: aborts when the user cancels `/commit`.
- `waitFor(source, check)`: registers a check. Call it before the handler's first `await`. `source` names the check in notifications and in the agent message. `check` resolves to `{ passed, text }`; `text` is sent to the agent when `passed` is false. A rejected promise counts as a failure.

When no handler calls `waitFor`, `/commit` skips this step. Checks must not change files; `/commit` stops if the working tree changes while they run.

## `tau:tool-row-state.set`

Set visual state on a Tau tool row (for example pruned).

```ts
pi.events.emit("tau:tool-row-state.set", {
  rowId: "some-row-id",
  state: "pruned",
});
```

Fields:

- `rowId`: tool row id.
- `state`: optional visual state. Omit to clear.

Most extenders do not need this; it is for coordinating tool-row rendering with Tau's patch tooling.
