dsh-llm-fallbacks 0.1.3 → 0.1.5
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 +4 -2
- package/README.zh-CN.md +4 -2
- package/dist/client/fallbacks-store.d.ts +34 -2
- package/dist/client/index.js +464 -335
- package/dist/client/locales.d.ts +14 -4
- package/dist/gateway.d.ts +75 -23
- package/dist/index.d.ts +13 -3
- package/dist/index.js +243 -5
- package/dist/seeds.d.ts +141 -0
- package/package.json +2 -1
package/dist/client/locales.d.ts
CHANGED
|
@@ -35,6 +35,8 @@ export declare const zh: {
|
|
|
35
35
|
'alwaysModeRetryCap.hint': string;
|
|
36
36
|
'alwaysModeRetryCap.tooltip': string;
|
|
37
37
|
'advanced.label': string;
|
|
38
|
+
'advanced.expand': string;
|
|
39
|
+
'advanced.collapse': string;
|
|
38
40
|
'rootChain.label': string;
|
|
39
41
|
'rootChain.hint': string;
|
|
40
42
|
'rootChain.tooltip': string;
|
|
@@ -42,8 +44,7 @@ export declare const zh: {
|
|
|
42
44
|
'chains.selector.remove': string;
|
|
43
45
|
'chains.selector.providerPlaceholder': string;
|
|
44
46
|
'chains.selector.modelPlaceholder': string;
|
|
45
|
-
'chains.selector.
|
|
46
|
-
'chains.selector.noModels': string;
|
|
47
|
+
'chains.selector.wildcardLegacy': string;
|
|
47
48
|
'chains.selector.noModelsStrict': string;
|
|
48
49
|
'roles.list.label': string;
|
|
49
50
|
'roles.list.hint': string;
|
|
@@ -53,6 +54,10 @@ export declare const zh: {
|
|
|
53
54
|
'roles.idPlaceholder': string;
|
|
54
55
|
'roles.persona': string;
|
|
55
56
|
'roles.personaPlaceholder': string;
|
|
57
|
+
'roles.seedDefault': string;
|
|
58
|
+
'roles.seedOverride': string;
|
|
59
|
+
'roles.revertPersona': string;
|
|
60
|
+
'roles.seedChainOptional': string;
|
|
56
61
|
'roles.fallback': string;
|
|
57
62
|
'roles.fallback.inherit-root': string;
|
|
58
63
|
'roles.fallback.none': string;
|
|
@@ -166,6 +171,8 @@ export declare const en: {
|
|
|
166
171
|
'alwaysModeRetryCap.hint': string;
|
|
167
172
|
'alwaysModeRetryCap.tooltip': string;
|
|
168
173
|
'advanced.label': string;
|
|
174
|
+
'advanced.expand': string;
|
|
175
|
+
'advanced.collapse': string;
|
|
169
176
|
'rootChain.label': string;
|
|
170
177
|
'rootChain.hint': string;
|
|
171
178
|
'rootChain.tooltip': string;
|
|
@@ -173,8 +180,7 @@ export declare const en: {
|
|
|
173
180
|
'chains.selector.remove': string;
|
|
174
181
|
'chains.selector.providerPlaceholder': string;
|
|
175
182
|
'chains.selector.modelPlaceholder': string;
|
|
176
|
-
'chains.selector.
|
|
177
|
-
'chains.selector.noModels': string;
|
|
183
|
+
'chains.selector.wildcardLegacy': string;
|
|
178
184
|
'chains.selector.noModelsStrict': string;
|
|
179
185
|
'roles.list.label': string;
|
|
180
186
|
'roles.list.hint': string;
|
|
@@ -184,6 +190,10 @@ export declare const en: {
|
|
|
184
190
|
'roles.idPlaceholder': string;
|
|
185
191
|
'roles.persona': string;
|
|
186
192
|
'roles.personaPlaceholder': string;
|
|
193
|
+
'roles.seedDefault': string;
|
|
194
|
+
'roles.seedOverride': string;
|
|
195
|
+
'roles.revertPersona': string;
|
|
196
|
+
'roles.seedChainOptional': string;
|
|
187
197
|
'roles.fallback': string;
|
|
188
198
|
'roles.fallback.inherit-root': string;
|
|
189
199
|
'roles.fallback.none': string;
|
package/dist/gateway.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* T1 (plan llm-fallbacks-settings-gateway)
|
|
3
|
-
* gateway: the `/api/fallbacks/get` +
|
|
4
|
-
* `/api/fallbacks/reset`
|
|
2
|
+
* T1 (plan llm-fallbacks-settings-gateway) + T3 (plan fallbacks-role-seeds) —
|
|
3
|
+
* host-side `fallbacks` config gateway: the `/api/fallbacks/get` +
|
|
4
|
+
* `/api/fallbacks/set` + `/api/fallbacks/reset` + `/api/fallbacks/revert-seed`
|
|
5
|
+
* endpoints.
|
|
5
6
|
*
|
|
6
7
|
* Transport: the typertGateway `/api` interceptor is the single host-wide RPC
|
|
7
8
|
* slot (a plugin must NOT `connection.rpc.intercept('/api')` again — it would
|
|
@@ -37,7 +38,11 @@
|
|
|
37
38
|
* method — advisor has only get/set) clears the user layer via
|
|
38
39
|
* `ctx.settings.replace(ns, {})`: `set` is merge-only and cannot express
|
|
39
40
|
* "reset to composition defaults" (sending default VALUES as a patch would
|
|
40
|
-
* pin stale defaults into the user layer).
|
|
41
|
+
* pin stale defaults into the user layer). Every read response (get/set/reset)
|
|
42
|
+
* carries the additive `seeds: SeedsWireStatus[]` badge state (spec §9.4) and
|
|
43
|
+
* `revert-seed` exposes revert-to-current-seed-default for one id — both
|
|
44
|
+
* delegate to the per-apply `FallbacksSeedManager` passed into the
|
|
45
|
+
* constructor (single point of truth, no copied manager logic).
|
|
41
46
|
*
|
|
42
47
|
* The settings service is OPTIONAL (no settings service → the bridge source
|
|
43
48
|
* stays the entry, `get` still works; `set`/`reset` fail with a clear
|
|
@@ -57,6 +62,7 @@ import type { Context } from '@deepseek-ai/cordis';
|
|
|
57
62
|
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
58
63
|
import type { TypertContribution } from '@deepseek-ai/dsh-typert-registry';
|
|
59
64
|
import type { FallbacksConfig } from './config';
|
|
65
|
+
import type { FallbacksSeedManager, SeedRevertOutcome, SeedsWireStatus } from './seeds';
|
|
60
66
|
/** The `fallbacks` settings namespace (registered when a settings service exists). */
|
|
61
67
|
export declare const FALLBACKS_SETTINGS_NAMESPACE: import("@deepseek-ai/dsh-settings").SettingsNamespace;
|
|
62
68
|
/**
|
|
@@ -72,6 +78,20 @@ export interface FallbacksSettingsBridge {
|
|
|
72
78
|
}
|
|
73
79
|
/** Patch shape accepted by `fallbacks.set` — any subset of the config keys. */
|
|
74
80
|
export type FallbacksConfigPatch = Partial<FallbacksConfig>;
|
|
81
|
+
/**
|
|
82
|
+
* The wire response of every read (get/set/reset — W-1/F-1): the normalized
|
|
83
|
+
* config plus `legacyKeys` plus the additive `seeds` badge state (spec §9.4,
|
|
84
|
+
* legacyKeys precedent — old clients ignore it).
|
|
85
|
+
*/
|
|
86
|
+
export interface FallbacksReadResult {
|
|
87
|
+
config: FallbacksConfig;
|
|
88
|
+
legacyKeys: string[];
|
|
89
|
+
seeds: SeedsWireStatus[];
|
|
90
|
+
}
|
|
91
|
+
/** The `fallbacks/revert-seed` response — a read result plus the revert outcome. */
|
|
92
|
+
export interface FallbacksRevertResult extends FallbacksReadResult {
|
|
93
|
+
outcome: SeedRevertOutcome;
|
|
94
|
+
}
|
|
75
95
|
/**
|
|
76
96
|
* The host-side `fallbacks` config gateway (`/api/fallbacks/get` +
|
|
77
97
|
* `/api/fallbacks/set` + `/api/fallbacks/reset`). Registered as the cordis
|
|
@@ -86,14 +106,19 @@ export type FallbacksConfigPatch = Partial<FallbacksConfig>;
|
|
|
86
106
|
*/
|
|
87
107
|
export declare class FallbacksConfigGateway extends TypertRemoteService {
|
|
88
108
|
private readonly bridge;
|
|
109
|
+
/** The per-apply seed manager — single point of truth for seed state (spec §9.4). */
|
|
110
|
+
private readonly seeds;
|
|
89
111
|
/** The live settings service once the optional inject child activates. */
|
|
90
112
|
private settings;
|
|
91
113
|
/**
|
|
92
114
|
* @param ctx - owning context (the plugin fiber's ctx inside `apply`).
|
|
93
115
|
* @param bridge - the same `FallbacksSettingsBridge` the runtime reads, so
|
|
94
116
|
* get/set/reset always operate on the live composed config.
|
|
117
|
+
* @param seeds - the per-apply `FallbacksSeedManager` constructed in
|
|
118
|
+
* `apply()` — the gateway delegates badge state and revert to it (no
|
|
119
|
+
* copied manager logic), through the io seam built over this bridge.
|
|
95
120
|
*/
|
|
96
|
-
constructor(ctx: Context, bridge: FallbacksSettingsBridge);
|
|
121
|
+
constructor(ctx: Context, bridge: FallbacksSettingsBridge, seeds: FallbacksSeedManager);
|
|
97
122
|
/**
|
|
98
123
|
* Read the current composed config (schema defaults → entry base → settings
|
|
99
124
|
* user layer). No hard-gate resolver (ADR-2): the raw composed config is
|
|
@@ -102,12 +127,11 @@ export declare class FallbacksConfigGateway extends TypertRemoteService {
|
|
|
102
127
|
* two-block-era fields (`chains` / `roles.default` / undeclared rule
|
|
103
128
|
* role refs) detected on the composed source (schemastery retains them,
|
|
104
129
|
* plan Task 1 Step 1), so the client can show a migration banner (spec
|
|
105
|
-
* §9, incremental field — old clients ignore it)
|
|
130
|
+
* §9, incremental field — old clients ignore it) — plus the additive
|
|
131
|
+
* `seeds` badge state (spec §9.4, legacyKeys precedent — old clients
|
|
132
|
+
* ignore it).
|
|
106
133
|
*/
|
|
107
|
-
get():
|
|
108
|
-
config: FallbacksConfig;
|
|
109
|
-
legacyKeys: string[];
|
|
110
|
-
};
|
|
134
|
+
get(): FallbacksReadResult;
|
|
111
135
|
/**
|
|
112
136
|
* Validate a config patch and write it to the settings USER layer (live —
|
|
113
137
|
* the runtime re-reads the same bridge source; no restart needed).
|
|
@@ -117,28 +141,42 @@ export declare class FallbacksConfigGateway extends TypertRemoteService {
|
|
|
117
141
|
* POST-WRITE composed source (W-1/F-1): `set` is a settings MERGE, so a
|
|
118
142
|
* legacy user layer (`chains` / `roles.default`) survives a new-shape
|
|
119
143
|
* save — the response must keep reporting it, or the client banner
|
|
120
|
-
* would clear against server truth
|
|
144
|
+
* would clear against server truth — plus the post-write `seeds` badge
|
|
145
|
+
* state (same W-1/F-1 rule as `legacyKeys`). Same shape as `get`.
|
|
121
146
|
* @throws when the patch fails `Config` validation, or when no settings
|
|
122
147
|
* service is composed (KD-G5: the write channel is unavailable).
|
|
123
148
|
*/
|
|
124
|
-
set(patch: FallbacksConfigPatch): Promise<
|
|
125
|
-
config: FallbacksConfig;
|
|
126
|
-
legacyKeys: string[];
|
|
127
|
-
}>;
|
|
149
|
+
set(patch: FallbacksConfigPatch): Promise<FallbacksReadResult>;
|
|
128
150
|
/**
|
|
129
151
|
* Clear the fallbacks settings USER layer so the composition defaults
|
|
130
152
|
* reapply (`settings.replace(ns, {})` — the in-process removal path a
|
|
131
153
|
* merge-only `set` cannot express).
|
|
132
154
|
* @returns the new composed config plus `legacyKeys` on the post-write
|
|
133
155
|
* source — `replace` drops the user layer, but legacy keys carried by
|
|
134
|
-
* the entry base survive and are correctly re-reported (W-1/F-1)
|
|
156
|
+
* the entry base survive and are correctly re-reported (W-1/F-1) — plus
|
|
157
|
+
* the post-write `seeds` badge state (clearing the user layer also
|
|
158
|
+
* clears the materialized seed rows, so the honest response reports the
|
|
159
|
+
* emptied state; the registry survives and the next declare re-materializes).
|
|
135
160
|
* @throws when no settings service is composed (KD-G5: the write channel
|
|
136
161
|
* is unavailable).
|
|
137
162
|
*/
|
|
138
|
-
reset(): Promise<
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
163
|
+
reset(): Promise<FallbacksReadResult>;
|
|
164
|
+
/**
|
|
165
|
+
* Revert one seeded role to its CURRENT declared seed default (AC-3, spec
|
|
166
|
+
* §9.4) — the gateway half of surface (c), delegating to the same manager
|
|
167
|
+
* the service method uses (single point of truth). Business failures are
|
|
168
|
+
* values, never throws: a non-seeded id or a deleted row returns
|
|
169
|
+
* `{ reverted: false, reason }` without writing. The only throw is the
|
|
170
|
+
* KD-G5 settings-unavailable path, and only when a write is actually
|
|
171
|
+
* needed (an idempotent revert at the default needs no channel).
|
|
172
|
+
* @param id - the seeded role id; matched by trimmed id against the
|
|
173
|
+
* registry (row matching, spec §9.3).
|
|
174
|
+
* @returns the post-write read result (config / legacyKeys / seeds — the
|
|
175
|
+
* write happened before this read, W-1/F-1) plus the revert `outcome`.
|
|
176
|
+
* @throws TypeError when `id` is not a string; Error when the settings
|
|
177
|
+
* write channel is unavailable (KD-G5).
|
|
178
|
+
*/
|
|
179
|
+
revertSeed(id: string): Promise<FallbacksRevertResult>;
|
|
142
180
|
/**
|
|
143
181
|
* Read the live composed config and normalize it to the typertGateway JSON
|
|
144
182
|
* wire boundary. Containment (guide §10): a malformed stored user layer
|
|
@@ -156,11 +194,25 @@ export declare class FallbacksConfigGateway extends TypertRemoteService {
|
|
|
156
194
|
/**
|
|
157
195
|
* The wire response of every read (get/set/reset — W-1/F-1): the
|
|
158
196
|
* normalized config plus `legacyKeys` detected on the live composed
|
|
159
|
-
* source. set/reset must report the
|
|
160
|
-
* merge retains legacy user-layer keys,
|
|
161
|
-
* the honest response keeps the migration
|
|
197
|
+
* source plus the additive `seeds` badge state. set/reset must report the
|
|
198
|
+
* POST-WRITE source: the settings merge retains legacy user-layer keys,
|
|
199
|
+
* so a save cannot clear them — the honest response keeps the migration
|
|
200
|
+
* banner until a get agrees; `seeds` follows the same rule (post-write
|
|
201
|
+
* badge state, W-1/F-1).
|
|
162
202
|
*/
|
|
163
203
|
private readResult;
|
|
204
|
+
/**
|
|
205
|
+
* The io seam the seed manager writes through (spec §9.1): `read` walks
|
|
206
|
+
* the same live bridge source the gateway reads; `writeRoles` persists a
|
|
207
|
+
* full `{ list, rules }` to the settings user layer — both arrays always
|
|
208
|
+
* computed from a fresh composed read, so the write stays correct under
|
|
209
|
+
* dsh-settings `mergeLayers` array-replace semantics and never touches
|
|
210
|
+
* operator rules. The write channel fails with the same KD-G5 message as
|
|
211
|
+
* set/reset when no settings service is composed. Built fresh per call so
|
|
212
|
+
* the mutable settings capture is read at call time (the inject child
|
|
213
|
+
* swaps it when the settings service appears/disappears).
|
|
214
|
+
*/
|
|
215
|
+
private seedsIo;
|
|
164
216
|
}
|
|
165
217
|
/**
|
|
166
218
|
* The explicit typert contribution for the `fallbacks` gateway endpoints —
|
package/dist/index.d.ts
CHANGED
|
@@ -34,6 +34,7 @@ import { Config } from './schema.ts';
|
|
|
34
34
|
import { resolveChain } from './chains.ts';
|
|
35
35
|
import { resolveRole } from './roles.ts';
|
|
36
36
|
import { FallbackStateStore } from './state.ts';
|
|
37
|
+
import { type EffectiveRolesReadback, type SeedDeclaration, type SeedDeclareOutcome, type SeedRevertOutcome } from './seeds.ts';
|
|
37
38
|
/** The plugin row id mounted by the profile bundle patch. */
|
|
38
39
|
export declare const name = "llm-fallbacks";
|
|
39
40
|
/**
|
|
@@ -46,9 +47,11 @@ export declare const provide: readonly ["llm-fallbacks"];
|
|
|
46
47
|
/**
|
|
47
48
|
* The named cordis service `ctx.get('llm-fallbacks')` exposes while the
|
|
48
49
|
* plugin is applied: the pure-function library surface + `name`/`version`
|
|
49
|
-
* metadata
|
|
50
|
-
*
|
|
51
|
-
*
|
|
50
|
+
* metadata, plus the three ADDITIVE role-seed methods (spec §9.1, plan
|
|
51
|
+
* fallbacks-role-seeds T2). Deliberately no state BEARING FIELDS (no
|
|
52
|
+
* stateStore, no event emitters, no filter helpers) — the seed methods are
|
|
53
|
+
* closures over the per-apply `FallbacksSeedManager`, so state stays behind
|
|
54
|
+
* the closure and dies with the fiber (spec §9.5).
|
|
52
55
|
*/
|
|
53
56
|
export interface FallbacksService {
|
|
54
57
|
/** Matches the plugin `name`. */
|
|
@@ -59,6 +62,12 @@ export interface FallbacksService {
|
|
|
59
62
|
resolveChain: typeof resolveChain;
|
|
60
63
|
validateFallbacksConfig: typeof validateFallbacksConfig;
|
|
61
64
|
detectLegacyKeys: typeof detectLegacyKeys;
|
|
65
|
+
/** (a) Declare the companion's FULL current seed set (replacement semantics, spec §9.1). */
|
|
66
|
+
declareSeeds(seeds: readonly SeedDeclaration[]): Promise<SeedDeclareOutcome>;
|
|
67
|
+
/** (b) Sync readback — effective taxonomy with seed annotations. */
|
|
68
|
+
getEffectiveRoles(): EffectiveRolesReadback;
|
|
69
|
+
/** (c) Revert one id to the CURRENT declared seed default. */
|
|
70
|
+
revertSeededPersona(id: string): Promise<SeedRevertOutcome>;
|
|
62
71
|
}
|
|
63
72
|
declare module '@deepseek-ai/cordis' {
|
|
64
73
|
interface Context {
|
|
@@ -75,6 +84,7 @@ export { resolveRole, type AgentLike, type Origin, } from './roles.ts';
|
|
|
75
84
|
export { annotateCandidates, createCandidateFilter, hasWildcardEntry, resolveCandidate, resolveChain, resolveChainViews, selectCandidates, type AnnotatedCandidate, type CandidateFilterOptions, type CandidateSkipReason, type FailingModel, } from './chains.ts';
|
|
76
85
|
export { defaultFallbacksConfig, detectLegacyKeys, INHERIT_ROLE_ID, ROLE_ID_PATTERN, validateFallbacksConfig, type FallbacksConfig, type FallbacksConfigLogger, type FallbacksRole, type FallbacksRoles, type FallbacksRoleRule, type FallbackStrategy, type RevertPolicy, } from './config.ts';
|
|
77
86
|
export { parseSelector, SelectorError, type Selector, } from './selectors.ts';
|
|
87
|
+
export { FallbacksSeedManager, type EffectiveRole, type EffectiveRolesReadback, type SeedConflict, type SeedDeclaration, type SeedDeclareOutcome, type SeedRevertFailReason, type SeedRevertOutcome, type SeedSkipReason, type SeedsIo, type SeedsWireStatus, } from './seeds.ts';
|
|
78
88
|
/**
|
|
79
89
|
* @internal Test seam (T3 review Minor 3): the per-agent fallback state store
|
|
80
90
|
* of the plugin applied to `ctx` — last apply wins. Not part of the plugin's
|
package/dist/index.js
CHANGED
|
@@ -403,10 +403,12 @@ const ROLES_KEYS = {
|
|
|
403
403
|
};
|
|
404
404
|
var FallbacksConfigGateway = class extends TypertRemoteService {
|
|
405
405
|
bridge;
|
|
406
|
+
seeds;
|
|
406
407
|
settings;
|
|
407
|
-
constructor(ctx, bridge) {
|
|
408
|
+
constructor(ctx, bridge, seeds) {
|
|
408
409
|
super(ctx, "fallbacks");
|
|
409
410
|
this.bridge = bridge;
|
|
411
|
+
this.seeds = seeds;
|
|
410
412
|
ctx.inject(["settings"], (sctx) => {
|
|
411
413
|
this.settings = sctx.settings;
|
|
412
414
|
return () => {
|
|
@@ -433,6 +435,14 @@ var FallbacksConfigGateway = class extends TypertRemoteService {
|
|
|
433
435
|
await settings.replace(FALLBACKS_SETTINGS_NAMESPACE, {});
|
|
434
436
|
return this.readResult();
|
|
435
437
|
}
|
|
438
|
+
async revertSeed(id) {
|
|
439
|
+
if (typeof id !== "string") throw new TypeError("dsh-llm-fallbacks: seed revert id must be a string");
|
|
440
|
+
const outcome = await this.seeds.revert(id, this.seedsIo());
|
|
441
|
+
return {
|
|
442
|
+
...this.readResult(),
|
|
443
|
+
outcome
|
|
444
|
+
};
|
|
445
|
+
}
|
|
436
446
|
readConfig(source = this.bridge.source()) {
|
|
437
447
|
const wire = {};
|
|
438
448
|
for (const key of Object.keys(CONFIG_KEYS)) {
|
|
@@ -446,7 +456,18 @@ var FallbacksConfigGateway = class extends TypertRemoteService {
|
|
|
446
456
|
const source = this.bridge.source();
|
|
447
457
|
return {
|
|
448
458
|
config: this.readConfig(source),
|
|
449
|
-
legacyKeys: detectLegacyKeys(source)
|
|
459
|
+
legacyKeys: detectLegacyKeys(source),
|
|
460
|
+
seeds: this.seeds.wireStatus(this.seedsIo())
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
seedsIo() {
|
|
464
|
+
const settings = this.settings;
|
|
465
|
+
return {
|
|
466
|
+
read: () => this.bridge.source(),
|
|
467
|
+
writeRoles: (roles) => {
|
|
468
|
+
if (settings === void 0) throw new Error("fallbacks: settings service is unavailable — configuration cannot be written");
|
|
469
|
+
return settings.update(FALLBACKS_SETTINGS_NAMESPACE, { roles });
|
|
470
|
+
}
|
|
450
471
|
};
|
|
451
472
|
}
|
|
452
473
|
};
|
|
@@ -514,6 +535,21 @@ function fallbacksTypertContribution() {
|
|
|
514
535
|
invocation: { kind: "direct" },
|
|
515
536
|
parameters: [],
|
|
516
537
|
result: { mode: "src-json" }
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
id: "dsh-llm-fallbacks#fallbacks/revert-seed",
|
|
541
|
+
service: "fallbacks",
|
|
542
|
+
namespace: "fallbacks",
|
|
543
|
+
method: "revert-seed",
|
|
544
|
+
implementation: "revertSeed",
|
|
545
|
+
invocation: { kind: "direct" },
|
|
546
|
+
parameters: [{
|
|
547
|
+
name: "id",
|
|
548
|
+
wire: "id",
|
|
549
|
+
source: "json",
|
|
550
|
+
codec: { mode: "src-json" }
|
|
551
|
+
}],
|
|
552
|
+
result: { mode: "src-json" }
|
|
517
553
|
}
|
|
518
554
|
]
|
|
519
555
|
};
|
|
@@ -644,6 +680,188 @@ function registerFallbacksCommands(registry, controller, locale = "zh") {
|
|
|
644
680
|
});
|
|
645
681
|
}
|
|
646
682
|
//#endregion
|
|
683
|
+
//#region .build/host/seeds.js
|
|
684
|
+
var FallbacksSeedManager = class {
|
|
685
|
+
logger;
|
|
686
|
+
registry = new Map();
|
|
687
|
+
constructor(logger) {
|
|
688
|
+
this.logger = logger;
|
|
689
|
+
}
|
|
690
|
+
async declare(seeds, io) {
|
|
691
|
+
const outcome = {
|
|
692
|
+
applied: [],
|
|
693
|
+
skipped: [],
|
|
694
|
+
conflicts: []
|
|
695
|
+
};
|
|
696
|
+
const registry = new Map();
|
|
697
|
+
for (const seed of seeds) {
|
|
698
|
+
if (typeof seed.id !== "string" || !ROLE_ID_PATTERN.test(seed.id)) {
|
|
699
|
+
outcome.skipped.push({
|
|
700
|
+
id: String(seed.id),
|
|
701
|
+
reason: "invalid-id"
|
|
702
|
+
});
|
|
703
|
+
this.warnSkip(seed.id, "invalid-id");
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
if (seed.id === "inherit") {
|
|
707
|
+
outcome.skipped.push({
|
|
708
|
+
id: seed.id,
|
|
709
|
+
reason: "reserved-id"
|
|
710
|
+
});
|
|
711
|
+
this.warnSkip(seed.id, "reserved-id");
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
if (registry.has(seed.id)) {
|
|
715
|
+
outcome.skipped.push({
|
|
716
|
+
id: seed.id,
|
|
717
|
+
reason: "duplicate-in-batch"
|
|
718
|
+
});
|
|
719
|
+
this.warnSkip(seed.id, "duplicate-in-batch");
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
registry.set(seed.id, seed.persona);
|
|
723
|
+
outcome.applied.push(seed.id);
|
|
724
|
+
}
|
|
725
|
+
const config = io.read();
|
|
726
|
+
const currentList = roleRows(config);
|
|
727
|
+
const currentRules = roleRules(config);
|
|
728
|
+
const newList = materialize(currentList, registry, this.registry, outcome.conflicts);
|
|
729
|
+
for (const conflict of outcome.conflicts) this.logger.warn(`llm-fallbacks: seeds: persona-source conflict for seed id ${JSON.stringify(conflict.id)} — operator row persona kept (never overwritten)`);
|
|
730
|
+
const computed = {
|
|
731
|
+
list: newList,
|
|
732
|
+
rules: currentRules
|
|
733
|
+
};
|
|
734
|
+
if (!deepEqual(newList, currentList)) await io.writeRoles(computed);
|
|
735
|
+
this.registry = registry;
|
|
736
|
+
return outcome;
|
|
737
|
+
}
|
|
738
|
+
effectiveRoles(io) {
|
|
739
|
+
return { roles: roleRows(io.read()).map((row) => {
|
|
740
|
+
const seedPersona = this.registry.get(row.id.trim());
|
|
741
|
+
const seeded = seedPersona !== void 0;
|
|
742
|
+
const effective = {
|
|
743
|
+
id: row.id,
|
|
744
|
+
persona: row.persona,
|
|
745
|
+
seeded,
|
|
746
|
+
personaOverridden: seeded && row.persona !== seedPersona
|
|
747
|
+
};
|
|
748
|
+
if (seeded) effective.seedPersona = seedPersona;
|
|
749
|
+
if (row.chain !== void 0) effective.chain = row.chain;
|
|
750
|
+
if (row.fallback !== void 0) effective.fallback = row.fallback;
|
|
751
|
+
return effective;
|
|
752
|
+
}) };
|
|
753
|
+
}
|
|
754
|
+
wireStatus(io) {
|
|
755
|
+
const status = [];
|
|
756
|
+
for (const row of roleRows(io.read())) {
|
|
757
|
+
const seedPersona = this.registry.get(row.id.trim());
|
|
758
|
+
if (seedPersona === void 0) continue;
|
|
759
|
+
status.push({
|
|
760
|
+
id: row.id,
|
|
761
|
+
overridden: row.persona !== seedPersona
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
return status;
|
|
765
|
+
}
|
|
766
|
+
async revert(id, io) {
|
|
767
|
+
const seedId = id.trim();
|
|
768
|
+
const seedPersona = this.registry.get(seedId);
|
|
769
|
+
if (seedPersona === void 0) return {
|
|
770
|
+
reverted: false,
|
|
771
|
+
reason: "not-seeded"
|
|
772
|
+
};
|
|
773
|
+
const config = io.read();
|
|
774
|
+
const rows = roleRows(config);
|
|
775
|
+
const rules = roleRules(config);
|
|
776
|
+
const index = rows.findIndex((row) => row.id.trim() === seedId);
|
|
777
|
+
if (index === -1) return {
|
|
778
|
+
reverted: false,
|
|
779
|
+
reason: "row-absent"
|
|
780
|
+
};
|
|
781
|
+
if (rows[index].persona === seedPersona) return {
|
|
782
|
+
reverted: true,
|
|
783
|
+
persona: seedPersona
|
|
784
|
+
};
|
|
785
|
+
const nextList = rows.map((row, i) => i === index ? {
|
|
786
|
+
...row,
|
|
787
|
+
persona: seedPersona
|
|
788
|
+
} : row);
|
|
789
|
+
await io.writeRoles({
|
|
790
|
+
list: nextList,
|
|
791
|
+
rules
|
|
792
|
+
});
|
|
793
|
+
return {
|
|
794
|
+
reverted: true,
|
|
795
|
+
persona: seedPersona
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
warnSkip(id, reason) {
|
|
799
|
+
const shown = typeof id === "string" ? JSON.stringify(id) : String(id);
|
|
800
|
+
if (reason === "invalid-id") this.logger.warn(`llm-fallbacks: seeds: skipping seed id ${shown} — invalid-id (must match ${String(ROLE_ID_PATTERN)} as declared)`);
|
|
801
|
+
else if (reason === "reserved-id") this.logger.warn(`llm-fallbacks: seeds: skipping seed id ${shown} — reserved-id ("${INHERIT_ROLE_ID}" is not a legal seed target)`);
|
|
802
|
+
else this.logger.warn(`llm-fallbacks: seeds: skipping seed id ${shown} — duplicate-in-batch (first wins)`);
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
function materialize(rows, registry, previous, conflicts) {
|
|
806
|
+
const next = [];
|
|
807
|
+
for (const row of rows) {
|
|
808
|
+
const seedId = row.id.trim();
|
|
809
|
+
const incoming = registry.get(seedId);
|
|
810
|
+
if (incoming === void 0) {
|
|
811
|
+
next.push(row);
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
const prior = previous.get(seedId);
|
|
815
|
+
if (prior === void 0) {
|
|
816
|
+
if (row.persona !== incoming) conflicts.push({
|
|
817
|
+
id: seedId,
|
|
818
|
+
kind: "persona-source"
|
|
819
|
+
});
|
|
820
|
+
next.push(row);
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
if (row.persona === prior) {
|
|
824
|
+
next.push({
|
|
825
|
+
...row,
|
|
826
|
+
persona: incoming
|
|
827
|
+
});
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
if (row.persona !== incoming) conflicts.push({
|
|
831
|
+
id: seedId,
|
|
832
|
+
kind: "persona-source"
|
|
833
|
+
});
|
|
834
|
+
next.push(row);
|
|
835
|
+
}
|
|
836
|
+
for (const [id, persona] of registry) if (!rows.some((row) => row.id.trim() === id)) next.push({
|
|
837
|
+
id,
|
|
838
|
+
persona
|
|
839
|
+
});
|
|
840
|
+
return next;
|
|
841
|
+
}
|
|
842
|
+
function roleRows(config) {
|
|
843
|
+
const list = config.roles?.list;
|
|
844
|
+
return Array.isArray(list) ? list : [];
|
|
845
|
+
}
|
|
846
|
+
function roleRules(config) {
|
|
847
|
+
const rules = config.roles?.rules;
|
|
848
|
+
return Array.isArray(rules) ? rules : [];
|
|
849
|
+
}
|
|
850
|
+
function deepEqual(a, b) {
|
|
851
|
+
if (a === b) return true;
|
|
852
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
853
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
854
|
+
if (Array.isArray(a)) {
|
|
855
|
+
const aa = a;
|
|
856
|
+
const bb = b;
|
|
857
|
+
return aa.length === bb.length && aa.every((item, index) => deepEqual(item, bb[index]));
|
|
858
|
+
}
|
|
859
|
+
const aKeys = Object.keys(a);
|
|
860
|
+
const bKeys = Object.keys(b);
|
|
861
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
862
|
+
return aKeys.every((key) => deepEqual(a[key], b[key]));
|
|
863
|
+
}
|
|
864
|
+
//#endregion
|
|
647
865
|
//#region .build/host/index.js
|
|
648
866
|
const name = "llm-fallbacks";
|
|
649
867
|
const provide = ["llm-fallbacks"];
|
|
@@ -695,6 +913,23 @@ function overrideConfig(seed, to) {
|
|
|
695
913
|
function apply(ctx, config = defaultFallbacksConfig) {
|
|
696
914
|
const logger = ctx.logger("llm-fallbacks");
|
|
697
915
|
const entry = Config(config);
|
|
916
|
+
const seeds = new FallbacksSeedManager(logger);
|
|
917
|
+
const seedsSettingsUnavailable = "llm-fallbacks: seeds: settings service is unavailable — seed roles cannot be written";
|
|
918
|
+
let writeRoles = () => {
|
|
919
|
+
throw new Error(seedsSettingsUnavailable);
|
|
920
|
+
};
|
|
921
|
+
ctx.inject(["settings"], (sctx) => {
|
|
922
|
+
writeRoles = (roles) => sctx.settings.update(FALLBACKS_SETTINGS_NAMESPACE, { roles });
|
|
923
|
+
return () => {
|
|
924
|
+
writeRoles = () => {
|
|
925
|
+
throw new Error(seedsSettingsUnavailable);
|
|
926
|
+
};
|
|
927
|
+
};
|
|
928
|
+
});
|
|
929
|
+
const seedsIo = {
|
|
930
|
+
read: () => source(),
|
|
931
|
+
writeRoles: (roles) => writeRoles(roles)
|
|
932
|
+
};
|
|
698
933
|
try {
|
|
699
934
|
ctx.provide("llm-fallbacks", {
|
|
700
935
|
name: "llm-fallbacks",
|
|
@@ -702,7 +937,10 @@ function apply(ctx, config = defaultFallbacksConfig) {
|
|
|
702
937
|
resolveRole,
|
|
703
938
|
resolveChain,
|
|
704
939
|
validateFallbacksConfig,
|
|
705
|
-
detectLegacyKeys
|
|
940
|
+
detectLegacyKeys,
|
|
941
|
+
declareSeeds: (declarations) => seeds.declare(declarations, seedsIo),
|
|
942
|
+
getEffectiveRoles: () => seeds.effectiveRoles(seedsIo),
|
|
943
|
+
revertSeededPersona: (id) => seeds.revert(id, seedsIo)
|
|
706
944
|
});
|
|
707
945
|
} catch (error) {
|
|
708
946
|
if (!(error instanceof Error) || !error.message.includes("has been registered")) throw error;
|
|
@@ -726,7 +964,7 @@ function apply(ctx, config = defaultFallbacksConfig) {
|
|
|
726
964
|
});
|
|
727
965
|
const bridge = { source: () => source() };
|
|
728
966
|
try {
|
|
729
|
-
new FallbacksConfigGateway(ctx, bridge);
|
|
967
|
+
new FallbacksConfigGateway(ctx, bridge, seeds);
|
|
730
968
|
} catch (error) {
|
|
731
969
|
if (!(error instanceof Error) || !error.message.includes("has been registered")) throw error;
|
|
732
970
|
ctx.logger("llm-fallbacks").debug("fallbacks gateway already registered — no gateway on this fiber (multi-fiber dedupe)");
|
|
@@ -861,4 +1099,4 @@ function apply(ctx, config = defaultFallbacksConfig) {
|
|
|
861
1099
|
});
|
|
862
1100
|
}
|
|
863
1101
|
//#endregion
|
|
864
|
-
export { Config, INHERIT_ROLE_ID, ROLE_ID_PATTERN, SelectorError, annotateCandidates, apply, countRetryEvents, createCandidateFilter, defaultFallbacksConfig, detectLegacyKeys, hasWildcardEntry, name, parseSelector, provide, resolveCandidate, resolveChain, resolveChainViews, resolveRole, selectCandidates, stateStore, validateFallbacksConfig };
|
|
1102
|
+
export { Config, FallbacksSeedManager, INHERIT_ROLE_ID, ROLE_ID_PATTERN, SelectorError, annotateCandidates, apply, countRetryEvents, createCandidateFilter, defaultFallbacksConfig, detectLegacyKeys, hasWildcardEntry, name, parseSelector, provide, resolveCandidate, resolveChain, resolveChainViews, resolveRole, selectCandidates, stateStore, validateFallbacksConfig };
|