Skip to content

feat(kimi): add China-region Kimi Code provider - #400

Merged
caigee-cmd merged 3 commits into
caigee-cmd:mainfrom
yoruuuchan:feat/kimi-code-cn-provider
Oct 26, 2026
Merged

caigee-cmd merged 3 commits into
caigee-cmd:mainfrom
yoruuuchan:feat/kimi-code-cn-provider

Conversation

@yoruuuchan

@yoruuuchan yoruuuchan commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds a China-region Kimi Code provider (subscription product, not the Moonshot Open Platform API-key product). Kimi Code's CLI, desktop app, and the older kimi-cli all authorize with the same public OAuth client over the RFC 8628 device grant and talk to https://api.kimi.com/coding/v1, so the gateway can hold the login the user already completed and serve it through the existing endpoint set.

Two credential universes stay separate: this provider is OAuth-only against auth.kimi.com; an sk-… Open Platform key is not accepted here.

  • Account onboarding. Device-code login through the console (no loopback listener, so it works for a remote/Docker deployment), plus credential import from the JSON the Kimi Code client writes to ~/.kimi-code/credentials/kimi-code.json (or {"format":"kimi-oauth-v1","import_from":"local"} to read that file on the gateway host). Account readiness comes from GET /models; GET /me fills the display profile. Import only — the descriptor advertises import_export because it covers the import side, but export is deliberately not offered: the credential is a bearer token pair, so exporting it would hand out the account.
  • Token lifecycle. Access tokens refresh five minutes before expiry, single-flighted per account. Kimi rotates the refresh token on every refresh, so the rotated pair is persisted immediately and a failed write now fails the request (see the review follow-up) instead of continuing with an in-memory-only token.
  • Model discovery. GET /models is the single source of truth for the model list and every capability: context window, declared thinking mode (only/both/none) and effort ladder, image input, and tool support. Nothing is invented; the legacy flat field shape is accepted too.
  • Reasoning default. The Providers page can store a per-model reasoning default. Resolution order is the client value, then the saved console default, then the model's catalog default, and only then the clamp to the declared ladder. The console catalog shows the saved value back.
  • Chat. The managed endpoint speaks OpenAI Chat Completions with four Kimi deviations the adapter applies: reasoning travels as a top-level thinking object (never reasoning_effort), only max_completion_tokens is accepted, temperature only accepts 0.6 (thinking off) or 1.0 (on) so any other value is omitted rather than clamped, and developer is rewritten to system. Tool-call ids are capped at 64 characters consistently across calls and results. Streams are returned to the gateway unchanged, so /v1/chat/completions, /v1/responses, and /v1/messages all work through the existing shared translation.
  • Quota. GET /usages maps the subscription windows (5-hour, 7-day, monthly, monthly-code) into QuotaInfo.Windows; an account without the managed API (404) reports unknown instead of zero. The boosterWallet block is intentionally not surfaced — the console has no prepaid-balance slot, so it stays an unverified metric rather than being forced into a percentage window.
  • Plan-gated models. The managed /models list is not scoped to the subscription tier: it lists models the plan cannot call, and the call then fails with an authentication-shaped "your current subscription does not have access to <model>". That is a model entitlement, not an account failure, so it classifies as model_not_available and the executor evicts the model for that account while the account stays ready. The chat path also skips the token refresh for that shape instead of spending a refresh round.

Wiring follows the existing provider pattern: descriptor in internal/providers/registry.go, adapter registered in internal/app/app.go, kimi/ prefix added to the executor and console model-prefix lists, console parity (account-type labels in both locales, brand mark), plus one bilingual changelog fragment.

Review follow-up (1a24bcd)

