Skip to content
Moku
@moku-labs/ai pages

anthropic

Provider. prompt-gen over the Messages API with the official Anthropic SDK: tools, prompt cache, effort.

On this page

What it does

Provider. anthropic registers one handler with registry in onInit: ("prompt-gen", "anthropic"). It defines no task of its own. app.promptGen.generate and the runner reach it through the registry.

It reads the same request contract as every prompt-gen provider: prompt or messages, system, tools, toolChoice, cacheSystem, cacheTtl, part cache: true and params. A call moves from another provider to Anthropic by the provider and model name only.

Every request is streamed, so a 128K max_tokens works. The SDK's own retries are off: the runner owns retries. Prompts, answers and the key never appear in errors, logs or result metadata.

Its own public API is one method, app.anthropic.info().

Setup

One env var. Exported in the shell or in .env.local.

VariableNeeded by
ANTHROPIC_API_KEYexecute only

info() and estimate() never need it. Without it, execute throws PromptGenUnavailableError with reason "auth", so promptGen moves to the next provider of its fallback chain. No request is sent.

[ai] ANTHROPIC_API_KEY is not set.
  Export it, or use another prompt-gen provider.

Tasks it serves

TaskAPIDefault provider of the task
prompt-genMessages API, streamedno: name provider: anthropic

prompt-gen

Request fieldSent asNotes
systemsystemWith cacheSystem: one text block with cache_control.
prompt (no messages)one user messageWith params.images: the image blocks first, then the text.
messagesuser and assistant turnsprompt is ignored.
cacheTtlttl of every cache_control"1h" sends ttl: "1h". Unset or "5m" sends no ttl. Other values: 400.
tools{ name, description, input_schema }Sent only when there is at least one tool.
toolChoiceauto, none, any, toolOpus 5.5, Sonnet 5.5 and Fable 5.1 answer a forced choice with a terminal 400.
params.reasoningoutput_config.effortoff and low send low, medium sends medium, high sends high. Unset: medium. Other: 400.
params.responseSchemaoutput_config.format of type json_schemaNot a plain object: 400. params.strictSchema is not read.
params.max_tokensmax_tokensPositive integer, else config.maxTokens.
params.imagesbase64 image blocksOne { path, mimeType, hash } or a list. Refused with messages. jpeg, png, gif and webp only.
temperaturenever sentCurrent models reject it.

Refused with a terminal 400 before any I/O: empty messages, an empty prompt without messages, a model without a price, a bad params.reasoning, params.responseSchema or cacheTtl, params.images with messages, an image that is not a local file or has a type the API cannot read.

Answers: stop_reason end_turn and stop_sequence give finishReason: "stop", tool_use gives "tool_calls", max_tokens gives "length" and logs anthropic:prompt-gen:partial, refusal throws FlaggedProviderError. text is the text blocks joined. meta is { model, effort, stopReason, promptTokens, completionTokens }.

hello.moku.yaml
  - id: judge
    task: prompt-gen
    provider: anthropic
    input:
      model: anthropic/claude-sonnet-5.5
      system: "You judge storyboard frames."
      cacheSystem: true
      prompt: "Is the subject in frame?"
    params: { reasoning: medium, max_tokens: 2000 }

Prompt cache

  • A mark is cacheSystem on the system text, or cache: true on a text part. Each becomes a cache_control on its block. The API takes at most 4 marks: the first 4 in request order keep theirs.
  • cacheTtl sets one lifetime for every mark of the request. Unset or "5m": 5 minutes. "1h": one hour. A 1-hour write costs 2× input, a 5-minute write 1.25×. A read costs the same on both. Use "1h" for calls that share a prefix but start more than 5 minutes apart.
  • usage.cachedReadTokens are the reads, cacheWriteTokens the writes. promptTokens is every input token: uncached, written and read.

Tools and thinking

A tool_use block becomes a ToolCall. Consecutive tool messages are sent as one user message of tool_result blocks. Thinking blocks must go back unchanged with their tool turn, and the ChatMessage type cannot carry them, so the plugin keeps the whole content of every answer with tool calls in state, keyed by its first tool_use id. The memory holds the last 500 turns and is lost on a restart: a resumed loop then sends the open tool turn without its thinking, and the API may answer with a terminal 400.

Models and prices

The plugin ships a price table in USD per one million tokens. priceOverrides merges over it field by field.

ModelInputOutputCache readCache write 5mCache write 1hLong context
claude-opus-5-54200.2058
claude-sonnet-5-52100.202.504
claude-sonnet-52100.202.504
claude-haiku-5-50.100.500.010.1250.20above 100 000 input tokens: × 5 in, × 5 out
claude-fable-5-110500.2512.5020

Aliases, the ids the studio uses with fal and OpenRouter:

