pi-provider-kimi-code 0.6.0 → 0.6.2
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.
- package/README.md +34 -9
- package/index.ts +255 -69
- package/package.json +1 -1
- package/src/config.ts +450 -92
- package/src/constants.ts +36 -13
- package/src/device.ts +16 -2
- package/src/models.ts +97 -52
- package/src/payload.ts +81 -50
- package/src/schema-dedup.ts +256 -0
- package/src/stream.ts +54 -20
- package/src/tools/datasource.ts +52 -4
package/src/config.ts
CHANGED
|
@@ -1,23 +1,100 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
|
|
4
|
+
import { PROVIDER_ID } from "./constants.ts";
|
|
5
|
+
|
|
4
6
|
export const KIMI_TOOL_NAMES = ["moonshot_search", "moonshot_fetch", "kimi_datasource"] as const;
|
|
5
7
|
|
|
6
8
|
export type KimiToolName = (typeof KIMI_TOOL_NAMES)[number];
|
|
7
9
|
|
|
8
|
-
export type KimiConfigSource = "project" | "home" | "default";
|
|
10
|
+
export type KimiConfigSource = "runtime" | "env" | "project" | "home" | "default";
|
|
11
|
+
|
|
12
|
+
export type KimiInputModality = "text" | "image" | "video";
|
|
13
|
+
|
|
14
|
+
export interface ModelReasoningEntry {
|
|
15
|
+
effort: string | null;
|
|
16
|
+
enabled: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type ModelReasoningMap = Record<string, ModelReasoningEntry>;
|
|
20
|
+
|
|
21
|
+
export interface ModelGeneration {
|
|
22
|
+
temperature?: number;
|
|
23
|
+
topP?: number;
|
|
24
|
+
maxCompletionTokens?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ModelConfig {
|
|
28
|
+
contextWindow: number;
|
|
29
|
+
maxTokens: number;
|
|
30
|
+
input: KimiInputModality[];
|
|
31
|
+
reasoning: boolean;
|
|
32
|
+
reasoningMap: ModelReasoningMap;
|
|
33
|
+
thinkingKeep: "all" | "last" | "none" | null;
|
|
34
|
+
generation: ModelGeneration;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface KimiResolvedModelConfig extends ModelConfig {
|
|
38
|
+
supportsThinkingType?: "only" | "no" | "both";
|
|
39
|
+
}
|
|
9
40
|
|
|
10
41
|
export interface KimiCodeConfig {
|
|
42
|
+
model: ModelConfig;
|
|
11
43
|
tools: Record<KimiToolName, { enabled: boolean; default_collapsed: boolean }>;
|
|
44
|
+
uploads: { thresholdBytes: number };
|
|
45
|
+
protocol: "openai" | "anthropic";
|
|
12
46
|
}
|
|
13
47
|
|
|
48
|
+
export type KimiCodeConfigPatch = Partial<{
|
|
49
|
+
model: Partial<{
|
|
50
|
+
contextWindow: unknown;
|
|
51
|
+
maxTokens: unknown;
|
|
52
|
+
input: unknown;
|
|
53
|
+
reasoning: unknown;
|
|
54
|
+
reasoningMap: unknown;
|
|
55
|
+
thinkingKeep: unknown;
|
|
56
|
+
generation: unknown;
|
|
57
|
+
}>;
|
|
58
|
+
tools: Partial<Record<KimiToolName, Partial<{ enabled: unknown; default_collapsed: unknown }>>>;
|
|
59
|
+
uploads: Partial<{ thresholdBytes: unknown }>;
|
|
60
|
+
protocol: unknown;
|
|
61
|
+
}>;
|
|
62
|
+
|
|
14
63
|
export interface KimiCodeConfigSources {
|
|
64
|
+
model: {
|
|
65
|
+
contextWindow: KimiConfigSource;
|
|
66
|
+
maxTokens: KimiConfigSource;
|
|
67
|
+
input: KimiConfigSource;
|
|
68
|
+
reasoning: KimiConfigSource;
|
|
69
|
+
reasoningMap: KimiConfigSource;
|
|
70
|
+
thinkingKeep: KimiConfigSource;
|
|
71
|
+
generation: {
|
|
72
|
+
temperature: KimiConfigSource;
|
|
73
|
+
topP: KimiConfigSource;
|
|
74
|
+
maxCompletionTokens: KimiConfigSource;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
15
77
|
tools: Record<KimiToolName, { enabled: KimiConfigSource; default_collapsed: KimiConfigSource }>;
|
|
78
|
+
uploads: { thresholdBytes: KimiConfigSource };
|
|
79
|
+
protocol: KimiConfigSource;
|
|
16
80
|
}
|
|
17
81
|
|
|
18
82
|
export interface LoadKimiCodeConfigOptions {
|
|
19
83
|
cwd: string;
|
|
20
84
|
home: string;
|
|
85
|
+
env?: NodeJS.ProcessEnv;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export class ConfigError extends Error {
|
|
89
|
+
public readonly configPath: string;
|
|
90
|
+
public readonly pointer?: string;
|
|
91
|
+
|
|
92
|
+
constructor(message: string, configPath: string, pointer?: string) {
|
|
93
|
+
super(pointer ? `${configPath}${pointer}: ${message}` : `${configPath}: ${message}`);
|
|
94
|
+
this.name = "ConfigError";
|
|
95
|
+
this.configPath = configPath;
|
|
96
|
+
this.pointer = pointer;
|
|
97
|
+
}
|
|
21
98
|
}
|
|
22
99
|
|
|
23
100
|
function makeDefaultTools(): KimiCodeConfig["tools"] {
|
|
@@ -29,15 +106,60 @@ function makeDefaultTools(): KimiCodeConfig["tools"] {
|
|
|
29
106
|
}
|
|
30
107
|
|
|
31
108
|
export const DEFAULT_KIMI_CODE_CONFIG: KimiCodeConfig = {
|
|
109
|
+
model: {
|
|
110
|
+
contextWindow: 262144,
|
|
111
|
+
maxTokens: 32000,
|
|
112
|
+
input: ["text", "image"],
|
|
113
|
+
reasoning: true,
|
|
114
|
+
reasoningMap: {
|
|
115
|
+
none: { effort: null, enabled: false },
|
|
116
|
+
off: { effort: null, enabled: false },
|
|
117
|
+
minimal: { effort: "low", enabled: true },
|
|
118
|
+
low: { effort: "low", enabled: true },
|
|
119
|
+
medium: { effort: "medium", enabled: true },
|
|
120
|
+
high: { effort: "high", enabled: true },
|
|
121
|
+
xhigh: { effort: "high", enabled: true },
|
|
122
|
+
},
|
|
123
|
+
thinkingKeep: "all",
|
|
124
|
+
generation: {},
|
|
125
|
+
},
|
|
32
126
|
tools: makeDefaultTools(),
|
|
127
|
+
uploads: { thresholdBytes: 1048576 },
|
|
128
|
+
protocol: "openai",
|
|
33
129
|
};
|
|
34
130
|
|
|
131
|
+
let runtimeOverride: KimiCodeConfigPatch = {};
|
|
132
|
+
|
|
133
|
+
export function setRuntimeKimiCodeConfigOverride(patch: KimiCodeConfigPatch): void {
|
|
134
|
+
runtimeOverride = mergeConfigPatch(runtimeOverride, patch);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function replaceRuntimeKimiCodeConfigOverride(patch: KimiCodeConfigPatch): void {
|
|
138
|
+
runtimeOverride = patch;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function clearRuntimeKimiCodeConfigOverride(): void {
|
|
142
|
+
runtimeOverride = {};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function getRuntimeKimiCodeConfigOverride(): KimiCodeConfigPatch {
|
|
146
|
+
return clone(runtimeOverride) as KimiCodeConfigPatch;
|
|
147
|
+
}
|
|
148
|
+
|
|
35
149
|
export function getGlobalKimiCodeConfigPath(home: string): string {
|
|
36
|
-
return join(home, ".pi", "
|
|
150
|
+
return join(home, ".pi", "providers", PROVIDER_ID, "config.json");
|
|
37
151
|
}
|
|
38
152
|
|
|
39
153
|
export function getProjectKimiCodeConfigPath(cwd: string): string {
|
|
40
|
-
return join(cwd, ".pi", "
|
|
154
|
+
return join(cwd, ".pi", "providers", PROVIDER_ID, "config.json");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function kimiCodeConfigPath(home: string): string {
|
|
158
|
+
return getGlobalKimiCodeConfigPath(home);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function clone<T>(value: T): T {
|
|
162
|
+
return JSON.parse(JSON.stringify(value)) as T;
|
|
41
163
|
}
|
|
42
164
|
|
|
43
165
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -45,130 +167,377 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
45
167
|
}
|
|
46
168
|
|
|
47
169
|
function readConfigFile(path: string): Record<string, unknown> {
|
|
170
|
+
if (!existsSync(path)) return {};
|
|
171
|
+
|
|
172
|
+
let contents: string;
|
|
48
173
|
try {
|
|
49
|
-
|
|
50
|
-
const parsed = JSON.parse(readFileSync(path, "utf8")) as unknown;
|
|
51
|
-
return isRecord(parsed) ? parsed : {};
|
|
174
|
+
contents = readFileSync(path, "utf8");
|
|
52
175
|
} catch (error) {
|
|
53
|
-
|
|
54
|
-
|
|
176
|
+
throw new ConfigError(
|
|
177
|
+
`failed to read config: ${error instanceof Error ? error.message : String(error)}`,
|
|
178
|
+
path,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const parsed = JSON.parse(contents) as unknown;
|
|
184
|
+
if (isRecord(parsed)) return parsed;
|
|
185
|
+
throw new ConfigError("config file must be a JSON object", path);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
if (error instanceof ConfigError) throw error;
|
|
188
|
+
throw new ConfigError(
|
|
189
|
+
`invalid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
190
|
+
path,
|
|
191
|
+
);
|
|
55
192
|
}
|
|
56
193
|
}
|
|
57
194
|
|
|
58
195
|
function readConfigFileQuiet(path: string): Record<string, unknown> {
|
|
59
196
|
try {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
} catch {
|
|
197
|
+
return readConfigFile(path);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.error(`[kimi-coding] failed to read config file ${path}:`, error);
|
|
64
200
|
return {};
|
|
65
201
|
}
|
|
66
202
|
}
|
|
67
203
|
|
|
68
|
-
function
|
|
69
|
-
base:
|
|
70
|
-
|
|
71
|
-
):
|
|
204
|
+
function mergeConfigPatch<T extends Record<string, unknown>>(
|
|
205
|
+
base: T,
|
|
206
|
+
patch: Record<string, unknown>,
|
|
207
|
+
): T {
|
|
72
208
|
const result: Record<string, unknown> = { ...base };
|
|
73
|
-
for (const [key, value] of Object.entries(
|
|
209
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
210
|
+
if (value === undefined) continue;
|
|
74
211
|
const current = result[key];
|
|
75
212
|
if (isRecord(current) && isRecord(value)) {
|
|
76
|
-
result[key] =
|
|
213
|
+
result[key] = mergeConfigPatch(current, value);
|
|
77
214
|
} else {
|
|
78
215
|
result[key] = value;
|
|
79
216
|
}
|
|
80
217
|
}
|
|
218
|
+
return result as T;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function envConfigPatch(env: NodeJS.ProcessEnv): KimiCodeConfigPatch {
|
|
222
|
+
const patch: KimiCodeConfigPatch = {};
|
|
223
|
+
|
|
224
|
+
const maxContext = parsePositiveNumber(env.KIMI_MODEL_MAX_CONTEXT_SIZE);
|
|
225
|
+
if (maxContext !== undefined) patch.model = { ...patch.model, contextWindow: maxContext };
|
|
226
|
+
|
|
227
|
+
const temp = parseFiniteNumber(env.KIMI_MODEL_TEMPERATURE);
|
|
228
|
+
const topP = parseFiniteNumber(env.KIMI_MODEL_TOP_P);
|
|
229
|
+
const maxCompletionTokens = parsePositiveNumber(env.KIMI_MODEL_MAX_COMPLETION_TOKENS);
|
|
230
|
+
const thinkingKeep = env.KIMI_MODEL_THINKING_KEEP?.trim();
|
|
231
|
+
if (
|
|
232
|
+
temp !== undefined ||
|
|
233
|
+
topP !== undefined ||
|
|
234
|
+
maxCompletionTokens !== undefined ||
|
|
235
|
+
thinkingKeep
|
|
236
|
+
) {
|
|
237
|
+
patch.model = {
|
|
238
|
+
...patch.model,
|
|
239
|
+
...(thinkingKeep ? { thinkingKeep } : {}),
|
|
240
|
+
generation: {
|
|
241
|
+
...(temp !== undefined ? { temperature: temp } : {}),
|
|
242
|
+
...(topP !== undefined ? { topP } : {}),
|
|
243
|
+
...(maxCompletionTokens !== undefined ? { maxCompletionTokens } : {}),
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const uploadThreshold = parsePositiveNumber(env.KIMI_CODE_UPLOAD_THRESHOLD_BYTES);
|
|
249
|
+
if (uploadThreshold !== undefined) patch.uploads = { thresholdBytes: uploadThreshold };
|
|
250
|
+
|
|
251
|
+
const protocol = env.KIMI_CODE_PROTOCOL?.trim();
|
|
252
|
+
if (protocol) patch.protocol = protocol;
|
|
253
|
+
|
|
254
|
+
const capabilities = env.KIMI_MODEL_CAPABILITIES;
|
|
255
|
+
if (capabilities) {
|
|
256
|
+
const caps = new Set(
|
|
257
|
+
capabilities
|
|
258
|
+
.split(",")
|
|
259
|
+
.map((cap) => cap.trim().toLowerCase())
|
|
260
|
+
.filter(Boolean),
|
|
261
|
+
);
|
|
262
|
+
patch.model = {
|
|
263
|
+
...patch.model,
|
|
264
|
+
reasoning: caps.has("thinking") || caps.has("always_thinking"),
|
|
265
|
+
input: caps.has("image_in") ? ["text", "image"] : ["text"],
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return patch;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function parsePositiveNumber(value: string | undefined): number | undefined {
|
|
273
|
+
if (!value) return undefined;
|
|
274
|
+
const parsed = Number(value);
|
|
275
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function parseFiniteNumber(value: string | undefined): number | undefined {
|
|
279
|
+
if (!value) return undefined;
|
|
280
|
+
const parsed = Number(value);
|
|
281
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function fail(configPath: string, pointer: string, message: string): never {
|
|
285
|
+
throw new ConfigError(message, configPath, pointer);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function requireRecord(raw: unknown, configPath: string, pointer: string): Record<string, unknown> {
|
|
289
|
+
if (isRecord(raw)) return raw;
|
|
290
|
+
return fail(
|
|
291
|
+
configPath,
|
|
292
|
+
pointer,
|
|
293
|
+
`expected an object, got ${Array.isArray(raw) ? "array" : typeof raw}`,
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function requirePositiveNumber(raw: unknown, configPath: string, pointer: string): number {
|
|
298
|
+
if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) return raw;
|
|
299
|
+
return fail(configPath, pointer, `expected a positive number, got ${JSON.stringify(raw)}`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function requireBoolean(raw: unknown, configPath: string, pointer: string): boolean {
|
|
303
|
+
if (typeof raw === "boolean") return raw;
|
|
304
|
+
return fail(configPath, pointer, `expected a boolean, got ${JSON.stringify(raw)}`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function requireProtocol(
|
|
308
|
+
raw: unknown,
|
|
309
|
+
configPath: string,
|
|
310
|
+
pointer: string,
|
|
311
|
+
): KimiCodeConfig["protocol"] {
|
|
312
|
+
if (raw === "openai" || raw === "anthropic") return raw;
|
|
313
|
+
return fail(configPath, pointer, `expected "openai" | "anthropic", got ${JSON.stringify(raw)}`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function requireInputArray(raw: unknown, configPath: string, pointer: string): KimiInputModality[] {
|
|
317
|
+
const valid = new Set(["text", "image", "video"]);
|
|
318
|
+
if (!Array.isArray(raw)) return fail(configPath, pointer, `expected an array, got ${typeof raw}`);
|
|
319
|
+
if (raw.length === 0) return fail(configPath, pointer, "expected a non-empty array");
|
|
320
|
+
return raw.map((value, index) => {
|
|
321
|
+
if (typeof value !== "string" || !valid.has(value)) {
|
|
322
|
+
return fail(
|
|
323
|
+
configPath,
|
|
324
|
+
`${pointer}/${index}`,
|
|
325
|
+
`expected one of text, image, video, got ${JSON.stringify(value)}`,
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
return value as KimiInputModality;
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function requireReasoningMap(raw: unknown, configPath: string, pointer: string): ModelReasoningMap {
|
|
333
|
+
const record = requireRecord(raw, configPath, pointer);
|
|
334
|
+
const result: ModelReasoningMap = {};
|
|
335
|
+
for (const [key, value] of Object.entries(record)) {
|
|
336
|
+
const entryPointer = `${pointer}/${key}`;
|
|
337
|
+
const entry = requireRecord(value, configPath, entryPointer);
|
|
338
|
+
result[key] = {
|
|
339
|
+
effort:
|
|
340
|
+
entry.effort === null || typeof entry.effort === "string"
|
|
341
|
+
? entry.effort
|
|
342
|
+
: fail(configPath, `${entryPointer}/effort`, "expected a string or null"),
|
|
343
|
+
enabled: requireBoolean(entry.enabled, configPath, `${entryPointer}/enabled`),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
81
346
|
return result;
|
|
82
347
|
}
|
|
83
348
|
|
|
84
|
-
function
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
349
|
+
function requireThinkingKeep(
|
|
350
|
+
raw: unknown,
|
|
351
|
+
configPath: string,
|
|
352
|
+
pointer: string,
|
|
353
|
+
): ModelConfig["thinkingKeep"] {
|
|
354
|
+
if (raw === null || raw === "all" || raw === "last" || raw === "none") return raw;
|
|
355
|
+
return fail(
|
|
356
|
+
configPath,
|
|
357
|
+
pointer,
|
|
358
|
+
`expected "all" | "last" | "none" | null, got ${JSON.stringify(raw)}`,
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function requireGeneration(raw: unknown, configPath: string, pointer: string): ModelGeneration {
|
|
363
|
+
const record = requireRecord(raw, configPath, pointer);
|
|
364
|
+
const result: ModelGeneration = {};
|
|
365
|
+
const knownKeys = ["temperature", "topP", "maxCompletionTokens"] as const;
|
|
366
|
+
for (const key of knownKeys) {
|
|
367
|
+
const value = record[key];
|
|
368
|
+
if (value === undefined || value === null) continue;
|
|
369
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
370
|
+
fail(configPath, `${pointer}/${key}`, `expected a number, got ${JSON.stringify(value)}`);
|
|
371
|
+
}
|
|
372
|
+
result[key] = value;
|
|
373
|
+
}
|
|
374
|
+
return result;
|
|
90
375
|
}
|
|
91
376
|
|
|
92
|
-
function
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
377
|
+
function validateModelConfig(raw: unknown, configPath: string, pointer: string): ModelConfig {
|
|
378
|
+
const model = requireRecord(raw, configPath, pointer);
|
|
379
|
+
return {
|
|
380
|
+
contextWindow: requirePositiveNumber(
|
|
381
|
+
model.contextWindow,
|
|
382
|
+
configPath,
|
|
383
|
+
`${pointer}/contextWindow`,
|
|
384
|
+
),
|
|
385
|
+
maxTokens: requirePositiveNumber(model.maxTokens, configPath, `${pointer}/maxTokens`),
|
|
386
|
+
input: requireInputArray(model.input, configPath, `${pointer}/input`),
|
|
387
|
+
reasoning: requireBoolean(model.reasoning, configPath, `${pointer}/reasoning`),
|
|
388
|
+
reasoningMap: requireReasoningMap(model.reasoningMap, configPath, `${pointer}/reasoningMap`),
|
|
389
|
+
thinkingKeep: requireThinkingKeep(model.thinkingKeep, configPath, `${pointer}/thinkingKeep`),
|
|
390
|
+
generation: requireGeneration(model.generation, configPath, `${pointer}/generation`),
|
|
391
|
+
};
|
|
98
392
|
}
|
|
99
393
|
|
|
100
|
-
function
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
):
|
|
105
|
-
const tools =
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
394
|
+
function validateToolsConfig(
|
|
395
|
+
raw: unknown,
|
|
396
|
+
configPath: string,
|
|
397
|
+
pointer: string,
|
|
398
|
+
): KimiCodeConfig["tools"] {
|
|
399
|
+
const tools = requireRecord(raw, configPath, pointer);
|
|
400
|
+
const result = {} as KimiCodeConfig["tools"];
|
|
401
|
+
for (const name of KIMI_TOOL_NAMES) {
|
|
402
|
+
const tool = requireRecord(tools[name], configPath, `${pointer}/${name}`);
|
|
403
|
+
result[name] = {
|
|
404
|
+
enabled: requireBoolean(tool.enabled, configPath, `${pointer}/${name}/enabled`),
|
|
405
|
+
default_collapsed: requireBoolean(
|
|
406
|
+
tool.default_collapsed,
|
|
407
|
+
configPath,
|
|
408
|
+
`${pointer}/${name}/default_collapsed`,
|
|
409
|
+
),
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
return result;
|
|
110
413
|
}
|
|
111
414
|
|
|
112
|
-
function
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
):
|
|
415
|
+
export function validateKimiCodeConfig(
|
|
416
|
+
raw: unknown,
|
|
417
|
+
configPath = "<kimi-code-config>",
|
|
418
|
+
): KimiCodeConfig {
|
|
419
|
+
const config = requireRecord(raw, configPath, "");
|
|
116
420
|
return {
|
|
117
|
-
|
|
118
|
-
|
|
421
|
+
model: validateModelConfig(config.model, configPath, "/model"),
|
|
422
|
+
tools: validateToolsConfig(config.tools, configPath, "/tools"),
|
|
423
|
+
uploads: {
|
|
424
|
+
thresholdBytes: requirePositiveNumber(
|
|
425
|
+
requireRecord(config.uploads, configPath, "/uploads").thresholdBytes,
|
|
426
|
+
configPath,
|
|
427
|
+
"/uploads/thresholdBytes",
|
|
428
|
+
),
|
|
429
|
+
},
|
|
430
|
+
protocol: requireProtocol(config.protocol, configPath, "/protocol"),
|
|
119
431
|
};
|
|
120
432
|
}
|
|
121
433
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
434
|
+
function hasPath(config: Record<string, unknown>, path: readonly string[]): boolean {
|
|
435
|
+
let current: unknown = config;
|
|
436
|
+
for (const key of path) {
|
|
437
|
+
if (!isRecord(current) || !Object.hasOwn(current, key)) return false;
|
|
438
|
+
current = current[key];
|
|
439
|
+
}
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
126
442
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
443
|
+
function sourceForPath(
|
|
444
|
+
layers: Array<{ source: KimiConfigSource; config: Record<string, unknown> }>,
|
|
445
|
+
path: readonly string[],
|
|
446
|
+
): KimiConfigSource {
|
|
447
|
+
for (let i = layers.length - 1; i >= 0; i--) {
|
|
448
|
+
if (hasPath(layers[i].config, path)) return layers[i].source;
|
|
130
449
|
}
|
|
131
|
-
return
|
|
450
|
+
return "default";
|
|
132
451
|
}
|
|
133
452
|
|
|
134
|
-
|
|
135
|
-
|
|
453
|
+
function buildSources(
|
|
454
|
+
layers: Array<{ source: KimiConfigSource; config: Record<string, unknown> }>,
|
|
136
455
|
): KimiCodeConfigSources {
|
|
137
|
-
const homeConfig = readConfigFileQuiet(getGlobalKimiCodeConfigPath(options.home));
|
|
138
|
-
const projectConfig = readConfigFileQuiet(getProjectKimiCodeConfigPath(options.cwd));
|
|
139
456
|
const tools = {} as KimiCodeConfigSources["tools"];
|
|
140
457
|
for (const name of KIMI_TOOL_NAMES) {
|
|
141
458
|
tools[name] = {
|
|
142
|
-
enabled:
|
|
143
|
-
|
|
144
|
-
: hasToolField(homeConfig, name, "enabled")
|
|
145
|
-
? "home"
|
|
146
|
-
: "default",
|
|
147
|
-
default_collapsed: hasToolField(projectConfig, name, "default_collapsed")
|
|
148
|
-
? "project"
|
|
149
|
-
: hasToolField(homeConfig, name, "default_collapsed")
|
|
150
|
-
? "home"
|
|
151
|
-
: "default",
|
|
459
|
+
enabled: sourceForPath(layers, ["tools", name, "enabled"]),
|
|
460
|
+
default_collapsed: sourceForPath(layers, ["tools", name, "default_collapsed"]),
|
|
152
461
|
};
|
|
153
462
|
}
|
|
154
|
-
return {
|
|
463
|
+
return {
|
|
464
|
+
model: {
|
|
465
|
+
contextWindow: sourceForPath(layers, ["model", "contextWindow"]),
|
|
466
|
+
maxTokens: sourceForPath(layers, ["model", "maxTokens"]),
|
|
467
|
+
input: sourceForPath(layers, ["model", "input"]),
|
|
468
|
+
reasoning: sourceForPath(layers, ["model", "reasoning"]),
|
|
469
|
+
reasoningMap: sourceForPath(layers, ["model", "reasoningMap"]),
|
|
470
|
+
thinkingKeep: sourceForPath(layers, ["model", "thinkingKeep"]),
|
|
471
|
+
generation: {
|
|
472
|
+
temperature: sourceForPath(layers, ["model", "generation", "temperature"]),
|
|
473
|
+
topP: sourceForPath(layers, ["model", "generation", "topP"]),
|
|
474
|
+
maxCompletionTokens: sourceForPath(layers, ["model", "generation", "maxCompletionTokens"]),
|
|
475
|
+
},
|
|
476
|
+
},
|
|
477
|
+
tools,
|
|
478
|
+
uploads: { thresholdBytes: sourceForPath(layers, ["uploads", "thresholdBytes"]) },
|
|
479
|
+
protocol: sourceForPath(layers, ["protocol"]),
|
|
480
|
+
};
|
|
155
481
|
}
|
|
156
482
|
|
|
157
|
-
|
|
158
|
-
|
|
483
|
+
function loadLayers(
|
|
484
|
+
options: LoadKimiCodeConfigOptions,
|
|
485
|
+
): Array<{ source: KimiConfigSource; config: Record<string, unknown> }> {
|
|
486
|
+
return [
|
|
487
|
+
{ source: "home", config: readConfigFile(getGlobalKimiCodeConfigPath(options.home)) },
|
|
488
|
+
{ source: "project", config: readConfigFile(getProjectKimiCodeConfigPath(options.cwd)) },
|
|
489
|
+
{
|
|
490
|
+
source: "env",
|
|
491
|
+
config: envConfigPatch(options.env ?? process.env) as Record<string, unknown>,
|
|
492
|
+
},
|
|
493
|
+
{ source: "runtime", config: runtimeOverride as Record<string, unknown> },
|
|
494
|
+
];
|
|
159
495
|
}
|
|
160
496
|
|
|
161
|
-
export function
|
|
162
|
-
|
|
497
|
+
export function loadKimiCodeConfig(
|
|
498
|
+
options: LoadKimiCodeConfigOptions,
|
|
499
|
+
overrides?: KimiCodeConfigPatch,
|
|
500
|
+
): KimiCodeConfig {
|
|
501
|
+
let merged = clone(DEFAULT_KIMI_CODE_CONFIG) as unknown as Record<string, unknown>;
|
|
502
|
+
for (const layer of loadLayers(options)) {
|
|
503
|
+
merged = mergeConfigPatch(merged, layer.config);
|
|
504
|
+
}
|
|
505
|
+
if (overrides) {
|
|
506
|
+
merged = mergeConfigPatch(merged, overrides as Record<string, unknown>);
|
|
507
|
+
}
|
|
508
|
+
return validateKimiCodeConfig(merged);
|
|
163
509
|
}
|
|
164
510
|
|
|
165
|
-
function
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
511
|
+
export function loadKimiCodeConfigSources(
|
|
512
|
+
options: LoadKimiCodeConfigOptions,
|
|
513
|
+
): KimiCodeConfigSources {
|
|
514
|
+
return buildSources(loadLayers(options));
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
export function ensureKimiCodeConfig(pathOrHome: string): boolean {
|
|
518
|
+
const path = pathOrHome.endsWith(".json") ? pathOrHome : getGlobalKimiCodeConfigPath(pathOrHome);
|
|
519
|
+
if (existsSync(path)) return false;
|
|
520
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
521
|
+
writeFileSync(path, `${JSON.stringify(DEFAULT_KIMI_CODE_CONFIG, null, 2)}\n`, "utf8");
|
|
522
|
+
return true;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
export function loadProjectKimiCodeConfig(cwd: string): KimiCodeConfig {
|
|
526
|
+
return validateKimiCodeConfig(
|
|
527
|
+
mergeConfigPatch(
|
|
528
|
+
clone(DEFAULT_KIMI_CODE_CONFIG) as unknown as Record<string, unknown>,
|
|
529
|
+
readConfigFileQuiet(getProjectKimiCodeConfigPath(cwd)),
|
|
530
|
+
),
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
export function loadHomeKimiCodeConfig(home: string): KimiCodeConfig {
|
|
535
|
+
return validateKimiCodeConfig(
|
|
536
|
+
mergeConfigPatch(
|
|
537
|
+
clone(DEFAULT_KIMI_CODE_CONFIG) as unknown as Record<string, unknown>,
|
|
538
|
+
readConfigFileQuiet(getGlobalKimiCodeConfigPath(home)),
|
|
539
|
+
),
|
|
540
|
+
);
|
|
172
541
|
}
|
|
173
542
|
|
|
174
543
|
export function saveProjectKimiCodeConfig(cwd: string, config: KimiCodeConfig): void {
|
|
@@ -180,17 +549,6 @@ export function saveHomeKimiCodeConfig(home: string, config: KimiCodeConfig): vo
|
|
|
180
549
|
}
|
|
181
550
|
|
|
182
551
|
function saveKimiCodeConfigFile(path: string, config: KimiCodeConfig): void {
|
|
183
|
-
const raw = readConfigFileQuiet(path);
|
|
184
|
-
const tools: Record<string, unknown> = {
|
|
185
|
-
...(isRecord(raw.tools) ? raw.tools : {}),
|
|
186
|
-
};
|
|
187
|
-
for (const name of KIMI_TOOL_NAMES) {
|
|
188
|
-
tools[name] = { ...config.tools[name] };
|
|
189
|
-
}
|
|
190
|
-
const next = {
|
|
191
|
-
...raw,
|
|
192
|
-
tools,
|
|
193
|
-
};
|
|
194
552
|
mkdirSync(dirname(path), { recursive: true });
|
|
195
|
-
writeFileSync(path, `${JSON.stringify(
|
|
553
|
+
writeFileSync(path, `${JSON.stringify(validateKimiCodeConfig(config, path), null, 2)}\n`, "utf8");
|
|
196
554
|
}
|