dsh-theone 0.3.9 → 0.3.12
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/README.md +77 -53
- package/README.zh.md +82 -58
- package/cordis.patch.yml +0 -0
- package/dist/catalog-types.d.ts +0 -0
- package/dist/catalog-types.js +0 -0
- package/dist/client-locales.d.ts +11 -0
- package/dist/client-locales.js +22 -0
- package/dist/client-navigation.d.ts +0 -0
- package/dist/client-navigation.js +0 -0
- package/dist/client.d.ts +0 -0
- package/dist/client.js +130 -10
- package/dist/gateway-compaction.d.ts +0 -0
- package/dist/gateway-compaction.js +0 -0
- package/dist/history-catalog.d.ts +0 -0
- package/dist/history-catalog.js +0 -0
- package/dist/index.d.ts +13 -3
- package/dist/index.js +102 -22
- package/dist/linkage.d.ts +0 -0
- package/dist/linkage.js +0 -0
- package/dist/llm-router.d.ts +0 -0
- package/dist/llm-router.js +0 -0
- package/dist/model-json.d.ts +0 -0
- package/dist/model-json.js +0 -0
- package/dist/router.d.ts +0 -0
- package/dist/router.js +0 -0
- package/dist/routing-policy.d.ts +0 -0
- package/dist/routing-policy.js +0 -0
- package/dist/run.d.ts +0 -0
- package/dist/run.js +0 -0
- package/dist/settings-types.d.ts +0 -0
- package/dist/settings-types.js +0 -0
- package/dist/settings.d.ts +0 -0
- package/dist/settings.js +0 -0
- package/dist/store.d.ts +0 -0
- package/dist/store.js +0 -0
- package/dist/types.d.ts +0 -0
- package/dist/types.js +0 -0
- package/dist/update.d.ts +55 -0
- package/dist/update.js +121 -0
- package/docs/plugin-v0.1.md +0 -0
- package/docs/plugin-v0.1.zh.md +0 -0
- package/docs/plugin-v0.2.md +0 -0
- package/docs/plugin-v0.2.zh.md +0 -0
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -88,6 +88,17 @@ var zh = {
|
|
|
88
88
|
"gateway.opening": "\u6B63\u5728\u6253\u5F00 TheOne \u4E3B\u804A\u5929\u2026",
|
|
89
89
|
"gateway.error": "\u4E3B\u804A\u5929\u6682\u65F6\u65E0\u6CD5\u6253\u5F00\uFF0C\u8BF7\u68C0\u67E5 DSH \u8FDE\u63A5\u548C TheOne \u63D2\u4EF6\u72B6\u6001\u3002",
|
|
90
90
|
"retry": "\u91CD\u8BD5",
|
|
91
|
+
"update.available": "\u66F4\u65B0",
|
|
92
|
+
"update.installing": "\u66F4\u65B0\u4E2D\u2026",
|
|
93
|
+
"update.restart": "\u91CD\u542F\u751F\u6548",
|
|
94
|
+
"update.reloading": "\u6B63\u5728\u91CD\u65B0\u52A0\u8F7D\u2026",
|
|
95
|
+
"update.reloadingHint": "TheOne \u6B63\u5728\u7528\u65B0\u7248\u672C\u91CD\u65B0\u52A0\u8F7D\uFF0CDSH \u4E0D\u9700\u8981\u91CD\u542F\uFF1B\u5B8C\u6210\u540E\u9875\u9762\u4F1A\u81EA\u52A8\u5237\u65B0\u3002",
|
|
96
|
+
"update.busy": "\u6709\u56DE\u590D\u6B63\u5728\u8FDB\u884C\uFF0C\u7B49\u5B83\u7ED3\u675F\u540E\u518D\u70B9\u66F4\u65B0\u3002",
|
|
97
|
+
"update.failed": "\u66F4\u65B0\u5931\u8D25",
|
|
98
|
+
"update.hint": "\u5F53\u524D {current}\uFF0C\u53EF\u66F4\u65B0\u5230 {latest}\u3002\u70B9\u51FB\u4E00\u952E\u66F4\u65B0\uFF0C\u4E0D\u7528\u91CD\u542F DSH\u3002",
|
|
99
|
+
"update.manualHint": "\u5F53\u524D {current}\uFF0C\u6700\u65B0 {latest}\u3002\u8FD9\u4EFD\u63D2\u4EF6\u4E0D\u662F\u4ECE GitHub \u6216 npm \u5B89\u88C5\u7684\uFF0C\u8BF7\u5728\u300C\u63D2\u4EF6\u300D\u9875\u9762\u91CD\u65B0\u5B89\u88C5\u3002",
|
|
100
|
+
"update.restartHint": "\u5DF2\u66F4\u65B0\uFF0C\u91CD\u542F DSH \u540E\u751F\u6548\u3002",
|
|
101
|
+
"update.failedHint": "\u66F4\u65B0\u6CA1\u6709\u5B8C\u6210\uFF08{error}\uFF09\u3002\u70B9\u51FB\u91CD\u8BD5\uFF0C\u6216\u5728\u300C\u63D2\u4EF6\u300D\u9875\u9762\u91CD\u65B0\u5B89\u88C5\u3002",
|
|
91
102
|
"settings.title": "TheOne \u8BBE\u7F6E",
|
|
92
103
|
"settings.menu": "\u8BBE\u7F6E",
|
|
93
104
|
"settings.subtitle": "\u67E5\u770B\u5F53\u524D\u751F\u6548\u7684\u914D\u7F6E\u548C\u5404\u9879\u7528\u9014\u3002",
|
|
@@ -257,6 +268,17 @@ var en = {
|
|
|
257
268
|
"gateway.opening": "Opening TheOne main chat\u2026",
|
|
258
269
|
"gateway.error": "Main chat could not open. Check your DSH connection and TheOne plugin status.",
|
|
259
270
|
"retry": "Retry",
|
|
271
|
+
"update.available": "Update",
|
|
272
|
+
"update.installing": "Updating\u2026",
|
|
273
|
+
"update.restart": "Restart to apply",
|
|
274
|
+
"update.reloading": "Reloading\u2026",
|
|
275
|
+
"update.reloadingHint": "TheOne is reloading with the new version; DSH keeps running, and the page refreshes when it is done.",
|
|
276
|
+
"update.busy": "A reply is in progress. Update once it finishes.",
|
|
277
|
+
"update.failed": "Update failed",
|
|
278
|
+
"update.hint": "You have {current}; {latest} is available. Click to update without restarting DSH.",
|
|
279
|
+
"update.manualHint": "You have {current}; {latest} is available. This copy was not installed from GitHub or npm, so reinstall it from the Plugins page.",
|
|
280
|
+
"update.restartHint": "Updated. Restart DSH to apply it.",
|
|
281
|
+
"update.failedHint": "The update did not finish ({error}). Click to retry, or reinstall from the Plugins page.",
|
|
260
282
|
"settings.title": "TheOne settings",
|
|
261
283
|
"settings.menu": "Settings",
|
|
262
284
|
"settings.subtitle": "Review active configuration and what each option does.",
|
|
@@ -497,9 +519,94 @@ function apply(ctx) {
|
|
|
497
519
|
});
|
|
498
520
|
});
|
|
499
521
|
});
|
|
522
|
+
let update;
|
|
523
|
+
const updateListeners = /* @__PURE__ */ new Set();
|
|
524
|
+
const setUpdate = (value) => {
|
|
525
|
+
update = value;
|
|
526
|
+
for (const listener of updateListeners) listener();
|
|
527
|
+
};
|
|
528
|
+
const subscribeUpdate = (listener) => {
|
|
529
|
+
updateListeners.add(listener);
|
|
530
|
+
return () => {
|
|
531
|
+
updateListeners.delete(listener);
|
|
532
|
+
};
|
|
533
|
+
};
|
|
534
|
+
const readUpdate = async (method = "GET") => {
|
|
535
|
+
try {
|
|
536
|
+
const response = await fetch("/api/theone/update", { method, signal: lifetime.signal, cache: "no-store" });
|
|
537
|
+
if (response.ok || response.status === 409) setUpdate(await response.json());
|
|
538
|
+
if (update?.state === "reloading") void awaitReload(update.current);
|
|
539
|
+
} catch {
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
async function awaitReload(previous) {
|
|
543
|
+
for (const started = Date.now(); Date.now() - started < 12e4; ) {
|
|
544
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
545
|
+
try {
|
|
546
|
+
const response = await fetch("/api/theone/update", { cache: "no-store" });
|
|
547
|
+
if (response.ok && (await response.json()).current !== previous) {
|
|
548
|
+
window.location.reload();
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
} catch {
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (update) setUpdate({ ...update, state: "restart" });
|
|
555
|
+
}
|
|
556
|
+
ctx.effect(() => {
|
|
557
|
+
void readUpdate();
|
|
558
|
+
const timer = setInterval(() => {
|
|
559
|
+
void readUpdate();
|
|
560
|
+
}, 6 * 36e5);
|
|
561
|
+
return () => clearInterval(timer);
|
|
562
|
+
});
|
|
563
|
+
function UpdateButton() {
|
|
564
|
+
const t2 = useText();
|
|
565
|
+
const status = (0, import_react.useSyncExternalStore)(subscribeUpdate, () => update);
|
|
566
|
+
if (!status || !status.available && !status.state) return null;
|
|
567
|
+
const label = status.state === "installing" ? t2("update.installing") : status.state === "reloading" ? t2("update.reloading") : status.state === "restart" ? t2("update.restart") : status.state === "failed" ? t2("update.failed") : t2("update.available");
|
|
568
|
+
const title = status.error === "GATEWAY_BUSY" ? t2("update.busy") : status.state === "reloading" ? t2("update.reloadingHint") : status.state === "restart" ? t2("update.restartHint") : status.state === "failed" ? t2("update.failedHint", { error: status.error ?? "" }) : status.installable ? t2("update.hint", { current: status.current, latest: status.latest ?? "" }) : t2("update.manualHint", { current: status.current, latest: status.latest ?? "" });
|
|
569
|
+
const act = (event) => {
|
|
570
|
+
event.preventDefault();
|
|
571
|
+
event.stopPropagation();
|
|
572
|
+
if (status.state === "installing" || status.state === "reloading" || status.state === "restart") return;
|
|
573
|
+
if (!status.installable) {
|
|
574
|
+
window.open("https://github.com/YunongDai2005/dsh-theone#readme", "_blank", "noopener");
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
setUpdate({ ...status, state: "installing" });
|
|
578
|
+
void readUpdate("POST");
|
|
579
|
+
};
|
|
580
|
+
return (0, import_react.createElement)(
|
|
581
|
+
"span",
|
|
582
|
+
{
|
|
583
|
+
className: "theone-update",
|
|
584
|
+
role: "button",
|
|
585
|
+
tabIndex: 0,
|
|
586
|
+
title,
|
|
587
|
+
"aria-label": title,
|
|
588
|
+
"data-state": status.state ?? "available",
|
|
589
|
+
onClick: act,
|
|
590
|
+
onPointerDown: (event) => event.stopPropagation(),
|
|
591
|
+
onKeyDown: (event) => {
|
|
592
|
+
if (event.key === "Enter" || event.key === " ") act(event);
|
|
593
|
+
}
|
|
594
|
+
},
|
|
595
|
+
status.state === "installing" || status.state === "reloading" ? (0, import_react.createElement)("span", { className: "theone-update-spin", "aria-hidden": true }) : null,
|
|
596
|
+
label,
|
|
597
|
+
!status.state && status.latest ? (0, import_react.createElement)("small", null, `v${status.latest}`) : null
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
const symbol = () => (0, import_react.createElement)(
|
|
601
|
+
"svg",
|
|
602
|
+
{ className: "theone-symbol", viewBox: "0 0 20 20", width: 20, height: 20, "aria-hidden": true, focusable: false },
|
|
603
|
+
(0, import_react.createElement)("circle", { cx: 10, cy: 10, r: 8.6, fill: "none", stroke: "currentColor", strokeWidth: 1.4 }),
|
|
604
|
+
(0, import_react.createElement)("circle", { cx: 10, cy: 10, r: 2.4, fill: "currentColor" })
|
|
605
|
+
);
|
|
500
606
|
function SidebarEntry({ size }) {
|
|
501
607
|
const t2 = useText();
|
|
502
608
|
const marker = (0, import_react.useRef)(null);
|
|
609
|
+
const pending = (0, import_react.useSyncExternalStore)(subscribeUpdate, () => !!update?.available && !update.state);
|
|
503
610
|
const id = (0, import_react.useSyncExternalStore)(navigation.subscribe, navigation.getSnapshot);
|
|
504
611
|
const sessions = (0, import_react.useSyncExternalStore)(ctx.sessions.list.subscribe, ctx.sessions.list.getSnapshot);
|
|
505
612
|
const panel = (0, import_react.useSyncExternalStore)(ctx.layout.panelInfo.subscribe, ctx.layout.panelInfo.getSnapshot);
|
|
@@ -606,7 +713,7 @@ function apply(ctx) {
|
|
|
606
713
|
return (0, import_react.createElement)(
|
|
607
714
|
"span",
|
|
608
715
|
{ ref: marker, className: "theone-nav", translate: "no", "data-wide": size === 16, "data-active": active },
|
|
609
|
-
(0, import_react.createElement)("span", { className: "theone-symbol" }),
|
|
716
|
+
(0, import_react.createElement)("span", { className: "theone-symbol-wrap" }, symbol(), size !== 16 && pending ? (0, import_react.createElement)("span", { className: "theone-update-dot", "aria-hidden": true }) : null),
|
|
610
717
|
size === 16 && (0, import_react.createElement)(
|
|
611
718
|
"span",
|
|
612
719
|
{ className: "theone-entry-copy" },
|
|
@@ -622,7 +729,8 @@ function apply(ctx) {
|
|
|
622
729
|
(0, import_react.createElement)("span", { className: "theone-entry-label" }, t2("gateway.label"))
|
|
623
730
|
),
|
|
624
731
|
(0, import_react.createElement)("span", { className: "theone-entry-sub" }, t2("gateway.subtitle"))
|
|
625
|
-
)
|
|
732
|
+
),
|
|
733
|
+
size === 16 && (0, import_react.createElement)(UpdateButton)
|
|
626
734
|
);
|
|
627
735
|
}
|
|
628
736
|
function GatewayPanel() {
|
|
@@ -1317,17 +1425,17 @@ function apply(ctx) {
|
|
|
1317
1425
|
ctx.effect(() => {
|
|
1318
1426
|
const style = document.createElement("style");
|
|
1319
1427
|
style.dataset.plugin = "dsh-theone-gateway-row";
|
|
1320
|
-
const
|
|
1428
|
+
const update2 = () => {
|
|
1321
1429
|
const id = navigation.getSnapshot();
|
|
1322
1430
|
style.textContent = id ? `[role="treeitem"][data-row-key="${CSS.escape(`session:${id}`)}"]{display:none!important}` : "";
|
|
1323
1431
|
};
|
|
1324
|
-
|
|
1325
|
-
const unsubscribe = navigation.subscribe(
|
|
1326
|
-
window.addEventListener("storage",
|
|
1432
|
+
update2();
|
|
1433
|
+
const unsubscribe = navigation.subscribe(update2);
|
|
1434
|
+
window.addEventListener("storage", update2);
|
|
1327
1435
|
document.head.append(style);
|
|
1328
1436
|
return () => {
|
|
1329
1437
|
unsubscribe();
|
|
1330
|
-
window.removeEventListener("storage",
|
|
1438
|
+
window.removeEventListener("storage", update2);
|
|
1331
1439
|
style.remove();
|
|
1332
1440
|
};
|
|
1333
1441
|
});
|
|
@@ -1355,9 +1463,21 @@ button:has(.theone-nav[data-wide=true])>span:not(:has(.theone-nav)){display:none
|
|
|
1355
1463
|
button:has(.theone-nav):hover{background:var(--one-tint);border-color:color-mix(in srgb,var(--one-accent) 40%,var(--one-line))}
|
|
1356
1464
|
button:has(.theone-nav[data-active=true]){border-color:color-mix(in srgb,var(--one-accent) 40%,var(--one-line))}
|
|
1357
1465
|
.theone-nav{display:flex;align-items:center;gap:10px;color:var(--one-accent);font-family:inherit;position:relative;z-index:1}
|
|
1358
|
-
.theone-symbol{
|
|
1359
|
-
.theone-symbol
|
|
1360
|
-
.theone-nav[data-wide=true] .theone-symbol{margin:0
|
|
1466
|
+
.theone-symbol-wrap{position:relative;display:inline-flex;flex:none;width:20px;height:20px}
|
|
1467
|
+
.theone-nav .theone-symbol{display:block;width:20px;height:20px;flex:none;overflow:visible;border:0;border-radius:0;background:none}
|
|
1468
|
+
.theone-nav[data-wide=true] .theone-symbol-wrap{margin:0 2px}
|
|
1469
|
+
.theone-update-dot{position:absolute;top:-2px;right:-2px;width:7px;height:7px;border-radius:50%;background:#e8590c;box-shadow:0 0 0 2px var(--one-tint)}
|
|
1470
|
+
button:has(.theone-nav[data-wide=true])>span:has(.theone-nav){flex:1;min-width:0}
|
|
1471
|
+
.theone-nav[data-wide=true]{width:100%}
|
|
1472
|
+
.theone-update{margin-left:auto;align-self:center;display:inline-flex;align-items:center;gap:5px;padding:5px 10px;border-radius:999px;border:1px solid color-mix(in srgb,var(--one-accent) 45%,transparent);background:color-mix(in srgb,var(--one-accent) 12%,transparent);color:var(--one-accent);font-size:12px;line-height:16px;white-space:nowrap;cursor:pointer;transition:background 150ms ease}
|
|
1473
|
+
.theone-update:hover{background:color-mix(in srgb,var(--one-accent) 22%,transparent)}
|
|
1474
|
+
.theone-update:focus-visible{outline:2px solid var(--one-accent);outline-offset:2px}
|
|
1475
|
+
.theone-update small{font-size:11px;opacity:.75}
|
|
1476
|
+
.theone-update[data-state=installing],.theone-update[data-state=reloading],.theone-update[data-state=restart]{cursor:default}
|
|
1477
|
+
.theone-update[data-state=failed]{color:#d9480f;border-color:#d9480f66;background:#d9480f14}
|
|
1478
|
+
.theone-update-spin{width:10px;height:10px;border-radius:50%;border:1.5px solid currentColor;border-right-color:transparent;animation:theone-spin 800ms linear infinite}
|
|
1479
|
+
@keyframes theone-spin{to{transform:rotate(360deg)}}
|
|
1480
|
+
@media(prefers-reduced-motion:reduce){.theone-update-spin{animation:none}}
|
|
1361
1481
|
.theone-entry-copy{display:flex;flex-direction:column;align-items:flex-start;gap:2px}
|
|
1362
1482
|
.theone-entry-title{display:flex;align-items:center;gap:9px;line-height:22px}
|
|
1363
1483
|
.theone-wordmark{display:inline-flex;align-items:baseline;gap:1px;white-space:nowrap}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/dist/history-catalog.js
CHANGED
|
File without changes
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Context, Service } from '@deepseek-ai/cordis';
|
|
2
2
|
import z from '@deepseek-ai/schemastery';
|
|
3
|
-
import type { GenerateOptions, LlmResolvedModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
3
|
+
import type { GenerateOptions, LlmModelInfo, LlmResolvedModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
4
|
+
import type { ModelSelection } from '@deepseek-ai/dsh-agent';
|
|
4
5
|
import type { SessionEventWindow } from '@deepseek-ai/dsh-session-query';
|
|
5
6
|
import { HistoryCatalog } from './history-catalog.ts';
|
|
6
7
|
import { ContextStore } from './store.ts';
|
|
@@ -8,6 +9,7 @@ import type { RouterReceipt } from './llm-router.ts';
|
|
|
8
9
|
import type { LinkageSnapshot } from './catalog-types.ts';
|
|
9
10
|
import { type SettingsSnapshot } from './settings-types.ts';
|
|
10
11
|
import { type LinkScope } from './linkage.ts';
|
|
12
|
+
import { Updater } from './update.ts';
|
|
11
13
|
export interface Config {
|
|
12
14
|
databasePath?: string;
|
|
13
15
|
contextsPath?: string;
|
|
@@ -54,6 +56,8 @@ declare module '@deepseek-ai/dsh-llm' {
|
|
|
54
56
|
};
|
|
55
57
|
}
|
|
56
58
|
}
|
|
59
|
+
export declare function viaModel(selection: ModelSelection): string;
|
|
60
|
+
export declare function parseVia(id: string | undefined): ModelSelection | undefined;
|
|
57
61
|
export default class TheOne extends Service {
|
|
58
62
|
private config;
|
|
59
63
|
static inject: string[];
|
|
@@ -72,6 +76,8 @@ export default class TheOne extends Service {
|
|
|
72
76
|
private readonly closing;
|
|
73
77
|
/** A model the user picked in main chat's own model selector; it answers through the Workers. */
|
|
74
78
|
private pickedModel?;
|
|
79
|
+
/** Update checks and one-click install through DSH's plugin manager. */
|
|
80
|
+
readonly updater: Updater;
|
|
75
81
|
/** Sessions whose current step answers through TheOne; only these refuse to run tools themselves. */
|
|
76
82
|
private readonly throughTheOne;
|
|
77
83
|
constructor(ctx: Context, config: Config);
|
|
@@ -89,8 +95,12 @@ export default class TheOne extends Service {
|
|
|
89
95
|
/** Capture before Web saves the gateway itself as DSH's new default. */
|
|
90
96
|
captureDefaultModel(): void;
|
|
91
97
|
private backingModel;
|
|
92
|
-
/**
|
|
93
|
-
|
|
98
|
+
/** Every model DSH offers besides TheOne; a provider that cannot list its models in time offers none. */
|
|
99
|
+
private offeredModels;
|
|
100
|
+
/** Main chat's model menu: plain TheOne (follow DSH), then "TheOne · <model>" for every model DSH offers. */
|
|
101
|
+
gatewayModels(provider: string): Promise<LlmModelInfo[]>;
|
|
102
|
+
/** The entry has exactly the backing model's capacity, including DSH overrides. */
|
|
103
|
+
gatewayModelInfo(provider?: string, signal?: AbortSignal, model?: string): Promise<LlmResolvedModelInfo>;
|
|
94
104
|
/** This main chat's recent text turns, each attributed to the topic it was routed to. */
|
|
95
105
|
private recentMainChat;
|
|
96
106
|
/** Recover bounded routing context from DSH references after the Gateway is rebuilt. */
|
package/dist/index.js
CHANGED
|
@@ -19,10 +19,26 @@ import { EDITABLE_SETTINGS_KEYS, RESTART_SETTINGS_KEYS } from "./settings-types.
|
|
|
19
19
|
import { validateSettings } from "./settings.js";
|
|
20
20
|
import { RESTART_CODE, WorkerRun } from "./run.js";
|
|
21
21
|
import { buildBriefing, LINK_SIGNAL, relatedTopics } from "./linkage.js";
|
|
22
|
+
import { PACKAGE_NAME, Updater } from "./update.js";
|
|
22
23
|
function redactDescriptor(text) {
|
|
23
24
|
return text.replace(/\bsk-[a-zA-Z0-9_-]{16,}|\bBearer\s+\S+/gi, '[REDACTED]')
|
|
24
25
|
.replace(/((?:api[_ -]?key|password|密码|密钥)\s*[:=:]\s*)\S+/gi, '$1[REDACTED]');
|
|
25
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Main chat's model entries besides plain "TheOne": "TheOne · <model>" still answers through TheOne,
|
|
29
|
+
* with that model doing the routing and the background work.
|
|
30
|
+
*/
|
|
31
|
+
const VIA = 'via:';
|
|
32
|
+
export function viaModel(selection) { return `${VIA}${selection.provider}/${selection.model}`; }
|
|
33
|
+
export function parseVia(id) {
|
|
34
|
+
if (!id?.startsWith(VIA))
|
|
35
|
+
return undefined;
|
|
36
|
+
const rest = id.slice(VIA.length);
|
|
37
|
+
const slash = rest.indexOf('/');
|
|
38
|
+
if (slash <= 0 || slash === rest.length - 1 || rest.slice(0, slash) === 'theone')
|
|
39
|
+
return undefined;
|
|
40
|
+
return { provider: rest.slice(0, slash), model: rest.slice(slash + 1) };
|
|
41
|
+
}
|
|
26
42
|
/** Gateway provider delegates each accepted input to its context's DSH worker. */
|
|
27
43
|
class GatewayAdapter extends LlmAdapter {
|
|
28
44
|
service;
|
|
@@ -31,15 +47,11 @@ class GatewayAdapter extends LlmAdapter {
|
|
|
31
47
|
this.service = service;
|
|
32
48
|
}
|
|
33
49
|
providerInfo(provider) { return { id: provider, name: 'TheOne' }; }
|
|
34
|
-
|
|
35
|
-
// The entry accepts whatever the backing model accepts (e.g. images).
|
|
36
|
-
const info = await this.service.gatewayModelInfo(provider).catch(() => undefined);
|
|
37
|
-
return [{ provider, id: 'gateway', name: 'TheOne', inputModalities: [...(info?.inputModalities ?? ['text'])] }];
|
|
38
|
-
}
|
|
50
|
+
listModels(provider) { return this.service.gatewayModels(provider); }
|
|
39
51
|
resolveModel(provider, model, signal) {
|
|
40
|
-
if (model !== 'gateway')
|
|
41
|
-
throw new Error('TheOne only exposes
|
|
42
|
-
return this.service.gatewayModelInfo(provider, signal);
|
|
52
|
+
if (model !== 'gateway' && !parseVia(model))
|
|
53
|
+
throw new Error('TheOne only exposes its gateway models');
|
|
54
|
+
return this.service.gatewayModelInfo(provider, signal, model);
|
|
43
55
|
}
|
|
44
56
|
providerRetryPolicy() { return resolveRetryPolicy({ mode: 'normal', maxRetries: 0 }, 'theone.retry'); }
|
|
45
57
|
stream(options) { return this.service.answer(options); }
|
|
@@ -85,6 +97,28 @@ function reasonLabel(reason) {
|
|
|
85
97
|
function settingsConfig(values) {
|
|
86
98
|
return { ...values, workerProvider: values.workerProvider ?? undefined, workerModel: values.workerModel ?? undefined, contextsPath: values.contextsPath ?? undefined };
|
|
87
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Switch an installed bundle off and on so DSH loads its new version. This disposes the running
|
|
102
|
+
* TheOne, so it runs after the current request has answered and outlives this instance.
|
|
103
|
+
*/
|
|
104
|
+
function reloadBundle(manager, bundle) {
|
|
105
|
+
setTimeout(() => {
|
|
106
|
+
void (async () => {
|
|
107
|
+
const off = await manager.setBundleEnabled(bundle, false);
|
|
108
|
+
if (off.application === 'failed' || off.application === 'cancelled')
|
|
109
|
+
return;
|
|
110
|
+
await manager.setBundleEnabled(bundle, true);
|
|
111
|
+
})().catch(error => console.warn('TheOne could not reload itself after updating; restart DSH to apply the update.', error));
|
|
112
|
+
}, 300);
|
|
113
|
+
}
|
|
114
|
+
function readOwnVersion() {
|
|
115
|
+
try {
|
|
116
|
+
return String(JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version ?? '0.0.0');
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return '0.0.0';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
88
122
|
function readDescriptors(path) {
|
|
89
123
|
const value = JSON.parse(readFileSync(path, 'utf8'));
|
|
90
124
|
if (!Array.isArray(value))
|
|
@@ -132,6 +166,8 @@ export default class TheOne extends Service {
|
|
|
132
166
|
closing = new Map();
|
|
133
167
|
/** A model the user picked in main chat's own model selector; it answers through the Workers. */
|
|
134
168
|
pickedModel;
|
|
169
|
+
/** Update checks and one-click install through DSH's plugin manager. */
|
|
170
|
+
updater;
|
|
135
171
|
/** Sessions whose current step answers through TheOne; only these refuse to run tools themselves. */
|
|
136
172
|
throughTheOne = new WeakMap();
|
|
137
173
|
constructor(ctx, config) {
|
|
@@ -164,6 +200,18 @@ export default class TheOne extends Service {
|
|
|
164
200
|
}
|
|
165
201
|
descriptors ??= config.contextsPath ? readDescriptors(config.contextsPath) : [];
|
|
166
202
|
this.gatewayDirectory = resolve(dirname(databasePath), 'gateway');
|
|
203
|
+
this.updater = new Updater(readOwnVersion(), () => {
|
|
204
|
+
// The profile's package.json records how this plugin was installed (GitHub, npm or a local path).
|
|
205
|
+
const dir = ctx.profileContext?.dir;
|
|
206
|
+
if (!dir)
|
|
207
|
+
return undefined;
|
|
208
|
+
try {
|
|
209
|
+
return JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')).dependencies?.[PACKAGE_NAME];
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
});
|
|
167
215
|
ctx.effect(() => async () => {
|
|
168
216
|
try {
|
|
169
217
|
await Promise.all([...this.workers.values()].map(handle => handle.dispose()));
|
|
@@ -264,8 +312,8 @@ export default class TheOne extends Service {
|
|
|
264
312
|
return config;
|
|
265
313
|
}
|
|
266
314
|
this.throughTheOne.set(payload.agent, true);
|
|
267
|
-
// The resolved selection is authoritative; TheOne
|
|
268
|
-
this.pickedModel = config.provider === 'theone' ?
|
|
315
|
+
// The resolved selection is authoritative; plain TheOne means "follow DSH's selected model".
|
|
316
|
+
this.pickedModel = config.provider === 'theone' ? parseVia(config.model) : { provider: config.provider, model: config.model };
|
|
269
317
|
return config.provider === 'theone' ? config : { ...config, provider: 'theone', model: 'gateway' };
|
|
270
318
|
}, { prepend: true });
|
|
271
319
|
ctx.on('agent/pre-step', async ({ agent, signal }, next) => {
|
|
@@ -278,7 +326,7 @@ export default class TheOne extends Service {
|
|
|
278
326
|
// Know main chat's model choice before routing, so this very message is classified with it.
|
|
279
327
|
const pickedNow = selected?.signal === signal ? selected.model : undefined;
|
|
280
328
|
if (provider === 'theone')
|
|
281
|
-
this.pickedModel =
|
|
329
|
+
this.pickedModel = parseVia(pickedNow ?? agent.options.model);
|
|
282
330
|
else if (provider && pickedNow)
|
|
283
331
|
this.pickedModel = { provider, model: pickedNow };
|
|
284
332
|
const users = decision.messages.filter(message => message.source.kind === 'user');
|
|
@@ -566,6 +614,18 @@ export default class TheOne extends Service {
|
|
|
566
614
|
return Response.json({ error: code }, { status: code === 'GATEWAY_BUSY' ? 409 : 400 });
|
|
567
615
|
}
|
|
568
616
|
} }));
|
|
617
|
+
// Is a newer TheOne out, and install it with DSH's plugin manager (it loads after a restart).
|
|
618
|
+
child.effect(() => connection.fetch.register({ path: '/api/theone/update', methods: ['GET', 'POST'], requestBody: 'buffered', fetch: async (request) => {
|
|
619
|
+
if (request.method === 'POST' && (this.active || this.reservedGateway))
|
|
620
|
+
return Response.json({ ...await this.updater.status(), error: 'GATEWAY_BUSY' }, { status: 409 });
|
|
621
|
+
const manager = this.ctx.get('pluginManager');
|
|
622
|
+
// With DSH's hot reload, switching the bundle off and on loads the new version without a DSH restart.
|
|
623
|
+
const live = !!this.ctx.get('hmr') && typeof manager?.setBundleEnabled === 'function';
|
|
624
|
+
const status = request.method === 'POST'
|
|
625
|
+
? await this.updater.install(manager, live ? bundle => reloadBundle(manager, bundle) : undefined)
|
|
626
|
+
: await this.updater.status(new URL(request.url).searchParams.has('force'));
|
|
627
|
+
return Response.json(status, { headers: { 'cache-control': 'no-store' } });
|
|
628
|
+
} }));
|
|
569
629
|
child.effect(() => connection.fetch.register({ path: '/api/theone/sessions', methods: ['GET'], requestBody: 'buffered', fetch: async () => Response.json({ sessions: await this.attachableSessions() }, { headers: { 'cache-control': 'no-store' } }) }));
|
|
570
630
|
// The user's corrections to topic linking always take precedence over what was learned.
|
|
571
631
|
child.effect(() => connection.fetch.register({ path: '/api/theone/links', methods: ['POST'], requestBody: 'buffered', fetch: async (request) => {
|
|
@@ -701,7 +761,7 @@ export default class TheOne extends Service {
|
|
|
701
761
|
const defaultSelection = this.ctx.agentDefaultModel.currentSelection();
|
|
702
762
|
const selection = c.workerProvider && c.workerModel
|
|
703
763
|
? { provider: c.workerProvider, model: c.workerModel }
|
|
704
|
-
: defaultSelection.provider !== 'theone' ? defaultSelection : this.store.rememberedModel(c.gatewayKey);
|
|
764
|
+
: defaultSelection.provider !== 'theone' ? defaultSelection : parseVia(defaultSelection.model) ?? this.store.rememberedModel(c.gatewayKey);
|
|
705
765
|
let model = selection ? { provider: selection.provider, model: selection.model } : null;
|
|
706
766
|
let modelUnavailable = !model;
|
|
707
767
|
if (selection) {
|
|
@@ -729,10 +789,7 @@ export default class TheOne extends Service {
|
|
|
729
789
|
savedValues = validateSettings(saved.values, { contextsPath: c.contextsPath ?? null });
|
|
730
790
|
}
|
|
731
791
|
catch { /* Keep the current usable form. */ }
|
|
732
|
-
|
|
733
|
-
const listed = await Promise.all(this.ctx.llm.listProviders().filter(provider => provider.id !== 'theone').map(provider => Promise.race([this.ctx.llm.listModels(provider.id), new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 5000).unref())])
|
|
734
|
-
.then(models => models.map(model => ({ provider: provider.id, id: model.id, name: model.name })), () => [])));
|
|
735
|
-
const models = listed.flat();
|
|
792
|
+
const models = (await this.offeredModels()).map(model => ({ provider: model.provider, id: model.id, name: model.name }));
|
|
736
793
|
return { values, model, models, modelUnavailable, savedValues, revision: saved?.revision ?? 0,
|
|
737
794
|
restartRequired: RESTART_SETTINGS_KEYS.some(key => savedValues[key] !== activeEditable[key]) };
|
|
738
795
|
}
|
|
@@ -749,8 +806,9 @@ export default class TheOne extends Service {
|
|
|
749
806
|
}
|
|
750
807
|
/** Capture before Web saves the gateway itself as DSH's new default. */
|
|
751
808
|
captureDefaultModel() {
|
|
752
|
-
const
|
|
753
|
-
|
|
809
|
+
const current = this.ctx.agentDefaultModel.currentSelection();
|
|
810
|
+
const selection = current.provider === 'theone' ? parseVia(current.model) : current;
|
|
811
|
+
if (selection?.provider && selection.model)
|
|
754
812
|
this.store.rememberModel(this.config.gatewayKey, selection);
|
|
755
813
|
}
|
|
756
814
|
backingModel() {
|
|
@@ -764,12 +822,32 @@ export default class TheOne extends Service {
|
|
|
764
822
|
throw new RouterFailure('ROUTER_MODEL_MISSING');
|
|
765
823
|
return selection;
|
|
766
824
|
}
|
|
767
|
-
/**
|
|
768
|
-
async
|
|
769
|
-
const
|
|
825
|
+
/** Every model DSH offers besides TheOne; a provider that cannot list its models in time offers none. */
|
|
826
|
+
async offeredModels() {
|
|
827
|
+
const listed = await Promise.all(this.ctx.llm.listProviders().filter(provider => provider.id !== 'theone').map(provider => Promise.race([this.ctx.llm.listModels(provider.id), new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 5000).unref())])
|
|
828
|
+
.catch(() => [])));
|
|
829
|
+
return listed.flat();
|
|
830
|
+
}
|
|
831
|
+
/** Main chat's model menu: plain TheOne (follow DSH), then "TheOne · <model>" for every model DSH offers. */
|
|
832
|
+
async gatewayModels(provider) {
|
|
833
|
+
// The entry accepts whatever the backing model accepts (e.g. images).
|
|
834
|
+
const info = await this.gatewayModelInfo(provider).catch(() => undefined);
|
|
835
|
+
const models = [{ provider, id: 'gateway', name: 'TheOne', inputModalities: [...(info?.inputModalities ?? ['text'])] }];
|
|
836
|
+
// A model pinned in TheOne's settings decides for the Workers, so choices here would do nothing.
|
|
837
|
+
if (this.config.workerProvider && this.config.workerModel)
|
|
838
|
+
return models;
|
|
839
|
+
for (const model of await this.offeredModels())
|
|
840
|
+
models.push({ provider, id: viaModel({ provider: model.provider, model: model.id }), name: `TheOne · ${model.name}`,
|
|
841
|
+
...(model.description ? { description: model.description } : {}), ...(model.inputModalities ? { inputModalities: [...model.inputModalities] } : {}) });
|
|
842
|
+
return models;
|
|
843
|
+
}
|
|
844
|
+
/** The entry has exactly the backing model's capacity, including DSH overrides. */
|
|
845
|
+
async gatewayModelInfo(provider = 'theone', signal, model = 'gateway') {
|
|
846
|
+
const chosen = parseVia(model);
|
|
847
|
+
const info = { provider, id: model, name: 'TheOne Gateway', inputModalities: ['text'] };
|
|
770
848
|
let selection;
|
|
771
849
|
try {
|
|
772
|
-
selection = this.backingModel();
|
|
850
|
+
selection = chosen ?? this.backingModel();
|
|
773
851
|
}
|
|
774
852
|
catch (error) {
|
|
775
853
|
if (error instanceof RouterFailure && error.code === 'ROUTER_MODEL_MISSING')
|
|
@@ -777,6 +855,8 @@ export default class TheOne extends Service {
|
|
|
777
855
|
throw error;
|
|
778
856
|
}
|
|
779
857
|
const backing = await this.ctx.llm.resolveModelInfo(selection.provider, selection.model, signal);
|
|
858
|
+
if (chosen)
|
|
859
|
+
info.name = `TheOne · ${backing.name}`;
|
|
780
860
|
// Image input and the thinking-effort choices are the backing model's, so main chat offers the same controls.
|
|
781
861
|
return { ...info, ...(backing.inputModalities ? { inputModalities: [...backing.inputModalities] } : {}),
|
|
782
862
|
...(backing.reasoning ? { reasoning: backing.reasoning } : {}),
|
package/dist/linkage.d.ts
CHANGED
|
File without changes
|
package/dist/linkage.js
CHANGED
|
File without changes
|
package/dist/llm-router.d.ts
CHANGED
|
File without changes
|
package/dist/llm-router.js
CHANGED
|
File without changes
|
package/dist/model-json.d.ts
CHANGED
|
File without changes
|
package/dist/model-json.js
CHANGED
|
File without changes
|
package/dist/router.d.ts
CHANGED
|
File without changes
|
package/dist/router.js
CHANGED
|
File without changes
|
package/dist/routing-policy.d.ts
CHANGED
|
File without changes
|
package/dist/routing-policy.js
CHANGED
|
File without changes
|
package/dist/run.d.ts
CHANGED
|
File without changes
|
package/dist/run.js
CHANGED
|
File without changes
|
package/dist/settings-types.d.ts
CHANGED
|
File without changes
|
package/dist/settings-types.js
CHANGED
|
File without changes
|
package/dist/settings.d.ts
CHANGED
|
File without changes
|
package/dist/settings.js
CHANGED
|
File without changes
|
package/dist/store.d.ts
CHANGED
|
File without changes
|
package/dist/store.js
CHANGED
|
File without changes
|
package/dist/types.d.ts
CHANGED
|
File without changes
|
package/dist/types.js
CHANGED
|
File without changes
|
package/dist/update.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export declare const REPOSITORY = "YunongDai2005/dsh-theone";
|
|
2
|
+
export declare const PACKAGE_NAME = "dsh-theone";
|
|
3
|
+
/** Where this copy was installed from, which decides where its updates come from. */
|
|
4
|
+
export type UpdateSource = 'github' | 'npm' | 'other';
|
|
5
|
+
export interface UpdateStatus {
|
|
6
|
+
current: string;
|
|
7
|
+
latest?: string;
|
|
8
|
+
/** A newer version exists. */
|
|
9
|
+
available: boolean;
|
|
10
|
+
/** It can be installed from here (otherwise reinstall by hand). */
|
|
11
|
+
installable: boolean;
|
|
12
|
+
source: UpdateSource;
|
|
13
|
+
/** installing → reloading (TheOne restarts itself; DSH keeps running), restart (applies after DSH restarts) or failed. */
|
|
14
|
+
state?: 'installing' | 'reloading' | 'restart' | 'failed';
|
|
15
|
+
error?: string;
|
|
16
|
+
}
|
|
17
|
+
/** The DSH plugin manager's install call, as TheOne uses it. */
|
|
18
|
+
export interface PluginInstaller {
|
|
19
|
+
installBundle(spec: string, options?: {
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
}): Promise<{
|
|
22
|
+
application: string;
|
|
23
|
+
bundle?: string;
|
|
24
|
+
error?: {
|
|
25
|
+
code?: string;
|
|
26
|
+
message?: string;
|
|
27
|
+
} | unknown;
|
|
28
|
+
}>;
|
|
29
|
+
}
|
|
30
|
+
/** Compare dotted versions numerically; a pre-release sorts before its release. */
|
|
31
|
+
export declare function compareVersions(a: string, b: string): number;
|
|
32
|
+
/** Read the dependency spec DSH saved for this plugin: GitHub, npm, or something else (a local path, a tarball). */
|
|
33
|
+
export declare function installSource(spec: string | undefined): UpdateSource;
|
|
34
|
+
/** Checks for a newer TheOne and installs it through DSH's own plugin manager. */
|
|
35
|
+
export declare class Updater {
|
|
36
|
+
readonly current: string;
|
|
37
|
+
private readonly spec;
|
|
38
|
+
private readonly fetcher;
|
|
39
|
+
private readonly now;
|
|
40
|
+
private checked?;
|
|
41
|
+
private state?;
|
|
42
|
+
private error?;
|
|
43
|
+
private pending?;
|
|
44
|
+
constructor(current: string, spec: () => string | undefined, fetcher?: typeof fetch, now?: () => number);
|
|
45
|
+
get source(): UpdateSource;
|
|
46
|
+
/** The latest known status; checks again at most every six hours (or now, when forced). */
|
|
47
|
+
status(force?: boolean): Promise<UpdateStatus>;
|
|
48
|
+
/**
|
|
49
|
+
* Install the newer version. With `reload`, TheOne then restarts itself so the new version runs
|
|
50
|
+
* without restarting DSH; otherwise it is loaded the next time DSH starts.
|
|
51
|
+
*/
|
|
52
|
+
install(installer: PluginInstaller | undefined, reload?: (bundle: string) => void): Promise<UpdateStatus>;
|
|
53
|
+
private snapshot;
|
|
54
|
+
private check;
|
|
55
|
+
}
|