feat: add Synthetic provider plugin#329
Conversation
Add a new provider plugin for Synthetic (synthetic.new) that displays quota usage from their /v2/quotas API endpoint. Supports both the new v3 rate limit system (rolling 5h request limit and weekly mana bar) and the legacy subscription based quota, showing whichever is relevant to the user's account. Search quota is shown for all users. API key discovery checks multiple sources in order: Pi auth.json, Pi models.json, Factory/Droid settings.json, OpenCode auth.json, and the SYNTHETIC_API_KEY env var. Two new env vars are whitelisted in the Rust host (SYNTHETIC_API_KEY and PI_CODING_AGENT_DIR). Icon sourced from synthetic.new/favicon.svg. Brand color is black to match their branding. Files added: - plugins/synthetic/plugin.json - plugins/synthetic/plugin.js - plugins/synthetic/plugin.test.js (52 tests) - plugins/synthetic/icon.svg - docs/providers/synthetic.md Files modified: - src-tauri/src/plugin_engine/host_api.rs (env var whitelist)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 54e15dd895
ℹ️ 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".
| var onV3 = !!json.rollingFiveHourLimit || !!json.weeklyTokenLimit; | ||
| if (!onV3 && json.subscription && typeof json.subscription.limit === "number") { |
There was a problem hiding this comment.
Require usable v3 fields before hiding legacy lines
onV3 is set from object truthiness alone, so any truthy placeholder (for example rollingFiveHourLimit: {} from a partial/transition payload) suppresses both Subscription and Free Tool Calls even when the v3 lines are not renderable. In that case users lose their only meaningful quota lines and may see incomplete usage data. Gate this on the expected numeric fields (or both v3 blocks) instead of plain object presence.
Useful? React with 👍 / 👎.
| var msg = | ||
| json && json.error ? json.error : "Request failed (HTTP " + resp.status + ")"; | ||
| throw msg; |
There was a problem hiding this comment.
Normalize API error payload to a string before throwing
For non-2xx responses this throws json.error directly, but many APIs return structured objects there. When a non-string is thrown, runtime error extraction falls back to a generic failure message, so users lose the actionable server error text. Convert object-shaped errors to a string (for example error.message) before throwing.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
Adds a new “Synthetic” provider plugin that fetches and displays quota usage from Synthetic’s /v2/quotas endpoint, including support for both the new v3 rate-limit system and the legacy subscription model.
Changes:
- Added
syntheticprovider plugin (manifest, implementation, icon) and comprehensive Vitest coverage. - Added provider documentation for Synthetic (auth discovery, endpoint details, displayed metrics).
- Whitelisted
SYNTHETIC_API_KEYandPI_CODING_AGENT_DIRfor plugin env access in the Tauri host API.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src-tauri/src/plugin_engine/host_api.rs | Extends env-var allowlist to support Synthetic plugin auth/discovery. |
| plugins/synthetic/plugin.json | Declares Synthetic plugin metadata and line definitions (scopes/orders). |
| plugins/synthetic/plugin.js | Implements API key discovery and /v2/quotas probing + line rendering. |
| plugins/synthetic/plugin.test.js | Adds extensive tests covering auth resolution, errors, and v3/legacy outputs. |
| plugins/synthetic/icon.svg | Adds Synthetic icon used in UI. |
| docs/providers/synthetic.md | Documents auth sources, API shape, and displayed lines for Synthetic. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Subscription — legacy request count, only shown if NOT on v3 rate limits | ||
| var onV3 = !!json.rollingFiveHourLimit || !!json.weeklyTokenLimit; | ||
| if (!onV3 && json.subscription && typeof json.subscription.limit === "number") { |
There was a problem hiding this comment.
onV3 detection uses rollingFiveHourLimit || weeklyTokenLimit, but the PR description says the plugin should auto-detect v3 based on the presence of both fields. If the API ever returns only one of these fields, this will treat the user as v3 and hide legacy lines unexpectedly. Either update the detection logic to match the documented behavior, or update the PR description/docs to reflect the OR-based detection.
| it("returns 5 lines when rate limited", () => { | ||
| var ctx = makeCtx(); | ||
| setPiAuth(ctx, "syn_testkey"); | ||
| mockHttp( | ||
| ctx, | ||
| successPayload({ | ||
| rollingFiveHourLimit: { | ||
| nextTickAt: "2026-03-30T15:30:29.000Z", | ||
| tickPercent: 0.05, | ||
| remaining: 0, | ||
| max: 600, | ||
| limited: true, | ||
| }, | ||
| }) | ||
| ); | ||
| var result = plugin.probe(ctx); | ||
| expect(result.lines.length).toBe(4); // v3 user rate limited: no subscription | ||
| expect(result.lines[2].label).toBe("Rate Limited"); |
There was a problem hiding this comment.
The test name says "returns 5 lines when rate limited" but the assertion expects 4 lines (and the listed labels also add up to 4). This mismatch makes the test misleading—rename the test or update the expectation to match the intended behavior.
| | Line | Scope | Condition | Description | | ||
| |----------------|----------|----------------------------------|------------------------------------------------| | ||
| | 5h Rate Limit | overview | `rollingFiveHourLimit` present | Requests remaining in 5-hour rolling window | | ||
| | Mana Bar | overview | `weeklyTokenLimit` present | Weekly token budget as percentage | | ||
| | Rate Limited | detail | `rollingFiveHourLimit.limited` | Red badge shown only when actively rate limited| | ||
| | Subscription | detail | `subscription` present | Legacy request count for billing period | | ||
| | Search | detail | `search.hourly` present | Hourly search request quota | |
There was a problem hiding this comment.
The "Displayed Lines" table doesn’t match the implementation/manifest: (1) the plugin can emit a "Free Tool Calls" progress line for legacy users but it’s missing from the table, (2) "Subscription" is listed as scope=detail but the manifest declares it as scope=overview, and (3) both legacy lines are only shown when NOT on v3 (when v3 fields are absent). Consider updating the table (and the 5h Rate Limit description if you intend to describe used vs remaining) to reflect what the plugin actually returns/displays.
| | Line | Scope | Condition | Description | | |
| |----------------|----------|----------------------------------|------------------------------------------------| | |
| | 5h Rate Limit | overview | `rollingFiveHourLimit` present | Requests remaining in 5-hour rolling window | | |
| | Mana Bar | overview | `weeklyTokenLimit` present | Weekly token budget as percentage | | |
| | Rate Limited | detail | `rollingFiveHourLimit.limited` | Red badge shown only when actively rate limited| | |
| | Subscription | detail | `subscription` present | Legacy request count for billing period | | |
| | Search | detail | `search.hourly` present | Hourly search request quota | | |
| | Line | Scope | Condition | Description | | |
| |----------------|----------|---------------------------------------------------|-----------------------------------------------------| | |
| | 5h Rate Limit | overview | `rollingFiveHourLimit` present | Usage in 5-hour rolling window (used / limit) | | |
| | Mana Bar | overview | `weeklyTokenLimit` present | Weekly token budget as percentage | | |
| | Rate Limited | detail | `rollingFiveHourLimit.limited` | Red badge shown only when actively rate limited | | |
| | Subscription | overview | `subscription` present and v3 fields absent | Legacy request count for billing period | | |
| | Free Tool Calls| overview | `freeToolCalls` present and v3 fields absent | Legacy free tool-call quota | | |
| | Search | detail | `search.hourly` present | Hourly search request quota | |
| Progress lines include: | ||
| - `resetsAt` — ISO timestamp of next restoration tick (5h, mana) or renewal (subscription, search) | ||
| - `periodDurationMs` — 5 hours (rate limit), 1 week (mana), or 1 hour (search) |
There was a problem hiding this comment.
Docs state that progress lines include resetsAt and periodDurationMs for 5h rate limit and mana (restoration ticks), but the plugin implementation/tests intentionally omit these fields for those lines (only Search/legacy lines include reset info). Please update this section to match the actual output (or update the plugin if the intent is to expose those fields).
| Progress lines include: | |
| - `resetsAt` — ISO timestamp of next restoration tick (5h, mana) or renewal (subscription, search) | |
| - `periodDurationMs` — 5 hours (rate limit), 1 week (mana), or 1 hour (search) | |
| Progress lines expose the following fields: | |
| - 5h Rate Limit / Mana Bar: remaining quota or percentage only (no reset metadata fields) | |
| - Subscription / Search: include `resetsAt` (ISO timestamp of next renewal) and `periodDurationMs` (billing-period duration for subscription, 1 hour for search) |
Description
Adds a new provider plugin for Synthetic that displays quota usage from their
/v2/quotasAPI endpoint.Synthetic is currently rolling out a new rate limit system (v3) alongside the legacy subscription model. This plugin supports both:
The plugin auto-detects which system the user is on based on the presence of
rollingFiveHourLimitandweeklyTokenLimitin the API response.API key discovery checks multiple sources in order:
auth.jsonmodels.jsonsettings.json(matchescustomModelsentries withsynthetic.newin the baseUrl)auth.jsonSYNTHETIC_API_KEYenv varRelated Issue
N/A — no existing issue for this provider.
Type of Change
Testing
bun run buildand it succeededbun run testand all tests pass (991 tests, 59 files, including 52 new tests for this plugin)bun tauri devScreenshots
Overview — Synthetic in the main plugin list
Detail view — 5h Rate Limit, Mana Bar, and Search
Settings — Synthetic as a toggleable plugin
API response — raw /v2/quotas output matching the displayed values
Synthetic billing page — their own dashboard for comparison
Files
Added:
plugins/synthetic/plugin.json— manifest with 6 line declarationsplugins/synthetic/plugin.js— probe implementation with multi-source auth discoveryplugins/synthetic/plugin.test.js— 52 tests covering auth, errors, all line types, v3/legacy detectionplugins/synthetic/icon.svg— sourced from synthetic.new/favicon.svgdocs/providers/synthetic.md— provider documentationModified:
src-tauri/src/plugin_engine/host_api.rs— addedSYNTHETIC_API_KEYandPI_CODING_AGENT_DIRto env var whitelistChecklist
mainbranch