@klarkxy/dsh-plugin-kit 0.6.1 → 0.7.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 CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [简体中文](docs/README.zh-CN.md)
6
6
 
7
- Shared runtime and UI support library for the DeepSeek Harness plugins in this repository — covering the default auxiliary model, shared session settings, storage and host-client helpers, official UI styling, and subagent session detection. Plugins that depend on it install it automatically; plugin developers should refer to the source code and type declarations.
7
+ Shared runtime and UI support library for the DeepSeek Harness plugins in this repository — covering the default auxiliary model, shared session settings, storage and host-client helpers, official UI styling, the optional shared model picker hookup, and subagent session detection. Plugins that depend on it install it automatically; plugin developers should refer to the source code and type declarations.
8
8
 
9
9
  ## License
10
10
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  [English](../README.md)
6
6
 
7
- 本仓库 DeepSeek Harness 插件共用的运行时与界面支撑库,提供默认辅助模型、共享会话设置页、存储与前后端通信辅助、官方 UI 样式及子代理会话识别等共享能力。依赖它的插件会自动安装;插件开发者请直接参考源码和类型声明。
7
+ 本仓库 DeepSeek Harness 插件共用的运行时与界面支撑库,提供默认辅助模型、共享会话设置页、存储与前后端通信辅助、官方 UI 样式、可选共享模型选择器接入及子代理会话识别等共享能力。依赖它的插件会自动安装;插件开发者请直接参考源码和类型声明。
8
8
 
9
9
  ## 许可证
10
10
 
@@ -1,3 +1,4 @@
1
+ import { RenderFactorySlot } from "./model-picker.js";
1
2
  import { AuxiliaryModelDefaults } from "./contracts.js";
2
3
  import { ReactNode } from "react";
3
4
  import { Context } from "@deepseek-ai/cordis";
