Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
17d5e87
Add the Synthetic provider
Finesssee Oct 11, 2026
b358acd
Add the ClawRouter provider
Finesssee Oct 11, 2026
5ce549d
Add the IBM Bob provider
Finesssee Oct 11, 2026
749b296
Merge remote-tracking branch 'origin/main' into feat/provider-synthetic
Finesssee Oct 11, 2026
3070281
Merge branch 'feat/provider-synthetic' into feat/provider-clawrouter
Finesssee Oct 11, 2026
f5dab54
Merge branch 'feat/provider-clawrouter' into feat/provider-ibmbob
Finesssee Oct 11, 2026
2fb5592
Merge branch 'feat/provider-clawrouter' into feat/provider-ibmbob
Finesssee Oct 11, 2026
b27bef0
Merge branch 'feat/provider-synthetic' into feat/provider-clawrouter
Finesssee Oct 11, 2026
fab0fde
Merge remote-tracking branch 'origin/main' into feat/provider-synthetic
Finesssee Oct 11, 2026
ff82741
IBM Bob: show the upstream message when no API key is set
Finesssee Oct 11, 2026
5ad2913
ClawRouter: classify rejected keys as needs-auth
Finesssee Oct 11, 2026
ad8e802
Synthetic: hide weekly cost like the Mac card
Finesssee Oct 11, 2026
b375963
Merge branch 'feat/provider-synthetic' into feat/provider-clawrouter
Finesssee Oct 11, 2026
876d6a6
Merge branch 'feat/provider-clawrouter' into feat/provider-ibmbob
Finesssee Oct 11, 2026
e121931
Merge remote-tracking branch 'origin/main' into feat/provider-synthetic
Finesssee Oct 11, 2026
fd471ad
Merge branch 'feat/provider-synthetic' into feat/provider-clawrouter
Finesssee Oct 11, 2026
dccba63
Merge branch 'feat/provider-clawrouter' into feat/provider-ibmbob
Finesssee Oct 11, 2026
5fef9b1
Merge origin/main into feat/provider-synthetic
Finesssee Oct 11, 2026
97ac168
Merge feat/provider-synthetic into feat/provider-clawrouter
Finesssee Oct 11, 2026
b90e275
Merge feat/provider-clawrouter into feat/provider-ibmbob
Finesssee Oct 11, 2026
2166d52
Merge origin/main into feat/provider-synthetic
Finesssee Oct 11, 2026
7d80a3d
Synthetic: expect its near-black icon to lift on dark surfaces
Finesssee Oct 11, 2026
4c6be0e
Merge feat/provider-synthetic into feat/provider-clawrouter
Finesssee Oct 11, 2026
acf2a1a
Merge feat/provider-clawrouter into feat/provider-ibmbob
Finesssee Oct 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,16 @@ See the full history in [CHANGELOG.md](CHANGELOG.md).
| Raycast | Cookies (Chrome auto or manual) | Monthly AI credits, plan, renewal |
| Replicate | Cookies / token accounts | Monthly spend, credit balance |
| Aixy | API Key / token accounts | Applicable budget balances, 7-day key usage |
| ClawRouter | API Key | Monthly budget, requests, tokens, routed-provider spend |
| ElevenLabs | API Key | Subscription Credits, Voice Slots |
| Deepgram | API Key | Project Usage |
| Groq | API Key | Enterprise Metrics |
| LLM Proxy | API Key | Quota Stats |
| llmman | Local daemon / optional API Key | Memory in use, loaded and stored models |
| DevPass | API Key | Plan credits, Premium weekly, API-key spend |
| IBM Bob | API Key / token accounts | Monthly Bobcoins, per-team budgets |
| xKiro | API Key | Daily free tokens |
| Synthetic | API Key | Five-hour quota, Weekly tokens, Hourly search, Plan |

