Documentation
¶
Overview ¶
Package translate converts Anthropic Messages API requests/responses to and from OpenAI ChatCompletions shape. Pure: no I/O, no upstream calls. Adapter-specific quirks live in internal/adapter/<provider>.
Index ¶
- Constants
- Variables
- func AnthropicErrorJSON(errType, message string) []byte
- type AnthropicBlock
- type AnthropicImageSource
- type AnthropicMessage
- type AnthropicRequest
- type AnthropicResponse
- type AnthropicThinkingConfig
- type AnthropicTool
- type AnthropicUsage
- type DeepSeekThinkingConfig
- type OpenAIChoice
- type OpenAIContentPart
- type OpenAIImageURL
- type OpenAIMessage
- type OpenAIRequest
- type OpenAIResponse
- type OpenAITool
- type OpenAIToolCall
- type OpenAIToolCallBody
- type OpenAIToolFunction
- type OpenAIUsage
- type SSEEvent
- type Translator
Constants ¶
const ( ErrTypeInvalidRequest = "invalid_request_error" ErrTypeAuthentication = "authentication_error" ErrTypePermission = "permission_error" ErrTypeRateLimit = "rate_limit_error" ErrTypeAPI = "api_error" ErrTypeOverloaded = "overloaded_error" )
Anthropic error "type" taxonomy — the canonical wire strings for the inner `error.type` of the Anthropic error envelope. Single source of truth, shared by the server (client-side errors) and the OpenAI-dialect translator's upstream-error re-wrap (FromUpstreamError). The identity translator never uses them: it forwards the native-Anthropic error body verbatim.
Variables ¶
var ( ErrUpstreamMalformed = errors.New("upstream returned malformed JSON") ErrBackTranslation = errors.New("back-translation failed") )
Translator error categories. The server maps these to HTTP status: ErrBackTranslation (a structurally-valid upstream response shim can't convert) is a 500; a malformed upstream body is a 502 (gateway problem).
Functions ¶
func AnthropicErrorJSON ¶ added in v0.3.1
AnthropicErrorJSON marshals an Anthropic-shaped error envelope. errType is one of the ErrType* constants; message is human-readable. Marshalling two strings cannot fail in practice — the fallback is defensive only, never reached.
Types ¶
type AnthropicBlock ¶
type AnthropicBlock struct {
Type string `json:"type"`
// type: "text"
Text string `json:"text,omitempty"`
// type: "image"
Source *AnthropicImageSource `json:"source,omitempty"`
// type: "tool_use"
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Input json.RawMessage `json:"input,omitempty"`
// type: "tool_result"
ToolUseID string `json:"tool_use_id,omitempty"`
// Content on tool_result is either a string or a content[] of text/image
// blocks. We keep RawMessage for the boundary.
ToolContent json.RawMessage `json:"content,omitempty"`
IsError bool `json:"is_error,omitempty"`
// type: "thinking" (Stage 2.6c). Thinking carries the model's reasoning
// text; Signature is constant "shim-passthrough-v1" — shim doesn't
// verify on roundtrip and DeepSeek discards it. See README "Errors and
// debugging" for the no-verification posture rationale.
Thinking string `json:"thinking,omitempty"`
Signature string `json:"signature,omitempty"`
}
AnthropicBlock is a tagged union of content block types: text, image, tool_use, tool_result, and thinking. Stage 2.6c lifts the assistant-side thinking 501; user-side thinking is still rejected because the Anthropic spec only produces thinking on assistant turns.
type AnthropicImageSource ¶
type AnthropicImageSource struct {
Type string `json:"type"` // "base64" | "url"
MediaType string `json:"media_type"` // e.g. "image/png"
Data string `json:"data,omitempty"`
URL string `json:"url,omitempty"`
}
AnthropicImageSource carries either base64 data with a media_type or a URL. Anthropic supports both; OpenAI takes a single URL form (data: for base64).
type AnthropicMessage ¶
type AnthropicMessage struct {
Role string `json:"role"`
Content json.RawMessage `json:"content"`
}
AnthropicMessage holds a role and content. Content is either a string or an array of typed blocks — we keep RawMessage for the boundary and parse in helpers.
type AnthropicRequest ¶
type AnthropicRequest struct {
Model string `json:"model"`
Messages []AnthropicMessage `json:"messages"`
System json.RawMessage `json:"system,omitempty"` // string | content[]
MaxTokens int `json:"max_tokens"`
Stream bool `json:"stream,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
TopP *float64 `json:"top_p,omitempty"`
StopSequences []string `json:"stop_sequences,omitempty"`
Tools []AnthropicTool `json:"tools,omitempty"`
ToolChoice json.RawMessage `json:"tool_choice,omitempty"`
Thinking *AnthropicThinkingConfig `json:"thinking,omitempty"`
}
AnthropicRequest mirrors the documented Anthropic Messages request body. Fields shim doesn't act on are omitted (e.g. metadata, service_tier, top_k); json.Unmarshal DROPS them, so re-marshalling this struct loses them. The translating path only needs the modelled fields; the identity (passthrough) translator forwards the original request bytes verbatim to avoid the drop.
type AnthropicResponse ¶
type AnthropicResponse struct {
ID string `json:"id"`
Type string `json:"type"` // always "message"
Role string `json:"role"` // always "assistant"
Model string `json:"model"`
Content []AnthropicBlock `json:"content"`
StopReason string `json:"stop_reason"`
StopSequence *string `json:"stop_sequence,omitempty"`
Usage AnthropicUsage `json:"usage"`
}
AnthropicResponse is what we send back to Claude Code.
func OpenAIToAnthropic ¶
func OpenAIToAnthropic(resp *OpenAIResponse, originalModel string) (*AnthropicResponse, error)
OpenAIToAnthropic converts an OpenAI ChatCompletions response back to Anthropic Messages shape. Only choice[0] is consumed (Stage 0; n=1).
originalModel is the model name the client sent (passed through into the response so Claude Code sees what it asked for).
type AnthropicThinkingConfig ¶ added in v0.0.2
type AnthropicThinkingConfig struct {
Type string `json:"type"`
}
AnthropicThinkingConfig is the extended-thinking request param. Stage 2.6b only inspects Type ("enabled"|"disabled") — budget_tokens, display, and other fields get json-unmarshal-silently-dropped. Stage 2.6c adds them when there's a code path that uses them.
type AnthropicTool ¶
type AnthropicTool struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
InputSchema json.RawMessage `json:"input_schema"`
}
AnthropicTool is the tool declaration shape on the request side. Schema stays a RawMessage so we don't validate JSON Schema here.
type AnthropicUsage ¶
type AnthropicUsage struct {
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
}
AnthropicUsage mirrors Anthropic's usage shape.
type DeepSeekThinkingConfig ¶ added in v0.0.2
type DeepSeekThinkingConfig struct {
Type string `json:"type"`
}
DeepSeekThinkingConfig is the thinking-mode control param on DeepSeek's OpenAI-format endpoint. Stage 2.6b only ever emits {Type: "disabled"} — reasoning_effort and other fields land in 2.6c when there's a code path that uses them.
type OpenAIChoice ¶
type OpenAIChoice struct {
Index int `json:"index"`
Message OpenAIMessage `json:"message"`
FinishReason string `json:"finish_reason"`
}
OpenAIChoice is one completion option. We only consume index 0.
type OpenAIContentPart ¶
type OpenAIContentPart struct {
Type string `json:"type"` // "text" | "image_url"
Text string `json:"text,omitempty"`
ImageURL *OpenAIImageURL `json:"image_url,omitempty"`
}
OpenAIContentPart represents a single chunk within a multi-modal user message: text or image_url.
type OpenAIImageURL ¶
type OpenAIImageURL struct {
URL string `json:"url"`
}
OpenAIImageURL accepts http(s) URLs or data: URLs for inline base64.
type OpenAIMessage ¶
type OpenAIMessage struct {
Role string `json:"role"`
Content json.RawMessage `json:"content,omitempty"`
ToolCalls []OpenAIToolCall `json:"tool_calls,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"`
ReasoningContent string `json:"reasoning_content,omitempty"`
}
OpenAIMessage covers system, user, assistant, and tool roles. Content is string for simple cases or an array of parts when mixed text+image. ReasoningContent (Stage 2.6c) carries DeepSeek's thinking-mode reasoning text on assistant messages — emitted by upstream in responses, optionally echoed back in continuation requests so the upstream's "reasoning_content required on tool continuations" contract holds.
type OpenAIRequest ¶
type OpenAIRequest struct {
Model string `json:"model"`
Messages []OpenAIMessage `json:"messages"`
MaxTokens int `json:"max_tokens,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
TopP *float64 `json:"top_p,omitempty"`
Stop []string `json:"stop,omitempty"`
Tools []OpenAITool `json:"tools,omitempty"`
ToolChoice json.RawMessage `json:"tool_choice,omitempty"`
Stream bool `json:"stream,omitempty"`
Thinking *DeepSeekThinkingConfig `json:"thinking,omitempty"`
}
OpenAIRequest is the body we send to {adapter.BaseURL}/chat/completions.
func AnthropicToOpenAI ¶
func AnthropicToOpenAI(req *AnthropicRequest) (*OpenAIRequest, error)
AnthropicToOpenAI converts an Anthropic Messages request to an OpenAI ChatCompletions request. Tools-related fields are delegated to tools.go (step 5) but the wiring lives here.
Model name mapping is delegated to the Adapter — translate stays pure.
type OpenAIResponse ¶
type OpenAIResponse struct {
ID string `json:"id"`
Model string `json:"model"`
Choices []OpenAIChoice `json:"choices"`
Usage OpenAIUsage `json:"usage"`
}
OpenAIResponse is the upstream response we receive.
type OpenAITool ¶
type OpenAITool struct {
Type string `json:"type"` // always "function"
Function OpenAIToolFunction `json:"function"`
}
OpenAITool is the function-wrapped tool declaration.
type OpenAIToolCall ¶
type OpenAIToolCall struct {
ID string `json:"id"`
Type string `json:"type"` // always "function"
Function OpenAIToolCallBody `json:"function"`
}
OpenAIToolCall is one tool invocation on the assistant side.
type OpenAIToolCallBody ¶
type OpenAIToolCallBody struct {
Name string `json:"name"`
Arguments string `json:"arguments"` // JSON-encoded string per spec
}
OpenAIToolCallBody is the name + serialised JSON arguments.
type OpenAIToolFunction ¶
type OpenAIToolFunction struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Parameters json.RawMessage `json:"parameters"`
}
OpenAIToolFunction inner declaration.
type OpenAIUsage ¶
type OpenAIUsage struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
}
OpenAIUsage carries prompt/completion token counts.
type SSEEvent ¶
SSEEvent is one Anthropic-shaped SSE event, ready to wire-format. Marshal as `event: <Name>\ndata: <JSON(Data)>\n\n`.
func ToAnthropicSSE ¶
func ToAnthropicSSE(resp *OpenAIResponse, originalModel string) ([]SSEEvent, error)
ToAnthropicSSE converts a complete OpenAI response into an ordered sequence of Anthropic SSE events. Stage 0 emits the sequence in a single burst (buffer-then-restream) so callers see the canonical event order even though no real per-token streaming happens. Tool-use blocks emit one content_block_start + a single input_json_delta carrying the full arguments + content_block_stop.
type Translator ¶ added in v0.3.0
type Translator interface {
// ToUpstream builds the upstream request body. req is the parsed request;
// raw is the original inbound bytes (the identity translator forwards them
// verbatim, so client fields shim doesn't model survive). mappedModel is
// the upstream model name (from Adapter.MapModel). stopCapped reports how
// many stop sequences the dialect dropped, so the server emits the
// loud-fail metric/log (thesis-2) while the translator stays pure. The
// implementation owns the upstream `stream` flag for its dialect.
ToUpstream(req *AnthropicRequest, raw []byte, mappedModel string) (body []byte, stopCapped int, err error)
// FromUpstream converts a buffered (non-streaming) upstream response body
// into the Anthropic-shaped response BYTES the client should receive, plus
// normalized usage so the caller records the token delta without knowing
// the dialect. Returning bytes (not a struct) lets the identity translator
// forward the upstream body verbatim — lossless passthrough, no field-drop
// from re-encoding through shim's response struct.
FromUpstream(body []byte, originalModel string) (respBody []byte, usage AnthropicUsage, err error)
// StreamChunks returns a pull iterator over wire-ready Anthropic SSE event
// bytes (`event: <name>\ndata: <json>\n\n`). The caller writes + flushes
// each chunk. usage is populated by the time next reports ok=false. The
// implementation owns reading resp.Body; the caller has already gated on a
// 2xx upstream status.
StreamChunks(resp *http.Response, originalModel string) (next func() ([]byte, bool, error), usage *AnthropicUsage, err error)
// FromUpstreamError renders a NON-2xx upstream response for the client,
// returning the client-facing status and body. It is the error-path analog
// of FromUpstream: the dialect — not the handler — owns how an upstream
// failure surfaces. The OpenAI dialect re-classifies the status and emits a
// fresh Anthropic-shaped error envelope (the OpenAI body is OpenAI-shaped,
// may carry prompt content, and must not leak to an Anthropic client);
// identity forwards the upstream status + body verbatim (the upstream IS
// native Anthropic — its error is already correctly shaped, so re-wrapping
// would only lose fidelity). The server owns logging, measurement, and the
// write; this method owns only the dialect render.
FromUpstreamError(upstreamStatus int, upstreamBody []byte) (clientStatus int, clientBody []byte)
}
Translator converts between the Anthropic Messages wire format and one upstream transport dialect. shim supports two dialects: OpenAI ChatCompletions (DeepSeek, OpenAI — see AnthropicOpenAI) and identity (anthropic-passthrough — defined in the passthrough adapter).
Implementations are pure for request/response (no client I/O, no config, no state). StreamChunks reads the upstream response body (input) but never touches the client ResponseWriter — the server handler owns the writer and the flush loop. This keeps dialect knowledge out of the handler entirely.
func AnthropicOpenAI ¶ added in v0.3.0
func AnthropicOpenAI() Translator
AnthropicOpenAI returns the Translator for OpenAI-ChatCompletions-dialect upstreams. It wraps the pure AnthropicToOpenAI / OpenAIToAnthropic / ToAnthropicSSE functions. Streaming is buffer-then-restream: the upstream is driven non-streaming and the canonical SSE sequence is synthesized from the complete response (true per-token streaming for translating providers is out of v1.0 scope).
func Identity ¶ added in v0.3.0
func Identity() Translator
Identity returns the passthrough Translator for a native-Anthropic upstream: the request is forwarded verbatim and the response body is returned unchanged, with no dialect conversion. Used by the anthropic-passthrough adapter. Forwarding bytes verbatim is the point — shim must not drop Anthropic response fields it doesn't model.