Skip to content

fix: support configurable Codex usage endpoints#108

Open
Daltonganger wants to merge 11 commits intoopgginc:mainfrom
Daltonganger:fix/codex-openai-endpoint-config
Open

fix: support configurable Codex usage endpoints#108
Daltonganger wants to merge 11 commits intoopgginc:mainfrom
Daltonganger:fix/codex-openai-endpoint-config

Conversation

@Daltonganger
Copy link
Copy Markdown
Contributor

Summary

  • add OpenCode-config-driven Codex usage endpoint resolution with explicit override, derived external endpoint support, and safe default behavior
  • route codex-lb external usage requests through chatgpt_account_id while keeping existing account IDs for direct ChatGPT usage
  • add unit coverage for Codex endpoint selection and codex-lb external account ID mapping

Update

  • replace the crashing default-URL assertion with a logged provider error so invalid default URL handling fails gracefully instead of terminating the app

Verification

  • xcodebuild build -project "CopilotMonitor/CopilotMonitor.xcodeproj" -scheme "CopilotMonitor" -configuration Debug -clonedSourcePackagesDirPath "/Users/rubenbeuker/Library/Developer/Xcode/DerivedData/CopilotMonitor-aggycqisuzewkzczboobrrtuajll/SourcePackages"
  • xcodebuild test -project "CopilotMonitor/CopilotMonitor.xcodeproj" -scheme "CopilotMonitor" -destination 'platform=macOS' -clonedSourcePackagesDirPath "/Users/rubenbeuker/Library/Developer/Xcode/DerivedData/CopilotMonitor-aggycqisuzewkzczboobrrtuajll/SourcePackages"
  • launched the Debug app build and checked /usr/bin/log show --last 2m --predicate 'subsystem == "com.opencodeproviders"'

Daltonganger and others added 7 commits February 12, 2026 18:20
Integrate Nano-GPT as a quota-based provider with daily/monthly reset tracking, local icon assets, and auth parsing support. Add UI/CLI wiring and tests so Nano-GPT usage, balances, and the  subscription preset are available end-to-end.
…ations

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
# Conflicts:
#	CopilotMonitor/CopilotMonitor.xcodeproj/project.pbxproj
#	CopilotMonitor/CopilotMonitor/App/StatusBarController.swift
#	CopilotMonitor/CopilotMonitor/Helpers/ProviderMenuBuilder.swift
#	CopilotMonitor/CopilotMonitor/Providers/NanoGptProvider.swift
#	CopilotMonitor/CopilotMonitorTests/NanoGptProviderTests.swift
@op-gg-ai-devops
Copy link
Copy Markdown
Contributor

This PR introduces configurable Codex usage endpoints, adds fallback account fetching for Antigravity, and enhances API key discovery for search providers by supporting search-keys.json and JSON comment stripping.


The fallback mechanisms and centralized TokenManager updates for handling search-keys.json are clean and robust. However, writing a custom Python JSON comment stripper inside the shell scripts is a bit wild tbh—it works, but it might be slightly brittle to maintain. Also, good job using privacy: .public for safe logging, but just double-check that the new chatgpt_account_id mapping doesn't accidentally leak any sensitive PII in edge cases.


This is just a short preview. For a detailed, line-by-line code review (including bug detection and security checks), please write a comment like @op-gg-ai-devops review this / @op-gg-ai-devops 리뷰해줘, or add @op-gg-ai-devops as a Reviewer (automatically review when a commit is pushed to this PR).

@kargnas
Copy link
Copy Markdown
Member

kargnas commented Mar 27, 2026

@op-gg-ai-devops

@op-gg-ai-devops
Copy link
Copy Markdown
Contributor

op-gg-ai-devops bot commented Mar 27, 2026

❌ AI Code Review Cancelled

Newer commit detected or workflow was manually cancelled.


📋 View Logs | 🤖 Model: anthropic/claude-opus-4-6

@kargnas
Copy link
Copy Markdown
Member

kargnas commented Mar 27, 2026

@Daltonganger this looks like include some other changes not related to the PR

@op-gg-ai-devops
Copy link
Copy Markdown
Contributor

op-gg-ai-devops bot commented Mar 27, 2026

✅ AI Code Review Completed

Review posted on PR #108 — 4 inline comments with 1 critical bug, 1 major issue, and 2 minor findings. Check the review thread for details.

Reviewed with anthropic/claude-opus-4-6


📋 View Full Logs

@Daltonganger
Copy link
Copy Markdown
Contributor Author

