@khorsheed/dsh-local-agent-kimi 0.1.0-rc.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.en.md +132 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +132 -0
  4. package/cordis.patch.yml +34 -0
  5. package/lib/client.js +1066 -0
  6. package/lib/index.js +4716 -0
  7. package/lib/invariant.js +31 -0
  8. package/lib/types/client/SettingsCard.d.ts +77 -0
  9. package/lib/types/client/SettingsCard.js +197 -0
  10. package/lib/types/client/icons.d.ts +18 -0
  11. package/lib/types/client/icons.js +2 -0
  12. package/lib/types/client/index.d.ts +42 -0
  13. package/lib/types/client/index.js +89 -0
  14. package/lib/types/client/locales.d.ts +64 -0
  15. package/lib/types/client/locales.js +55 -0
  16. package/lib/types/credential-guard.d.ts +29 -0
  17. package/lib/types/credential-guard.js +107 -0
  18. package/lib/types/index.d.ts +69 -0
  19. package/lib/types/index.js +270 -0
  20. package/lib/types/invariant.d.ts +16 -0
  21. package/lib/types/invariant.js +33 -0
  22. package/lib/types/kimi-cli-provider.d.ts +215 -0
  23. package/lib/types/kimi-cli-provider.js +855 -0
  24. package/lib/types/live-driver.d.ts +245 -0
  25. package/lib/types/live-driver.js +1312 -0
  26. package/lib/types/live-switch.d.ts +78 -0
  27. package/lib/types/live-switch.js +112 -0
  28. package/lib/types/member-bridge-config.d.ts +43 -0
  29. package/lib/types/member-bridge-config.js +80 -0
  30. package/lib/types/model-broker.d.ts +97 -0
  31. package/lib/types/model-broker.js +205 -0
  32. package/lib/types/model-catalog.d.ts +26 -0
  33. package/lib/types/model-catalog.js +86 -0
  34. package/lib/types/model-configuration.d.ts +11 -0
  35. package/lib/types/model-configuration.js +55 -0
  36. package/lib/types/preset-tools.d.ts +20 -0
  37. package/lib/types/preset-tools.js +36 -0
  38. package/lib/types/provision.d.ts +131 -0
  39. package/lib/types/provision.js +363 -0
  40. package/lib/types/records.d.ts +35 -0
  41. package/lib/types/records.js +85 -0
  42. package/lib/types/session-configuration.d.ts +5 -0
  43. package/lib/types/session-configuration.js +26 -0
  44. package/lib/types/session-mirror.d.ts +180 -0
  45. package/lib/types/session-mirror.js +446 -0
  46. package/lib/types/session-view.d.ts +121 -0
  47. package/lib/types/session-view.js +423 -0
  48. package/locale/en.json +6 -0
  49. package/locale/zh.json +6 -0
  50. package/package.json +107 -0
