@actalk/inkos-studio 1.3.1 → 1.3.2-canary.28.1

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.
Files changed (62) hide show
  1. package/dist/api/server.d.ts.map +1 -1
  2. package/dist/api/server.js +205 -77
  3. package/dist/api/server.js.map +1 -1
  4. package/dist/assets/{_baseUniq-C-g5htb0.js → _baseUniq-DJdqK28N.js} +1 -1
  5. package/dist/assets/{arc-B13N8XoZ.js → arc-DVORsyAf.js} +1 -1
  6. package/dist/assets/{architectureDiagram-Q4EWVU46-COxJdWXS.js → architectureDiagram-Q4EWVU46-DjRJ006-.js} +1 -1
  7. package/dist/assets/{blockDiagram-DXYQGD6D-DS_ZpHJ5.js → blockDiagram-DXYQGD6D-BrZF7yPR.js} +1 -1
  8. package/dist/assets/{c4Diagram-AHTNJAMY-BxSij0o0.js → c4Diagram-AHTNJAMY-BUjFHcqo.js} +1 -1
  9. package/dist/assets/channel-DAwqKtrf.js +1 -0
  10. package/dist/assets/{chunk-4BX2VUAB-DFB21W8n.js → chunk-4BX2VUAB-Ck01cjOa.js} +1 -1
  11. package/dist/assets/{chunk-4TB4RGXK-BnJ77uxv.js → chunk-4TB4RGXK-fYtywxAI.js} +1 -1
  12. package/dist/assets/{chunk-55IACEB6-CQ_glRcB.js → chunk-55IACEB6-CFNhvTQn.js} +1 -1
  13. package/dist/assets/{chunk-EDXVE4YY-Dyd-Q8PI.js → chunk-EDXVE4YY-CowuqSRt.js} +1 -1
  14. package/dist/assets/{chunk-FMBD7UC4-BDiYToLm.js → chunk-FMBD7UC4-BWgw8cku.js} +1 -1
  15. package/dist/assets/{chunk-OYMX7WX6-DEzfeklY.js → chunk-OYMX7WX6-C01Lww9H.js} +1 -1
  16. package/dist/assets/{chunk-QZHKN3VN-CJPYUr7I.js → chunk-QZHKN3VN-BjoO0Ggu.js} +1 -1
  17. package/dist/assets/{chunk-YZCP3GAM-CBxzODC9.js → chunk-YZCP3GAM-1_oXxO-A.js} +1 -1
  18. package/dist/assets/classDiagram-6PBFFD2Q-BzzGlc6B.js +1 -0
  19. package/dist/assets/classDiagram-v2-HSJHXN6E-BzzGlc6B.js +1 -0
  20. package/dist/assets/clone-JyT4Y8M6.js +1 -0
  21. package/dist/assets/{cose-bilkent-S5V4N54A-BDcwl962.js → cose-bilkent-S5V4N54A-Bh_7Rb-J.js} +1 -1
  22. package/dist/assets/{dagre-KV5264BT-q3iXqwp5.js → dagre-KV5264BT-cbSoIqAE.js} +1 -1
  23. package/dist/assets/{diagram-5BDNPKRD-CSyQp4XD.js → diagram-5BDNPKRD-CFZvz8Qc.js} +1 -1
  24. package/dist/assets/{diagram-G4DWMVQ6-CyO2u0j-.js → diagram-G4DWMVQ6-M_Np88bq.js} +1 -1
  25. package/dist/assets/{diagram-MMDJMWI5-0Rum0AK-.js → diagram-MMDJMWI5-DS_yZb3X.js} +1 -1
  26. package/dist/assets/{diagram-TYMM5635-CpFFDrQG.js → diagram-TYMM5635-BTfEx65z.js} +1 -1
  27. package/dist/assets/{erDiagram-SMLLAGMA-DgPF_t0E.js → erDiagram-SMLLAGMA-BbnGBWpv.js} +1 -1
  28. package/dist/assets/{flowDiagram-DWJPFMVM-D1sVXEs4.js → flowDiagram-DWJPFMVM-Dit2fK7O.js} +1 -1
  29. package/dist/assets/{ganttDiagram-T4ZO3ILL-Cah2BN0H.js → ganttDiagram-T4ZO3ILL-9qXAYw0r.js} +1 -1
  30. package/dist/assets/{gitGraphDiagram-UUTBAWPF-Bb32ArKL.js → gitGraphDiagram-UUTBAWPF-Bt07S0SH.js} +1 -1
  31. package/dist/assets/{graph-DPYgoB8M.js → graph-BsmzKTDM.js} +1 -1
  32. package/dist/assets/{highlighted-body-OFNGDK62-C5OR7F5H.js → highlighted-body-OFNGDK62-C8RVuZxY.js} +1 -1
  33. package/dist/assets/index-B6QaiVya.css +1 -0
  34. package/dist/assets/{index-CdSd4xAM.js → index-C1gtMavo.js} +134 -134
  35. package/dist/assets/{infoDiagram-42DDH7IO-BRPlXDRX.js → infoDiagram-42DDH7IO-CfENgx7D.js} +1 -1
  36. package/dist/assets/{ishikawaDiagram-UXIWVN3A-DIMNZ5LJ.js → ishikawaDiagram-UXIWVN3A-DkgHB7oO.js} +1 -1
  37. package/dist/assets/{journeyDiagram-VCZTEJTY-CQ9Buybm.js → journeyDiagram-VCZTEJTY-D2QeXsGQ.js} +1 -1
  38. package/dist/assets/{kanban-definition-6JOO6SKY-CsZuyfP0.js → kanban-definition-6JOO6SKY-C0PuwOLs.js} +1 -1
  39. package/dist/assets/{layout-wiYzOkdn.js → layout-BcHWSmtt.js} +1 -1
  40. package/dist/assets/{linear-D2SNljoC.js → linear-DhG1QI2m.js} +1 -1
  41. package/dist/assets/{min-NQHw0HnA.js → min-BMoenYqy.js} +1 -1
  42. package/dist/assets/{mindmap-definition-QFDTVHPH-CLFneoyC.js → mindmap-definition-QFDTVHPH-BYqUeO0C.js} +1 -1
  43. package/dist/assets/{pieDiagram-DEJITSTG-CGetk0td.js → pieDiagram-DEJITSTG-vL_-Xhe9.js} +1 -1
  44. package/dist/assets/{quadrantDiagram-34T5L4WZ-DGvGQ55s.js → quadrantDiagram-34T5L4WZ-DlqKvO89.js} +1 -1
  45. package/dist/assets/{requirementDiagram-MS252O5E-BtWYKfId.js → requirementDiagram-MS252O5E-CPK-hyCG.js} +1 -1
  46. package/dist/assets/{sankeyDiagram-XADWPNL6-B7dHCFEb.js → sankeyDiagram-XADWPNL6-BYcLLwpo.js} +1 -1
  47. package/dist/assets/{sequenceDiagram-FGHM5R23-SCghmIaN.js → sequenceDiagram-FGHM5R23-BHGNED6Y.js} +1 -1
  48. package/dist/assets/{stateDiagram-FHFEXIEX-Da-qWGV4.js → stateDiagram-FHFEXIEX-CFtLWm9U.js} +1 -1
  49. package/dist/assets/stateDiagram-v2-QKLJ7IA2-RRV8L_Kf.js +1 -0
  50. package/dist/assets/{timeline-definition-GMOUNBTQ-DXzhFcp9.js → timeline-definition-GMOUNBTQ-DMXdyWjw.js} +1 -1
  51. package/dist/assets/{vennDiagram-DHZGUBPP-CHwAxJ-d.js → vennDiagram-DHZGUBPP-CDQM5jJf.js} +1 -1
  52. package/dist/assets/{wardley-RL74JXVD-DOtmpHBm.js → wardley-RL74JXVD-DNoHFV5F.js} +1 -1
  53. package/dist/assets/{wardleyDiagram-NUSXRM2D-DqzDdtmB.js → wardleyDiagram-NUSXRM2D-CwhahRKS.js} +1 -1
  54. package/dist/assets/{xychartDiagram-5P7HB3ND-B5UU8au-.js → xychartDiagram-5P7HB3ND-C0WIVgD-.js} +1 -1
  55. package/dist/index.html +2 -2
  56. package/package.json +2 -2
  57. package/dist/assets/channel-C2r273Z1.js +0 -1
  58. package/dist/assets/classDiagram-6PBFFD2Q-CSUo3NoK.js +0 -1
  59. package/dist/assets/classDiagram-v2-HSJHXN6E-CSUo3NoK.js +0 -1
  60. package/dist/assets/clone-DJaLNYqk.js +0 -1
  61. package/dist/assets/index-hHRX6lZN.css +0 -1
  62. package/dist/assets/stateDiagram-v2-QKLJ7IA2-EC5r6wTi.js +0 -1
