opencami 1.9.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/{CSPContext-B3PAVjBL.js → CSPContext-CxrBvJ99.js} +1 -1
- package/dist/client/assets/{DirectionContext-CR5CCisG.js → DirectionContext-BOL0P_1j.js} +1 -1
- package/dist/client/assets/_sessionKey-CTtu2ipR.js +25 -0
- package/dist/client/assets/agents-D7JS19Lo.js +2 -0
- package/dist/client/assets/{agents-screen--ZMzN5cB.js → agents-screen-CqdzZKaR.js} +1 -1
- package/dist/client/assets/bots-DAInzfLx.js +2 -0
- package/dist/client/assets/{bots-screen-C_uJBNwI.js → bots-screen-RYovD6cu.js} +1 -1
- package/dist/client/assets/button-Bv-7bwZZ.js +1 -0
- package/dist/client/assets/{composite-DArWbFHm.js → composite-DwQIZFe9.js} +1 -1
- package/dist/client/assets/{connect-B48HecjN.js → connect-DZSrhhNT.js} +1 -1
- package/dist/client/assets/{dashboard-BVKx9FMy.js → dashboard-y8KVUVF-.js} +1 -1
- package/dist/client/assets/{event-BCwqPPkP.js → event-Cnr9OETn.js} +1 -1
- package/dist/client/assets/file-explorer-screen-C_mYUJsr.js +1 -0
- package/dist/client/assets/files-CG0jFYdO.js +2 -0
- package/dist/client/assets/follow-up-suggestions-C6RzpstA.js +2 -0
- package/dist/client/assets/{index-0gdwm1_H.js → index-3-ASY4Cl.js} +1 -1
- package/dist/client/assets/index-BZxJSF4T.js +3 -0
- package/dist/client/assets/{keyboard-shortcuts-dialog-CK_XTzLr.js → keyboard-shortcuts-dialog-jm4-DftO.js} +1 -1
- package/dist/client/assets/{main-B2CrcRuC.js → main-CbISK-pm.js} +3 -3
- package/dist/client/assets/{markdown-CdkuX06F.js → markdown-UD7RzskQ.js} +1 -1
- package/dist/client/assets/memory-BYRJ1l_4.js +2 -0
- package/dist/client/assets/{memory-screen-C1RfLy-d.js → memory-screen-BRsm-Q8A.js} +1 -1
- package/dist/client/assets/{menu-CIwnliij.js → menu-DRjoHFvG.js} +1 -1
- package/dist/client/assets/{opencami-logo-SVuYD55V.js → opencami-logo-D2mv84sX.js} +1 -1
- package/dist/client/assets/{proxy-BijR8W1L.js → proxy-DtxqfBHY.js} +1 -1
- package/dist/client/assets/{react-DWx7OvUo.js → react-C_7UlEtd.js} +1 -1
- package/dist/client/assets/{search-dialog-CB4KE8ec.js → search-dialog-lsT8JUtT.js} +1 -1
- package/dist/client/assets/{search-sources-badge-B8Z-8sSf.js → search-sources-badge-BnqKxoia.js} +1 -1
- package/dist/client/assets/{session-export-dialog-tDiFuv3a.js → session-export-dialog-DHHpvwI8.js} +1 -1
- package/dist/client/assets/settings-dialog-vrd0eJGw.js +1 -0
- package/dist/client/assets/skills-Svd-7oxQ.js +2 -0
- package/dist/client/assets/{skills-panel-fjJQVMog.js → skills-panel-Dkuo0yyD.js} +1 -1
- package/dist/client/assets/styles-CWabEzNU.css +1 -0
- package/dist/client/assets/{switch-Bn5uei2k.js → switch-BYFBYPmi.js} +1 -1
- package/dist/client/assets/{tabs-DOBNAUVE.js → tabs-1jrPkdUE.js} +1 -1
- package/dist/client/assets/{thinking-dfGrFAMV.js → thinking-D9BBn5kK.js} +1 -1
- package/dist/client/assets/{tooltip-DOKkNFvu.js → tooltip-DzFgDY86.js} +1 -1
- package/dist/client/assets/{use-file-explorer-state-BAa6Cxyr.js → use-file-explorer-state-Dwhw6Mpg.js} +2 -2
- package/dist/client/assets/{useBaseUiId-DFpBD0sg.js → useBaseUiId-BdQC9KF3.js} +1 -1
- package/dist/client/assets/useCompositeItem-BI09zNFD.js +1 -0
- package/dist/client/assets/{useControlled-CQHE0ITz.js → useControlled-Bc2Emhlx.js} +1 -1
- package/dist/client/assets/{useMutation-BFl-7GnD.js → useMutation-CMp81lDO.js} +1 -1
- package/dist/client/assets/{useOnFirstRender-DlXHIIGk.js → useOnFirstRender-DpB3w0o4.js} +1 -1
- package/dist/client/assets/{useQuery-D-sF8Tld.js → useQuery-_BEWdUe1.js} +1 -1
- package/dist/server/assets/{_sessionKey-D8TGrDRM.js → _sessionKey-COz7RLKC.js} +170 -279
- package/dist/server/assets/_tanstack-start-manifest_v-D0f-0Utn.js +4 -0
- package/dist/server/assets/{connect-CTVBm0Vc.js → connect-sd_NDvQN.js} +1 -1
- package/dist/server/assets/follow-up-suggestions-DRQqO8_S.js +183 -0
- package/dist/server/assets/{index-B_F4DTUu.js → index-197lqzv2.js} +2 -0
- package/dist/server/assets/{index-gRco4Ina.js → index-sXe2QC5V.js} +1 -1
- package/dist/server/assets/{markdown-CFdYXCRQ.js → markdown-Dk1YdosA.js} +1 -1
- package/dist/server/assets/{memory-screen-vqXczcVo.js → memory-screen-DA_o-N1Z.js} +2 -2
- package/dist/server/assets/{memory-rBB015W-.js → memory-woPzJFfs.js} +1 -1
- package/dist/server/assets/{router-DaKDqc9w.js → router-CZhxPkYW.js} +347 -355
- package/dist/server/assets/{search-dialog-DSSK93kq.js → search-dialog-DlvWfq1e.js} +5 -5
- package/dist/server/assets/{settings-dialog-DyWNblva.js → settings-dialog-BGtrS2RJ.js} +63 -255
- package/dist/server/assets/{thinking-CU0FRlzT.js → thinking-fzwT_aVT.js} +5 -5
- package/dist/server/server.js +2 -2
- package/package.json +1 -1
- package/dist/client/assets/_sessionKey-Bg_9uype.js +0 -23
- package/dist/client/assets/agents-BiTHBb6Z.js +0 -2
- package/dist/client/assets/bots-DxhRnQp5.js +0 -2
- package/dist/client/assets/button-BciDmec0.js +0 -1
- package/dist/client/assets/file-explorer-screen-DPs-FWeA.js +0 -1
- package/dist/client/assets/files-SEycwYCa.js +0 -2
- package/dist/client/assets/follow-up-suggestions-BPjWBpiy.js +0 -5
- package/dist/client/assets/index-CGeJcqZ3.js +0 -3
- package/dist/client/assets/memory-C7lKdkmc.js +0 -2
- package/dist/client/assets/settings-dialog-aL-AH4Rt.js +0 -1
- package/dist/client/assets/skills-D1T6uemU.js +0 -2
- package/dist/client/assets/styles-D0L88B64.css +0 -1
- package/dist/client/assets/useCompositeItem-B-Axq9-D.js +0 -1
- package/dist/server/assets/_tanstack-start-manifest_v-DalBo2bY.js +0 -4
- package/dist/server/assets/follow-up-suggestions-C65ptDij.js +0 -336
|
@@ -12,7 +12,7 @@ import { execFile, execSync } from "node:child_process";
|
|
|
12
12
|
import { promisify } from "node:util";
|
|
13
13
|
import { readFile, mkdir, writeFile, rename, stat, readdir, rm, realpath, lstat } from "node:fs/promises";
|
|
14
14
|
import { posix } from "path";
|
|
15
|
-
const appCss = "/assets/styles-
|
|
15
|
+
const appCss = "/assets/styles-CWabEzNU.css";
|
|
16
16
|
const swRegisterScript = `
|
|
17
17
|
(() => {
|
|
18
18
|
// Skip PWA service worker inside Capacitor native shell — they conflict
|
|
@@ -223,7 +223,7 @@ function NotFoundRedirect() {
|
|
|
223
223
|
}
|
|
224
224
|
return null;
|
|
225
225
|
}
|
|
226
|
-
const Route$
|
|
226
|
+
const Route$C = createRootRoute({
|
|
227
227
|
notFoundComponent: NotFoundRedirect,
|
|
228
228
|
head: () => ({
|
|
229
229
|
meta: [
|
|
@@ -332,11 +332,11 @@ function RootDocument({ children }) {
|
|
|
332
332
|
] });
|
|
333
333
|
}
|
|
334
334
|
const $$splitComponentImporter$9 = () => import("./skills-BXUivxuo.js");
|
|
335
|
-
const Route$
|
|
335
|
+
const Route$B = createFileRoute("/skills")({
|
|
336
336
|
component: lazyRouteComponent($$splitComponentImporter$9, "component")
|
|
337
337
|
});
|
|
338
338
|
const $$splitComponentImporter$8 = () => import("./new-Dzk5YxE9.js");
|
|
339
|
-
const Route$
|
|
339
|
+
const Route$A = createFileRoute("/new")({
|
|
340
340
|
beforeLoad: function redirectToNewChat() {
|
|
341
341
|
throw redirect({
|
|
342
342
|
to: "/chat/$sessionKey",
|
|
@@ -348,36 +348,36 @@ const Route$z = createFileRoute("/new")({
|
|
|
348
348
|
},
|
|
349
349
|
component: lazyRouteComponent($$splitComponentImporter$8, "component")
|
|
350
350
|
});
|
|
351
|
-
const $$splitComponentImporter$7 = () => import("./memory-
|
|
352
|
-
const Route$
|
|
351
|
+
const $$splitComponentImporter$7 = () => import("./memory-woPzJFfs.js");
|
|
352
|
+
const Route$z = createFileRoute("/memory")({
|
|
353
353
|
component: lazyRouteComponent($$splitComponentImporter$7, "component")
|
|
354
354
|
});
|
|
355
355
|
const $$splitComponentImporter$6 = () => import("./files-DLxqp-h5.js");
|
|
356
|
-
const Route$
|
|
356
|
+
const Route$y = createFileRoute("/files")({
|
|
357
357
|
component: lazyRouteComponent($$splitComponentImporter$6, "component")
|
|
358
358
|
});
|
|
359
359
|
const $$splitComponentImporter$5 = () => import("./dashboard-UYRCu_mQ.js");
|
|
360
|
-
const Route$
|
|
360
|
+
const Route$x = createFileRoute("/dashboard")({
|
|
361
361
|
component: lazyRouteComponent($$splitComponentImporter$5, "component")
|
|
362
362
|
});
|
|
363
|
-
const $$splitComponentImporter$4 = () => import("./connect-
|
|
364
|
-
const Route$
|
|
363
|
+
const $$splitComponentImporter$4 = () => import("./connect-sd_NDvQN.js");
|
|
364
|
+
const Route$w = createFileRoute("/connect")({
|
|
365
365
|
component: lazyRouteComponent($$splitComponentImporter$4, "component")
|
|
366
366
|
});
|
|
367
367
|
const $$splitComponentImporter$3 = () => import("./bots-BDHeSvSQ.js");
|
|
368
|
-
const Route$
|
|
368
|
+
const Route$v = createFileRoute("/bots")({
|
|
369
369
|
component: lazyRouteComponent($$splitComponentImporter$3, "component")
|
|
370
370
|
});
|
|
371
371
|
const $$splitComponentImporter$2 = () => import("./agents-BuE0Yum3.js");
|
|
372
|
-
const Route$
|
|
372
|
+
const Route$u = createFileRoute("/agents")({
|
|
373
373
|
component: lazyRouteComponent($$splitComponentImporter$2, "component")
|
|
374
374
|
});
|
|
375
|
-
const $$splitComponentImporter$1 = () => import("./index-
|
|
376
|
-
const Route$
|
|
375
|
+
const $$splitComponentImporter$1 = () => import("./index-sXe2QC5V.js");
|
|
376
|
+
const Route$t = createFileRoute("/")({
|
|
377
377
|
component: lazyRouteComponent($$splitComponentImporter$1, "component")
|
|
378
378
|
});
|
|
379
|
-
const $$splitComponentImporter = () => import("./_sessionKey-
|
|
380
|
-
const Route$
|
|
379
|
+
const $$splitComponentImporter = () => import("./_sessionKey-COz7RLKC.js").then((n) => n.$);
|
|
380
|
+
const Route$s = createFileRoute("/chat/$sessionKey")({
|
|
381
381
|
component: lazyRouteComponent($$splitComponentImporter, "component")
|
|
382
382
|
});
|
|
383
383
|
function getGatewayConfig() {
|
|
@@ -1064,7 +1064,7 @@ async function ttsEdge(text, voice) {
|
|
|
1064
1064
|
}
|
|
1065
1065
|
});
|
|
1066
1066
|
}
|
|
1067
|
-
const Route$
|
|
1067
|
+
const Route$r = createFileRoute("/api/tts")({
|
|
1068
1068
|
server: {
|
|
1069
1069
|
handlers: {
|
|
1070
1070
|
POST: async ({ request }) => {
|
|
@@ -1219,7 +1219,7 @@ async function sttOpenAI(audioBlob, apiKey, language) {
|
|
|
1219
1219
|
const data = await res.json();
|
|
1220
1220
|
return { text: data.text || "" };
|
|
1221
1221
|
}
|
|
1222
|
-
const Route$
|
|
1222
|
+
const Route$q = createFileRoute("/api/stt")({
|
|
1223
1223
|
server: {
|
|
1224
1224
|
handlers: {
|
|
1225
1225
|
POST: async ({ request }) => {
|
|
@@ -1321,7 +1321,7 @@ const Route$p = createFileRoute("/api/stt")({
|
|
|
1321
1321
|
}
|
|
1322
1322
|
}
|
|
1323
1323
|
});
|
|
1324
|
-
const Route$
|
|
1324
|
+
const Route$p = createFileRoute("/api/stream")({
|
|
1325
1325
|
server: {
|
|
1326
1326
|
handlers: {
|
|
1327
1327
|
GET: async ({ request }) => {
|
|
@@ -1504,7 +1504,7 @@ function parseSearchResults(output) {
|
|
|
1504
1504
|
return { slug: line.trim(), displayName: line.trim(), version: "", summary: "" };
|
|
1505
1505
|
});
|
|
1506
1506
|
}
|
|
1507
|
-
const Route$
|
|
1507
|
+
const Route$o = createFileRoute("/api/skills")({
|
|
1508
1508
|
server: {
|
|
1509
1509
|
handlers: {
|
|
1510
1510
|
GET: async ({ request }) => {
|
|
@@ -1618,7 +1618,7 @@ function normalizeSessions(payload) {
|
|
|
1618
1618
|
});
|
|
1619
1619
|
return { sessions: normalized };
|
|
1620
1620
|
}
|
|
1621
|
-
const Route$
|
|
1621
|
+
const Route$n = createFileRoute("/api/sessions")({
|
|
1622
1622
|
server: {
|
|
1623
1623
|
handlers: {
|
|
1624
1624
|
GET: async () => {
|
|
@@ -1771,7 +1771,7 @@ const Route$m = createFileRoute("/api/sessions")({
|
|
|
1771
1771
|
}
|
|
1772
1772
|
}
|
|
1773
1773
|
});
|
|
1774
|
-
const Route$
|
|
1774
|
+
const Route$m = createFileRoute("/api/send")({
|
|
1775
1775
|
server: {
|
|
1776
1776
|
handlers: {
|
|
1777
1777
|
POST: async ({ request }) => {
|
|
@@ -1837,7 +1837,7 @@ const Route$l = createFileRoute("/api/send")({
|
|
|
1837
1837
|
}
|
|
1838
1838
|
}
|
|
1839
1839
|
});
|
|
1840
|
-
const Route$
|
|
1840
|
+
const Route$l = createFileRoute("/api/ping")({
|
|
1841
1841
|
server: {
|
|
1842
1842
|
handlers: {
|
|
1843
1843
|
GET: async () => {
|
|
@@ -1887,7 +1887,7 @@ const categories = { "core": [{ "id": "cami", "name": "Cami", "emoji": "🦎", "
|
|
|
1887
1887
|
const personasData = {
|
|
1888
1888
|
categories
|
|
1889
1889
|
};
|
|
1890
|
-
const Route$
|
|
1890
|
+
const Route$k = createFileRoute("/api/personas")({
|
|
1891
1891
|
server: {
|
|
1892
1892
|
handlers: {
|
|
1893
1893
|
GET: async () => {
|
|
@@ -1932,7 +1932,7 @@ function resolveSessionsDir() {
|
|
|
1932
1932
|
)
|
|
1933
1933
|
};
|
|
1934
1934
|
}
|
|
1935
|
-
const Route$
|
|
1935
|
+
const Route$j = createFileRoute("/api/paths")({
|
|
1936
1936
|
server: {
|
|
1937
1937
|
handlers: {
|
|
1938
1938
|
GET: () => {
|
|
@@ -1957,7 +1957,7 @@ function parseModelName(modelId) {
|
|
|
1957
1957
|
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
1958
1958
|
}).join(" ");
|
|
1959
1959
|
}
|
|
1960
|
-
const Route$
|
|
1960
|
+
const Route$i = createFileRoute("/api/models")({
|
|
1961
1961
|
server: {
|
|
1962
1962
|
handlers: {
|
|
1963
1963
|
GET: async () => {
|
|
@@ -2017,180 +2017,259 @@ const Route$h = createFileRoute("/api/models")({
|
|
|
2017
2017
|
}
|
|
2018
2018
|
}
|
|
2019
2019
|
});
|
|
2020
|
-
const
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
"
|
|
2024
|
-
"
|
|
2025
|
-
"
|
|
2026
|
-
|
|
2020
|
+
const ALLOWED_AGENT_IDS = /* @__PURE__ */ new Set([
|
|
2021
|
+
"main",
|
|
2022
|
+
"codex",
|
|
2023
|
+
"gpt54",
|
|
2024
|
+
"gpt54mini",
|
|
2025
|
+
"kimi",
|
|
2026
|
+
"grok",
|
|
2027
|
+
"gemini",
|
|
2028
|
+
"gh-sonnet",
|
|
2029
|
+
"gh-haiku",
|
|
2030
|
+
"oc-mimo-pro",
|
|
2031
|
+
"oc-mimo-omni",
|
|
2032
|
+
"hermes"
|
|
2033
|
+
]);
|
|
2034
|
+
const AGENT_DISPLAY_NAMES = {
|
|
2035
|
+
main: "Main (MiMo)",
|
|
2036
|
+
codex: "Codex (GPT-5.4)",
|
|
2037
|
+
gpt54: "GPT-5.4",
|
|
2038
|
+
gpt54mini: "GPT-5.4 Mini",
|
|
2039
|
+
kimi: "Kimi K2.5",
|
|
2040
|
+
grok: "Grok 4.20",
|
|
2041
|
+
gemini: "Gemini 3.1",
|
|
2042
|
+
"gh-sonnet": "Claude Sonnet (GitHub)",
|
|
2043
|
+
"gh-haiku": "Claude Haiku (GitHub)",
|
|
2044
|
+
"oc-mimo-pro": "MiMo Pro",
|
|
2045
|
+
"oc-mimo-omni": "MiMo Omni",
|
|
2046
|
+
hermes: "Hermes"
|
|
2047
|
+
};
|
|
2048
|
+
const GATEWAY_MODELS_URL = (process.env.CLAWDBOT_GATEWAY_URL?.replace(/^ws/, "http") ?? "http://127.0.0.1:18789") + "/v1/models";
|
|
2049
|
+
function getGatewayToken$1() {
|
|
2050
|
+
return process.env.CLAWDBOT_GATEWAY_TOKEN?.trim() || process.env.OPENCLAW_GATEWAY_TOKEN?.trim() || null;
|
|
2051
|
+
}
|
|
2052
|
+
const Route$h = createFileRoute("/api/llm-models")({
|
|
2053
|
+
server: {
|
|
2054
|
+
handlers: {
|
|
2055
|
+
GET: async () => {
|
|
2056
|
+
try {
|
|
2057
|
+
const token = getGatewayToken$1();
|
|
2058
|
+
const response = await fetch(GATEWAY_MODELS_URL, {
|
|
2059
|
+
headers: {
|
|
2060
|
+
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
2061
|
+
}
|
|
2062
|
+
});
|
|
2063
|
+
if (!response.ok) {
|
|
2064
|
+
const errorText = await response.text().catch(() => "");
|
|
2065
|
+
throw new Error(
|
|
2066
|
+
`Gateway models request failed: ${response.status}${errorText ? ` ${errorText}` : ""}`
|
|
2067
|
+
);
|
|
2068
|
+
}
|
|
2069
|
+
const data = await response.json();
|
|
2070
|
+
const seenAgentIds = /* @__PURE__ */ new Set();
|
|
2071
|
+
const models = (data.data ?? []).map((model) => model.id?.trim()).filter((id) => Boolean(id?.startsWith("openclaw/"))).map((id) => {
|
|
2072
|
+
const agentId = id.slice("openclaw/".length);
|
|
2073
|
+
return { id, agentId };
|
|
2074
|
+
}).filter(({ agentId }) => ALLOWED_AGENT_IDS.has(agentId)).filter(({ agentId }) => {
|
|
2075
|
+
if (seenAgentIds.has(agentId)) return false;
|
|
2076
|
+
seenAgentIds.add(agentId);
|
|
2077
|
+
return true;
|
|
2078
|
+
}).map(({ id, agentId }) => ({
|
|
2079
|
+
id,
|
|
2080
|
+
agentId,
|
|
2081
|
+
displayName: AGENT_DISPLAY_NAMES[agentId] ?? agentId
|
|
2082
|
+
}));
|
|
2083
|
+
return json({
|
|
2084
|
+
ok: true,
|
|
2085
|
+
models,
|
|
2086
|
+
defaultModel: "gpt54mini"
|
|
2087
|
+
});
|
|
2088
|
+
} catch (err) {
|
|
2089
|
+
console.error("[llm-models] Error fetching models:", err);
|
|
2090
|
+
return json(
|
|
2091
|
+
{
|
|
2092
|
+
ok: false,
|
|
2093
|
+
models: [
|
|
2094
|
+
{
|
|
2095
|
+
id: "openclaw/gpt54mini",
|
|
2096
|
+
agentId: "gpt54mini",
|
|
2097
|
+
displayName: AGENT_DISPLAY_NAMES.gpt54mini
|
|
2098
|
+
}
|
|
2099
|
+
],
|
|
2100
|
+
defaultModel: "gpt54mini",
|
|
2101
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2102
|
+
},
|
|
2103
|
+
{ status: 500 }
|
|
2104
|
+
);
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
});
|
|
2110
|
+
const OPENCLAW_GATEWAY_URL = (process.env.CLAWDBOT_GATEWAY_URL?.replace(/^ws/, "http") ?? "http://127.0.0.1:18789") + "/v1/chat/completions";
|
|
2111
|
+
const OPENCLAW_MODEL_OVERRIDE = process.env.OPENCAMI_LLM_FEATURES_MODEL?.trim() || process.env.OPENCLAW_LLM_FEATURES_MODEL?.trim() || "";
|
|
2112
|
+
const OPENCLAW_MODEL_FALLBACKS = OPENCLAW_MODEL_OVERRIDE ? [OPENCLAW_MODEL_OVERRIDE] : ["openclaw/gpt54mini", "openclaw"];
|
|
2113
|
+
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
2114
|
+
function getGatewayToken() {
|
|
2115
|
+
return process.env.OPENCLAW_GATEWAY_TOKEN?.trim() || process.env.CLAWDBOT_GATEWAY_TOKEN?.trim() || null;
|
|
2116
|
+
}
|
|
2027
2117
|
async function chatCompletion(messages, options) {
|
|
2028
|
-
const
|
|
2029
|
-
apiKey,
|
|
2030
|
-
baseUrl = DEFAULT_BASE_URL,
|
|
2031
|
-
model,
|
|
2032
|
-
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
2033
|
-
maxTokens = 100
|
|
2034
|
-
} = options;
|
|
2035
|
-
const modelsToTry = model ? [model] : MODEL_FALLBACK_CHAIN;
|
|
2118
|
+
const token = getGatewayToken();
|
|
2036
2119
|
let lastError = null;
|
|
2037
|
-
|
|
2120
|
+
const models = options?.model ? [options.model] : OPENCLAW_MODEL_FALLBACKS;
|
|
2121
|
+
for (const model of models) {
|
|
2038
2122
|
const controller = new AbortController();
|
|
2039
|
-
const timeoutId = setTimeout(
|
|
2123
|
+
const timeoutId = setTimeout(
|
|
2124
|
+
() => controller.abort(),
|
|
2125
|
+
options?.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
|
2126
|
+
);
|
|
2040
2127
|
try {
|
|
2041
|
-
const response = await fetch(
|
|
2128
|
+
const response = await fetch(OPENCLAW_GATEWAY_URL, {
|
|
2042
2129
|
method: "POST",
|
|
2043
2130
|
headers: {
|
|
2044
2131
|
"Content-Type": "application/json",
|
|
2045
|
-
|
|
2132
|
+
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
2046
2133
|
},
|
|
2047
2134
|
body: JSON.stringify({
|
|
2048
|
-
model
|
|
2135
|
+
model,
|
|
2049
2136
|
messages,
|
|
2050
|
-
max_tokens: maxTokens,
|
|
2051
|
-
temperature: 0.7
|
|
2137
|
+
max_tokens: options?.maxTokens ?? 200,
|
|
2138
|
+
temperature: options?.temperature ?? 0.7
|
|
2052
2139
|
}),
|
|
2053
2140
|
signal: controller.signal
|
|
2054
2141
|
});
|
|
2055
|
-
clearTimeout(timeoutId);
|
|
2056
2142
|
if (!response.ok) {
|
|
2057
|
-
const errorText = await response.text();
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
}
|
|
2063
|
-
throw new Error(`OpenAI API error: ${response.status} ${errorText}`);
|
|
2143
|
+
const errorText = await response.text().catch(() => "");
|
|
2144
|
+
lastError = new Error(
|
|
2145
|
+
`OpenClaw Gateway error (${model}): ${response.status}${errorText ? ` ${errorText}` : ""}`
|
|
2146
|
+
);
|
|
2147
|
+
continue;
|
|
2064
2148
|
}
|
|
2065
2149
|
const data = await response.json();
|
|
2066
|
-
const content = data.choices[0]?.message?.content?.trim() || "";
|
|
2067
|
-
if (content) {
|
|
2068
|
-
|
|
2150
|
+
const content = data.choices?.[0]?.message?.content?.trim() || "";
|
|
2151
|
+
if (!content) {
|
|
2152
|
+
lastError = new Error(`OpenClaw Gateway returned empty content for ${model}`);
|
|
2153
|
+
continue;
|
|
2069
2154
|
}
|
|
2070
|
-
|
|
2071
|
-
continue;
|
|
2155
|
+
return content;
|
|
2072
2156
|
} catch (error) {
|
|
2073
|
-
clearTimeout(timeoutId);
|
|
2074
2157
|
if (error instanceof Error && error.name === "AbortError") {
|
|
2075
|
-
lastError = new Error(
|
|
2158
|
+
lastError = new Error(`OpenClaw Gateway request timed out for ${model}`);
|
|
2076
2159
|
continue;
|
|
2077
2160
|
}
|
|
2078
2161
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
2079
|
-
|
|
2162
|
+
} finally {
|
|
2163
|
+
clearTimeout(timeoutId);
|
|
2080
2164
|
}
|
|
2081
2165
|
}
|
|
2082
|
-
throw lastError || new Error("
|
|
2166
|
+
throw lastError || new Error("OpenClaw Gateway request failed for all models");
|
|
2167
|
+
}
|
|
2168
|
+
function dedupeFollowUps(lines) {
|
|
2169
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2170
|
+
return lines.filter((line) => {
|
|
2171
|
+
const key = line.toLocaleLowerCase("de-DE");
|
|
2172
|
+
if (seen.has(key)) {
|
|
2173
|
+
return false;
|
|
2174
|
+
}
|
|
2175
|
+
seen.add(key);
|
|
2176
|
+
return true;
|
|
2177
|
+
});
|
|
2178
|
+
}
|
|
2179
|
+
function looksLikeQuestion(line) {
|
|
2180
|
+
if (line.endsWith("?")) return true;
|
|
2181
|
+
if (line.length < 8 || line.length > 90) return false;
|
|
2182
|
+
if (/[.!:]$/.test(line)) return false;
|
|
2183
|
+
if (/\b(?:ja|nein|ok|okay|danke|bitte)\b/i.test(line)) return false;
|
|
2184
|
+
return /^(?:wie|was|warum|wieso|weshalb|wo|wohin|woher|wann|welche(?:r|s|n)?|welcher|welches|welchen|wer|wen|wem|kann(?:st)?|könn(?:te|test)|soll(?:te|st)|möcht(?:est|et)|willst|gibt|brauche|zeigt|erklär|hilf|nenn|sag)\b/i.test(
|
|
2185
|
+
line
|
|
2186
|
+
);
|
|
2187
|
+
}
|
|
2188
|
+
function parseFollowUps(text) {
|
|
2189
|
+
const candidates = text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => line.replace(/^["']|["']$/g, "").trim()).map((line) => line.replace(/^\d+[.)\s]+/, "").trim()).map((line) => line.replace(/^[-•*]\s*/, "").trim()).map((line) => line.replace(/\s+/g, " ")).filter((line) => line.length > 0 && line.length < 150).filter((line) => !/[`*_#]/.test(line)).filter((line) => !/^#{1,6}\s/.test(line));
|
|
2190
|
+
const explicitQuestions = candidates.filter((line) => line.endsWith("?"));
|
|
2191
|
+
const inferredQuestions = candidates.filter((line) => looksLikeQuestion(line));
|
|
2192
|
+
const inlineQuestions = text.match(/[^\n?.!]*\?/g)?.map((line) => line.trim()) ?? [];
|
|
2193
|
+
return dedupeFollowUps([
|
|
2194
|
+
...explicitQuestions,
|
|
2195
|
+
...inferredQuestions,
|
|
2196
|
+
...inlineQuestions
|
|
2197
|
+
]).slice(0, 3);
|
|
2083
2198
|
}
|
|
2084
|
-
async function
|
|
2085
|
-
const systemPrompt = `
|
|
2199
|
+
async function generateTitleViaOpenclaw(message, model) {
|
|
2200
|
+
const systemPrompt = `You are a title generator. Output ONLY a short title, nothing else.
|
|
2086
2201
|
Rules:
|
|
2087
|
-
-
|
|
2088
|
-
-
|
|
2089
|
-
-
|
|
2090
|
-
-
|
|
2202
|
+
- 3-6 words maximum
|
|
2203
|
+
- No quotes, no punctuation at the end
|
|
2204
|
+
- No explanation, no markdown
|
|
2205
|
+
- Just the title text
|
|
2206
|
+
- Use the same language as the input
|
|
2207
|
+
|
|
2208
|
+
Example: "Nginx Reverse Proxy Setup"`;
|
|
2091
2209
|
return chatCompletion(
|
|
2092
2210
|
[
|
|
2093
2211
|
{ role: "system", content: systemPrompt },
|
|
2094
2212
|
{ role: "user", content: message }
|
|
2095
2213
|
],
|
|
2096
|
-
{
|
|
2214
|
+
{ maxTokens: 60, temperature: 0.3, model }
|
|
2097
2215
|
);
|
|
2098
2216
|
}
|
|
2099
|
-
async function
|
|
2100
|
-
const
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2217
|
+
async function generateFollowUpsViaOpenclaw(responseText, contextSummary, model) {
|
|
2218
|
+
const truncatedResponse = responseText.length > 1500 ? `${responseText.slice(0, 1500)}...` : responseText;
|
|
2219
|
+
const trimmedSummary = contextSummary?.slice(0, 500).trim() || "";
|
|
2220
|
+
const userPrompt = trimmedSummary ? `Context: ${trimmedSummary}
|
|
2221
|
+
|
|
2222
|
+
Assistant's response:
|
|
2223
|
+
${truncatedResponse}` : `Assistant's response:
|
|
2224
|
+
${truncatedResponse}`;
|
|
2225
|
+
const systemPrompt = `You generate follow-up suggestions for a chat UI.
|
|
2226
|
+
|
|
2227
|
+
Return exactly 3 short follow-up QUESTIONS in the SAME LANGUAGE as the conversation context.
|
|
2228
|
+
|
|
2229
|
+
Hard rules:
|
|
2230
|
+
- Output exactly 3 lines
|
|
2231
|
+
- Every line must be a natural question in the same language as the input
|
|
2232
|
+
- No markdown
|
|
2233
|
+
- No bullet points
|
|
2234
|
+
- No numbering
|
|
2235
|
+
- No asterisks
|
|
2236
|
+
- No explanations
|
|
2237
|
+
- No intro or outro text
|
|
2238
|
+
- No quotes
|
|
2239
|
+
- Keep each question concise and relevant
|
|
2240
|
+
- Prefer under 60 characters when possible
|
|
2241
|
+
- Vary them across clarification, deeper detail, and practical use
|
|
2242
|
+
|
|
2243
|
+
Example output for English input:
|
|
2244
|
+
How exactly does this work?
|
|
2245
|
+
When would you typically use this?
|
|
2246
|
+
What should I watch out for in practice?
|
|
2247
|
+
|
|
2248
|
+
Example output for German input:
|
|
2249
|
+
Wie funktioniert das genau?
|
|
2250
|
+
Wann wird das typischerweise verwendet?
|
|
2251
|
+
Worauf sollte ich in der Praxis achten?`;
|
|
2106
2252
|
const response = await chatCompletion(
|
|
2107
2253
|
[
|
|
2108
2254
|
{ role: "system", content: systemPrompt },
|
|
2109
|
-
{ role: "user", content:
|
|
2255
|
+
{ role: "user", content: userPrompt }
|
|
2110
2256
|
],
|
|
2111
|
-
{
|
|
2257
|
+
{ maxTokens: 200, temperature: 0.2, model }
|
|
2112
2258
|
);
|
|
2113
|
-
|
|
2114
|
-
let jsonStr = response.trim();
|
|
2115
|
-
if (jsonStr.startsWith("```")) {
|
|
2116
|
-
jsonStr = jsonStr.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "");
|
|
2117
|
-
}
|
|
2118
|
-
const parsed = JSON.parse(jsonStr);
|
|
2119
|
-
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
2120
|
-
return parsed.slice(0, 3).map(String);
|
|
2121
|
-
}
|
|
2122
|
-
} catch {
|
|
2123
|
-
}
|
|
2124
|
-
return [];
|
|
2259
|
+
return parseFollowUps(response);
|
|
2125
2260
|
}
|
|
2126
|
-
async function
|
|
2127
|
-
const llmOptions = typeof options === "string" ? { apiKey: options } : options;
|
|
2261
|
+
async function isOpenclawAvailable() {
|
|
2128
2262
|
try {
|
|
2129
|
-
await chatCompletion(
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2263
|
+
await chatCompletion([{ role: "user", content: "Hi" }], {
|
|
2264
|
+
maxTokens: 1,
|
|
2265
|
+
temperature: 0,
|
|
2266
|
+
timeoutMs: 1e4
|
|
2267
|
+
});
|
|
2133
2268
|
return true;
|
|
2134
2269
|
} catch {
|
|
2135
2270
|
return false;
|
|
2136
2271
|
}
|
|
2137
2272
|
}
|
|
2138
|
-
const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
2139
|
-
const PRESET_BASE_URL_ORIGINS = /* @__PURE__ */ new Set([
|
|
2140
|
-
"https://api.openai.com",
|
|
2141
|
-
"https://openrouter.ai",
|
|
2142
|
-
"https://api.kilo.ai",
|
|
2143
|
-
"http://localhost:11434",
|
|
2144
|
-
"http://127.0.0.1:11434"
|
|
2145
|
-
]);
|
|
2146
|
-
function getOrigin(rawBaseUrl) {
|
|
2147
|
-
try {
|
|
2148
|
-
return new URL(rawBaseUrl).origin;
|
|
2149
|
-
} catch {
|
|
2150
|
-
return null;
|
|
2151
|
-
}
|
|
2152
|
-
}
|
|
2153
|
-
function isAllowedClientBaseUrl(rawBaseUrl) {
|
|
2154
|
-
const parsed = new URL(rawBaseUrl);
|
|
2155
|
-
if (!["http:", "https:"].includes(parsed.protocol)) return false;
|
|
2156
|
-
if (parsed.username || parsed.password) return false;
|
|
2157
|
-
const hostname = parsed.hostname.toLowerCase();
|
|
2158
|
-
const isLocalHost = hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
|
|
2159
|
-
if (!isLocalHost && parsed.protocol !== "https:") return false;
|
|
2160
|
-
const origin = parsed.origin;
|
|
2161
|
-
if (PRESET_BASE_URL_ORIGINS.has(origin)) return true;
|
|
2162
|
-
const envBaseUrl = process.env.LLM_BASE_URL?.trim();
|
|
2163
|
-
const envOrigin = envBaseUrl ? getOrigin(envBaseUrl) : null;
|
|
2164
|
-
return Boolean(envOrigin && envOrigin === origin);
|
|
2165
|
-
}
|
|
2166
|
-
function detectProvider(rawBaseUrl) {
|
|
2167
|
-
const baseUrl = rawBaseUrl?.toLowerCase() || "";
|
|
2168
|
-
if (baseUrl.includes("openrouter.ai")) return "openrouter";
|
|
2169
|
-
if (baseUrl.includes("kilo.ai")) return "kilocode";
|
|
2170
|
-
return "openai";
|
|
2171
|
-
}
|
|
2172
|
-
function getLlmConfig(request) {
|
|
2173
|
-
const headerKey = request.headers.get("X-OpenAI-API-Key");
|
|
2174
|
-
const headerBaseUrl = request.headers.get("X-LLM-Base-URL")?.trim() || null;
|
|
2175
|
-
const envBaseUrl = process.env.LLM_BASE_URL?.trim() || null;
|
|
2176
|
-
if (headerBaseUrl) {
|
|
2177
|
-
const origin = getOrigin(headerBaseUrl);
|
|
2178
|
-
if (!origin || !isAllowedClientBaseUrl(headerBaseUrl)) {
|
|
2179
|
-
return {
|
|
2180
|
-
apiKey: null,
|
|
2181
|
-
baseUrl: null,
|
|
2182
|
-
model: null,
|
|
2183
|
-
error: "Disallowed X-LLM-Base-URL value"
|
|
2184
|
-
};
|
|
2185
|
-
}
|
|
2186
|
-
}
|
|
2187
|
-
const baseUrl = headerBaseUrl || envBaseUrl || DEFAULT_OPENAI_BASE_URL;
|
|
2188
|
-
const provider = detectProvider(baseUrl);
|
|
2189
|
-
const envKey = provider === "openrouter" ? process.env.OPENROUTER_API_KEY?.trim() || process.env.OPENAI_API_KEY?.trim() : provider === "kilocode" ? process.env.KILOCODE_API_KEY?.trim() || process.env.OPENAI_API_KEY?.trim() : process.env.OPENAI_API_KEY?.trim();
|
|
2190
|
-
const apiKey = headerKey?.trim() || envKey || null;
|
|
2191
|
-
const model = request.headers.get("X-LLM-Model")?.trim() || process.env.LLM_MODEL?.trim() || null;
|
|
2192
|
-
return { apiKey, baseUrl, model, error: null };
|
|
2193
|
-
}
|
|
2194
2273
|
function generateHeuristicTitle(message) {
|
|
2195
2274
|
let text = message.replace(/```[\s\S]*?```/g, " ");
|
|
2196
2275
|
text = text.replace(/`[^`]+`/g, " ");
|
|
@@ -2214,88 +2293,56 @@ function generateHeuristicTitle(message) {
|
|
|
2214
2293
|
const Route$g = createFileRoute("/api/llm-features")({
|
|
2215
2294
|
server: {
|
|
2216
2295
|
handlers: {
|
|
2217
|
-
/**
|
|
2218
|
-
* GET /api/llm-features - Check LLM features status
|
|
2219
|
-
*/
|
|
2220
2296
|
GET: async () => {
|
|
2221
2297
|
try {
|
|
2222
|
-
const
|
|
2223
|
-
const hasOpenRouterKey = Boolean(process.env.OPENROUTER_API_KEY?.trim());
|
|
2224
|
-
const hasKilocodeKey = Boolean(process.env.KILOCODE_API_KEY?.trim());
|
|
2298
|
+
const available = await isOpenclawAvailable();
|
|
2225
2299
|
return json({
|
|
2226
2300
|
ok: true,
|
|
2227
|
-
|
|
2228
|
-
hasOpenRouterKey,
|
|
2229
|
-
hasKilocodeKey
|
|
2301
|
+
available
|
|
2230
2302
|
});
|
|
2231
2303
|
} catch (err) {
|
|
2232
2304
|
return json({
|
|
2233
2305
|
ok: false,
|
|
2234
|
-
|
|
2306
|
+
available: false,
|
|
2235
2307
|
error: err instanceof Error ? err.message : String(err)
|
|
2236
2308
|
});
|
|
2237
2309
|
}
|
|
2238
2310
|
},
|
|
2239
|
-
/**
|
|
2240
|
-
* POST /api/llm-features - Handle LLM feature requests
|
|
2241
|
-
*
|
|
2242
|
-
* Request body should include an "action" field:
|
|
2243
|
-
* - action: "title" - Generate session title
|
|
2244
|
-
* - action: "followups" - Generate follow-up suggestions
|
|
2245
|
-
* - action: "test" - Test API key validity
|
|
2246
|
-
*/
|
|
2247
2311
|
POST: async ({ request }) => {
|
|
2248
2312
|
try {
|
|
2249
2313
|
const body = await request.json().catch(() => ({}));
|
|
2250
2314
|
const action = body.action;
|
|
2251
2315
|
switch (action) {
|
|
2252
2316
|
case "title": {
|
|
2253
|
-
const { message } = body;
|
|
2317
|
+
const { message, model } = body;
|
|
2254
2318
|
if (!message || typeof message !== "string" || message.trim().length < 3) {
|
|
2255
|
-
return json(
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
return json({
|
|
2263
|
-
ok: false,
|
|
2264
|
-
error: llmConfig.error
|
|
2265
|
-
}, { status: 400 });
|
|
2266
|
-
}
|
|
2267
|
-
if (!llmConfig.apiKey && !llmConfig.baseUrl?.includes("localhost")) {
|
|
2268
|
-
const title = generateHeuristicTitle(message);
|
|
2269
|
-
return json({
|
|
2270
|
-
ok: true,
|
|
2271
|
-
title,
|
|
2272
|
-
source: "heuristic"
|
|
2273
|
-
});
|
|
2319
|
+
return json(
|
|
2320
|
+
{
|
|
2321
|
+
ok: false,
|
|
2322
|
+
error: "Message is required and must be at least 3 characters"
|
|
2323
|
+
},
|
|
2324
|
+
{ status: 400 }
|
|
2325
|
+
);
|
|
2274
2326
|
}
|
|
2275
2327
|
try {
|
|
2276
|
-
const title = await
|
|
2277
|
-
apiKey: llmConfig.apiKey || "",
|
|
2278
|
-
...llmConfig.baseUrl ? { baseUrl: llmConfig.baseUrl } : {},
|
|
2279
|
-
...llmConfig.model ? { model: llmConfig.model } : {}
|
|
2280
|
-
});
|
|
2328
|
+
const title = await generateTitleViaOpenclaw(message, model);
|
|
2281
2329
|
return json({
|
|
2282
2330
|
ok: true,
|
|
2283
2331
|
title,
|
|
2284
|
-
source: "
|
|
2332
|
+
source: "openclaw"
|
|
2285
2333
|
});
|
|
2286
2334
|
} catch (err) {
|
|
2287
2335
|
console.error("[llm-features] Title generation error:", err);
|
|
2288
|
-
const title = generateHeuristicTitle(message);
|
|
2289
2336
|
return json({
|
|
2290
2337
|
ok: true,
|
|
2291
|
-
title,
|
|
2338
|
+
title: generateHeuristicTitle(message),
|
|
2292
2339
|
source: "heuristic",
|
|
2293
|
-
error: err instanceof Error ? err.message : "
|
|
2340
|
+
error: err instanceof Error ? err.message : "OpenClaw error, used heuristic"
|
|
2294
2341
|
});
|
|
2295
2342
|
}
|
|
2296
2343
|
}
|
|
2297
2344
|
case "followups": {
|
|
2298
|
-
const { conversationContext } = body;
|
|
2345
|
+
const { conversationContext, model } = body;
|
|
2299
2346
|
if (!conversationContext || typeof conversationContext !== "string" || conversationContext.trim().length < 10) {
|
|
2300
2347
|
return json({
|
|
2301
2348
|
ok: true,
|
|
@@ -2303,30 +2350,16 @@ const Route$g = createFileRoute("/api/llm-features")({
|
|
|
2303
2350
|
source: "heuristic"
|
|
2304
2351
|
});
|
|
2305
2352
|
}
|
|
2306
|
-
const llmConfig = getLlmConfig(request);
|
|
2307
|
-
if (llmConfig.error) {
|
|
2308
|
-
return json({
|
|
2309
|
-
ok: false,
|
|
2310
|
-
error: llmConfig.error
|
|
2311
|
-
}, { status: 400 });
|
|
2312
|
-
}
|
|
2313
|
-
if (!llmConfig.apiKey && !llmConfig.baseUrl?.includes("localhost")) {
|
|
2314
|
-
return json({
|
|
2315
|
-
ok: true,
|
|
2316
|
-
suggestions: [],
|
|
2317
|
-
source: "heuristic"
|
|
2318
|
-
});
|
|
2319
|
-
}
|
|
2320
2353
|
try {
|
|
2321
|
-
const suggestions = await
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2354
|
+
const suggestions = await generateFollowUpsViaOpenclaw(
|
|
2355
|
+
conversationContext,
|
|
2356
|
+
void 0,
|
|
2357
|
+
model
|
|
2358
|
+
);
|
|
2326
2359
|
return json({
|
|
2327
2360
|
ok: true,
|
|
2328
2361
|
suggestions,
|
|
2329
|
-
source: "
|
|
2362
|
+
source: "openclaw"
|
|
2330
2363
|
});
|
|
2331
2364
|
} catch (err) {
|
|
2332
2365
|
console.error("[llm-features] Follow-ups generation error:", err);
|
|
@@ -2334,54 +2367,35 @@ const Route$g = createFileRoute("/api/llm-features")({
|
|
|
2334
2367
|
ok: true,
|
|
2335
2368
|
suggestions: [],
|
|
2336
2369
|
source: "heuristic",
|
|
2337
|
-
error: err instanceof Error ? err.message : "
|
|
2370
|
+
error: err instanceof Error ? err.message : "OpenClaw error"
|
|
2338
2371
|
});
|
|
2339
2372
|
}
|
|
2340
2373
|
}
|
|
2341
2374
|
case "test": {
|
|
2342
|
-
const
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
}, { status: 400 });
|
|
2348
|
-
}
|
|
2349
|
-
if (!llmConfig.apiKey && !llmConfig.baseUrl?.includes("localhost")) {
|
|
2350
|
-
return json({
|
|
2351
|
-
ok: false,
|
|
2352
|
-
error: "API key required (or use Ollama for keyless access)"
|
|
2353
|
-
});
|
|
2354
|
-
}
|
|
2355
|
-
try {
|
|
2356
|
-
const valid = await testApiKey({
|
|
2357
|
-
apiKey: llmConfig.apiKey || "",
|
|
2358
|
-
...llmConfig.baseUrl ? { baseUrl: llmConfig.baseUrl } : {},
|
|
2359
|
-
...llmConfig.model ? { model: llmConfig.model } : {}
|
|
2360
|
-
});
|
|
2361
|
-
return json({
|
|
2362
|
-
ok: true,
|
|
2363
|
-
valid
|
|
2364
|
-
});
|
|
2365
|
-
} catch (err) {
|
|
2366
|
-
return json({
|
|
2367
|
-
ok: true,
|
|
2368
|
-
valid: false,
|
|
2369
|
-
error: err instanceof Error ? err.message : "Test failed"
|
|
2370
|
-
});
|
|
2371
|
-
}
|
|
2375
|
+
const available = await isOpenclawAvailable();
|
|
2376
|
+
return json({
|
|
2377
|
+
ok: true,
|
|
2378
|
+
available
|
|
2379
|
+
});
|
|
2372
2380
|
}
|
|
2373
2381
|
default:
|
|
2374
|
-
return json(
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2382
|
+
return json(
|
|
2383
|
+
{
|
|
2384
|
+
ok: false,
|
|
2385
|
+
error: `Unknown action: ${action}. Valid actions: title, followups, test`
|
|
2386
|
+
},
|
|
2387
|
+
{ status: 400 }
|
|
2388
|
+
);
|
|
2378
2389
|
}
|
|
2379
2390
|
} catch (err) {
|
|
2380
2391
|
console.error("[llm-features] Error:", err);
|
|
2381
|
-
return json(
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2392
|
+
return json(
|
|
2393
|
+
{
|
|
2394
|
+
ok: false,
|
|
2395
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2396
|
+
},
|
|
2397
|
+
{ status: 500 }
|
|
2398
|
+
);
|
|
2385
2399
|
}
|
|
2386
2400
|
}
|
|
2387
2401
|
}
|
|
@@ -2435,21 +2449,6 @@ const Route$f = createFileRoute("/api/history")({
|
|
|
2435
2449
|
}
|
|
2436
2450
|
}
|
|
2437
2451
|
});
|
|
2438
|
-
const FOLLOW_UP_SYSTEM_PROMPT = `You are a helpful assistant that generates follow-up question suggestions.
|
|
2439
|
-
Given the assistant's last response, generate exactly 3 short, natural follow-up questions the user might want to ask.
|
|
2440
|
-
|
|
2441
|
-
Rules:
|
|
2442
|
-
- Each suggestion should be a single, concise question (under 60 characters preferred)
|
|
2443
|
-
- Make them contextually relevant to the response
|
|
2444
|
-
- Vary the types: clarification, deeper exploration, practical application
|
|
2445
|
-
- Use natural, conversational language
|
|
2446
|
-
- Do not number them or add any prefix
|
|
2447
|
-
|
|
2448
|
-
Output format: Return ONLY the 3 questions, one per line, nothing else.`;
|
|
2449
|
-
function parseFollowUps(text) {
|
|
2450
|
-
const lines = text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => line.replace(/^\d+[.)\s]+/, "").trim()).map((line) => line.replace(/^[-•*]\s*/, "").trim()).map((line) => line.replace(/^["']|["']$/g, "").trim()).filter((line) => line.length > 0 && line.length < 150);
|
|
2451
|
-
return lines.slice(0, 3);
|
|
2452
|
-
}
|
|
2453
2452
|
const Route$e = createFileRoute("/api/follow-ups")({
|
|
2454
2453
|
server: {
|
|
2455
2454
|
handlers: {
|
|
@@ -2460,24 +2459,11 @@ const Route$e = createFileRoute("/api/follow-ups")({
|
|
|
2460
2459
|
if (!responseText || responseText.length < 30) {
|
|
2461
2460
|
return json({ ok: true, suggestions: [] });
|
|
2462
2461
|
}
|
|
2463
|
-
const
|
|
2464
|
-
const
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
${truncatedResponse}` : `Assistant's response:
|
|
2469
|
-
${truncatedResponse}`;
|
|
2470
|
-
const res = await gatewayRpc("chat.complete", {
|
|
2471
|
-
messages: [
|
|
2472
|
-
{ role: "system", content: FOLLOW_UP_SYSTEM_PROMPT },
|
|
2473
|
-
{ role: "user", content: userPrompt }
|
|
2474
|
-
],
|
|
2475
|
-
maxTokens: 200,
|
|
2476
|
-
temperature: 0.7
|
|
2477
|
-
// Use session's default model (no hardcoding!)
|
|
2478
|
-
});
|
|
2479
|
-
const content = res.content || res.message?.content || res.choices?.[0]?.message?.content || "";
|
|
2480
|
-
const suggestions = parseFollowUps(content);
|
|
2462
|
+
const contextSummary = typeof body.contextSummary === "string" ? body.contextSummary : void 0;
|
|
2463
|
+
const suggestions = await generateFollowUpsViaOpenclaw(
|
|
2464
|
+
responseText,
|
|
2465
|
+
contextSummary
|
|
2466
|
+
);
|
|
2481
2467
|
return json({ ok: true, suggestions });
|
|
2482
2468
|
} catch (err) {
|
|
2483
2469
|
console.error("[follow-ups] Error generating suggestions:", err);
|
|
@@ -4003,190 +3989,195 @@ const Route = createFileRoute("/api/dashboard/crons")({
|
|
|
4003
3989
|
}
|
|
4004
3990
|
}
|
|
4005
3991
|
});
|
|
4006
|
-
const SkillsRoute = Route$
|
|
3992
|
+
const SkillsRoute = Route$B.update({
|
|
4007
3993
|
id: "/skills",
|
|
4008
3994
|
path: "/skills",
|
|
4009
|
-
getParentRoute: () => Route$
|
|
3995
|
+
getParentRoute: () => Route$C
|
|
4010
3996
|
});
|
|
4011
|
-
const NewRoute = Route$
|
|
3997
|
+
const NewRoute = Route$A.update({
|
|
4012
3998
|
id: "/new",
|
|
4013
3999
|
path: "/new",
|
|
4014
|
-
getParentRoute: () => Route$
|
|
4000
|
+
getParentRoute: () => Route$C
|
|
4015
4001
|
});
|
|
4016
|
-
const MemoryRoute = Route$
|
|
4002
|
+
const MemoryRoute = Route$z.update({
|
|
4017
4003
|
id: "/memory",
|
|
4018
4004
|
path: "/memory",
|
|
4019
|
-
getParentRoute: () => Route$
|
|
4005
|
+
getParentRoute: () => Route$C
|
|
4020
4006
|
});
|
|
4021
|
-
const FilesRoute = Route$
|
|
4007
|
+
const FilesRoute = Route$y.update({
|
|
4022
4008
|
id: "/files",
|
|
4023
4009
|
path: "/files",
|
|
4024
|
-
getParentRoute: () => Route$
|
|
4010
|
+
getParentRoute: () => Route$C
|
|
4025
4011
|
});
|
|
4026
|
-
const DashboardRoute = Route$
|
|
4012
|
+
const DashboardRoute = Route$x.update({
|
|
4027
4013
|
id: "/dashboard",
|
|
4028
4014
|
path: "/dashboard",
|
|
4029
|
-
getParentRoute: () => Route$
|
|
4015
|
+
getParentRoute: () => Route$C
|
|
4030
4016
|
});
|
|
4031
|
-
const ConnectRoute = Route$
|
|
4017
|
+
const ConnectRoute = Route$w.update({
|
|
4032
4018
|
id: "/connect",
|
|
4033
4019
|
path: "/connect",
|
|
4034
|
-
getParentRoute: () => Route$
|
|
4020
|
+
getParentRoute: () => Route$C
|
|
4035
4021
|
});
|
|
4036
|
-
const BotsRoute = Route$
|
|
4022
|
+
const BotsRoute = Route$v.update({
|
|
4037
4023
|
id: "/bots",
|
|
4038
4024
|
path: "/bots",
|
|
4039
|
-
getParentRoute: () => Route$
|
|
4025
|
+
getParentRoute: () => Route$C
|
|
4040
4026
|
});
|
|
4041
|
-
const AgentsRoute = Route$
|
|
4027
|
+
const AgentsRoute = Route$u.update({
|
|
4042
4028
|
id: "/agents",
|
|
4043
4029
|
path: "/agents",
|
|
4044
|
-
getParentRoute: () => Route$
|
|
4030
|
+
getParentRoute: () => Route$C
|
|
4045
4031
|
});
|
|
4046
|
-
const IndexRoute = Route$
|
|
4032
|
+
const IndexRoute = Route$t.update({
|
|
4047
4033
|
id: "/",
|
|
4048
4034
|
path: "/",
|
|
4049
|
-
getParentRoute: () => Route$
|
|
4035
|
+
getParentRoute: () => Route$C
|
|
4050
4036
|
});
|
|
4051
|
-
const ChatSessionKeyRoute = Route$
|
|
4037
|
+
const ChatSessionKeyRoute = Route$s.update({
|
|
4052
4038
|
id: "/chat/$sessionKey",
|
|
4053
4039
|
path: "/chat/$sessionKey",
|
|
4054
|
-
getParentRoute: () => Route$
|
|
4040
|
+
getParentRoute: () => Route$C
|
|
4055
4041
|
});
|
|
4056
|
-
const ApiTtsRoute = Route$
|
|
4042
|
+
const ApiTtsRoute = Route$r.update({
|
|
4057
4043
|
id: "/api/tts",
|
|
4058
4044
|
path: "/api/tts",
|
|
4059
|
-
getParentRoute: () => Route$
|
|
4045
|
+
getParentRoute: () => Route$C
|
|
4060
4046
|
});
|
|
4061
|
-
const ApiSttRoute = Route$
|
|
4047
|
+
const ApiSttRoute = Route$q.update({
|
|
4062
4048
|
id: "/api/stt",
|
|
4063
4049
|
path: "/api/stt",
|
|
4064
|
-
getParentRoute: () => Route$
|
|
4050
|
+
getParentRoute: () => Route$C
|
|
4065
4051
|
});
|
|
4066
|
-
const ApiStreamRoute = Route$
|
|
4052
|
+
const ApiStreamRoute = Route$p.update({
|
|
4067
4053
|
id: "/api/stream",
|
|
4068
4054
|
path: "/api/stream",
|
|
4069
|
-
getParentRoute: () => Route$
|
|
4055
|
+
getParentRoute: () => Route$C
|
|
4070
4056
|
});
|
|
4071
|
-
const ApiSkillsRoute = Route$
|
|
4057
|
+
const ApiSkillsRoute = Route$o.update({
|
|
4072
4058
|
id: "/api/skills",
|
|
4073
4059
|
path: "/api/skills",
|
|
4074
|
-
getParentRoute: () => Route$
|
|
4060
|
+
getParentRoute: () => Route$C
|
|
4075
4061
|
});
|
|
4076
|
-
const ApiSessionsRoute = Route$
|
|
4062
|
+
const ApiSessionsRoute = Route$n.update({
|
|
4077
4063
|
id: "/api/sessions",
|
|
4078
4064
|
path: "/api/sessions",
|
|
4079
|
-
getParentRoute: () => Route$
|
|
4065
|
+
getParentRoute: () => Route$C
|
|
4080
4066
|
});
|
|
4081
|
-
const ApiSendRoute = Route$
|
|
4067
|
+
const ApiSendRoute = Route$m.update({
|
|
4082
4068
|
id: "/api/send",
|
|
4083
4069
|
path: "/api/send",
|
|
4084
|
-
getParentRoute: () => Route$
|
|
4070
|
+
getParentRoute: () => Route$C
|
|
4085
4071
|
});
|
|
4086
|
-
const ApiPingRoute = Route$
|
|
4072
|
+
const ApiPingRoute = Route$l.update({
|
|
4087
4073
|
id: "/api/ping",
|
|
4088
4074
|
path: "/api/ping",
|
|
4089
|
-
getParentRoute: () => Route$
|
|
4075
|
+
getParentRoute: () => Route$C
|
|
4090
4076
|
});
|
|
4091
|
-
const ApiPersonasRoute = Route$
|
|
4077
|
+
const ApiPersonasRoute = Route$k.update({
|
|
4092
4078
|
id: "/api/personas",
|
|
4093
4079
|
path: "/api/personas",
|
|
4094
|
-
getParentRoute: () => Route$
|
|
4080
|
+
getParentRoute: () => Route$C
|
|
4095
4081
|
});
|
|
4096
|
-
const ApiPathsRoute = Route$
|
|
4082
|
+
const ApiPathsRoute = Route$j.update({
|
|
4097
4083
|
id: "/api/paths",
|
|
4098
4084
|
path: "/api/paths",
|
|
4099
|
-
getParentRoute: () => Route$
|
|
4085
|
+
getParentRoute: () => Route$C
|
|
4100
4086
|
});
|
|
4101
|
-
const ApiModelsRoute = Route$
|
|
4087
|
+
const ApiModelsRoute = Route$i.update({
|
|
4102
4088
|
id: "/api/models",
|
|
4103
4089
|
path: "/api/models",
|
|
4104
|
-
getParentRoute: () => Route$
|
|
4090
|
+
getParentRoute: () => Route$C
|
|
4091
|
+
});
|
|
4092
|
+
const ApiLlmModelsRoute = Route$h.update({
|
|
4093
|
+
id: "/api/llm-models",
|
|
4094
|
+
path: "/api/llm-models",
|
|
4095
|
+
getParentRoute: () => Route$C
|
|
4105
4096
|
});
|
|
4106
4097
|
const ApiLlmFeaturesRoute = Route$g.update({
|
|
4107
4098
|
id: "/api/llm-features",
|
|
4108
4099
|
path: "/api/llm-features",
|
|
4109
|
-
getParentRoute: () => Route$
|
|
4100
|
+
getParentRoute: () => Route$C
|
|
4110
4101
|
});
|
|
4111
4102
|
const ApiHistoryRoute = Route$f.update({
|
|
4112
4103
|
id: "/api/history",
|
|
4113
4104
|
path: "/api/history",
|
|
4114
|
-
getParentRoute: () => Route$
|
|
4105
|
+
getParentRoute: () => Route$C
|
|
4115
4106
|
});
|
|
4116
4107
|
const ApiFollowUpsRoute = Route$e.update({
|
|
4117
4108
|
id: "/api/follow-ups",
|
|
4118
4109
|
path: "/api/follow-ups",
|
|
4119
|
-
getParentRoute: () => Route$
|
|
4110
|
+
getParentRoute: () => Route$C
|
|
4120
4111
|
});
|
|
4121
4112
|
const ApiCronRoute = Route$d.update({
|
|
4122
4113
|
id: "/api/cron",
|
|
4123
4114
|
path: "/api/cron",
|
|
4124
|
-
getParentRoute: () => Route$
|
|
4115
|
+
getParentRoute: () => Route$C
|
|
4125
4116
|
});
|
|
4126
4117
|
const ApiAgentsRoute = Route$c.update({
|
|
4127
4118
|
id: "/api/agents",
|
|
4128
4119
|
path: "/api/agents",
|
|
4129
|
-
getParentRoute: () => Route$
|
|
4120
|
+
getParentRoute: () => Route$C
|
|
4130
4121
|
});
|
|
4131
4122
|
const ApiFilesUploadRoute = Route$b.update({
|
|
4132
4123
|
id: "/api/files/upload",
|
|
4133
4124
|
path: "/api/files/upload",
|
|
4134
|
-
getParentRoute: () => Route$
|
|
4125
|
+
getParentRoute: () => Route$C
|
|
4135
4126
|
});
|
|
4136
4127
|
const ApiFilesSaveRoute = Route$a.update({
|
|
4137
4128
|
id: "/api/files/save",
|
|
4138
4129
|
path: "/api/files/save",
|
|
4139
|
-
getParentRoute: () => Route$
|
|
4130
|
+
getParentRoute: () => Route$C
|
|
4140
4131
|
});
|
|
4141
4132
|
const ApiFilesRenameRoute = Route$9.update({
|
|
4142
4133
|
id: "/api/files/rename",
|
|
4143
4134
|
path: "/api/files/rename",
|
|
4144
|
-
getParentRoute: () => Route$
|
|
4135
|
+
getParentRoute: () => Route$C
|
|
4145
4136
|
});
|
|
4146
4137
|
const ApiFilesReadRoute = Route$8.update({
|
|
4147
4138
|
id: "/api/files/read",
|
|
4148
4139
|
path: "/api/files/read",
|
|
4149
|
-
getParentRoute: () => Route$
|
|
4140
|
+
getParentRoute: () => Route$C
|
|
4150
4141
|
});
|
|
4151
4142
|
const ApiFilesMkdirRoute = Route$7.update({
|
|
4152
4143
|
id: "/api/files/mkdir",
|
|
4153
4144
|
path: "/api/files/mkdir",
|
|
4154
|
-
getParentRoute: () => Route$
|
|
4145
|
+
getParentRoute: () => Route$C
|
|
4155
4146
|
});
|
|
4156
4147
|
const ApiFilesListRoute = Route$6.update({
|
|
4157
4148
|
id: "/api/files/list",
|
|
4158
4149
|
path: "/api/files/list",
|
|
4159
|
-
getParentRoute: () => Route$
|
|
4150
|
+
getParentRoute: () => Route$C
|
|
4160
4151
|
});
|
|
4161
4152
|
const ApiFilesInfoRoute = Route$5.update({
|
|
4162
4153
|
id: "/api/files/info",
|
|
4163
4154
|
path: "/api/files/info",
|
|
4164
|
-
getParentRoute: () => Route$
|
|
4155
|
+
getParentRoute: () => Route$C
|
|
4165
4156
|
});
|
|
4166
4157
|
const ApiFilesDownloadRoute = Route$4.update({
|
|
4167
4158
|
id: "/api/files/download",
|
|
4168
4159
|
path: "/api/files/download",
|
|
4169
|
-
getParentRoute: () => Route$
|
|
4160
|
+
getParentRoute: () => Route$C
|
|
4170
4161
|
});
|
|
4171
4162
|
const ApiFilesDeleteRoute = Route$3.update({
|
|
4172
4163
|
id: "/api/files/delete",
|
|
4173
4164
|
path: "/api/files/delete",
|
|
4174
|
-
getParentRoute: () => Route$
|
|
4165
|
+
getParentRoute: () => Route$C
|
|
4175
4166
|
});
|
|
4176
4167
|
const ApiDashboardSystemRoute = Route$2.update({
|
|
4177
4168
|
id: "/api/dashboard/system",
|
|
4178
4169
|
path: "/api/dashboard/system",
|
|
4179
|
-
getParentRoute: () => Route$
|
|
4170
|
+
getParentRoute: () => Route$C
|
|
4180
4171
|
});
|
|
4181
4172
|
const ApiDashboardGatewayRoute = Route$1.update({
|
|
4182
4173
|
id: "/api/dashboard/gateway",
|
|
4183
4174
|
path: "/api/dashboard/gateway",
|
|
4184
|
-
getParentRoute: () => Route$
|
|
4175
|
+
getParentRoute: () => Route$C
|
|
4185
4176
|
});
|
|
4186
4177
|
const ApiDashboardCronsRoute = Route.update({
|
|
4187
4178
|
id: "/api/dashboard/crons",
|
|
4188
4179
|
path: "/api/dashboard/crons",
|
|
4189
|
-
getParentRoute: () => Route$
|
|
4180
|
+
getParentRoute: () => Route$C
|
|
4190
4181
|
});
|
|
4191
4182
|
const rootRouteChildren = {
|
|
4192
4183
|
IndexRoute,
|
|
@@ -4203,6 +4194,7 @@ const rootRouteChildren = {
|
|
|
4203
4194
|
ApiFollowUpsRoute,
|
|
4204
4195
|
ApiHistoryRoute,
|
|
4205
4196
|
ApiLlmFeaturesRoute,
|
|
4197
|
+
ApiLlmModelsRoute,
|
|
4206
4198
|
ApiModelsRoute,
|
|
4207
4199
|
ApiPathsRoute,
|
|
4208
4200
|
ApiPersonasRoute,
|
|
@@ -4227,7 +4219,7 @@ const rootRouteChildren = {
|
|
|
4227
4219
|
ApiFilesSaveRoute,
|
|
4228
4220
|
ApiFilesUploadRoute
|
|
4229
4221
|
};
|
|
4230
|
-
const routeTree = Route$
|
|
4222
|
+
const routeTree = Route$C._addFileChildren(rootRouteChildren)._addFileTypes();
|
|
4231
4223
|
const getRouter = () => {
|
|
4232
4224
|
const router2 = createRouter({
|
|
4233
4225
|
routeTree,
|
|
@@ -4242,7 +4234,7 @@ const router = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
|
|
|
4242
4234
|
getRouter
|
|
4243
4235
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
4244
4236
|
export {
|
|
4245
|
-
Route$
|
|
4246
|
-
Route$
|
|
4237
|
+
Route$t as R,
|
|
4238
|
+
Route$s as a,
|
|
4247
4239
|
router as r
|
|
4248
4240
|
};
|