mirror of
https://github.com/lemon07r/opencode-kimi-full.git
synced 2026-07-18 08:05:52 +02:00
Add README and AGENTS.md
This commit is contained in:
@@ -2,3 +2,4 @@ research/
|
||||
node_modules/
|
||||
dist/
|
||||
*.log
|
||||
.junie/
|
||||
@@ -0,0 +1,113 @@
|
||||
# AGENTS.md — working notes for coding agents (and humans)
|
||||
|
||||
This file is the single source of truth for any AI agent (or human) modifying this repo. Read it top-to-bottom before touching code. If something you learn here contradicts what you see in the code, the **code wins** — update this file in the same commit.
|
||||
|
||||
User-facing install / usage documentation lives in [`README.md`](./README.md). Do **not** duplicate it here.
|
||||
|
||||
---
|
||||
|
||||
### Purpose
|
||||
|
||||
One plugin, one job: make `opencode` talk to Kimi's `kimi-for-coding` endpoint **exactly the way the official `kimi-cli` does**. Everything in this repo exists to minimize drift from upstream kimi-cli.
|
||||
|
||||
### The one rule that matters
|
||||
|
||||
> Moonshot's backend picks the model (K2.5 vs K2.6) from the **auth token type**, not the model-name string.
|
||||
|
||||
- Static `sk-kimi-...` API key → K2.5.
|
||||
- OAuth JWT with `scope: kimi-code` → K2.6.
|
||||
|
||||
Every design decision here follows from that: we do device-flow OAuth, we do not accept API keys, we do not let the upstream SDK attach its own Authorization header.
|
||||
|
||||
### Non-goals
|
||||
|
||||
- No support for K2.5 or any non-`kimi-for-coding` model. opencode already handles those via Moonshot / Baseten / Alibaba-CN / etc.
|
||||
- No support for static API keys. Users who want that can use a different opencode provider entry.
|
||||
- No custom SSE parser, tool-call normalizer, or message rewriter. `@ai-sdk/openai-compatible` already does SSE/`reasoning_content` correctly.
|
||||
|
||||
---
|
||||
|
||||
### Architecture
|
||||
|
||||
Three files, 1 job each. Do not add a fourth unless the existing three genuinely can't hold a new concern.
|
||||
|
||||
| File | Responsibility |
|
||||
|--------------------|--------------------------------------------------------------------------------|
|
||||
| `src/constants.ts` | Pinned strings that must mirror upstream kimi-cli (version, endpoints, client id, scope). |
|
||||
| `src/headers.ts` | The seven `X-Msh-*` / UA headers + the persistent `~/.kimi/device_id` file. |
|
||||
| `src/oauth.ts` | Device-code start, device-code poll, refresh-token exchange. Nothing else. |
|
||||
| `src/index.ts` | Plugin entry. Wires `auth` hook (login + loader) and `chat.params` hook. |
|
||||
|
||||
Data flow on a chat request:
|
||||
|
||||
1. opencode asks the `@ai-sdk/openai-compatible` provider for a language model.
|
||||
2. Before instantiating it, opencode calls our `auth.loader`. We return `{ apiKey, fetch }`.
|
||||
3. The SDK uses our `fetch` for every HTTP call (models, chat, whatever).
|
||||
4. Our `fetch` calls `ensureFresh()` → maybe refreshes → sets Authorization + the seven `X-Msh-*` headers → on 401 refreshes once and retries.
|
||||
5. Separately, opencode runs the `chat.params` hook and writes `thinking`, `reasoning_effort`, `prompt_cache_key` into `output.options`. opencode wraps those as `{ [providerID]: options }` and the openai-compatible SDK forwards them as top-level body fields. That is why those keys must use **exactly** the wire names (`prompt_cache_key`, `reasoning_effort`, `thinking`).
|
||||
|
||||
### Contracts to keep intact
|
||||
|
||||
These are the invariants that, if broken, silently degrade K2.6 → K2.5 or produce fingerprint-based throttling. Do not "clean them up" without reading the linked upstream.
|
||||
|
||||
1. **`X-Msh-Version` and `User-Agent` must track `kimi-cli`.** Bumping involves exactly one line in `src/constants.ts`. See upstream `research/kimi-cli/src/kimi_cli/constant.py`.
|
||||
2. **`X-Msh-Device-Id` must be stable across runs.** Never regenerate a fresh UUID at import time. `getDeviceId()` reads/writes `~/.kimi/device_id`; that path is shared with `kimi-cli` on purpose.
|
||||
3. **`Authorization` header is owned by `loader.fetch`.** Anything else (opencode core, the SDK, future hooks) must be overridden. Our `loader` deletes both `authorization` and `Authorization` before setting its own.
|
||||
4. **Effort ↔ fields mapping** (kimi-cli `llm.py` / `kosong/chat_provider/kimi.py`):
|
||||
|
||||
| Effort | `reasoning_effort` | `thinking.type` |
|
||||
|----------|--------------------|-----------------|
|
||||
| `off` | *(omitted)* | `"disabled"` |
|
||||
| `low` | `"low"` | `"enabled"` |
|
||||
| `medium` | `"medium"` | `"enabled"` |
|
||||
| `high` | `"high"` | `"enabled"` |
|
||||
|
||||
Do not send `thinking.type="enabled"` with no `reasoning_effort` unless the request never had one to begin with (the default "server picks" case).
|
||||
|
||||
5. **`prompt_cache_key` only for `kimi-for-coding`.** Never attach it to unrelated models. The check is `input.model.id === MODEL_ID` in `chat.params`.
|
||||
6. **Model id goes over the wire verbatim.** Don't strip the `kimi-` prefix — the backend expects exactly `kimi-for-coding`.
|
||||
7. **Auth store is opencode's, not kimi-cli's.** We use `client.auth.get/set` against the `kimi-for-coding` provider id. Do not read/write `~/.kimi/credentials/kimi-code.json`; that's kimi-cli's file and sharing it across independent apps causes token-race bugs.
|
||||
|
||||
### Working on this repo
|
||||
|
||||
- **Code style:** see `tsconfig.json` (strict, `noUncheckedIndexedAccess`, ES2022). Prefer small pure functions, avoid `try`/`catch` except where we genuinely convert one error shape to another.
|
||||
- **Comments:** match the existing density — only explain non-obvious upstream-parity reasoning. Do not narrate the obvious ("// refresh the token"); instead reference upstream files when the reasoning is "because kimi-cli does it that way".
|
||||
- **Dependencies:** runtime deps stay at **zero**. The only dev/peer dep is `@opencode-ai/plugin` for types.
|
||||
- **Git commits:** small, logical, imperative subject ("Add oauth device flow"). When committing, pass `--trailer "Co-authored-by: Junie <junie@jetbrains.com>"` if you are an AI agent.
|
||||
- **Upstream research:** the `research/` directory is a read-only git-ignored pair of shallow clones (opencode + kimi-cli) for grep. Never edit files there; re-clone if you suspect drift. When citing upstream in a comment, use the `research/…` path so the reference is resolvable.
|
||||
- **Version bumps:** when kimi-cli bumps, (1) pull a fresh `research/kimi-cli`, (2) update `KIMI_CLI_VERSION` in `src/constants.ts`, (3) re-diff `_kimi_default_headers()` / `oauth.py` against `src/headers.ts` and `src/oauth.ts`, (4) smoke-test with `opencode auth login kimi-for-coding` and a one-turn chat, (5) tag release.
|
||||
|
||||
### What not to do
|
||||
|
||||
- ❌ Don't add heuristics that look at the model id outside of `chat.params`. The `auth.loader` fetch is already scoped to this provider; the only place that needs to match on `kimi-for-coding` is the params hook.
|
||||
- ❌ Don't add new header values that kimi-cli doesn't send. The fingerprint matters.
|
||||
- ❌ Don't call out to other files to "share" the kimi-cli credentials. Different OAuth consumers must have independent refresh-token chains or one will invalidate the other.
|
||||
- ❌ Don't introduce a build step. The plugin ships as `.ts` and opencode's bun-based loader handles it.
|
||||
- ❌ Don't add tests that require real Kimi credentials and check them in. If you add offline unit tests, put them under `test/` and mock `fetch`.
|
||||
|
||||
### How to verify a change
|
||||
|
||||
Offline:
|
||||
|
||||
```sh
|
||||
bun build --target=node --no-bundle src/index.ts # syntax/type-ish check
|
||||
```
|
||||
|
||||
Online (requires a real Kimi-for-coding account):
|
||||
|
||||
1. `cd ~/.opencode && bun add /path/to/this/repo`
|
||||
2. Paste the provider block from `README.md` into your opencode config.
|
||||
3. `opencode auth login kimi-for-coding` — confirm a token lands in opencode's `auth.json` with `type: "oauth"`, a JWT `access`, and `expires` ~15 min in the future.
|
||||
4. Start opencode, select `kimi-for-coding/kimi-for-coding`, and ask the model to self-identify. It should claim to be K2.6 / `kimi-for-coding`.
|
||||
5. Confirm `reasoning_content` deltas render as thinking content (not assistant text).
|
||||
6. In a second turn of the same session, confirm the response comes back faster (cache hit via `prompt_cache_key`).
|
||||
|
||||
If any of 3–6 fails, diff `research/kimi-cli` against the contracts above.
|
||||
|
||||
### House rules for AI agents
|
||||
|
||||
- Read this file first. Every time.
|
||||
- Don't grow the dependency footprint to "simplify" something; this plugin's value is being small and audit-able.
|
||||
- When in doubt, mirror kimi-cli exactly, then comment the upstream reference. "We used to deviate, it broke" — document it here.
|
||||
- Keep `README.md` user-focused and this file contributor-focused. If you catch yourself duplicating, move content here and link from the README.
|
||||
- Any new rule you add here must have a real incident or a grep-verified upstream source behind it. No speculative "best practices".
|
||||
@@ -0,0 +1,140 @@
|
||||
## opencode-kimi-full
|
||||
|
||||
An [opencode](https://opencode.ai) plugin for the **Kimi For Coding** plan. It authenticates the same way the official [`kimi` CLI](https://github.com/MoonshotAI/kimi-cli) does and mirrors its wire shape, so opencode requests to Moonshot's `/coding` endpoint match `kimi` CLI's byte-for-byte.
|
||||
|
||||
Contributor and agent documentation lives in [`AGENTS.md`](./AGENTS.md).
|
||||
|
||||
---
|
||||
|
||||
### Why this plugin exists
|
||||
|
||||
There are two ways to talk to Moonshot's Kimi For Coding plan today: the way `kimi` CLI does it, and the way opencode does it. They target different endpoints and use different authentication. This plugin brings the `kimi` CLI approach into opencode.
|
||||
|
||||
**How `kimi` CLI does it.** OAuth device-code flow against `auth.moonshot.cn` with `scope: kimi-code`, producing a short-lived JWT. Requests go to `https://api.kimi.com/coding/v1` (OpenAI-compatible) with the JWT as the bearer token, seven `X-Msh-*` fingerprint headers, a stable `~/.kimi/device_id`, and per-request extras: `prompt_cache_key` (an opt-in, session-scoped cache key) and paired `thinking.type` + `reasoning_effort` (sent together, matching `kimi` CLI). The backend routes this token to K2.6.
|
||||
|
||||
**How opencode does it.** `opencode auth login` selects a Kimi For Coding provider from the catalog and prompts for a `KIMI_API_KEY` (a static `sk-kimi-...` key). The catalog entry uses `@ai-sdk/anthropic` against `api.kimi.com/coding`, which is valid since the endpoint exposes both OpenAI-compatible and Anthropic-compatible routes for third-party agents. Authentication is the static key; no Kimi-specific request extras are sent (opencode's generic plumbing has no code path for `prompt_cache_key`, the paired `thinking` + `reasoning_effort` shape, or the `X-Msh-*` headers). The backend currently routes a static `sk-kimi-...` key to K2.5.
|
||||
|
||||
**What this plugin gives you.** Everything `kimi` CLI does, inside opencode. OAuth device flow with `scope: kimi-code` (so you land on K2.6), `prompt_cache_key` set to the opencode session id, paired `thinking` + `reasoning_effort`, the seven `X-Msh-*` headers and `kimi`-CLI-shaped UA, and a `~/.kimi/device_id` shared with a locally-installed `kimi` CLI. Tokens are stored in opencode's `auth.json` under a dedicated `kimi-for-coding` provider id, so the plugin and `kimi` CLI keep independent refresh-token chains and do not invalidate each other. Streaming, `reasoning_content` deltas, and tool-call schemas are handled upstream by `@ai-sdk/openai-compatible` and are not reimplemented.
|
||||
|
||||
Two upstream changes would narrow the gap between the two paths. Even after both, the plugin remains a higher-fidelity alternative to opencode's built-in Kimi For Coding path:
|
||||
|
||||
- If Moonshot starts routing `sk-kimi-...` keys to K2.6, opencode's built-in path reaches K2.6 too, but still without `prompt_cache_key` or the paired reasoning fields. Explicit session-scoped cache reuse via `prompt_cache_key` stays unavailable on that path (any automatic prefix caching Moonshot may do is orthogonal and would apply to both paths), and reasoning is controlled on the Anthropic route via `thinking.budget_tokens` (a token budget); the paired `reasoning_effort: low|medium|high` knob that `kimi` CLI exposes has no equivalent there.
|
||||
- If opencode ships a native Kimi For Coding OAuth, the auth story converges, but the request-field gap stays until opencode's provider code emits these exact fields for `/coding`. `kimi` CLI is Moonshot's first-party client and targets the OpenAI-compatible route, so mirroring its wire shape is the lowest-risk way to stay aligned with upstream. Fingerprint parity with `kimi` CLI (same `X-Msh-Device-Id` and headers as `kimi` CLI, `kimi`-CLI-shaped UA) and independent refresh-token chains are unlikely to be replicated by a first-party integration.
|
||||
|
||||
---
|
||||
|
||||
### Requirements
|
||||
|
||||
- `opencode` ≥ 1.4.6
|
||||
- A Kimi account with an active **Kimi For Coding** subscription (the same plan that works with `kimi` CLI)
|
||||
|
||||
### Install
|
||||
|
||||
```sh
|
||||
cd ~/.opencode
|
||||
bun add opencode-kimi-full
|
||||
```
|
||||
|
||||
From a local checkout:
|
||||
|
||||
```sh
|
||||
cd ~/.opencode
|
||||
bun add /path/to/opencode-kimi-full
|
||||
```
|
||||
|
||||
### Configure
|
||||
|
||||
Add the plugin and a provider entry to `opencode.json` (or `~/.config/opencode/opencode.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"plugin": ["opencode-kimi-full"],
|
||||
"provider": {
|
||||
"kimi-for-coding": {
|
||||
"name": "Kimi K2.6 (for coding)",
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"options": {
|
||||
"baseURL": "https://api.kimi.com/coding/v1"
|
||||
},
|
||||
"models": {
|
||||
"kimi-for-coding": {
|
||||
"name": "Kimi K2.6 Code Preview",
|
||||
"limit": { "context": 262144, "output": 32768 },
|
||||
"reasoning": true,
|
||||
"options": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Two identifiers are load-bearing and must not be renamed:
|
||||
|
||||
- the **provider id** `kimi-for-coding`. The plugin's `auth` and `chat.params` hooks match on it.
|
||||
- the **model id** `kimi-for-coding`. Sent to Moonshot verbatim; do not strip the `kimi-` prefix.
|
||||
|
||||
### Log in
|
||||
|
||||
```sh
|
||||
opencode auth login kimi-for-coding
|
||||
```
|
||||
|
||||
The plugin returns a verification URL and user code. After browser approval it polls the device-auth endpoint and persists tokens through opencode's `auth.json`. Access tokens have a ~15 minute TTL and refresh automatically; refresh tokens last ~30 days.
|
||||
|
||||
### Use
|
||||
|
||||
Select `kimi-for-coding/kimi-for-coding` in opencode.
|
||||
|
||||
---
|
||||
|
||||
### Request fields in detail
|
||||
|
||||
| Field | Wire shape | Purpose |
|
||||
|---|---|---|
|
||||
| `prompt_cache_key` | top-level body, snake_case, set to opencode's `sessionID` | Opt-in, session-scoped cache key, mirroring `kimi` CLI. |
|
||||
| `thinking` + `reasoning_effort` | `thinking: { type: "enabled" \| "disabled" }` with sibling `reasoning_effort: "low" \| "medium" \| "high"` | Sent together, matching `kimi` CLI. |
|
||||
| Seven `X-Msh-*` headers + UA | `User-Agent`, `X-Msh-Platform`, `X-Msh-Version`, `X-Msh-Device-Name`, `X-Msh-Device-Model`, `X-Msh-Device-Id`, `X-Msh-OS-Version` | Matches `kimi` CLI's `_kimi_default_headers()` at the pinned `KIMI_CLI_VERSION`. |
|
||||
| `~/.kimi/device_id` | UUID persisted on disk, embedded in `X-Msh-Device-Id` | Sends the same `X-Msh-Device-Id` as a locally-installed `kimi` CLI. |
|
||||
|
||||
Effort-to-field mapping, taken verbatim from `kimi` CLI:
|
||||
|
||||
| user effort | `reasoning_effort` | `thinking.type` |
|
||||
|---|---|---|
|
||||
| `off` | *(omitted)* | `"disabled"` |
|
||||
| `low` / `medium` / `high` | same string | `"enabled"` |
|
||||
|
||||
---
|
||||
|
||||
### Files the plugin touches
|
||||
|
||||
| Path | Purpose |
|
||||
|---|---|
|
||||
| `~/.kimi/device_id` | Stable UUID used in `X-Msh-Device-Id`. Shared with `kimi` CLI. |
|
||||
| `<opencode data>/auth.json` | Token storage, managed by opencode through `client.auth.*`. |
|
||||
|
||||
No other state is persisted. Credentials are never written to `~/.kimi/credentials/`; that path belongs to `kimi` CLI, and sharing it would cause refresh-token races between the two clients.
|
||||
|
||||
### Architecture
|
||||
|
||||
```
|
||||
┌────────────── opencode core ─────────────┐
|
||||
│ │
|
||||
│ auth.login ─▶ plugin.auth.authorize() │ device-code flow, poll
|
||||
│ └─▶ oauth.ts │
|
||||
│ │
|
||||
│ chat ──────▶ plugin.loader() │ custom fetch that:
|
||||
│ ├─▶ ensureFresh() │ • proactive refresh
|
||||
│ └─▶ kimiHeaders() │ • 7 X-Msh-* headers
|
||||
│ │ • 401 → force-refresh + retry
|
||||
│ chat.params ─▶ plugin "chat.params" │ thinking / reasoning_effort /
|
||||
│ │ prompt_cache_key
|
||||
└──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
A full description of the invariants that keep this working is in [`AGENTS.md`](./AGENTS.md), under "Architecture" and "Contracts to keep intact".
|
||||
|
||||
### License
|
||||
|
||||
MIT.
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "opencode-kimi-for-coding",
|
||||
"name": "opencode-kimi-full",
|
||||
"version": "0.1.0",
|
||||
"description": "OpenCode plugin that adds first-class support for Kimi K2.6 (kimi-for-coding) via the official Kimi OAuth device flow, matching the upstream kimi-cli 1:1.",
|
||||
"license": "MIT",
|
||||
|
||||
Reference in New Issue
Block a user