@klarkxy/dsh-model-hub 0.1.1 → 0.1.3
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 +7 -3
- package/docs/README.zh-CN.md +7 -3
- package/lib/client.inner.cjs +238 -53
- package/lib/client.inner.cjs.map +1 -1
- package/lib/client.js +238 -53
- package/lib/index.js +143 -5
- package/lib/index.js.map +1 -1
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +5 -5
package/lib/client.js
CHANGED
|
@@ -67,12 +67,68 @@ function normalizeModelRoute(value) {
|
|
|
67
67
|
}
|
|
68
68
|
//#endregion
|
|
69
69
|
//#region ../dsh-model-route/src/catalog.ts
|
|
70
|
+
/** Both services are required: Cordis checks the nested namespace separately. */
|
|
71
|
+
const MODEL_CATALOG_INJECT = ["remote", "remote.session"];
|
|
70
72
|
function asRecord(value) {
|
|
71
73
|
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
72
74
|
}
|
|
73
75
|
function asText(value) {
|
|
74
76
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
75
77
|
}
|
|
78
|
+
/** A useful diagnostic even when a transport rejects with a plain object. */
|
|
79
|
+
function modelCatalogErrorMessage(value) {
|
|
80
|
+
if (value instanceof Error && value.message) return value.message;
|
|
81
|
+
return asText(asRecord(value)?.message) ?? asText(value) ?? "Unable to load the model catalog.";
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Validate and unwrap a successful catalog without discarding provider failures
|
|
85
|
+
* or newer host metadata. Names and metadata remain optional for older hosts.
|
|
86
|
+
* A broken response is never presented as a successfully loaded empty menu.
|
|
87
|
+
*/
|
|
88
|
+
function checkedModelCatalog(value) {
|
|
89
|
+
const envelope = asRecord(value);
|
|
90
|
+
if (envelope?.ok === false) throw new Error(modelCatalogErrorMessage(envelope.error ?? envelope));
|
|
91
|
+
const root = envelope?.ok === true ? asRecord(envelope.value) : envelope;
|
|
92
|
+
const malformed = (field) => {
|
|
93
|
+
throw new Error(`Invalid model catalog: ${field}.`);
|
|
94
|
+
};
|
|
95
|
+
if (!root || !Array.isArray(root.groups)) return malformed("groups must be an array");
|
|
96
|
+
for (const value of root.groups) {
|
|
97
|
+
const group = asRecord(value);
|
|
98
|
+
if (!group || !asText(group.id) || !Array.isArray(group.models)) return malformed("each group needs an id and models array");
|
|
99
|
+
for (const value of group.models) {
|
|
100
|
+
const model = asRecord(value);
|
|
101
|
+
if (!model || !asText(model.id)) return malformed("each model needs an id");
|
|
102
|
+
if (model.reasoning !== void 0) {
|
|
103
|
+
const reasoning = asRecord(model.reasoning);
|
|
104
|
+
if (!reasoning || !Array.isArray(reasoning.efforts) || reasoning.efforts.some((value) => !parseEffort(value))) return malformed("reasoning efforts must be an array of identified efforts");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (root.failures !== void 0 && (!Array.isArray(root.failures) || root.failures.some((value) => {
|
|
109
|
+
const failure = asRecord(value);
|
|
110
|
+
return !failure || !asText(failure.id) || typeof failure.name !== "string" || typeof failure.message !== "string";
|
|
111
|
+
}))) return malformed("failures must be an array of provider failures");
|
|
112
|
+
return root;
|
|
113
|
+
}
|
|
114
|
+
/** Read through the injected session namespace; absence and rejection are errors. */
|
|
115
|
+
async function readModelCatalog(client) {
|
|
116
|
+
const session = client.remote?.session;
|
|
117
|
+
if (typeof session?.modelCatalog !== "function") throw new Error("Model catalog unavailable: inject both remote and remote.session.");
|
|
118
|
+
return checkedModelCatalog(await session.modelCatalog());
|
|
119
|
+
}
|
|
120
|
+
/** Provider failures are partial results, not a transport failure. */
|
|
121
|
+
function modelCatalogFailures(value) {
|
|
122
|
+
try {
|
|
123
|
+
return checkedModelCatalog(value).failures ?? [];
|
|
124
|
+
} catch {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/** Text adapter for existing model-hub status messages. */
|
|
129
|
+
function catalogFailures(value) {
|
|
130
|
+
return modelCatalogFailures(value).map((failure) => `${failure.name || failure.id}: ${failure.message}`);
|
|
131
|
+
}
|
|
76
132
|
function parseEffort(value) {
|
|
77
133
|
const row = asRecord(value);
|
|
78
134
|
const id = row ? asText(row.id) : void 0;
|
|
@@ -169,6 +225,59 @@ function groupModelMenuChoices(choices, query) {
|
|
|
169
225
|
return groups;
|
|
170
226
|
}
|
|
171
227
|
//#endregion
|
|
228
|
+
//#region ../dsh-model-route/src/catalog-ui.tsx
|
|
229
|
+
/** Also accepts a mapped state from a caller that owns broader async orchestration. */
|
|
230
|
+
function ModelCatalogStatus({ state, locale }) {
|
|
231
|
+
const zh = locale?.toLowerCase().startsWith("zh") === true;
|
|
232
|
+
const copy = zh ? {
|
|
233
|
+
loading: "正在加载模型…",
|
|
234
|
+
error: "模型列表加载失败",
|
|
235
|
+
partial: "部分提供商的模型未能加载",
|
|
236
|
+
retry: "重试"
|
|
237
|
+
} : {
|
|
238
|
+
loading: "Loading models…",
|
|
239
|
+
error: "Unable to load models",
|
|
240
|
+
partial: "Some providers could not load their models",
|
|
241
|
+
retry: "Retry"
|
|
242
|
+
};
|
|
243
|
+
if (!state.loading && !state.error && state.failures.length === 0) return null;
|
|
244
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
245
|
+
className: "dsh-model-catalog-status",
|
|
246
|
+
lang: zh ? "zh" : "en",
|
|
247
|
+
children: [
|
|
248
|
+
state.loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
249
|
+
role: "status",
|
|
250
|
+
children: copy.loading
|
|
251
|
+
}) : null,
|
|
252
|
+
state.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
253
|
+
role: "alert",
|
|
254
|
+
children: [
|
|
255
|
+
copy.error,
|
|
256
|
+
": ",
|
|
257
|
+
state.error
|
|
258
|
+
]
|
|
259
|
+
}) : null,
|
|
260
|
+
state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
261
|
+
role: "status",
|
|
262
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: copy.partial }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { children: state.failures.map((failure, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
263
|
+
failure.name || failure.id,
|
|
264
|
+
": ",
|
|
265
|
+
failure.message
|
|
266
|
+
] }, `${failure.id}:${index}`)) })]
|
|
267
|
+
}) : null,
|
|
268
|
+
state.error || state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
269
|
+
type: "button",
|
|
270
|
+
size: "sm",
|
|
271
|
+
disabled: state.loading,
|
|
272
|
+
onClick: () => {
|
|
273
|
+
state.refresh();
|
|
274
|
+
},
|
|
275
|
+
children: copy.retry
|
|
276
|
+
}) : null
|
|
277
|
+
]
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
//#endregion
|
|
172
281
|
//#region ../dsh-model-route/src/ui.tsx
|
|
173
282
|
/**
|
|
174
283
|
* ModelMenu: the standard editor of one model-route field — the visible half
|
|
@@ -1178,6 +1287,29 @@ ${scope} {
|
|
|
1178
1287
|
& .dsh-ui-select:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }
|
|
1179
1288
|
& .dsh-ui-select[aria-invalid='true'] { border-color: var(--dsw-alias-state-error-primary); }
|
|
1180
1289
|
|
|
1290
|
+
/* --- Native textarea. The primitives ship no textarea either, so a settings
|
|
1291
|
+
* prompt keeps the platform element and the same field shell as the select:
|
|
1292
|
+
* 0.5px l4, radius-md, layer-3, 13/1.5 type. It grows vertically instead of
|
|
1293
|
+
* locking to the select's 34px row. --- */
|
|
1294
|
+
& .dsh-ui-textarea {
|
|
1295
|
+
box-sizing: border-box;
|
|
1296
|
+
width: 100%;
|
|
1297
|
+
min-width: 0;
|
|
1298
|
+
resize: vertical;
|
|
1299
|
+
padding: 8px 12px;
|
|
1300
|
+
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
1301
|
+
border-radius: var(--dsw-radius-md);
|
|
1302
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
1303
|
+
font: inherit;
|
|
1304
|
+
font-size: 13px;
|
|
1305
|
+
line-height: 1.5;
|
|
1306
|
+
color: var(--dsw-alias-label-primary);
|
|
1307
|
+
}
|
|
1308
|
+
& .dsh-ui-textarea:focus-visible { outline: none; border-color: var(--dsw-alias-state-business-primary); }
|
|
1309
|
+
& .dsh-ui-textarea:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }
|
|
1310
|
+
& .dsh-ui-textarea::placeholder { color: var(--dsw-alias-label-dimmed); }
|
|
1311
|
+
& .dsh-ui-textarea[aria-invalid='true'] { border-color: var(--dsw-alias-state-error-primary); }
|
|
1312
|
+
|
|
1181
1313
|
/* Long panels scroll inside themselves instead of growing the host page. */
|
|
1182
1314
|
& .dsh-ui-panel { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
|
|
1183
1315
|
& .dsh-ui-panel-wide { max-width: 100%; }
|
|
@@ -1435,20 +1567,6 @@ function messageOf(error) {
|
|
|
1435
1567
|
if (error && typeof error === "object" && "message" in error) return String(error.message);
|
|
1436
1568
|
return String(error);
|
|
1437
1569
|
}
|
|
1438
|
-
function checkedCatalog(value) {
|
|
1439
|
-
if (value && typeof value === "object" && "ok" in value && value.ok === false) throw new Error("error" in value ? messageOf(value.error) : "Request failed");
|
|
1440
|
-
return value;
|
|
1441
|
-
}
|
|
1442
|
-
function catalogFailures(value) {
|
|
1443
|
-
if (!value || typeof value !== "object") return [];
|
|
1444
|
-
const root = "ok" in value && value.ok === true && "value" in value ? value.value : value;
|
|
1445
|
-
if (!root || typeof root !== "object" || !("failures" in root) || !Array.isArray(root.failures)) return [];
|
|
1446
|
-
return root.failures.map((failure) => {
|
|
1447
|
-
if (!failure || typeof failure !== "object") return messageOf(failure);
|
|
1448
|
-
const provider = "name" in failure ? failure.name : "id" in failure ? failure.id : "provider" in failure ? failure.provider : "";
|
|
1449
|
-
return provider ? `${provider}: ${messageOf(failure)}` : messageOf(failure);
|
|
1450
|
-
});
|
|
1451
|
-
}
|
|
1452
1570
|
//#endregion
|
|
1453
1571
|
//#region src/client-copy.ts
|
|
1454
1572
|
function hubText(value, language) {
|
|
@@ -1553,7 +1671,6 @@ const TEXT$1 = {
|
|
|
1553
1671
|
loading: "正在读取…",
|
|
1554
1672
|
failed: (message) => `读取失败:${message}`,
|
|
1555
1673
|
syncFailed: (message) => `自动同步不可用:${message}`,
|
|
1556
|
-
catalogFailed: (message) => `模型列表读取失败:${message}`,
|
|
1557
1674
|
writeFailed: (message) => `未保存:${message}。请刷新后重试。`,
|
|
1558
1675
|
saved: "已保存,正在刷新…",
|
|
1559
1676
|
savedRefreshFailed: (message) => `已保存;阵容刷新失败:${message}。请刷新以读取最新设置。`,
|
|
@@ -1576,7 +1693,13 @@ const TEXT$1 = {
|
|
|
1576
1693
|
effortDefault: "默认",
|
|
1577
1694
|
searchModels: "搜索模型…",
|
|
1578
1695
|
noModels: "没有匹配的模型",
|
|
1579
|
-
clearSearch: "清除搜索"
|
|
1696
|
+
clearSearch: "清除搜索",
|
|
1697
|
+
runningTitle: "运行中的子代理",
|
|
1698
|
+
runningEmpty: "当前没有运行中的子代理。",
|
|
1699
|
+
runningPending: "尚未发起模型请求",
|
|
1700
|
+
runningRemote: "远程子代理,无法读取路由",
|
|
1701
|
+
runningFailed: (message) => `运行状态读取失败:${message}`,
|
|
1702
|
+
effortAdapterDefault: (effort) => `${effort}(适配器默认)`
|
|
1580
1703
|
},
|
|
1581
1704
|
en: {
|
|
1582
1705
|
title: "Subagent Cast",
|
|
@@ -1585,7 +1708,6 @@ const TEXT$1 = {
|
|
|
1585
1708
|
loading: "Loading…",
|
|
1586
1709
|
failed: (message) => `Load failed: ${message}`,
|
|
1587
1710
|
syncFailed: (message) => `Live sync unavailable: ${message}`,
|
|
1588
|
-
catalogFailed: (message) => `Model list failed: ${message}`,
|
|
1589
1711
|
writeFailed: (message) => `Not saved: ${message}. Refresh, then try again.`,
|
|
1590
1712
|
saved: "Saved; refreshing…",
|
|
1591
1713
|
savedRefreshFailed: (message) => `Saved; cast refresh failed: ${message}. Refresh to read the latest settings.`,
|
|
@@ -1608,7 +1730,13 @@ const TEXT$1 = {
|
|
|
1608
1730
|
effortDefault: "Default",
|
|
1609
1731
|
searchModels: "Search models…",
|
|
1610
1732
|
noModels: "No matching models",
|
|
1611
|
-
clearSearch: "Clear search"
|
|
1733
|
+
clearSearch: "Clear search",
|
|
1734
|
+
runningTitle: "Running subagents",
|
|
1735
|
+
runningEmpty: "No subagents are running.",
|
|
1736
|
+
runningPending: "No model request yet",
|
|
1737
|
+
runningRemote: "Remote subagent; route unavailable",
|
|
1738
|
+
runningFailed: (message) => `Running state failed: ${message}`,
|
|
1739
|
+
effortAdapterDefault: (effort) => `${effort} (adapter default)`
|
|
1612
1740
|
}
|
|
1613
1741
|
};
|
|
1614
1742
|
function useText$1(locale) {
|
|
@@ -1631,6 +1759,11 @@ const castCss = `
|
|
|
1631
1759
|
.dsh-model-hub .cast-banner{display:flex;gap:12px;align-items:center;justify-content:space-between;flex-wrap:wrap}
|
|
1632
1760
|
.dsh-model-hub .cast-add{display:flex}
|
|
1633
1761
|
.dsh-model-hub .cast-add .dsh-model-menu-root{max-width:100%}
|
|
1762
|
+
.dsh-model-hub .cast-running{margin-top:16px}
|
|
1763
|
+
.dsh-model-hub .cast-running-title{font-size:14px;line-height:22px;font-weight:600;margin:0 0 4px;color:var(--dsw-alias-label-primary)}
|
|
1764
|
+
.dsh-model-hub .cast-running-row{display:flex;gap:12px;align-items:baseline;flex-wrap:wrap;padding:6px 0;border-bottom:.5px solid var(--dsw-alias-border-l1)}
|
|
1765
|
+
.dsh-model-hub .cast-running-label{font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary);overflow-wrap:anywhere;min-width:0}
|
|
1766
|
+
.dsh-model-hub .cast-running-route{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;min-width:0}
|
|
1634
1767
|
@media(max-width:520px){.dsh-model-hub .cast-row{grid-template-columns:minmax(0,1fr);gap:8px}}
|
|
1635
1768
|
`;
|
|
1636
1769
|
/** CAS rejections all carry a refresh hint in either language. */
|
|
@@ -1654,6 +1787,14 @@ function routeInput(route) {
|
|
|
1654
1787
|
model: normalized.model
|
|
1655
1788
|
};
|
|
1656
1789
|
}
|
|
1790
|
+
/** One live subagent row: label (or short id) plus its effective route. */
|
|
1791
|
+
function runningRouteText(entry, t) {
|
|
1792
|
+
if (!entry.local) return t("runningRemote");
|
|
1793
|
+
const route = entry.route;
|
|
1794
|
+
if (!route) return t("runningPending");
|
|
1795
|
+
const effort = route.reasoningEffort ? entry.effortIsAdapterDefault ? t("effortAdapterDefault", route.reasoningEffort) : route.reasoningEffort : "";
|
|
1796
|
+
return `${route.provider} / ${route.model}${effort ? ` · ${effort}` : ""}`;
|
|
1797
|
+
}
|
|
1657
1798
|
/** One cast entry: model picker, tendency input saved on blur, remove button. */
|
|
1658
1799
|
function CastRow({ t, catalog, route, tendencyRevision, draftReview, disabled, tendencyDisabled, menuDisabled, onSetRoute, onSetTendency, onRemove }) {
|
|
1659
1800
|
const label = `${route.provider} / ${route.model}`;
|
|
@@ -1737,6 +1878,8 @@ function CastPage({ api, locale }) {
|
|
|
1737
1878
|
const t = useText$1(locale);
|
|
1738
1879
|
const [cast, setCast] = (0, react.useState)(null);
|
|
1739
1880
|
const [catalog, setCatalog] = (0, react.useState)(null);
|
|
1881
|
+
const [running, setRunning] = (0, react.useState)(null);
|
|
1882
|
+
const [runningError, setRunningError] = (0, react.useState)("");
|
|
1740
1883
|
const [loadError, setLoadError] = (0, react.useState)("");
|
|
1741
1884
|
const [catalogError, setCatalogError] = (0, react.useState)("");
|
|
1742
1885
|
const [syncError, setSyncError] = (0, react.useState)("");
|
|
@@ -1756,7 +1899,11 @@ function CastPage({ api, locale }) {
|
|
|
1756
1899
|
scope.queued = false;
|
|
1757
1900
|
scope.busy = true;
|
|
1758
1901
|
setRefreshing(true);
|
|
1759
|
-
const [castResult, models] = await Promise.allSettled([
|
|
1902
|
+
const [castResult, models, live] = await Promise.allSettled([
|
|
1903
|
+
Promise.resolve().then(() => scope.api.getCast()),
|
|
1904
|
+
Promise.resolve().then(() => scope.api.modelCatalog()).then(checkedModelCatalog),
|
|
1905
|
+
Promise.resolve().then(() => scope.api.listRunning())
|
|
1906
|
+
]);
|
|
1760
1907
|
if (!scope.active) return;
|
|
1761
1908
|
if (castResult.status === "fulfilled") {
|
|
1762
1909
|
setCast(castResult.value);
|
|
@@ -1770,6 +1917,10 @@ function CastPage({ api, locale }) {
|
|
|
1770
1917
|
setCatalog(models.value);
|
|
1771
1918
|
setCatalogError("");
|
|
1772
1919
|
} else setCatalogError(messageOf(models.reason));
|
|
1920
|
+
if (live.status === "fulfilled") {
|
|
1921
|
+
setRunning(live.value);
|
|
1922
|
+
setRunningError("");
|
|
1923
|
+
} else setRunningError(messageOf(live.reason));
|
|
1773
1924
|
scope.busy = false;
|
|
1774
1925
|
setRefreshing(false);
|
|
1775
1926
|
if (scope.queued) refresh(scope);
|
|
@@ -1791,6 +1942,8 @@ function CastPage({ api, locale }) {
|
|
|
1791
1942
|
setSyncError("");
|
|
1792
1943
|
setCast(null);
|
|
1793
1944
|
setCatalog(null);
|
|
1945
|
+
setRunning(null);
|
|
1946
|
+
setRunningError("");
|
|
1794
1947
|
refresh(scope);
|
|
1795
1948
|
const unsubscribe = api.subscribeChanges?.(() => {
|
|
1796
1949
|
if (!scope.active) return;
|
|
@@ -1833,12 +1986,17 @@ function CastPage({ api, locale }) {
|
|
|
1833
1986
|
}
|
|
1834
1987
|
});
|
|
1835
1988
|
};
|
|
1836
|
-
const refreshNow = () => {
|
|
1989
|
+
const refreshNow = async () => {
|
|
1837
1990
|
const scope = scopeRef.current;
|
|
1838
|
-
if (scope?.api === api) refresh(scope, true);
|
|
1991
|
+
if (scope?.api === api && !scope.busy) await refresh(scope, true);
|
|
1839
1992
|
};
|
|
1840
1993
|
const disabled = saving !== "" || refreshing || writeError !== "" || saved && loadError !== "";
|
|
1841
|
-
const
|
|
1994
|
+
const catalogStatus = {
|
|
1995
|
+
loading: refreshing,
|
|
1996
|
+
error: catalogError ? hubMessage(catalogError, t.language) : "",
|
|
1997
|
+
failures: modelCatalogFailures(catalog),
|
|
1998
|
+
refresh: refreshNow
|
|
1999
|
+
};
|
|
1842
2000
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1843
2001
|
className: "cast-root dsh-ui-stack",
|
|
1844
2002
|
"aria-label": t("title"),
|
|
@@ -1883,16 +2041,10 @@ function CastPage({ api, locale }) {
|
|
|
1883
2041
|
className: "dsh-ui-error dsh-ui-wrap",
|
|
1884
2042
|
children: t("syncFailed", hubMessage(syncError, t.language))
|
|
1885
2043
|
}) : null,
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
}) : null,
|
|
1891
|
-
failures.map((failure, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1892
|
-
role: "alert",
|
|
1893
|
-
className: "dsh-ui-error dsh-ui-wrap",
|
|
1894
|
-
children: t("catalogFailed", hubMessage(failure, t.language))
|
|
1895
|
-
}, index)),
|
|
2044
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalogStatus, {
|
|
2045
|
+
state: catalogStatus,
|
|
2046
|
+
locale: t.language
|
|
2047
|
+
}),
|
|
1896
2048
|
writeError ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1897
2049
|
className: "dsh-ui-banner dsh-ui-banner--danger cast-banner",
|
|
1898
2050
|
role: "alert",
|
|
@@ -1969,7 +2121,40 @@ function CastPage({ api, locale }) {
|
|
|
1969
2121
|
expectedRevision: cast.revision
|
|
1970
2122
|
}))
|
|
1971
2123
|
})
|
|
1972
|
-
}) : null
|
|
2124
|
+
}) : null,
|
|
2125
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2126
|
+
className: "cast-running",
|
|
2127
|
+
"aria-label": t("runningTitle"),
|
|
2128
|
+
children: [
|
|
2129
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
2130
|
+
className: "cast-running-title",
|
|
2131
|
+
children: t("runningTitle")
|
|
2132
|
+
}),
|
|
2133
|
+
runningError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2134
|
+
role: "alert",
|
|
2135
|
+
className: "dsh-ui-error dsh-ui-wrap",
|
|
2136
|
+
children: t("runningFailed", hubMessage(runningError, t.language))
|
|
2137
|
+
}) : null,
|
|
2138
|
+
running === null && !runningError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2139
|
+
className: "dsh-ui-loading",
|
|
2140
|
+
children: t("loading")
|
|
2141
|
+
}) : null,
|
|
2142
|
+
running !== null && running.entries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2143
|
+
className: "dsh-ui-help",
|
|
2144
|
+
children: t("runningEmpty")
|
|
2145
|
+
}) : null,
|
|
2146
|
+
running?.entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2147
|
+
className: "cast-running-row",
|
|
2148
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2149
|
+
className: "cast-running-label",
|
|
2150
|
+
children: entry.label || entry.id
|
|
2151
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2152
|
+
className: "cast-running-route",
|
|
2153
|
+
children: runningRouteText(entry, t)
|
|
2154
|
+
})]
|
|
2155
|
+
}, entry.id))
|
|
2156
|
+
]
|
|
2157
|
+
})
|
|
1973
2158
|
]
|
|
1974
2159
|
});
|
|
1975
2160
|
}
|
|
@@ -1987,8 +2172,7 @@ const name = "dsh-model-hub-client";
|
|
|
1987
2172
|
const inject = [
|
|
1988
2173
|
"slots",
|
|
1989
2174
|
"connection",
|
|
1990
|
-
|
|
1991
|
-
"remote.session",
|
|
2175
|
+
...MODEL_CATALOG_INJECT,
|
|
1992
2176
|
"locale"
|
|
1993
2177
|
];
|
|
1994
2178
|
const CHANNEL = "/dsh-model-hub";
|
|
@@ -2120,7 +2304,7 @@ function HubPage({ view, api, locale, initialEntries = null, initialCatalog = nu
|
|
|
2120
2304
|
scope.queued = false;
|
|
2121
2305
|
scope.busy = true;
|
|
2122
2306
|
setRefreshing(true);
|
|
2123
|
-
const [fields, models] = await Promise.allSettled([Promise.resolve().then(() => scope.api.list()), Promise.resolve().then(() => scope.api.modelCatalog()).then(
|
|
2307
|
+
const [fields, models] = await Promise.allSettled([Promise.resolve().then(() => scope.api.list()), Promise.resolve().then(() => scope.api.modelCatalog()).then(checkedModelCatalog)]);
|
|
2124
2308
|
if (!scope.active) return;
|
|
2125
2309
|
if (fields.status === "fulfilled") {
|
|
2126
2310
|
setEntries(fields.value);
|
|
@@ -2233,7 +2417,16 @@ function HubPage({ view, api, locale, initialEntries = null, initialCatalog = nu
|
|
|
2233
2417
|
})
|
|
2234
2418
|
})).filter((entry) => entry.fields.length > 0 || (entry.error || entry.catalogError) && (!needle || [entry.ns, pluginTitle(entry, t)].some((text) => text.toLocaleLowerCase().includes(needle))));
|
|
2235
2419
|
const disabled = writing !== "" || refreshing || writeError !== "" || saved && listError !== "";
|
|
2236
|
-
const
|
|
2420
|
+
const refreshNow = async () => {
|
|
2421
|
+
const scope = scopeRef.current;
|
|
2422
|
+
if (scope?.api === api && !scope.busy) await refresh(scope);
|
|
2423
|
+
};
|
|
2424
|
+
const catalogStatus = {
|
|
2425
|
+
loading: refreshing,
|
|
2426
|
+
error: catalogError ? hubMessage(catalogError, t.language) : "",
|
|
2427
|
+
failures: modelCatalogFailures(catalog),
|
|
2428
|
+
refresh: refreshNow
|
|
2429
|
+
};
|
|
2237
2430
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2238
2431
|
className: "dsh-model-hub dsh-ui-stack",
|
|
2239
2432
|
lang: t.language,
|
|
@@ -2277,10 +2470,7 @@ function HubPage({ view, api, locale, initialEntries = null, initialCatalog = nu
|
|
|
2277
2470
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2278
2471
|
variant: "ghost",
|
|
2279
2472
|
disabled: writing !== "" || refreshing,
|
|
2280
|
-
onClick:
|
|
2281
|
-
const scope = scopeRef.current;
|
|
2282
|
-
if (scope?.api === api) refresh(scope);
|
|
2283
|
-
},
|
|
2473
|
+
onClick: refreshNow,
|
|
2284
2474
|
children: refreshing ? t("refreshing") : t("refresh")
|
|
2285
2475
|
})]
|
|
2286
2476
|
}),
|
|
@@ -2294,16 +2484,10 @@ function HubPage({ view, api, locale, initialEntries = null, initialCatalog = nu
|
|
|
2294
2484
|
className: "dsh-ui-error dsh-ui-wrap",
|
|
2295
2485
|
children: t("syncFailed", hubMessage(syncError, t.language))
|
|
2296
2486
|
}) : null,
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
}) : null,
|
|
2302
|
-
failures.map((failure, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2303
|
-
role: "alert",
|
|
2304
|
-
className: "dsh-ui-error dsh-ui-wrap",
|
|
2305
|
-
children: t("catalogFailed", hubMessage(failure, t.language))
|
|
2306
|
-
}, index)),
|
|
2487
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalogStatus, {
|
|
2488
|
+
state: catalogStatus,
|
|
2489
|
+
locale: t.language
|
|
2490
|
+
}),
|
|
2307
2491
|
writeError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2308
2492
|
role: "alert",
|
|
2309
2493
|
className: "dsh-ui-error dsh-ui-wrap",
|
|
@@ -2423,14 +2607,15 @@ async function apply(ctx) {
|
|
|
2423
2607
|
const api = Object.freeze({
|
|
2424
2608
|
list: () => call("fields.list", {}),
|
|
2425
2609
|
setField: (payload) => call("fields.set", payload),
|
|
2426
|
-
modelCatalog: () => client
|
|
2610
|
+
modelCatalog: () => readModelCatalog(client),
|
|
2427
2611
|
subscribeChanges: changes.subscribe,
|
|
2428
2612
|
getCast: () => call("cast.get", {}),
|
|
2429
2613
|
setCastEnabled: (payload) => call("cast.setEnabled", payload),
|
|
2430
2614
|
addCastRoute: (payload) => call("cast.addRoute", payload),
|
|
2431
2615
|
setCastRoute: (payload) => call("cast.setRoute", payload),
|
|
2432
2616
|
removeCastRoute: (payload) => call("cast.removeRoute", payload),
|
|
2433
|
-
setCastTendency: (payload) => call("cast.setTendency", payload)
|
|
2617
|
+
setCastTendency: (payload) => call("cast.setTendency", payload),
|
|
2618
|
+
listRunning: () => call("cast.running", {})
|
|
2434
2619
|
});
|
|
2435
2620
|
client.slots.inject("plugins.bundle.config", () => client.slots.register({
|
|
2436
2621
|
name: "plugins.bundle.config",
|
package/lib/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { modelRouteRoleSchemastery, modelRouteSchemastery } from "@klarkxy/dsh-m
|
|
|
6
6
|
import { detectModelFields, modelRouteOverride, normalizeModelRoute } from "@klarkxy/dsh-model-route";
|
|
7
7
|
import { MODEL_SETTINGS_CHANGED_EVENT, collectModelSettings, notifyModelSettingsChanged } from "@klarkxy/dsh-plugin-kit/model-settings";
|
|
8
8
|
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
9
|
-
import { MODEL_SETTINGS_CHANGES_REMOTE } from "@klarkxy/dsh-plugin-kit/contracts";
|
|
9
|
+
import { MODEL_SETTINGS_CHANGES_REMOTE, isSubagentSession } from "@klarkxy/dsh-plugin-kit/contracts";
|
|
10
10
|
import { parseModelSettingsChangeFrame } from "@klarkxy/dsh-plugin-kit/model-settings-client";
|
|
11
11
|
//#region src/probe.ts
|
|
12
12
|
/**
|
|
@@ -457,9 +457,15 @@ function validateIndex(value) {
|
|
|
457
457
|
*/
|
|
458
458
|
function buildCastPrompt(enabled, routes) {
|
|
459
459
|
if (!enabled || !routes.length) return "";
|
|
460
|
-
const annotated = routes.filter((route) => typeof route.tendency === "string" && route.tendency);
|
|
461
|
-
|
|
462
|
-
|
|
460
|
+
const annotated = routes.filter((route) => typeof route.tendency === "string" && route.tendency.trim());
|
|
461
|
+
const guidance = [
|
|
462
|
+
"Subagent model selection:",
|
|
463
|
+
"When delegating an independent task, actively choose a suitable model instead of silently inheriting your own. If list_subagent_models and a delegation tool with provider/model fields are available, inspect the available routes before the first model-selectable delegation in this task, reuse those results for later delegations, and supply provider and model together explicitly.",
|
|
464
|
+
"For independent reviews, alternative approaches, or disputed conclusions, prefer a model different from your own when a suitable candidate is available. Choose for task fit and the user's constraints; using the same model is appropriate when it fits better or no suitable alternative is available. Do not create extra subtasks solely for model diversity.",
|
|
465
|
+
"The current Session's discovery results and tool schema are authoritative. Configured model notes may refer to routes unavailable in this Session. If discovery or model-selection fields are unavailable, use the supported delegation behavior; do not invent routes or pass unsupported fields to tools such as subagent_fork. When changing models, omit reasoning_effort unless you have verified a supported value."
|
|
466
|
+
];
|
|
467
|
+
if (annotated.length) guidance.push("User-provided task tendencies (advisory): prefer a matching tendency among available models. Models without a tendency remain general-purpose candidates; no model-specific strengths are assumed.", ...annotated.map((route) => `- ${route.provider}/${route.model}${route.reasoningEffort ? ` (reasoning effort: ${route.reasoningEffort})` : ""}: ${route.tendency}`));
|
|
468
|
+
return guidance.join("\n");
|
|
463
469
|
}
|
|
464
470
|
function createCast(deps) {
|
|
465
471
|
const castFile = () => {
|
|
@@ -683,7 +689,7 @@ function createCast(deps) {
|
|
|
683
689
|
return buildCastPrompt(snapshot.enabled, mergeTendencies(snapshot.routes, readTendencies().tendencies));
|
|
684
690
|
} catch (error) {
|
|
685
691
|
deps.logger?.warn("model-hub: cast tendencies unavailable: %s", error.message);
|
|
686
|
-
return
|
|
692
|
+
return buildCastPrompt(snapshot.enabled, snapshot.routes);
|
|
687
693
|
}
|
|
688
694
|
};
|
|
689
695
|
return {
|
|
@@ -697,6 +703,128 @@ function createCast(deps) {
|
|
|
697
703
|
};
|
|
698
704
|
}
|
|
699
705
|
//#endregion
|
|
706
|
+
//#region src/running.ts
|
|
707
|
+
/**
|
|
708
|
+
* dsh-model-hub — running subagent routes (host half).
|
|
709
|
+
*
|
|
710
|
+
* Read-only live view answering "which model and reasoning effort is each
|
|
711
|
+
* running subagent actually using". `subagent/start` and `subagent/end`
|
|
712
|
+
* maintain the live set; each child's effective route is read from its
|
|
713
|
+
* session epoch header (`session.requestHeader()`) and kept current through
|
|
714
|
+
* the session's own `request/header` events (reason `change` covers mid-run
|
|
715
|
+
* model switches). Children already running when this plugin (re)loads are
|
|
716
|
+
* recovered from the live agent registry via `isSubagentSession`.
|
|
717
|
+
*
|
|
718
|
+
* The effective route is session data recorded by the host; this tracker
|
|
719
|
+
* never writes to sessions or settings. Display-only invalidations reuse the
|
|
720
|
+
* model-settings change channel with RUNNING_NAMESPACE so open pages refetch.
|
|
721
|
+
*/
|
|
722
|
+
/** Invalidation namespace for the running-subagent view; carries no settings. */
|
|
723
|
+
const RUNNING_NAMESPACE = "@klarkxy/dsh-model-hub/running-subagents";
|
|
724
|
+
function text(value) {
|
|
725
|
+
return typeof value === "string" ? value.trim() : "";
|
|
726
|
+
}
|
|
727
|
+
/** Normalize one epoch header into the display route plus the adapter-default flag. */
|
|
728
|
+
function routeFromHeader(header) {
|
|
729
|
+
const config = header?.config;
|
|
730
|
+
const provider = text(config?.provider);
|
|
731
|
+
const model = text(config?.model);
|
|
732
|
+
if (!provider || !model) return {};
|
|
733
|
+
const effort = text(config?.reasoningEffort);
|
|
734
|
+
return {
|
|
735
|
+
route: effort ? {
|
|
736
|
+
provider,
|
|
737
|
+
model,
|
|
738
|
+
reasoningEffort: effort
|
|
739
|
+
} : {
|
|
740
|
+
provider,
|
|
741
|
+
model
|
|
742
|
+
},
|
|
743
|
+
...effort && header?.adapterDefaults?.reasoningEffort === true ? { effortIsAdapterDefault: true } : {}
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
function sameRoute(left, right) {
|
|
747
|
+
return left.route?.provider === right.route?.provider && left.route?.model === right.route?.model && left.route?.reasoningEffort === right.route?.reasoningEffort && left.effortIsAdapterDefault === right.effortIsAdapterDefault;
|
|
748
|
+
}
|
|
749
|
+
function createRunningTracker(deps) {
|
|
750
|
+
const entries = /* @__PURE__ */ new Map();
|
|
751
|
+
let revision = 0;
|
|
752
|
+
const bump = () => deps.notify(++revision);
|
|
753
|
+
const add = (id, info) => {
|
|
754
|
+
const session = (deps.agents?.get(id))?.session;
|
|
755
|
+
const next = routeFromHeader(session?.requestHeader?.());
|
|
756
|
+
const existing = entries.get(id);
|
|
757
|
+
if (existing) {
|
|
758
|
+
const providerName = info?.provider ?? existing.providerName;
|
|
759
|
+
const local = info?.local ?? existing.local;
|
|
760
|
+
const changed = existing.providerName !== providerName || existing.local !== local || !sameRoute(existing, next);
|
|
761
|
+
existing.providerName = providerName;
|
|
762
|
+
existing.local = local;
|
|
763
|
+
existing.route = next.route;
|
|
764
|
+
existing.effortIsAdapterDefault = next.effortIsAdapterDefault;
|
|
765
|
+
if (changed) bump();
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
entries.set(id, {
|
|
769
|
+
id,
|
|
770
|
+
local: info?.local !== false,
|
|
771
|
+
...info?.provider ? { providerName: info.provider } : {},
|
|
772
|
+
...typeof session?.header?.parentSession === "string" && session.header.parentSession ? { parentSessionId: session.header.parentSession } : {},
|
|
773
|
+
...typeof session?.header?.createdAt === "number" ? { startedAt: session.header.createdAt } : {},
|
|
774
|
+
route: next.route,
|
|
775
|
+
effortIsAdapterDefault: next.effortIsAdapterDefault
|
|
776
|
+
});
|
|
777
|
+
bump();
|
|
778
|
+
};
|
|
779
|
+
const remove = (id) => {
|
|
780
|
+
if (entries.delete(id)) bump();
|
|
781
|
+
};
|
|
782
|
+
try {
|
|
783
|
+
for (const agent of deps.agents?.list() ?? []) if (agent?.id && isSubagentSession(agent.session)) add(agent.id);
|
|
784
|
+
} catch {}
|
|
785
|
+
deps.events.on("subagent/start", (info) => {
|
|
786
|
+
if (typeof info?.id === "string" && info.id) add(info.id, info);
|
|
787
|
+
});
|
|
788
|
+
deps.events.on("subagent/end", (info) => {
|
|
789
|
+
if (typeof info?.id === "string") remove(info.id);
|
|
790
|
+
});
|
|
791
|
+
deps.events.on("agent/disposed", (payload) => {
|
|
792
|
+
if (typeof payload?.agent?.id === "string") remove(payload.agent.id);
|
|
793
|
+
});
|
|
794
|
+
deps.events.on("session/event", (session, event) => {
|
|
795
|
+
if (event?.type !== "request/header") return;
|
|
796
|
+
const id = typeof session?.id === "string" ? session.id : void 0;
|
|
797
|
+
const entry = id ? entries.get(id) : void 0;
|
|
798
|
+
if (!entry) return;
|
|
799
|
+
const next = routeFromHeader(event.data?.header);
|
|
800
|
+
if (sameRoute(entry, next)) return;
|
|
801
|
+
entry.route = next.route;
|
|
802
|
+
entry.effortIsAdapterDefault = next.effortIsAdapterDefault;
|
|
803
|
+
bump();
|
|
804
|
+
});
|
|
805
|
+
return { async list(signal) {
|
|
806
|
+
const pendingParents = /* @__PURE__ */ new Set();
|
|
807
|
+
for (const entry of entries.values()) if (entry.label === void 0 && entry.parentSessionId) pendingParents.add(entry.parentSessionId);
|
|
808
|
+
if (deps.subagents && pendingParents.size) await Promise.allSettled([...pendingParents].map(async (parent) => {
|
|
809
|
+
const children = await deps.subagents.listChildren(parent, signal);
|
|
810
|
+
const labels = new Map(children.map((child) => [child.id, child.label]));
|
|
811
|
+
for (const entry of entries.values()) {
|
|
812
|
+
if (entry.parentSessionId !== parent || entry.label !== void 0) continue;
|
|
813
|
+
const label = labels.get(entry.id);
|
|
814
|
+
if (typeof label === "string" && label.trim()) entry.label = label.trim();
|
|
815
|
+
}
|
|
816
|
+
}));
|
|
817
|
+
const snapshot = [...entries.values()].map((entry) => ({
|
|
818
|
+
...entry,
|
|
819
|
+
route: entry.route ? { ...entry.route } : void 0
|
|
820
|
+
})).sort((a, b) => (a.startedAt ?? 0) - (b.startedAt ?? 0) || a.id.localeCompare(b.id));
|
|
821
|
+
return {
|
|
822
|
+
revision,
|
|
823
|
+
entries: snapshot
|
|
824
|
+
};
|
|
825
|
+
} };
|
|
826
|
+
}
|
|
827
|
+
//#endregion
|
|
700
828
|
//#region src/index.ts
|
|
701
829
|
/**
|
|
702
830
|
* dsh-model-hub — cross-plugin model settings hub.
|
|
@@ -751,6 +879,12 @@ function apply(ctx) {
|
|
|
751
879
|
logger: ctx.logger,
|
|
752
880
|
notifyTendenciesChanged: (revision) => notifyModelSettingsChanged(ctx, CAST_TENDENCIES_NAMESPACE, revision)
|
|
753
881
|
});
|
|
882
|
+
const running = createRunningTracker({
|
|
883
|
+
events: ctx,
|
|
884
|
+
agents: ctx.get("agents"),
|
|
885
|
+
subagents: ctx.get("subagents"),
|
|
886
|
+
notify: (revision) => notifyModelSettingsChanged(ctx, RUNNING_NAMESPACE, revision)
|
|
887
|
+
});
|
|
754
888
|
ctx.systemPrompt.section({
|
|
755
889
|
name: "model-hub:subagent-cast",
|
|
756
890
|
order: ctx.systemPrompt.getSectionOrder("TOOL_SUBAGENT") + 1,
|
|
@@ -812,6 +946,10 @@ function apply(ctx) {
|
|
|
812
946
|
ok: true,
|
|
813
947
|
value: cast.setTendency(row.provider, row.model, row.tendency, row.expectedRevision, signal)
|
|
814
948
|
};
|
|
949
|
+
if (endpoint === "cast.running") return {
|
|
950
|
+
ok: true,
|
|
951
|
+
value: await running.list(signal)
|
|
952
|
+
};
|
|
815
953
|
}
|
|
816
954
|
throw new Error(`Unknown endpoint: ${endpoint}`);
|
|
817
955
|
} catch (error) {
|