@@ -0,0 +1,86 @@
1
+ import { join } from 'node:path';
2
+ import { ModelDirectoryCache, modelDirectoryContextKey } from '@khorsheed/dsh-local-agent';
3
+ import { readKimiModelConfiguration } from './model-configuration.js';
4
+ const object = (value) => typeof value === 'object' && value !== null ? value : undefined;
5
+ function options(value) {
6
+ if (!Array.isArray(value))
7
+ return [];
8
+ return value.flatMap(candidate => {
9
+ const entry = object(candidate);
10
+ if (entry === undefined)
11
+ return [];
12
+ if (Array.isArray(entry['options']))
13
+ return options(entry['options']);
14
+ if (typeof entry['value'] !== 'string')
15
+ return [];
16
+ return [{ value: entry['value'], label: typeof entry['name'] === 'string' ? entry['name'] : entry['value'], ...typeof entry['description'] === 'string' ? { description: entry['description'] } : {} }];
17
+ });
18
+ }
19
+ /** ACP v1 configOptions are authoritative; old models metadata is a read-only fallback. */
20
+ export function kimiNativeConfiguration(value) {
21
+ const response = object(value);
22
+ if (response === undefined)
23
+ return undefined;
24
+ const configuration = response['configOptions'];
25
+ if (Array.isArray(configuration)) {
26
+ const choices = configuration.map(object).filter((entry) => entry !== undefined && entry['type'] === 'select');
27
+ const model = choices.find(entry => entry['category'] === 'model');
28
+ const effort = choices.find(entry => entry['category'] === 'thought_level');
29
+ if (model !== undefined) {
30
+ const currentModel = typeof model['currentValue'] === 'string' ? model['currentValue'] : undefined;
31
+ const entries = options(model['options']).map(option => ({
32
+ ...option, source: 'native',
33
+ // Dependent options describe the CURRENT model only, not every candidate.
34
+ ...effort === undefined || option.value !== currentModel ? {} : { reasoning: { options: options(effort['options']) } },
35
+ }));
36
+ return {
37
+ directory: { entries, complete: true, customInput: false },
38
+ ...typeof model['id'] === 'string' ? { modelConfigId: model['id'] } : {},
39
+ ...typeof effort?.['id'] === 'string' ? { effortConfigId: effort['id'] } : {},
40
+ ...currentModel === undefined ? {} : { currentModel },
41
+ ...typeof effort?.['currentValue'] === 'string' ? { currentEffort: effort['currentValue'] } : {},
42
+ };
43
+ }
44
+ }
45
+ const legacy = object(response['models']);
46
+ if (!Array.isArray(legacy?.['availableModels']))
47
+ return undefined;
48
+ const entries = legacy['availableModels'].flatMap(candidate => {
49
+ const model = object(candidate);
50
+ if (typeof model?.['modelId'] !== 'string')
51
+ return [];
52
+ return [{ value: model['modelId'], label: typeof model['name'] === 'string' ? model['name'] : model['modelId'], source: 'native', ...typeof model['description'] === 'string' ? { description: model['description'] } : {} }];
53
+ });
54
+ return {
55
+ directory: { entries, complete: true, customInput: false },
56
+ ...typeof legacy['currentModelId'] === 'string' ? { currentModel: legacy['currentModelId'] } : {},
57
+ };
58
+ }
59
+ /** Reads configuration before a session exists; never starts a disposable ACP session. */
60
+ export class KimiModelCatalog {
61
+ context;
62
+ cache = new ModelDirectoryCache({ load: async (key) => {
63
+ const context = JSON.parse(key);
64
+ if (context.native !== undefined)
65
+ return context.native;
66
+ const { entries, defaultModel } = await readKimiModelConfiguration(context.homeDir);
67
+ return {
68
+ entries,
69
+ ...defaultModel === undefined ? {} : { defaultModel },
70
+ complete: false, customInput: true,
71
+ reason: 'Configured candidates; native session model metadata is not available yet',
72
+ };
73
+ } });
74
+ constructor(context) {
75
+ this.context = context;
76
+ }
77
+ key(childSessionId) {
78
+ const context = this.context(childSessionId);
79
+ const stamp = modelDirectoryContextKey({ provider: 'kimi', homeDir: context.homeDir, cwd: context.cwd ?? context.homeDir, cli: ['kimi'], files: [join(context.homeDir, 'config.toml'), join(context.homeDir, 'credentials', 'kimi-code.json')] });
80
+ return JSON.stringify({ homeDir: context.homeDir, stamp, childSessionId, ...context.native === undefined ? {} : { native: context.native.directory } });
81
+ }
82
+ read(childSessionId) { return this.cache.read(this.key(childSessionId)); }
83
+ refresh(childSessionId) { return this.cache.refresh(this.key(childSessionId)); }
84
+ follow(childSessionId, signal) { return this.cache.follow(() => this.key(childSessionId), signal); }
85
+ dispose() { this.cache.dispose(); }
86
+ }
@@ -0,0 +1,11 @@
1
+ import type { LocalAgentModelEntry } from '@khorsheed/dsh-local-agent/types';
2
+ /** Native configuration metadata only; provider credentials never leave this function. */
3
+ export declare function readKimiModelConfiguration(home: string, selectedModel?: string): Promise<{
4
+ entries: LocalAgentModelEntry[];
5
+ defaultModel?: string;
6
+ effort?: string;
7
+ protocol?: string;
8
+ }>;
9
+ /** Only the native Kimi protocol advertises a general exec effort override. */
10
+ export declare function kimiExecConfiguration(home: string, model: string | undefined, effort: string | undefined): Promise<Record<string, string>>;
11
+ //# sourceMappingURL=model-configuration.d.ts.map
@@ -0,0 +1,55 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { parse } from 'smol-toml';
4
+ const table = (value) => typeof value === 'object' && value !== null && !Array.isArray(value) ? value : {};
5
+ const string = (value) => typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined;
6
+ /** Native configuration metadata only; provider credentials never leave this function. */
7
+ export async function readKimiModelConfiguration(home, selectedModel) {
8
+ let text;
9
+ try {
10
+ text = await readFile(join(home, 'config.toml'), 'utf8');
11
+ }
12
+ catch (error) {
13
+ if (error.code === 'ENOENT')
14
+ return { entries: [] };
15
+ throw error;
16
+ }
17
+ const config = parse(text);
18
+ const models = table(config['models']);
19
+ const entries = Object.entries(models).map(([value, raw]) => {
20
+ const model = table(raw);
21
+ const efforts = model['support_efforts'];
22
+ const defaultEffort = string(model['default_effort']);
23
+ return {
24
+ value, label: string(model['display_name']) ?? value, source: 'configuration',
25
+ ...!Array.isArray(efforts) ? {} : { reasoning: {
26
+ options: efforts.flatMap(value => typeof value === 'string' ? [{ value, label: value }] : []),
27
+ ...defaultEffort === undefined ? {} : { default: defaultEffort },
28
+ } },
29
+ };
30
+ });
31
+ const defaultModel = string(config['default_model']);
32
+ const selected = selectedModel ?? defaultModel;
33
+ const model = table(selected === undefined ? undefined : models[selected]);
34
+ const provider = table(table(config['providers'])[string(model['provider']) ?? '']);
35
+ const protocol = string(model['protocol']) ?? string(provider['type']);
36
+ const thinking = table(config['thinking']);
37
+ const requestedEffort = string(thinking['effort']);
38
+ const options = entries.find(entry => entry.value === selected)?.reasoning?.options;
39
+ const effort = thinking['enabled'] === false ? undefined
40
+ : requestedEffort !== undefined && options?.some(option => option.value === requestedEffort) ? requestedEffort : string(model['default_effort']);
41
+ return { entries, ...defaultModel === undefined ? {} : { defaultModel }, ...effort === undefined ? {} : { effort }, ...protocol === undefined ? {} : { protocol } };
42
+ }
43
+ /** Only the native Kimi protocol advertises a general exec effort override. */
44
+ export async function kimiExecConfiguration(home, model, effort) {
45
+ if (effort === undefined)
46
+ return {};
47
+ const config = await readKimiModelConfiguration(home, model);
48
+ const entry = config.entries.find(entry => entry.value === (model ?? config.defaultModel));
49
+ if (!entry?.reasoning?.options.some(option => option.value === effort))
50
+ throw new Error('Kimi configuration does not advertise this model/effort combination');
51
+ if (config.protocol !== 'kimi') {
52
+ throw new Error('This Kimi model protocol has no per-invocation exec effort control; use ACP live mode');
53
+ }
54
+ return { KIMI_MODEL_THINKING_EFFORT: effort };
55
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Legacy kimi preset-variant cleanup. Earlier versions of this bundle
3
+ * bootstrapped `standard-kimi`, `code-kimi`, `minimal-kimi`, and `kimi`
4
+ * preset variants into the user preset root so an official-style preset could
5
+ * carry the `subagent_kimi` tool row. The tool row now mounts at the profile
6
+ * root (see the bundle patch), so every preset delegates without variants;
7
+ * this module removes the leftovers once on startup. The repo is pre-release,
8
+ * so no compatibility path is kept — variants that still exist are deleted.
9
+ * @module @khorsheed/dsh-local-agent-kimi/preset-tools
10
+ */
11
+ import type { Context } from '@deepseek-ai/cordis';
12
+ /**
13
+ * Remove the legacy kimi preset variants from the user preset root. Uses the
14
+ * agent-presets service's remove (which also clears a default that named one
15
+ * of them), so a session later created under a different default no longer
16
+ * points at a deleted composition.
17
+ * @param ctx - host context carrying the agent-presets service.
18
+ */
19
+ export declare function removeLegacyVariants(ctx: Context): Promise<void>;
20
+ //# sourceMappingURL=preset-tools.d.ts.map
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Legacy kimi preset-variant cleanup. Earlier versions of this bundle
3
+ * bootstrapped `standard-kimi`, `code-kimi`, `minimal-kimi`, and `kimi`
4
+ * preset variants into the user preset root so an official-style preset could
5
+ * carry the `subagent_kimi` tool row. The tool row now mounts at the profile
6
+ * root (see the bundle patch), so every preset delegates without variants;
7
+ * this module removes the leftovers once on startup. The repo is pre-release,
8
+ * so no compatibility path is kept — variants that still exist are deleted.
9
+ * @module @khorsheed/dsh-local-agent-kimi/preset-tools
10
+ */
11
+ /** The variant preset ids earlier bundle versions bootstrapped. */
12
+ const LEGACY_VARIANTS = ['standard-kimi', 'code-kimi', 'minimal-kimi', 'kimi'];
13
+ /**
14
+ * Remove the legacy kimi preset variants from the user preset root. Uses the
15
+ * agent-presets service's remove (which also clears a default that named one
16
+ * of them), so a session later created under a different default no longer
17
+ * points at a deleted composition.
18
+ * @param ctx - host context carrying the agent-presets service.
19
+ */
20
+ export async function removeLegacyVariants(ctx) {
21
+ const presets = ctx.get('agentPresets');
22
+ if (presets === undefined)
23
+ return;
24
+ for (const id of LEGACY_VARIANTS) {
25
+ try {
26
+ await presets.remove(id);
27
+ }
28
+ catch (error) {
29
+ // A variant that does not exist (or was already removed) is the success
30
+ // case; anything else is unexpected and worth surfacing.
31
+ if (error.code === 'ENOENT')
32
+ continue;
33
+ throw error;
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Kimi Code scoped-home provisioning: a working `config.toml` (provider and
3
+ * model definitions) is required for the ACP server to authenticate. The
4
+ * scoped home is provisioned once — from the user's own config, redacted —
5
+ * so the scoped setup mirrors the user's models while carrying no secrets.
6
+ * Sign-out removes the credentials and OAuth cache so a later login can
7
+ * authorize a different account. The agent preset the delegation tool row
8
+ * needs is bootstrapped into the user preset root on first boot, so
9
+ * installing the bundle needs no manual copy step.
10
+ * @module @khorsheed/dsh-local-agent-kimi/provision
11
+ */
12
+ /**
13
+ * The managed provider's endpoint, written into a fresh scoped config and the
14
+ * baseline the eval snapshot compares against: a scoped config still pointing
15
+ * here routes through kimi's own service, i.e. no custom endpoint is in force.
16
+ */
17
+ export declare const KIMI_MANAGED_BASE_URL = "https://api.kimi.com/coding/v1";
18
+ /**
19
+ * The reasoning effort a fresh scoped config gets when the plugin config does
20
+ * not override it — the value the pre-config-item provisioning hardcoded, so
21
+ * the default is behavior-preserving.
22
+ */
23
+ export declare const DEFAULT_THINKING_EFFORT = "high";
24
+ /**
25
+ * Redact every api_key value so the scoped copy carries no secrets. The
26
+ * kimi-managed provider keeps an empty api_key (its auth routes through
27
+ * oauth), which is exactly what an empty value means here.
28
+ * @param config - the user's config text.
29
+ * @returns the same config with all api_key values blanked.
30
+ */
31
+ export declare function redactApiKeys(config: string): string;
32
+ /**
33
+ * Provision `<homeDir>/config.toml` unless one already exists. A present
34
+ * config is respected untouched (a person may have edited it); otherwise the
35
+ * user's own real config is copied redacted so the scoped home mirrors the
36
+ * user's models; with no real config, a minimal kimi-managed config is
37
+ * written. `thinkingEffort` feeds the minimal config's `[thinking] effort`
38
+ * (and the model's `default_effort`) — it applies at provision time only,
39
+ * never retroactively to an existing config.
40
+ * @param homeDir - the `kimi` harness's scoped home.
41
+ * @param model - the kimi-managed model id for a fresh minimal config.
42
+ * @param thinkingEffort - the reasoning effort a fresh minimal config pins.
43
+ * @returns true when a config was written, false when one already existed.
44
+ */
45
+ export declare function provisionKimiConfig(homeDir: string, model: string, thinkingEffort: string): Promise<boolean>;
46
+ /**
47
+ * Read the scoped config's effective LLM endpoint: the
48
+ * `[providers."managed:kimi-code"].base_url` key, if present. A missing or
49
+ * malformed config yields undefined (the CLI falls back to its default
50
+ * endpoint). Used only for diagnostics — the config itself is authoritative,
51
+ * and a user-edited value is respected untouched.
52
+ * @param homeDir - the `kimi` harness's scoped home.
53
+ * @returns the configured base URL, or undefined when absent/unreadable.
54
+ */
55
+ export declare function readKimiBaseUrl(homeDir: string): Promise<string | undefined>;
56
+ /**
57
+ * Read the scoped config's configured default model: the top-level
58
+ * `default_model` key — the selection a plain `kimi -p` round runs with. A
59
+ * missing or malformed config, or no key, yields undefined — the honest
60
+ * "unknown", never a guessed default. Read-only: the config is authoritative
61
+ * and a user-edited value is reported as-is.
62
+ * @param homeDir - the `kimi` harness's scoped home.
63
+ * @returns the configured model identifier, or undefined when none is set.
64
+ */
65
+ export declare function readKimiDefaultModel(homeDir: string): Promise<string | undefined>;
66
+ /**
67
+ * List the model identifiers the scoped config DEFINES: every
68
+ * `[models."…"]` table key (bare `[models.x]` keys included). This is the
69
+ * pickable vocabulary a model surface can offer without hardcoding a catalog —
70
+ * the same table `-m` resolves against. A missing or malformed config yields
71
+ * an empty list. Read-only: the config is authoritative.
72
+ * @param homeDir - the `kimi` harness's scoped home.
73
+ * @returns the defined model identifiers, in file order, deduplicated.
74
+ */
75
+ export declare function listKimiConfigModels(homeDir: string): Promise<string[]>;
76
+ /**
77
+ * Rewrite the scoped config's top-level `default_model` to `model`, so the
78
+ * NEXT process started under this home runs that model. This is the resident
79
+ * path's only lever: `kimi acp` takes no model flag (verified against kimi
80
+ * 0.39.1), while `kimi -p` takes `-m` — so the one-shot rounds pass the flag
81
+ * and the live driver writes the file before spawning its runtime.
82
+ *
83
+ * The write is surgical: the first top-level `default_model` assignment is
84
+ * replaced in place and everything else — model tables, providers, thinking
85
+ * effort, comments, key order — is left byte-identical. A config with no
86
+ * top-level `default_model` gains one as its first line; an absent or
87
+ * unreadable config is left alone (provisioning owns creation, and inventing
88
+ * a config here would hide a broken home).
89
+ * @param homeDir - the `kimi` harness's scoped home.
90
+ * @param model - the model identifier to pin as the default.
91
+ * @returns true when the file now names `model`, false when there was no
92
+ * config to write into.
93
+ */
94
+ export declare function writeKimiDefaultModel(homeDir: string, model: string): Promise<boolean>;
95
+ /**
96
+ * Read the scoped config's effective reasoning effort: the `[thinking] effort`
97
+ * key, falling back to a `[models."…"]` section's `default_effort` when the
98
+ * thinking table carries none (the CLI resolves effort the same way). A
99
+ * missing or malformed config, or no effort key anywhere, yields undefined —
100
+ * the honest "unknown", never a guessed default. Read-only: the config is
101
+ * authoritative, a user-edited value is reported as-is.
102
+ * @param homeDir - the `kimi` harness's scoped home.
103
+ * @returns the effective effort, or undefined when it cannot be determined.
104
+ */
105
+ export declare function readKimiReasoningEffort(homeDir: string): Promise<string | undefined>;
106
+ /**
107
+ * Whether the scoped config's permission rules auto-approve the subagent's
108
+ * tool use: the `Bash(*)` allow rule the provisioning gate
109
+ * ({@link ensureKimiPermissions}) keys on, detected with the exact same
110
+ * substring so the snapshot and the provisioning can never disagree about
111
+ * the same file.
112
+ * @param homeDir - the `kimi` harness's scoped home.
113
+ * @returns whether the auto-approve rule is present.
114
+ */
115
+ export declare function readKimiAutoApprove(homeDir: string): Promise<boolean>;
116
+ /**
117
+ * Ensure the scoped config allows the subagent's tool use. The one-shot
118
+ * `kimi -p` child uses the config's permission rules, so without a rule it
119
+ * answers without executing tools. Idempotent; an existing rule is respected.
120
+ * @param homeDir - the `kimi` harness's scoped home.
121
+ */
122
+ export declare function ensureKimiPermissions(homeDir: string): Promise<void>;
123
+ /**
124
+ * Sign out of the scoped account: remove the credentials and OAuth token
125
+ * cache, so `/<name> status` reports not authenticated and the next login
126
+ * authorizes a fresh account. The kimi CLI has no `logout` command, so the
127
+ * scoped files are the logout path.
128
+ * @param homeDir - the `kimi` harness's scoped home.
129
+ */
130
+ export declare function kimiLogout(homeDir: string): Promise<void>;
131
+ //# sourceMappingURL=provision.d.ts.map