Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/acp-api-key-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Allow ACP sessions to use configured API-key providers.
10 changes: 5 additions & 5 deletions docs/en/guides/ides.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ Kimi Code CLI supports integration into IDEs via the [Agent Client Protocol (ACP

## Prerequisites

Before configuring your IDE, make sure Kimi Code CLI is installed and you have completed the login setup.
Before configuring your IDE, make sure Kimi Code CLI is installed and has usable authentication: complete the OAuth login flow or configure a provider with an API key.

The ACP adapter is exposed as the `kimi acp` subcommand. The IDE launches it as a child process and communicates over stdin/stdout using JSON-RPC. Each time the IDE creates a session, the CLI reuses its existing authentication state — no need to log in again.
The ACP adapter is exposed as the `kimi acp` subcommand. The IDE launches it as a child process and communicates over stdin/stdout using JSON-RPC. Each time the IDE creates a session, the CLI reuses its existing OAuth login or configured API key — no need to authenticate again.

::: tip Path note
Child processes launched from an IDE GUI on macOS typically do **not** inherit the terminal shell's `PATH`. If `kimi` is not in a system directory like `/usr/local/bin`, use the absolute path in your IDE configuration. Run `which kimi` in a terminal to find the active path.
Expand Down Expand Up @@ -82,12 +82,12 @@ Pick **Kimi Code CLI** from Paseo's built-in ACP provider catalog, or add a cust
}
```

Paseo's generic ACP adapter does not drive the login flow, so complete the terminal login first (see [Prerequisites](#prerequisites)) — otherwise session creation fails with `Authentication required`.
Paseo's generic ACP adapter does not drive the login flow. Complete the terminal OAuth login or configure a provider with an API key first (see [Prerequisites](#prerequisites)) — otherwise session creation fails with `Authentication required`.

## Troubleshooting

- **Session disconnects immediately / IDE shows "agent exited"**: usually a wrong `command` path or a missing login. Run `kimi acp` in a terminal first to verify — if it blocks waiting for stdin, the CLI itself is fine and the problem is in the IDE configuration; if it exits immediately with an error, follow the error message (most commonly you need to run `/login`).
- **IDE shows "auth required"**: the CLI has no usable authentication token. Exit the IDE, run `kimi` in a terminal to complete login, then restart the IDE.
- **Session disconnects immediately / IDE shows "agent exited"**: usually a wrong `command` path or missing authentication. Run `kimi acp` in a terminal first to verify — if it blocks waiting for stdin, the CLI itself is fine and the problem is in the IDE configuration; if it exits immediately with an error, follow the error message (most commonly you need to run `/login` or configure a provider with an API key).
- **IDE shows "auth required"**: the CLI has no usable OAuth login or configured API key. Exit the IDE, complete login or configure a provider in a terminal, then restart the IDE.
- **MCP tools not visible**: check the [`kimi acp` reference](../reference/kimi-acp.md) capability table to confirm that the MCP transport type configured in your IDE is supported. The Kimi Code CLI ACP adapter currently supports `http`, `stdio`, and `sse` transports; `acp` transport MCP servers are silently dropped and a warning is written to the log.

## Next steps
Expand Down
10 changes: 5 additions & 5 deletions docs/zh/guides/ides.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ Kimi Code CLI 支持通过 [Agent Client Protocol (ACP)](https://agentclientprot

## 前置准备

在配置 IDE 之前,请确保已安装 Kimi Code CLI 并完成登录配置
在配置 IDE 之前,请确保已安装 Kimi Code CLI,并完成 OAuth 登录或配置带有 API 密钥的 provider

ACP 适配层暴露子命令 `kimi acp`,IDE 通过子进程方式启动它,并在标准输入/输出上跑 JSON-RPC。每次 IDE 创建会话时,CLI 会复用它的鉴权状态——不需要重复登录
ACP 适配层暴露子命令 `kimi acp`,IDE 通过子进程方式启动它,并在标准输入/输出上跑 JSON-RPC。每次 IDE 创建会话时,CLI 会复用已有的 OAuth 登录或已配置的 API 密钥——不需要重复鉴权

::: tip 路径提示
macOS 下从 IDE GUI 启动的子进程通常**不会**继承终端 shell 的 `PATH`,所以如果 `kimi` 不在 `/usr/local/bin` 这类系统目录里,IDE 配置中要使用绝对路径。终端里运行 `which kimi` 可以查到当前生效的路径。
Expand Down Expand Up @@ -82,12 +82,12 @@ JetBrains 这一侧对 `command` 字段处理较严格——务必填写**绝对
}
```

Paseo 的通用 ACP 适配层不会帮你走登录流程,所以请先完成终端登录(见[前置准备](#前置准备))——否则创建会话会以 `Authentication required` 失败。
Paseo 的通用 ACP 适配层不会帮你走登录流程,所以请先完成终端 OAuth 登录或配置带有 API 密钥的 provider(见[前置准备](#前置准备))——否则创建会话会以 `Authentication required` 失败。

## 故障排查

- **会话立刻被中断 / IDE 提示 "agent exited"**:通常是 `command` 路径不对或 kimi 没登录。先在终端跑一次 `kimi acp` 验证:如果阻塞等待标准输入则说明 CLI 本身没问题,问题在 IDE 配置;如果立刻报错则按报错提示处理(多数是没 `/login`)。
- **IDE 显示 "auth required"**:表示 CLI 没有可用的鉴权令牌。退出 IDE,在终端执行 `kimi` 完成登录后再启动 IDE 即可。
- **会话立刻被中断 / IDE 提示 "agent exited"**:通常是 `command` 路径不对或缺少鉴权。先在终端跑一次 `kimi acp` 验证:如果阻塞等待标准输入则说明 CLI 本身没问题,问题在 IDE 配置;如果立刻报错则按报错提示处理(多数是没 `/login`,或还没配置带有 API 密钥的 provider)。
- **IDE 显示 "auth required"**:表示 CLI 没有可用的 OAuth 登录或已配置的 API 密钥。退出 IDE,在终端完成登录或配置 provider 后再启动 IDE 即可。
- **MCP 工具看不到**:参考 [`kimi acp`](../reference/kimi-acp.md) 中的能力表确认 IDE 配的 MCP 传输类型是否被支持。当前 Kimi Code CLI 的 ACP 适配层支持 `http`、`stdio` 与 `sse` 三种传输方式;`acp` 传输的 MCP server 会被静默丢弃并在日志中给出 warn。

## 下一步
Expand Down
47 changes: 46 additions & 1 deletion packages/node-sdk/src/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
writeConfigFile,
type KimiConfig,
type OAuthRef,
type ProviderConfig,
} from '@moonshot-ai/agent-core';
import {
applyManagedKimiCodeConfig,
Expand Down Expand Up @@ -119,7 +120,26 @@ export class KimiAuthFacade {
}

async status(providerName?: string | undefined): Promise<AuthStatus> {
return this.toolkit.status(providerName, this.resolveRuntimeManagedAuth(providerName).oauthRef);
const status = await this.toolkit.status(
providerName,
this.resolveRuntimeManagedAuth(providerName).oauthRef,
);
const config = loadRuntimeConfigSafe(this.options.configPath).config;
const apiKeyProviderNames = new Set(
Object.entries(config.providers)
.filter(([, provider]) => hasConfiguredApiKey(provider))
.map(([name]) => name),
);
const providers = status.providers.map((entry) => {
const hasApiKey = apiKeyProviderNames.delete(entry.providerName);
return { ...entry, hasToken: entry.hasToken || hasApiKey };
});
if (providerName !== undefined || apiKeyProviderNames.size === 0) return { providers };

for (const providerName of apiKeyProviderNames) {
providers.push({ providerName, hasToken: true });
}
return { providers };
}

async login(
Expand Down Expand Up @@ -317,3 +337,28 @@ export class KimiAuthFacade {
}).oauthRef;
}
}

function hasConfiguredApiKey(provider: ProviderConfig): boolean {
if (nonEmpty(provider.apiKey)) return true;

switch (provider.type) {
case 'anthropic':
return nonEmpty(provider.env?.['ANTHROPIC_API_KEY']);
case 'openai':
case 'openai_responses':
return nonEmpty(provider.env?.['OPENAI_API_KEY']);
case 'kimi':
return nonEmpty(provider.env?.['KIMI_API_KEY']);
case 'google-genai':
return nonEmpty(provider.env?.['GOOGLE_API_KEY']);
case 'vertexai':
return (
nonEmpty(provider.env?.['VERTEXAI_API_KEY']) ||
nonEmpty(provider.env?.['GOOGLE_API_KEY'])
);
}
}

function nonEmpty(value: string | undefined): boolean {
return (value?.trim().length ?? 0) > 0;
}
65 changes: 65 additions & 0 deletions packages/node-sdk/test/auth-facade.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,71 @@ oauth = { storage = "file", key = "${oauthKey}", oauth_host = "https://auth.dev.
});
});

