Add Anthropic (Claude) API backend#30
Merged
Merged
Conversation
refac now defaults to the Claude Messages API (model claude-opus-4-8); OpenAI remains available via `provider = "openai"`. New `src/anthropic.rs` talks to the REST API with reqwest (no official Rust SDK): x-api-key + anthropic-version headers, top-level `system`, and required `max_tokens`. It adapts refac's flat message list to Anthropic's shape — lifts the system prompt out of messages and merges the consecutive user turns to satisfy user/assistant alternation — and marks the static system prompt + few-shot examples `cache_control: ephemeral` so repeat calls only pay for the varying input. Config gains `provider`, optional `model` (defaulted per provider), and `max_tokens`; secrets hold either/both keys. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Some few-shot samples have an empty `selected`; Anthropic rejects empty text content blocks (OpenAI tolerated them), so skip empty messages when building the request. Add a REFAC_DEBUG env that dumps the request JSON. Verified end-to-end: 'Me like toast.' / 'Correct grammar.' -> 'I like toast.'
bddap
reviewed
Jun 2, 2026
bddap
reviewed
Jun 2, 2026
…intln Per review: drop the ad-hoc REFAC_DEBUG env-gated eprintln and use the existing tracing setup, gated by the subscriber's level filter like the rest of the code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bddap
reviewed
Jun 2, 2026
bddap
reviewed
Jun 2, 2026
bddap
reviewed
Jun 2, 2026
bddap
reviewed
Jun 2, 2026
Per review: `provider` is now optional. When it isn't set explicitly (config file or REFAC_PROVIDER), resolve it from which keys are configured — only an OpenAI key -> OpenAI; Anthropic-only, both, or neither -> lean Anthropic. An explicit choice still wins. Adds resolve_provider() + tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bddap
reviewed
Jun 2, 2026
bddap
reviewed
Jun 2, 2026
Per review: a `max_tokens` config knob is a representable invalid state — `provider = "openai"` + `max_tokens` does nothing (the OpenAI path ignores it). Remove it. The Messages API still requires `max_tokens`, so hardcode it as a constant in the anthropic module. Can be reintroduced later with real support in both backends. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bddap
reviewed
Jun 2, 2026
Per review: - REFAC_PROVIDER no longer silently falls back to Anthropic on an unrecognized value — it errors with the accepted options. - OpenAI default model o1 -> gpt-5.5 (current flagship). - Drop a WHAT doc-comment on model(). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bddap
reviewed
Jun 2, 2026
Per review: `refac login` now accepts `--provider anthropic|openai`; without it, the user selects a provider via a dialoguer prompt rather than the key-inference heuristic (you're choosing which key to add). Provider derives clap::ValueEnum. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Owner
|
we have a merge conflict |
Per review: the prompt-cache breakpoint was inferred from message structure (last assistant turn), which baked in refac's usage pattern. Take the static prefix length from the caller instead — refactor() passes chat_prefix().len(), the part that's fixed across calls. build_request places the breakpoint at that boundary and never groups a varying turn into the cached prefix. Also corrected stale comments (alternation is no longer required; merging is just grouping). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts: # Cargo.toml
Contributor
Author
bddap
reviewed
Jun 2, 2026
bddap
reviewed
Jun 2, 2026
Per review: that claim could go stale; omit it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bddap
reviewed
Jun 2, 2026
bddap
reviewed
Jun 2, 2026
bddap
reviewed
Jun 2, 2026
Per review: - module doc was tmi / drifting — cut to a one-liner. - removed the verbose MAX_TOKENS comment (kept the const; the API requires the field). - replaced the stringly-typed content/cache `type` tags with enums (BlockType, CacheType). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bddap
reviewed
Jun 2, 2026
bddap
reviewed
Jun 2, 2026
The match arms say it; the doc comment carries the why. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bddap
reviewed
Jun 2, 2026
Per review. Also drop the WHAT comments restating the test assertions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bddap
reviewed
Jun 2, 2026
bddap
reviewed
Jun 2, 2026
Per review: drop the mirrored label array; map choices through Debug instead. Also drop a comment per suggestion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bddap
reviewed
Jun 2, 2026
Message is now `{ role: Role, fields: Vec<String>, cache: bool }` instead of a
single-string OpenAI-shaped struct. A turn carries one or more text fields (a
transform user turn is [selected, transform]); `cache` marks the last turn of a
static prefix.
- anthropic: each field -> a content block; empty fields render as `(empty)`
(the API rejects empty text); `cache` -> cache_control on the turn's last
block. Drops the old consecutive-same-role merge and the cache_prefix_len
parameter — the data model carries both now.
- openai: its own `OpenAiMessage` wire type; the adapter joins a turn's fields
and drops `cache`.
- prompt: chat_prefix marks its last message cached.
Live-tested against Anthropic: a normal transform and an empty-selected
generate ("find .") both work. 5 unit tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per review: joining a turn's fields into one string blurs the selected text into the transform with no reliable separator. Send each field as a separate message instead (same as the pre-refactor OpenAI path). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bddap
reviewed
Jun 2, 2026
~$2.00 of Opus 4.8 output at $25/M. Verified the API accepts it for the model. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bddap
reviewed
Jun 2, 2026
bddap
reviewed
Jun 2, 2026
bddap
reviewed
Jun 2, 2026
Per review:
- CacheControl and the content block are internally-tagged unions; use
`#[serde(tag = "type")]` enums instead of a struct with a manual `type` field.
- message roles are `Role` enums (anthropic ChatMessage and OpenAiMessage), not
strings; Role gains a lowercase serde repr and `as_str` goes away.
- the response block is a tagged enum (`Text { text } | Other`) rather than a
stringly `kind` compared against "text".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bddap
reviewed
Jun 2, 2026
api.rs is now just the provider-agnostic core (`Message`, `Role`). OpenAI's wire types and `complete` move to a new openai.rs; the Anthropic-only `field_or_placeholder` moves into anthropic.rs (and OpenAI no longer applies it — it tolerates empty content). Also drops the unused OpenAI edits-API types (EditRequest/EditResponse/Choice), which were dead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bddap
reviewed
Jun 2, 2026
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bddap
reviewed
Jun 2, 2026
bddap
reviewed
Jun 2, 2026
Drop the removed `max_tokens` config setting, note provider is inferred from keys when unset, fix the API-key link to Anthropic (the default), and reword "still supported" -> "also supported". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bddap
reviewed
Jun 2, 2026
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Owner
|
Almost ready. Do a sub-agent reviewer loop, then regen the examples in readme using latest opus. |
Review-loop findings: - the saved secrets file held the API key but was created world-readable (default 0644); create/force it 0600 on unix. - LogEntry.provider was a Debug-formatted string; use the Provider enum so the log matches the config-file casing and stays typed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
Author
|
Done — review loop + example regen. Sub-agent review loop (3 agents: correctness, security, design/taste) over the full diff. Fixes landed in eca0c21:
No correctness or injection bugs found. User text is serialized via serde (never concatenated into JSON), the key never hits logs or error bodies. Judgment calls I left (flagging, not blocking — your call):
README examples regenerated with Opus 4.8 (610e2db) — all 7 are fresh real outputs. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hooks refac into Anthropic via API key, as requested.
What changed
claude-opus-4-8). OpenAI still supported; default modelgpt-5.5.provider(config orREFAC_PROVIDER) wins; unset → inferred from which API keys are configured (only OpenAI → OpenAI; Anthropic-only/both/neither → Anthropic).refac login [--provider anthropic|openai], else an interactive pick.Message:{ role: Role, fields: Vec<String>, cache: bool }. A turn carries one or more text fields (a transform user turn is[selected, transform]);cachemarks the last turn of a static prefix. Each backend adapts it:src/anthropic.rs):/v1/messagesviareqwest; each field → a content block; empty fields render as(empty)(the API rejects empty text);cache→cache_control: ephemeralon the turn's last block (caches system + few-shot, so repeat calls only pay for the appended input); System → top-levelsystem.src/main.rs): its ownOpenAiMessagewire type; the adapter joins a turn's fields and dropscache.provider(optional),model(optional, defaulted per provider). Secrets hold either/both keys; honorANTHROPIC_API_KEY/OPENAI_API_KEY.Testing
cargo test— 5 passing (request-builder shape + cache placement, empty-field(empty)rendering, no-system, provider inference + override).me like toast/correct grammar→I like toast.) and an empty-selected generate (""/command to list files recursively→find .).🤖 Generated with Claude Code