From be2eb046b79f0098be74ccdec4d1906a40bc7360 Mon Sep 17 00:00:00 2001 From: "haozhe.yang" Date: Wed, 10 Jun 2026 12:16:36 +0800 Subject: [PATCH] refactor(services,protocol): remove node-sdk dependency - Replace `@moonshot-ai/kimi-code-sdk` with `@moonshot-ai/agent-core` in protocol\n- Remove `@moonshot-ai/kimi-code-sdk` from services dependencies\n- Introduce internal `managedAuth` facade in services to replace `KimiAuthFacade`\n- Add compile-time assertions that neither package references the node SDK --- .changeset/quiet-sdk-boundary.md | 7 + packages/services/package.json | 1 - packages/services/src/auth/managedAuth.ts | 178 ++++++++++++++++++ .../services/src/authSummary/authSummary.ts | 8 +- .../src/authSummary/authSummaryService.ts | 9 +- .../services/src/coreProcess/coreProcess.ts | 3 +- .../src/coreProcess/coreProcessService.ts | 6 +- .../src/modelCatalog/modelCatalogService.ts | 9 +- packages/services/src/oauth/oauth.ts | 14 +- packages/services/src/oauth/oauthService.ts | 13 +- packages/services/test/interfaces.test.ts | 37 ++++ packages/services/test/oauth-service.test.ts | 8 +- packages/services/tsdown.config.ts | 3 - packages/services/vitest.config.ts | 6 - pnpm-lock.yaml | 3 - 15 files changed, 245 insertions(+), 60 deletions(-) create mode 100644 .changeset/quiet-sdk-boundary.md create mode 100644 packages/services/src/auth/managedAuth.ts diff --git a/.changeset/quiet-sdk-boundary.md b/.changeset/quiet-sdk-boundary.md new file mode 100644 index 000000000..b782688f1 --- /dev/null +++ b/.changeset/quiet-sdk-boundary.md @@ -0,0 +1,7 @@ +--- +"@moonshot-ai/services": patch +"@moonshot-ai/protocol": patch +"@moonshot-ai/kimi-code": patch +--- + +Remove daemon service and protocol package dependencies on the node SDK. diff --git a/packages/services/package.json b/packages/services/package.json index 84b34fa69..440063306 100644 --- a/packages/services/package.json +++ b/packages/services/package.json @@ -35,7 +35,6 @@ "dependencies": { "@moonshot-ai/agent-core": "workspace:^", "@moonshot-ai/kimi-code-oauth": "workspace:^", - "@moonshot-ai/kimi-code-sdk": "workspace:^", "@moonshot-ai/protocol": "workspace:^", "chokidar": "^4.0.3", "ignore": "^5.3.2", diff --git a/packages/services/src/auth/managedAuth.ts b/packages/services/src/auth/managedAuth.ts new file mode 100644 index 000000000..f8bd8e58e --- /dev/null +++ b/packages/services/src/auth/managedAuth.ts @@ -0,0 +1,178 @@ +import { + readConfigFile, + writeConfigFile, + type KimiConfig, + type OAuthRef, + type OAuthTokenProviderResolver, +} from '@moonshot-ai/agent-core'; +import { + applyManagedKimiCodeConfig, + applyManagedKimiCodeLogoutConfig, + KIMI_CODE_PROVIDER_NAME, + KimiOAuthToolkit, + resolveKimiCodeLoginAuth, + resolveKimiCodeRuntimeAuth, + type BearerTokenProvider, + type KimiOAuthLoginOptions, + type ManagedKimiConfigShape, +} from '@moonshot-ai/kimi-code-oauth'; + +import type { IEnvironmentService } from '../environment/environment'; + +type ServicesManagedConfig = KimiConfig & ManagedKimiConfigShape; + +type ServicesAuthLoginOptions = Omit; + +interface ServicesAuthLoginResult { + readonly providerName: string; + readonly ok: true; + readonly defaultModel: string; + readonly defaultThinking: boolean; + readonly configPath?: string | undefined; +} + +interface ServicesAuthLogoutResult { + readonly providerName: string; + readonly ok: true; +} + +export interface ServicesAuthFacade { + login( + providerName?: string | undefined, + options?: ServicesAuthLoginOptions, + ): Promise; + logout(providerName?: string | undefined): Promise; + getCachedAccessToken( + providerName?: string, + oauthRef?: OAuthRef | undefined, + ): Promise; + readonly resolveOAuthTokenProvider: OAuthTokenProviderResolver; +} + +class ServicesManagedAuthFacade implements ServicesAuthFacade { + private readonly toolkit: KimiOAuthToolkit; + + constructor( + private readonly options: Pick, + ) { + this.toolkit = new KimiOAuthToolkit({ + homeDir: options.homeDir, + configAdapter: { + configPath: options.configPath, + read: () => readConfigFile(options.configPath) as ServicesManagedConfig, + write: async (config) => { + await writeConfigFile(options.configPath, config); + }, + apply: applyManagedKimiCodeConfig, + remove: applyManagedKimiCodeLogoutConfig, + }, + }); + } + + async login( + providerName: string | undefined = KIMI_CODE_PROVIDER_NAME, + options: ServicesAuthLoginOptions = {}, + ): Promise { + const auth = this.resolveManagedAuth(providerName); + const loginAuth = resolveKimiCodeLoginAuth({ + configuredBaseUrl: auth.baseUrl, + configuredOAuthRef: auth.oauthRef, + requestedBaseUrl: options.baseUrl, + requestedOAuthHost: options.oauthHost, + }); + const result = await this.toolkit.login(providerName, { + ...options, + baseUrl: loginAuth.baseUrl, + oauthHost: loginAuth.oauthHost, + oauthRef: options.oauthRef ?? loginAuth.oauthRef, + provisionConfig: true, + }); + if (result.provision === undefined) { + throw new Error('Kimi auth login did not provision model config.'); + } + return { + providerName: result.providerName, + ok: true, + defaultModel: result.provision.defaultModel, + defaultThinking: result.provision.defaultThinking, + configPath: result.provision.configPath, + }; + } + + async logout( + providerName?: string | undefined, + ): Promise { + const result = await this.toolkit.logout( + providerName, + this.resolveRuntimeManagedAuth(providerName).oauthRef, + ); + return { + providerName: result.providerName, + ok: result.ok, + }; + } + + async getCachedAccessToken( + providerName?: string, + oauthRef?: OAuthRef | undefined, + ): Promise { + return this.toolkit.getCachedAccessToken( + providerName, + this.runtimeOAuthRef(providerName, oauthRef), + ); + } + + readonly resolveOAuthTokenProvider = ( + providerName: string, + oauthRef?: OAuthRef | undefined, + ): BearerTokenProvider => { + return this.toolkit.tokenProvider( + providerName, + this.runtimeOAuthRef(providerName, oauthRef), + ); + }; + + private resolveManagedAuth(providerName?: string | undefined): { + readonly oauthRef?: OAuthRef | undefined; + readonly baseUrl?: string | undefined; + } { + const name = providerName ?? KIMI_CODE_PROVIDER_NAME; + const config = readConfigFile(this.options.configPath); + const provider = config.providers[name]; + return { + oauthRef: provider?.oauth, + baseUrl: provider?.baseUrl, + }; + } + + private resolveRuntimeManagedAuth(providerName?: string | undefined): { + readonly oauthRef: OAuthRef; + readonly baseUrl?: string | undefined; + } { + const auth = this.resolveManagedAuth(providerName); + return resolveKimiCodeRuntimeAuth({ + configuredBaseUrl: auth.baseUrl, + configuredOAuthRef: auth.oauthRef, + }); + } + + private runtimeOAuthRef( + providerName: string | undefined, + oauthRef?: OAuthRef | undefined, + ): OAuthRef | undefined { + if ((providerName ?? KIMI_CODE_PROVIDER_NAME) !== KIMI_CODE_PROVIDER_NAME) { + return oauthRef; + } + const auth = this.resolveManagedAuth(providerName); + return resolveKimiCodeRuntimeAuth({ + configuredBaseUrl: auth.baseUrl, + configuredOAuthRef: oauthRef ?? auth.oauthRef, + }).oauthRef; + } +} + +export function createManagedAuthFacade( + env: Pick, +): ServicesAuthFacade { + return new ServicesManagedAuthFacade(env); +} diff --git a/packages/services/src/authSummary/authSummary.ts b/packages/services/src/authSummary/authSummary.ts index 21ba02353..888921195 100644 --- a/packages/services/src/authSummary/authSummary.ts +++ b/packages/services/src/authSummary/authSummary.ts @@ -22,14 +22,12 @@ * * **Implementation** (`AuthSummaryService`): Reads the live config via * `ICoreProcessService.rpc.getKimiConfig({})` and the managed-OAuth credential - * state via `KimiAuthFacade.status(...)`. Both are cheap (in-process RPC + + * state via a cached-token lookup. Both are cheap (in-process RPC + * a token-file existence probe), so we run them on every call instead of * caching — keeps the staleness window at zero. */ -import { createDecorator, Disposable } from '@moonshot-ai/agent-core'; -import type { KimiConfig } from '@moonshot-ai/agent-core'; -import { KimiAuthFacade } from '@moonshot-ai/kimi-code-sdk'; +import { createDecorator } from '@moonshot-ai/agent-core'; import type { AuthSummary } from '@moonshot-ai/protocol'; export interface IAuthSummaryService { @@ -111,5 +109,3 @@ export class AuthModelNotResolvedError extends Error { this.providerId = providerId; } } - - diff --git a/packages/services/src/authSummary/authSummaryService.ts b/packages/services/src/authSummary/authSummaryService.ts index d2890d6f9..0f205cd03 100644 --- a/packages/services/src/authSummary/authSummaryService.ts +++ b/packages/services/src/authSummary/authSummaryService.ts @@ -4,8 +4,8 @@ import { Disposable, InstantiationType, registerSingleton } from '@moonshot-ai/agent-core'; import type { KimiConfig } from '@moonshot-ai/agent-core'; -import { KimiAuthFacade } from '@moonshot-ai/kimi-code-sdk'; import type { AuthSummary } from '@moonshot-ai/protocol'; +import { createManagedAuthFacade, type ServicesAuthFacade } from '../auth/managedAuth'; import { IEnvironmentService } from '../environment/environment'; import { ICoreProcessService } from '../coreProcess/coreProcess'; import { @@ -23,17 +23,14 @@ export class AuthSummaryService implements IAuthSummaryService { readonly _serviceBrand: undefined; - private readonly _authFacade: KimiAuthFacade; + private readonly _authFacade: ServicesAuthFacade; constructor( @IEnvironmentService private readonly env: IEnvironmentService, @ICoreProcessService private readonly core: ICoreProcessService, ) { super(); - this._authFacade = new KimiAuthFacade({ - homeDir: env.homeDir, - configPath: env.configPath, - }); + this._authFacade = createManagedAuthFacade(env); } async get(): Promise { diff --git a/packages/services/src/coreProcess/coreProcess.ts b/packages/services/src/coreProcess/coreProcess.ts index 7a858e92b..f5fcef2f2 100644 --- a/packages/services/src/coreProcess/coreProcess.ts +++ b/packages/services/src/coreProcess/coreProcess.ts @@ -13,8 +13,7 @@ * `PromptService`, …) dispatch on through the proxy below. * * The result is wrapped in a small `SDKRpcClient`-shaped proxy so that - * service impls get the same ergonomics as `@moonshot-ai/kimi-code-sdk` - * (`SDKRpcClientBase` subclass). The proxy is exposed as `rpc` for in-package + * service impls get SDK-style RPC ergonomics. The proxy is exposed as `rpc` for in-package * consumers; the public package barrel does NOT re-export `SDKRpcClientBase`, * so daemon-side code stays one abstraction layer away. * diff --git a/packages/services/src/coreProcess/coreProcessService.ts b/packages/services/src/coreProcess/coreProcessService.ts index 27ed2fb2b..47bb7f3e0 100644 --- a/packages/services/src/coreProcess/coreProcessService.ts +++ b/packages/services/src/coreProcess/coreProcessService.ts @@ -17,8 +17,8 @@ import { createKimiDefaultHeaders, type KimiHostIdentity, } from '@moonshot-ai/kimi-code-oauth'; -import { KimiAuthFacade } from '@moonshot-ai/kimi-code-sdk'; +import { createManagedAuthFacade } from '../auth/managedAuth'; import { BridgeClientAPI } from './coreProcessClient'; import { IApprovalService } from '../approval/approval'; import { IEnvironmentService } from '../environment/environment'; @@ -78,7 +78,7 @@ export class CoreProcessService extends Disposable implements ICoreProcessServic // is a different code path (file existence on the credentials store) so // it stays green; the failure only surfaces inside the prompt turn, as // an `auth.login_required` error after `turn.step.started`. We bridge - // the gap by default-constructing a `KimiAuthFacade` against the same + // the gap by default-constructing a managed auth facade against the same // home + config paths KimiCore will use, and handing its // `resolveOAuthTokenProvider` into the core. Callers (e.g. node-sdk // tests) can still override via `options.resolveOAuthTokenProvider`. @@ -197,7 +197,7 @@ export class CoreProcessService extends Disposable implements ICoreProcessServic homeDir: string, configPath: string, ): OAuthTokenProviderResolver { - const facade = new KimiAuthFacade({ homeDir, configPath }); + const facade = createManagedAuthFacade({ homeDir, configPath }); return facade.resolveOAuthTokenProvider; } diff --git a/packages/services/src/modelCatalog/modelCatalogService.ts b/packages/services/src/modelCatalog/modelCatalogService.ts index fb5e085f0..938fbb814 100644 --- a/packages/services/src/modelCatalog/modelCatalogService.ts +++ b/packages/services/src/modelCatalog/modelCatalogService.ts @@ -4,13 +4,13 @@ import { registerSingleton, } from '@moonshot-ai/agent-core'; import type { KimiConfig, ProviderConfig } from '@moonshot-ai/agent-core'; -import { KimiAuthFacade } from '@moonshot-ai/kimi-code-sdk'; import type { ModelCatalogItem, ProviderCatalogItem, SetDefaultModelResponse, } from '@moonshot-ai/protocol'; +import { createManagedAuthFacade, type ServicesAuthFacade } from '../auth/managedAuth'; import { ICoreProcessService } from '../coreProcess/coreProcess'; import { IEnvironmentService } from '../environment/environment'; import { @@ -26,17 +26,14 @@ export class ModelCatalogService implements IModelCatalogService { readonly _serviceBrand: undefined; - private readonly _authFacade: KimiAuthFacade; + private readonly _authFacade: ServicesAuthFacade; constructor( @IEnvironmentService env: IEnvironmentService, @ICoreProcessService private readonly core: ICoreProcessService, ) { super(); - this._authFacade = new KimiAuthFacade({ - homeDir: env.homeDir, - configPath: env.configPath, - }); + this._authFacade = createManagedAuthFacade(env); } async listModels(): Promise { diff --git a/packages/services/src/oauth/oauth.ts b/packages/services/src/oauth/oauth.ts index bfb46dcd0..f62831fcd 100644 --- a/packages/services/src/oauth/oauth.ts +++ b/packages/services/src/oauth/oauth.ts @@ -29,7 +29,7 @@ * │ * ▼ * startLogin() ──┐ - * │ KimiAuthFacade.login() runs in BACKGROUND + * │ managed auth facade login runs in BACKGROUND * ▼ │ * ┌─ onDeviceCode(auth) ◄────────────────────┘ (fires once) * │ │ @@ -62,14 +62,7 @@ * `DeviceCodeTimeoutError`. */ -import { createDecorator, Disposable } from '@moonshot-ai/agent-core'; -import { - DeviceCodeTimeoutError, - KIMI_CODE_PROVIDER_NAME, - OAuthError, - type DeviceAuthorization, -} from '@moonshot-ai/kimi-code-oauth'; -import { KimiAuthFacade } from '@moonshot-ai/kimi-code-sdk'; +import { createDecorator } from '@moonshot-ai/agent-core'; import type { OAuthFlowSnapshot, OAuthFlowStart, @@ -77,7 +70,6 @@ import type { OAuthLoginCancelResponse, OAuthLogoutResponse, } from '@moonshot-ai/protocol'; -import { ulid } from 'ulid'; export interface IOAuthService { readonly _serviceBrand: undefined; @@ -114,5 +106,3 @@ export interface IOAuthService { // eslint-disable-next-line @typescript-eslint/no-redeclare export const IOAuthService = createDecorator('oauthService'); - - diff --git a/packages/services/src/oauth/oauthService.ts b/packages/services/src/oauth/oauthService.ts index 7da0b4133..f55fcc9e9 100644 --- a/packages/services/src/oauth/oauthService.ts +++ b/packages/services/src/oauth/oauthService.ts @@ -9,7 +9,6 @@ import { OAuthError, type DeviceAuthorization, } from '@moonshot-ai/kimi-code-oauth'; -import { KimiAuthFacade } from '@moonshot-ai/kimi-code-sdk'; import type { OAuthFlowSnapshot, OAuthFlowStart, @@ -19,6 +18,7 @@ import type { } from '@moonshot-ai/protocol'; import { ulid } from 'ulid'; +import { createManagedAuthFacade, type ServicesAuthFacade } from '../auth/managedAuth'; import { IEnvironmentService } from '../environment/environment'; import { IOAuthService } from './oauth'; @@ -76,20 +76,17 @@ const TERMINAL_RETENTION_MS = 5 * 60 * 1000; export class OAuthService extends Disposable implements IOAuthService { readonly _serviceBrand: undefined; - private readonly _authFacade: KimiAuthFacade; + private readonly _authFacade: ServicesAuthFacade; private readonly _flows: DisposableMap; constructor(@IEnvironmentService private readonly env: IEnvironmentService) { super(); this._flows = this._register(new DisposableMap()); - this._authFacade = new KimiAuthFacade({ - homeDir: env.homeDir, - configPath: env.configPath, - }); + this._authFacade = createManagedAuthFacade(env); } /** @internal Test-only factory that injects a mock facade. */ - static _createForTest(env: IEnvironmentService, facade: KimiAuthFacade): OAuthService { + static _createForTest(env: IEnvironmentService, facade: ServicesAuthFacade): OAuthService { const svc = new (OAuthService as any)(env) as OAuthService; (svc as any)._authFacade = facade; return svc; @@ -108,7 +105,7 @@ export class OAuthService extends Disposable implements IOAuthService { const flowId = `oauth_${ulid()}`; const controller = new AbortController(); - // Capture the device authorization via a deferred. `KimiAuthFacade.login` + // Capture the device authorization via a deferred. The managed auth facade // calls `onDeviceCode` exactly once, then starts polling. We resolve the // deferred from inside the callback so this method can return as soon as // the URLs are known — well before the polling completes. diff --git a/packages/services/test/interfaces.test.ts b/packages/services/test/interfaces.test.ts index f34ca0218..af1ae3fa7 100644 --- a/packages/services/test/interfaces.test.ts +++ b/packages/services/test/interfaces.test.ts @@ -1,3 +1,7 @@ +import { readFileSync, readdirSync, statSync } from 'node:fs'; +import { join, relative } from 'node:path'; +import { fileURLToPath } from 'node:url'; + import { describe, expect, it, vi } from 'vitest'; import { @@ -31,6 +35,35 @@ import { type QuestionResult, } from '../src'; +const packageRoot = fileURLToPath(new URL('..', import.meta.url)); +const sdkPackageName = ['@moonshot-ai', 'kimi-code-sdk'].join('/'); + +function readPackageFiles(): string { + const files = [ + 'package.json', + 'tsdown.config.ts', + 'vitest.config.ts', + ...sourceFiles(join(packageRoot, 'src')), + ]; + return files + .map((file) => readFileSync(join(packageRoot, file), 'utf8')) + .join('\n'); +} + +function sourceFiles(dir: string): string[] { + const files: string[] = []; + for (const entry of readdirSync(dir)) { + const full = join(dir, entry); + const stat = statSync(full); + if (stat.isDirectory()) { + files.push(...sourceFiles(full)); + } else if (entry.endsWith('.ts')) { + files.push(relative(packageRoot, full)); + } + } + return files; +} + class FakeEventService implements IEventService { readonly _serviceBrand: undefined; @@ -113,6 +146,10 @@ function makeFakeQuestion(): QuestionRequest & { sessionId: string; agentId: str } describe('@moonshot-ai/services · interfaces', () => { + it('does not depend on the node SDK package', () => { + expect(readPackageFiles()).not.toContain(sdkPackageName); + }); + it('registers all three peer services in a test instantiation service', () => { const events = new FakeEventService(); const approvals = new FakeApprovalService(); diff --git a/packages/services/test/oauth-service.test.ts b/packages/services/test/oauth-service.test.ts index 783a54985..1cf374846 100644 --- a/packages/services/test/oauth-service.test.ts +++ b/packages/services/test/oauth-service.test.ts @@ -1,7 +1,7 @@ /** * `OAuthService` (P2.7) unit tests. * - * Hermetic: a mock `KimiAuthFacade` is injected so we don't need a real + * Hermetic: a mock managed auth facade is injected so we don't need a real * OAuth host on the network. The mock's `login()` exposes a deferred device * authorization + completion promise so tests can drive each transition * independently: @@ -32,8 +32,8 @@ import { OAuthError, type DeviceAuthorization, } from '@moonshot-ai/kimi-code-oauth'; -import type { KimiAuthFacade } from '@moonshot-ai/kimi-code-sdk'; +import type { ServicesAuthFacade } from '../src/auth/managedAuth'; import { IEnvironmentService } from '../src/environment/environment'; import { OAuthService } from '../src/oauth/oauthService'; @@ -47,7 +47,7 @@ interface LoginCall { } interface MockFacade { - facade: KimiAuthFacade; + facade: ServicesAuthFacade; loginCalls: LoginCall[]; logoutCalls: Array<{ providerName: string | undefined }>; } @@ -81,7 +81,7 @@ function makeMockFacade(): MockFacade { logoutCalls.push({ providerName }); return { providerName: providerName ?? 'managed:kimi-code', ok: true as const }; }), - } as unknown as KimiAuthFacade; + } as unknown as ServicesAuthFacade; return { facade, loginCalls, logoutCalls }; } diff --git a/packages/services/tsdown.config.ts b/packages/services/tsdown.config.ts index 69a6b1ea5..e41cd7eb3 100644 --- a/packages/services/tsdown.config.ts +++ b/packages/services/tsdown.config.ts @@ -12,9 +12,6 @@ export default defineConfig({ clean: true, plugins: [rawTextPlugin()], alias: { - '@moonshot-ai/kimi-code-sdk': fileURLToPath( - new URL('../node-sdk/src/index.ts', import.meta.url), - ), '@moonshot-ai/agent-core': fileURLToPath( new URL('../agent-core/src/index.ts', import.meta.url), ), diff --git a/packages/services/vitest.config.ts b/packages/services/vitest.config.ts index 31d5dce74..5958985cb 100644 --- a/packages/services/vitest.config.ts +++ b/packages/services/vitest.config.ts @@ -26,12 +26,6 @@ export default defineConfig({ new URL('../agent-core/src/di/test.ts', import.meta.url), ), }, - { - find: '@moonshot-ai/kimi-code-sdk', - replacement: fileURLToPath( - new URL('../node-sdk/src/index.ts', import.meta.url), - ), - }, { find: '@moonshot-ai/agent-core', replacement: fileURLToPath( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7cb413198..ce091291c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -565,9 +565,6 @@ importers: '@moonshot-ai/kimi-code-oauth': specifier: workspace:^ version: link:../oauth - '@moonshot-ai/kimi-code-sdk': - specifier: workspace:^ - version: link:../node-sdk '@moonshot-ai/protocol': specifier: workspace:^ version: link:../protocol