Add ChatGPT/Codex subscription tier (loopback proxy)#7401
Add ChatGPT/Codex subscription tier (loopback proxy)#7401Git-on-my-level wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bcdc7bbed2
ℹ️ 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".
| raise HTTPException( | ||
| status_code=400, | ||
| detail='Invalid fingerprint: expected lowercase hex SHA-256 (64 chars)', | ||
| ) | ||
| users_db.set_chatgpt_active(uid, data.fingerprint) |
There was a problem hiding this comment.
Verify subscription ownership before setting chatgpt.active
This endpoint grants ChatGPT enrollment after only a format check on fingerprint, so any authenticated user can POST an arbitrary 64-hex string and flip chatgpt.active to true. Because downstream gating (is_chatgpt_active) is what unlocks unlimited subscription/quota paths, this creates a direct paid-feature bypass without proving the caller actually has a ChatGPT/Codex subscription.
Useful? React with 👍 / 👎.
| "model": if requested_model_hint.trim().is_empty() { Value::Null } else { Value::String(requested_model_hint.clone()) }, | ||
| "choices": [{ | ||
| "index": 0, | ||
| "message": { "role": "assistant", "content": assistant_text }, | ||
| "logprobs": null, |
There was a problem hiding this comment.
Preserve tool-call outputs in proxy chat completion responses
The proxy response synthesized here always returns only assistant text content and never includes message.tool_calls, even when the upstream Codex stream represents tool-calling output. CodexLLMClient.performGeminiCompatibleToolRound depends on tool_calls to drive Task/Insight tool loops, so in ChatGPT mode those flows can terminate early or fail on the first required-tool round because no executable tool call is propagated.
Useful? React with 👍 / 👎.
| if isinstance(last_seen, datetime): | ||
| age = (datetime.now(timezone.utc) - last_seen).total_seconds() | ||
| else: | ||
| return False | ||
| return age <= BYOK_HEARTBEAT_TTL_SECONDS |
There was a problem hiding this comment.
Refresh ChatGPT enrollment before TTL-based deactivation
is_chatgpt_active expires enrollment strictly by a 7-day TTL on last_seen_at, but desktop enrollment currently updates that timestamp only during explicit connect flow (not on normal app startup/use). As a result, active users can silently drop out of ChatGPT-unlimited status after seven days unless they manually reconnect, which causes unexpected quota/paywall regressions.
Useful? React with 👍 / 👎.
Greptile SummaryThis PR introduces a ChatGPT/Codex subscription tier that lets desktop users route proactive LLM workloads through a local loopback proxy (
Confidence Score: 3/5The enrollment endpoint accepts any valid SHA-256 hex as a legitimate Codex fingerprint without verifying it against OpenAI, meaning any authenticated Omi user can self-enroll and permanently bypass both the 3-day desktop trial and monthly LLM quota. The fingerprint stored at enrollment is never validated against a live ChatGPT session — only its format (64-char hex) is checked. Because
Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Desktop as Desktop (Swift)
participant Proxy as codex-proxy (Rust :10531)
participant Omi as Omi Backend (FastAPI)
participant Firestore as Firestore
participant Codex as ChatGPT Codex API
Note over Desktop: User clicks Sign in with ChatGPT
Desktop->>Desktop: "launchCodexLogin terminal npx @openai/codex login"
Desktop->>Desktop: Poll ~/.codex/auth.json (2s interval, 120s timeout)
Desktop->>Proxy: Start omi-codex-proxy process
Proxy->>Proxy: Load auth.json, bind :10531
Desktop->>Proxy: GET /health (poll 30x100ms)
Proxy-->>Desktop: 200 ok
Desktop->>Omi: POST /v1/users/me/chatgpt-active fingerprint SHA256(account_id)
Omi->>Firestore: set_chatgpt_active(uid, fingerprint)
Omi-->>Desktop: active true
Note over Desktop: Per-request (proactive assistants)
Desktop->>Proxy: POST /v1/chat/completions messages model
Proxy->>Proxy: codex_payload_from_openai_chat()
Proxy->>Codex: POST /backend-api/codex/responses SSE
Codex-->>Proxy: text/event-stream chunks
Proxy->>Proxy: collect_text_from_codex_sse()
Proxy-->>Desktop: choices message content
Note over Desktop: Per-request (backend API calls)
Desktop->>Omi: Any API call + X-ChatGPT-Fingerprint header
Omi->>Omi: ChatGPTMiddleware sets ContextVar
Omi->>Firestore: is_chatgpt_active(uid) read 1
Omi->>Firestore: get_chatgpt_state(uid) read 2
Omi->>Omi: verify fingerprint matches, refresh heartbeat
Omi-->>Desktop: bypass quota / trial paywall
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Desktop as Desktop (Swift)
participant Proxy as codex-proxy (Rust :10531)
participant Omi as Omi Backend (FastAPI)
participant Firestore as Firestore
participant Codex as ChatGPT Codex API
Note over Desktop: User clicks Sign in with ChatGPT
Desktop->>Desktop: "launchCodexLogin terminal npx @openai/codex login"
Desktop->>Desktop: Poll ~/.codex/auth.json (2s interval, 120s timeout)
Desktop->>Proxy: Start omi-codex-proxy process
Proxy->>Proxy: Load auth.json, bind :10531
Desktop->>Proxy: GET /health (poll 30x100ms)
Proxy-->>Desktop: 200 ok
Desktop->>Omi: POST /v1/users/me/chatgpt-active fingerprint SHA256(account_id)
Omi->>Firestore: set_chatgpt_active(uid, fingerprint)
Omi-->>Desktop: active true
Note over Desktop: Per-request (proactive assistants)
Desktop->>Proxy: POST /v1/chat/completions messages model
Proxy->>Proxy: codex_payload_from_openai_chat()
Proxy->>Codex: POST /backend-api/codex/responses SSE
Codex-->>Proxy: text/event-stream chunks
Proxy->>Proxy: collect_text_from_codex_sse()
Proxy-->>Desktop: choices message content
Note over Desktop: Per-request (backend API calls)
Desktop->>Omi: Any API call + X-ChatGPT-Fingerprint header
Omi->>Omi: ChatGPTMiddleware sets ContextVar
Omi->>Firestore: is_chatgpt_active(uid) read 1
Omi->>Firestore: get_chatgpt_state(uid) read 2
Omi->>Omi: verify fingerprint matches, refresh heartbeat
Omi-->>Desktop: bypass quota / trial paywall
Reviews (2): Last reviewed commit: "Fix ChatGPT tier security and desktop Co..." | Re-trigger Greptile |
| let raw = UserDefaults.standard.string(forKey: "memory_search_mode") ?? "local_wiki" | ||
| return raw == "vector" ? .vectorEmbeddings : .localWiki |
There was a problem hiding this comment.
The fallback value
"local_wiki" causes all desktop users (regardless of ChatGPT tier) to use the local wiki search by default. Any user without a stored memory_search_mode preference gets .localWiki, which disables vector embedding search system-wide. The PR description states local wiki should only activate when ChatGPT tier is active, so non-ChatGPT users would lose vector-search deduplication in both TaskAssistant and MemoryAssistant.
| let raw = UserDefaults.standard.string(forKey: "memory_search_mode") ?? "local_wiki" | |
| return raw == "vector" ? .vectorEmbeddings : .localWiki | |
| let raw = UserDefaults.standard.string(forKey: "memory_search_mode") ?? "vector" | |
| return raw == "vector" ? .vectorEmbeddings : .localWiki |
| @router.post('/v1/users/me/chatgpt-active', tags=['v1']) | ||
| def activate_chatgpt_endpoint( | ||
| data: ChatGPTActivateRequest, uid: str = Depends(auth.get_current_user_uid_no_byok_validation) | ||
| ): | ||
| """Enroll ChatGPT / Codex subscription tier (LLM workloads only; no provider keys stored).""" | ||
| if not _SHA256_HEX_RE.match(data.fingerprint): | ||
| raise HTTPException( | ||
| status_code=400, | ||
| detail='Invalid fingerprint: expected lowercase hex SHA-256 (64 chars)', | ||
| ) | ||
| users_db.set_chatgpt_active(uid, data.fingerprint) | ||
| clear_trial_paywall_cache(uid) | ||
| return {"active": True} |
There was a problem hiding this comment.
No server-side validation of ChatGPT subscription
The activation endpoint accepts any 64-char hex string as a valid enrollment fingerprint. The fingerprint is stored in Firestore, but is_chatgpt_active (and therefore enforce_chat_quota) never re-verifies it against OpenAI — it only checks that active=True and the Firestore timestamp is fresh. Any authenticated Omi user can POST an arbitrary SHA-256 hex to permanently bypass the 3-day trial paywall and monthly LLM quota without ever having a ChatGPT subscription. Unlike BYOK, there is no per-request proof-of-possession; once enrolled, the bypass is unconditional.
| if isinstance(last_seen, datetime): | ||
| age = (datetime.now(timezone.utc) - last_seen).total_seconds() | ||
| else: | ||
| return False | ||
| return age <= BYOK_HEARTBEAT_TTL_SECONDS | ||
|
|
||
|
|
||
| def set_chatgpt_active(uid: str, fingerprint: str): |
There was a problem hiding this comment.
The ChatGPT TTL reuses
BYOK_HEARTBEAT_TTL_SECONDS, a constant whose name and comment are semantically tied to the BYOK feature. If the two TTLs ever diverge, this shared constant would be the wrong one to change. A dedicated constant makes the intent explicit.
| if isinstance(last_seen, datetime): | |
| age = (datetime.now(timezone.utc) - last_seen).total_seconds() | |
| else: | |
| return False | |
| return age <= BYOK_HEARTBEAT_TTL_SECONDS | |
| def set_chatgpt_active(uid: str, fingerprint: str): | |
| if isinstance(last_seen, datetime): | |
| age = (datetime.now(timezone.utc) - last_seen).total_seconds() | |
| else: | |
| return False | |
| return age <= CHATGPT_HEARTBEAT_TTL_SECONDS | |
| def set_chatgpt_active(uid: str, fingerprint: str): |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| fn codex_body_to_chat_completion(model_fallback: &str, bytes: &[u8]) -> Result<Value, String> { | ||
| let v: Value = serde_json::from_slice(bytes).map_err(|e| format!("upstream json: {e}"))?; | ||
|
|
||
| if v.get("choices").is_some() { | ||
| let mut enriched = v; | ||
| if enriched.get("id").and_then(Value::as_str).is_none() | ||
| || enriched.get("id") == Some(&Value::Null) | ||
| { | ||
| enriched["id"] = Value::String(new_chat_completion_id()); | ||
| } | ||
| if enriched.get("object").and_then(Value::as_str).is_none() | ||
| || enriched.get("object") == Some(&Value::Null) | ||
| { | ||
| enriched["object"] = Value::from("chat.completion"); | ||
| } | ||
| if enriched.get("created").and_then(Value::as_i64).is_none() | ||
| || enriched.get("created") == Some(&Value::Null) | ||
| { | ||
| enriched["created"] = Value::Number(unix_secs().into()); | ||
| } | ||
| Ok(enriched) | ||
| } else { | ||
| let text = extract_assistant_text(&v) | ||
| .ok_or_else(|| serde_json::to_string(&v).unwrap_or_else(|_| "(unprintable)".into()))?; | ||
| let model = chat_model_choice(&v, model_fallback)?; | ||
| Ok(json!({ | ||
| "id": new_chat_completion_id(), | ||
| "object": "chat.completion", | ||
| "created": unix_secs(), | ||
| "model": model, | ||
| "choices": [{ | ||
| "index": 0, | ||
| "message": { "role": "assistant", "content": text}, | ||
| "logprobs": null, | ||
| "finish_reason": infer_finish_reason(&v), | ||
| }], | ||
| "usage": v.get("usage").cloned().unwrap_or(Value::Null), | ||
| })) | ||
| } | ||
| } |
There was a problem hiding this comment.
codex_body_to_chat_completion is dead production code
invoke_codex assembles the response inline using collect_text_from_codex_sse and never calls this function. It is referenced only by the maps_responses_like_output_message unit test. Keeping it creates a diverging code path that can mislead future contributors into thinking the proxy has a non-SSE fallback mode.
| proc.standardOutput = FileHandle.nullDevice | ||
| proc.standardError = FileHandle.nullDevice |
There was a problem hiding this comment.
Proxy stderr silenced — startup failures are undiagnosable
proc.standardError = FileHandle.nullDevice discards all error output from the proxy process. If the proxy crashes on startup (missing auth file, port conflict, bad token format), the only signal the Swift side sees is a health-check timeout with the generic message "Codex proxy failed to start". Routing stderr to a pipe would allow the error message to be surfaced in lastError.
Route proactive LLM through a localhost Codex proxy using ~/.codex auth, add Settings enrollment UX, local memory wiki + FTS for search, backend tier activation, and bundle the proxy in run.sh. Adapted for upstream main without hybrid local-daemon dependencies. Co-authored-by: Cursor <cursoragent@cursor.com>
Require X-ChatGPT-Fingerprint on requests for quota and subscription bypass, refresh enrollment heartbeat from desktop launch and throttled server updates, resolve Codex transport at call time in GeminiClient, label wiki search hits distinctly from tasks, and include memory id in wiki slugs to avoid collisions.
63fde7b to
2cdc24b
Compare
There was a problem hiding this comment.
17 issues found across 27 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="desktop/macos/Desktop/Sources/ProactiveAssistants/Assistants/MemoryExtraction/MemoryAssistant.swift">
<violation number="1" location="desktop/macos/Desktop/Sources/ProactiveAssistants/Assistants/MemoryExtraction/MemoryAssistant.swift:258">
P2: `try?` silently suppresses wiki indexing failures, making missing memory-search entries hard to detect and debug. Log the error (or surface it) instead of discarding it.</violation>
</file>
<file name="desktop/macos/run.sh">
<violation number="1" location="desktop/macos/run.sh:474">
P1: Codex proxy path is incorrect in run.sh, so the new proxy is never built or bundled.</violation>
<violation number="2" location="desktop/macos/run.sh:479">
P2: New helper binary is executable but never individually codesigned. Unsigned nested executables are commonly blocked at runtime, so Codex proxy launch can fail even when app signing succeeds.</violation>
</file>
<file name="desktop/macos/Desktop/Tests/CodexAuthServiceTests.swift">
<violation number="1" location="desktop/macos/Desktop/Tests/CodexAuthServiceTests.swift:31">
P2: `testMemorySearchModeDefaultsToWikiWhenCodexEnrolled` does not actually validate the enrollment-to-wiki path — no auth file is configured, so `CodexAuthService.isActive` is always false and `MemorySearchMode.current` never enters the `isActive` branch. The assertion is vacuously true via the UserDefaults default.</violation>
</file>
<file name="desktop/macos/Desktop/Sources/CodexProxyService.swift">
<violation number="1" location="desktop/macos/Desktop/Sources/CodexProxyService.swift:154">
P2: Repo-relative executable lookup is one directory too deep and misses the local proxy build output. In dev setups without a bundled binary/PATH entry, `ensureRunning()` fails with “binary not found.”</violation>
</file>
<file name="desktop/macos/Desktop/Sources/MainWindow/Pages/SettingsPage.swift">
<violation number="1" location="desktop/macos/Desktop/Sources/MainWindow/Pages/SettingsPage.swift:5430">
P2: Settings copy promises main chat is routed through ChatGPT, but current chat flow is still pi-mono/ACP. This misleads users about what enrollment changes.</violation>
</file>
<file name="backend/utils/subscription.py">
<violation number="1" location="backend/utils/subscription.py:126">
P1: Trial/paywall bypass is granted from persisted ChatGPT enrollment alone, without requiring the per-request fingerprint proof used elsewhere.</violation>
<violation number="2" location="backend/utils/subscription.py:625">
P0: Chat quota bypass is grantable via self-enrolled fingerprint, not a verified subscription entitlement. This allows free users to bypass quota limits by posting an arbitrary fingerprint and replaying it in request headers.</violation>
</file>
<file name="desktop/macos/Desktop/Sources/CodexAuthService.swift">
<violation number="1" location="desktop/macos/Desktop/Sources/CodexAuthService.swift:107">
P2: Guard order triggers auth-file disk reads even when user is not enrolled. Check `isEnrolled` first to skip file I/O on the common non-enrolled path.</violation>
</file>
<file name="desktop/macos/Desktop/Sources/ProactiveAssistants/Core/GeminiClient.swift">
<violation number="1" location="desktop/macos/Desktop/Sources/ProactiveAssistants/Core/GeminiClient.swift:306">
P2: OCR fallback leaks raw Codex client errors instead of mapping them to GeminiClientError. This can skip transient retry handling and inconsistent user-facing errors.</violation>
</file>
<file name="desktop/macos/Desktop/Sources/OmiApp.swift">
<violation number="1" location="desktop/macos/Desktop/Sources/OmiApp.swift:114">
P2: `try?` suppresses ChatGPT activation errors at launch, so backend enrollment sync can fail silently. Catch and log the error (or trigger retry) so users don’t remain in an unsynced tier state without diagnostics.</violation>
</file>
<file name="desktop/macos/Desktop/Sources/MemoryWikiStorage.swift">
<violation number="1" location="desktop/macos/Desktop/Sources/MemoryWikiStorage.swift:44">
P1: ensureDB can reuse a stale DatabasePool across sign-out/sign-in. This risks cross-account reads/writes in memory wiki data.</violation>
<violation number="2" location="desktop/macos/Desktop/Sources/MemoryWikiStorage.swift:171">
P2: The default search mode is inverted for non-Codex users. Falling back to `local_wiki` globally disables vector embedding search unless a hidden override is manually set.</violation>
</file>
<file name="backend/utils/chatgpt.py">
<violation number="1" location="backend/utils/chatgpt.py:47">
P2: Bypass validation does two Firestore reads per request when one is enough. This adds avoidable latency/cost on chat and quota paths for enrolled desktop users.</violation>
</file>
<file name="desktop/codex-proxy/src/main.rs">
<violation number="1" location="desktop/codex-proxy/src/main.rs:211">
P2: Response `model` is set to null when request omits `model`, breaking OpenAI-compatible schema expectations. Use the effective upstream model fallback instead of `null`.</violation>
</file>
<file name="backend/routers/users.py">
<violation number="1" location="backend/routers/users.py:931">
P1: ChatGPT enrollment trusts a self-asserted fingerprint with no server-side proof of subscription ownership. This lets authenticated users mint their own bypass token and get unlimited chat quota.</violation>
</file>
<file name="desktop/macos/Desktop/Sources/CodexLLMClient.swift">
<violation number="1" location="desktop/macos/Desktop/Sources/CodexLLMClient.swift:67">
P2: `postJSON` decodes JSON before checking status code, which can misclassify HTTP failures and bypass downstream fallback handling.</violation>
</file>
Tip: instead of fixing issues one by one fix them all with cubic
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
|
|
||
| # BYOK users pay their own LLM provider — no Omi-side cost to cap. | ||
| # ChatGPT/Codex tier: bypass only when this request proves Codex enrollment (header). | ||
| if chatgpt_request_grants_bypass(uid): |
There was a problem hiding this comment.
P0: Chat quota bypass is grantable via self-enrolled fingerprint, not a verified subscription entitlement. This allows free users to bypass quota limits by posting an arbitrary fingerprint and replaying it in request headers.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/utils/subscription.py, line 625:
<comment>Chat quota bypass is grantable via self-enrolled fingerprint, not a verified subscription entitlement. This allows free users to bypass quota limits by posting an arbitrary fingerprint and replaying it in request headers.</comment>
<file context>
@@ -613,6 +621,10 @@ def enforce_chat_quota(uid: str, platform: Optional[str] = None) -> None:
# BYOK users pay their own LLM provider — no Omi-side cost to cap.
+ # ChatGPT/Codex tier: bypass only when this request proves Codex enrollment (header).
+ if chatgpt_request_grants_bypass(uid):
+ return
+
</file context>
| } | ||
|
|
||
| private func ensureDB() async throws -> DatabasePool { | ||
| if let dbQueue { return dbQueue } |
There was a problem hiding this comment.
P1: ensureDB can reuse a stale DatabasePool across sign-out/sign-in. This risks cross-account reads/writes in memory wiki data.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At desktop/macos/Desktop/Sources/MemoryWikiStorage.swift, line 44:
<comment>ensureDB can reuse a stale DatabasePool across sign-out/sign-in. This risks cross-account reads/writes in memory wiki data.</comment>
<file context>
@@ -0,0 +1,178 @@
+ }
+
+ private func ensureDB() async throws -> DatabasePool {
+ if let dbQueue { return dbQueue }
+ try await RewindDatabase.shared.initialize()
+ guard let queue = await RewindDatabase.shared.getDatabaseQueue() else {
</file context>
| fi | ||
|
|
||
| substep "Building Codex proxy (omi-codex-proxy)" | ||
| CODEX_PROXY_DIR="$(dirname "$0")/codex-proxy" |
There was a problem hiding this comment.
P1: Codex proxy path is incorrect in run.sh, so the new proxy is never built or bundled.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At desktop/macos/run.sh, line 474:
<comment>Codex proxy path is incorrect in run.sh, so the new proxy is never built or bundled.</comment>
<file context>
@@ -470,6 +470,17 @@ else
fi
+substep "Building Codex proxy (omi-codex-proxy)"
+CODEX_PROXY_DIR="$(dirname "$0")/codex-proxy"
+if [ -d "$CODEX_PROXY_DIR" ]; then
+ (cd "$CODEX_PROXY_DIR" && cargo build --release --quiet)
</file context>
| CODEX_PROXY_DIR="$(dirname "$0")/codex-proxy" | |
| CODEX_PROXY_DIR="$(dirname "$0")/../codex-proxy" |
| status_code=400, | ||
| detail='Invalid fingerprint: expected lowercase hex SHA-256 (64 chars)', | ||
| ) | ||
| users_db.set_chatgpt_active(uid, data.fingerprint) |
There was a problem hiding this comment.
P1: ChatGPT enrollment trusts a self-asserted fingerprint with no server-side proof of subscription ownership. This lets authenticated users mint their own bypass token and get unlimited chat quota.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/routers/users.py, line 931:
<comment>ChatGPT enrollment trusts a self-asserted fingerprint with no server-side proof of subscription ownership. This lets authenticated users mint their own bypass token and get unlimited chat quota.</comment>
<file context>
@@ -913,6 +914,47 @@ def deactivate_byok_endpoint(uid: str = Depends(auth.get_current_user_uid_no_byo
+ status_code=400,
+ detail='Invalid fingerprint: expected lowercase hex SHA-256 (64 chars)',
+ )
+ users_db.set_chatgpt_active(uid, data.fingerprint)
+ clear_trial_paywall_cache(uid)
+ return {"active": True}
</file context>
| return False | ||
| if users_db.is_byok_active(uid): | ||
| return False | ||
| if users_db.is_chatgpt_active(uid): |
There was a problem hiding this comment.
P1: Trial/paywall bypass is granted from persisted ChatGPT enrollment alone, without requiring the per-request fingerprint proof used elsewhere.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/utils/subscription.py, line 126:
<comment>Trial/paywall bypass is granted from persisted ChatGPT enrollment alone, without requiring the per-request fingerprint proof used elsewhere.</comment>
<file context>
@@ -122,6 +123,8 @@ def _is_trial_expired_uncached(uid: str) -> bool:
return False
if users_db.is_byok_active(uid):
return False
+ if users_db.is_chatgpt_active(uid):
+ return False
user_record = firebase_auth.get_user(uid)
</file context>
| Task { | ||
| await CodexProxyService.shared.ensureRunning() | ||
| if let fingerprint = CodexAuthService.enrollmentFingerprintIfActive() { | ||
| try? await APIClient.shared.activateChatGPT(fingerprint: fingerprint) |
There was a problem hiding this comment.
P2: try? suppresses ChatGPT activation errors at launch, so backend enrollment sync can fail silently. Catch and log the error (or trigger retry) so users don’t remain in an unsynced tier state without diagnostics.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At desktop/macos/Desktop/Sources/OmiApp.swift, line 114:
<comment>`try?` suppresses ChatGPT activation errors at launch, so backend enrollment sync can fail silently. Catch and log the error (or trigger retry) so users don’t remain in an unsynced tier state without diagnostics.</comment>
<file context>
@@ -107,6 +107,14 @@ struct OMIApp: App {
+ Task {
+ await CodexProxyService.shared.ensureRunning()
+ if let fingerprint = CodexAuthService.enrollmentFingerprintIfActive() {
+ try? await APIClient.shared.activateChatGPT(fingerprint: fingerprint)
+ }
+ }
</file context>
| try? await APIClient.shared.activateChatGPT(fingerprint: fingerprint) | |
| do { | |
| try await APIClient.shared.activateChatGPT(fingerprint: fingerprint) | |
| } catch { | |
| log("OmiApp: Failed to activate ChatGPT tier on launch: \(error.localizedDescription)") | |
| } |
| if CodexAuthService.isActive { | ||
| return .localWiki | ||
| } | ||
| let raw = UserDefaults.standard.string(forKey: "memory_search_mode") ?? "local_wiki" |
There was a problem hiding this comment.
P2: The default search mode is inverted for non-Codex users. Falling back to local_wiki globally disables vector embedding search unless a hidden override is manually set.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At desktop/macos/Desktop/Sources/MemoryWikiStorage.swift, line 171:
<comment>The default search mode is inverted for non-Codex users. Falling back to `local_wiki` globally disables vector embedding search unless a hidden override is manually set.</comment>
<file context>
@@ -0,0 +1,178 @@
+ if CodexAuthService.isActive {
+ return .localWiki
+ }
+ let raw = UserDefaults.standard.string(forKey: "memory_search_mode") ?? "local_wiki"
+ return raw == "vector" ? .vectorEmbeddings : .localWiki
+ }
</file context>
| let raw = UserDefaults.standard.string(forKey: "memory_search_mode") ?? "local_wiki" | |
| let raw = UserDefaults.standard.string(forKey: "memory_search_mode") ?? "vector" |
| fp = _chatgpt_fp_ctx.get() | ||
| if not fp or not _SHA256_HEX_RE.match(fp): | ||
| return False | ||
| if not users_db.is_chatgpt_active(uid): |
There was a problem hiding this comment.
P2: Bypass validation does two Firestore reads per request when one is enough. This adds avoidable latency/cost on chat and quota paths for enrolled desktop users.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/utils/chatgpt.py, line 47:
<comment>Bypass validation does two Firestore reads per request when one is enough. This adds avoidable latency/cost on chat and quota paths for enrolled desktop users.</comment>
<file context>
@@ -0,0 +1,74 @@
+ fp = _chatgpt_fp_ctx.get()
+ if not fp or not _SHA256_HEX_RE.match(fp):
+ return False
+ if not users_db.is_chatgpt_active(uid):
+ return False
+ state = users_db.get_chatgpt_state(uid)
</file context>
| "id": new_chat_completion_id(), | ||
| "object": "chat.completion", | ||
| "created": unix_secs(), | ||
| "model": if requested_model_hint.trim().is_empty() { Value::Null } else { Value::String(requested_model_hint.clone()) }, |
There was a problem hiding this comment.
P2: Response model is set to null when request omits model, breaking OpenAI-compatible schema expectations. Use the effective upstream model fallback instead of null.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At desktop/codex-proxy/src/main.rs, line 211:
<comment>Response `model` is set to null when request omits `model`, breaking OpenAI-compatible schema expectations. Use the effective upstream model fallback instead of `null`.</comment>
<file context>
@@ -0,0 +1,799 @@
+ "id": new_chat_completion_id(),
+ "object": "chat.completion",
+ "created": unix_secs(),
+ "model": if requested_model_hint.trim().is_empty() { Value::Null } else { Value::String(requested_model_hint.clone()) },
+ "choices": [{
+ "index": 0,
</file context>
| guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else { | ||
| throw ClientError.invalidResponse | ||
| } | ||
| guard (200..<300).contains(http.statusCode) else { | ||
| let body = String(data: data.prefix(512), encoding: .utf8) ?? "" | ||
| throw ClientError.httpFailure(status: http.statusCode, body: body) | ||
| } |
There was a problem hiding this comment.
P2: postJSON decodes JSON before checking status code, which can misclassify HTTP failures and bypass downstream fallback handling.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At desktop/macos/Desktop/Sources/CodexLLMClient.swift, line 67:
<comment>`postJSON` decodes JSON before checking status code, which can misclassify HTTP failures and bypass downstream fallback handling.</comment>
<file context>
@@ -0,0 +1,401 @@
+ guard let http = response as? HTTPURLResponse else {
+ throw ClientError.invalidResponse
+ }
+ guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
+ throw ClientError.invalidResponse
+ }
</file context>
| guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else { | |
| throw ClientError.invalidResponse | |
| } | |
| guard (200..<300).contains(http.statusCode) else { | |
| let body = String(data: data.prefix(512), encoding: .utf8) ?? "" | |
| throw ClientError.httpFailure(status: http.statusCode, body: body) | |
| } | |
| guard (200..<300).contains(http.statusCode) else { | |
| let body = String(data: data.prefix(512), encoding: .utf8) ?? "" | |
| throw ClientError.httpFailure(status: http.statusCode, body: body) | |
| } | |
| guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else { | |
| throw ClientError.invalidResponse | |
| } |
|
Closing as stale after confirmed stale-close triage. This PR was identified as high-confidence stale due to age plus one or more of: conflicts, draft/failing status, superseded work, or obsolete architecture. No merge was performed. If the work is still needed, please reopen with a fresh rebase or start from current main. |
|
Hey @Git-on-my-level 👋 Thank you so much for taking the time to contribute to Omi! We truly appreciate you putting in the effort to submit this pull request. After careful review, we've decided not to merge this particular PR. Please don't take this personally — we genuinely try to merge as many contributions as possible, but sometimes we have to make tough calls based on:
Your contribution is still valuable to us, and we'd love to see you contribute again in the future! If you'd like feedback on how to improve this PR or want to discuss alternative approaches, please don't hesitate to reach out. Thank you for being part of the Omi community! 💜 |
Summary
/v1/users/me/chatgpt-active) with LLM quota bypass (separate from four-key BYOK; transcription gates unchanged).desktop/codex-proxy) and desktop Settings UX to sign in viacodex login, enroll, and route proactive LLM workloads through the proxy.Test plan
backend/tests/unit/test_chatgpt_enrollment.pycd desktop/codex-proxy && cargo build --releasecodex login→ verify proxy health and subscription shows unlimited LLMMade with Cursor