@@ -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,EA4BL,KAAK,aAAa,EAInB,MAAM,oBAAoB,CAAC;AAiS5B,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,8EA6wD5E;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"}
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"}
@@ -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: normalizeBaseUrl(value.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: normalizeBaseUrl(value.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
- const svc = typeof entry.service === "string" && entry.service.length > 0 ? entry.service : "custom";
127
- const isCustom = svc === "custom";
128
- return {
129
- service: svc,
130
- ...(typeof entry.name === "string" && entry.name.length > 0 ? { name: entry.name } : {}),
131
- ...(typeof entry.baseUrl === "string" && entry.baseUrl.length > 0 ? { baseUrl: isCustom ? normalizeBaseUrl(entry.baseUrl) : entry.baseUrl } : {}),
132
- ...(typeof entry.temperature === "number" ? { temperature: entry.temperature } : {}),
133
- ...(typeof entry.maxTokens === "number" ? { maxTokens: entry.maxTokens } : {}),
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 isCustomServiceId(serviceId) ? normalizeBaseUrl(inlineBaseUrl.trim()) : inlineBaseUrl.trim();
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
- // Try /models API — validates key + discovers models in one call
623
- const modelsUrl = resolvedBaseUrl.replace(/\/$/, "") + "/models";
624
- let models = [];
625
- try {
626
- const res = await fetch(modelsUrl, {
627
- headers: { Authorization: `Bearer ${apiKey.trim()}` },
628
- signal: AbortSignal.timeout(10_000),
629
- });
630
- if (res.status === 401 || res.status === 403) {
631
- return c.json({ ok: false, error: "API Key 无效,请检查后重试" }, 400);
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({ ok: true, modelCount: models.length, models: models.slice(0, 50) });
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
- // Call real /models API, fallback to pi-ai built-in list
684
- let models = [];
685
- try {
686
- const modelsUrl = resolvedBaseUrl.replace(/\/$/, "") + "/models";
687
- const res = await fetch(modelsUrl, {
688
- headers: { Authorization: `Bearer ${apiKey}` },
689
- signal: AbortSignal.timeout(10_000),
690
- });
691
- if (res.ok) {
692
- const json = await res.json();
693
- models = (json.data ?? []).map((m) => ({ id: m.id, name: m.id }));
694
- }
695
- }
696
- catch { /* timeout or network error */ }
697
- if (models.length === 0) {
698
- const builtIn = await listModelsForService(service, apiKey);
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 client = createLLMClient(currentConfig.llm);
1779
- const { chatCompletion } = await import("@actalk/inkos-core");
1780
- await chatCompletion(client, currentConfig.llm.model, [{ role: "user", content: "ping" }], { maxTokens: 5 });
1781
- checks.llmConnected = true;
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);