@co0ontty/wand 4.23.0 → 4.24.0
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/dist/build-info.json +3 -3
- package/dist/claude-sdk-runner.d.ts +3 -0
- package/dist/claude-sdk-runner.js +1 -0
- package/dist/config.d.ts +6 -2
- package/dist/config.js +7 -9
- package/dist/git-quick-commit.d.ts +3 -6
- package/dist/git-quick-commit.js +137 -25
- package/dist/server-settings-routes.js +1 -5
- package/dist/session-ai-context.d.ts +2 -3
- package/dist/session-ai-context.js +13 -15
- package/dist/structured-provider-common.d.ts +2 -1
- package/dist/structured-provider-common.js +11 -0
- package/dist/system-ai.d.ts +5 -0
- package/dist/system-ai.js +153 -13
- package/dist/types.d.ts +1 -1
- package/dist/web-ui/content/scripts.js +60 -60
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/embedded-assets.d.ts +1 -1
- package/dist/web-ui/embedded-assets.js +3 -3
- package/package.json +1 -1
package/dist/system-ai.js
CHANGED
|
@@ -35,7 +35,9 @@ export function normalizeSystemAiConfig(value, fallback) {
|
|
|
35
35
|
apiKey: typeof raw.apiKey === "string" ? raw.apiKey.trim() : fallback?.apiKey ?? "",
|
|
36
36
|
model: typeof raw.model === "string" ? raw.model.trim() : fallback?.model ?? "",
|
|
37
37
|
authHeader: raw.authHeader === "x-api-key" ? "x-api-key" : "bearer",
|
|
38
|
-
source: raw.source === "claude" || raw.source === "codex" || raw.source === "opencode"
|
|
38
|
+
source: raw.source === "claude" || raw.source === "codex" || raw.source === "opencode" || raw.source === "grok"
|
|
39
|
+
? raw.source
|
|
40
|
+
: "custom",
|
|
39
41
|
};
|
|
40
42
|
if (Array.isArray(raw.fallbacks)) {
|
|
41
43
|
normalized.fallbacks = raw.fallbacks
|
|
@@ -57,6 +59,25 @@ export function normalizeSystemAiConfig(value, fallback) {
|
|
|
57
59
|
}
|
|
58
60
|
return normalized;
|
|
59
61
|
}
|
|
62
|
+
function tryNormalizeSystemAiConfig(value) {
|
|
63
|
+
try {
|
|
64
|
+
return normalizeSystemAiConfig(value);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// A stale or partially edited tool profile must not prevent the remaining
|
|
68
|
+
// APIs—or the final current-session CLI fallback—from being tried.
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function systemAiProfileKey(profile) {
|
|
73
|
+
return [
|
|
74
|
+
profile.protocol,
|
|
75
|
+
profile.baseUrl,
|
|
76
|
+
profile.apiKey,
|
|
77
|
+
profile.model,
|
|
78
|
+
profile.authHeader ?? "bearer",
|
|
79
|
+
].join("\0");
|
|
80
|
+
}
|
|
60
81
|
function readJson(filePath) {
|
|
61
82
|
if (!existsSync(filePath))
|
|
62
83
|
return null;
|
|
@@ -67,6 +88,54 @@ function readJson(filePath) {
|
|
|
67
88
|
return null;
|
|
68
89
|
}
|
|
69
90
|
}
|
|
91
|
+
function parseTomlString(raw) {
|
|
92
|
+
const value = raw.trim();
|
|
93
|
+
if (value.startsWith('"')) {
|
|
94
|
+
const quoted = /^"(?:\\.|[^"\\])*"/.exec(value)?.[0];
|
|
95
|
+
if (!quoted)
|
|
96
|
+
return null;
|
|
97
|
+
try {
|
|
98
|
+
const parsed = JSON.parse(quoted);
|
|
99
|
+
return typeof parsed === "string" ? parsed : null;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (value.startsWith("'")) {
|
|
106
|
+
const end = value.indexOf("'", 1);
|
|
107
|
+
return end < 0 ? null : value.slice(1, end);
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
function readTomlStringSections(filePath) {
|
|
112
|
+
if (!existsSync(filePath))
|
|
113
|
+
return null;
|
|
114
|
+
try {
|
|
115
|
+
const sections = new Map();
|
|
116
|
+
let section = "";
|
|
117
|
+
sections.set(section, new Map());
|
|
118
|
+
for (const line of readFileSync(filePath, "utf8").split(/\r?\n/)) {
|
|
119
|
+
const sectionMatch = /^\s*\[([^\]]+)\]\s*(?:#.*)?$/.exec(line);
|
|
120
|
+
if (sectionMatch) {
|
|
121
|
+
section = sectionMatch[1].trim();
|
|
122
|
+
if (!sections.has(section))
|
|
123
|
+
sections.set(section, new Map());
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const assignment = /^\s*([A-Za-z0-9_-]+)\s*=\s*(.+)$/.exec(line);
|
|
127
|
+
if (!assignment)
|
|
128
|
+
continue;
|
|
129
|
+
const value = parseTomlString(assignment[2]);
|
|
130
|
+
if (value !== null)
|
|
131
|
+
sections.get(section).set(assignment[1], value);
|
|
132
|
+
}
|
|
133
|
+
return sections;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
70
139
|
function discoverClaude(home) {
|
|
71
140
|
const settings = readJson(path.join(home, ".claude", "settings.json"));
|
|
72
141
|
const env = settings?.env && typeof settings.env === "object" ? settings.env : {};
|
|
@@ -76,7 +145,7 @@ function discoverClaude(home) {
|
|
|
76
145
|
const model = typeof settings?.model === "string" ? settings.model : "";
|
|
77
146
|
if (!apiKey || !model)
|
|
78
147
|
return null;
|
|
79
|
-
return
|
|
148
|
+
return tryNormalizeSystemAiConfig({
|
|
80
149
|
enabled: true, protocol: "anthropic", baseUrl, apiKey, model,
|
|
81
150
|
authHeader: typeof env.ANTHROPIC_AUTH_TOKEN === "string" ? "bearer" : "x-api-key",
|
|
82
151
|
source: "claude",
|
|
@@ -100,7 +169,7 @@ function discoverOpenCode(home) {
|
|
|
100
169
|
: typeof provider?.model === "string" ? provider.model : Object.keys(models)[0] ?? "";
|
|
101
170
|
if (!apiKey || !baseUrl || !configuredModel)
|
|
102
171
|
continue;
|
|
103
|
-
|
|
172
|
+
const profile = tryNormalizeSystemAiConfig({
|
|
104
173
|
enabled: true,
|
|
105
174
|
protocol: "openai",
|
|
106
175
|
baseUrl,
|
|
@@ -108,7 +177,9 @@ function discoverOpenCode(home) {
|
|
|
108
177
|
model: configuredModel,
|
|
109
178
|
authHeader: "bearer",
|
|
110
179
|
source: "opencode",
|
|
111
|
-
})
|
|
180
|
+
});
|
|
181
|
+
if (profile)
|
|
182
|
+
found.push(profile);
|
|
112
183
|
}
|
|
113
184
|
return found;
|
|
114
185
|
}
|
|
@@ -127,7 +198,40 @@ function discoverCodex(home) {
|
|
|
127
198
|
catch { /* optional config */ }
|
|
128
199
|
if (!model)
|
|
129
200
|
return null;
|
|
130
|
-
return
|
|
201
|
+
return tryNormalizeSystemAiConfig({ enabled: true, protocol: "openai", baseUrl, apiKey, model, authHeader: "bearer", source: "codex" });
|
|
202
|
+
}
|
|
203
|
+
function discoverGrok(home) {
|
|
204
|
+
const sections = readTomlStringSections(path.join(home, ".grok", "config.toml"));
|
|
205
|
+
if (!sections)
|
|
206
|
+
return [];
|
|
207
|
+
const defaultProfile = sections.get("models")?.get("default") ?? "";
|
|
208
|
+
const modelSections = [...sections.entries()].filter(([name]) => name.startsWith("model."));
|
|
209
|
+
const orderedSections = [
|
|
210
|
+
...modelSections.filter(([name]) => name === `model.${defaultProfile}`),
|
|
211
|
+
...modelSections.filter(([name]) => name !== `model.${defaultProfile}`),
|
|
212
|
+
];
|
|
213
|
+
const found = [];
|
|
214
|
+
for (const [section, values] of orderedSections) {
|
|
215
|
+
if (values.get("api_backend")?.trim().toLowerCase() !== "chat_completions")
|
|
216
|
+
continue;
|
|
217
|
+
const apiKey = values.get("api_key") ?? "";
|
|
218
|
+
const baseUrl = values.get("base_url") ?? "";
|
|
219
|
+
const model = values.get("model") ?? section.slice("model.".length);
|
|
220
|
+
if (!apiKey || !baseUrl || !model)
|
|
221
|
+
continue;
|
|
222
|
+
const profile = tryNormalizeSystemAiConfig({
|
|
223
|
+
enabled: true,
|
|
224
|
+
protocol: "openai",
|
|
225
|
+
baseUrl,
|
|
226
|
+
apiKey,
|
|
227
|
+
model,
|
|
228
|
+
authHeader: "bearer",
|
|
229
|
+
source: "grok",
|
|
230
|
+
});
|
|
231
|
+
if (profile)
|
|
232
|
+
found.push(profile);
|
|
233
|
+
}
|
|
234
|
+
return found;
|
|
131
235
|
}
|
|
132
236
|
/** Copy every usable direct-API profile from the user's configured CLIs. */
|
|
133
237
|
export function discoverCliSystemAiConfigs(preferred, home = os.homedir()) {
|
|
@@ -135,15 +239,23 @@ export function discoverCliSystemAiConfigs(preferred, home = os.homedir()) {
|
|
|
135
239
|
claude: (dir) => [discoverClaude(dir)].filter((item) => item !== null),
|
|
136
240
|
codex: (dir) => [discoverCodex(dir)].filter((item) => item !== null),
|
|
137
241
|
opencode: discoverOpenCode,
|
|
242
|
+
grok: discoverGrok,
|
|
138
243
|
};
|
|
139
|
-
const order = [preferred ?? "claude", "claude", "opencode", "codex"];
|
|
244
|
+
const order = [preferred ?? "claude", "claude", "opencode", "grok", "codex"];
|
|
140
245
|
const found = [];
|
|
141
246
|
const seen = new Set();
|
|
142
247
|
for (const provider of [...new Set(order)]) {
|
|
143
|
-
if (provider === "
|
|
248
|
+
if (provider === "qoder")
|
|
144
249
|
continue;
|
|
145
|
-
|
|
146
|
-
|
|
250
|
+
let discovered;
|
|
251
|
+
try {
|
|
252
|
+
discovered = discoverers[provider](home);
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
for (const profile of discovered) {
|
|
258
|
+
const key = systemAiProfileKey(profile);
|
|
147
259
|
if (seen.has(key))
|
|
148
260
|
continue;
|
|
149
261
|
seen.add(key);
|
|
@@ -163,25 +275,53 @@ export function systemAiProfiles(config, forceEnabled = false) {
|
|
|
163
275
|
const candidates = [config, ...(config.fallbacks ?? [])];
|
|
164
276
|
const seen = new Set();
|
|
165
277
|
return candidates.flatMap((candidate) => {
|
|
166
|
-
const normalized =
|
|
278
|
+
const normalized = tryNormalizeSystemAiConfig({ ...candidate, enabled: true, fallbacks: undefined });
|
|
279
|
+
if (!normalized)
|
|
280
|
+
return [];
|
|
167
281
|
if (!normalized.baseUrl || !normalized.apiKey || !normalized.model)
|
|
168
282
|
return [];
|
|
169
|
-
const key =
|
|
283
|
+
const key = systemAiProfileKey(normalized);
|
|
170
284
|
if (seen.has(key))
|
|
171
285
|
return [];
|
|
172
286
|
seen.add(key);
|
|
173
287
|
return [normalized];
|
|
174
288
|
});
|
|
175
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* Flatten and combine direct-API groups in priority order. Dynamically discovered
|
|
292
|
+
* profiles can be passed first, followed by stored/legacy settings.
|
|
293
|
+
*/
|
|
294
|
+
export function mergeSystemAiConfigs(...groups) {
|
|
295
|
+
const profiles = [];
|
|
296
|
+
const seen = new Set();
|
|
297
|
+
for (const group of groups) {
|
|
298
|
+
for (const config of Array.isArray(group) ? group : group ? [group] : []) {
|
|
299
|
+
for (const profile of systemAiProfiles(config, true)) {
|
|
300
|
+
const key = systemAiProfileKey(profile);
|
|
301
|
+
if (seen.has(key))
|
|
302
|
+
continue;
|
|
303
|
+
seen.add(key);
|
|
304
|
+
profiles.push({ ...profile, enabled: true, fallbacks: undefined });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
const [primary, ...fallbacks] = profiles;
|
|
309
|
+
if (!primary)
|
|
310
|
+
return undefined;
|
|
311
|
+
const merged = { ...primary, enabled: true };
|
|
312
|
+
if (fallbacks.length)
|
|
313
|
+
merged.fallbacks = fallbacks;
|
|
314
|
+
return merged;
|
|
315
|
+
}
|
|
176
316
|
function endpoint(baseUrl, protocol) {
|
|
177
317
|
const url = new URL(baseUrl);
|
|
178
318
|
const pathName = url.pathname.replace(/\/+$/, "");
|
|
179
319
|
const fullSuffix = protocol === "anthropic" ? "/v1/messages" : "/v1/chat/completions";
|
|
180
320
|
const shortSuffix = protocol === "anthropic" ? "/messages" : "/chat/completions";
|
|
181
|
-
if (pathName.toLowerCase().endsWith(
|
|
321
|
+
if (pathName.toLowerCase().endsWith(shortSuffix)) {
|
|
182
322
|
url.pathname = pathName;
|
|
183
323
|
}
|
|
184
|
-
else if (
|
|
324
|
+
else if (/\/v\d+(?:\.\d+)?$/i.test(pathName)) {
|
|
185
325
|
url.pathname = `${pathName}${shortSuffix}`;
|
|
186
326
|
}
|
|
187
327
|
else {
|
package/dist/types.d.ts
CHANGED
|
@@ -145,7 +145,7 @@ export interface SystemAiConfig {
|
|
|
145
145
|
model: string;
|
|
146
146
|
authHeader?: SystemAiAuthHeader;
|
|
147
147
|
/** 自动导入时记录来源,仅用于设置页说明。 */
|
|
148
|
-
source?: "claude" | "codex" | "opencode" | "custom";
|
|
148
|
+
source?: "claude" | "codex" | "opencode" | "grok" | "custom";
|
|
149
149
|
/**
|
|
150
150
|
* 其余可直连 API,按数组顺序依次尝试。保留顶层字段作为首选项,
|
|
151
151
|
* 以兼容已有配置与手工编辑入口。
|