diff --git a/.changeset/strict-config-flag.md b/.changeset/strict-config-flag.md new file mode 100644 index 000000000..8a9b7acde --- /dev/null +++ b/.changeset/strict-config-flag.md @@ -0,0 +1,7 @@ +--- +"@moonshot-ai/agent-core": minor +"@moonshot-ai/kimi-code-sdk": minor +"@moonshot-ai/kimi-code": minor +--- + +Add a `--strict-config` CLI flag that rejects unknown keys in `config.toml`. diff --git a/apps/kimi-code/src/cli/commands.ts b/apps/kimi-code/src/cli/commands.ts index faf1e1da8..6c1865f6e 100644 --- a/apps/kimi-code/src/cli/commands.ts +++ b/apps/kimi-code/src/cli/commands.ts @@ -73,7 +73,8 @@ export function createProgram( ) .addOption(new Option('--yes').hideHelp().default(false)) .addOption(new Option('--auto-approve').hideHelp().default(false)) - .option('--plan', 'Start in plan mode.', false); + .option('--plan', 'Start in plan mode.', false) + .option('--strict-config', 'Reject unknown keys in config.toml.', false); registerExportCommand(program); registerProviderCommand(program); @@ -119,6 +120,7 @@ export function createProgram( outputFormat: raw['outputFormat'] as CLIOptions['outputFormat'], prompt: raw['prompt'] as string | undefined, skillsDirs: raw['skillsDir'] as string[], + strictConfig: raw['strictConfig'] as boolean, }; onMain(opts); diff --git a/apps/kimi-code/src/cli/options.ts b/apps/kimi-code/src/cli/options.ts index 98f4cb196..24eef82b0 100644 --- a/apps/kimi-code/src/cli/options.ts +++ b/apps/kimi-code/src/cli/options.ts @@ -11,6 +11,7 @@ export interface CLIOptions { outputFormat: PromptOutputFormat | undefined; prompt: string | undefined; skillsDirs: string[]; + strictConfig: boolean; } export interface ValidatedOptions { diff --git a/apps/kimi-code/src/cli/run-prompt.ts b/apps/kimi-code/src/cli/run-prompt.ts index f7cef067d..0958e17c1 100644 --- a/apps/kimi-code/src/cli/run-prompt.ts +++ b/apps/kimi-code/src/cli/run-prompt.ts @@ -76,6 +76,7 @@ export async function runPrompt( uiMode: PROMPT_UI_MODE, skillDirs: opts.skillsDirs, telemetry: telemetryClient, + strictConfig: opts.strictConfig, onOAuthRefresh: (outcome) => { if (outcome.success) { track('oauth_refresh', { success: true }); diff --git a/apps/kimi-code/src/cli/run-shell.ts b/apps/kimi-code/src/cli/run-shell.ts index e5bdfef24..6715a0a51 100644 --- a/apps/kimi-code/src/cli/run-shell.ts +++ b/apps/kimi-code/src/cli/run-shell.ts @@ -61,6 +61,7 @@ export async function runShell( homeDir: telemetryBootstrap.homeDir, identity: createKimiCodeHostIdentity(version), telemetry: telemetryClient, + strictConfig: opts.strictConfig, onOAuthRefresh: (outcome) => { if (outcome.success) { track('oauth_refresh', { success: true }); diff --git a/apps/kimi-code/src/main.ts b/apps/kimi-code/src/main.ts index e94472590..c45a18407 100644 --- a/apps/kimi-code/src/main.ts +++ b/apps/kimi-code/src/main.ts @@ -109,6 +109,7 @@ const MIGRATE_CLI_OPTIONS: CLIOptions = { yolo: false, auto: false, plan: false, + strictConfig: false, model: undefined, outputFormat: undefined, prompt: undefined, diff --git a/apps/kimi-code/test/cli/goal-prompt.test.ts b/apps/kimi-code/test/cli/goal-prompt.test.ts index 04780bd26..22a2b2892 100644 --- a/apps/kimi-code/test/cli/goal-prompt.test.ts +++ b/apps/kimi-code/test/cli/goal-prompt.test.ts @@ -143,6 +143,7 @@ function opts(overrides: Partial[0]> = {}) { yolo: false, auto: false, plan: false, + strictConfig: false, model: undefined, outputFormat: undefined, prompt: '/goal Ship feature X', diff --git a/apps/kimi-code/test/cli/main.test.ts b/apps/kimi-code/test/cli/main.test.ts index 52aba94b1..20ee825f0 100644 --- a/apps/kimi-code/test/cli/main.test.ts +++ b/apps/kimi-code/test/cli/main.test.ts @@ -140,6 +140,7 @@ function defaultOpts(): CLIOptions { yolo: false, auto: false, plan: false, + strictConfig: false, model: undefined, outputFormat: undefined, prompt: undefined, diff --git a/apps/kimi-code/test/cli/options.test.ts b/apps/kimi-code/test/cli/options.test.ts index e14629e01..724c6734a 100644 --- a/apps/kimi-code/test/cli/options.test.ts +++ b/apps/kimi-code/test/cli/options.test.ts @@ -167,6 +167,16 @@ describe('CLI options parsing', () => { }); }); + describe('--strict-config', () => { + it('defaults to false', () => { + expect(parse([]).strictConfig).toBe(false); + }); + + it('sets strict config flag', () => { + expect(parse(['--strict-config']).strictConfig).toBe(true); + }); + }); + describe('--auto / --yolo / --plan with --session / --continue', () => { it('allows --auto with --continue', () => { const opts = parse(['--auto', '--continue']); diff --git a/apps/kimi-code/test/cli/run-prompt.test.ts b/apps/kimi-code/test/cli/run-prompt.test.ts index a3620aa35..a3a13155c 100644 --- a/apps/kimi-code/test/cli/run-prompt.test.ts +++ b/apps/kimi-code/test/cli/run-prompt.test.ts @@ -131,6 +131,7 @@ function opts(overrides: Partial[0]> = {}) { yolo: false, auto: false, plan: false, + strictConfig: false, model: undefined, outputFormat: undefined, prompt: 'say hello', diff --git a/apps/kimi-code/test/cli/run-shell.test.ts b/apps/kimi-code/test/cli/run-shell.test.ts index bab4fb152..0c2c56fb1 100644 --- a/apps/kimi-code/test/cli/run-shell.test.ts +++ b/apps/kimi-code/test/cli/run-shell.test.ts @@ -177,6 +177,7 @@ describe('runShell', () => { yolo: true, auto: false, plan: true, + strictConfig: false, model: undefined, outputFormat: undefined, prompt: undefined, @@ -265,6 +266,7 @@ describe('runShell', () => { yolo: false, auto: false, plan: false, + strictConfig: false, model: undefined, outputFormat: undefined, prompt: undefined, @@ -305,6 +307,7 @@ describe('runShell', () => { yolo: false, auto: false, plan: false, + strictConfig: false, model: undefined, outputFormat: undefined, prompt: undefined, @@ -343,6 +346,7 @@ describe('runShell', () => { yolo: false, auto: false, plan: false, + strictConfig: false, model: undefined, outputFormat: undefined, prompt: undefined, @@ -381,6 +385,7 @@ describe('runShell', () => { yolo: false, auto: false, plan: false, + strictConfig: false, model: undefined, outputFormat: undefined, prompt: undefined, @@ -414,6 +419,7 @@ describe('runShell', () => { yolo: false, auto: false, plan: false, + strictConfig: false, model: undefined, outputFormat: undefined, prompt: undefined, @@ -465,6 +471,7 @@ describe('runShell', () => { yolo: false, auto: false, plan: false, + strictConfig: false, model: undefined, outputFormat: undefined, prompt: undefined, @@ -503,6 +510,7 @@ describe('runShell', () => { yolo: false, auto: false, plan: false, + strictConfig: false, model: undefined, outputFormat: undefined, prompt: undefined, @@ -533,6 +541,7 @@ describe('runShell', () => { yolo: false, auto: false, plan: false, + strictConfig: false, model: undefined, outputFormat: undefined, prompt: undefined, @@ -570,6 +579,7 @@ describe('runShell', () => { yolo: false, auto: false, plan: false, + strictConfig: false, model: undefined, outputFormat: undefined, prompt: undefined, @@ -623,6 +633,7 @@ describe('runShell', () => { yolo: false, auto: false, plan: false, + strictConfig: false, model: undefined, outputFormat: undefined, prompt: undefined, diff --git a/apps/kimi-code/test/tui/activity-pane.test.ts b/apps/kimi-code/test/tui/activity-pane.test.ts index b719da163..892d15add 100644 --- a/apps/kimi-code/test/tui/activity-pane.test.ts +++ b/apps/kimi-code/test/tui/activity-pane.test.ts @@ -22,6 +22,7 @@ function makeStartupInput(): KimiTUIStartupInput { yolo: false, auto: false, plan: false, + strictConfig: false, model: undefined, outputFormat: undefined, prompt: undefined, diff --git a/apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts b/apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts index d069dd112..2dc7270b4 100644 --- a/apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts +++ b/apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts @@ -95,6 +95,7 @@ function makeStartupInput(): KimiTUIStartupInput { yolo: false, auto: false, plan: false, + strictConfig: false, model: undefined, outputFormat: undefined, prompt: undefined, diff --git a/apps/kimi-code/test/tui/kimi-tui-startup.test.ts b/apps/kimi-code/test/tui/kimi-tui-startup.test.ts index bed844af8..54175c976 100644 --- a/apps/kimi-code/test/tui/kimi-tui-startup.test.ts +++ b/apps/kimi-code/test/tui/kimi-tui-startup.test.ts @@ -68,6 +68,7 @@ function makeStartupInput( yolo: false, auto: false, plan: false, + strictConfig: false, model: undefined, outputFormat: undefined, prompt: undefined, diff --git a/apps/kimi-code/test/tui/message-replay.test.ts b/apps/kimi-code/test/tui/message-replay.test.ts index f54bac27b..c6352f7df 100644 --- a/apps/kimi-code/test/tui/message-replay.test.ts +++ b/apps/kimi-code/test/tui/message-replay.test.ts @@ -45,6 +45,7 @@ function makeStartupInput(): KimiTUIStartupInput { yolo: false, auto: false, plan: false, + strictConfig: false, model: undefined, outputFormat: undefined, prompt: undefined, diff --git a/apps/kimi-code/test/tui/signal-handlers.test.ts b/apps/kimi-code/test/tui/signal-handlers.test.ts index 9d630a26d..c952074fd 100644 --- a/apps/kimi-code/test/tui/signal-handlers.test.ts +++ b/apps/kimi-code/test/tui/signal-handlers.test.ts @@ -18,6 +18,7 @@ function makeStartupInput(): KimiTUIStartupInput { yolo: false, auto: false, plan: false, + strictConfig: false, model: undefined, outputFormat: undefined, prompt: undefined, diff --git a/packages/agent-core/src/config/schema.ts b/packages/agent-core/src/config/schema.ts index 094239b73..af59ca30b 100644 --- a/packages/agent-core/src/config/schema.ts +++ b/packages/agent-core/src/config/schema.ts @@ -128,6 +128,13 @@ export const MoonshotServiceConfigSchema = z.object({ export type MoonshotServiceConfig = z.infer; +export const ServicesConfigStrictSchema = z.object({ + moonshotSearch: MoonshotServiceConfigSchema.strict().optional(), + moonshotFetch: MoonshotServiceConfigSchema.strict().optional(), +}).strict(); + +export type ServicesConfigStrict = z.infer; + export const ServicesConfigSchema = z.object({ moonshotSearch: MoonshotServiceConfigSchema.optional(), moonshotFetch: MoonshotServiceConfigSchema.optional(), @@ -208,6 +215,18 @@ export const KimiConfigSchema = z.object({ raw: z.record(z.string(), z.unknown()).optional(), }); +/** + * Strict variant used by `--strict-config`. Rejects unknown keys at the + * section level while keeping provider/model aliases open for custom fields. + */ +export const KimiConfigStrictSchema = KimiConfigSchema.extend({ + permission: PermissionConfigSchema.strict().optional(), + services: ServicesConfigStrictSchema.optional(), + loopControl: LoopControlSchema.strict().optional(), + background: BackgroundConfigSchema.strict().optional(), + thinking: ThinkingConfigSchema.strict().optional(), +}).strict(); + export type KimiConfig = z.infer; const ProviderConfigPatchSchema = ProviderConfigSchema.partial(); diff --git a/packages/agent-core/src/config/toml.ts b/packages/agent-core/src/config/toml.ts index 172e97cfc..d5681934e 100644 --- a/packages/agent-core/src/config/toml.ts +++ b/packages/agent-core/src/config/toml.ts @@ -6,6 +6,7 @@ import { ErrorCodes, KimiError } from '#/errors'; import { applyEnvModelConfig, stripEnvModelConfig } from './env-model'; import { KimiConfigSchema, + KimiConfigStrictSchema, formatConfigValidationError, getDefaultConfig, type BackgroundConfig, @@ -62,12 +63,19 @@ export async function ensureConfigFile(filePath: string): Promise { } } -export function readConfigFile(filePath: string): KimiConfig { +export interface ReadConfigOptions { + readonly strict?: boolean; +} + +export function readConfigFile( + filePath: string, + options: ReadConfigOptions = {}, +): KimiConfig { if (!existsSync(filePath)) { return getDefaultConfig(); } const text = readFileSync(filePath, 'utf-8'); - return parseConfigString(text, filePath); + return parseConfigString(text, filePath, options); } /** @@ -76,9 +84,12 @@ export function readConfigFile(filePath: string): KimiConfig { * sections). Re-throws validation failures with a short actionable message — * UIs surface it directly — instead of the raw validation details. */ -export function readConfigFileForUpdate(filePath: string): KimiConfig { +export function readConfigFileForUpdate( + filePath: string, + options: ReadConfigOptions = {}, +): KimiConfig { try { - return readConfigFile(filePath); + return readConfigFile(filePath, options); } catch (error) { if (error instanceof KimiError && error.code === ErrorCodes.CONFIG_INVALID) { throw new KimiError( @@ -100,8 +111,9 @@ export function readConfigFileForUpdate(filePath: string): KimiConfig { export function loadRuntimeConfig( filePath: string, env: Readonly> = process.env, + options: ReadConfigOptions = {}, ): KimiConfig { - return applyEnvModelConfig(readConfigFile(filePath), env); + return applyEnvModelConfig(readConfigFile(filePath, options), env); } export interface RuntimeConfigLoadResult { @@ -262,7 +274,11 @@ function describeTomlSyntaxError(error: unknown): string { return firstLine; } -export function parseConfigString(tomlText: string, filePath = 'config.toml'): KimiConfig { +export function parseConfigString( + tomlText: string, + filePath = 'config.toml', + options: ReadConfigOptions = {}, +): KimiConfig { if (tomlText.trim().length === 0) { return getDefaultConfig(); } @@ -276,16 +292,21 @@ export function parseConfigString(tomlText: string, filePath = 'config.toml'): K }); } - return parseConfigData(data, filePath); + return parseConfigData(data, filePath, options); } -function parseConfigData(data: Record, filePath: string): KimiConfig { +function parseConfigData( + data: Record, + filePath: string, + options: ReadConfigOptions = {}, +): KimiConfig { const raw = cloneRecord(data); const transformed = transformTomlData(data); transformed['raw'] = raw; + const schema = options.strict ? KimiConfigStrictSchema : KimiConfigSchema; try { - return KimiConfigSchema.parse(transformed); + return schema.parse(transformed); } catch (error) { throw new KimiError(ErrorCodes.CONFIG_INVALID, `Invalid configuration in ${filePath}: ${formatConfigValidationError(error)}`, { cause: error, @@ -364,7 +385,7 @@ function transformModelData(data: Record): Record): Record { const raw = transformPlainObject(data); - const out: Record = {}; + const out: Record = { ...raw }; const rules: unknown[] = []; appendPermissionRules(rules, raw['rules']); diff --git a/packages/agent-core/src/rpc/core-impl.ts b/packages/agent-core/src/rpc/core-impl.ts index 204715da6..490367982 100644 --- a/packages/agent-core/src/rpc/core-impl.ts +++ b/packages/agent-core/src/rpc/core-impl.ts @@ -13,6 +13,7 @@ import { resolveThinkingLevel } from '../agent/config/thinking'; import { Agent } from '../agent'; import { ensureKimiHome, + loadRuntimeConfig, loadRuntimeConfigSafe, mergeConfigPatch, readConfigFileForUpdate, @@ -118,6 +119,7 @@ export interface KimiCoreOptions { readonly skillDirs?: readonly string[]; readonly telemetry?: TelemetryClient | undefined; readonly appVersion?: string; + readonly strictConfig?: boolean; } export class KimiCore implements PromisableMethods { @@ -142,6 +144,7 @@ export class KimiCore implements PromisableMethods { private pluginsLoadError: Error | undefined; private readonly appVersion: string | undefined; private readonly experimentalFlags: FlagResolver; + private readonly strictConfig: boolean; constructor( protected readonly rpcClient: CoreRPCClient, @@ -160,20 +163,9 @@ export class KimiCore implements PromisableMethods { this.skillDirs = options.skillDirs ?? []; this.telemetry = options.telemetry ?? noopTelemetryClient; this.appVersion = options.appVersion; + this.strictConfig = options.strictConfig ?? false; ensureKimiHome(this.homeDir); - // Schema errors degrade (invalid sections are dropped with warnings) so a - // typo cannot prevent startup, but a file that cannot be used at all — - // TOML syntax error, unreadable — fails fast: defaults-only would start - // the app looking logged out, which is worse than the parse error. - const loaded = loadRuntimeConfigSafe(this.configPath); - if (loaded.fileError !== undefined) { - throw loaded.fileError; - } - this.config = loaded.config; - this.configWarnings = [...loaded.fileWarnings, ...loaded.envWarnings]; - if (this.configWarnings.length > 0) { - log.warn('config load degraded', { warnings: this.configWarnings }); - } + this.config = this.loadInitialConfig(); this.experimentalFlags = new FlagResolver( process.env, FLAG_DEFINITIONS, @@ -882,10 +874,30 @@ export class KimiCore implements PromisableMethods { } private readConfigForWrite(): KimiConfig { - return readConfigFileForUpdate(this.configPath); + return readConfigFileForUpdate(this.configPath, { strict: this.strictConfig }); + } + + private loadInitialConfig(): KimiConfig { + if (this.strictConfig) { + return loadRuntimeConfig(this.configPath, process.env, { strict: true }); + } + const loaded = loadRuntimeConfigSafe(this.configPath); + if (loaded.fileError !== undefined) { + throw loaded.fileError; + } + this.configWarnings = [...loaded.fileWarnings, ...loaded.envWarnings]; + if (this.configWarnings.length > 0) { + log.warn('config load degraded', { warnings: this.configWarnings }); + } + return loaded.config; } private reloadRuntimeConfig(): KimiConfig { + if (this.strictConfig) { + return this.setRuntimeConfig( + loadRuntimeConfig(this.configPath, process.env, { strict: true }), + ); + } const loaded = loadRuntimeConfigSafe(this.configPath); if (loaded.fileWarnings.length > 0) { // Keep the last good config: adopting a salvaged config mid-run could diff --git a/packages/agent-core/test/config/configs.test.ts b/packages/agent-core/test/config/configs.test.ts index 091eee384..8e730a270 100644 --- a/packages/agent-core/test/config/configs.test.ts +++ b/packages/agent-core/test/config/configs.test.ts @@ -8,6 +8,7 @@ import { afterEach, describe, expect, it } from 'vitest'; import { ErrorCodes, KimiError } from '../../src/errors'; import { KimiConfigSchema, + KimiConfigStrictSchema, ensureConfigFile, loadRuntimeConfig, loadRuntimeConfigSafe, @@ -453,6 +454,183 @@ hooks = [{ type = "pre-tool-call", command = "echo hi" }] ErrorCodes.CONFIG_INVALID, ); }); + + it('allows unknown top-level keys in non-strict mode', () => { + const config = parseConfigString('unknown_key = "value"\n', 'config.toml'); + expect(config.providers).toEqual({}); + expect(config.raw?.['unknown_key']).toBe('value'); + }); +}); + +describe('strict config parsing', () => { + it('accepts a valid config in strict mode', () => { + const config = parseConfigString( + ` +default_model = "kimi-code/kimi-for-coding" + +[permission] + +[[permission.rules]] +decision = "allow" +pattern = "Read(src/**)" + +[thinking] +mode = "auto" +`, + 'config.toml', + { strict: true }, + ); + expect(config.defaultModel).toBe('kimi-code/kimi-for-coding'); + expect(config.permission).toEqual({ rules: [{ decision: 'allow', scope: 'user', pattern: 'Read(src/**)', reason: undefined }] }); + expect(config.thinking).toEqual({ mode: 'auto' }); + }); + + it('rejects unknown top-level keys in strict mode', () => { + expectKimiErrorCode( + () => parseConfigString('unknown_key = "value"\n', 'config.toml', { strict: true }), + ErrorCodes.CONFIG_INVALID, + ); + }); + + it('rejects unknown keys in strict permission section', () => { + expectKimiErrorCode( + () => + parseConfigString( + ` +[permission] +mode = "manual" +unknown = "value" +`, + 'config.toml', + { strict: true }, + ), + ErrorCodes.CONFIG_INVALID, + ); + }); + + it('rejects unknown keys in strict thinking section', () => { + expectKimiErrorCode( + () => + parseConfigString( + ` +[thinking] +mode = "auto" +unknown = "value" +`, + 'config.toml', + { strict: true }, + ), + ErrorCodes.CONFIG_INVALID, + ); + }); + + it('rejects unknown keys in strict loop_control section', () => { + expectKimiErrorCode( + () => + parseConfigString( + ` +[loop_control] +max_steps_per_turn = 10 +unknown = "value" +`, + 'config.toml', + { strict: true }, + ), + ErrorCodes.CONFIG_INVALID, + ); + }); + + it('rejects unknown keys in strict background section', () => { + expectKimiErrorCode( + () => + parseConfigString( + ` +[background] +max_running_tasks = 2 +unknown = "value" +`, + 'config.toml', + { strict: true }, + ), + ErrorCodes.CONFIG_INVALID, + ); + }); + + it('rejects unknown keys in strict services section', () => { + expectKimiErrorCode( + () => + parseConfigString( + ` +[services.moonshot_search] +base_url = "https://api.kimi.com/coding/v1/search" +unknown = "value" +`, + 'config.toml', + { strict: true }, + ), + ErrorCodes.CONFIG_INVALID, + ); + }); + + it('allows custom provider fields in strict mode', () => { + const config = parseConfigString( + ` +[providers.custom] +type = "openai" +base_url = "https://custom.example/v1" +api_key = "sk-test" +custom_field = "value" +`, + 'config.toml', + { strict: true }, + ); + // Provider schemas remain open: unknown keys are not rejected. They are + // stripped from the typed object but preserved in raw for round-trips. + expect(config.providers['custom']).toMatchObject({ + type: 'openai', + baseUrl: 'https://custom.example/v1', + apiKey: 'sk-test', + }); + expect(config.raw?.['providers']).toMatchObject({ + custom: { custom_field: 'value' }, + }); + }); + + it('allows custom model alias fields in strict mode', () => { + const config = parseConfigString( + ` +[providers.local] +type = "openai" + +[models."local.custom"] +provider = "local" +model = "custom-model" +max_context_size = 128000 +custom_field = "value" +`, + 'config.toml', + { strict: true }, + ); + // Model alias schemas remain open: unknown keys are not rejected. They are + // stripped from the typed object but preserved in raw for round-trips. + expect(config.models?.['local.custom']).toMatchObject({ + provider: 'local', + model: 'custom-model', + maxContextSize: 128000, + }); + expect(config.raw?.['models']).toMatchObject({ + 'local.custom': { custom_field: 'value' }, + }); + }); + + it('exposes the strict schema', () => { + expect(() => + KimiConfigStrictSchema.parse({ + providers: {}, + permission: { unknown: true }, + }), + ).toThrow(); + }); }); describe('harness config schema and patch merge', () => { diff --git a/packages/node-sdk/src/sdk-rpc-client.ts b/packages/node-sdk/src/sdk-rpc-client.ts index 2ffdb7986..125cc9db5 100644 --- a/packages/node-sdk/src/sdk-rpc-client.ts +++ b/packages/node-sdk/src/sdk-rpc-client.ts @@ -37,6 +37,7 @@ export interface SDKRpcClientOptions { readonly skillDirs?: readonly string[]; readonly telemetry?: TelemetryClient; readonly onOAuthRefresh?: (outcome: OAuthRefreshOutcome) => void; + readonly strictConfig?: boolean; } export class SDKRpcClient extends SDKRpcClientBase { @@ -78,6 +79,7 @@ export class SDKRpcClient extends SDKRpcClientBase { skillDirs: options.skillDirs, telemetry: this.telemetry, appVersion: this.identity?.version, + strictConfig: options.strictConfig, }); this.ready = sdkRpc(new ClientAPI(this)); } diff --git a/packages/node-sdk/src/types.ts b/packages/node-sdk/src/types.ts index 041d78495..b3b0a749d 100644 --- a/packages/node-sdk/src/types.ts +++ b/packages/node-sdk/src/types.ts @@ -88,6 +88,7 @@ export interface KimiHarnessOptions { readonly skillDirs?: readonly string[]; readonly telemetry?: TelemetryClient | undefined; readonly onOAuthRefresh?: ((outcome: OAuthRefreshOutcome) => void) | undefined; + readonly strictConfig?: boolean; } export interface CreateSessionOptions {