Skip to main content

Crate cratefield_adapter_anthropic

Crate cratefield_adapter_anthropic 

Source
Expand description

cratefield-adapter-anthropic: the minimal TextModel port over the Anthropic Messages API (issue #430). Uses the runtime’s HttpClient port — no reqwest, no vendor SDK — so the same adapter runs on Workers and natively, and inherits that port’s destination vetting, deadline and response-size caps.

Degraded mode. When the API key is absent the adapter answers TextModelError::NotConfigured without any network call, so a pipeline stage can degrade instead of breaking.

Tools (issue #665): Prompt::tools travel as the Messages API’s tools array, Prompt::tool_choice as its tool_choice, and the answer’s tool_use blocks come back as Completion::tool_calls. A turn built with Turn::assistant_tool_calls or Turn::tool_results is sent as the content-block array the API requires; every other turn keeps the bare-string content it always sent. Anthropic::supports reports Capability::Tools. A prompt that carries both Prompt::json_schema and its own tools is refused with TextModelError::Rejected before any request.

Streaming (issue #859): Anthropic::stream sends the same request with "stream": true and answers the port’s TextDeltas straight off the Messages event stream — text and reasoning as they are produced, each tool call from its start through its argument chunks to the finished call, running usage, and one Finish naming the wire’s stop_reason. A finished call is the same ToolCall complete builds — one shared conversion — and the stream owns the response body, so dropping it cancels the upstream exchange. A prompt carrying Prompt::json_schema still completes and decomposes: Completion::json is a buffered feature.

Images (issue #628): a turn built with Turn::user_parts is sent as the content-block array the Messages API requires — each Part as a text block or an inline base64 image block, in order — and Anthropic::supports reports Capability::Images, because every current Claude model accepts image input. An over-limit prompt is refused by Prompt::check_images before any network call.

cratefield-adapter-anthropic on crates.io cratefield-adapter-anthropic documentation MIT

§cratefield-adapter-anthropic

TextModel port over the Anthropic Messages API for the Cratefield harness — the first text-model adapter (issue #430). Uses the runtime’s HttpClient port — no reqwest, no vendor SDK — so it runs unchanged on Workers (worker::Fetch) and natively, and inherits that port’s destination vetting, deadline and response-size caps.

§Usage

ⓘ
use std::sync::Arc;
use cratefield_adapter_anthropic::Anthropic;
use cratefield_core::{Prompt, TextModel};
use cratefield_runtime_cloudflare::{FetchClient, WorkersClock};

// With a key:
let model = Anthropic::new(
    Arc::new(FetchClient),
    Arc::new(WorkersClock),
    Some(key),
    "claude-opus-5",
);
// Without a key (degraded mode): complete() -> Err(NotConfigured), with
// no network call. No retries inside the adapter:
let completion = model
    .complete(Prompt::user("Summarise this in one line", 256))
    .await?;

Anthropic::from_env(http, clock) reads ANTHROPIC_API_KEY and ANTHROPIC_MODEL (default claude-opus-5) from the process environment (native/self-hosted). On Workers, read the secret from the venture’s Env and call Anthropic::new. The Clock is what lets the adapter read the HTTP-date form of Retry-After (issue #278); without it a date-form 429 would read as “retry now”.

With Prompt::json_schema set, the request declares a single tool whose input schema is the caller’s schema and forces it with tool_choice: {type: "tool"}; the tool input comes back as Completion::json. Without it, no tools are sent and json stays None.

With Prompt::tools set (issue #665), the request carries a tools array — each spec’s parameters as the tool’s input_schema — and, when the caller steered it, a tool_choice (Auto → {type: "auto"}, None → {type: "none"}, Required → {type: "any"}, Tool(n) → {type: "tool", name: n}; omitted when unset). The answer’s tool_use blocks come back as Completion::tool_calls, and a turn built with Turn::assistant_tool_calls or Turn::tool_results is sent as the content-block array the Messages API expects — every other turn keeps the bare-string content it always sent. The model’s text may be empty when it only calls tools, and that is not an error. A tool call cut off by max_tokens is refused as Rejected rather than handed on with partial arguments. A prompt that sets both json_schema and tools is refused as Rejected before any request: the schema path already declares its own forced tool, so the two would collide.

§Streaming

stream (issue #859) sends the same request with "stream": true and answers cratefield_core::TextDeltas straight off the Messages event stream: text and reasoning as they are produced, each tool call from its start through its argument chunks to the finished call, running usage, and one Finish naming the wire’s stop_reason. A finished call is exactly the cratefield_core::ToolCall complete returns for the same answer — one shared conversion — and a CompletionBuilder over the stream equals complete’s completion, field for field. The stream owns the response body, so dropping it cancels the upstream exchange. Two divergences: a json_schema prompt still completes and decomposes (structured output is a buffered feature), and a tool call cut off at max_tokens ends the stream with the same Rejected truncation refusal complete raises — never a half-runnable call.

With a Turn::user_parts message (issue #628), the turn goes as the Messages API content-block array: each Part::Text as a text block and each Part::Image as an inline image block, in order. supports reports Capability::Images for every current Claude model, and the port’s bounds are enforced locally before any request, an over-limit prompt refused as TextModelError::ImageLimit rather than paid for.

Every completion reports token usage. The Messages API splits the prompt across input_tokens, cache_creation_input_tokens and cache_read_input_tokens when prompt caching is in play; the adapter sums them into the one input_tokens total the port carries, and reports the read half alone as cached_input_tokens — None when the answer carried no cache fields at all.

Error mapping (to cratefield_core::TextModelError): 429 → Transient { retry_after } (from the Retry-After header, both RFC 9110 forms), any 5xx — including Anthropic’s 529 overloaded_error → Transient { retry_after: None }, 400/422 → Rejected (malformed request, filtered content), 401/403 → Rejected (a bad key is not worth retrying), any other 4xx → Rejected. Everything that stops the call from completing maps to Transport: timeout, blocked destination, a body over the response cap, a success body that does not parse, a forced JSON call that comes back without its tool block. No error Display ever includes the API key, and neither the prompt nor the completion text is ever logged — a prompt is user content.

§The caps are the design

The adapter attaches the port’s widest HttpPolicy to every request: a 30 s deadline — the HttpClient ceiling, up from that port’s 10 s default, because a model call’s normal case sits past it — and the port’s 4 MiB response cap, which HttpPolicy::clamped lets a caller tighten but never raise. What that implies for a caller: keep max_tokens modest (an oversized completion is refused as Transport, not truncated), make one model call per pipeline stage, and let a timeout surface as Transport for the caller to judge — never a retry inside the adapter.


MIT. Built in the open for Cratefield, a Factory Zero venture.

Structs§

Anthropic
The Anthropic TextModel over POST https://api.anthropic.com/v1/messages.

Constants§

DEFAULT_MODEL
The model Anthropic::from_env uses when ANTHROPIC_MODEL is unset: the current most-capable Claude 5 model id. The Messages API takes exact ids — no date suffixes. Overriding it has a catch: the JSON path forces tool_choice: {"type":"tool"}, and a model that rejects a forced tool choice makes every json_schema call come back a 400 TextModelError::Rejected.