@actalk/inkos-studio 1.3.10 → 1.3.11-canary.41.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.
- package/dist/api/book-create.d.ts +1 -0
- package/dist/api/book-create.d.ts.map +1 -1
- package/dist/api/book-create.js.map +1 -1
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +231 -42
- package/dist/api/server.js.map +1 -1
- package/dist/assets/{_baseUniq-BIUIXKF-.js → _baseUniq-7G2LyAeZ.js} +1 -1
- package/dist/assets/{arc-aMAvM09U.js → arc-_dxZ6Uaw.js} +1 -1
- package/dist/assets/{architectureDiagram-Q4EWVU46-D_lBhs6P.js → architectureDiagram-Q4EWVU46-DG9F2FBp.js} +1 -1
- package/dist/assets/{blockDiagram-DXYQGD6D-KR-H0WWB.js → blockDiagram-DXYQGD6D-D-CY5CJN.js} +1 -1
- package/dist/assets/{c4Diagram-AHTNJAMY-Cwu8C3Zc.js → c4Diagram-AHTNJAMY-DMYQ0GVk.js} +1 -1
- package/dist/assets/channel-CSdpem6d.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-D08-gave.js → chunk-4BX2VUAB-CQaukBhH.js} +1 -1
- package/dist/assets/{chunk-4TB4RGXK-B1iuP-xq.js → chunk-4TB4RGXK-DmrBJvqL.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-CPT6hIdV.js → chunk-55IACEB6-CU4wdVK-.js} +1 -1
- package/dist/assets/{chunk-EDXVE4YY-BGhU0HmZ.js → chunk-EDXVE4YY-8z43jT1h.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-BbfWy7cs.js → chunk-FMBD7UC4-1BEu2BaJ.js} +1 -1
- package/dist/assets/{chunk-OYMX7WX6-DDlL2KHi.js → chunk-OYMX7WX6-DB0Db5P1.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-MBgry6KJ.js → chunk-QZHKN3VN-iJXHP1Sf.js} +1 -1
- package/dist/assets/{chunk-YZCP3GAM-CbVklXFK.js → chunk-YZCP3GAM-CiMYHKaZ.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-B13AJwWX.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-B13AJwWX.js +1 -0
- package/dist/assets/clone-BaUjTciQ.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-DqDcpKPh.js → cose-bilkent-S5V4N54A-BAVfrwUP.js} +1 -1
- package/dist/assets/{dagre-KV5264BT-B9xLUzSB.js → dagre-KV5264BT-COBdfx_y.js} +1 -1
- package/dist/assets/{diagram-5BDNPKRD-DL4-J1SV.js → diagram-5BDNPKRD-B4NwbGUz.js} +1 -1
- package/dist/assets/{diagram-G4DWMVQ6-DC-cxfUZ.js → diagram-G4DWMVQ6-fH0CQPnB.js} +1 -1
- package/dist/assets/{diagram-MMDJMWI5-D5Aj0XZi.js → diagram-MMDJMWI5-D40FXwBs.js} +1 -1
- package/dist/assets/{diagram-TYMM5635-Cf-PA8ze.js → diagram-TYMM5635-iCuM3F7o.js} +1 -1
- package/dist/assets/{erDiagram-SMLLAGMA-KpouMvBQ.js → erDiagram-SMLLAGMA-DrzCmbj5.js} +1 -1
- package/dist/assets/{flowDiagram-DWJPFMVM-ChC9iSz9.js → flowDiagram-DWJPFMVM-BQvPE59f.js} +1 -1
- package/dist/assets/{ganttDiagram-T4ZO3ILL-Ba7mX0Y_.js → ganttDiagram-T4ZO3ILL-D9PnE2oQ.js} +1 -1
- package/dist/assets/{gitGraphDiagram-UUTBAWPF-VgeSu4pJ.js → gitGraphDiagram-UUTBAWPF-C-QllrHf.js} +1 -1
- package/dist/assets/{graph-tzWJpM37.js → graph-Dywc2VEx.js} +1 -1
- package/dist/assets/{highlighted-body-OFNGDK62-mJKtZPbz.js → highlighted-body-OFNGDK62-DaFK7awa.js} +1 -1
- package/dist/assets/{index-B5Wyr9-b.js → index-C2YvCS7q.js} +235 -235
- package/dist/assets/index-DQJevqxF.css +1 -0
- package/dist/assets/{infoDiagram-42DDH7IO-D0QACLUg.js → infoDiagram-42DDH7IO-2hFeNxWi.js} +1 -1
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-BHyP0O_G.js → ishikawaDiagram-UXIWVN3A-CVJY8gu8.js} +1 -1
- package/dist/assets/{journeyDiagram-VCZTEJTY-Bz7DUjEr.js → journeyDiagram-VCZTEJTY-CKwHhVW2.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-DQ_d7Tz2.js → kanban-definition-6JOO6SKY-2GDDzYma.js} +1 -1
- package/dist/assets/{layout-DSXDnsbV.js → layout-BYHHetdW.js} +1 -1
- package/dist/assets/{linear-BjDS5uge.js → linear-D6iQrqq7.js} +1 -1
- package/dist/assets/{min-DTqcDUV8.js → min-D9uYkhrp.js} +1 -1
- package/dist/assets/{mindmap-definition-QFDTVHPH-BnSR0r7K.js → mindmap-definition-QFDTVHPH-luhQnNv0.js} +1 -1
- package/dist/assets/{pieDiagram-DEJITSTG-Bz7EEyv2.js → pieDiagram-DEJITSTG-jkwzyOiV.js} +1 -1
- package/dist/assets/{quadrantDiagram-34T5L4WZ-65j7Dqyo.js → quadrantDiagram-34T5L4WZ-BFGvMOfi.js} +1 -1
- package/dist/assets/{requirementDiagram-MS252O5E-D3RqbgZ0.js → requirementDiagram-MS252O5E-BbwkmIU4.js} +1 -1
- package/dist/assets/{sankeyDiagram-XADWPNL6-qI55uI06.js → sankeyDiagram-XADWPNL6-DRxiWtKC.js} +1 -1
- package/dist/assets/{sequenceDiagram-FGHM5R23-DWiM9nfv.js → sequenceDiagram-FGHM5R23-DfCjYThV.js} +1 -1
- package/dist/assets/{stateDiagram-FHFEXIEX-esMtXi1p.js → stateDiagram-FHFEXIEX-BQG8YiO-.js} +1 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-AwixmqPB.js +1 -0
- package/dist/assets/{timeline-definition-GMOUNBTQ-D-nc3pMe.js → timeline-definition-GMOUNBTQ-DBfF6YXh.js} +1 -1
- package/dist/assets/{vennDiagram-DHZGUBPP-D-lm61Mn.js → vennDiagram-DHZGUBPP-DrPJ3v19.js} +1 -1
- package/dist/assets/{wardley-RL74JXVD-DvBsmvaJ.js → wardley-RL74JXVD-ChBtSh1E.js} +1 -1
- package/dist/assets/{wardleyDiagram-NUSXRM2D-BZH1nsAY.js → wardleyDiagram-NUSXRM2D-Cq06_btE.js} +1 -1
- package/dist/assets/{xychartDiagram-5P7HB3ND-gFMR57LI.js → xychartDiagram-5P7HB3ND-CUxp1BUy.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +2 -2
- package/dist/assets/channel-BnEvL0hC.js +0 -1
- package/dist/assets/classDiagram-6PBFFD2Q-DeOSGvih.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-DeOSGvih.js +0 -1
- package/dist/assets/clone-nC-ZF2rq.js +0 -1
- package/dist/assets/index-Cks9xRnD.css +0 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-BP_Hh6U7.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"book-create.d.ts","sourceRoot":"","sources":["../../src/api/book-create.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE7E,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"book-create.d.ts","sourceRoot":"","sources":["../../src/api/book-create.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE7E,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,UAAU,gBAAgB;IACxB,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IAC1C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,UAAU,6BAA6B;IACrC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IAClC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,CAEnE;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,oBAAoB,EAAE,GAAG,EAAE,MAAM,GAAG,qBAAqB,CAqBpG;AAMD,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,6BAAkC,GAC1C,OAAO,CAAC,gBAAgB,CAAC,CAqB3B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"book-create.js","sourceRoot":"","sources":["../../src/api/book-create.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAiB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"book-create.js","sourceRoot":"","sources":["../../src/api/book-create.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAiB,MAAM,oBAAoB,CAAC;AAsC7E,MAAM,UAAU,uBAAuB,CAAC,QAAiB;IACvD,OAAO,wBAAwB,CAAC,QAAQ,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,IAA0B,EAAE,GAAW;IAC3E,OAAO;QACL,EAAE,EAAE,IAAI,CAAC,KAAK;aACX,WAAW,EAAE;aACb,OAAO,CAAC,yBAAyB,EAAE,GAAG,CAAC;aACvC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;aACnB,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;QACf,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,QAAQ,EAAE,uBAAuB,CAAC,IAAI,CAAC,QAAQ,CAAC;QAChD,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,MAAM,EAAE,WAAW;QACnB,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,GAAG;QAC1C,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,IAAI,IAAI;QAC/C,GAAG,CAAC,IAAI,CAAC,QAAQ,KAAK,IAAI;YACxB,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAa,EAAE;YAC7B,CAAC,CAAC,IAAI,CAAC,QAAQ,KAAK,IAAI;gBACtB,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAa,EAAE;gBAC7B,CAAC,CAAC,EAAE,CAAC;QACT,SAAS,EAAE,GAAG;QACd,SAAS,EAAE,GAAG;KACf,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,OAAe;IAClC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,MAAc,EACd,UAAyC,EAAE;IAE3C,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,KAAK,CAAC;IAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,WAAW,CAAC;IACzC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,CAAC,CAAC;IAC7C,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,GAAG,CAAC;IAEjD,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,WAAW,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QAC3D,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,iBAAiB,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAChF,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;YAChB,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAsB,CAAC;QACnD,CAAC;QAED,IAAI,OAAO,GAAG,WAAW,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACrD,MAAM,IAAI,CAAC,YAAY,CAAC,CAAC;YACzB,SAAS;QACX,CAAC;QAED,MAAM;IACR,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,SAAS,MAAM,yBAAyB,WAAW,YAAY,CAAC,CAAC;AACnF,CAAC"}
|
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,EAsCL,KAAK,aAAa,EAGnB,MAAM,oBAAoB,CAAC;AAk3B5B,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,8EAouE5E;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,8 +2,8 @@ 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, listBookSessions, loadBookSession, appendManualSessionMessages, createAndPersistBookSession, renameBookSession, deleteBookSession, migrateBookSession, SessionAlreadyMigratedError, runAgentSession, buildAgentSystemPrompt, resolveServicePreset, resolveServiceProviderFamily, resolveServiceModelsBaseUrl, resolveServiceModel, loadSecrets, saveSecrets, listModelsForService, isApiKeyOptionalForEndpoint, getAllEndpoints, probeModelsFromUpstream, fetchWithProxy, chatCompletion, buildExportArtifact, GLOBAL_ENV_PATH, } from "@actalk/inkos-core";
|
|
6
|
-
import { access, readFile, readdir, writeFile } from "node:fs/promises";
|
|
5
|
+
import { StateManager, PipelineRunner, createLLMClient, createLogger, createInteractionToolsFromDeps, computeAnalytics, loadProjectConfig, loadProjectSession, processProjectInteractionRequest, resolveSessionActiveBook, listBookSessions, loadBookSession, appendManualSessionMessages, createAndPersistBookSession, renameBookSession, deleteBookSession, migrateBookSession, SessionAlreadyMigratedError, runAgentSession, buildAgentSystemPrompt, resolveServicePreset, resolveServiceProviderFamily, resolveServiceModelsBaseUrl, resolveServiceModel, loadSecrets, saveSecrets, listModelsForService, isApiKeyOptionalForEndpoint, getAllEndpoints, probeModelsFromUpstream, fetchWithProxy, chatCompletion, buildExportArtifact, GLOBAL_ENV_PATH, Scheduler, } from "@actalk/inkos-core";
|
|
6
|
+
import { access, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
7
7
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
8
8
|
import { isSafeBookId } from "./safety.js";
|
|
9
9
|
import { ApiError } from "./errors.js";
|
|
@@ -49,6 +49,18 @@ function summarizeResult(result) {
|
|
|
49
49
|
}
|
|
50
50
|
return String(result).slice(0, 200);
|
|
51
51
|
}
|
|
52
|
+
function compareServiceListItems(left, right) {
|
|
53
|
+
if (left.service === "kkaiapi" && right.service !== "kkaiapi")
|
|
54
|
+
return -1;
|
|
55
|
+
if (right.service === "kkaiapi" && left.service !== "kkaiapi")
|
|
56
|
+
return 1;
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
function isHeaderSafeApiKey(value) {
|
|
60
|
+
if (!value)
|
|
61
|
+
return true;
|
|
62
|
+
return /^[\x21-\x7E]+$/.test(value);
|
|
63
|
+
}
|
|
52
64
|
const NON_TEXT_MODEL_ID_PARTS = [
|
|
53
65
|
"image",
|
|
54
66
|
"embedding",
|
|
@@ -59,6 +71,10 @@ const NON_TEXT_MODEL_ID_PARTS = [
|
|
|
59
71
|
"audio",
|
|
60
72
|
"moderation",
|
|
61
73
|
];
|
|
74
|
+
const SERVICE_MODELS_PROBE_TIMEOUT_MS = 4_000;
|
|
75
|
+
const SERVICE_CHAT_PROBE_TIMEOUT_MS = 8_000;
|
|
76
|
+
const MAX_DISCOVERED_MODELS_TO_PING = 2;
|
|
77
|
+
const MAX_GENERIC_FALLBACK_MODELS_TO_PING = 2;
|
|
62
78
|
function isTextChatModelId(modelId) {
|
|
63
79
|
const normalized = modelId.trim().toLowerCase();
|
|
64
80
|
if (!normalized)
|
|
@@ -344,15 +360,12 @@ function buildProbePlans(preferredApiFormat, preferredStream) {
|
|
|
344
360
|
};
|
|
345
361
|
if (preferredApiFormat) {
|
|
346
362
|
push(preferredApiFormat, preferredStream ?? false);
|
|
347
|
-
|
|
363
|
+
if (preferredStream)
|
|
364
|
+
push(preferredApiFormat, false);
|
|
365
|
+
return candidates;
|
|
348
366
|
}
|
|
349
|
-
const alternate = preferredApiFormat === "responses" ? "chat" : "responses";
|
|
350
|
-
push(alternate, false);
|
|
351
|
-
push(alternate, true);
|
|
352
367
|
push("chat", false);
|
|
353
|
-
push("chat", true);
|
|
354
368
|
push("responses", false);
|
|
355
|
-
push("responses", true);
|
|
356
369
|
return candidates;
|
|
357
370
|
}
|
|
358
371
|
function buildModelCandidates(args) {
|
|
@@ -370,17 +383,103 @@ function buildModelCandidates(args) {
|
|
|
370
383
|
push(args.preferredModel);
|
|
371
384
|
push(args.configModel);
|
|
372
385
|
push(args.envModel ?? undefined);
|
|
373
|
-
for (const model of args.discoveredModels)
|
|
386
|
+
for (const model of args.discoveredModels.slice(0, MAX_DISCOVERED_MODELS_TO_PING))
|
|
374
387
|
push(model.id);
|
|
375
388
|
if (args.includeGenericFallbacks === false)
|
|
376
389
|
return candidates;
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
390
|
+
for (const fallback of [
|
|
391
|
+
"gpt-5.4",
|
|
392
|
+
"gpt-4o",
|
|
393
|
+
"claude-sonnet-4-6",
|
|
394
|
+
"MiniMax-M2.7",
|
|
395
|
+
"kimi-k2.5",
|
|
396
|
+
].slice(0, MAX_GENERIC_FALLBACK_MODELS_TO_PING)) {
|
|
397
|
+
push(fallback);
|
|
398
|
+
}
|
|
382
399
|
return candidates;
|
|
383
400
|
}
|
|
401
|
+
function yamlScalar(value) {
|
|
402
|
+
return JSON.stringify(String(value ?? ""));
|
|
403
|
+
}
|
|
404
|
+
function radarTimestampForFilename(value) {
|
|
405
|
+
const date = value ? new Date(value) : new Date();
|
|
406
|
+
const safeDate = Number.isNaN(date.getTime()) ? new Date() : date;
|
|
407
|
+
return safeDate.toISOString().replace(/[:.]/g, "-");
|
|
408
|
+
}
|
|
409
|
+
async function saveRadarScan(root, result) {
|
|
410
|
+
const radarDir = join(root, "radar");
|
|
411
|
+
await mkdir(radarDir, { recursive: true });
|
|
412
|
+
const timestamp = typeof result === "object" && result !== null && "timestamp" in result
|
|
413
|
+
? String(result.timestamp ?? "")
|
|
414
|
+
: "";
|
|
415
|
+
const fileName = `scan-${radarTimestampForFilename(timestamp)}.json`;
|
|
416
|
+
const filePath = join(radarDir, fileName);
|
|
417
|
+
await writeFile(filePath, JSON.stringify(result, null, 2), "utf-8");
|
|
418
|
+
return filePath;
|
|
419
|
+
}
|
|
420
|
+
async function loadRadarHistory(root) {
|
|
421
|
+
const radarDir = join(root, "radar");
|
|
422
|
+
let files = [];
|
|
423
|
+
try {
|
|
424
|
+
files = await readdir(radarDir);
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
return [];
|
|
428
|
+
}
|
|
429
|
+
const scans = await Promise.all(files
|
|
430
|
+
.filter((file) => /^scan-.+\.json$/.test(file))
|
|
431
|
+
.map(async (file) => {
|
|
432
|
+
try {
|
|
433
|
+
const raw = await readFile(join(radarDir, file), "utf-8");
|
|
434
|
+
const result = JSON.parse(raw);
|
|
435
|
+
const timestamp = typeof result.timestamp === "string"
|
|
436
|
+
? result.timestamp
|
|
437
|
+
: file.replace(/^scan-/, "").replace(/\.json$/, "");
|
|
438
|
+
const marketSummary = typeof result.marketSummary === "string" ? result.marketSummary : "";
|
|
439
|
+
return {
|
|
440
|
+
file,
|
|
441
|
+
timestamp,
|
|
442
|
+
marketSummary,
|
|
443
|
+
summaryPreview: marketSummary.slice(0, 100),
|
|
444
|
+
result,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
}));
|
|
451
|
+
return scans
|
|
452
|
+
.filter((item) => item !== null)
|
|
453
|
+
.sort((a, b) => b.file.localeCompare(a.file));
|
|
454
|
+
}
|
|
455
|
+
function fallbackTextModelsForEndpoint(endpoint, preset) {
|
|
456
|
+
const endpointModels = endpoint?.models
|
|
457
|
+
.filter((model) => model.enabled !== false)
|
|
458
|
+
.filter((model) => isTextChatModelId(model.id))
|
|
459
|
+
.map((model) => ({ id: model.id, name: model.id }))
|
|
460
|
+
?? [];
|
|
461
|
+
if (endpointModels.length > 0)
|
|
462
|
+
return endpointModels;
|
|
463
|
+
return preset?.knownModels?.map((id) => ({ id, name: id })) ?? [];
|
|
464
|
+
}
|
|
465
|
+
function shouldTrustStaticModelsWhenLiveListUnavailable(endpoint) {
|
|
466
|
+
return endpoint?.group === "aggregator";
|
|
467
|
+
}
|
|
468
|
+
async function withTimeout(promise, timeoutMs, label) {
|
|
469
|
+
let timeout;
|
|
470
|
+
try {
|
|
471
|
+
return await Promise.race([
|
|
472
|
+
promise,
|
|
473
|
+
new Promise((_, reject) => {
|
|
474
|
+
timeout = setTimeout(() => reject(new Error(`${label} 超时(${timeoutMs}ms)`)), timeoutMs);
|
|
475
|
+
}),
|
|
476
|
+
]);
|
|
477
|
+
}
|
|
478
|
+
finally {
|
|
479
|
+
if (timeout)
|
|
480
|
+
clearTimeout(timeout);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
384
483
|
function formatServiceProbeError(args) {
|
|
385
484
|
const rawDetail = args.error
|
|
386
485
|
.replace(/\n\s*\(baseUrl:[\s\S]*?\)$/m, "")
|
|
@@ -434,8 +533,8 @@ async function fetchModelsFromServiceBaseUrl(serviceId, baseUrl, apiKey, proxyUr
|
|
|
434
533
|
const modelsUrl = modelsBaseUrl.replace(/\/$/, "") + "/models";
|
|
435
534
|
try {
|
|
436
535
|
const res = await fetchWithProxy(modelsUrl, {
|
|
437
|
-
headers:
|
|
438
|
-
signal: AbortSignal.timeout(
|
|
536
|
+
headers: buildBearerAuthHeaders(apiKey),
|
|
537
|
+
signal: AbortSignal.timeout(SERVICE_MODELS_PROBE_TIMEOUT_MS),
|
|
439
538
|
}, proxyUrl);
|
|
440
539
|
if (!res.ok) {
|
|
441
540
|
const body = await res.text().catch(() => "");
|
|
@@ -457,6 +556,15 @@ async function fetchModelsFromServiceBaseUrl(serviceId, baseUrl, apiKey, proxyUr
|
|
|
457
556
|
};
|
|
458
557
|
}
|
|
459
558
|
}
|
|
559
|
+
function buildBearerAuthHeaders(apiKey) {
|
|
560
|
+
const trimmed = apiKey?.trim() ?? "";
|
|
561
|
+
if (!trimmed)
|
|
562
|
+
return {};
|
|
563
|
+
if (!/^[\x20-\x7e]+$/.test(trimmed)) {
|
|
564
|
+
throw new Error("API Key 只能包含英文、数字和常见 ASCII 符号,请检查是否误粘贴了中文说明。");
|
|
565
|
+
}
|
|
566
|
+
return { Authorization: `Bearer ${trimmed}` };
|
|
567
|
+
}
|
|
460
568
|
async function probeServiceCapabilities(args) {
|
|
461
569
|
const rawConfig = await loadRawConfig(args.root).catch(() => ({}));
|
|
462
570
|
const llm = rawConfig.llm ?? {};
|
|
@@ -476,14 +584,54 @@ async function probeServiceCapabilities(args) {
|
|
|
476
584
|
};
|
|
477
585
|
}
|
|
478
586
|
const discoveredModels = modelsResponse.models;
|
|
479
|
-
// For bank services, probe with the service's own check model first — not the global default.
|
|
480
587
|
const endpoint = getAllEndpoints().find((ep) => ep.id === baseService);
|
|
481
588
|
const preset = resolveServicePreset(baseService);
|
|
589
|
+
const discoveredFirstModel = discoveredModels.find((model) => isTextChatModelId(model.id))?.id
|
|
590
|
+
?? discoveredModels[0]?.id;
|
|
591
|
+
if (discoveredModels.length > 0) {
|
|
592
|
+
if (!discoveredFirstModel || !isTextChatModelId(discoveredFirstModel)) {
|
|
593
|
+
return {
|
|
594
|
+
ok: false,
|
|
595
|
+
models: discoveredModels,
|
|
596
|
+
error: "模型列表可访问,但没有发现可用于文本对话的模型。",
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
return {
|
|
600
|
+
ok: true,
|
|
601
|
+
models: discoveredModels,
|
|
602
|
+
selectedModel: discoveredFirstModel,
|
|
603
|
+
apiFormat: args.preferredApiFormat ?? "chat",
|
|
604
|
+
stream: args.preferredStream ?? false,
|
|
605
|
+
baseUrl: args.baseUrl,
|
|
606
|
+
modelsSource: "api",
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
if (shouldTrustStaticModelsWhenLiveListUnavailable(endpoint)) {
|
|
610
|
+
const models = fallbackTextModelsForEndpoint(endpoint, preset);
|
|
611
|
+
const selectedModel = endpoint?.checkModel && models.some((model) => model.id === endpoint.checkModel)
|
|
612
|
+
? endpoint.checkModel
|
|
613
|
+
: models[0]?.id;
|
|
614
|
+
if (selectedModel) {
|
|
615
|
+
return {
|
|
616
|
+
ok: true,
|
|
617
|
+
models,
|
|
618
|
+
selectedModel,
|
|
619
|
+
apiFormat: args.preferredApiFormat ?? "chat",
|
|
620
|
+
stream: args.preferredStream ?? false,
|
|
621
|
+
baseUrl: args.baseUrl,
|
|
622
|
+
modelsSource: "fallback",
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
// Prefer live /models results; if unavailable, probe with the service's own check model before global defaults.
|
|
482
627
|
const serviceFirstModel = endpoint?.checkModel
|
|
483
628
|
?? preset?.knownModels?.[0]
|
|
484
629
|
?? endpoint?.models.find((model) => model.enabled !== false)?.id;
|
|
485
630
|
const useDynamicLocalModels = baseService === "ollama";
|
|
486
|
-
const useEndpointCheckModel = !useDynamicLocalModels
|
|
631
|
+
const useEndpointCheckModel = !useDynamicLocalModels
|
|
632
|
+
&& !isCustomServiceId(args.service)
|
|
633
|
+
&& discoveredModels.length === 0
|
|
634
|
+
&& Boolean(endpoint?.checkModel);
|
|
487
635
|
const configService = typeof llm.service === "string" ? llm.service : undefined;
|
|
488
636
|
const configModel = !useEndpointCheckModel && configService === args.service
|
|
489
637
|
? typeof llm.defaultModel === "string"
|
|
@@ -492,9 +640,9 @@ async function probeServiceCapabilities(args) {
|
|
|
492
640
|
? llm.model
|
|
493
641
|
: undefined
|
|
494
642
|
: undefined;
|
|
495
|
-
const useCustomFallbacks =
|
|
643
|
+
const useCustomFallbacks = false;
|
|
496
644
|
const modelCandidates = buildModelCandidates({
|
|
497
|
-
preferredModel: args.preferredModel ??
|
|
645
|
+
preferredModel: args.preferredModel ?? serviceFirstModel,
|
|
498
646
|
configModel,
|
|
499
647
|
envModel: useCustomFallbacks ? envModel : undefined,
|
|
500
648
|
discoveredModels: useEndpointCheckModel ? [] : discoveredModels,
|
|
@@ -518,25 +666,20 @@ async function probeServiceCapabilities(args) {
|
|
|
518
666
|
apiKey: args.apiKey.trim(),
|
|
519
667
|
model,
|
|
520
668
|
temperature: 0.7,
|
|
521
|
-
maxTokens:
|
|
669
|
+
maxTokens: 16,
|
|
522
670
|
thinkingBudget: 0,
|
|
523
671
|
proxyUrl: args.proxyUrl,
|
|
524
672
|
apiFormat: plan.apiFormat,
|
|
525
673
|
stream: plan.stream,
|
|
526
674
|
});
|
|
527
675
|
try {
|
|
528
|
-
await chatCompletion(client, model, [{ role: "user", content: "
|
|
676
|
+
await withTimeout(chatCompletion(client, model, [{ role: "user", content: "Reply with OK only." }], { maxTokens: 16 }), SERVICE_CHAT_PROBE_TIMEOUT_MS, "service connection test");
|
|
529
677
|
const models = discoveredModels.length > 0
|
|
530
678
|
? discoveredModels
|
|
531
|
-
: endpoint
|
|
532
|
-
.filter((m) => m.enabled !== false)
|
|
533
|
-
.filter((m) => isTextChatModelId(m.id))
|
|
534
|
-
.map((m) => ({ id: m.id, name: m.id }))
|
|
535
|
-
?? preset?.knownModels?.map((id) => ({ id, name: id }))
|
|
536
|
-
?? [{ id: model, name: model }];
|
|
679
|
+
: fallbackTextModelsForEndpoint(endpoint, preset);
|
|
537
680
|
return {
|
|
538
681
|
ok: true,
|
|
539
|
-
models,
|
|
682
|
+
models: models.length > 0 ? models : [{ id: model, name: model }],
|
|
540
683
|
selectedModel: model,
|
|
541
684
|
apiFormat: plan.apiFormat,
|
|
542
685
|
stream: plan.stream,
|
|
@@ -574,6 +717,11 @@ export function createStudioServer(initialConfig, root) {
|
|
|
574
717
|
if (error instanceof ApiError) {
|
|
575
718
|
return c.json({ error: { code: error.code, message: error.message } }, error.status);
|
|
576
719
|
}
|
|
720
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
721
|
+
if (message.includes("LLM API key not set") || message.includes("INKOS_LLM_API_KEY not set")) {
|
|
722
|
+
return c.json({ error: { code: "LLM_CONFIG_ERROR", message } }, 400);
|
|
723
|
+
}
|
|
724
|
+
console.error("[studio] Unexpected server error", error);
|
|
577
725
|
return c.json({ error: { code: "INTERNAL_ERROR", message: "Unexpected server error." } }, 500);
|
|
578
726
|
});
|
|
579
727
|
// BookId validation middleware — blocks path traversal on all book routes
|
|
@@ -712,6 +860,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
712
860
|
platform: body.platform,
|
|
713
861
|
chapterWordCount: body.chapterWordCount,
|
|
714
862
|
targetChapters: body.targetChapters,
|
|
863
|
+
blurb: body.blurb,
|
|
715
864
|
},
|
|
716
865
|
tools,
|
|
717
866
|
}).then(async (result) => {
|
|
@@ -971,7 +1120,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
971
1120
|
label: ep.label,
|
|
972
1121
|
group: ep.group,
|
|
973
1122
|
connected: Boolean(secrets.services[ep.id]?.apiKey),
|
|
974
|
-
}));
|
|
1123
|
+
})).sort(compareServiceListItems);
|
|
975
1124
|
// Add custom services from inkos.json
|
|
976
1125
|
try {
|
|
977
1126
|
const config = await loadRawConfig(root);
|
|
@@ -1031,6 +1180,27 @@ export function createStudioServer(initialConfig, root) {
|
|
|
1031
1180
|
await saveRawConfig(root, config);
|
|
1032
1181
|
return c.json({ ok: true });
|
|
1033
1182
|
});
|
|
1183
|
+
app.delete("/api/v1/services/:service", async (c) => {
|
|
1184
|
+
const service = c.req.param("service");
|
|
1185
|
+
const config = await loadRawConfig(root);
|
|
1186
|
+
const llm = config.llm ?? {};
|
|
1187
|
+
const existingServices = normalizeServiceConfig(llm.services);
|
|
1188
|
+
const nextServices = existingServices.filter((entry) => serviceConfigKey(entry) !== service);
|
|
1189
|
+
if (!config.llm)
|
|
1190
|
+
config.llm = {};
|
|
1191
|
+
const nextLlm = config.llm;
|
|
1192
|
+
nextLlm.services = nextServices;
|
|
1193
|
+
if (nextLlm.service === service) {
|
|
1194
|
+
delete nextLlm.service;
|
|
1195
|
+
delete nextLlm.defaultModel;
|
|
1196
|
+
}
|
|
1197
|
+
await saveRawConfig(root, config);
|
|
1198
|
+
const secrets = await loadSecrets(root);
|
|
1199
|
+
delete secrets.services[service];
|
|
1200
|
+
await saveSecrets(root, secrets);
|
|
1201
|
+
modelListCache.clear();
|
|
1202
|
+
return c.json({ ok: true, service });
|
|
1203
|
+
});
|
|
1034
1204
|
app.post("/api/v1/services/:service/test", async (c) => {
|
|
1035
1205
|
const service = c.req.param("service");
|
|
1036
1206
|
const { apiKey, baseUrl, apiFormat, stream } = await c.req.json();
|
|
@@ -1044,7 +1214,10 @@ export function createStudioServer(initialConfig, root) {
|
|
|
1044
1214
|
baseUrl: resolvedBaseUrl,
|
|
1045
1215
|
});
|
|
1046
1216
|
if (!apiKey?.trim() && !apiKeyOptional) {
|
|
1047
|
-
return c.json({
|
|
1217
|
+
return c.json({
|
|
1218
|
+
ok: false,
|
|
1219
|
+
error: "API Key 不能为空",
|
|
1220
|
+
}, 400);
|
|
1048
1221
|
}
|
|
1049
1222
|
const rawConfig = await loadRawConfig(root).catch(() => ({}));
|
|
1050
1223
|
const llm = rawConfig.llm ?? {};
|
|
@@ -1091,8 +1264,15 @@ export function createStudioServer(initialConfig, root) {
|
|
|
1091
1264
|
const service = c.req.param("service");
|
|
1092
1265
|
const { apiKey } = await c.req.json();
|
|
1093
1266
|
const secrets = await loadSecrets(root);
|
|
1094
|
-
|
|
1095
|
-
|
|
1267
|
+
const trimmedKey = apiKey?.trim() ?? "";
|
|
1268
|
+
if (trimmedKey) {
|
|
1269
|
+
if (!isHeaderSafeApiKey(trimmedKey)) {
|
|
1270
|
+
return c.json({
|
|
1271
|
+
ok: false,
|
|
1272
|
+
error: "API Key 只能包含可放进 HTTP Authorization header 的非空白 ASCII 字符;请不要粘贴连接失败提示或诊断文本。",
|
|
1273
|
+
}, 400);
|
|
1274
|
+
}
|
|
1275
|
+
secrets.services[service] = { apiKey: trimmedKey };
|
|
1096
1276
|
}
|
|
1097
1277
|
else {
|
|
1098
1278
|
delete secrets.services[service];
|
|
@@ -1294,7 +1474,6 @@ export function createStudioServer(initialConfig, root) {
|
|
|
1294
1474
|
return c.json({ error: "Daemon already running" }, 400);
|
|
1295
1475
|
}
|
|
1296
1476
|
try {
|
|
1297
|
-
const { Scheduler } = await import("@actalk/inkos-core");
|
|
1298
1477
|
const currentConfig = await loadCurrentProjectConfig();
|
|
1299
1478
|
const scheduler = new Scheduler({
|
|
1300
1479
|
...(await buildPipelineConfig()),
|
|
@@ -2264,15 +2443,15 @@ export function createStudioServer(initialConfig, root) {
|
|
|
2264
2443
|
await mkdirFs(genresDir, { recursive: true });
|
|
2265
2444
|
const frontmatter = [
|
|
2266
2445
|
"---",
|
|
2267
|
-
`name: ${body.name}`,
|
|
2268
|
-
`id: ${body.id}`,
|
|
2269
|
-
`language: ${body.language ?? "zh"}`,
|
|
2446
|
+
`name: ${yamlScalar(body.name)}`,
|
|
2447
|
+
`id: ${yamlScalar(body.id)}`,
|
|
2448
|
+
`language: ${yamlScalar(body.language ?? "zh")}`,
|
|
2270
2449
|
`chapterTypes: ${JSON.stringify(body.chapterTypes ?? [])}`,
|
|
2271
2450
|
`fatigueWords: ${JSON.stringify(body.fatigueWords ?? [])}`,
|
|
2272
2451
|
`numericalSystem: ${body.numericalSystem ?? false}`,
|
|
2273
2452
|
`powerScaling: ${body.powerScaling ?? false}`,
|
|
2274
2453
|
`eraResearch: ${body.eraResearch ?? false}`,
|
|
2275
|
-
`pacingRule:
|
|
2454
|
+
`pacingRule: ${yamlScalar(body.pacingRule ?? "")}`,
|
|
2276
2455
|
`satisfactionTypes: ${JSON.stringify(body.satisfactionTypes ?? [])}`,
|
|
2277
2456
|
`auditDimensions: ${JSON.stringify(body.auditDimensions ?? [])}`,
|
|
2278
2457
|
"---",
|
|
@@ -2295,15 +2474,15 @@ export function createStudioServer(initialConfig, root) {
|
|
|
2295
2474
|
const p = body.profile;
|
|
2296
2475
|
const frontmatter = [
|
|
2297
2476
|
"---",
|
|
2298
|
-
`name: ${p.name ?? genreId}`,
|
|
2299
|
-
`id: ${p.id ?? genreId}`,
|
|
2300
|
-
`language: ${p.language ?? "zh"}`,
|
|
2477
|
+
`name: ${yamlScalar(p.name ?? genreId)}`,
|
|
2478
|
+
`id: ${yamlScalar(p.id ?? genreId)}`,
|
|
2479
|
+
`language: ${yamlScalar(p.language ?? "zh")}`,
|
|
2301
2480
|
`chapterTypes: ${JSON.stringify(p.chapterTypes ?? [])}`,
|
|
2302
2481
|
`fatigueWords: ${JSON.stringify(p.fatigueWords ?? [])}`,
|
|
2303
2482
|
`numericalSystem: ${p.numericalSystem ?? false}`,
|
|
2304
2483
|
`powerScaling: ${p.powerScaling ?? false}`,
|
|
2305
2484
|
`eraResearch: ${p.eraResearch ?? false}`,
|
|
2306
|
-
`pacingRule:
|
|
2485
|
+
`pacingRule: ${yamlScalar(p.pacingRule ?? "")}`,
|
|
2307
2486
|
`satisfactionTypes: ${JSON.stringify(p.satisfactionTypes ?? [])}`,
|
|
2308
2487
|
`auditDimensions: ${JSON.stringify(p.auditDimensions ?? [])}`,
|
|
2309
2488
|
"---",
|
|
@@ -2469,6 +2648,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
2469
2648
|
try {
|
|
2470
2649
|
const pipeline = new PipelineRunner(await buildPipelineConfig());
|
|
2471
2650
|
const result = await pipeline.runRadar();
|
|
2651
|
+
await saveRadarScan(root, result);
|
|
2472
2652
|
broadcast("radar:complete", { result });
|
|
2473
2653
|
return c.json(result);
|
|
2474
2654
|
}
|
|
@@ -2477,6 +2657,15 @@ export function createStudioServer(initialConfig, root) {
|
|
|
2477
2657
|
return c.json({ error: String(e) }, 500);
|
|
2478
2658
|
}
|
|
2479
2659
|
});
|
|
2660
|
+
app.get("/api/v1/radar/history", async (c) => {
|
|
2661
|
+
try {
|
|
2662
|
+
const items = await loadRadarHistory(root);
|
|
2663
|
+
return c.json({ items });
|
|
2664
|
+
}
|
|
2665
|
+
catch (e) {
|
|
2666
|
+
return c.json({ error: String(e) }, 500);
|
|
2667
|
+
}
|
|
2668
|
+
});
|
|
2480
2669
|
// --- Doctor (environment health check) ---
|
|
2481
2670
|
app.get("/api/v1/doctor", async (c) => {
|
|
2482
2671
|
const { existsSync } = await import("node:fs");
|