mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-08-19 13:45:28 +00:00
Rename bare `onDidChange` to semantic `onDidChange<Facet>` following the
VSCode event-naming convention (the event name carries what changed), and
carry a payload so consumers know what changed without re-reading:
- IConfigService.onDidChangeConfiguration
- IProviderService.onDidChangeProviders ({ added, removed, changed })
- IModelService.onDidChangeModels ({ added, removed, changed })
- ISessionMetadata.onDidChangeMetadata ({ changed: (keyof SessionMeta)[] })
- ISessionInteractionService.onDidChangePending ({ pending: string[] })
Add ISessionLifecycleService session-lifecycle events (parity with v1):
onDidCreateSession / onDidCloseSession / onDidArchiveSession /
onDidForkSession, fired from create / close / archive / fork.
Add IEventService.onDidPublish: Event<DomainEvent> so the bus matches the
typed Event<T> convention used elsewhere (publish/subscribe retained).
Update tests, examples, and docs to the new names; add payload and
lifecycle-event assertions.
109 lines
4 KiB
TypeScript
109 lines
4 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import type { IOAuthService } from '#/app/auth';
|
|
import type { IConfigService } from '#/app/config';
|
|
import { ErrorCodes, KimiError } from '#/errors';
|
|
import { SessionModelResolver } from '#/session/modelRuntime';
|
|
|
|
function stubConfig(sections: Record<string, unknown>): IConfigService {
|
|
return {
|
|
_serviceBrand: undefined,
|
|
ready: Promise.resolve(),
|
|
onDidChangeConfiguration: () => ({ dispose: () => {} }),
|
|
get: <T>(domain: string) => sections[domain] as T,
|
|
inspect: () => ({ value: undefined, defaultValue: undefined, userValue: undefined, memoryValue: undefined }),
|
|
getAll: () => ({ ...sections }),
|
|
set: () => Promise.resolve(),
|
|
replace: () => Promise.resolve(),
|
|
reload: () => Promise.resolve(),
|
|
diagnostics: () => [],
|
|
} as unknown as IConfigService;
|
|
}
|
|
|
|
function stubOAuth(): IOAuthService {
|
|
return {
|
|
_serviceBrand: undefined,
|
|
startLogin: () => Promise.reject(new Error('not implemented')),
|
|
getFlow: () => undefined,
|
|
cancelLogin: () => Promise.reject(new Error('not implemented')),
|
|
logout: () => Promise.reject(new Error('not implemented')),
|
|
status: () => Promise.resolve({ loggedIn: false }),
|
|
refreshOAuthProviderModels: () => Promise.reject(new Error('not implemented')),
|
|
resolveTokenProvider: () => undefined,
|
|
getCachedAccessToken: () => Promise.resolve(undefined),
|
|
};
|
|
}
|
|
|
|
function baseSections(overrides: Record<string, unknown> = {}): Record<string, unknown> {
|
|
return {
|
|
defaultProvider: 'kimi',
|
|
defaultModel: 'k1',
|
|
providers: {
|
|
kimi: { type: 'kimi', apiKey: 'sk-test' },
|
|
},
|
|
models: {
|
|
k1: { provider: 'kimi', model: 'kimi-model', maxContextSize: 128000 },
|
|
},
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('SessionModelResolver', () => {
|
|
it('resolves a configured model to its provider', () => {
|
|
const resolver = new SessionModelResolver(stubConfig(baseSections()), stubOAuth());
|
|
const resolved = resolver.resolve('k1');
|
|
expect(resolved.providerName).toBe('kimi');
|
|
expect(resolved.provider).toMatchObject({ model: 'kimi-model' });
|
|
expect(resolved.modelCapabilities).toBeDefined();
|
|
});
|
|
|
|
it('exposes defaultModel from config', () => {
|
|
const resolver = new SessionModelResolver(stubConfig(baseSections()), stubOAuth());
|
|
expect(resolver.defaultModel).toBe('k1');
|
|
});
|
|
|
|
it('reads providers and models from the config service', () => {
|
|
const config = stubConfig(baseSections());
|
|
const resolver = new SessionModelResolver(config, stubOAuth());
|
|
expect(resolver.defaultModel).toBe('k1');
|
|
expect(resolver.resolve('k1').providerName).toBe('kimi');
|
|
});
|
|
|
|
it('throws CONFIG_INVALID for an unknown model', () => {
|
|
const resolver = new SessionModelResolver(stubConfig(baseSections()), stubOAuth());
|
|
expect(() => resolver.resolve('does-not-exist')).toThrowError(
|
|
expect.objectContaining({ code: ErrorCodes.CONFIG_INVALID } as Partial<KimiError>),
|
|
);
|
|
});
|
|
|
|
it('throws CONFIG_INVALID when model has no provider and no defaultProvider', () => {
|
|
const config = stubConfig(
|
|
baseSections({
|
|
defaultProvider: undefined,
|
|
models: {
|
|
k1: { provider: 'kimi', model: 'kimi-model', maxContextSize: 128000 },
|
|
orphan: { model: 'm', maxContextSize: 1000 },
|
|
},
|
|
}),
|
|
);
|
|
const resolver = new SessionModelResolver(config, stubOAuth());
|
|
expect(() => resolver.resolve('orphan')).toThrowError(
|
|
expect.objectContaining({ code: ErrorCodes.CONFIG_INVALID } as Partial<KimiError>),
|
|
);
|
|
});
|
|
|
|
it('throws CONFIG_INVALID when provider is not configured', () => {
|
|
const config = stubConfig(
|
|
baseSections({
|
|
models: {
|
|
k1: { provider: 'kimi', model: 'kimi-model', maxContextSize: 128000 },
|
|
ghost: { provider: 'missing', model: 'm', maxContextSize: 1000 },
|
|
},
|
|
}),
|
|
);
|
|
const resolver = new SessionModelResolver(config, stubOAuth());
|
|
expect(() => resolver.resolve('ghost')).toThrowError(
|
|
expect.objectContaining({ code: ErrorCodes.CONFIG_INVALID } as Partial<KimiError>),
|
|
);
|
|
});
|
|
});
|