dsh-daruma 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 +56 -0
- package/cordis.patch.yml +24 -0
- package/lib/client/BackupPanel.d.ts +16 -0
- package/lib/client/BackupPanel.js +132 -0
- package/lib/client/BackupPanel.js.map +1 -0
- package/lib/client/StatusDock.d.ts +12 -0
- package/lib/client/StatusDock.js +67 -0
- package/lib/client/StatusDock.js.map +1 -0
- package/lib/client/api.d.ts +47 -0
- package/lib/client/api.js +24 -0
- package/lib/client/api.js.map +1 -0
- package/lib/client/index.d.ts +27 -0
- package/lib/client/index.js +43 -0
- package/lib/client/index.js.map +1 -0
- package/lib/client/locales.d.ts +53 -0
- package/lib/client/locales.js +54 -0
- package/lib/client/locales.js.map +1 -0
- package/lib/client.js +497 -0
- package/lib/client.js.map +1 -0
- package/lib/config.d.ts +20 -0
- package/lib/config.js +25 -0
- package/lib/config.js.map +1 -0
- package/lib/engine.d.ts +40 -0
- package/lib/engine.js +80 -0
- package/lib/engine.js.map +1 -0
- package/lib/index.d.ts +35 -0
- package/lib/index.js +76 -0
- package/lib/index.js.map +1 -0
- package/lib/mapping.d.ts +17 -0
- package/lib/mapping.js +43 -0
- package/lib/mapping.js.map +1 -0
- package/lib/rpc.d.ts +29 -0
- package/lib/rpc.js +122 -0
- package/lib/rpc.js.map +1 -0
- package/lib/status.d.ts +46 -0
- package/lib/status.js +49 -0
- package/lib/status.js.map +1 -0
- package/lib/store.d.ts +17 -0
- package/lib/store.js +54 -0
- package/lib/store.js.map +1 -0
- package/package.json +92 -0
package/lib/engine.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RecoveryEngine — the adapter-side state holder that wraps the pure
|
|
3
|
+
* `decide()` from daruma-core.
|
|
4
|
+
*
|
|
5
|
+
* It owns the mutable channel-health map, the per-session failover counter,
|
|
6
|
+
* and persistence through a `ChannelHealthStore`. All decision logic stays in
|
|
7
|
+
* daruma-core; this class only threads state and I/O.
|
|
8
|
+
*/
|
|
9
|
+
import { decide, freshHealth, } from 'daruma-core';
|
|
10
|
+
const systemClock = { nowMs: () => Date.now() };
|
|
11
|
+
const FAILOVER_HISTORY_LIMIT = 5;
|
|
12
|
+
export class RecoveryEngine {
|
|
13
|
+
config;
|
|
14
|
+
store;
|
|
15
|
+
healths = new Map();
|
|
16
|
+
failoverTotal = 0;
|
|
17
|
+
failoverHistory = [];
|
|
18
|
+
clock;
|
|
19
|
+
constructor(config, store, clock) {
|
|
20
|
+
this.config = config;
|
|
21
|
+
this.store = store;
|
|
22
|
+
this.clock = clock ?? systemClock;
|
|
23
|
+
this.preload();
|
|
24
|
+
}
|
|
25
|
+
/** Load every configured channel's persisted health into memory. */
|
|
26
|
+
preload() {
|
|
27
|
+
for (const channel of this.config.channels) {
|
|
28
|
+
if (this.healths.has(channel.id))
|
|
29
|
+
continue;
|
|
30
|
+
const stored = this.store.load(channel.id);
|
|
31
|
+
this.healths.set(channel.id, stored ?? freshHealth(channel.id, this.clock.nowMs()));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Feed one failure signal and persist the resulting state. */
|
|
35
|
+
onFailure(signal, preferred) {
|
|
36
|
+
const plan = decide({
|
|
37
|
+
signal,
|
|
38
|
+
healths: this.healths,
|
|
39
|
+
failoverCount: this.failoverTotal,
|
|
40
|
+
config: this.config,
|
|
41
|
+
nowMs: this.clock.nowMs(),
|
|
42
|
+
...(preferred !== undefined ? { preferred } : {}),
|
|
43
|
+
});
|
|
44
|
+
this.healths.set(signal.channel, plan.healthAfter);
|
|
45
|
+
this.store.save(plan.healthAfter);
|
|
46
|
+
if (plan.verdict.kind === 'FAILOVER') {
|
|
47
|
+
this.failoverTotal = plan.failoverCount;
|
|
48
|
+
this.failoverHistory.push({
|
|
49
|
+
from: signal.channel,
|
|
50
|
+
to: plan.verdict.target.id,
|
|
51
|
+
reason: signal.code,
|
|
52
|
+
at: this.clock.nowMs(),
|
|
53
|
+
});
|
|
54
|
+
if (this.failoverHistory.length > FAILOVER_HISTORY_LIMIT) {
|
|
55
|
+
this.failoverHistory.shift();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return plan;
|
|
59
|
+
}
|
|
60
|
+
/** Snapshot of every tracked channel's health, for the status UI. */
|
|
61
|
+
listHealth() {
|
|
62
|
+
return [...this.healths.values()].map((health) => ({
|
|
63
|
+
channel: health.channel,
|
|
64
|
+
state: health.state,
|
|
65
|
+
failures: health.consecutiveFailures,
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
get failoverCount() {
|
|
69
|
+
return this.failoverTotal;
|
|
70
|
+
}
|
|
71
|
+
/** Configured failover channels (for provider discovery). */
|
|
72
|
+
get channels() {
|
|
73
|
+
return this.config.channels;
|
|
74
|
+
}
|
|
75
|
+
/** Recent failovers, newest last (bounded). */
|
|
76
|
+
get history() {
|
|
77
|
+
return this.failoverHistory;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-daruma — Daruma resilience plugin for DeepSeek Harness.
|
|
3
|
+
*
|
|
4
|
+
* Mounts the recovery engine on the agent loop's two model-request extension
|
|
5
|
+
* points:
|
|
6
|
+
*
|
|
7
|
+
* - `agent/request-error`: observe failures; when a channel trips, arm a
|
|
8
|
+
* failover target and own recovery by returning `{ kind: 'retry' }`.
|
|
9
|
+
* - `agent/request`: swap the request config onto the armed target.
|
|
10
|
+
*
|
|
11
|
+
* It is intentionally downstream of the in-box `dsh-llm-retry`: that plugin
|
|
12
|
+
* owns same-channel retry and delegates here (via `next()`) when it gives up,
|
|
13
|
+
* so daruma only escalates after retry has exhausted its budget.
|
|
14
|
+
*
|
|
15
|
+
* Additionally mounts the `/dsh-daruma` RPC channel (status, candidate
|
|
16
|
+
* discovery, backup selection) for the web client UI, and appends a durable
|
|
17
|
+
* `daruma/failover` session event on every channel switch.
|
|
18
|
+
*/
|
|
19
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
20
|
+
import { type PluginConfig } from './config.ts';
|
|
21
|
+
export declare const name = "dsh-daruma";
|
|
22
|
+
export declare const inject: readonly ["agents", "settings", "llm"];
|
|
23
|
+
/** Durable session event appended on every channel switch. */
|
|
24
|
+
export interface DarumaFailoverEvent {
|
|
25
|
+
from: string;
|
|
26
|
+
to: string;
|
|
27
|
+
reason: string;
|
|
28
|
+
at: number;
|
|
29
|
+
}
|
|
30
|
+
declare module '@deepseek-ai/dsh-session/types' {
|
|
31
|
+
interface SessionEventMap {
|
|
32
|
+
'daruma/failover': DarumaFailoverEvent;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export declare function apply(ctx: Context, rawConfig?: PluginConfig): void;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-daruma — Daruma resilience plugin for DeepSeek Harness.
|
|
3
|
+
*
|
|
4
|
+
* Mounts the recovery engine on the agent loop's two model-request extension
|
|
5
|
+
* points:
|
|
6
|
+
*
|
|
7
|
+
* - `agent/request-error`: observe failures; when a channel trips, arm a
|
|
8
|
+
* failover target and own recovery by returning `{ kind: 'retry' }`.
|
|
9
|
+
* - `agent/request`: swap the request config onto the armed target.
|
|
10
|
+
*
|
|
11
|
+
* It is intentionally downstream of the in-box `dsh-llm-retry`: that plugin
|
|
12
|
+
* owns same-channel retry and delegates here (via `next()`) when it gives up,
|
|
13
|
+
* so daruma only escalates after retry has exhausted its budget.
|
|
14
|
+
*
|
|
15
|
+
* Additionally mounts the `/dsh-daruma` RPC channel (status, candidate
|
|
16
|
+
* discovery, backup selection) for the web client UI, and appends a durable
|
|
17
|
+
* `daruma/failover` session event on every channel switch.
|
|
18
|
+
*/
|
|
19
|
+
import { channelIdOf, channelIdOfConfig, toCallConfig, toFailureSignal } from "./mapping.js";
|
|
20
|
+
import { resolveConfig } from "./config.js";
|
|
21
|
+
import { RecoveryEngine } from "./engine.js";
|
|
22
|
+
import { JsonFileChannelHealthStore } from "./store.js";
|
|
23
|
+
import { mountStatus } from "./status.js";
|
|
24
|
+
import { mountRpc } from "./rpc.js";
|
|
25
|
+
import { modelId } from 'daruma-core';
|
|
26
|
+
export const name = 'dsh-daruma';
|
|
27
|
+
export const inject = ['agents', 'settings', 'llm'];
|
|
28
|
+
export function apply(ctx, rawConfig = {}) {
|
|
29
|
+
const config = resolveConfig(rawConfig);
|
|
30
|
+
const store = new JsonFileChannelHealthStore(config.stateFile);
|
|
31
|
+
const engine = new RecoveryEngine(config, store);
|
|
32
|
+
const status = mountStatus(ctx);
|
|
33
|
+
// Per-agent channel currently in use (tracked from the last request).
|
|
34
|
+
const currentChannel = new Map();
|
|
35
|
+
// Per-agent failover target armed on the previous failed request.
|
|
36
|
+
const pending = new Map();
|
|
37
|
+
/** The user-chosen backup channel, if set and healthy. */
|
|
38
|
+
const backupChannel = () => {
|
|
39
|
+
const backup = status.getBackup();
|
|
40
|
+
if (backup === undefined)
|
|
41
|
+
return undefined;
|
|
42
|
+
return { id: channelIdOf(backup.provider, backup.model), provider: backup.provider, model: modelId(backup.model) };
|
|
43
|
+
};
|
|
44
|
+
ctx.on('agent/request', async (payload, next) => {
|
|
45
|
+
const current = await next();
|
|
46
|
+
const armed = pending.get(payload.agent.id);
|
|
47
|
+
if (armed) {
|
|
48
|
+
pending.delete(payload.agent.id);
|
|
49
|
+
const swapped = toCallConfig(current, armed);
|
|
50
|
+
currentChannel.set(payload.agent.id, channelIdOfConfig(swapped));
|
|
51
|
+
ctx.logger.warn(`dsh-daruma: switching ${current.provider}/${current.model} -> ${swapped.provider}/${swapped.model}`);
|
|
52
|
+
return swapped;
|
|
53
|
+
}
|
|
54
|
+
currentChannel.set(payload.agent.id, channelIdOfConfig(current));
|
|
55
|
+
return current;
|
|
56
|
+
});
|
|
57
|
+
ctx.on('agent/request-error', async (payload, next) => {
|
|
58
|
+
const channel = currentChannel.get(payload.agent.id) ?? channelIdOf(payload.provider, '');
|
|
59
|
+
const signal = toFailureSignal(payload.failure, channel, Date.now());
|
|
60
|
+
const plan = engine.onFailure(signal, backupChannel());
|
|
61
|
+
if (plan.verdict.kind === 'FAILOVER') {
|
|
62
|
+
pending.set(payload.agent.id, plan.verdict.target);
|
|
63
|
+
const eventData = { from: channel, to: plan.verdict.target.id, reason: signal.code, at: Date.now() };
|
|
64
|
+
payload.agent.session.append('daruma/failover', eventData);
|
|
65
|
+
ctx.logger.warn(`dsh-daruma: failover ${channel} -> ${plan.verdict.target.id} (${signal.code})`);
|
|
66
|
+
return { kind: 'retry' };
|
|
67
|
+
}
|
|
68
|
+
if (plan.verdict.kind === 'GIVE_UP') {
|
|
69
|
+
ctx.logger.error(`dsh-daruma: giving up (${plan.verdict.reason})`);
|
|
70
|
+
}
|
|
71
|
+
// RETRY_NOW / GIVE_UP: delegate downstream (retry may still own it).
|
|
72
|
+
return next();
|
|
73
|
+
});
|
|
74
|
+
mountRpc(ctx, { engine, currentChannel, status, getLlm: () => ctx.llm, getSettings: () => ctx.settings });
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/lib/mapping.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translation between DeepSeek Harness types and the daruma-core domain.
|
|
3
|
+
*
|
|
4
|
+
* DSH failure `code`s are stable provider-neutral strings; the domain
|
|
5
|
+
* taxonomy mirrors them. Anything unknown is downgraded to `UNKNOWN`
|
|
6
|
+
* (treated as retryable).
|
|
7
|
+
*/
|
|
8
|
+
import type { LlmCallConfig, LlmFailure } from '@deepseek-ai/dsh-llm';
|
|
9
|
+
import { type Channel, type ChannelId, type FailureCode, type FailureSignal } from 'daruma-core';
|
|
10
|
+
export declare function toFailureCode(code: string): FailureCode;
|
|
11
|
+
/** Stable channel identity from a provider+model pair. */
|
|
12
|
+
export declare function channelIdOf(provider: string, model: string): ChannelId;
|
|
13
|
+
/** Channel identity of a resolved request config. */
|
|
14
|
+
export declare function channelIdOfConfig(config: LlmCallConfig): ChannelId;
|
|
15
|
+
export declare function toFailureSignal(failure: LlmFailure, channel: ChannelId, occurredAtMs: number): FailureSignal;
|
|
16
|
+
/** Swap a request config onto a failover target channel. */
|
|
17
|
+
export declare function toCallConfig(config: LlmCallConfig, target: Channel): LlmCallConfig;
|
package/lib/mapping.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translation between DeepSeek Harness types and the daruma-core domain.
|
|
3
|
+
*
|
|
4
|
+
* DSH failure `code`s are stable provider-neutral strings; the domain
|
|
5
|
+
* taxonomy mirrors them. Anything unknown is downgraded to `UNKNOWN`
|
|
6
|
+
* (treated as retryable).
|
|
7
|
+
*/
|
|
8
|
+
import { channelId, } from 'daruma-core';
|
|
9
|
+
const KNOWN_FAILURE_CODES = new Set([
|
|
10
|
+
'RATE_LIMIT',
|
|
11
|
+
'SERVER',
|
|
12
|
+
'TIMEOUT',
|
|
13
|
+
'TRANSPORT',
|
|
14
|
+
'EMPTY_RESPONSE',
|
|
15
|
+
'QUOTA',
|
|
16
|
+
'CONTEXT_WINDOW_EXCEEDED',
|
|
17
|
+
'INVALID_CREDENTIAL',
|
|
18
|
+
]);
|
|
19
|
+
export function toFailureCode(code) {
|
|
20
|
+
return (KNOWN_FAILURE_CODES.has(code) ? code : 'UNKNOWN');
|
|
21
|
+
}
|
|
22
|
+
/** Stable channel identity from a provider+model pair. */
|
|
23
|
+
export function channelIdOf(provider, model) {
|
|
24
|
+
return channelId(model ? `${provider}::${model}` : provider);
|
|
25
|
+
}
|
|
26
|
+
/** Channel identity of a resolved request config. */
|
|
27
|
+
export function channelIdOfConfig(config) {
|
|
28
|
+
return channelIdOf(config.provider, config.model);
|
|
29
|
+
}
|
|
30
|
+
export function toFailureSignal(failure, channel, occurredAtMs) {
|
|
31
|
+
return {
|
|
32
|
+
code: toFailureCode(failure.code),
|
|
33
|
+
status: failure.status,
|
|
34
|
+
channel,
|
|
35
|
+
occurredAtMs,
|
|
36
|
+
message: failure.message,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Swap a request config onto a failover target channel. */
|
|
40
|
+
export function toCallConfig(config, target) {
|
|
41
|
+
return { ...config, provider: target.provider, model: target.model };
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=mapping.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mapping.js","sourceRoot":"","sources":["../src/mapping.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EACL,SAAS,GAKV,MAAM,aAAa,CAAA;AAEpB,MAAM,mBAAmB,GAAwB,IAAI,GAAG,CAAC;IACvD,YAAY;IACZ,QAAQ;IACR,SAAS;IACT,WAAW;IACX,gBAAgB;IAChB,OAAO;IACP,yBAAyB;IACzB,oBAAoB;CACrB,CAAC,CAAA;AAEF,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,CAAC,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAgB,CAAA;AAC1E,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,KAAa;IACzD,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,QAAQ,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAA;AAC9D,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,iBAAiB,CAAC,MAAqB;IACrD,OAAO,WAAW,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,CAAA;AACnD,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,OAAmB,EACnB,OAAkB,EAClB,YAAoB;IAEpB,OAAO;QACL,IAAI,EAAE,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC;QACjC,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,OAAO;QACP,YAAY;QACZ,OAAO,EAAE,OAAO,CAAC,OAAO;KACzB,CAAA;AACH,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,YAAY,CAAC,MAAqB,EAAE,MAAe;IACjE,OAAO,EAAE,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAA;AACtE,CAAC"}
|
package/lib/rpc.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/dsh-daruma` RPC channel: status snapshot, candidate discovery, and
|
|
3
|
+
* backup-channel management for the client UI.
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
import type { LlmRuntime } from '@deepseek-ai/dsh-llm';
|
|
7
|
+
import type { SettingsProvider } from '@deepseek-ai/dsh-settings';
|
|
8
|
+
import type { Channel, ChannelId } from 'daruma-core';
|
|
9
|
+
import type { RecoveryEngine } from './engine.ts';
|
|
10
|
+
import type { StatusService } from './status.ts';
|
|
11
|
+
export type RpcResult = {
|
|
12
|
+
ok: true;
|
|
13
|
+
value: unknown;
|
|
14
|
+
} | {
|
|
15
|
+
ok: false;
|
|
16
|
+
error: {
|
|
17
|
+
code: string;
|
|
18
|
+
message: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export interface RpcDeps {
|
|
22
|
+
readonly engine: RecoveryEngine;
|
|
23
|
+
readonly currentChannel: ReadonlyMap<string, ChannelId>;
|
|
24
|
+
readonly status: StatusService;
|
|
25
|
+
readonly getLlm: () => LlmRuntime | undefined;
|
|
26
|
+
readonly getSettings: () => SettingsProvider | undefined;
|
|
27
|
+
}
|
|
28
|
+
export declare function mountRpc(ctx: Context, deps: RpcDeps): void;
|
|
29
|
+
export type { Channel };
|
package/lib/rpc.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/dsh-daruma` RPC channel: status snapshot, candidate discovery, and
|
|
3
|
+
* backup-channel management for the client UI.
|
|
4
|
+
*/
|
|
5
|
+
import { channelIdOf } from "./mapping.js";
|
|
6
|
+
function messageOf(error) {
|
|
7
|
+
return error instanceof Error ? error.message : String(error);
|
|
8
|
+
}
|
|
9
|
+
function isRecord(value) {
|
|
10
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
function isString(value) {
|
|
13
|
+
return typeof value === 'string';
|
|
14
|
+
}
|
|
15
|
+
/** `{ provider, model }` — a backup selection. */
|
|
16
|
+
function isBackupPayload(value) {
|
|
17
|
+
return isRecord(value) && isString(value.provider) && value.provider !== ''
|
|
18
|
+
&& isString(value.model) && value.model !== '';
|
|
19
|
+
}
|
|
20
|
+
/** Known provider names: daruma's configured channels plus settings providers. */
|
|
21
|
+
function knownProviders(deps) {
|
|
22
|
+
const names = new Set();
|
|
23
|
+
for (const channel of deps.engine.channels)
|
|
24
|
+
names.add(channel.provider);
|
|
25
|
+
const settings = deps.getSettings();
|
|
26
|
+
if (settings !== undefined) {
|
|
27
|
+
try {
|
|
28
|
+
const section = settings.get('llm-pi-ai');
|
|
29
|
+
for (const provider of Object.keys(section?.providers ?? {}))
|
|
30
|
+
names.add(provider);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// settings unavailable — configured channels still known
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return [...names];
|
|
37
|
+
}
|
|
38
|
+
async function listCandidates(llm, provider, settings) {
|
|
39
|
+
try {
|
|
40
|
+
const models = await llm.listModels(provider);
|
|
41
|
+
if (models.length > 0)
|
|
42
|
+
return models.map((info) => ({ provider, model: info.id }));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// fall through to the settings model directory
|
|
46
|
+
}
|
|
47
|
+
if (settings !== undefined) {
|
|
48
|
+
try {
|
|
49
|
+
const section = settings.get('llm-pi-ai');
|
|
50
|
+
const models = section?.providers?.[provider]?.models;
|
|
51
|
+
if (models !== undefined && models.length > 0) {
|
|
52
|
+
return models
|
|
53
|
+
.map((model) => model.id)
|
|
54
|
+
.filter((id) => id !== undefined && id !== '')
|
|
55
|
+
.map((model) => ({ provider, model }));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// settings unavailable
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
export function mountRpc(ctx, deps) {
|
|
65
|
+
const connection = ctx.get('connection');
|
|
66
|
+
if (connection === undefined)
|
|
67
|
+
return; // no web transport (headless)
|
|
68
|
+
const dispatch = async (endpoint, payload, signal) => {
|
|
69
|
+
try {
|
|
70
|
+
switch (endpoint) {
|
|
71
|
+
case 'status': {
|
|
72
|
+
const sessionId = isRecord(payload) && isString(payload.sessionId) ? payload.sessionId : undefined;
|
|
73
|
+
const current = sessionId !== undefined ? deps.currentChannel.get(sessionId) : undefined;
|
|
74
|
+
const backup = deps.status.getBackup();
|
|
75
|
+
return {
|
|
76
|
+
ok: true,
|
|
77
|
+
value: {
|
|
78
|
+
current: current ?? null,
|
|
79
|
+
backup: backup ?? null,
|
|
80
|
+
channels: deps.engine.listHealth(),
|
|
81
|
+
failoverCount: deps.engine.failoverCount,
|
|
82
|
+
failoverHistory: deps.engine.history,
|
|
83
|
+
providers: knownProviders(deps),
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
case 'listCandidates': {
|
|
88
|
+
const provider = isRecord(payload) && isString(payload.provider) ? payload.provider : undefined;
|
|
89
|
+
if (provider === undefined)
|
|
90
|
+
throw new Error('bad-request: listCandidates needs a provider');
|
|
91
|
+
const llm = deps.getLlm();
|
|
92
|
+
if (llm === undefined)
|
|
93
|
+
throw new Error('llm service unavailable');
|
|
94
|
+
return { ok: true, value: await listCandidates(llm, provider, deps.getSettings()) };
|
|
95
|
+
}
|
|
96
|
+
case 'setBackup': {
|
|
97
|
+
if (!isBackupPayload(payload))
|
|
98
|
+
throw new Error('bad-request: setBackup needs {provider, model}');
|
|
99
|
+
await deps.status.setBackup({ provider: payload.provider, model: payload.model });
|
|
100
|
+
return { ok: true, value: { ok: true } };
|
|
101
|
+
}
|
|
102
|
+
case 'clearBackup': {
|
|
103
|
+
await deps.status.clearBackup();
|
|
104
|
+
return { ok: true, value: { ok: true } };
|
|
105
|
+
}
|
|
106
|
+
default:
|
|
107
|
+
throw new Error(`bad-request: unknown endpoint ${JSON.stringify(endpoint)}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
const message = messageOf(error);
|
|
112
|
+
const code = message.startsWith('bad-request:') ? 'bad-request' : 'internal';
|
|
113
|
+
return {
|
|
114
|
+
ok: false,
|
|
115
|
+
error: { code, message: message.replace(/^bad-request: /u, '') },
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const dispose = connection.rpc.handle('/dsh-daruma', dispatch, { authority: 'loopback' });
|
|
120
|
+
ctx.effect(() => dispose, 'dsh-daruma: /dsh-daruma rpc channel');
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=rpc.js.map
|
package/lib/rpc.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc.js","sourceRoot":"","sources":["../src/rpc.ts"],"names":[],"mappings":"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"}
|
package/lib/status.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daruma status service: the `daruma` settings namespace (user-chosen backup
|
|
3
|
+
* channel) plus the runtime status snapshot surface.
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
import z from '@deepseek-ai/schemastery';
|
|
7
|
+
import { type SettingsProvider, type SettingsScope } from '@deepseek-ai/dsh-settings';
|
|
8
|
+
export declare const DARUMA_NS: import("@deepseek-ai/dsh-settings").SettingsNamespace;
|
|
9
|
+
/** The `daruma:` settings namespace schema. */
|
|
10
|
+
export declare const DarumaSettingsSchema: z<Schemastery.ObjectS<{
|
|
11
|
+
backup: z<Schemastery.ObjectS<{
|
|
12
|
+
provider: z<string, string>;
|
|
13
|
+
model: z<string, string>;
|
|
14
|
+
}>, Schemastery.ObjectT<{
|
|
15
|
+
provider: z<string, string>;
|
|
16
|
+
model: z<string, string>;
|
|
17
|
+
}>>;
|
|
18
|
+
}>, Schemastery.ObjectT<{
|
|
19
|
+
backup: z<Schemastery.ObjectS<{
|
|
20
|
+
provider: z<string, string>;
|
|
21
|
+
model: z<string, string>;
|
|
22
|
+
}>, Schemastery.ObjectT<{
|
|
23
|
+
provider: z<string, string>;
|
|
24
|
+
model: z<string, string>;
|
|
25
|
+
}>>;
|
|
26
|
+
}>>;
|
|
27
|
+
export interface DarumaSettings {
|
|
28
|
+
backup?: {
|
|
29
|
+
provider: string;
|
|
30
|
+
model: string;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export interface BackupSelection {
|
|
34
|
+
readonly provider: string;
|
|
35
|
+
readonly model: string;
|
|
36
|
+
}
|
|
37
|
+
export declare class StatusService {
|
|
38
|
+
private readonly settings;
|
|
39
|
+
private readonly scope;
|
|
40
|
+
constructor(settings: SettingsProvider, scope: SettingsScope<DarumaSettings>);
|
|
41
|
+
getBackup(): BackupSelection | undefined;
|
|
42
|
+
setBackup(selection: BackupSelection): Promise<void>;
|
|
43
|
+
clearBackup(): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
/** Register the namespace and return the service. */
|
|
46
|
+
export declare function mountStatus(ctx: Context): StatusService;
|
package/lib/status.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daruma status service: the `daruma` settings namespace (user-chosen backup
|
|
3
|
+
* channel) plus the runtime status snapshot surface.
|
|
4
|
+
*/
|
|
5
|
+
import z from '@deepseek-ai/schemastery';
|
|
6
|
+
import { settingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
7
|
+
export const DARUMA_NS = settingsNamespace('daruma');
|
|
8
|
+
const backupShape = z.object({
|
|
9
|
+
provider: z.string(),
|
|
10
|
+
model: z.string(),
|
|
11
|
+
});
|
|
12
|
+
/** The `daruma:` settings namespace schema. */
|
|
13
|
+
export const DarumaSettingsSchema = z.object({
|
|
14
|
+
backup: backupShape,
|
|
15
|
+
});
|
|
16
|
+
/** True when a value is a usable backup selection (non-empty strings). */
|
|
17
|
+
function isValidBackup(value) {
|
|
18
|
+
if (typeof value !== 'object' || value === null)
|
|
19
|
+
return false;
|
|
20
|
+
const candidate = value;
|
|
21
|
+
return typeof candidate.provider === 'string' && candidate.provider !== ''
|
|
22
|
+
&& typeof candidate.model === 'string' && candidate.model !== '';
|
|
23
|
+
}
|
|
24
|
+
export class StatusService {
|
|
25
|
+
settings;
|
|
26
|
+
scope;
|
|
27
|
+
constructor(settings, scope) {
|
|
28
|
+
this.settings = settings;
|
|
29
|
+
this.scope = scope;
|
|
30
|
+
}
|
|
31
|
+
getBackup() {
|
|
32
|
+
const backup = this.scope.get().backup;
|
|
33
|
+
// The settings layer can resolve an absent section to an empty object;
|
|
34
|
+
// treat any selection without non-empty provider/model as unset.
|
|
35
|
+
return isValidBackup(backup) ? backup : undefined;
|
|
36
|
+
}
|
|
37
|
+
async setBackup(selection) {
|
|
38
|
+
await this.scope.update({ backup: selection });
|
|
39
|
+
}
|
|
40
|
+
async clearBackup() {
|
|
41
|
+
await this.settings.mutate(DARUMA_NS, [{ op: 'unset', path: ['backup'] }]);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Register the namespace and return the service. */
|
|
45
|
+
export function mountStatus(ctx) {
|
|
46
|
+
const scope = ctx.settings.register(DARUMA_NS, DarumaSettingsSchema);
|
|
47
|
+
return new StatusService(ctx.settings, scope);
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,CAAC,MAAM,0BAA0B,CAAA;AACxC,OAAO,EAAE,iBAAiB,EAA6C,MAAM,2BAA2B,CAAA;AAExG,MAAM,CAAC,MAAM,SAAS,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAA;AAEpD,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;CAClB,CAAC,CAAA;AAEF,+CAA+C;AAC/C,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,MAAM,EAAE,WAAW;CACpB,CAAC,CAAA;AAWF,0EAA0E;AAC1E,SAAS,aAAa,CAAC,KAAc;IACnC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAA;IAC7D,MAAM,SAAS,GAAG,KAAiC,CAAA;IACnD,OAAO,OAAO,SAAS,CAAC,QAAQ,KAAK,QAAQ,IAAI,SAAS,CAAC,QAAQ,KAAK,EAAE;WACrE,OAAO,SAAS,CAAC,KAAK,KAAK,QAAQ,IAAI,SAAS,CAAC,KAAK,KAAK,EAAE,CAAA;AACpE,CAAC;AAED,MAAM,OAAO,aAAa;IAEL;IACA;IAFnB,YACmB,QAA0B,EAC1B,KAAoC;QADpC,aAAQ,GAAR,QAAQ,CAAkB;QAC1B,UAAK,GAAL,KAAK,CAA+B;IACpD,CAAC;IAEJ,SAAS;QACP,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,MAAM,CAAA;QACtC,uEAAuE;QACvE,iEAAiE;QACjE,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;IACnD,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,SAA0B;QACxC,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAA;IAChD,CAAC;IAED,KAAK,CAAC,WAAW;QACf,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAA;IAC5E,CAAC;CACF;AAED,qDAAqD;AACrD,MAAM,UAAU,WAAW,CAAC,GAAY;IACtC,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,SAAS,EAAE,oBAA6B,CAA6C,CAAA;IACzH,OAAO,IAAI,aAAa,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;AAC/C,CAAC"}
|
package/lib/store.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-file implementation of the domain's `ChannelHealthStore` port.
|
|
3
|
+
*
|
|
4
|
+
* Persistence is best-effort: a corrupt or unreadable state file makes the
|
|
5
|
+
* store start fresh rather than crashing the plugin.
|
|
6
|
+
*/
|
|
7
|
+
import type { ChannelHealth, ChannelHealthStore, ChannelId } from 'daruma-core';
|
|
8
|
+
export declare class JsonFileChannelHealthStore implements ChannelHealthStore {
|
|
9
|
+
private readonly file;
|
|
10
|
+
private readonly healths;
|
|
11
|
+
private loaded;
|
|
12
|
+
constructor(file: string);
|
|
13
|
+
private ensureLoaded;
|
|
14
|
+
load(channel: ChannelId): ChannelHealth | undefined;
|
|
15
|
+
save(health: ChannelHealth): void;
|
|
16
|
+
private persist;
|
|
17
|
+
}
|
package/lib/store.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-file implementation of the domain's `ChannelHealthStore` port.
|
|
3
|
+
*
|
|
4
|
+
* Persistence is best-effort: a corrupt or unreadable state file makes the
|
|
5
|
+
* store start fresh rather than crashing the plugin.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { dirname } from 'node:path';
|
|
9
|
+
export class JsonFileChannelHealthStore {
|
|
10
|
+
file;
|
|
11
|
+
healths = new Map();
|
|
12
|
+
loaded = false;
|
|
13
|
+
constructor(file) {
|
|
14
|
+
this.file = file;
|
|
15
|
+
}
|
|
16
|
+
ensureLoaded() {
|
|
17
|
+
if (this.loaded)
|
|
18
|
+
return;
|
|
19
|
+
this.loaded = true;
|
|
20
|
+
try {
|
|
21
|
+
if (!existsSync(this.file))
|
|
22
|
+
return;
|
|
23
|
+
const raw = JSON.parse(readFileSync(this.file, 'utf8'));
|
|
24
|
+
for (const [key, health] of Object.entries(raw.healths ?? {})) {
|
|
25
|
+
this.healths.set(key, health);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Corrupt state: start fresh.
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
load(channel) {
|
|
33
|
+
this.ensureLoaded();
|
|
34
|
+
return this.healths.get(channel);
|
|
35
|
+
}
|
|
36
|
+
save(health) {
|
|
37
|
+
this.ensureLoaded();
|
|
38
|
+
this.healths.set(health.channel, health);
|
|
39
|
+
this.persist();
|
|
40
|
+
}
|
|
41
|
+
persist() {
|
|
42
|
+
try {
|
|
43
|
+
mkdirSync(dirname(this.file), { recursive: true });
|
|
44
|
+
const healths = {};
|
|
45
|
+
for (const [key, health] of this.healths)
|
|
46
|
+
healths[key] = health;
|
|
47
|
+
writeFileSync(this.file, JSON.stringify({ healths }, null, 2));
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Persistence failure must not take down the plugin.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=store.js.map
|
package/lib/store.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAOnC,MAAM,OAAO,0BAA0B;IAIR;IAHZ,OAAO,GAAG,IAAI,GAAG,EAA4B,CAAA;IACtD,MAAM,GAAG,KAAK,CAAA;IAEtB,YAA6B,IAAY;QAAZ,SAAI,GAAJ,IAAI,CAAQ;IAAG,CAAC;IAErC,YAAY;QAClB,IAAI,IAAI,CAAC,MAAM;YAAE,OAAM;QACvB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAA;QAClB,IAAI,CAAC;YACH,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,OAAM;YAClC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAgB,CAAA;YACtE,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,CAAC;gBAC9D,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAgB,EAAE,MAAuB,CAAC,CAAA;YAC7D,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,8BAA8B;QAChC,CAAC;IACH,CAAC;IAED,IAAI,CAAC,OAAkB;QACrB,IAAI,CAAC,YAAY,EAAE,CAAA;QACnB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;IAClC,CAAC;IAED,IAAI,CAAC,MAAqB;QACxB,IAAI,CAAC,YAAY,EAAE,CAAA;QACnB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QACxC,IAAI,CAAC,OAAO,EAAE,CAAA;IAChB,CAAC;IAEO,OAAO;QACb,IAAI,CAAC;YACH,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;YAClD,MAAM,OAAO,GAAkC,EAAE,CAAA;YACjD,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO;gBAAE,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,CAAA;YAC/D,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;QAChE,CAAC;QAAC,MAAM,CAAC;YACP,qDAAqD;QACvD,CAAC;IACH,CAAC;CACF"}
|