Skip to content

Commit 0d22121

Browse files
committed
Fix invalid login config hint
1 parent d9b8919 commit 0d22121

5 files changed

Lines changed: 12 additions & 8 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ These are the invariants that, if broken, silently degrade K2.6 → K2.5 or prod
7070
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.
7171
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).
7272
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.
73+
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`).
7374

7475
### Working on this repo
7576

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ Two identifiers are load-bearing:
100100
opencode auth login -p kimi-for-coding-oauth
101101
```
102102

103-
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`.
103+
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`.
104104

105105
### Use
106106

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "opencode-kimi-full",
3-
"version": "1.2.2",
3+
"version": "1.2.3",
44
"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.",
55
"license": "MIT",
66
"repository": {

src/index.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,17 @@ function applyDiscoveryToModels<T extends Record<string, ModelWithContextLimit>>
174174
}
175175
}
176176

177-
function buildConfigBlock(info: { model_id: string; context_length?: number; display?: string }) {
177+
function buildConfigBlock(info: { model_id: string; display?: string }) {
178178
const name = info.display ?? "Kimi For Coding"
179-
const ctx = info.context_length ?? 0
180179
// The opencode-side model key is always MODEL_ID ("kimi-for-coding"); the
181180
// plugin rewrites the wire `model` body field to `info.model_id` inside
182181
// `loader.fetch`. This way both K2.5 and K2.6 users paste identical
183182
// config — only the wire request differs.
183+
//
184+
// Intentionally omit `limit`: opencode's config schema requires
185+
// `limit.output` whenever a `limit` object is present, but Kimi's
186+
// `/coding/v1/models` discovery only tells us `context_length`. The
187+
// provider.models hook backfills `limit.context` at runtime.
184188
return JSON.stringify(
185189
{
186190
provider: {
@@ -193,7 +197,6 @@ function buildConfigBlock(info: { model_id: string; context_length?: number; dis
193197
name,
194198
reasoning: true,
195199
options: {},
196-
...(ctx > 0 ? { limit: { context: ctx } } : {}),
197200
variants: {
198201
off: { reasoning_effort: "off" },
199202
auto: { reasoning_effort: "auto" },
@@ -462,7 +465,6 @@ const plugin: Plugin = async ({ client }) => {
462465
// this next to the "Authorized" message.
463466
const block = buildConfigBlock({
464467
model_id: discovered.model_id,
465-
context_length: discovered.context_length,
466468
display: discovered.model_display,
467469
})
468470
console.log(

test/plugin.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -692,7 +692,7 @@ test("auth.methods[0].authorize returns URL + instructions + async callback", as
692692
expect(typeof cb.expires).toBe("number")
693693
})
694694

695-
test("auth callback prints a config snippet with top-level model variants", async () => {
695+
test("auth callback prints a schema-valid config snippet with top-level model variants", async () => {
696696
mock = installFetchMock((call) => {
697697
if (call.url.includes("device_authorization")) {
698698
return {
@@ -744,7 +744,8 @@ test("auth callback prints a config snippet with top-level model variants", asyn
744744
}
745745
}
746746
const model = parsed.provider[PROVIDER_ID]!.models[MODEL_ID]!
747-
expect(model.limit?.context).toBe(262144)
747+
expect(text).toContain("context 262144")
748+
expect(model.limit).toBeUndefined()
748749
expect(model.options).toEqual({})
749750
expect(model.variants?.off).toEqual({ reasoning_effort: "off" })
750751
expect(model.variants?.auto).toEqual({ reasoning_effort: "auto" })

0 commit comments

Comments
 (0)