</details>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ mod tests {
(ProviderId::Wayfinder, "http://localhost:8787"),
(ProviderId::Bifrost, "https://bifrost.example.com"),
(ProviderId::Aixy, "https://aixy.example.com/prefix"),
(ProviderId::ClawRouter, "https://router.example.com/v1"),
] {
settings.set_gateway_url(id, url);
let ctx = super::super::providers::build_fetch_context(
Expand Down Expand Up @@ -485,6 +486,7 @@ mod tests {
assert_eq!(gateway_provider("wayfinder"), Some(ProviderId::Wayfinder));
assert_eq!(gateway_provider("bifrost"), Some(ProviderId::Bifrost));
assert_eq!(gateway_provider("aixy"), Some(ProviderId::Aixy));
assert_eq!(gateway_provider("clawrouter"), Some(ProviderId::ClawRouter));
assert_eq!(gateway_provider("codex"), None);
}

Expand Down Expand Up @@ -548,6 +550,7 @@ fn gateway_provider(provider_id: &str) -> Option<codexbar::core::ProviderId> {
"wayfinder" => Some(codexbar::core::ProviderId::Wayfinder),
"bifrost" => Some(codexbar::core::ProviderId::Bifrost),
"aixy" => Some(codexbar::core::ProviderId::Aixy),
"clawrouter" => Some(codexbar::core::ProviderId::ClawRouter),
_ => None,
}
}
Expand Down Expand Up @@ -577,6 +580,10 @@ pub fn set_provider_gateway_url(provider_id: String, gateway_url: String) -> Res
codexbar::providers::aixy::validate_gateway_url(gateway_url)
.map_err(|error| error.to_string())?;
}
codexbar::core::ProviderId::ClawRouter => {
codexbar::providers::clawrouter::validate_gateway_url(gateway_url)
.map_err(|error| error.to_string())?;
}
_ => unreachable!("gateway_provider only returns gateway providers"),
}

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ describe("provider icon registry", () => {
"elevenlabs",
"manus",
"replicate",
"synthetic",
"typesafe",
"v0",
]);
Expand Down
9 changes: 9 additions & 0 deletions apps/desktop-tauri/src/components/providers/providerIcons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import gemini from "./icons/ProviderIcon-gemini.svg?raw";
import grok from "./icons/ProviderIcon-grok.svg?raw";
import groq from "./icons/ProviderIcon-groq.svg?raw";
import huggingface from "./icons/ProviderIcon-huggingface.svg?raw";
import ibmbob from "./icons/ProviderIcon-ibmbob.svg?raw";
import jetbrains from "./icons/ProviderIcon-jetbrains.svg?raw";
import kilo from "./icons/ProviderIcon-kilo.svg?raw";
import kimi from "./icons/ProviderIcon-kimi.svg?raw";
Expand Down Expand Up @@ -64,6 +65,8 @@ import replicate from "./icons/ProviderIcon-replicate.svg?raw";
import sakana from "./icons/ProviderIcon-sakana.svg?raw";
import stepfun from "./icons/ProviderIcon-stepfun.svg?raw";
import sub2api from "./icons/ProviderIcon-sub2api.svg?raw";
import synthetic from "./icons/ProviderIcon-synthetic.svg?raw";
import clawrouter from "./icons/ProviderIcon-clawrouter.svg?raw";
import t3chat from "./icons/ProviderIcon-t3chat.svg?raw";
import venice from "./icons/ProviderIcon-venice.svg?raw";
import vercel from "./icons/ProviderIcon-vercel.svg?raw";
Expand Down Expand Up @@ -131,6 +134,7 @@ const RAW: Record<string, string> = {
grok: tint(grok),
groq: tint(groq),
huggingface: tint(huggingface),
ibmbob: tint(ibmbob),
jetbrains: tint(jetbrains),
kilo: tint(kilo),
kimi: tint(kimi),
Expand Down Expand Up @@ -158,6 +162,8 @@ const RAW: Record<string, string> = {
sakana: tint(sakana),
stepfun: tint(stepfun),
sub2api: tint(sub2api),
synthetic: tint(synthetic),
clawrouter: tint(clawrouter),
t3chat: tint(t3chat),
venice: tint(venice),
vercel: tint(vercel),
Expand Down Expand Up @@ -251,6 +257,9 @@ export const PROVIDER_ICON_REGISTRY: Record<string, ProviderIcon> = {
sub2api: { id: "sub2api", brandColor: "#14b8a6", fallbackLetter: "S", svgPath: RAW.sub2api },
venice: { id: "venice", brandColor: "#3c8fdd", fallbackLetter: "V", svgPath: RAW.venice },
vercel: { id: "vercel", brandColor: "#737373", fallbackLetter: "V", svgPath: RAW.vercel },
synthetic: { id: "synthetic", brandColor: "#141414", fallbackLetter: "S", svgPath: RAW.synthetic },
clawrouter: { id: "clawrouter", brandColor: "#596EF6", fallbackLetter: "C", svgPath: RAW.clawrouter },
ibmbob: { id: "ibmbob", brandColor: "#0E61FA", fallbackLetter: "B", svgPath: RAW.ibmbob },
openaiapi: { id: "openaiapi", brandColor: "#0f826e", fallbackLetter: "O" },
chutes: { id: "chutes", brandColor: "#3184ff", fallbackLetter: "C" },
litellm: { id: "litellm", brandColor: "#4c89f0", fallbackLetter: "L" },
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop-tauri/src/i18n/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ export const ALL_LOCALE_KEYS = [
"AixyGatewayTitle",
"AixyGatewayLabel",
"AixyGatewayHelp",
"ClawRouterGatewayTitle",
"ClawRouterGatewayHelp",
"WayfinderModels",
"WayfinderRequests",
"WayfinderTokens",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ describe("WayfinderGatewaySection", () => {
["wayfinder", "WayfinderGatewayTitle", "WayfinderGatewayLabel", "WayfinderGatewayHelp"],
["bifrost", "BifrostGatewayTitle", "WayfinderGatewayLabel", "BifrostGatewayHelp"],
["aixy", "AixyGatewayTitle", "AixyGatewayLabel", "AixyGatewayHelp"],
["clawrouter", "ClawRouterGatewayTitle", "AixyGatewayLabel", "ClawRouterGatewayHelp"],
] as const)("uses localized %s copy", (providerId, title, label, help) => {
renderSection(providerId);

Expand All @@ -45,6 +46,7 @@ describe("WayfinderGatewaySection", () => {

it("recognizes only gateway providers", () => {
expect(isGatewayProviderId("aixy")).toBe(true);
expect(isGatewayProviderId("clawrouter")).toBe(true);
expect(isGatewayProviderId("bifrost")).toBe(true);
expect(isGatewayProviderId("wayfinder")).toBe(true);
expect(isGatewayProviderId("codex")).toBe(false);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { LocaleKey } from "../../../../i18n/keys";

export type GatewayProviderId = "wayfinder" | "bifrost" | "aixy";
export type GatewayProviderId = "wayfinder" | "bifrost" | "aixy" | "clawrouter";

interface GatewayCopy {
title: LocaleKey;
Expand All @@ -24,6 +24,11 @@ const GATEWAY_COPY: Record<GatewayProviderId, GatewayCopy> = {
label: "AixyGatewayLabel",
help: "AixyGatewayHelp",
},
clawrouter: {
title: "ClawRouterGatewayTitle",
label: "AixyGatewayLabel",
help: "ClawRouterGatewayHelp",
},
};

export function isGatewayProviderId(id: string): id is GatewayProviderId {
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-tauri/src/test/providerCatalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,7 @@ export const TEST_PROVIDER_CATALOG: Array<[string, string]> = [
["devpass", "DevPass"],
["xkiro", "xKiro"],
["raycast", "Raycast"],
["synthetic", "Synthetic"],
["clawrouter", "ClawRouter"],
["ibmbob", "IBM Bob"],
];
58 changes: 58 additions & 0 deletions docs/PROVIDERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,64 @@ are never followed and response bodies are never echoed in errors.
budget, inconsistent coverage, malformed amounts) is a parse error, not a
partial balance.

### Synthetic

Synthetic reads `GET https://api.synthetic.new/v2/quotas` with
`Authorization: Bearer <key>`. Configure the key in Settings → Providers →
Synthetic (or `SYNTHETIC_API_KEY`); the CLI also accepts `synthetic.new`.

- `rollingFiveHourLimit` is the "Five-hour quota" lane, `weeklyTokenLimit` the
"Weekly tokens" lane and `search.hourly` a "Search hourly" window. Other
payload shapes fall back to the first quota-like objects found under
`quotas`, `quota`, `limits`, `usage`, `entries`, `subscription` or `data`.
- When the five-hour quota reports `tickPercent`, the lane resets at the next
regen tick and its detail line reads, for example,
"76% after next regen · Full in ~3 regens". The weekly lane does the same
when it carries `maxCredits` and `nextRegenCredits`. Like the Mac card, no
weekly USD cost is shown (on the card or in CLI JSON).
- The plan name (`plan`, `planName`, `tier`, ...) is shown as the plan.
- 401 and 403 report "Invalid Synthetic API credentials."; response bodies are
never echoed in errors.

### ClawRouter

ClawRouter reports the monthly budget, spend and routed-provider usage of one
policy key (`GET {base}/v1/usage`, `Authorization: Bearer <key>`). Configure
the key in Settings → Providers → ClawRouter (or `CLAWROUTER_API_KEY`). Leave
the Base URL empty for `https://clawrouter.openclaw.ai`; set it (or
`CLAWROUTER_BASE_URL`) for another deployment. The service root and its `/v1`
URL both work, a bare host is treated as HTTPS, and plain HTTP, embedded
credentials, a query or a fragment are rejected before the key is read.
Redirects are never followed and response bodies are never echoed in errors.

- The primary lane is the monthly budget (spent / limit), resetting on the
first day of the next month (UTC). The cost line shows this month's spend
against the limit.
- Details show requests, tokens, actual cost, the budget ledger and the
monthly budget, then up to 20 routed providers ordered by spend and
requests.
- A policy without a monthly limit shows "Unmetered" and this month's actual
cost.

### IBM Bob

IBM Bob reads monthly Bobcoin usage. Configure an API key from the IBM Bob web
portal in Settings → Providers → IBM Bob, as token accounts, or with
`BOBSHELL_API_KEY`; the CLI also accepts `ibm-bob`, `bob` and `bobshell`.

- `GET https://api.us-east.bob.ibm.com/admin/v1/profile` lists the
subscription instances; then each team's budget comes from
`GET /admin/v1/teams/{team}/users/{user}` on the instance's regional host.
- Credentials go only to HTTPS hosts named `bob.ibm.com` or under
`.bob.ibm.com`; any other `region_domain` stops the refresh before a team
request. A JWT session token is sent as `Bearer`, any other key as `Apikey`.
- The "Monthly Bobcoins" bar sums every visible team and shows a percentage
only when every team has a budget; otherwise it shows Bobcoins used. It
resets at the earliest `refresh_at`. The "Bobcoin usage" rows list each team
with its plan, and the plan names are shown as the organization.
- 401 and 403 report that the key was rejected; response bodies are never
echoed in errors.

## Listing what is enabled

```powershell
Expand Down
Loading