@klarkxy/dsh-plugin-kit 0.4.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/official-ui.js +15 -1
- package/lib/official-ui.js.map +1 -1
- package/lib/zod.js +13566 -0
- package/lib/zod.js.map +1 -0
- package/package.json +24 -3
package/lib/client-utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-utils.js","names":[],"sources":["../src/client-utils.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useSyncExternalStore } from 'react'\n\n/** Fixed pause for failing background reads; manual work is never replayed. */\nexport function createPluginReadGate(now: () => number = Date.now) {\n let pausedUntil = 0\n let epoch = 0\n return {\n canRead: () => now() >= pausedUntil,\n reset: () => { epoch++; pausedUntil = 0 },\n async run<T>(read: () => Promise<T>): Promise<T> {\n const current = epoch\n try {\n const value = await read()\n if (current === epoch) pausedUntil = 0\n return value\n } catch (error) {\n // The native Connection error is an Error with this exact HTTP text.\n // Authentication, application failures, and cancellation stay distinct.\n const message = error instanceof Error ? error.message : ''\n if (current === epoch && (/(?:^|\\s)transport failure for .+: HTTP (?:404|405|503)\\b/.test(message)\n || (error instanceof TypeError && /^(?:Failed to fetch|fetch failed|NetworkError when attempting to fetch resource\\.?)$/i.test(message)))) {\n pausedUntil = now() + 30_000\n }\n throw error\n }\n },\n }\n}\n\nexport interface NativeSurfaceClient {\n sessions: {\n binding?(sessionId: string): { eventSource: { subscribe(listener: () => void): () => void } } | undefined\n }\n uiWorkspace: { current?: { getSnapshot(): { sessionId: string } | undefined; subscribe(listener: () => void): () => void } }\n uiSession: { adapter: { current: { getSnapshot(): { key?: string }; subscribe(listener: () => void): () => void } } }\n locale: { getSnapshot(): { active: string }; subscribe(listener: () => void): () => void }\n connection?: { generation?: { subscribe(listener: () => void): () => void } }\n}\n\n/** The Editor retains its manuscript session separately; native DSH exposes the main-view binding. */\nexport function selectedSessionId(client: Pick<NativeSurfaceClient, 'uiWorkspace' | 'uiSession'>): string {\n const workspace = client.uiWorkspace?.current\n return workspace\n ? workspace.getSnapshot()?.sessionId ?? ''\n : client.uiSession.adapter.current.getSnapshot().key ?? ''\n}\n\n/**\n * Owner props are data the host composed, never a Cordis context: reading an\n * undeclared property off one throws instead of returning undefined, which\n * would crash the slot entry and abdicate its cell for good. Tolerate a\n * throwing read so a mistaken prop degrades to \"not supplied\".\n */\nfunction read(source: unknown, key: string): unknown {\n if (source === null || typeof source !== 'object') return undefined\n try {\n return (source as Record<string, unknown>)[key]\n } catch {\n return undefined\n }\n}\n\nfunction readString(source: unknown, key: string): string | undefined {\n const value = read(source, key)\n return typeof value === 'string' ? value : undefined\n}\n\n/** Owner props win; native settings only supplies close, so resolve its selected session. */\nexport function useNativeSeat(client: NativeSurfaceClient, props?: unknown) {\n const current = useSyncExternalStore(\n useCallback((fn: () => void) => (client.uiWorkspace?.current ?? client.uiSession.adapter.current).subscribe(fn), [client]),\n useCallback(() => selectedSessionId(client), [client]),\n () => '',\n )\n const language = useSyncExternalStore(\n useCallback((fn: () => void) => client.locale.subscribe(fn), [client]),\n useCallback(() => client.locale.getSnapshot().active, [client]),\n () => 'en',\n )\n const sessionId = readString(props, 'sessionId') ?? readString(read(props, 'owner'), 'sessionId') ?? current\n const requestedLocale = read(props, 'locale') ?? read(read(props, 'owner'), 'locale') ?? language\n return { sessionId, locale: (String(requestedLocale).startsWith('zh') ? 'zh' : 'en') as 'zh' | 'en',\n hidden: read(props, 'hidden') === true || read(read(props, 'owner'), 'hidden') === true }\n}\n\n/** Refresh on native log boundaries/reconnect/focus. Read-only settling is bounded to an observed job. */\nexport function useFeatureRefresh(\n client: NativeSurfaceClient, sessionId: string, refresh: () => void,\n working = false, enabled = true,\n): void {\n const refreshRef = useRef(refresh)\n refreshRef.current = refresh\n useEffect(() => {\n if (!enabled) return\n let timer: ReturnType<typeof setTimeout> | undefined\n const queue = () => {\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return\n clearTimeout(timer)\n timer = setTimeout(() => refreshRef.current(), 150)\n }\n const source = sessionId ? client.sessions.binding?.(sessionId)?.eventSource : undefined\n const stopEvents = source?.subscribe(queue)\n const stopConnection = client.connection?.generation?.subscribe(queue)\n const visibility = () => { if (document.visibilityState === 'visible') queue() }\n window.addEventListener('focus', queue)\n document.addEventListener('visibilitychange', visibility)\n // Details are collapsed by default; opening an existing panel must get current state.\n document.addEventListener('toggle', queue, true)\n return () => {\n clearTimeout(timer)\n stopEvents?.(); stopConnection?.()\n window.removeEventListener('focus', queue)\n document.removeEventListener('visibilitychange', visibility)\n document.removeEventListener('toggle', queue, true)\n }\n }, [client, sessionId, enabled])\n useEffect(() => {\n if (!enabled || !working) return\n let delay = 250\n const until = Date.now() + 300_000\n let timer: ReturnType<typeof setTimeout>\n const tick = () => {\n if (Date.now() >= until) return\n if (document.visibilityState !== 'hidden') refreshRef.current()\n delay = Math.min(delay * 2, 2000)\n timer = setTimeout(tick, delay)\n }\n timer = setTimeout(tick, delay)\n return () => clearTimeout(timer)\n }, [client, sessionId, working, enabled])\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client-utils.js","names":[],"sources":["../src/client-utils.ts"],"sourcesContent":["import type { Context } from '@deepseek-ai/cordis'\nimport { createElement, useCallback, useEffect, useRef, useSyncExternalStore, type ReactNode } from 'react'\nimport { SESSION_SETTINGS_ITEM_SLOT, SESSION_SETTINGS_SECTION_ID, SETTINGS_SECTION_ICON_SLOT } from './contracts.ts'\n\n/** Fixed pause for failing background reads; manual work is never replayed. */\nexport function createPluginReadGate(now: () => number = Date.now) {\n let pausedUntil = 0\n let epoch = 0\n return {\n canRead: () => now() >= pausedUntil,\n reset: () => { epoch++; pausedUntil = 0 },\n async run<T>(read: () => Promise<T>): Promise<T> {\n const current = epoch\n try {\n const value = await read()\n if (current === epoch) pausedUntil = 0\n return value\n } catch (error) {\n // The native Connection error is an Error with this exact HTTP text.\n // Authentication, application failures, and cancellation stay distinct.\n const message = error instanceof Error ? error.message : ''\n if (current === epoch && (/(?:^|\\s)transport failure for .+: HTTP (?:404|405|503)\\b/.test(message)\n || (error instanceof TypeError && /^(?:Failed to fetch|fetch failed|NetworkError when attempting to fetch resource\\.?)$/i.test(message)))) {\n pausedUntil = now() + 30_000\n }\n throw error\n }\n },\n }\n}\n\nexport interface NativeSurfaceClient {\n sessions: {\n binding?(sessionId: string): { eventSource: { subscribe(listener: () => void): () => void } } | undefined\n }\n uiWorkspace: { current?: { getSnapshot(): { sessionId: string } | undefined; subscribe(listener: () => void): () => void } }\n uiSession: { adapter: { current: { getSnapshot(): { key?: string }; subscribe(listener: () => void): () => void } } }\n locale: { getSnapshot(): { active: string }; subscribe(listener: () => void): () => void }\n connection?: { generation?: { subscribe(listener: () => void): () => void } }\n}\n\n/** The Editor retains its manuscript session separately; native DSH exposes the main-view binding. */\nexport function selectedSessionId(client: Pick<NativeSurfaceClient, 'uiWorkspace' | 'uiSession'>): string {\n const workspace = client.uiWorkspace?.current\n return workspace\n ? workspace.getSnapshot()?.sessionId ?? ''\n : client.uiSession.adapter.current.getSnapshot().key ?? ''\n}\n\n/**\n * Owner props are data the host composed, never a Cordis context: reading an\n * undeclared property off one throws instead of returning undefined, which\n * would crash the slot entry and abdicate its cell for good. Tolerate a\n * throwing read so a mistaken prop degrades to \"not supplied\".\n */\nfunction read(source: unknown, key: string): unknown {\n if (source === null || typeof source !== 'object') return undefined\n try {\n return (source as Record<string, unknown>)[key]\n } catch {\n return undefined\n }\n}\n\nfunction readString(source: unknown, key: string): string | undefined {\n const value = read(source, key)\n return typeof value === 'string' ? value : undefined\n}\n\n/** Owner props win; native settings only supplies close, so resolve its selected session. */\nexport function useNativeSeat(client: NativeSurfaceClient, props?: unknown) {\n const current = useSyncExternalStore(\n useCallback((fn: () => void) => (client.uiWorkspace?.current ?? client.uiSession.adapter.current).subscribe(fn), [client]),\n useCallback(() => selectedSessionId(client), [client]),\n () => '',\n )\n const language = useSyncExternalStore(\n useCallback((fn: () => void) => client.locale.subscribe(fn), [client]),\n useCallback(() => client.locale.getSnapshot().active, [client]),\n () => 'en',\n )\n const sessionId = readString(props, 'sessionId') ?? readString(read(props, 'owner'), 'sessionId') ?? current\n const requestedLocale = read(props, 'locale') ?? read(read(props, 'owner'), 'locale') ?? language\n return { sessionId, locale: (String(requestedLocale).startsWith('zh') ? 'zh' : 'en') as 'zh' | 'en',\n hidden: read(props, 'hidden') === true || read(read(props, 'owner'), 'hidden') === true }\n}\n\n/** Refresh on native log boundaries/reconnect/focus. Read-only settling is bounded to an observed job. */\nexport function useFeatureRefresh(\n client: NativeSurfaceClient, sessionId: string, refresh: () => void,\n working = false, enabled = true,\n): void {\n const refreshRef = useRef(refresh)\n refreshRef.current = refresh\n useEffect(() => {\n if (!enabled) return\n let timer: ReturnType<typeof setTimeout> | undefined\n const queue = () => {\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return\n clearTimeout(timer)\n timer = setTimeout(() => refreshRef.current(), 150)\n }\n const source = sessionId ? client.sessions.binding?.(sessionId)?.eventSource : undefined\n const stopEvents = source?.subscribe(queue)\n const stopConnection = client.connection?.generation?.subscribe(queue)\n const visibility = () => { if (document.visibilityState === 'visible') queue() }\n window.addEventListener('focus', queue)\n document.addEventListener('visibilitychange', visibility)\n // Details are collapsed by default; opening an existing panel must get current state.\n document.addEventListener('toggle', queue, true)\n return () => {\n clearTimeout(timer)\n stopEvents?.(); stopConnection?.()\n window.removeEventListener('focus', queue)\n document.removeEventListener('visibilitychange', visibility)\n document.removeEventListener('toggle', queue, true)\n }\n }, [client, sessionId, enabled])\n useEffect(() => {\n if (!enabled || !working) return\n let delay = 250\n const until = Date.now() + 300_000\n let timer: ReturnType<typeof setTimeout>\n const tick = () => {\n if (Date.now() >= until) return\n if (document.visibilityState !== 'hidden') refreshRef.current()\n delay = Math.min(delay * 2, 2000)\n timer = setTimeout(tick, delay)\n }\n timer = setTimeout(tick, delay)\n return () => clearTimeout(timer)\n }, [client, sessionId, working, enabled])\n}\n\n/* Shared \"Session Management\" settings page. The page is root-owned shared\n * infrastructure, not a business plugin: any participant alone yields the same\n * section, and participants never import each other. Synchronization crosses\n * separately bundled copies of this helper through the Cordis root context —\n * `root.get`/`root.provide` is the only rendezvous, so no window global or\n * module singleton is needed and no participant depends on another being enabled.\n */\n\n/** A row contributed into the shared Session Management page. */\nexport interface SessionSettingsItemSpec {\n readonly id: string\n readonly order: number\n}\n\n/* Original line icons for settings-section navigation seats. The official\n * `dsh-client-ui-primitives` icon set covers generic glyphs only; these keep\n * the same conventions (16×16 viewBox, 1px stroke, round caps, currentColor)\n * so the plugin set stays visually consistent with the native nav. Colors\n * never appear here: `currentColor` lets the host style the active state, and\n * `aria-hidden` keeps the section label the single accessible name. */\n\n/** Owner props the host's `settings.section.icon` seat hands to its cell. */\nexport interface SettingsSectionIconProps {\n readonly size?: number\n /** Selection state; the nav styles active icons through color, not geometry. */\n readonly active?: boolean\n}\n\ntype IconChild = readonly [tag: string, attrs: Record<string, string | number>]\n\n/** One `<svg>` with the shared icon chrome; children are plain elements. */\nfunction lineIcon(children: IconChild[]) {\n return function SettingsSectionIcon(props: SettingsSectionIconProps): ReactNode {\n return createElement('svg', {\n 'aria-hidden': 'true',\n width: props.size ?? 16,\n height: props.size ?? 16,\n viewBox: '0 0 16 16',\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 1,\n strokeLinecap: 'round',\n strokeLinejoin: 'round',\n }, ...children.map(([tag, attrs], index) => createElement(tag, { key: index, ...attrs })))\n }\n}\n\n/** Chat bubble for the shared Session Management section. */\nexport const IconSessionSettings = lineIcon([\n ['path', { d: 'M8.5 13a5.5 5.5 0 0 0 5.5-5.5A5.5 5.5 0 0 0 8.5 2 5.5 5.5 0 0 0 3 7.5a5.48 5.48 0 0 0 .9 3L2.8 12.6a.5.5 0 0 0 .62.62l2.9-.98a5.48 5.48 0 0 0 2.18.76Z' }],\n])\n\n/** Serif-flavoured letter A for the Font section. */\nexport const IconFontSettings = lineIcon([\n ['path', { d: 'M3.3 12.6 7.9 3.1a.5.5 0 0 1 .9 0l4.6 9.5' }],\n ['path', { d: 'M5.2 9.4h6.3' }],\n ['path', { d: 'M2.4 12.6h2.4' }],\n ['path', { d: 'M11.9 12.6h2.4' }],\n])\n\n/** Keyboard for the Shortcuts section. */\nexport const IconKeyboardSettings = lineIcon([\n ['rect', { x: 1.75, y: 4.75, width: 12.5, height: 7.5, rx: 1.2 }],\n ['path', { d: 'M4.5 7.25h0' }],\n ['path', { d: 'M8 7.25h0' }],\n ['path', { d: 'M11.5 7.25h0' }],\n ['path', { d: 'M5.25 9.75h5.5' }],\n])\n\n/** Linked model nodes for the Model Management section. */\nexport const IconModelNetworkSettings = lineIcon([\n ['line', { x1: 10.6, y1: 5.3, x2: 8.8, y2: 7.1 }],\n ['line', { x1: 7.2, y1: 8.9, x2: 5.4, y2: 10.7 }],\n ['circle', { cx: 12, cy: 4, r: 1.9 }],\n ['circle', { cx: 4, cy: 12, r: 1.9 }],\n ['circle', { cx: 8, cy: 8, r: 1.5 }],\n])\n\n/**\n * Contribute the nav icon of a `settings.section` entry. The `settings\n * .section.icon` seat is declared by the host independently of the section\n * list, so the wait runs in parallel: the icon appears as soon as both seats\n * exist and unloading removes it again, restoring the host's default glyph.\n * The `id` must equal the section id; `order`/`priority` mirror `order` so the\n * icon cell sorts like its section and, for shared sections, several\n * contributors can register the same icon id under different priorities\n * without duplicating nav entries.\n */\nexport function registerSettingsSectionIcon(\n ctx: Context,\n sectionId: string,\n order: number,\n component: (props: SettingsSectionIconProps) => ReactNode,\n): () => void {\n const slots = (ctx as unknown as { slots: SessionSettingsSlots }).slots\n return slots.inject(SETTINGS_SECTION_ICON_SLOT, () =>\n slots.register({ name: SETTINGS_SECTION_ICON_SLOT, id: sectionId, order, priority: order }, component))\n}\n\ninterface SessionSettingsSlots {\n inject(key: string, callback: () => Iterable<(() => void) | void> | (() => void) | void): () => void\n register(spec: Record<string, unknown>, render: unknown): () => void\n}\n\ninterface SessionSettingsRoot {\n get(name: string, strict?: boolean): unknown\n provide(name: string, value: unknown): () => void\n readonly slots?: SessionSettingsSlots\n}\n\n/** Private Cordis rendezvous; every bundled copy of this helper resolves the same root service. */\nconst SESSION_SETTINGS_PAGE_SERVICE = 'dshSessionSettingsPage'\n\ninterface SessionSettingsPage {\n retain(): void\n release(): void\n}\n\n/** The page component renders only its declared child list: items paint their own titles and styles. */\nfunction SessionSettingsPageView(props: { renderSlot: (key: string, owner: Record<string, never>) => ReactNode }): ReactNode {\n return props.renderSlot(SESSION_SETTINGS_ITEM_SLOT, {})\n}\n\n/**\n * Acquire the root-owned page service, creating and providing it on first use.\n * One counter covers participants, the section wait and the provider: the last\n * release removes the wait and then the provider synchronously (Cordis deletes\n * the impl before its async disposer awaits), so a later acquire sees a clean\n * get() miss and can re-establish the page.\n */\nfunction acquireSessionSettingsPage(root: SessionSettingsRoot): SessionSettingsPage {\n const existing = root.get(SESSION_SETTINGS_PAGE_SERVICE, false) as SessionSettingsPage | undefined\n if (existing) return existing\n const slots = root.slots as SessionSettingsSlots\n let users = 0\n let stopPage: (() => void) | undefined\n let unprovide: (() => void) | undefined\n const page: SessionSettingsPage = {\n retain() {\n // The reservation is counted before the throwing install, so a failure\n // lets the caller's release() undo it fully instead of leaking the service.\n users += 1\n if (!stopPage) {\n stopPage = slots.inject('settings.section', () => slots.register(\n {\n name: 'settings.section',\n id: SESSION_SETTINGS_SECTION_ID,\n order: 30,\n label: () => {\n const locale = root.get('locale', false) as { getSnapshot?(): { active?: unknown } } | undefined\n return String(locale?.getSnapshot?.().active ?? '').startsWith('zh') ? '会话管理' : 'Session Management'\n },\n children: { [SESSION_SETTINGS_ITEM_SLOT]: { kind: 'list', scope: 'root' } },\n },\n SessionSettingsPageView,\n ))\n }\n },\n release() {\n if (users <= 0) return\n users -= 1\n if (users > 0) return\n // The last contributor tears the section down; collapsing its children\n // declaration clears the item slot for every other bundle as well.\n stopPage?.()\n stopPage = undefined\n unprovide?.()\n },\n }\n unprovide = root.provide(SESSION_SETTINGS_PAGE_SERVICE, page)\n return page\n}\n\n/**\n * Contribute one row to the shared Session Management settings page.\n *\n * `ctx` is the plugin's own context: the row's slot wait unloads with the\n * plugin, while the page itself is retained on the shared root context for as\n * long as any contributor lives. There is no fallback seat — when every\n * contributor is gone the section disappears with the page. Returns a disposer.\n * Shared root-owned infrastructure (the auxiliary-model card) calls this on\n * `ctx.root` so its row, icon and page share are owned by the root lifetime\n * and released by the returned disposer, not by any plugin fiber.\n */\nexport function registerSessionSettingsItem(\n ctx: Context,\n item: SessionSettingsItemSpec,\n component: (props: never) => ReactNode,\n): () => void {\n const slots = (ctx as unknown as { slots: SessionSettingsSlots }).slots\n const root = ctx.root as unknown as SessionSettingsRoot\n const page = acquireSessionSettingsPage(root)\n let stopItem: (() => void) | undefined\n let stopIcon: (() => void) | undefined\n try {\n page.retain()\n stopItem = slots.inject(SESSION_SETTINGS_ITEM_SLOT, () => slots.register({ name: SESSION_SETTINGS_ITEM_SLOT, ...item }, component))\n // Every contributor also waits on the nav-icon seat, registering the same\n // section icon id under its own priority (its row order). Same-id entries\n // coexist on the ledger while the host renders one icon, so no contributor\n // owns the icon and none depends on another for it; the wait lives with\n // this ctx, so it fires now on fresh loads and retro-fills on HMR.\n stopIcon = slots.inject(SETTINGS_SECTION_ICON_SLOT, () => slots.register(\n { name: SETTINGS_SECTION_ICON_SLOT, id: SESSION_SETTINGS_SECTION_ID, order: item.order, priority: item.order },\n IconSessionSettings,\n ))\n } catch (error) {\n // A synchronous failure (for example a duplicate row id or a failing page\n // install) must not pin the shared page for a contributor that never joined.\n stopIcon?.()\n stopItem?.()\n page.release()\n throw error\n }\n // The disposer may be invoked by both ctx.effect and an explicit caller;\n // releasing twice would unbalance the shared refcount.\n let disposed = false\n return () => {\n if (disposed) return\n disposed = true\n stopIcon?.()\n stopItem?.()\n page.release()\n }\n}\n"],"mappings":"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"}
|
package/lib/contracts.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ContextFormed } from "@deepseek-ai/dsh-llm";
|
|
2
1
|
import { ModelRoute } from "@klarkxy/dsh-model-route";
|
|
2
|
+
import { ContextFormed } from "@deepseek-ai/dsh-llm";
|
|
3
3
|
//#region src/contracts.d.ts
|
|
4
4
|
/** Public, browser-safe contracts. Host implementations stay in their owning packages. */
|
|
5
5
|
type ProducerSourceKind = `plugin:${string}`;
|
|
@@ -218,6 +218,56 @@ interface DshWebManagementDescriptor {
|
|
|
218
218
|
* Plugins that gate Creator-only tools or prompt sections compare against this.
|
|
219
219
|
*/
|
|
220
220
|
declare const CREATOR_PRESET_ID = "cordis";
|
|
221
|
+
/**
|
|
222
|
+
* Shared "Session Management" settings section id. The section is provided by
|
|
223
|
+
* shared infrastructure (see `registerSessionSettingsItem` in `client-utils`),
|
|
224
|
+
* not by any one business plugin: every participant registers only a row into
|
|
225
|
+
* {@link SESSION_SETTINGS_ITEM_SLOT}, so any enabled subset yields the same page.
|
|
226
|
+
*/
|
|
227
|
+
declare const SESSION_SETTINGS_SECTION_ID = "session-manager";
|
|
228
|
+
/**
|
|
229
|
+
* Child list slot declared by the shared Session Management page. Neutral
|
|
230
|
+
* naming on purpose: participants never import each other, they only
|
|
231
|
+
* contribute `{id, order}` rows into this slot while the page is alive.
|
|
232
|
+
*/
|
|
233
|
+
declare const SESSION_SETTINGS_ITEM_SLOT = "dsh.session-settings.item";
|
|
234
|
+
/**
|
|
235
|
+
* Host-owned nav icon seat: a root-scoped list where each entry's `id` must
|
|
236
|
+
* equal the `settings.section` id it decorates. Owner props are
|
|
237
|
+
* `{size, active}`; icons stay `aria-hidden` so the section label remains the
|
|
238
|
+
* accessible name. Contributors into a shared section may register the same
|
|
239
|
+
* icon `id` with different priorities — the ledger keeps every entry while the
|
|
240
|
+
* host renders one icon per section.
|
|
241
|
+
*/
|
|
242
|
+
declare const SETTINGS_SECTION_ICON_SLOT = "settings.section.icon";
|
|
243
|
+
/** Root context service key holding the shared auxiliary-model state. */
|
|
244
|
+
declare const AUXILIARY_MODEL_SERVICE = "dshAuxiliaryModel";
|
|
245
|
+
/** Authenticated host-RPC channel for get/set of the shared default. */
|
|
246
|
+
declare const AUXILIARY_MODEL_CHANNEL = "/dsh-auxiliary-model";
|
|
247
|
+
/** Model-settings namespace under which the shared default is published. */
|
|
248
|
+
declare const AUXILIARY_MODEL_NS = "@klarkxy/dsh-plugin-kit/auxiliary-model";
|
|
249
|
+
/**
|
|
250
|
+
* Typert identity of the shared change stream. Mounted on the root context
|
|
251
|
+
* with ownerNs filtering, so frames only carry {@link AUXILIARY_MODEL_NS}.
|
|
252
|
+
*/
|
|
253
|
+
declare const AUXILIARY_MODEL_CHANGES_IDENTITY: ModelSettingsChangesIdentity;
|
|
254
|
+
/** The value the shared default persists: `model: null` means follow the live session/host model. */
|
|
255
|
+
interface AuxiliaryModelDefaults {
|
|
256
|
+
readonly revision: number;
|
|
257
|
+
readonly model: ModelRoute | null;
|
|
258
|
+
}
|
|
259
|
+
/** Shape of the root singleton; produced and owned by `auxiliary-model.ts`. */
|
|
260
|
+
interface AuxiliaryModelShared {
|
|
261
|
+
readonly ready: Promise<void>;
|
|
262
|
+
snapshot(): AuxiliaryModelDefaults;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Synchronously read the shared auxiliary default; `undefined` only when no
|
|
266
|
+
* participant retains the singleton or the default is legitimately unset.
|
|
267
|
+
* An initialization or snapshot failure propagates: silently falling back to
|
|
268
|
+
* a different model after a storage error would spend the wrong budget.
|
|
269
|
+
*/
|
|
270
|
+
declare function getAuxiliaryModel(host: unknown): ModelRoute | undefined;
|
|
221
271
|
//#endregion
|
|
222
|
-
export { CHAT_EVENTS_SLOT, CREATOR_PRESET_ID, ContextKnowledge, DshWebManagementDescriptor, EvidenceRef, KnowledgeKind, KnowledgeScope, MemoryMutationOptions, MemoryQuery, MemoryRecord, MemoryService, type ModelRoute, ModelSettingsChangesIdentity, NewMemoryRecord, ProcedureKnowledge, ProducerMessageSource, ProducerSourceKind, RpcResult, SubagentSessionLike, TaskCheckpoint, TaskContract, WEB_PROVIDER_AVAILABILITY_EVENT, isSubagentSession, modelSettingsChangesIdentity, producerMessageSource, projectIdFromCwd };
|
|
272
|
+
export { AUXILIARY_MODEL_CHANGES_IDENTITY, AUXILIARY_MODEL_CHANNEL, AUXILIARY_MODEL_NS, AUXILIARY_MODEL_SERVICE, AuxiliaryModelDefaults, AuxiliaryModelShared, CHAT_EVENTS_SLOT, CREATOR_PRESET_ID, ContextKnowledge, DshWebManagementDescriptor, EvidenceRef, KnowledgeKind, KnowledgeScope, MemoryMutationOptions, MemoryQuery, MemoryRecord, MemoryService, type ModelRoute, ModelSettingsChangesIdentity, NewMemoryRecord, ProcedureKnowledge, ProducerMessageSource, ProducerSourceKind, RpcResult, SESSION_SETTINGS_ITEM_SLOT, SESSION_SETTINGS_SECTION_ID, SETTINGS_SECTION_ICON_SLOT, SubagentSessionLike, TaskCheckpoint, TaskContract, WEB_PROVIDER_AVAILABILITY_EVENT, getAuxiliaryModel, isSubagentSession, modelSettingsChangesIdentity, producerMessageSource, projectIdFromCwd };
|
|
223
273
|
//# sourceMappingURL=contracts.d.ts.map
|
package/lib/contracts.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { modelRouteOverride } from "@klarkxy/dsh-model-route";
|
|
1
2
|
//#region src/contracts.ts
|
|
2
3
|
function producerMessageSource(plugin) {
|
|
3
4
|
return {
|
|
@@ -52,7 +53,60 @@ const WEB_PROVIDER_AVAILABILITY_EVENT = "web/provider-availability-updated";
|
|
|
52
53
|
* Plugins that gate Creator-only tools or prompt sections compare against this.
|
|
53
54
|
*/
|
|
54
55
|
const CREATOR_PRESET_ID = "cordis";
|
|
56
|
+
/**
|
|
57
|
+
* Shared "Session Management" settings section id. The section is provided by
|
|
58
|
+
* shared infrastructure (see `registerSessionSettingsItem` in `client-utils`),
|
|
59
|
+
* not by any one business plugin: every participant registers only a row into
|
|
60
|
+
* {@link SESSION_SETTINGS_ITEM_SLOT}, so any enabled subset yields the same page.
|
|
61
|
+
*/
|
|
62
|
+
const SESSION_SETTINGS_SECTION_ID = "session-manager";
|
|
63
|
+
/**
|
|
64
|
+
* Child list slot declared by the shared Session Management page. Neutral
|
|
65
|
+
* naming on purpose: participants never import each other, they only
|
|
66
|
+
* contribute `{id, order}` rows into this slot while the page is alive.
|
|
67
|
+
*/
|
|
68
|
+
const SESSION_SETTINGS_ITEM_SLOT = "dsh.session-settings.item";
|
|
69
|
+
/**
|
|
70
|
+
* Host-owned nav icon seat: a root-scoped list where each entry's `id` must
|
|
71
|
+
* equal the `settings.section` id it decorates. Owner props are
|
|
72
|
+
* `{size, active}`; icons stay `aria-hidden` so the section label remains the
|
|
73
|
+
* accessible name. Contributors into a shared section may register the same
|
|
74
|
+
* icon `id` with different priorities — the ledger keeps every entry while the
|
|
75
|
+
* host renders one icon per section.
|
|
76
|
+
*/
|
|
77
|
+
const SETTINGS_SECTION_ICON_SLOT = "settings.section.icon";
|
|
78
|
+
/** Root context service key holding the shared auxiliary-model state. */
|
|
79
|
+
const AUXILIARY_MODEL_SERVICE = "dshAuxiliaryModel";
|
|
80
|
+
/** Authenticated host-RPC channel for get/set of the shared default. */
|
|
81
|
+
const AUXILIARY_MODEL_CHANNEL = "/dsh-auxiliary-model";
|
|
82
|
+
/** Model-settings namespace under which the shared default is published. */
|
|
83
|
+
const AUXILIARY_MODEL_NS = "@klarkxy/dsh-plugin-kit/auxiliary-model";
|
|
84
|
+
/**
|
|
85
|
+
* Typert identity of the shared change stream. Mounted on the root context
|
|
86
|
+
* with ownerNs filtering, so frames only carry {@link AUXILIARY_MODEL_NS}.
|
|
87
|
+
*/
|
|
88
|
+
const AUXILIARY_MODEL_CHANGES_IDENTITY = modelSettingsChangesIdentity("@klarkxy/dsh-plugin-kit");
|
|
89
|
+
function sharedAuxiliaryModel(host) {
|
|
90
|
+
const root = host?.root;
|
|
91
|
+
try {
|
|
92
|
+
const shared = root?.get?.(AUXILIARY_MODEL_SERVICE, false);
|
|
93
|
+
return shared && typeof shared.snapshot === "function" ? shared : void 0;
|
|
94
|
+
} catch {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Synchronously read the shared auxiliary default; `undefined` only when no
|
|
100
|
+
* participant retains the singleton or the default is legitimately unset.
|
|
101
|
+
* An initialization or snapshot failure propagates: silently falling back to
|
|
102
|
+
* a different model after a storage error would spend the wrong budget.
|
|
103
|
+
*/
|
|
104
|
+
function getAuxiliaryModel(host) {
|
|
105
|
+
const shared = sharedAuxiliaryModel(host);
|
|
106
|
+
if (!shared) return void 0;
|
|
107
|
+
return modelRouteOverride(shared.snapshot().model ?? void 0);
|
|
108
|
+
}
|
|
55
109
|
//#endregion
|
|
56
|
-
export { CHAT_EVENTS_SLOT, CREATOR_PRESET_ID, WEB_PROVIDER_AVAILABILITY_EVENT, isSubagentSession, modelSettingsChangesIdentity, producerMessageSource, projectIdFromCwd };
|
|
110
|
+
export { AUXILIARY_MODEL_CHANGES_IDENTITY, AUXILIARY_MODEL_CHANNEL, AUXILIARY_MODEL_NS, AUXILIARY_MODEL_SERVICE, CHAT_EVENTS_SLOT, CREATOR_PRESET_ID, SESSION_SETTINGS_ITEM_SLOT, SESSION_SETTINGS_SECTION_ID, SETTINGS_SECTION_ICON_SLOT, WEB_PROVIDER_AVAILABILITY_EVENT, getAuxiliaryModel, isSubagentSession, modelSettingsChangesIdentity, producerMessageSource, projectIdFromCwd };
|
|
57
111
|
|
|
58
112
|
//# sourceMappingURL=contracts.js.map
|
package/lib/contracts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contracts.js","names":[],"sources":["../src/contracts.ts"],"sourcesContent":["import type { ContextFormed } from '@deepseek-ai/dsh-llm'\nimport type { ModelRoute } from '@klarkxy/dsh-model-route'\n\n/**\n * The canonical home is @klarkxy/dsh-model-route; re-exported here so existing\n * plugin-kit consumers keep compiling.\n */\nexport type { ModelRoute }\n\n/** Public, browser-safe contracts. Host implementations stay in their owning packages. */\nexport type ProducerSourceKind = `plugin:${string}`\nexport type ProducerMessageSource = { kind: ProducerSourceKind; plugin: string } & ContextFormed\n\n/**\n * V4 sessions persist the producing extension in `kind`; the retired generic\n * `plugin` wrapper is not admitted. Each feature plugin declares its own kind.\n */\ndeclare module '@deepseek-ai/dsh-llm' {\n interface MessageSourceMap {\n [kind: ProducerSourceKind]: ProducerMessageSource\n }\n}\n\nexport function producerMessageSource(plugin: string): ProducerMessageSource {\n return { kind: `plugin:${plugin}`, plugin }\n}\n\nexport type RpcResult<T = unknown> = { ok: true; value: T } | { ok: false; error: { code: string; message: string } }\nexport const CHAT_EVENTS_SLOT = 'dsh-editor.chat.events'\nexport interface EvidenceRef { sessionId: string; seq: number; kind: 'user' | 'tool' | 'turn' | 'manual'; excerpt?: string }\nexport interface TaskContract {\n id: string\n sessionId: string\n sourceVersion: string\n revision: number\n goal: string\n deliverables: string[]\n inScope: string[]\n outOfScope: string[]\n constraints: string[]\n acceptance: string[]\n assumptions: string[]\n questions: string[]\n evidence: EvidenceRef[]\n readiness: 'pending' | 'clear-request' | 'user-confirmed' | 'disclosed-assumptions' | 'cancelled' | 'stale'\n updatedAt: number\n}\nexport interface TaskCheckpoint {\n id: string\n sessionId: string\n sourceVersion: string\n contractVersion?: number\n fromSeq: number\n toSeq: number\n revision: number\n status: 'running' | 'completed' | 'failed' | 'cancelled'\n items: Array<{ label: string; state: 'pending' | 'verified' | 'failed'; evidence: EvidenceRef[] }>\n constraints: string[]\n nextAction: string\n createdAt: number\n}\nexport type KnowledgeScope = { kind: 'global' } | { kind: 'project'; projectId: string }\nexport type KnowledgeKind = 'preference' | 'project-fact' | 'decision' | 'vocabulary' | 'activity' | 'lesson'\n/** Descriptive context, never an executable instruction or authorization. */\nexport interface ContextKnowledge {\n subject: string\n domain: string\n key: string\n aliases: string[]\n observedAt: number\n /** The user's own time expression, not an inferred completion date. */\n eventTime?: string\n activityStatus?: 'planned' | 'in-progress' | 'blocked' | 'paused' | 'completed' | 'cancelled' | 'unknown'\n}\n/** A scoped method. Observations remain candidates until explicitly accepted. */\nexport interface ProcedureKnowledge {\n origin: 'instruction' | 'observation'\n goal: string\n when: string[]\n steps: string[]\n avoid: string[]\n verify: string[]\n}\nexport interface MemoryRecord {\n id: string\n revision: number\n scope: KnowledgeScope\n kind: KnowledgeKind\n status: 'candidate' | 'active' | 'rejected' | 'superseded' | 'revoked' | 'deleted'\n title: string\n content: string\n tags: string[]\n evidence: EvidenceRef[]\n exceptions: string[]\n source: 'user' | 'memory' | 'dream' | 'self-improvement'\n context?: ContextKnowledge\n procedure?: ProcedureKnowledge\n createdAt: number\n updatedAt: number\n expiresAt?: number\n supersedes?: string[]\n /** Exact record revisions behind a derived candidate; revalidated on adoption. */\n basis?: Array<{ id: string; revision: number }>\n}\nexport type NewMemoryRecord = Omit<MemoryRecord, 'id' | 'revision' | 'createdAt' | 'updatedAt'>\nexport interface MemoryQuery { scope: KnowledgeScope; query?: string; kinds?: KnowledgeKind[]; statuses?: MemoryRecord['status'][]; limit?: number }\nexport interface MemoryMutationOptions { signal?: AbortSignal; isCurrent?: () => boolean }\nexport interface MemoryService {\n list(query: MemoryQuery): Promise<MemoryRecord[]>\n create(record: NewMemoryRecord, options?: MemoryMutationOptions): Promise<MemoryRecord>\n update(id: string, patch: Partial<Pick<MemoryRecord, 'title' | 'content' | 'tags' | 'exceptions' | 'status' | 'expiresAt'>>, expectedRevision: number, options?: MemoryMutationOptions): Promise<MemoryRecord>\n /** Explicit user promotion, persisted atomically on the same record; preserves expiry and evidence. */\n promoteToGlobal(id: string, expectedRevision: number, options?: MemoryMutationOptions): Promise<MemoryRecord>\n remove(id: string, expectedRevision: number, options?: MemoryMutationOptions): Promise<void>\n recall(query: MemoryQuery): Promise<MemoryRecord[]>\n}\n\n/**\n * The session creation metadata the host snapshots when it creates a delegated child\n * thread. Mirrors `CreateAgentOptions.meta` in `@deepseek-ai/dsh-agent`; it is durable\n * session data, so it survives a restart and needs no ancestry walk.\n */\nexport interface SubagentSessionLike {\n readonly header?: {\n readonly parentSession?: unknown\n readonly origin?: unknown\n readonly delegationDepth?: unknown\n readonly isSeeded?: unknown\n readonly classification?: unknown\n }\n}\n\n/**\n * True when `session` is a delegated child thread rather than a human-driven top-level\n * conversation.\n *\n * A message's `source.kind === 'user'` cannot answer this: a lead agent's dispatch prompt\n * is delivered as the child's own user message, so delegated work is indistinguishable\n * from a person typing once you only look at the message. Plugins use this to keep\n * automatic writes and derived model calls on top-level sessions.\n */\nexport function isSubagentSession(session: unknown): boolean {\n const header = (session as SubagentSessionLike | null | undefined)?.header\n if (!header || typeof header !== 'object') return false\n if (header.origin === 'subagent' || Number(header.delegationDepth ?? 0) > 0) return true\n // Native human forks also persist parentSession for history lineage. A fork's\n // seeded header distinguishes it from legacy children whose parent is the\n // only marker. Native rc.2 forks omit depth; patched hosts may persist zero.\n // Explicit delegation and unknown metadata still keep the child guard.\n const humanFork = header.isSeeded === true\n && (header.delegationDepth === undefined || header.delegationDepth === 0)\n && header.origin === undefined\n && (header.classification === undefined || header.classification === 'ordinary' || header.classification === 'free')\n return Boolean(header.parentSession) && !humanFork\n}\n\n/** Project keys come only from the host-validated session directory, never client input. */\nexport function projectIdFromCwd(cwd: string | undefined): string | undefined {\n if (typeof cwd !== 'string') return undefined\n const normalized = cwd.trim().replaceAll('\\\\', '/')\n if (!normalized) return undefined\n return normalized === '/' || /^[A-Za-z]:\\/$/.test(normalized) ? normalized : normalized.replace(/\\/+$/, '')\n}\n\n/**\n * One settings owner's Remote stream. Typert accepts either case in an RPC\n * segment, so the Cordis service key and the wire namespace use the same\n * Pascal segment. The scope's first letter stays uppercase.\n */\nexport interface ModelSettingsChangesIdentity {\n readonly service: string\n readonly namespace: string\n}\n\n/** `@klarkxy/dsh-git-commit` becomes `KlarkxyDshGitCommitModelSettingsChanges`. */\nexport function modelSettingsChangesIdentity(packageName: string): ModelSettingsChangesIdentity {\n const scoped = /^@([^/]+)\\/(.+)$/.exec(packageName)\n const body = scoped ? `${scoped[1]}/${scoped[2]}` : packageName\n const parts = body.split(/[^A-Za-z0-9]+/).filter(Boolean)\n if (!parts.length) throw new Error('Model settings package name has no RPC segment')\n const segment = `${parts.map(part => part.charAt(0).toUpperCase() + part.slice(1)).join('')}ModelSettingsChanges`\n if (!/^[A-Z][A-Za-z0-9]*$/.test(segment)) throw new Error(`Model settings sync name is not an RPC segment: ${segment}`)\n return { service: segment, namespace: segment }\n}\n\n/**\n * Repository extension: a native web provider announces that its local\n * availability changed. Kind is `search` or `fetch`; id is the provider id.\n * Listeners refresh management state and must not treat this as a network probe.\n */\nexport const WEB_PROVIDER_AVAILABILITY_EVENT = 'web/provider-availability-updated' as const\n\n/**\n * Optional display metadata a provider may attach when it registers directly\n * with `ctx.web`. Execution and credentials stay with the provider; this object\n * is display-only and must never carry secret values.\n */\nexport interface DshWebManagementDescriptor {\n readonly id: string\n readonly label: string\n readonly description: string\n readonly defaultBaseURL?: string\n readonly credentialRef?: string\n /** True when the key is shared with another surface. The page must not delete it. */\n readonly credentialShared?: boolean\n /** Optional notice naming the settings surface that owns a shared credential. */\n readonly credentialHint?: string\n readonly billing: 'request' | 'model-and-tools' | 'none' | 'unknown'\n /** Optional provider-supplied billing summary. */\n readonly pricing?: string\n /** Public HTTPS page explaining provider charges. */\n readonly pricingUrl?: string\n /** Public HTTPS page where the user can create an API key. */\n readonly signupUrl?: string\n /** Plugin that owns configuration. Native providers keep their settings there. */\n readonly configurationOwner?: string\n /** Explicit settings link supplied by the provider, never inferred from its id. */\n readonly configurationUrl?: string\n /** Fixed legacy route migrated by a bundle-owned adapter; not used by native plugins. */\n readonly legacyId?: string\n}\n\n/**\n * Host Creator agent-preset id (`agentPresets.composedPreset`).\n * Plugins that gate Creator-only tools or prompt sections compare against this.\n */\nexport const CREATOR_PRESET_ID = 'cordis'\n"],"mappings":";AAuBA,SAAgB,sBAAsB,QAAuC;CAC3E,OAAO;EAAE,MAAM,UAAU;EAAU;CAAO;AAC5C;AAGA,MAAa,mBAAmB;;;;;;;;;;AAiHhC,SAAgB,kBAAkB,SAA2B;CAC3D,MAAM,SAAU,SAAoD;CACpE,IAAI,CAAC,UAAU,OAAO,WAAW,UAAU,OAAO;CAClD,IAAI,OAAO,WAAW,cAAc,OAAO,OAAO,mBAAmB,CAAC,IAAI,GAAG,OAAO;CAKpF,MAAM,YAAY,OAAO,aAAa,SAChC,OAAO,oBAAoB,KAAA,KAAa,OAAO,oBAAoB,MACpE,OAAO,WAAW,KAAA,MACjB,OAAO,mBAAmB,KAAA,KAAa,OAAO,mBAAmB,cAAc,OAAO,mBAAmB;CAC/G,OAAO,QAAQ,OAAO,aAAa,KAAK,CAAC;AAC3C;;AAGA,SAAgB,iBAAiB,KAA6C;CAC5E,IAAI,OAAO,QAAQ,UAAU,OAAO,KAAA;CACpC,MAAM,aAAa,IAAI,KAAK,CAAC,CAAC,WAAW,MAAM,GAAG;CAClD,IAAI,CAAC,YAAY,OAAO,KAAA;CACxB,OAAO,eAAe,OAAO,gBAAgB,KAAK,UAAU,IAAI,aAAa,WAAW,QAAQ,QAAQ,EAAE;AAC5G;;AAaA,SAAgB,6BAA6B,aAAmD;CAC9F,MAAM,SAAS,mBAAmB,KAAK,WAAW;CAElD,MAAM,SADO,SAAS,GAAG,OAAO,GAAG,GAAG,OAAO,OAAO,YAAA,CACjC,MAAM,eAAe,CAAC,CAAC,OAAO,OAAO;CACxD,IAAI,CAAC,MAAM,QAAQ,MAAM,IAAI,MAAM,gDAAgD;CACnF,MAAM,UAAU,GAAG,MAAM,KAAI,SAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE;CAC5F,IAAI,CAAC,sBAAsB,KAAK,OAAO,GAAG,MAAM,IAAI,MAAM,mDAAmD,SAAS;CACtH,OAAO;EAAE,SAAS;EAAS,WAAW;CAAQ;AAChD;;;;;;AAOA,MAAa,kCAAkC;;;;;AAoC/C,MAAa,oBAAoB"}
|
|
1
|
+
{"version":3,"file":"contracts.js","names":[],"sources":["../src/contracts.ts"],"sourcesContent":["import type { ContextFormed } from '@deepseek-ai/dsh-llm'\nimport { modelRouteOverride, type ModelRoute } from '@klarkxy/dsh-model-route'\n\n/**\n * The canonical home is @klarkxy/dsh-model-route; re-exported here so existing\n * plugin-kit consumers keep compiling.\n */\nexport type { ModelRoute }\n\n/** Public, browser-safe contracts. Host implementations stay in their owning packages. */\nexport type ProducerSourceKind = `plugin:${string}`\nexport type ProducerMessageSource = { kind: ProducerSourceKind; plugin: string } & ContextFormed\n\n/**\n * V4 sessions persist the producing extension in `kind`; the retired generic\n * `plugin` wrapper is not admitted. Each feature plugin declares its own kind.\n */\ndeclare module '@deepseek-ai/dsh-llm' {\n interface MessageSourceMap {\n [kind: ProducerSourceKind]: ProducerMessageSource\n }\n}\n\nexport function producerMessageSource(plugin: string): ProducerMessageSource {\n return { kind: `plugin:${plugin}`, plugin }\n}\n\nexport type RpcResult<T = unknown> = { ok: true; value: T } | { ok: false; error: { code: string; message: string } }\nexport const CHAT_EVENTS_SLOT = 'dsh-editor.chat.events'\nexport interface EvidenceRef { sessionId: string; seq: number; kind: 'user' | 'tool' | 'turn' | 'manual'; excerpt?: string }\nexport interface TaskContract {\n id: string\n sessionId: string\n sourceVersion: string\n revision: number\n goal: string\n deliverables: string[]\n inScope: string[]\n outOfScope: string[]\n constraints: string[]\n acceptance: string[]\n assumptions: string[]\n questions: string[]\n evidence: EvidenceRef[]\n readiness: 'pending' | 'clear-request' | 'user-confirmed' | 'disclosed-assumptions' | 'cancelled' | 'stale'\n updatedAt: number\n}\nexport interface TaskCheckpoint {\n id: string\n sessionId: string\n sourceVersion: string\n contractVersion?: number\n fromSeq: number\n toSeq: number\n revision: number\n status: 'running' | 'completed' | 'failed' | 'cancelled'\n items: Array<{ label: string; state: 'pending' | 'verified' | 'failed'; evidence: EvidenceRef[] }>\n constraints: string[]\n nextAction: string\n createdAt: number\n}\nexport type KnowledgeScope = { kind: 'global' } | { kind: 'project'; projectId: string }\nexport type KnowledgeKind = 'preference' | 'project-fact' | 'decision' | 'vocabulary' | 'activity' | 'lesson'\n/** Descriptive context, never an executable instruction or authorization. */\nexport interface ContextKnowledge {\n subject: string\n domain: string\n key: string\n aliases: string[]\n observedAt: number\n /** The user's own time expression, not an inferred completion date. */\n eventTime?: string\n activityStatus?: 'planned' | 'in-progress' | 'blocked' | 'paused' | 'completed' | 'cancelled' | 'unknown'\n}\n/** A scoped method. Observations remain candidates until explicitly accepted. */\nexport interface ProcedureKnowledge {\n origin: 'instruction' | 'observation'\n goal: string\n when: string[]\n steps: string[]\n avoid: string[]\n verify: string[]\n}\nexport interface MemoryRecord {\n id: string\n revision: number\n scope: KnowledgeScope\n kind: KnowledgeKind\n status: 'candidate' | 'active' | 'rejected' | 'superseded' | 'revoked' | 'deleted'\n title: string\n content: string\n tags: string[]\n evidence: EvidenceRef[]\n exceptions: string[]\n source: 'user' | 'memory' | 'dream' | 'self-improvement'\n context?: ContextKnowledge\n procedure?: ProcedureKnowledge\n createdAt: number\n updatedAt: number\n expiresAt?: number\n supersedes?: string[]\n /** Exact record revisions behind a derived candidate; revalidated on adoption. */\n basis?: Array<{ id: string; revision: number }>\n}\nexport type NewMemoryRecord = Omit<MemoryRecord, 'id' | 'revision' | 'createdAt' | 'updatedAt'>\nexport interface MemoryQuery { scope: KnowledgeScope; query?: string; kinds?: KnowledgeKind[]; statuses?: MemoryRecord['status'][]; limit?: number }\nexport interface MemoryMutationOptions { signal?: AbortSignal; isCurrent?: () => boolean }\nexport interface MemoryService {\n list(query: MemoryQuery): Promise<MemoryRecord[]>\n create(record: NewMemoryRecord, options?: MemoryMutationOptions): Promise<MemoryRecord>\n update(id: string, patch: Partial<Pick<MemoryRecord, 'title' | 'content' | 'tags' | 'exceptions' | 'status' | 'expiresAt'>>, expectedRevision: number, options?: MemoryMutationOptions): Promise<MemoryRecord>\n /** Explicit user promotion, persisted atomically on the same record; preserves expiry and evidence. */\n promoteToGlobal(id: string, expectedRevision: number, options?: MemoryMutationOptions): Promise<MemoryRecord>\n remove(id: string, expectedRevision: number, options?: MemoryMutationOptions): Promise<void>\n recall(query: MemoryQuery): Promise<MemoryRecord[]>\n}\n\n/**\n * The session creation metadata the host snapshots when it creates a delegated child\n * thread. Mirrors `CreateAgentOptions.meta` in `@deepseek-ai/dsh-agent`; it is durable\n * session data, so it survives a restart and needs no ancestry walk.\n */\nexport interface SubagentSessionLike {\n readonly header?: {\n readonly parentSession?: unknown\n readonly origin?: unknown\n readonly delegationDepth?: unknown\n readonly isSeeded?: unknown\n readonly classification?: unknown\n }\n}\n\n/**\n * True when `session` is a delegated child thread rather than a human-driven top-level\n * conversation.\n *\n * A message's `source.kind === 'user'` cannot answer this: a lead agent's dispatch prompt\n * is delivered as the child's own user message, so delegated work is indistinguishable\n * from a person typing once you only look at the message. Plugins use this to keep\n * automatic writes and derived model calls on top-level sessions.\n */\nexport function isSubagentSession(session: unknown): boolean {\n const header = (session as SubagentSessionLike | null | undefined)?.header\n if (!header || typeof header !== 'object') return false\n if (header.origin === 'subagent' || Number(header.delegationDepth ?? 0) > 0) return true\n // Native human forks also persist parentSession for history lineage. A fork's\n // seeded header distinguishes it from legacy children whose parent is the\n // only marker. Native rc.2 forks omit depth; patched hosts may persist zero.\n // Explicit delegation and unknown metadata still keep the child guard.\n const humanFork = header.isSeeded === true\n && (header.delegationDepth === undefined || header.delegationDepth === 0)\n && header.origin === undefined\n && (header.classification === undefined || header.classification === 'ordinary' || header.classification === 'free')\n return Boolean(header.parentSession) && !humanFork\n}\n\n/** Project keys come only from the host-validated session directory, never client input. */\nexport function projectIdFromCwd(cwd: string | undefined): string | undefined {\n if (typeof cwd !== 'string') return undefined\n const normalized = cwd.trim().replaceAll('\\\\', '/')\n if (!normalized) return undefined\n return normalized === '/' || /^[A-Za-z]:\\/$/.test(normalized) ? normalized : normalized.replace(/\\/+$/, '')\n}\n\n/**\n * One settings owner's Remote stream. Typert accepts either case in an RPC\n * segment, so the Cordis service key and the wire namespace use the same\n * Pascal segment. The scope's first letter stays uppercase.\n */\nexport interface ModelSettingsChangesIdentity {\n readonly service: string\n readonly namespace: string\n}\n\n/** `@klarkxy/dsh-git-commit` becomes `KlarkxyDshGitCommitModelSettingsChanges`. */\nexport function modelSettingsChangesIdentity(packageName: string): ModelSettingsChangesIdentity {\n const scoped = /^@([^/]+)\\/(.+)$/.exec(packageName)\n const body = scoped ? `${scoped[1]}/${scoped[2]}` : packageName\n const parts = body.split(/[^A-Za-z0-9]+/).filter(Boolean)\n if (!parts.length) throw new Error('Model settings package name has no RPC segment')\n const segment = `${parts.map(part => part.charAt(0).toUpperCase() + part.slice(1)).join('')}ModelSettingsChanges`\n if (!/^[A-Z][A-Za-z0-9]*$/.test(segment)) throw new Error(`Model settings sync name is not an RPC segment: ${segment}`)\n return { service: segment, namespace: segment }\n}\n\n/**\n * Repository extension: a native web provider announces that its local\n * availability changed. Kind is `search` or `fetch`; id is the provider id.\n * Listeners refresh management state and must not treat this as a network probe.\n */\nexport const WEB_PROVIDER_AVAILABILITY_EVENT = 'web/provider-availability-updated' as const\n\n/**\n * Optional display metadata a provider may attach when it registers directly\n * with `ctx.web`. Execution and credentials stay with the provider; this object\n * is display-only and must never carry secret values.\n */\nexport interface DshWebManagementDescriptor {\n readonly id: string\n readonly label: string\n readonly description: string\n readonly defaultBaseURL?: string\n readonly credentialRef?: string\n /** True when the key is shared with another surface. The page must not delete it. */\n readonly credentialShared?: boolean\n /** Optional notice naming the settings surface that owns a shared credential. */\n readonly credentialHint?: string\n readonly billing: 'request' | 'model-and-tools' | 'none' | 'unknown'\n /** Optional provider-supplied billing summary. */\n readonly pricing?: string\n /** Public HTTPS page explaining provider charges. */\n readonly pricingUrl?: string\n /** Public HTTPS page where the user can create an API key. */\n readonly signupUrl?: string\n /** Plugin that owns configuration. Native providers keep their settings there. */\n readonly configurationOwner?: string\n /** Explicit settings link supplied by the provider, never inferred from its id. */\n readonly configurationUrl?: string\n /** Fixed legacy route migrated by a bundle-owned adapter; not used by native plugins. */\n readonly legacyId?: string\n}\n\n/**\n * Host Creator agent-preset id (`agentPresets.composedPreset`).\n * Plugins that gate Creator-only tools or prompt sections compare against this.\n */\nexport const CREATOR_PRESET_ID = 'cordis'\n\n/**\n * Shared \"Session Management\" settings section id. The section is provided by\n * shared infrastructure (see `registerSessionSettingsItem` in `client-utils`),\n * not by any one business plugin: every participant registers only a row into\n * {@link SESSION_SETTINGS_ITEM_SLOT}, so any enabled subset yields the same page.\n */\nexport const SESSION_SETTINGS_SECTION_ID = 'session-manager'\n\n/**\n * Child list slot declared by the shared Session Management page. Neutral\n * naming on purpose: participants never import each other, they only\n * contribute `{id, order}` rows into this slot while the page is alive.\n */\nexport const SESSION_SETTINGS_ITEM_SLOT = 'dsh.session-settings.item'\n\n/**\n * Host-owned nav icon seat: a root-scoped list where each entry's `id` must\n * equal the `settings.section` id it decorates. Owner props are\n * `{size, active}`; icons stay `aria-hidden` so the section label remains the\n * accessible name. Contributors into a shared section may register the same\n * icon `id` with different priorities — the ledger keeps every entry while the\n * host renders one icon per section.\n */\nexport const SETTINGS_SECTION_ICON_SLOT = 'settings.section.icon'\n\n/* Shared auxiliary-model default. Feature plugins may store a global default\n * route that fills the tier between their own saved override and the live\n * session model. The durable state is a root-context singleton (like the\n * shared session-settings page): it exists while any participant retains it\n * and is never owned by a business plugin. See `auxiliary-model.ts`. */\n\n/** Root context service key holding the shared auxiliary-model state. */\nexport const AUXILIARY_MODEL_SERVICE = 'dshAuxiliaryModel'\n\n/** Authenticated host-RPC channel for get/set of the shared default. */\nexport const AUXILIARY_MODEL_CHANNEL = '/dsh-auxiliary-model'\n\n/** Model-settings namespace under which the shared default is published. */\nexport const AUXILIARY_MODEL_NS = '@klarkxy/dsh-plugin-kit/auxiliary-model'\n\n/**\n * Typert identity of the shared change stream. Mounted on the root context\n * with ownerNs filtering, so frames only carry {@link AUXILIARY_MODEL_NS}.\n */\nexport const AUXILIARY_MODEL_CHANGES_IDENTITY = modelSettingsChangesIdentity('@klarkxy/dsh-plugin-kit')\n\n/** The value the shared default persists: `model: null` means follow the live session/host model. */\nexport interface AuxiliaryModelDefaults {\n readonly revision: number\n readonly model: ModelRoute | null\n}\n\n/** Shape of the root singleton; produced and owned by `auxiliary-model.ts`. */\nexport interface AuxiliaryModelShared {\n readonly ready: Promise<void>\n snapshot(): AuxiliaryModelDefaults\n}\n\nfunction sharedAuxiliaryModel(host: unknown): AuxiliaryModelShared | undefined {\n const root = (host as { root?: unknown } | undefined)?.root as { get?(name: string, strict?: boolean): unknown } | undefined\n try {\n const shared = root?.get?.(AUXILIARY_MODEL_SERVICE, false) as AuxiliaryModelShared | undefined\n return shared && typeof shared.snapshot === 'function' ? shared : undefined\n } catch { return undefined }\n}\n\n/**\n * Synchronously read the shared auxiliary default; `undefined` only when no\n * participant retains the singleton or the default is legitimately unset.\n * An initialization or snapshot failure propagates: silently falling back to\n * a different model after a storage error would spend the wrong budget.\n */\nexport function getAuxiliaryModel(host: unknown): ModelRoute | undefined {\n const shared = sharedAuxiliaryModel(host)\n if (!shared) return undefined\n return modelRouteOverride(shared.snapshot().model ?? undefined)\n}\n"],"mappings":";;AAuBA,SAAgB,sBAAsB,QAAuC;CAC3E,OAAO;EAAE,MAAM,UAAU;EAAU;CAAO;AAC5C;AAGA,MAAa,mBAAmB;;;;;;;;;;AAiHhC,SAAgB,kBAAkB,SAA2B;CAC3D,MAAM,SAAU,SAAoD;CACpE,IAAI,CAAC,UAAU,OAAO,WAAW,UAAU,OAAO;CAClD,IAAI,OAAO,WAAW,cAAc,OAAO,OAAO,mBAAmB,CAAC,IAAI,GAAG,OAAO;CAKpF,MAAM,YAAY,OAAO,aAAa,SAChC,OAAO,oBAAoB,KAAA,KAAa,OAAO,oBAAoB,MACpE,OAAO,WAAW,KAAA,MACjB,OAAO,mBAAmB,KAAA,KAAa,OAAO,mBAAmB,cAAc,OAAO,mBAAmB;CAC/G,OAAO,QAAQ,OAAO,aAAa,KAAK,CAAC;AAC3C;;AAGA,SAAgB,iBAAiB,KAA6C;CAC5E,IAAI,OAAO,QAAQ,UAAU,OAAO,KAAA;CACpC,MAAM,aAAa,IAAI,KAAK,CAAC,CAAC,WAAW,MAAM,GAAG;CAClD,IAAI,CAAC,YAAY,OAAO,KAAA;CACxB,OAAO,eAAe,OAAO,gBAAgB,KAAK,UAAU,IAAI,aAAa,WAAW,QAAQ,QAAQ,EAAE;AAC5G;;AAaA,SAAgB,6BAA6B,aAAmD;CAC9F,MAAM,SAAS,mBAAmB,KAAK,WAAW;CAElD,MAAM,SADO,SAAS,GAAG,OAAO,GAAG,GAAG,OAAO,OAAO,YAAA,CACjC,MAAM,eAAe,CAAC,CAAC,OAAO,OAAO;CACxD,IAAI,CAAC,MAAM,QAAQ,MAAM,IAAI,MAAM,gDAAgD;CACnF,MAAM,UAAU,GAAG,MAAM,KAAI,SAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE;CAC5F,IAAI,CAAC,sBAAsB,KAAK,OAAO,GAAG,MAAM,IAAI,MAAM,mDAAmD,SAAS;CACtH,OAAO;EAAE,SAAS;EAAS,WAAW;CAAQ;AAChD;;;;;;AAOA,MAAa,kCAAkC;;;;;AAoC/C,MAAa,oBAAoB;;;;;;;AAQjC,MAAa,8BAA8B;;;;;;AAO3C,MAAa,6BAA6B;;;;;;;;;AAU1C,MAAa,6BAA6B;;AAS1C,MAAa,0BAA0B;;AAGvC,MAAa,0BAA0B;;AAGvC,MAAa,qBAAqB;;;;;AAMlC,MAAa,mCAAmC,6BAA6B,yBAAyB;AActG,SAAS,qBAAqB,MAAiD;CAC7E,MAAM,OAAQ,MAAyC;CACvD,IAAI;EACF,MAAM,SAAS,MAAM,MAAM,yBAAyB,KAAK;EACzD,OAAO,UAAU,OAAO,OAAO,aAAa,aAAa,SAAS,KAAA;CACpE,QAAQ;EAAE;CAAiB;AAC7B;;;;;;;AAQA,SAAgB,kBAAkB,MAAuC;CACvE,MAAM,SAAS,qBAAqB,IAAI;CACxC,IAAI,CAAC,QAAQ,OAAO,KAAA;CACpB,OAAO,mBAAmB,OAAO,SAAS,CAAC,CAAC,SAAS,KAAA,CAAS;AAChE"}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AuxiliaryContextTooLargeError, buildAuxiliaryUserText, packAuxiliaryContext } from "./auxiliary-context.js";
|
|
2
|
+
import { AUXILIARY_MODEL_CHANGES_IDENTITY, AUXILIARY_MODEL_CHANNEL, AUXILIARY_MODEL_NS, AUXILIARY_MODEL_SERVICE, AuxiliaryModelDefaults, AuxiliaryModelShared, CHAT_EVENTS_SLOT, CREATOR_PRESET_ID, ContextKnowledge, DshWebManagementDescriptor, EvidenceRef, KnowledgeKind, KnowledgeScope, MemoryMutationOptions, MemoryQuery, MemoryRecord, MemoryService, ModelRoute, ModelSettingsChangesIdentity, NewMemoryRecord, ProcedureKnowledge, ProducerMessageSource, ProducerSourceKind, RpcResult, SESSION_SETTINGS_ITEM_SLOT, SESSION_SETTINGS_SECTION_ID, SETTINGS_SECTION_ICON_SLOT, SubagentSessionLike, TaskCheckpoint, TaskContract, WEB_PROVIDER_AVAILABILITY_EVENT, getAuxiliaryModel, isSubagentSession, modelSettingsChangesIdentity, producerMessageSource, projectIdFromCwd } from "./contracts.js";
|
|
2
3
|
import { openCompatibleDomain } from "./domain.js";
|
|
3
4
|
import { HostRpcContext, registerHostRpc } from "./host-rpc.js";
|
|
4
5
|
import { FeatureModelRoute, LlmTextCaller, callLlmText, resolveFeatureModel } from "./llm-call.js";
|
|
5
|
-
export { CHAT_EVENTS_SLOT, type CREATOR_PRESET_ID, type ContextKnowledge, type DshWebManagementDescriptor, type EvidenceRef, type FeatureModelRoute, type HostRpcContext, type KnowledgeKind, type KnowledgeScope, type LlmTextCaller, type MemoryMutationOptions, type MemoryQuery, type MemoryRecord, type MemoryService, type ModelRoute, type ModelSettingsChangesIdentity, type NewMemoryRecord, type ProcedureKnowledge, type ProducerMessageSource, type ProducerSourceKind, type RpcResult, type SubagentSessionLike, type TaskCheckpoint, type TaskContract, type WEB_PROVIDER_AVAILABILITY_EVENT, callLlmText, type isSubagentSession, type modelSettingsChangesIdentity, openCompatibleDomain, producerMessageSource, projectIdFromCwd, registerHostRpc, resolveFeatureModel };
|
|
6
|
+
export { type AUXILIARY_MODEL_CHANGES_IDENTITY, type AUXILIARY_MODEL_CHANNEL, type AUXILIARY_MODEL_NS, type AUXILIARY_MODEL_SERVICE, AuxiliaryContextTooLargeError, type AuxiliaryModelDefaults, type AuxiliaryModelShared, CHAT_EVENTS_SLOT, type CREATOR_PRESET_ID, type ContextKnowledge, type DshWebManagementDescriptor, type EvidenceRef, type FeatureModelRoute, type HostRpcContext, type KnowledgeKind, type KnowledgeScope, type LlmTextCaller, type MemoryMutationOptions, type MemoryQuery, type MemoryRecord, type MemoryService, type ModelRoute, type ModelSettingsChangesIdentity, type NewMemoryRecord, type ProcedureKnowledge, type ProducerMessageSource, type ProducerSourceKind, type RpcResult, type SESSION_SETTINGS_ITEM_SLOT, type SESSION_SETTINGS_SECTION_ID, type SETTINGS_SECTION_ICON_SLOT, type SubagentSessionLike, type TaskCheckpoint, type TaskContract, type WEB_PROVIDER_AVAILABILITY_EVENT, buildAuxiliaryUserText, callLlmText, type getAuxiliaryModel, type isSubagentSession, type modelSettingsChangesIdentity, openCompatibleDomain, packAuxiliaryContext, producerMessageSource, projectIdFromCwd, registerHostRpc, resolveFeatureModel };
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CHAT_EVENTS_SLOT, producerMessageSource, projectIdFromCwd } from "./contracts.js";
|
|
2
2
|
import { registerHostRpc } from "./host-rpc.js";
|
|
3
3
|
import { openCompatibleDomain } from "./domain.js";
|
|
4
|
+
import { AuxiliaryContextTooLargeError, buildAuxiliaryUserText, packAuxiliaryContext } from "./auxiliary-context.js";
|
|
4
5
|
import { callLlmText, resolveFeatureModel } from "./llm-call.js";
|
|
5
|
-
export { CHAT_EVENTS_SLOT, callLlmText, openCompatibleDomain, producerMessageSource, projectIdFromCwd, registerHostRpc, resolveFeatureModel };
|
|
6
|
+
export { AuxiliaryContextTooLargeError, CHAT_EVENTS_SLOT, buildAuxiliaryUserText, callLlmText, openCompatibleDomain, packAuxiliaryContext, producerMessageSource, projectIdFromCwd, registerHostRpc, resolveFeatureModel };
|
package/lib/llm-call.d.ts
CHANGED
|
@@ -9,11 +9,21 @@ interface FeatureModelRoute {
|
|
|
9
9
|
type LlmTextCaller = Pick<LlmRuntime, 'prepareCall' | 'resolveCallConfig'>;
|
|
10
10
|
interface LlmTextRequest {
|
|
11
11
|
plugin: string;
|
|
12
|
+
/** Optional feature id, stamped on the context envelope for attribution. */
|
|
13
|
+
feature?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Stable material rebuilt from current facts (session events, durable
|
|
16
|
+
* records). Packed before the live text so provider-side caching reuses
|
|
17
|
+
* the prefix; never model outputs or approval history. Strings or
|
|
18
|
+
* pre-ordered segments.
|
|
19
|
+
*/
|
|
20
|
+
context?: string | readonly string[];
|
|
12
21
|
route: FeatureModelRoute;
|
|
13
22
|
system: string;
|
|
14
23
|
text: string;
|
|
15
24
|
maxTokens: number;
|
|
16
25
|
signal: AbortSignal;
|
|
26
|
+
/** The real session id for host attribution; never a feature-scoped alias. */
|
|
17
27
|
sessionId?: string;
|
|
18
28
|
/** Auxiliary-call marker adapters may special-case (e.g. disable thinking for titles). */
|
|
19
29
|
purpose?: GenerateOptions['purpose'];
|
|
@@ -28,11 +38,17 @@ interface LlmTextResult {
|
|
|
28
38
|
model: string;
|
|
29
39
|
inputTokens?: number;
|
|
30
40
|
outputTokens?: number;
|
|
41
|
+
totalTokens?: number;
|
|
42
|
+
cacheReadTokens?: number;
|
|
43
|
+
cacheWriteTokens?: number;
|
|
44
|
+
reasoningTokens?: number;
|
|
31
45
|
}
|
|
32
46
|
/**
|
|
33
|
-
* Plugin selection first;
|
|
34
|
-
*
|
|
35
|
-
*
|
|
47
|
+
* Plugin selection first; then the shared auxiliary default (when any
|
|
48
|
+
* participant retains it); otherwise the live session model, then the host
|
|
49
|
+
* default. Cordis callers must declare agents, sessionProjections and
|
|
50
|
+
* agentDefaultModel in their inject list so the fallback reads use the
|
|
51
|
+
* caller's live services.
|
|
36
52
|
*/
|
|
37
53
|
declare function resolveFeatureModel(host: unknown, saved: FeatureModelRoute | undefined, sessionId?: string): FeatureModelRoute | undefined;
|
|
38
54
|
/** One native model call. No retry, timeout policy, or provider queue. */
|
package/lib/llm-call.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { producerMessageSource } from "./contracts.js";
|
|
1
|
+
import { getAuxiliaryModel, producerMessageSource } from "./contracts.js";
|
|
2
|
+
import { buildAuxiliaryUserText } from "./auxiliary-context.js";
|
|
2
3
|
import { BlockAssembler, ReasoningEffortId, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
3
4
|
//#region src/llm-call.ts
|
|
4
5
|
function routeOf(value) {
|
|
@@ -15,13 +16,17 @@ function routeOf(value) {
|
|
|
15
16
|
};
|
|
16
17
|
}
|
|
17
18
|
/**
|
|
18
|
-
* Plugin selection first;
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* Plugin selection first; then the shared auxiliary default (when any
|
|
20
|
+
* participant retains it); otherwise the live session model, then the host
|
|
21
|
+
* default. Cordis callers must declare agents, sessionProjections and
|
|
22
|
+
* agentDefaultModel in their inject list so the fallback reads use the
|
|
23
|
+
* caller's live services.
|
|
21
24
|
*/
|
|
22
25
|
function resolveFeatureModel(host, saved, sessionId) {
|
|
23
26
|
const explicit = routeOf(saved);
|
|
24
27
|
if (explicit) return explicit;
|
|
28
|
+
const auxiliary = getAuxiliaryModel(host);
|
|
29
|
+
if (auxiliary) return auxiliary;
|
|
25
30
|
const face = host;
|
|
26
31
|
if (sessionId && face?.agents && face.sessionProjections) {
|
|
27
32
|
const session = face.agents.get(sessionId)?.session;
|
|
@@ -87,7 +92,7 @@ async function callLlmText(llm, request) {
|
|
|
87
92
|
source: producerMessageSource(request.plugin),
|
|
88
93
|
content: [{
|
|
89
94
|
type: "text",
|
|
90
|
-
text: request
|
|
95
|
+
text: buildAuxiliaryUserText(request)
|
|
91
96
|
}]
|
|
92
97
|
})],
|
|
93
98
|
signal: request.signal,
|
|
@@ -103,13 +108,18 @@ async function callLlmText(llm, request) {
|
|
|
103
108
|
}
|
|
104
109
|
if (assembler.blocks().some((block) => block.type === "tool-call") || assembler.finish.kind === "tool-calls") throw new Error("这次调用不使用工具。");
|
|
105
110
|
if (assembler.finish.kind !== "stop") throw new Error("模型调用未完成。");
|
|
111
|
+
const usage = assembler.usage;
|
|
106
112
|
return {
|
|
107
113
|
text: joinText(assembler),
|
|
108
114
|
provider: prepared.config.provider,
|
|
109
115
|
model: prepared.config.model,
|
|
110
|
-
...
|
|
111
|
-
inputTokens:
|
|
112
|
-
outputTokens:
|
|
116
|
+
...usage ? {
|
|
117
|
+
inputTokens: usage.inputTokens,
|
|
118
|
+
outputTokens: usage.outputTokens,
|
|
119
|
+
...usage.totalTokens !== void 0 ? { totalTokens: usage.totalTokens } : {},
|
|
120
|
+
...usage.cacheReadTokens !== void 0 ? { cacheReadTokens: usage.cacheReadTokens } : {},
|
|
121
|
+
...usage.cacheWriteTokens !== void 0 ? { cacheWriteTokens: usage.cacheWriteTokens } : {},
|
|
122
|
+
...usage.reasoningTokens !== void 0 ? { reasoningTokens: usage.reasoningTokens } : {}
|
|
113
123
|
} : {}
|
|
114
124
|
};
|
|
115
125
|
}
|
package/lib/llm-call.js.map
CHANGED
|
@@ -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 { producerMessageSource, type ModelRoute } from './contracts.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 route: FeatureModelRoute\n system: string\n text: string\n maxTokens: number\n signal: AbortSignal\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}\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; otherwise the live session model, then the host default.\n * Cordis callers must declare agents, sessionProjections and agentDefaultModel\n * in their inject list so the fallback reads use the 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 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: request.text }],\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 return {\n text: joinText(assembler),\n provider: prepared.config.provider,\n model: prepared.config.model,\n ...(assembler.usage ? { inputTokens: assembler.usage.inputTokens, outputTokens: assembler.usage.outputTokens } : {}),\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 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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"}
|
package/lib/model-menu.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
1
|
import { groupModelMenuChoices, knownModelFromChoices, modelMenuEffortLabel, modelMenuEffortOptions, modelMenuShortName, modelRouteKey as modelMenuChoiceKey, modelRouteOverride as modelMenuOverride, normalizeModelRoute as normalizeModelMenuRoute, parseModelMenuChoices, parseModelMenuChoices as parseModelMenuChoices$1, parseModelRouteKey as parseModelMenuChoiceKey, readModelCatalog } from "@klarkxy/dsh-model-route";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
3
|
//#region src/model-menu.ts
|
|
4
4
|
/**
|
|
5
5
|
* Shared plugin-page model menu.
|
package/lib/official-ui.js
CHANGED
|
@@ -219,6 +219,17 @@ ${scope} {
|
|
|
219
219
|
/* --- Layout scaffolding. --- */
|
|
220
220
|
& .dsh-ui-stack { display: flex; flex-direction: column; gap: 12px; }
|
|
221
221
|
& .dsh-ui-stack-lg { display: flex; flex-direction: column; gap: 20px; }
|
|
222
|
+
/* Plugin detail pages share one reading column. These are layout recipes;
|
|
223
|
+
* controls keep their own primitive styles. Form spacing follows the host's
|
|
224
|
+
* settings-form/fields.module.css and SettingsForm.module.css. */
|
|
225
|
+
& .dsh-ui-page { display: flex; flex-direction: column; gap: 16px; width: 100%; max-width: 760px; min-width: 0; }
|
|
226
|
+
& .dsh-ui-form { display: flex; flex-direction: column; gap: 0; min-width: 0; }
|
|
227
|
+
&.dsh-ui-form > .dsh-ui-field, & .dsh-ui-form > .dsh-ui-field { padding: 12px 0; }
|
|
228
|
+
& .dsh-ui-form-footer { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding-top: 16px; min-width: 0; }
|
|
229
|
+
& .dsh-ui-fields-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 20px; min-width: 0; margin: 0; padding: 0; border: 0; }
|
|
230
|
+
@media (max-width: 560px) {
|
|
231
|
+
& .dsh-ui-fields-grid { grid-template-columns: minmax(0, 1fr); }
|
|
232
|
+
}
|
|
222
233
|
& .dsh-ui-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
223
234
|
& .dsh-ui-row-wrap { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
|
|
224
235
|
& .dsh-ui-spacer { flex: 1; min-width: 0; }
|
|
@@ -246,6 +257,9 @@ ${scope} {
|
|
|
246
257
|
* official primitive, so this only owns the vertical rhythm. --- */
|
|
247
258
|
& .dsh-ui-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
|
248
259
|
& .dsh-ui-field + .dsh-ui-field { border-top: 0.5px solid var(--dsw-alias-border-l2); padding-top: 12px; }
|
|
260
|
+
/* Grid fields share a row, so the stacked-field divider must not win later in
|
|
261
|
+
* the cascade. Reset it here, after the field recipe. */
|
|
262
|
+
& .dsh-ui-fields-grid > .dsh-ui-field { padding: 0; border: 0; }
|
|
249
263
|
& .dsh-ui-label { display: flex; align-items: center; gap: 6px; font-size: 13px; line-height: 20px; font-weight: 500; color: var(--dsw-alias-label-primary); }
|
|
250
264
|
& .dsh-ui-label-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
|
|
251
265
|
& .dsh-ui-help { font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); }
|
|
@@ -436,7 +450,7 @@ ${scope} {
|
|
|
436
450
|
& *, & *::before, & *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
|
|
437
451
|
}
|
|
438
452
|
}
|
|
439
|
-
`.replace(/^& (\.dsh-ui-[a-z0-9-]+)(
|
|
453
|
+
`.replace(/^& (\.dsh-ui-[a-z0-9-]+)(?=\s*\{)/gm, (_match, className) => `&${className}, & ${className}`);
|
|
440
454
|
}
|
|
441
455
|
//#endregion
|
|
442
456
|
export { OFFICIAL_FOCUS_RING, OFFICIAL_THEME_TOKENS, OFFICIAL_THEME_TOKEN_NAMES, officialElevation, officialFocus, officialStrokedElevation, officialUiCss };
|