> ## Documentation Index
> Fetch the complete documentation index at: https://smithers.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK Agents

> Provider-backed AI SDK agent wrappers for Anthropic, OpenAI, and Hermes that work like first-class Smithers agents.

`AnthropicAgent`, `OpenAIAgent`, and `HermesAgent` are provider-backed [AI SDK](https://ai-sdk.dev) agents with class-style ergonomics matching the [CLI agents](/integrations/cli-agents). `AnthropicAgent` and `OpenAIAgent` wrap `ToolLoopAgent` directly; `HermesAgent` extends the OpenAI-compatible path with Hermes defaults.

<Note>API reference: [Agents](/reference/agents) lists every agent class, its options, and links to source and tests.</Note>

## Import

```ts theme={null}
import {
  AnthropicAgent,
  OpenAIAgent,
  createHttpTool,
  tools,
} from "smithers-orchestrator";
import { stepCountIs } from "ai";
```

## Quick Start

```ts theme={null}
const claude = new AnthropicAgent({
  model: "claude-fable-5",
  tools,
  instructions: "You are a careful planner.",
  stopWhen: stepCountIs(40),
});

const codex = new OpenAIAgent({
  model: "gpt-5.5",
  tools,
  instructions: "You are a precise implementation agent.",
  stopWhen: stepCountIs(40),
});
```

```tsx theme={null}
/** @jsxImportSource smithers-orchestrator */
import { AnthropicAgent, createSmithers } from "smithers-orchestrator";
import { z } from "zod";

const { Workflow, Task, smithers, outputs } = createSmithers({
  input: z.object({ repo: z.string() }),
  plan: z.object({
    summary: z.string(),
    risk: z.enum(["low", "medium", "high"]),
  }),
});

const claude = new AnthropicAgent({
  model: "claude-fable-5",
  instructions: "You are a careful planner. Return structured JSON.",
});

export default smithers((ctx) => (
  <Workflow name="sdk-agent-plan">
    <Task id="plan" output={outputs.plan} agent={claude}>
      {`Analyze ${ctx.input.repo} and propose a migration plan.`}
    </Task>
  </Workflow>
));
```

In workflow JSX, put the schema on the Smithers task by passing
`output={outputs.plan}`. You do not also configure the schema on the agent
constructor. At execution time the engine infers the task's Zod schema and calls
the SDK agent with `agent.generate({ outputSchema })`; the agent receives both
the prompt and the schema for that task. The `ctx` variable in the example is
the normal parameter of the `smithers((ctx) => ...)` builder, typed from the
`input` schema above.

You can also call an SDK agent once without rendering a workflow. Pass
`outputSchema` directly to `generate()` when you want typed structured output:

```ts theme={null}
import { z } from "zod";

const schema = z.object({
  summary: z.string(),
  risk: z.enum(["low", "medium", "high"]),
});

const result = await claude.generate({
  prompt: "Summarize this change and classify its risk.",
  outputSchema: schema,
  timeout: { totalMs: 60_000 },
});

const parsed = schema.parse(result.output ?? JSON.parse(result.text));
```

For `AnthropicAgent`, `outputSchema` is forwarded as AI SDK
`Output.object({ schema })`, which uses Anthropic's native structured-output
request path for Claude models. `OpenAIAgent` does the same unless
`nativeStructuredOutput: false`; `HermesAgent` defaults that option to `false`
because many local Hermes servers do not honor JSON-schema response formats.

## Model Input

`AnthropicAgent` and `OpenAIAgent` accept a model ID string (`"claude-fable-5"`, `"gpt-5.5"`) or a prebuilt provider model instance. `HermesAgent` accepts an optional model ID string and defaults it to `"hermes"`.

## Options

Constructors forward standard AI SDK `ToolLoopAgent` settings: `instructions`, `tools`, `stopWhen`, `maxOutputTokens`, `temperature`, `providerOptions`, `prepareCall`. The wrappers add provider model resolution on top.

* `AnthropicAgentOptions` is `ToolLoopAgentSettings` without `model`, plus required `model: string | anthropic(...)`.
* `OpenAIAgentOptions` adds `nativeStructuredOutput?: boolean` and has two model forms: a string model may include `baseURL` and `apiKey`; a prebuilt OpenAI provider model must not include `baseURL` or `apiKey`.
* `HermesAgentOptions` makes `model` optional, allows `baseURL` and `apiKey`, and defaults `nativeStructuredOutput` to `false`. A runtime `baseURL` or `HERMES_BASE_URL` is required.

For the string model form of `OpenAIAgent`, pass `baseURL` and `apiKey` directly when targeting an OpenAI-compatible endpoint instead of the default OpenAI API. This is the simplest path for local servers such as llama.cpp:

```ts theme={null}
const local = new OpenAIAgent({
  model: "llama-3.1-8b-instruct",
  baseURL: "http://127.0.0.1:8080/v1",
  apiKey: "none",
  tools,
  instructions: "You are a local coding assistant.",
  stopWhen: stepCountIs(40),
});
```

Set `apiKey: "none"` in the `OpenAIAgent` config when your local server accepts OpenAI-compatible requests but does not require a real key.

Some OpenAI-compatible local servers accept chat requests but do not reliably implement JSON schema structured output. For those servers, keep the output schema on the Smithers task and disable native structured output on the agent so Smithers uses prompt-based JSON extraction instead:

```ts theme={null}
const local = new OpenAIAgent({
  model: "llama-3.1-8b-instruct",
  baseURL: "http://127.0.0.1:8080/v1",
  apiKey: "none",
  nativeStructuredOutput: false,
  tools,
  instructions: "You are a local coding assistant.",
  stopWhen: stepCountIs(40),
});
```

For advanced provider setup, create the AI SDK OpenAI provider yourself and pass the prebuilt model into `OpenAIAgent`:

```ts theme={null}
import { createOpenAI } from "@ai-sdk/openai";

const localOpenAI = createOpenAI({
  baseURL: "http://127.0.0.1:8080/v1",
  apiKey: "none",
});

const local = new OpenAIAgent({
  model: localOpenAI("llama-3.1-8b-instruct"),
  tools,
  instructions: "You are a local coding assistant.",
  stopWhen: stepCountIs(40),
});
```

Use the `createOpenAI` path when you need provider-level configuration beyond `baseURL` and `apiKey`; in that form, `baseURL` and `apiKey` belong in the `createOpenAI` config, not in the `OpenAIAgent` constructor.

## Generic HTTP Tool

`createHttpTool()` returns an AI SDK tool that can call any REST endpoint without an OpenAPI spec. Use it as the universal escape hatch when no curated connector or MCP server exists yet.

```ts theme={null}
const http = createHttpTool({
  defaultHeaders: { "user-agent": "smithers-workflow" },
});

const ops = new AnthropicAgent({
  model: "claude-fable-5",
  tools: { http },
  instructions: "Use the HTTP tool only for APIs the workflow explicitly names.",
  stopWhen: stepCountIs(20),
});
```

The tool input accepts `method`, `url`, `headers`, `query`, `body`, optional `auth` (`bearer`, `basic`, or custom header), and `timeoutMs`. Results include `ok`, `status`, `statusText`, response `headers`, and a parsed JSON or text `body`.

## Generic HTTP Tool

`createHttpTool()` returns an AI SDK tool that can call any REST endpoint without an OpenAPI spec. Use it as the universal escape hatch when no curated connector or MCP server exists yet.

```ts theme={null}
const http = createHttpTool({
  defaultHeaders: { "user-agent": "smithers-workflow" },
});

const ops = new AnthropicAgent({
  model: "claude-fable-5",
  tools: { http },
  instructions: "Use the HTTP tool only for APIs the workflow explicitly names.",
  stopWhen: stepCountIs(20),
});
```

The tool input accepts `method`, `url`, `headers`, `query`, `body`, optional `auth` (`bearer`, `basic`, or custom header), and `timeoutMs`. Results include `ok`, `status`, `statusText`, response `headers`, and a parsed JSON or text `body`.

## Generic HTTP Tool

`createHttpTool()` returns an AI SDK tool that can call any REST endpoint without an OpenAPI spec. Use it as the universal escape hatch when no curated connector or MCP server exists yet.

```ts theme={null}
const http = createHttpTool({
  defaultHeaders: { "user-agent": "smithers-workflow" },
});

const ops = new AnthropicAgent({
  model: "claude-fable-5",
  tools: { http },
  instructions: "Use the HTTP tool only for APIs the workflow explicitly names.",
  stopWhen: stepCountIs(20),
});
```

The tool input accepts `method`, `url`, `headers`, `query`, `body`, optional `auth` (`bearer`, `basic`, or custom header), and `timeoutMs`. Results include `ok`, `status`, `statusText`, response `headers`, and a parsed JSON or text `body`.

## Hermes

[Hermes](https://github.com/NousResearch/hermes-agent) (Nous Research) exposes an OpenAI-compatible HTTP API, so `HermesAgent` is a convenience subclass of `OpenAIAgent` that points the provider at your Hermes server and disables native structured output by default (a local Hermes server may not honor JSON-schema response formats).

```ts theme={null}
import { HermesAgent } from "smithers-orchestrator";

const hermes = new HermesAgent({
  baseURL: "http://127.0.0.1:5123/v1", // or set HERMES_BASE_URL
  model: "hermes",                      // whatever model id your server advertises
  tools,
  instructions: "You are a careful implementation agent.",
  stopWhen: stepCountIs(40),
});
```

`baseURL` falls back to the `HERMES_BASE_URL` env var and must be set in either place. `apiKey` falls back to `HERMES_API_KEY` (then `"hermes"`). Pass `nativeStructuredOutput: true` if your server does honor JSON-schema output. To use Smithers *from* Hermes instead of running Hermes as a worker, see [Agent Support → Hermes](/agents/hermes).

## Hijack Support

SDK agents do not reopen a provider-native CLI. Smithers persists the agent conversation and reopens it through a Smithers-managed REPL via `bunx smithers-orchestrator hijack RUN_ID`.

Live-run behavior:

* Smithers captures response history after each step via `onStepFinish`.
* `bunx smithers-orchestrator hijack` waits until history is durable, aborts the current agent task (handing it off to the REPL), and opens the REPL.
* On clean REPL exit, Smithers writes updated message history back and resumes the workflow automatically.

Limits:

* Smithers reconstructs the agent from the workflow source on hijack. This means cross-engine hijack is not supported: the REPL will use the same agent class that ran originally.

## CLI vs SDK

|                   | CLI Agents                              | SDK Agents                                                           |
| ----------------- | --------------------------------------- | -------------------------------------------------------------------- |
| Billing           | Provider subscription / local CLI       | API billing                                                          |
| Tools             | Provider CLI tool ecosystem             | Smithers [tools](/integrations/tools) [sandbox](/components/sandbox) |
| Flexibility       | Native CLI flags                        | AI SDK `providerOptions`                                             |
| Structured output | Prompt-injection + text JSON extraction | Native (`supportsNativeStructuredOutput`)                            |

**Choosing an agent for typed/JSON-output tasks.** Only `AnthropicAgent` and `OpenAIAgent` declare `supportsNativeStructuredOutput = true`, so when a `<Task>` has an `output` schema they pass it through the AI SDK's native structured-output API (`Output.object({ schema })`) and the result is schema-constrained. CLI agents (`ClaudeCodeAgent`, `CodexAgent`, etc.) do **not** set this flag: the engine falls back to injecting JSON instructions into the prompt and extracting the object from the model's text, and emits a `console.warn` ("engine ... does not support native structured output. Falling back to prompt-injection + text JSON extraction"). Schema-validation retries still run, but valid JSON shape does not guarantee meaningful values. For tasks whose primary job is producing typed/JSON output, prefer `AnthropicAgent`/`OpenAIAgent` (or, for an OpenAI-compatible endpoint that honors JSON schema, an `OpenAIAgent` with `nativeStructuredOutput` left enabled). `CodexAgent` additionally forwards the task schema to the CLI via `--output-schema` for constrained decoding, but the engine still wraps it with the prompt-injection fallback because the flag is unset.

Pass a raw `ToolLoopAgent` directly if you prefer; the wrappers are convenience, not a separate runtime.

## Transcription Tool

Use `createTranscriptionTool` when an SDK agent needs to transcribe audio as part of its tool loop. The tool accepts either an `audioUrl` or `audioBase64` input and normalizes Whisper or Deepgram responses to `{ text, provider, language?, durationSeconds? }`.

```ts theme={null}
import { AnthropicAgent, createTranscriptionTool } from "@smithers-orchestrator/agents";

const transcribeAudio = createTranscriptionTool({
  provider: "deepgram", // or "whisper"
  apiKey: process.env.DEEPGRAM_API_KEY!,
});

const agent = new AnthropicAgent({
  model: "claude-fable-5",
  tools: { transcribeAudio },
  instructions: "Transcribe audio clips and summarize the result.",
});
```

## Transcription Tool

Use `createTranscriptionTool` when an SDK agent needs to transcribe audio as part of its tool loop. The tool accepts either an `audioUrl` or `audioBase64` input and normalizes Whisper or Deepgram responses to `{ text, provider, language?, durationSeconds? }`.

```ts theme={null}
import { AnthropicAgent, createTranscriptionTool } from "@smithers-orchestrator/agents";

const transcribeAudio = createTranscriptionTool({
  provider: "deepgram", // or "whisper"
  apiKey: process.env.DEEPGRAM_API_KEY!,
});

const agent = new AnthropicAgent({
  model: "claude-fable-5",
  tools: { transcribeAudio },
  instructions: "Transcribe audio clips and summarize the result.",
});
```

## Transcription Tool

Use `createTranscriptionTool` when an SDK agent needs to transcribe audio as part of its tool loop. The tool accepts either an `audioUrl` or `audioBase64` input and normalizes Whisper or Deepgram responses to `{ text, provider, language?, durationSeconds? }`.

```ts theme={null}
import { AnthropicAgent, createTranscriptionTool } from "@smithers-orchestrator/agents";

const transcribeAudio = createTranscriptionTool({
  provider: "deepgram", // or "whisper"
  apiKey: process.env.DEEPGRAM_API_KEY!,
});

const agent = new AnthropicAgent({
  model: "claude-fable-5",
  tools: { transcribeAudio },
  instructions: "Transcribe audio clips and summarize the result.",
});
```

## Example: Dual Setup

```ts theme={null}
const useCli = process.env.USE_CLI_AGENTS === "1";

export const claude = useCli
  ? new ClaudeCodeAgent({
      model: "claude-fable-5",
      dangerouslySkipPermissions: true,
    })
  : new AnthropicAgent({
      model: "claude-fable-5",
      tools,
      instructions: "You are a careful planner.",
      stopWhen: stepCountIs(40),
    });
```

## Next Steps

* [CLI Agents](/integrations/cli-agents)
* [Built-in Tools](/integrations/tools)
* [Agents and Tools](/agents/overview)