it('reports configured API-key providers as authenticated', async () => {
await writeFile(
join(homeDir, 'config.toml'),
`
[providers."moonshot-cn"]
type = "kimi"
base_url = "https://api.example.test/v1"
api_key = "YOUR_API_KEY"
`,
);
const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY });

await expect(harness.auth.status()).resolves.toEqual({
providers: [
{ providerName: KIMI_CODE_PROVIDER_NAME, hasToken: false },
{ providerName: 'moonshot-cn', hasToken: true },
],
});
await expect(harness.auth.status('moonshot-cn')).resolves.toEqual({
providers: [{ providerName: 'moonshot-cn', hasToken: true }],
});
});

it('reports env-backed API-key providers as authenticated', async () => {
await writeFile(
join(homeDir, 'config.toml'),
`
[providers."moonshot-env"]
type = "kimi"
base_url = "https://api.example.test/v1"

[providers."moonshot-env".env]
KIMI_API_KEY = "YOUR_API_KEY"
`,
);
const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY });

await expect(harness.auth.status()).resolves.toEqual({
providers: [
{ providerName: KIMI_CODE_PROVIDER_NAME, hasToken: false },
{ providerName: 'moonshot-env', hasToken: true },
],
});
await expect(harness.auth.status('moonshot-env')).resolves.toEqual({
providers: [{ providerName: 'moonshot-env', hasToken: true }],
});
});

it('does not duplicate a provider with both OAuth and an API key', async () => {
await new FileTokenStorage(join(homeDir, 'credentials')).save('kimi-code', freshToken());
await writeFile(
join(homeDir, 'config.toml'),
`
[providers."managed:kimi-code"]
type = "kimi"
api_key = "YOUR_API_KEY"
`,
);
const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY });

await expect(harness.auth.status()).resolves.toEqual({
providers: [{ providerName: KIMI_CODE_PROVIDER_NAME, hasToken: true }],
});
});

it('provisions SDK config using an existing Kimi OAuth token', async () => {
await new FileTokenStorage(join(homeDir, 'credentials')).save('kimi-code', freshToken());
const fetchMock = vi.fn<FetchMock>(
Expand Down