pi-provider-kimi-code 0.5.1 → 0.6.1
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 +94 -46
- package/index.ts +310 -110
- package/package.json +8 -8
- package/src/config.ts +475 -72
- package/src/constants.ts +29 -11
- package/src/device.ts +14 -0
- package/src/models.ts +115 -51
- package/src/payload.ts +90 -47
- package/src/schema-dedup.ts +256 -0
- package/src/stream.ts +70 -17
- package/src/tools/common.ts +115 -0
- package/src/tools/datasource.ts +239 -0
- package/src/tools/moonshot.ts +22 -111
package/src/config.ts
CHANGED
|
@@ -1,31 +1,165 @@
|
|
|
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
|
+
|
|
6
|
+
export const KIMI_TOOL_NAMES = ["moonshot_search", "moonshot_fetch", "kimi_datasource"] as const;
|
|
7
|
+
|
|
8
|
+
export type KimiToolName = (typeof KIMI_TOOL_NAMES)[number];
|
|
9
|
+
|
|
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
|
+
}
|
|
40
|
+
|
|
4
41
|
export interface KimiCodeConfig {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
42
|
+
model: ModelConfig;
|
|
43
|
+
tools: Record<KimiToolName, { enabled: boolean; default_collapsed: boolean }>;
|
|
44
|
+
uploads: { thresholdBytes: number };
|
|
45
|
+
protocol: "openai" | "anthropic";
|
|
46
|
+
}
|
|
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
|
+
|
|
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
|
+
};
|
|
8
76
|
};
|
|
77
|
+
tools: Record<KimiToolName, { enabled: KimiConfigSource; default_collapsed: KimiConfigSource }>;
|
|
78
|
+
uploads: { thresholdBytes: KimiConfigSource };
|
|
79
|
+
protocol: KimiConfigSource;
|
|
9
80
|
}
|
|
10
81
|
|
|
11
82
|
export interface LoadKimiCodeConfigOptions {
|
|
12
83
|
cwd: string;
|
|
13
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
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function makeDefaultTools(): KimiCodeConfig["tools"] {
|
|
101
|
+
const tools = {} as KimiCodeConfig["tools"];
|
|
102
|
+
for (const name of KIMI_TOOL_NAMES) {
|
|
103
|
+
tools[name] = { enabled: false, default_collapsed: true };
|
|
104
|
+
}
|
|
105
|
+
return tools;
|
|
14
106
|
}
|
|
15
107
|
|
|
16
108
|
export const DEFAULT_KIMI_CODE_CONFIG: KimiCodeConfig = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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: {},
|
|
20
125
|
},
|
|
126
|
+
tools: makeDefaultTools(),
|
|
127
|
+
uploads: { thresholdBytes: 1048576 },
|
|
128
|
+
protocol: "openai",
|
|
21
129
|
};
|
|
22
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
|
+
|
|
23
149
|
export function getGlobalKimiCodeConfigPath(home: string): string {
|
|
24
|
-
return join(home, ".pi", "
|
|
150
|
+
return join(home, ".pi", "providers", PROVIDER_ID, "config.json");
|
|
25
151
|
}
|
|
26
152
|
|
|
27
153
|
export function getProjectKimiCodeConfigPath(cwd: string): string {
|
|
28
|
-
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;
|
|
29
163
|
}
|
|
30
164
|
|
|
31
165
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -33,101 +167,379 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
33
167
|
}
|
|
34
168
|
|
|
35
169
|
function readConfigFile(path: string): Record<string, unknown> {
|
|
170
|
+
if (!existsSync(path)) return {};
|
|
171
|
+
|
|
172
|
+
let contents: string;
|
|
36
173
|
try {
|
|
37
|
-
|
|
38
|
-
const parsed = JSON.parse(readFileSync(path, "utf8")) as unknown;
|
|
39
|
-
return isRecord(parsed) ? parsed : {};
|
|
174
|
+
contents = readFileSync(path, "utf8");
|
|
40
175
|
} catch (error) {
|
|
41
|
-
|
|
42
|
-
|
|
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
|
+
);
|
|
43
192
|
}
|
|
44
193
|
}
|
|
45
194
|
|
|
46
195
|
function readConfigFileQuiet(path: string): Record<string, unknown> {
|
|
47
196
|
try {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
} catch {
|
|
197
|
+
return readConfigFile(path);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.error(`[kimi-coding] failed to read config file ${path}:`, error);
|
|
52
200
|
return {};
|
|
53
201
|
}
|
|
54
202
|
}
|
|
55
203
|
|
|
56
|
-
function
|
|
57
|
-
base:
|
|
58
|
-
|
|
59
|
-
):
|
|
204
|
+
function mergeConfigPatch<T extends Record<string, unknown>>(
|
|
205
|
+
base: T,
|
|
206
|
+
patch: Record<string, unknown>,
|
|
207
|
+
): T {
|
|
60
208
|
const result: Record<string, unknown> = { ...base };
|
|
61
|
-
for (const [key, value] of Object.entries(
|
|
209
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
210
|
+
if (value === undefined) continue;
|
|
62
211
|
const current = result[key];
|
|
63
212
|
if (isRecord(current) && isRecord(value)) {
|
|
64
|
-
result[key] =
|
|
213
|
+
result[key] = mergeConfigPatch(current, value);
|
|
65
214
|
} else {
|
|
66
215
|
result[key] = value;
|
|
67
216
|
}
|
|
68
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
|
+
}
|
|
69
346
|
return result;
|
|
70
347
|
}
|
|
71
348
|
|
|
72
|
-
function
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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;
|
|
78
375
|
}
|
|
79
376
|
|
|
80
|
-
function
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
+
};
|
|
86
392
|
}
|
|
87
393
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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;
|
|
413
|
+
}
|
|
92
414
|
|
|
415
|
+
export function validateKimiCodeConfig(
|
|
416
|
+
raw: unknown,
|
|
417
|
+
configPath = "<kimi-code-config>",
|
|
418
|
+
): KimiCodeConfig {
|
|
419
|
+
const config = requireRecord(raw, configPath, "");
|
|
93
420
|
return {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
},
|
|
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
|
+
),
|
|
103
429
|
},
|
|
430
|
+
protocol: requireProtocol(config.protocol, configPath, "/protocol"),
|
|
104
431
|
};
|
|
105
432
|
}
|
|
106
433
|
|
|
107
|
-
|
|
108
|
-
|
|
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;
|
|
109
441
|
}
|
|
110
442
|
|
|
111
|
-
|
|
112
|
-
|
|
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;
|
|
449
|
+
}
|
|
450
|
+
return "default";
|
|
113
451
|
}
|
|
114
452
|
|
|
115
|
-
function
|
|
116
|
-
|
|
453
|
+
function buildSources(
|
|
454
|
+
layers: Array<{ source: KimiConfigSource; config: Record<string, unknown> }>,
|
|
455
|
+
): KimiCodeConfigSources {
|
|
456
|
+
const tools = {} as KimiCodeConfigSources["tools"];
|
|
457
|
+
for (const name of KIMI_TOOL_NAMES) {
|
|
458
|
+
tools[name] = {
|
|
459
|
+
enabled: sourceForPath(layers, ["tools", name, "enabled"]),
|
|
460
|
+
default_collapsed: sourceForPath(layers, ["tools", name, "default_collapsed"]),
|
|
461
|
+
};
|
|
462
|
+
}
|
|
117
463
|
return {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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"]),
|
|
126
475
|
},
|
|
127
476
|
},
|
|
477
|
+
tools,
|
|
478
|
+
uploads: { thresholdBytes: sourceForPath(layers, ["uploads", "thresholdBytes"]) },
|
|
479
|
+
protocol: sourceForPath(layers, ["protocol"]),
|
|
128
480
|
};
|
|
129
481
|
}
|
|
130
482
|
|
|
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
|
+
];
|
|
495
|
+
}
|
|
496
|
+
|
|
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);
|
|
509
|
+
}
|
|
510
|
+
|
|
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
|
+
);
|
|
541
|
+
}
|
|
542
|
+
|
|
131
543
|
export function saveProjectKimiCodeConfig(cwd: string, config: KimiCodeConfig): void {
|
|
132
544
|
saveKimiCodeConfigFile(getProjectKimiCodeConfigPath(cwd), config);
|
|
133
545
|
}
|
|
@@ -137,15 +549,6 @@ export function saveHomeKimiCodeConfig(home: string, config: KimiCodeConfig): vo
|
|
|
137
549
|
}
|
|
138
550
|
|
|
139
551
|
function saveKimiCodeConfigFile(path: string, config: KimiCodeConfig): void {
|
|
140
|
-
const raw = readConfigFileQuiet(path);
|
|
141
|
-
const next = {
|
|
142
|
-
...raw,
|
|
143
|
-
tools: {
|
|
144
|
-
...(isRecord(raw.tools) ? raw.tools : {}),
|
|
145
|
-
moonshot_search: { ...config.tools.moonshot_search },
|
|
146
|
-
moonshot_fetch: { ...config.tools.moonshot_fetch },
|
|
147
|
-
},
|
|
148
|
-
};
|
|
149
552
|
mkdirSync(dirname(path), { recursive: true });
|
|
150
|
-
writeFileSync(path, `${JSON.stringify(
|
|
553
|
+
writeFileSync(path, `${JSON.stringify(validateKimiCodeConfig(config, path), null, 2)}\n`, "utf8");
|
|
151
554
|
}
|
package/src/constants.ts
CHANGED
|
@@ -9,20 +9,37 @@ import { join } from "node:path";
|
|
|
9
9
|
export const CLIENT_ID = "17e5f671-d194-4dfb-9706-5516cb48c098";
|
|
10
10
|
export const DEFAULT_OAUTH_HOST = "https://auth.kimi.com";
|
|
11
11
|
|
|
12
|
+
export type KimiWireProtocol = "openai" | "anthropic";
|
|
13
|
+
|
|
12
14
|
// KIMI_CODE_PROTOCOL supports two values: "openai" (default) and "anthropic".
|
|
13
|
-
export const
|
|
15
|
+
export const ENV_KIMI_CODE_PROTOCOL: KimiWireProtocol =
|
|
16
|
+
process.env.KIMI_CODE_PROTOCOL === "anthropic" ? "anthropic" : "openai";
|
|
17
|
+
|
|
18
|
+
export const IS_OPENAI_PROTOCOL = ENV_KIMI_CODE_PROTOCOL === "openai";
|
|
19
|
+
|
|
20
|
+
export function getApiProtocol(
|
|
21
|
+
protocol: KimiWireProtocol,
|
|
22
|
+
): "openai-completions" | "anthropic-messages" {
|
|
23
|
+
return protocol === "openai" ? "openai-completions" : "anthropic-messages";
|
|
24
|
+
}
|
|
14
25
|
|
|
15
|
-
export const PROTOCOL =
|
|
26
|
+
export const PROTOCOL = getApiProtocol(ENV_KIMI_CODE_PROTOCOL);
|
|
27
|
+
|
|
28
|
+
export function getKimiApiType(
|
|
29
|
+
protocol: KimiWireProtocol,
|
|
30
|
+
): "kimi-openai-completions" | "kimi-anthropic-messages" {
|
|
31
|
+
return protocol === "openai" ? "kimi-openai-completions" : "kimi-anthropic-messages";
|
|
32
|
+
}
|
|
16
33
|
|
|
17
34
|
// Use a custom api identifier so this provider never conflicts with the
|
|
18
35
|
// built-in "anthropic-messages" or "openai-completions" stream handlers.
|
|
19
|
-
export const KIMI_API_TYPE =
|
|
20
|
-
|
|
21
|
-
|
|
36
|
+
export const KIMI_API_TYPE = getKimiApiType(ENV_KIMI_CODE_PROTOCOL);
|
|
37
|
+
|
|
38
|
+
export function getDefaultBaseUrl(protocol: KimiWireProtocol): string {
|
|
39
|
+
return protocol === "openai" ? "https://api.kimi.com/coding/v1" : "https://api.kimi.com/coding";
|
|
40
|
+
}
|
|
22
41
|
|
|
23
|
-
export const DEFAULT_BASE_URL =
|
|
24
|
-
? "https://api.kimi.com/coding/v1"
|
|
25
|
-
: "https://api.kimi.com/coding";
|
|
42
|
+
export const DEFAULT_BASE_URL = getDefaultBaseUrl(ENV_KIMI_CODE_PROTOCOL);
|
|
26
43
|
|
|
27
44
|
export const KIMI_CODE_VERSION = "0.6.0";
|
|
28
45
|
export const KIMI_CODE_USER_AGENT = `kimi-code-cli/${KIMI_CODE_VERSION}`;
|
|
@@ -47,7 +64,8 @@ export function getOAuthHost(): string {
|
|
|
47
64
|
return value.trim() || DEFAULT_OAUTH_HOST;
|
|
48
65
|
}
|
|
49
66
|
|
|
50
|
-
export function getBaseUrl(): string {
|
|
51
|
-
const
|
|
52
|
-
|
|
67
|
+
export function getBaseUrl(protocol: KimiWireProtocol = ENV_KIMI_CODE_PROTOCOL): string {
|
|
68
|
+
const defaultBaseUrl = getDefaultBaseUrl(protocol);
|
|
69
|
+
const value = process.env.KIMI_CODE_BASE_URL || process.env.KIMI_BASE_URL || defaultBaseUrl;
|
|
70
|
+
return value.trim() || defaultBaseUrl;
|
|
53
71
|
}
|