anthropicapi

package
v0.12.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 25, 2026 License: Apache-2.0 Imports: 24 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DecodeCountTokensRequest

func DecodeCountTokensRequest(req *http.Request) (codec.DecodedRequest, error)

DecodeCountTokensRequest decodes a POST /v1/messages/count_tokens request using the same semantic message/tool/system/image/thinking decoding as DecodeRequest. The count_tokens wire body has the same shape as a Messages request minus max_tokens/stream (both are optional on decode already), so it shares decodeMessagesBody. Streaming is always false: count_tokens has no streaming mode. As with decodeMessagesRequest, Request.Model stays unresolved.

func DecodeResponse

func DecodeResponse(body []byte) (*inference.Response, error)

DecodeResponse parses a non-streaming Anthropic Messages API response body into a provider-neutral *inference.Response. An `error`-type envelope (a 200 body carrying {"type":"error",...}) is surfaced as a *failure.APIError. An empty content array is a valid response (e.g. a refusal or a pure stop), not an error.

func EncodeRequest

func EncodeRequest(req inference.Request, stream bool) ([]byte, error)

EncodeRequest converts a provider-neutral inference.Request into an Anthropic `POST /v1/messages` JSON body. stream=true adds "stream":true to the body. Request.System becomes the top-level `system` field; any SystemMessage in the thread is folded into it (Anthropic has no in-thread system role).

func MatchCountTokensRequest

func MatchCountTokensRequest(req *http.Request) bool

MatchCountTokensRequest reports whether req is a POST /v1/messages/count_tokens request. It is not part of codec.ServerCodec: the count_tokens endpoint is a separate, narrower auxiliary the gateway wires up on its own route, composing this decode helper with its own target resolution and a contextcount.ContextCounter.

func WriteCountTokensResponse

func WriteCountTokensResponse(w http.ResponseWriter, inputTokens int) error

WriteCountTokensResponse writes Anthropic's count_tokens response shape given an already-computed token count. It does not compute the count itself: the gateway resolves the target model and calls a contextcount.ContextCounter, then calls this helper with the result.

Types

type Codec

type Codec struct{}

Codec is the Anthropic Messages API wire dialect expressed as an codec.Codec (and, via DecodeStream, an codec.StreamingCodec). It is stateless (an empty struct with value-receiver methods), so one value is safely shared across goroutines: the transport owns HTTP mechanics, the Codec owns the JSON body, per-event semantics, and SSE stream decoding. The methods delegate to package-level free functions so the method surface and the free surface cannot diverge.

func (Codec) DecodeEvent

func (Codec) DecodeEvent(event []byte) ([]content.Chunk, error)

