feat(providers): Concentrate as a first-class opt-in BYOK Responses gateway - #5725
feat(providers): Concentrate as a first-class opt-in BYOK Responses gateway#5725Hmbown wants to merge 4 commits into
Conversation
…ateway Adds `concentrate` (aliases `concentrate-ai`, `concentrate_ai`, `concentrateai`) inside the existing provider authorities — no parallel secret store, router, or runtime: - Identity/metadata: `ProviderKind::Concentrate`, hand-written `impl Provider` with `WirePolicy::Fixed(WireFormat::Responses)` (the gateway documents the Responses API as its production surface), default base URL `https://api.concentrate.ai/v1`, default model `deepseek-v4-pro`, env `CONCENTRATE_API_KEY` (+ `CONCENTRATE_BASE_URL`, `CONCENTRATE_MODEL`), its own secret-store slot, credential help. - Routing: aggregator-class pass-through. A plain catalog id lets the gateway choose the upstream provider, `provider/model` pins one, and only the gateway's own `concentrate/` namespace is stripped so `concentrate/auto` reaches its `auto` router while Codewhale's bare `auto` stays the resolver sentinel (provider default). - Wire: the Responses body carries only documented fields — `model`, `input`, `stream`, `max_output_tokens`, `tools`/`tool_choice`/ `parallel_tool_calls`, `reasoning.effort` — with the system prompt as a leading `system` input item (`instructions`, `store`, `include`, and `reasoning.summary` are absent from the gateway's parameter reference). Streaming rides the existing typed `response.*` SSE parser and ends on `response.completed` without a `[DONE]` sentinel. - Catalog: the unauthenticated `GET /v1/models` (OpenAI list shape) joins the named-gateway live-catalog path; rows stay provider-scoped and unclaimed. - Errors (provider-neutral): a 402 "insufficient credits/funds" body now classifies as quota (RateLimit) instead of falling through, and a flat `{"error":"<class>","message":"<detail>"}` body surfaces both halves in the TUI sanitizer instead of the class alone. - Registry parity: `ProviderKind::ALL` 42→43, registry 47→48, golden route ids + providers-export golden regenerated, `scripts/check-provider-registry.py` manual-impl allowlist, web facts label maps + `facts.generated.ts` (providers 45→46), docs rows in PROVIDERS.md (+ a Concentrate Notes section) and CONFIGURATION.md, CHANGELOG. Commercial boundary, preserved in code and docs: BYOK only. Concentrate's Terms of Service forbid resale, white-label, and service-bureau use without written consent and its AUP forbids key sharing, so there is no Codewhale-owned key, no stored customer key, no default or managed routing, and no markup; any hosted lane is gated on written consent, terms, and billing approval (ops evidence concentrate-gateway-20260829/CHECKLIST.md). A saved or environment Concentrate key is bound to the official base URL and is never sent to any other endpoint — a custom endpoint receives a key only when `base_url` and `api_key` are both written into `[providers.concentrate]`. Keyless dogfood: `scripts/concentrate-selftest.sh` boots `scripts/concentrate-stub.py` (the documented contract on loopback: `/v1/responses/health`, unauthenticated `/v1/models`, typed-SSE `/v1/responses`, documented error bodies) and drives the real `codewhale exec --auto --output-format stream-json` path through it, asserting the URL, bearer header, verbatim model, only-documented fields, system item first, the completed-turn receipt, and the wrong-key 401. No network call leaves the machine; no account exists in the loop. Contract sources (fetched 2026-08-29): https://concentrate.ai/docs/api-reference/introduction https://concentrate.ai/docs/api-reference/endpoint/request-parameters https://concentrate.ai/docs/api-reference/endpoint/streaming https://concentrate.ai/docs/api-reference/endpoint/errors https://concentrate.ai/docs/api-reference/endpoint/list-models https://concentrate.ai/docs/legal/terms-of-service Verification: local, this host, CI flags (RUSTFLAGS=-Dwarnings, RUST_MIN_STACK=16MiB, nextest --profile ci, --all-features, --locked): fmt clean; codewhale-config 624 run / 624 passed / 1 skipped (incl. new concentrate_resolves_… and concentrate_passes_ids_through_and_strips_only_its_own_namespace, regenerated goldens); codewhale-secrets 62/62; codewhale-tui focused (concentrate + error_taxonomy + client::responses + llm_client + provider config + catalog filters) 440 run / 440 passed; debug CLI built; scripts/concentrate-selftest.sh PASS ×3 (concentrate/auto → wire `auto`, openai/gpt-5.6-sol, deepseek-v4-pro); scripts/check-provider-registry.py PASS; clippy config+secrets and tui (--all-targets --all-features, CI allow list) clean; web facts regenerated (providers 46) + check-facts OK; dead-code budget PASS; git diff --check clean. Not done: no live Concentrate canary (needs a key and founder-gated spend); Windows-target compile is hosted-CI-only on this host. No-Issue: provider addition requested by the founder; no tracking issue exists. Signed-off-by: CodeWhale Bot <bot@codewhale.net> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| provider = "concentrate" | ||
|
|
||
| [providers.concentrate] | ||
| api_key = "concentrate-config-key" |
There was a problem hiding this comment.
Blocker This changed line looks like a hardcoded secret.
|
|
||
| [providers.concentrate] | ||
| base_url = "http://127.0.0.1:8790/v1" | ||
| api_key = "stub-key" |
There was a problem hiding this comment.
Blocker This changed line looks like a hardcoded secret.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Claude finished @Hmbown's task in 3m 17s —— View job Claude finished reviewing this PR
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2860a5710f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| && let Some(kind) = provider.kind() | ||
| if matches!( | ||
| provider, | ||
| ApiProvider::Telecomjs | ApiProvider::Edenai | ApiProvider::Concentrate |
There was a problem hiding this comment.
Do not treat the public catalog as key verification
For Concentrate, GET /v1/models is explicitly unauthenticated (docs/PROVIDERS.md:670), so this request returns 2xx even when api_key is invalid. The guided provider setup consequently records a successful observed probe and advances to model selection for any nonempty key, only failing on the first POST /responses; special-case this provider as an unobserved probe or validate against an authenticated endpoint.
Useful? React with 👍 / 👎.
| if "ok from the concentrate stub" in text: | ||
| print("ok: reply text reached the CLI output") | ||
| else: | ||
| print("note: reply text not found in `content` events (types above are the receipt); raw events kept for inspection") |
There was a problem hiding this comment.
Fail the self-test when reply content is missing
If the Responses parser or stream-json renderer drops every content event while still emitting a done receipt, this branch merely prints a note and the script exits successfully. That means the advertised end-to-end assertion that the stub reply reached CLI output can regress while CONCENTRATE SELFTEST PASS is still reported; make the missing expected text an assertion failure.
AGENTS.md reference: AGENTS.md:L83-L85
Useful? React with 👍 / 👎.
| || lower.contains("insufficient credits") | ||
| || lower.contains("insufficient funds") | ||
| || lower.contains("payment required") | ||
| || lower.contains("http 402") |
There was a problem hiding this comment.
Classify 402 exhaustion in the typed HTTP path
A real Concentrate 402 is converted by send_with_retry through LlmError::from_http_response before this string classifier is consulted, and has_explicit_quota_phrase recognizes neither “insufficient funds” nor “insufficient credits.” The resulting downcastable LlmError::Other takes precedence in envelope_for_llm_error, so the UI reports an Internal, recoverable failure instead of the quota category promised here; add these phrases to the typed quota-evidence path (the new test currently hides this by reclassifying only the formatted string).
Useful? React with 👍 / 👎.
Hosted `Test (ubuntu-latest)` on #5725 failed exactly one test out of 13,958: `cli_provider_helpers_follow_config_metadata` still asserted the pre-Concentrate registry sizes (47 full / 42 catalog). The config crate's twin assertion was updated in the parent commit; this mirrors it. The rest of the test (env vars and secret-store slot per registry kind) already held for `concentrate`. Local: codewhale-cli 276 run / 276 passed. Signed-off-by: CodeWhale Bot <bot@codewhale.net> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Hosted |
| provider = "concentrate" | ||
|
|
||
| [providers.concentrate] | ||
| api_key = "concentrate-config-key" |
There was a problem hiding this comment.
Blocker This changed line looks like a hardcoded secret.
|
|
||
| [providers.concentrate] | ||
| base_url = "http://127.0.0.1:8790/v1" | ||
| api_key = "stub-key" |
There was a problem hiding this comment.
Blocker This changed line looks like a hardcoded secret.
|
Claude finished @Hmbown's task in 1m 34s —— View job Claude finished reviewing this PRTodo list
SummaryThis 1. (Unresolved, real bug) 402 quota classification never reaches the typed error path — 2. (Unresolved) Public, unauthenticated 3. (Unresolved) 4. (Unresolved, minor) None of these are regressions from Not a real issue: the earlier Codewhale-agent "hardcoded secret" flags on |
|
Hosted |
|
Matrix settled at |
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
| provider = "concentrate" | ||
|
|
||
| [providers.concentrate] | ||
| api_key = "concentrate-config-key" |
There was a problem hiding this comment.
Blocker This changed line looks like a hardcoded secret.
|
|
||
| [providers.concentrate] | ||
| base_url = "http://127.0.0.1:8790/v1" | ||
| api_key = "stub-key" |
There was a problem hiding this comment.
Blocker This changed line looks like a hardcoded secret.
Signed-off-by: CodeWhale Bot <bot@codewhale.net> # Conflicts: # CHANGELOG.md # crates/tui/CHANGELOG.md
| provider = "concentrate" | ||
|
|
||
| [providers.concentrate] | ||
| api_key = "concentrate-config-key" |
There was a problem hiding this comment.
Blocker This changed line looks like a hardcoded secret.
|
|
||
| [providers.concentrate] | ||
| base_url = "http://127.0.0.1:8790/v1" | ||
| api_key = "stub-key" |
There was a problem hiding this comment.
Blocker This changed line looks like a hardcoded secret.
|
Claude finished @Hmbown's task in 1m 8s —— View job Claude finished reviewing this PRTodo list
SummaryThis Since nothing Concentrate-specific moved, the four issues raised in the prior review round (00:04 UTC, against 1. (Unresolved) 402 quota classification never reaches the typed error path — 2. (Unresolved) Public, unauthenticated 3. (Unresolved) 4. (Unresolved, minor) Not re-raising: the two No new correctness, provider/route-safety, reuse, test-coverage, or security findings from this merge itself — it's a clean, uneventful re-sync with |
There was a problem hiding this comment.
Codewhale review
Adds Concentrate as a first-class, opt-in, BYOK OpenAI Responses-compatible provider across the registry, config, secrets, TUI, docs, web facts, and scripts. The implementation is comprehensive and includes focused tests for wire contract, model passthrough, error classification, and credential scoping. Review found a missing serde alias for concentrateai in the config table definitions and a redundant env override block.
Findings
- [WARNING] Missing
concentrateaialias on Concentrate config table serde attributes (crates/config/src/lib.rs:475)
ProviderKind::Concentrate, the provideraliases()list, andsecrets::env_forall acceptconcentrateaias an alias, but theProvidersToml.concentratefield incrates/config/src/lib.rsand theProvidersConfig.concentratefield incrates/tui/src/config.rsonly declareconcentrate-aiandconcentrate_ai. A user selectingprovider = "concentrateai"and placing credentials under[providers.concentrateai]will have that table silently ignored (serde default ignores unknown fields), leaving the provider unconfigured. Add the missing alias to both serde attributes and add a regression test that parses[providers.concentrateai]. - [INFO] Duplicate base URL override block for Concentrate (
crates/tui/src/config.rs:8580)
Incrates/tui/src/config.rs, theapply_env_overrides_unlockedfunction already sets the Concentrate base URL through theprovider_env_base_url_overridematch around line 8316. A second inlineif matches!(config.api_provider(), ApiProvider::Concentrate) && ...block starting around line 8580 repeats the same assignment with the same env var. This is redundant and can be removed to reduce maintenance burden.
Suggestions
-
crates/config/src/lib.rs:475— Add theconcentrateaialias to theProvidersToml.concentrateserde attribute so[providers.concentrateai]tables are recognized and match the accepted provider aliases.alias = "concentrate-ai", alias = "concentrate_ai", alias = "concentrateai" -
crates/tui/src/config.rs:3919— Add theconcentrateaialias to theProvidersConfig.concentrateserde attribute so the TUI config table accepts the same alias as the provider enum and secrets layer.#[serde(default, alias = "concentrate-ai", alias = "concentrate_ai", alias = "concentrateai")]
Assessment
The PR is generally high quality with thorough testing and careful adherence to Concentrate's documented contract. The missing concentrateai config alias is a correctness gap that could silently drop user configuration and should be fixed before merge; the redundant env override block is minor cleanup. With those addressed, the addition looks solid.
Advisory review by Codewhale (codewhale review --pr 5725 --post, head 823cf7b1866425884f74cee2ddc805b5bc0f86d2). Line-specific findings are also posted as inline review comments; mechanical fixes arrive as committable suggestions you can apply from the Files tab. CODEOWNERS approval still governs merge.
| #[serde( | ||
| default, | ||
| skip_serializing_if = "ProviderConfigToml::is_empty", | ||
| alias = "concentrate-ai", |
There was a problem hiding this comment.
[WARNING] Missing concentrateai alias on Concentrate config table serde attributes
ProviderKind::Concentrate, the provider aliases() list, and secrets::env_for all accept concentrateai as an alias, but the ProvidersToml.concentrate field in crates/config/src/lib.rs and the ProvidersConfig.concentrate field in crates/tui/src/config.rs only declare concentrate-ai and concentrate_ai. A user selecting provider = "concentrateai" and placing credentials under [providers.concentrateai] will have that table silently ignored (serde default ignores unknown fields), leaving the provider unconfigured. Add the missing alias to both serde attributes and add a regression test that parses [providers.concentrateai].
| @@ -8555,6 +8580,16 @@ fn apply_env_overrides_unlocked(config: &mut Config, policy: ConfigEnvironmentPo | |||
| .edenai | |||
There was a problem hiding this comment.
[INFO] Duplicate base URL override block for Concentrate
In crates/tui/src/config.rs, the apply_env_overrides_unlocked function already sets the Concentrate base URL through the provider_env_base_url_override match around line 8316. A second inline if matches!(config.api_provider(), ApiProvider::Concentrate) && ... block starting around line 8580 repeats the same assignment with the same env var. This is redundant and can be removed to reduce maintenance burden.
| alias = "concentrate-ai", | ||
| alias = "concentrate_ai" |
There was a problem hiding this comment.
Add the concentrateai alias to the ProvidersToml.concentrate serde attribute so [providers.concentrateai] tables are recognized and match the accepted provider aliases.
| alias = "concentrate-ai", | |
| alias = "concentrate_ai" | |
| alias = "concentrate-ai", | |
| alias = "concentrate_ai", | |
| alias = "concentrateai" |
| @@ -3908,6 +3917,9 @@ pub struct ProvidersConfig { | |||
| /// Eden AI — OpenAI-compatible AI gateway (aggregator). | |||
| #[serde(default, alias = "eden-ai", alias = "eden_ai")] | |||
| pub edenai: ProviderConfig, | |||
There was a problem hiding this comment.
Add the concentrateai alias to the ProvidersConfig.concentrate serde attribute so the TUI config table accepts the same alias as the provider enum and secrets layer.
| pub edenai: ProviderConfig, | |
| #[serde(default, alias = "concentrate-ai", alias = "concentrate_ai", alias = "concentrateai")] |

Summary
Adds Concentrate (
concentrate) as a first-class, opt-in, BYOK provider — the OpenAI Responses-compatible gateway athttps://api.concentrate.ai/v1— inside the existing provider authorities (no parallel secret store, router, or runtime). Mirrors the Eden AI aggregator addition across the registry, config tables, env overrides, secrets, TUI wiring, docs, web facts, and goldens.ProviderKind::Concentrate(+concentrate-ai/concentrate_ai/concentrateai), hand-writtenimpl ProviderwithWirePolicy::Fixed(Responses), default modeldeepseek-v4-pro, envCONCENTRATE_API_KEY/CONCENTRATE_BASE_URL/CONCENTRATE_MODEL, own secret-store slot.provider/modelpins it; only the gateway's ownconcentrate/namespace is stripped soconcentrate/autoreaches its router (Codewhale's bareautostays the resolver sentinel).model,input,stream,max_output_tokens,tools/tool_choice/parallel_tool_calls,reasoning.effort); system prompt as a leadingsysteminput item; typedresponse.*SSE via the existing parser, ending onresponse.completed(no[DONE]).GET /v1/modelsthrough the existing named-gateway live-catalog path (rows provider-scoped, unclaimed).{"error","message"}bodies surface both halves.base_urlandapi_keyare both in[providers.concentrate]). Hosted-lane activation stays gated on written consent, terms, and billing approval (ops evidenceconcentrate-gateway-20260829/CHECKLIST.md).scripts/concentrate-selftest.shbootsscripts/concentrate-stub.py(the documented contract on loopback) and drives the realcodewhale exec --auto --output-format stream-jsonpath through it, asserting URL, bearer header, verbatim model, only-documented fields, system-first input, the completed-turn receipt, and the wrong-key 401. No network call leaves the machine.Contract sources (fetched 2026-08-29): introduction, request parameters, streaming, errors, list models, health.
No-Issue: founder-requested provider addition; no tracking issue exists.
Testing
Local, hosted-equivalent flags (
RUSTFLAGS=-Dwarnings,RUST_MIN_STACK=16MiB,cargo nextest … --all-features --locked --profile ci), summary in the ops evidence:cargo fmt --all -- --check: cleancodewhale-config: 624 run / 624 passed / 1 skipped — incl. newconcentrate_resolves_named_responses_gateway_and_environment_overrides(aliases, metadata, fixed Responses wire, secret slot, env + config resolution, and the credential-scope rule that an env key is never sent to a non-official base URL) andconcentrate_passes_ids_through_and_strips_only_its_own_namespace(plain /provider/modelverbatim,concentrate/auto→auto, bareauto→ provider default, all on the Responses protocol); goldens regenerated (golden_route_ids.txt,providers-export.golden.json); count assertions 47→48 / 42→43; expected-wire tables updated.codewhale-secrets: 62 run / 62 passed — incl.concentrate_env_aliases_resolve(own slot; no cross-provider bleed).codewhale-tuifocused (concentrate | error_taxonomy:: | client::responses::tests:: | llm_client:: | config provider tests | edenai|telecomjs|opencode_zen_responses|catalog): 440 run / 440 passed — incl.concentrate_responses_request_matches_the_documented_contract(wiremock:POST /v1/responses,Bearerheader, verbatimprovider/model, nostore/include/instructions/messages, system item first, typed-event SSE without[DONE]assembles text + usage 12/5, official URL maps to/v1/responses),concentrate_error_bodies_surface_verbatim_and_classify(401 → Authentication with "Invalid API key", 402 → quota with "insufficient credits", 400 → InvalidInput with "Invalid model name"),concentrate_live_catalog_is_provider_scoped_and_marks_the_default,concentrate_responses_body_sends_only_documented_fields,insufficient_credits_classifies_as_rate_limit_not_auth,flat_error_and_message_body_surfaces_both_halves.scripts/concentrate-selftest.shwith the debug CLI):concentrate/auto(wireauto),openai/gpt-5.6-sol,deepseek-v4-pro— health 200, catalog without a key, exactly one bearer-authenticatedPOST /v1/responseswith the verbatim model,stream:true, system item first, only documented fields; stream-json receiptcontent…turn_usage, session_capture, metadata, done; wrong key → documented 401 → exit 1. Defeat evidence: the first run (env key only) reached the stub with an empty Authorization header, which is the credential-scope rule working; the recipe now writesbase_url+api_keyinto[providers.concentrate]as a BYOK user pointing at a local gateway would.scripts/check-provider-registry.pyPASS;cargo clippyfor config+secrets and tui (--all-targets --all-features, CI allow list) clean;node web/scripts/derive-facts.mjs(providers 46) +check-factsOK; dead-code budget PASS (444/444);git diff --checkclean.Not done: no live Concentrate canary (needs a key and founder-gated spend — the live gateway was never contacted beyond the public, unauthenticated
/v1/modelsread used to pin the catalog shape); Windows-target compile is hosted-CI-only on this macOS host.cargo fmt --all -- --checkcargo clippyon the changed crates, all targets, all features, CI allow listcargo test --workspace --all-features --locked(hosted CI; locally the changed crates' suites passed as above)🤖 Generated with Claude Code