# Factory Droid Backend

The Factory Droid provider (`factory-droid`) is a **session-based agent adapter**
— Factory Droid is **NOT** an OpenAI/Anthropic-compatible model endpoint, so it
cannot be wired through pi-ai's built-in streamers. This backend drives the
[Droid Sessions API](https://docs.factory.ai/api-reference/sessions) directly.

**Important — what this provider actually is.** When pi sends a message to a
Factory session, the **DROID runs its own complete agentic loop** on a Factory
computer (its own file reads, command execution, edits) and returns the final
result text. Consequences:

- pi's own tool definitions are **ignored** — Droid manages its own tools.
- Each pi turn equals **one complete Factory agent turn**.
- The assistant content's `tool_use` / `tool_result` blocks are Droid's
  _internal_ actions and are **not** surfaced as pi tool calls.
- Factory sessions are **stateful**: history is retained server-side, so per
  turn the adapter sends only the _newest_ user message (not a replay).

---

## Wire protocol (REST, poll-based)

| Step | Endpoint                                            | Purpose                                                       |
| ---- | --------------------------------------------------- | ------------------------------------------------------------- |
| 1    | `GET /api/v0/computers`                             | find a reusable active `e2b` computer                         |
| 2    | `POST /api/v0/computers`                            | auto-create one from a cloud template (option B)              |
| 3    | `POST /api/v0/sessions`                             | create a Factory session (`computerId` **required**)          |
| 4    | `POST /api/v0/sessions/{id}/messages`               | fire-and-forget user message → `messageId`                    |
| 5    | `GET /api/v0/sessions/{id}`                         | poll `status` until `idle` (and read cumulative `tokenUsage`) |
| 6    | `GET /api/v0/sessions/{id}/messages?role=assistant` | extract assistant text/thinking blocks                        |
| 7    | `POST /api/v0/sessions/{id}/interrupt`              | best-effort stop on abort/error                               |

**No SSE exists** — Factory's REST API has no streaming endpoint. The engine
simulates streaming by polling and emitting `text_delta` events as the
assistant text grows between polls.

Auth: `Authorization: Bearer fk-…` (key prefix `fk-`). Key resolution order:

1. `FACTORY_API_KEY` env var
2. `~/.factory/settings.json` → `apiKey`
3. `<agent-dir>/auth.json` → `factory` / `factory-droid` / `factoryDroid` key

Errors use Factory's envelope `{ "detail": …, "status": …, "title": … }`, which
differs from OpenAI's `{ "error": { … } }`; the client surfaces `detail`.

---

## Computer lifecycle (option B — auto-create)

A Factory **session requires a `computerId`** (persistent compute env). The
provider resolves one in this order:

1. **`FACTORY_DROID_COMPUTER_ID`** env var — explicit override (recommended for
   stability).
2. **Reuse** the first existing computer with `providerType: "e2b"` and
   `status: "active"`.
3. **Auto-create** from a cloud template:
    - `FACTORY_DROID_MACHINE_TEMPLATE_ID` env var, else the first template with
      `buildStatus.status === "success"` from `GET /api/v0/machines/templates`.
    - `POST /api/v0/computers` with `{ provider: "e2b", source: { kind:
"template", templateId } }`, then poll `status` → `active` (5 min cap).

> Cloud templates are **deprecated** by Factory in favor of Droid Computers.
> The API still supports them, but for production prefer setting
> `FACTORY_DROID_COMPUTER_ID` to an existing computer you manage.

---

## Session mapping & usage

- pi conversation id (`options.sessionId`) → Factory session id, cached
  in-memory per extension load. One Factory session per pi conversation;
  created once, reused for all turns.
- **Usage** is read from the session's cumulative `tokenUsage`; the adapter
  computes a **per-turn delta** (baselined at session creation) and applies
  `calculateFactoryDroidCost` (per-million-token arithmetic).
- **Pricing:** Factory is subscription-based (Pro/Plus/Max + credit
  multipliers). There are no public per-token USD prices, so the pricing table
  is **all zeros**. A `$0` display does **NOT** mean free — usage is billed via
  your Factory plan (see `FACTORY_DROID_MODEL_MULTIPLIERS` for the relative
  credit costs).

---

## Abort & errors

- `AbortSignal` → best-effort `POST …/interrupt` (stops the running agent loop)
  and the stream ends with `error(reason: "aborted")`.
- Poll timeout: 10 min default per turn (`options.timeoutMs` overrides).
- 401/403 → surfaced as "Invalid or expired API key"-style messages from
  Factory's `detail` field.

---

## Configuration

| Env var                             | Purpose                                               |
| ----------------------------------- | ----------------------------------------------------- |
| `FACTORY_API_KEY`                   | Factory API key (`fk-…`). **Required.**               |
| `FACTORY_DROID_COMPUTER_ID`         | Reuse a specific Factory computer (recommended).      |
| `FACTORY_DROID_MACHINE_TEMPLATE_ID` | Template to auto-create a computer from.              |
| `FACTORY_DROID_BASE`                | API base override (default `https://api.factory.ai`). |

Visibility (showOnly/hide, provider on/off) works exactly like the other
providers via `pi-other-provider.json` / `PI_OTHER_PROVIDER_DISABLE`.

---

## Testing

`tests/test-factorydroid-vendor.ts` covers converters, auth, usage deltas,
session-turn filtering, catalog sanity, and **end-to-end stream runs with a
mocked `fetchImpl`** (no real API, no droid CLI). Run:

```bash
npx tsx tests/test-factorydroid-vendor.ts
```
