dsh-advisor 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.i18n.yaml +7 -0
- package/README.md +303 -0
- package/README.zh.md +166 -0
- package/cordis.patch.yml +6 -0
- package/lib/advisor-runtime.d.ts +242 -0
- package/lib/advisor-runtime.js +662 -0
- package/lib/advisor-runtime.js.map +1 -0
- package/lib/client/advisor-card.d.ts +90 -0
- package/lib/client/advisor-store.d.ts +310 -0
- package/lib/client/index.d.ts +39 -0
- package/lib/client/locales.d.ts +40 -0
- package/lib/client.d.ts +1 -0
- package/lib/client.js +840 -0
- package/lib/commands.d.ts +136 -0
- package/lib/commands.js +185 -0
- package/lib/commands.js.map +1 -0
- package/lib/config.d.ts +74 -0
- package/lib/config.js +93 -0
- package/lib/config.js.map +1 -0
- package/lib/delivery.d.ts +129 -0
- package/lib/delivery.js +169 -0
- package/lib/delivery.js.map +1 -0
- package/lib/emission-guard.d.ts +99 -0
- package/lib/emission-guard.js +155 -0
- package/lib/emission-guard.js.map +1 -0
- package/lib/gateway.d.ts +116 -0
- package/lib/gateway.js +214 -0
- package/lib/gateway.js.map +1 -0
- package/lib/index.d.ts +48 -0
- package/lib/index.js +485 -0
- package/lib/index.js.map +1 -0
- package/lib/kinds.d.ts +38 -0
- package/lib/kinds.js +24 -0
- package/lib/kinds.js.map +1 -0
- package/lib/prompts.d.ts +22 -0
- package/lib/prompts.js +38 -0
- package/lib/prompts.js.map +1 -0
- package/lib/settings.d.ts +96 -0
- package/lib/settings.js +141 -0
- package/lib/settings.js.map +1 -0
- package/lib/transcript.d.ts +257 -0
- package/lib/transcript.js +530 -0
- package/lib/transcript.js.map +1 -0
- package/package.json +90 -0
- package/scripts/build-client.mjs +268 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-session advisor runtime (spec §2 S2, §4 mapping rows, §6, §8.2 KD-2,
|
|
3
|
+
* §8.5 KD-5) — the "advisor model call + note extraction + drain/backlog"
|
|
4
|
+
* core.
|
|
5
|
+
*
|
|
6
|
+
* One {@link AdvisorRuntime} exists per session (created on `agent/created` or
|
|
7
|
+
* lazily on the first stepped `turn/end`, disposed on `agent/disposed` /
|
|
8
|
+
* `session/disposed` — wired in `index.ts`). It owns:
|
|
9
|
+
*
|
|
10
|
+
* - a FIFO queue of pending transcript deltas (bounded — spec §6 "bounded
|
|
11
|
+
* backlog"; drop-newest when full);
|
|
12
|
+
* - a serialized async drain loop: one `llm.stream` call per delta with
|
|
13
|
+
* `{ provider, model, system, messages: [user delta], maxTokens: 5120 }` and
|
|
14
|
+
* `purpose` left UNSET (KD-5 — an advisor call is an ordinary conversation
|
|
15
|
+
* request);
|
|
16
|
+
* - a call-level deadline on every `llm.stream` call (dsh-timeout `deadline`,
|
|
17
|
+
* fused with the dispose signal and raced per chunk): a hung provider stream
|
|
18
|
+
* times out instead of wedging the drain, and a timeout is a transient
|
|
19
|
+
* failure (KD-5 retry → drop);
|
|
20
|
+
* - KD-2 JSON-frame extraction: the first balanced `{…}` in the reply is
|
|
21
|
+
* parsed (tolerant of prose/fences), `note` must be non-empty (else
|
|
22
|
+
* drop+log), `severity` missing/invalid defaults to `nit`, no parse retry;
|
|
23
|
+
* - the KD-5 failure policy: transient → 1 retry with a short backoff → drop;
|
|
24
|
+
* 3 consecutive dropped deltas → flush the pending backlog (never stall);
|
|
25
|
+
* permanent errors (`invalid_request_error`, model-not-found, "is not
|
|
26
|
+
* supported when") → halt the session's advisor; quota/rate-limit → pause
|
|
27
|
+
* (`quota_exhausted`), batch retained, no auto-resume timer; the in-flight
|
|
28
|
+
* call is aborted on dispose via the `signal`. `halted` is terminal in
|
|
29
|
+
* place — the command layer rebuilds the runtime (dispose + recreate); a
|
|
30
|
+
* `quota_exhausted` runtime resumes via {@link AdvisorRuntime.resume}.
|
|
31
|
+
*
|
|
32
|
+
* The runtime never parks the primary loop: everything is fire-and-forget
|
|
33
|
+
* async and a failing advisor can only drop its own backlog.
|
|
34
|
+
*
|
|
35
|
+
* @module dsh-advisor/advisor-runtime
|
|
36
|
+
*/
|
|
37
|
+
import type { GenerateOptions, LlmResolvedModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
38
|
+
import type { EmissionGuard } from './emission-guard.js';
|
|
39
|
+
import type { Delta } from './transcript.js';
|
|
40
|
+
/** Severity vocabulary (spec §6) carried by every delivered advice note. */
|
|
41
|
+
export type AdviceSeverity = 'nit' | 'concern' | 'blocker';
|
|
42
|
+
/** One extracted advice note, fed to the T5 emission guard for delivery. */
|
|
43
|
+
export interface AdviceNote {
|
|
44
|
+
readonly note: string;
|
|
45
|
+
readonly severity: AdviceSeverity;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Per-session runtime status surface for T7 `/advisor status`.
|
|
49
|
+
*
|
|
50
|
+
* T4 sets `running` | `quota_exhausted` | `halted`; `paused` is reserved for
|
|
51
|
+
* explicit pause semantics and `disabled` for the config gate — the runtime is
|
|
52
|
+
* never constructed in either state (`index.ts` returns early when the resolved
|
|
53
|
+
* config is disabled).
|
|
54
|
+
*/
|
|
55
|
+
export type AdvisorRuntimeStatus = 'running' | 'paused' | 'quota_exhausted' | 'halted' | 'disabled';
|
|
56
|
+
/** Minimal `ctx.llm` surface the runtime drives (satisfied by `LlmRuntime`). */
|
|
57
|
+
export interface AdvisorLlm {
|
|
58
|
+
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
59
|
+
/**
|
|
60
|
+
* Optional capability query (LlmRuntime exposes it; injected fakes may not).
|
|
61
|
+
* Used to capability-gate `reasoningEffort` (qc2 W-1 / qc1 W-1 / qc3 F-3):
|
|
62
|
+
* the dsh LlmRuntime rejects an explicit effort for any model whose adapter
|
|
63
|
+
* does not declare it, so the runtime resolves the model's declared efforts
|
|
64
|
+
* and passes `'off'` only when the model supports it. Absent → the option
|
|
65
|
+
* is omitted and `resolveCallFor` materializes the adapter default.
|
|
66
|
+
*/
|
|
67
|
+
resolveModelInfo?(provider: string, model: string, signal?: AbortSignal): Promise<LlmResolvedModelInfo>;
|
|
68
|
+
}
|
|
69
|
+
/** Logger seam (cordis `ctx.logger('advisor')` satisfies it; console works too). */
|
|
70
|
+
export interface AdvisorRuntimeLogger {
|
|
71
|
+
debug(message: string, ...args: unknown[]): void;
|
|
72
|
+
warn(message: string, ...args: unknown[]): void;
|
|
73
|
+
}
|
|
74
|
+
/** Options for one per-session {@link AdvisorRuntime}. */
|
|
75
|
+
export interface AdvisorRuntimeOptions {
|
|
76
|
+
/** Resolved provider route (the explicit gate guarantees it when enabled). */
|
|
77
|
+
readonly provider: string;
|
|
78
|
+
/** Resolved model id (the explicit gate guarantees it when enabled). */
|
|
79
|
+
readonly model: string;
|
|
80
|
+
/** System prompt sent with every advisor call (config override or KD-2 default). */
|
|
81
|
+
readonly systemPrompt: string;
|
|
82
|
+
/** Output-token cap (KD-2); default 256. */
|
|
83
|
+
readonly maxTokens?: number;
|
|
84
|
+
/** Backoff for the single transient retry (KD-5); default 1000ms. */
|
|
85
|
+
readonly retryBackoffMs?: number;
|
|
86
|
+
/**
|
|
87
|
+
* Call-level deadline for one `llm.stream` call (qc2 W-4 / qc3 W-1): a hung
|
|
88
|
+
* provider stream (no chunk, no end, no error) times out instead of wedging
|
|
89
|
+
* this session's drain. A timeout is classified as a transient failure —
|
|
90
|
+
* KD-5 retry(1) → drop. Default 60000ms.
|
|
91
|
+
*/
|
|
92
|
+
readonly callTimeoutMs?: number;
|
|
93
|
+
/** Bounded backlog (spec §6); default 32 — drop-newest with a log when full. */
|
|
94
|
+
readonly maxQueued?: number;
|
|
95
|
+
/** The llm service (`ctx.llm`); injectable for tests. */
|
|
96
|
+
readonly llm: AdvisorLlm;
|
|
97
|
+
/**
|
|
98
|
+
* The T5 emission guard gating extracted notes before delivery. Defaults to
|
|
99
|
+
* a fresh {@link createEmissionGuard} (per-runtime lifetime — a new guard
|
|
100
|
+
* per session); injectable for tests.
|
|
101
|
+
*/
|
|
102
|
+
readonly guard?: EmissionGuard;
|
|
103
|
+
/**
|
|
104
|
+
* Invoked once per extracted note that passes the T5 emission guard
|
|
105
|
+
* (accepted = delivered to T6; suppressed notes are dropped silently).
|
|
106
|
+
*/
|
|
107
|
+
readonly onNote: (note: AdviceNote) => void;
|
|
108
|
+
readonly logger?: AdvisorRuntimeLogger;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* n4 user direction: the advisor call runs with a 20x token budget
|
|
112
|
+
* (256 -> 5120) so even a reasoning-heavy reply cannot starve the JSON frame.
|
|
113
|
+
* Exported so the test suites assert the pinned value instead of a magic
|
|
114
|
+
* literal.
|
|
115
|
+
*
|
|
116
|
+
* Supersession note (qc2 S-2 / qc1 S-2 / qc3 F-2): the frozen spec §8.2
|
|
117
|
+
* (KD-2) pins `maxTokens: 256` ("so a runaway reply cannot blow the budget").
|
|
118
|
+
* This 5120 value is the USER-DIRECTED supersession of that pin — a 20x
|
|
119
|
+
* worst-case per-call ceiling, adopted together with `reasoningEffort: 'off'`
|
|
120
|
+
* (capability-gated, see `resolveReasoningEffort`) so the raised budget goes
|
|
121
|
+
* to the JSON frame rather than reasoning output. The looser runaway-reply
|
|
122
|
+
* guard is re-bounded downstream: `extractAdviceNote` caps the note at
|
|
123
|
+
* `ADVISOR_NOTE_MAX_CHARS` and `buildAdvisorMessage` bounds the notice
|
|
124
|
+
* summary via `boundContextSummary`.
|
|
125
|
+
*/
|
|
126
|
+
export declare const ADVISOR_MAX_TOKENS = 5120;
|
|
127
|
+
/**
|
|
128
|
+
* One extracted note's length cap (qc3 F-2 / qc2 S-1): a verbose/rogue advisor
|
|
129
|
+
* reply with the 20x token budget must not inject an unbounded user-role
|
|
130
|
+
* message into the primary session. Truncated with a '…' marker.
|
|
131
|
+
*/
|
|
132
|
+
export declare const ADVISOR_NOTE_MAX_CHARS = 1000;
|
|
133
|
+
/**
|
|
134
|
+
* Extract one {@link AdviceNote} from the advisor's reply (KD-2).
|
|
135
|
+
*
|
|
136
|
+
* Locates the first balanced `{…}` object (tolerant of surrounding prose and
|
|
137
|
+
* markdown fences), parses it, and validates: `note` must be a non-empty
|
|
138
|
+
* string after trim (else drop), `severity` missing/invalid defaults to
|
|
139
|
+
* `nit`. A reply with no parseable frame returns `undefined` — the caller
|
|
140
|
+
* drops + logs, and there is NO retry for parse failures (the retry budget is
|
|
141
|
+
* reserved for transport errors, KD-2). The note is capped at
|
|
142
|
+
* {@link ADVISOR_NOTE_MAX_CHARS} with a '…' marker (qc3 F-2 — the 20x token
|
|
143
|
+
* budget must not translate into an unbounded injection into the primary
|
|
144
|
+
* session). Never throws.
|
|
145
|
+
*/
|
|
146
|
+
export declare function extractAdviceNote(reply: string): AdviceNote | undefined;
|
|
147
|
+
/**
|
|
148
|
+
* Per-session advisor runtime: queue deltas, async drain, `llm.stream` call,
|
|
149
|
+
* JSON-frame note extraction, and the KD-5 failure policy. All async work is
|
|
150
|
+
* fire-and-forget — the primary loop is never parked.
|
|
151
|
+
*/
|
|
152
|
+
export declare class AdvisorRuntime {
|
|
153
|
+
private readonly provider;
|
|
154
|
+
private readonly model;
|
|
155
|
+
private readonly systemPrompt;
|
|
156
|
+
private readonly maxTokens;
|
|
157
|
+
private readonly retryBackoffMs;
|
|
158
|
+
private readonly callTimeoutMs;
|
|
159
|
+
private readonly maxQueued;
|
|
160
|
+
private readonly llm;
|
|
161
|
+
private readonly guard;
|
|
162
|
+
private readonly onNote;
|
|
163
|
+
private readonly logger;
|
|
164
|
+
private readonly controller;
|
|
165
|
+
/**
|
|
166
|
+
* Per-(provider, model) reasoning-effort capability cache (qc2 W-1 / qc1
|
|
167
|
+
* W-1 / qc3 F-3): resolving the model's declared efforts on EVERY advisor
|
|
168
|
+
* call would add an adapter round-trip per delta; one resolution per
|
|
169
|
+
* (provider, model) per runtime suffices — the route is pinned for the
|
|
170
|
+
* runtime's lifetime.
|
|
171
|
+
*/
|
|
172
|
+
private readonly reasoningEffortCache;
|
|
173
|
+
private readonly queue;
|
|
174
|
+
private state;
|
|
175
|
+
private draining;
|
|
176
|
+
private disposed;
|
|
177
|
+
private consecutiveDrops;
|
|
178
|
+
private drainPromise;
|
|
179
|
+
/** Epoch-ms of the last note accepted by the emission guard (T7 status). */
|
|
180
|
+
private lastActivityAt;
|
|
181
|
+
constructor(options: AdvisorRuntimeOptions);
|
|
182
|
+
/** Current per-session status (T7 `/advisor status` surface). */
|
|
183
|
+
status(): AdvisorRuntimeStatus;
|
|
184
|
+
/** Number of deltas waiting to be drained (bounded by `maxQueued`). */
|
|
185
|
+
get pendingCount(): number;
|
|
186
|
+
/**
|
|
187
|
+
* T7 `/advisor status` surface — epoch-ms of the last note accepted by the
|
|
188
|
+
* emission guard, or `undefined` before the first accepted note.
|
|
189
|
+
*/
|
|
190
|
+
get lastActivity(): number | undefined;
|
|
191
|
+
/**
|
|
192
|
+
* Queue one rendered transcript delta (from the T3 observer's `onDelta`).
|
|
193
|
+
* While `quota_exhausted`, deltas queue up (bounded) but the drain is never
|
|
194
|
+
* auto-restarted (KD-5 — no auto-resume timer); while `halted`/disposed they
|
|
195
|
+
* are dropped with a log. Never throws, never parks the caller.
|
|
196
|
+
*/
|
|
197
|
+
enqueue(delta: Delta): void;
|
|
198
|
+
/** Abort the in-flight call and stop the drain (wiring: session/agent disposed). */
|
|
199
|
+
dispose(): void;
|
|
200
|
+
/**
|
|
201
|
+
* Manual resume after a quota pause (T7 `/advisor on`); no-op when halted/
|
|
202
|
+
* disposed — a halted runtime is terminal in place and is recovered by the
|
|
203
|
+
* command layer via dispose-and-recreate (qc1/qc2/qc3 W-1/I-4), never
|
|
204
|
+
* resumed here.
|
|
205
|
+
*/
|
|
206
|
+
resume(): void;
|
|
207
|
+
/**
|
|
208
|
+
* KD-5 reset trigger: a compaction / surface rewrite clears the emission
|
|
209
|
+
* guard's dedupe history and per-update latch — the session state is being
|
|
210
|
+
* rewritten, so the old note history no longer applies (a note already
|
|
211
|
+
* advised before the rewrite may legitimately be advised again). The wiring
|
|
212
|
+
* (`index.ts` onRewrite) calls this alongside the delivery cooldown reset.
|
|
213
|
+
*/
|
|
214
|
+
resetGuard(): void;
|
|
215
|
+
/** Resolve once the current drain run settles (test/integration hook). */
|
|
216
|
+
waitForDrain(): Promise<void>;
|
|
217
|
+
private kickDrain;
|
|
218
|
+
/** Serialized drain loop: process the queue one delta at a time. */
|
|
219
|
+
private drain;
|
|
220
|
+
/** KD-5: clear the pending backlog after consecutive failures (never stall). */
|
|
221
|
+
private flushBacklog;
|
|
222
|
+
/** Process one delta: attempt + single retry (transient), classify terminal failures. */
|
|
223
|
+
private processDelta;
|
|
224
|
+
/** One model call: build options, stream text, extract the note (KD-2). */
|
|
225
|
+
private callModel;
|
|
226
|
+
private buildOptions;
|
|
227
|
+
/**
|
|
228
|
+
* Capability-gate the `reasoningEffort: 'off'` option (qc2 W-1 / qc1 W-1 /
|
|
229
|
+
* qc3 F-3): pass the branded 'off' effort only when the resolved model's
|
|
230
|
+
* `reasoning.efforts` includes it, else omit the option (`resolveCallFor`
|
|
231
|
+
* materializes the adapter default). Cached per (provider, model) — one
|
|
232
|
+
* resolution per runtime, never per call. A resolution failure (unknown
|
|
233
|
+
* route, adapter throw, or a deadline abort — n4 QC N-5) is advisory: the
|
|
234
|
+
* call proceeds WITHOUT the option, matching the pre-n4 behavior for every
|
|
235
|
+
* model that does not declare 'off'. The optional `signal` (the call's
|
|
236
|
+
* deadline signal) is threaded into `resolveModelInfo`, whose contract
|
|
237
|
+
* allows adapter-owned asynchronous lookup with cancellation — a hung
|
|
238
|
+
* lookup that honors the signal aborts with the call deadline instead of
|
|
239
|
+
* wedging the drain.
|
|
240
|
+
*/
|
|
241
|
+
private resolveReasoningEffort;
|
|
242
|
+
}
|