DecodeEvent decodes one already-de-framed SSE event payload into the chunk(s) it yields. It is stateless, and tolerant of every uninteresting or unknown but VALID event (message_start, content_block_stop, message_delta, message_stop, ping, unrecognized future types, …), which return (nil, nil) — a skip, not an error. Malformed JSON is the one intolerant case: it yields a *StreamEventDecodeError, because a truncated frame is indistinguishable from a dropped one and skipping it would let a lossy stream report success. Cross-event assembly (concatenating a tool call's start + input_json_delta fragments into a ToolUseBlock) happens downstream in the stream accumulator, not here.

func (Codec) DecodeRequest

func (Codec) DecodeRequest(req *http.Request) (codec.DecodedRequest, error)

DecodeRequest decodes a matched POST /v1/messages request into a codec.DecodedRequest, delegating to the free decodeMessagesRequest.

func (Codec) DecodeResponse

func (Codec) DecodeResponse(body []byte) (*inference.Response, error)

DecodeResponse parses a non-streaming Anthropic Messages response body, delegating to the free DecodeResponse.

func (Codec) DecodeStream

func (Codec) DecodeStream(resp *http.Response) (*stream.StreamReader[content.Chunk], error)

DecodeStream frames a successful Anthropic Messages streaming response with wire/sse and maps each frame through the codec's per-event decode logic. The message_stop marker authorizes the terminal result but yields no chunk; the body's natural EOF ends the transport stream. Because that EOF is indistinguishable from a dropped connection, a body that reaches it without message_stop fails with a *StreamDecodeError rather than reporting a clean, truncated success. It owns resp.Body: the returned reader's Close closes it.

func (Codec) EncodeRequest

func (Codec) EncodeRequest(req inference.Request, mode codec.RequestMode) (codec.EncodedRequest, error)

EncodeRequest builds the Anthropic Messages request: a JSON body reader plus the application/json content type as an EncodedRequest. RequestModeStream sets "stream":true in the body, every other mode omits it.

func (Codec) MatchRequest

func (Codec) MatchRequest(req *http.Request) bool

MatchRequest reports whether req is a POST /v1/messages request.

func (Codec) OpenStream

func (Codec) OpenStream(w http.ResponseWriter) (codec.StreamEncoder, error)

OpenStream begins the native Anthropic Messages streaming response and returns its request-scoped StreamEncoder, delegating to the free openMessagesStream.

func (Codec) WriteError

func (Codec) WriteError(w http.ResponseWriter, err error)

WriteError encodes err as the native Anthropic error envelope, delegating to the free writeMessageError.

func (Codec) WriteResponse

func (Codec) WriteResponse(w http.ResponseWriter, resp *inference.Response) error

WriteResponse encodes a complete inference.Response as the native Anthropic Messages API non-streaming response, delegating to the free writeMessageResponse.

type ConversationCollisionError

type ConversationCollisionError struct {
	Reason string
}

ConversationCollisionError reports adjacent neutral turns that cannot be combined without violating Anthropic's tool-result ordering rules.

func (*ConversationCollisionError) Error

type DuplicateKeyError

type DuplicateKeyError struct {
	Key string
}

DuplicateKeyError reports a request body with a duplicate JSON object member name. encoding/json silently takes the last occurrence; this codec rejects the request instead so a client cannot smuggle a semantically different value past a naive review of the first occurrence.

func (*DuplicateKeyError) Error

func (e *DuplicateKeyError) Error() string

type EmptyTextBlockError

type EmptyTextBlockError struct{}

EmptyTextBlockError is returned by the encoder when a text content block carries no text. Anthropic's RequestTextBlock declares `text` required with minLength 1, so an empty text block is not a representable wire shape: it would encode to the invalid `{"type":"text"}` and draw an HTTP 400. The codec refuses it here instead, fail-secure like UnsupportedBlockError, so the defect surfaces at the call site rather than as an opaque provider rejection.

func (*EmptyTextBlockError) Error

func (e *EmptyTextBlockError) Error() string

type ForeignThinkingSignatureError

type ForeignThinkingSignatureError struct {
	// Format is the label carried by the signature, or "" when it carries none.
	Format string
}

ForeignThinkingSignatureError reports a reasoning signature this dialect cannot prove it minted, either because another dialect's label is attached (Format names it) or because no label is attached at all (Format is empty).

It is a hard error rather than a degrade, which is the one design decision in this type worth stating. Probed against api.anthropic.com with claude-haiku-4-5 on 2026-08-13: a verbatim signature is accepted; a signature with eight characters changed draws `messages.N.content.0: Invalid "signature" in "thinking" block` (HTTP 400); and an EMPTY signature draws that same 400. So both available degrades lose. Forwarding a foreign signature sends a request that is certain to be rejected, and stripping it sends an unsigned thinking block that is equally certain to be rejected — while destroying the only copy of the continuation state on the way. Failing here costs the same turn and names the cause.

The realistic source is not an attacker. Bedrock Converse and the Messages API are two endpoints for the same Claude models; their thinking blocks are structurally identical and their signatures are not interchangeable, so a session moved between them carries a block nothing else can tell apart.

func (*ForeignThinkingSignatureError) Error

type InvalidToolNameError

type InvalidToolNameError struct {
	Name   string
	Reason string
}

InvalidToolNameError is returned by the encoder when a tool name cannot satisfy Anthropic's ^[a-zA-Z0-9_-]{1,128}$. Tool servers, MCP ones in particular, routinely publish names containing "." or "/".

func (*InvalidToolNameError) Error

func (e *InvalidToolNameError) Error() string

type InvalidToolSchemaError

type InvalidToolSchemaError struct {
	Name   string
	Reason string
}

InvalidToolSchemaError is returned by the encoder when a tool's input schema is not a JSON object, which Anthropic's InputSchema requires.

func (*InvalidToolSchemaError) Error

func (e *InvalidToolSchemaError) Error() string

type InvalidToolUseIDError

type InvalidToolUseIDError struct {
	ID     string
	Reason string
}

InvalidToolUseIDError is returned by the encoder when a tool_use id or a tool_result tool_use_id cannot satisfy Anthropic's ^[a-zA-Z0-9_-]+$. Identifiers are minted by whichever provider issued the call, so a conversation replayed from a dialect with a wider class (Bedrock Converse permits "." and ":") can carry one Anthropic rejects. An empty id is the worse case: it is omitempty on the wire, so it does not travel as "" — the required property simply disappears.

func (*InvalidToolUseIDError) Error

func (e *InvalidToolUseIDError) Error() string

type SamplingRangeError

type SamplingRangeError struct {
	Field string
	Value float64
}

SamplingRangeError is returned by the encoder when temperature or top_p falls outside the [0, 1] interval Anthropic declares. The shared model.Sampling vocabulary is wider (an OpenAI-shaped temperature runs to 2), so switching a session onto an Anthropic model can carry a value the API refuses.

func (*SamplingRangeError) Error

func (e *SamplingRangeError) Error() string

type ServerDecodeError

type ServerDecodeError struct {
	Reason string
	Detail string
}

ServerDecodeError reports a native Messages request body this codec cannot decode into the provider-neutral vocabulary: malformed shape, a missing required field, or a recognized-but-unsupported feature (e.g. a tool_choice variant or thinking mode the neutral Request cannot represent). Reason is a short machine-checkable diagnostic code; Detail elaborates for logs/messages.

func (*ServerDecodeError) Error

func (e *ServerDecodeError) Error() string

type StreamAPIError

type StreamAPIError struct {
	Type    string
	Message string
}

StreamAPIError reports an Anthropic error event received after a streaming request crossed the successful HTTP-status boundary. It retains only the provider's structured error type and message, never the raw response frame.

func (*StreamAPIError) Error

func (e *StreamAPIError) Error() string

type StreamDecodeError

type StreamDecodeError struct {
	Reason string
	Err    error
}

StreamDecodeError reports a Messages stream that is framed and parseable but structurally wrong — currently only a body that reaches EOF without the message_stop event the MessageStreamEvent union ends with, which means the answer was truncated in flight. It never includes the raw provider payload in its diagnostic. Named and shaped after the equivalent type in codec/bedrockconverse and codec/geminiapi. It lives here rather than in errors.go because the gate it serves is the only thing that raises it.

func (*StreamDecodeError) Error

func (e *StreamDecodeError) Error() string

func (*StreamDecodeError) Unwrap

func (e *StreamDecodeError) Unwrap() error

type StreamEventDecodeError

type StreamEventDecodeError struct{ Err error }

StreamEventDecodeError reports malformed JSON inside an otherwise successfully framed Anthropic Messages stream. A truncated or corrupt frame is indistinguishable from a dropped one, so it is a terminal decode failure rather than a skip: swallowing it would let a stream that lost content still report an authoritative clean success. Unknown-but-VALID event types remain tolerant skips — only unparseable bytes reach here.

func (*StreamEventDecodeError) Error

func (e *StreamEventDecodeError) Error() string

func (*StreamEventDecodeError) Unwrap

func (e *StreamEventDecodeError) Unwrap() error

type StreamTerminatedError

type StreamTerminatedError struct{}

StreamTerminatedError is returned by StreamEncoder.WriteChunk, Finish, or Fail once the stream has already been terminated by a prior Finish or Fail call, per the single-termination-ownership rule in codec.StreamEncoder.

func (*StreamTerminatedError) Error

func (e *StreamTerminatedError) Error() string

type ThinkingBudgetError

type ThinkingBudgetError struct {
	Model     string
	MaxTokens int
}

ThinkingBudgetError is returned by the encoder when the budget dialect has no legal budget_tokens available for the request's max_tokens.

Two constraints bound the field and only one of them is in the schema. ThinkingConfigEnabled declares budget_tokens with minimum 1024; Anthropic documents separately that it must be less than max_tokens, which is a cross-field rule no JSON Schema keyword in the derived document expresses — the gate was measured accepting budget_tokens 99999 against max_tokens 1024. Both together make max_tokens 1025 the smallest cap that admits any legal budget, so a smaller cap is refused here, where the diagnostic can name the field, rather than at the provider.

func (*ThinkingBudgetError) Error

func (e *ThinkingBudgetError) Error() string

type UndeclaredThinkingDialectError

type UndeclaredThinkingDialectError struct {
	Model string
}

UndeclaredThinkingDialectError is returned by the encoder when a request asks for reasoning from a model advertised as thinking-capable whose Caps.ThinkingDialect the catalogue never declared.

Anthropic serves two on-modes concurrently and rejects the wrong one with an HTTP 400 rather than degrading: measured on 2026-08-13, claude-haiku-4-5 answers `{"type":"adaptive"}` with "adaptive thinking is not supported on this model", and claude-sonnet-5 answers `{"type":"enabled","budget_tokens":N}` with "\"thinking.type.enabled\" is not supported for this model. Use \"thinking.type.adaptive\" and \"output_config.effort\"". Nothing in the request document distinguishes them, so with no declared dialect the encoder has a coin flip between two bodies, one of which is provably rejected.

It therefore fails closed and names the model, because that is the piece of information the fix needs: a provider 400 says the request was wrong, this says which catalogue row is incomplete. The provider's WithThinking escape hatch remains available for a caller who knows better than the catalogue.

func (*UndeclaredThinkingDialectError) Error

type UnsupportedAudioError

type UnsupportedAudioError struct {
	MediaType string
}

UnsupportedAudioError is returned by the encoder for an audio content block.

This is a hard limitation of the format, not a gap in this codec: the Anthropic Messages API document declares no audio content block in any request or response shape, and the substring "audio" does not occur anywhere in it. An audio block therefore has no wire form to encode toward, and there is nothing to route it to.

It gets a typed error of its own rather than the generic UnsupportedBlockError because this path is reachable in ordinary operation: an MCP tool returning an audio result produces a content.AudioBlock that the harness clones and persists, so the failure surfaces on every subsequent turn of that session and the message has to say why.

func (*UnsupportedAudioError) Error

func (e *UnsupportedAudioError) Error() string

type UnsupportedBlockError

type UnsupportedBlockError struct {
	Block string
}

UnsupportedBlockError is returned by the encoder when a content block has a concrete type the Anthropic Messages API dialect does not model (e.g. audio or document blocks). Block holds the Go type name for diagnosis. Callers may errors.As to detect an unencodable block rather than string-matching.

func (*UnsupportedBlockError) Error

func (e *UnsupportedBlockError) Error() string

type UnsupportedChunkError

type UnsupportedChunkError struct {
	Chunk string
}

UnsupportedChunkError is returned when a content.Chunk has a concrete type this dialect's stream encoder does not model. content.Chunk is a sealed interface, so this only guards against future variants added to the vocabulary.

func (*UnsupportedChunkError) Error

func (e *UnsupportedChunkError) Error() string

type UnsupportedConversationError

type UnsupportedConversationError struct {
	Conversation string
}

UnsupportedConversationError is returned by the encoder when a conversation turn has a concrete type outside the closed content.Conversation union the dialect maps (user / assistant / tool-result / system). Conversation holds the Go type name for diagnosis.

func (*UnsupportedConversationError) Error

type UnsupportedDocumentError

type UnsupportedDocumentError struct {
	Reason string
}

UnsupportedDocumentError is returned by the encoder when a document block has no legal RequestDocumentBlock form. Reason names the violated constraint.

The neutral content.DocumentBlock is wider than Anthropic's source union in both directions that matter. Base64PDFSource.media_type is const "application/pdf" and PlainTextSource.media_type is const "text/plain", so a DOCX payload or a markdown body has no representable source at all — and re-labelling either one to satisfy the const would forward a document the caller never described. RequestDocumentBlock.title is additionally capped at 500 characters, which a file name can exceed.

func (*UnsupportedDocumentError) Error

func (e *UnsupportedDocumentError) Error() string

type UnsupportedEffortError added in v0.12.0

type UnsupportedEffortError struct {
	Effort string
}

UnsupportedEffortError reports a neutral reasoning effort for which the Anthropic Messages wire contract has no symbolic effort member.

func (*UnsupportedEffortError) Error added in v0.12.0

func (e *UnsupportedEffortError) Error() string

type UnsupportedImageMediaTypeError

type UnsupportedImageMediaTypeError struct {
	MediaType string
}

UnsupportedImageMediaTypeError is returned by the encoder when an inline image block carries a media type outside Anthropic's Base64ImageSource enum (image/jpeg, image/png, image/gif, image/webp). The shared content.MediaType vocabulary is wider — content.MediaTypeImageSVG has no Anthropic equivalent — so this is a representable Looprig block with no representable wire form, refused here for the same reason as EmptyTextBlockError.

func (*UnsupportedImageMediaTypeError) Error

type UnsupportedRefusalError

type UnsupportedRefusalError struct{}

UnsupportedRefusalError is returned by the encoder for a content.RefusalBlock.

This is a hard limitation of the format, not a gap in this codec. Anthropic models a refusal as RESPONSE metadata — stop_reason "refusal" alongside a RefusalStopDetails object carrying a category and an explanation — and the Messages API request document declares no refusal content block in any position. A refusal therefore has no wire form to encode toward, and the alternatives are all worse than failing: sending it as `text` shows the model its own decline quoted back as something it said (the exact defect content.RefusalBlock exists to remove), and dropping it silently loses the fact that the turn was declined.

Like UnsupportedAudioError it gets a typed error of its own rather than the generic UnsupportedBlockError, and for the same reason: the path is reachable in ordinary operation. A refused turn is stored in session history, so the failure surfaces on every subsequent turn of that session and the message has to say why.

func (*UnsupportedRefusalError) Error

func (e *UnsupportedRefusalError) Error() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL