fix(cursor): login URL visibility, model registry, and oauth-model-alias#124
Open
tan-yong-sheng wants to merge 4 commits into
Open
fix(cursor): login URL visibility, model registry, and oauth-model-alias#124tan-yong-sheng wants to merge 4 commits into
tan-yong-sheng wants to merge 4 commits into
Conversation
When logging-to-file is true, ConfigureLogOutput redirects all logrus output to ~/.cli-proxy-api/logs/main.log. The cursor login URL was therefore hidden in the log file and the program appeared to hang with no output on the terminal. Switch the four user-facing prompts (Starting / URL / Waiting / Success) in sdk/auth/cursor.go from log.* to fmt.Println/Printf so they always reach stdout, regardless of the logging-to-file setting. log.Warnf for the browser-open failure is kept on logrus since it is diagnostic, not user-facing. Fixes kaitranntt#122
FetchCursorModels previously returned GetCursorFallbackModels() on every failure (DNS timeout, non-2xx, empty parse, etc.). The hardcoded list was also months out of date — it contained composer-2, claude-3.5-sonnet, gpt-4o, cursor-small, gemini-2.5-pro — none of which Cursor serves today. Combined effect: a single transient GetUsableModels failure (observed at 2026-06-27 21:53:33, dns i/o timeout to api2.cursor.sh) caused the registry reconcile to overwrite the live 119-model list with the stale 6-model fallback, permanently removing composer-2.5 and friends from the registry until the auth file was reloaded. User calls to cursor/composer-2.5 then returned an instant 503 with no executor trace. Two changes: 1. Add a package-level cache keyed by auth.ID. On a successful live fetch, store the result. On failure, return the cached list when available; fall back to GetCursorFallbackModels only when no prior fetch has populated the cache. A transient blip can no longer collapse the registry. 2. Refresh GetCursorFallbackModels to current Cursor model ids (composer-2.5, composer-2.5-fast, gpt-5.3-codex, gpt-5.2, gpt-5.5-high, gpt-5.4-high, claude-opus-4-8-thinking-high, claude-opus-4-7-thinking-high, claude-4.5-sonnet, gemini-3.1-pro). Verified live against api2.cursor.sh on 2026-06-27. Adds cursor_models_cache_test.go covering both the cache-or-fallback preference and the freshness of the hardcoded list.
The cursor executor used parsed.Model (the model name extracted from the translated request payload) when building the upstream Run request to api2.cursor.sh. For OpenAI-source requests the translator is skipped entirely (the from!=openai guard at line 293), so parsed.Model always equalled whatever the client sent — including an alias name like "cursor/composer-2.5" defined under oauth-model-alias in config.yaml. The conductor does resolve the alias correctly: applyOAuthModelAlias in sdk/cliproxy/auth/conductor.go sets the upstream model name on req.Model. But because parsed.Model still held the alias, the protobuf Run request was encoded with the alias as ModelId. Cursor's Run endpoint does not know "cursor/composer-2.5" (only "composer-2.5") and returned Connect error not_found. Calling the upstream model name directly worked because no alias was involved and the payload already held the real name. Fix: give buildRunRequestParams a third modelOverride parameter. When non-empty it is used as ModelId; otherwise parsed.Model is used so existing call sites behave unchanged. All four call sites (Execute + three sites in ExecuteStream's checkpoint branches) pass req.Model. parsed.Model is untouched, so the OpenAI response still echoes the client-facing alias name as before. Adds cursor_executor_buildrequest_test.go covering override wins over parsed.Model, empty override falls back, whitespace override falls back, and override wins even when parsed.Model is empty.
Addresses three findings from PR #1 review (Gemini Code Assist): 1. FetchCursorModels dereferenced auth.ID before any nil check, so a nil auth (legal per the function signature; caller in service.go currently passes a non-nil auth but the signature allows nil) would panic at line 1536 and crash the registry reconcile goroutine. Add an explicit nil guard at function entry that returns the hardcoded fallback. 2. cursorModelsOrFallback returned the cached slice directly. If a caller replaced a slice element via the returned slice (e.g. 'got[0] = newEntry'), the change propagated to the cache and corrupted future failure-path fetches. Return a shallow copy instead. ModelInfo pointer fields (e.g. *ThinkingSupport) are still shared -- callers must treat returned models as immutable, which is the existing convention used by registry reconcile. 3. cacheCursorModels stored the caller's slice directly. If the caller mutated its own slice header (append, element replace) after caching, the cache was corrupted. Store a shallow copy. Adds three regression tests covering each finding: - TestFetchCursorModels_NilAuthReturnsFallback - TestCursorModelsOrFallback_ReturnIsDefensivelyCopied - TestCacheCursorModels_StoresDefensiveCopy Verified go test -race on the executor package.
Author
|
Don't merge, not working when tool calling ... |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Declaration
This pull request is vibe coded with GLM 5.2 + Minimax M3 and Pi Coding Agent.
Summary
Three independent cursor fixes discovered while debugging issue #122:
--cursor-loginprints the URL on stdout even withlogging-to-file: trueoauth-model-aliasnow works for cursor — the alias resolves correctly to the upstream model nameEach fix has its own commit in this PR for independent review and revert.
Fix 1 — Login URL visibility (issue #122)
When
logging-to-file: true,ConfigureLogOutputredirects all logrus output to~/.cli-proxy-api/logs/main.log. The cursor login URL was therefore hidden in the log file and the program appeared to hang with a blinking cursor on the terminal — no indication that the URL was waiting in the log.Switched the four user-facing prompts in
sdk/auth/cursor.gofromlog.*tofmt.Println/Printfso they always reach stdout regardless of the logging setting.log.Warnffor the browser-open failure stays on logrus (diagnostic, not user-facing).Verified end-to-end: with the new binary and
logging-to-file: true, the terminal prints the URL immediately.Fixes #122
Fix 2 — Stale model registry on transient failure
FetchCursorModelspreviously returnedGetCursorFallbackModels()on every failure (DNS timeout, non-2xx, empty parse, etc.). The hardcoded list was also months out of date — it containedcomposer-2,claude-3.5-sonnet,gpt-4o,cursor-small,gemini-2.5-pro— none of which Cursor serves today.Combined effect: a single transient
GetUsableModelsfailure (observed in the production log at 2026-06-27 21:53:33, dns i/o timeout toapi2.cursor.sh) caused the registry reconcile to overwrite the live 119-model list with the stale 6-model fallback, permanently removingcomposer-2.5and friends from the registry until the auth file was reloaded. User calls tocursor/composer-2.5then returned an instant 503 with no executor trace.Two changes:
cursorModelsCachekeyed byauth.ID. On successful live fetch, store the result. On failure, return the cached list when available; fall back toGetCursorFallbackModelsonly when no prior fetch has populated the cache. A transient blip can no longer collapse the registry.GetCursorFallbackModelsto current Cursor model ids (composer-2.5, composer-2.5-fast, gpt-5.3-codex, gpt-5.2, gpt-5.5-high, gpt-5.4-high, claude-opus-4-8-thinking-high, claude-opus-4-7-thinking-high, claude-4.5-sonnet, gemini-3.1-pro). Verified live againstapi2.cursor.shon 2026-06-27.Verified: with the new binary, a configured cursor auth registers 119 models from the live fetch, and a subsequent network blip leaves the registry intact.
Fix 3 —
oauth-model-aliasresolution for cursorThe cursor executor used
parsed.Model(the model name extracted from the translated request payload) when building the upstream Run request toapi2.cursor.sh. For OpenAI-source requests the translator is skipped entirely (thefrom != "openai"guard), soparsed.Modelalways equalled whatever the client sent — including an alias name likecursor/composer-2.5defined underoauth-model-aliasin config.yaml.The conductor does resolve the alias correctly:
applyOAuthModelAliasinsdk/cliproxy/auth/conductor.gosets the upstream model name onreq.Model. But becauseparsed.Modelstill held the alias, the protobuf Run request was encoded with the alias asModelId. Cursor's Run endpoint does not knowcursor/composer-2.5(onlycomposer-2.5) and returnedConnect error not_found. Calling the upstream model name directly worked because no alias was involved.Fix:
buildRunRequestParamsgets a thirdmodelOverrideparameter. When non-empty it is used asModelId; otherwiseparsed.Modelis used so existing behavior is unchanged. All four call sites passreq.Model.parsed.Modelis untouched, so the OpenAI response still echoes the client-facing alias name.Verified: with config:
both
cursor/composer-2.5andcomposer-2.5return 200; the alias call's response has"model":"cursor/composer-2.5"(alias echoed), the real-name call has"model":"composer-2.5"(real name). Both reach Cursor withmodel=composer-2.5.Test plan
gofmt -lcleango build ./cmd/serverokgo test ./...— 0 FAILs across the repoTestCursorModelsOrFallback_PrefersCacheOverHardcoded,TestGetCursorFallbackModels_IsCurrent,TestBuildRunRequestParams_ModelOverride(4 subcases)Files changed
sdk/auth/cursor.gointernal/runtime/executor/cursor_executor.gointernal/runtime/executor/cursor_models_cache_test.gointernal/runtime/executor/cursor_executor_buildrequest_test.go