mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-10 01:39:25 +00:00
feat: add experimental feature-flag system (#205)
Introduce a central, env-driven flag registry in agent-core. Each flag is declared once with an id, full env var name, default, and surface. Within agent-core, flags are consulted through a process-global 'flags' constant that reads live process.env. Resolution precedence: master switch KIMI_CODE_EXPERIMENTAL_FLAG > per-feature KIMI_CODE_EXPERIMENTAL_<NAME> > registry default, with lenient boolean parsing via parseBooleanEnv. FlagId is a literal union derived from the registry for compile-time autocomplete and typo-checking.
SDK boundary: KimiHarness.getExperimentalFlags() returns the resolved values over RPC, and the SDK re-exports only the flag *types* — no runtime value crosses the boundary. The TUI caches that snapshot once at startup and reads it synchronously for command gating.
Gate the plugin system behind the 'plugins' flag, off by default: PluginManager.load() consults flags.enabled('plugins'), so when off no installed plugins are loaded or activated, and the TUI /plugins command is hidden from the palette and resolves as an unknown command.
Tests cover the resolver precedence matrix, registry invariants, the FlagId type guard, the live-env singleton, the plugin-load gate, the getExperimentalFlags RPC, and the TUI command gating.
This commit is contained in:
parent
b9860e9f6e
commit
96bbc471c4
21 changed files with 297 additions and 3 deletions
5
.changeset/experimental-flags.md
Normal file
5
.changeset/experimental-flags.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@moonshot-ai/agent-core": minor
|
||||
---
|
||||
|
||||
Add an experimental feature-flag system: a central registry (`flags/registry.ts`) plus an env-driven resolver. Gate a feature with `flags.enabled('id')`, toggled via `KIMI_CODE_EXPERIMENTAL_<NAME>` or the `KIMI_CODE_EXPERIMENTAL_FLAG` master switch. No flags are defined yet.
|
||||
|
|
@ -44,6 +44,10 @@ This is a TypeScript monorepo built for agent-assisted development. Keep the roo
|
|||
- When a test fails because of a user modification, default to fixing the test first; do not change the implementation to satisfy an old test unless the implementation truly has a bug.
|
||||
- Do not sacrifice code quality for external compatibility unless the user explicitly asks for it. Breaking changes go through changesets and a `major` bump, gated by the rule below.
|
||||
|
||||
## Experimental Features
|
||||
|
||||
- Gate a not-yet-public feature behind an experimental flag. Add the flag to the registry at `packages/agent-core/src/flags/registry.ts`, then check it with `flags.enabled('my-feature')`. Flags are env-driven and default off: `KIMI_CODE_EXPERIMENTAL_<NAME>` toggles one, `KIMI_CODE_EXPERIMENTAL_FLAG` enables all. Release by flipping the entry's `default` to `true`.
|
||||
|
||||
## Where to Update Instructions
|
||||
|
||||
- Hard rules that affect almost every task: update the root `AGENTS.md`.
|
||||
|
|
|
|||
15
apps/kimi-code/src/tui/commands/experimental-flags.ts
Normal file
15
apps/kimi-code/src/tui/commands/experimental-flags.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import type { ExperimentalFlagMap } from '@moonshot-ai/kimi-code-sdk';
|
||||
|
||||
// Resolved experimental flags, fetched once from the core over RPC at startup and then read
|
||||
// synchronously by the command palette and dispatch. App-local cache, not a source of truth.
|
||||
let snapshot: ExperimentalFlagMap = {};
|
||||
|
||||
/** Replace the cached flag snapshot. Call once after fetching via `harness.getExperimentalFlags()`. */
|
||||
export function setExperimentalFlags(flags: ExperimentalFlagMap): void {
|
||||
snapshot = flags;
|
||||
}
|
||||
|
||||
/** An `undefined` flag means "not gated" → always enabled, so callers can pass an optional flag id. */
|
||||
export function isExperimentalFlagEnabled(flag: string | undefined): boolean {
|
||||
return flag === undefined || snapshot[flag] === true;
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
export * from './experimental-flags';
|
||||
export * from './parse';
|
||||
export * from './registry';
|
||||
export * from './resolve';
|
||||
|
|
|
|||
|
|
@ -4,8 +4,13 @@ import {
|
|||
type BuiltinSlashCommand,
|
||||
type BuiltinSlashCommandName,
|
||||
} from './registry';
|
||||
import { isExperimentalFlagEnabled } from './experimental-flags';
|
||||
import { parseSlashInput } from './parse';
|
||||
import type { SlashCommandBusyReason, SlashCommandInvalidReason } from './types';
|
||||
import type {
|
||||
KimiSlashCommand,
|
||||
SlashCommandBusyReason,
|
||||
SlashCommandInvalidReason,
|
||||
} from './types';
|
||||
|
||||
export type SlashCommandIntent =
|
||||
| { readonly kind: 'not-command' }
|
||||
|
|
@ -45,7 +50,11 @@ export function resolveSlashCommandInput(options: ResolveSlashCommandInput): Sla
|
|||
if (parsed === null) return { kind: 'not-command' };
|
||||
|
||||
const command = findBuiltInSlashCommand(parsed.name);
|
||||
if (command !== undefined) {
|
||||
// `command` is a literal union where only some members carry `experimentalFlag`; widen to read it.
|
||||
if (
|
||||
command !== undefined &&
|
||||
isExperimentalFlagEnabled((command as KimiSlashCommand).experimentalFlag)
|
||||
) {
|
||||
const busyReason = slashCommandBusyReason(options);
|
||||
if (
|
||||
busyReason !== undefined &&
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { SlashCommand } from '@earendil-works/pi-tui';
|
||||
import type { FlagId } from '@moonshot-ai/kimi-code-sdk';
|
||||
|
||||
export type SlashCommandAvailability = 'always' | 'idle-only';
|
||||
|
||||
|
|
@ -8,6 +9,8 @@ export interface KimiSlashCommand<Name extends string = string> extends SlashCom
|
|||
readonly description: string;
|
||||
readonly priority?: number;
|
||||
readonly availability?: SlashCommandAvailability | ((args: string) => SlashCommandAvailability);
|
||||
/** When set, the command is hidden from the palette and blocked unless this flag is enabled. */
|
||||
readonly experimentalFlag?: FlagId;
|
||||
}
|
||||
|
||||
export interface ParsedSlashInput {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ import { detectFdPath } from '#/utils/process/fd-detect';
|
|||
import {
|
||||
BUILTIN_SLASH_COMMANDS,
|
||||
buildSkillSlashCommands,
|
||||
isExperimentalFlagEnabled,
|
||||
setExperimentalFlags,
|
||||
sortSlashCommands,
|
||||
type KimiSlashCommand,
|
||||
type SkillListSession,
|
||||
|
|
@ -287,7 +289,10 @@ export class KimiTUI {
|
|||
// =========================================================================
|
||||
|
||||
private getSlashCommands(): readonly KimiSlashCommand[] {
|
||||
return [...sortSlashCommands(BUILTIN_SLASH_COMMANDS), ...this.skillCommands];
|
||||
const builtins = sortSlashCommands(BUILTIN_SLASH_COMMANDS).filter((command) =>
|
||||
isExperimentalFlagEnabled(command.experimentalFlag),
|
||||
);
|
||||
return [...builtins, ...this.skillCommands];
|
||||
}
|
||||
|
||||
private setupAutocomplete(): void {
|
||||
|
|
@ -380,6 +385,7 @@ export class KimiTUI {
|
|||
// Mount only after init() succeeds; see mountFooter().
|
||||
this.mountFooter();
|
||||
this.renderWelcome();
|
||||
setExperimentalFlags(await this.harness.getExperimentalFlags());
|
||||
this.setupAutocomplete();
|
||||
void this.loadPersistedInputHistory();
|
||||
this.state.editorContainer.clear();
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ describe('resolveSlashCommandInput', () => {
|
|||
input: '/does-not-exist arg',
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('slash command busy helpers', () => {
|
||||
|
|
|
|||
|
|
@ -200,6 +200,7 @@ function makeHarness(session = makeSession(), overrides: Record<string, unknown>
|
|||
track: vi.fn(),
|
||||
setTelemetryContext: vi.fn(),
|
||||
interactiveAgentId: 'main',
|
||||
getExperimentalFlags: vi.fn(async () => ({})),
|
||||
auth: {
|
||||
status: vi.fn(),
|
||||
login: vi.fn(),
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ function makeHarness(session = makeSession(), overrides: Record<string, unknown>
|
|||
close: vi.fn(async () => {}),
|
||||
track: vi.fn(),
|
||||
setTelemetryContext: vi.fn(),
|
||||
getExperimentalFlags: vi.fn(async () => ({})),
|
||||
auth: {
|
||||
status: vi.fn(async () => ({ providers: [] })),
|
||||
login: vi.fn(async () => {}),
|
||||
|
|
|
|||
3
packages/agent-core/src/flags/index.ts
Normal file
3
packages/agent-core/src/flags/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export * from './types';
|
||||
export * from './registry';
|
||||
export * from './resolver';
|
||||
16
packages/agent-core/src/flags/registry.ts
Normal file
16
packages/agent-core/src/flags/registry.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import type { FlagDefinitionInput } from './types';
|
||||
|
||||
/**
|
||||
* Experimental feature flags. Empty by default — there are no experimental features yet.
|
||||
*
|
||||
* To add one, append an entry and gate the feature with `flags.enabled('my-feature')`:
|
||||
* { id: 'my-feature', env: 'KIMI_CODE_EXPERIMENTAL_MY_FEATURE', default: false, surface: 'both' }
|
||||
*
|
||||
* Keep the `as const satisfies` — it derives the literal `FlagId` union that gives `enabled()`
|
||||
* autocomplete and typo-checking. `env` must start with 'KIMI_CODE_EXPERIMENTAL_', be unique, and
|
||||
* not equal the master switch 'KIMI_CODE_EXPERIMENTAL_FLAG'; `id` must not be 'flag'.
|
||||
*/
|
||||
export const FLAG_DEFINITIONS = [] as const satisfies readonly FlagDefinitionInput[];
|
||||
|
||||
/** Literal union of registered flag ids (currently none → `never`). */
|
||||
export type FlagId = (typeof FLAG_DEFINITIONS)[number]['id'];
|
||||
65
packages/agent-core/src/flags/resolver.ts
Normal file
65
packages/agent-core/src/flags/resolver.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import { parseBooleanEnv } from '#/config/resolve';
|
||||
|
||||
import { FLAG_DEFINITIONS, type FlagId } from './registry';
|
||||
import type { FlagDefinitionInput } from './types';
|
||||
|
||||
/** Master switch: when truthy, forces every flag on (highest priority). */
|
||||
export const MASTER_ENV = 'KIMI_CODE_EXPERIMENTAL_FLAG';
|
||||
|
||||
/** Shared prefix for per-feature variables. */
|
||||
export const EXPERIMENTAL_PREFIX = 'KIMI_CODE_EXPERIMENTAL_';
|
||||
|
||||
/**
|
||||
* Conventional env-name generator: flag id → recommended env variable name. Only used when
|
||||
* authoring a new flag (to fill `env`) and for an optional consistency test; NOT used during
|
||||
* resolution.
|
||||
*/
|
||||
export function flagEnvKey(id: string): string {
|
||||
return `${EXPERIMENTAL_PREFIX}${id.toUpperCase().replaceAll('-', '_')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pure, synchronous flag resolver. State comes entirely from (env, registry) and nothing is
|
||||
* cached: env is read live on every call, so a single shared instance always reflects the current
|
||||
* process env. Defaults to process.env + FLAG_DEFINITIONS; tests can inject a custom env / defs.
|
||||
*
|
||||
* Precedence (highest wins):
|
||||
* L1 master switch KIMI_CODE_EXPERIMENTAL_FLAG → every flag is on
|
||||
* L2 per-feature def.env (parseBooleanEnv, may force on or off)
|
||||
* L3 registry default
|
||||
*/
|
||||
export class FlagResolver {
|
||||
private readonly env: Readonly<Record<string, string | undefined>>;
|
||||
private readonly byId: ReadonlyMap<string, FlagDefinitionInput>;
|
||||
|
||||
constructor(
|
||||
env: Readonly<Record<string, string | undefined>> = process.env,
|
||||
definitions: readonly FlagDefinitionInput[] = FLAG_DEFINITIONS,
|
||||
) {
|
||||
this.env = env;
|
||||
this.byId = new Map(definitions.map((def) => [def.id, def]));
|
||||
}
|
||||
|
||||
enabled(id: FlagId): boolean {
|
||||
const def = this.byId.get(id);
|
||||
if (def === undefined) return false;
|
||||
if (parseBooleanEnv(this.env[MASTER_ENV]) === true) return true; // L1 master switch
|
||||
const override = parseBooleanEnv(this.env[def.env]); // L2 per-feature
|
||||
if (override !== undefined) return override;
|
||||
return def.default; // L3 default
|
||||
}
|
||||
}
|
||||
|
||||
export function createFlagResolver(
|
||||
env?: Readonly<Record<string, string | undefined>>,
|
||||
definitions?: readonly FlagDefinitionInput[],
|
||||
): FlagResolver {
|
||||
return new FlagResolver(env, definitions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process-global flag accessor. Flags are env-driven and process-global, so a single shared
|
||||
* instance (reading live process.env) is the canonical way to consult them — import this directly
|
||||
* rather than constructing or injecting a resolver.
|
||||
*/
|
||||
export const flags = new FlagResolver();
|
||||
19
packages/agent-core/src/flags/types.ts
Normal file
19
packages/agent-core/src/flags/types.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import type { FlagId } from './registry';
|
||||
|
||||
/** Which layer consumes a flag — documentation/grouping only; not used in resolution. */
|
||||
export type FlagSurface = 'core' | 'tui' | 'both';
|
||||
|
||||
/** Shape of a registry entry (id is a loose string so `as const satisfies` can validate it). */
|
||||
export interface FlagDefinitionInput {
|
||||
readonly id: string;
|
||||
/** Full environment variable name, e.g. `KIMI_CODE_EXPERIMENTAL_MY_FEATURE`. Read directly by the resolver. */
|
||||
readonly env: string;
|
||||
readonly default: boolean;
|
||||
readonly surface: FlagSurface;
|
||||
}
|
||||
|
||||
/** FlagId-typed view so consumers can fetch a definition by its literal id. */
|
||||
export type FlagDefinition = FlagDefinitionInput & { readonly id: FlagId };
|
||||
|
||||
/** Resolved enabled-state of every experimental flag (flag id → enabled); used for the SDK snapshot. */
|
||||
export type ExperimentalFlagMap = Record<string, boolean>;
|
||||
|
|
@ -2,6 +2,7 @@ export * from './agent';
|
|||
export * from './session';
|
||||
export * from './rpc';
|
||||
export * from './config';
|
||||
export * from './flags';
|
||||
export * from './session/export';
|
||||
export * from './telemetry';
|
||||
export * from './errors';
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import type { PermissionData, PermissionMode } from '#/agent/permission';
|
|||
import type { PlanData } from '#/agent/plan';
|
||||
import type { ToolInfo } from '#/agent/tool';
|
||||
import type { KimiConfig, KimiConfigPatch } from '#/config';
|
||||
import type { ExperimentalFlagMap } from '#/flags';
|
||||
import type { ResumeSessionResult } from '#/rpc/resumed';
|
||||
import type { SessionMeta } from '#/session';
|
||||
import type { BackgroundTaskInfo } from '#/tools/builtin';
|
||||
|
|
@ -307,6 +308,7 @@ type SessionAPIWithId = WithSessionId<SessionAPI>;
|
|||
|
||||
export interface CoreAPI extends SessionAPIWithId {
|
||||
getCoreInfo: (payload: EmptyPayload) => CoreInfo;
|
||||
getExperimentalFlags: (payload: EmptyPayload) => ExperimentalFlagMap;
|
||||
getKimiConfig: (payload: GetKimiConfigPayload) => KimiConfig;
|
||||
setKimiConfig: (payload: SetKimiConfigPayload) => KimiConfig;
|
||||
removeKimiProvider: (payload: RemoveKimiProviderPayload) => KimiConfig;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,13 @@ import {
|
|||
type KimiConfig,
|
||||
type MoonshotServiceConfig,
|
||||
} from '../config';
|
||||
import {
|
||||
FLAG_DEFINITIONS,
|
||||
flags,
|
||||
type ExperimentalFlagMap,
|
||||
type FlagDefinitionInput,
|
||||
type FlagId,
|
||||
} from '../flags';
|
||||
import type { Logger } from '../logging/types';
|
||||
import { resolveSessionMcpConfig, type SessionMcpConfig } from '../mcp';
|
||||
import { Session, type SessionMeta, type SessionSkillConfig } from '../session';
|
||||
|
|
@ -242,6 +249,11 @@ export class KimiCore implements PromisableMethods<CoreAPI> {
|
|||
return { version: getCoreVersion() };
|
||||
}
|
||||
|
||||
getExperimentalFlags(): ExperimentalFlagMap {
|
||||
const defs: readonly FlagDefinitionInput[] = FLAG_DEFINITIONS;
|
||||
return Object.fromEntries(defs.map((def) => [def.id, flags.enabled(def.id as FlagId)]));
|
||||
}
|
||||
|
||||
async closeSession({ sessionId }: CloseSessionPayload): Promise<void> {
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (session) {
|
||||
|
|
|
|||
108
packages/agent-core/test/flags/resolver.test.ts
Normal file
108
packages/agent-core/test/flags/resolver.test.ts
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
EXPERIMENTAL_PREFIX,
|
||||
FLAG_DEFINITIONS,
|
||||
MASTER_ENV,
|
||||
createFlagResolver,
|
||||
flagEnvKey,
|
||||
type FlagDefinitionInput,
|
||||
type FlagId,
|
||||
} from '../../src/flags';
|
||||
|
||||
// Controlled fake definitions to assert the precedence matrix precisely (independent of the
|
||||
// real registry contents).
|
||||
const DEFS = [
|
||||
{
|
||||
id: 'a-on-default',
|
||||
env: 'KIMI_CODE_EXPERIMENTAL_A',
|
||||
default: true,
|
||||
surface: 'core',
|
||||
},
|
||||
{
|
||||
id: 'b-off-default',
|
||||
env: 'KIMI_CODE_EXPERIMENTAL_B',
|
||||
default: false,
|
||||
surface: 'tui',
|
||||
},
|
||||
] as const satisfies readonly FlagDefinitionInput[];
|
||||
|
||||
type Env = Record<string, string | undefined>;
|
||||
|
||||
function make(env: Env) {
|
||||
const resolver = createFlagResolver(env, DEFS);
|
||||
// The fake ids are not part of the real FlagId union, so cast to FlagId when calling.
|
||||
return (id: string) => resolver.enabled(id as FlagId);
|
||||
}
|
||||
|
||||
describe('FlagResolver', () => {
|
||||
it('L3 default: returns the registry default when env is empty', () => {
|
||||
const enabled = make({});
|
||||
expect(enabled('a-on-default')).toBe(true);
|
||||
expect(enabled('b-off-default')).toBe(false);
|
||||
});
|
||||
|
||||
it('L2 per-feature on (lenient truthy values)', () => {
|
||||
for (const v of ['1', 'true', 'yes', 'on', 'TRUE', ' On ']) {
|
||||
expect(make({ KIMI_CODE_EXPERIMENTAL_B: v })('b-off-default')).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('L2 per-feature off (lenient falsy values) overrides default=true', () => {
|
||||
for (const v of ['0', 'false', 'no', 'off']) {
|
||||
expect(make({ KIMI_CODE_EXPERIMENTAL_A: v })('a-on-default')).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it('L2 unparseable value falls back to default', () => {
|
||||
expect(make({ KIMI_CODE_EXPERIMENTAL_B: 'maybe' })('b-off-default')).toBe(false);
|
||||
expect(make({ KIMI_CODE_EXPERIMENTAL_A: 'maybe' })('a-on-default')).toBe(true);
|
||||
});
|
||||
|
||||
it('L1 master switch: every flag is on when enabled (including default=false)', () => {
|
||||
const enabled = make({ [MASTER_ENV]: '1' });
|
||||
expect(enabled('a-on-default')).toBe(true);
|
||||
expect(enabled('b-off-default')).toBe(true);
|
||||
});
|
||||
|
||||
it('L1 master switch beats an L2 per-feature off (D2)', () => {
|
||||
const enabled = make({ [MASTER_ENV]: '1', KIMI_CODE_EXPERIMENTAL_A: '0' });
|
||||
expect(enabled('a-on-default')).toBe(true);
|
||||
});
|
||||
|
||||
it('master switch is inactive for lenient falsy values', () => {
|
||||
const enabled = make({ [MASTER_ENV]: '0' });
|
||||
expect(enabled('b-off-default')).toBe(false);
|
||||
});
|
||||
|
||||
it('reads the env name declared in the registry (the declared name works, others do not)', () => {
|
||||
expect(make({ KIMI_CODE_EXPERIMENTAL_B: '1' })('b-off-default')).toBe(true);
|
||||
// The name mechanically derived from the id must not take effect (env is explicitly ..._B).
|
||||
expect(make({ KIMI_CODE_EXPERIMENTAL_B_OFF_DEFAULT: '1' })('b-off-default')).toBe(false);
|
||||
});
|
||||
|
||||
it('unknown id resolves to false (defensive)', () => {
|
||||
expect(make({})('not-a-real-flag')).toBe(false);
|
||||
});
|
||||
|
||||
it('flagEnvKey convention: kebab -> prefix + upper snake', () => {
|
||||
expect(flagEnvKey('my-feature')).toBe('KIMI_CODE_EXPERIMENTAL_MY_FEATURE');
|
||||
});
|
||||
});
|
||||
|
||||
describe('FLAG_DEFINITIONS invariants', () => {
|
||||
it('every env satisfies: prefix / unique / not the master switch', () => {
|
||||
const seenEnv = new Set<string>();
|
||||
const seenId = new Set<string>();
|
||||
const defs: readonly FlagDefinitionInput[] = FLAG_DEFINITIONS;
|
||||
for (const def of defs) {
|
||||
expect(def.env.startsWith(EXPERIMENTAL_PREFIX)).toBe(true);
|
||||
expect(def.env).not.toBe(MASTER_ENV);
|
||||
expect(def.id).not.toBe('flag'); // reserved: would collide with the master switch
|
||||
expect(seenEnv.has(def.env)).toBe(false);
|
||||
expect(seenId.has(def.id)).toBe(false);
|
||||
seenEnv.add(def.env);
|
||||
seenId.add(def.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -44,6 +44,16 @@ export {
|
|||
} from '@moonshot-ai/agent-core';
|
||||
export type { LogContext, LogLevel, LogPayload, Logger } from '@moonshot-ai/agent-core';
|
||||
|
||||
// Experimental feature flags — types only. Resolved values come from
|
||||
// `KimiHarness.getExperimentalFlags()` over RPC, not from a re-exported runtime value.
|
||||
export type {
|
||||
ExperimentalFlagMap,
|
||||
FlagDefinition,
|
||||
FlagDefinitionInput,
|
||||
FlagId,
|
||||
FlagSurface,
|
||||
} from '@moonshot-ai/agent-core';
|
||||
|
||||
export type {
|
||||
KimiAuthLoginResult,
|
||||
KimiAuthLogoutResult,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
resolveKimiHome,
|
||||
resolveLoggingConfig,
|
||||
withTelemetryContext,
|
||||
type ExperimentalFlagMap,
|
||||
type TelemetryClient,
|
||||
type TelemetryContextPatch,
|
||||
type TelemetryProperties,
|
||||
|
|
@ -191,6 +192,11 @@ export class KimiHarness {
|
|||
return this.rpc.getConfig(options);
|
||||
}
|
||||
|
||||
/** Resolved enabled-state of every experimental flag (flag id → enabled). */
|
||||
async getExperimentalFlags(): Promise<ExperimentalFlagMap> {
|
||||
return this.rpc.getExperimentalFlags();
|
||||
}
|
||||
|
||||
async ensureConfigFile(): Promise<void> {
|
||||
await ensureConfigFile(this.configPath);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
type ApprovalResponse,
|
||||
type CoreAPI,
|
||||
type Event,
|
||||
type ExperimentalFlagMap,
|
||||
type OAuthTokenProviderResolver,
|
||||
type QuestionRequest,
|
||||
type QuestionResult,
|
||||
|
|
@ -197,6 +198,11 @@ export class SDKRpcClient {
|
|||
return rpc.getKimiConfig(input ?? {});
|
||||
}
|
||||
|
||||
async getExperimentalFlags(): Promise<ExperimentalFlagMap> {
|
||||
const rpc = await this.getRpc();
|
||||
return rpc.getExperimentalFlags({});
|
||||
}
|
||||
|
||||
async setConfig(input: KimiConfigPatch): Promise<KimiConfig> {
|
||||
const rpc = await this.getRpc();
|
||||
return rpc.setKimiConfig(input);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue