@di-code/coding-agent 0.1.3 → 0.1.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.
- package/README.md +91 -30
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +96 -17
- package/dist/cli.js.map +1 -1
- package/dist/core/clipboard-image.d.ts +3 -3
- package/dist/core/clipboard-image.d.ts.map +1 -1
- package/dist/core/clipboard-image.js +26 -13
- package/dist/core/clipboard-image.js.map +1 -1
- package/dist/core/context-builder.d.ts.map +1 -1
- package/dist/core/context-builder.js +6 -2
- package/dist/core/context-builder.js.map +1 -1
- package/dist/core/image-input.d.ts +1 -1
- package/dist/core/image-input.d.ts.map +1 -1
- package/dist/core/image-input.js +55 -10
- package/dist/core/image-input.js.map +1 -1
- package/dist/core/resources/loader.d.ts.map +1 -1
- package/dist/core/resources/loader.js +15 -42
- package/dist/core/resources/loader.js.map +1 -1
- package/dist/core/resources/types.d.ts +6 -9
- package/dist/core/resources/types.d.ts.map +1 -1
- package/dist/core/resources/types.js.map +1 -1
- package/dist/core/session/session-manager.d.ts +12 -1
- package/dist/core/session/session-manager.d.ts.map +1 -1
- package/dist/core/session/session-manager.js +66 -3
- package/dist/core/session/session-manager.js.map +1 -1
- package/dist/core/session/session-storage.d.ts.map +1 -1
- package/dist/core/session/session-storage.js +30 -15
- package/dist/core/session/session-storage.js.map +1 -1
- package/dist/core/session/types.d.ts +6 -1
- package/dist/core/session/types.d.ts.map +1 -1
- package/dist/core/session/types.js +1 -1
- package/dist/core/session/types.js.map +1 -1
- package/dist/core/session.d.ts +38 -3
- package/dist/core/session.d.ts.map +1 -1
- package/dist/core/session.js +167 -9
- package/dist/core/session.js.map +1 -1
- package/dist/core/system-prompt.js +2 -2
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +2 -2
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +25 -0
- package/dist/core/tools/edit-diff.d.ts.map +1 -0
- package/dist/core/tools/edit-diff.js +136 -0
- package/dist/core/tools/edit-diff.js.map +1 -0
- package/dist/core/tools/edit.d.ts +13 -4
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +64 -30
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/file-search.d.ts +14 -0
- package/dist/core/tools/file-search.d.ts.map +1 -0
- package/dist/core/tools/file-search.js +99 -0
- package/dist/core/tools/file-search.js.map +1 -0
- package/dist/core/tools/glob.d.ts +11 -0
- package/dist/core/tools/glob.d.ts.map +1 -0
- package/dist/core/tools/glob.js +44 -0
- package/dist/core/tools/glob.js.map +1 -0
- package/dist/core/tools/grep.d.ts +13 -0
- package/dist/core/tools/grep.d.ts.map +1 -0
- package/dist/core/tools/grep.js +67 -0
- package/dist/core/tools/grep.js.map +1 -0
- package/dist/core/tools/load-skill.d.ts +10 -0
- package/dist/core/tools/load-skill.d.ts.map +1 -0
- package/dist/core/tools/load-skill.js +22 -0
- package/dist/core/tools/load-skill.js.map +1 -0
- package/dist/core/tools/path-boundary.d.ts +2 -0
- package/dist/core/tools/path-boundary.d.ts.map +1 -1
- package/dist/core/tools/path-boundary.js +4 -0
- package/dist/core/tools/path-boundary.js.map +1 -1
- package/dist/core/tools/read.d.ts +2 -2
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/write.d.ts +2 -2
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js.map +1 -1
- package/dist/core/user-data.d.ts +3 -0
- package/dist/core/user-data.d.ts.map +1 -0
- package/dist/core/user-data.js +8 -0
- package/dist/core/user-data.js.map +1 -0
- package/dist/entry.js +22 -2
- package/dist/entry.js.map +1 -1
- package/dist/extensions/runtime.js +2 -2
- package/dist/extensions/runtime.js.map +1 -1
- package/dist/i18n.d.ts +5 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +223 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +20 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +272 -17
- package/dist/main.js.map +1 -1
- package/dist/mcp/config.d.ts +33 -0
- package/dist/mcp/config.d.ts.map +1 -0
- package/dist/mcp/config.js +211 -0
- package/dist/mcp/config.js.map +1 -0
- package/dist/mcp/loader.d.ts +21 -0
- package/dist/mcp/loader.d.ts.map +1 -0
- package/dist/mcp/loader.js +60 -0
- package/dist/mcp/loader.js.map +1 -0
- package/dist/mcp/tool-adapter.d.ts +5 -0
- package/dist/mcp/tool-adapter.d.ts.map +1 -0
- package/dist/mcp/tool-adapter.js +149 -0
- package/dist/mcp/tool-adapter.js.map +1 -0
- package/dist/modes/interactive-components.d.ts +2 -0
- package/dist/modes/interactive-components.d.ts.map +1 -1
- package/dist/modes/interactive-components.js +55 -31
- package/dist/modes/interactive-components.js.map +1 -1
- package/dist/modes/interactive-diff.d.ts +8 -0
- package/dist/modes/interactive-diff.d.ts.map +1 -0
- package/dist/modes/interactive-diff.js +78 -0
- package/dist/modes/interactive-diff.js.map +1 -0
- package/dist/modes/interactive-layout.d.ts +4 -0
- package/dist/modes/interactive-layout.d.ts.map +1 -1
- package/dist/modes/interactive-layout.js +4 -0
- package/dist/modes/interactive-layout.js.map +1 -1
- package/dist/modes/interactive-state.d.ts +5 -0
- package/dist/modes/interactive-state.d.ts.map +1 -1
- package/dist/modes/interactive-state.js +65 -11
- package/dist/modes/interactive-state.js.map +1 -1
- package/dist/modes/interactive.d.ts +19 -0
- package/dist/modes/interactive.d.ts.map +1 -1
- package/dist/modes/interactive.js +351 -52
- package/dist/modes/interactive.js.map +1 -1
- package/dist/modes/tree-selector.d.ts +30 -0
- package/dist/modes/tree-selector.d.ts.map +1 -0
- package/dist/modes/tree-selector.js +205 -0
- package/dist/modes/tree-selector.js.map +1 -0
- package/dist/plugins/loader.d.ts +19 -1
- package/dist/plugins/loader.d.ts.map +1 -1
- package/dist/plugins/loader.js +21 -2
- package/dist/plugins/loader.js.map +1 -1
- package/dist/provider-onboarding.d.ts +8 -1
- package/dist/provider-onboarding.d.ts.map +1 -1
- package/dist/provider-onboarding.js +160 -40
- package/dist/provider-onboarding.js.map +1 -1
- package/dist/rpc-entry.js +1 -1
- package/dist/rpc-entry.js.map +1 -1
- package/dist/startup.d.ts +34 -3
- package/dist/startup.d.ts.map +1 -1
- package/dist/startup.js +407 -87
- package/dist/startup.js.map +1 -1
- package/package.json +7 -4
package/dist/startup.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"startup.d.ts","sourceRoot":"","sources":["../src/startup.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"startup.d.ts","sourceRoot":"","sources":["../src/startup.ts"],"names":[],"mappings":"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"}
|
package/dist/startup.js
CHANGED
|
@@ -1,119 +1,397 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
2
3
|
import { join } from "node:path";
|
|
3
|
-
import { createAnthropicProvider, createDeepSeekProvider, createFauxProvider, createOpenAIProvider, createZhipuProvider, } from "@di-code/ai";
|
|
4
|
+
import { createAnthropicProvider, createDeepSeekProvider, createFauxProvider, createKimiProvider, createOpenAIChatCompletionsProvider, createOpenAIProvider, createZhipuProvider, findBuiltinModel, } from "@di-code/ai";
|
|
5
|
+
import { isLocale } from "./i18n.js";
|
|
4
6
|
const FAUX_RESPONSES = [
|
|
5
|
-
{
|
|
7
|
+
{
|
|
8
|
+
type: "success",
|
|
9
|
+
content: [
|
|
10
|
+
{
|
|
11
|
+
type: "text",
|
|
12
|
+
text: "你好,我是di-code,一个面向终端的 TypeScript AI Coding Agent,支持多 Provider 流式对话、工具调用、JSONL 会话持久化、交互式 TUI、插件扩展与 JSONL RPC 集成。",
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
{ type: "success", content: [{ type: "text", text: "当前回复为faux数据" }] },
|
|
6
17
|
];
|
|
7
|
-
const
|
|
18
|
+
const SETTINGS_FILE_NAME = "settings.json";
|
|
19
|
+
const SETTINGS_PATH = join(".di-code", SETTINGS_FILE_NAME);
|
|
8
20
|
export function resolveStartupArgs(args) {
|
|
9
21
|
return args.length === 0 ? ["--interactive"] : args;
|
|
10
22
|
}
|
|
11
|
-
function optionalString(value, path) {
|
|
23
|
+
function optionalString(value, path, settingsPath) {
|
|
12
24
|
if (value === undefined)
|
|
13
25
|
return undefined;
|
|
14
26
|
if (typeof value !== "string")
|
|
15
|
-
throw new Error(`${
|
|
27
|
+
throw new Error(`${settingsPath}: ${path} must be a string`);
|
|
16
28
|
return value;
|
|
17
29
|
}
|
|
18
|
-
function requiredString(value, path) {
|
|
19
|
-
const result = optionalString(value, path);
|
|
30
|
+
function requiredString(value, path, settingsPath = SETTINGS_PATH) {
|
|
31
|
+
const result = optionalString(value, path, settingsPath);
|
|
20
32
|
if (!result?.trim())
|
|
21
|
-
throw new Error(`${
|
|
33
|
+
throw new Error(`${settingsPath}: ${path} must be a non-empty string`);
|
|
22
34
|
return result.trim();
|
|
23
35
|
}
|
|
24
|
-
function positiveInteger(value, path, fallback) {
|
|
36
|
+
function positiveInteger(value, path, fallback, settingsPath) {
|
|
25
37
|
const result = value ?? fallback;
|
|
26
38
|
if (!Number.isInteger(result) || result <= 0) {
|
|
27
|
-
throw new Error(`${
|
|
39
|
+
throw new Error(`${settingsPath}: ${path} must be a positive integer`);
|
|
28
40
|
}
|
|
29
41
|
return result;
|
|
30
42
|
}
|
|
31
|
-
function nonNegativeNumber(value, path, fallback) {
|
|
43
|
+
function nonNegativeNumber(value, path, fallback, settingsPath) {
|
|
32
44
|
const result = value ?? fallback;
|
|
33
45
|
if (typeof result !== "number" || !Number.isFinite(result) || result < 0) {
|
|
34
|
-
throw new Error(`${
|
|
46
|
+
throw new Error(`${settingsPath}: ${path} must be a non-negative finite number`);
|
|
35
47
|
}
|
|
36
48
|
return result;
|
|
37
49
|
}
|
|
38
|
-
function
|
|
50
|
+
function parseChatCompletionsCompat(value, path, settingsPath) {
|
|
51
|
+
if (value === undefined)
|
|
52
|
+
return undefined;
|
|
53
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
54
|
+
throw new Error(`${settingsPath}: ${path} must be an object`);
|
|
55
|
+
}
|
|
56
|
+
const compat = value;
|
|
57
|
+
const optionalBoolean = (field) => {
|
|
58
|
+
const candidate = compat[field];
|
|
59
|
+
if (candidate === undefined)
|
|
60
|
+
return undefined;
|
|
61
|
+
if (typeof candidate !== "boolean")
|
|
62
|
+
throw new Error(`${settingsPath}: ${path}.${field} must be a boolean`);
|
|
63
|
+
return candidate;
|
|
64
|
+
};
|
|
65
|
+
const maxTokensField = compat.maxTokensField;
|
|
66
|
+
if (maxTokensField !== undefined && maxTokensField !== "max_tokens" && maxTokensField !== "max_completion_tokens") {
|
|
67
|
+
throw new Error(`${settingsPath}: ${path}.maxTokensField is invalid`);
|
|
68
|
+
}
|
|
69
|
+
const thinkingFormat = compat.thinkingFormat;
|
|
70
|
+
if (thinkingFormat !== undefined &&
|
|
71
|
+
thinkingFormat !== "zai" &&
|
|
72
|
+
thinkingFormat !== "deepseek" &&
|
|
73
|
+
thinkingFormat !== "kimi") {
|
|
74
|
+
throw new Error(`${settingsPath}: ${path}.thinkingFormat is invalid`);
|
|
75
|
+
}
|
|
76
|
+
const supportsUsageInStreaming = optionalBoolean("supportsUsageInStreaming");
|
|
77
|
+
const supportsReasoningEffort = optionalBoolean("supportsReasoningEffort");
|
|
78
|
+
const zaiToolStream = optionalBoolean("zaiToolStream");
|
|
79
|
+
return {
|
|
80
|
+
...(maxTokensField === undefined ? {} : { maxTokensField }),
|
|
81
|
+
...(thinkingFormat === undefined ? {} : { thinkingFormat }),
|
|
82
|
+
...(supportsUsageInStreaming === undefined ? {} : { supportsUsageInStreaming }),
|
|
83
|
+
...(supportsReasoningEffort === undefined ? {} : { supportsReasoningEffort }),
|
|
84
|
+
...(zaiToolStream === undefined ? {} : { zaiToolStream }),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function parseModels(value, path, providerId, providerApi, providerBaseUrl, settingsPath) {
|
|
39
88
|
if (value === undefined)
|
|
40
89
|
return undefined;
|
|
41
90
|
if (!Array.isArray(value))
|
|
42
|
-
throw new Error(`${
|
|
91
|
+
throw new Error(`${settingsPath}: ${path} must be an array`);
|
|
43
92
|
return value.map((entry, index) => {
|
|
44
93
|
const modelPath = `${path}[${index}]`;
|
|
45
94
|
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
46
|
-
throw new Error(`${
|
|
95
|
+
throw new Error(`${settingsPath}: ${modelPath} must be an object`);
|
|
47
96
|
}
|
|
48
97
|
const model = entry;
|
|
49
98
|
const input = model.input ?? ["text"];
|
|
50
99
|
if (!Array.isArray(input) || input.length === 0 || input.some((item) => item !== "text" && item !== "image")) {
|
|
51
|
-
throw new Error(`${
|
|
100
|
+
throw new Error(`${settingsPath}: ${modelPath}.input must contain text and/or image`);
|
|
52
101
|
}
|
|
53
102
|
const cost = model.cost ?? {};
|
|
54
103
|
if (typeof cost !== "object" || cost === null || Array.isArray(cost)) {
|
|
55
|
-
throw new Error(`${
|
|
104
|
+
throw new Error(`${settingsPath}: ${modelPath}.cost must be an object`);
|
|
56
105
|
}
|
|
57
106
|
const prices = cost;
|
|
58
107
|
if (model.reasoning !== undefined && typeof model.reasoning !== "boolean") {
|
|
59
|
-
throw new Error(`${
|
|
108
|
+
throw new Error(`${settingsPath}: ${modelPath}.reasoning must be a boolean`);
|
|
109
|
+
}
|
|
110
|
+
const configuredReasoningEfforts = model.reasoningEfforts;
|
|
111
|
+
if (configuredReasoningEfforts !== undefined) {
|
|
112
|
+
if (!Array.isArray(configuredReasoningEfforts) || configuredReasoningEfforts.length === 0) {
|
|
113
|
+
throw new Error(`${settingsPath}: ${modelPath}.reasoningEfforts must be a non-empty array`);
|
|
114
|
+
}
|
|
115
|
+
if (model.reasoning !== true ||
|
|
116
|
+
configuredReasoningEfforts.some((effort) => effort !== "low" && effort !== "medium" && effort !== "high" && effort !== "max") ||
|
|
117
|
+
new Set(configuredReasoningEfforts).size !== configuredReasoningEfforts.length) {
|
|
118
|
+
throw new Error(`${settingsPath}: ${modelPath}.reasoningEfforts requires reasoning=true and unique low, medium, high, or max values`);
|
|
119
|
+
}
|
|
60
120
|
}
|
|
61
121
|
if (model.cacheRetention !== undefined && model.cacheRetention !== "long") {
|
|
62
|
-
throw new Error(`${
|
|
122
|
+
throw new Error(`${settingsPath}: ${modelPath}.cacheRetention must be "long" when provided`);
|
|
63
123
|
}
|
|
64
124
|
if (model.sessionAffinity !== undefined && model.sessionAffinity !== "codex") {
|
|
65
|
-
throw new Error(`${
|
|
125
|
+
throw new Error(`${settingsPath}: ${modelPath}.sessionAffinity must be "codex" when provided`);
|
|
66
126
|
}
|
|
67
|
-
const api = optionalString(model.api, `${modelPath}.api
|
|
127
|
+
const api = optionalString(model.api, `${modelPath}.api`, settingsPath) ?? providerApi;
|
|
68
128
|
if (!api)
|
|
69
|
-
throw new Error(`${
|
|
70
|
-
|
|
71
|
-
|
|
129
|
+
throw new Error(`${settingsPath}: ${modelPath}.api is required`);
|
|
130
|
+
if (!["faux", "openai-responses", "openai-chat-completions", "anthropic-messages"].includes(api)) {
|
|
131
|
+
throw new Error(`${settingsPath}: ${modelPath}.api is unsupported`);
|
|
132
|
+
}
|
|
133
|
+
const typedApi = api;
|
|
134
|
+
const reasoningEfforts = configuredReasoningEfforts === undefined && model.reasoning === true && api === "openai-responses"
|
|
135
|
+
? ["low", "medium", "high"]
|
|
136
|
+
: configuredReasoningEfforts === undefined
|
|
137
|
+
? undefined
|
|
138
|
+
: [...configuredReasoningEfforts];
|
|
139
|
+
const baseUrl = optionalString(model.baseUrl, `${modelPath}.baseUrl`, settingsPath) ?? providerBaseUrl;
|
|
140
|
+
const id = requiredString(model.id, `${modelPath}.id`, settingsPath);
|
|
141
|
+
const chatCompletionsCompat = parseChatCompletionsCompat(model.chatCompletionsCompat, `${modelPath}.chatCompletionsCompat`, settingsPath);
|
|
142
|
+
if (chatCompletionsCompat !== undefined && typedApi !== "openai-chat-completions") {
|
|
143
|
+
throw new Error(`${settingsPath}: ${modelPath}.chatCompletionsCompat requires api "openai-chat-completions"`);
|
|
144
|
+
}
|
|
72
145
|
return {
|
|
73
146
|
id,
|
|
74
|
-
name: requiredString(model.name ?? id, `${modelPath}.name
|
|
147
|
+
name: requiredString(model.name ?? id, `${modelPath}.name`, settingsPath),
|
|
75
148
|
provider: providerId,
|
|
76
|
-
api,
|
|
149
|
+
api: typedApi,
|
|
77
150
|
...(baseUrl === undefined ? {} : { baseUrl }),
|
|
78
151
|
input: [...input],
|
|
79
152
|
reasoning: model.reasoning ?? false,
|
|
153
|
+
...(reasoningEfforts ? { reasoningEfforts } : {}),
|
|
154
|
+
...(chatCompletionsCompat ? { chatCompletionsCompat } : {}),
|
|
80
155
|
...(model.cacheRetention === "long" ? { cacheRetention: "long" } : {}),
|
|
81
156
|
...(model.sessionAffinity === "codex" ? { sessionAffinity: "codex" } : {}),
|
|
82
|
-
contextWindow: positiveInteger(model.contextWindow, `${modelPath}.contextWindow`, 128_000),
|
|
83
|
-
maxOutputTokens: positiveInteger(model.maxTokens ?? model.maxOutputTokens, `${modelPath}.maxTokens`, 16_384),
|
|
157
|
+
contextWindow: positiveInteger(model.contextWindow, `${modelPath}.contextWindow`, 128_000, settingsPath),
|
|
158
|
+
maxOutputTokens: positiveInteger(model.maxTokens ?? model.maxOutputTokens, `${modelPath}.maxTokens`, 16_384, settingsPath),
|
|
84
159
|
cost: {
|
|
85
|
-
input: nonNegativeNumber(prices.input, `${modelPath}.cost.input`, 0) / 1_000_000,
|
|
86
|
-
output: nonNegativeNumber(prices.output, `${modelPath}.cost.output`, 0) / 1_000_000,
|
|
87
|
-
cacheRead: nonNegativeNumber(prices.cacheRead, `${modelPath}.cost.cacheRead`, 0) / 1_000_000,
|
|
88
|
-
cacheWrite: nonNegativeNumber(prices.cacheWrite, `${modelPath}.cost.cacheWrite`, 0) / 1_000_000,
|
|
160
|
+
input: nonNegativeNumber(prices.input, `${modelPath}.cost.input`, 0, settingsPath) / 1_000_000,
|
|
161
|
+
output: nonNegativeNumber(prices.output, `${modelPath}.cost.output`, 0, settingsPath) / 1_000_000,
|
|
162
|
+
cacheRead: nonNegativeNumber(prices.cacheRead, `${modelPath}.cost.cacheRead`, 0, settingsPath) / 1_000_000,
|
|
163
|
+
cacheWrite: nonNegativeNumber(prices.cacheWrite, `${modelPath}.cost.cacheWrite`, 0, settingsPath) / 1_000_000,
|
|
89
164
|
},
|
|
90
165
|
};
|
|
91
166
|
});
|
|
92
167
|
}
|
|
93
|
-
|
|
168
|
+
/** Validates a Custom onboarding endpoint without issuing a network request. */
|
|
169
|
+
export function validateCustomBaseUrl(value) {
|
|
170
|
+
const baseUrl = value.trim();
|
|
171
|
+
if (!baseUrl)
|
|
172
|
+
throw new Error("Base URL cannot be empty.");
|
|
173
|
+
let url;
|
|
174
|
+
try {
|
|
175
|
+
url = new URL(baseUrl);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
throw new Error("Base URL must be an absolute http or https URL.");
|
|
179
|
+
}
|
|
180
|
+
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
181
|
+
throw new Error("Base URL must use http or https.");
|
|
182
|
+
if (url.username || url.password || url.search || url.hash) {
|
|
183
|
+
throw new Error("Base URL must not contain credentials, query, or hash.");
|
|
184
|
+
}
|
|
185
|
+
if (baseUrl.endsWith("/"))
|
|
186
|
+
throw new Error("Base URL must not end with /.");
|
|
187
|
+
return baseUrl;
|
|
188
|
+
}
|
|
189
|
+
function requireCustomValue(value, field) {
|
|
190
|
+
const trimmed = value.trim();
|
|
191
|
+
if (!trimmed)
|
|
192
|
+
throw new Error(`${field === "apiKey" ? "API key" : "Model ID"} cannot be empty.`);
|
|
193
|
+
return trimmed;
|
|
194
|
+
}
|
|
195
|
+
/** Builds the single user-managed Custom provider from validated onboarding input. */
|
|
196
|
+
export function createCustomProviderConfiguration(input) {
|
|
197
|
+
const baseUrl = validateCustomBaseUrl(input.baseUrl);
|
|
198
|
+
const modelId = requireCustomValue(input.modelId, "modelId");
|
|
199
|
+
const apiKey = requireCustomValue(input.apiKey, "apiKey");
|
|
200
|
+
const known = findBuiltinModel(input.api, modelId);
|
|
201
|
+
const model = known
|
|
202
|
+
? {
|
|
203
|
+
...known,
|
|
204
|
+
id: modelId,
|
|
205
|
+
provider: "custom",
|
|
206
|
+
api: input.api,
|
|
207
|
+
baseUrl,
|
|
208
|
+
}
|
|
209
|
+
: {
|
|
210
|
+
id: modelId,
|
|
211
|
+
name: modelId,
|
|
212
|
+
provider: "custom",
|
|
213
|
+
api: input.api,
|
|
214
|
+
baseUrl,
|
|
215
|
+
input: ["text"],
|
|
216
|
+
reasoning: false,
|
|
217
|
+
contextWindow: 128_000,
|
|
218
|
+
maxOutputTokens: 16_384,
|
|
219
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
220
|
+
};
|
|
221
|
+
return { id: "custom", name: "Custom", api: input.api, apiKey, baseUrl, models: [model] };
|
|
222
|
+
}
|
|
223
|
+
function serializeModel(model) {
|
|
224
|
+
return {
|
|
225
|
+
id: model.id,
|
|
226
|
+
name: model.name,
|
|
227
|
+
input: model.input,
|
|
228
|
+
reasoning: model.reasoning,
|
|
229
|
+
...(model.reasoningEfforts ? { reasoningEfforts: model.reasoningEfforts } : {}),
|
|
230
|
+
...(model.chatCompletionsCompat ? { chatCompletionsCompat: model.chatCompletionsCompat } : {}),
|
|
231
|
+
...(model.cacheRetention ? { cacheRetention: model.cacheRetention } : {}),
|
|
232
|
+
...(model.sessionAffinity ? { sessionAffinity: model.sessionAffinity } : {}),
|
|
233
|
+
contextWindow: model.contextWindow,
|
|
234
|
+
maxTokens: model.maxOutputTokens,
|
|
235
|
+
cost: Object.fromEntries(Object.entries(model.cost).map(([key, value]) => [key, value * 1_000_000])),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function parseSettings(value, settingsPath) {
|
|
94
239
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
95
|
-
throw new Error(`${
|
|
240
|
+
throw new Error(`${settingsPath}: root value must be an object`);
|
|
96
241
|
}
|
|
97
242
|
const record = value;
|
|
98
243
|
const providersValue = record.providers;
|
|
99
244
|
if (typeof providersValue !== "object" || providersValue === null || Array.isArray(providersValue)) {
|
|
100
|
-
throw new Error(`${
|
|
245
|
+
throw new Error(`${settingsPath}: providers must be an object`);
|
|
101
246
|
}
|
|
102
247
|
const providers = {};
|
|
103
248
|
for (const [id, entry] of Object.entries(providersValue)) {
|
|
104
249
|
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
105
|
-
throw new Error(`${
|
|
250
|
+
throw new Error(`${settingsPath}: providers.${id} must be an object`);
|
|
106
251
|
}
|
|
107
252
|
const provider = entry;
|
|
108
253
|
providers[id] = {
|
|
109
|
-
name: optionalString(provider.name, `providers.${id}.name
|
|
110
|
-
baseUrl: optionalString(provider.baseUrl, `providers.${id}.baseUrl
|
|
111
|
-
apiKey: optionalString(provider.apiKey, `providers.${id}.apiKey
|
|
112
|
-
api: optionalString(provider.api, `providers.${id}.api
|
|
254
|
+
name: optionalString(provider.name, `providers.${id}.name`, settingsPath),
|
|
255
|
+
baseUrl: optionalString(provider.baseUrl, `providers.${id}.baseUrl`, settingsPath),
|
|
256
|
+
apiKey: optionalString(provider.apiKey, `providers.${id}.apiKey`, settingsPath),
|
|
257
|
+
api: optionalString(provider.api, `providers.${id}.api`, settingsPath),
|
|
113
258
|
models: provider.models,
|
|
114
259
|
};
|
|
115
260
|
}
|
|
116
|
-
|
|
261
|
+
const defaultProvider = record.defaultProvider === undefined
|
|
262
|
+
? undefined
|
|
263
|
+
: requiredString(record.defaultProvider, "defaultProvider", settingsPath);
|
|
264
|
+
const defaultModel = record.defaultModel === undefined ? undefined : requiredString(record.defaultModel, "defaultModel", settingsPath);
|
|
265
|
+
if (record.locale !== undefined && !isLocale(record.locale)) {
|
|
266
|
+
throw new Error(`${settingsPath}: locale must be "en" or "zh-CN"`);
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
providers,
|
|
270
|
+
...(defaultProvider === undefined ? {} : { defaultProvider }),
|
|
271
|
+
...(defaultModel === undefined ? {} : { defaultModel }),
|
|
272
|
+
...(record.locale === undefined ? {} : { locale: record.locale }),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
async function readSettingsFile(path, displayPath) {
|
|
276
|
+
try {
|
|
277
|
+
const source = await readFile(path, "utf8");
|
|
278
|
+
if (source.trim().length === 0)
|
|
279
|
+
return undefined;
|
|
280
|
+
return parseSettings(JSON.parse(source), displayPath);
|
|
281
|
+
}
|
|
282
|
+
catch (cause) {
|
|
283
|
+
if (cause instanceof Error && "code" in cause && cause.code === "ENOENT")
|
|
284
|
+
return undefined;
|
|
285
|
+
if (cause instanceof SyntaxError)
|
|
286
|
+
throw new Error(`${displayPath}: invalid JSON`, { cause });
|
|
287
|
+
throw cause;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function mergeSettings(globalSettings, projectSettings) {
|
|
291
|
+
const globalProviders = globalSettings?.providers ?? {};
|
|
292
|
+
const projectProviders = projectSettings?.providers ?? {};
|
|
293
|
+
const providers = { ...globalProviders };
|
|
294
|
+
for (const [id, projectProvider] of Object.entries(projectProviders)) {
|
|
295
|
+
providers[id] = {
|
|
296
|
+
...globalProviders[id],
|
|
297
|
+
...(projectProvider.name === undefined ? {} : { name: projectProvider.name }),
|
|
298
|
+
...(projectProvider.baseUrl === undefined ? {} : { baseUrl: projectProvider.baseUrl }),
|
|
299
|
+
...(projectProvider.apiKey === undefined ? {} : { apiKey: projectProvider.apiKey }),
|
|
300
|
+
...(projectProvider.api === undefined ? {} : { api: projectProvider.api }),
|
|
301
|
+
...(projectProvider.models === undefined ? {} : { models: projectProvider.models }),
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
const defaultProvider = projectSettings?.defaultProvider ?? globalSettings?.defaultProvider;
|
|
305
|
+
const defaultModel = projectSettings?.defaultModel ??
|
|
306
|
+
(projectSettings?.defaultProvider === undefined ? globalSettings?.defaultModel : undefined);
|
|
307
|
+
const locale = globalSettings?.locale;
|
|
308
|
+
return {
|
|
309
|
+
providers,
|
|
310
|
+
...(defaultProvider === undefined ? {} : { defaultProvider }),
|
|
311
|
+
...(defaultModel === undefined ? {} : { defaultModel }),
|
|
312
|
+
...(locale === undefined ? {} : { locale }),
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
/** Persists a user-level terminal language preference without changing project settings. */
|
|
316
|
+
export async function saveGlobalLocale(agentDir, locale) {
|
|
317
|
+
const settingsFilePath = join(agentDir, SETTINGS_FILE_NAME);
|
|
318
|
+
const existingSettings = await readSettingsFile(settingsFilePath, settingsFilePath);
|
|
319
|
+
const settings = {
|
|
320
|
+
providers: existingSettings?.providers ?? {},
|
|
321
|
+
...(existingSettings?.defaultProvider === undefined ? {} : { defaultProvider: existingSettings.defaultProvider }),
|
|
322
|
+
...(existingSettings?.defaultModel === undefined ? {} : { defaultModel: existingSettings.defaultModel }),
|
|
323
|
+
locale,
|
|
324
|
+
};
|
|
325
|
+
await mkdir(agentDir, { recursive: true, mode: 0o700 });
|
|
326
|
+
await writeFile(settingsFilePath, `${JSON.stringify(settings, null, "\t")}\n`, { encoding: "utf8", mode: 0o600 });
|
|
327
|
+
}
|
|
328
|
+
export async function saveGlobalProviderApiKey(agentDir, providerId, api, apiKey, modelId) {
|
|
329
|
+
const settingsFilePath = join(agentDir, SETTINGS_FILE_NAME);
|
|
330
|
+
const trimmedApiKey = apiKey.trim();
|
|
331
|
+
if (!trimmedApiKey)
|
|
332
|
+
throw new Error(`${settingsFilePath}: apiKey must be a non-empty string`);
|
|
333
|
+
const existingSettings = await readSettingsFile(settingsFilePath, settingsFilePath);
|
|
334
|
+
const settings = mergeSettings(existingSettings, {
|
|
335
|
+
providers: { [providerId]: { api, apiKey: trimmedApiKey } },
|
|
336
|
+
defaultProvider: providerId,
|
|
337
|
+
...(modelId === undefined ? {} : { defaultModel: modelId }),
|
|
338
|
+
});
|
|
339
|
+
await mkdir(agentDir, { recursive: true, mode: 0o700 });
|
|
340
|
+
await writeFile(settingsFilePath, `${JSON.stringify(settings, null, "\t")}\n`, { encoding: "utf8", mode: 0o600 });
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Replaces the user-managed `custom` provider while retaining other global
|
|
344
|
+
* providers and preferences. The key is intentionally stored only here, never
|
|
345
|
+
* in project settings or process environment.
|
|
346
|
+
*/
|
|
347
|
+
export async function saveGlobalCustomProvider(agentDir, input) {
|
|
348
|
+
const configuration = createCustomProviderConfiguration(input);
|
|
349
|
+
const settingsFilePath = join(agentDir, SETTINGS_FILE_NAME);
|
|
350
|
+
const existingSettings = await readSettingsFile(settingsFilePath, settingsFilePath);
|
|
351
|
+
const settings = mergeSettings(existingSettings, {
|
|
352
|
+
providers: {
|
|
353
|
+
custom: {
|
|
354
|
+
name: configuration.name,
|
|
355
|
+
api: configuration.api,
|
|
356
|
+
baseUrl: configuration.baseUrl,
|
|
357
|
+
apiKey: configuration.apiKey,
|
|
358
|
+
models: configuration.models?.map(serializeModel),
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
defaultProvider: "custom",
|
|
362
|
+
defaultModel: configuration.models?.[0]?.id,
|
|
363
|
+
});
|
|
364
|
+
await mkdir(agentDir, { recursive: true, mode: 0o700 });
|
|
365
|
+
await writeFile(settingsFilePath, `${JSON.stringify(settings, null, "\t")}\n`, { encoding: "utf8", mode: 0o600 });
|
|
366
|
+
return configuration;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Removes one persisted credential and clears it as the default when applicable.
|
|
370
|
+
* Environment variables and other Provider configuration remain untouched.
|
|
371
|
+
*/
|
|
372
|
+
export async function removeGlobalProviderApiKey(agentDir, providerId) {
|
|
373
|
+
const settingsFilePath = join(agentDir, SETTINGS_FILE_NAME);
|
|
374
|
+
const existingSettings = await readSettingsFile(settingsFilePath, settingsFilePath);
|
|
375
|
+
const provider = existingSettings?.providers[providerId];
|
|
376
|
+
if (!provider || provider.apiKey === undefined)
|
|
377
|
+
return false;
|
|
378
|
+
const { apiKey: _apiKey, ...providerWithoutApiKey } = provider;
|
|
379
|
+
const clearsDefault = existingSettings.defaultProvider === providerId;
|
|
380
|
+
const settings = {
|
|
381
|
+
providers: {
|
|
382
|
+
...existingSettings.providers,
|
|
383
|
+
[providerId]: providerWithoutApiKey,
|
|
384
|
+
},
|
|
385
|
+
...(clearsDefault || existingSettings.defaultProvider === undefined
|
|
386
|
+
? {}
|
|
387
|
+
: { defaultProvider: existingSettings.defaultProvider }),
|
|
388
|
+
...(clearsDefault || existingSettings.defaultModel === undefined
|
|
389
|
+
? {}
|
|
390
|
+
: { defaultModel: existingSettings.defaultModel }),
|
|
391
|
+
...(existingSettings.locale === undefined ? {} : { locale: existingSettings.locale }),
|
|
392
|
+
};
|
|
393
|
+
await writeFile(settingsFilePath, `${JSON.stringify(settings, null, "\t")}\n`, { encoding: "utf8", mode: 0o600 });
|
|
394
|
+
return true;
|
|
117
395
|
}
|
|
118
396
|
function resolveConfigValue(value, env) {
|
|
119
397
|
if (value === undefined)
|
|
@@ -144,7 +422,9 @@ function selectProviderModel(provider, providerId, env) {
|
|
|
144
422
|
? provider.models.find((candidate) => candidate.id === "gpt-4o")?.id
|
|
145
423
|
: providerId === "zhipu"
|
|
146
424
|
? provider.models.find((candidate) => candidate.id === "glm-5.3")?.id
|
|
147
|
-
:
|
|
425
|
+
: providerId === "kimi"
|
|
426
|
+
? provider.models.find((candidate) => candidate.id === "k3")?.id
|
|
427
|
+
: undefined) ||
|
|
148
428
|
provider.models[0]?.id;
|
|
149
429
|
if (!modelId)
|
|
150
430
|
throw new Error("DI_CODE_MODEL is required when the selected provider has no models");
|
|
@@ -172,62 +452,96 @@ function createBuiltInRuntime(env, providerId) {
|
|
|
172
452
|
const provider = createZhipuProvider({ env });
|
|
173
453
|
return { provider, model: selectProviderModel(provider, providerId, env) };
|
|
174
454
|
}
|
|
455
|
+
if (providerId === "kimi") {
|
|
456
|
+
const provider = createKimiProvider({ env });
|
|
457
|
+
return { provider, model: selectProviderModel(provider, providerId, env) };
|
|
458
|
+
}
|
|
175
459
|
return undefined;
|
|
176
460
|
}
|
|
177
461
|
function createConfiguredRuntime(env, configuration) {
|
|
178
462
|
if (configuration.api !== "openai-responses" &&
|
|
179
|
-
configuration.api !== "
|
|
180
|
-
configuration.api !== "zhipu-chat-completions" &&
|
|
463
|
+
configuration.api !== "openai-chat-completions" &&
|
|
181
464
|
configuration.api !== "anthropic-messages") {
|
|
182
|
-
throw new Error(`Unsupported API "${configuration.api ?? "missing"}" for provider "${configuration.id}". Expected openai-responses,
|
|
465
|
+
throw new Error(`Unsupported API "${configuration.api ?? "missing"}" for provider "${configuration.id}". Expected openai-responses, openai-chat-completions, or anthropic-messages.`);
|
|
183
466
|
}
|
|
184
467
|
if (configuration.id !== "openai" &&
|
|
185
468
|
configuration.id !== "deepseek" &&
|
|
186
469
|
configuration.id !== "zhipu" &&
|
|
470
|
+
configuration.id !== "kimi" &&
|
|
187
471
|
configuration.id !== "anthropic" &&
|
|
188
472
|
!configuration.models) {
|
|
189
473
|
throw new Error(`${SETTINGS_PATH}: providers.${configuration.id}.models is required for a custom provider`);
|
|
190
474
|
}
|
|
191
|
-
const provider = configuration.api === "
|
|
192
|
-
?
|
|
193
|
-
env: configuration.id === "
|
|
475
|
+
const provider = configuration.api === "openai-chat-completions"
|
|
476
|
+
? createOpenAIChatCompletionsProvider({
|
|
477
|
+
env: configuration.id === "zhipu" || configuration.id === "deepseek" || configuration.id === "kimi"
|
|
194
478
|
? env
|
|
195
|
-
: Object.fromEntries(Object.entries(env).filter(([name]) => name !== "
|
|
479
|
+
: Object.fromEntries(Object.entries(env).filter(([name]) => name !== "OPENAI_API_KEY" && name !== "OPENAI_BASE_URL")),
|
|
196
480
|
models: configuration.models,
|
|
197
481
|
apiKey: resolveConfigValue(configuration.apiKey, env),
|
|
198
482
|
baseUrl: configuration.models ? undefined : configuration.baseUrl,
|
|
199
483
|
providerId: configuration.id,
|
|
200
|
-
name: configuration.name,
|
|
484
|
+
name: configuration.name ?? configuration.id,
|
|
485
|
+
...(configuration.id === "zhipu"
|
|
486
|
+
? {
|
|
487
|
+
apiKeyEnvironmentVariable: "ZAI_API_KEY",
|
|
488
|
+
baseUrlEnvironmentVariable: "ZHIPU_BASE_URL",
|
|
489
|
+
defaultBaseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
|
|
490
|
+
}
|
|
491
|
+
: configuration.id === "deepseek"
|
|
492
|
+
? {
|
|
493
|
+
apiKeyEnvironmentVariable: "DEEPSEEK_API_KEY",
|
|
494
|
+
baseUrlEnvironmentVariable: "DEEPSEEK_BASE_URL",
|
|
495
|
+
defaultBaseUrl: "https://api.deepseek.com",
|
|
496
|
+
}
|
|
497
|
+
: configuration.id === "kimi"
|
|
498
|
+
? {
|
|
499
|
+
apiKeyEnvironmentVariable: "KIMI_API_KEY",
|
|
500
|
+
baseUrlEnvironmentVariable: "KIMI_BASE_URL",
|
|
501
|
+
defaultBaseUrl: "https://api.kimi.com/coding/v1",
|
|
502
|
+
}
|
|
503
|
+
: {}),
|
|
201
504
|
})
|
|
202
|
-
: configuration.
|
|
203
|
-
?
|
|
204
|
-
env
|
|
505
|
+
: configuration.api === "anthropic-messages"
|
|
506
|
+
? createAnthropicProvider({
|
|
507
|
+
env: configuration.id === "anthropic"
|
|
508
|
+
? env
|
|
509
|
+
: Object.fromEntries(Object.entries(env).filter(([name]) => name !== "ANTHROPIC_API_KEY" && name !== "ANTHROPIC_BASE_URL")),
|
|
205
510
|
models: configuration.models,
|
|
206
511
|
apiKey: resolveConfigValue(configuration.apiKey, env),
|
|
207
512
|
baseUrl: configuration.models ? undefined : configuration.baseUrl,
|
|
513
|
+
providerId: configuration.id,
|
|
514
|
+
name: configuration.name,
|
|
208
515
|
})
|
|
209
|
-
: configuration.id === "
|
|
210
|
-
?
|
|
516
|
+
: configuration.id === "deepseek"
|
|
517
|
+
? createDeepSeekProvider({
|
|
211
518
|
env,
|
|
212
519
|
models: configuration.models,
|
|
213
520
|
apiKey: resolveConfigValue(configuration.apiKey, env),
|
|
214
521
|
baseUrl: configuration.models ? undefined : configuration.baseUrl,
|
|
215
522
|
})
|
|
216
|
-
:
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
:
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
523
|
+
: configuration.id === "zhipu"
|
|
524
|
+
? createZhipuProvider({
|
|
525
|
+
env,
|
|
526
|
+
models: configuration.models,
|
|
527
|
+
apiKey: resolveConfigValue(configuration.apiKey, env),
|
|
528
|
+
baseUrl: configuration.models ? undefined : configuration.baseUrl,
|
|
529
|
+
})
|
|
530
|
+
: createOpenAIProvider({
|
|
531
|
+
env: configuration.id === "openai"
|
|
532
|
+
? env
|
|
533
|
+
: Object.fromEntries(Object.entries(env).filter(([name]) => name !== "OPENAI_API_KEY" && name !== "OPENAI_BASE_URL")),
|
|
534
|
+
models: configuration.models,
|
|
535
|
+
apiKey: resolveConfigValue(configuration.apiKey, env),
|
|
536
|
+
baseUrl: configuration.models ? undefined : configuration.baseUrl,
|
|
537
|
+
providerId: configuration.id,
|
|
538
|
+
name: configuration.name,
|
|
539
|
+
});
|
|
226
540
|
const model = selectProviderModel(provider, configuration.id, env);
|
|
227
541
|
return { provider, model };
|
|
228
542
|
}
|
|
229
|
-
export function resolveStartupRuntime(env, providers) {
|
|
230
|
-
const selectedProviderId = env.DI_CODE_PROVIDER?.trim() || (providers.length === 1 ? providers[0]?.id : undefined);
|
|
543
|
+
export function resolveStartupRuntime(env, providers, defaults = {}) {
|
|
544
|
+
const selectedProviderId = env.DI_CODE_PROVIDER?.trim() || defaults.providerId || (providers.length === 1 ? providers[0]?.id : undefined);
|
|
231
545
|
if (selectedProviderId === "faux")
|
|
232
546
|
return createFauxRuntime();
|
|
233
547
|
if (!selectedProviderId) {
|
|
@@ -236,29 +550,26 @@ export function resolveStartupRuntime(env, providers) {
|
|
|
236
550
|
}
|
|
237
551
|
throw new Error("DI_CODE_PROVIDER is required when more than one provider is configured.");
|
|
238
552
|
}
|
|
553
|
+
const runtimeEnvironment = env.DI_CODE_MODEL?.trim() || defaults.providerId !== selectedProviderId || !defaults.modelId
|
|
554
|
+
? env
|
|
555
|
+
: { ...env, DI_CODE_MODEL: defaults.modelId };
|
|
239
556
|
const selectedProvider = providers.find((provider) => provider.id === selectedProviderId);
|
|
240
557
|
if (selectedProvider)
|
|
241
|
-
return createConfiguredRuntime(
|
|
242
|
-
const builtInRuntime = createBuiltInRuntime(
|
|
558
|
+
return createConfiguredRuntime(runtimeEnvironment, selectedProvider);
|
|
559
|
+
const builtInRuntime = createBuiltInRuntime(runtimeEnvironment, selectedProviderId);
|
|
243
560
|
if (builtInRuntime)
|
|
244
561
|
return builtInRuntime;
|
|
245
562
|
throw new Error(`Unknown configured provider "${selectedProviderId}". Available providers: ${providers.map((provider) => provider.id).join(", ")}.`);
|
|
246
563
|
}
|
|
247
|
-
export async function loadStartupConfiguration(cwd, environment = process.env) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if (cause instanceof Error && "code" in cause && cause.code === "ENOENT") {
|
|
257
|
-
return { environment, providers: [] };
|
|
258
|
-
}
|
|
259
|
-
if (cause instanceof SyntaxError)
|
|
260
|
-
throw new Error(`${SETTINGS_PATH}: invalid JSON`, { cause });
|
|
261
|
-
throw cause;
|
|
564
|
+
export async function loadStartupConfiguration(cwd, environment = process.env, agentDir = join(homedir(), ".di-code")) {
|
|
565
|
+
const [globalSettings, projectSettings] = await Promise.all([
|
|
566
|
+
readSettingsFile(join(agentDir, SETTINGS_FILE_NAME), join(agentDir, SETTINGS_FILE_NAME)),
|
|
567
|
+
readSettingsFile(join(cwd, SETTINGS_PATH), SETTINGS_PATH),
|
|
568
|
+
]);
|
|
569
|
+
const settings = mergeSettings(globalSettings, projectSettings);
|
|
570
|
+
const requestedLocale = environment.DI_CODE_LOCALE?.trim();
|
|
571
|
+
if (requestedLocale !== undefined && requestedLocale !== "" && !isLocale(requestedLocale)) {
|
|
572
|
+
throw new Error('DI_CODE_LOCALE must be "en" or "zh-CN".');
|
|
262
573
|
}
|
|
263
574
|
const providers = Object.entries(settings.providers).map(([id, provider]) => ({
|
|
264
575
|
id: requiredString(id, `providers.${id}`),
|
|
@@ -266,9 +577,18 @@ export async function loadStartupConfiguration(cwd, environment = process.env) {
|
|
|
266
577
|
api: provider.api,
|
|
267
578
|
apiKey: provider.apiKey,
|
|
268
579
|
baseUrl: provider.baseUrl,
|
|
269
|
-
models: parseModels(provider.models, `providers.${id}.models`, id, provider.api, provider.baseUrl),
|
|
580
|
+
models: parseModels(provider.models, `providers.${id}.models`, id, provider.api, provider.baseUrl, SETTINGS_PATH),
|
|
270
581
|
}));
|
|
271
|
-
|
|
582
|
+
const defaults = settings.defaultProvider === undefined && settings.defaultModel === undefined
|
|
583
|
+
? undefined
|
|
584
|
+
: { providerId: settings.defaultProvider, modelId: settings.defaultModel };
|
|
585
|
+
const locale = requestedLocale && isLocale(requestedLocale) ? requestedLocale : settings.locale;
|
|
586
|
+
return {
|
|
587
|
+
environment,
|
|
588
|
+
providers,
|
|
589
|
+
...(locale === undefined ? {} : { locale }),
|
|
590
|
+
...(defaults === undefined ? {} : { defaults }),
|
|
591
|
+
};
|
|
272
592
|
}
|
|
273
593
|
export async function loadStartupEnvironment(cwd, environment = process.env) {
|
|
274
594
|
return (await loadStartupConfiguration(cwd, environment)).environment;
|