@klarkxy/dsh-plugin-kit 0.5.0 → 0.6.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/README.md +1 -3
- package/docs/README.zh-CN.md +1 -3
- package/lib/auxiliary-context.d.ts +42 -0
- package/lib/auxiliary-context.js +65 -0
- package/lib/auxiliary-context.js.map +1 -0
- package/lib/auxiliary-model-client.d.ts +57 -0
- package/lib/auxiliary-model-client.js +497 -0
- package/lib/auxiliary-model-client.js.map +1 -0
- package/lib/auxiliary-model.d.ts +21 -0
- package/lib/auxiliary-model.js +354 -0
- package/lib/auxiliary-model.js.map +1 -0
- package/lib/client-utils.d.ts +45 -1
- package/lib/client-utils.js +187 -2
- package/lib/client-utils.js.map +1 -1
- package/lib/contracts.d.ts +52 -2
- package/lib/contracts.js +55 -1
- package/lib/contracts.js.map +1 -1
- package/lib/index.d.ts +3 -2
- package/lib/index.js +2 -1
- package/lib/llm-call.d.ts +19 -3
- package/lib/llm-call.js +18 -8
- package/lib/llm-call.js.map +1 -1
- package/lib/model-menu.js +1 -1
- package/lib/zod.js +13566 -0
- package/lib/zod.js.map +1 -0
- package/package.json +23 -2
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import { AUXILIARY_MODEL_CHANGES_IDENTITY, AUXILIARY_MODEL_CHANNEL, AUXILIARY_MODEL_NS } from "./contracts.js";
|
|
2
|
+
import { createModelSettingsSubscriber, modelSettingsChangesRemote } from "./model-settings-client.js";
|
|
3
|
+
import { registerSessionSettingsItem } from "./client-utils.js";
|
|
4
|
+
import { officialUiCss } from "./official-ui.js";
|
|
5
|
+
import { modelMenuEffortLabel, modelMenuEffortOptions, parseModelMenuChoices, readModelCatalog } from "@klarkxy/dsh-model-route";
|
|
6
|
+
import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
7
|
+
import { Button, IconChevronDownOutlineRegular } from "@deepseek-ai/dsh-client-ui-primitives";
|
|
8
|
+
import { ModelCatalogStatus, ModelMenu, modelMenuCss, useModelCatalog } from "@klarkxy/dsh-model-route/ui";
|
|
9
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
//#region src/auxiliary-model-client.tsx
|
|
11
|
+
const AUXILIARY_MODEL_PACKAGE = "@klarkxy/dsh-plugin-kit";
|
|
12
|
+
const AUXILIARY_MODEL_SEAT_ID = "auxiliary-model";
|
|
13
|
+
const AUXILIARY_MODEL_SEAT_ORDER = 55;
|
|
14
|
+
/** Private Cordis rendezvous; every bundled copy resolves the same root service. */
|
|
15
|
+
const AUXILIARY_MODEL_CARD_SERVICE = "dshAuxiliaryModelSettingsCard";
|
|
16
|
+
/** Theme-token-only stylesheet: officialUiCss scope plus the shared ModelMenu recipe. */
|
|
17
|
+
const auxiliaryModelCss = `${officialUiCss("ams-root")}${modelMenuCss}
|
|
18
|
+
.ams-root .ams-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
|
|
19
|
+
`;
|
|
20
|
+
const auxiliaryCopy = {
|
|
21
|
+
zh: {
|
|
22
|
+
title: "默认辅助模型",
|
|
23
|
+
help: "各辅助功能(如标题生成、摘要)没有单独指定模型时使用的模型;仅影响声明使用它的功能。",
|
|
24
|
+
model: "辅助模型",
|
|
25
|
+
modelDefault: "跟随会话或宿主默认",
|
|
26
|
+
modelHint: "不选择模型时,各功能按自己的规则决定对话模型。",
|
|
27
|
+
effortDefault: "跟随模型默认",
|
|
28
|
+
loading: "正在读取设置…",
|
|
29
|
+
failed: "无法读取模型设置。",
|
|
30
|
+
stale: "设置已被其他窗口更新,请刷新后重试。",
|
|
31
|
+
saveFailed: "保存失败,原设置未更改。请重试。",
|
|
32
|
+
saved: "已保存。",
|
|
33
|
+
retry: "重试",
|
|
34
|
+
refresh: "刷新设置",
|
|
35
|
+
syncUnavailable: "自动刷新不可用,请手动刷新设置。"
|
|
36
|
+
},
|
|
37
|
+
en: {
|
|
38
|
+
title: "Default Auxiliary Model",
|
|
39
|
+
help: "The model used by auxiliary features (such as title generation or summarizing) that do not name their own model; only features that opted in are affected.",
|
|
40
|
+
model: "Auxiliary model",
|
|
41
|
+
modelDefault: "Follow session or host default",
|
|
42
|
+
modelHint: "With no model selected, each feature follows its own rules for the conversation model.",
|
|
43
|
+
effortDefault: "Follow model default",
|
|
44
|
+
loading: "Loading settings…",
|
|
45
|
+
failed: "Could not load model settings.",
|
|
46
|
+
stale: "Settings were changed elsewhere; refresh and retry.",
|
|
47
|
+
saveFailed: "Save failed; the previous setting was not changed. Try again.",
|
|
48
|
+
saved: "Saved.",
|
|
49
|
+
retry: "Retry",
|
|
50
|
+
refresh: "Refresh settings",
|
|
51
|
+
syncUnavailable: "Automatic refresh unavailable; refresh settings manually."
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
/** A failed RPC keeps its code so the card can translate known rejections. */
|
|
55
|
+
var AuxiliaryRpcError = class extends Error {
|
|
56
|
+
code;
|
|
57
|
+
constructor(code, message) {
|
|
58
|
+
super(message);
|
|
59
|
+
this.code = code;
|
|
60
|
+
this.name = "AuxiliaryRpcError";
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
/** Host UI language, following live switches with the host's English fallback. */
|
|
64
|
+
function useAuxiliaryLocale(client) {
|
|
65
|
+
const subscribe = useCallback((listener) => client.locale?.subscribe(listener) ?? (() => {}), [client]);
|
|
66
|
+
const snapshot = useCallback(() => client.locale?.getSnapshot().active ?? "en", [client]);
|
|
67
|
+
return String(useSyncExternalStore(subscribe, snapshot, snapshot)).startsWith("zh") ? "zh" : "en";
|
|
68
|
+
}
|
|
69
|
+
function isAbort(error) {
|
|
70
|
+
return error instanceof Error && (error.name === "AbortError" || /abort|cancel/i.test(error.message));
|
|
71
|
+
}
|
|
72
|
+
function isStaleRejection(error) {
|
|
73
|
+
if (!(error instanceof AuxiliaryRpcError)) return false;
|
|
74
|
+
return error.code === "stale" || error.code === "auxiliary-model/conflict" || /changed|revision|reload|stale|conflict/i.test(error.message);
|
|
75
|
+
}
|
|
76
|
+
async function callAuxiliaryRpc(client, endpoint, payload = {}) {
|
|
77
|
+
const result = await client.connection.rpc.call(AUXILIARY_MODEL_CHANNEL, endpoint, payload);
|
|
78
|
+
if (!result || result.ok !== true) {
|
|
79
|
+
const error = result && "error" in result ? result.error : void 0;
|
|
80
|
+
throw new AuxiliaryRpcError(error?.code ?? "unknown", error?.message ?? `RPC ${endpoint} failed`);
|
|
81
|
+
}
|
|
82
|
+
return result.value;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The shared card. All state comes from the shared host service over the
|
|
86
|
+
* plugin-kit RPC channel; the card only renders the stored row.
|
|
87
|
+
*/
|
|
88
|
+
function AuxiliaryModelCard({ client, subscribe }) {
|
|
89
|
+
const language = useAuxiliaryLocale(client);
|
|
90
|
+
const text = auxiliaryCopy[language];
|
|
91
|
+
const [settings, setSettings] = useState();
|
|
92
|
+
const loadCatalog = useCallback(() => readModelCatalog(client), [client]);
|
|
93
|
+
const subscribeCatalog = useCallback((change) => client.connection.generation?.subscribe(change), [client]);
|
|
94
|
+
const catalogState = useModelCatalog(loadCatalog, { subscribe: subscribeCatalog });
|
|
95
|
+
const { refresh: refreshCatalog } = catalogState;
|
|
96
|
+
const choices = parseModelMenuChoices(catalogState.catalog, settings?.model ?? void 0);
|
|
97
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
98
|
+
const [error, setError] = useState("");
|
|
99
|
+
const [note, setNote] = useState(false);
|
|
100
|
+
const [busy, setBusy] = useState(false);
|
|
101
|
+
const [syncError, setSyncError] = useState("");
|
|
102
|
+
const operation = useRef({
|
|
103
|
+
epoch: 0,
|
|
104
|
+
active: false,
|
|
105
|
+
reading: false,
|
|
106
|
+
writing: false,
|
|
107
|
+
pending: false
|
|
108
|
+
});
|
|
109
|
+
const refreshRef = useRef(() => {});
|
|
110
|
+
const refresh = () => {
|
|
111
|
+
const state = operation.current;
|
|
112
|
+
if (!state.active) return;
|
|
113
|
+
if (state.reading || state.writing) {
|
|
114
|
+
state.pending = true;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
state.pending = false;
|
|
118
|
+
state.reading = true;
|
|
119
|
+
setBusy(true);
|
|
120
|
+
const epoch = state.epoch;
|
|
121
|
+
callAuxiliaryRpc(client, "defaults.get").then((next) => {
|
|
122
|
+
if (state.active && state.epoch === epoch && !state.pending) {
|
|
123
|
+
setSettings(next);
|
|
124
|
+
setError("");
|
|
125
|
+
}
|
|
126
|
+
}).catch((reason) => {
|
|
127
|
+
if (state.active && state.epoch === epoch && !state.pending && !isAbort(reason)) setError("failed");
|
|
128
|
+
}).finally(() => {
|
|
129
|
+
if (!state.active || state.epoch !== epoch) return;
|
|
130
|
+
state.reading = false;
|
|
131
|
+
setBusy(false);
|
|
132
|
+
if (state.pending) refreshRef.current();
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
refreshRef.current = refresh;
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
const state = operation.current;
|
|
138
|
+
state.epoch++;
|
|
139
|
+
state.active = true;
|
|
140
|
+
state.reading = false;
|
|
141
|
+
state.writing = false;
|
|
142
|
+
state.pending = false;
|
|
143
|
+
setBusy(false);
|
|
144
|
+
setSettings(void 0);
|
|
145
|
+
setError("");
|
|
146
|
+
setNote(false);
|
|
147
|
+
refreshRef.current();
|
|
148
|
+
return () => {
|
|
149
|
+
state.active = false;
|
|
150
|
+
state.epoch++;
|
|
151
|
+
};
|
|
152
|
+
}, [client]);
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
setSyncError("");
|
|
155
|
+
let live = true;
|
|
156
|
+
let stop;
|
|
157
|
+
try {
|
|
158
|
+
stop = subscribe?.(() => {
|
|
159
|
+
if (live) {
|
|
160
|
+
setSyncError("");
|
|
161
|
+
refreshCatalog();
|
|
162
|
+
refreshRef.current();
|
|
163
|
+
}
|
|
164
|
+
}, (message) => {
|
|
165
|
+
if (live) setSyncError(message);
|
|
166
|
+
});
|
|
167
|
+
} catch {
|
|
168
|
+
if (live) setSyncError("Model settings sync service unavailable");
|
|
169
|
+
}
|
|
170
|
+
return () => {
|
|
171
|
+
live = false;
|
|
172
|
+
try {
|
|
173
|
+
stop?.();
|
|
174
|
+
} catch {}
|
|
175
|
+
};
|
|
176
|
+
}, [subscribe, refreshCatalog]);
|
|
177
|
+
const save = useCallback((model) => {
|
|
178
|
+
const state = operation.current;
|
|
179
|
+
if (!settings || !state.active || state.writing || state.reading) return;
|
|
180
|
+
state.writing = true;
|
|
181
|
+
const epoch = state.epoch;
|
|
182
|
+
setBusy(true);
|
|
183
|
+
setNote(false);
|
|
184
|
+
setError("");
|
|
185
|
+
callAuxiliaryRpc(client, "defaults.set", {
|
|
186
|
+
model: model ?? {
|
|
187
|
+
provider: "",
|
|
188
|
+
model: ""
|
|
189
|
+
},
|
|
190
|
+
expectedRevision: settings.revision
|
|
191
|
+
}).then((next) => {
|
|
192
|
+
if (state.active && state.epoch === epoch) {
|
|
193
|
+
setSettings(next);
|
|
194
|
+
setNote(true);
|
|
195
|
+
refreshCatalog();
|
|
196
|
+
}
|
|
197
|
+
}).catch((reason) => {
|
|
198
|
+
if (state.active && state.epoch === epoch) setError(isStaleRejection(reason) ? "stale" : "saveFailed");
|
|
199
|
+
}).finally(() => {
|
|
200
|
+
if (!state.active || state.epoch !== epoch) return;
|
|
201
|
+
state.writing = false;
|
|
202
|
+
setBusy(false);
|
|
203
|
+
if (state.pending) refreshRef.current();
|
|
204
|
+
});
|
|
205
|
+
}, [
|
|
206
|
+
client,
|
|
207
|
+
settings,
|
|
208
|
+
refreshCatalog
|
|
209
|
+
]);
|
|
210
|
+
const locked = busy;
|
|
211
|
+
if (!settings) return /* @__PURE__ */ jsxs("section", {
|
|
212
|
+
className: "ams-root dsh-ui-card",
|
|
213
|
+
"data-testid": "auxiliary-model-settings",
|
|
214
|
+
children: [
|
|
215
|
+
/* @__PURE__ */ jsx("span", {
|
|
216
|
+
className: "dsh-ui-heading",
|
|
217
|
+
children: text.title
|
|
218
|
+
}),
|
|
219
|
+
/* @__PURE__ */ jsx("p", {
|
|
220
|
+
className: "dsh-ui-help",
|
|
221
|
+
children: text.help
|
|
222
|
+
}),
|
|
223
|
+
error ? /* @__PURE__ */ jsx("p", {
|
|
224
|
+
role: "alert",
|
|
225
|
+
className: "dsh-ui-error",
|
|
226
|
+
children: text[error]
|
|
227
|
+
}) : /* @__PURE__ */ jsx("p", {
|
|
228
|
+
role: "status",
|
|
229
|
+
className: "dsh-ui-hint",
|
|
230
|
+
children: text.loading
|
|
231
|
+
}),
|
|
232
|
+
syncError && /* @__PURE__ */ jsx("p", {
|
|
233
|
+
role: "status",
|
|
234
|
+
className: "dsh-ui-hint",
|
|
235
|
+
children: text.syncUnavailable
|
|
236
|
+
}),
|
|
237
|
+
/* @__PURE__ */ jsx("div", {
|
|
238
|
+
className: "dsh-ui-actions",
|
|
239
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
240
|
+
variant: "ghost",
|
|
241
|
+
size: "sm",
|
|
242
|
+
disabled: locked,
|
|
243
|
+
onClick: () => refreshRef.current(),
|
|
244
|
+
children: text.retry
|
|
245
|
+
})
|
|
246
|
+
})
|
|
247
|
+
]
|
|
248
|
+
});
|
|
249
|
+
const current = settings.model ?? {
|
|
250
|
+
provider: "",
|
|
251
|
+
model: ""
|
|
252
|
+
};
|
|
253
|
+
const follow = !current.provider || !current.model;
|
|
254
|
+
const selected = follow ? void 0 : choices.find((item) => item.provider === current.provider && item.model === current.model);
|
|
255
|
+
const efforts = follow ? [] : modelMenuEffortOptions(selected, current.reasoningEffort);
|
|
256
|
+
const currentLabel = follow ? text.modelDefault : selected?.label ?? `${current.provider} / ${current.model}`;
|
|
257
|
+
const selectedEffort = efforts.find((item) => item.id === (current.reasoningEffort || ""));
|
|
258
|
+
const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, language) : void 0;
|
|
259
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
260
|
+
className: "ams-root dsh-ui-card",
|
|
261
|
+
"data-testid": "auxiliary-model-settings",
|
|
262
|
+
children: [
|
|
263
|
+
/* @__PURE__ */ jsx("span", {
|
|
264
|
+
className: "dsh-ui-heading",
|
|
265
|
+
children: text.title
|
|
266
|
+
}),
|
|
267
|
+
/* @__PURE__ */ jsx("p", {
|
|
268
|
+
className: "dsh-ui-help",
|
|
269
|
+
children: text.help
|
|
270
|
+
}),
|
|
271
|
+
/* @__PURE__ */ jsxs("div", {
|
|
272
|
+
className: "dsh-ui-field",
|
|
273
|
+
children: [
|
|
274
|
+
/* @__PURE__ */ jsx("span", {
|
|
275
|
+
className: "dsh-ui-label",
|
|
276
|
+
children: text.model
|
|
277
|
+
}),
|
|
278
|
+
/* @__PURE__ */ jsx(ModelCatalogStatus, {
|
|
279
|
+
state: catalogState,
|
|
280
|
+
locale: language
|
|
281
|
+
}),
|
|
282
|
+
/* @__PURE__ */ jsx(ModelMenu, {
|
|
283
|
+
locale: language,
|
|
284
|
+
open: menuOpen && !locked,
|
|
285
|
+
onOpenChange: (next) => setMenuOpen(next && !locked),
|
|
286
|
+
listClassName: "ams-root ams-menu-portal",
|
|
287
|
+
anchor: /* @__PURE__ */ jsxs("button", {
|
|
288
|
+
type: "button",
|
|
289
|
+
className: "dsh-model-menu-trigger",
|
|
290
|
+
disabled: locked,
|
|
291
|
+
"aria-label": `${text.model}: ${currentLabel}`,
|
|
292
|
+
children: [
|
|
293
|
+
/* @__PURE__ */ jsx("span", {
|
|
294
|
+
className: "dsh-model-menu-triggerLabel",
|
|
295
|
+
children: currentLabel
|
|
296
|
+
}),
|
|
297
|
+
!follow && efforts.length ? /* @__PURE__ */ jsx("span", {
|
|
298
|
+
className: "dsh-model-menu-triggerEffort",
|
|
299
|
+
children: effortName ?? text.effortDefault
|
|
300
|
+
}) : null,
|
|
301
|
+
/* @__PURE__ */ jsx(IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen && !locked ? " dsh-model-menu-chevronOpen" : ""}` })
|
|
302
|
+
]
|
|
303
|
+
}),
|
|
304
|
+
choices,
|
|
305
|
+
selected: follow ? void 0 : {
|
|
306
|
+
provider: current.provider,
|
|
307
|
+
model: current.model
|
|
308
|
+
},
|
|
309
|
+
onPick: (picked) => save({
|
|
310
|
+
provider: picked.provider,
|
|
311
|
+
model: picked.model
|
|
312
|
+
}),
|
|
313
|
+
leading: [{
|
|
314
|
+
id: "default",
|
|
315
|
+
label: text.modelDefault,
|
|
316
|
+
selected: follow
|
|
317
|
+
}],
|
|
318
|
+
onPickLeading: () => save(null),
|
|
319
|
+
efforts,
|
|
320
|
+
selectedEffort: current.reasoningEffort || "",
|
|
321
|
+
onPickEffort: (effort) => save(follow ? null : {
|
|
322
|
+
provider: current.provider,
|
|
323
|
+
model: current.model,
|
|
324
|
+
...effort ? { reasoningEffort: effort } : {}
|
|
325
|
+
}),
|
|
326
|
+
modelLabel: text.model,
|
|
327
|
+
modelValue: currentLabel,
|
|
328
|
+
defaultEffortLabel: text.effortDefault
|
|
329
|
+
}),
|
|
330
|
+
/* @__PURE__ */ jsx("span", {
|
|
331
|
+
className: "dsh-ui-hint",
|
|
332
|
+
children: text.modelHint
|
|
333
|
+
})
|
|
334
|
+
]
|
|
335
|
+
}),
|
|
336
|
+
/* @__PURE__ */ jsxs("div", {
|
|
337
|
+
className: "ams-row",
|
|
338
|
+
children: [
|
|
339
|
+
syncError && /* @__PURE__ */ jsx("p", {
|
|
340
|
+
role: "status",
|
|
341
|
+
className: "dsh-ui-hint",
|
|
342
|
+
children: text.syncUnavailable
|
|
343
|
+
}),
|
|
344
|
+
error && /* @__PURE__ */ jsx("p", {
|
|
345
|
+
role: "alert",
|
|
346
|
+
className: "dsh-ui-error",
|
|
347
|
+
children: text[error]
|
|
348
|
+
}),
|
|
349
|
+
note && /* @__PURE__ */ jsx("p", {
|
|
350
|
+
role: "status",
|
|
351
|
+
className: "dsh-ui-notice",
|
|
352
|
+
children: text.saved
|
|
353
|
+
}),
|
|
354
|
+
error || syncError ? /* @__PURE__ */ jsx(Button, {
|
|
355
|
+
variant: "ghost",
|
|
356
|
+
size: "sm",
|
|
357
|
+
disabled: locked,
|
|
358
|
+
onClick: () => refreshRef.current(),
|
|
359
|
+
children: text.refresh
|
|
360
|
+
}) : null
|
|
361
|
+
]
|
|
362
|
+
})
|
|
363
|
+
]
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
const AUXILIARY_MODEL_WATCH_COORDINATOR = "dshAuxiliaryModelWatchCoordinator";
|
|
367
|
+
function acquireWatchCoordinator(root) {
|
|
368
|
+
const existing = root.get(AUXILIARY_MODEL_WATCH_COORDINATOR, false);
|
|
369
|
+
if (existing) return existing;
|
|
370
|
+
let unprovide;
|
|
371
|
+
const coordinator = {
|
|
372
|
+
tail: Promise.resolve(),
|
|
373
|
+
active: 0,
|
|
374
|
+
mount() {
|
|
375
|
+
coordinator.active += 1;
|
|
376
|
+
const previousTail = coordinator.tail;
|
|
377
|
+
const lease = {
|
|
378
|
+
closed: false,
|
|
379
|
+
done() {
|
|
380
|
+
if (lease.closed) return;
|
|
381
|
+
lease.closed = true;
|
|
382
|
+
coordinator.active -= 1;
|
|
383
|
+
const owned = lease.fiber;
|
|
384
|
+
lease.fiber = void 0;
|
|
385
|
+
coordinator.tail = coordinator.tail.then(() => Promise.resolve(owned?.dispose()).catch(() => {}));
|
|
386
|
+
coordinator.tail.then(() => {
|
|
387
|
+
if (coordinator.active > 0) return;
|
|
388
|
+
try {
|
|
389
|
+
if (root.get(AUXILIARY_MODEL_WATCH_COORDINATOR, false) === coordinator) Promise.resolve(unprovide?.()).catch(() => {});
|
|
390
|
+
} catch {}
|
|
391
|
+
});
|
|
392
|
+
},
|
|
393
|
+
fiber: void 0
|
|
394
|
+
};
|
|
395
|
+
previousTail.then(() => {
|
|
396
|
+
if (lease.closed) return;
|
|
397
|
+
try {
|
|
398
|
+
lease.fiber = root.inject(["remote"], (injected) => {
|
|
399
|
+
const scope = injected;
|
|
400
|
+
const mounting = scope.remote.$mount(modelSettingsChangesRemote(AUXILIARY_MODEL_PACKAGE, AUXILIARY_MODEL_CHANGES_IDENTITY));
|
|
401
|
+
mounting.catch(() => {});
|
|
402
|
+
scope.effect(() => async () => {
|
|
403
|
+
await (await mounting.catch(() => void 0))?.();
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
} catch {}
|
|
407
|
+
});
|
|
408
|
+
return lease;
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
unprovide = root.provide(AUXILIARY_MODEL_WATCH_COORDINATOR, coordinator);
|
|
412
|
+
return coordinator;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Acquire the root-owned card service, creating it on first use. One counter
|
|
416
|
+
* covers participants, the seat wait and the change-stream mount: the last
|
|
417
|
+
* release removes the wait, then the watch contribution, then the provider
|
|
418
|
+
* itself synchronously (Cordis deletes the impl before its async disposer
|
|
419
|
+
* awaits), so a later acquire sees a clean get() miss and can re-establish.
|
|
420
|
+
*/
|
|
421
|
+
function acquireAuxiliaryModelCard(root, ctx) {
|
|
422
|
+
const existing = root.get(AUXILIARY_MODEL_CARD_SERVICE, false);
|
|
423
|
+
if (existing) return existing;
|
|
424
|
+
let users = 0;
|
|
425
|
+
let styleElement;
|
|
426
|
+
let stopItem;
|
|
427
|
+
let unprovide;
|
|
428
|
+
const watchLease = acquireWatchCoordinator(root).mount();
|
|
429
|
+
const subscriber = createModelSettingsSubscriber(ctx.root, AUXILIARY_MODEL_PACKAGE, AUXILIARY_MODEL_CHANGES_IDENTITY, (ns) => ns === AUXILIARY_MODEL_NS);
|
|
430
|
+
const render = () => /* @__PURE__ */ jsx(AuxiliaryModelCard, {
|
|
431
|
+
client: root,
|
|
432
|
+
subscribe: subscriber.subscribe
|
|
433
|
+
});
|
|
434
|
+
const service = {
|
|
435
|
+
retain() {
|
|
436
|
+
users += 1;
|
|
437
|
+
if (styleElement === void 0 && typeof document !== "undefined") {
|
|
438
|
+
styleElement = document.createElement("style");
|
|
439
|
+
styleElement.setAttribute("data-plugin", "dsh-auxiliary-model-shared");
|
|
440
|
+
styleElement.textContent = auxiliaryModelCss;
|
|
441
|
+
document.head.appendChild(styleElement);
|
|
442
|
+
}
|
|
443
|
+
if (!stopItem) stopItem = registerSessionSettingsItem(ctx.root, {
|
|
444
|
+
id: AUXILIARY_MODEL_SEAT_ID,
|
|
445
|
+
order: 55
|
|
446
|
+
}, render);
|
|
447
|
+
},
|
|
448
|
+
release() {
|
|
449
|
+
if (users <= 0) return;
|
|
450
|
+
users -= 1;
|
|
451
|
+
if (users > 0) return;
|
|
452
|
+
stopItem?.();
|
|
453
|
+
stopItem = void 0;
|
|
454
|
+
styleElement?.remove();
|
|
455
|
+
styleElement = void 0;
|
|
456
|
+
watchLease.done();
|
|
457
|
+
try {
|
|
458
|
+
Promise.resolve(unprovide?.()).catch(() => {});
|
|
459
|
+
} catch {}
|
|
460
|
+
unprovide = void 0;
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
unprovide = root.provide(AUXILIARY_MODEL_CARD_SERVICE, service);
|
|
464
|
+
return service;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Show the shared "default auxiliary model" card while the calling plugin is
|
|
468
|
+
* enabled.
|
|
469
|
+
*
|
|
470
|
+
* `ctx` is the plugin's own client context: the card itself is retained on the
|
|
471
|
+
* shared root context for as long as any participant lives, so an arbitrary
|
|
472
|
+
* subset of enabled plugins yields the same single card. The mount's slot wait
|
|
473
|
+
* and the change-stream contribution are owned by the root service — the first
|
|
474
|
+
* participant's unload cannot take the card down. Returns a disposer; it may
|
|
475
|
+
* be invoked by both ctx.effect and an explicit caller and releases at most
|
|
476
|
+
* once, so a double call cannot unbalance the shared refcount.
|
|
477
|
+
*/
|
|
478
|
+
function registerAuxiliaryModelSettings(ctx) {
|
|
479
|
+
const root = ctx.root;
|
|
480
|
+
const service = acquireAuxiliaryModelCard(root, ctx);
|
|
481
|
+
try {
|
|
482
|
+
service.retain();
|
|
483
|
+
} catch (error) {
|
|
484
|
+
service.release();
|
|
485
|
+
throw error;
|
|
486
|
+
}
|
|
487
|
+
let disposed = false;
|
|
488
|
+
return () => {
|
|
489
|
+
if (disposed) return;
|
|
490
|
+
disposed = true;
|
|
491
|
+
service.release();
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
//#endregion
|
|
495
|
+
export { AUXILIARY_MODEL_SEAT_ID, AUXILIARY_MODEL_SEAT_ORDER, AuxiliaryModelCard, auxiliaryModelCss, registerAuxiliaryModelSettings };
|
|
496
|
+
|
|
497
|
+
//# sourceMappingURL=auxiliary-model-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auxiliary-model-client.js","names":[],"sources":["../src/auxiliary-model-client.tsx"],"sourcesContent":["/**\n * Shared \"default auxiliary model\" settings card.\n *\n * Any number of feature plugins call `registerAuxiliaryModelSettings(ctx)`\n * from their client half; a single card appears as the `auxiliary-model` row\n * of the shared Session Management settings column for as long as at least\n * one participant lives. The card is root-owned shared infrastructure, not a\n * business plugin: every bundled copy of this helper rendezvouses on the same\n * Cordis root service, so no participant depends on another being enabled, the\n * card is mounted once (duplicate row ids would throw), and the last\n * `release()` removes it. The card also retains the shared page itself, so a\n * lone auxiliary plugin still yields the column — participants never depend\n * on another business plugin providing their settings page.\n *\n * The storage side lives in the shared host service behind the plugin-kit RPC\n * channel: `defaults.get` returns `{revision, model}` and `defaults.set`\n * carries `{model, expectedRevision}` with an empty provider/model clearing\n * the override. Cross-window invalidation rides the existing model-settings\n * change stream — no polling is added anywhere in this file.\n */\nimport type { Context } from '@deepseek-ai/cordis'\nimport { Button, IconChevronDownOutlineRegular } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { useCallback, useEffect, useRef, useState, useSyncExternalStore, type ReactNode } from 'react'\nimport {\n readModelCatalog, modelMenuEffortLabel, modelMenuEffortOptions, parseModelMenuChoices, type ModelRoute,\n} from '@klarkxy/dsh-model-route'\nimport { ModelCatalogStatus, ModelMenu, modelMenuCss, useModelCatalog } from '@klarkxy/dsh-model-route/ui'\nimport { officialUiCss } from './official-ui.ts'\nimport {\n createModelSettingsSubscriber, modelSettingsChangesRemote, type ModelSettingsSubscriber,\n} from './model-settings-client.ts'\nimport { registerSessionSettingsItem } from './client-utils.ts'\nimport {\n AUXILIARY_MODEL_CHANNEL, AUXILIARY_MODEL_CHANGES_IDENTITY, AUXILIARY_MODEL_NS,\n type AuxiliaryModelDefaults, type RpcResult,\n} from './contracts.ts'\n\nconst AUXILIARY_MODEL_PACKAGE = '@klarkxy/dsh-plugin-kit'\n\n/** The stored row; re-exported so card consumers share the contract shape. */\nexport type AuxiliaryModelSnapshot = AuxiliaryModelDefaults\n\n/* The card is one row of the shared Session Management column: the item slot\n * is a root-scope list declared by the shared page, and row ids must be\n * unique. The row sorts ahead of every business participant. */\nexport const AUXILIARY_MODEL_SEAT_ID = 'auxiliary-model'\nexport const AUXILIARY_MODEL_SEAT_ORDER = 55\n\n/** Private Cordis rendezvous; every bundled copy resolves the same root service. */\nconst AUXILIARY_MODEL_CARD_SERVICE = 'dshAuxiliaryModelSettingsCard'\n\n/** Theme-token-only stylesheet: officialUiCss scope plus the shared ModelMenu recipe. */\nexport const auxiliaryModelCss = `${officialUiCss('ams-root')}${modelMenuCss}\n.ams-root .ams-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }\n`\n\nconst auxiliaryCopy = {\n zh: {\n title: '默认辅助模型',\n help: '各辅助功能(如标题生成、摘要)没有单独指定模型时使用的模型;仅影响声明使用它的功能。',\n model: '辅助模型',\n modelDefault: '跟随会话或宿主默认',\n modelHint: '不选择模型时,各功能按自己的规则决定对话模型。',\n effortDefault: '跟随模型默认',\n loading: '正在读取设置…',\n failed: '无法读取模型设置。',\n stale: '设置已被其他窗口更新,请刷新后重试。',\n saveFailed: '保存失败,原设置未更改。请重试。',\n saved: '已保存。',\n retry: '重试',\n refresh: '刷新设置',\n syncUnavailable: '自动刷新不可用,请手动刷新设置。',\n },\n en: {\n title: 'Default Auxiliary Model',\n help: 'The model used by auxiliary features (such as title generation or summarizing) that do not name their own model; only features that opted in are affected.',\n model: 'Auxiliary model',\n modelDefault: 'Follow session or host default',\n modelHint: 'With no model selected, each feature follows its own rules for the conversation model.',\n effortDefault: 'Follow model default',\n loading: 'Loading settings…',\n failed: 'Could not load model settings.',\n stale: 'Settings were changed elsewhere; refresh and retry.',\n saveFailed: 'Save failed; the previous setting was not changed. Try again.',\n saved: 'Saved.',\n retry: 'Retry',\n refresh: 'Refresh settings',\n syncUnavailable: 'Automatic refresh unavailable; refresh settings manually.',\n },\n} as const\ntype AuxiliaryCopy = (typeof auxiliaryCopy)['zh'] | (typeof auxiliaryCopy)['en']\n\n/** A failed RPC keeps its code so the card can translate known rejections. */\nclass AuxiliaryRpcError extends Error {\n constructor(readonly code: string, message: string) { super(message); this.name = 'AuxiliaryRpcError' }\n}\n\n/** The client services this card reads; the shared service passes ctx.root. */\nexport interface AuxiliaryModelClient {\n connection: {\n rpc: { call(channel: string, endpoint: string, payload: unknown): Promise<unknown> }\n generation?: { subscribe(listener: () => void): () => void }\n }\n remote?: { session?: { modelCatalog?: () => Promise<unknown> } }\n locale?: { getSnapshot(): { active: string }; subscribe(listener: () => void): () => void }\n}\n\n/** Host UI language, following live switches with the host's English fallback. */\nfunction useAuxiliaryLocale(client: AuxiliaryModelClient): 'zh' | 'en' {\n const subscribe = useCallback((listener: () => void) => client.locale?.subscribe(listener) ?? (() => {}), [client])\n const snapshot = useCallback(() => client.locale?.getSnapshot().active ?? 'en', [client])\n return String(useSyncExternalStore(subscribe, snapshot, snapshot)).startsWith('zh') ? 'zh' : 'en'\n}\n\nfunction isAbort(error: unknown): boolean {\n return error instanceof Error && (error.name === 'AbortError' || /abort|cancel/i.test(error.message))\n}\n\n/* A revision mismatch is a lost race, not a broken save: the stored row moved\n * past the one the user edited. The host reports it as auxiliary-model/conflict;\n * the message fallback covers older hosts that surfaced it as internal. */\nfunction isStaleRejection(error: unknown): boolean {\n if (!(error instanceof AuxiliaryRpcError)) return false\n return error.code === 'stale' || error.code === 'auxiliary-model/conflict'\n || /changed|revision|reload|stale|conflict/i.test(error.message)\n}\n\nasync function callAuxiliaryRpc<T>(\n client: AuxiliaryModelClient, endpoint: string, payload: unknown = {},\n): Promise<T> {\n const result = await client.connection.rpc.call(AUXILIARY_MODEL_CHANNEL, endpoint, payload) as RpcResult<T> | undefined\n if (!result || result.ok !== true) {\n const error = result && 'error' in result ? result.error : undefined\n throw new AuxiliaryRpcError(error?.code ?? 'unknown', error?.message ?? `RPC ${endpoint} failed`)\n }\n return result.value\n}\n\n/**\n * The shared card. All state comes from the shared host service over the\n * plugin-kit RPC channel; the card only renders the stored row.\n */\nexport function AuxiliaryModelCard({ client, subscribe }: {\n client: AuxiliaryModelClient\n /** Settings-change invalidation; injected for tests without a live stream. */\n subscribe?: (change: () => void, error?: (message: string) => void) => () => void\n}): ReactNode {\n const language = useAuxiliaryLocale(client)\n const text: AuxiliaryCopy = auxiliaryCopy[language]\n const [settings, setSettings] = useState<AuxiliaryModelSnapshot>()\n const loadCatalog = useCallback(() => readModelCatalog(client), [client])\n const subscribeCatalog = useCallback(\n (change: () => void) => client.connection.generation?.subscribe(change), [client])\n const catalogState = useModelCatalog(loadCatalog, { subscribe: subscribeCatalog })\n const { refresh: refreshCatalog } = catalogState\n const choices = parseModelMenuChoices(catalogState.catalog, settings?.model ?? undefined)\n const [menuOpen, setMenuOpen] = useState(false)\n const [error, setError] = useState<'' | 'failed' | 'stale' | 'saveFailed'>('')\n const [note, setNote] = useState(false)\n const [busy, setBusy] = useState(false)\n const [syncError, setSyncError] = useState('')\n const operation = useRef({ epoch: 0, active: false, reading: false, writing: false, pending: false })\n const refreshRef = useRef<() => void>(() => {})\n\n const refresh = () => {\n const state = operation.current\n if (!state.active) return\n if (state.reading || state.writing) { state.pending = true; return }\n state.pending = false; state.reading = true; setBusy(true)\n const epoch = state.epoch\n void callAuxiliaryRpc<AuxiliaryModelSnapshot>(client, 'defaults.get').then(next => {\n if (state.active && state.epoch === epoch && !state.pending) {\n setSettings(next); setError('')\n }\n }).catch(reason => {\n if (state.active && state.epoch === epoch && !state.pending && !isAbort(reason)) setError('failed')\n }).finally(() => {\n if (!state.active || state.epoch !== epoch) return\n state.reading = false; setBusy(false)\n if (state.pending) refreshRef.current()\n })\n }\n refreshRef.current = refresh\n useEffect(() => {\n const state = operation.current\n state.epoch++; state.active = true; state.reading = false; state.writing = false; state.pending = false\n setBusy(false); setSettings(undefined); setError(''); setNote(false)\n refreshRef.current()\n return () => { state.active = false; state.epoch++ }\n }, [client])\n // Cross-window invalidation rides the shared model-settings change stream.\n useEffect(() => {\n setSyncError('')\n let live = true\n let stop: (() => void) | undefined\n try {\n stop = subscribe?.(() => {\n if (live) { setSyncError(''); refreshCatalog(); refreshRef.current() }\n }, message => { if (live) setSyncError(message) })\n } catch {\n // A subscriber that cannot even attach degrades to manual refresh.\n if (live) setSyncError('Model settings sync service unavailable')\n }\n return () => { live = false; try { stop?.() } catch { /* detach is best-effort */ } }\n }, [subscribe, refreshCatalog])\n\n const save = useCallback((model: ModelRoute | null) => {\n const state = operation.current\n if (!settings || !state.active || state.writing || state.reading) return\n state.writing = true\n const epoch = state.epoch\n setBusy(true); setNote(false); setError('')\n void callAuxiliaryRpc<AuxiliaryModelSnapshot>(client, 'defaults.set', {\n model: model ?? { provider: '', model: '' },\n expectedRevision: settings.revision,\n }).then(next => {\n if (state.active && state.epoch === epoch) { setSettings(next); setNote(true); refreshCatalog() }\n }).catch(reason => {\n if (state.active && state.epoch === epoch) {\n setError(isStaleRejection(reason) ? 'stale' : 'saveFailed')\n }\n }).finally(() => {\n if (!state.active || state.epoch !== epoch) return\n state.writing = false; setBusy(false)\n if (state.pending) refreshRef.current()\n })\n }, [client, settings, refreshCatalog])\n\n const locked = busy\n if (!settings) {\n return <section className=\"ams-root dsh-ui-card\" data-testid=\"auxiliary-model-settings\">\n <span className=\"dsh-ui-heading\">{text.title}</span>\n <p className=\"dsh-ui-help\">{text.help}</p>\n {error\n ? <p role=\"alert\" className=\"dsh-ui-error\">{text[error]}</p>\n : <p role=\"status\" className=\"dsh-ui-hint\">{text.loading}</p>}\n {syncError && <p role=\"status\" className=\"dsh-ui-hint\">{text.syncUnavailable}</p>}\n <div className=\"dsh-ui-actions\">\n <Button variant=\"ghost\" size=\"sm\" disabled={locked} onClick={() => refreshRef.current()}>{text.retry}</Button>\n </div>\n </section>\n }\n\n const current = settings.model ?? { provider: '', model: '' }\n const follow = !current.provider || !current.model\n const selected = follow ? undefined : choices.find(item => item.provider === current.provider && item.model === current.model)\n const efforts = follow ? [] : modelMenuEffortOptions(selected, current.reasoningEffort)\n const currentLabel = follow ? text.modelDefault : selected?.label ?? `${current.provider} / ${current.model}`\n const selectedEffort = efforts.find(item => item.id === (current.reasoningEffort || ''))\n const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, language) : undefined\n\n return <section className=\"ams-root dsh-ui-card\" data-testid=\"auxiliary-model-settings\">\n <span className=\"dsh-ui-heading\">{text.title}</span>\n <p className=\"dsh-ui-help\">{text.help}</p>\n <div className=\"dsh-ui-field\">\n <span className=\"dsh-ui-label\">{text.model}</span>\n <ModelCatalogStatus state={catalogState} locale={language} />\n <ModelMenu\n locale={language}\n open={menuOpen && !locked}\n onOpenChange={next => setMenuOpen(next && !locked)}\n listClassName=\"ams-root ams-menu-portal\"\n anchor={<button type=\"button\" className=\"dsh-model-menu-trigger\" disabled={locked}\n aria-label={`${text.model}: ${currentLabel}`}>\n <span className=\"dsh-model-menu-triggerLabel\">{currentLabel}</span>\n {!follow && efforts.length ? <span className=\"dsh-model-menu-triggerEffort\">{effortName ?? text.effortDefault}</span> : null}\n <IconChevronDownOutlineRegular className={`dsh-model-menu-chevron${menuOpen && !locked ? ' dsh-model-menu-chevronOpen' : ''}`} />\n </button>}\n choices={choices}\n selected={follow ? undefined : { provider: current.provider, model: current.model }}\n onPick={picked => save({ provider: picked.provider, model: picked.model })}\n leading={[{ id: 'default', label: text.modelDefault, selected: follow }]}\n onPickLeading={() => save(null)}\n efforts={efforts}\n selectedEffort={current.reasoningEffort || ''}\n onPickEffort={effort => save(follow\n ? null\n : { provider: current.provider, model: current.model, ...(effort ? { reasoningEffort: effort } : {}) })}\n modelLabel={text.model}\n modelValue={currentLabel}\n defaultEffortLabel={text.effortDefault}\n />\n <span className=\"dsh-ui-hint\">{text.modelHint}</span>\n </div>\n <div className=\"ams-row\">\n {syncError && <p role=\"status\" className=\"dsh-ui-hint\">{text.syncUnavailable}</p>}\n {error && <p role=\"alert\" className=\"dsh-ui-error\">{text[error]}</p>}\n {note && <p role=\"status\" className=\"dsh-ui-notice\">{text.saved}</p>}\n {error || syncError ? (\n <Button variant=\"ghost\" size=\"sm\" disabled={locked} onClick={() => refreshRef.current()}>{text.refresh}</Button>\n ) : null}\n </div>\n </section>\n}\n\ninterface AuxiliaryRoot {\n get(name: string, strict?: boolean): unknown\n provide(name: string, value: unknown): () => void\n inject(deps: string[], callback: (scope: unknown) => void): AuxiliaryFiber\n}\ninterface AuxiliaryFiber {\n dispose(): void | Promise<void>\n}\ninterface AuxiliaryRemoteMountScope {\n remote: { $mount(contribution: unknown): Promise<unknown> }\n effect(cb: () => () => void | Promise<void>): unknown\n}\n\n/* The client-side stream descriptor mount, serialized through one root\n * coordinator. Its tail carries mounts and unmounts in strict order: a new\n * mount cannot start while a previous fiber's unmount is in flight, and a\n * lease that closes before its continuation runs never mounts at all. The\n * coordinator leaves the store only when the queue is drained and no lease\n * is active — a fast teardown→re-acquire chains onto the same tail instead\n * of hitting \"already registered\" or overlapping a second mount. */\ninterface AuxiliaryWatchLease {\n fiber: AuxiliaryFiber | undefined\n closed: boolean\n done(): void\n}\ninterface AuxiliaryWatchCoordinator {\n tail: Promise<void>\n active: number\n mount(): AuxiliaryWatchLease\n}\nconst AUXILIARY_MODEL_WATCH_COORDINATOR = 'dshAuxiliaryModelWatchCoordinator'\n\nfunction acquireWatchCoordinator(root: AuxiliaryRoot): AuxiliaryWatchCoordinator {\n const existing = root.get(AUXILIARY_MODEL_WATCH_COORDINATOR, false) as AuxiliaryWatchCoordinator | undefined\n if (existing) return existing\n let unprovide: (() => void) | undefined\n const coordinator: AuxiliaryWatchCoordinator = {\n tail: Promise.resolve(),\n active: 0,\n mount() {\n coordinator.active += 1\n const previousTail = coordinator.tail\n const lease: AuxiliaryWatchLease = {\n closed: false,\n done() {\n if (lease.closed) return\n lease.closed = true\n coordinator.active -= 1\n const owned = lease.fiber\n lease.fiber = undefined\n // The unmount joins the tail every later mount waits on.\n coordinator.tail = coordinator.tail\n .then(() => Promise.resolve(owned?.dispose()).catch(() => {}))\n // Unprovide only once the queue is really drained: a newer lease\n // already queued on this coordinator must keep it alive.\n void coordinator.tail.then(() => {\n if (coordinator.active > 0) return\n try {\n if (root.get(AUXILIARY_MODEL_WATCH_COORDINATOR, false) === coordinator) {\n void Promise.resolve(unprovide?.()).catch(() => {})\n }\n } catch { /* Root already unloading. */ }\n })\n },\n fiber: undefined,\n }\n void previousTail.then(() => {\n // A release that beat the mount ends here: nothing mounted, nothing\n // to unmount, and the tail already moved on.\n if (lease.closed) return\n // mountModelSettingsChanges performs this exact inject/$mount pair;\n // it is repeated here only because that helper returns void and the\n // fiber must be captured for symmetric teardown.\n try {\n lease.fiber = root.inject(['remote'], injected => {\n const scope = injected as AuxiliaryRemoteMountScope\n const mounting = scope.remote.$mount(\n modelSettingsChangesRemote(AUXILIARY_MODEL_PACKAGE, AUXILIARY_MODEL_CHANGES_IDENTITY),\n ) as Promise<(() => Promise<void>) | undefined>\n void mounting.catch(() => {})\n scope.effect(() => async () => {\n const unmount = await mounting.catch(() => undefined)\n await unmount?.()\n })\n })\n } catch { /* remote unavailable: the card degrades to manual refresh */ }\n })\n return lease\n },\n }\n unprovide = root.provide(AUXILIARY_MODEL_WATCH_COORDINATOR, coordinator)\n return coordinator\n}\n\ninterface AuxiliaryCardService {\n retain(): void\n release(): void\n}\n\n/**\n * Acquire the root-owned card service, creating it on first use. One counter\n * covers participants, the seat wait and the change-stream mount: the last\n * release removes the wait, then the watch contribution, then the provider\n * itself synchronously (Cordis deletes the impl before its async disposer\n * awaits), so a later acquire sees a clean get() miss and can re-establish.\n */\nfunction acquireAuxiliaryModelCard(root: AuxiliaryRoot, ctx: Context): AuxiliaryCardService {\n const existing = root.get(AUXILIARY_MODEL_CARD_SERVICE, false) as AuxiliaryCardService | undefined\n if (existing) return existing\n let users = 0\n let styleElement: HTMLStyleElement | undefined\n let stopItem: (() => void) | undefined\n let unprovide: (() => void) | undefined\n // The client-side stream descriptor hangs off the shared root context —\n // never a plugin fiber — so the first participant's unload cannot take the\n // card's change stream down. The coordinator still unmounts it, in tail\n // order, on the last release.\n const watchLease = acquireWatchCoordinator(root).mount()\n const subscriber: ModelSettingsSubscriber = createModelSettingsSubscriber(\n ctx.root as unknown as Parameters<typeof createModelSettingsSubscriber>[0],\n AUXILIARY_MODEL_PACKAGE,\n AUXILIARY_MODEL_CHANGES_IDENTITY,\n ns => ns === AUXILIARY_MODEL_NS,\n )\n const render = () => <AuxiliaryModelCard client={root as unknown as AuxiliaryModelClient}\n subscribe={subscriber.subscribe} />\n const service: AuxiliaryCardService = {\n retain() {\n // The reservation is counted before the throwing installs, so a failure\n // lets the caller's release() undo it fully instead of leaking the service.\n users += 1\n if (styleElement === undefined && typeof document !== 'undefined') {\n styleElement = document.createElement('style')\n styleElement.setAttribute('data-plugin', 'dsh-auxiliary-model-shared')\n styleElement.textContent = auxiliaryModelCss\n document.head.appendChild(styleElement)\n }\n /* The card is a participant of the shared column too: the root-owned\n * call contributes its row AND retains the page and nav icon, which is\n * what guarantees a lone auxiliary plugin still creates the Session\n * Management section — no business plugin has to provide it. The waits\n * sit on the root context, so they live exactly as long as the card. */\n if (!stopItem) {\n stopItem = registerSessionSettingsItem(ctx.root, {\n id: AUXILIARY_MODEL_SEAT_ID,\n order: AUXILIARY_MODEL_SEAT_ORDER,\n }, render)\n }\n },\n release() {\n if (users <= 0) return\n users -= 1\n if (users > 0) return\n stopItem?.()\n stopItem = undefined\n styleElement?.remove()\n styleElement = undefined\n watchLease.done()\n // The store entry leaves synchronously, like the shared page service:\n // the next acquire must not see a stale \"already registered\" marker.\n try { void Promise.resolve(unprovide?.()).catch(() => {}) } catch { /* Root already unloading. */ }\n unprovide = undefined\n },\n }\n unprovide = root.provide(AUXILIARY_MODEL_CARD_SERVICE, service)\n return service\n}\n\n/**\n * Show the shared \"default auxiliary model\" card while the calling plugin is\n * enabled.\n *\n * `ctx` is the plugin's own client context: the card itself is retained on the\n * shared root context for as long as any participant lives, so an arbitrary\n * subset of enabled plugins yields the same single card. The mount's slot wait\n * and the change-stream contribution are owned by the root service — the first\n * participant's unload cannot take the card down. Returns a disposer; it may\n * be invoked by both ctx.effect and an explicit caller and releases at most\n * once, so a double call cannot unbalance the shared refcount.\n */\nexport function registerAuxiliaryModelSettings(ctx: Context): () => void {\n const root = ctx.root as unknown as AuxiliaryRoot\n const service = acquireAuxiliaryModelCard(root, ctx)\n try {\n service.retain()\n } catch (error) {\n // A synchronous failure (for example a duplicate row id) must not pin the\n // shared card for a participant that never joined.\n service.release()\n throw error\n }\n let disposed = false\n return () => {\n if (disposed) return\n disposed = true\n service.release()\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getAuxiliaryModel } from "./contracts.js";
|
|
2
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
3
|
+
//#region src/auxiliary-model.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Retain the shared auxiliary-model store for the calling plugin.
|
|
6
|
+
*
|
|
7
|
+
* Resolves once storage is open and the read/write surfaces are live, with a
|
|
8
|
+
* release function; register it as `ctx.effect(() => release)` so the store
|
|
9
|
+
* unloads with the plugin. The first participant opens the shared owner
|
|
10
|
+
* fiber; `await release()` drains pending writes and fully tears the owner
|
|
11
|
+
* down when the last participant leaves. Requires `storageDomain`,
|
|
12
|
+
* `webServer` and `connection` to exist on the host.
|
|
13
|
+
*
|
|
14
|
+
* Concurrent retainers share one owner; a retain arriving during teardown
|
|
15
|
+
* waits for the closing owner and then reinstalls — it never inherits a
|
|
16
|
+
* half-torn-down state.
|
|
17
|
+
*/
|
|
18
|
+
declare function retainAuxiliaryModel(ctx: Context): Promise<() => Promise<void>>;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { getAuxiliaryModel, retainAuxiliaryModel };
|
|
21
|
+
//# sourceMappingURL=auxiliary-model.d.ts.map
|