dsh-llm-fallbacks 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +124 -0
- package/README.zh-CN.md +119 -0
- package/bundle/cordis.patch.yml +19 -0
- package/dist/chains.d.ts +167 -0
- package/dist/client/ConversationFallbackSwitch.d.ts +87 -0
- package/dist/client/FallbacksCard.d.ts +86 -0
- package/dist/client/GeneralFallbacksRow.d.ts +19 -0
- package/dist/client/fallbacks-store.d.ts +379 -0
- package/dist/client/index.d.ts +56 -0
- package/dist/client/index.js +3916 -0
- package/dist/client/locales.d.ts +281 -0
- package/dist/client/switch-guard.d.ts +17 -0
- package/dist/commands.d.ts +176 -0
- package/dist/config.d.ts +136 -0
- package/dist/cooldown.d.ts +48 -0
- package/dist/events.d.ts +36 -0
- package/dist/gateway.d.ts +176 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.js +1853 -0
- package/dist/roles.d.ts +70 -0
- package/dist/selectors.d.ts +35 -0
- package/dist/state.d.ts +112 -0
- package/package.json +91 -0
- package/scripts/build-client.ts +374 -0
- package/scripts/verify-dist.mjs +68 -0
package/dist/roles.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Role resolution for fallback chains (spec §7.1; plan fallbacks-role-runtime
|
|
3
|
+
* Task 1).
|
|
4
|
+
*
|
|
5
|
+
* Precedence (first hit wins):
|
|
6
|
+
* 1. the first `rules` entry whose specified origin/provider/model patterns
|
|
7
|
+
* all match the agent;
|
|
8
|
+
* 2. the built-in `'inherit'` role (no-rule-match default, spec §7.1 / D4).
|
|
9
|
+
*
|
|
10
|
+
* A matched rule must target a declared role id (`roleIds`, derived from
|
|
11
|
+
* `config.roles.list`) or the built-in `'inherit'` — an undeclared target
|
|
12
|
+
* warns and falls back to `'inherit'` (defensive; startup validation already
|
|
13
|
+
* flagged the reference, spec §7.1 / AC-4).
|
|
14
|
+
*
|
|
15
|
+
* Role ids are canonicalized by trim (qc2 F-001, client-canonical trim
|
|
16
|
+
* alignment — `validateFallbacksConfig` trims ids and rule references, so
|
|
17
|
+
* the runtime must too): `roleIds` maps a TRIMMED id to the DECLARED RAW
|
|
18
|
+
* id, membership compares `rule.role.trim()`, and a matched rule returns
|
|
19
|
+
* the declared raw id — so roleDef lookups in `chains.ts` / `commands.ts`
|
|
20
|
+
* match the stored `roles.list[].id` exactly and a padded YAML id
|
|
21
|
+
* (`' coder '`) with a trimmed rule reference (`'coder'`) resolves to the
|
|
22
|
+
* same role instead of silently degrading to `'inherit'`.
|
|
23
|
+
*
|
|
24
|
+
* A missing agent origin is treated as `'root'`. Origin is read from
|
|
25
|
+
* `session.header.origin` — a native `SessionHeader` field the store folds
|
|
26
|
+
* from `CreateSessionOptions.meta.origin` (`packages/core/session/src/
|
|
27
|
+
* index.ts:884`); subagent children set it via `childSessionMeta`
|
|
28
|
+
* (`packages/subagent/subagent/src/child-agent.ts:115`), root agents carry
|
|
29
|
+
* none. A subagent `persona` is NOT readable at the decision point:
|
|
30
|
+
* `AgentOptions` is provider/model/maxTokens only, and persona is installed
|
|
31
|
+
* as a scoped system-prompt section in the child's creation window — see
|
|
32
|
+
* `.mstar/iterations/iter-20260811-fallbacks-mount-only/guides/
|
|
33
|
+
* role-and-model-selection-exploration.md` (Role section).
|
|
34
|
+
*
|
|
35
|
+
* @module dsh-llm-fallbacks/roles
|
|
36
|
+
*/
|
|
37
|
+
import { type FallbacksRoleRule } from './config.ts';
|
|
38
|
+
export type { FallbacksRoleRule } from './config.ts';
|
|
39
|
+
/** Agent origins understood by role rules (spec §3). */
|
|
40
|
+
export type Origin = 'root' | 'subagent';
|
|
41
|
+
/** Loose agent shape sufficient for role resolution (spec §3 / brief). */
|
|
42
|
+
export interface AgentLike {
|
|
43
|
+
options?: {
|
|
44
|
+
provider?: string;
|
|
45
|
+
model?: string;
|
|
46
|
+
};
|
|
47
|
+
session?: {
|
|
48
|
+
header?: {
|
|
49
|
+
origin?: Origin;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolve the fallback-chain role for an agent: first matching rule (in
|
|
55
|
+
* listed order) → the built-in `'inherit'` role. A rule targeting an id
|
|
56
|
+
* outside the declared-id set ∪ {'inherit'} warns once and resolves to
|
|
57
|
+
* `'inherit'` (the defensive path — `validateFallbacksConfig` already
|
|
58
|
+
* warned at startup, spec §7.1).
|
|
59
|
+
*
|
|
60
|
+
* `roleIds` is the canonical trimmed-id map built by the wiring
|
|
61
|
+
* (`trimmed id → declared raw id`, see `src/index.ts` apply()): a matched
|
|
62
|
+
* rule returns the declared RAW id so the roleDef lookups in
|
|
63
|
+
* `chains.ts` / `commands.ts` find the stored `roles.list[]` entry exactly
|
|
64
|
+
* (qc2 F-001 — no silent rule inertness on padded YAML ids).
|
|
65
|
+
*
|
|
66
|
+
* `warn` defaults to `console.warn` (direct-call compatibility); the
|
|
67
|
+
* decision path injects the cordis `logger.warn` so every plugin warning
|
|
68
|
+
* flows through the plugin log namespace (qc2 F-002 / qc3 S-3).
|
|
69
|
+
*/
|
|
70
|
+
export declare function resolveRole(agent: AgentLike, rules: FallbacksRoleRule[], roleIds: ReadonlyMap<string, string>, warn?: (message: string) => void): string;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Selector parsing for `fallbacks` chains (spec §4, plan Task 2).
|
|
3
|
+
*
|
|
4
|
+
* Grammar: `provider/model` (exact) and `provider/*` (wildcard — the parsed
|
|
5
|
+
* `model` is `undefined`). Illegal selectors throw {@link SelectorError} —
|
|
6
|
+
* the catchable "config warning" path; warn-and-continue lives in Task 3.
|
|
7
|
+
* These modules never crash on their own.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-llm-fallbacks/selectors
|
|
10
|
+
*/
|
|
11
|
+
/** A parsed selector: `provider` + optional `model` (`undefined` = wildcard). */
|
|
12
|
+
export interface Selector {
|
|
13
|
+
provider: string;
|
|
14
|
+
model?: string;
|
|
15
|
+
/** Original selector string, kept for diagnostics/logging. */
|
|
16
|
+
raw: string;
|
|
17
|
+
}
|
|
18
|
+
/** Catchable error for illegal/unknown selectors (config-warning path). */
|
|
19
|
+
export declare class SelectorError extends Error {
|
|
20
|
+
constructor(message: string);
|
|
21
|
+
}
|
|
22
|
+
/** Canonical key: `provider/model`, or `provider/*` for a wildcard model. */
|
|
23
|
+
export declare function selectorKey(provider: string, model?: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Parse a chain key or entry selector.
|
|
26
|
+
*
|
|
27
|
+
* Accepts `provider/model` and `provider/*`; throws {@link SelectorError}
|
|
28
|
+
* on anything else (missing separator, empty parts, extra separators).
|
|
29
|
+
*/
|
|
30
|
+
export declare function parseSelector(input: string): Selector;
|
|
31
|
+
/**
|
|
32
|
+
* Wildcard-entry resolution: keep the failing model id, swap only the
|
|
33
|
+
* provider (`provider/*` entry semantics, spec §2 clause 2).
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveWildcardEntry(failingModel: string, provider: string): Selector;
|
package/dist/state.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-agent fallback state machine (spec §5.1; plan Task 3).
|
|
3
|
+
*
|
|
4
|
+
* One `AgentFallbackState` per `agent.id`, held in a `FallbackStateStore`
|
|
5
|
+
* created at plugin apply, removed on `agent/disposed`, defensively pruned on
|
|
6
|
+
* `agent/status` idle, and cleared wholesale on plugin dispose (spec §6:
|
|
7
|
+
* 无残留状态).
|
|
8
|
+
*
|
|
9
|
+
* **pendingSwitch lifecycle** (spec §5.1):
|
|
10
|
+
* 1. **Produce** (decision point — request-error trigger-code or
|
|
11
|
+
* agent/request always-cap): `writePending` stores the decision and clears
|
|
12
|
+
* `appliedTurnStep`, so a *fresh* decision is always applicable at the
|
|
13
|
+
* current (turn, step) — required for chains (decision B→C must apply even
|
|
14
|
+
* though A→B was already applied at the same (turn, step)).
|
|
15
|
+
* 2. **Apply** (`agent/request`): `applyPending` returns the pending switch
|
|
16
|
+
* when one exists and has not already been applied at the current
|
|
17
|
+
* (turn, step), records `appliedTurnStep`, and clears `pendingSwitch`
|
|
18
|
+
* (anti-replay guard).
|
|
19
|
+
* 3. **Discard**: chain exhaustion / safety valve → the caller delegates
|
|
20
|
+
* (`next()`) and the pending state dies with the agent.
|
|
21
|
+
*
|
|
22
|
+
* **stepFailures** resets whenever (turn, step) advances (`syncStep`);
|
|
23
|
+
* **cooldown** (`CooldownStore`) is a `provider/model → expiry epoch ms` map
|
|
24
|
+
* with lazy expiry (`revertPolicy: 'never'` = `Infinity` TTL). The time-boxed
|
|
25
|
+
* cooldown deliberately survives idle cleanup so `cooldown-expiry` revert
|
|
26
|
+
* (US-4 / T4 integration) works across turns.
|
|
27
|
+
*
|
|
28
|
+
* @module dsh-llm-fallbacks/state
|
|
29
|
+
*/
|
|
30
|
+
import type { FallbackSwitchReason } from './events.ts';
|
|
31
|
+
import { CooldownStore, StepFailureSet } from './cooldown.ts';
|
|
32
|
+
/** Decision awaiting application at the next `agent/request` boundary (spec §5.1). */
|
|
33
|
+
export interface PendingSwitch {
|
|
34
|
+
from: {
|
|
35
|
+
provider: string;
|
|
36
|
+
model: string;
|
|
37
|
+
};
|
|
38
|
+
to: {
|
|
39
|
+
provider: string;
|
|
40
|
+
model: string;
|
|
41
|
+
};
|
|
42
|
+
role: string;
|
|
43
|
+
reason: FallbackSwitchReason;
|
|
44
|
+
}
|
|
45
|
+
/** The current (turn, step)'s failed-model set and switch budget (spec §5.1). */
|
|
46
|
+
export interface StepFailures {
|
|
47
|
+
turn: number;
|
|
48
|
+
step: number;
|
|
49
|
+
/** `${provider}/${model}` keys failed in this step (double suppression with cooldown). */
|
|
50
|
+
failed: StepFailureSet;
|
|
51
|
+
/** Switches committed in this step; the caller judges it against `maxSwitchesPerStep`. */
|
|
52
|
+
switchCount: number;
|
|
53
|
+
}
|
|
54
|
+
/** One agent's whole fallback runtime state (spec §5.1). */
|
|
55
|
+
export interface AgentFallbackState {
|
|
56
|
+
/** Decision produced but not yet applied at an `agent/request` boundary. */
|
|
57
|
+
pendingSwitch?: PendingSwitch;
|
|
58
|
+
/** The (turn, step) the last pending switch was applied to — anti-replay. */
|
|
59
|
+
appliedTurnStep?: {
|
|
60
|
+
turn: number;
|
|
61
|
+
step: number;
|
|
62
|
+
};
|
|
63
|
+
/** Current (turn, step)'s failure bookkeeping. */
|
|
64
|
+
stepFailures: StepFailures;
|
|
65
|
+
/** `provider/model → expiry epoch ms`; lazily expired on read. */
|
|
66
|
+
cooldown: CooldownStore;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The `Map<agent.id, AgentFallbackState>` store plus the state-machine
|
|
70
|
+
* operations (spec §5.1).
|
|
71
|
+
*/
|
|
72
|
+
export declare class FallbackStateStore {
|
|
73
|
+
private readonly states;
|
|
74
|
+
/** Number of tracked agents. */
|
|
75
|
+
get size(): number;
|
|
76
|
+
has(agentId: string): boolean;
|
|
77
|
+
/** Read without creating; `undefined` for unknown agents. */
|
|
78
|
+
peek(agentId: string): AgentFallbackState | undefined;
|
|
79
|
+
/** Read, creating an empty state on first sight. */
|
|
80
|
+
get(agentId: string): AgentFallbackState;
|
|
81
|
+
/** Remove one agent's state (`agent/disposed`). */
|
|
82
|
+
delete(agentId: string): void;
|
|
83
|
+
/** Remove every agent's state (plugin dispose — no residual state). */
|
|
84
|
+
clear(): void;
|
|
85
|
+
/** Reset step-scoped bookkeeping when (turn, step) advances (spec §5.1). */
|
|
86
|
+
syncStep(state: AgentFallbackState, turn: number, step: number): void;
|
|
87
|
+
/** Record the current model as failed in this step (spec §5.1). */
|
|
88
|
+
recordFailure(state: AgentFallbackState, key: string): void;
|
|
89
|
+
/** Bump the step's switch budget (spec §5.1). */
|
|
90
|
+
recordSwitch(state: AgentFallbackState): void;
|
|
91
|
+
/**
|
|
92
|
+
* Produce a pending switch. A fresh decision always supersedes a previous
|
|
93
|
+
* one and must be applicable at the current (turn, step), so the applied
|
|
94
|
+
* marker is cleared here (see module doc — chains).
|
|
95
|
+
*/
|
|
96
|
+
writePending(state: AgentFallbackState, pending: PendingSwitch): void;
|
|
97
|
+
/**
|
|
98
|
+
* Apply the pending switch at (turn, step) when one exists and has not
|
|
99
|
+
* already been applied there; records the applied marker and clears the
|
|
100
|
+
* pending switch (spec §5.1 lifecycle step 2).
|
|
101
|
+
*/
|
|
102
|
+
applyPending(state: AgentFallbackState, turn: number, step: number): PendingSwitch | undefined;
|
|
103
|
+
/**
|
|
104
|
+
* Defensive `agent/status` idle cleanup: drop per-step state. The time-boxed
|
|
105
|
+
* cooldown survives so `cooldown-expiry` revert works across turns (US-4).
|
|
106
|
+
*/
|
|
107
|
+
clearStepState(state: AgentFallbackState): void;
|
|
108
|
+
/** Suppress `key` until `untilEpochMs` (`Infinity` for `revertPolicy: 'never'`). */
|
|
109
|
+
suppress(state: AgentFallbackState, key: string, untilEpochMs: number): void;
|
|
110
|
+
/** Lazy cooldown read: expired entries are dropped on read (spec §5.1). */
|
|
111
|
+
isSuppressed(state: AgentFallbackState, key: string, now?: number): boolean;
|
|
112
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-llm-fallbacks",
|
|
3
|
+
"description": "Automatic provider/model fallback chains for DeepSeek Harness agents when LLM requests keep failing (retry exhausted, auth, quota, rate limit)",
|
|
4
|
+
"keywords": [
|
|
5
|
+
"dsh",
|
|
6
|
+
"dsh-plugin"
|
|
7
|
+
],
|
|
8
|
+
"version": "0.1.0-alpha.1",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./client": {
|
|
18
|
+
"types": "./dist/client/index.d.ts",
|
|
19
|
+
"default": "./dist/client/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./bundle/cordis.patch.yml": "./bundle/cordis.patch.yml",
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"bundle",
|
|
27
|
+
"scripts"
|
|
28
|
+
],
|
|
29
|
+
"dsh": {
|
|
30
|
+
"bundle": {
|
|
31
|
+
"patch": "./bundle/cordis.patch.yml"
|
|
32
|
+
},
|
|
33
|
+
"client": {
|
|
34
|
+
"inject": [
|
|
35
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
36
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins",
|
|
37
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
38
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
39
|
+
"@deepseek-ai/dsh-client-locale",
|
|
40
|
+
"@deepseek-ai/dsh-client-connection",
|
|
41
|
+
"@deepseek-ai/dsh-api-remotes"
|
|
42
|
+
],
|
|
43
|
+
"platform": "web"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsc -p tsconfig.build.json && tsdown && pnpm run build-client && tsc && node scripts/verify-dist.mjs",
|
|
48
|
+
"build-client": "tsx scripts/build-client.ts",
|
|
49
|
+
"test": "vitest run",
|
|
50
|
+
"prepare": "pnpm run build"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=22"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
57
|
+
"@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
|
|
58
|
+
"@deepseek-ai/dsh-api-gateway": "^0.1.0-rc.6",
|
|
59
|
+
"@deepseek-ai/dsh-api-remotes": "^0.1.0-rc.6",
|
|
60
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.0-rc.6",
|
|
61
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
|
|
62
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
|
|
63
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.0-rc.6",
|
|
64
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
|
|
65
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.6",
|
|
66
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
|
|
67
|
+
"@deepseek-ai/dsh-client-web-react": "^0.1.0-rc.6",
|
|
68
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6",
|
|
69
|
+
"@deepseek-ai/dsh-commands": "^0.1.0-rc.6",
|
|
70
|
+
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
|
|
71
|
+
"@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
|
|
72
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.1.0-rc.6",
|
|
73
|
+
"@deepseek-ai/dsh-typert-registry": "^0.1.0-rc.6",
|
|
74
|
+
"react": "^18.2.0"
|
|
75
|
+
},
|
|
76
|
+
"devDependencies": {
|
|
77
|
+
"@testing-library/dom": "^10.4.1",
|
|
78
|
+
"@testing-library/react": "^16.3.2",
|
|
79
|
+
"@types/node": "^22.20.0",
|
|
80
|
+
"@types/react": "~18.3.31",
|
|
81
|
+
"@types/react-dom": "~18.3.7",
|
|
82
|
+
"jsdom": "29.1.1",
|
|
83
|
+
"react": "^18.3.1",
|
|
84
|
+
"react-dom": "^18.3.1",
|
|
85
|
+
"schemastery": "^3.18.0",
|
|
86
|
+
"tsdown": "^0.22.2",
|
|
87
|
+
"tsx": "^4.22.4",
|
|
88
|
+
"typescript": "^5.9.3",
|
|
89
|
+
"vitest": "^4.1.8"
|
|
90
|
+
}
|
|
91
|
+
}
|