Add Qoder usage provider#1736
Conversation
|
Codex review: needs changes before merge. Reviewed June 25, 2026, 6:51 AM ET / 10:51 UTC. Summary Reproducibility: yes. for the review findings: source inspection shows Qoder totals still flow through resetDescription into a standard menu path that does not whitelist Qoder, manual cookies are always labeled qoder.com, and invalid auto-cookie retry advances only once. No unrequested live provider or Keychain validation was run under AGENTS.md guidance. Review metrics: 2 noteworthy metrics.
Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Security Review findings
Review detailsBest possible solution: Land Qoder after credit-detail rendering is consistent across menu/card/CLI paths, cookie resolution handles both Qoder domains through all imported candidates, and maintainers accept the new cookie-auth provider behavior. Do we have a high-confidence way to reproduce the issue? Yes for the review findings: source inspection shows Qoder totals still flow through resetDescription into a standard menu path that does not whitelist Qoder, manual cookies are always labeled qoder.com, and invalid auto-cookie retry advances only once. No unrequested live provider or Keychain validation was run under AGENTS.md guidance. Is this the best way to solve the issue? No, not yet: the provider shape and proof are promising, but the maintainable solution still needs consistent detail rendering, domain-aware manual cookie handling, full candidate retry, and maintainer acceptance for cookie-auth scope. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against ada3660e9d61. Label changesLabel justifications:
Evidence reviewedAcceptance criteria:
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7d94f1fef0
ℹ️ 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 usedValue: Int | ||
| let limitValue: Int | ||
| let remainingValue: Int | ||
| let usagePercentage: Int |
There was a problem hiding this comment.
Decode Qoder credit values as decimals
When Qoder returns fractional credit balances (for example after a 0.35-credit usage event), these Int fields make JSONDecoder throw a type-mismatch and the provider shows a parse error instead of usage. The snapshot already stores Doubles, so the wire model should accept decimal values for used/limit/remaining/percentage rather than requiring integral JSON numbers.
Useful? React with 👍 / 👎.
| .crof: CrofProviderDescriptor.descriptor, | ||
| .venice: VeniceProviderDescriptor.descriptor, | ||
| .commandcode: CommandCodeProviderDescriptor.descriptor, | ||
| .qoder: QoderProviderDescriptor.descriptor, |
There was a problem hiding this comment.
Wire Qoder into CLI cookie settings
Registering Qoder exposes it through the CLI provider flow, but TokenAccountCLI.makeCookieBackedSnapshot has no .qoder case and makeSnapshot has no qoder parameter. In CLI contexts, a configured manual Cookie header or selected Qoder token account therefore never populates ProviderSettingsSnapshot.qoder, so codexbar usage qoder --account .../manual config falls back to auto browser cookies or missing credentials instead of using the supplied header.
Useful? React with 👍 / 👎.
| #if os(macOS) | ||
| return true | ||
| #else | ||
| return false |
There was a problem hiding this comment.
Allow manual Qoder cookies off macOS
For non-macOS CLI builds, this makes the strategy unavailable before resolveCookieHeader can consume settings.qoder.cookieSource == .manual, and fetch also compiles to missingCredentials. Manual Cookie headers and token-account Cookie overrides should work without browser import, as the HTTP fetcher itself is platform-neutral; only the automatic browser import/cache path needs to be macOS-gated.
Useful? React with 👍 / 👎.
| for source in sources where !source.records.isEmpty { | ||
| let cookies = BrowserCookieClient.makeHTTPCookies(source.records, origin: query.origin) | ||
| guard !cookies.isEmpty else { continue } | ||
| self.emit("Found \(cookies.count) cookies in \(source.label)", logger: logger) | ||
| return SessionInfo(cookies: cookies, sourceLabel: source.label) |
There was a problem hiding this comment.
Try later Qoder cookie sources after stale cookies
When automatic import finds any cookies in the first browser/profile, it returns them immediately without checking for an auth cookie or preserving later candidates. If that first source only has anonymous or expired qoder.com cookies, the fetch gets invalidCredentials, clears the cache, then re-imports and retries the same bad source, so a valid Qoder session in a later profile/browser is never used.
Useful? React with 👍 / 👎.
| case crof | ||
| case venice | ||
| case commandcode | ||
| case qoder |
There was a problem hiding this comment.
Update config docs for Qoder provider ID
Adding .qoder to UsageProvider.allCases also requires updating docs/configuration.md's Provider IDs list; ConfigurationDocsProviderIDTests reads that section and expects it to exactly match enum order. As committed, the docs list jumps from commandcode to stepfun, so the documentation test fails for this provider addition.
Useful? React with 👍 / 👎.
| let cookieSource = context.settings?.qoder?.cookieSource ?? .auto | ||
| do { | ||
| let (cookieHeader, sourceLabel) = try Self.resolveCookieHeader(context: context, allowCached: true) | ||
| let snapshot = try await QoderUsageFetcher.fetchUsage(cookieHeader: cookieHeader) |
There was a problem hiding this comment.
Honor the Qoder web timeout setting
Qoder fetches ignore context.webTimeout here because fetchUsage has no timeout parameter and always uses the fetcher's hard-coded 15 seconds. In CLI runs with a larger --web-timeout or app refreshes that pass 60 seconds, a slow Qoder API response can still fail early even though the caller explicitly allowed more time.
Useful? React with 👍 / 👎.
| toggleTitle: "Show Qoder usage", | ||
| cliName: "qoder", | ||
| defaultEnabled: false, | ||
| isPrimaryProvider: false, |
There was a problem hiding this comment.
Keep Qoder's default cookie import Chrome-only
Using ProviderBrowserCookieDefaults.defaultImportOrder makes automatic Qoder refresh probe every default browser, so enabling this provider can trigger unrelated Safari/Firefox/Chromium keychain prompts. The repository guidance for cookie imports is to default Chrome-only when possible, and Qoder still has manual Cookie headers/token accounts for non-Chrome sessions.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7adc20978d
ℹ️ 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 totalQuota: QoderQuotaContainer? | ||
|
|
||
| private enum CodingKeys: String, CodingKey { | ||
| case totalQuota = "total_quota" |
There was a problem hiding this comment.
Match Qoder's documented quota JSON shape
Qoder's documented Get member quota payload (https://docs.qoder.com/account/teams/openapi/members#get-member-quota) uses the camelCase path totalQuota.quotaSummary with usedValue/limitValue/unit, while this decoder looks for total_quota.quota_summary and then requires derived fields the documented response does not send. Any account returning the documented quota schema will hit the missing total_quota.quota_summary parse error instead of showing usage; accept the documented camelCase shape and derive remaining/percentage from used/limit.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 42b4921c53
ℹ️ 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".
| usedPercent: min(100, max(0, self.usagePercentage)), | ||
| windowMinutes: nil, | ||
| resetsAt: nil, | ||
| resetDescription: creditSummary) |
There was a problem hiding this comment.
Stop using resetDescription for Qoder totals
For any successful Qoder response, creditSummary is not a reset time, but the normal menu/CLI renderers call UsageFormatter.resetLine for primary windows and prefix a non-empty resetDescription with Resets. That makes Qoder display Resets 125 / 500 credits even though there is no reset, so the credit total should be carried as provider-specific detail or left out of resetDescription instead of being attached here.
Useful? React with 👍 / 👎.
| let primary = RateWindow( | ||
| usedPercent: min(100, max(0, self.usagePercentage)), | ||
| windowMinutes: nil, | ||
| resetsAt: nil, |
There was a problem hiding this comment.
Preserve Qoder quota reset dates
When Qoder returns the documented quota payload with nextResetAt/lastResetAt, this always sets resetsAt to nil, so the menu/CLI cannot show the monthly reset countdown even though reset metadata was available. Decode nextResetAt in the fetcher and pass it through here as the primary window's reset date.
Useful? React with 👍 / 👎.
eb2e136 to
d9e4f73
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d9e4f73651
ℹ️ 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".
| throw QoderUsageError.parseFailed("invalid JSON: \(error.localizedDescription)") | ||
| } | ||
|
|
||
| guard let summary = response.totalQuota?.quotaSummary else { |
There was a problem hiding this comment.
Include shared Qoder credits in quota totals
For team accounts that receive sharedQuota in Qoder's member quota response, totalQuota is only the plan plus resource-pack quota. Reading only this field drops shared add-on credits; for example, an exhausted personal quota with 1,000 shared credits still available displays as 100% used and 0 remaining. Decode sharedQuota and fold it into the displayed total/remaining when present.
Useful? React with 👍 / 👎.
| providerID: .qoder, | ||
| accountEmail: nil, | ||
| accountOrganization: nil, | ||
| loginMethod: "\(Self.formatCredits(self.remainingCredits)) credits remaining") |
There was a problem hiding this comment.
Stop storing Qoder remaining credits as loginMethod
For every successful Qoder snapshot, this value is consumed by UsageMenuCardView.Model.plan and CLIRenderer.appendIdentityAndNotes as plan/login text, so the UI/CLI show a bogus line such as Plan: 375 Credits Remaining. Remaining credits are quota detail, not identity or plan data; leave loginMethod nil or expose the value through a Qoder-specific detail field.
Useful? React with 👍 / 👎.
Fold optional sharedQuota into displayed totals, keep remaining credits out of loginMethod, and align fixtures with the schema from CodexBar#1590. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c2cde4dd94
ℹ️ 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 snapshot = try await QoderUsageFetcher.fetchUsage( | ||
| cookieHeader: cookieHeader, | ||
| timeout: context.webTimeout) | ||
| return self.makeResult(usage: snapshot.toUsageSnapshot(), sourceLabel: sourceLabel) |
There was a problem hiding this comment.
Continue past every stale Qoder source
When automatic import has more than one unauthenticated/expired Qoder cookie set before a valid profile, this retry fetch is attempted only once; if the second candidate also returns invalidCredentials, the error escapes and later imported sessions are never tried. Fresh evidence in this version is that the invalid-credential catch resolves and fetches exactly one alternate source here, with no loop/catch around that second request.
Useful? React with 👍 / 👎.
|
Added real Qoder behavior proof from a free Qoder China account. What I ran on the PR branch after adding qoder.com.cn support: $ GIT_OPTIONAL_LOCKS=0 swift run CodexBarCLI usage --provider qoder --source web --format json --pretty > /tmp/qoder-proof.json
Build of product 'CodexBarCLI' complete!
$ rg -n "Cookie|Authorization|sid=|token|secret|password|Bearer|qoder-account-token" /tmp/qoder-proof.json
# no matches
$ python3 -m json.tool /tmp/qoder-proof.json
[
{
"provider": "qoder",
"source": "Chrome Default / qoder.com.cn",
"usage": {
"identity": {
"providerID": "qoder"
},
"primary": {
"resetDescription": "0 / 300 credits",
"usedPercent": 0
},
"secondary": null,
"tertiary": null,
"updatedAt": "2026-06-25T10:40:58Z"
}
}
]This confirms the provider can import a real Chrome session for @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6ed44d1c6f
ℹ️ 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".
| guard let manual = CookieHeaderNormalizer.normalize(context.settings?.qoder?.manualCookieHeader) else { | ||
| throw QoderUsageError.missingCredentials | ||
| } | ||
| return (manual, Self.sourceLabel(browserLabel: "manual", site: .international)) |
There was a problem hiding this comment.
Route manual Qoder China cookies to the China host
When a user pastes a Cookie header or cURL capture from qoder.com.cn, this branch always labels the manual credentials as qoder.com, and site(for:) then sends the request to the international https://qoder.com/... endpoint. Because the importer explicitly supports qoder.com.cn sessions but manual mode has no way to select that site, China-domain manual cookies/token accounts will be sent to the wrong host and fail even though the same session works through automatic import.
Useful? React with 👍 / 👎.
Summary
big_model_creditsusage endpointCloses #1590.
Tests
swiftformat Sources/CodexBarCore/Providers/Qoder Sources/CodexBar/Providers/Qoder Tests/CodexBarTests/QoderProviderTests.swift Tests/CodexBarTests/QoderUsageFetcherTests.swift Sources/CodexBarCore/Logging/LogCategories.swift Sources/CodexBarCore/Providers/Providers.swift Sources/CodexBarCore/Providers/ProviderDescriptor.swift Sources/CodexBarCore/Providers/ProviderSettingsSnapshot.swift Sources/CodexBarCore/TokenAccountSupportCatalog+Data.swift Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift Sources/CodexBar/Providers/Shared/ProviderImplementationRegistry.swift Sources/CodexBar/UsageStore.swift Sources/CodexBarWidget/CodexBarWidgetViews.swift Sources/CodexBarWidget/CodexBarWidgetProvider.swiftgit diff --cached --checkHOME=/Users/yuxinqiao/Documents/Codex/Codexbar-bugfix1/CodexBar-main/.codex-home CLANG_MODULE_CACHE_PATH=/Users/yuxinqiao/Documents/Codex/Codexbar-bugfix1/CodexBar-main/.build/module-cache swift test --filter 'Qoder|ProviderRegistryTests|ProviderIconResourcesTests|ProviderLabelMetadataCharacterizationTests'(passed in the full local source tree used for implementation)\n\nNote: I also attempted the same focusedswift teston this pushed branch after expanding the partial clone, but SwiftPM stalled downloading the Sparkle binary artifact before compilation produced any Qoder errors.