Kimi Code joins as an OAuth plan provider: RFC 8628 device flow, verified against the live endpoints

- New oauth/kimi-code-flow.ts mirrors the official kimi-cli: form-encoded device_authorization + token polling at auth.kimi.com with the required X-Msh-* device headers, pure poll interpreter, 15-minute tokens refreshed via refresh_token grant
- Agent loop runs chat completions at https://api.kimi.com/coding/v1 with the Bearer access token; default model kimi-for-coding (kimi-for-coding-highspeed for Allegretto+ plans)
- Live model listing via /coding/v1/models; credential refresh wired into the turn credential resolver
- Migration 057 seeds the provider (disabled) into existing organizations
- Wire protocol probed live: device code issued, authorization_pending poll observed, 401 envelope on the coding API confirmed
This commit is contained in:
ZacharyZhang-NY committed 2026-07-10 17:18:31 -04:00
1 parent 813276de71
commit 327de38e78
14 files changed
+281 -2

No files matched your search

@@ -28,6 +28,7 @@ import { refreshCodex } from "../oauth/openai-codex-flow";
import { refreshClaudePro } from "../oauth/anthropic-claude-flow";
import { refreshAntigravity } from "../oauth/google-antigravity-flow";
import { refreshGitHubCopilotToken } from "../oauth/github-copilot-flow";
import { refreshKimiCode } from "../oauth/kimi-code-flow";
interface StoredOAuth {
access: string;
@@ -63,6 +64,8 @@ function refreshOAuth(providerKey: string, creds: StoredOAuth): Promise<OAuthCre
return refreshAntigravity(creds.refresh, creds.projectId);
case "github-copilot":
return refreshGitHubCopilotToken(creds.refresh, creds.enterpriseUrl ?? undefined);
case "kimi-code":
return refreshKimiCode(creds.refresh);
default:
throw new Error(`${providerKey} cannot refresh its credential.`);
}
@@ -10,6 +10,7 @@ import {
runCopilotTurn,
runCustomAnthropicTurn,
runCustomOpenAiTurn,
runKimiCodeTurn,
runOpenAiResponsesTurn,
runVertexTurn,
} from "./turns";
@@ -29,6 +30,7 @@ const TURNS: Record<string, ToolTurnFn> = {
"github-copilot": runCopilotTurn,
"google-antigravity": runAntigravityTurn,
"claude-pro": runClaudeProTurn,
"kimi-code": runKimiCodeTurn,
"custom-openai": runCustomOpenAiTurn,
"custom-anthropic": runCustomAnthropicTurn,
"azure-openai": runAzureTurn,
@@ -14,6 +14,7 @@ import { bedrockToolConfig, parseBedrockOutput, toBedrockMessages } from "./bedr
import { signAwsRequest } from "./aws-sigv4";
import { getGcpAccessToken } from "./gcp-auth";
import { COPILOT_HEADERS, copilotBaseUrl } from "../oauth/github-copilot-flow";
import { KIMI_CODE_BASE_URL } from "../oauth/kimi-code-flow";
const ANTIGRAVITY_VERSION = "1.21.9";
const ANTIGRAVITY_ENDPOINT = "https://cloudcode-pa.googleapis.com";
@@ -108,6 +109,22 @@ export async function runClaudeProTurn(input: ProviderToolTurnInput): Promise<Ag
return { ...parsed, rawResponseJson: JSON.stringify(response.data) };
}
// ---- Kimi Code (OAuth plan, chat completions at the coding endpoint) ----
export async function runKimiCodeTurn(input: ProviderToolTurnInput): Promise<AgentTurnResult> {
const access = requireAccessToken(input.credential, "Kimi Code");
const body: JsonRecord = {
model: input.model,
messages: toChatMessages(input.instructions, input.messages),
tools: chatToolDefs(input.tools),
tool_choice: "auto",
stream: false,
};
const response = await postJson(`${KIMI_CODE_BASE_URL}/chat/completions`, { authorization: `Bearer ${access}` }, body);
const parsed = parseChatChoice(response.data);
return { ...parsed, rawResponseJson: JSON.stringify(response.data) };
}
// ---- GPT account / OpenAI Codex (OAuth, SSE) ----
export async function runCodexTurn(input: ProviderToolTurnInput): Promise<AgentTurnResult> {