@actalk/inkos-studio 1.3.1 → 1.3.2
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/api/server.d.ts.map +1 -1
- package/dist/api/server.js +205 -77
- package/dist/api/server.js.map +1 -1
- package/dist/assets/{_baseUniq-C-g5htb0.js → _baseUniq-DJdqK28N.js} +1 -1
- package/dist/assets/{arc-B13N8XoZ.js → arc-DVORsyAf.js} +1 -1
- package/dist/assets/{architectureDiagram-Q4EWVU46-COxJdWXS.js → architectureDiagram-Q4EWVU46-DjRJ006-.js} +1 -1
- package/dist/assets/{blockDiagram-DXYQGD6D-DS_ZpHJ5.js → blockDiagram-DXYQGD6D-BrZF7yPR.js} +1 -1
- package/dist/assets/{c4Diagram-AHTNJAMY-BxSij0o0.js → c4Diagram-AHTNJAMY-BUjFHcqo.js} +1 -1
- package/dist/assets/channel-DAwqKtrf.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-DFB21W8n.js → chunk-4BX2VUAB-Ck01cjOa.js} +1 -1
- package/dist/assets/{chunk-4TB4RGXK-BnJ77uxv.js → chunk-4TB4RGXK-fYtywxAI.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-CQ_glRcB.js → chunk-55IACEB6-CFNhvTQn.js} +1 -1
- package/dist/assets/{chunk-EDXVE4YY-Dyd-Q8PI.js → chunk-EDXVE4YY-CowuqSRt.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-BDiYToLm.js → chunk-FMBD7UC4-BWgw8cku.js} +1 -1
- package/dist/assets/{chunk-OYMX7WX6-DEzfeklY.js → chunk-OYMX7WX6-C01Lww9H.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-CJPYUr7I.js → chunk-QZHKN3VN-BjoO0Ggu.js} +1 -1
- package/dist/assets/{chunk-YZCP3GAM-CBxzODC9.js → chunk-YZCP3GAM-1_oXxO-A.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-BzzGlc6B.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-BzzGlc6B.js +1 -0
- package/dist/assets/clone-JyT4Y8M6.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-BDcwl962.js → cose-bilkent-S5V4N54A-Bh_7Rb-J.js} +1 -1
- package/dist/assets/{dagre-KV5264BT-q3iXqwp5.js → dagre-KV5264BT-cbSoIqAE.js} +1 -1
- package/dist/assets/{diagram-5BDNPKRD-CSyQp4XD.js → diagram-5BDNPKRD-CFZvz8Qc.js} +1 -1
- package/dist/assets/{diagram-G4DWMVQ6-CyO2u0j-.js → diagram-G4DWMVQ6-M_Np88bq.js} +1 -1
- package/dist/assets/{diagram-MMDJMWI5-0Rum0AK-.js → diagram-MMDJMWI5-DS_yZb3X.js} +1 -1
- package/dist/assets/{diagram-TYMM5635-CpFFDrQG.js → diagram-TYMM5635-BTfEx65z.js} +1 -1
- package/dist/assets/{erDiagram-SMLLAGMA-DgPF_t0E.js → erDiagram-SMLLAGMA-BbnGBWpv.js} +1 -1
- package/dist/assets/{flowDiagram-DWJPFMVM-D1sVXEs4.js → flowDiagram-DWJPFMVM-Dit2fK7O.js} +1 -1
- package/dist/assets/{ganttDiagram-T4ZO3ILL-Cah2BN0H.js → ganttDiagram-T4ZO3ILL-9qXAYw0r.js} +1 -1
- package/dist/assets/{gitGraphDiagram-UUTBAWPF-Bb32ArKL.js → gitGraphDiagram-UUTBAWPF-Bt07S0SH.js} +1 -1
- package/dist/assets/{graph-DPYgoB8M.js → graph-BsmzKTDM.js} +1 -1
- package/dist/assets/{highlighted-body-OFNGDK62-C5OR7F5H.js → highlighted-body-OFNGDK62-C8RVuZxY.js} +1 -1
- package/dist/assets/index-B6QaiVya.css +1 -0
- package/dist/assets/{index-CdSd4xAM.js → index-C1gtMavo.js} +134 -134
- package/dist/assets/{infoDiagram-42DDH7IO-BRPlXDRX.js → infoDiagram-42DDH7IO-CfENgx7D.js} +1 -1
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-DIMNZ5LJ.js → ishikawaDiagram-UXIWVN3A-DkgHB7oO.js} +1 -1
- package/dist/assets/{journeyDiagram-VCZTEJTY-CQ9Buybm.js → journeyDiagram-VCZTEJTY-D2QeXsGQ.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-CsZuyfP0.js → kanban-definition-6JOO6SKY-C0PuwOLs.js} +1 -1
- package/dist/assets/{layout-wiYzOkdn.js → layout-BcHWSmtt.js} +1 -1
- package/dist/assets/{linear-D2SNljoC.js → linear-DhG1QI2m.js} +1 -1
- package/dist/assets/{min-NQHw0HnA.js → min-BMoenYqy.js} +1 -1
- package/dist/assets/{mindmap-definition-QFDTVHPH-CLFneoyC.js → mindmap-definition-QFDTVHPH-BYqUeO0C.js} +1 -1
- package/dist/assets/{pieDiagram-DEJITSTG-CGetk0td.js → pieDiagram-DEJITSTG-vL_-Xhe9.js} +1 -1
- package/dist/assets/{quadrantDiagram-34T5L4WZ-DGvGQ55s.js → quadrantDiagram-34T5L4WZ-DlqKvO89.js} +1 -1
- package/dist/assets/{requirementDiagram-MS252O5E-BtWYKfId.js → requirementDiagram-MS252O5E-CPK-hyCG.js} +1 -1
- package/dist/assets/{sankeyDiagram-XADWPNL6-B7dHCFEb.js → sankeyDiagram-XADWPNL6-BYcLLwpo.js} +1 -1
- package/dist/assets/{sequenceDiagram-FGHM5R23-SCghmIaN.js → sequenceDiagram-FGHM5R23-BHGNED6Y.js} +1 -1
- package/dist/assets/{stateDiagram-FHFEXIEX-Da-qWGV4.js → stateDiagram-FHFEXIEX-CFtLWm9U.js} +1 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-RRV8L_Kf.js +1 -0
- package/dist/assets/{timeline-definition-GMOUNBTQ-DXzhFcp9.js → timeline-definition-GMOUNBTQ-DMXdyWjw.js} +1 -1
- package/dist/assets/{vennDiagram-DHZGUBPP-CHwAxJ-d.js → vennDiagram-DHZGUBPP-CDQM5jJf.js} +1 -1
- package/dist/assets/{wardley-RL74JXVD-DOtmpHBm.js → wardley-RL74JXVD-DNoHFV5F.js} +1 -1
- package/dist/assets/{wardleyDiagram-NUSXRM2D-DqzDdtmB.js → wardleyDiagram-NUSXRM2D-CwhahRKS.js} +1 -1
- package/dist/assets/{xychartDiagram-5P7HB3ND-B5UU8au-.js → xychartDiagram-5P7HB3ND-C0WIVgD-.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +2 -2
- package/dist/assets/channel-C2r273Z1.js +0 -1
- package/dist/assets/classDiagram-6PBFFD2Q-CSUo3NoK.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-CSUo3NoK.js +0 -1
- package/dist/assets/clone-DJaLNYqk.js +0 -1
- package/dist/assets/index-hHRX6lZN.css +0 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-EC5r6wTi.js +0 -1
package/dist/api/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/api/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAI5B,OAAO,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/api/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAI5B,OAAO,EA8BL,KAAK,aAAa,EAInB,MAAM,oBAAoB,CAAC;AAwc5B,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,8EA2wD5E;AAID,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,IAAI,SAAO,EACX,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACxC,OAAO,CAAC,IAAI,CAAC,CA8Cf"}
|
package/dist/api/server.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Hono } from "hono";
|
|
|
2
2
|
import { cors } from "hono/cors";
|
|
3
3
|
import { streamSSE } from "hono/streaming";
|
|
4
4
|
import { serve } from "@hono/node-server";
|
|
5
|
-
import { StateManager, PipelineRunner, createLLMClient, createLogger, createInteractionToolsFromDeps, computeAnalytics, loadProjectConfig, loadProjectSession, processProjectInteractionRequest, resolveSessionActiveBook, findOrCreateBookSession, listBookSessions, loadBookSession, persistBookSession, appendBookSessionMessage, runAgentSession, buildAgentSystemPrompt, resolveServicePreset, resolveServiceModel, loadSecrets, saveSecrets, getServiceApiKey, listModelsForService, chatCompletion, GLOBAL_ENV_PATH, } from "@actalk/inkos-core";
|
|
5
|
+
import { StateManager, PipelineRunner, createLLMClient, createLogger, createInteractionToolsFromDeps, computeAnalytics, loadProjectConfig, loadProjectSession, processProjectInteractionRequest, resolveSessionActiveBook, findOrCreateBookSession, listBookSessions, loadBookSession, persistBookSession, appendBookSessionMessage, runAgentSession, buildAgentSystemPrompt, resolveServicePreset, resolveServiceProviderFamily, resolveServiceModelsBaseUrl, resolveServiceModel, loadSecrets, saveSecrets, getServiceApiKey, listModelsForService, chatCompletion, GLOBAL_ENV_PATH, } from "@actalk/inkos-core";
|
|
6
6
|
import { access, readFile, readdir, writeFile } from "node:fs/promises";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { isSafeBookId } from "./safety.js";
|
|
@@ -82,7 +82,7 @@ function normalizeServiceEntry(serviceId, value) {
|
|
|
82
82
|
return {
|
|
83
83
|
service: "custom",
|
|
84
84
|
name: decodeURIComponent(serviceId.slice("custom:".length)),
|
|
85
|
-
...(typeof value.baseUrl === "string" && value.baseUrl.length > 0 ? { baseUrl:
|
|
85
|
+
...(typeof value.baseUrl === "string" && value.baseUrl.length > 0 ? { baseUrl: value.baseUrl } : {}),
|
|
86
86
|
...(typeof value.temperature === "number" ? { temperature: value.temperature } : {}),
|
|
87
87
|
...(typeof value.maxTokens === "number" ? { maxTokens: value.maxTokens } : {}),
|
|
88
88
|
...(value.apiFormat === "chat" || value.apiFormat === "responses" ? { apiFormat: value.apiFormat } : {}),
|
|
@@ -93,7 +93,7 @@ function normalizeServiceEntry(serviceId, value) {
|
|
|
93
93
|
return {
|
|
94
94
|
service: "custom",
|
|
95
95
|
...(typeof value.name === "string" && value.name.length > 0 ? { name: value.name } : {}),
|
|
96
|
-
...(typeof value.baseUrl === "string" && value.baseUrl.length > 0 ? { baseUrl:
|
|
96
|
+
...(typeof value.baseUrl === "string" && value.baseUrl.length > 0 ? { baseUrl: value.baseUrl } : {}),
|
|
97
97
|
...(typeof value.temperature === "number" ? { temperature: value.temperature } : {}),
|
|
98
98
|
...(typeof value.maxTokens === "number" ? { maxTokens: value.maxTokens } : {}),
|
|
99
99
|
...(value.apiFormat === "chat" || value.apiFormat === "responses" ? { apiFormat: value.apiFormat } : {}),
|
|
@@ -111,30 +111,19 @@ function normalizeServiceEntry(serviceId, value) {
|
|
|
111
111
|
function normalizeConfigSource(value) {
|
|
112
112
|
return value === "studio" ? "studio" : "env";
|
|
113
113
|
}
|
|
114
|
-
/** Ensure custom baseUrl ends with /v1 (common convention for OpenAI-compatible APIs). */
|
|
115
|
-
function normalizeBaseUrl(url) {
|
|
116
|
-
const trimmed = url.replace(/\/+$/, "");
|
|
117
|
-
if (/\/v\d+$/.test(trimmed))
|
|
118
|
-
return trimmed;
|
|
119
|
-
return trimmed + "/v1";
|
|
120
|
-
}
|
|
121
114
|
function normalizeServiceConfig(raw) {
|
|
122
115
|
if (Array.isArray(raw)) {
|
|
123
116
|
return raw
|
|
124
117
|
.filter((entry) => Boolean(entry) && typeof entry === "object")
|
|
125
|
-
.map((entry) => {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
...(entry.apiFormat === "chat" || entry.apiFormat === "responses" ? { apiFormat: entry.apiFormat } : {}),
|
|
135
|
-
...(typeof entry.stream === "boolean" ? { stream: entry.stream } : {}),
|
|
136
|
-
};
|
|
137
|
-
});
|
|
118
|
+
.map((entry) => ({
|
|
119
|
+
service: typeof entry.service === "string" && entry.service.length > 0 ? entry.service : "custom",
|
|
120
|
+
...(typeof entry.name === "string" && entry.name.length > 0 ? { name: entry.name } : {}),
|
|
121
|
+
...(typeof entry.baseUrl === "string" && entry.baseUrl.length > 0 ? { baseUrl: entry.baseUrl } : {}),
|
|
122
|
+
...(typeof entry.temperature === "number" ? { temperature: entry.temperature } : {}),
|
|
123
|
+
...(typeof entry.maxTokens === "number" ? { maxTokens: entry.maxTokens } : {}),
|
|
124
|
+
...(entry.apiFormat === "chat" || entry.apiFormat === "responses" ? { apiFormat: entry.apiFormat } : {}),
|
|
125
|
+
...(typeof entry.stream === "boolean" ? { stream: entry.stream } : {}),
|
|
126
|
+
}));
|
|
138
127
|
}
|
|
139
128
|
if (raw && typeof raw === "object") {
|
|
140
129
|
return Object.entries(raw)
|
|
@@ -206,7 +195,7 @@ async function readEnvConfigStatus(root) {
|
|
|
206
195
|
}
|
|
207
196
|
async function resolveConfiguredServiceBaseUrl(root, serviceId, inlineBaseUrl) {
|
|
208
197
|
if (inlineBaseUrl?.trim())
|
|
209
|
-
return
|
|
198
|
+
return inlineBaseUrl.trim();
|
|
210
199
|
if (!isCustomServiceId(serviceId)) {
|
|
211
200
|
return resolveServicePreset(serviceId)?.baseUrl;
|
|
212
201
|
}
|
|
@@ -230,6 +219,146 @@ async function resolveConfiguredServiceEntry(root, serviceId) {
|
|
|
230
219
|
return undefined;
|
|
231
220
|
}
|
|
232
221
|
}
|
|
222
|
+
function buildProbePlans(preferredApiFormat, preferredStream) {
|
|
223
|
+
const candidates = [];
|
|
224
|
+
const seen = new Set();
|
|
225
|
+
const push = (apiFormat, stream) => {
|
|
226
|
+
const key = `${apiFormat}:${stream ? "1" : "0"}`;
|
|
227
|
+
if (seen.has(key))
|
|
228
|
+
return;
|
|
229
|
+
seen.add(key);
|
|
230
|
+
candidates.push({ apiFormat, stream });
|
|
231
|
+
};
|
|
232
|
+
if (preferredApiFormat) {
|
|
233
|
+
push(preferredApiFormat, preferredStream ?? false);
|
|
234
|
+
push(preferredApiFormat, !(preferredStream ?? false));
|
|
235
|
+
}
|
|
236
|
+
const alternate = preferredApiFormat === "responses" ? "chat" : "responses";
|
|
237
|
+
push(alternate, false);
|
|
238
|
+
push(alternate, true);
|
|
239
|
+
push("chat", false);
|
|
240
|
+
push("chat", true);
|
|
241
|
+
push("responses", false);
|
|
242
|
+
push("responses", true);
|
|
243
|
+
return candidates;
|
|
244
|
+
}
|
|
245
|
+
function buildModelCandidates(args) {
|
|
246
|
+
const seen = new Set();
|
|
247
|
+
const candidates = [];
|
|
248
|
+
const push = (value) => {
|
|
249
|
+
if (!value || value.trim().length === 0)
|
|
250
|
+
return;
|
|
251
|
+
const id = value.trim();
|
|
252
|
+
if (seen.has(id))
|
|
253
|
+
return;
|
|
254
|
+
seen.add(id);
|
|
255
|
+
candidates.push(id);
|
|
256
|
+
};
|
|
257
|
+
push(args.preferredModel);
|
|
258
|
+
push(args.configModel);
|
|
259
|
+
push(args.envModel ?? undefined);
|
|
260
|
+
for (const model of args.discoveredModels)
|
|
261
|
+
push(model.id);
|
|
262
|
+
push("gpt-5.4");
|
|
263
|
+
push("gpt-4o");
|
|
264
|
+
push("claude-sonnet-4-6");
|
|
265
|
+
push("MiniMax-M2.7");
|
|
266
|
+
push("kimi-k2.5");
|
|
267
|
+
return candidates;
|
|
268
|
+
}
|
|
269
|
+
async function fetchModelsFromServiceBaseUrl(serviceId, baseUrl, apiKey) {
|
|
270
|
+
const modelsBaseUrl = isCustomServiceId(serviceId)
|
|
271
|
+
? baseUrl
|
|
272
|
+
: resolveServiceModelsBaseUrl(serviceId) ?? baseUrl;
|
|
273
|
+
const modelsUrl = modelsBaseUrl.replace(/\/$/, "") + "/models";
|
|
274
|
+
try {
|
|
275
|
+
const res = await fetch(modelsUrl, {
|
|
276
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
277
|
+
signal: AbortSignal.timeout(10_000),
|
|
278
|
+
});
|
|
279
|
+
if (!res.ok) {
|
|
280
|
+
const body = await res.text().catch(() => "");
|
|
281
|
+
return { models: [], error: `服务商返回 ${res.status}: ${body.slice(0, 200)}` };
|
|
282
|
+
}
|
|
283
|
+
const json = await res.json();
|
|
284
|
+
return {
|
|
285
|
+
models: (json.data ?? []).map((m) => ({ id: m.id, name: m.id })),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
return {
|
|
290
|
+
models: [],
|
|
291
|
+
error: error instanceof Error ? error.message : String(error),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
async function probeServiceCapabilities(args) {
|
|
296
|
+
const rawConfig = await loadRawConfig(args.root).catch(() => ({}));
|
|
297
|
+
const llm = rawConfig.llm ?? {};
|
|
298
|
+
const envConfig = await readEnvConfigStatus(args.root);
|
|
299
|
+
const envModel = envConfig.effectiveSource === "project"
|
|
300
|
+
? envConfig.project.model
|
|
301
|
+
: envConfig.effectiveSource === "global"
|
|
302
|
+
? envConfig.global.model
|
|
303
|
+
: null;
|
|
304
|
+
const baseService = isCustomServiceId(args.service) ? "custom" : args.service;
|
|
305
|
+
const modelsResponse = await fetchModelsFromServiceBaseUrl(baseService, args.baseUrl, args.apiKey);
|
|
306
|
+
const discoveredModels = modelsResponse.models;
|
|
307
|
+
const modelCandidates = buildModelCandidates({
|
|
308
|
+
preferredModel: args.preferredModel,
|
|
309
|
+
configModel: typeof llm.defaultModel === "string" ? llm.defaultModel : typeof llm.model === "string" ? llm.model : undefined,
|
|
310
|
+
envModel,
|
|
311
|
+
discoveredModels,
|
|
312
|
+
});
|
|
313
|
+
if (modelCandidates.length === 0) {
|
|
314
|
+
return {
|
|
315
|
+
ok: false,
|
|
316
|
+
models: [],
|
|
317
|
+
error: "无法自动确定模型,请先填写可用模型或提供支持 /models 的服务端点。",
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
let lastError = modelsResponse.error ?? "自动探测失败";
|
|
321
|
+
for (const model of modelCandidates) {
|
|
322
|
+
for (const plan of buildProbePlans(args.preferredApiFormat, args.preferredStream)) {
|
|
323
|
+
const client = createLLMClient({
|
|
324
|
+
provider: resolveServiceProviderFamily(baseService) ?? "openai",
|
|
325
|
+
service: baseService,
|
|
326
|
+
configSource: "studio",
|
|
327
|
+
baseUrl: args.baseUrl,
|
|
328
|
+
apiKey: args.apiKey.trim(),
|
|
329
|
+
model,
|
|
330
|
+
temperature: 0.7,
|
|
331
|
+
maxTokens: 64,
|
|
332
|
+
thinkingBudget: 0,
|
|
333
|
+
apiFormat: plan.apiFormat,
|
|
334
|
+
stream: plan.stream,
|
|
335
|
+
});
|
|
336
|
+
try {
|
|
337
|
+
await chatCompletion(client, model, [{ role: "user", content: "ping" }], { maxTokens: 5 });
|
|
338
|
+
const models = discoveredModels.length > 0
|
|
339
|
+
? discoveredModels
|
|
340
|
+
: [{ id: model, name: model }];
|
|
341
|
+
return {
|
|
342
|
+
ok: true,
|
|
343
|
+
models,
|
|
344
|
+
selectedModel: model,
|
|
345
|
+
apiFormat: plan.apiFormat,
|
|
346
|
+
stream: plan.stream,
|
|
347
|
+
baseUrl: args.baseUrl,
|
|
348
|
+
modelsSource: discoveredModels.length > 0 ? "api" : "fallback",
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
ok: false,
|
|
358
|
+
models: discoveredModels,
|
|
359
|
+
error: lastError,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
233
362
|
// --- Server factory ---
|
|
234
363
|
export function createStudioServer(initialConfig, root) {
|
|
235
364
|
const app = new Hono();
|
|
@@ -606,6 +735,9 @@ export function createStudioServer(initialConfig, root) {
|
|
|
606
735
|
if (body.configSource !== undefined) {
|
|
607
736
|
llm.configSource = normalizeConfigSource(body.configSource);
|
|
608
737
|
}
|
|
738
|
+
if (body.service !== undefined) {
|
|
739
|
+
llm.service = body.service;
|
|
740
|
+
}
|
|
609
741
|
await saveRawConfig(root, config);
|
|
610
742
|
return c.json({ ok: true });
|
|
611
743
|
});
|
|
@@ -619,37 +751,29 @@ export function createStudioServer(initialConfig, root) {
|
|
|
619
751
|
if (!resolvedBaseUrl) {
|
|
620
752
|
return c.json({ ok: false, error: `未知服务商: ${service}` }, 400);
|
|
621
753
|
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
}
|
|
633
|
-
if (res.ok) {
|
|
634
|
-
const json = await res.json();
|
|
635
|
-
models = (json.data ?? []).map((m) => ({ id: m.id, name: m.id }));
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
catch (err) {
|
|
639
|
-
if (err?.name === "TimeoutError" || err?.name === "AbortError") {
|
|
640
|
-
return c.json({ ok: false, error: `连接超时:无法访问 ${modelsUrl}` }, 400);
|
|
641
|
-
}
|
|
642
|
-
// Network error — continue to fallback
|
|
643
|
-
}
|
|
644
|
-
// /models unavailable (404 etc.) — fallback to pi-ai built-in model list
|
|
645
|
-
if (models.length === 0) {
|
|
646
|
-
const builtIn = await listModelsForService(service);
|
|
647
|
-
models = builtIn.map((m) => ({ id: m.id, name: m.name }));
|
|
648
|
-
}
|
|
649
|
-
if (models.length === 0) {
|
|
650
|
-
return c.json({ ok: false, error: "未找到可用模型" }, 400);
|
|
754
|
+
const probe = await probeServiceCapabilities({
|
|
755
|
+
root,
|
|
756
|
+
service,
|
|
757
|
+
apiKey: apiKey.trim(),
|
|
758
|
+
baseUrl: resolvedBaseUrl,
|
|
759
|
+
preferredApiFormat: apiFormat,
|
|
760
|
+
preferredStream: stream,
|
|
761
|
+
});
|
|
762
|
+
if (!probe.ok) {
|
|
763
|
+
return c.json({ ok: false, error: probe.error ?? "连接失败" }, 400);
|
|
651
764
|
}
|
|
652
|
-
return c.json({
|
|
765
|
+
return c.json({
|
|
766
|
+
ok: true,
|
|
767
|
+
modelCount: probe.models.length,
|
|
768
|
+
models: probe.models.slice(0, 50),
|
|
769
|
+
selectedModel: probe.selectedModel,
|
|
770
|
+
detected: {
|
|
771
|
+
apiFormat: probe.apiFormat,
|
|
772
|
+
stream: probe.stream,
|
|
773
|
+
baseUrl: probe.baseUrl,
|
|
774
|
+
modelsSource: probe.modelsSource,
|
|
775
|
+
},
|
|
776
|
+
});
|
|
653
777
|
});
|
|
654
778
|
app.put("/api/v1/services/:service/secret", async (c) => {
|
|
655
779
|
const service = c.req.param("service");
|
|
@@ -680,25 +804,22 @@ export function createStudioServer(initialConfig, root) {
|
|
|
680
804
|
const resolvedBaseUrl = await resolveConfiguredServiceBaseUrl(root, service);
|
|
681
805
|
if (!resolvedBaseUrl)
|
|
682
806
|
return c.json({ models: [] });
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
models = builtIn.map((m) => ({ id: m.id, name: m.name }));
|
|
700
|
-
}
|
|
701
|
-
return c.json({ models });
|
|
807
|
+
const rawConfig = await loadRawConfig(root).catch(() => ({}));
|
|
808
|
+
const llm = rawConfig.llm ?? {};
|
|
809
|
+
const preferredModel = typeof llm.defaultModel === "string" ? llm.defaultModel : undefined;
|
|
810
|
+
const serviceEntry = await resolveConfiguredServiceEntry(root, service);
|
|
811
|
+
const probe = await probeServiceCapabilities({
|
|
812
|
+
root,
|
|
813
|
+
service,
|
|
814
|
+
apiKey,
|
|
815
|
+
baseUrl: resolvedBaseUrl,
|
|
816
|
+
preferredApiFormat: serviceEntry?.apiFormat,
|
|
817
|
+
preferredStream: serviceEntry?.stream,
|
|
818
|
+
preferredModel,
|
|
819
|
+
});
|
|
820
|
+
return c.json({
|
|
821
|
+
models: probe.ok ? probe.models : [],
|
|
822
|
+
});
|
|
702
823
|
});
|
|
703
824
|
// --- Project info ---
|
|
704
825
|
app.get("/api/v1/project", async (c) => {
|
|
@@ -1775,10 +1896,17 @@ export function createStudioServer(initialConfig, root) {
|
|
|
1775
1896
|
catch { /* ignore */ }
|
|
1776
1897
|
try {
|
|
1777
1898
|
const currentConfig = await loadCurrentProjectConfig({ requireApiKey: false });
|
|
1778
|
-
const
|
|
1779
|
-
const
|
|
1780
|
-
|
|
1781
|
-
|
|
1899
|
+
const service = currentConfig.llm.service ?? currentConfig.llm.provider;
|
|
1900
|
+
const probe = await probeServiceCapabilities({
|
|
1901
|
+
root,
|
|
1902
|
+
service,
|
|
1903
|
+
apiKey: currentConfig.llm.apiKey,
|
|
1904
|
+
baseUrl: currentConfig.llm.baseUrl,
|
|
1905
|
+
preferredApiFormat: currentConfig.llm.apiFormat,
|
|
1906
|
+
preferredStream: currentConfig.llm.stream,
|
|
1907
|
+
preferredModel: currentConfig.llm.model,
|
|
1908
|
+
});
|
|
1909
|
+
checks.llmConnected = probe.ok;
|
|
1782
1910
|
}
|
|
1783
1911
|
catch { /* ignore */ }
|
|
1784
1912
|
return c.json(checks);
|