feat: support OpenAI Codex auth and reasoning effort in agent settings#1921
feat: support OpenAI Codex auth and reasoning effort in agent settings#1921
Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughAdded OAuth authentication support for agent providers (specifically OpenAI Codex subscription) with a new OAuth manager package handling login flows, credential storage, and validation. Extended LLM request configuration with optional "thinking effort" (reasoning) propagation through agent sessions and loops. Implemented file-based encrypted credential persistence and new frontend API endpoints for provider connection management. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User/Browser
participant Frontend as Frontend API
participant OAuthMgr as OAuth Manager
participant OAuthStore as Credential Store
participant Provider as OpenAI Codex Provider
participant ExternalOAuth as OpenAI Auth Service
User->>Frontend: POST /settings/agent/auth/providers/{id}/login
Frontend->>OAuthMgr: StartLogin(ctx, provider)
OAuthMgr->>Provider: StartAuthFlow()
Provider->>Provider: Generate PKCE verifier, state
Provider-->>OAuthMgr: authFlow (state, verifier)
OAuthMgr-->>Frontend: FlowID, AuthURL, Instructions
Frontend-->>User: Redirect to AuthURL (OpenAI login page)
User->>ExternalOAuth: Authorize & Redirect
ExternalOAuth->>Frontend: Redirect to callback with code, state
User->>Frontend: POST /settings/agent/auth/providers/{id}/login/complete
Frontend->>OAuthMgr: CompleteLogin(ctx, provider, code, state)
OAuthMgr->>OAuthMgr: Validate state, lookup flowID
OAuthMgr->>Provider: ExchangeCode(code, verifier)
Provider->>ExternalOAuth: POST token endpoint (authorization_code)
ExternalOAuth-->>Provider: access_token, refresh_token, expires_in
Provider->>Provider: Extract account ID from JWT
Provider-->>OAuthMgr: Credential (tokens, account ID, expiry)
OAuthMgr->>OAuthStore: Set(credential)
OAuthStore-->>OAuthMgr: Stored
OAuthMgr-->>Frontend: Credential (account ID, connected status)
Frontend-->>User: Success response
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
internal/agent/api.go (1)
872-909:⚠️ Potential issue | 🟠 MajorReactivated sessions now inherit the current default model's reasoning settings.
This block still rebuilds model-specific runtime state from
defaultModelID, andreactivateSessionnever restores the session's originalmgr.model. After cleanup/restart, a session created on a non-default model can resume on a different model with different pricing andthinkingEffort. Please persist and restore the session model before applying model-specific runtime config here.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/agent/api.go` around lines 872 - 909, The reactivation code currently resolves provider using a.getDefaultModelID and applies model-specific runtime state, which can change a session's original model; instead, read and restore the session's persisted model ID (e.g., sess.Model or sess.ModelID) first and use that model ID when calling a.resolveProvider to populate InputCostPer1M, OutputCostPer1M and ThinkingEffort; only fall back to a.getDefaultModelID if the session has no stored model; also ensure the restored model ID is passed into SessionManagerConfig / NewSessionManager (e.g., include ModelID or Model field) so the manager keeps the original session model rather than inheriting the current default.
🧹 Nitpick comments (9)
internal/agent/model_config.go (1)
132-132: Consider adding a doc comment for validThinkingEffortvalues.The field is correctly added with
omitemptyfor optional serialization. For maintainability, consider adding a brief comment documenting the expected values (e.g.,"low","medium","high"or similar) so future developers understand the valid range without needing to trace through the validation logic.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/agent/model_config.go` at line 132, Add a concise doc comment above the ThinkingEffort struct field in model_config.go explaining the expected/allowed string values (e.g., "low", "medium", "high" or whatever the validation accepts) so future readers know the valid range without tracing validation logic; reference the ThinkingEffort string field in the struct and include note about omitempty and whether values are case-sensitive or mapped to enums.internal/agent/provider_cache_test.go (1)
25-25: Add one test for non-emptyProviderDepspath.These updates correctly align callsites, but the suite still validates only
ProviderDeps{}. Please add one case with populated deps (OAuth manager path) so the new dependency-based behavior is covered.Also applies to: 29-29, 46-46, 62-62, 73-73, 99-99, 195-195, 252-252
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/agent/provider_cache_test.go` at line 25, Add a test case that calls cache.GetOrCreate with a non-empty ProviderDeps (e.g., ProviderDeps{OAuthManagerPath: "some/path"}) instead of only ProviderDeps{} so the dependency-based path is exercised; update the test(s) that currently invoke cache.GetOrCreate(cfg, ProviderDeps{}) to also call cache.GetOrCreate(cfg, ProviderDeps{OAuthManagerPath: "<valid-or-temp-path>"}), then assert the same expectations you have for p1/model1 (or the relevant returned values) to ensure the OAuth-manager path branch is covered. Ensure you adjust all similar test sites that currently use ProviderDeps{} (including other occurrences calling cache.GetOrCreate, cfg, p1, model1) to include one non-empty deps case.internal/agent/api_test.go (1)
1094-1109: Capturemodel-brequests too.Line 1108 only receives a second request if
SendMessagekeeps reusingmodel-a's provider. Registeringmodel-bwith a capturing provider would keep this test focused on thinking-effort propagation instead of current provider-reuse semantics.Suggested change
reqCh := make(chan *llm.ChatRequest, 2) api, _ := testAPIWithModels(t, modelA, modelB) api.providers.Set(modelA.ToLLMConfig(), newCapturingProvider(reqCh, simpleStopResponse("a"))) -api.providers.Set(modelB.ToLLMConfig(), newStopProvider("b")) +api.providers.Set(modelB.ToLLMConfig(), newCapturingProvider(reqCh, simpleStopResponse("b")))🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/agent/api_test.go` around lines 1094 - 1109, The test registers modelB with newStopProvider so the second request isn't captured; update the provider registration to use a capturing provider for modelB (e.g., call api.providers.Set(modelB.ToLLMConfig(), newCapturingProvider(reqCh, simpleStopResponse("b")))) so that the second request emitted by api.SendMessage is received on reqCh; look for the reqCh, testAPIWithModels, api.providers.Set, newCapturingProvider/newStopProvider, CreateSession and SendMessage calls to make the change.internal/cmd/context.go (1)
576-591: Extract the encrypted OAuth-store bootstrap into one helper.This key → encryptor → store sequence is now duplicated here, in
buildRemoteNodeResolver, and again ininternal/service/worker/remote_handler.go. Centralizing it would keep the directory layout and warning behavior from drifting.Possible extraction
+func newAgentOAuthManager(dataDir string) (*agentoauth.Manager, error) { + encKey, err := crypto.ResolveKey(dataDir) + if err != nil { + return nil, err + } + enc, err := crypto.NewEncryptor(encKey) + if err != nil { + return nil, err + } + store, err := fileagentoauth.New(filepath.Join(dataDir, "agent", "oauth"), enc) + if err != nil { + return nil, err + } + return agentoauth.NewManager(store), nil +} + encKey, err := crypto.ResolveKey(c.Config.Paths.DataDir) if err != nil { logger.Warn(c, "Failed to resolve encryption key for agent OAuth store", tag.Error(err)) } else { - enc, err := crypto.NewEncryptor(encKey) - if err != nil { - logger.Warn(c, "Failed to create encryptor for agent OAuth store", tag.Error(err)) - } else { - store, err := fileagentoauth.New(filepath.Join(c.Config.Paths.DataDir, "agent", "oauth"), enc) - if err != nil { - logger.Warn(c, "Failed to create agent OAuth store", tag.Error(err)) - } else { - result.OAuthManager = agentoauth.NewManager(store) - } - } + manager, err := newAgentOAuthManager(c.Config.Paths.DataDir) + if err != nil { + logger.Warn(c, "Failed to create agent OAuth manager", tag.Error(err)) + } else { + result.OAuthManager = manager + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/cmd/context.go` around lines 576 - 591, Extract the repeated key→encryptor→store bootstrapping into a single helper (e.g., bootstrapAgentOAuthStore or NewAgentOAuthManager) that encapsulates crypto.ResolveKey, crypto.NewEncryptor, fileagentoauth.New and returns the created agentoauth.Manager (or the store plus manager) and any error; replace the inline sequences in context.go (where result.OAuthManager is set) and in buildRemoteNodeResolver and internal/service/worker/remote_handler.go to call this helper, preserving the same filepath.Join(c.Config.Paths.DataDir, "agent", "oauth") layout and the existing logger.Warn messages (use the same messages and tag.Error(err) when bubbling warnings) so behavior and logs remain identical.internal/agentoauth/openai_codex.go (1)
154-170: Consider adding timeout to HTTP client for token exchange.Using
http.DefaultClientwithout a timeout could cause indefinite hangs if the OpenAI auth server is unresponsive. Consider using a client with a configured timeout (e.g., 30 seconds).♻️ Proposed fix to add timeout
+var tokenClient = &http.Client{ + Timeout: 30 * time.Second, +} + func exchangeToken(ctx context.Context, form url.Values) (*Credential, error) { req, err := http.NewRequestWithContext(ctx, http.MethodPost, openAICodexTokenURL, strings.NewReader(form.Encode())) if err != nil { return nil, fmt.Errorf("create token request: %w", err) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - resp, err := http.DefaultClient.Do(req) + resp, err := tokenClient.Do(req) if err != nil { return nil, fmt.Errorf("exchange OAuth token: %w", err) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/agentoauth/openai_codex.go` around lines 154 - 170, The exchangeToken function uses http.DefaultClient which has no timeout and can hang; replace the HTTP call to use a dedicated http.Client with a timeout (e.g., 30s) instead of http.DefaultClient.Do; update the call in exchangeToken where resp, err := http.DefaultClient.Do(req) to use a locally constructed client (or a shared client variable) with Timeout set so the token exchange is bounded, keeping all other behavior (headers, body read/close, and status handling) the same and still using the existing openAICodexTokenURL and ctx.internal/persis/fileagentoauth/store.go (1)
219-228: Consider logging time parse failures.Failed time parsing is silently ignored, leaving zero-value timestamps. While this is safe, logging a warning could help diagnose corrupted credential files.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/persis/fileagentoauth/store.go` around lines 219 - 228, When parseTime fails for stored.ExpiresAt or stored.UpdatedAt the error is currently ignored; update the blocks that call parseTime (around stored.ExpiresAt and stored.UpdatedAt) to log a warning including the field name, the raw string (stored.ExpiresAt / stored.UpdatedAt) and the parse error before leaving the timestamp as the zero value; keep the existing behavior of only assigning cred.ExpiresAt / cred.UpdatedAt on successful parse. Use the project's logging mechanism (or fmt/log if none exists) to emit the warning so parse failures are visible for debugging.internal/agentoauth/manager.go (1)
174-176: Minor: Consider iterating overm.providerskeys instead of hardcoded list.Line 175 uses a hardcoded
[]string{ProviderOpenAICodex}whilem.providersalready contains the registered providers. If providers are added/removed, this list must be updated manually.Suggested improvement
func (m *Manager) Status(ctx context.Context) ([]ProviderStatus, error) { statuses := make([]ProviderStatus, 0, len(m.providers)) - for _, provider := range []string{ProviderOpenAICodex} { + for provider := range m.providers { prov, err := m.lookupProvider(provider)Note: If ordering matters, you may want to sort the keys or maintain a separate ordered list.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/agentoauth/manager.go` around lines 174 - 176, The loop currently iterates a hardcoded slice []string{ProviderOpenAICodex}; change it to iterate the keys of m.providers so new providers are handled automatically: build a slice of provider names from the map m.providers (optionally sort it if deterministic order is required) and then call m.lookupProvider for each name to populate statuses (types: ProviderStatus, variables: statuses, provider, prov, err, method: lookupProvider). Ensure the code replaces the literal ProviderOpenAICodex usage with the dynamic key iteration.internal/llm/providers/openaicodex/codex.go (1)
238-241: Consider logging JSON parse errors for debugging.Silent
continueon unmarshal errors (line 240) could hide malformed events from Codex. While skipping unknown data is reasonable, logging at debug level would help diagnose issues in production.Suggested improvement
var event responseEvent if err := json.Unmarshal([]byte(data), &event); err != nil { + // Log at debug level to aid troubleshooting without noise + // logger.Debug(ctx, "skipping unparseable SSE event", "error", err, "data", data) continue }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/llm/providers/openaicodex/codex.go` around lines 238 - 241, The JSON unmarshal error is being swallowed when parsing Codex events; update the block around json.Unmarshal([]byte(data), &event) (responseEvent/event) to log the error at debug level including the error and the raw data before continuing (e.g., call the package's debug logger with a message like "failed to unmarshal Codex response" plus err and data) so malformed events are visible for troubleshooting, then continue as before.internal/service/frontend/api/v1/agent_auth.go (1)
85-97: Consider using returned credential to avoid extra store lookup.The credential returned by
CompleteLogin(line 85) is discarded, thenproviderStatus(line 94) re-fetches it from the store. While functionally correct, this creates redundant I/O.Possible optimization
If
ProviderStatuscan be constructed from the credential, consider building the response directly:cred, err := a.agentOAuthManager.CompleteLogin(...) if err != nil { return nil, toAgentAuthError(ctx, "complete login", err) } // Build status from cred instead of re-fetchingHowever, if
providerStatusincludes additional data beyond the credential, the current approach is fine.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/service/frontend/api/v1/agent_auth.go` around lines 85 - 97, CompleteLogin currently returns a credential that is ignored and then providerStatus re-reads the store, causing redundant I/O; change the flow in the handler to capture the returned credential from a.agentOAuthManager.CompleteLogin (e.g., store it in a local variable like cred) and use that credential to construct the ProviderStatus response directly (or add a helper that builds providerStatus from the credential) instead of calling providerStatus(ctx, request.ProviderId) again; if providerStatus truly requires extra store-derived fields, update or add a function (e.g., providerStatusFromCredential or augmentProviderStatus) that merges cred with the additional data to avoid a full duplicate lookup.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@api/v1/api.yaml`:
- Around line 9437-9449: The CompleteAgentAuthProviderLoginRequest schema
currently allows only flowId and thus accepts payloads the handler must reject;
update the CompleteAgentAuthProviderLoginRequest definition to keep flowId
required, add a oneOf (or anyOf) constraint requiring at least one of
redirectUrl or code, and make redirectUrl and code non-empty strings by adding
minLength: 1 to each property; reference the schema and properties by name
(CompleteAgentAuthProviderLoginRequest, flowId, redirectUrl, code) when making
the change.
In `@internal/llm/providers/openaicodex/codex.go`:
- Around line 37-45: The constructor New currently allows creating a Provider
when only cfg.APIKey is set but resolveCredential expects both APIKey and
AccountID; update New (the New function that returns *Provider) to validate that
if cfg.OAuthCredentialProvider is nil and cfg.APIKey is non-empty then
cfg.AccountID must also be non-empty and return an error (e.g. a descriptive
fmt.Errorf("account ID required when using API key")) if it's missing, so
creation fails early instead of deferring to resolveCredential; keep the
existing httpClient/Provider return when validation passes.
In `@internal/service/frontend/api/v1/agent_models_test.go`:
- Around line 469-487: The subtest "openai-codex clears api key and base url"
currently expects CreateAgentModel to return an error but should assert success
and that credentials are normalized away; update the test (inside the t.Run
block using newAgentTestSetupWithOAuth and the call to
setup.api.CreateAgentModel with apigen.CreateAgentModelRequestObject /
CreateModelConfigRequest) to require no error, require a non-nil resp, and
assert that the returned model/response has empty ApiKey and BaseUrl fields (or
their equivalent in the response object) instead of expecting failure.
In `@ui/src/api/v1/schema.ts`:
- Around line 1895-1898: The CompleteAgentAuthProviderLoginRequest schema
currently allows only { flowId } and must be changed in the OpenAPI source so
the request requires one completion input; update the
CompleteAgentAuthProviderLoginRequest definition (used by the post operation for
completeAgentAuthProviderLogin) to enforce that either redirectUrl or code is
provided (use a oneOf/anyOf with object variants that each require flowId plus
either redirectUrl or code, or use required + oneOf on the properties), then
regenerate frontend types with pnpm gen:api so the generated
ui/src/api/v1/schema.ts reflects the new validation.
In `@ui/src/pages/agent-settings/index.tsx`:
- Around line 899-907: The cell currently treats a null codexProvider as "Not
connected" which conflates loading/error with an actual disconnected state;
update the rendering for the openai-codex branch to first check the auth query
state (e.g., the codex auth query's isLoading/isError flags or an explicit
codexProviderLoading/codexProviderError) and render a distinct "Loading..." or
"Error" label when loading/error, only falling back to "Not connected" when
codexProvider is non-null and connected is false; keep references to m.provider
=== 'openai-codex', codexProvider, and m.apiKeyConfigured to locate the code to
change.
---
Outside diff comments:
In `@internal/agent/api.go`:
- Around line 872-909: The reactivation code currently resolves provider using
a.getDefaultModelID and applies model-specific runtime state, which can change a
session's original model; instead, read and restore the session's persisted
model ID (e.g., sess.Model or sess.ModelID) first and use that model ID when
calling a.resolveProvider to populate InputCostPer1M, OutputCostPer1M and
ThinkingEffort; only fall back to a.getDefaultModelID if the session has no
stored model; also ensure the restored model ID is passed into
SessionManagerConfig / NewSessionManager (e.g., include ModelID or Model field)
so the manager keeps the original session model rather than inheriting the
current default.
---
Nitpick comments:
In `@internal/agent/api_test.go`:
- Around line 1094-1109: The test registers modelB with newStopProvider so the
second request isn't captured; update the provider registration to use a
capturing provider for modelB (e.g., call
api.providers.Set(modelB.ToLLMConfig(), newCapturingProvider(reqCh,
simpleStopResponse("b")))) so that the second request emitted by api.SendMessage
is received on reqCh; look for the reqCh, testAPIWithModels, api.providers.Set,
newCapturingProvider/newStopProvider, CreateSession and SendMessage calls to
make the change.
In `@internal/agent/model_config.go`:
- Line 132: Add a concise doc comment above the ThinkingEffort struct field in
model_config.go explaining the expected/allowed string values (e.g., "low",
"medium", "high" or whatever the validation accepts) so future readers know the
valid range without tracing validation logic; reference the ThinkingEffort
string field in the struct and include note about omitempty and whether values
are case-sensitive or mapped to enums.
In `@internal/agent/provider_cache_test.go`:
- Line 25: Add a test case that calls cache.GetOrCreate with a non-empty
ProviderDeps (e.g., ProviderDeps{OAuthManagerPath: "some/path"}) instead of only
ProviderDeps{} so the dependency-based path is exercised; update the test(s)
that currently invoke cache.GetOrCreate(cfg, ProviderDeps{}) to also call
cache.GetOrCreate(cfg, ProviderDeps{OAuthManagerPath: "<valid-or-temp-path>"}),
then assert the same expectations you have for p1/model1 (or the relevant
returned values) to ensure the OAuth-manager path branch is covered. Ensure you
adjust all similar test sites that currently use ProviderDeps{} (including other
occurrences calling cache.GetOrCreate, cfg, p1, model1) to include one non-empty
deps case.
In `@internal/agentoauth/manager.go`:
- Around line 174-176: The loop currently iterates a hardcoded slice
[]string{ProviderOpenAICodex}; change it to iterate the keys of m.providers so
new providers are handled automatically: build a slice of provider names from
the map m.providers (optionally sort it if deterministic order is required) and
then call m.lookupProvider for each name to populate statuses (types:
ProviderStatus, variables: statuses, provider, prov, err, method:
lookupProvider). Ensure the code replaces the literal ProviderOpenAICodex usage
with the dynamic key iteration.
In `@internal/agentoauth/openai_codex.go`:
- Around line 154-170: The exchangeToken function uses http.DefaultClient which
has no timeout and can hang; replace the HTTP call to use a dedicated
http.Client with a timeout (e.g., 30s) instead of http.DefaultClient.Do; update
the call in exchangeToken where resp, err := http.DefaultClient.Do(req) to use a
locally constructed client (or a shared client variable) with Timeout set so the
token exchange is bounded, keeping all other behavior (headers, body read/close,
and status handling) the same and still using the existing openAICodexTokenURL
and ctx.
In `@internal/cmd/context.go`:
- Around line 576-591: Extract the repeated key→encryptor→store bootstrapping
into a single helper (e.g., bootstrapAgentOAuthStore or NewAgentOAuthManager)
that encapsulates crypto.ResolveKey, crypto.NewEncryptor, fileagentoauth.New and
returns the created agentoauth.Manager (or the store plus manager) and any
error; replace the inline sequences in context.go (where result.OAuthManager is
set) and in buildRemoteNodeResolver and
internal/service/worker/remote_handler.go to call this helper, preserving the
same filepath.Join(c.Config.Paths.DataDir, "agent", "oauth") layout and the
existing logger.Warn messages (use the same messages and tag.Error(err) when
bubbling warnings) so behavior and logs remain identical.
In `@internal/llm/providers/openaicodex/codex.go`:
- Around line 238-241: The JSON unmarshal error is being swallowed when parsing
Codex events; update the block around json.Unmarshal([]byte(data), &event)
(responseEvent/event) to log the error at debug level including the error and
the raw data before continuing (e.g., call the package's debug logger with a
message like "failed to unmarshal Codex response" plus err and data) so
malformed events are visible for troubleshooting, then continue as before.
In `@internal/persis/fileagentoauth/store.go`:
- Around line 219-228: When parseTime fails for stored.ExpiresAt or
stored.UpdatedAt the error is currently ignored; update the blocks that call
parseTime (around stored.ExpiresAt and stored.UpdatedAt) to log a warning
including the field name, the raw string (stored.ExpiresAt / stored.UpdatedAt)
and the parse error before leaving the timestamp as the zero value; keep the
existing behavior of only assigning cred.ExpiresAt / cred.UpdatedAt on
successful parse. Use the project's logging mechanism (or fmt/log if none
exists) to emit the warning so parse failures are visible for debugging.
In `@internal/service/frontend/api/v1/agent_auth.go`:
- Around line 85-97: CompleteLogin currently returns a credential that is
ignored and then providerStatus re-reads the store, causing redundant I/O;
change the flow in the handler to capture the returned credential from
a.agentOAuthManager.CompleteLogin (e.g., store it in a local variable like cred)
and use that credential to construct the ProviderStatus response directly (or
add a helper that builds providerStatus from the credential) instead of calling
providerStatus(ctx, request.ProviderId) again; if providerStatus truly requires
extra store-derived fields, update or add a function (e.g.,
providerStatusFromCredential or augmentProviderStatus) that merges cred with the
additional data to avoid a full duplicate lookup.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 8fce0caf-8b1d-4162-89d5-10c11ced29b1
📒 Files selected for processing (43)
api/v1/api.gen.goapi/v1/api.yamlinternal/agent/api.gointernal/agent/api_test.gointernal/agent/contextkeys.gointernal/agent/loop.gointernal/agent/model_config.gointernal/agent/presets.gointernal/agent/presets_test.gointernal/agent/provider_cache.gointernal/agent/provider_cache_test.gointernal/agent/session.gointernal/agentoauth/manager.gointernal/agentoauth/manager_test.gointernal/agentoauth/openai_codex.gointernal/agentoauth/openai_codex_test.gointernal/agentoauth/types.gointernal/cmd/context.gointernal/cmd/dry.gointernal/cmd/restart.gointernal/cmd/retry.gointernal/cmd/start.gointernal/llm/allproviders/allproviders.gointernal/llm/provider.gointernal/llm/providers/openaicodex/codex.gointernal/llm/providers/openaicodex/codex_test.gointernal/llm/types.gointernal/persis/fileagentoauth/store.gointernal/persis/fileagentoauth/store_test.gointernal/runtime/agent/agent.gointernal/runtime/builtin/agentstep/executor.gointernal/service/frontend/api/v1/agent_auth.gointernal/service/frontend/api/v1/agent_models.gointernal/service/frontend/api/v1/agent_models_test.gointernal/service/frontend/api/v1/api.gointernal/service/frontend/server.gointernal/service/worker/remote_handler.goui/src/api/v1/schema.tsui/src/features/agent/components/ProviderAuthCard.tsxui/src/features/agent/hooks/useAgentAuthProviders.tsui/src/pages/agent-settings/ModelFormModal.tsxui/src/pages/agent-settings/index.tsxui/src/pages/setup.tsx
| CompleteAgentAuthProviderLoginRequest: | ||
| type: object | ||
| required: | ||
| - flowId | ||
| description: "Complete a manual OAuth login flow" | ||
| properties: | ||
| flowId: | ||
| type: string | ||
| redirectUrl: | ||
| type: string | ||
| code: | ||
| type: string | ||
|
|
There was a problem hiding this comment.
Require at least one OAuth callback input.
flowId alone satisfies this schema today, so the contract accepts a payload the handler can only reject at runtime. Model redirectUrl or code as required alternatives, and make these strings non-empty.
Suggested schema tightening
CompleteAgentAuthProviderLoginRequest:
type: object
required:
- flowId
+ anyOf:
+ - required: [redirectUrl]
+ - required: [code]
description: "Complete a manual OAuth login flow"
properties:
flowId:
type: string
+ minLength: 1
redirectUrl:
type: string
+ format: uri
+ minLength: 1
code:
type: string
+ minLength: 1📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| CompleteAgentAuthProviderLoginRequest: | |
| type: object | |
| required: | |
| - flowId | |
| description: "Complete a manual OAuth login flow" | |
| properties: | |
| flowId: | |
| type: string | |
| redirectUrl: | |
| type: string | |
| code: | |
| type: string | |
| CompleteAgentAuthProviderLoginRequest: | |
| type: object | |
| required: | |
| - flowId | |
| anyOf: | |
| - required: [redirectUrl] | |
| - required: [code] | |
| description: "Complete a manual OAuth login flow" | |
| properties: | |
| flowId: | |
| type: string | |
| minLength: 1 | |
| redirectUrl: | |
| type: string | |
| format: uri | |
| minLength: 1 | |
| code: | |
| type: string | |
| minLength: 1 |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@api/v1/api.yaml` around lines 9437 - 9449, The
CompleteAgentAuthProviderLoginRequest schema currently allows only flowId and
thus accepts payloads the handler must reject; update the
CompleteAgentAuthProviderLoginRequest definition to keep flowId required, add a
oneOf (or anyOf) constraint requiring at least one of redirectUrl or code, and
make redirectUrl and code non-empty strings by adding minLength: 1 to each
property; reference the schema and properties by name
(CompleteAgentAuthProviderLoginRequest, flowId, redirectUrl, code) when making
the change.
| func New(cfg llm.Config) (llm.Provider, error) { | ||
| if cfg.OAuthCredentialProvider == nil && strings.TrimSpace(cfg.APIKey) == "" { | ||
| return nil, llm.ErrNoAPIKey | ||
| } | ||
| return &Provider{ | ||
| config: cfg, | ||
| httpClient: &http.Client{Timeout: cfg.Timeout}, | ||
| }, nil | ||
| } |
There was a problem hiding this comment.
Incomplete validation: AccountID not checked when APIKey is provided.
The constructor allows creation with only an APIKey, but resolveCredential (line 112-114) requires both APIKey AND AccountID to be non-empty. This causes a deferred failure: the provider is created successfully but fails on the first request.
Proposed fix
func New(cfg llm.Config) (llm.Provider, error) {
- if cfg.OAuthCredentialProvider == nil && strings.TrimSpace(cfg.APIKey) == "" {
+ if cfg.OAuthCredentialProvider == nil && (strings.TrimSpace(cfg.APIKey) == "" || strings.TrimSpace(cfg.AccountID) == "") {
return nil, llm.ErrNoAPIKey
}
return &Provider{📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| func New(cfg llm.Config) (llm.Provider, error) { | |
| if cfg.OAuthCredentialProvider == nil && strings.TrimSpace(cfg.APIKey) == "" { | |
| return nil, llm.ErrNoAPIKey | |
| } | |
| return &Provider{ | |
| config: cfg, | |
| httpClient: &http.Client{Timeout: cfg.Timeout}, | |
| }, nil | |
| } | |
| func New(cfg llm.Config) (llm.Provider, error) { | |
| if cfg.OAuthCredentialProvider == nil && (strings.TrimSpace(cfg.APIKey) == "" || strings.TrimSpace(cfg.AccountID) == "") { | |
| return nil, llm.ErrNoAPIKey | |
| } | |
| return &Provider{ | |
| config: cfg, | |
| httpClient: &http.Client{Timeout: cfg.Timeout}, | |
| }, nil | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@internal/llm/providers/openaicodex/codex.go` around lines 37 - 45, The
constructor New currently allows creating a Provider when only cfg.APIKey is set
but resolveCredential expects both APIKey and AccountID; update New (the New
function that returns *Provider) to validate that if cfg.OAuthCredentialProvider
is nil and cfg.APIKey is non-empty then cfg.AccountID must also be non-empty and
return an error (e.g. a descriptive fmt.Errorf("account ID required when using
API key")) if it's missing, so creation fails early instead of deferring to
resolveCredential; keep the existing httpClient/Provider return when validation
passes.
| t.Run("openai-codex clears api key and base url", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| setup := newAgentTestSetupWithOAuth(t, true) | ||
| customURL := "https://example.com" | ||
| customKey := "sk-should-not-stick" | ||
|
|
||
| resp, err := setup.api.CreateAgentModel(adminCtx(), apigen.CreateAgentModelRequestObject{ | ||
| Body: &apigen.CreateModelConfigRequest{ | ||
| Name: "Codex", | ||
| Provider: "openai-codex", | ||
| Model: "gpt-5.4", | ||
| ApiKey: &customKey, | ||
| BaseUrl: &customURL, | ||
| }, | ||
| }) | ||
| require.Error(t, err) | ||
| assert.Nil(t, resp) | ||
| }) |
There was a problem hiding this comment.
This subtest asserts the opposite of its stated behavior.
The case is named "openai-codex clears api key and base url", but it currently expects CreateAgentModel to fail. Once the backend normalizes those fields away, this test will fail despite the intended behavior. Assert success and verify the stored model/response has empty credentials instead.
🧪 Suggested assertion change
- require.Error(t, err)
- assert.Nil(t, resp)
+ require.NoError(t, err)
+ createResp, ok := resp.(apigen.CreateAgentModel201JSONResponse)
+ require.True(t, ok)
+ assert.Empty(t, setup.modelStore.models[createResp.Id].APIKey)
+ assert.Empty(t, setup.modelStore.models[createResp.Id].BaseURL)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| t.Run("openai-codex clears api key and base url", func(t *testing.T) { | |
| t.Parallel() | |
| setup := newAgentTestSetupWithOAuth(t, true) | |
| customURL := "https://example.com" | |
| customKey := "sk-should-not-stick" | |
| resp, err := setup.api.CreateAgentModel(adminCtx(), apigen.CreateAgentModelRequestObject{ | |
| Body: &apigen.CreateModelConfigRequest{ | |
| Name: "Codex", | |
| Provider: "openai-codex", | |
| Model: "gpt-5.4", | |
| ApiKey: &customKey, | |
| BaseUrl: &customURL, | |
| }, | |
| }) | |
| require.Error(t, err) | |
| assert.Nil(t, resp) | |
| }) | |
| t.Run("openai-codex clears api key and base url", func(t *testing.T) { | |
| t.Parallel() | |
| setup := newAgentTestSetupWithOAuth(t, true) | |
| customURL := "https://example.com" | |
| customKey := "sk-should-not-stick" | |
| resp, err := setup.api.CreateAgentModel(adminCtx(), apigen.CreateAgentModelRequestObject{ | |
| Body: &apigen.CreateModelConfigRequest{ | |
| Name: "Codex", | |
| Provider: "openai-codex", | |
| Model: "gpt-5.4", | |
| ApiKey: &customKey, | |
| BaseUrl: &customURL, | |
| }, | |
| }) | |
| require.NoError(t, err) | |
| createResp, ok := resp.(apigen.CreateAgentModel201JSONResponse) | |
| require.True(t, ok) | |
| assert.Empty(t, setup.modelStore.models[createResp.Id].APIKey) | |
| assert.Empty(t, setup.modelStore.models[createResp.Id].BaseURL) | |
| }) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@internal/service/frontend/api/v1/agent_models_test.go` around lines 469 -
487, The subtest "openai-codex clears api key and base url" currently expects
CreateAgentModel to return an error but should assert success and that
credentials are normalized away; update the test (inside the t.Run block using
newAgentTestSetupWithOAuth and the call to setup.api.CreateAgentModel with
apigen.CreateAgentModelRequestObject / CreateModelConfigRequest) to require no
error, require a non-nil resp, and assert that the returned model/response has
empty ApiKey and BaseUrl fields (or their equivalent in the response object)
instead of expecting failure.
| * Complete agent auth provider login | ||
| * @description Completes a manual OAuth login flow using the pasted redirect URL or authorization code. Requires admin role. | ||
| */ | ||
| post: operations["completeAgentAuthProviderLogin"]; |
There was a problem hiding this comment.
Require one completion input in the schema.
CompleteAgentAuthProviderLoginRequest currently accepts { flowId } with neither redirectUrl nor code, even though the endpoint is documented as completing the flow with one of those inputs. That weakens the generated client contract for a new auth flow and shifts an avoidable validation failure to runtime. Since this file is generated, the fix should be made in the source OpenAPI schema.
🛠️ Suggested OpenAPI shape
CompleteAgentAuthProviderLoginRequest:
type: object
required:
- flowId
properties:
flowId:
type: string
redirectUrl:
type: string
code:
type: string
+ oneOf:
+ - required: [redirectUrl]
+ - required: [code]As per coding guidelines, "Frontend API types must be generated from OpenAPI spec via pnpm gen:api to maintain type safety with the backend".
Also applies to: 4009-4014
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ui/src/api/v1/schema.ts` around lines 1895 - 1898, The
CompleteAgentAuthProviderLoginRequest schema currently allows only { flowId }
and must be changed in the OpenAPI source so the request requires one completion
input; update the CompleteAgentAuthProviderLoginRequest definition (used by the
post operation for completeAgentAuthProviderLogin) to enforce that either
redirectUrl or code is provided (use a oneOf/anyOf with object variants that
each require flowId plus either redirectUrl or code, or use required + oneOf on
the properties), then regenerate frontend types with pnpm gen:api so the
generated ui/src/api/v1/schema.ts reflects the new validation.
| {m.provider === 'openai-codex' ? ( | ||
| <span className={`text-xs ${codexProvider?.connected ? 'text-green-600' : 'text-muted-foreground'}`}> | ||
| {codexProvider?.connected ? 'Subscription' : 'Not connected'} | ||
| </span> | ||
| ) : ( | ||
| <span className={`text-xs ${m.apiKeyConfigured ? 'text-green-600' : 'text-muted-foreground'}`}> | ||
| {m.apiKeyConfigured ? 'Configured' : 'Not set'} | ||
| </span> | ||
| )} |
There was a problem hiding this comment.
Don't collapse "unknown" into "Not connected" here.
codexProvider is null both while the auth query is still loading and when it failed, so this cell briefly reports a disconnected subscription even though the status is actually unknown. That is especially misleading right after page load or a remote-node switch. Render a loading/error state before falling back to "Not connected".
💡 Possible fix
- {m.provider === 'openai-codex' ? (
- <span className={`text-xs ${codexProvider?.connected ? 'text-green-600' : 'text-muted-foreground'}`}>
- {codexProvider?.connected ? 'Subscription' : 'Not connected'}
- </span>
- ) : (
+ {m.provider === 'openai-codex' ? (
+ <span
+ className={`text-xs ${
+ authLoading
+ ? 'text-muted-foreground'
+ : authError
+ ? 'text-amber-600'
+ : codexProvider?.connected
+ ? 'text-green-600'
+ : 'text-muted-foreground'
+ }`}
+ >
+ {authLoading
+ ? 'Checking...'
+ : authError
+ ? 'Unavailable'
+ : codexProvider?.connected
+ ? 'Subscription'
+ : 'Not connected'}
+ </span>
+ ) : (📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {m.provider === 'openai-codex' ? ( | |
| <span className={`text-xs ${codexProvider?.connected ? 'text-green-600' : 'text-muted-foreground'}`}> | |
| {codexProvider?.connected ? 'Subscription' : 'Not connected'} | |
| </span> | |
| ) : ( | |
| <span className={`text-xs ${m.apiKeyConfigured ? 'text-green-600' : 'text-muted-foreground'}`}> | |
| {m.apiKeyConfigured ? 'Configured' : 'Not set'} | |
| </span> | |
| )} | |
| {m.provider === 'openai-codex' ? ( | |
| <span | |
| className={`text-xs ${ | |
| authLoading | |
| ? 'text-muted-foreground' | |
| : authError | |
| ? 'text-amber-600' | |
| : codexProvider?.connected | |
| ? 'text-green-600' | |
| : 'text-muted-foreground' | |
| }`} | |
| > | |
| {authLoading | |
| ? 'Checking...' | |
| : authError | |
| ? 'Unavailable' | |
| : codexProvider?.connected | |
| ? 'Subscription' | |
| : 'Not connected'} | |
| </span> | |
| ) : ( | |
| <span className={`text-xs ${m.apiKeyConfigured ? 'text-green-600' : 'text-muted-foreground'}`}> | |
| {m.apiKeyConfigured ? 'Configured' : 'Not set'} | |
| </span> | |
| )} |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ui/src/pages/agent-settings/index.tsx` around lines 899 - 907, The cell
currently treats a null codexProvider as "Not connected" which conflates
loading/error with an actual disconnected state; update the rendering for the
openai-codex branch to first check the auth query state (e.g., the codex auth
query's isLoading/isError flags or an explicit
codexProviderLoading/codexProviderError) and render a distinct "Loading..." or
"Error" label when loading/error, only falling back to "Not connected" when
codexProvider is non-null and connected is false; keep references to m.provider
=== 'openai-codex', codexProvider, and m.apiKeyConfigured to locate the code to
change.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1921 +/- ##
==========================================
- Coverage 68.77% 68.64% -0.13%
==========================================
Files 457 462 +5
Lines 57637 58222 +585
==========================================
+ Hits 39638 39966 +328
- Misses 14388 14576 +188
- Partials 3611 3680 +69
... and 11 files with indirect coverage changes Continue to review full report in Codecov by Sentry.
🚀 New features to boost your workflow:
|
Summary
Testing
Summary by CodeRabbit
New Features
Updates