Skip to content

fix(acp): allow API-key based auth to bypass forced OAuth login - #2185

Open
yogaxu wants to merge 1 commit into
MoonshotAI:mainfrom
yogaxu:main
Open

fix(acp): allow API-key based auth to bypass forced OAuth login#2185
yogaxu wants to merge 1 commit into
MoonshotAI:mainfrom
yogaxu:main

Conversation

@yogaxu

@yogaxu yogaxu commented May 8, 2026

Copy link
Copy Markdown

Problem

When using Kimi Code CLI via ACP (e.g., JetBrains IDE), the server forces OAuth authentication even when the user has already configured a provider with api_key in config.toml.

In the terminal, kimi works fine because it directly uses the configured provider + API key. But through ACP (kimi acp), the IDE pops up a "Login with Kimi account" dialog with only a web/OAuth login option, leaving API-key users no way to proceed.

Reproduction Steps

  1. Configure a provider with api_key in ~/.kimi/config.toml (do not run kimi login):
    [providers.moonshot]
    type = "openai"
    base_url = "https://api.moonshot.cn/v1"
    api_key = "sk-..."
  2. Confirm terminal mode works: kimi --prompt "hello"
  3. Configure the ACP agent in JetBrains IDE (~/.jetbrains/acp.json):
    {
      "agent_servers": {
        "Kimi Code CLI": {
          "command": "kimi",
          "args": ["acp"]
        }
      }
    }
  4. Open AI Chat in JetBrains and select Kimi Code CLI.
  5. Observed: A "Login with Kimi account" dialog appears immediately, with no option to skip or use the configured API key.

Root Cause

There are two places in kimi_cli/acp/server.py that together create this forced-login behavior:

1. _check_auth() only checks OAuth token

def _check_auth(self) -> None:
    reason = self._check_token_usable()   # Only checks OAuth token
    if reason:
        raise acp.RequestError.auth_required({"authMethods": ...})

It completely ignores valid api_key configurations in config.providers.

2. initialize() unconditionally advertises login auth method

self._auth_methods = [
    acp.schema.AuthMethod(id="login", name="Login with Kimi account", ...),
]

Even if the user has an API key, the ACP server tells the client during initialize() that the only available auth method is OAuth login. Some ACP clients (e.g., JetBrains AI Assistant) proactively show an authentication UI when auth_methods is non-empty, before any session is created.

Changes

initialize() — Dynamic auth_methods

config = load_config()
has_api_key = any(
    provider.api_key and provider.api_key.get_secret_value()
    for provider in config.providers.values()
)
if has_api_key or self._check_token_usable() is None:
    self._auth_methods = []
else:
    self._auth_methods = [login_method]
  • If the user already has API-key auth or valid OAuth → auth_methods is empty, preventing proactive auth UI popups.
  • Otherwise → keep the existing OAuth login flow.

_check_auth() — API-key fallback

reason = self._check_token_usable()
if reason:
    config = load_config()
    has_api_key = any(
        provider.api_key and provider.api_key.get_secret_value()
        for provider in config.providers.values()
    )
    if has_api_key:
        return
    # ... existing AUTH_REQUIRED error
  • When OAuth token is unavailable, check if any provider has a non-empty api_key.
  • If so, treat the user as authenticated and do not raise AUTH_REQUIRED.

Relation to PR #1445

PR #1445 removes the _check_auth() gate from new_session and load_session, which addresses part of this issue. However, it does not modify initialize(), so clients that react to auth_methods may still force a login prompt at connection time.

This PR complements PR #1445 by also fixing the initialize() side:

Together they provide a complete fix for API-key-based ACP usage.

Compatibility

  • OAuth users: Behavior unchanged. Valid OAuth token → auth_methods=[] and _check_auth() passes.
  • API-key users: No longer forced into OAuth login. Both initialize() and session operations work seamlessly.
  • Unauthenticated users: Still get the login auth method in initialize() and AUTH_REQUIRED on session creation, preserving the existing onboarding flow.

Open in Devin Review

