@co0ontty/wand 4.14.0 → 4.15.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/cli.js +5 -0
- package/dist/config.js +2 -3
- package/dist/git-quick-commit.js +2 -2
- package/dist/process-manager.js +2 -2
- package/dist/prompt-optimizer.js +13 -2
- package/dist/server-settings-routes.js +31 -11
- package/dist/session-ai-context.d.ts +2 -0
- package/dist/session-ai-context.js +13 -1
- package/dist/structured-session-manager.js +2 -2
- package/dist/system-ai.d.ts +7 -1
- package/dist/system-ai.js +102 -16
- package/dist/types.d.ts +5 -0
- package/dist/web-ui/content/scripts.js +48 -48
- package/dist/web-ui/embedded-assets.d.ts +1 -1
- package/dist/web-ui/embedded-assets.js +2 -2
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-07-18T00:
|
|
4
|
-
"version": "4.
|
|
2
|
+
"commit": "4cb2d575fe6ffe06c991374d39dfef79740befeb",
|
|
3
|
+
"builtAt": "2026-07-18T00:38:58.299Z",
|
|
4
|
+
"version": "4.15.0",
|
|
5
5
|
"channel": "stable"
|
|
6
6
|
}
|
package/dist/cli.js
CHANGED
|
@@ -110,6 +110,11 @@ async function main() {
|
|
|
110
110
|
systemAi: config.systemAi ? {
|
|
111
111
|
...config.systemAi,
|
|
112
112
|
apiKey: config.systemAi.apiKey ? "<set>" : "",
|
|
113
|
+
fallbacks: config.systemAi.fallbacks?.map((profile) => ({
|
|
114
|
+
...profile,
|
|
115
|
+
apiKey: profile.apiKey ? "<set>" : "",
|
|
116
|
+
fallbacks: undefined,
|
|
117
|
+
})),
|
|
113
118
|
} : undefined,
|
|
114
119
|
};
|
|
115
120
|
process.stdout.write(`${JSON.stringify(display, null, 2)}\n`);
|
package/dist/config.js
CHANGED
|
@@ -4,7 +4,7 @@ import { chmod, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promi
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import process from "node:process";
|
|
6
6
|
import { isRunningAsRoot } from "./env-utils.js";
|
|
7
|
-
import { normalizeSystemAiConfig } from "./system-ai.js";
|
|
7
|
+
import { normalizeSystemAiConfig, systemAiProfiles } from "./system-ai.js";
|
|
8
8
|
function isThinkingEffort(value) {
|
|
9
9
|
return value === "off"
|
|
10
10
|
|| value === "standard"
|
|
@@ -513,8 +513,7 @@ export function writePreferenceToStorage(config, storage, key, value, options =
|
|
|
513
513
|
export function validateCommitAiConfig(config) {
|
|
514
514
|
if (config.commitAiSource !== "api")
|
|
515
515
|
return;
|
|
516
|
-
|
|
517
|
-
if (!directApi?.baseUrl || !directApi.apiKey || !directApi.model) {
|
|
516
|
+
if (!systemAiProfiles(config.systemAi, true).length) {
|
|
518
517
|
throw new Error("选择直连 API 生成 Commit 时,必须先填写 API 地址、API Key 和模型。");
|
|
519
518
|
}
|
|
520
519
|
}
|
package/dist/git-quick-commit.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { ClaudeRunError, runClaudePrint } from "./claude-sdk-runner.js";
|
|
4
|
-
import {
|
|
4
|
+
import { callSystemAiTextWithFallback } from "./system-ai.js";
|
|
5
5
|
import { buildChildEnv } from "./env-utils.js";
|
|
6
6
|
import { runGit as runGitBase, runGitAsync as runGitAsyncBase, runGitRaw as runGitRawBase, runGitRawAsync as runGitRawAsyncBase, getGitErrorMessage, } from "./git-utils.js";
|
|
7
7
|
import { thinkingEffortToClaudeCliEffort, thinkingEffortToCodexReasoningEffort, thinkingEffortToOpenCodeVariant } from "./structured-provider-common.js";
|
|
@@ -615,7 +615,7 @@ async function callCliAiText(prompt, cwd, language, opts) {
|
|
|
615
615
|
return callClaudeText(prompt, cwd, language, opts.model);
|
|
616
616
|
}
|
|
617
617
|
async function callDirectApiText(prompt, systemAi) {
|
|
618
|
-
const text = await
|
|
618
|
+
const text = await callSystemAiTextWithFallback(prompt, systemAi);
|
|
619
619
|
if (!text.trim()) {
|
|
620
620
|
throw new QuickCommitError("直连 API 返回了空结果。", "EMPTY_AI_MESSAGE");
|
|
621
621
|
}
|
package/dist/process-manager.js
CHANGED
|
@@ -18,7 +18,7 @@ import { getProviderCommandSessionId, getProviderResumeCommandSessionId } from "
|
|
|
18
18
|
import { normalizeThinkingEffort, thinkingEffortToClaudeCliEffort, thinkingEffortToClaudeSlashEffort, thinkingEffortToCodexReasoningEffort, thinkingEffortToOpenCodeVariant } from "./structured-provider-common.js";
|
|
19
19
|
import { SessionTopicCoordinator } from "./session-topic.js";
|
|
20
20
|
import { getErrorMessage } from "./error-utils.js";
|
|
21
|
-
import {
|
|
21
|
+
import { resolveSystemAiContext } from "./session-ai-context.js";
|
|
22
22
|
import { resolveSessionCwd } from "./session-cwd.js";
|
|
23
23
|
import { ProviderHistoryScanner, } from "./provider-history-scanner.js";
|
|
24
24
|
function resolveProviderFromCommand(command) {
|
|
@@ -1720,7 +1720,7 @@ export class ProcessManager extends EventEmitter {
|
|
|
1720
1720
|
input: prompt,
|
|
1721
1721
|
cwd: record.cwd,
|
|
1722
1722
|
language: this.config.language,
|
|
1723
|
-
ai:
|
|
1723
|
+
ai: resolveSystemAiContext(record, this.config),
|
|
1724
1724
|
onGenerating: (generating) => {
|
|
1725
1725
|
if (!this.disposed)
|
|
1726
1726
|
this.setSessionTopicGenerating(id, generating);
|
package/dist/prompt-optimizer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ClaudeRunError, runClaudePrint } from "./claude-sdk-runner.js";
|
|
2
|
-
import {
|
|
2
|
+
import { callSystemAiTextWithFallback } from "./system-ai.js";
|
|
3
3
|
const CLAUDE_TIMEOUT_MS = 60_000;
|
|
4
4
|
const MAX_INPUT_LENGTH = 8000;
|
|
5
5
|
export class PromptOptimizeError extends Error {
|
|
@@ -51,7 +51,18 @@ export async function optimizePrompt(rawText, language, cwd, systemAi) {
|
|
|
51
51
|
throw new PromptOptimizeError(`输入过长(${text.length} 字符),请缩短到 ${MAX_INPUT_LENGTH} 以内。`, "INPUT_TOO_LONG");
|
|
52
52
|
}
|
|
53
53
|
const prompt = buildOptimizePrompt(text, language);
|
|
54
|
-
|
|
54
|
+
let raw;
|
|
55
|
+
if (systemAi?.enabled) {
|
|
56
|
+
try {
|
|
57
|
+
raw = await callSystemAiTextWithFallback(prompt, systemAi);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
raw = await callClaudeText(prompt, cwd, language);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
raw = await callClaudeText(prompt, cwd, language);
|
|
65
|
+
}
|
|
55
66
|
const cleaned = raw
|
|
56
67
|
.replace(/^```[a-zA-Z]*\n?/, "")
|
|
57
68
|
.replace(/\n?```$/, "")
|
|
@@ -6,17 +6,13 @@ import { asyncRoute } from "./express-async.js";
|
|
|
6
6
|
import { getProviderDefaultModels, PREFERENCE_KEYS, saveConfig, validateCommitAiConfig, writePreferenceToStorage, } from "./config.js";
|
|
7
7
|
import { getCachedModels, refreshModels } from "./models.js";
|
|
8
8
|
import { DEPLOYMENT_CONFIG_KEYS } from "./runtime-config.js";
|
|
9
|
-
import {
|
|
9
|
+
import { discoverCliSystemAiConfigs, normalizeSystemAiConfig } from "./system-ai.js";
|
|
10
10
|
function publicConfig(config) {
|
|
11
11
|
const { password: _password, appSecret: _appSecret, ...safe } = config;
|
|
12
12
|
const defaultModels = getProviderDefaultModels(config);
|
|
13
13
|
return {
|
|
14
14
|
...safe,
|
|
15
|
-
systemAi: safe.systemAi
|
|
16
|
-
...safe.systemAi,
|
|
17
|
-
apiKey: "",
|
|
18
|
-
hasApiKey: Boolean(safe.systemAi.apiKey),
|
|
19
|
-
} : undefined,
|
|
15
|
+
systemAi: publicSystemAi(safe.systemAi),
|
|
20
16
|
defaultModel: defaultModels.claude,
|
|
21
17
|
defaultCodexModel: defaultModels.codex,
|
|
22
18
|
defaultOpenCodeModel: defaultModels.opencode,
|
|
@@ -25,6 +21,21 @@ function publicConfig(config) {
|
|
|
25
21
|
defaultModels,
|
|
26
22
|
};
|
|
27
23
|
}
|
|
24
|
+
function publicSystemAi(systemAi) {
|
|
25
|
+
if (!systemAi)
|
|
26
|
+
return undefined;
|
|
27
|
+
return {
|
|
28
|
+
...systemAi,
|
|
29
|
+
apiKey: "",
|
|
30
|
+
hasApiKey: Boolean(systemAi.apiKey),
|
|
31
|
+
fallbacks: systemAi.fallbacks?.map((profile) => ({
|
|
32
|
+
...profile,
|
|
33
|
+
apiKey: "",
|
|
34
|
+
hasApiKey: Boolean(profile.apiKey),
|
|
35
|
+
fallbacks: undefined,
|
|
36
|
+
})),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
28
39
|
function publicDistributionInfo(distribution) {
|
|
29
40
|
const androidApk = { ...distribution.androidApk };
|
|
30
41
|
const macosDmg = { ...distribution.macosDmg };
|
|
@@ -117,18 +128,19 @@ export function registerSettingsRoutes(app, deps) {
|
|
|
117
128
|
const source = body.source === "codex" || body.source === "opencode" || body.source === "claude"
|
|
118
129
|
? body.source
|
|
119
130
|
: config.commitCli;
|
|
120
|
-
const imported =
|
|
121
|
-
if (!imported) {
|
|
131
|
+
const imported = discoverCliSystemAiConfigs(source);
|
|
132
|
+
if (!imported.length) {
|
|
122
133
|
res.status(404).json({ error: "没有在已配置的 CLI 文件中找到可直连的 API 地址、密钥和模型。" });
|
|
123
134
|
return;
|
|
124
135
|
}
|
|
125
136
|
const candidate = runtimeConfig.createCandidate();
|
|
126
137
|
writePreferenceToStorage(candidate, storage, "systemAi", {
|
|
127
|
-
...imported,
|
|
138
|
+
...imported[0],
|
|
128
139
|
enabled: candidate.systemAi?.enabled === true,
|
|
140
|
+
fallbacks: imported.slice(1),
|
|
129
141
|
});
|
|
130
142
|
runtimeConfig.commit(candidate, new Set(["systemAi"]));
|
|
131
|
-
res.json({ ok: true, systemAi: (publicConfig(candidate).systemAi) });
|
|
143
|
+
res.json({ ok: true, count: imported.length, systemAi: (publicConfig(candidate).systemAi) });
|
|
132
144
|
});
|
|
133
145
|
app.get("/api/app-connect-code", requireAdmin, (req, res) => {
|
|
134
146
|
res.json(deps.resolveAppConnectCode(req));
|
|
@@ -199,7 +211,15 @@ export function registerSettingsRoutes(app, deps) {
|
|
|
199
211
|
const apiKey = typeof body.systemAi.apiKey === "string" && body.systemAi.apiKey.trim()
|
|
200
212
|
? body.systemAi.apiKey.trim()
|
|
201
213
|
: previous?.apiKey ?? "";
|
|
202
|
-
|
|
214
|
+
const submittedFallbacks = Array.isArray(body.systemAi.fallbacks)
|
|
215
|
+
? body.systemAi.fallbacks.map((item, index) => {
|
|
216
|
+
const raw = item && typeof item === "object" && !Array.isArray(item) ? item : {};
|
|
217
|
+
const prior = previous?.fallbacks?.[index];
|
|
218
|
+
const fallbackApiKey = typeof raw.apiKey === "string" && raw.apiKey.trim() ? raw.apiKey.trim() : prior?.apiKey ?? "";
|
|
219
|
+
return { ...prior, ...raw, apiKey: fallbackApiKey, fallbacks: undefined };
|
|
220
|
+
})
|
|
221
|
+
: previous?.fallbacks;
|
|
222
|
+
stagePreference("systemAi", normalizeSystemAiConfig({ ...previous, ...body.systemAi, apiKey, fallbacks: submittedFallbacks }, previous));
|
|
203
223
|
}
|
|
204
224
|
for (const field of PREFERENCE_KEYS) {
|
|
205
225
|
if (field === "systemAi")
|
|
@@ -16,5 +16,7 @@ export interface SessionAiContext {
|
|
|
16
16
|
export declare function resolveSessionProvider(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command">): SessionProvider;
|
|
17
17
|
/** Build the provider-specific settings used by session-adjacent AI actions. */
|
|
18
18
|
export declare function resolveSessionAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultThinkingEffort" | "inheritEnv">): SessionAiContext;
|
|
19
|
+
/** Build the source order for Wand-owned AI features such as titles. */
|
|
20
|
+
export declare function resolveSystemAiContext(snapshot: Parameters<typeof resolveSessionAiContext>[0], config: Parameters<typeof resolveSessionAiContext>[1] & Pick<WandConfig, "systemAi" | "commitCli" | "commitModel">): SessionAiContext;
|
|
19
21
|
/** Build the AI context for quick-commit actions from their global preferences. */
|
|
20
22
|
export declare function resolveCommitAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultThinkingEffort" | "inheritEnv" | "commitCli" | "commitModel" | "commitAiSource" | "systemAi">): SessionAiContext;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getDefaultModelForProvider } from "./config.js";
|
|
2
|
+
import { systemAiProfiles } from "./system-ai.js";
|
|
2
3
|
/**
|
|
3
4
|
* Resolve the provider from every representation used by current and legacy
|
|
4
5
|
* sessions. Older persisted sessions may not have the top-level provider, but
|
|
@@ -45,7 +46,7 @@ function normalizeModel(value) {
|
|
|
45
46
|
return model && model !== "default" ? model : undefined;
|
|
46
47
|
}
|
|
47
48
|
function usableSystemAi(config) {
|
|
48
|
-
if (!config
|
|
49
|
+
if (!systemAiProfiles(config, true).length)
|
|
49
50
|
return undefined;
|
|
50
51
|
return { ...config, enabled: true };
|
|
51
52
|
}
|
|
@@ -61,6 +62,17 @@ export function resolveSessionAiContext(snapshot, config) {
|
|
|
61
62
|
inheritEnv: config.inheritEnv,
|
|
62
63
|
};
|
|
63
64
|
}
|
|
65
|
+
/** Build the source order for Wand-owned AI features such as titles. */
|
|
66
|
+
export function resolveSystemAiContext(snapshot, config) {
|
|
67
|
+
const sessionContext = resolveSessionAiContext(snapshot, config);
|
|
68
|
+
const directApi = config.systemAi ? usableSystemAi(config.systemAi) : undefined;
|
|
69
|
+
const cliContext = {
|
|
70
|
+
...sessionContext,
|
|
71
|
+
provider: config.commitCli === "codex" || config.commitCli === "opencode" ? config.commitCli : "claude",
|
|
72
|
+
model: normalizeModel(config.commitModel),
|
|
73
|
+
};
|
|
74
|
+
return directApi && config.systemAi?.enabled ? { ...cliContext, systemAi: directApi } : cliContext;
|
|
75
|
+
}
|
|
64
76
|
/** Build the AI context for quick-commit actions from their global preferences. */
|
|
65
77
|
export function resolveCommitAiContext(snapshot, config) {
|
|
66
78
|
const sessionContext = resolveSessionAiContext(snapshot, config);
|
|
@@ -7,7 +7,7 @@ import { getErrorMessage } from "./error-utils.js";
|
|
|
7
7
|
import { resolveSdkClaudeBinary } from "./claude-sdk-runner.js";
|
|
8
8
|
import { SessionTopicCoordinator } from "./session-topic.js";
|
|
9
9
|
import { resolveSessionCwd } from "./session-cwd.js";
|
|
10
|
-
import {
|
|
10
|
+
import { resolveSystemAiContext } from "./session-ai-context.js";
|
|
11
11
|
import { CodexRunner } from "./structured-codex-adapter.js";
|
|
12
12
|
import { normalizeStructuredToolResultContent } from "./structured-content.js";
|
|
13
13
|
import { buildAppendSystemPromptParts, buildClaudeSdkThinking, ClaudeCliRunner, derivePermissionPolicy, } from "./structured-claude-adapter.js";
|
|
@@ -501,7 +501,7 @@ export class StructuredSessionManager {
|
|
|
501
501
|
input,
|
|
502
502
|
cwd: session.cwd,
|
|
503
503
|
language: this.config.language,
|
|
504
|
-
ai:
|
|
504
|
+
ai: resolveSystemAiContext(session, this.config),
|
|
505
505
|
onGenerating: (generating) => {
|
|
506
506
|
if (!this.disposed)
|
|
507
507
|
this.setSessionTopicGenerating(id, generating);
|
package/dist/system-ai.d.ts
CHANGED
|
@@ -4,6 +4,12 @@ export declare class SystemAiError extends Error {
|
|
|
4
4
|
constructor(message: string, code: string);
|
|
5
5
|
}
|
|
6
6
|
export declare function normalizeSystemAiConfig(value: unknown, fallback?: SystemAiConfig): SystemAiConfig;
|
|
7
|
-
/** Copy
|
|
7
|
+
/** Copy every usable direct-API profile from the user's configured CLIs. */
|
|
8
|
+
export declare function discoverCliSystemAiConfigs(preferred?: SessionProvider, home?: string): SystemAiConfig[];
|
|
9
|
+
/** Backward-compatible first-profile discovery. */
|
|
8
10
|
export declare function discoverCliSystemAiConfig(preferred?: SessionProvider, home?: string): SystemAiConfig | null;
|
|
11
|
+
/** Return the configured API chain in call order, excluding incomplete entries. */
|
|
12
|
+
export declare function systemAiProfiles(config: SystemAiConfig | undefined, forceEnabled?: boolean): SystemAiConfig[];
|
|
9
13
|
export declare function callSystemAiText(prompt: string, config: SystemAiConfig, timeoutMs?: number): Promise<string>;
|
|
14
|
+
/** Try every configured API in order. Empty responses are treated as unavailable. */
|
|
15
|
+
export declare function callSystemAiTextWithFallback(prompt: string, config: SystemAiConfig, timeoutMs?: number): Promise<string>;
|
package/dist/system-ai.js
CHANGED
|
@@ -28,7 +28,7 @@ export function normalizeSystemAiConfig(value, fallback) {
|
|
|
28
28
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
|
|
29
29
|
throw new Error("系统 AI API 地址必须使用 http 或 https。");
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
const normalized = {
|
|
32
32
|
enabled: raw.enabled === true,
|
|
33
33
|
protocol,
|
|
34
34
|
baseUrl,
|
|
@@ -37,6 +37,25 @@ export function normalizeSystemAiConfig(value, fallback) {
|
|
|
37
37
|
authHeader: raw.authHeader === "x-api-key" ? "x-api-key" : "bearer",
|
|
38
38
|
source: raw.source === "claude" || raw.source === "codex" || raw.source === "opencode" ? raw.source : "custom",
|
|
39
39
|
};
|
|
40
|
+
if (Array.isArray(raw.fallbacks)) {
|
|
41
|
+
normalized.fallbacks = raw.fallbacks
|
|
42
|
+
.map((item, index) => {
|
|
43
|
+
try {
|
|
44
|
+
const itemFallback = fallback?.fallbacks?.[index];
|
|
45
|
+
const profile = normalizeSystemAiConfig(item, itemFallback);
|
|
46
|
+
delete profile.fallbacks;
|
|
47
|
+
return profile;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
.filter((item) => item !== null);
|
|
54
|
+
}
|
|
55
|
+
else if (fallback?.fallbacks?.length) {
|
|
56
|
+
normalized.fallbacks = fallback.fallbacks.map((item) => ({ ...item, fallbacks: undefined }));
|
|
57
|
+
}
|
|
58
|
+
return normalized;
|
|
40
59
|
}
|
|
41
60
|
function readJson(filePath) {
|
|
42
61
|
if (!existsSync(filePath))
|
|
@@ -68,14 +87,30 @@ function discoverOpenCode(home) {
|
|
|
68
87
|
const selectedModel = typeof config?.model === "string" ? config.model : "";
|
|
69
88
|
const providerId = selectedModel.split("/", 1)[0] ?? "";
|
|
70
89
|
const providers = config?.provider && typeof config.provider === "object" ? config.provider : {};
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
90
|
+
const orderedProviders = [providerId, ...Object.keys(providers).filter((id) => id !== providerId)];
|
|
91
|
+
const found = [];
|
|
92
|
+
for (const id of orderedProviders) {
|
|
93
|
+
const provider = providers[id] && typeof providers[id] === "object" ? providers[id] : null;
|
|
94
|
+
const options = provider?.options && typeof provider.options === "object" ? provider.options : {};
|
|
95
|
+
const apiKey = typeof options.apiKey === "string" ? options.apiKey : "";
|
|
96
|
+
const baseUrl = typeof options.baseURL === "string" ? options.baseURL : typeof options.baseUrl === "string" ? options.baseUrl : "";
|
|
97
|
+
const models = provider?.models && typeof provider.models === "object" ? provider.models : {};
|
|
98
|
+
const configuredModel = id === providerId && selectedModel
|
|
99
|
+
? (selectedModel.includes("/") ? selectedModel.slice(selectedModel.indexOf("/") + 1) : selectedModel)
|
|
100
|
+
: typeof provider?.model === "string" ? provider.model : Object.keys(models)[0] ?? "";
|
|
101
|
+
if (!apiKey || !baseUrl || !configuredModel)
|
|
102
|
+
continue;
|
|
103
|
+
found.push(normalizeSystemAiConfig({
|
|
104
|
+
enabled: true,
|
|
105
|
+
protocol: "openai",
|
|
106
|
+
baseUrl,
|
|
107
|
+
apiKey,
|
|
108
|
+
model: configuredModel,
|
|
109
|
+
authHeader: "bearer",
|
|
110
|
+
source: "opencode",
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
return found;
|
|
79
114
|
}
|
|
80
115
|
function discoverCodex(home) {
|
|
81
116
|
const auth = readJson(path.join(home, ".codex", "auth.json"));
|
|
@@ -94,18 +129,49 @@ function discoverCodex(home) {
|
|
|
94
129
|
return null;
|
|
95
130
|
return normalizeSystemAiConfig({ enabled: true, protocol: "openai", baseUrl, apiKey, model, authHeader: "bearer", source: "codex" });
|
|
96
131
|
}
|
|
97
|
-
/** Copy
|
|
98
|
-
export function
|
|
99
|
-
const discoverers = {
|
|
132
|
+
/** Copy every usable direct-API profile from the user's configured CLIs. */
|
|
133
|
+
export function discoverCliSystemAiConfigs(preferred, home = os.homedir()) {
|
|
134
|
+
const discoverers = {
|
|
135
|
+
claude: (dir) => [discoverClaude(dir)].filter((item) => item !== null),
|
|
136
|
+
codex: (dir) => [discoverCodex(dir)].filter((item) => item !== null),
|
|
137
|
+
opencode: discoverOpenCode,
|
|
138
|
+
};
|
|
100
139
|
const order = [preferred ?? "claude", "claude", "opencode", "codex"];
|
|
140
|
+
const found = [];
|
|
141
|
+
const seen = new Set();
|
|
101
142
|
for (const provider of [...new Set(order)]) {
|
|
102
143
|
if (provider === "grok" || provider === "qoder")
|
|
103
144
|
continue;
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
145
|
+
for (const profile of discoverers[provider](home)) {
|
|
146
|
+
const key = [profile.protocol, profile.baseUrl, profile.apiKey, profile.model].join("\0");
|
|
147
|
+
if (seen.has(key))
|
|
148
|
+
continue;
|
|
149
|
+
seen.add(key);
|
|
150
|
+
found.push(profile);
|
|
151
|
+
}
|
|
107
152
|
}
|
|
108
|
-
return
|
|
153
|
+
return found;
|
|
154
|
+
}
|
|
155
|
+
/** Backward-compatible first-profile discovery. */
|
|
156
|
+
export function discoverCliSystemAiConfig(preferred, home = os.homedir()) {
|
|
157
|
+
return discoverCliSystemAiConfigs(preferred, home)[0] ?? null;
|
|
158
|
+
}
|
|
159
|
+
/** Return the configured API chain in call order, excluding incomplete entries. */
|
|
160
|
+
export function systemAiProfiles(config, forceEnabled = false) {
|
|
161
|
+
if (!config || (!forceEnabled && !config.enabled))
|
|
162
|
+
return [];
|
|
163
|
+
const candidates = [config, ...(config.fallbacks ?? [])];
|
|
164
|
+
const seen = new Set();
|
|
165
|
+
return candidates.flatMap((candidate) => {
|
|
166
|
+
const normalized = normalizeSystemAiConfig({ ...candidate, enabled: true, fallbacks: undefined });
|
|
167
|
+
if (!normalized.baseUrl || !normalized.apiKey || !normalized.model)
|
|
168
|
+
return [];
|
|
169
|
+
const key = [normalized.protocol, normalized.baseUrl, normalized.apiKey, normalized.model].join("\0");
|
|
170
|
+
if (seen.has(key))
|
|
171
|
+
return [];
|
|
172
|
+
seen.add(key);
|
|
173
|
+
return [normalized];
|
|
174
|
+
});
|
|
109
175
|
}
|
|
110
176
|
function endpoint(baseUrl, protocol) {
|
|
111
177
|
const url = new URL(baseUrl);
|
|
@@ -166,3 +232,23 @@ export async function callSystemAiText(prompt, config, timeoutMs = SYSTEM_AI_TIM
|
|
|
166
232
|
throw new SystemAiError("系统 AI API 返回了无法解析的响应。", "SYSTEM_AI_INVALID_RESPONSE");
|
|
167
233
|
}
|
|
168
234
|
}
|
|
235
|
+
/** Try every configured API in order. Empty responses are treated as unavailable. */
|
|
236
|
+
export async function callSystemAiTextWithFallback(prompt, config, timeoutMs = SYSTEM_AI_TIMEOUT_MS) {
|
|
237
|
+
const profiles = systemAiProfiles(config, true);
|
|
238
|
+
if (!profiles.length) {
|
|
239
|
+
throw new SystemAiError("系统 AI API 配置不完整。", "SYSTEM_AI_CONFIG_INVALID");
|
|
240
|
+
}
|
|
241
|
+
const errors = [];
|
|
242
|
+
for (const profile of profiles) {
|
|
243
|
+
try {
|
|
244
|
+
const text = await callSystemAiText(prompt, profile, timeoutMs);
|
|
245
|
+
if (text.trim())
|
|
246
|
+
return text;
|
|
247
|
+
errors.push(`${profile.source ?? "custom"}: 返回空结果`);
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
errors.push(`${profile.source ?? "custom"}: ${error instanceof Error ? error.message : String(error)}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
throw new SystemAiError(`所有系统 AI API 均不可用:${errors.join(";")}`, "SYSTEM_AI_ALL_FAILED");
|
|
254
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -146,6 +146,11 @@ export interface SystemAiConfig {
|
|
|
146
146
|
authHeader?: SystemAiAuthHeader;
|
|
147
147
|
/** 自动导入时记录来源,仅用于设置页说明。 */
|
|
148
148
|
source?: "claude" | "codex" | "opencode" | "custom";
|
|
149
|
+
/**
|
|
150
|
+
* 其余可直连 API,按数组顺序依次尝试。保留顶层字段作为首选项,
|
|
151
|
+
* 以兼容已有配置与手工编辑入口。
|
|
152
|
+
*/
|
|
153
|
+
fallbacks?: SystemAiConfig[];
|
|
149
154
|
}
|
|
150
155
|
export type ClaudeModelSource = "builtin" | "configured" | "verified-cache" | "models-api";
|
|
151
156
|
export type ClaudeModelAvailability = "default" | "candidate" | "verified" | "stale";
|