dsh-llm-fallbacks 0.3.5 → 0.4.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 +23 -1
- package/README.zh-CN.md +23 -1
- package/dist/authorized-route.d.ts +68 -0
- package/dist/client/ConversationFallbackSwitch.d.ts +18 -14
- package/dist/client/fallbacks-store.d.ts +120 -18
- package/dist/client/index.d.ts +9 -8
- package/dist/client/index.js +170 -39
- package/dist/client/locales.d.ts +14 -0
- package/dist/commands.d.ts +1 -1
- package/dist/gateway.d.ts +79 -3
- package/dist/index.d.ts +24 -4
- package/dist/index.js +343 -41
- package/dist/override.d.ts +39 -0
- package/dist/route-allowlist.d.ts +43 -0
- package/dist/state.d.ts +40 -0
- package/dist/subagent-policy.d.ts +123 -0
- package/dist/virtual-adapter.d.ts +49 -0
- package/package.json +25 -21
- package/scripts/build-client.ts +10 -6
package/dist/state.d.ts
CHANGED
|
@@ -44,6 +44,46 @@ export interface PendingSwitch {
|
|
|
44
44
|
role: string;
|
|
45
45
|
reason: FallbackSwitchReason;
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* One plugin-originated subagent switch the host allowlist blocked (plan
|
|
49
|
+
* dsh-012-subagent-routing T3; spec D1). The intersection of the resolved
|
|
50
|
+
* candidates with the effective allowlist was empty while the 0.3.5 walk
|
|
51
|
+
* would have switched — the attempt is recorded in-memory only (issue #52:
|
|
52
|
+
* no durable session write) for the Subagents card warning (spec D4/T5).
|
|
53
|
+
*/
|
|
54
|
+
export interface BlockedSwitchAttempt {
|
|
55
|
+
/** Epoch ms when the switch was blocked. */
|
|
56
|
+
at: number;
|
|
57
|
+
/** The route the 0.3.5 walk would have switched to (exact provider+model). */
|
|
58
|
+
route: {
|
|
59
|
+
provider: string;
|
|
60
|
+
model: string;
|
|
61
|
+
};
|
|
62
|
+
/** The failure reason that triggered the would-be switch. */
|
|
63
|
+
reason: FallbackSwitchReason;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* How the effective subagent chain head was chosen (plan dsh-012-subagent-routing
|
|
67
|
+
* T5 / T2 M3). Lives on a per-agent map (mirrors {@link BlockedSwitchAttempt}),
|
|
68
|
+
* not on {@link AgentFallbackState} — recording a head is not a switch intent
|
|
69
|
+
* (F-004: the fallback store grows only inside `commit()`).
|
|
70
|
+
*/
|
|
71
|
+
export type ChainHeadSource = 'authorized' | 'injected';
|
|
72
|
+
/**
|
|
73
|
+
* The effective chain head for one subagent, plus the source label the
|
|
74
|
+
* Subagents card renders (spec D4). In-memory only (issue #52).
|
|
75
|
+
*/
|
|
76
|
+
export interface EffectiveChainHead {
|
|
77
|
+
/** Epoch ms when this head was recorded (recency for the card snapshot). */
|
|
78
|
+
at: number;
|
|
79
|
+
/** Exact provider+model of the effective chain head. */
|
|
80
|
+
route: {
|
|
81
|
+
provider: string;
|
|
82
|
+
model: string;
|
|
83
|
+
};
|
|
84
|
+
/** `authorized` = host spawn preserved; `injected` = plugin role-inject. */
|
|
85
|
+
source: ChainHeadSource;
|
|
86
|
+
}
|
|
47
87
|
/** The current (turn, step)'s failed-model set and switch budget (spec §5.1). */
|
|
48
88
|
export interface StepFailures {
|
|
49
89
|
turn: number;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subagent routing policy reader (plan dsh-012-subagent-routing T1; spec D1).
|
|
3
|
+
*
|
|
4
|
+
* Pure, host-free module: plain data in, results out. It resolves the
|
|
5
|
+
* effective subagent model-selection policy from two snapshots the wiring
|
|
6
|
+
* layer extracts from the host:
|
|
7
|
+
*
|
|
8
|
+
* 1. the session's `subagent/model-selection-policy` event payload, read
|
|
9
|
+
* STRUCTURALLY — the upstream reader is not published from
|
|
10
|
+
* `@deepseek-ai/dsh-tool-subagent` and throws on malformed payloads,
|
|
11
|
+
* which is incompatible with the fail-closed no-throw requirement;
|
|
12
|
+
* 2. the host settings service snapshot (`SubagentModelSelectionConfig`
|
|
13
|
+
* `.current()`), typed via a type-only peer import that is erased at emit.
|
|
14
|
+
*
|
|
15
|
+
* The session event wins over settings (ADR read order). Anything present but
|
|
16
|
+
* unreadable yields `'unprovable'` (fail-closed): callers must not emit a
|
|
17
|
+
* plugin-originated route they cannot prove is allowed. This module never
|
|
18
|
+
* throws.
|
|
19
|
+
*
|
|
20
|
+
* @module dsh-llm-fallbacks/subagent-policy
|
|
21
|
+
*/
|
|
22
|
+
import type { SubagentModelSelectionSettings } from '@deepseek-ai/dsh-tool-subagent/model-selection-settings';
|
|
23
|
+
declare module '@deepseek-ai/dsh-session/types' {
|
|
24
|
+
interface SessionEventMap {
|
|
25
|
+
/**
|
|
26
|
+
* Host-recorded route policy for a model-selectable subagent session.
|
|
27
|
+
* The payload shape mirrors upstream exactly, so a future published
|
|
28
|
+
* upstream augmentation merges identically. Read structurally by this
|
|
29
|
+
* plugin; never written here.
|
|
30
|
+
*/
|
|
31
|
+
'subagent/model-selection-policy': {
|
|
32
|
+
/** Exact routes this session may select explicitly for a child. */
|
|
33
|
+
allowedModels: {
|
|
34
|
+
provider: string;
|
|
35
|
+
model: string;
|
|
36
|
+
}[];
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** One exact child LLM route (structural twin of upstream `AllowedModelRoute`). */
|
|
41
|
+
export type PolicyRoute = {
|
|
42
|
+
readonly provider: string;
|
|
43
|
+
readonly model: string;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Effective subagent routing policy for one session.
|
|
47
|
+
*
|
|
48
|
+
* - `disabled`: host policy off or absent — selection matches pre-policy
|
|
49
|
+
* (0.3.5) behavior.
|
|
50
|
+
* - `enabled`: an allowlist is proven; plugin-originated routes must stay
|
|
51
|
+
* inside it.
|
|
52
|
+
* - `unprovable`: the policy is on but unreadable (malformed event payload,
|
|
53
|
+
* or enabled settings with a malformed route list) — fail-closed; no
|
|
54
|
+
* plugin-originated route may be emitted.
|
|
55
|
+
*/
|
|
56
|
+
export type EffectivePolicy = {
|
|
57
|
+
readonly state: 'disabled';
|
|
58
|
+
} | {
|
|
59
|
+
readonly state: 'enabled';
|
|
60
|
+
readonly allowedModels: readonly PolicyRoute[];
|
|
61
|
+
} | {
|
|
62
|
+
readonly state: 'unprovable';
|
|
63
|
+
};
|
|
64
|
+
/** Outcome of the structural session-event read. */
|
|
65
|
+
export type SessionPolicyEventRead = {
|
|
66
|
+
readonly ok: true;
|
|
67
|
+
readonly allowedModels: PolicyRoute[];
|
|
68
|
+
} | {
|
|
69
|
+
readonly ok: false;
|
|
70
|
+
readonly present: true;
|
|
71
|
+
} | {
|
|
72
|
+
readonly ok: false;
|
|
73
|
+
readonly present: false;
|
|
74
|
+
};
|
|
75
|
+
/** Settings snapshot consumed by the reader — plain structural data, no host objects. */
|
|
76
|
+
export type PolicySettings = {
|
|
77
|
+
readonly enabled: boolean;
|
|
78
|
+
readonly allowedModels: readonly PolicyRoute[];
|
|
79
|
+
};
|
|
80
|
+
/** Assignability guard: fails typecheck if the peer's settings shape drifts from the reader's snapshot contract. */
|
|
81
|
+
type AssertSettingsShape<T extends PolicySettings> = T;
|
|
82
|
+
/**
|
|
83
|
+
* The host settings service snapshot type, re-exported from the published
|
|
84
|
+
* peer subpath (type-only). Wiring reads the service's `current()` and passes
|
|
85
|
+
* the result in as plain data; if upstream drifts, this alias fails typecheck
|
|
86
|
+
* instead of drifting silently at runtime (plan ADR).
|
|
87
|
+
*/
|
|
88
|
+
export type PeerSettingsSnapshot = AssertSettingsShape<SubagentModelSelectionSettings>;
|
|
89
|
+
/**
|
|
90
|
+
* Structurally read the session's `subagent/model-selection-policy` event.
|
|
91
|
+
* Never throws: a present-but-malformed payload is reported, not raised.
|
|
92
|
+
*
|
|
93
|
+
* Recency (qc fix wave S-asym — deliberate asymmetry, both sides mirror
|
|
94
|
+
* upstream): this read takes the FIRST occurrence, exactly like the upstream
|
|
95
|
+
* reader's `.find` — the host writes the policy event at most once per
|
|
96
|
+
* session (its ensure-guard checks the reader before appending), so a second
|
|
97
|
+
* occurrence would already be off-contract. The re-emittable
|
|
98
|
+
* `model/selection` log is the opposite: `authorized-route.ts`
|
|
99
|
+
* `lastModelSelection` scans backwards because upstream's selection
|
|
100
|
+
* projection folds to the newest entry.
|
|
101
|
+
*
|
|
102
|
+
* @param events - plain event descriptors (`type` + optional `data`) captured
|
|
103
|
+
* from the session by the wiring layer.
|
|
104
|
+
* @returns the detached route list when the event is present and valid;
|
|
105
|
+
* otherwise a presence flag distinguishing "no policy event" (pure
|
|
106
|
+
* inheritance path) from "policy event unreadable" (fail-closed).
|
|
107
|
+
*/
|
|
108
|
+
export declare function readSessionPolicyEvent(events: readonly {
|
|
109
|
+
type: string;
|
|
110
|
+
data?: unknown;
|
|
111
|
+
}[]): SessionPolicyEventRead;
|
|
112
|
+
/**
|
|
113
|
+
* Resolve the effective policy from the session-event read and the settings
|
|
114
|
+
* snapshot. The session event wins over settings (ADR read order).
|
|
115
|
+
* Fail-closed: a present-but-malformed event, or enabled settings with a
|
|
116
|
+
* malformed route list, yields `'unprovable'`. Never throws.
|
|
117
|
+
*
|
|
118
|
+
* @param event - result of {@link readSessionPolicyEvent}.
|
|
119
|
+
* @param settings - settings service snapshot, or undefined when the host
|
|
120
|
+
* does not compose the service.
|
|
121
|
+
*/
|
|
122
|
+
export declare function effectivePolicy(event: SessionPolicyEventRead, settings: PolicySettings | undefined): EffectivePolicy;
|
|
123
|
+
export {};
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
* @module dsh-llm-fallbacks/virtual-adapter
|
|
32
32
|
*/
|
|
33
33
|
import type { Context } from '@deepseek-ai/cordis';
|
|
34
|
+
import { LlmAdapter, type GenerateOptions, type LlmImageRequestPricing, type LlmModelInfo, type LlmResolvedModelInfo, type LlmRuntime, type StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
34
35
|
import type { FallbacksConfig } from './config.ts';
|
|
35
36
|
/** Provider route of the virtual adapter (exact string, spec lock). */
|
|
36
37
|
export declare const FALLBACKS_PROVIDER = "FallbacksChain";
|
|
@@ -78,6 +79,54 @@ export declare function firstDispatchableExactHead(chain: readonly string[]): Ef
|
|
|
78
79
|
* @param modelDisplayName - catalog/resolved name; falls back to the id.
|
|
79
80
|
*/
|
|
80
81
|
export declare function pickerDisplayName(config: FallbacksConfig, now?: Date, modelDisplayName?: string): string;
|
|
82
|
+
/**
|
|
83
|
+
* The virtual adapter (P1/P3). `stream()` is a thin head-delegate, never a
|
|
84
|
+
* second routing engine: no chain walk, cooldown, caps, revert bookkeeping,
|
|
85
|
+
* or state writes live here — those stay in the `agent/request` /
|
|
86
|
+
* `agent/request-error` listeners. Exported so the defensive guard paths
|
|
87
|
+
* the registration lifecycle cannot reach (a registered route whose `llm`
|
|
88
|
+
* vanished mid-teardown) stay unit-testable by direct construction.
|
|
89
|
+
*/
|
|
90
|
+
export declare class FallbacksChainAdapter extends LlmAdapter {
|
|
91
|
+
private readonly readConfig;
|
|
92
|
+
private readonly getLlm;
|
|
93
|
+
constructor(readConfig: () => FallbacksConfig, getLlm: () => LlmRuntime | undefined);
|
|
94
|
+
/** Minimal honest descriptor (P3) — the provider route IS the display name. */
|
|
95
|
+
providerInfo(provider: string): {
|
|
96
|
+
id: string;
|
|
97
|
+
name: string;
|
|
98
|
+
};
|
|
99
|
+
/** Advisory catalog: one virtual row; `name` is the live picker label. */
|
|
100
|
+
listModels(provider: string): Promise<readonly LlmModelInfo[]>;
|
|
101
|
+
/**
|
|
102
|
+
* Proxy the current effective head's model metadata when resolvable (P3):
|
|
103
|
+
* modalities/context-window/reasoning follow the head — the truthful
|
|
104
|
+
* answer for the CURRENT head. Permissive default otherwise; never throws.
|
|
105
|
+
*/
|
|
106
|
+
resolveModel(provider: string, model: string, signal?: AbortSignal): Promise<LlmResolvedModelInfo>;
|
|
107
|
+
/**
|
|
108
|
+
* Route-accurate image pricing (0.1.2 adoption): the virtual row has no
|
|
109
|
+
* pricing of its own — the virtual `provider`/`model` arguments are
|
|
110
|
+
* intentionally ignored. Resolves the SAME effective head `stream()`
|
|
111
|
+
* dispatches and delegates through the runtime lookup, so the token
|
|
112
|
+
* meter gets exactly the answer the concrete route's adapter would give
|
|
113
|
+
* (including `undefined`). Never throws: an unresolvable head, a
|
|
114
|
+
* vanished `llm`, or a throwing delegate degrades to `undefined` — the
|
|
115
|
+
* token meter's neutral estimate.
|
|
116
|
+
*/
|
|
117
|
+
imageRequestPricing(_provider: string, _model: string): LlmImageRequestPricing | undefined;
|
|
118
|
+
/**
|
|
119
|
+
* Thin head-delegate (P1): resolve the effective chain head and dispatch
|
|
120
|
+
* that REAL pair through the host LLM runtime. Throws an explicit
|
|
121
|
+
* `LlmError` only when there is no real pair to delegate to: an empty
|
|
122
|
+
* effective chain, an undispatchable head (wildcard / malformed /
|
|
123
|
+
* self-route — the recursion guard), or a vanished `llm` runtime. The
|
|
124
|
+
* runtime normalizes the throw into a terminal error finish chunk, so the
|
|
125
|
+
* documented listener-order degradation stays graceful instead of a hard
|
|
126
|
+
* outage.
|
|
127
|
+
*/
|
|
128
|
+
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
129
|
+
}
|
|
81
130
|
/**
|
|
82
131
|
* Install the virtual adapter registration lifecycle (P2).
|
|
83
132
|
*
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"dsh",
|
|
6
6
|
"dsh-plugin"
|
|
7
7
|
],
|
|
8
|
-
"version": "0.
|
|
8
|
+
"version": "0.4.0",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"main": "./dist/index.js",
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
@@ -32,7 +32,6 @@
|
|
|
32
32
|
},
|
|
33
33
|
"client": {
|
|
34
34
|
"inject": [
|
|
35
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
36
35
|
"@deepseek-ai/dsh-client-ui-settings-plugins",
|
|
37
36
|
"@deepseek-ai/dsh-client-ui-settings",
|
|
38
37
|
"@deepseek-ai/dsh-client-ui-conversation",
|
|
@@ -60,25 +59,30 @@
|
|
|
60
59
|
"use-sync-external-store": "^1.6.0"
|
|
61
60
|
},
|
|
62
61
|
"peerDependencies": {
|
|
63
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
64
|
-
"@deepseek-ai/dsh-agent": "^0.1.
|
|
65
|
-
"@deepseek-ai/dsh-api-gateway": "^0.1.
|
|
66
|
-
"@deepseek-ai/dsh-api-remotes": "^0.1.
|
|
67
|
-
"@deepseek-ai/dsh-
|
|
68
|
-
"@deepseek-ai/dsh-client-
|
|
69
|
-
"@deepseek-ai/dsh-client-
|
|
70
|
-
"@deepseek-ai/dsh-client-
|
|
71
|
-
"@deepseek-ai/dsh-client-ui-
|
|
72
|
-
"@deepseek-ai/dsh-client-ui-
|
|
73
|
-
"@deepseek-ai/dsh-client-ui-
|
|
74
|
-
"@deepseek-ai/dsh-client-ui-
|
|
75
|
-
"@deepseek-ai/dsh-
|
|
76
|
-
"@deepseek-ai/dsh-
|
|
77
|
-
"@deepseek-ai/dsh-
|
|
78
|
-
"@deepseek-ai/dsh-
|
|
79
|
-
"@deepseek-ai/dsh-
|
|
80
|
-
"@deepseek-ai/dsh-
|
|
81
|
-
"@deepseek-ai/
|
|
62
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
63
|
+
"@deepseek-ai/dsh-agent": "^0.1.2-rc.1",
|
|
64
|
+
"@deepseek-ai/dsh-api-gateway": "^0.1.2-rc.1",
|
|
65
|
+
"@deepseek-ai/dsh-api-remotes": "^0.1.2-rc.1",
|
|
66
|
+
"@deepseek-ai/dsh-api-session-controller": "^0.1.2-rc.1",
|
|
67
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.2-rc.1",
|
|
68
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.2-rc.1",
|
|
69
|
+
"@deepseek-ai/dsh-client-store": "^0.1.2-rc.1",
|
|
70
|
+
"@deepseek-ai/dsh-client-ui-chat": "^0.1.2-rc.1",
|
|
71
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.2-rc.1",
|
|
72
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.2-rc.1",
|
|
73
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.2-rc.1",
|
|
74
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.2-rc.1",
|
|
75
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.2-rc.1",
|
|
76
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.2-rc.1",
|
|
77
|
+
"@deepseek-ai/dsh-commands": "^0.1.2-rc.1",
|
|
78
|
+
"@deepseek-ai/dsh-llm": "^0.1.2-rc.1",
|
|
79
|
+
"@deepseek-ai/dsh-llm-retry": "^0.1.2-rc.1",
|
|
80
|
+
"@deepseek-ai/dsh-session": "^0.1.2-rc.1",
|
|
81
|
+
"@deepseek-ai/dsh-settings": "^0.1.2-rc.1",
|
|
82
|
+
"@deepseek-ai/dsh-tool-subagent": "^0.1.2-rc.1",
|
|
83
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.1.2-rc.1",
|
|
84
|
+
"@deepseek-ai/dsh-typert-registry": "^0.1.2-rc.1",
|
|
85
|
+
"@deepseek-ai/schemastery": "^3.18.2",
|
|
82
86
|
"react": "^18.2.0"
|
|
83
87
|
},
|
|
84
88
|
"devDependencies": {
|
package/scripts/build-client.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Client bundle build: emits the closure-factory CJS artifact the dsh web
|
|
3
3
|
* loader consumes — `window.__ModuleLoader__.load({ id: 'dsh-llm-fallbacks',
|
|
4
4
|
* factory: (require) => { … return module.exports; } })`. Externals resolve
|
|
5
|
-
* through the loader module table (platform seed entries
|
|
6
|
-
* `@deepseek-ai/dsh-client-
|
|
5
|
+
* through the loader module table (the platform seed entries — in 0.1.2 the
|
|
6
|
+
* former runtime exemption is folded into the `@deepseek-ai/dsh-client-store`
|
|
7
|
+
* platform module, the client half's one VALUE import); everything else
|
|
7
8
|
* inlines.
|
|
8
9
|
*
|
|
9
10
|
* Build tool: `tsdown` (rolldown) — the same tool the dsh host itself uses
|
|
@@ -47,9 +48,12 @@ const OUT_FILE = 'index.js'
|
|
|
47
48
|
|
|
48
49
|
/**
|
|
49
50
|
* Frozen loader module table (dsh mechanism-guide): the plan-documented
|
|
50
|
-
* composition = PLATFORM_MODULES (
|
|
51
|
-
*
|
|
52
|
-
*
|
|
51
|
+
* composition = PLATFORM_MODULES (8 entries in 0.1.2, `web/src/platform.ts:8-15`
|
|
52
|
+
* — the old runtime `./client` exemption became the
|
|
53
|
+
* `@deepseek-ai/dsh-client-store` platform module, so the store VALUE import
|
|
54
|
+
* stays external and resolves from the host loader table) + the
|
|
55
|
+
* PRELOADED_CLIENT_EXTERNALS (empty in 0.1.2, `web/src/platform.ts:16-17`).
|
|
56
|
+
* rc.8 dropped `dsh-client-web-react` (deleted
|
|
53
57
|
* package — the uSES bridge is vendored in `src/client/use-snapshot.ts`),
|
|
54
58
|
* `dsh-client-ui-attachment` and `dsh-client-schema-form` from the platform
|
|
55
59
|
* table. `@deepseek-ai/cordis` is the in-box cordis framework (dsh-advisor
|
|
@@ -63,9 +67,9 @@ export const CLIENT_EXTERNALS: readonly string[] = [
|
|
|
63
67
|
'react-dom',
|
|
64
68
|
'react-dom/client',
|
|
65
69
|
'@deepseek-ai/cordis',
|
|
70
|
+
'@deepseek-ai/dsh-client-store',
|
|
66
71
|
'@deepseek-ai/dsh-client-ui-slots',
|
|
67
72
|
'@deepseek-ai/dsh-client-ui-primitives',
|
|
68
|
-
'@deepseek-ai/dsh-client-runtime/client',
|
|
69
73
|
]
|
|
70
74
|
|
|
71
75
|
/**
|