Skip to main content
AnthropicAgent, OpenAIAgent, and HermesAgent are provider-backed AI SDK agents with class-style ergonomics matching the CLI agents. AnthropicAgent and OpenAIAgent wrap ToolLoopAgent directly; HermesAgent extends the OpenAI-compatible path with Hermes defaults.
API reference: Agents lists every agent class, its options, and links to source and tests.

Import

import {
  AnthropicAgent,
  OpenAIAgent,
  createHttpTool,
  tools,
} from "smithers-orchestrator";
import { stepCountIs } from "ai";

Quick Start

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),
});
/** @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:
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:
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:
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:
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.
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.
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.
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 (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).
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.

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 AgentsSDK Agents
BillingProvider subscription / local CLIAPI billing
ToolsProvider CLI tool ecosystemSmithers tools sandbox
FlexibilityNative CLI flagsAI SDK providerOptions
Structured outputPrompt-injection + text JSON extractionNative (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? }.
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? }.
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? }.
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

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