@@ -34,10 +35,12 @@ interface AuxiliaryModelClient {
34
35
  * The shared card. All state comes from the shared host service over the
35
36
  * plugin-kit RPC channel; the card only renders the stored row.
36
37
  */
37
- declare function AuxiliaryModelCard({ client, subscribe }: {
38
+ declare function AuxiliaryModelCard({ client, subscribe, renderFactorySlot }: {
38
39
  client: AuxiliaryModelClient;
39
40
  /** Settings-change invalidation; injected for tests without a live stream. */
40
41
  subscribe?: (change: () => void, error?: (message: string) => void) => () => void;
42
+ /** Framework-composed factory renderer; absent under direct test renders. */
43
+ renderFactorySlot?: RenderFactorySlot;
41
44
  }): ReactNode;
42
45
  /**
43
46
  * Show the shared "default auxiliary model" card while the calling plugin is
@@ -1,12 +1,13 @@
1
1
  import { AUXILIARY_MODEL_CHANGES_IDENTITY, AUXILIARY_MODEL_CHANNEL, AUXILIARY_MODEL_NS } from "./contracts.js";
2
2
  import { createModelSettingsSubscriber, modelSettingsChangesRemote } from "./model-settings-client.js";
3
3
  import { registerSessionSettingsItem } from "./client-utils.js";
4
+ import { renderModelPicker, useModelMenuAnchor } from "./model-picker.js";
4
5
  import { officialUiCss } from "./official-ui.js";
5
6
  import { modelMenuEffortLabel, modelMenuEffortOptions, parseModelMenuChoices, readModelCatalog } from "@klarkxy/dsh-model-route";
6
7
  import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
7
8
  import { Button, IconChevronDownOutlineRegular } from "@deepseek-ai/dsh-client-ui-primitives";
8
- import { ModelCatalogStatus, ModelMenu, modelMenuCss, useModelCatalog } from "@klarkxy/dsh-model-route/ui";
9
9
  import { jsx, jsxs } from "react/jsx-runtime";
10
+ import { ModelCatalogStatus, ModelMenu, modelMenuCss, useModelCatalog } from "@klarkxy/dsh-model-route/ui";
10
11
  //#region src/auxiliary-model-client.tsx
11
12
  const AUXILIARY_MODEL_PACKAGE = "@klarkxy/dsh-plugin-kit";
12
13
  const AUXILIARY_MODEL_SEAT_ID = "auxiliary-model";
@@ -85,7 +86,7 @@ async function callAuxiliaryRpc(client, endpoint, payload = {}) {
85
86
  * The shared card. All state comes from the shared host service over the
86
87
  * plugin-kit RPC channel; the card only renders the stored row.
87
88
  */
88
- function AuxiliaryModelCard({ client, subscribe }) {
89
+ function AuxiliaryModelCard({ client, subscribe, renderFactorySlot }) {
89
90
  const language = useAuxiliaryLocale(client);
90
91
  const text = auxiliaryCopy[language];
91
92
  const [settings, setSettings] = useState();
@@ -208,6 +209,17 @@ function AuxiliaryModelCard({ client, subscribe }) {
208
209
  refreshCatalog
209
210
  ]);
210
211
  const locked = busy;
212
+ const current = settings?.model ?? {
213
+ provider: "",
214
+ model: ""
215
+ };
216
+ const follow = !current.provider || !current.model;
217
+ const selected = follow ? void 0 : choices.find((item) => item.provider === current.provider && item.model === current.model);
218
+ const efforts = follow ? [] : modelMenuEffortOptions(selected, current.reasoningEffort);
219
+ const currentLabel = follow ? text.modelDefault : selected?.label ?? `${current.provider} / ${current.model}`;
220
+ const selectedEffort = efforts.find((item) => item.id === (current.reasoningEffort || ""));
221
+ const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, language) : void 0;
222
+ const anchor = useModelMenuAnchor(`${text.model}: ${settings ? currentLabel : ""}`, void 0, () => setMenuOpen(false));
211
223
  if (!settings) return /* @__PURE__ */ jsxs("section", {
212
224
  className: "ams-root dsh-ui-card",
213
225
  "data-testid": "auxiliary-model-settings",
@@ -246,16 +258,6 @@ function AuxiliaryModelCard({ client, subscribe }) {
246
258
  })
247
259
  ]
248
260
  });
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
261
  return /* @__PURE__ */ jsxs("section", {
260
262
  className: "ams-root dsh-ui-card",
261
263
  "data-testid": "auxiliary-model-settings",
@@ -279,28 +281,12 @@ function AuxiliaryModelCard({ client, subscribe }) {
279
281
  state: catalogState,
280
282
  locale: language
281
283
  }),
282
- /* @__PURE__ */ jsx(ModelMenu, {
284
+ renderModelPicker(renderFactorySlot, {
283
285
  locale: language,
284
- open: menuOpen && !locked,
286
+ disabled: locked,
287
+ busy: locked,
285
288
  onOpenChange: (next) => setMenuOpen(next && !locked),
286
289
  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
290
  choices,
305
291
  selected: follow ? void 0 : {
306
292
  provider: current.provider,
@@ -323,9 +309,61 @@ function AuxiliaryModelCard({ client, subscribe }) {
323
309
  model: current.model,
324
310
  ...effort ? { reasoningEffort: effort } : {}
325
311
  }),
312
+ triggerLabel: currentLabel,
313
+ triggerEffortLabel: !follow && efforts.length ? effortName ?? text.effortDefault : void 0,
326
314
  modelLabel: text.model,
327
315
  modelValue: currentLabel,
328
316
  defaultEffortLabel: text.effortDefault
317
+ }, {
318
+ anchor,
319
+ fallback: /* @__PURE__ */ jsx(ModelMenu, {
320
+ locale: language,
321
+ open: menuOpen && !locked,
322
+ onOpenChange: (next) => setMenuOpen(next && !locked),
323
+ listClassName: "ams-root ams-menu-portal",
324
+ anchor: /* @__PURE__ */ jsxs("button", {
325
+ type: "button",
326
+ className: "dsh-model-menu-trigger",
327
+ disabled: locked,
328
+ "aria-label": `${text.model}: ${currentLabel}`,
329
+ children: [
330
+ /* @__PURE__ */ jsx("span", {
331
+ className: "dsh-model-menu-triggerLabel",
332
+ children: currentLabel
333
+ }),
334
+ !follow && efforts.length ? /* @__PURE__ */ jsx("span", {
335
+ className: "dsh-model-menu-triggerEffort",
336
+ children: effortName ?? text.effortDefault
337
+ }) : null,
338
+ /* @__PURE__ */ jsx(IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen && !locked ? " dsh-model-menu-chevronOpen" : ""}` })
339
+ ]
340
+ }),
341
+ choices,
342
+ selected: follow ? void 0 : {
343
+ provider: current.provider,
344
+ model: current.model
345
+ },
346
+ onPick: (picked) => save({
347
+ provider: picked.provider,
348
+ model: picked.model
349
+ }),
350
+ leading: [{
351
+ id: "default",
352
+ label: text.modelDefault,
353
+ selected: follow
354
+ }],
355
+ onPickLeading: () => save(null),
356
+ efforts,
357
+ selectedEffort: current.reasoningEffort || "",
358
+ onPickEffort: (effort) => save(follow ? null : {
359
+ provider: current.provider,
360
+ model: current.model,
361
+ ...effort ? { reasoningEffort: effort } : {}
362
+ }),
363
+ modelLabel: text.model,
364
+ modelValue: currentLabel,
365
+ defaultEffortLabel: text.effortDefault
366
+ })
329
367
  }),
330
368
  /* @__PURE__ */ jsx("span", {
331
369
  className: "dsh-ui-hint",
@@ -427,9 +465,10 @@ function acquireAuxiliaryModelCard(root, ctx) {
427
465
  let unprovide;
428
466
  const watchLease = acquireWatchCoordinator(root).mount();
429
467
  const subscriber = createModelSettingsSubscriber(ctx.root, AUXILIARY_MODEL_PACKAGE, AUXILIARY_MODEL_CHANGES_IDENTITY, (ns) => ns === AUXILIARY_MODEL_NS);
430
- const render = () => /* @__PURE__ */ jsx(AuxiliaryModelCard, {
468
+ const render = (props) => /* @__PURE__ */ jsx(AuxiliaryModelCard, {
431
469
  client: root,
432
- subscribe: subscriber.subscribe
470
+ subscribe: subscriber.subscribe,
471
+ renderFactorySlot: props.renderFactorySlot
433
472
  });
434
473
  const service = {
435
474
  retain() {
@@ -1 +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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"}
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 { renderModelPicker, useModelMenuAnchor, type RenderFactorySlot } from './model-picker.tsx'\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, renderFactorySlot }: {\n client: AuxiliaryModelClient\n /** Settings-change invalidation; injected for tests without a live stream. */\n subscribe?: (change: () => void, error?: (message: string) => void) => () => void\n /** Framework-composed factory renderer; absent under direct test renders. */\n renderFactorySlot?: RenderFactorySlot\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 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 // The shared picker's anchor mirrors the trigger the fallback menu renders.\n const anchor = useModelMenuAnchor(`${text.model}: ${settings ? currentLabel : ''}`, undefined,\n () => setMenuOpen(false))\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\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 {renderModelPicker(renderFactorySlot, {\n locale: language,\n disabled: locked,\n busy: locked,\n onOpenChange: next => setMenuOpen(next && !locked),\n listClassName: 'ams-root ams-menu-portal',\n 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,\n selectedEffort: current.reasoningEffort || '',\n onPickEffort: effort => save(follow\n ? null\n : { provider: current.provider, model: current.model, ...(effort ? { reasoningEffort: effort } : {}) }),\n triggerLabel: currentLabel,\n triggerEffortLabel: !follow && efforts.length ? (effortName ?? text.effortDefault) : undefined,\n modelLabel: text.model,\n modelValue: currentLabel,\n defaultEffortLabel: text.effortDefault,\n }, {\n anchor,\n fallback: <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 })}\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 = (props: { renderFactorySlot?: RenderFactorySlot }) => <AuxiliaryModelCard\n client={root as unknown as AuxiliaryModelClient}\n subscribe={subscriber.subscribe}\n renderFactorySlot={props.renderFactorySlot} />\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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"}
package/lib/domain.d.ts CHANGED
@@ -14,9 +14,12 @@ type PluginDomainHandle = {
14
14
  };
15
15
  /**
16
16
  * Open table-only plugin state using the host's lossless legacy bootstrap.
17
- * The JSON backend ignores compatibleVersions in single layout, and treats
18
- * unsupported/malformed legacy files as absent in per-record layout. Probe
19
- * an empty result so persistent settings cannot silently become defaults.
17
+ * The JSON backend treats unsupported/malformed legacy files as absent in
18
+ * per-record layout; the probe below opens `single` with compatibleVersions
19
+ * emptied so a legacy stamp reports `version-mismatch` (whether or not the
20
+ * installed backend accepts compatible stamps in single layout, which
21
+ * storage-json began doing at 0.2.2). Probe an empty result so persistent
22
+ * settings cannot silently become defaults.
20
23
  * A populated per-record tree is authoritative; the retained legacy source
21
24
  * is an initial backup, not a current-data rollback after subsequent writes.
22
25
  */
package/lib/domain.js CHANGED
@@ -1,9 +1,12 @@
1
1
  //#region src/domain.ts
2
2
  /**
3
3
  * Open table-only plugin state using the host's lossless legacy bootstrap.
4
- * The JSON backend ignores compatibleVersions in single layout, and treats
5
- * unsupported/malformed legacy files as absent in per-record layout. Probe
6
- * an empty result so persistent settings cannot silently become defaults.
4
+ * The JSON backend treats unsupported/malformed legacy files as absent in
5
+ * per-record layout; the probe below opens `single` with compatibleVersions
6
+ * emptied so a legacy stamp reports `version-mismatch` (whether or not the
7
+ * installed backend accepts compatible stamps in single layout, which
8
+ * storage-json began doing at 0.2.2). Probe an empty result so persistent
9
+ * settings cannot silently become defaults.
7
10
  * A populated per-record tree is authoritative; the retained legacy source
8
11
  * is an initial backup, not a current-data rollback after subsequent writes.
9
12
  */
package/lib/domain.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"domain.js","names":[],"sources":["../src/domain.ts"],"sourcesContent":["type PluginDomainSpec = {\n version: number\n compatibleVersions?: readonly number[]\n layout?: 'single' | 'per-record'\n tables: Record<string, unknown>\n global?: never\n}\n\ntype PluginDomainHandle = {\n table(name: string): { entries(): IterableIterator<[unknown, unknown]> }\n close(): Promise<void>\n}\n\n/**\n * Open table-only plugin state using the host's lossless legacy bootstrap.\n * The JSON backend ignores compatibleVersions in single layout, and treats\n * unsupported/malformed legacy files as absent in per-record layout. Probe\n * an empty result so persistent settings cannot silently become defaults.\n * A populated per-record tree is authoritative; the retained legacy source\n * is an initial backup, not a current-data rollback after subsequent writes.\n */\nexport async function openCompatibleDomain<S extends PluginDomainSpec, D extends PluginDomainHandle>(\n open: (spec: S) => Promise<D>, spec: S,\n): Promise<D> {\n const perRecord = { ...spec, layout: 'per-record' } as S\n const domain = await open(perRecord)\n let populated = false\n try {\n populated = Object.keys(spec.tables).some(table => !domain.table(table).entries().next().done)\n } finally {\n if (!populated) await domain.close()\n }\n if (populated) return domain\n\n let mismatch: unknown\n for (const version of new Set([spec.version, ...spec.compatibleVersions ?? []])) {\n try {\n const legacy = await open({ ...spec, layout: 'single', version, compatibleVersions: [] } as S)\n await legacy.close()\n return open(perRecord)\n } catch (error) {\n if (!error || typeof error !== 'object' || !('code' in error) || error.code !== 'version-mismatch') throw error\n mismatch = error\n }\n }\n throw mismatch\n}\n"],"mappings":";;;;;;;;;AAqBA,eAAsB,qBACpB,MAA+B,MACnB;CACZ,MAAM,YAAY;EAAE,GAAG;EAAM,QAAQ;CAAa;CAClD,MAAM,SAAS,MAAM,KAAK,SAAS;CACnC,IAAI,YAAY;CAChB,IAAI;EACF,YAAY,OAAO,KAAK,KAAK,MAAM,CAAC,CAAC,MAAK,UAAS,CAAC,OAAO,MAAM,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI;CAC/F,UAAU;EACR,IAAI,CAAC,WAAW,MAAM,OAAO,MAAM;CACrC;CACA,IAAI,WAAW,OAAO;CAEtB,IAAI;CACJ,KAAK,MAAM,2BAAW,IAAI,IAAI,CAAC,KAAK,SAAS,GAAG,KAAK,sBAAsB,CAAC,CAAC,CAAC,GAC5E,IAAI;EAEF,OAAM,MADe,KAAK;GAAE,GAAG;GAAM,QAAQ;GAAU;GAAS,oBAAoB,CAAC;EAAE,CAAM,EAAA,CAChF,MAAM;EACnB,OAAO,KAAK,SAAS;CACvB,SAAS,OAAO;EACd,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,EAAE,UAAU,UAAU,MAAM,SAAS,oBAAoB,MAAM;EAC1G,WAAW;CACb;CAEF,MAAM;AACR"}
1
+ {"version":3,"file":"domain.js","names":[],"sources":["../src/domain.ts"],"sourcesContent":["type PluginDomainSpec = {\n version: number\n compatibleVersions?: readonly number[]\n layout?: 'single' | 'per-record'\n tables: Record<string, unknown>\n global?: never\n}\n\ntype PluginDomainHandle = {\n table(name: string): { entries(): IterableIterator<[unknown, unknown]> }\n close(): Promise<void>\n}\n\n/**\n * Open table-only plugin state using the host's lossless legacy bootstrap.\n * The JSON backend treats unsupported/malformed legacy files as absent in\n * per-record layout; the probe below opens `single` with compatibleVersions\n * emptied so a legacy stamp reports `version-mismatch` (whether or not the\n * installed backend accepts compatible stamps in single layout, which\n * storage-json began doing at 0.2.2). Probe an empty result so persistent\n * settings cannot silently become defaults.\n * A populated per-record tree is authoritative; the retained legacy source\n * is an initial backup, not a current-data rollback after subsequent writes.\n */\nexport async function openCompatibleDomain<S extends PluginDomainSpec, D extends PluginDomainHandle>(\n open: (spec: S) => Promise<D>, spec: S,\n): Promise<D> {\n const perRecord = { ...spec, layout: 'per-record' } as S\n const domain = await open(perRecord)\n let populated = false\n try {\n populated = Object.keys(spec.tables).some(table => !domain.table(table).entries().next().done)\n } finally {\n if (!populated) await domain.close()\n }\n if (populated) return domain\n\n let mismatch: unknown\n for (const version of new Set([spec.version, ...spec.compatibleVersions ?? []])) {\n try {\n const legacy = await open({ ...spec, layout: 'single', version, compatibleVersions: [] } as S)\n await legacy.close()\n return open(perRecord)\n } catch (error) {\n if (!error || typeof error !== 'object' || !('code' in error) || error.code !== 'version-mismatch') throw error\n mismatch = error\n }\n }\n throw mismatch\n}\n"],"mappings":";;;;;;;;;;;;AAwBA,eAAsB,qBACpB,MAA+B,MACnB;CACZ,MAAM,YAAY;EAAE,GAAG;EAAM,QAAQ;CAAa;CAClD,MAAM,SAAS,MAAM,KAAK,SAAS;CACnC,IAAI,YAAY;CAChB,IAAI;EACF,YAAY,OAAO,KAAK,KAAK,MAAM,CAAC,CAAC,MAAK,UAAS,CAAC,OAAO,MAAM,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI;CAC/F,UAAU;EACR,IAAI,CAAC,WAAW,MAAM,OAAO,MAAM;CACrC;CACA,IAAI,WAAW,OAAO;CAEtB,IAAI;CACJ,KAAK,MAAM,2BAAW,IAAI,IAAI,CAAC,KAAK,SAAS,GAAG,KAAK,sBAAsB,CAAC,CAAC,CAAC,GAC5E,IAAI;EAEF,OAAM,MADe,KAAK;GAAE,GAAG;GAAM,QAAQ;GAAU;GAAS,oBAAoB,CAAC;EAAE,CAAM,EAAA,CAChF,MAAM;EACnB,OAAO,KAAK,SAAS;CACvB,SAAS,OAAO;EACd,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,EAAE,UAAU,UAAU,MAAM,SAAS,oBAAoB,MAAM;EAC1G,WAAW;CACb;CAEF,MAAM;AACR"}
package/lib/llm-call.d.ts CHANGED
@@ -21,7 +21,8 @@ interface LlmTextRequest {
21
21
  route: FeatureModelRoute;
22
22
  system: string;
23
23
  text: string;
24
- maxTokens: number;
24
+ /** Omit to use the selected model's normal output settings. */
25
+ maxTokens?: number;
25
26
  signal: AbortSignal;
26
27
  /** The real session id for host attribution; never a feature-scoped alias. */
27
28
  sessionId?: string;
package/lib/llm-call.js CHANGED
@@ -77,14 +77,15 @@ async function callLlmText(llm, request) {
77
77
  provider: request.route.provider,
78
78
  model: request.route.model,
79
79
  ...effort ? { reasoningEffort: ReasoningEffortId(effort) } : {},
80
- maxTokens: request.maxTokens
80
+ ...request.maxTokens !== void 0 ? { maxTokens: request.maxTokens } : {}
81
81
  }, request.signal, request.configure);
82
82
  if (!live()) throw Object.assign(/* @__PURE__ */ new Error("调用已取消。"), { name: "AbortError" });
83
+ const maxTokens = request.maxTokens === void 0 ? prepared.config.maxTokens : Math.min(request.maxTokens, prepared.config.maxTokens ?? request.maxTokens);
83
84
  const options = {
84
85
  provider: prepared.config.provider,
85
86
  model: prepared.config.model,
86
87
  ...prepared.config.reasoningEffort ? { reasoningEffort: prepared.config.reasoningEffort } : {},
87
- ...prepared.config.maxTokens !== void 0 ? { maxTokens: Math.min(request.maxTokens, prepared.config.maxTokens) } : { maxTokens: request.maxTokens },
88
+ ...maxTokens !== void 0 ? { maxTokens } : {},
88
89
  ...prepared.config.temperature !== void 0 ? { temperature: prepared.config.temperature } : {},
89
90
  ...prepared.config.stop ? { stop: [...prepared.config.stop] } : {},
90
91
  system: request.system,
@@ -1 +1 @@
1
- {"version":3,"file":"llm-call.js","names":[],"sources":["../src/llm-call.ts"],"sourcesContent":["import {\n BlockAssembler, ReasoningEffortId, createUserMessage,\n type GenerateOptions, type LlmRuntime, type StreamChunk,\n} from '@deepseek-ai/dsh-llm'\nimport { getAuxiliaryModel, producerMessageSource, type ModelRoute } from './contracts.ts'\nimport { buildAuxiliaryUserText } from './auxiliary-context.ts'\n\n/** A feature's own saved model. Empty means the host default chat model. */\nexport interface FeatureModelRoute {\n readonly provider: string\n readonly model: string\n readonly reasoningEffort?: string\n}\n\nexport type LlmTextCaller = Pick<LlmRuntime, 'prepareCall' | 'resolveCallConfig'>\n\nexport interface LlmTextRequest {\n plugin: string\n /** Optional feature id, stamped on the context envelope for attribution. */\n feature?: string\n /**\n * Stable material rebuilt from current facts (session events, durable\n * records). Packed before the live text so provider-side caching reuses\n * the prefix; never model outputs or approval history. Strings or\n * pre-ordered segments.\n */\n context?: string | readonly string[]\n route: FeatureModelRoute\n system: string\n text: string\n maxTokens: number\n signal: AbortSignal\n /** The real session id for host attribution; never a feature-scoped alias. */\n sessionId?: string\n /** Auxiliary-call marker adapters may special-case (e.g. disable thinking for titles). */\n purpose?: GenerateOptions['purpose']\n /** Pure control selection using the exact model captured for this call. */\n configure?: Parameters<LlmRuntime['prepareCall']>[2]\n /** Rechecked before the call returns text. A false result discards the output. */\n isCurrent?: () => boolean\n}\n\nexport interface LlmTextResult {\n text: string\n provider: string\n model: string\n inputTokens?: number\n outputTokens?: number\n totalTokens?: number\n cacheReadTokens?: number\n cacheWriteTokens?: number\n reasoningTokens?: number\n}\n\ntype SessionModelsHost = {\n agents?: { get(sessionId: string): { session?: { requestHeader?: () => { config?: ModelRoute; adapterDefaults?: { reasoningEffort?: unknown } } | undefined } } | undefined }\n sessionProjections?: { stateOf(session: unknown, key: string): { pending?: ModelRoute | null } | undefined }\n agentDefaultModel?: { currentSelection(): ModelRoute | undefined }\n}\n\nfunction routeOf(value: { provider?: unknown; model?: unknown; reasoningEffort?: unknown } | undefined): FeatureModelRoute | undefined {\n if (typeof value?.provider !== 'string' || !value.provider.trim()) return undefined\n if (typeof value.model !== 'string' || !value.model.trim()) return undefined\n const effort = typeof value.reasoningEffort === 'string' ? value.reasoningEffort.trim() : ''\n return effort\n ? { provider: value.provider.trim(), model: value.model.trim(), reasoningEffort: effort }\n : { provider: value.provider.trim(), model: value.model.trim() }\n}\n\n/**\n * Plugin selection first; then the shared auxiliary default (when any\n * participant retains it); otherwise the live session model, then the host\n * default. Cordis callers must declare agents, sessionProjections and\n * agentDefaultModel in their inject list so the fallback reads use the\n * caller's live services.\n */\nexport function resolveFeatureModel(host: unknown, saved: FeatureModelRoute | undefined, sessionId?: string): FeatureModelRoute | undefined {\n const explicit = routeOf(saved)\n if (explicit) return explicit\n const auxiliary = getAuxiliaryModel(host)\n if (auxiliary) return auxiliary\n const face = host as SessionModelsHost | undefined\n if (sessionId && face?.agents && face.sessionProjections) {\n const session = face.agents.get(sessionId)?.session\n if (session) {\n try {\n const pending = face.sessionProjections.stateOf(session, 'modelSelection')?.pending\n const selected = routeOf(pending ?? undefined)\n if (selected) return selected\n } catch { /* fall through to the recorded route */ }\n const header = session.requestHeader?.()\n const recorded = header?.config\n if (recorded) {\n const selected = header?.adapterDefaults?.reasoningEffort === true\n ? { provider: recorded.provider, model: recorded.model }\n : recorded\n const route = routeOf(selected)\n if (route) return route\n }\n }\n }\n return routeOf(face?.agentDefaultModel?.currentSelection())\n}\n\nfunction joinText(assembler: BlockAssembler): string {\n return assembler.blocks()\n .filter((block): block is { type: 'text'; text: string } => block.type === 'text')\n .map(block => block.text)\n .join('')\n}\n\nasync function readStream(stream: AsyncIterable<StreamChunk>, signal: AbortSignal, live: () => boolean): Promise<BlockAssembler> {\n const assembler = new BlockAssembler()\n const iterator = stream[Symbol.asyncIterator]()\n try {\n while (live()) {\n if (signal.aborted) break\n const item = await iterator.next()\n if (item.done) break\n assembler.push(item.value)\n }\n } finally {\n // Settlement includes producer cleanup; never leave a detached return()\n // running after the caller has released its lifecycle ownership.\n await iterator.return?.()\n }\n return assembler\n}\n\n/** One native model call. No retry, timeout policy, or provider queue. */\nexport async function callLlmText(llm: LlmTextCaller, request: LlmTextRequest): Promise<LlmTextResult> {\n request.signal.throwIfAborted()\n const live = () => request.isCurrent?.() !== false && !request.signal.aborted\n if (!live()) throw Object.assign(new Error('调用已取消。'), { name: 'AbortError' })\n const effort = request.route.reasoningEffort?.trim()\n const prepared = await llm.prepareCall({\n provider: request.route.provider,\n model: request.route.model,\n ...(effort ? { reasoningEffort: ReasoningEffortId(effort) } : {}),\n maxTokens: request.maxTokens,\n }, request.signal, request.configure)\n if (!live()) throw Object.assign(new Error('调用已取消。'), { name: 'AbortError' })\n const options: GenerateOptions = {\n provider: prepared.config.provider,\n model: prepared.config.model,\n ...(prepared.config.reasoningEffort ? { reasoningEffort: prepared.config.reasoningEffort } : {}),\n ...(prepared.config.maxTokens !== undefined ? { maxTokens: Math.min(request.maxTokens, prepared.config.maxTokens) } : { maxTokens: request.maxTokens }),\n ...(prepared.config.temperature !== undefined ? { temperature: prepared.config.temperature } : {}),\n ...(prepared.config.stop ? { stop: [...prepared.config.stop] } : {}),\n system: request.system,\n messages: [createUserMessage({\n source: producerMessageSource(request.plugin),\n content: [{ type: 'text', text: buildAuxiliaryUserText(request) }],\n })],\n signal: request.signal,\n ...(request.purpose ? { purpose: request.purpose } : {}),\n ...(request.sessionId ? { sessionId: request.sessionId as GenerateOptions['sessionId'] } : {}),\n }\n const assembler = await readStream(prepared.stream(options), request.signal, live)\n if (request.signal.aborted || assembler.finish.kind === 'aborted') {\n throw Object.assign(new Error('调用已取消。'), { name: 'AbortError' })\n }\n if (!live()) throw Object.assign(new Error('调用已取消。'), { name: 'AbortError' })\n if (assembler.finish.kind === 'error') {\n const code = assembler.finish.failure.code\n throw Object.assign(new Error(assembler.finish.failure.message || '模型调用失败。'), { code })\n }\n if (assembler.blocks().some(block => block.type === 'tool-call') || assembler.finish.kind === 'tool-calls') {\n throw new Error('这次调用不使用工具。')\n }\n if (assembler.finish.kind !== 'stop') throw new Error('模型调用未完成。')\n // Every usable host usage field passes through; absent fields stay absent\n // (the caller's accounting must not see invented zeros).\n const usage = assembler.usage\n return {\n text: joinText(assembler),\n provider: prepared.config.provider,\n model: prepared.config.model,\n ...(usage ? {\n inputTokens: usage.inputTokens,\n outputTokens: usage.outputTokens,\n ...(usage.totalTokens !== undefined ? { totalTokens: usage.totalTokens } : {}),\n ...(usage.cacheReadTokens !== undefined ? { cacheReadTokens: usage.cacheReadTokens } : {}),\n ...(usage.cacheWriteTokens !== undefined ? { cacheWriteTokens: usage.cacheWriteTokens } : {}),\n ...(usage.reasoningTokens !== undefined ? { reasoningTokens: usage.reasoningTokens } : {}),\n } : {}),\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"llm-call.js","names":[],"sources":["../src/llm-call.ts"],"sourcesContent":["import {\n BlockAssembler, ReasoningEffortId, createUserMessage,\n type GenerateOptions, type LlmRuntime, type StreamChunk,\n} from '@deepseek-ai/dsh-llm'\nimport { getAuxiliaryModel, producerMessageSource, type ModelRoute } from './contracts.ts'\nimport { buildAuxiliaryUserText } from './auxiliary-context.ts'\n\n/** A feature's own saved model. Empty means the host default chat model. */\nexport interface FeatureModelRoute {\n readonly provider: string\n readonly model: string\n readonly reasoningEffort?: string\n}\n\nexport type LlmTextCaller = Pick<LlmRuntime, 'prepareCall' | 'resolveCallConfig'>\n\nexport interface LlmTextRequest {\n plugin: string\n /** Optional feature id, stamped on the context envelope for attribution. */\n feature?: string\n /**\n * Stable material rebuilt from current facts (session events, durable\n * records). Packed before the live text so provider-side caching reuses\n * the prefix; never model outputs or approval history. Strings or\n * pre-ordered segments.\n */\n context?: string | readonly string[]\n route: FeatureModelRoute\n system: string\n text: string\n /** Omit to use the selected model's normal output settings. */\n maxTokens?: number\n signal: AbortSignal\n /** The real session id for host attribution; never a feature-scoped alias. */\n sessionId?: string\n /** Auxiliary-call marker adapters may special-case (e.g. disable thinking for titles). */\n purpose?: GenerateOptions['purpose']\n /** Pure control selection using the exact model captured for this call. */\n configure?: Parameters<LlmRuntime['prepareCall']>[2]\n /** Rechecked before the call returns text. A false result discards the output. */\n isCurrent?: () => boolean\n}\n\nexport interface LlmTextResult {\n text: string\n provider: string\n model: string\n inputTokens?: number\n outputTokens?: number\n totalTokens?: number\n cacheReadTokens?: number\n cacheWriteTokens?: number\n reasoningTokens?: number\n}\n\ntype SessionModelsHost = {\n agents?: { get(sessionId: string): { session?: { requestHeader?: () => { config?: ModelRoute; adapterDefaults?: { reasoningEffort?: unknown } } | undefined } } | undefined }\n sessionProjections?: { stateOf(session: unknown, key: string): { pending?: ModelRoute | null } | undefined }\n agentDefaultModel?: { currentSelection(): ModelRoute | undefined }\n}\n\nfunction routeOf(value: { provider?: unknown; model?: unknown; reasoningEffort?: unknown } | undefined): FeatureModelRoute | undefined {\n if (typeof value?.provider !== 'string' || !value.provider.trim()) return undefined\n if (typeof value.model !== 'string' || !value.model.trim()) return undefined\n const effort = typeof value.reasoningEffort === 'string' ? value.reasoningEffort.trim() : ''\n return effort\n ? { provider: value.provider.trim(), model: value.model.trim(), reasoningEffort: effort }\n : { provider: value.provider.trim(), model: value.model.trim() }\n}\n\n/**\n * Plugin selection first; then the shared auxiliary default (when any\n * participant retains it); otherwise the live session model, then the host\n * default. Cordis callers must declare agents, sessionProjections and\n * agentDefaultModel in their inject list so the fallback reads use the\n * caller's live services.\n */\nexport function resolveFeatureModel(host: unknown, saved: FeatureModelRoute | undefined, sessionId?: string): FeatureModelRoute | undefined {\n const explicit = routeOf(saved)\n if (explicit) return explicit\n const auxiliary = getAuxiliaryModel(host)\n if (auxiliary) return auxiliary\n const face = host as SessionModelsHost | undefined\n if (sessionId && face?.agents && face.sessionProjections) {\n const session = face.agents.get(sessionId)?.session\n if (session) {\n try {\n const pending = face.sessionProjections.stateOf(session, 'modelSelection')?.pending\n const selected = routeOf(pending ?? undefined)\n if (selected) return selected\n } catch { /* fall through to the recorded route */ }\n const header = session.requestHeader?.()\n const recorded = header?.config\n if (recorded) {\n const selected = header?.adapterDefaults?.reasoningEffort === true\n ? { provider: recorded.provider, model: recorded.model }\n : recorded\n const route = routeOf(selected)\n if (route) return route\n }\n }\n }\n return routeOf(face?.agentDefaultModel?.currentSelection())\n}\n\nfunction joinText(assembler: BlockAssembler): string {\n return assembler.blocks()\n .filter((block): block is { type: 'text'; text: string } => block.type === 'text')\n .map(block => block.text)\n .join('')\n}\n\nasync function readStream(stream: AsyncIterable<StreamChunk>, signal: AbortSignal, live: () => boolean): Promise<BlockAssembler> {\n const assembler = new BlockAssembler()\n const iterator = stream[Symbol.asyncIterator]()\n try {\n while (live()) {\n if (signal.aborted) break\n const item = await iterator.next()\n if (item.done) break\n assembler.push(item.value)\n }\n } finally {\n // Settlement includes producer cleanup; never leave a detached return()\n // running after the caller has released its lifecycle ownership.\n await iterator.return?.()\n }\n return assembler\n}\n\n/** One native model call. No retry, timeout policy, or provider queue. */\nexport async function callLlmText(llm: LlmTextCaller, request: LlmTextRequest): Promise<LlmTextResult> {\n request.signal.throwIfAborted()\n const live = () => request.isCurrent?.() !== false && !request.signal.aborted\n if (!live()) throw Object.assign(new Error('调用已取消。'), { name: 'AbortError' })\n const effort = request.route.reasoningEffort?.trim()\n const prepared = await llm.prepareCall({\n provider: request.route.provider,\n model: request.route.model,\n ...(effort ? { reasoningEffort: ReasoningEffortId(effort) } : {}),\n ...(request.maxTokens !== undefined ? { maxTokens: request.maxTokens } : {}),\n }, request.signal, request.configure)\n if (!live()) throw Object.assign(new Error('调用已取消。'), { name: 'AbortError' })\n const maxTokens = request.maxTokens === undefined\n ? prepared.config.maxTokens\n : Math.min(request.maxTokens, prepared.config.maxTokens ?? request.maxTokens)\n const options: GenerateOptions = {\n provider: prepared.config.provider,\n model: prepared.config.model,\n ...(prepared.config.reasoningEffort ? { reasoningEffort: prepared.config.reasoningEffort } : {}),\n ...(maxTokens !== undefined ? { maxTokens } : {}),\n ...(prepared.config.temperature !== undefined ? { temperature: prepared.config.temperature } : {}),\n ...(prepared.config.stop ? { stop: [...prepared.config.stop] } : {}),\n system: request.system,\n messages: [createUserMessage({\n source: producerMessageSource(request.plugin),\n content: [{ type: 'text', text: buildAuxiliaryUserText(request) }],\n })],\n signal: request.signal,\n ...(request.purpose ? { purpose: request.purpose } : {}),\n ...(request.sessionId ? { sessionId: request.sessionId as GenerateOptions['sessionId'] } : {}),\n }\n const assembler = await readStream(prepared.stream(options), request.signal, live)\n if (request.signal.aborted || assembler.finish.kind === 'aborted') {\n throw Object.assign(new Error('调用已取消。'), { name: 'AbortError' })\n }\n if (!live()) throw Object.assign(new Error('调用已取消。'), { name: 'AbortError' })\n if (assembler.finish.kind === 'error') {\n const code = assembler.finish.failure.code\n throw Object.assign(new Error(assembler.finish.failure.message || '模型调用失败。'), { code })\n }\n if (assembler.blocks().some(block => block.type === 'tool-call') || assembler.finish.kind === 'tool-calls') {\n throw new Error('这次调用不使用工具。')\n }\n if (assembler.finish.kind !== 'stop') throw new Error('模型调用未完成。')\n // Every usable host usage field passes through; absent fields stay absent\n // (the caller's accounting must not see invented zeros).\n const usage = assembler.usage\n return {\n text: joinText(assembler),\n provider: prepared.config.provider,\n model: prepared.config.model,\n ...(usage ? {\n inputTokens: usage.inputTokens,\n outputTokens: usage.outputTokens,\n ...(usage.totalTokens !== undefined ? { totalTokens: usage.totalTokens } : {}),\n ...(usage.cacheReadTokens !== undefined ? { cacheReadTokens: usage.cacheReadTokens } : {}),\n ...(usage.cacheWriteTokens !== undefined ? { cacheWriteTokens: usage.cacheWriteTokens } : {}),\n ...(usage.reasoningTokens !== undefined ? { reasoningTokens: usage.reasoningTokens } : {}),\n } : {}),\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import { ReactNode } from "react";
2
+ import { MODEL_PICKER_ANCHOR_SLOT, MODEL_PICKER_FACTORY, ModelPickerAnchorProps, ModelPickerAnchorProps as ModelPickerAnchorProps$1, ModelPickerLeadingItem, ModelPickerProps, ModelPickerProps as ModelPickerProps$1, ModelPickerStatus } from "@klarkxy/dsh-model-route/model-picker";
3
+ import { RenderFactorySlot, RenderFactorySlot as RenderFactorySlot$1 } from "@deepseek-ai/dsh-client-ui-slots";
4
+ //#region src/model-picker.d.ts
5
+ /**
6
+ * The trigger every consumer used to hand its own ModelMenu, rebuilt as the
7
+ * factory's `anchor` local component: identical classes and chevron, the menu
8
+ * state coming from the occurrence's props instead of local open state.
9
+ *
10
+ * `ariaLabel` and `title` are the whole accessible name and tooltip (the
11
+ * caller formats "field: current route" itself because the factory's resolved
12
+ * caption may intentionally be a short name). The component identity stays
13
+ * put across label changes — a new function would unmount the trigger, and
14
+ * its cleanup would close a menu that `closeOnPick={false}` is keeping open.
15
+ * The latest label, title, and `onClose` are read from refs.
16
+ */
17
+ declare function useModelMenuAnchor(ariaLabel: string, title?: string, onClose?: () => void): (props: ModelPickerAnchorProps$1) => ReactNode;
18
+ /**
19
+ * Render the shared model picker while it is registered; `options.fallback`
20
+ * whenever it is not (plugin absent, disabled, or unloaded).
21
+ *
22
+ * `options.anchor` is optional: the factory ships a default trigger, so only
23
+ * consumers that need their own chrome (custom aria-label, tooltip, class)
24
+ * pass one built with {@link useModelMenuAnchor}. The returned node is never
25
+ * assumed to be the factory's own output — a defensive `?? fallback` covers a
26
+ * renderer that yields null for an unregistered definition.
27
+ */
28
+ declare function renderModelPicker(renderFactorySlot: RenderFactorySlot$1 | undefined, props: ModelPickerProps$1, options: {
29
+ readonly anchor?: (props: ModelPickerAnchorProps$1) => ReactNode;
30
+ readonly fallback: ReactNode;
31
+ }): ReactNode;
32
+ //#endregion
33
+ export { MODEL_PICKER_ANCHOR_SLOT, MODEL_PICKER_FACTORY, type ModelPickerAnchorProps, type ModelPickerLeadingItem, type ModelPickerProps, type ModelPickerStatus, type RenderFactorySlot, renderModelPicker, useModelMenuAnchor };
34
+ //# sourceMappingURL=model-picker.d.ts.map