Addresses every blocking item from the review. origin/main is f16d802, which is exactly this branch's base, so there was nothing to merge.

  1. The reasoning default now sticks. UpdateModelSetting / ReadModelSetting / UpdateProviderModelSetting accept kimi (with max_mode rejected, because Kimi has one context tier), modelSettingResponse returns the reasoning payload for kimi, DecorateModelsWithContext decorates kimi like trae/workbuddy so the catalog shows the saved value, and the Providers page sends the PATCH for kimi. resolveLevel now applies the order from AGENTS.md: client value → saved console default → catalog default, and only then the clamp (previously ResolveReasoningLevel("", caps) filled the catalog default first, so storedReasoning was dead).
    • New tests: TestUpdateModelSettingPersistsKimiReasoningDefault, TestUpdateModelSettingRejectsKimiMaxMode, TestModelSettingResponseKimiCarriesReasoningOnly, kimi added to TestDecorateProviderSettingsRestoresDefaultReasoningState, and TestReasoningPriority (client wins, saved default wins over catalog, both are clamped).
    • Live: PATCH low → /api/models shows reasoning_effort: low; a request with no level logs level="low"; a request with reasoning_effort: max logs level="max"; clearing the setting restores the catalog default.
  2. New Kimi accounts keep system prompts. accountOptions() now sends drop_system_prompt: false for kimi; the dead setDropSystemPrompt line is gone. Other providers keep the historical true default, WorkBuddy keeps its switch.
    • Live: account created through the API the way the wizard does it stores drop_system_prompt=false and reads back false; a chat with a system message ("answer with exactly one uppercase word") returned HELLO, which proves the prompt reached the model.
  3. A failed persist after rotation now fails the request. Both credential() and forceRefresh() go through persistRefreshed, which retries the write once and then returns a KindUnavailable error naming the problem; it never returns the unpersisted credential. The account fails over instead of falling into a spurious KindAuth on its next request.
    • New tests: TestRefreshPersistFailureFailsTheRequest (no chat request is made, stored credential is untouched), TestForceRefreshPersistFailureSurfaces (401 → refresh → write failure surfaces), TestConcurrentRefreshIsSingleFlight (8 goroutines, exactly one refresh call, one persisted pair).
  4. slow_down now backs off. pollDeviceToken reports a distinct slow_down kind, PollLogin raises loginState.interval by 5 s per occurrence (capped at 60 s so a pending code still finishes inside its window), and the existing throttle then makes the next console poll wait.
    • New tests: TestDeviceLoginSlowDownBacksOff (two consecutive slow_downs widen 5 s → 10 s → 15 s, the throttled poll does not hit the token endpoint, then success) and TestDeviceLoginPendingDoesNotWidenInterval.

Nits:

  • supportsTools default. I checked the live payload before changing anything: GET /models on this account omits supports_tool_use on all six models, and those same models complete real tool calls (finish_reason: tool_calls, arguments round-tripped). Kimi Code's own client also treats an absent field as supported. Defaulting to false would therefore hide a capability that demonstrably works, so the default stays true, the reasoning is documented in catalog.go, and an explicit supports_tool_use: false is still honored (covered by a fixture in TestParseCatalogJSONCurrentShape).
  • Import-only export. Documented in doc.go and in the changelog fragment.

Test plan

  • go test ./... (new and touched packages green; the same pre-existing Windows-only failures as main remain in internal/api, internal/executor, internal/providers/qoder, internal/runtime, and internal/store — file-mode and path-separator assertions that also fail on a clean main checkout)
  • go vet ./...
  • cd frontend && npm run build && npm run lint (0 errors, warnings unchanged) and npm run sync
  • python scripts/release-notes.py validate
  • git diff --check
  • Added changelog/unreleased/kimi-code-provider.md
  • No tokens, auth blobs, raw captures, or host details in the diff
  • cd worker && npm test — not touched by this PR
  • go test -race ./... — could not run locally (no cgo toolchain on the Windows host); CI covers it

All six CI checks pass on 5dab128: go, frontend, worker, container, secrets, and windows-host. An earlier run of 1a24bcd failed only the go job, on TestSaturatedPoolKeepsFiveSecondRetryAfter — a pool-saturation test this branch does not touch. It passed on the same code in the windows-host job of that very run, on the rerun, 15 local runs before the retrigger, and 30 local runs at GOMAXPROCS=2; the retrigger commit exists only to get a fresh green run.

Live acceptance (one real China-region subscription)

Run against a locally built binary on a fresh data dir, through the public endpoints only:

Check Result
Console device-code sign-in login/device returned the verification URL, login/status reached ok, account became ready/hot
Readiness and profile GET /models succeeded; /me filled remote_uid and the plan label
Model discovery 6 models announced: kimi-for-coding (1,048,576 ctx, thinking only, efforts low/high/max, default max, image in), k3, k3-256k, k3.1-pro, k3.1-pro-highspeed, kimi-for-coding-highspeed
Console reasoning default PATCH → catalog decoration → request path, plus clear, all verified after the follow-up
Chat, non-stream kimi/kimi-for-coding answered, usage reported
Chat, stream SSE deltas with reasoning_content and final usage (cached_tokens present)
Tool calls finish_reason: tool_calls with {"city":"Chongqing"}; a second request replaying assistant.tool_calls + tool result produced the final answer
Thinking effort reasoning_effort: max produced 115 reasoning deltas and a 91-reasoning-token completion
Vision A generated PNG data URL was described correctly
/v1/responses Both non-stream (reasoning + message items) and stream (response.completed with usage)
/v1/messages Both non-stream and stream (message_start … message_delta)
Quota 5-hour and 7-day windows with reset times; monthly windows absent from this account's payload and therefore not shown
Plan-gated model kimi/k3 returned model_not_available; the account stayed ready/hot; the second request failed fast from the learned model state; the entitled model kept working

Scope of this account: kimi-for-coding and k3-256k are callable; k3, k3.1-pro, k3.1-pro-highspeed, and kimi-for-coding-highspeed are advertised by /models but the subscription does not include them, which is exactly the entitlement case above.

Multi-account

Only one real account exists, so multi-account behavior is covered by automated tests only: independent credentials per account, per-account catalog cache, per-account refresh single-flight (a rotation on account B does not touch account A), and per-account login state.