AliasNative id
anthropic/claude-opus-5.5claude-opus-5-5
anthropic/claude-sonnet-5.5claude-sonnet-5-5
anthropic/claude-sonnet-5claude-sonnet-5
anthropic/claude-haiku-5.5claude-haiku-5-5
anthropic/claude-fable-5.1claude-fable-5-1

Any other id is sent as is. It needs all five rates in priceOverrides, or the request is refused with a terminal 400 before any I/O.

Cost: uncached input × input, 5m writes × write 5m, 1h writes × write 1h, cache reads × read, output × output, rounded to micro-dollars. Without the lifetime breakdown every write is priced at 5m. Long context: when the total input passes the threshold, every input-side rate and the output rate take their multiplier for the whole request. estimate is offline: chars / 4 of the system text and the prompt, or of every message and tool-call JSON, plus 1 600 tokens per image, in; max_tokens out.

moku.config.ts
import { createApp } from "@moku-labs/ai";

const app = createApp({
  pluginConfigs: {
    anthropic: {
      model: "claude-sonnet-5-5",
      maxTokens: 64_000,
      priceOverrides: {
        "claude-next-6": { inputPerM: 5, outputPerM: 25, cacheReadPerM: 0.5, cacheWrite5mPerM: 6.25, cacheWrite1hPerM: 10 }
      }
    }
  }
});

console.log(app.anthropic.info().model); // "claude-sonnet-5-5"
  1. All five rates, or the model is refused.

Config

pluginConfigs.anthropic.

OptionTypeDefaultDescription
apiKeyEnvstring"ANTHROPIC_API_KEY"Name of the env var with the API key. Read through ctx.env on the first execute.
baseUrlstring (optional)SDK defaultAPI base URL override.
modelstring"claude-opus-5-5"Model when the request names none, or names "default". Aliases work here too.
maxTokensnumber32_000Output cap when params.max_tokens is not a positive integer.
timeoutMsnumber900_000Request timeout, ms. A timeout is retryable.
priceOverridesRecord<string, Partial<AnthropicPrice>>{}Per-model prices merged over the bundled table. AnthropicPrice has inputPerM, outputPerM, cacheReadPerM, cacheWrite5mPerM, cacheWrite1hPerM and an optional longContext.

API

Mounted at app.anthropic.

MethodSignatureDescription
info() => { provider: "anthropic"; configured: boolean; model: string }configured is whether the key env var is set. model is the default model. Never throws and never builds the client.
status.ts
import { app } from "./app";

const { configured, model } = app.anthropic.info();
console.log(configured, model); // true, "claude-opus-5-5"

Types ship under the Anthropic namespace: Anthropic.Config, Anthropic.AnthropicPrice, Anthropic.AnthropicApi.

Errors

SDK errorClassFieldsRunner
APIConnectionTimeoutErrorRetryableProviderErrorkind: "timeout"retried
RateLimitError (429)RetryableProviderErrorstatus: 429, retryAfterMs from retry-afterretried after the hint
APIError with status 500 or above, 529 overloaded includedRetryableProviderErrorstatusretried
Any other APIError with a status, a refused requestTerminalProviderErrorstatusitem failed
A refusal stop reasonFlaggedProviderErrorkind: "content-policy"item flagged, never re-queued
Anything else: connection error, stream errorRetryableProviderErrorkind: "network"retried
Missing keyPromptGenUnavailableErrorreason: "auth"promptGen falls back

A 401, 402, 403 or 429 also makes promptGen fall back to the next provider. An APIUserAbortError, or any failure after the caller aborted, is rethrown unchanged. Messages are fixed [ai] Anthropic ... strings, never the SDK's message.

The classes are values under AnthropicErrors:

handle.ts
import { AnthropicErrors } from "@moku-labs/ai";
import { app } from "./app";

try {
  await app.promptGen.generate({ prompt: "Say ok." }, { provider: "anthropic" });
} catch (error) {
  if (error instanceof AnthropicErrors.RetryableProviderError) {
    console.warn("transient", error.status ?? error.kind);
  } else if (error instanceof AnthropicErrors.TerminalProviderError) {
    console.error("failed", error.status, error.api?.type);
  } else {
    throw error;
  }
}

TerminalProviderError also carries api, the API's { type, message } error detail when there is one.

  • No temperature. It is never sent. Current models reject it.
  • One TTL per request. cacheTtl applies to every mark. The API needs longer-lived marks before shorter ones.
  • The thinking memory is per process. A tool loop resumed after a restart may get a terminal 400 on its open turn.

Events

Emits

None.

Listens to

None. Diagnostics go through ctx.log: anthropic:prompt-gen:done with model and token counts, anthropic:prompt-gen:partial on max_tokens, and anthropic:prompt-gen:failed with the error class and status. No text.

Depends on

  • prompt-gen: the task, its fallback chain and runToolLoop.
  • claude: the same models through the Claude Code CLI, plan-billed.
  • runner: how the error classes become retries.