dsh-model-switch 0.0.0-stage → 0.4.18

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.
Files changed (63) hide show
  1. package/CHANGELOG.md +184 -0
  2. package/IMPLEMENTATION_PLAN.md +40 -0
  3. package/LICENSE +21 -0
  4. package/PRODUCT.md +33 -0
  5. package/README.md +198 -3
  6. package/README.zh.md +196 -0
  7. package/cordis.patch.yml +18 -0
  8. package/docs/screenshots/composer-picker.png +0 -0
  9. package/docs/screenshots/plan-review.png +0 -0
  10. package/docs/screenshots/settings-subagent.png +0 -0
  11. package/lib/adapter-registry.js +59 -0
  12. package/lib/capabilities.js +73 -0
  13. package/lib/client.js +3956 -0
  14. package/lib/index.js +1030 -0
  15. package/lib/main-settings.js +30 -0
  16. package/lib/subagent-route-policy.js +55 -0
  17. package/lib/subagent-runtime.js +186 -0
  18. package/lib/types/adapter-registry.d.ts +60 -0
  19. package/lib/types/capabilities-rpc.d.ts +12 -0
  20. package/lib/types/capabilities.d.ts +22 -0
  21. package/lib/types/client/ModelSwitchSettings.d.ts +32 -0
  22. package/lib/types/client/antigravity-catalog.d.ts +30 -0
  23. package/lib/types/client/derived-config-form.d.ts +10 -0
  24. package/lib/types/client/derived-settings-scope.d.ts +4 -0
  25. package/lib/types/client/index.d.ts +10 -0
  26. package/lib/types/client/locales.d.ts +45 -0
  27. package/lib/types/client/main-row-controller.d.ts +49 -0
  28. package/lib/types/client/nav-icon.d.ts +3 -0
  29. package/lib/types/client/picker/ComposerPicker.d.ts +47 -0
  30. package/lib/types/client/picker/PickerDirectory.d.ts +31 -0
  31. package/lib/types/client/picker/PickerSeatBoundary.d.ts +7 -0
  32. package/lib/types/client/picker/PlanReviewCard.d.ts +37 -0
  33. package/lib/types/client/picker/RetryBoundary.d.ts +20 -0
  34. package/lib/types/client/picker/external-catalog.d.ts +14 -0
  35. package/lib/types/client/picker/install.d.ts +23 -0
  36. package/lib/types/client/picker/locales.d.ts +104 -0
  37. package/lib/types/client/picker/main-default-restore.d.ts +56 -0
  38. package/lib/types/client/picker/popup-dismissal.d.ts +22 -0
  39. package/lib/types/client/picker/useComposerPickerSurface.d.ts +25 -0
  40. package/lib/types/client/plugin-rpc.d.ts +10 -0
  41. package/lib/types/client/provider-directory.d.ts +23 -0
  42. package/lib/types/client/remote-settings-scope.d.ts +45 -0
  43. package/lib/types/client/runtime-lock.d.ts +68 -0
  44. package/lib/types/client/search-capabilities.d.ts +21 -0
  45. package/lib/types/client-contract.d.ts +51 -0
  46. package/lib/types/compatibility.d.ts +38 -0
  47. package/lib/types/deepseek-search-adapter.d.ts +14 -0
  48. package/lib/types/host-runtime.d.ts +78 -0
  49. package/lib/types/host-settings.d.ts +49 -0
  50. package/lib/types/image-tool.d.ts +17 -0
  51. package/lib/types/index.d.ts +7 -0
  52. package/lib/types/main-settings.d.ts +9 -0
  53. package/lib/types/picker/family.d.ts +108 -0
  54. package/lib/types/picker/pending-dismissal.d.ts +34 -0
  55. package/lib/types/picker/plan-review.d.ts +57 -0
  56. package/lib/types/picker/public.d.ts +5 -0
  57. package/lib/types/picker/selection-feedback.d.ts +2 -0
  58. package/lib/types/runtime-capabilities.d.ts +50 -0
  59. package/lib/types/search-provider.d.ts +23 -0
  60. package/lib/types/subagent-route-policy.d.ts +26 -0
  61. package/lib/types/subagent-runtime.d.ts +51 -0
  62. package/lib/types/switch-compact.d.ts +72 -0
  63. package/package.json +343 -6