Known limitations

  • Only the China region is implemented (auth.kimi.com / api.kimi.com/coding/v1); the global region is not part of this change.
  • /models is not plan-scoped, so an unentitled model is only hidden after its first attempted call (the executor's existing model eviction then handles it).
  • The boosterWallet (prepaid booster balance) is not rendered in the console yet.
  • Tool JSON-Schema normalization is not applied; the endpoint accepts ordinary client schemas, but schemas with missing nested types (the case Kimi's own CLI rewrites) are passed through unchanged.
  • The console UI was verified through the built bundle and the API it calls, not with a browser screenshot.
  • The live re-verification exercises one real account; the slow_down path and the persist-failure path are only covered by the httptest suite, because neither can be triggered on the production endpoint on demand.

Kimi Code is a subscription product separate from the Moonshot Open
Platform. Its CLI, desktop app, and the older kimi-cli all authorize with
the same OAuth client over the RFC 8628 device grant and talk to
https://api.kimi.com/coding/v1, so the gateway can hold that same login.

- device-code login through the console, plus credential import from the
  file the Kimi Code client already writes under ~/.kimi-code
- token refresh five minutes before expiry, single-flighted per account and
  persisted because Kimi rotates the refresh token
- model discovery from GET /models: context window, thinking mode and
  efforts, image input, and tool support, all taken from the declaration
- Chat Completions with Kimi's wire deviations (top-level thinking,
  max_completion_tokens, temperature 0.6/1.0, developer to system, 64-char
  tool ids), which serves /v1/chat/completions, /v1/responses, and
  /v1/messages through the shared gateway translation
- readiness from /models, profile from /me, and quota from /usages
  (5-hour, 7-day, monthly, monthly-code windows)
- plan-gated models classify as model_not_available instead of auth, so a
  healthy account stays ready and only the model is evicted

Verified against a live China-region subscription: sign-in, model
discovery, chat, streaming, tool calls, thinking deltas, vision, and all
three public APIs.

@caigee-cmd caigee-cmd left a comment •

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this — it's a really solid first cut of an in-process provider. The package split matches the existing adapter pattern, OAuth stays on the public Kimi Code client, the catalog/quota/chat deviations are documented and tested, and the live-acceptance table is more honest than most new-provider PRs. CI is also green.

A few of the behaviors described in the PR summary don't quite land yet, and one of them is load-bearing because Kimi rotates the refresh token. Could you take a look at the notes below? Happy to re-review as soon as they're in.

It would also help to merge current main first (AGENTS.md asks for this). The branch is mergeable but a bit behind, and a couple of the console files this change needs to touch have moved.

Please take a look

  1. Console reasoning default looks wired, but the setting never actually sticks.
    AGENTS.md says the console value is a default only when the client omits a level. The adapter already has storedReasoning(), but:

    • internal/control/settings.go UpdateModelSetting only persists reasoning_effort for trae / workbuddy.
    • frontend/src/pages/ProvidersPage.tsx onReasoningChange returns early for any other provider, so the Kimi dropdown is currently a no-op.
    • internal/console/models.go modelSettingResponse also only returns reasoning_effort for those two providers (the kimi/ prefix was added to path splitting, not to the setting payload).
    • resolveLevel clamps the request before reading the stored default. ResolveReasoningLevel("", caps) already fills the catalog default, so storedReasoning() is dead even after a PATCH lands.
      WorkBuddy's order is the one we want: empty client level → stored console default → then clamp. Could you extend the settings PATCH, modelSettingResponse, and the Providers page allow-list to kimi as well?
  2. New Kimi accounts still drop caller system prompts.
    The PR text and the chooseProvider comment say Kimi keeps system prompts because there is no upstream screening to work around. accountOptions() still sends drop_system_prompt: true for every non-WorkBuddy provider (showDropSystem ? dropSystemPrompt : true), so setDropSystemPrompt(option?.provider !== 'kimi') doesn't change what gets stored. Either persist false for Kimi on create, or we can drop that claim — either is fine.

  3. A failed persist after refresh can burn the account.
    Kimi rotates the refresh token on every refresh. credential() / forceRefresh() currently do if err := saveCredential(...); err != nil { return refreshed, nil }. The in-flight request keeps going with the new access token, but the next request reloads the old refresh token and the account falls into KindAuth. Could you surface (or retry) the persist error here? Swallowing it is much more expensive on this provider than on ones that reuse the same refresh token.

  4. RFC 8628 slow_down doesn't actually back off.
    pollSlowDownStep is documented but unused. pollDeviceToken treats slow_down as authorization_pending without raising loginState.interval, so a polite server is still polled at the original cadence. PollLogin already throttles on state.interval — bumping it on slow_down should be enough.

Nits (non-blocking)

  • supportsTools defaults to true when supports_tool_use is absent. That invents a capability the catalog did not declare; false is safer unless a live payload actually omits the field on tool-capable models.
  • Export returns ErrUnsupported while the descriptor advertises ImportExport. Totally fine if import-only is intentional — a one-line note in the changelog/docs would help so the empty console export looks expected.
  • Device-code login, entitlement-vs-auth classification, temperature omit-not-clamp, 64-char tool ids, /usages 404 → unknown, and the httptest flow coverage are all in good shape. Please keep those.

Thanks again for the live-account work on this — that's the hard part. Looking forward to the follow-up.

Comment thread internal/providers/kimi/client.go Outdated
return Credential{}, err
}
if err := c.saveCredential(ctx, accountID, refreshed); err != nil {
return refreshed, nil

@caigee-cmd caigee-cmd Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kimi rotates the refresh token on every successful refresh, so this persist really has to land.

If SaveCredentialPayload fails here, the current request continues with refreshed in memory, but the next loadCredential still has the previous refresh token — which the server has already invalidated. That turns into a spurious KindAuth / re-login.

Could you return (or retry) the write error instead of swallowing it? forceRefresh in chat.go has the same pattern.

Comment thread internal/providers/kimi/chat.go Outdated
}
level := providers.ResolveReasoningLevel(requested, caps)
if level == "" || len(caps.ReasoningOptions) == 0 {
level = providers.ResolveReasoningLevel(c.storedReasoning(ctx, reqModel), caps)

@caigee-cmd caigee-cmd Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

providers.ResolveReasoningLevel("", caps) already returns ReasoningDefault or ReasoningOptions[0] when the model declares a ladder. That means this if level == "" branch never reads storedReasoning for a real Kimi thinking model, so the console default can't apply even after settings persistence is wired.

WorkBuddy does the order AGENTS.md asks for: requested level, else stored console default, then clamp. Could you match that here?

Comment thread internal/providers/kimi/oauth.go Outdated
return devicePollResult{}, fmt.Errorf("kimi token polling server error (HTTP %d): %s", status, apiMessage(body, "unknown error"))
}
switch payload.Error {
case "authorization_pending", "slow_down":

@caigee-cmd caigee-cmd Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slow_down is currently classified as pending, but loginState.interval never increases and pollSlowDownStep is unused. RFC 8628 asks the client to back off on slow_down.

PollLogin already throttles on state.interval — bumping it here (or in the pending branch of PollLogin) should be enough for the next console poll to wait longer.

setTab(option?.provider === 'zhipu' ? 'pat' : 'browser')
// Kimi Code has no content screening that would need caller system prompts
// stripped, so a new Kimi account keeps them by default.
setDropSystemPrompt(option?.provider !== 'kimi')

@caigee-cmd caigee-cmd Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setDropSystemPrompt(option?.provider !== 'kimi') doesn't currently change what gets persisted. showDropSystem is WorkBuddy-only, and accountOptions() sends drop_system_prompt: showDropSystem ? dropSystemPrompt : true, so a new Kimi account still stores true.

The PR summary says Kimi keeps caller system prompts by default. Either sending false on create for provider === 'kimi', or dropping this comment, would make the two match — right now this is dead code that contradicts the stored flag.

Review follow-up for caigee-cmd#400.

- Persist, read, decorate, and PATCH the Kimi reasoning default. The
  console value now reaches the request: priority is the client value,
  then the saved console default, then the catalog default, and only then
  the clamp (previously the clamp ran first, so the saved value was dead).
- New Kimi accounts keep caller system prompts. The wizard sends
  drop_system_prompt=false for Kimi instead of relying on a switch Kimi
  does not expose.
- Fail the request when a rotated refresh token cannot be persisted. Kimi
  invalidates the previous refresh token on every rotation, so continuing
  with an in-memory-only token left the stored credential already dead.
- Back off the device-token poll interval on RFC 8628 slow_down, capped so
  a still-pending code finishes inside its window.
- Document that Kimi Code is import-only, and pin the tool-support default
  to the observed catalog behavior: the live payload omits
  supports_tool_use on every model and those models do complete tool calls.

Live re-verification: catalog shows the saved level, a request without a
level is sent at that level, an explicit client level still wins, clearing
the setting restores the catalog default, a system prompt round-trips, and
streaming plus tool calls still work.
TestSaturatedPoolKeepsFiveSecondRetryAfter failed once on CI while passing
locally (15 runs before the retrigger, plus 30 runs at GOMAXPROCS=2 and two
shuffled full-package runs). The test does not touch any file this branch
changes, so retrigger rather than change pool behavior.
@yoruuuchan

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review — the refresh-token point especially, that one was load-bearing and I had it wrong. Follow-up pushed as 1a24bcd on the same branch.

On merging main first: origin/main is f16d802, which is exactly this branch's base, so there was nothing to merge (checked with a fresh git fetch; the PR reports commits: 2 with no base drift).

Blockers

1. The reasoning default now actually sticks.
The chain had three gaps, all closed:

  • internal/control/settings.go:119 and :167 accept kimi in ReadModelSetting / UpdateModelSetting, and UpdateProviderModelSetting (:215) rejects max_mode for Kimi with a clear error, since Kimi sells one context tier.
  • internal/console/models.go:86 returns the reasoning/context payload for kimi (no max_mode, because there is no switch to report).
  • internal/control/catalog_policy.go:369 decorates kimi like trae/workbuddy, so the Providers page reads back the saved level instead of the catalog default.
  • frontend/src/pages/ProvidersPage.tsx:304 no longer returns early for kimi.
  • internal/providers/kimi/chat.go:153 applies the order AGENTS.md asks for: client value → saved console default → catalog default, then the clamp. You were right that the old code clamped first and made storedReasoning dead.

Tests: TestUpdateModelSettingPersistsKimiReasoningDefault, TestUpdateModelSettingRejectsKimiMaxMode, TestModelSettingResponseKimiCarriesReasoningOnly, kimi added to the existing TestDecorateProviderSettingsRestoresDefaultReasoningState, and TestReasoningPriority (client wins, saved default beats catalog, both clamped).

Live, against the real account: PATCH low → /api/models shows reasoning_effort: low; a request with no level logs level="low"; a request with reasoning_effort: max logs level="max"; clearing the setting restores max from the catalog.

2. New Kimi accounts keep system prompts.
accountOptions() (frontend/src/components/AddAccountModal.tsx:249) now sends drop_system_prompt: false for kimi; the dead setDropSystemPrompt call is removed. WorkBuddy keeps its switch, every other provider keeps the historical true.

Live: an account created through the API the way the wizard does it stores and reads back false, and a chat with system: "answer with exactly one uppercase word" returned HELLO.

3. A failed persist after rotation now fails the request.
Both credential() and forceRefresh() go through persistRefreshed (internal/providers/kimi/client.go:247), which retries the write once and then returns a KindUnavailable error that names the problem. It never returns the unpersisted credential, so the attempt fails over instead of walking into a spurious KindAuth on the next request.

Tests: TestRefreshPersistFailureFailsTheRequest (no upstream chat is attempted and the stored credential is untouched), TestForceRefreshPersistFailureSurfaces (401 → refresh → write failure surfaces), TestConcurrentRefreshIsSingleFlight (8 goroutines, exactly one refresh call, one persisted pair).

4. slow_down backs off.
pollDeviceToken reports a distinct kind (internal/providers/kimi/oauth.go:150), and PollLogin calls backOffLogin (internal/providers/kimi/login.go:192) to add 5 s to loginState.interval, capped at 60 s so a code that is still pending can finish inside its window. The existing throttle makes the next console poll wait.

Tests: TestDeviceLoginSlowDownBacksOff (two consecutive slow_downs widen 5 s → 10 s → 15 s, the throttled poll does not reach the token endpoint, then the login completes) and TestDeviceLoginPendingDoesNotWidenInterval.

Nits

  • supportsTools default. I checked the live payload before touching it: GET /models on this account omits supports_tool_use on all six models, and those models do complete real tool calls (finish_reason: tool_calls with round-tripped arguments). Kimi Code's own client also treats an absent field as supported. Flipping the default to false would hide a capability that demonstrably works, so true stays — now documented at the function, and an explicit supports_tool_use: false is honored (fixture added in TestParseCatalogJSONCurrentShape).
  • Import vs export. doc.go and the changelog fragment now say import-only and why: the credential is a bearer token pair, so export would hand out the account.

Verification

  • go test ./...: new and touched packages green; the pre-existing Windows-only failures in internal/api, internal/executor, internal/providers/qoder, internal/runtime, and internal/store are unchanged (same set fails on a clean main checkout).
  • go vet ./..., python scripts/release-notes.py validate, git diff --check: clean.
  • frontend: npm run build && npm run lint (0 errors, same warnings), npm run sync committed.
  • Live re-run on the real CN account: reasoning default end-to-end (set, display, request, clear), system prompt round-trip, streaming, tool calls, and the plan-gated model still classifying as model_not_available without cooling the account.
  • CI: all six checks (go, frontend, worker, container, secrets, windows-host) pass on 5dab128. One run of 1a24bcd failed only the go job on TestSaturatedPoolKeepsFiveSecondRetryAfter, a pool-saturation test this branch does not touch; the same code passed in the windows-host job of that run, and it passes locally (15 plain runs plus 30 runs at GOMAXPROCS=2). 5dab128 is an empty retrigger commit for a fresh green run, not a code change.

Unchanged by design: device-code login, entitlement-vs-auth classification, temperature omit-not-clamp, 64-char tool ids, /usages 404 → unknown, and the httptest flow coverage.

Ready for another look when you have a moment — happy to split any of this further if you'd prefer it smaller.

@caigee-cmd caigee-cmd left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed on 5dab128. The four blocking items from the previous review are addressed:

  1. Reasoning default — UpdateModelSetting / ReadModelSetting / modelSettingResponse / Providers page / DecorateModelsWithContext all include kimi; resolveLevel applies client → console default → catalog clamp.
  2. System prompts kept — accountOptions() sets drop_system_prompt: false for new Kimi accounts.
  3. Refresh write failure fails hard — persistRefreshed retries once then returns KindUnavailable; credential() and forceRefresh() never return an unpersisted rotated token.
  4. slow_down backoff — distinct poll kind + backOffLogin (+5s, cap 60s) so PollLogin throttles correctly.

CI is green on all six checks (go, frontend, worker, container, secrets, windows-host). LGTM — not merging from this review.

@caigee-cmd
caigee-cmd merged commit e55ba46 into caigee-cmd:main Oct 26, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants