dsh-fallback-router 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +162 -2
- package/README.zh-CN.md +170 -0
- package/bundle/cordis.patch.yml +19 -0
- package/dist/authorized-route.d.ts +68 -0
- package/dist/automatch.d.ts +98 -0
- package/dist/chains.d.ts +186 -0
- package/dist/client/ConversationFallbackSwitch.d.ts +91 -0
- package/dist/client/FallbacksCard.d.ts +36 -0
- package/dist/client/GeneralFallbacksRow.d.ts +19 -0
- package/dist/client/ModelsPanel.d.ts +64 -0
- package/dist/client/SubagentRoleBadge.d.ts +53 -0
- package/dist/client/fallbacks-store.d.ts +631 -0
- package/dist/client/index.d.ts +64 -0
- package/dist/client/index.js +3420 -0
- package/dist/client/locales.d.ts +454 -0
- package/dist/client/switch-guard.d.ts +17 -0
- package/dist/client/use-snapshot.d.ts +11 -0
- package/dist/commands.d.ts +380 -0
- package/dist/config.d.ts +239 -0
- package/dist/cooldown.d.ts +57 -0
- package/dist/events.d.ts +52 -0
- package/dist/gateway.d.ts +359 -0
- package/dist/index.d.ts +195 -0
- package/dist/index.js +4587 -0
- package/dist/models.d.ts +306 -0
- package/dist/override.d.ts +39 -0
- package/dist/presets.d.ts +20 -0
- package/dist/recovery.d.ts +92 -0
- package/dist/role-notice.d.ts +184 -0
- package/dist/role-persona.d.ts +71 -0
- package/dist/role-projection-key.d.ts +41 -0
- package/dist/role-projection.d.ts +152 -0
- package/dist/role-resolution.d.ts +105 -0
- package/dist/roles.d.ts +90 -0
- package/dist/route-allowlist.d.ts +43 -0
- package/dist/schema.d.ts +46 -0
- package/dist/seeds.d.ts +237 -0
- package/dist/selectors.d.ts +38 -0
- package/dist/state.d.ts +205 -0
- package/dist/subagent-policy.d.ts +123 -0
- package/dist/subagents-seam.d.ts +305 -0
- package/dist/time-slots.d.ts +147 -0
- package/dist/tui-settings.d.ts +145 -0
- package/dist/tui.d.ts +63 -0
- package/dist/virtual-adapter.d.ts +259 -0
- package/icon.svg +16 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +138 -6
package/dist/chains.d.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fallback chain resolution (spec §7.2; plan fallbacks-role-runtime Task 1).
|
|
3
|
+
*
|
|
4
|
+
* `resolveChain` returns the ordered candidate list for a failing
|
|
5
|
+
* (provider, model). Candidates come from the role's own chain plus
|
|
6
|
+
* `rootChain` appended as an inherit-root fallback — **append-not-replace**:
|
|
7
|
+
* `[...(roleDef.chain ?? []), ...(roleDef.fallback === 'none' ? [] :
|
|
8
|
+
* rootChain)]` (role entries first, rootChain entries after). The reserved
|
|
9
|
+
* built-in `inherit` id (INHERIT_ROLE_ID) resolves to `rootChain` silently —
|
|
10
|
+
* it is the legal no-rule-match role, not a typo'd id. A truly undeclared
|
|
11
|
+
* role id (defense) resolves to `rootChain` alone + one warn. Chain-key
|
|
12
|
+
* specificity lookup (`exact` / `provider/*` / role / `default` keys) is
|
|
13
|
+
* deleted — the two-block config model has no chain keys.
|
|
14
|
+
*
|
|
15
|
+
* Wildcard entries (`provider/*`) are resolved against the failing model —
|
|
16
|
+
* keep the model id, swap only the provider; the "target provider has no
|
|
17
|
+
* such model id" skip is judged by {@link resolveCandidate} (via
|
|
18
|
+
* `modelExists`) or by the caller's filter.
|
|
19
|
+
*
|
|
20
|
+
* The concatenation itself lives in ONE helper — {@link buildRoleEntries}
|
|
21
|
+
* (qc1 S-3 SSOT) — shared by {@link resolveChainViews} and
|
|
22
|
+
* {@link hasWildcardEntry}, so the wildcard probe is exact (qc2 F-003:
|
|
23
|
+
* `fallback: 'none'` excludes `rootChain`) and can never diverge from the
|
|
24
|
+
* resolution. Defensive warns (unknown role / undeclared rule target) go
|
|
25
|
+
* through an injected `warn` callback — the wiring passes the cordis
|
|
26
|
+
* `logger.warn` (qc2 F-002 / qc3 S-3), with `console.warn` as the
|
|
27
|
+
* direct-call default.
|
|
28
|
+
*
|
|
29
|
+
* Cooldown / failed-set / same-as-current filtering is caller-side;
|
|
30
|
+
* {@link createCandidateFilter} provides the ready-made predicate. A
|
|
31
|
+
* `CONTEXT_WINDOW_EXCEEDED` walk adds one more caller-side dimension to the
|
|
32
|
+
* same predicate — candidates whose disclosed context window is not larger
|
|
33
|
+
* than the failing model's cannot fit the request either
|
|
34
|
+
* ({@link fitsContextWindow}; unknown windows keep the candidate).
|
|
35
|
+
*
|
|
36
|
+
* @module dsh-fallback-router/chains
|
|
37
|
+
*/
|
|
38
|
+
import { type FallbacksRole } from './config.ts';
|
|
39
|
+
import type { CooldownStore, StepFailureSet } from './cooldown.ts';
|
|
40
|
+
import { type Selector } from './selectors.ts';
|
|
41
|
+
/** The model that just failed — the anchor for chain resolution. */
|
|
42
|
+
export interface FailingModel {
|
|
43
|
+
provider: string;
|
|
44
|
+
model: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolve one chain entry against the failing model.
|
|
48
|
+
*
|
|
49
|
+
* - `provider/model` → that exact selector (not subject to `modelExists` —
|
|
50
|
+
* the user explicitly listed it).
|
|
51
|
+
* - `provider/*` → keeps the failing model id, swaps only the provider; when
|
|
52
|
+
* `modelExists` is given and the target provider has no such model id →
|
|
53
|
+
* `null` (skip).
|
|
54
|
+
* - Malformed entries → `null`: they "do not take effect"; the strict throw
|
|
55
|
+
* path is {@link parseSelector}, used by the Task 3 config-warning path.
|
|
56
|
+
*/
|
|
57
|
+
export declare function resolveCandidate(entry: string, failing: FailingModel, modelExists?: (provider: string, model: string) => boolean): Selector | null;
|
|
58
|
+
/**
|
|
59
|
+
* Single-pass chain resolution for the decision path (T1 review Important
|
|
60
|
+
* #2). Walks the role's concatenated entries ONCE and returns the unfiltered
|
|
61
|
+
* view — `all` (every resolvable candidate, no filter and no existence
|
|
62
|
+
* probe: the caller's early-exit / annotation view) — plus the parallel
|
|
63
|
+
* `wildcard` provenance (`wildcard[i]` = candidate `all[i]` came from a
|
|
64
|
+
* `provider/*` entry). The caller builds the existence probe from `all` and
|
|
65
|
+
* then derives the surviving view with {@link selectCandidates}, so the
|
|
66
|
+
* decision path never resolves the chain twice (and an unknown role warns
|
|
67
|
+
* at most once per decision).
|
|
68
|
+
*
|
|
69
|
+
* The reserved built-in `inherit` id (INHERIT_ROLE_ID) is legal — it
|
|
70
|
+
* resolves to `rootChain` silently (the no-rule-match role, not a typo'd
|
|
71
|
+
* id). Only a truly undeclared id (∉ declared ids ∪ {'inherit'}) warns once
|
|
72
|
+
* (defensive — never crashes) through `warn` (defaults to `console.warn`
|
|
73
|
+
* for direct-call compatibility; the decision path injects the cordis
|
|
74
|
+
* `logger.warn` — qc2 F-002 / qc3 S-3). Malformed entries never become
|
|
75
|
+
* candidates.
|
|
76
|
+
*/
|
|
77
|
+
export declare function resolveChainViews(roles: readonly FallbacksRole[], rootChain: readonly string[], role: string, provider: string, model: string, warn?: (message: string) => void): {
|
|
78
|
+
all: Selector[];
|
|
79
|
+
wildcard: boolean[];
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Derive the surviving candidates from a {@link resolveChainViews} pass:
|
|
83
|
+
* the caller filter plus the wildcard-only existence probe. `modelExists`
|
|
84
|
+
* applies to `provider/*`-origin candidates only (scoped by the parallel
|
|
85
|
+
* `wildcard` provenance — T2 review Important #1: exact entries are never
|
|
86
|
+
* existence-filtered), mirroring how {@link resolveCandidate} receives it.
|
|
87
|
+
* Order and duplicates are preserved.
|
|
88
|
+
*/
|
|
89
|
+
export declare function selectCandidates(all: readonly Selector[], wildcard: readonly boolean[], filter?: (candidate: Selector) => boolean, modelExists?: (provider: string, model: string) => boolean): Selector[];
|
|
90
|
+
/**
|
|
91
|
+
* Ordered fallback candidates for the failing (provider, model).
|
|
92
|
+
*
|
|
93
|
+
* Candidates are the role's concatenated entries (spec §7.2, see
|
|
94
|
+
* {@link buildRoleEntries} — the single concatenation SSOT): the declared
|
|
95
|
+
* role's own `chain` followed by `rootChain` unless `fallback: 'none'`
|
|
96
|
+
* (append-not-replace — role entries first, rootChain as the tail). The
|
|
97
|
+
* reserved built-in `inherit` id resolves to `rootChain` silently; a truly
|
|
98
|
+
* undeclared/unknown `role` id resolves to `rootChain` alone and warns once
|
|
99
|
+
* (defensive — never crashes). `filter` optionally drops candidates — the
|
|
100
|
+
* caller owns cooldown/failed-set/same-model filtering (see
|
|
101
|
+
* {@link createCandidateFilter}). The `modelExists` existence probe is NOT
|
|
102
|
+
* applied per entry here: {@link resolveChainViews} returns the parallel
|
|
103
|
+
* `wildcard` provenance, and {@link selectCandidates} applies `modelExists`
|
|
104
|
+
* to `provider/*`-origin candidates ONLY (qc1 S-2 — exact entries are never
|
|
105
|
+
* existence-filtered, spec §2 clause 2 / T2 review Important #1
|
|
106
|
+
* decision-path contract). `warn` forwards to {@link resolveChainViews}
|
|
107
|
+
* (defaults to `console.warn`; the decision path injects `logger.warn`).
|
|
108
|
+
*
|
|
109
|
+
* Single-pass: delegates to {@link resolveChainViews} + {@link selectCandidates}
|
|
110
|
+
* (one concatenated-entry walk, warn at most once per call).
|
|
111
|
+
*/
|
|
112
|
+
export declare function resolveChain(roles: readonly FallbacksRole[], rootChain: readonly string[], role: string, provider: string, model: string, filter?: (candidate: Selector) => boolean, modelExists?: (provider: string, model: string) => boolean, warn?: (message: string) => void): Selector[];
|
|
113
|
+
/**
|
|
114
|
+
* Whether any entry of the role's concatenated candidates is a wildcard
|
|
115
|
+
* (`provider/*`). F-002: {@link resolveChain} resolves wildcard entries to
|
|
116
|
+
* concrete models, so the wildcard provenance is invisible on the resolved
|
|
117
|
+
* candidate list — the decision path consults the same concatenation
|
|
118
|
+
* `resolveChain` walks to decide whether the catalog existence probe is
|
|
119
|
+
* needed at all.
|
|
120
|
+
*
|
|
121
|
+
* The probe walks {@link buildRoleEntries} — the SAME concatenation
|
|
122
|
+
* {@link resolveChainViews} resolves (qc1 S-3 single SSOT) — so it is
|
|
123
|
+
* exact, never an over-approximation (qc2 F-003 / qc3 S-1): for
|
|
124
|
+
* `fallback: 'none'` roles `rootChain` is excluded, and a wildcard that can
|
|
125
|
+
* never reach the candidate list never builds a catalog probe. No
|
|
126
|
+
* false-negative is possible: any wildcard the resolution could reach is on
|
|
127
|
+
* this concatenation by construction. Malformed entries never become
|
|
128
|
+
* candidates and are skipped.
|
|
129
|
+
*/
|
|
130
|
+
export declare function hasWildcardEntry(roles: readonly FallbacksRole[], rootChain: readonly string[], role: string): boolean;
|
|
131
|
+
/** Inputs for {@link createCandidateFilter}. */
|
|
132
|
+
export interface CandidateFilterOptions {
|
|
133
|
+
/** The currently active (failing) model — skipped as "same as current". */
|
|
134
|
+
current: FailingModel;
|
|
135
|
+
/** Cooldown store: suppressed candidates are skipped (keyed `provider/model`). */
|
|
136
|
+
cooldown: Pick<CooldownStore, 'isSuppressed'>;
|
|
137
|
+
/** Failed-set for the current step: already-failed candidates are skipped. */
|
|
138
|
+
failed: Pick<StepFailureSet, 'has'>;
|
|
139
|
+
/** Optional existence probe: candidates the target provider lacks are skipped. */
|
|
140
|
+
modelExists?: (provider: string, model: string) => boolean;
|
|
141
|
+
/**
|
|
142
|
+
* The failing model's context window, when known — set only for a
|
|
143
|
+
* `CONTEXT_WINDOW_EXCEEDED` walk. Candidates whose own known window is not
|
|
144
|
+
* LARGER than this cannot fit the request either and are skipped.
|
|
145
|
+
*/
|
|
146
|
+
minContextWindow?: number;
|
|
147
|
+
/** Context-window lookup for a candidate route; `undefined` = not disclosed. */
|
|
148
|
+
contextWindowOf?: (provider: string, model: string) => number | undefined;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The caller-side candidate filter (Task 3): a candidate is usable when it
|
|
152
|
+
* differs from the current model, is not cooldown-suppressed, has not failed
|
|
153
|
+
* in this step, can fit the request that overflowed (context-window walks
|
|
154
|
+
* only — see {@link fitsContextWindow}), and (when `modelExists` is given)
|
|
155
|
+
* exists on its provider.
|
|
156
|
+
*/
|
|
157
|
+
export declare function createCandidateFilter(options: CandidateFilterOptions): (candidate: Selector) => boolean;
|
|
158
|
+
/**
|
|
159
|
+
* Why one considered candidate was excluded from the selection (spec §2
|
|
160
|
+
* 行为可见性). `context-window` is the `CONTEXT_WINDOW_EXCEEDED` walk's
|
|
161
|
+
* exclusion: the candidate's disclosed window is not larger than the failing
|
|
162
|
+
* model's, so the request that just overflowed cannot fit there either.
|
|
163
|
+
*/
|
|
164
|
+
export type CandidateSkipReason = 'same-as-current' | 'cooldown' | 'step-failed' | 'context-window' | 'missing-id';
|
|
165
|
+
/** One entry of the ordered, per-candidate annotation (T3 review Minor 1). */
|
|
166
|
+
export interface AnnotatedCandidate {
|
|
167
|
+
candidate: Selector;
|
|
168
|
+
/** Why this candidate was excluded; `undefined` = it survived every exclusion. */
|
|
169
|
+
skip?: CandidateSkipReason;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Annotate the ordered "considered" candidate list with each candidate's skip
|
|
173
|
+
* reason (spec §2 行为可见性: the switch log must show the attempt order and
|
|
174
|
+
* why each candidate was skipped). The selection-relevant view is the
|
|
175
|
+
* `surviving` list the decision path resolved with filter + existence probe:
|
|
176
|
+
* a candidate absent from it failed one of the exclusions, and the concrete
|
|
177
|
+
* reason is derived from the same checks {@link createCandidateFilter}
|
|
178
|
+
* applies, in the same precedence. `missing-id` therefore only ever labels
|
|
179
|
+
* entries the existence probe dropped — exact entries are never
|
|
180
|
+
* existence-probed (T2 contract), so they stay in `surviving` and are
|
|
181
|
+
* reported as usable, never as missing-id.
|
|
182
|
+
*
|
|
183
|
+
* This is the diagnostic counterpart of {@link createCandidateFilter} — pure,
|
|
184
|
+
* order-preserving, and duplicate-preserving.
|
|
185
|
+
*/
|
|
186
|
+
export declare function annotateCandidates(candidates: readonly Selector[], surviving: readonly Selector[], options: Pick<CandidateFilterOptions, 'current' | 'cooldown' | 'failed' | 'minContextWindow' | 'contextWindowOf'>): AnnotatedCandidate[];
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversation-level fallback-switch visibility (plan fallbacks-aux-seams,
|
|
3
|
+
* task 2, D1+D2 seam).
|
|
4
|
+
*
|
|
5
|
+
* Every `fallbacks/switch` session event becomes its own chat-transcript
|
|
6
|
+
* node (`fallbacks-switch`), rendered as a compact system-style line at the
|
|
7
|
+
* switch's event seq — the user sees the recovery happen in place
|
|
8
|
+
* (provider/model A → B, role · reason), instead of the event existing only
|
|
9
|
+
* in the raw `sessions.history` event feed (it is NOT a SurfaceEventType,
|
|
10
|
+
* so the `unknown-surface` fallback never picked it up and the transcript
|
|
11
|
+
* showed nothing).
|
|
12
|
+
*
|
|
13
|
+
* Contract notes (dsh-private, current homes, verified 2026-08-28):
|
|
14
|
+
* - D1 registry: `ConversationEventRegistry.register(definition)` — the
|
|
15
|
+
* client Context's `uiConversation.events` service seat
|
|
16
|
+
* (`ui-conversation conversation/event-registry.ts:17-25`); external
|
|
17
|
+
* registration precedent `ui-workflow-run/src/client/index.ts:22-26`
|
|
18
|
+
* (its inject list declares `uiConversation` for the same direct read).
|
|
19
|
+
* The engine feeds EVERY session event to each definition's `match`
|
|
20
|
+
* (`ui-conversation conversation/assembler.ts:406-414`) — non-surface
|
|
21
|
+
* plugin events included — and live events reach the engine through the
|
|
22
|
+
* assembly window pump (`ui-conversation conversation/assembly.ts:97-105`
|
|
23
|
+
* `assembler.append`).
|
|
24
|
+
* - D2 seat: `conversation.chat.node` is a keyed seat dispatched by
|
|
25
|
+
* `ChatConversationViewNode.kind` (`ui-chat contract/slots.ts:171-180`;
|
|
26
|
+
* `chat/ChatNodeSeat.tsx:212` renderSlot dispatch), externally registrable
|
|
27
|
+
* as `{ name, key, locale }` (precedents: ui-tool `tool-call`
|
|
28
|
+
* `apply.ts:28-31`, ui-goal `command-input` `index.ts:60-63`,
|
|
29
|
+
* ui-workflow-run `workflow-run`).
|
|
30
|
+
* - Purity: this file only type-imports `@deepseek-ai/dsh-client-ui-chat/client`
|
|
31
|
+
* and `@deepseek-ai/dsh-client-ui-conversation/client` (both erased at
|
|
32
|
+
* build); the renderer self-draws on `--dsw-alias-*` tokens. Render-only:
|
|
33
|
+
* the Definition is a pure view contribution — no message construction,
|
|
34
|
+
* no model-context injection (C4 excluded by scope).
|
|
35
|
+
*/
|
|
36
|
+
import type { ReactNode } from 'react';
|
|
37
|
+
import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
38
|
+
import type { ConversationNodeDefinition } from '@deepseek-ai/dsh-client-ui-conversation/client';
|
|
39
|
+
import type { FallbackSwitchReason } from '../events.ts';
|
|
40
|
+
/** Final chat payload of one decided fallback switch (snapshot of the event). */
|
|
41
|
+
export interface FallbacksSwitchChatData {
|
|
42
|
+
readonly seq: number;
|
|
43
|
+
readonly time: number;
|
|
44
|
+
readonly turn: number;
|
|
45
|
+
readonly step: number;
|
|
46
|
+
/** The model the request was using when the switch was decided. */
|
|
47
|
+
readonly from: {
|
|
48
|
+
readonly provider: string;
|
|
49
|
+
readonly model: string;
|
|
50
|
+
};
|
|
51
|
+
/** The chain candidate the switch moves to. */
|
|
52
|
+
readonly to: {
|
|
53
|
+
readonly provider: string;
|
|
54
|
+
readonly model: string;
|
|
55
|
+
};
|
|
56
|
+
/** The fallback-chain role the decision resolved for the agent. */
|
|
57
|
+
readonly role: string;
|
|
58
|
+
readonly reason: FallbackSwitchReason;
|
|
59
|
+
}
|
|
60
|
+
declare module '@deepseek-ai/dsh-client-ui-chat/client' {
|
|
61
|
+
interface ChatNodeDataMap {
|
|
62
|
+
/** One decided fallback provider/model switch, rendered at its event seq. */
|
|
63
|
+
'fallbacks-switch': FallbacksSwitchChatData;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* One switch event → one chat node. Each `fallbacks/switch` event is its own
|
|
68
|
+
* Context (id = event seq — the durable unique key), so every match is a
|
|
69
|
+
* `start`; `update` is a passthrough (no aggregation — D3's per-Turn
|
|
70
|
+
* counting is a separate, unselected seam).
|
|
71
|
+
*/
|
|
72
|
+
export declare const fallbackSwitchDefinition: ConversationNodeDefinition<FallbacksSwitchChatData>;
|
|
73
|
+
/**
|
|
74
|
+
* Props delivered by the keyed chat-node seat: runtime share + the `fallbacks` locale seat.
|
|
75
|
+
*/
|
|
76
|
+
export type ConversationFallbackSwitchProps = PropsRuntime<'conversation.chat.node', 'fallbacks-switch'> & PropsLocale<'fallbacks'>;
|
|
77
|
+
/**
|
|
78
|
+
* Render one fallback switch as a compact system-style transcript line.
|
|
79
|
+
*
|
|
80
|
+
* Geometry follows the upstream chat system rows (the compaction boundary
|
|
81
|
+
* notice: warning-toned title + separator + ellipsized summary —
|
|
82
|
+
* `chat/MessageItem .module.css:38-122`); every color resolves through a
|
|
83
|
+
* `--dsw-alias-*` token. A reason outside the current union renders raw (forward-compatible
|
|
84
|
+
* durable log, same rule as the card/general row summaries). A malformed or
|
|
85
|
+
* partial payload (version skew) degrades to the title-only line instead of
|
|
86
|
+
* throwing during interpolation — the transcript slot stays visible with the
|
|
87
|
+
* warning-toned "model downgraded" title (T1 copy) and no summary details.
|
|
88
|
+
* @param props - composed keyed seat props.
|
|
89
|
+
* @returns the switch line element tree.
|
|
90
|
+
*/
|
|
91
|
+
export declare function ConversationFallbackSwitch({ node, t }: ConversationFallbackSwitchProps): ReactNode;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `llm-router` plugin card on the web Plugins page.
|
|
3
|
+
*
|
|
4
|
+
* SCOPE (deliberate, post-merge): the card renders ONE thing — the 模型配置
|
|
5
|
+
* panel. The legacy fallbacks surface (the global 默认降级链 editor, the
|
|
6
|
+
* time-slot table, the subagent role list, the advanced scalars) is not
|
|
7
|
+
* editable here any more:
|
|
8
|
+
*
|
|
9
|
+
* - `rootChain` / `timeSlots` / `namedChains` are COMPILED from the model
|
|
10
|
+
* rows by `withCompiledRouter`, so hand-editing them would be overwritten
|
|
11
|
+
* on the next read;
|
|
12
|
+
* - `roles` / `cooldownMs` / `revertPolicy` / `presets` / `tz` keep whatever
|
|
13
|
+
* the composed config already carries — a save writes only `models` and
|
|
14
|
+
* `providers`, so nothing is dropped;
|
|
15
|
+
* - provider API keys and other credentials belong to the provider plugins
|
|
16
|
+
* (`llm-pi-ai`, …) and are never surfaced (let alone made editable) here.
|
|
17
|
+
*
|
|
18
|
+
* The card is a thin shell around {@link ModelsPanel}: it loads the store on
|
|
19
|
+
* mount, renders the channel/error notices, and hands the panel the write
|
|
20
|
+
* gate. All authoring logic lives in the panel.
|
|
21
|
+
*/
|
|
22
|
+
import { type ReactNode } from 'react';
|
|
23
|
+
import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
24
|
+
import type { InjectFace } from '@deepseek-ai/dsh-client-ui-slots';
|
|
25
|
+
import { FallbacksSettingsController, type FallbacksSettingsState } from './fallbacks-store.ts';
|
|
26
|
+
/** The card store (loaded on mount, refreshed on pushed invalidations). */
|
|
27
|
+
export interface FallbacksCardInjected {
|
|
28
|
+
controller: FallbacksSettingsController;
|
|
29
|
+
/** Bare snapshot source the renderer binds as `useSnapshot`. */
|
|
30
|
+
hooks: {
|
|
31
|
+
snapshot: import('@deepseek-ai/dsh-client-store').SnapshotStore<FallbacksSettingsState>;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** Props delivered by the slot outlet: runtime share + locale seat + inject face. */
|
|
35
|
+
export type FallbacksCardProps = PropsRuntime<'plugins.bundle.config'> & PropsLocale<'fallbacks'> & InjectFace<FallbacksCardInjected>;
|
|
36
|
+
export declare function FallbacksCard({ controller, useSnapshot }: FallbacksCardProps): ReactNode;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
3
|
+
import type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots';
|
|
4
|
+
import type { FallbacksSettingsController, FallbacksSettingsState } from './fallbacks-store.ts';
|
|
5
|
+
/** Injected dependencies of {@link GeneralFallbacksRow} (slot `inject`). */
|
|
6
|
+
export interface GeneralFallbacksRowInjected {
|
|
7
|
+
/** The shared controller (loaded on first mount, refreshed on pushed invalidations). */
|
|
8
|
+
controller: FallbacksSettingsController;
|
|
9
|
+
/** uSES subscription hook bound to the store (inject face — advisor pattern). */
|
|
10
|
+
useSnapshot: SnapshotSelectorHook<FallbacksSettingsState>;
|
|
11
|
+
}
|
|
12
|
+
/** Props delivered by the slot outlet: runtime share + locale seat + inject face. */
|
|
13
|
+
export type GeneralFallbacksRowProps = PropsRuntime<'settings.general.item'> & PropsLocale<'fallbacks'> & GeneralFallbacksRowInjected;
|
|
14
|
+
/**
|
|
15
|
+
* Render the Fallbacks status row.
|
|
16
|
+
* @param props - composed slot props.
|
|
17
|
+
* @returns the row element tree.
|
|
18
|
+
*/
|
|
19
|
+
export declare function GeneralFallbacksRow({ controller, useSnapshot, t }: GeneralFallbacksRowProps): ReactNode;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模型配置 (model profiles) panel — the ONLY authoring surface of
|
|
3
|
+
* dsh-fallback-router.
|
|
4
|
+
*
|
|
5
|
+
* Every row is a MODEL CONFIGURATION. Two shapes exist:
|
|
6
|
+
*
|
|
7
|
+
* - 单一模型 (`kind: 'route'`) — a `provider/model` route with its own time
|
|
8
|
+
* windows (时间区间, either a frozen preset like 梁高峰/梁低谷 or a
|
|
9
|
+
* hand-written window), its own fallback policy (官方兜底 / 循环兜底 /
|
|
10
|
+
* 不兜底), the extra hops it may try (追加兜底路由) and its own failure
|
|
11
|
+
* rules (错误码 + 正则 + 结构判定).
|
|
12
|
+
* - 自由组合 (`kind: 'combo'`) — no route of its own; every 时间区间 names an
|
|
13
|
+
* existing configuration (`使用配置`), so 梁高峰 can run config A while
|
|
14
|
+
* 梁低谷 runs config B without duplicating either one's settings.
|
|
15
|
+
*
|
|
16
|
+
* Every provider/model field is a dropdown fed by the host catalog
|
|
17
|
+
* (`llm/listConfigurableProviders` + `session/modelCatalog`); a value the
|
|
18
|
+
* catalog does not know is kept as an extra option so a hand-written route is
|
|
19
|
+
* never silently replaced, and an empty catalog degrades to a text input.
|
|
20
|
+
*
|
|
21
|
+
* The host compiles this list down to `rootChain` / `timeSlots` /
|
|
22
|
+
* `namedChains` (`withCompiledRouter`), so the chain-walking engine keeps
|
|
23
|
+
* working unchanged.
|
|
24
|
+
*
|
|
25
|
+
* The panel owns its own draft and its own Save: it writes ONLY `models` and
|
|
26
|
+
* `providers`. Everything the legacy card used to expose (the global chain,
|
|
27
|
+
* the time-slot table, the subagent roles, the advanced scalars) is
|
|
28
|
+
* deliberately NOT editable here — it is either compiled from these rows or
|
|
29
|
+
* left at whatever the config already carries.
|
|
30
|
+
*
|
|
31
|
+
* @module dsh-fallback-router/client/ModelsPanel
|
|
32
|
+
*/
|
|
33
|
+
import { type ReactNode } from 'react';
|
|
34
|
+
import type { FallbacksConfig } from '../config.ts';
|
|
35
|
+
/** One model the host can dispatch to, as a dropdown shows it. */
|
|
36
|
+
export interface ModelsCatalogModel {
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
/** 推理等级 the model advertises (empty when it has none). */
|
|
40
|
+
efforts?: {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
}[];
|
|
44
|
+
/** The level the model picks when the request names none. */
|
|
45
|
+
defaultEffort?: string;
|
|
46
|
+
}
|
|
47
|
+
/** One provider and its models, as the dropdowns show them. */
|
|
48
|
+
export interface ModelsCatalogProvider {
|
|
49
|
+
id: string;
|
|
50
|
+
name: string;
|
|
51
|
+
models: ModelsCatalogModel[];
|
|
52
|
+
}
|
|
53
|
+
/** The dropdown source, as the card passes it in. */
|
|
54
|
+
export type ModelsCatalog = readonly ModelsCatalogProvider[];
|
|
55
|
+
/** Props: the live config, the catalog behind the dropdowns, and the write path. */
|
|
56
|
+
export interface ModelsPanelProps {
|
|
57
|
+
config: FallbacksConfig;
|
|
58
|
+
writable: boolean;
|
|
59
|
+
saving: boolean;
|
|
60
|
+
onSave: (next: FallbacksConfig) => void;
|
|
61
|
+
/** Provider/model catalog for the dropdowns; omit to fall back to text inputs. */
|
|
62
|
+
catalog?: ModelsCatalog;
|
|
63
|
+
}
|
|
64
|
+
export declare function ModelsPanel({ config, writable, saving, onSave, catalog }: ModelsPanelProps): ReactNode;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-header subagent role/model badge (plan subagent-role-badge T3 +
|
|
3
|
+
* plan subagent-role-model): a compact read-only pill in
|
|
4
|
+
* `conversation.session.header.actions` showing the viewed CHILD session's
|
|
5
|
+
* dispatch role and the model route it runs on, hovering as
|
|
6
|
+
* `role → provider/model (request route)`.
|
|
7
|
+
*
|
|
8
|
+
* Contract notes (re-verified 2026-10-23 against the installed dsh 0.2.1-rc.2):
|
|
9
|
+
* - The slot is `kind: 'list'`, `scope: 'session'`, owner props an empty marker
|
|
10
|
+
* (`ConversationHeaderActionOwnerProps`, `children?: never`) — the header
|
|
11
|
+
* renders occupants with `renderSlot(..., {})`, so ALL data flows through the
|
|
12
|
+
* session standard kit.
|
|
13
|
+
* - Every `scope: 'session'` occupant receives the session standard kit, whose
|
|
14
|
+
* members this component uses are `sessionId`, `useSessions` (the shared
|
|
15
|
+
* Session store) and `useProjection` (the viewed session's projection cut).
|
|
16
|
+
* The compiled peer types do not carry that merge into this plugin's
|
|
17
|
+
* typecheck program (ui-session is deliberately not a plugin peer), so the
|
|
18
|
+
* seats are read STRUCTURALLY and guarded — a version-skewed host without them
|
|
19
|
+
* renders nothing, never throws.
|
|
20
|
+
* - The badge belongs to a CHILD session: `byId[sessionId].origin === 'subagent'`
|
|
21
|
+
* is the host's own child-session idiom (`dsh-client-ui-subagent`
|
|
22
|
+
* `SubagentCatalogAction`). Root rows omit `origin`, so a root session renders
|
|
23
|
+
* nothing even though its own `modelSelection` carries a route — the composer
|
|
24
|
+
* owns that control.
|
|
25
|
+
* - Role data: the host's `fallbacksSubagentRole` projection (Task 3b), folded
|
|
26
|
+
* server-side out of the child's own durable log — the ONE role source. The
|
|
27
|
+
* session kit binds the hook to the VIEWED session, so no `sessionId`
|
|
28
|
+
* plumbing, no effect, no state, and no polling. An absent key (host without
|
|
29
|
+
* the unit, or no notice row) omits the role half; `inherit` omits it too.
|
|
30
|
+
* - Route data: the host's EXISTING `modelSelection` projection, read as
|
|
31
|
+
* `next ?? lastUsed` — the route the child's next request will use, falling
|
|
32
|
+
* back to the one its latest recorded request ran on. The two halves are
|
|
33
|
+
* INDEPENDENT: either alone still renders, and both missing renders nothing.
|
|
34
|
+
* - Hook order is render-independent (Task 3b L2 review C-1): the seats are real
|
|
35
|
+
* hooks, so the three reads are unconditional and only their derived values
|
|
36
|
+
* are guarded — a conditional read would change the hook count on the very
|
|
37
|
+
* re-render that publishes a running child's role.
|
|
38
|
+
* - Render-only discipline (C4 pattern, same as `ConversationFallbackSwitch`):
|
|
39
|
+
* the badge contributes a view; no message construction, no model-context
|
|
40
|
+
* injection. Degrade-never-crash: a missing/foreign projection value renders
|
|
41
|
+
* `null` — the actions row collapses cleanly.
|
|
42
|
+
*/
|
|
43
|
+
import type { ReactNode } from 'react';
|
|
44
|
+
import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
45
|
+
/** Props delivered by the actions slot outlet: runtime share + locale seat. */
|
|
46
|
+
export type SubagentRoleBadgeProps = PropsRuntime<'conversation.session.header.actions'> & PropsLocale<'fallbacks'>;
|
|
47
|
+
/**
|
|
48
|
+
* Render the viewed SUBAGENT session's role and model.
|
|
49
|
+
* @param props - composed slot props (the session-kit `useProjection` /
|
|
50
|
+
* `useSessions` seats are read structurally — see the module docblock).
|
|
51
|
+
* @returns the badge element, or `null` when there is nothing to show.
|
|
52
|
+
*/
|
|
53
|
+
export declare function SubagentRoleBadge(props: SubagentRoleBadgeProps): ReactNode;
|