Done. I removed the unrelated changes and left only the Codex external URL / endpoint update in this PR.

Copy link
Copy Markdown
Contributor

@op-gg-ai-devops op-gg-ai-devops bot left a comment

Choose a reason for hiding this comment

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

Review Summary

Solid overall design here — the endpoint configuration system with CodexEndpointConfiguration / CodexEndpointMode is clean, the OpenCode config resolution chain with explicit > derived > default priority is well thought out, and the test coverage for the endpoint selection logic is genuinely good. The Antigravity accounts fallback is also a nice resilience improvement.

However, there's a critical off-by-one bug in the SQLite column indexing that will silently break ALL codex-lb account loading. This needs to be fixed before merge.

What's Good

  • Config-driven endpoint resolution with proper validation (scheme check, host check, malformed URL fallback)
  • resolveConfigValue() handles env var references and Bearer prefix stripping — nice touch
  • Test cases cover the important edge cases: default, derived from baseURL, explicit override, malformed fallback, all-malformed
  • Antigravity fallback with cache → API graceful degradation

What Needs Fixing

  • 🔴 Critical: SQLite column index off-by-one — access_token_encrypted is read from the wrong column, causing silent failure for all codex-lb accounts
  • 🟡 Bug: Dead duplicate branch in codexRequestAccountID
  • 🟡 Security: JSON injection via string interpolation in Antigravity fallback HTTP body

CI Status

  • ✅ Lint passed
  • ⏳ Build, Test, CI still running at time of review

Note

Build/typecheck smoke tests could not be run — this is a macOS-only project and the review runner lacks Xcode. CI results should be checked before merge.


Also — repo owner @kargnas noted this PR may include some changes not directly related to the title. Might be worth cleaning up the commit history or splitting out the Antigravity fallback and NanoGPT changes into separate PRs for cleaner review scope.


Push a new commit to this branch to trigger an automatic re-review. You can also mention @op-gg-ai-devops in a comment to request a new review — optionally with specific directions, e.g.:

  • @op-gg-ai-devops review the SQLite column indexing fix
  • @op-gg-ai-devops check security implications
  • @op-gg-ai-devops review typos

chatGPTAccountId: sqliteColumnString(statement, index: 1),
email: sqliteColumnString(statement, index: 2),
planType: sqliteColumnString(statement, index: 3),
status: sqliteColumnString(statement, index: 4),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Critical: Bug SQLite column off-by-one: reads status instead of encrypted token
This is a critical off-by-one bug introduced by adding chatgpt_account_id at column index 1.

The SQL query column order is now:

0: id
1: chatgpt_account_id  ← NEW
2: email
3: plan_type
4: status
5: access_token_encrypted
6: refresh_token_encrypted
7: id_token_encrypted
8: last_refresh

But line 1140 reads index: 4 for accessTokenEncrypted — that's the status column, not the encrypted token. Then line 1149 also reads index: 4 for status, which is correct but means accessTokenEncrypted contains the status string.

The result: decryptCodexLBFernetToken() receives a status string (like "active") instead of actual encrypted token data, Fernet decryption fails, and every codex-lb account silently fails to load.

Fix:

Suggested change
status: sqliteColumnString(statement, index: 4),
guard let accessTokenEncrypted = sqliteColumnData(statement, index: 5), !accessTokenEncrypted.isEmpty else {
continue
}
let account = CodexLBEncryptedAccount(
accountId: sqliteColumnString(statement, index: 0),
chatGPTAccountId: sqliteColumnString(statement, index: 1),
email: sqliteColumnString(statement, index: 2),
planType: sqliteColumnString(statement, index: 3),
status: sqliteColumnString(statement, index: 4),
accessTokenEncrypted: accessTokenEncrypted,
refreshTokenEncrypted: sqliteColumnData(statement, index: 6),
idTokenEncrypted: sqliteColumnData(statement, index: 7),
lastRefresh: sqliteColumnString(statement, index: 8)
)

Wait — index: 5 for accessTokenEncrypted is now correct, but the guard on line 1140 still says index: 4. The assignment on line 1150 uses the accessTokenEncrypted variable (from the guard), so the guard line is the only one that needs changing:

guard let accessTokenEncrypted = sqliteColumnData(statement, index: 5), ...

