The previous display name 'Kimi K2.6 (for coding)' was confusing —
K2.6 is a model, not a provider, and it collided visually with the
models.dev 'Kimi For Coding' entry in interactive 'opencode auth login'.
Name now clearly signals this is the OAuth variant.
Plugin was silently 403ing every live request with
'access_terminated_error: only available for Coding Agents'. Discovered by
capturing what kimi-cli actually sends on the wire and diffing.
Three fingerprint bugs, each sufficient on its own to trigger the 403:
* User-Agent was 'KimiCodeCLI/<v>'; upstream sends 'KimiCLI/<v>'.
(research/kimi-cli/src/kimi_cli/constant.py::get_user_agent)
* X-Msh-Device-Model was 'x86_64'; upstream sends
'{system} {release} {machine}' e.g. 'Linux 7.0.0 x86_64'.
(research/kimi-cli/src/kimi_cli/auth/oauth.py::_device_model)
* X-Msh-Os-Version was '{type} {release}' e.g. 'Linux 7.0.0'; upstream
sends platform.version() i.e. the kernel build string. Node equivalent
is os.version().
Verified live against api.kimi.com/coding/v1 with a freshly-minted JWT:
200 OK and real K2.6 response after the fix; 403 before.
Locked in with regression tests in test/headers.test.ts and
test/constants.test.ts, and documented in AGENTS.md contract rule 1.
Adds one test file per source file plus a shared fetch mock helper:
- test/constants.test.ts: OAuth scope/client_id/PROVIDER_ID/MODEL_ID pins,
rules 1, 6, 8.
- test/headers.test.ts: seven fingerprint header keys, UA/X-Msh-Version
track KIMI_CLI_VERSION, ASCII-only values, device-id format and
stability across calls (rules 1, 2).
- test/oauth.test.ts: form-encoded bodies for device-start/refresh,
authorization_pending/expired_token/unknown-error branches, non-OK and
non-JSON error shapes.
- test/plugin.test.ts: chat.params provider+model gating, full effort
matrix (rule 4), prompt_cache_key gating (rule 5), camelCase effort
input, loader Authorization ownership (rule 3), refresh-on-expiry with
persistAuth, 401 single-retry no-loop, device-flow authorize wiring.
- test/_util/fetchMock.ts: zero-dep global-fetch swap returning canned
Responses with call capture.
Notes:
- Tests use the real ~/.kimi/device_id; getDeviceId is idempotent and the
file is shared with kimi-cli by design (AGENTS.md rule 2), so no HOME
redirect is needed. Attempted monkey-patching os.homedir instead is
fragile under Bun's eager import-binding resolution.
- pollDeviceToken tests use interval=1 because `device.interval || 5`
treats 0 as the default 5s and would push each iteration past the
default bun-test timeout.
Total: 35 tests, ~5s to run.
Plugin v1.0.0 exported a named PROVIDER_ID constant alongside the default
export. opencode's plugin loader (getLegacyPlugins) iterates every export
and requires each to be a function; the named string export caused it to
throw 'Plugin export is not a function', which was only visible in the
log file — the provider silently vanished from 'opencode auth login'.
- Move PROVIDER_ID into src/constants.ts and import it in src/index.ts.
- Add test/exports.test.ts as a regression guard (bun test).
- Add bunfig.toml to scope bun test away from research/ and node_modules/.
- Wire 'bun test' into the release workflow.
- Document rule 9 in AGENTS.md (single default export in src/index.ts).
- Also pick up the previous session's README fix (opencode auth login -p).