# 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

`injectFiles()` reads or outlines files and injects them as visible `tau.file` messages, in request order. `prepareFileInjection(pi, request)` builds the same messages without sending them, for callers that need to inspect or append the prepared rows themselves. Both APIs use the supplied Pi runtime to reach Explore's runtime-scoped outline provider.

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

await injectFiles(pi, {
  cwd: ctx.cwd,
  source: "my-extension",
  batchId,
  files: [
    { path: "src/server.ts", mode: "full" },
    { path: "src/router.ts", mode: "outline" },
  ],
});
```

Fields:

- `cwd`: root used to resolve file paths.
- `source`: caller identifier recorded on each message.
- `batchId`: groups the visible rows produced by one call.
- `files[].path`: file path relative to `cwd`.
- `files[].mode`: `full` injects complete contents, `outline` injects structure, `auto` injects contents unless the file is larger than Explore's structural read threshold.
- `signal`: optional cancellation signal.

Behavior:

- Messages are sent in request order and resolve to the prepared messages.
- Missing, unreadable, or oversized files produce visible failed rows instead of rejecting.
- Outlining requires Tau's Explore extension to be loaded.

## Deferred tool groups

Extensions can register tools with Tau's `load_tools` registry while keeping their schemas out of the active tool set until the agent needs them. The extension and Tau must run in the same Pi runtime.

```ts
import { defineTool, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { registerDeferredToolGroup } 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 {
    registerDeferredToolGroup(pi, {
        id: "confluence",
        description: "Search and read Confluence pages",
        tools: [confluenceSearch],
    });
}
```

`registerDeferredToolGroup()` registers tool definitions with Pi and exposes the group through `load_tools`. Call it during extension initialization. Project-local and global package extensions use the same API. `id` must be unique in the runtime, and tool names must be unique within the group.

Deferred loading 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 after Tau additively activates the group.

## 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:

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

## `tau:footer-item`

Publish a bottom-right footer item in Tau's status footer.

```ts
pi.events.emit("tau:footer-item", {
  id: "my-extension.status",
  text: "syncing",
  priority: 10,
});
```

Fields:

- `id`: stable item id. Re-emitting the same id replaces the previous item.
- `text`: optional display text. Omit or clear to remove the item (implementation may treat empty/undefined as hide).
- `priority`: optional sort priority (higher shows first when the footer ranks items).

## `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:file-mutation.applied`

Emitted after Tau's `patch` tool applies file changes.

```ts
pi.events.on("tau:file-mutation.applied", (data) => {
  // data.source === "patch"
  // data.status: "completed" | "partial" | "failed"
  // data.changes: path, kind, line stats, optional move/snapshotRanges
});
```

Fields:

- `source`: currently `"patch"`.
- `toolCallId`: tool call that produced the mutation.
- `cwd`: working directory for the tool call.
- `status`: overall result.
- `changes[]`: per-file change summary (`path`, `kind`, optional `move`, `linesAdded`, `linesRemoved`, optional `snapshotRanges`).

Use this to react after mutations (formatters, review hooks, status UI). Do not treat it as a request channel.

## `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 explore/patch/subagent tooling.