@@ -0,0 +1,37 @@
1
+ import type { PendingQuestion } from '@deepseek-ai/dsh-client-ui-user-questions/client';
2
+ import type { ConfigFormSnapshot } from '@deepseek-ai/dsh-client-ui-settings/client';
3
+ import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
4
+ import type { MainSettingsView } from '../../client-contract.ts';
5
+ import { type PickerDirectoryFace } from './PickerDirectory.ts';
6
+ import type { PickerInteractionOperations } from './popup-dismissal.ts';
7
+ import { type RuntimeProviderLock } from '../runtime-lock.ts';
8
+ export interface PlanReviewFace extends PickerDirectoryFace {
9
+ available: boolean;
10
+ resolveInteractionOperations?: () => PickerInteractionOperations | undefined;
11
+ /** Resolve a provider key to its ProviderDirectory role for runtime icons. */
12
+ roleOf?: (providerKey: string) => string | undefined;
13
+ /** Shared native-binding lock state for the seat session. */
14
+ providerLockStore: {
15
+ subscribe: (listener: () => void) => () => void;
16
+ getSnapshot: () => {
17
+ provider: RuntimeProviderLock;
18
+ failed: boolean;
19
+ };
20
+ };
21
+ /** Re-read the native binding now (mount, turn transitions, pre-selection). */
22
+ refreshProviderLock: () => void;
23
+ subscribeMainDefaults: (listener: () => void) => () => void;
24
+ getMainDefaultsSnapshot: () => ConfigFormSnapshot<MainSettingsView>;
25
+ /** Live catalog-group-id → card-key map from ProviderDirectory. */
26
+ catalogRoutes?: () => Readonly<Record<string, string>>;
27
+ }
28
+ export type PlanReviewCardProps = PropsRuntime<'conversation.composer'> & PropsLocale<'composer-picker'> & InjectFace<PlanReviewFace> & {
29
+ matched: PendingQuestion;
30
+ };
31
+ /** Match the model seat's admission guard before letting either picker offer a change. */
32
+ export declare function mainDefaultsUnavailableReason(snapshot: ConfigFormSnapshot<MainSettingsView>, t: PlanReviewCardProps['t']): string | undefined;
33
+ /** Inline failed lock-read status; history and log reading stay unaffected. */
34
+ export declare function ProviderLockHint(props: {
35
+ t: PlanReviewCardProps['t'];
36
+ }): import("react").JSX.Element;
37
+ export declare function PlanReviewCard(props: PlanReviewCardProps): import("react").JSX.Element;
@@ -0,0 +1,20 @@
1
+ import { Component, type ErrorInfo, type ReactNode } from 'react';
2
+ interface RetryBoundaryProps {
3
+ children: ReactNode;
4
+ logLabel: string;
5
+ renderFallback: (message: string, retry: () => void) => ReactNode;
6
+ }
7
+ /** Own retryable React error-boundary state while callers own presentation and copy. */
8
+ export declare class RetryBoundary extends Component<RetryBoundaryProps, {
9
+ message: string | null;
10
+ }> {
11
+ state: {
12
+ message: null;
13
+ };
14
+ static getDerivedStateFromError(error: unknown): {
15
+ message: string;
16
+ };
17
+ componentDidCatch(error: unknown, info: ErrorInfo): void;
18
+ render(): ReactNode;
19
+ }
20
+ export {};
@@ -0,0 +1,14 @@
1
+ import type { ModelSelection } from '@deepseek-ai/dsh-api-session-controller/types';
2
+ import type { PickerDirectorySnapshot, PickerDirectoryStore } from './PickerDirectory.ts';
3
+ type CatalogGroup = PickerDirectorySnapshot['groups'][number];
4
+ /** Overlay groups from External Agent catalog onto the LLM session.models snapshot. */
5
+ export declare function mergePickerGroups(base: readonly CatalogGroup[], extra: readonly CatalogGroup[]): CatalogGroup[];
6
+ export declare function overlayPickerSnapshot(base: PickerDirectorySnapshot, extra: readonly CatalogGroup[], current: ModelSelection | null): PickerDirectorySnapshot;
7
+ /** Subscribe-able overlay that hides unready External Agent groups by simply omitting them. */
8
+ export declare function createExternalCatalogStore(base: PickerDirectoryStore): {
9
+ store: PickerDirectoryStore;
10
+ setExtra(groups: readonly CatalogGroup[]): void;
11
+ setCurrent(current: ModelSelection | null): void;
12
+ extraIds(): ReadonlySet<string>;
13
+ };
14
+ export {};
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Composer model seat + Plan Review execution picker.
3
+ */
4
+ import type { Context as ClientContext } from '@deepseek-ai/cordis';
5
+ import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
6
+ import type { ProviderOrderSettings } from 'dsh-llm-providers-ui/order';
7
+ import { type PickerKey } from './locales.ts';
8
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
9
+ interface LocaleNamespaceMap {
10
+ 'composer-picker': PickerKey;
11
+ }
12
+ }
13
+ /**
14
+ * Bind the optional Providers entry order as a React external store.
15
+ * @returns A subscribable order snapshot with an invalidation hook for directory changes.
16
+ */
17
+ export declare function providerOrderStore(form: ConfigForm<ProviderOrderSettings>): {
18
+ subscribe: (listener: () => void) => () => void;
19
+ getSnapshot: () => readonly string[];
20
+ invalidate: () => void;
21
+ };
22
+ /** Register composer model picker and Plan Review execution picker. */
23
+ export declare function installComposerPicker(ctx: ClientContext): void;
@@ -0,0 +1,104 @@
1
+ /** `composer-picker` namespace dictionaries. */
2
+ export declare const zh: {
3
+ 'trigger.fallback': string;
4
+ 'trigger.selectAria': string;
5
+ 'trigger.aria': string;
6
+ 'trigger.ariaEffort': string;
7
+ 'menu.aria': string;
8
+ 'menu.back': string;
9
+ 'menu.closeSearch': string;
10
+ 'menu.model': string;
11
+ 'menu.effort': string;
12
+ 'menu.context': string;
13
+ 'menu.fast': string;
14
+ 'menu.thinking': string;
15
+ 'menu.search': string;
16
+ 'context.standard': string;
17
+ 'fast.on': string;
18
+ 'fast.off': string;
19
+ 'thinking.on': string;
20
+ 'thinking.off': string;
21
+ 'effort.providerDefault': string;
22
+ 'status.loading': string;
23
+ 'settings.remoteUnavailable': string;
24
+ 'settings.loading': string;
25
+ 'settings.unavailable': string;
26
+ 'error.action': string;
27
+ 'error.picker': string;
28
+ 'action.reload': string;
29
+ retry: string;
30
+ 'warning.groupLoad': string;
31
+ 'empty.models': string;
32
+ 'empty.efforts': string;
33
+ 'lock.readFailed': string;
34
+ 'choice.unavailable': string;
35
+ 'plan.kicker': string;
36
+ 'plan.header': string;
37
+ 'plan.execution': string;
38
+ 'plan.approve': string;
39
+ 'plan.keep': string;
40
+ 'plan.discuss': string;
41
+ 'plan.modelFailed': string;
42
+ 'plan.pickerCrash': string;
43
+ 'plan.feedback': string;
44
+ 'plan.feedbackPlaceholder': string;
45
+ 'plan.notRestored': string;
46
+ 'plan.dismissUnsupported': string;
47
+ 'plan.responseRejected': string;
48
+ 'plan.cancelRejected': string;
49
+ 'markdown.copy': string;
50
+ 'markdown.copied': string;
51
+ 'markdown.footnotes': string;
52
+ };
53
+ export type PickerKey = keyof typeof zh;
54
+ export declare const en: {
55
+ 'trigger.fallback': string;
56
+ 'trigger.selectAria': string;
57
+ 'trigger.aria': string;
58
+ 'trigger.ariaEffort': string;
59
+ 'menu.aria': string;
60
+ 'menu.back': string;
61
+ 'menu.closeSearch': string;
62
+ 'menu.model': string;
63
+ 'menu.effort': string;
64
+ 'menu.context': string;
65
+ 'menu.fast': string;
66
+ 'menu.thinking': string;
67
+ 'menu.search': string;
68
+ 'context.standard': string;
69
+ 'fast.on': string;
70
+ 'fast.off': string;
71
+ 'thinking.on': string;
72
+ 'thinking.off': string;
73
+ 'effort.providerDefault': string;
74
+ 'status.loading': string;
75
+ 'settings.remoteUnavailable': string;
76
+ 'settings.loading': string;
77
+ 'settings.unavailable': string;
78
+ 'error.action': string;
79
+ 'error.picker': string;
80
+ 'action.reload': string;
81
+ retry: string;
82
+ 'warning.groupLoad': string;
83
+ 'empty.models': string;
84
+ 'empty.efforts': string;
85
+ 'lock.readFailed': string;
86
+ 'choice.unavailable': string;
87
+ 'plan.kicker': string;
88
+ 'plan.header': string;
89
+ 'plan.execution': string;
90
+ 'plan.approve': string;
91
+ 'plan.keep': string;
92
+ 'plan.discuss': string;
93
+ 'plan.modelFailed': string;
94
+ 'plan.pickerCrash': string;
95
+ 'plan.feedback': string;
96
+ 'plan.feedbackPlaceholder': string;
97
+ 'plan.notRestored': string;
98
+ 'plan.dismissUnsupported': string;
99
+ 'plan.responseRejected': string;
100
+ 'plan.cancelRejected': string;
101
+ 'markdown.copy': string;
102
+ 'markdown.copied': string;
103
+ 'markdown.footnotes': string;
104
+ };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Compensate the Host's own deployment-default write after a Session-only
3
+ * model switch.
4
+ *
5
+ * `session.selectModel` installs the Session selection and, on Hosts that
6
+ * persist a selection as the deployment default, also writes that default.
7
+ * The plugin switches a Session only, so the captured Main default is restored
8
+ * afterwards.
9
+ *
10
+ * That Host write is fenced by a settings revision, and its timing is not part
11
+ * of the released contract: the 0.1.x generation awaited the default write
12
+ * before its RPC resolved, while the 0.2.x generation fires it without
13
+ * awaiting. A fixed `captured + 1` fence therefore either refuses the restore
14
+ * or silently leaves the default switched. Poll-by-revision is used instead:
15
+ * wait for the observed revision to leave the captured one, then fence on the
16
+ * revision actually observed.
17
+ */
18
+ import type { SettingsPathOpView } from '@deepseek-ai/dsh-api-remotes/client';
19
+ import type { ConfigFormSnapshot } from '@deepseek-ai/dsh-client-ui-settings/client';
20
+ import type { MainSettingsView } from '../../client-contract.ts';
21
+ /** The Main Settings namespace surface this module needs. */
22
+ export interface MainDefaultForm {
23
+ getSnapshot(): ConfigFormSnapshot<MainSettingsView>;
24
+ subscribe(listener: () => void): () => void;
25
+ mutate(ops: readonly SettingsPathOpView[], expectedRevision?: number): Promise<boolean>;
26
+ }
27
+ /** Outcome of one compensation attempt. */
28
+ export interface MainDefaultRestore {
29
+ /** The captured Main default is the effective one again (or no write occurred). */
30
+ restored: boolean;
31
+ /** A newer revision won instead; the concurrent edit is left untouched. */
32
+ conflict: boolean;
33
+ }
34
+ export interface MainDefaultRestoreOptions {
35
+ /** Timeout primitive; tests inject a deterministic one. */
36
+ wait?: (ms: number) => Promise<void>;
37
+ /** How long to await the unawaited Host default write. */
38
+ timeoutMs?: number;
39
+ /** Total fences, including one retry after a concurrent revision change. */
40
+ attempts?: number;
41
+ }
42
+ /** Field writes restoring one complete Main selection; the Host replaces the section. */
43
+ export declare function mainDefaultOps(selection: MainSettingsView): SettingsPathOpView[];
44
+ /**
45
+ * Restore the Main default captured before a Session-only model switch.
46
+ *
47
+ * Never throws and never blocks a Plan answer: a failed compensation is
48
+ * reported, not enforced, because the Session selection the human asked for is
49
+ * already installed by the time this runs.
50
+ *
51
+ * @param form - the official Main Settings namespace form.
52
+ * @param before - the snapshot captured before the switch.
53
+ * @param options - wait/timeout/attempt overrides for tests.
54
+ * @returns Whether the captured default stands again, and whether a newer edit won.
55
+ */
56
+ export declare function restoreMainDefault(form: MainDefaultForm, before: ConfigFormSnapshot<MainSettingsView>, options?: MainDefaultRestoreOptions): Promise<MainDefaultRestore>;
@@ -0,0 +1,22 @@
1
+ /** Dismissal contract shared by the portaled picker and mobile Back routing. */
2
+ export interface PickerInteractionSource {
3
+ kind: 'touch' | 'keyboard' | 'platform' | 'spatial' | 'programmatic';
4
+ detail?: string;
5
+ }
6
+ export interface PickerInteractionOperations {
7
+ registerSurface(surface: {
8
+ readonly id: string;
9
+ readonly kind: 'popup';
10
+ dismiss(source: PickerInteractionSource): void;
11
+ }): () => void;
12
+ }
13
+ export interface PickerDismissalOptions {
14
+ documentTarget: Document;
15
+ surfaceId: string;
16
+ interaction?: PickerInteractionOperations;
17
+ trigger: () => HTMLElement | null;
18
+ popup: () => HTMLElement | null;
19
+ dismiss: () => void;
20
+ }
21
+ /** Install capture-phase outside-pointer dismissal and optional mobile Back registration. */
22
+ export declare function installPickerDismissal({ documentTarget, surfaceId, interaction, trigger, popup, dismiss, }: PickerDismissalOptions): () => void;
@@ -0,0 +1,25 @@
1
+ import { type CSSProperties, type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent, type RefObject } from 'react';
2
+ import { type PickerInteractionOperations } from './popup-dismissal.ts';
3
+ export interface ComposerPickerSurfaceOptions {
4
+ locked: boolean;
5
+ embedded: boolean;
6
+ tone?: 'capsule';
7
+ pane: string;
8
+ reload: () => void;
9
+ onOpen: () => void;
10
+ onClose: () => void;
11
+ resolveInteractionOperations?: () => PickerInteractionOperations | undefined;
12
+ }
13
+ export interface ComposerPickerSurface {
14
+ id: string;
15
+ open: boolean;
16
+ menuStyle: CSSProperties;
17
+ triggerRef: RefObject<HTMLButtonElement>;
18
+ menuRef: RefObject<HTMLDivElement>;
19
+ show: () => void;
20
+ close: (restoreFocus?: boolean) => void;
21
+ onTriggerPointerDown: (event: ReactPointerEvent<HTMLButtonElement>) => void;
22
+ onTriggerClick: (event?: ReactMouseEvent<HTMLButtonElement>) => void;
23
+ }
24
+ /** Own popup activation, positioning, and dismissal behind one internal seam. */
25
+ export declare function useComposerPickerSurface(options: ComposerPickerSurfaceOptions): ComposerPickerSurface;
@@ -0,0 +1,10 @@
1
+ export interface PluginRpcClient {
2
+ call(channel: string, endpoint: string, payload: unknown, signal?: AbortSignal): Promise<{
3
+ ok: boolean;
4
+ value?: unknown;
5
+ }>;
6
+ }
7
+ export declare function callPluginRpc(rpc: PluginRpcClient, method: string, endpoint: string, payload: unknown, signal?: AbortSignal): Promise<{
8
+ ok: boolean;
9
+ value?: unknown;
10
+ }>;
@@ -0,0 +1,23 @@
1
+ /** Optional ProviderDirectory operations consumed by Model Switch. */
2
+ import type { NativeBindingSource } from './runtime-lock.ts';
3
+ /** Provider-directory operations that may be supplied by the Providers UI plugin. */
4
+ export interface ProviderDirectoryFace {
5
+ subscribe(listener: () => void): () => void;
6
+ roleOf?(key: string): string | undefined;
7
+ catalogRoutes?(): Readonly<Record<string, string>>;
8
+ nativeBindings?(): readonly NativeBindingSource[];
9
+ }
10
+ /** Read the optional catalog route map without assuming a particular Providers UI release. */
11
+ export declare function readCatalogRoutes(directory: ProviderDirectoryFace | undefined): Readonly<Record<string, string>>;
12
+ /** Read the optional native-binding declarations without assuming a particular Providers UI release. */
13
+ export declare function readNativeBindings(directory: ProviderDirectoryFace | undefined): readonly NativeBindingSource[];
14
+ /**
15
+ * Rank picker/catalog groups from live ProviderDirectory routes.
16
+ * Published 0.2.8 `sortCatalogGroups` only understands hardcoded LLM routes and
17
+ * ignores a third argument, so Agent catalog ids would keep source order.
18
+ * When the Owner publishes `catalogRoutes`, this ranks those keys locally.
19
+ * With no live map, fall back to the compile-pinned 0.2.8 LLM-only sort.
20
+ */
21
+ export declare function sortCatalogGroupsWithRoutes<T extends {
22
+ id: string;
23
+ }>(groups: readonly T[], saved?: readonly string[], catalogKeys?: Readonly<Record<string, string>>): T[];
@@ -0,0 +1,45 @@
1
+ import type { SettingsPathOpView } from '@deepseek-ai/dsh-api-remotes/client';
2
+ import type { SettingsScope, SettingsScopeSnapshot } from '@deepseek-ai/dsh-client-ui-settings/client';
3
+ interface RemoteError {
4
+ code: string;
5
+ message: string;
6
+ }
7
+ interface RemoteResult<T> {
8
+ ok: boolean;
9
+ value?: T;
10
+ error?: RemoteError;
11
+ }
12
+ interface NamespaceView {
13
+ ns: string;
14
+ value: unknown;
15
+ base?: unknown;
16
+ user?: unknown;
17
+ revision: number;
18
+ }
19
+ interface DescribeValue {
20
+ writable: boolean;
21
+ namespaces: readonly NamespaceView[];
22
+ }
23
+ export interface RemoteSettingsFace {
24
+ describe(): Promise<RemoteResult<DescribeValue>>;
25
+ mutate(ns: string, ops: readonly unknown[], expectedRevision: number | undefined): Promise<RemoteResult<NamespaceView>>;
26
+ }
27
+ /** Host-backed settings scope used by this plugin on authenticated remote browsers. */
28
+ export declare class RemoteSettingsScope<T> implements SettingsScope<T> {
29
+ private readonly api;
30
+ private readonly namespace;
31
+ private readonly decode;
32
+ private snapshot;
33
+ private readonly listeners;
34
+ constructor(api: RemoteSettingsFace, namespace: string, decode: (value: unknown) => T | undefined);
35
+ getSnapshot: () => SettingsScopeSnapshot<T>;
36
+ subscribe: (listener: () => void) => (() => void);
37
+ reload(): Promise<void>;
38
+ set(field: string, value: unknown): Promise<void>;
39
+ unset(field: string): Promise<void>;
40
+ mutate(ops: readonly SettingsPathOpView[], expectedRevision?: number): Promise<void>;
41
+ private write;
42
+ private accept;
43
+ private publish;
44
+ }
45
+ export {};
@@ -0,0 +1,68 @@
1
+ import { type PluginRpcClient } from './plugin-rpc.ts';
2
+ /** Session execution-runtime lock from native binding and request activity. */
3
+ /** Native binding query declared by an installed Agent provider. */
4
+ export interface NativeBindingSource {
5
+ readonly provider: string;
6
+ readonly channel: string;
7
+ readonly endpoint: string;
8
+ }
9
+ export type RuntimeProviderLock = string | null;
10
+ /** A known binding remains authoritative even if another query fails. */
11
+ export interface ProviderLockState {
12
+ readonly provider: RuntimeProviderLock;
13
+ readonly failed: boolean;
14
+ }
15
+ /** Decode a wire reply only for the provider that owns the query. */
16
+ export declare function decodeBindingProvider(value: unknown, provider: string): RuntimeProviderLock | undefined;
17
+ /** Query installed declarations; one failed query must never become a successful unbound read. */
18
+ export declare function fetchSessionBinding(rpc: PluginRpcClient | undefined, sessionId: string, sources: readonly NativeBindingSource[]): Promise<ProviderLockState>;
19
+ /** Whether one provider remains selectable under a known lock read. */
20
+ export declare function providerSelectable(lock: RuntimeProviderLock, provider: string): boolean;
21
+ /** Extra facts for Agent-role selection on blank vs existing DSH sessions. */
22
+ export interface ProviderAllowContext {
23
+ /** Empty-log bit from the conversation snapshot; omit when unknown. */
24
+ blank?: boolean;
25
+ /** A prompt is being submitted, awaits its first turn, or is running. */
26
+ active?: boolean;
27
+ /** Whether the candidate provider is an Agent-role External Agent. */
28
+ agent?: boolean;
29
+ /** Whether the current selection is an Agent provider, from its declaration. */
30
+ currentAgent?: boolean;
31
+ }
32
+ /**
33
+ * Existing DSH history cannot convert to an External Agent. Blank sessions
34
+ * and already-bound native sessions keep their current Agent choice.
35
+ */
36
+ export declare function agentProviderLocked(blank: boolean, bound: RuntimeProviderLock, active?: boolean): boolean;
37
+ /** Picker allow-check over the already-effective lock and the agentLocked bit. */
38
+ export declare function runtimeChoiceAllowed(lock: RuntimeProviderLock, agentLocked: boolean, provider: string, currentProvider: string | undefined, agent: boolean): boolean;
39
+ /**
40
+ * Whether one provider remains selectable under a lock read that may have failed.
41
+ * Failed reads keep only the known binding or current selection selectable;
42
+ * active Agent selections reserve their runtime before the first binding arrives.
43
+ * Unbound sessions with DSH history cannot select a new Agent provider.
44
+ * Log reading is never gated by this.
45
+ * @param state - Latest lock read for the session.
46
+ * @param provider - Candidate provider for the pending selection.
47
+ * @param currentProvider - Session current provider, if any.
48
+ * @param context - Session history, request activity, and candidate Agent role.
49
+ */
50
+ export declare function isProviderAllowed(state: ProviderLockState, provider: string, currentProvider: string | undefined, context?: ProviderAllowContext): boolean;
51
+ /**
52
+ * Effective single-provider lock for pickers that only understand
53
+ * RuntimeProviderLock. Errors fail closed exactly as isProviderAllowed does.
54
+ */
55
+ export declare function effectiveProviderLock(state: ProviderLockState, currentProvider: string | undefined, active?: boolean, currentAgent?: boolean): RuntimeProviderLock;
56
+ /** Minimal shared lock state: one snapshot per session, refreshed on demand. */
57
+ export interface ProviderLockStore {
58
+ subscribe: (listener: () => void) => () => void;
59
+ getSnapshot: () => ProviderLockState;
60
+ /** Re-read; concurrent reads resolve in call order, stale ones are dropped. */
61
+ refresh: () => Promise<ProviderLockState>;
62
+ }
63
+ /**
64
+ * Create one session lock store over an injecting query.
65
+ * The query sees the previous snapshot for sticky failure mapping and never
66
+ * throws (failures resolve to failed reads); refresh never rejects.
67
+ */
68
+ export declare function createProviderLockStore(query: (previous: ProviderLockState) => Promise<ProviderLockState>): ProviderLockStore;
@@ -0,0 +1,21 @@
1
+ /** Host-owned search capability metadata, decoded at the Connection trust seam. */
2
+ import type { ModelProviderGroup } from '@deepseek-ai/dsh-api-session-controller/types';
3
+ import type { SearchProviderMetadata } from '../adapter-registry.js';
4
+ import type { CapabilitiesSnapshot } from '../capabilities-rpc.js';
5
+ import type { RuntimeCapabilities } from '../runtime-capabilities.js';
6
+ export type { CapabilitiesSnapshot };
7
+ /**
8
+ * Strictly decode the Host search catalog into fresh plain metadata. Any malformed
9
+ * entry fails the whole catalog (undefined): when the Host claims search is
10
+ * available, invalid config must surface as an error, never hide as dropped rows.
11
+ * Extra fields (functions, credentials) never survive: only id/name copy over.
12
+ */
13
+ export declare function decodeSearchCatalog(value: unknown): SearchProviderMetadata[] | undefined;
14
+ /**
15
+ * Strictly decode one capabilities long-poll value; undefined when untrusted.
16
+ * Only the decoded search block is taken from the network and overlaid onto the
17
+ * frozen local defaults: no arbitrary Host fields pass through.
18
+ */
19
+ export declare function decodeCapabilitiesSnapshot(value: unknown): CapabilitiesSnapshot | undefined;
20
+ /** Project already-validated Host search metadata onto group shape (no re-decode). */
21
+ export declare function searchGroupsFromCapabilities(capabilities: RuntimeCapabilities | undefined): ModelProviderGroup[];
@@ -0,0 +1,51 @@
1
+ export declare const MODEL_SWITCH_CONFIG_ID = "model-switch";
2
+ export declare const MAIN_DEFAULT_CONFIG_ID = "agent-default-model";
3
+ export declare const PROVIDERS_CONFIG_ID = "llm-providers-ui";
4
+ export interface MainSettingsView {
5
+ provider: string;
6
+ model: string;
7
+ reasoningEffort?: string;
8
+ }
9
+ export declare class MainSettingsConflictError extends Error {
10
+ readonly name = "MainSettingsConflictError";
11
+ }
12
+ export interface ModelSwitchSettingsView {
13
+ subagentMode: 'follow-main' | 'fixed';
14
+ subagentProvider?: string;
15
+ subagentModel?: string;
16
+ subagentReasoningEffort?: string;
17
+ searchProvider?: string;
18
+ searchModel?: string;
19
+ imageProvider?: string;
20
+ imageModel?: string;
21
+ compactOnSwitch?: boolean;
22
+ }
23
+ export interface SubagentSettingsView {
24
+ mode: 'follow-main' | 'fixed';
25
+ provider?: string;
26
+ model?: string;
27
+ reasoningEffort?: string;
28
+ }
29
+ export interface CapabilityRouteView {
30
+ provider?: string;
31
+ model?: string;
32
+ }
33
+ export declare const SUBAGENT_SETTINGS_FIELDS: Readonly<{
34
+ readonly mode: "subagentMode";
35
+ readonly provider: "subagentProvider";
36
+ readonly model: "subagentModel";
37
+ readonly reasoningEffort: "subagentReasoningEffort";
38
+ }>;
39
+ /** Instant Subagent header toggle. Off keeps the stored `follow-main` unset token. */
40
+ export declare function subagentModeForEnabled(enabled: boolean): SubagentSettingsView['mode'];
41
+ export declare const SEARCH_SETTINGS_FIELDS: Readonly<{
42
+ readonly provider: "searchProvider";
43
+ readonly model: "searchModel";
44
+ }>;
45
+ export declare const IMAGE_SETTINGS_FIELDS: Readonly<{
46
+ readonly provider: "imageProvider";
47
+ readonly model: "imageModel";
48
+ }>;
49
+ export declare function deriveSubagentSettings(settings: ModelSwitchSettingsView): SubagentSettingsView;
50
+ export declare function deriveSearchSettings(settings: ModelSwitchSettingsView): CapabilityRouteView;
51
+ export declare function deriveImageSettings(settings: ModelSwitchSettingsView): CapabilityRouteView;
@@ -0,0 +1,38 @@
1
+ interface CompatibilityLogger {
2
+ warn(message: string): void;
3
+ }
4
+ export type DshRuntimeClassification = {
5
+ kind: 'verified';
6
+ } | {
7
+ kind: 'unverified';
8
+ } | {
9
+ kind: 'blocked';
10
+ reason: string;
11
+ };
12
+ /**
13
+ * Classify one runtime without treating the verified table as an allowlist.
14
+ * @param version - Resolved DSH runtime version.
15
+ * @param verified - Releases with direct compatibility evidence.
16
+ * @param blocklist - Versions excluded after reproduced failures.
17
+ * @returns The fail-open mount decision.
18
+ */
19
+ export declare function classifyDshRuntime(version: string, verified: ReadonlySet<string>, blocklist?: Readonly<Record<string, unknown>>): DshRuntimeClassification;
20
+ /**
21
+ * Apply the fail-open decision and emit at most one visible warning.
22
+ * @param logger - Host logger receiving compatibility warnings.
23
+ * @param pluginName - Plugin identifier used in diagnostics.
24
+ * @param version - Resolved DSH runtime version.
25
+ * @param verified - Releases with direct compatibility evidence.
26
+ * @param blocklist - Versions excluded after reproduced failures.
27
+ * @returns Whether the host mount should continue.
28
+ */
29
+ export declare function shouldMountDshRuntime(logger: CompatibilityLogger, pluginName: string, version: string, verified: ReadonlySet<string>, blocklist?: Readonly<Record<string, unknown>>): boolean;
30
+ /**
31
+ * Warn once for an unknown runtime while keeping the normal host mount path.
32
+ * @param logger - Host logger receiving compatibility warnings.
33
+ * @param pluginName - Plugin identifier used in diagnostics.
34
+ * @param candidates - DSH peer packages used to resolve the host version.
35
+ * @returns Whether the host mount should continue.
36
+ */
37
+ export declare function allowDshRuntime(logger: CompatibilityLogger, pluginName: string, candidates: readonly string[]): boolean;
38
+ export {};
@@ -0,0 +1,14 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { WebSearchRequest, WebSearchResult } from '@deepseek-ai/dsh-web';
3
+ import type { ModelSwitchSearchAdapter, SearchModel } from './adapter-registry.js';
4
+ export declare const DEEPSEEK_SEARCH_MODELS: readonly SearchModel[];
5
+ export declare class DeepSeekSearchAdapter implements ModelSwitchSearchAdapter {
6
+ private readonly ctx;
7
+ readonly provider = "deepseek-official";
8
+ readonly label = "DeepSeek";
9
+ readonly models: readonly SearchModel[];
10
+ constructor(ctx: Context);
11
+ supportsModel(model: string): boolean;
12
+ search(model: string, request: WebSearchRequest, signal?: AbortSignal): Promise<WebSearchResult>;
13
+ }
14
+ export declare function installDeepSeekSearchAdapter(ctx: Context): void;