dsh-model-switch 0.4.23 → 0.4.25
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/CHANGELOG.md +25 -0
- package/PRODUCT.md +6 -3
- package/README.md +33 -12
- package/README.zh.md +33 -12
- package/cordis.patch.yml +15 -1
- package/docs/screenshots/settings-subagent.png +0 -0
- package/lib/auto-review.js +58 -7
- package/lib/client.js +438 -377
- package/lib/compaction.js +147 -19
- package/lib/index.js +10 -363
- package/lib/provider-group-CwadImhH.js +88 -0
- package/lib/session-route-BCQFzqRP.js +172 -0
- package/lib/session-title.js +217 -0
- package/lib/subagent-runtime.js +20 -17
- package/lib/types/adapter-registry.d.ts +1 -1
- package/lib/types/auto-review.d.ts +39 -4
- package/lib/types/client/ModelSwitchSettings.d.ts +8 -4
- package/lib/types/client/derived-config-form.d.ts +14 -0
- package/lib/types/client/locales.d.ts +8 -6
- package/lib/types/client-contract.d.ts +8 -1
- package/lib/types/compaction.d.ts +69 -4
- package/lib/types/host-settings.d.ts +6 -2
- package/lib/types/provider-group.d.ts +13 -35
- package/lib/types/session-route.d.ts +111 -0
- package/lib/types/session-title.d.ts +104 -0
- package/lib/types/subagent-runtime.d.ts +14 -3
- package/package.json +24 -1
- package/lib/types/client/derived-settings-scope.d.ts +0 -4
- package/lib/types/client/picker/main-default-restore.d.ts +0 -56
- package/lib/types/client/picker/provider-group-watch.d.ts +0 -48
- package/lib/types/client/remote-settings-scope.d.ts +0 -45
- package/lib/types/switch-compact.d.ts +0 -72
|
@@ -4,12 +4,9 @@ export declare const zh: {
|
|
|
4
4
|
readonly subtitle: "为子代理与能力路由设置固定模型;当前会话模型用输入框旁的选择器直接切换。";
|
|
5
5
|
readonly conversationRoutes: "对话路由";
|
|
6
6
|
readonly capabilityRoutes: "能力路由";
|
|
7
|
-
readonly sendProtection: "发送保护";
|
|
8
7
|
readonly settingsSynced: "设置已同步";
|
|
9
8
|
readonly remoteSettingsUnavailable: "官方 DSH 在非本机浏览器禁用配置读取与写入;请在 Host 的 127.0.0.1 页面打开设置。";
|
|
10
9
|
readonly cancel: "取消";
|
|
11
|
-
readonly compactOnSwitch: "切换时压缩";
|
|
12
|
-
readonly compactOnSwitchHelp: "发送且换了模型时,先按目标窗口检查上下文;超了就用切换前的模型压一次历史,再继续请求。只选模型、不发送,不会压缩。官方自动压缩和 /compact 不受影响。压缩会产生额外模型用量,摘要可能省略细节。";
|
|
13
10
|
readonly provider: "提供商";
|
|
14
11
|
readonly model: "模型";
|
|
15
12
|
readonly providerDefault: "使用提供商默认值";
|
|
@@ -39,13 +36,18 @@ export declare const zh: {
|
|
|
39
36
|
readonly compactionFollow: "跟随会话模型";
|
|
40
37
|
readonly compactionHelp: "为官方压缩(/compact 与官方自动压缩)指定摘要模型;关闭(两个字段都留空)时使用官方默认,即当前会话的目标模型。";
|
|
41
38
|
readonly compactionRouteHelp: "摘要请求固定使用这里的 Provider/模型,与会话目标模型无关;保存后对下一次压缩立即生效。保存时若恰有压缩进行中,可能被打断并在下次重试。";
|
|
39
|
+
readonly sessionTitle: "会话命名模型";
|
|
40
|
+
readonly titleFollow: "跟随会话模型";
|
|
41
|
+
readonly titleHelp: "为官方自动命名(新会话首条消息后生成标题)指定模型;关闭(两个字段都留空)时命名跟随当前会话模型。";
|
|
42
|
+
readonly titleRouteHelp: "命名请求固定使用这里的 Provider/模型(不带推理强度);关闭时跟随会话模型。保存后立即对下一次命名生效,不会中断正在进行的命名,会话日志记录的也是实际使用的模型。";
|
|
42
43
|
readonly providerGroups: "供应商配置组";
|
|
43
44
|
readonly providerGroupsCard: "按供应商预设路由";
|
|
44
|
-
readonly providerGroupsSummary: "
|
|
45
|
-
readonly providerGroupsHelp: "
|
|
46
|
-
readonly providerGroupsBodyHelp: "
|
|
45
|
+
readonly providerGroupsSummary: "使用该供应商的会话自动采用对应组";
|
|
46
|
+
readonly providerGroupsHelp: "为压缩、自动审查、会话命名与子代理按供应商分别保存路由。";
|
|
47
|
+
readonly providerGroupsBodyHelp: "每个供应商保存一组压缩、自动审查、会话命名与子代理路由。会话自身的供应商与该组一致时,这四项能力按组实时解析,未固定项回落到上方卡片里的全局设置。切换某个会话的模型不会写入任何全局设置,因此不同供应商的多个会话并行时互不影响,会话中切换供应商后下一次调用也会立即跟随新供应商的配置组。";
|
|
47
48
|
readonly providerGroupPick: "选择供应商以编辑其配置组。";
|
|
48
49
|
readonly providerGroupDelete: "删除该组";
|
|
50
|
+
readonly saveTimeout: "保存超时,请重试";
|
|
49
51
|
readonly 'reason.central-subagent-routing': "Alpha.4 没有全局子代理启动路由接口。";
|
|
50
52
|
readonly 'reason.packaged-preset-roots': "Alpha.4 不支持插件提供额外 preset root。";
|
|
51
53
|
readonly 'reason.tool-owner-suppression': "Alpha.4 没有工具所有者或来源抑制接口。";
|
|
@@ -10,11 +10,12 @@ export interface ModelSwitchSettingsView {
|
|
|
10
10
|
searchModel?: string;
|
|
11
11
|
imageProvider?: string;
|
|
12
12
|
imageModel?: string;
|
|
13
|
-
compactOnSwitch?: boolean;
|
|
14
13
|
autoReviewProvider?: string;
|
|
15
14
|
autoReviewModel?: string;
|
|
16
15
|
compactProvider?: string;
|
|
17
16
|
compactModel?: string;
|
|
17
|
+
titleProvider?: string;
|
|
18
|
+
titleModel?: string;
|
|
18
19
|
/** Raw saved per-provider groups; edited whole through the provider-groups card. */
|
|
19
20
|
providerGroups?: ProviderGroupsStorage;
|
|
20
21
|
}
|
|
@@ -54,10 +55,16 @@ export declare const COMPACTION_SETTINGS_FIELDS: Readonly<{
|
|
|
54
55
|
readonly provider: "compactProvider";
|
|
55
56
|
readonly model: "compactModel";
|
|
56
57
|
}>;
|
|
58
|
+
/** Automatic session-title naming route; both unset means the official default (the session's own logged route). */
|
|
59
|
+
export declare const TITLE_SETTINGS_FIELDS: Readonly<{
|
|
60
|
+
readonly provider: "titleProvider";
|
|
61
|
+
readonly model: "titleModel";
|
|
62
|
+
}>;
|
|
57
63
|
export declare function deriveSubagentSettings(settings: ModelSwitchSettingsView): SubagentSettingsView;
|
|
58
64
|
export declare function deriveSearchSettings(settings: ModelSwitchSettingsView): CapabilityRouteView;
|
|
59
65
|
export declare function deriveImageSettings(settings: ModelSwitchSettingsView): CapabilityRouteView;
|
|
60
66
|
export declare function deriveAutoReviewSettings(settings: ModelSwitchSettingsView): CapabilityRouteView;
|
|
61
67
|
export declare function deriveCompactionSettings(settings: ModelSwitchSettingsView): CapabilityRouteView;
|
|
68
|
+
export declare function deriveTitleSettings(settings: ModelSwitchSettingsView): CapabilityRouteView;
|
|
62
69
|
/** Whether both fields of a model route form a complete pair; an incomplete pair stays off. */
|
|
63
70
|
export declare function routePairConfigured(route: CapabilityRouteView | undefined): boolean;
|
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
import type { Context } from '@deepseek-ai/cordis';
|
|
2
2
|
import type { Entry, Loader } from '@deepseek-ai/cordis-plugin-loader';
|
|
3
3
|
import type { BasicCompactionConfig } from '@deepseek-ai/dsh-compaction-basic';
|
|
4
|
+
import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
4
5
|
/** Cordis plugin identity; matches the cordis.patch.yml insert row id. */
|
|
5
6
|
export declare const name = "model-switch-compaction";
|
|
6
7
|
/** The loader is awaited at row activation; settings and the official row are still read structurally so stock compaction survives a disabled Model Switch row. */
|
|
7
8
|
export declare const inject: string[];
|
|
8
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Both identifiers trimmed and non-empty; undefined means "official default".
|
|
11
|
+
*
|
|
12
|
+
* Each summarization call derives its route per session (the session's provider
|
|
13
|
+
* group → the flat compaction card → the official row's `modelPolicies` /
|
|
14
|
+
* conversation-target fallback) and mutates `options.provider`/`options.model`
|
|
15
|
+
* in place before `ctx.llm.stream(options)` so the official `summarizeWithLlm`
|
|
16
|
+
* returns and durably records the exact route that wrote the summary in
|
|
17
|
+
* `compaction/summary`.
|
|
18
|
+
*/
|
|
9
19
|
export interface CompactionRoute {
|
|
10
20
|
readonly provider: string;
|
|
11
21
|
readonly model: string;
|
|
12
22
|
}
|
|
23
|
+
/** Public llm face the official compaction engine uses (`stream` + `resolveModelInfo`). */
|
|
24
|
+
export interface CompactionLlm {
|
|
25
|
+
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
26
|
+
}
|
|
13
27
|
/**
|
|
14
28
|
* Resolve the configurable summarization route from live settings.
|
|
15
29
|
* @param settings - Model Switch settings carrying the optional compaction pair.
|
|
@@ -39,6 +53,56 @@ export declare function officialCompactionEntry(loader: Loader | undefined): Ent
|
|
|
39
53
|
* @returns A plain object spread; missing or invalid configs become `{}`.
|
|
40
54
|
*/
|
|
41
55
|
export declare function forwardedConfig(entry: Entry): BasicCompactionConfig;
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the compaction summarization route for one session id.
|
|
58
|
+
*
|
|
59
|
+
* Looks up the session from `ctx.sessions` when available, derives its current
|
|
60
|
+
* provider (`pending` selection preferred over `lastUsed`, then logged routes),
|
|
61
|
+
* and resolves the provider group's compaction pair before falling back to the
|
|
62
|
+
* deployment-wide compaction pair.
|
|
63
|
+
* @param ctx - this row's context.
|
|
64
|
+
* @param sessionId - the session id carried on `GenerateOptions.sessionId`.
|
|
65
|
+
* @returns The derived compaction route, or undefined for the engine's own route.
|
|
66
|
+
*/
|
|
67
|
+
export declare function resolveSessionCompactionRoute(ctx: Context, sessionId: string | undefined): CompactionRoute | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* Wrap the real llm service so `purpose === 'compaction'` calls resolve their
|
|
70
|
+
* summarization route per session while keeping `compaction/summary` truthful.
|
|
71
|
+
*
|
|
72
|
+
* `summarizeWithLlm` builds a mutable `options` literal with `purpose: 'compaction'`
|
|
73
|
+
* and `sessionId: agent.session.id`, calls `ctx.llm.stream(options)`, and then
|
|
74
|
+
* returns `{ provider: options.provider, model: options.model }` to `commitCompactionBody`.
|
|
75
|
+
* Mutating `options.provider` and `options.model` in place before delegating to
|
|
76
|
+
* `target.stream(options)` ensures the durable `compaction/summary` event
|
|
77
|
+
* records the exact provider/model that wrote the summary. All other members
|
|
78
|
+
* (such as `resolveModelInfo`) pass through bound to `real`.
|
|
79
|
+
* @param real - the host's `ctx.llm`.
|
|
80
|
+
* @param resolve - per-session route resolver; undefined keeps `options` untouched.
|
|
81
|
+
* @param onError - optional error sink when the resolver throws.
|
|
82
|
+
* @returns A transparent Proxy over `real`.
|
|
83
|
+
*/
|
|
84
|
+
export declare function compactionLlm<T extends CompactionLlm>(real: T, resolve: (sessionId: string) => CompactionRoute | undefined, onError?: (cause: unknown) => void): T;
|
|
85
|
+
/**
|
|
86
|
+
* Build the child context the compaction engine mounts under.
|
|
87
|
+
*
|
|
88
|
+
* When `ctx.llm` is present, shadows it with {@link compactionLlm} on an
|
|
89
|
+
* extended context so the child engine fiber resolves the per-session wrapper
|
|
90
|
+
* while registering `ctx.compaction` on the shared context tree.
|
|
91
|
+
* @param ctx - this row's context.
|
|
92
|
+
* @returns A context shadowing `llm` when available, else `ctx`.
|
|
93
|
+
*/
|
|
94
|
+
export declare function compactionScope(ctx: Context): Context;
|
|
95
|
+
/**
|
|
96
|
+
* Whether the compaction capability is actually served from this context.
|
|
97
|
+
*
|
|
98
|
+
* Awaiting a plugin fiber settles even when the plugin's injections were never
|
|
99
|
+
* satisfied, so the mount is confirmed through the public service instead of by
|
|
100
|
+
* the absence of an error: an unactivated engine must never be recorded as the
|
|
101
|
+
* live engine, or the row would stop retrying while `/compact` had no service.
|
|
102
|
+
* @param ctx - this row's context.
|
|
103
|
+
* @returns true when `ctx.compaction` exposes the engine's public entry points.
|
|
104
|
+
*/
|
|
105
|
+
export declare function engineActive(ctx: Context): boolean;
|
|
42
106
|
/**
|
|
43
107
|
* Mount the Model Switch compaction engine for this profile.
|
|
44
108
|
*
|
|
@@ -46,9 +110,10 @@ export declare function forwardedConfig(entry: Entry): BasicCompactionConfig;
|
|
|
46
110
|
* with the same package under the configurable summarization route. Host
|
|
47
111
|
* settings and the loader are read structurally so a disabled `model-switch`
|
|
48
112
|
* row leaves official compaction untouched. Reconciles run on
|
|
49
|
-
* `model-switch/settings-update` (emitted by the host
|
|
50
|
-
* ordinary saves)
|
|
51
|
-
*
|
|
113
|
+
* `model-switch/settings-update` (emitted by the host at construction, on
|
|
114
|
+
* volatile commits, and on ordinary saves) and read the settled settings, so one
|
|
115
|
+
* save costs at most one remount; the listener is global so instances of this
|
|
116
|
+
* row inside agent-preset realms still hear the host row's emission. The initial
|
|
52
117
|
* reconcile is awaited so `/compact` resolves the service as soon as this row
|
|
53
118
|
* completes.
|
|
54
119
|
* @param ctx - this row's context.
|
|
@@ -12,7 +12,6 @@ export interface ModelSwitchSettings {
|
|
|
12
12
|
searchModel?: string;
|
|
13
13
|
imageProvider?: string;
|
|
14
14
|
imageModel?: string;
|
|
15
|
-
compactOnSwitch: boolean;
|
|
16
15
|
/** Reviewer provider for the Auto preset; both fields unset = follow the session model. */
|
|
17
16
|
autoReviewProvider?: string;
|
|
18
17
|
/** Reviewer model for the Auto preset; set together with `autoReviewProvider`. */
|
|
@@ -21,6 +20,10 @@ export interface ModelSwitchSettings {
|
|
|
21
20
|
compactProvider?: string;
|
|
22
21
|
/** Summarization model for `/compact` and automatic compaction; set together with `compactProvider`. */
|
|
23
22
|
compactModel?: string;
|
|
23
|
+
/** Naming provider for automatic session titles; both fields unset = official default (the session's own logged route). */
|
|
24
|
+
titleProvider?: string;
|
|
25
|
+
/** Naming model for automatic session titles; set together with `titleProvider`. */
|
|
26
|
+
titleModel?: string;
|
|
24
27
|
/** Per-provider compaction/subagent/review presets, keyed by provider id; absent when no group is saved. */
|
|
25
28
|
providerGroups?: ProviderGroups;
|
|
26
29
|
}
|
|
@@ -34,11 +37,12 @@ export interface Config {
|
|
|
34
37
|
searchModel: Volatile<string | undefined>;
|
|
35
38
|
imageProvider: Volatile<string | undefined>;
|
|
36
39
|
imageModel: Volatile<string | undefined>;
|
|
37
|
-
compactOnSwitch: Volatile<boolean>;
|
|
38
40
|
autoReviewProvider: Volatile<string | undefined>;
|
|
39
41
|
autoReviewModel: Volatile<string | undefined>;
|
|
40
42
|
compactProvider: Volatile<string | undefined>;
|
|
41
43
|
compactModel: Volatile<string | undefined>;
|
|
44
|
+
titleProvider: Volatile<string | undefined>;
|
|
45
|
+
titleModel: Volatile<string | undefined>;
|
|
42
46
|
/** Saved per-provider capability groups as one entry per provider; entry route fields are '' when the capability stays at its default. */
|
|
43
47
|
providerGroups: Volatile<readonly ProviderGroupEntry[]>;
|
|
44
48
|
}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Provider configuration groups: per-provider presets for the compaction,
|
|
3
|
-
*
|
|
3
|
+
* auto-review, session-naming, and subagent routes.
|
|
4
4
|
*
|
|
5
|
-
* A group
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* A group is a DERIVED policy, not a write: `src/session-route.ts` resolves the
|
|
6
|
+
* routes one session should use from its own provider plus this stored table, so
|
|
7
|
+
* switching a session's provider never rewrites deployment-wide settings and two
|
|
8
|
+
* sessions on different providers cannot overwrite each other. The field names
|
|
9
|
+
* below are the flat settings field names a group pins, which keeps the stored
|
|
10
|
+
* shape identical across releases.
|
|
11
|
+
*
|
|
12
|
+
* All functions here are pure and never throw on malformed storage.
|
|
9
13
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export type ProviderGroupRouteField = 'compactProvider' | 'compactModel' | 'autoReviewProvider' | 'autoReviewModel' | 'subagentProvider' | 'subagentModel' | 'subagentReasoningEffort';
|
|
14
|
+
/** Flat settings fields a provider group carries across all four per-session capabilities. */
|
|
15
|
+
export type ProviderGroupRouteField = 'compactProvider' | 'compactModel' | 'autoReviewProvider' | 'autoReviewModel' | 'subagentProvider' | 'subagentModel' | 'subagentReasoningEffort' | 'titleProvider' | 'titleModel';
|
|
13
16
|
/** Stored entry: the owning provider plus every route field; '' means the capability stays at its default. */
|
|
14
17
|
export interface ProviderGroupEntry {
|
|
15
18
|
provider: string;
|
|
@@ -20,6 +23,8 @@ export interface ProviderGroupEntry {
|
|
|
20
23
|
subagentProvider: string;
|
|
21
24
|
subagentModel: string;
|
|
22
25
|
subagentReasoningEffort: string;
|
|
26
|
+
titleProvider: string;
|
|
27
|
+
titleModel: string;
|
|
23
28
|
}
|
|
24
29
|
/** Normalized view of one provider's group; absent fields follow their default state. */
|
|
25
30
|
export type ProviderGroupConfig = Partial<Record<ProviderGroupRouteField, string>>;
|
|
@@ -40,22 +45,6 @@ export declare function normalizeProviderGroups(raw: unknown): ProviderGroups;
|
|
|
40
45
|
* which is what leaves the live routes untouched during a switch.
|
|
41
46
|
*/
|
|
42
47
|
export declare function groupOf(groups: ProviderGroups | undefined, provider: string): ProviderGroupConfig | undefined;
|
|
43
|
-
/** One application write; `value` undefined means "unset this field". */
|
|
44
|
-
export type ProviderGroupWrite = {
|
|
45
|
-
readonly field: 'subagentMode';
|
|
46
|
-
readonly mode: SubagentMode;
|
|
47
|
-
} | {
|
|
48
|
-
readonly field: ProviderGroupRouteField;
|
|
49
|
-
readonly value: string | undefined;
|
|
50
|
-
};
|
|
51
|
-
/**
|
|
52
|
-
* Ordered writes applying one group with full-replacement semantics: pinned
|
|
53
|
-
* capabilities land as pairs, everything else reverts to its default state
|
|
54
|
-
* (compaction/review follow the session model; Subagent follows official
|
|
55
|
-
* inherit). The mode write comes last so a fixed route never publishes with
|
|
56
|
-
* the previous provider's fields still half-written.
|
|
57
|
-
*/
|
|
58
|
-
export declare function providerGroupWrites(group: ProviderGroupConfig): ProviderGroupWrite[];
|
|
59
48
|
/** Storage entry for one provider's group; absent or blank route fields become ''. */
|
|
60
49
|
export declare function entryOf(provider: string, group: ProviderGroupConfig): ProviderGroupEntry;
|
|
61
50
|
/**
|
|
@@ -65,14 +54,3 @@ export declare function entryOf(provider: string, group: ProviderGroupConfig): P
|
|
|
65
54
|
* provider only normalizes.
|
|
66
55
|
*/
|
|
67
56
|
export declare function withProviderGroup(storage: unknown, provider: string, group: ProviderGroupConfig | undefined): ProviderGroupsStorage;
|
|
68
|
-
/** Minimal writable settings surface an application needs; satisfied by the owned ConfigForm. */
|
|
69
|
-
export interface ProviderGroupSink {
|
|
70
|
-
set(field: 'subagentMode' | ProviderGroupRouteField, value: string): Promise<unknown>;
|
|
71
|
-
unset(field: ProviderGroupRouteField): Promise<unknown>;
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Apply a saved group's routes to the live flat settings.
|
|
75
|
-
* @returns true when a group existed and every write settled; false when the
|
|
76
|
-
* provider has no group (nothing was written). A rejected write propagates.
|
|
77
|
-
*/
|
|
78
|
-
export declare function applyProviderGroupTo(sink: ProviderGroupSink, groups: ProviderGroups | undefined, provider: string): Promise<boolean>;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-session capability route derivation.
|
|
3
|
+
*
|
|
4
|
+
* Model Switch stores one deployment-wide route per capability (the flat
|
|
5
|
+
* settings fields) plus, per provider, a saved provider group. Nothing is
|
|
6
|
+
* written when a session changes provider: this module DERIVES the route a
|
|
7
|
+
* capability should use for one session from (a) the session's own provider and
|
|
8
|
+
* (b) the stored group table. Two sessions on different providers therefore
|
|
9
|
+
* resolve independently, and no session's actions rewrite another session's
|
|
10
|
+
* route.
|
|
11
|
+
*
|
|
12
|
+
* The session's provider is read from durable, official state in this order:
|
|
13
|
+
* the `modelSelection` session projection (`pending` before `lastUsed`, so a
|
|
14
|
+
* selection the user just made counts), then the latest logged request-context
|
|
15
|
+
* route, then the latest `request/header` route. Every read is optional: a
|
|
16
|
+
* session with no observable provider, an unregistered projection, or a
|
|
17
|
+
* profile without the projection package all resolve to the deployment default,
|
|
18
|
+
* which is exactly the pre-derivation behavior.
|
|
19
|
+
*
|
|
20
|
+
* All functions are pure except the readers, which never throw and never write.
|
|
21
|
+
*/
|
|
22
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
23
|
+
import type { Session } from '@deepseek-ai/dsh-session';
|
|
24
|
+
import { type ProviderGroups } from './provider-group.js';
|
|
25
|
+
/** Capabilities whose route can be derived per session. */
|
|
26
|
+
export type CapabilityId = 'compaction' | 'autoReview' | 'title' | 'subagent';
|
|
27
|
+
/** A complete pinned route; either half missing means "follow the default". */
|
|
28
|
+
export interface CapabilityRoute {
|
|
29
|
+
readonly provider: string;
|
|
30
|
+
readonly model: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Settings fields a route can be derived from. Structurally satisfied by
|
|
34
|
+
* {@link ModelSwitchSettings}; declared separately so the resolver stays pure
|
|
35
|
+
* and testable without a host runtime.
|
|
36
|
+
*/
|
|
37
|
+
export interface RouteBearingSettings {
|
|
38
|
+
readonly subagentMode?: string;
|
|
39
|
+
readonly subagentProvider?: string;
|
|
40
|
+
readonly subagentModel?: string;
|
|
41
|
+
readonly subagentReasoningEffort?: string;
|
|
42
|
+
readonly autoReviewProvider?: string;
|
|
43
|
+
readonly autoReviewModel?: string;
|
|
44
|
+
readonly compactProvider?: string;
|
|
45
|
+
readonly compactModel?: string;
|
|
46
|
+
readonly titleProvider?: string;
|
|
47
|
+
readonly titleModel?: string;
|
|
48
|
+
readonly providerGroups?: ProviderGroups;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The deployment-wide route for one capability: the flat settings pair.
|
|
52
|
+
* @param settings - live Model Switch settings; undefined means none are reachable.
|
|
53
|
+
* @param capability - capability whose flat pair is read.
|
|
54
|
+
* @returns The complete pair, or undefined when the capability stays at its official/derived default.
|
|
55
|
+
*/
|
|
56
|
+
export declare function deploymentRoute(settings: RouteBearingSettings | undefined, capability: CapabilityId): CapabilityRoute | undefined;
|
|
57
|
+
/** One capability's route plus the reasoning effort a subagent spawn also carries. */
|
|
58
|
+
export interface SubagentRoute extends CapabilityRoute {
|
|
59
|
+
readonly reasoningEffort?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The route a subagent spawn should use for one parent-session provider.
|
|
63
|
+
*
|
|
64
|
+
* A saved group for that provider pins the spawn; otherwise the flat Subagent
|
|
65
|
+
* card pins it, but only in `fixed` mode — `follow-main` IS the official inherit
|
|
66
|
+
* path, expressed by leaving the request untouched rather than by naming it.
|
|
67
|
+
* @param settings - live Model Switch settings.
|
|
68
|
+
* @param provider - the delegating session's provider, when one is observable.
|
|
69
|
+
* @returns The complete route, or undefined for official inherit.
|
|
70
|
+
*/
|
|
71
|
+
export declare function subagentRouteOf(settings: RouteBearingSettings | undefined, provider: string | undefined): SubagentRoute | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* One provider's saved route for one capability.
|
|
74
|
+
* @param settings - live Model Switch settings carrying the group table.
|
|
75
|
+
* @param provider - the session's provider; blank or unknown resolves to undefined.
|
|
76
|
+
* @param capability - capability whose group pin is read.
|
|
77
|
+
* @returns The complete group pair, or undefined when the group pins nothing here.
|
|
78
|
+
*/
|
|
79
|
+
export declare function groupRoute(settings: RouteBearingSettings | undefined, provider: string | undefined, capability: CapabilityId): CapabilityRoute | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* The route one capability should use for one session.
|
|
82
|
+
*
|
|
83
|
+
* A saved provider group for the session's provider wins; otherwise the flat
|
|
84
|
+
* deployment default applies. An unknown session provider skips every group, so
|
|
85
|
+
* a session Model Switch cannot attribute keeps the deployment default.
|
|
86
|
+
* @param settings - live Model Switch settings.
|
|
87
|
+
* @param provider - the session's provider, when one is observable.
|
|
88
|
+
* @param capability - capability being resolved.
|
|
89
|
+
* @returns The complete pair, or undefined for the capability's official default.
|
|
90
|
+
*/
|
|
91
|
+
export declare function resolveCapabilityRoute(settings: RouteBearingSettings | undefined, provider: string | undefined, capability: CapabilityId): CapabilityRoute | undefined;
|
|
92
|
+
/**
|
|
93
|
+
* The provider one session is using, from official durable state.
|
|
94
|
+
*
|
|
95
|
+
* Preferred source is the session's own model selection (it covers a switch the
|
|
96
|
+
* client has sent but no request has consumed yet); the logged request-context
|
|
97
|
+
* and request-header routes cover profiles without the projection package and
|
|
98
|
+
* replayed sessions that never emitted a selection.
|
|
99
|
+
* @param ctx - context used for the optional projection read.
|
|
100
|
+
* @param session - the session to resolve; undefined resolves to undefined.
|
|
101
|
+
* @returns The provider, or undefined when nothing durable names one.
|
|
102
|
+
*/
|
|
103
|
+
export declare function sessionProviderOf(ctx: Context, session: Session | undefined): string | undefined;
|
|
104
|
+
/**
|
|
105
|
+
* The subagent route one exact session's spawns should use.
|
|
106
|
+
* @param ctx - context used for the optional session reads.
|
|
107
|
+
* @param session - the delegating session; undefined keeps official inherit.
|
|
108
|
+
* @param settings - live Model Switch settings.
|
|
109
|
+
* @returns The complete route, or undefined for official inherit.
|
|
110
|
+
*/
|
|
111
|
+
export declare function sessionSubagentRouteOf(ctx: Context, session: Session | undefined, settings: RouteBearingSettings | undefined): SubagentRoute | undefined;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Switch-owned session-naming row.
|
|
3
|
+
*
|
|
4
|
+
* The official naming rows hardwire the naming route at registration time: the
|
|
5
|
+
* configured provider/model pair wins over the session's own logged route for
|
|
6
|
+
* every session in the profile. This row registers ONE provider of its own and
|
|
7
|
+
* resolves the route PER REQUEST instead, so a session whose provider has a
|
|
8
|
+
* saved group is named through that group's pair while every other session keeps
|
|
9
|
+
* following its own model.
|
|
10
|
+
*
|
|
11
|
+
* The call itself stays inside the official implementation: the exported
|
|
12
|
+
* `generateSessionTitleWithLlm` receives the session-resolved config and records
|
|
13
|
+
* exactly that route in `session/title-llm-request`, so the durable record and
|
|
14
|
+
* the actual request can never disagree. Nothing is remounted when settings
|
|
15
|
+
* change — the route is read through on every naming request — so a settings
|
|
16
|
+
* save can neither interrupt an in-flight naming request nor leave the profile
|
|
17
|
+
* without a registered provider.
|
|
18
|
+
*
|
|
19
|
+
* `cordis.patch.yml` disables the official row (the seam allows one registered
|
|
20
|
+
* provider per context), and this row mirrors the cadence the disabled row
|
|
21
|
+
* declared, so a profile that restated the row as all-prompts keeps its cadence.
|
|
22
|
+
*
|
|
23
|
+
* @module dsh-model-switch/session-title
|
|
24
|
+
*/
|
|
25
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
26
|
+
import type { Entry, Loader } from '@deepseek-ai/cordis-plugin-loader';
|
|
27
|
+
import type { SessionTitleAutomaticMode, SessionTitleProviderId, SessionTitleUserMessage } from '@deepseek-ai/dsh-session-title';
|
|
28
|
+
import type { ResolvedSessionTitleLlmConfig, SessionTitleLlmConfig } from '@deepseek-ai/dsh-session-title-llm';
|
|
29
|
+
import { type CapabilityRoute } from './session-route.js';
|
|
30
|
+
/** Cordis plugin identity; matches the cordis.patch.yml insert row id. */
|
|
31
|
+
export declare const name = "model-switch-session-title";
|
|
32
|
+
/** The loader names the disabled row, and the seam services the call needs are awaited before the first naming request. */
|
|
33
|
+
export declare const inject: string[];
|
|
34
|
+
/** One official cadence package; the row's own package selects which one this profile runs. */
|
|
35
|
+
export interface TitleCadence {
|
|
36
|
+
/** Official npm package that declares this cadence. */
|
|
37
|
+
readonly pkg: string;
|
|
38
|
+
/** Provider id recorded with every generated title; mirrors the official registration. */
|
|
39
|
+
readonly id: SessionTitleProviderId;
|
|
40
|
+
/** When the service starts automatic generation. */
|
|
41
|
+
readonly automatic: SessionTitleAutomaticMode;
|
|
42
|
+
/** Whether only the first human message is framed into the request. */
|
|
43
|
+
readonly firstPromptOnly: boolean;
|
|
44
|
+
}
|
|
45
|
+
/** Both official cadence packages, in the order a profile may declare them. */
|
|
46
|
+
export declare const TITLE_CADENCES: readonly TitleCadence[];
|
|
47
|
+
/**
|
|
48
|
+
* The cadence one official package name declares.
|
|
49
|
+
* @param pkg - the disabled row's package name.
|
|
50
|
+
* @returns The matching cadence, or undefined for a row this row cannot mirror.
|
|
51
|
+
*/
|
|
52
|
+
export declare function titleCadence(pkg: string | undefined): TitleCadence | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Overlay the session-resolved route over the row's own validated policy.
|
|
55
|
+
*
|
|
56
|
+
* A resolved route always names both halves. When nothing resolves, the row's
|
|
57
|
+
* own policy is returned unchanged: an explicit pair the profile configured on
|
|
58
|
+
* the official row stays the deployment default, and a row without one keeps
|
|
59
|
+
* following each session's logged route.
|
|
60
|
+
* @param config - policy validated from the disabled official row.
|
|
61
|
+
* @param route - session-resolved naming route, or undefined for the row's own policy.
|
|
62
|
+
* @returns A detached config for one naming request.
|
|
63
|
+
*/
|
|
64
|
+
export declare function composeTitleRoute(config: ResolvedSessionTitleLlmConfig, route: CapabilityRoute | undefined): ResolvedSessionTitleLlmConfig;
|
|
65
|
+
/**
|
|
66
|
+
* Frame the exact human messages one naming revision represents.
|
|
67
|
+
* @param messages - every eligible human message of the revision.
|
|
68
|
+
* @param firstPromptOnly - cadence flag: true mirrors the first-prompt provider.
|
|
69
|
+
* @returns The selected messages, in order.
|
|
70
|
+
* @throws when a first-prompt cadence receives an empty revision — the official provider's own contract.
|
|
71
|
+
*/
|
|
72
|
+
export declare function selectTitleMessages(messages: readonly SessionTitleUserMessage[], firstPromptOnly: boolean): readonly SessionTitleUserMessage[];
|
|
73
|
+
/**
|
|
74
|
+
* Locate the official session-title provider row in the loader tree.
|
|
75
|
+
* Disabled entries stay in the tree, so a name-matched disable still matches.
|
|
76
|
+
* @param loader - the host loader, when one is reachable.
|
|
77
|
+
* @returns The official entry, or undefined when this profile has no such row.
|
|
78
|
+
*/
|
|
79
|
+
export declare function officialTitleEntry(loader: Loader | undefined): Entry | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* Copy the official row's stored config, ignoring non-object payloads.
|
|
82
|
+
* @param entry - the official session-title provider entry.
|
|
83
|
+
* @returns A plain object spread; missing or invalid configs become `{}`. The
|
|
84
|
+
* result is untrusted: the profile may be hand-edited, so the official policy
|
|
85
|
+
* validator runs over it before any naming request is served.
|
|
86
|
+
*/
|
|
87
|
+
export declare function forwardedConfig(entry: Entry): Partial<SessionTitleLlmConfig>;
|
|
88
|
+
/**
|
|
89
|
+
* The official package name this row mounts (either cadence).
|
|
90
|
+
* @param entry - the official session-title provider entry.
|
|
91
|
+
* @returns The package name, or undefined when the row declares none.
|
|
92
|
+
*/
|
|
93
|
+
export declare function officialTitlePackage(entry: Entry): string | undefined;
|
|
94
|
+
/**
|
|
95
|
+
* Mount the Model Switch session-naming row for this profile.
|
|
96
|
+
*
|
|
97
|
+
* A profile with no official naming row, an unrecognized row package, a missing
|
|
98
|
+
* auxiliary-LLM package, or an invalid stored policy keeps the session-title
|
|
99
|
+
* service's deterministic fallback titles and reports the lost capability
|
|
100
|
+
* explicitly instead of guessing a route. The registration is an effect of this
|
|
101
|
+
* row's fiber, so unloading the profile unregisters the provider.
|
|
102
|
+
* @param ctx - this row's context.
|
|
103
|
+
*/
|
|
104
|
+
export declare function apply(ctx: Context): Promise<void>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Context } from '@deepseek-ai/cordis';
|
|
2
2
|
import OfficialSubagentRuntime, { type ContinuableStart, type ContinuableStartSpec, type SubagentRun, type SubagentStartRequest } from '@deepseek-ai/dsh-subagent';
|
|
3
|
-
import type
|
|
3
|
+
import { type SubagentRoute } from './session-route.js';
|
|
4
4
|
/** Raised only when an explicit startup check finds an unsupported public surface. */
|
|
5
5
|
export declare class StartupIncompatibilityError extends Error {
|
|
6
6
|
readonly name = "StartupIncompatibilityError";
|
|
@@ -21,8 +21,19 @@ export interface MountedStartup<T> {
|
|
|
21
21
|
readonly dispose: () => Promise<void>;
|
|
22
22
|
}
|
|
23
23
|
type RoutableSubagentRequest = Pick<SubagentStartRequest, 'parent' | 'agentOptions'>;
|
|
24
|
-
/**
|
|
25
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Inject one resolved route into a spawn that names none.
|
|
26
|
+
*
|
|
27
|
+
* A spawn that already names a provider, model, or effort keeps it: the caller's
|
|
28
|
+
* explicit selection outranks any profile policy. The route itself is derived
|
|
29
|
+
* from the DELEGATING session (its provider selects a saved group, else the flat
|
|
30
|
+
* Subagent card in `fixed` mode), so two sessions on different providers route
|
|
31
|
+
* their own spawns and neither can rewrite the other's policy.
|
|
32
|
+
* @param request - the caller's spawn request.
|
|
33
|
+
* @param route - the delegating session's resolved route, or undefined for official inherit.
|
|
34
|
+
* @returns The request to hand the official runtime.
|
|
35
|
+
*/
|
|
36
|
+
export declare function routeSubagentRequest<T extends RoutableSubagentRequest>(request: T, route: SubagentRoute | undefined): T;
|
|
26
37
|
/**
|
|
27
38
|
* Mount a candidate and use the fallback only for typed startup incompatibility.
|
|
28
39
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-model-switch",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.25",
|
|
4
4
|
"description": "Explicit model routing plus composer/Plan picker for DeepSeek Harness",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -46,6 +46,10 @@
|
|
|
46
46
|
"types": "./lib/types/compaction.d.ts",
|
|
47
47
|
"default": "./lib/compaction.js"
|
|
48
48
|
},
|
|
49
|
+
"./session-title": {
|
|
50
|
+
"types": "./lib/types/session-title.d.ts",
|
|
51
|
+
"default": "./lib/session-title.js"
|
|
52
|
+
},
|
|
49
53
|
"./subagent-runtime": {
|
|
50
54
|
"types": "./lib/types/subagent-runtime.d.ts",
|
|
51
55
|
"default": "./lib/subagent-runtime.js"
|
|
@@ -134,6 +138,10 @@
|
|
|
134
138
|
"@deepseek-ai/dsh-permission-presets": ">=0.1.7-alpha.2",
|
|
135
139
|
"@deepseek-ai/dsh-plan-mode": ">=0.1.7-alpha.2",
|
|
136
140
|
"@deepseek-ai/dsh-session": ">=0.1.7-alpha.2",
|
|
141
|
+
"@deepseek-ai/dsh-session-title": ">=0.1.7-alpha.2",
|
|
142
|
+
"@deepseek-ai/dsh-session-title-all-prompts-llm": ">=0.1.7-alpha.2",
|
|
143
|
+
"@deepseek-ai/dsh-session-title-first-prompt-llm": ">=0.1.7-alpha.2",
|
|
144
|
+
"@deepseek-ai/dsh-session-title-llm": ">=0.1.7-alpha.2",
|
|
137
145
|
"@deepseek-ai/dsh-settings": ">=0.1.7-alpha.2",
|
|
138
146
|
"@deepseek-ai/dsh-subagent": ">=0.1.7-alpha.2",
|
|
139
147
|
"@deepseek-ai/dsh-system-prompt": ">=0.1.7-alpha.2",
|
|
@@ -210,6 +218,9 @@
|
|
|
210
218
|
"@deepseek-ai/dsh-session-query": ">=0.1.7-alpha.2",
|
|
211
219
|
"@deepseek-ai/dsh-session-reference": ">=0.1.7-alpha.2",
|
|
212
220
|
"@deepseek-ai/dsh-session-title": ">=0.1.7-alpha.2",
|
|
221
|
+
"@deepseek-ai/dsh-session-title-all-prompts-llm": ">=0.1.7-alpha.2",
|
|
222
|
+
"@deepseek-ai/dsh-session-title-first-prompt-llm": ">=0.1.7-alpha.2",
|
|
223
|
+
"@deepseek-ai/dsh-session-title-llm": ">=0.1.7-alpha.2",
|
|
213
224
|
"@deepseek-ai/dsh-settings": ">=0.1.7-alpha.2",
|
|
214
225
|
"@deepseek-ai/dsh-shell": ">=0.1.7-alpha.2",
|
|
215
226
|
"@deepseek-ai/dsh-skill": ">=0.1.7-alpha.2",
|
|
@@ -323,6 +334,18 @@
|
|
|
323
334
|
"@deepseek-ai/dsh-compaction-basic": {
|
|
324
335
|
"optional": true
|
|
325
336
|
},
|
|
337
|
+
"@deepseek-ai/dsh-session-title": {
|
|
338
|
+
"optional": true
|
|
339
|
+
},
|
|
340
|
+
"@deepseek-ai/dsh-session-title-all-prompts-llm": {
|
|
341
|
+
"optional": true
|
|
342
|
+
},
|
|
343
|
+
"@deepseek-ai/dsh-session-title-first-prompt-llm": {
|
|
344
|
+
"optional": true
|
|
345
|
+
},
|
|
346
|
+
"@deepseek-ai/dsh-session-title-llm": {
|
|
347
|
+
"optional": true
|
|
348
|
+
},
|
|
326
349
|
"@deepseek-ai/dsh-settings": {
|
|
327
350
|
"optional": true
|
|
328
351
|
},
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
|
|
2
|
-
export type SettingsFieldMap<View> = Readonly<Partial<Record<keyof View & string, string>>>;
|
|
3
|
-
/** Projection over one shared SettingsScope controller/store, with explicit view-to-owner field mapping. */
|
|
4
|
-
export declare function deriveSettingsScope<Source, View>(source: SettingsScope<Source>, project: (value: Source) => View, fields?: SettingsFieldMap<View>): SettingsScope<View>;
|
|
@@ -1,56 +0,0 @@
|
|
|
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>;
|