dsh-llm-fallbacks 0.4.4 → 0.5.1
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 +86 -25
- package/README.zh-CN.md +86 -25
- package/dist/client/SubagentRoleBadge.d.ts +40 -15
- package/dist/client/fallbacks-store.d.ts +0 -53
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +77 -150
- package/dist/client/locales.d.ts +2 -0
- package/dist/events.d.ts +2 -2
- package/dist/gateway.d.ts +2 -55
- package/dist/index.d.ts +6 -27
- package/dist/index.js +713 -129
- package/dist/role-notice.d.ts +156 -0
- package/dist/role-projection-key.d.ts +41 -0
- package/dist/role-projection.d.ts +151 -0
- package/dist/role-resolution.d.ts +11 -0
- package/dist/roles.d.ts +4 -1
- package/dist/seeds.d.ts +63 -21
- package/dist/subagents-seam.d.ts +292 -0
- package/dist/time-slots.d.ts +3 -2
- package/dist/virtual-adapter.d.ts +42 -6
- package/package.json +27 -25
- package/scripts/build-client.ts +1 -1
- package/scripts/repair-session-logs.ts +2040 -0
- package/scripts/session-logs/catalog.ts +547 -0
- package/scripts/session-logs/classify.ts +47 -0
- package/scripts/session-logs/publish.ts +583 -0
- package/scripts/session-logs/rules.ts +1227 -0
- package/scripts/repair-fallbacks-switch-logs.ts +0 -342
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dispatch-seam role resolution + per-child record (plan
|
|
3
|
+
* role-based-subagent-adoption Task 1).
|
|
4
|
+
*
|
|
5
|
+
* The seam is the documented cordis service-read waterfall
|
|
6
|
+
* (`Events['internal/get']`, `@deepseek-ai/cordis` 4.0.2
|
|
7
|
+
* `lib/types/events.d.ts`): a listener wraps the `subagents` service VALUE on
|
|
8
|
+
* read and returns a prototype-delegating wrapper whose `start` /
|
|
9
|
+
* `startContinuable` resolve the dispatch role from the incoming request
|
|
10
|
+
* BEFORE delegating. The real service object is never mutated; readers of the
|
|
11
|
+
* `subagents` service transparently receive the wrapper.
|
|
12
|
+
*
|
|
13
|
+
* Role source (plan Global Constraints): the child request's prompt carries
|
|
14
|
+
* the Assignment header field `**Execute as**: <id>` — dsh role binding is
|
|
15
|
+
* prompt-only. {@link resolveDeclaredRoleFromAssignment} is the ONE pure
|
|
16
|
+
* resolution point; it canonicalizes exactly like the existing trimmed-id
|
|
17
|
+
* map (trim, drop ONE leading `@`, case-insensitive match against the
|
|
18
|
+
* declared trimmed ids) and returns the DECLARED RAW id. Undeclared, absent,
|
|
19
|
+
* `inherit`, or ambiguous declarations resolve to `undefined` = no role.
|
|
20
|
+
*
|
|
21
|
+
* The resolution reads the Assignment HEADER region only, never the whole
|
|
22
|
+
* prompt (Task 1 CF-2): the region ends at the first Assignment body marker,
|
|
23
|
+
* and when the prompt carries NO marker it is bounded to the LEADING HEADER
|
|
24
|
+
* BLOCK — the first paragraph plus every following paragraph that opens with
|
|
25
|
+
* header material, ending at the prompt's first prose paragraph. So a
|
|
26
|
+
* `**Execute as**: <id>` line quoted inside a body paragraph can never shape a
|
|
27
|
+
* resolution (it is outside the region) even though mstar's canonical dispatch
|
|
28
|
+
* text carries no body marker at all. Inside the region the engine's pinned
|
|
29
|
+
* ambiguity rule stands unchanged: the DISTINCT DECLARED ids must collapse to
|
|
30
|
+
* exactly one, else the dispatch is a no-op — never a first-occurrence pick.
|
|
31
|
+
*
|
|
32
|
+
* Per-child record: the wrapped start's RESULT carries the child session id —
|
|
33
|
+
* `start` resolves to a `SubagentRun` (`id` IS the published child session id
|
|
34
|
+
* for a local run) and `startContinuable` to a `ContinuableStart` (`childId`).
|
|
35
|
+
* The record `{ role, at, firstNoticePending }` is keyed by that id (Task 3
|
|
36
|
+
* reads it to emit the one role notice row). A result carrying only a
|
|
37
|
+
* `jobId` — a registry job id, NOT a session id — is never keyed: the child is
|
|
38
|
+
* correlated instead through the parent-owned `subagent/catalog` session event
|
|
39
|
+
* `{ childId, label }` joined on the delegating parent + request label.
|
|
40
|
+
*
|
|
41
|
+
* Persona delivery (plan Task 2): the SAME single resolution point merges the
|
|
42
|
+
* declared role's `roles.list[].persona` (trimmed, non-empty) into the
|
|
43
|
+
* request's NATIVE `persona` slot — the slot the runtime composes as the
|
|
44
|
+
* scoped `deployment:persona-prefix` system-prompt section on the child. The
|
|
45
|
+
* source is the RESOLVED ROLE only, so delivery is chain-independent: a
|
|
46
|
+
* `chain: []` role gets its persona exactly like a chained one (no routing
|
|
47
|
+
* state is read). Both native start surfaces are covered, with the gate each
|
|
48
|
+
* one's fail-loud contract needs (measured on the installed
|
|
49
|
+
* `@deepseek-ai/dsh-subagent` `0.1.5-rc.1`): the one-shot `start` REJECTS a
|
|
50
|
+
* request carrying `persona` for a provider whose
|
|
51
|
+
* `getProvider(name).capabilities.persona` is not `true` (`lib/index.js:3202-3227`,
|
|
52
|
+
* `assertCapabilities`), so the merge pre-checks that flag; the continuable
|
|
53
|
+
* surface is composed by the continuation manager, which applies
|
|
54
|
+
* `request.persona` unconditionally (`lib/index.js:1703-1705`) and is gated by
|
|
55
|
+
* the provider's `prepareContinuable` presence instead (`lib/index.js:3179-3183`).
|
|
56
|
+
* A gate miss skips the persona with ONE contained debug log — never a failed
|
|
57
|
+
* start. An explicit caller `persona` WINS (the slot is filled only when it is
|
|
58
|
+
* absent), and every skip path returns the caller's OWN request object, so the
|
|
59
|
+
* native call stays byte-identical.
|
|
60
|
+
*
|
|
61
|
+
* In-session notice row (plan Task 3): the SAME install point registers the
|
|
62
|
+
* `agent/pre-step` emitter (`./role-notice.ts`) over the record map it owns, so
|
|
63
|
+
* the role is announced once in the child's own session. Its per-agent
|
|
64
|
+
* `noticeEmitted` marker is exposed here for the caller's cleanup sites: the
|
|
65
|
+
* plugin dispose effect clears it, while `agent/disposed` deliberately KEEPS it
|
|
66
|
+
* (CF-6 — the marker is SESSION-stable and bounded, because a continuable
|
|
67
|
+
* child's durable session survives its activation).
|
|
68
|
+
*
|
|
69
|
+
* Durable role visibility (plan Task 3b): the SAME install point registers the
|
|
70
|
+
* host-side session projection unit (`./role-projection.ts`) that folds that
|
|
71
|
+
* notice row out of the child's own log, so the session-header badge can show
|
|
72
|
+
* the role for a settled child and after a host restart — a read of the SAME
|
|
73
|
+
* single write primitive, never a second one.
|
|
74
|
+
*
|
|
75
|
+
* Multi-fiber composition (CF-5): the record map and the notice marker are
|
|
76
|
+
* ROOT-shared, while only the `internal/get` wrapper (+ the catalog listener it
|
|
77
|
+
* feeds) is single-owner. Every applied fiber registers its OWN notice emitter
|
|
78
|
+
* and projection unit and reports its ownership structurally
|
|
79
|
+
* (`SubagentSeam.ownsWrapper`), so a disposing fiber cannot silently take the
|
|
80
|
+
* role surfaces down with it.
|
|
81
|
+
*
|
|
82
|
+
* Degrade-never-crash: an absent/reshaped service, an unexpected result
|
|
83
|
+
* shape, or any throwing bookkeeping degrades to the native path with at most
|
|
84
|
+
* ONE contained debug log — a dispatch is never affected.
|
|
85
|
+
*
|
|
86
|
+
* @module dsh-llm-fallbacks/subagents-seam
|
|
87
|
+
*/
|
|
88
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
89
|
+
import { type FallbacksRole } from './config.ts';
|
|
90
|
+
/**
|
|
91
|
+
* The cordis service-read waterfall event the seam registers on. Referenced
|
|
92
|
+
* ONCE (registration) so a future cordis rename is a one-line change here.
|
|
93
|
+
*/
|
|
94
|
+
export declare const SUBAGENT_SEAM_EVENT = "internal/get";
|
|
95
|
+
/** The service name the seam wraps (`ctx.subagents`). */
|
|
96
|
+
export declare const SUBAGENT_SEAM_SERVICE = "subagents";
|
|
97
|
+
/**
|
|
98
|
+
* One dispatch-resolved per-child record (Task 3 reads it to emit exactly one
|
|
99
|
+
* role notice row per child session).
|
|
100
|
+
*/
|
|
101
|
+
export interface SubagentSeamRecord {
|
|
102
|
+
/**
|
|
103
|
+
* The DECLARED RAW role id resolved at the dispatch seam.
|
|
104
|
+
*
|
|
105
|
+
* Authoritative-role precedence (CF-10, documented rather than resolved by
|
|
106
|
+
* re-branding): a `startContinuable` resume re-keys the child and may resolve
|
|
107
|
+
* a DIFFERENT declared role, and this field is then rewritten while the
|
|
108
|
+
* durable notice row the child's session already carries keeps the FIRST role.
|
|
109
|
+
* The projection deliberately reads the notice row, so the durable role is the
|
|
110
|
+
* child's announced identity and this record is only the LIVE dispatch hint
|
|
111
|
+
* (what the next start / the resume fallback would use). The two can therefore
|
|
112
|
+
* disagree after a re-dispatch until the child is disposed; no consumer may
|
|
113
|
+
* treat the record as the durable role.
|
|
114
|
+
*/
|
|
115
|
+
role: string;
|
|
116
|
+
/** When the record was written (epoch ms). */
|
|
117
|
+
at: number;
|
|
118
|
+
/**
|
|
119
|
+
* `true` until the child's role notice row has been emitted (Task 3). A
|
|
120
|
+
* later write for the SAME child session preserves an already-cleared
|
|
121
|
+
* marker, so a repeat dispatch/resume cannot produce a second notice row.
|
|
122
|
+
* The root-shared `noticeEmitted` set is the SESSION-stable half of that
|
|
123
|
+
* guarantee (it survives `agent/disposed`); this field is the per-record half
|
|
124
|
+
* that a resume rewrite preserves.
|
|
125
|
+
*/
|
|
126
|
+
firstNoticePending: boolean;
|
|
127
|
+
/**
|
|
128
|
+
* `true` when the role DECLARES a persona and that persona was NOT delivered
|
|
129
|
+
* (Task 2's capability gate declined it, or no provider was registered to
|
|
130
|
+
* carry it). Absent when there was nothing to report: the persona was
|
|
131
|
+
* delivered, the caller set one (explicit intent wins), the role declares
|
|
132
|
+
* none, or no persona source is wired. Task 3 appends
|
|
133
|
+
* ` (persona not applied)` on `true` only.
|
|
134
|
+
*
|
|
135
|
+
* STICKY (Task 3 review Minor 4): a record rewrite (a `startContinuable`
|
|
136
|
+
* resume re-keys the child) can only ADD this verdict, never clear it. A
|
|
137
|
+
* resume recomputes the merge outcome on its own surface — and that surface
|
|
138
|
+
* legitimately returns `false` when it delivers the CALLER's persona or when
|
|
139
|
+
* its own merge degrades — but neither retroactively installs the role's
|
|
140
|
+
* DECLARED persona on the dispatch that already reported it missing. The
|
|
141
|
+
* child's one notice row is a claim about that dispatch, so the first `true`
|
|
142
|
+
* survives; a later `false` is simply not recorded.
|
|
143
|
+
*/
|
|
144
|
+
personaNotApplied?: boolean;
|
|
145
|
+
}
|
|
146
|
+
/** One consumed prompt content block (`@deepseek-ai/dsh-llm` `ContentBlock` text members). */
|
|
147
|
+
export interface SubagentPromptBlockView {
|
|
148
|
+
readonly type: string;
|
|
149
|
+
readonly text?: string;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Structural view of the one-shot start request (consumed fields only): the
|
|
153
|
+
* `prompt` is the role-extraction source, `label` + `parent` are the
|
|
154
|
+
* catalog-correlation join key, and `persona` is the NATIVE per-child persona
|
|
155
|
+
* slot the role persona is merged into (plan Global Constraints: an explicit
|
|
156
|
+
* caller value wins). The wrapper forwards the request object itself, so every
|
|
157
|
+
* other field reaches the service unchanged.
|
|
158
|
+
*/
|
|
159
|
+
export interface SubagentStartRequestView {
|
|
160
|
+
readonly prompt?: readonly SubagentPromptBlockView[];
|
|
161
|
+
readonly label?: string;
|
|
162
|
+
readonly parent?: {
|
|
163
|
+
readonly session?: {
|
|
164
|
+
readonly id?: string;
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
readonly persona?: string;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Structural view of the continuable start spec (consumed fields only):
|
|
171
|
+
* `childId` is the caller-reserved identity a resume already carries, used as
|
|
172
|
+
* the record fallback when the resume request has no Assignment header.
|
|
173
|
+
*/
|
|
174
|
+
export interface ContinuableStartSpecView {
|
|
175
|
+
readonly provider: string;
|
|
176
|
+
readonly label?: string;
|
|
177
|
+
readonly childId?: string;
|
|
178
|
+
readonly request: SubagentStartRequestView;
|
|
179
|
+
}
|
|
180
|
+
/** Options for {@link installSubagentSeam}. */
|
|
181
|
+
export interface SubagentSeamOptions {
|
|
182
|
+
/**
|
|
183
|
+
* Live declared-role id map (trimmed id → DECLARED RAW id), read per start
|
|
184
|
+
* so a settings change is observed without a re-install.
|
|
185
|
+
*/
|
|
186
|
+
roleIds: () => ReadonlyMap<string, string>;
|
|
187
|
+
/**
|
|
188
|
+
* Live declared roles (`roles.list`) — the persona source, read per start so
|
|
189
|
+
* a settings/persona edit is observed without a re-install. Absent ⇒ the
|
|
190
|
+
* seam only records roles (no persona delivery).
|
|
191
|
+
*/
|
|
192
|
+
roles?: () => readonly FallbacksRole[];
|
|
193
|
+
/** Contained debug sink (at most one line per contained degrade). */
|
|
194
|
+
debug?: (message: string) => void;
|
|
195
|
+
}
|
|
196
|
+
/** The installed seam (Task 3 surface + explicit teardown). */
|
|
197
|
+
export interface SubagentSeam {
|
|
198
|
+
/**
|
|
199
|
+
* Per-child records keyed by the child SESSION id (the wrapped start's
|
|
200
|
+
* result id). ROOT-shared (CF-5): every fiber applied over the same root reads
|
|
201
|
+
* the same map. Mutable by design: Task 3 clears `firstNoticePending` after
|
|
202
|
+
* emitting the notice.
|
|
203
|
+
*/
|
|
204
|
+
readonly records: Map<string, SubagentSeamRecord>;
|
|
205
|
+
/**
|
|
206
|
+
* Per-agent marker of children whose role notice row was already emitted
|
|
207
|
+
* (mirrors the runtime's `dispatchInjected`): the in-memory half of Task 3's
|
|
208
|
+
* once-per-child guarantee, also ROOT-shared. SESSION-stable (CF-6): it is
|
|
209
|
+
* deliberately NOT cleared on `agent/disposed` — a continuable child's durable
|
|
210
|
+
* session survives the activation, so clearing the marker would let a
|
|
211
|
+
* re-activation resume append a second notice row to the same log. It is
|
|
212
|
+
* bounded (`NOTICE_EMITTED_LIMIT`) and cleared only in the plugin dispose
|
|
213
|
+
* effect.
|
|
214
|
+
*/
|
|
215
|
+
readonly noticeEmitted: Set<string>;
|
|
216
|
+
/**
|
|
217
|
+
* `true` when THIS install owns the root-scoped `internal/get` wrapper (plus
|
|
218
|
+
* the catalog-correlation listener it feeds) — the only part that must have a
|
|
219
|
+
* single owner. `false` on a later fiber over the same root, which still
|
|
220
|
+
* registers its OWN notice emitter and projection unit. The callers' dedupe
|
|
221
|
+
* branch reads THIS field (CF-5): a structured marker, never a thrown
|
|
222
|
+
* message.
|
|
223
|
+
*/
|
|
224
|
+
readonly ownsWrapper: boolean;
|
|
225
|
+
/** Stop intercepting service reads (the owning fiber's teardown also does). */
|
|
226
|
+
dispose(): void;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Cap on un-correlated claims (a `jobId`-only result whose catalog event never
|
|
230
|
+
* arrives). Bounded insertion-order eviction — recency-ordered, because a
|
|
231
|
+
* re-claimed hot key is re-inserted (`beginCorrelation` deletes before it sets),
|
|
232
|
+
* so the busiest claim can never be the one evicted.
|
|
233
|
+
*
|
|
234
|
+
* @internal Exported for the bound test (mirrors `subagentSeamOf`): the value is
|
|
235
|
+
* not part of the plugin's public surface, but a test that hardcoded 64 could
|
|
236
|
+
* not tell the real bound from a stale copy.
|
|
237
|
+
*/
|
|
238
|
+
export declare const PENDING_CORRELATION_LIMIT = 64;
|
|
239
|
+
/**
|
|
240
|
+
* PURE dispatch-role resolution (plan Global Constraints): read the Assignment
|
|
241
|
+
* header field `**Execute as**: <id>` from `prompt` and return the DECLARED
|
|
242
|
+
* RAW role id, or `undefined` when nothing resolves. Never throws.
|
|
243
|
+
*
|
|
244
|
+
* - absent / empty / body-quoted-only → `undefined`
|
|
245
|
+
* - `inherit` (the reserved "no specific role" id) → `undefined`
|
|
246
|
+
* - undeclared id → `undefined` (never invent a role)
|
|
247
|
+
* - several DISTINCT declared ids in the header region → `undefined`
|
|
248
|
+
* (ambiguous); repeats of the same declared id (e.g. `coder` and `@CODER`)
|
|
249
|
+
* still resolve
|
|
250
|
+
*
|
|
251
|
+
* @param prompt - the Assignment text carried by the child request's prompt.
|
|
252
|
+
* @param roleIds - the declared trimmed-id map (`trimmed id → declared raw id`).
|
|
253
|
+
*/
|
|
254
|
+
export declare function resolveDeclaredRoleFromAssignment(prompt: string, roleIds: ReadonlyMap<string, string>): string | undefined;
|
|
255
|
+
/**
|
|
256
|
+
* PURE, chain-independent persona lookup (plan Task 2 / Global Constraints):
|
|
257
|
+
* the DECLARED `persona` of `roleId`, trimmed, and only when it is still
|
|
258
|
+
* non-empty after the trim — a blank persona is NO persona. `roleId` is the
|
|
259
|
+
* DECLARED RAW id the seam already resolved, so this is an exact id match, not
|
|
260
|
+
* a second canonicalization pass.
|
|
261
|
+
*
|
|
262
|
+
* Only `id` and `persona` are read: no `chain`, no `rootChain`, no routing
|
|
263
|
+
* state — a `chain: []` role's persona resolves exactly like a chained one's.
|
|
264
|
+
*
|
|
265
|
+
* @param roles - the live declared roles (`roles.list`).
|
|
266
|
+
* @param roleId - the DECLARED RAW role id resolved at the seam.
|
|
267
|
+
*/
|
|
268
|
+
export declare function personaForRole(roles: readonly FallbacksRole[], roleId: string): string | undefined;
|
|
269
|
+
/**
|
|
270
|
+
* @internal Test seam (mirrors `chainHeads`): the seam installed on
|
|
271
|
+
* `ctx`, if any. Not part of the plugin's public surface — lets tests read and
|
|
272
|
+
* seed the per-child record map without reaching into the installation
|
|
273
|
+
* closure. `undefined` when no seam is installed.
|
|
274
|
+
*/
|
|
275
|
+
export declare function subagentSeamOf(ctx: Context): SubagentSeam | undefined;
|
|
276
|
+
/**
|
|
277
|
+
* Install the dispatch seam on `ctx`: wrap every `subagents` service read and
|
|
278
|
+
* resolve + record the dispatch role per start, and register this fiber's role
|
|
279
|
+
* surfaces (the notice emitter + the projection unit).
|
|
280
|
+
*
|
|
281
|
+
* Never throws for a multi-fiber apply (CF-5): a fiber whose root already owns
|
|
282
|
+
* the `internal/get` wrapper gets a seam with `ownsWrapper: false` — it shares
|
|
283
|
+
* the root's record map / notice marker (so exactly one notice row is written
|
|
284
|
+
* per child) and still installs its OWN notice emitter and projection unit, so
|
|
285
|
+
* neither surface dies when another fiber of the same root disposes.
|
|
286
|
+
*
|
|
287
|
+
* Returns the seam (the shared record map Task 3 reads, the ownership marker,
|
|
288
|
+
* plus an idempotent `dispose` that withdraws THIS fiber's registrations). The
|
|
289
|
+
* `internal/get` listener is owned by this module; callers that rely on the
|
|
290
|
+
* fiber teardown alone may ignore `dispose`.
|
|
291
|
+
*/
|
|
292
|
+
export declare function installSubagentSeam(ctx: Context, options: SubagentSeamOptions): SubagentSeam;
|
package/dist/time-slots.d.ts
CHANGED
|
@@ -78,7 +78,7 @@ export declare const PRESETS: Record<PresetId, PresetDefinition>;
|
|
|
78
78
|
* are the ordered walk before that last-resort fallback. An empty chain or
|
|
79
79
|
* a chain whose tail is not an official model (including the retired V4
|
|
80
80
|
* ids) keeps slot rows inert and refuses the virtual-row
|
|
81
|
-
*
|
|
81
|
+
* delegate; the v0.2.2 failure walk over the raw chain stays
|
|
82
82
|
* verbatim.
|
|
83
83
|
*/
|
|
84
84
|
export declare function isAllDayConforming(chain: readonly string[]): boolean;
|
|
@@ -92,7 +92,8 @@ export declare function isAllDayConforming(chain: readonly string[]): boolean;
|
|
|
92
92
|
* P6 (qc1 F-001): without a conforming all-day the slot rows are inert, so
|
|
93
93
|
* this IS the raw `rootChain` — the gate lives in {@link resolveSlotState},
|
|
94
94
|
* the single source every slot surface (this, the 分时切换 log, the
|
|
95
|
-
* `/fallbacks` strip,
|
|
95
|
+
* `/fallbacks` strip, the virtual adapter delegate, its served-route anchor)
|
|
96
|
+
* reads.
|
|
96
97
|
*/
|
|
97
98
|
export declare function resolveEffectiveChain(config: FallbacksConfig, now: Date, tz: string): string[];
|
|
98
99
|
/**
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* reconcile thunk, wired by `apply()`) plus child activation. The row is
|
|
15
15
|
* visible whenever the plugin is enabled — a non-conforming all-day chain
|
|
16
16
|
* does NOT hide it (PR #62 feedback); conformance still gates a
|
|
17
|
-
* successful
|
|
17
|
+
* successful delegate (`effectiveHeadOf` below refuses a
|
|
18
18
|
* non-conforming all-day). The condition deliberately ignores `timeSlots`
|
|
19
19
|
* and conformance, so slot-row edits and chain edits never churn
|
|
20
20
|
* registration.
|
|
@@ -26,12 +26,14 @@
|
|
|
26
26
|
* `agent/request-error`, where the existing engine walks from there).
|
|
27
27
|
* `resolveModel` proxies the current effective head's metadata when
|
|
28
28
|
* resolvable (modalities/context-window/reasoning follow the head) with a
|
|
29
|
-
* permissive default otherwise — never throws.
|
|
29
|
+
* permissive default otherwise — never throws. `providerRetryPolicy` and
|
|
30
|
+
* `imageRequestPricing` mirror the same head for the host-captured retry
|
|
31
|
+
* policy and the token meter — also never throwing.
|
|
30
32
|
*
|
|
31
33
|
* @module dsh-llm-fallbacks/virtual-adapter
|
|
32
34
|
*/
|
|
33
35
|
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';
|
|
36
|
+
import { LlmAdapter, type GenerateOptions, type LlmImageRequestPricing, type LlmModelInfo, type LlmResolvedModelInfo, type LlmRuntime, type ResolvedRetryPolicy, type StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
35
37
|
import type { FallbacksConfig } from './config.ts';
|
|
36
38
|
/** Provider route of the virtual adapter (exact string, spec lock). */
|
|
37
39
|
export declare const FALLBACKS_PROVIDER = "FallbacksChain";
|
|
@@ -62,15 +64,33 @@ export interface EffectiveHead {
|
|
|
62
64
|
}
|
|
63
65
|
/**
|
|
64
66
|
* The FIRST DISPATCHABLE exact head of a chain — the single definition of
|
|
65
|
-
* "effective head" (F-001) shared by the
|
|
66
|
-
*
|
|
67
|
-
* chain `resolveEffectiveChain` produces,
|
|
67
|
+
* "effective head" (F-001) shared by the virtual adapter's delegate paths and
|
|
68
|
+
* the routing engine's served-route anchor (`anchorServedRoute` in
|
|
69
|
+
* `src/index.ts`). Walks the SAME chain `resolveEffectiveChain` produces,
|
|
70
|
+
* skipping entries that can never
|
|
68
71
|
* be dispatched: malformed selectors (config-warning path), `provider/*`
|
|
69
72
|
* `provider/*` wildcards (no real pair), and self-routes back to
|
|
70
73
|
* `FallbacksChain/*` (the P1 recursion guard). `undefined` when the chain
|
|
71
74
|
* is empty or no entry is dispatchable.
|
|
72
75
|
*/
|
|
73
76
|
export declare function firstDispatchableExactHead(chain: readonly string[]): EffectiveHead | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* The effective chain head at `now` (P1): the first DISPATCHABLE exact head
|
|
79
|
+
* of the SAME `resolveEffectiveChain` the routing engine uses — thin
|
|
80
|
+
* config+clock entry into the shared {@link firstDispatchableExactHead} (no
|
|
81
|
+
* divergent skip/walk rules). `undefined` when the chain is empty or no
|
|
82
|
+
* entry is dispatchable (wildcard, self-route to `FallbacksChain/*`,
|
|
83
|
+
* malformed selector).
|
|
84
|
+
*
|
|
85
|
+
* Conformance gate (PR #62 feedback): the all-day `rootChain` must be
|
|
86
|
+
* conforming (exactly one official model — Flash or Pro)
|
|
87
|
+
* for a successful delegate —
|
|
88
|
+
* a legacy multi-model (or empty) all-day chain earns no primary
|
|
89
|
+
* semantics even though the picker row is visible. `resolveModel` then
|
|
90
|
+
* falls back to the permissive default and `stream()` throws
|
|
91
|
+
* {@link UNDISPATCHABLE_HEAD_CODE}.
|
|
92
|
+
*/
|
|
93
|
+
export declare function effectiveHeadOf(config: FallbacksConfig, now: Date): EffectiveHead | undefined;
|
|
74
94
|
/**
|
|
75
95
|
* Host picker label: `Auto: <displayName>[<slot>]`.
|
|
76
96
|
* Display name first so the trigger stays readable and platforms stay
|
|
@@ -104,6 +124,22 @@ export declare class FallbacksChainAdapter extends LlmAdapter {
|
|
|
104
124
|
* answer for the CURRENT head. Permissive default otherwise; never throws.
|
|
105
125
|
*/
|
|
106
126
|
resolveModel(provider: string, model: string, signal?: AbortSignal): Promise<LlmResolvedModelInfo>;
|
|
127
|
+
/**
|
|
128
|
+
* Route-accurate retry attribution: the virtual row declares no policy of
|
|
129
|
+
* its own — the virtual `provider` argument is intentionally ignored. The
|
|
130
|
+
* host captures `adapter.providerRetryPolicy(provider) ??
|
|
131
|
+
* resolveRetryPolicy(void 0, …)` ONCE, when a route is registered, so
|
|
132
|
+
* answering here with the SAME policy the concrete head route declared
|
|
133
|
+
* (e.g. a user's `llm-deepseek.retryPolicy`, including `mode: 'always'`)
|
|
134
|
+
* keeps retries behaving exactly as they do on the head `stream()`
|
|
135
|
+
* dispatches. Never throws: an unresolvable head, a vanished `llm`, or an
|
|
136
|
+
* unregistered head provider degrades to `undefined` — the host's own
|
|
137
|
+
* default. That last arm matters: the runtime's lookup THROWS `NO_ADAPTER`
|
|
138
|
+
* for an unknown provider, and this call happens inside the plugin's
|
|
139
|
+
* `registerAdapter`, so an escaping throw would take the whole virtual
|
|
140
|
+
* route down instead of just degrading its policy.
|
|
141
|
+
*/
|
|
142
|
+
providerRetryPolicy(_provider: string): ResolvedRetryPolicy | undefined;
|
|
107
143
|
/**
|
|
108
144
|
* Route-accurate image pricing (0.1.2 adoption): the virtual row has no
|
|
109
145
|
* pricing of its own — the virtual `provider`/`model` arguments are
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"dsh",
|
|
6
6
|
"dsh-plugin"
|
|
7
7
|
],
|
|
8
|
-
"version": "0.
|
|
8
|
+
"version": "0.5.1",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"main": "./dist/index.js",
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
@@ -47,9 +47,10 @@
|
|
|
47
47
|
"build-client": "tsx scripts/build-client.ts",
|
|
48
48
|
"release:prepare": "tsx scripts/prepare-release.ts",
|
|
49
49
|
"release:validate": "tsx scripts/validate-release-version.ts",
|
|
50
|
-
"repair:
|
|
50
|
+
"repair:session-logs": "tsx scripts/repair-session-logs.ts",
|
|
51
51
|
"test": "vitest run",
|
|
52
|
-
"typecheck": "tsc -p tsconfig.build.json --noEmit && tsc --noEmit",
|
|
52
|
+
"typecheck": "tsc -p tsconfig.build.json --noEmit && tsc --noEmit && pnpm run typecheck:scripts",
|
|
53
|
+
"typecheck:scripts": "tsc -p tsconfig.scripts.json",
|
|
53
54
|
"prepare": "pnpm run build"
|
|
54
55
|
},
|
|
55
56
|
"engines": {
|
|
@@ -60,28 +61,29 @@
|
|
|
60
61
|
},
|
|
61
62
|
"peerDependencies": {
|
|
62
63
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
63
|
-
"@deepseek-ai/dsh-agent": "^0.1.5-rc.
|
|
64
|
-
"@deepseek-ai/dsh-api-gateway": "^0.1.5-rc.
|
|
65
|
-
"@deepseek-ai/dsh-api-remotes": "^0.1.5-rc.
|
|
66
|
-
"@deepseek-ai/dsh-api-session-controller": "^0.1.5-rc.
|
|
67
|
-
"@deepseek-ai/dsh-client-connection": "^0.1.5-rc.
|
|
68
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.5-rc.
|
|
69
|
-
"@deepseek-ai/dsh-client-store": "^0.1.5-rc.
|
|
70
|
-
"@deepseek-ai/dsh-client-ui-chat": "^0.1.5-rc.
|
|
71
|
-
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.
|
|
72
|
-
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.
|
|
73
|
-
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.
|
|
74
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.1.5-rc.
|
|
75
|
-
"@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.5-rc.
|
|
76
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.
|
|
77
|
-
"@deepseek-ai/dsh-commands": "^0.1.5-rc.
|
|
78
|
-
"@deepseek-ai/dsh-llm": "^0.1.5-rc.
|
|
79
|
-
"@deepseek-ai/dsh-llm-retry": "^0.1.5-rc.
|
|
80
|
-
"@deepseek-ai/dsh-session": "^0.1.5-rc.
|
|
81
|
-
"@deepseek-ai/dsh-
|
|
82
|
-
"@deepseek-ai/dsh-
|
|
83
|
-
"@deepseek-ai/dsh-
|
|
84
|
-
"@deepseek-ai/dsh-typert-
|
|
64
|
+
"@deepseek-ai/dsh-agent": "^0.1.5-rc.2",
|
|
65
|
+
"@deepseek-ai/dsh-api-gateway": "^0.1.5-rc.2",
|
|
66
|
+
"@deepseek-ai/dsh-api-remotes": "^0.1.5-rc.2",
|
|
67
|
+
"@deepseek-ai/dsh-api-session-controller": "^0.1.5-rc.2",
|
|
68
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.5-rc.2",
|
|
69
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.5-rc.2",
|
|
70
|
+
"@deepseek-ai/dsh-client-store": "^0.1.5-rc.2",
|
|
71
|
+
"@deepseek-ai/dsh-client-ui-chat": "^0.1.5-rc.2",
|
|
72
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.2",
|
|
73
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.2",
|
|
74
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.2",
|
|
75
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.5-rc.2",
|
|
76
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.5-rc.2",
|
|
77
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.2",
|
|
78
|
+
"@deepseek-ai/dsh-commands": "^0.1.5-rc.2",
|
|
79
|
+
"@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
|
|
80
|
+
"@deepseek-ai/dsh-llm-retry": "^0.1.5-rc.2",
|
|
81
|
+
"@deepseek-ai/dsh-session": "^0.1.5-rc.2",
|
|
82
|
+
"@deepseek-ai/dsh-session-projection": "^0.1.5-rc.2",
|
|
83
|
+
"@deepseek-ai/dsh-settings": "^0.1.5-rc.2",
|
|
84
|
+
"@deepseek-ai/dsh-tool-subagent": "^0.1.5-rc.2",
|
|
85
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.1.5-rc.2",
|
|
86
|
+
"@deepseek-ai/dsh-typert-registry": "^0.1.5-rc.2",
|
|
85
87
|
"@deepseek-ai/schemastery": "^3.18.2",
|
|
86
88
|
"react": "^18.2.0"
|
|
87
89
|
},
|
package/scripts/build-client.ts
CHANGED
|
@@ -48,7 +48,7 @@ const OUT_FILE = 'index.js'
|
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* Frozen loader module table (dsh mechanism-guide): the plan-documented
|
|
51
|
-
* composition = PLATFORM_MODULES (9 entries in 0.1.5-rc.
|
|
51
|
+
* composition = PLATFORM_MODULES (9 entries in 0.1.5-rc.2,
|
|
52
52
|
* `web/src/platform.ts:8-16` — the old runtime `./client` exemption became
|
|
53
53
|
* the `@deepseek-ai/dsh-client-store` platform module, so the store VALUE
|
|
54
54
|
* import stays external and resolves from the host loader table) + the
|