Fix invalid login config hint

This commit is contained in:
lemon07r
2026-04-18 23:02:03 -04:00
parent d9b89198b9
commit 0d22121a01
5 changed files with 12 additions and 8 deletions
+1
View File
@@ -70,6 +70,7 @@ These are the invariants that, if broken, silently degrade K2.6 → K2.5 or prod
7. **Auth store is opencode's, not kimi-cli's.** We use opencode's auth store for tokens under the `kimi-for-coding-oauth` 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. Also note that opencode's SDK auth schema only persists the standard oauth fields, so model discovery metadata cannot be stored there durably.
8. **Provider id must not collide with any id in the [models.dev](https://models.dev) catalog.** models.dev publishes `kimi-for-coding` (static `KIMI_API_KEY``@ai-sdk/anthropic` → K2.5). If we registered under that same id, `opencode auth login kimi-for-coding` would surface two methods under one entry and users picking the API-key one would silently land on K2.5. We deliberately use `kimi-for-coding-oauth` instead; `MODEL_ID` on the wire stays `kimi-for-coding` (rule 6).
9. **`src/index.ts` must have exactly one export — the default plugin function.** opencode's plugin loader (`research/opencode/packages/opencode/src/plugin/index.ts``getLegacyPlugins`) iterates every export of the plugin module and throws `Plugin export is not a function` if any named export is not callable. The failure mode is silent in the CLI (the provider just doesn't appear in `opencode auth login`); the error only surfaces in `~/.local/share/opencode/log/*.log`. Keep constants in `src/constants.ts` and import them in `src/index.ts` rather than re-exporting. `test/exports.test.ts` guards this.
10. **The post-login config hint must not emit a partial `limit` object.** opencode's live config schema at `https://opencode.ai/config.json` requires both `limit.context` and `limit.output` whenever `limit` is present, while Kimi's `GET /coding/v1/models` only gives us `context_length`. Therefore `buildConfigBlock()` omits `limit` entirely and leaves `provider.models` to backfill `limit.context` at runtime. Do not invent `output` or set `input` heuristically; opencode's overflow logic treats `limit.input` as authoritative (`research/opencode/packages/opencode/src/session/overflow.ts`).
### Working on this repo
+1 -1
View File
@@ -100,7 +100,7 @@ Two identifiers are load-bearing:
opencode auth login -p kimi-for-coding-oauth
```
The plugin returns a verification URL and user code. After browser approval it polls the device-auth endpoint, queries `/coding/v1/models` to discover the current model id and context length for your account, prints a config snippet with that context length filled in, and stores the token in opencode's auth store. Model discovery runs again on every token refresh, and a fresh loader instance will re-query `/coding/v1/models` on first use if it needs the current wire model id. Access tokens refresh automatically, and the loader retries once after a `401`.
The plugin returns a verification URL and user code. After browser approval it polls the device-auth endpoint, queries `/coding/v1/models` to discover the current model id and context length for your account, prints a ready-to-paste config snippet, and stores the token in opencode's auth store. The authorization message still includes the discovered context length; the snippet intentionally omits `limit` because opencode's schema requires `limit.output` too, and Kimi's models endpoint only exposes `context_length`. Model discovery runs again on every token refresh, and a fresh loader instance will re-query `/coding/v1/models` on first use if it needs the current wire model id. Access tokens refresh automatically, and the loader retries once after a `401`.
### Use
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "opencode-kimi-full",
"version": "1.2.2",
"version": "1.2.3",
"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",
"repository": {
+6 -4
View File
@@ -174,13 +174,17 @@ function applyDiscoveryToModels<T extends Record<string, ModelWithContextLimit>>
}
}
function buildConfigBlock(info: { model_id: string; context_length?: number; display?: string }) {
function buildConfigBlock(info: { model_id: string; display?: string }) {
const name = info.display ?? "Kimi For Coding"
const ctx = info.context_length ?? 0
// The opencode-side model key is always MODEL_ID ("kimi-for-coding"); the
// plugin rewrites the wire `model` body field to `info.model_id` inside
// `loader.fetch`. This way both K2.5 and K2.6 users paste identical
// config — only the wire request differs.
//
// Intentionally omit `limit`: opencode's config schema requires
// `limit.output` whenever a `limit` object is present, but Kimi's
// `/coding/v1/models` discovery only tells us `context_length`. The
// provider.models hook backfills `limit.context` at runtime.
return JSON.stringify(
{
provider: {
@@ -193,7 +197,6 @@ function buildConfigBlock(info: { model_id: string; context_length?: number; dis
name,
reasoning: true,
options: {},
...(ctx > 0 ? { limit: { context: ctx } } : {}),
variants: {
off: { reasoning_effort: "off" },
auto: { reasoning_effort: "auto" },
@@ -462,7 +465,6 @@ const plugin: Plugin = async ({ client }) => {
// this next to the "Authorized" message.
const block = buildConfigBlock({
model_id: discovered.model_id,
context_length: discovered.context_length,
display: discovered.model_display,
})
console.log(
+3 -2
View File
@@ -692,7 +692,7 @@ test("auth.methods[0].authorize returns URL + instructions + async callback", as
expect(typeof cb.expires).toBe("number")
})
test("auth callback prints a config snippet with top-level model variants", async () => {
test("auth callback prints a schema-valid config snippet with top-level model variants", async () => {
mock = installFetchMock((call) => {
if (call.url.includes("device_authorization")) {
return {
@@ -744,7 +744,8 @@ test("auth callback prints a config snippet with top-level model variants", asyn
}
}
const model = parsed.provider[PROVIDER_ID]!.models[MODEL_ID]!
expect(model.limit?.context).toBe(262144)
expect(text).toContain("context 262144")
expect(model.limit).toBeUndefined()
expect(model.options).toEqual({})
expect(model.variants?.off).toEqual({ reasoning_effort: "off" })
expect(model.variants?.auto).toEqual({ reasoning_effort: "auto" })