When using ACP with IDE clients (e.g., JetBrains), the server used to
force OAuth login even when users had already configured providers with
api_key in config.toml. This caused an unavoidable 'Login with Kimi
account' dialog in the IDE.

Changes:
- initialize(): dynamically set auth_methods to empty when the user
  already has API-key based auth configured, preventing proactive
  auth UI popups from ACP clients.
- _check_auth(): fallback to check config.providers for api_key when
  OAuth token is unavailable, instead of unconditionally raising
  AUTH_REQUIRED.

Fixes the scenario where terminal-mode kimi works fine with api_key,
but ACP mode (kimi acp) incorrectly demands OAuth login.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5d6e432fa1

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +83 to +84
if has_api_key or self._check_token_usable() is None:
self._auth_methods = []

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep login method cached for future auth failures

Avoid clearing self._auth_methods in initialize when auth currently looks usable. _check_auth() later builds its AUTH_REQUIRED payload from this cached list, so if the token expires (or API-key config is removed) after initialization, the server will return AUTH_REQUIRED with an empty authMethods array and clients lose the terminal login action (kimi login) needed to recover.

Useful? React with 👍 / 👎.

@devin-ai-integration devin-ai-integration Bot left a comment

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.

Devin Review found 1 potential issue.

View 3 additional findings in Devin Review.

Open in Devin Review

Comment on lines +83 to +84
if has_api_key or self._check_token_usable() is None:
self._auth_methods = []

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.

🟡 AUTH_REQUIRED error raised with empty authMethods when token expires mid-session

When the OAuth token is valid at initialize time, self._auth_methods is set to [] (line 84). If the token later expires (no refresh token) and the user has no API key, _check_auth (line 137) falls through to building auth_methods_data from self._auth_methods which is [], and raises auth_required({"authMethods": []}) at line 166. The client receives an AUTH_REQUIRED error but with no auth methods to present to the user, leaving them stuck with no way to re-authenticate. Before this PR, self._auth_methods was always populated with the login method, so the client always received actionable auth instructions.

Prompt for agents
The problem is that `self._auth_methods` is conditionally cleared in `initialize()` based on point-in-time auth state, but `_check_auth()` later depends on `self._auth_methods` to construct a useful AUTH_REQUIRED error. When the token was valid at init but expires later (with no refresh token and no API key), the AUTH_REQUIRED error is raised with an empty authMethods list.

Two possible approaches:
1. Always populate `self._auth_methods` with the login method (as it was before), but only include it in the `InitializeResponse.auth_methods` conditionally. This way the cached data is always available for `_check_auth` error construction.
2. In `_check_auth`, construct the auth methods on-the-fly (using the same terminal-auth data) rather than relying on the cached `self._auth_methods` list.

Approach 1 is the simplest — decouple what's advertised in the InitializeResponse from what's used internally for error reporting. The relevant code is in `src/kimi_cli/acp/server.py`, specifically the `initialize` method (lines 76-105) and `_check_auth` method (lines 137-166).
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@yogaxu

yogaxu commented May 8, 2026

Copy link
Copy Markdown
Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5d6e432fa1

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +83 to +84
if has_api_key or self._check_token_usable() is None:
self._auth_methods = []

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve login method for later AUTH_REQUIRED responses

initialize() clears self._auth_methods whenever the OAuth token is usable, but _check_auth() later derives the AUTH_REQUIRED payload from this cached list. If a long-lived ACP server starts while the token is valid and the token later expires without a refresh token, new_session/load_session will raise auth_required with an empty authMethods list, so clients lose the terminal-login metadata needed to guide re-authentication.

Useful? React with 👍 / 👎.

Comment on lines +143 to +145
has_api_key = any(
provider.api_key and provider.api_key.get_secret_value()
for provider in config.providers.values()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restrict API-key bypass to active/default provider

_check_auth() currently treats the presence of any provider API key as authenticated, regardless of which provider the session will actually use. In multi-provider configs, an unrelated API key can bypass auth even when the default model is on an OAuth provider with no usable token, causing session creation to pass and then fail at prompt time with 401/internal errors instead of returning a clear AUTH_REQUIRED flow.

Useful? React with 👍 / 👎.

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.

1 participant