@whispering233/ai-editor-server 0.0.30 → 0.0.33
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/client-dist/assets/{index-BBkKfUUn.css → index-oxaM6dHH.css} +1 -1
- package/client-dist/assets/index-tfvJfexp.js +523 -0
- package/client-dist/index.html +3 -3
- package/client-dist/provider-icons.svg +54 -0
- package/dist/backup.d.ts +8 -7
- package/dist/backup.d.ts.map +1 -1
- package/dist/backup.js +61 -45
- package/dist/backup.js.map +1 -1
- package/dist/chat-runtime.d.ts +44 -0
- package/dist/chat-runtime.d.ts.map +1 -0
- package/dist/chat-runtime.js +102 -0
- package/dist/chat-runtime.js.map +1 -0
- package/dist/debug.d.ts +1 -1
- package/dist/debug.d.ts.map +1 -1
- package/dist/debug.js +3 -3
- package/dist/debug.js.map +1 -1
- package/dist/http-dispatcher.d.ts +30 -0
- package/dist/http-dispatcher.d.ts.map +1 -0
- package/dist/http-dispatcher.js +101 -0
- package/dist/http-dispatcher.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -1
- package/dist/index.js.map +1 -1
- package/dist/last-project.d.ts +7 -0
- package/dist/last-project.d.ts.map +1 -0
- package/dist/last-project.js +48 -0
- package/dist/last-project.js.map +1 -0
- package/dist/middleware/project.d.ts.map +1 -1
- package/dist/middleware/project.js +4 -0
- package/dist/middleware/project.js.map +1 -1
- package/dist/model-runtime.d.ts +41 -0
- package/dist/model-runtime.d.ts.map +1 -0
- package/dist/model-runtime.js +81 -0
- package/dist/model-runtime.js.map +1 -0
- package/dist/routes/chat.d.ts +35 -72
- package/dist/routes/chat.d.ts.map +1 -1
- package/dist/routes/chat.js +390 -373
- package/dist/routes/chat.js.map +1 -1
- package/dist/routes/names.js +2 -2
- package/dist/routes/project.d.ts.map +1 -1
- package/dist/routes/project.js +6 -0
- package/dist/routes/project.js.map +1 -1
- package/dist/routes/settings.d.ts +0 -53
- package/dist/routes/settings.d.ts.map +1 -1
- package/dist/routes/settings.js +161 -194
- package/dist/routes/settings.js.map +1 -1
- package/package.json +8 -6
- package/client-dist/assets/index-ygCEt46m.js +0 -523
package/dist/routes/settings.js
CHANGED
|
@@ -1,218 +1,185 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
// 设置路由:GET/PUT /api/v1/settings/llm(pi 数据源)
|
|
2
|
+
//
|
|
3
|
+
// 契约 = docs/api/90-api-settings.md;配置载体与读写边界 = docs/design/config.md。
|
|
4
|
+
//
|
|
5
|
+
// **本模块不持有任何配置**:模型目录 / 凭据 / 运行参数全部来自 pi(`ModelRuntime` +
|
|
6
|
+
// `~/.pi/agent` 的 auth.json / models.json / settings.json),本端点只做「读快照 + 写 pi」。
|
|
7
|
+
// 凭据解析顺序(pi 0.85.1,`pi-ai` auth/resolve.js)——**auth.json 存量凭据优先**(一家一条,值可
|
|
8
|
+
// 为字面 key / `$ENV_VAR` 引用 / `!命令`)→ 该家无条目时才回落到 provider 内置环境变量
|
|
9
|
+
// (`DEEPSEEK_API_KEY` 等)——环境变量是兜底,不是与凭据并存的第二个来源。
|
|
10
|
+
//
|
|
11
|
+
// 写入语义(两个入口互不影响):
|
|
12
|
+
// - provider/model/thinking_level → pi settings(`~/.pi/agent/settings.json`)
|
|
13
|
+
// - api_key(单家)→ pi credential store(`~/.pi/agent/auth.json`);空串 = 清除该家存量的 API key 凭据
|
|
14
|
+
//
|
|
15
|
+
// 绝不写入项目文件(项目目录内的 `.pi/` 同样不参与:settings 实例 projectTrusted=false)。
|
|
13
16
|
import { Hono } from "hono";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { settingsLlmGetResSchema, settingsLlmPutReqSchema, userConfigFileSchema, } from "@whispering233/ai-editor-shared/schemas";
|
|
17
|
-
import { DEFAULT_PROVIDER, getAvailableModels, REGISTERED_PROVIDERS } from "@whispering233/ai-editor-llm";
|
|
17
|
+
import { CredentialSynchronizationError } from "@earendil-works/pi-coding-agent";
|
|
18
|
+
import { settingsLlmGetResSchema, settingsLlmPutReqSchema } from "@whispering233/ai-editor-shared/schemas";
|
|
18
19
|
import { HttpError, ok } from "../middleware/error.js";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const ENV_VAR_OF = {
|
|
26
|
-
deepseek: DEEPSEEK_API_KEY_ENV,
|
|
27
|
-
"opencode-go": OPENCODE_API_KEY_ENV,
|
|
28
|
-
};
|
|
29
|
-
/** 用户级配置文件相对 HOME 的路径(不入项目文件) */
|
|
30
|
-
export const USER_CONFIG_RELATIVE_PATH = join(".ai-editor", "config.json");
|
|
31
|
-
/** pi-agent 凭据文件相对 HOME 的路径(只读兜底;opencode-go 订阅 key 通常在此) */
|
|
32
|
-
export const PI_AGENT_AUTH_RELATIVE_PATH = join(".pi", "agent", "auth.json");
|
|
33
|
-
/** 缺省思考强度(与 SharedConfig 缺省一致,读侧兜底) */
|
|
34
|
-
export const DEFAULT_THINKING_LEVEL = "high";
|
|
35
|
-
/** 用户级配置文件绝对路径(os.homedir() 读 $HOME,测试设 HOME 即可隔离) */
|
|
36
|
-
export function userConfigPath() {
|
|
37
|
-
return join(homedir(), USER_CONFIG_RELATIVE_PATH);
|
|
38
|
-
}
|
|
39
|
-
/** pi-agent auth.json 绝对路径(只读;测试设 HOME 即可隔离) */
|
|
40
|
-
export function piAgentAuthPath() {
|
|
41
|
-
return join(homedir(), PI_AGENT_AUTH_RELATIVE_PATH);
|
|
20
|
+
import { PI_DEFAULT_THINKING_LEVEL, getModelRuntime, getSettingsManager, reloadModelRuntime, resolveActiveSelection, } from "../model-runtime.js";
|
|
21
|
+
/** 设置路由(挂载于 /api/v1/settings) */
|
|
22
|
+
export const settingsRoutes = new Hono();
|
|
23
|
+
/** 错误信息文本(不泄露凭据内容:pi 的错误只含 provider id/原因) */
|
|
24
|
+
function messageOf(err) {
|
|
25
|
+
return err instanceof Error ? err.message : String(err);
|
|
42
26
|
}
|
|
27
|
+
// GET /api/v1/settings/llm —— provider 目录 + 认证状态 + 激活模型(凭据明文永不回传)
|
|
28
|
+
settingsRoutes.get("/llm", async (c) => {
|
|
29
|
+
const modelRuntime = await getModelRuntime();
|
|
30
|
+
const settings = getSettingsManager();
|
|
31
|
+
// 本地重载(不联网):外部编辑 models.json / 环境变量变化在下次读取即可见
|
|
32
|
+
await reloadModelRuntime(modelRuntime);
|
|
33
|
+
const selection = await resolveActiveSelection(modelRuntime, settings);
|
|
34
|
+
const payload = settingsLlmGetResSchema.parse({
|
|
35
|
+
// 未配置任何模型 → 空串(前端展示「未配置」引导)
|
|
36
|
+
provider: selection?.provider ?? "",
|
|
37
|
+
model: selection?.modelId ?? "",
|
|
38
|
+
thinkingLevel: settings.getDefaultThinkingLevel() ?? PI_DEFAULT_THINKING_LEVEL,
|
|
39
|
+
// 全量 provider(含未配置认证者):设置页需要展示并引导录入 key
|
|
40
|
+
providers: modelRuntime.getProviders().map((provider) => {
|
|
41
|
+
const status = modelRuntime.getProviderAuthStatus(provider.id);
|
|
42
|
+
return {
|
|
43
|
+
id: provider.id,
|
|
44
|
+
displayName: provider.name,
|
|
45
|
+
authConfigured: status.configured,
|
|
46
|
+
...(status.source === undefined ? {} : { authSource: status.source }),
|
|
47
|
+
models: modelRuntime.getModels(provider.id).map((model) => ({
|
|
48
|
+
id: model.id,
|
|
49
|
+
provider: model.provider,
|
|
50
|
+
displayName: model.name,
|
|
51
|
+
contextWindow: model.contextWindow,
|
|
52
|
+
maxTokens: model.maxTokens,
|
|
53
|
+
reasoning: model.reasoning,
|
|
54
|
+
})),
|
|
55
|
+
};
|
|
56
|
+
}),
|
|
57
|
+
});
|
|
58
|
+
return c.json(ok(payload));
|
|
59
|
+
});
|
|
43
60
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
61
|
+
* 解析 PUT 的目标 (provider, model) 对(不变式:model 必须属于目标 provider 目录)。
|
|
62
|
+
* - provider+model 同传:跨 provider 激活(前端恒成对发送)
|
|
63
|
+
* - 只给 model:唯一归属自动定 provider;无归属 / 撞名歧义 → 400
|
|
64
|
+
* - 只给 provider:当前模型必须仍在其目录(防静默改用户选择)
|
|
48
65
|
*/
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return parsed.success ? parsed.data : {};
|
|
66
|
+
function resolveTargetModel(modelRuntime, input) {
|
|
67
|
+
const { provider, model, currentModelId } = input;
|
|
68
|
+
if (provider !== undefined && modelRuntime.getProvider(provider) === undefined) {
|
|
69
|
+
throw new HttpError(400, "VALIDATION_ERROR", `未知 provider: ${provider}`);
|
|
54
70
|
}
|
|
55
|
-
|
|
56
|
-
|
|
71
|
+
const modelIdsOf = (providerId) => new Set(modelRuntime.getModels(providerId).map((m) => m.id));
|
|
72
|
+
if (model !== undefined) {
|
|
73
|
+
let targetProvider = provider;
|
|
74
|
+
if (targetProvider === undefined) {
|
|
75
|
+
const owners = modelRuntime.getProviders().filter((p) => modelIdsOf(p.id).has(model));
|
|
76
|
+
if (owners.length === 0) {
|
|
77
|
+
throw new HttpError(400, "VALIDATION_ERROR", `模型不在任何 provider 目录: ${model}`);
|
|
78
|
+
}
|
|
79
|
+
if (owners.length > 1) {
|
|
80
|
+
throw new HttpError(400, "VALIDATION_ERROR", `模型在多 provider 目录中存在(撞名),请显式指定 provider: ${owners.map((p) => p.id).join("/")}`);
|
|
81
|
+
}
|
|
82
|
+
targetProvider = owners[0].id;
|
|
83
|
+
}
|
|
84
|
+
if (!modelIdsOf(targetProvider).has(model)) {
|
|
85
|
+
const displayName = modelRuntime.getProvider(targetProvider)?.name ?? targetProvider;
|
|
86
|
+
throw new HttpError(400, "VALIDATION_ERROR", `模型 ${model} 不在 provider ${displayName} 目录`);
|
|
87
|
+
}
|
|
88
|
+
return { provider: targetProvider, modelId: model };
|
|
57
89
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return
|
|
90
|
+
if (provider !== undefined) {
|
|
91
|
+
const current = currentModelId;
|
|
92
|
+
if (current === undefined || current === "") {
|
|
93
|
+
throw new HttpError(400, "VALIDATION_ERROR", `切换 provider 需显式指定 model(当前无激活模型)`);
|
|
94
|
+
}
|
|
95
|
+
if (!modelIdsOf(provider).has(current)) {
|
|
96
|
+
const displayName = modelRuntime.getProvider(provider)?.name ?? provider;
|
|
97
|
+
throw new HttpError(400, "VALIDATION_ERROR", `当前模型 ${current} 不在 provider ${displayName} 目录,请显式选择模型`);
|
|
98
|
+
}
|
|
99
|
+
return { provider, modelId: current };
|
|
68
100
|
}
|
|
69
|
-
if (provider === "deepseek" && typeof cfg.api_key === "string" && cfg.api_key !== "")
|
|
70
|
-
return cfg.api_key;
|
|
71
101
|
return null;
|
|
72
102
|
}
|
|
73
|
-
/** pi-agent auth.json 只读兜底:条目 type=api_key 且 key 非空才生效;任何异常 → null */
|
|
74
|
-
function piAgentKeyOf(provider) {
|
|
75
|
-
try {
|
|
76
|
-
const obj = JSON.parse(readFileSync(piAgentAuthPath(), "utf8"));
|
|
77
|
-
const entry = obj?.[provider];
|
|
78
|
-
if (typeof entry !== "object" || entry === null)
|
|
79
|
-
return null;
|
|
80
|
-
const e = entry;
|
|
81
|
-
if (e.type === "api_key" && typeof e.key === "string" && e.key !== "")
|
|
82
|
-
return e.key;
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
catch {
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
/** provider 展示名(错误文案/调试用);未知 provider → 原 id */
|
|
90
|
-
export function providerDisplayName(provider) {
|
|
91
|
-
return REGISTERED_PROVIDERS.find((p) => p.id === provider)?.displayName ?? provider;
|
|
92
|
-
}
|
|
93
|
-
/** provider 环境变量名(错误文案用) */
|
|
94
|
-
export function providerEnvVar(provider) {
|
|
95
|
-
return ENV_VAR_OF[provider];
|
|
96
|
-
}
|
|
97
103
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
104
|
+
* 写入 / 清除单家 API key 凭据(pi credential store)。
|
|
105
|
+
* - 非空 key:走 pi 的 `login("api_key")`——交互式提示由我们不变量地回填本次输入
|
|
106
|
+
* (pi 会把凭据落盘到 auth.json 并做**本地**凭据同步,不联网)
|
|
107
|
+
* - 空 key:删除该家存量的 api_key 凭据(回到 env 解析);OAuth 凭据不在此入口删除
|
|
108
|
+
* (设置页只写 API key,误删会让用户的订阅登录失效)
|
|
100
109
|
*/
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
* - provider/model/thinking_level 顶层覆盖
|
|
111
|
-
* - api_keys 按键合并:提供即覆盖,空字符串 = 删除该键(清空 map 时整个字段移除)
|
|
112
|
-
* - 落盘 schema_version=2;v1 旧 api_key 字段保留不迁移(读侧兼容已覆盖)
|
|
113
|
-
* 原子写:复用 @whispering233/ai-editor-db 的 writeJsonAtomic(临时文件 wx 独占 +
|
|
114
|
-
* 文件 fsync + rename + 目录 fsync)。目标在 $HOME/.ai-editor/ 下,目录 fsync 在部分
|
|
115
|
-
* 平台/文件系统受限时由 db 实现静默忽略(主链路已保证文件内容完整)
|
|
116
|
-
*/
|
|
117
|
-
export function saveUserConfig(partial) {
|
|
118
|
-
const file = userConfigPath();
|
|
119
|
-
const next = { ...getUserConfig() };
|
|
120
|
-
if (partial.provider !== undefined)
|
|
121
|
-
next.provider = partial.provider;
|
|
122
|
-
if (partial.model !== undefined)
|
|
123
|
-
next.model = partial.model;
|
|
124
|
-
if (partial.thinking_level !== undefined)
|
|
125
|
-
next.thinking_level = partial.thinking_level;
|
|
126
|
-
if (partial.api_keys !== undefined) {
|
|
127
|
-
const map = { ...(next.api_keys ?? {}) };
|
|
128
|
-
for (const [k, v] of Object.entries(partial.api_keys)) {
|
|
129
|
-
if (v === "")
|
|
130
|
-
delete map[k]; // 空字符串 = 清除该家已保存 key
|
|
131
|
-
else
|
|
132
|
-
map[k] = v;
|
|
110
|
+
async function applyApiKey(modelRuntime, providerId, key) {
|
|
111
|
+
if (modelRuntime.getProvider(providerId) === undefined) {
|
|
112
|
+
throw new HttpError(400, "VALIDATION_ERROR", `未知 provider: ${providerId}`);
|
|
113
|
+
}
|
|
114
|
+
const trimmed = key.trim();
|
|
115
|
+
if (trimmed === "") {
|
|
116
|
+
const stored = (await modelRuntime.listCredentials()).find((entry) => entry.providerId === providerId);
|
|
117
|
+
if (stored?.type === "oauth") {
|
|
118
|
+
throw new HttpError(400, "VALIDATION_ERROR", `${providerId} 当前使用 OAuth 凭据登录,本入口只能清除 API key(请用 pi CLI 管理订阅登录)`);
|
|
133
119
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
120
|
+
await modelRuntime.logout(providerId);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// 与清除路径对称:存量 OAuth(订阅登录)不得被一次 API key 写入静默覆盖
|
|
124
|
+
// (pi 每 provider 只存一条凭据;覆盖后用户需用 pi CLI 重新登录才能恢复)
|
|
125
|
+
const existing = (await modelRuntime.listCredentials()).find((entry) => entry.providerId === providerId);
|
|
126
|
+
if (existing?.type === "oauth") {
|
|
127
|
+
throw new HttpError(400, "VALIDATION_ERROR", `${providerId} 当前使用 OAuth 凭据登录,本入口不覆盖订阅登录(请用 pi CLI 管理订阅登录)`);
|
|
128
|
+
}
|
|
129
|
+
const interaction = {
|
|
130
|
+
notify: () => {
|
|
131
|
+
// pi 的登录进度提示(终端向);本入口无 UI,忽略
|
|
132
|
+
},
|
|
133
|
+
prompt: async (prompt) => {
|
|
134
|
+
if (prompt.type === "secret")
|
|
135
|
+
return trimmed;
|
|
136
|
+
if (prompt.type === "select") {
|
|
137
|
+
const option = prompt.options.find((o) => o.id === "api-key" || o.id === "bearer-token");
|
|
138
|
+
if (option !== undefined)
|
|
139
|
+
return option.id;
|
|
140
|
+
}
|
|
141
|
+
throw new Error(`${providerId} 需要交互式认证配置,本入口只支持 API key`);
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
try {
|
|
145
|
+
await modelRuntime.login(providerId, "api_key", interaction);
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
// 凭据已落盘、仅本地状态同步失败:按服务端错误呈现(让用户重试而不是怀疑输入)
|
|
149
|
+
if (err instanceof CredentialSynchronizationError) {
|
|
150
|
+
throw new HttpError(500, "INTERNAL_ERROR", `${providerId} 的凭据已写入,但模型状态同步失败:${messageOf(err)}`);
|
|
151
|
+
}
|
|
152
|
+
throw new HttpError(400, "VALIDATION_ERROR", `保存 ${providerId} 的 API key 失败:${messageOf(err)}`);
|
|
138
153
|
}
|
|
139
|
-
// 保存时落新格式:schema_version=2(读侧兼容已保证 v0/v1 文件零破坏)
|
|
140
|
-
next.schema_version = 2;
|
|
141
|
-
mkdirSync(dirname(file), { recursive: true }); // writeJsonAtomic 不负责建目录
|
|
142
|
-
writeJsonAtomic(file, next);
|
|
143
|
-
return next;
|
|
144
154
|
}
|
|
145
|
-
|
|
146
|
-
export const settingsRoutes = new Hono();
|
|
147
|
-
// GET /api/v1/settings/llm —— 读取 LLM 配置(key 不回传明文,仅掩码;全量 provider 目录 + 各家 key 状态)
|
|
148
|
-
settingsRoutes.get("/llm", (c) => {
|
|
149
|
-
const cfg = getUserConfig();
|
|
150
|
-
const provider = effectiveProvider();
|
|
151
|
-
const payload = settingsLlmGetResSchema.parse({
|
|
152
|
-
provider,
|
|
153
|
-
model: cfg.model ?? DEFAULT_MODEL,
|
|
154
|
-
thinkingLevel: cfg.thinking_level ?? DEFAULT_THINKING_LEVEL,
|
|
155
|
-
// 全量注册 provider:各家目录 + 有效 key 状态(前端下拉分组 + key 缺失整组禁用的依据)
|
|
156
|
-
providers: REGISTERED_PROVIDERS.map((p) => {
|
|
157
|
-
const { key, masked } = effectiveApiKey(p.id);
|
|
158
|
-
return {
|
|
159
|
-
id: p.id,
|
|
160
|
-
displayName: p.displayName,
|
|
161
|
-
apiKeySet: key !== null,
|
|
162
|
-
...(masked !== undefined ? { apiKeyMasked: masked } : {}),
|
|
163
|
-
models: getAvailableModels(p.id).map((m) => ({
|
|
164
|
-
id: m.id,
|
|
165
|
-
provider: m.provider,
|
|
166
|
-
displayName: m.displayName,
|
|
167
|
-
contextWindow: m.contextWindow,
|
|
168
|
-
maxTokens: m.maxTokens,
|
|
169
|
-
reasoning: m.reasoning,
|
|
170
|
-
})),
|
|
171
|
-
};
|
|
172
|
-
}),
|
|
173
|
-
});
|
|
174
|
-
return c.json(ok(payload));
|
|
175
|
-
});
|
|
176
|
-
// PUT /api/v1/settings/llm —— 更新 LLM 配置(写入 ~/.ai-editor/config.json,绝不入项目文件)
|
|
177
|
-
// 校验不变式:最终 (provider, model) 必须满足 model ∈ provider 目录
|
|
178
|
-
// - provider+model 成对(跨 provider 激活,前端恒成对发送);model 不在该目录 → 400
|
|
179
|
-
// - 只给 model:唯一归属自动定 provider;撞名(deepseek-v4-flash/pro 两目录都有)歧义 → 400(须显式 provider)
|
|
180
|
-
// - 只给 provider:当前 model 不在新目录 → 400(须显式 model;防静默改用户选择)
|
|
155
|
+
// PUT /api/v1/settings/llm —— 写 pi settings(模型/思考强度)+ pi credential store(key)
|
|
181
156
|
settingsRoutes.put("/llm", async (c) => {
|
|
182
157
|
const raw = await c.req.json().catch(() => null); // 空 body / 非法 JSON → 校验失败
|
|
183
158
|
const parsed = settingsLlmPutReqSchema.safeParse(raw);
|
|
184
|
-
if (!parsed.success)
|
|
159
|
+
if (!parsed.success)
|
|
185
160
|
throw parsed.error; // → app.onError → 400 VALIDATION_ERROR(含 fields)
|
|
161
|
+
const { provider, model, thinking_level, api_key } = parsed.data;
|
|
162
|
+
const modelRuntime = await getModelRuntime();
|
|
163
|
+
const settings = getSettingsManager();
|
|
164
|
+
// 本地重载(不联网):PUT 与 GET 看到同一份目录(外部编辑 models.json 后直接 PUT 模型也能校验通过)
|
|
165
|
+
await reloadModelRuntime(modelRuntime);
|
|
166
|
+
// ---- 凭据(独立于模型切换:只保存 key 不改激活模型) ----
|
|
167
|
+
if (api_key !== undefined) {
|
|
168
|
+
await applyApiKey(modelRuntime, api_key.provider, api_key.key);
|
|
186
169
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
else if (owners.length === 0)
|
|
198
|
-
throw new HttpError(400, "VALIDATION_ERROR", `模型不在任何已注册 provider 目录: ${model}`);
|
|
199
|
-
else
|
|
200
|
-
throw new HttpError(400, "VALIDATION_ERROR", `模型在多 provider 目录中存在(撞名),请显式指定 provider: ${owners.map((p) => p.id).join("/")}`);
|
|
201
|
-
}
|
|
202
|
-
if (!getAvailableModels(targetProvider).some((m) => m.id === model)) {
|
|
203
|
-
throw new HttpError(400, "VALIDATION_ERROR", `模型 ${model} 不在 provider ${providerDisplayName(targetProvider)} 目录`);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
else if (provider !== undefined && !getAvailableModels(provider).some((m) => m.id === currentModel)) {
|
|
207
|
-
// 只给 provider:当前 model 必须仍在新目录(默认模型两目录都有,正常路径恒过)
|
|
208
|
-
throw new HttpError(400, "VALIDATION_ERROR", `当前模型 ${currentModel} 不在 provider ${providerDisplayName(provider)} 目录,请显式选择模型`);
|
|
170
|
+
// ---- 激活模型(provider + model 成对;缺省按当前激活模型消歧) ----
|
|
171
|
+
if (provider !== undefined || model !== undefined) {
|
|
172
|
+
const selection = await resolveActiveSelection(modelRuntime, settings);
|
|
173
|
+
const target = resolveTargetModel(modelRuntime, {
|
|
174
|
+
provider,
|
|
175
|
+
model,
|
|
176
|
+
currentModelId: selection?.modelId,
|
|
177
|
+
});
|
|
178
|
+
if (target !== null)
|
|
179
|
+
settings.setDefaultModelAndProvider(target.provider, target.modelId);
|
|
209
180
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
model,
|
|
213
|
-
thinking_level,
|
|
214
|
-
api_keys,
|
|
215
|
-
});
|
|
181
|
+
if (thinking_level !== undefined)
|
|
182
|
+
settings.setDefaultThinkingLevel(thinking_level);
|
|
216
183
|
return c.json(ok({ saved: true })); // settingsLlmPutResSchema 形状
|
|
217
184
|
});
|
|
218
185
|
//# sourceMappingURL=settings.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/routes/settings.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/routes/settings.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@whispering233/ai-editor-server",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.33",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Hono API 服务端(单进程:API + SPA 静态托管)",
|
|
6
6
|
"type": "module",
|
|
@@ -31,17 +31,19 @@
|
|
|
31
31
|
"postpack": "node ../../scripts/restore-package-json.mjs"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@whispering233/ai-editor-agent": "0.0.30",
|
|
35
|
-
"@whispering233/ai-editor-db": "0.0.30",
|
|
36
|
-
"@whispering233/ai-editor-llm": "0.0.30",
|
|
37
|
-
"@whispering233/ai-editor-shared": "0.0.30",
|
|
38
|
-
"@whispering233/ai-editor-tools": "0.0.30",
|
|
39
34
|
"@hono/node-server": "^2.0.12",
|
|
35
|
+
"@whispering233/ai-editor-agent": "0.0.33",
|
|
36
|
+
"@whispering233/ai-editor-db": "0.0.33",
|
|
37
|
+
"@whispering233/ai-editor-shared": "0.0.33",
|
|
38
|
+
"@whispering233/ai-editor-tools": "0.0.33",
|
|
40
39
|
"fflate": "^0.8.3",
|
|
41
40
|
"hono": "^4.7.0",
|
|
41
|
+
"undici": "8.5.0",
|
|
42
42
|
"zod": "^4.4.3"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
+
"@earendil-works/pi-ai": "0.85.1",
|
|
46
|
+
"@earendil-works/pi-coding-agent": "0.85.1",
|
|
45
47
|
"@types/node": "^24.13.3",
|
|
46
48
|
"tsx": "^4.23.1",
|
|
47
49
|
"typescript": "^5.8.0"
|