Comment on lines +457 to +466
func codexRequestAccountID(for account: OpenAIAuthAccount, endpointMode: CodexEndpointMode) -> String? {
switch endpointMode {
case .directChatGPT:
return account.accountId
case .external:
if account.source == .codexLB {
return account.externalUsageAccountId ?? account.accountId
}
return account.externalUsageAccountId ?? account.accountId
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Major: Bug Duplicate branch: codexLB check has no effect
Both branches of the .external case return the exact same expression:

case .external:
    if account.source == .codexLB {
        return account.externalUsageAccountId ?? account.accountId  // branch A
    }
    return account.externalUsageAccountId ?? account.accountId      // branch B (identical)

Either the codexLB check was supposed to do something different (like return account.externalUsageAccountId without the ?? account.accountId fallback), or the if-statement is dead code and should be removed.

If the intent is "codex-lb uses external ID, other sources use regular accountId":

case .external:
    if account.source == .codexLB {
        return account.externalUsageAccountId ?? account.accountId
    }
    return account.accountId

Otherwise just collapse to one line:

case .external:
    return account.externalUsageAccountId ?? account.accountId

request.httpMethod = "POST"
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = "{\"project\":\"\(account.projectId)\"}".data(using: .utf8)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Minor: Security JSON string interpolation: malformed payload on special chars
String interpolation directly into a JSON literal is a classic injection vector. If account.projectId contains a double quote or backslash (unlikely but possible with user-sourced config), the JSON becomes malformed and the request fails silently or — worse — sends unexpected payload structure.

Use proper JSON serialization:

let payload: [String: String] = ["project": account.projectId]
request.httpBody = try JSONSerialization.data(withJSONObject: payload)

or even:

request.httpBody = try JSONEncoder().encode(["project": account.projectId])

OpenAIAuthAccount(
accessToken: access,
accountId: auth.openai?.accountId,
externalUsageAccountId: auth.openai?.accountId,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Minor: Edge Case externalUsageAccountId = accountId may send wrong ID to external endpoints
For opencodeAuth and codexAuth sources, externalUsageAccountId is set to the same value as accountId. This works for now since these sources don't have a separate ChatGPT account ID concept, but it means the external endpoint will send the OpenCode internal account ID as the ChatGPT-Account-Id header — which may or may not be valid for the external service.

Consider setting externalUsageAccountId: nil for non-codex-lb sources and letting the fallback in codexRequestAccountID handle it. This makes the intent clearer and avoids sending potentially wrong IDs to external endpoints.

externalUsageAccountId: nil,  // only codex-lb has a separate ChatGPT account ID

Ruben Beuker added 2 commits March 27, 2026 12:32
…ix/codex-openai-endpoint-config

# Conflicts:
#	CopilotMonitor/CopilotMonitor/Services/TokenManager.swift
@op-gg-ai-devops
Copy link
Copy Markdown
Contributor

op-gg-ai-devops bot commented Mar 27, 2026

✅ AI Code Review Completed

Review posted successfully for PR #108.

Reviewed with anthropic/claude-opus-4-6


📋 View Full Logs

Copy link
Copy Markdown
Contributor

@op-gg-ai-devops op-gg-ai-devops bot left a comment

Choose a reason for hiding this comment

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

Review Summary

The critical SQLite column off-by-one bug from the previous review is fixed — column indices are now correct with chatgpt_account_id at index 1 and all subsequent fields properly shifted. The unrelated changes (Antigravity fallback, NanoGPT, search config) have been cleaned out per the repo owner's request, leaving a focused and clean PR.

What's Good

  • Endpoint configuration system is genuinely well-designed. The CodexEndpointConfiguration / CodexEndpointMode types with the explicit > derived > default resolution chain is clean. URL validation (scheme + host check) with proper fallback on malformed config is solid defensive coding.
  • SQL query fallback cascade is clever. Four query variants handle both old schemas (without chatgpt_account_id) and new schemas gracefully. The DB can evolve without breaking existing installs — nice forward/backward compat.
  • Test coverage is excellent. 10 new test cases covering defaults, derived URLs, explicit overrides, malformed input fallbacks, account ID routing for both codexLB and non-codexLB sources, and the mapping function. The edge cases that matter are all covered.
  • makeCodexLBOpenAIAccount extraction makes the code more testable and the readCodexLBOpenAIAccounts() function cleaner.
  • Crash → graceful error in codexUsageURL replacing the old assertion with a proper throw — exactly the right call for a menu bar app that shouldn't crash on bad config.

CI Status

  • ✅ Lint passed
  • ⏳ Build, Test, CI still running at time of review

Note

Build/typecheck smoke tests could not be run — this is a macOS-only project and the review runner lacks Xcode. CI results should be checked before merge.


Reviewed with anthropic/claude-opus-4-6

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.

2 participants