Skip to content

feat: support model migration metadata - #4171

Draft
7Sageer wants to merge 1 commit into
mainfrom
model-upgrade-migration
Draft

7Sageer wants to merge 1 commit into
mainfrom
model-upgrade-migration

Conversation

@7Sageer

@7Sageer 7Sageer commented Oct 10, 2026

Copy link
Copy Markdown
Collaborator

Requirement or Bug

Support Codex-style upgrade metadata in custom registries, so Web and desktop clients can guide users through model migration.

Bug Reproduction Steps

N/A

Root Cause

N/A

Code Changes

  • Parse and validate upgrade in custom registries; reject malformed timestamps.
  • Return valid migration candidates from refresh results, without changing the default model.
  • Extend refresh responses and event.model_catalog.changed with optional migrations.
  • Document the registry fields and add coverage for parser, refresh, discovery, and WebSocket forwarding.

Behavior Changes and Affected Users

| Behavior | Before | After | Who relies on the old behavior | Escape hatch |
| Custom registry upgrade metadata | Ignored by the refresh pipeline | Returned as migration candidates and broadcast to Web/Desktop clients | Custom-registry providers and Web/Desktop users | N/A |
| Default model selection | Unchanged by refresh | Still unchanged; users opt into migration in model settings | All current default-model users | No automatic migration required |
| Refresh event broadcast | Sent only when aliases changed | Also sent when upgrade metadata exists | Existing refresh-event consumers | Additive optional field; existing payload shapes are unchanged |
| Invalid upgrade data | Accepted and skipped silently | Rejected with an explicit refresh error | Registry authors | Fix the invalid upgrade metadata |

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue (feature request).
  • I have added tests that prove my feature works.
  • The behavior-change table above is complete, and no removed defaults are named.
  • Ran gen-changesets.
  • Ran gen-docs.

Copilot AI balanced review requested due to automatic review settings October 10, 2026 15:23
@changeset-bot

changeset-bot Bot commented Oct 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e7a8dc7

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@7Hanrui

7Hanrui commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-10T15:31:04.765430Z e7a8dc7 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

if (input !== undefined && output !== undefined) {

P1 Badge Preserve one-sided modality metadata

When a custom registry supplies only modalities.input or only modalities.output, which the previous || condition accepted, this new && condition discards the entire object. An input-only image model can consequently lose image_in during refresh and even fall back to the default tool_use capability, silently changing which inputs the client permits; retain the object when either side is valid.

AGENTS.md reference: AGENTS.md:L98-L100


if (response.changed.length > 0 || (response.migrations?.length ?? 0) > 0) {
this.events.publish(new ModelCatalogChanged({ payload: response }));

P1 Badge Make migration candidates available after reconnects

Migration candidates are only delivered through this live global WebSocket event. The startup scheduler begins refreshing immediately after the server starts listening, global targets do not replay the __global__ journal, and neither /models nor /config exposes the latest candidates, so a web or desktop client that connects or reloads after that refresh starts with an empty migration list and shows no banner until another refresh—normally up to six hours later. Persist the latest candidates or provide an initialization endpoint that clients fetch on connection.

AGENTS.md reference: AGENTS.md:L110-L112


https://github.com/MoonshotAI/kimi-code/blob/e7a8dc7097e91b75829b4934ee61c992d1601049/apps/kimi-code/dist-web/assets/index-BUlO2hUi.js#L177
P2 Badge Merge scoped migration updates instead of replacing all candidates

This reducer treats every refresh report as a complete migration snapshot, but /providers/:id:refresh includes candidates only for the selected provider and OAuth refresh events contain none. Refreshing one provider therefore deletes banners for every other provider, while an OAuth model change clears the list entirely; merge candidates by provider/source alias or have the server emit a full snapshot, then update the code-app source and resync this bundle.

AGENTS.md reference: AGENTS.md:L18-L18


migrations: z
.array(
z.object({
provider_id: z.string(),
from_alias: z.string(),
from_model: z.string(),
to_alias: z.string(),
to_model: z.string(),
migration_markdown: z.string(),
retirement_at: z.string().optional(),
target_default_effort: z.string().optional(),
}),
)
.optional(),

P2 Badge Expose migrations in the public event payload type

Although this schema now preserves migrations at runtime, the exported CatalogChangedPayload above is still derived from IOAuthService['refreshOAuthProviderModels'], whose response type has no migrations property. Because that alias drives KlientEvents.on('kosong.changed', ...) and is re-exported publicly, TypeScript SDK consumers cannot access the new metadata without an unsafe cast; derive the payload type from the provider-discovery/event contract or from this schema.

AGENTS.md reference: packages/klient/AGENTS.md:L14-L17


migrations.push({
providerId: entry.id,
fromAlias: `${entry.id}/${fromKey}`,
fromModel: fromModel.id,
toAlias: `${entry.id}/${toKey}`,
toModel: toModel.id,
migrationMarkdown: upgrade.migration_markdown,
retirementAt: upgrade.retirement_at ?? undefined,
targetDefaultEffort: toModel.default_effort,
});

P2 Badge Emit migrations only after their provider refresh succeeds

Candidates are appended before the provider's credential and application checks run. If an existing registry provider has conflicting credentials or an api_key_env-protected endpoint change, the later branch records a refresh failure without installing the remote target alias, but this migration is still returned and broadcast; web and desktop can then advertise a replacement that is absent from the model list and fall back to preselecting an unrelated first model. Collect candidates only after applyCustomRegistryProvider succeeds, using the aliases actually present in next.

AGENTS.md reference: AGENTS.md:L98-L100


https://github.com/MoonshotAI/kimi-code/blob/e7a8dc7097e91b75829b4934ee61c992d1601049/apps/kimi-code/dist-web/assets/index-BUlO2hUi.js#L138
P2 Badge Apply the replacement model's declared effort

The bundle maps target_default_effort into targetDefaultEffort, but never reads that property afterward; accepting the migration only changes the model. When the old model's configured effort is unsupported by the replacement—for example max to a target declaring only high—OpenAI-compatible providers retain and send the stale effort, potentially making the first request after migration fail. Apply the target effort when the recommended replacement is selected, or omit this field if it is not part of the migration operation, then update code-app and resync the bundle.

AGENTS.md reference: AGENTS.md:L18-L18


https://github.com/MoonshotAI/kimi-code/blob/e7a8dc7097e91b75829b4934ee61c992d1601049/apps/kimi-code/dist-web/assets/index-BUlO2hUi.js#L177
P2 Badge Warn again when users return to a retired model

Completing a migration records a persistent switched decision, and shouldShowMigration suppresses every decision whose kind is not dismissed forever. If a user switches to the recommendation and later selects the retiring source model again, the banner remains hidden even after retirement_at passes; make visibility depend on the current model or allow completed decisions to resurface when the source is selected again, then update code-app and resync the bundle.

AGENTS.md reference: AGENTS.md:L18-L18


if (!isRecord(rawUpgrade)) {
throw new Error(`Invalid upgrade for model "${id}": expected an object.`);

P2 Badge Isolate malformed upgrade metadata to its model

Before this change an unknown upgrade field was ignored, but a registry model containing an unrelated value such as "upgrade": true now throws here. That exception escapes toProviderEntry and aborts fetchCustomRegistry, so one model's optional metadata prevents every provider at the same registry URL from importing or refreshing; skip the malformed upgrade or isolate the failure to that model/provider instead of rejecting the entire registry.

AGENTS.md reference: AGENTS.md:L98-L100

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

This branch has not been deployed

No deployments
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.

3 participants