Documentation
¶
Overview ¶
Package anthropic is the first real actor/actor.Provider implementation: it calls the Anthropic Messages API to propose the next action for an in-flight campaign task.
It composes with the frozen actor seam like any other Provider — nothing in this package changes actor.Provider, actor.Prompt, actor.Proposal, actor.Usage or the Loop's semantics. In particular it is meant to be wrapped in an actor.CassetteProvider: record once against the live API with a real key, commit the cassette under testdata/cassettes/, and CI replays it at zero token cost (see README.md).
Providers are dumb transports (see actor's package doc): this one renders a Prompt to text, asks the model to reply with exactly one JSON object (Anthropic's structured-outputs contract enforces the shape server-side — see prompt.go), and maps that reply to a Proposal. It never fabricates a Proposal: an unparseable or malformed reply is a typed error, not a guess — see response.go.
Index ¶
Constants ¶
const DefaultMaxTokens = int64(1024)
DefaultMaxTokens is the default Config.MaxTokens: generous headroom for a short rationale plus the fixed JSON scaffolding, well under the ~16000-token non-streaming ceiling, so every call stays a plain non-streaming request.
const DefaultModel = sdk.ModelClaudeHaiku4_5
DefaultModel is the default model this package proposes with: claude-haiku-4-5, Anthropic's fastest and most cost-effective current model (see README.md for the source). A campaign is many small, latency-sensitive turns per task — action selection from a short, well-structured prompt, not open-ended reasoning — so the fast/cheap tier is the right default; callers that want a stronger model for harder campaigns set Config.Model explicitly.
const PricingSnapshotDate = "2026-06-24"
PricingSnapshotDate is when PricingUSDPerMillionTokens was last checked against Anthropic's published pricing. It is a point-in-time snapshot, not a live price feed — Anthropic can change list prices at any time. Update both together when they drift from the source below.
Variables ¶
var ErrMissingAPIKey = errors.New("actor/anthropic: no API key: set Config.APIKey or the ANTHROPIC_API_KEY environment variable")
ErrMissingAPIKey means New was called with an empty Config.APIKey and the ANTHROPIC_API_KEY environment variable was also unset (or empty).
var PricingUSDPerMillionTokens = map[string]modelPrice{
DefaultModel: {Input: 1.00, Output: 5.00},
"claude-sonnet-5": {Input: 3.00, Output: 15.00},
"claude-sonnet-4-6": {Input: 3.00, Output: 15.00},
"claude-opus-4-8": {Input: 5.00, Output: 25.00},
"claude-opus-4-7": {Input: 5.00, Output: 25.00},
"claude-fable-5": {Input: 10.00, Output: 50.00},
"claude-mythos-5": {Input: 10.00, Output: 50.00},
}
PricingUSDPerMillionTokens is a snapshot of Anthropic's per-model list pricing (US dollars per 1,000,000 tokens) as of PricingSnapshotDate, sourced from pricingSourceURL. Propose uses it to fill actor.Usage.Cost automatically (see Config.DisableCostEstimate) for every model it has an entry for; a model with no entry leaves Usage.Cost nil rather than guess.
Every entry here is the model's standard, non-promotional rate. claude-sonnet-5 in particular carries a temporary lower "intro" rate ($2/$10 per MTok) through 2026-08-31 that is deliberately NOT used here, so a campaign's estimated spend against goal.Budgets.MaxCost never understates the model's steady-state cost.
Treat Usage.Cost as an estimate for campaign budgeting, not an invoice — see AGENTS.md's "fidelity is declared" principle. Refresh this table (and PricingSnapshotDate) when it drifts from pricingSourceURL.
Functions ¶
This section is empty.
Types ¶
type AuthenticationError ¶
type AuthenticationError struct{ Err error }
AuthenticationError wraps an Anthropic API 401/403 response: the API key is missing, invalid, revoked, or lacks access to the requested model. Never retryable without fixing the key.
func (*AuthenticationError) Error ¶
func (e *AuthenticationError) Error() string
func (*AuthenticationError) Unwrap ¶
func (e *AuthenticationError) Unwrap() error
type Config ¶
type Config struct {
// APIKey authenticates every request. If empty, New reads the
// ANTHROPIC_API_KEY environment variable; if that is also empty or
// unset, New returns ErrMissingAPIKey. Never sourced from an
// actor.Prompt.
APIKey string
// Model is the Anthropic model id to propose with. Empty uses
// DefaultModel.
Model string
// MaxTokens bounds the model's reply. <= 0 uses DefaultMaxTokens.
MaxTokens int64
// HTTPClient overrides the HTTP client the Anthropic SDK issues
// requests with. Nil uses the SDK's default client. Tests set this to
// an *http.Client backed by a fake http.RoundTripper so Propose never
// touches the network — see provider_test.go.
HTTPClient *http.Client
// BaseURL overrides the Anthropic API base URL. Empty uses the SDK's
// default (https://api.anthropic.com/). Tests that prefer a real HTTP
// server over a fake RoundTripper point this at an httptest.Server.
BaseURL string
// Now supplies the provider's notion of the current time, used only to
// measure Usage.Latency around the API call. Nil uses time.Now. Inject
// a fake clock for deterministic latency assertions in tests.
Now func() time.Time
// DisableCostEstimate turns off the automatic Usage.Cost estimate (see
// pricing.go) even for models this package has pricing for. Usage.Cost
// is always left nil for models it has no pricing for, regardless of
// this flag.
DisableCostEstimate bool
// MaxRetries overrides the Anthropic SDK's automatic retry count for
// retryable errors (429, 5xx, connection failures) with exponential
// backoff. Nil keeps the SDK default (2). Tests set this to a pointer
// to 0 so an error-taxonomy test asserts on the first response instead
// of waiting through real backoff delays.
MaxRetries *int
}
Config configures a Provider.
type InvalidResponseError ¶
type InvalidResponseError struct {
// Raw is the model's raw response text that failed to parse, or empty
// if the response carried no text content at all.
Raw string
// StopReason is the API response's stop_reason, when known — e.g.
// "refusal" or "max_tokens" explain why Raw is empty or truncated.
StopReason string
Err error
}
InvalidResponseError means the model's reply could not be turned into a valid actor.Proposal — malformed JSON even after the one repair attempt (see response.go), a JSON object that does not match the response contract (missing/invalid "kind", or a kind whose required field is empty), or an API response with no text content at all (e.g. a refusal with an empty content array). Raw carries the model's raw text (truncated for the error message) so a developer can see what went wrong; Propose never fabricates a Proposal in its place.
func (*InvalidResponseError) Error ¶
func (e *InvalidResponseError) Error() string
func (*InvalidResponseError) Unwrap ¶
func (e *InvalidResponseError) Unwrap() error
type Provider ¶
type Provider struct {
// contains filtered or unexported fields
}
Provider is an actor.Provider backed by the Anthropic Messages API. Build one with New. The zero value is not usable.
func New ¶
New builds a Provider from cfg. It returns ErrMissingAPIKey if neither cfg.APIKey nor the ANTHROPIC_API_KEY environment variable supplies a key.
func (*Provider) Propose ¶
func (p *Provider) Propose(ctx context.Context, prompt actor.Prompt) (actor.Proposal, actor.Usage, error)
Propose implements actor.Provider: it renders prompt (see prompt.go), calls the Anthropic Messages API for exactly one structured-output JSON reply, and maps that reply to a Proposal (see response.go). It never returns a fabricated Proposal — any failure to obtain and parse a valid reply is a typed error (see errors.go), leaving the caller's zero-value Proposal untouched.
type RateLimitError ¶
type RateLimitError struct{ Err error }
RateLimitError wraps an Anthropic API 429 response. Retryable after backoff; this package does not retry internally (see README.md) — the loop/caller decides whether and when to retry a failed Propose call.
func (*RateLimitError) Error ¶
func (e *RateLimitError) Error() string
func (*RateLimitError) Unwrap ¶
func (e *RateLimitError) Unwrap() error