From 43e4926b05d5bc7add5c8b1cbc56a4916163351a Mon Sep 17 00:00:00 2001 From: HeXian Date: Fri, 24 Jul 2026 21:36:41 +0800 Subject: [PATCH 1/2] fix(oauth): switch default model on explicit login --- .changeset/fix-oauth-login-default-model.md | 6 +++ packages/node-sdk/test/auth-facade.test.ts | 21 +++++++++- packages/oauth/src/managed-kimi-code.ts | 19 ++++++++- packages/oauth/src/toolkit.ts | 3 ++ packages/oauth/test/managed-kimi-code.test.ts | 1 + packages/oauth/test/toolkit.test.ts | 41 +++++++++++++++++++ 6 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 .changeset/fix-oauth-login-default-model.md diff --git a/.changeset/fix-oauth-login-default-model.md b/.changeset/fix-oauth-login-default-model.md new file mode 100644 index 0000000000..e39b814e3b --- /dev/null +++ b/.changeset/fix-oauth-login-default-model.md @@ -0,0 +1,6 @@ +--- +"@moonshot-ai/kimi-code": patch +"@moonshot-ai/kimi-code-sdk": patch +--- + +Fix OAuth login retaining an API key model as the default after switching back to Kimi Code. diff --git a/packages/node-sdk/test/auth-facade.test.ts b/packages/node-sdk/test/auth-facade.test.ts index 978bd25780..a13b7172f9 100644 --- a/packages/node-sdk/test/auth-facade.test.ts +++ b/packages/node-sdk/test/auth-facade.test.ts @@ -203,8 +203,23 @@ oauth = { storage = "file", key = "${oauthKey}", oauth_host = "https://auth.dev. }); }); - it('provisions SDK config using an existing Kimi OAuth token', async () => { + it('switches an API default when logging in with an existing Kimi OAuth token', async () => { await new FileTokenStorage(join(homeDir, 'credentials')).save('kimi-code', freshToken()); + await writeFile( + join(homeDir, 'config.toml'), + ` +default_model = "api-provider/api-model" + +[providers.api-provider] +type = "kimi" +api_key = "YOUR_API_KEY" + +[models."api-provider/api-model"] +provider = "api-provider" +model = "api-model" +max_context_size = 262144 +`, + ); const fetchMock = vi.fn( async (_input, _init) => new Response( @@ -258,6 +273,10 @@ oauth = { storage = "file", key = "${oauthKey}", oauth_host = "https://auth.dev. apiKey: '', oauth: { storage: 'file', key: 'oauth/kimi-code' }, }); + expect(config.providers['api-provider']).toMatchObject({ apiKey: 'YOUR_API_KEY' }); + expect(config.models?.['api-provider/api-model']).toMatchObject({ + provider: 'api-provider', + }); expect(config.services?.moonshotSearch?.oauth).toEqual({ storage: 'file', key: 'oauth/kimi-code', diff --git a/packages/oauth/src/managed-kimi-code.ts b/packages/oauth/src/managed-kimi-code.ts index 6b0ce7561c..e36b8a2ab0 100644 --- a/packages/oauth/src/managed-kimi-code.ts +++ b/packages/oauth/src/managed-kimi-code.ts @@ -200,6 +200,7 @@ export interface ManagedKimiConfigAdapter { readonly oauthKey?: string | undefined; readonly oauthHost?: string | undefined; readonly preserveDefaultModel?: boolean | undefined; + readonly preserveNonManagedDefaultModel?: boolean; }, ): ManagedKimiCodeApplyResult; remove?(config: TConfig): void; @@ -213,6 +214,7 @@ export interface ProvisionManagedKimiCodeConfigOptions { readonly oauthKey?: string | undefined; readonly oauthHost?: string | undefined; readonly preserveDefaultModel?: boolean | undefined; + readonly preserveNonManagedDefaultModel?: boolean; readonly fetchImpl?: typeof fetch | undefined; readonly headers?: Record | undefined; } @@ -568,6 +570,7 @@ export function applyManagedKimiCodeConfig( readonly oauthKey?: string | undefined; readonly oauthHost?: string | undefined; readonly preserveDefaultModel?: boolean | undefined; + readonly preserveNonManagedDefaultModel?: boolean; }, ): ManagedKimiCodeApplyResult { if (options.models.length === 0) { @@ -585,6 +588,7 @@ export function applyManagedKimiCodeConfig( const existingModels = config.models ?? {}; const selectedDefault = selectDefaultModel(config, options.models, { preserveExisting: options.preserveDefaultModel === true, + preserveNonManaged: options.preserveNonManagedDefaultModel !== false, }); config.providers[KIMI_CODE_PROVIDER_NAME] = { @@ -734,7 +738,10 @@ function forcedThinking( function selectDefaultModel( config: ManagedKimiConfigShape, models: readonly ManagedKimiCodeModelInfo[], - options: { readonly preserveExisting: boolean }, + options: { + readonly preserveExisting: boolean; + readonly preserveNonManaged: boolean; + }, ): SelectedDefaultModel { const firstModel = models[0]; if (firstModel === undefined) { @@ -751,7 +758,12 @@ function selectDefaultModel( if ( options.preserveExisting && currentDefault !== undefined && - canPreserveDefaultModel(existingModels, currentDefault, managedModels) + canPreserveDefaultModel( + existingModels, + currentDefault, + managedModels, + options.preserveNonManaged, + ) ) { const preservedModel = managedModels.get(currentDefault); return { @@ -773,8 +785,10 @@ function canPreserveDefaultModel( existingModels: Record>, defaultModel: string, managedModels: ReadonlyMap, + preserveNonManaged: boolean, ): boolean { if (managedModels.has(defaultModel)) return true; + if (!preserveNonManaged) return false; const existing = existingModels[defaultModel]; return isRecord(existing) && existing['provider'] !== KIMI_CODE_PROVIDER_NAME; } @@ -846,6 +860,7 @@ export async function provisionManagedKimiCodeConfig( oauthKey: options.oauthKey, oauthHost: options.oauthHost, preserveDefaultModel: options.preserveDefaultModel, + preserveNonManagedDefaultModel: options.preserveNonManagedDefaultModel, }); await options.adapter.write(config); return { diff --git a/packages/oauth/src/toolkit.ts b/packages/oauth/src/toolkit.ts index 0229db7b03..afc3eb29af 100644 --- a/packages/oauth/src/toolkit.ts +++ b/packages/oauth/src/toolkit.ts @@ -193,6 +193,9 @@ export class KimiOAuthToolkit { oauthKey, oauthHost, preserveDefaultModel: hadToken, + // Re-login keeps an existing managed model selection, but must not + // leave another provider as default after Kimi Code was selected. + preserveNonManagedDefaultModel: false, fetchImpl: this.fetchImpl, headers: this.identityHeaders(), }); diff --git a/packages/oauth/test/managed-kimi-code.test.ts b/packages/oauth/test/managed-kimi-code.test.ts index 92ffd2f5e8..790c70415c 100644 --- a/packages/oauth/test/managed-kimi-code.test.ts +++ b/packages/oauth/test/managed-kimi-code.test.ts @@ -1023,6 +1023,7 @@ describe('supports_thinking_type', () => { accessToken: 'oauth-access-token', fetchImpl: vi.fn(async () => makeThinkingTypeModelsResponse()) as unknown as typeof fetch, preserveDefaultModel: true, + preserveNonManagedDefaultModel: false, adapter: { read: () => config, write: vi.fn(), diff --git a/packages/oauth/test/toolkit.test.ts b/packages/oauth/test/toolkit.test.ts index 6ceaae4170..84916354f9 100644 --- a/packages/oauth/test/toolkit.test.ts +++ b/packages/oauth/test/toolkit.test.ts @@ -332,6 +332,47 @@ describe('KimiOAuthToolkit', () => { expect(write).toHaveBeenCalledWith(config); }); + it('replaces an existing API default during login', async () => { + const storage = new MemoryTokenStorage(); + const fetchImpl = vi.fn(async () => managedModelsResponse()) as unknown as typeof fetch; + const config: ManagedKimiConfigShape = { + providers: { + 'api-provider': { + type: 'kimi', + apiKey: 'YOUR_API_KEY', + }, + }, + defaultModel: 'api-provider/api-model', + models: { + 'api-provider/api-model': { + provider: 'api-provider', + model: 'api-model', + maxContextSize: 262144, + }, + }, + }; + const toolkit = new KimiOAuthToolkit({ + homeDir: join('/tmp', 'kimi-oauth-toolkit-test'), + identity: TEST_IDENTITY, + storage, + now: () => 100, + fetchImpl, + configAdapter: { + read: () => config, + write: vi.fn(), + apply: applyManagedKimiCodeConfig, + }, + }); + + storage.tokens.set('kimi-code', token('access-1')); + await expect(toolkit.login()).resolves.toMatchObject({ + provision: { + defaultModel: 'kimi-code/kimi-for-coding', + }, + }); + expect(config.defaultModel).toBe('kimi-code/kimi-for-coding'); + }); + it.each([401, 402])( 'force-refreshes a stored token when managed model provisioning rejects cached auth with HTTP %i', async (status) => { From 059cbe2649911c6f3b38aa0f38420c1e7917c03e Mon Sep 17 00:00:00 2001 From: HeXian Date: Fri, 24 Jul 2026 21:50:23 +0800 Subject: [PATCH 2/2] fix(oauth): scope default replacement to Kimi login --- packages/oauth/src/toolkit.ts | 2 +- packages/oauth/test/toolkit.test.ts | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/oauth/src/toolkit.ts b/packages/oauth/src/toolkit.ts index afc3eb29af..360ba216d1 100644 --- a/packages/oauth/src/toolkit.ts +++ b/packages/oauth/src/toolkit.ts @@ -195,7 +195,7 @@ export class KimiOAuthToolkit { preserveDefaultModel: hadToken, // Re-login keeps an existing managed model selection, but must not // leave another provider as default after Kimi Code was selected. - preserveNonManagedDefaultModel: false, + preserveNonManagedDefaultModel: name !== KIMI_CODE_PROVIDER_NAME, fetchImpl: this.fetchImpl, headers: this.identityHeaders(), }); diff --git a/packages/oauth/test/toolkit.test.ts b/packages/oauth/test/toolkit.test.ts index 84916354f9..2399026705 100644 --- a/packages/oauth/test/toolkit.test.ts +++ b/packages/oauth/test/toolkit.test.ts @@ -332,7 +332,18 @@ describe('KimiOAuthToolkit', () => { expect(write).toHaveBeenCalledWith(config); }); - it('replaces an existing API default during login', async () => { + it.each([ + { + description: 'replaces an existing API default during Kimi Code login', + providerName: undefined, + expectedDefaultModel: 'kimi-code/kimi-for-coding', + }, + { + description: 'preserves an existing API default during another provider login', + providerName: 'custom-provider', + expectedDefaultModel: 'api-provider/api-model', + }, + ])('$description', async ({ providerName, expectedDefaultModel }) => { const storage = new MemoryTokenStorage(); const fetchImpl = vi.fn(async () => managedModelsResponse()) as unknown as typeof fetch; const config: ManagedKimiConfigShape = { @@ -365,12 +376,12 @@ describe('KimiOAuthToolkit', () => { }); storage.tokens.set('kimi-code', token('access-1')); - await expect(toolkit.login()).resolves.toMatchObject({ + await expect(toolkit.login(providerName)).resolves.toMatchObject({ provision: { - defaultModel: 'kimi-code/kimi-for-coding', + defaultModel: expectedDefaultModel, }, }); - expect(config.defaultModel).toBe('kimi-code/kimi-for-coding'); + expect(config.defaultModel).toBe(expectedDefaultModel); }); it.each([401, 402])(