Skip to content

feat(flare-proxy): registry-driven provider system with Anthropic/Gemini/Cloudflare AI Gateway support - #439

Merged
getappz merged 9 commits into
masterfrom
task/438
Aug 11, 2026
Merged

getappz merged 9 commits into
masterfrom
task/438

Conversation

@getappz

@getappz getappz commented Aug 10, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Replaces flare-proxy's hardcoded 3-provider ProviderKind match (NVIDIA NIM/OpenRouter/LM Studio) with a small provider-kind system: openai_compatible (any /chat/completions-shaped endpoint), anthropic (near-passthrough, since flare-proxy's own inbound wire format already is Anthropic Messages), and gemini (new Anthropic<->Gemini translator, scoped to text + tool_calls).
  • Adds native Cloudflare AI Gateway support (cf_gateway_anthropic, cf_gateway_openai) — a URL prefix in front of the same wire protocols already spoken, not a new protocol.
  • Provider data moved out of Rust into registry/providers.toml, loaded via embedded defaults + a locally-cached, on-demand-refreshed remote copy (mirrors appz-cli's registry/toolchains.toml pattern, scoped to this crate's single call site). Adding or fixing a provider is now a TOML edit published upstream, not a new agentflare release.
  • Populated the registry with 24 providers total: the original 3 + Anthropic + Gemini + 2 Cloudflare Gateway routes + 17 more well-known OpenAI-compatible endpoints (OpenAI, DeepSeek, Groq, Mistral, xAI, Together, Perplexity, Fireworks, DeepInfra, Moonshot, Novita, Hyperbolic, Cerebras, SambaNova, GitHub Models, Cohere, Ollama).
  • Zero new runtime dependencies added to Cargo.lock — toml, dirs, and ureq were all already resolved via the main agentflare crate; adding them to flare-proxy/Cargo.toml reuses those exact resolutions.

Design notes

  • providers/anthropic.rs's near-passthrough means native Anthropic upstream doesn't need shape_xlat translation at all — forward.rs proxies the byte stream unchanged, since input and output are both Anthropic's own SSE shape.
  • providers/gemini.rs + new shape_xlat::gemini_chunk_to_anthropic_sse/gemini_finish_stream handle Gemini's distinct candidates[0].content.parts streaming shape.
  • providers/registry.rs's registry() accessor (used by find/resolve/known_prefixes, and all tests) never touches the network — it only reads the embedded TOML plus whatever's already cached on disk, so cargo test stays fast and deterministic (79 tests in 0.02s). ensure_fresh() is the separate, explicit "download on demand" step, called once at proxy startup (router()): if the local cache (~/.agentflare/flare-proxy-providers-registry.json) is missing or older than 24h, it attempts one 3-second-timeout live fetch of the latest published registry/providers.toml and merges remote entries over the embedded ones by prefix. Any failure (offline, DNS, non-2xx, malformed TOML) silently falls back to the embedded copy.
  • The 17 broad-provider base_url values are each provider's documented OpenAI-compat endpoint as of this session — best-effort from public docs, not individually live-verified for every entry given how many there are. Flagged directly in the TOML file; a wrong one is a config fix via the refresh mechanism above, not a code change.

Test plan

  • cargo test -p flare-proxy — 79/79 pass
  • cargo clippy -p flare-proxy --all-targets -- -D warnings -A unsafe_code -A clippy::pedantic — clean
  • cargo fmt -p flare-proxy --check — clean
  • git diff Cargo.lock shows zero new crate entries
  • Manual smoke test against a couple of live providers (not done in this session — no API keys available in the sandbox)

Summary by CodeRabbit

  • New Features

    • Added Anthropic and Gemini provider support, including text generation, tool calls, system instructions, and streaming responses.
    • Added broad OpenAI-compatible provider coverage, including OpenAI, DeepSeek, Groq, Mistral, xAI, and others.
    • Added Cloudflare AI Gateway configurations with environment-based routing and authentication.
    • Provider settings can refresh from a registry while retaining built-in defaults when unavailable.
  • Bug Fixes

    • Improved upstream error handling and consistent error responses.
    • Added automatic request defaults and provider-specific authentication headers.
    • Improved streaming translation and usage reporting across supported providers.

…ro new deps (item #438)

Replaces the hardcoded 3-provider ProviderKind match (NvidiaNim/OpenRouter/
LmStudio) with a small provider-kind registry: OpenAiCompatible (covers
NVIDIA NIM, OpenRouter, LM Studio, and any future OpenAI-compatible
endpoint via config alone), Anthropic, and Gemini.

Patterns vendored (not depended on) from litellm-rust (avivsinai, MIT) --
see items #436 (rig-core, rejected: streaming not raw-JSON-shaped) and
#437 (litellm-rs rejected for dependency weight; litellm-rust's design is
the reference). Zero new Cargo.toml/Cargo.lock entries: reuses reqwest/
serde_json/futures already in flare-proxy instead of importing an SSE
macro crate, an error crate, or a trait-object abstraction.

- providers/openai_compat.rs: header/auth wiring only -- request/response
  translation stays shape_xlat's existing Anthropic<->OpenAI logic.
- providers/anthropic.rs: near-passthrough. Since flare-proxy's own wire
  format already IS Anthropic Messages, native Anthropic upstream needs no
  shape_xlat at all -- forward.rs proxies the byte stream unchanged.
- providers/gemini.rs: new Anthropic<->Gemini translator, scoped to text +
  tool_calls (no image/audio/video/file parts -- out of scope for this
  proxy's actual traffic). New shape_xlat::gemini_chunk_to_anthropic_sse /
  gemini_finish_stream read Gemini's candidates[0].content.parts shape
  instead of OpenAI's choices[0].delta.

forward.rs restructured to dispatch per ProviderKind before body
translation (each upstream needs a different request shape and URL path,
not just different headers), with a shared stream_translated_sse() helper
for the two paths that do need chunk-by-chunk translation.

65/65 tests pass, clippy clean (-A unsafe_code -A clippy::pedantic),
fmt clean.

Agentflare-Agent: claude-code_2-1-225_agent
Agentflare-Branch: task/438
Agentflare-Item: 438
…opic/cf_gateway_openai (item #438)

Cloudflare AI Gateway is a URL prefix in front of a provider's own wire
protocol, not a distinct protocol -- gateway.ai.cloudflare.com/v1/{acct}/
{gw}/anthropic still speaks Anthropic's real Messages API shape,
.../compat still speaks OpenAI's /chat/completions shape. So this needed
no new ProviderKind: just two new provider prefixes whose base_url is
built from CF_AI_GATEWAY_ACCOUNT_ID/CF_AI_GATEWAY_ID, reusing the
Anthropic and OpenAiCompatible paths added earlier in this item unchanged.

cf-aig-authorization (Cloudflare's own gateway-level auth, separate from
the underlying provider's API key) is optional, set from
CF_AI_GATEWAY_TOKEN via the extra_headers mechanism already added for
OpenRouter. Missing CF_AI_GATEWAY_ACCOUNT_ID/ID fails fast to
default_free() at startup rather than shipping an unusable base_url.

Closes the Cloudflare AI Gateway gap identified earlier this session
(flare-proxy previously had zero Cloudflare support of any kind).

68/68 tests pass, clippy clean, fmt clean. Zero new dependencies (single
file changed).

Agentflare-Agent: claude-code_2-1-225_agent
Agentflare-Branch: task/438
Agentflare-Item: 438
…ded match arms (item #438)

Per appz-cli's registry/toolchains.toml pattern (crates/appz-core/src/
toolchain_registry.rs) -- scoped down to just what flare-proxy needs: no
HTTP-fetch-with-cache-TTL layer, just an embedded TOML file (include_str!)
parsed once via OnceLock. Adding a provider -- another OpenAI-compatible
endpoint, another Cloudflare AI Gateway route -- is now a
registry/providers.toml edit, not a Rust change.

providers/registry.rs owns the load/lookup/resolve logic:
- RegistrySpec mirrors ProviderEntry's shape (prefix, id, kind, base_url,
  api_key_env, extra_headers) plus two fields the hardcoded version didn't
  need: base_url_template ({ENV_VAR} placeholders, for Cloudflare's
  per-user account_id/gateway_id) and gateway_auth_env/gateway_auth_header
  (an extra header sourced from an env var, distinct from the provider's
  own api_key_env -- Cloudflare's cf-aig-authorization).
- resolve() returns None when a template references an unset env var, so
  from_env()'s existing 'fail fast to default_free()' behavior is
  preserved without each provider needing its own bespoke resolver
  function (deleted cloudflare_gateway_base_url/_headers and
  openrouter_headers -- all three are now registry.toml entries).

default_free() now also builds its 3 built-in providers by resolving them
out of the same registry, instead of duplicating base_url/api_key_env/
extra_headers as separate hardcoded ProviderEntry literals -- one source
of truth for provider identity, whether reached via MODEL=... or the
zero-config default path.

toml crate added to flare-proxy's Cargo.toml -- already a workspace
dependency (root Cargo.toml), so this adds zero new entries to
Cargo.lock.

74/74 tests pass, clippy clean, fmt clean.

Agentflare-Agent: claude-code_2-1-225_agent
Agentflare-Branch: task/438
Agentflare-Item: 438
… embed it (item #438)

The embedded-only registry.toml from the previous commit still required a
new agentflare release to add a provider -- it was baked into the binary
at compile time. This closes that gap the same way appz-cli's toolchain
registry does (crates/appz-core/src/{toolchain_registry,registry_cache}.rs),
scoped down to flare-proxy's single call site: no offline-mode CLI flag,
no generic <T> cache helper, since there's nothing else to share it with.

registry() -- used by find()/resolve()/known_prefixes(), and by this
module's own tests -- reads only the embedded TOML plus whatever's
already cached on disk. It never touches the network, so it stays fast
and deterministic in cargo test (77 tests in 0.02s, unchanged from
before this commit).

ensure_fresh() is the explicit 'download on demand' step: called once
from router() before the first ProviderConfig::from_env() read, it
checks the local cache (~/.agentflare/flare-proxy-providers-registry.json)
against a 24h TTL and, if stale, attempts one 3s-timeout live fetch of
registry/providers.toml's latest published version, merging remote
entries over the embedded ones by prefix. Any failure -- offline, DNS,
non-2xx, malformed TOML -- silently falls back to the embedded copy;
existing installs never break because of a network hiccup, they just
don't get the newest provider until the next successful refresh.

dirs and ureq added to flare-proxy's Cargo.toml -- both already
workspace dependencies via the main agentflare crate, so this adds zero
new entries to Cargo.lock (confirmed via diff).

77/77 tests pass, clippy clean (including a result_large_err fix --
ureq::Error is >270 bytes, fetch_remote() now maps to a plain String
error immediately, matching appz-cli's own Result<String, String>
convention for the same kind of fetch function), fmt clean.

Agentflare-Agent: claude-code_2-1-225_agent
Agentflare-Branch: task/438
Agentflare-Item: 438
…le provider set (item #438)

Adds 17 more openai_compatible entries alongside the original NVIDIA NIM/
OpenRouter/LM Studio/Cloudflare-Gateway set: OpenAI itself, DeepSeek, Groq,
Mistral, xAI, Together, Perplexity, Fireworks, DeepInfra, Moonshot,
Novita, Hyperbolic, Cerebras, SambaNova, GitHub Models, Cohere
(compatibility API), and local Ollama.

base_url values are each provider's own documented OpenAI-SDK-compatible
endpoint as of this session -- best-effort from public docs/conventions,
not individually live-verified against every provider's current docs
given how many there are. Flagged as such directly in the TOML file. If
one drifts, that's exactly what the on-demand refresh added in the
previous commit exists to fix -- correcting registry/providers.toml
upstream reaches every existing install on its next refresh, no new
release needed.

Two new regression tests: every_registry_entry_has_a_resolvable_static_or_
gateway_url (catches a future copy-paste mistake -- a provider block
missing both base_url and base_url_template -- at test time instead of a
confusing request-time 'no route' error) and
broad_openai_compatible_providers_are_present_and_resolve (locks in that
all 17 new prefixes parse, resolve, and produce a well-formed URL).

79/79 tests pass, clippy clean, fmt clean.

Agentflare-Agent: claude-code_2-1-225_agent
Agentflare-Branch: task/438
Agentflare-Item: 438
@coderabbitai

coderabbitai Bot commented Aug 10, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The proxy now loads provider definitions from an embedded and cached registry. It supports native Anthropic and Gemini requests, generic OpenAI-compatible providers, Cloudflare Gateway routes, provider authentication, and Gemini-to-Anthropic SSE translation.

Changes

Provider proxy expansion

Layer / File(s) Summary
Registry configuration and caching
crates/flare-proxy/Cargo.toml, crates/flare-proxy/registry/providers.toml, crates/flare-proxy/src/providers/registry.rs
The registry defines provider protocols, endpoints, credentials, headers, gateway templates, cache merging, refresh behavior, and resolution tests.
Provider contracts and request adapters
crates/flare-proxy/src/providers/mod.rs, crates/flare-proxy/src/providers/anthropic.rs, crates/flare-proxy/src/providers/gemini.rs, crates/flare-proxy/src/providers/openai_compat.rs
Provider construction now uses registry metadata. Anthropic and Gemini request builders translate the Anthropic request shape. OpenAI-compatible providers receive shared authentication and headers.
Dispatch and streaming translation
crates/flare-proxy/src/forward.rs, crates/flare-proxy/src/shape_xlat.rs
Forwarding dispatches by provider kind. Shared status handling and translated SSE support cover OpenAI-compatible and Gemini responses. Gemini text, tool calls, finish reasons, and usage become Anthropic events.
Startup refresh and test isolation
crates/flare-proxy/src/lib.rs
The router refreshes the registry before provider configuration loads. Environment-mutating tests use a process-wide lock.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AnthropicClient
  participant FlareProxy
  participant ProviderRegistry
  participant UpstreamProvider
  AnthropicClient->>FlareProxy: Send Messages request
  FlareProxy->>ProviderRegistry: Resolve provider and endpoint
  ProviderRegistry-->>FlareProxy: Return provider metadata
  FlareProxy->>UpstreamProvider: Send translated request
  UpstreamProvider-->>FlareProxy: Stream provider response
  FlareProxy-->>AnthropicClient: Stream Anthropic SSE response
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the registry-driven provider system and its Anthropic, Gemini, and Cloudflare AI Gateway support.
Description check ✅ Passed The description explains the changes, design, test results, and known limitation; the required summary and test plan sections are substantially complete.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch task/438

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (3)
crates/flare-proxy/src/forward.rs (2)

273-290: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy lift

Emit stream termination when the upstream stream ends without a finish marker.

finish runs only when a chunk carries choices[0].finish_reason or candidates[0].finishReason. If the upstream connection closes after the last content chunk, or the transport error branch at line 242 triggers, the proxy sends no content_block_stop, message_delta, or message_stop. The Anthropic client then waits for an incomplete message.

Track whether finish already ran, and emit it once from a stream-end step.

Also applies to: 329-330

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/flare-proxy/src/forward.rs` around lines 273 - 290, Update the stream
handling around the finish-marker detection and the stream-end/error branches to
track whether the termination sequence has already been emitted. Reuse the
existing finish logic to emit content_block_stop, message_delta, and
message_stop exactly once when the upstream ends without a finish marker,
including the transport error path, while preserving normal marker-triggered
termination.

317-327: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the discarded strip_think_tags call.

The result is dropped, so the call allocates a new String and a Vec<String> on every finish with no effect on output. The comment already explains why suppression is not implemented. Keep the comment and delete the call until buffering lands.

♻️ Proposed change
-                if needs_think && !accumulated_text.is_empty() {
-                    let _ = crate::think::strip_think_tags(&accumulated_text);
-                }

needs_think then becomes unused in this function; drop the parameter or keep it with #[allow(unused)] and a TODO.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/flare-proxy/src/forward.rs` around lines 317 - 327, Remove the
discarded crate::think::strip_think_tags call from the accumulated_text handling
block while preserving the explanatory comment. Then update the surrounding
function signature and call sites to remove the now-unused needs_think
parameter, unless the existing API requires retaining it; do not add replacement
processing.
crates/flare-proxy/Cargo.toml (1)

19-21: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the redundant ureq json feature from crates/flare-proxy/Cargo.toml.

flare-proxy uses only .call() and Response::into_string(). Keep ureq 2.x for the current API. Other workspace crates still require the JSON feature.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/flare-proxy/Cargo.toml` around lines 19 - 21, Update the flare-proxy
ureq dependency declaration to remove the redundant json feature while retaining
the existing ureq 2.x version and dependency. Do not change ureq feature
configuration in other workspace crates.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/flare-proxy/registry/providers.toml`:
- Around line 76-83: Remove the github_models provider entry from the additional
OpenAI-compatible providers registry in providers.toml, including its obsolete
endpoint, while leaving all other provider definitions unchanged.

In `@crates/flare-proxy/src/forward.rs`:
- Around line 96-110: The inline request builders in proxy_anthropic and
proxy_gemini drop provider.extra_headers, including gateway authentication. Add
a shared helper or equivalent loop that applies every provider.extra_headers
entry to both request builders; update crates/flare-proxy/src/forward.rs lines
96-110 and lines 140-147. crates/flare-proxy/registry/providers.toml lines 58-74
requires no direct change; re-verify cf_gateway_anthropic against an
authenticated gateway.

In `@crates/flare-proxy/src/lib.rs`:
- Around line 82-90: Remove the function-local environment mutex in the
providers registry tests and update every test there that mutates process-global
environment variables to acquire the shared crate::test_env_lock() helper from
lib.rs. Ensure all MODEL, MODEL_OPUS, AGENTFLARE_HOME_OVERRIDE, and related env
mutations use this single lock.

In `@crates/flare-proxy/src/providers/gemini.rs`:
- Around line 60-75: Update the tool conversion logic in the declarations
builder so each input_schema is recursively filtered to Gemini-supported schema
fields before assigning functionDeclarations.parameters. Remove unsupported
fields such as $schema and additionalProperties at every nested level while
preserving nullable, format, anyOf, $ref, $defs, and the other supported fields
defined by the existing schema filtering rules; keep the object-type fallback
when input_schema is absent.

In `@crates/flare-proxy/src/providers/mod.rs`:
- Around line 128-152: Update from_env() route construction so
parse_model_string’s raw provider prefixes are resolved through registry::find
and stored as the matching spec.id for the default, opus, sonnet, and haiku
routes; keep providers entries keyed by spec.id. Add a regression test using
MODEL="nvidia_nim/meta/llama-3.1-405b-instruct" that verifies the resulting
route resolves to an existing provider.

In `@crates/flare-proxy/src/providers/registry.rs`:
- Around line 30-31: Harden registry merging so untrusted remote entries cannot
alter embedded providers’ base_url or api_key_env and redirect API-key traffic.
Update the merge logic around parse_registry_toml and the registry refresh path
to permit remote entries only for new prefixes, or otherwise enforce an embedded
host/credential allowlist; ensure cached remote data receives the same
protection before use.
- Around line 317-328: Update the test
resolve_template_substitutes_multiple_placeholders to acquire
crate::test_env_lock() before modifying environment variables, retaining the
guard for the entire test until all variables are removed.

In `@crates/flare-proxy/src/shape_xlat.rs`:
- Around line 582-605: Update gemini_finish_stream to track whether
buffer.open_indices contained an emitted tool-use block, then derive stop_reason
as "tool_use" for that case even when finishReason is "STOP"; preserve the
existing MAX_TOKENS and explicit TOOL_CALLS mappings otherwise. Adjust the
corresponding test fixture and expectation to use Gemini finishReason "STOP"
with the tool block.

---

Nitpick comments:
In `@crates/flare-proxy/Cargo.toml`:
- Around line 19-21: Update the flare-proxy ureq dependency declaration to
remove the redundant json feature while retaining the existing ureq 2.x version
and dependency. Do not change ureq feature configuration in other workspace
crates.

In `@crates/flare-proxy/src/forward.rs`:
- Around line 273-290: Update the stream handling around the finish-marker
detection and the stream-end/error branches to track whether the termination
sequence has already been emitted. Reuse the existing finish logic to emit
content_block_stop, message_delta, and message_stop exactly once when the
upstream ends without a finish marker, including the transport error path, while
preserving normal marker-triggered termination.
- Around line 317-327: Remove the discarded crate::think::strip_think_tags call
from the accumulated_text handling block while preserving the explanatory
comment. Then update the surrounding function signature and call sites to remove
the now-unused needs_think parameter, unless the existing API requires retaining
it; do not add replacement processing.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0f549718-9d57-4b92-991d-9ada11c4c38b

📥 Commits

Reviewing files that changed from the base of the PR and between 9c05039 and abc9f23.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • crates/flare-proxy/Cargo.toml
  • crates/flare-proxy/registry/providers.toml
  • crates/flare-proxy/src/forward.rs
  • crates/flare-proxy/src/lib.rs
  • crates/flare-proxy/src/providers/anthropic.rs
  • crates/flare-proxy/src/providers/gemini.rs
  • crates/flare-proxy/src/providers/mod.rs
  • crates/flare-proxy/src/providers/openai_compat.rs
  • crates/flare-proxy/src/providers/registry.rs
  • crates/flare-proxy/src/shape_xlat.rs

Comment thread crates/flare-proxy/registry/providers.toml
Comment thread crates/flare-proxy/src/forward.rs
Comment on lines +82 to +90

/// Serializes tests that mutate process-global env vars (`MODEL`,
/// `MODEL_OPUS`, ...) — `cargo test` runs tests in parallel by default, and
/// unsynchronized `env::set_var` calls across tests race.
#[cfg(test)]
pub(crate) fn test_env_lock() -> std::sync::MutexGuard<'static, ()> {
static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
LOCK.lock().unwrap_or_else(|e| e.into_inner())
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Use one shared environment lock.

crates/flare-proxy/src/providers/registry.rs defines another function-local static LOCK. That mutex is distinct from the mutex in crates/flare-proxy/src/lib.rs, so registry tests do not synchronize with tests that call crate::test_env_lock(). Parallel tests can observe incorrect MODEL or AGENTFLARE_HOME_OVERRIDE values.

Remove the duplicate helper and make every environment-mutating test call this shared helper.

Proposed fix
-        let _guard = test_env_lock();
+        let _guard = crate::test_env_lock();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/flare-proxy/src/lib.rs` around lines 82 - 90, Remove the
function-local environment mutex in the providers registry tests and update
every test there that mutates process-global environment variables to acquire
the shared crate::test_env_lock() helper from lib.rs. Ensure all MODEL,
MODEL_OPUS, AGENTFLARE_HOME_OVERRIDE, and related env mutations use this single
lock.

Comment thread crates/flare-proxy/src/providers/gemini.rs
Comment thread crates/flare-proxy/src/providers/mod.rs
Comment thread crates/flare-proxy/src/providers/registry.rs
Comment thread crates/flare-proxy/src/providers/registry.rs
Comment thread crates/flare-proxy/src/shape_xlat.rs
…roviders (item #438)

Three more openai_compatible registry entries, picked for coding use:
siliconflow (Qwen3-Coder-480B, DeepSeek-V3.2/R1, permanently free after
identity verification), llm7 (Qwen2.5-Coder-32B, DeepSeek-R1, no-cost
daily tier), requesty (300+ model router, cleanest ToS rating of the
free aggregators surveyed).

base_url values verified against each provider's own docs (SiliconFlow
quickstart, LLM7 live /v1/models endpoint, Requesty gateway docs), not
guessed. Extended broad_openai_compatible_providers_are_present_and_resolve
to cover all three.

11/11 flare-proxy registry tests pass, clippy clean, fmt clean.

Agentflare-Agent: claude-code_2-1-225_agent
Agentflare-Branch: task/438
Agentflare-Item: 438
…(item #438)

New optional RegistrySpec fields -- free_type, monthly_tokens,
credit_tokens -- record each provider's documented free-tier budget:
recurring-daily/monthly (renews), recurring-uncapped (rate-limited, no
token cap), one-time-initial (signup credit), or unmetered (local).
Populated for all 21 providers with a documented free tier; omitted for
paid-key/pass-through providers (openai, xai, together's paid tier,
perplexity, moonshot, anthropic, cf_gateway_*) with no free-tier data.

Data pulled from OmniRoute's freeModelCatalog.data.ts (pool-deduped to
each provider's max), not estimated -- source noted in the TOML header.

Metadata only: no selection/load-balancing logic reads these fields yet.
That's the natural follow-up once this shape has been used for a bit,
scoped separately per plan.

New test (every_registry_free_type_is_a_known_value) catches a typo'd
free_type value against a known set. 12/12 flare-proxy registry tests
pass, clippy clean, fmt clean.

Agentflare-Agent: claude-code_2-1-225_agent
Agentflare-Branch: task/438
Agentflare-Item: 438
…tem #438)

New providers::quota module: tracks per-provider token consumption
against the free_type/monthly_tokens/credit_tokens registry metadata
added in the previous commit, and picks the first available provider
from a priority-ordered candidate list.

Design ported (not copied) from two real implementations, researched
and cloned locally for reference:
- LiteLLM's provider budget limiter (BerriAI/litellm, MIT,
  litellm/router_strategy/budget_limiter.py) -- the get-or-init window
  start / reset-if-expired / increment-if-current three-way branch,
  without its Redis multi-instance sync (flare-proxy is one process).
- OmniRoute's emergency fallback (diegosouzapw/OmniRoute, MIT,
  open-sse/services/emergencyFallback.ts) -- fail-open on tracking
  errors, walk a short ordered candidate list rather than re-rank a
  large pool.

Usage persists to flare-proxy-usage.json next to the existing registry
cache (agentflare_home(), now pub(super) for quota.rs to reuse rather
than duplicating the home-dir helper).

Scope boundary, called out in the module doc: this is the tracking/
selection primitive only, not wired into forward.rs's request path yet.
ProviderConfig/ModelRoute resolve to exactly one provider per request
today; using select_best for real routing needs a candidate list there,
and record_consumption needs a real token count, which the streaming
response path doesn't currently extract. Both are separate design
decisions, tracked as the next item #438 follow-up rather than bolted
on here unreviewed.

7 new tests (budget exhaustion, window expiry reset, one-time credits
never resetting, priority-ordered fallback selection). 87/87 flare-proxy
tests pass (stable across repeated runs -- fixed a test-isolation race
by reusing crate::test_env_lock() instead of a module-local mutex, since
AGENTFLARE_HOME_OVERRIDE is a process-global env var shared with
registry.rs's own tests). clippy clean, fmt clean.

Agentflare-Agent: claude-code_2-1-225_agent
Agentflare-Branch: task/438
Agentflare-Item: 438
- forward.rs: apply provider.extra_headers on the Anthropic/Gemini paths too, not just OpenAI-compat -- Cloudflare AI Gateway's cf-aig-authorization was silently dropped for cf_gateway_anthropic
- mod.rs: from_env() route construction now resolves MODEL prefixes (e.g. nvidia_nim) to their registry id (nvidia-nim) before storing provider_id, fixing a 400 "unknown provider" for any prefix whose id differs from its prefix
- gemini.rs: recursively strip JSON-Schema fields Gemini's function-calling API rejects ($schema, etc.) from tool input_schema before sending
- shape_xlat.rs: gemini_finish_stream now reports stop_reason "tool_use" whenever a tool block was opened, even when Gemini's finishReason is "STOP" (its common case for a turn containing a functionCall)
- registry.rs: merge() no longer lets a remote/cached registry entry override an existing provider's base_url/api_key_env, only its quota metadata -- a compromised publish path could otherwise redirect API traffic to an attacker host
- providers.toml: remove github_models -- GitHub retired the Models inference endpoint (410)
- registry.rs: add the missing test_env_lock() guard to resolve_template_substitutes_multiple_placeholders
- Cargo.toml: drop ureq's unused json feature
- forward.rs: remove a dead strip_think_tags() call whose result was discarded

Agentflare-Agent: claude-code_2-1-225_agent
Agentflare-Branch: task/438
Agentflare-Item: 438
@getappz
getappz merged commit 29b1031 into master Aug 11, 2026
24 of 26 checks passed
@getappz
getappz deleted the task/438 branch August 11, 2026 02:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant