dsh-context 0.22.0 → 0.22.2
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/lib/client.js +2361 -2307
- package/lib/client.js.map +1 -0
- package/lib/index.d.ts +584 -0
- package/lib/index.js +746 -636
- package/package.json +19 -12
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
import { ZodType, z } from "zod";
|
|
2
|
+
import { Session, SessionEvent } from "@deepseek-ai/dsh-session";
|
|
3
|
+
import { Context, Service } from "@deepseek-ai/cordis";
|
|
4
|
+
//#region src/host/config.d.ts
|
|
5
|
+
/** dsh-context host config. All fields optional; defaults below. */
|
|
6
|
+
interface Config {
|
|
7
|
+
/** Cap on kept per-step request records (the hard step backstop). */
|
|
8
|
+
maxRequestSteps?: number;
|
|
9
|
+
/** Newest whole-turn window kept; trimming crosses whole turns, never mid-turn. */
|
|
10
|
+
maxKeptTurns?: number;
|
|
11
|
+
/** Newest context-event records kept. */
|
|
12
|
+
maxEvents?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Surface nodes served to the browser (newest carry the signal; live
|
|
15
|
+
* inject nodes are always served — they are few and land first). The
|
|
16
|
+
* default is deliberately generous: auto-compaction keeps the live
|
|
17
|
+
* surface far below it in healthy sessions, so the browser effectively
|
|
18
|
+
* lists EVERY live node; the bound stays as a backstop for pathological
|
|
19
|
+
* sessions (every projection push ships the whole value, ~150B per node).
|
|
20
|
+
*/
|
|
21
|
+
maxNodes?: number;
|
|
22
|
+
/** Removed (shadowed) surface nodes kept for per-step reconstruction. */
|
|
23
|
+
maxArchiveNodes?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The cordis `Config` validator: strict on keys, defaults on the schema fields.
|
|
27
|
+
* Tolerates `undefined` (a patch row without a `config:` block — defaults win).
|
|
28
|
+
*/
|
|
29
|
+
declare const Config: z.ZodPreprocess<z.ZodObject<{
|
|
30
|
+
maxRequestSteps: z.ZodDefault<z.ZodNumber>;
|
|
31
|
+
maxKeptTurns: z.ZodDefault<z.ZodNumber>;
|
|
32
|
+
maxEvents: z.ZodDefault<z.ZodNumber>;
|
|
33
|
+
maxNodes: z.ZodDefault<z.ZodNumber>;
|
|
34
|
+
maxArchiveNodes: z.ZodDefault<z.ZodNumber>;
|
|
35
|
+
}, z.core.$strict>>;
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/shared/types.d.ts
|
|
38
|
+
/**
|
|
39
|
+
* Shared wire contract — the snapshot model exchanged between the Host and
|
|
40
|
+
* Client halves.
|
|
41
|
+
*
|
|
42
|
+
* The Host half no longer serves this over a custom RPC channel: it is the
|
|
43
|
+
* `view()` payload of the `contextTimeline` session projection, registered on
|
|
44
|
+
* the harness's `ctx.sessionProjections` registry. The registry drives
|
|
45
|
+
* `apply(state, event)` over every committed session event, persists the state
|
|
46
|
+
* through `ctx.sessionProjectionCache`, and pushes the finished value to the
|
|
47
|
+
* browser as a `session/projection` frame (with a tail-page baseline), where
|
|
48
|
+
* the Client reads it through the framework-standard `useProjection` seat.
|
|
49
|
+
*
|
|
50
|
+
* TYPE-ONLY host-side module: both halves import these as `import type`, so
|
|
51
|
+
* nothing from here ever reaches the runtime bundles.
|
|
52
|
+
*/
|
|
53
|
+
declare module '@deepseek-ai/dsh-session-projection/types' {
|
|
54
|
+
interface SessionProjectionMap {
|
|
55
|
+
/**
|
|
56
|
+
* The plugin's whole-value context timeline: current composition,
|
|
57
|
+
* per-request history, context events, and the model-visible surface.
|
|
58
|
+
* The Host folds it from the session log; clients receive the finished
|
|
59
|
+
* value (key absence = the plugin's host half is not composed).
|
|
60
|
+
*/
|
|
61
|
+
contextTimeline: ContextTimeline;
|
|
62
|
+
/**
|
|
63
|
+
* The request-header CONTENT epochs (full system prompt + tool schemas)
|
|
64
|
+
* behind the timeline's envelope figures. A separate unit so the hot
|
|
65
|
+
* `contextTimeline` value stays lean: headers change rarely, so this
|
|
66
|
+
* value (and its pushes) change only when a `request/header` lands.
|
|
67
|
+
* The Context browser card reads it to show the actual prompt/schema
|
|
68
|
+
* content of a picked step (key absence = older host: tokens only).
|
|
69
|
+
*/
|
|
70
|
+
contextHeaders: ContextHeaders;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** The five priced context categories (plus system/tools handled separately). */
|
|
74
|
+
type Category = 'user' | 'inject' | 'assistant' | 'tool';
|
|
75
|
+
interface Snapshot {
|
|
76
|
+
ok: boolean;
|
|
77
|
+
model?: string;
|
|
78
|
+
provider?: string;
|
|
79
|
+
contextWindow?: number;
|
|
80
|
+
current: {
|
|
81
|
+
system: number;
|
|
82
|
+
tools: number;
|
|
83
|
+
user: number;
|
|
84
|
+
inject: number;
|
|
85
|
+
assistant: number;
|
|
86
|
+
tool: number;
|
|
87
|
+
total: number;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Provider-anchored occupancy of the NEXT request. LEGACY since 0.11: the
|
|
91
|
+
* Host no longer folds this — the Client reads the official token-meter
|
|
92
|
+
* `contextPressure` projection key (`useProjection('contextPressure')`)
|
|
93
|
+
* instead. Kept optional for wire compatibility with older clients.
|
|
94
|
+
*/
|
|
95
|
+
occupancy?: {
|
|
96
|
+
/** Provider-reported prompt size of the most recent request (input + cache). */
|
|
97
|
+
pressureTokens?: number;
|
|
98
|
+
/** Heuristic total over the current model-visible surface. */
|
|
99
|
+
surfaceTokens: number;
|
|
100
|
+
/** `surfaceTokens` at the newest usage sample. */
|
|
101
|
+
sampledSurfaceTokens?: number;
|
|
102
|
+
/** pressureTokens + surface movement since the sample (clamped ≥ 0). */
|
|
103
|
+
projectedTokens?: number;
|
|
104
|
+
/** Newest recorded route capacity (last-wins). */
|
|
105
|
+
contextWindow?: number;
|
|
106
|
+
};
|
|
107
|
+
toolList: {
|
|
108
|
+
name: string;
|
|
109
|
+
tokens: number;
|
|
110
|
+
}[];
|
|
111
|
+
requests: RequestRecord[];
|
|
112
|
+
events: ContextEventRecord[];
|
|
113
|
+
/**
|
|
114
|
+
* Cumulative session-cost raw material (per-family, per-period billed
|
|
115
|
+
* token totals — see SessionCostUsage). Absent until a DeepSeek V4
|
|
116
|
+
* request reports usage.
|
|
117
|
+
*/
|
|
118
|
+
cost?: SessionCostUsage;
|
|
119
|
+
/**
|
|
120
|
+
* The served live surface: the newest `maxNodes` tail PLUS every live
|
|
121
|
+
* inject node older than the tail (injections land first and are few, so
|
|
122
|
+
* they are pinned — otherwise a long session would price them while the
|
|
123
|
+
* browser could list none). Seq-ordered, oldest first.
|
|
124
|
+
*/
|
|
125
|
+
nodes: SurfaceNode[];
|
|
126
|
+
/** Live nodes not served (the overflow beyond `maxNodes`, minus pinned injects — see `nodes`). */
|
|
127
|
+
droppedNodes: number;
|
|
128
|
+
/**
|
|
129
|
+
* Recently REMOVED surface nodes (compaction/prune shadows), each stamped
|
|
130
|
+
* with `gone` (the replacing event's seq). Together with `nodes` this lets
|
|
131
|
+
* the Context browser reconstruct the assembled surface of any retained
|
|
132
|
+
* step: alive at request R = seq < R.seq && (gone undefined || gone > R.seq).
|
|
133
|
+
*/
|
|
134
|
+
archive: SurfaceNode[];
|
|
135
|
+
/**
|
|
136
|
+
* Coverage floor of the served live `nodes`: the newest seq among the
|
|
137
|
+
* `droppedNodes` live nodes not served. Present only when droppedNodes > 0.
|
|
138
|
+
*/
|
|
139
|
+
surfaceFloor?: number;
|
|
140
|
+
/**
|
|
141
|
+
* Coverage floor of `archive`: the newest `gone` among archive entries the
|
|
142
|
+
* retention bounds dropped. Steps with seq < archiveFloor may miss removed
|
|
143
|
+
* nodes (the browser shows the reconstruction as approximate).
|
|
144
|
+
*/
|
|
145
|
+
archiveFloor?: number;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The `contextTimeline` projection's whole value — the same snapshot the
|
|
149
|
+
* Client has always rendered, now delivered through the session-projection
|
|
150
|
+
* pipeline. `ok` is always `true` here (a delivered projection is by
|
|
151
|
+
* definition available); it is kept for wire compatibility with the
|
|
152
|
+
* snapshot shape.
|
|
153
|
+
*/
|
|
154
|
+
type ContextTimeline = Snapshot;
|
|
155
|
+
/**
|
|
156
|
+
* Cumulative billed-token totals for one pricing bucket of the session-cost
|
|
157
|
+
* estimate (host-folded, never trimmed — running totals over the COMPLETE
|
|
158
|
+
* session log, immune to the request/event retention bounds).
|
|
159
|
+
*/
|
|
160
|
+
interface CostBucketTotals {
|
|
161
|
+
/** Billed prompt tokens that missed the provider cache. */
|
|
162
|
+
uncached: number;
|
|
163
|
+
/** Billed prompt tokens served from the provider cache. */
|
|
164
|
+
cacheRead: number;
|
|
165
|
+
/** Billed prompt tokens written into the provider cache. */
|
|
166
|
+
cacheWrite: number;
|
|
167
|
+
/** Billed output tokens (reasoning included). */
|
|
168
|
+
output: number;
|
|
169
|
+
}
|
|
170
|
+
/** One model family's totals split by DeepSeek's UTC pricing period. */
|
|
171
|
+
interface CostFamilyUsage {
|
|
172
|
+
/** Peak windows: 01:00-04:00 and 06:00-10:00 UTC. */
|
|
173
|
+
peak?: CostBucketTotals;
|
|
174
|
+
/** All other hours (half the peak rate). */
|
|
175
|
+
off?: CostBucketTotals;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* The session-cost estimate's raw material: cumulative provider-reported
|
|
179
|
+
* token totals per DeepSeek V4 model family (matched on the model NAME,
|
|
180
|
+
* provider-agnostic) and pricing period. The Client prices these with its
|
|
181
|
+
* hardcoded list-price table in the locale's currency. Absent until a
|
|
182
|
+
* deepseek-v4-flash / deepseek-v4-pro request reports usage.
|
|
183
|
+
*/
|
|
184
|
+
interface SessionCostUsage {
|
|
185
|
+
flash?: CostFamilyUsage;
|
|
186
|
+
pro?: CostFamilyUsage;
|
|
187
|
+
}
|
|
188
|
+
/** One model-visible message on the surface, with its heuristic token price. */
|
|
189
|
+
interface SurfaceNode {
|
|
190
|
+
seq: number;
|
|
191
|
+
/** Event timestamp (ms epoch); the Client shows it when present. */
|
|
192
|
+
time?: number;
|
|
193
|
+
cat: Category;
|
|
194
|
+
tokens: number;
|
|
195
|
+
/**
|
|
196
|
+
* Removal marker, present only on `archive` entries: the seq of the
|
|
197
|
+
* replacement surface event that shadowed this node (compaction/prune).
|
|
198
|
+
* The node is part of the assembled context of every request with
|
|
199
|
+
* seq > this node.seq and seq < gone.
|
|
200
|
+
*/
|
|
201
|
+
gone?: number;
|
|
202
|
+
form?: string;
|
|
203
|
+
text?: string;
|
|
204
|
+
tool?: string;
|
|
205
|
+
err?: boolean;
|
|
206
|
+
skill?: string;
|
|
207
|
+
calls?: string[];
|
|
208
|
+
}
|
|
209
|
+
/** One answered model call (a step); consecutive records of one turn form it. */
|
|
210
|
+
interface RequestRecord {
|
|
211
|
+
turn?: number;
|
|
212
|
+
step?: number;
|
|
213
|
+
time: number;
|
|
214
|
+
seq: number;
|
|
215
|
+
system: number;
|
|
216
|
+
tools: number;
|
|
217
|
+
user: number;
|
|
218
|
+
inject: number;
|
|
219
|
+
assistant: number;
|
|
220
|
+
tool: number;
|
|
221
|
+
total: number;
|
|
222
|
+
prompt?: number;
|
|
223
|
+
output?: number;
|
|
224
|
+
/**
|
|
225
|
+
* Turn-mode aggregate marker, set by the Client's aggregateByTurn (one bar
|
|
226
|
+
* per turn shows its LAST step's record). The Host never sets it.
|
|
227
|
+
*/
|
|
228
|
+
stepCount?: number;
|
|
229
|
+
}
|
|
230
|
+
/** A notable context event (compaction, prune, injection, model switch). */
|
|
231
|
+
interface ContextEventRecord {
|
|
232
|
+
seq: number;
|
|
233
|
+
time: number;
|
|
234
|
+
kind: 'compaction' | 'prune' | 'inject' | 'model';
|
|
235
|
+
form?: string;
|
|
236
|
+
tokens?: number;
|
|
237
|
+
count?: number;
|
|
238
|
+
sub?: string;
|
|
239
|
+
name?: string;
|
|
240
|
+
from?: string;
|
|
241
|
+
to?: string;
|
|
242
|
+
/** Turn/step of the request logged right BEFORE the event (host-stamped). */
|
|
243
|
+
fromTurn?: number;
|
|
244
|
+
fromStep?: number;
|
|
245
|
+
/** Turn/step of the request this event contributed to (host-stamped). */
|
|
246
|
+
turn?: number;
|
|
247
|
+
step?: number;
|
|
248
|
+
}
|
|
249
|
+
/** One tool schema as assembled into a request header, with its display price. */
|
|
250
|
+
interface HeaderTool {
|
|
251
|
+
name: string;
|
|
252
|
+
tokens: number;
|
|
253
|
+
/** Producer-declared description (may be long; the browser truncates). */
|
|
254
|
+
description?: string;
|
|
255
|
+
/** The raw JSON schema object the model received (plain JSON). */
|
|
256
|
+
schema?: unknown;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* One request-header epoch: the full system prompt and tool schemas in force
|
|
260
|
+
* from this event's seq until the next epoch. Headers change rarely (the loop
|
|
261
|
+
* only logs `request/header` on change), so this unit's pushes are rare and
|
|
262
|
+
* carrying full content is cheap.
|
|
263
|
+
*/
|
|
264
|
+
interface HeaderRecord {
|
|
265
|
+
seq: number;
|
|
266
|
+
time: number;
|
|
267
|
+
system?: string;
|
|
268
|
+
tools: HeaderTool[];
|
|
269
|
+
}
|
|
270
|
+
/** The `contextHeaders` projection value: the bounded epoch list (newest last). */
|
|
271
|
+
interface ContextHeaders {
|
|
272
|
+
headers: HeaderRecord[];
|
|
273
|
+
}
|
|
274
|
+
//#endregion
|
|
275
|
+
//#region src/host/fold.d.ts
|
|
276
|
+
/**
|
|
277
|
+
* History retention bounds (configurable since 0.11 — see config.ts; these
|
|
278
|
+
* are the defaults' values). The fold keeps per-STEP request records; once the
|
|
279
|
+
* newest run count exceeds `maxKeptTurns`, the timeline is trimmed to the
|
|
280
|
+
* most recent whole TURN runs (never cutting a turn in half), so turn
|
|
281
|
+
* granularity can always show the full recent turn range instead of a
|
|
282
|
+
* step-count fragment. The turn-run trim runs whenever the cap is crossed
|
|
283
|
+
* (not only when the raw step bound is), so the bounded state stays at the
|
|
284
|
+
* newest ~`maxKeptTurns` turns deterministically as a live log grows.
|
|
285
|
+
*/
|
|
286
|
+
/** The projection unit's persisted state (plain JSON, bounded see above). */
|
|
287
|
+
interface TimelineState {
|
|
288
|
+
/** Model-visible surface, newest last. */
|
|
289
|
+
surface: SurfaceNode[];
|
|
290
|
+
/** Live per-category token sums over the surface. */
|
|
291
|
+
sums: Record<Category, number>;
|
|
292
|
+
systemTokens: number;
|
|
293
|
+
toolsTokens: number;
|
|
294
|
+
toolList: {
|
|
295
|
+
name: string;
|
|
296
|
+
tokens: number;
|
|
297
|
+
}[];
|
|
298
|
+
/**
|
|
299
|
+
* The projection-cache precondition is plain JSON: a property whose value
|
|
300
|
+
* is `undefined` makes the whole checkpoint unserializable
|
|
301
|
+
* (`snapshotJsonValue` rejects it), which fails EVERY cache write for the
|
|
302
|
+
* session — including the `title` projection row that powers the session
|
|
303
|
+
* list after a restart. Optional fields therefore use absent properties
|
|
304
|
+
* (`model`/`provider`/`lastModel`/`contextWindow` are simply not set until
|
|
305
|
+
* a value is known) instead of `undefined`-valued ones. Reads via
|
|
306
|
+
* `state.model` are identical for both shapes (`undefined` on miss).
|
|
307
|
+
*/
|
|
308
|
+
model?: string;
|
|
309
|
+
provider?: string;
|
|
310
|
+
lastModel?: string;
|
|
311
|
+
contextWindow?: number;
|
|
312
|
+
requests: RequestRecord[];
|
|
313
|
+
events: ContextEventRecord[];
|
|
314
|
+
/**
|
|
315
|
+
* Recently removed surface nodes (stamped COPIES carrying `gone`), in
|
|
316
|
+
* removal order. Feeds the Context browser's per-step reconstruction.
|
|
317
|
+
* Bounded two ways in trimState: capped to `maxArchiveNodes`, and pruned
|
|
318
|
+
* to removals after the oldest retained request (older removals can only
|
|
319
|
+
* serve steps the requests trim already forgot).
|
|
320
|
+
*/
|
|
321
|
+
archived: SurfaceNode[];
|
|
322
|
+
/**
|
|
323
|
+
* Session-cost raw material: cumulative billed-token totals per DeepSeek
|
|
324
|
+
* V4 model family and pricing period (see SessionCostUsage). Running
|
|
325
|
+
* totals — never trimmed, so the estimate always covers the COMPLETE
|
|
326
|
+
* session log even after the request/event retention bounds cut in.
|
|
327
|
+
* Absent until a v4-flash / v4-pro request reports usage.
|
|
328
|
+
*/
|
|
329
|
+
cost?: SessionCostUsage;
|
|
330
|
+
/** Newest `gone` among archive entries dropped by the retention bounds. */
|
|
331
|
+
archiveFloor?: number;
|
|
332
|
+
callNames: Record<string, string>;
|
|
333
|
+
/**
|
|
334
|
+
* Seq list of the surface nodes the next replacement will shadow, armed by
|
|
335
|
+
* the metering event (`compaction/summary` | `compaction/prune`) and
|
|
336
|
+
* consumed by the replacement that must follow it synchronously. The
|
|
337
|
+
* producer's shadow price covers exactly these seqs — which can differ
|
|
338
|
+
* from the replacement's declared range (pruned replacement nodes keep
|
|
339
|
+
* their own seqs, beyond the range end) — so removal must follow the seqs.
|
|
340
|
+
* Absent until armed, and REMOVED (not set to `undefined`) when consumed,
|
|
341
|
+
* to keep the state plain JSON for the projection cache.
|
|
342
|
+
*/
|
|
343
|
+
pendingShadowedSeqs?: number[];
|
|
344
|
+
}
|
|
345
|
+
//#endregion
|
|
346
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-session-projection@0.1.0-rc.8_@deepseek-ai+cordis@4.0.1_@deepseek-ai+d_c0ffab70d18276fadfe75ed916313ea3/node_modules/@deepseek-ai/dsh-session-projection/lib/types/types.d.ts
|
|
347
|
+
/**
|
|
348
|
+
* Pure-type outlet of the session-projection Service Definition: the one projection type
|
|
349
|
+
* table, importable from client aggregates without dragging the host-side
|
|
350
|
+
* cordis Context merges of the package root (dsh-agent → dsh-session). Domain
|
|
351
|
+
* packages may declare-merge through either the package root or this outlet —
|
|
352
|
+
* re-export preserves symbol identity, so both land on the same table.
|
|
353
|
+
*
|
|
354
|
+
* @module @deepseek-ai/dsh-session-projection/types
|
|
355
|
+
*/
|
|
356
|
+
/**
|
|
357
|
+
* The single projection type table for the whole chain (host provider, wire
|
|
358
|
+
* block, client cell, React hook). Domain packages merge their key here via
|
|
359
|
+
* declaration merging; values are wire-JSON whole values. How a value is
|
|
360
|
+
* rendered is the slot system's business, never this layer's.
|
|
361
|
+
*/
|
|
362
|
+
interface SessionProjectionMap {}
|
|
363
|
+
//#endregion
|
|
364
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-session-projection@0.1.0-rc.8_@deepseek-ai+cordis@4.0.1_@deepseek-ai+d_c0ffab70d18276fadfe75ed916313ea3/node_modules/@deepseek-ai/dsh-session-projection/lib/types/index.d.ts
|
|
365
|
+
declare module '@deepseek-ai/cordis' {
|
|
366
|
+
interface Context {
|
|
367
|
+
sessionProjections: SessionProjectionRegistry;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* One domain's state-driven computation unit: three pure synchronous
|
|
372
|
+
* functions plus declarations — never an opaque getter. The framework drives
|
|
373
|
+
* `apply` on every committed session event; the domain holds no
|
|
374
|
+
* subscriptions and owns only the mathematics. All three functions MUST be
|
|
375
|
+
* synchronous (an async unit would tear the carriers' consistency cut) and
|
|
376
|
+
* `state` MUST be plain JSON (the persisted-cache precondition).
|
|
377
|
+
*/
|
|
378
|
+
interface ProjectionDefinition<K extends keyof SessionProjectionMap, S> {
|
|
379
|
+
/** The projection key this unit owns (its `SessionProjectionMap` entry). */
|
|
380
|
+
key: K;
|
|
381
|
+
/** Validates the wire payload (`view` output) before it leaves the host. */
|
|
382
|
+
schema: ZodType<SessionProjectionMap[K]>;
|
|
383
|
+
/**
|
|
384
|
+
* State for the empty log.
|
|
385
|
+
* @returns the initial state.
|
|
386
|
+
*/
|
|
387
|
+
init(): S;
|
|
388
|
+
/**
|
|
389
|
+
* Pure transition: previous state + one committed event → next state. A
|
|
390
|
+
* unit uninterested in an event MUST return the same state reference — an
|
|
391
|
+
* unchanged reference (`Object.is`) produces zero downstream work.
|
|
392
|
+
* @param state - the state covering all prior events.
|
|
393
|
+
* @param event - the next committed session event.
|
|
394
|
+
* @returns the next state (same reference when the event is not the unit's).
|
|
395
|
+
*/
|
|
396
|
+
apply(state: S, event: SessionEvent): S;
|
|
397
|
+
/**
|
|
398
|
+
* State → wire payload (the read-side projection).
|
|
399
|
+
* @param state - the current state.
|
|
400
|
+
* @returns the whole current value for this unit's key.
|
|
401
|
+
*/
|
|
402
|
+
view(state: S): SessionProjectionMap[K];
|
|
403
|
+
/**
|
|
404
|
+
* Persisted-cache invalidation version: bump whenever the serialized state fields or the
|
|
405
|
+
* fold semantics change, so persisted `(sessionId, key, ver, seq, val)`
|
|
406
|
+
* rows from an older unit are discarded instead of being forward-applied
|
|
407
|
+
* into garbage. Non-negative integer.
|
|
408
|
+
*/
|
|
409
|
+
stateVersion: number;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Change-feed listener: one unit's value changed for one session. `value` is
|
|
413
|
+
* the schema-validated `view` output; `seq` is the unit's watermark at
|
|
414
|
+
* emission (the seq of the event that caused the change).
|
|
415
|
+
*/
|
|
416
|
+
type ProjectionChangeListener = (session: Session, key: Extract<keyof SessionProjectionMap, string>, value: unknown, seq: number) => void;
|
|
417
|
+
/**
|
|
418
|
+
* One consistent read cut over every registered unit for one session.
|
|
419
|
+
* `asOfSeq` is the shared watermark — the seq of the last event every value
|
|
420
|
+
* reflects (`-1` for an empty log, mirroring `session/subscribed.lastSeq`).
|
|
421
|
+
*/
|
|
422
|
+
interface ProjectionSnapshot {
|
|
423
|
+
/** Seq of the last event the values reflect; -1 for an empty log. */
|
|
424
|
+
asOfSeq: number;
|
|
425
|
+
/** Whole current value per registered key. */
|
|
426
|
+
values: Partial<SessionProjectionMap>;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* One unit's checkpoint: its internal state (plain JSON by the unit
|
|
430
|
+
* contract), the seq of the last event folded into it, and the unit
|
|
431
|
+
* `stateVersion` that produced it — the persisted projection-cache row
|
|
432
|
+
* `(sessionId, key, ver, seq, val)` minus the two outer keys. A row is
|
|
433
|
+
* never authoritative, only a fold shortcut: `restore` discards it on a
|
|
434
|
+
* version mismatch or when it claims events past the stored log end.
|
|
435
|
+
*/
|
|
436
|
+
interface ProjectionCheckpointRow {
|
|
437
|
+
/** The registering unit's `stateVersion` at fold time. */
|
|
438
|
+
ver: number;
|
|
439
|
+
/** Seq of the last event folded into `val`; -1 for the empty log. */
|
|
440
|
+
seq: number;
|
|
441
|
+
/** The unit's internal state — plain JSON per the unit contract. */
|
|
442
|
+
val: unknown;
|
|
443
|
+
}
|
|
444
|
+
/** Checkpoint rows keyed by projection key (one session's persisted cache value). */
|
|
445
|
+
type ProjectionCheckpoint = Record<string, ProjectionCheckpointRow>;
|
|
446
|
+
/**
|
|
447
|
+
* `ctx.sessionProjections`: the projection unit table and its drive. The
|
|
448
|
+
* service subscribes to `session/event` once; every committed event passes
|
|
449
|
+
* every registered unit's `apply` (eager drive), and a changed state
|
|
450
|
+
* reference notifies the change feed with the schema-validated view.
|
|
451
|
+
* Cells build lazily — a unit registered after events flowed, or a session
|
|
452
|
+
* older than the registry, folds `init` over the in-memory log on first
|
|
453
|
+
* touch (event or read). Registration is an effect (disposer rides the
|
|
454
|
+
* calling fiber): an unloaded domain plugin's key disappears from snapshots
|
|
455
|
+
* and clients read it as capability absence. Domain
|
|
456
|
+
* plugins register under `ctx.inject(['sessionProjections'], …)` so headless
|
|
457
|
+
* assemblies without the registry stay unaffected. Registrants sharing a key
|
|
458
|
+
* share one unit and are counted: the same tool package mounted in N agent
|
|
459
|
+
* presets registers N times, and the key survives until the last one
|
|
460
|
+
* unloads.
|
|
461
|
+
*/
|
|
462
|
+
declare class SessionProjectionRegistry extends Service {
|
|
463
|
+
private readonly registrations;
|
|
464
|
+
private readonly listeners;
|
|
465
|
+
/**
|
|
466
|
+
* Create and install the registry as `ctx.sessionProjections`.
|
|
467
|
+
* @param ctx - Cordis context that owns the service.
|
|
468
|
+
*/
|
|
469
|
+
constructor(ctx: Context);
|
|
470
|
+
/**
|
|
471
|
+
* Register one domain's unit. The registration is an effect on the calling
|
|
472
|
+
* context's fiber: disposing the fiber (or calling the returned disposer)
|
|
473
|
+
* removes the key — and the unit's cached cells — from subsequent drives
|
|
474
|
+
* and snapshots.
|
|
475
|
+
* @param definition - key, state schema, pure unit functions, and stateVersion.
|
|
476
|
+
* @returns the exact disposer that unregisters this unit.
|
|
477
|
+
*/
|
|
478
|
+
register<K extends keyof SessionProjectionMap, S>(definition: ProjectionDefinition<K, S>): () => void;
|
|
479
|
+
/**
|
|
480
|
+
* Subscribe to the change feed. The registration is an effect on the
|
|
481
|
+
* calling context's fiber.
|
|
482
|
+
* @param listener - called once per unit whose state reference changed, per committed event.
|
|
483
|
+
* @returns the exact disposer that unsubscribes.
|
|
484
|
+
*/
|
|
485
|
+
onChanged(listener: ProjectionChangeListener): () => void;
|
|
486
|
+
/**
|
|
487
|
+
* One consistent cut over every registered unit for one session, read from
|
|
488
|
+
* the watermark cache (missing cells fold lazily over the in-memory log).
|
|
489
|
+
* Fully synchronous — every value and `asOfSeq` reflect the same log
|
|
490
|
+
* position. Each value passes its unit's schema before leaving.
|
|
491
|
+
* @param session - the session whose projection values are read.
|
|
492
|
+
* @returns the snapshot; `values` is empty when no unit is registered.
|
|
493
|
+
*/
|
|
494
|
+
snapshot(session: Session): ProjectionSnapshot;
|
|
495
|
+
/**
|
|
496
|
+
* State-level checkpoint of every registered unit for one session, read
|
|
497
|
+
* from the watermark cache (missing cells fold lazily over the in-memory
|
|
498
|
+
* log). This is the write side of the persisted projection cache: the
|
|
499
|
+
* returned rows are the `(key → {ver, seq, val})` part of the durable
|
|
500
|
+
* `(sessionId, key, ver, seq, val)`
|
|
501
|
+
* rows. Every `val` is a DETACHED structured clone — never the live
|
|
502
|
+
* cell reference: the watermark cache is this registry's authoritative
|
|
503
|
+
* mutable state, and a caller reaching the live reference could corrupt
|
|
504
|
+
* every subsequent snapshot and frame through it (plain JSON by the unit
|
|
505
|
+
* contract, so the clone is total).
|
|
506
|
+
* @param session - the session whose unit states are checkpointed.
|
|
507
|
+
* @returns one row per registered key; empty when no unit is registered.
|
|
508
|
+
*/
|
|
509
|
+
checkpoint(session: Session): ProjectionCheckpoint;
|
|
510
|
+
/**
|
|
511
|
+
* The stored seq a {@link restore} tail read over `checkpoint` must start
|
|
512
|
+
* at: one event BELOW the lowest usable watermark (a row is usable when
|
|
513
|
+
* its `ver` matches the live unit's `stateVersion`; an absent or mismatched row
|
|
514
|
+
* pulls the floor to `0` — that key must refold the full log). The
|
|
515
|
+
* one-below anchor is load-bearing: the tail then proves how far the
|
|
516
|
+
* stored log still extends, so {@link restore} can detect a log that
|
|
517
|
+
* shrank below a row's watermark (crash-repair truncation) instead of
|
|
518
|
+
* serving the stale row as current — an empty tail read from the anchor
|
|
519
|
+
* yields an end below every watermark and the restore rejects for a full
|
|
520
|
+
* re-read.
|
|
521
|
+
* @param checkpoint - persisted rows for one session (possibly stale or empty).
|
|
522
|
+
* @returns the seq to hand the persistence `readFrom`, or `undefined`
|
|
523
|
+
* when no unit is registered (no read needed — {@link restore} would
|
|
524
|
+
* serve empty values regardless).
|
|
525
|
+
*/
|
|
526
|
+
restoreFloor(checkpoint: ProjectionCheckpoint): number | undefined;
|
|
527
|
+
/**
|
|
528
|
+
* View a checkpoint's rows without any log read: for every registered
|
|
529
|
+
* unit whose row's `ver` matches, serve the schema-validated
|
|
530
|
+
* `view` of the stored state; mismatched or absent rows leave their key
|
|
531
|
+
* absent (a cold or listing consumer treats it as not-yet-available and a
|
|
532
|
+
* fuller read path refolds it). The zero-I/O rung of the read ladder —
|
|
533
|
+
* values are as stale as their rows, never wrong.
|
|
534
|
+
* @param checkpoint - persisted rows for one session (possibly stale or empty).
|
|
535
|
+
* @returns whole values per key with a usable row; empty when none.
|
|
536
|
+
*/
|
|
537
|
+
viewCheckpoint(checkpoint: ProjectionCheckpoint): Partial<SessionProjectionMap>;
|
|
538
|
+
/**
|
|
539
|
+
* Cold read: fold every registered unit over a stored log suffix, seeding
|
|
540
|
+
* each from its checkpoint row when usable — the one read recipe (cached
|
|
541
|
+
* state + forward tail replay + `view`) applied without a live `Session`.
|
|
542
|
+
* Call with the events returned by a persistence
|
|
543
|
+
* `readFrom(id, restoreFloor(checkpoint))` and that same floor as
|
|
544
|
+
* `baseSeq`; the floor's one-below anchor makes the supplied end honest,
|
|
545
|
+
* so a shrunk log is detected here. A row is usable iff its
|
|
546
|
+
* `ver` matches the live unit's `stateVersion`, it does not predate `baseSeq`
|
|
547
|
+
* (`seq >= baseSeq - 1`), and it does not claim events past the
|
|
548
|
+
* supplied end (`seq <= endSeq`); an unusable row is discarded
|
|
549
|
+
* and its key refolds from `init` — which is only sound over the full
|
|
550
|
+
* log, so a discarded row with `baseSeq > 0` throws (the caller re-reads
|
|
551
|
+
* from seq 0, e.g. after a crash-repair truncation shrank the log below
|
|
552
|
+
* a row's watermark).
|
|
553
|
+
* @param checkpoint - persisted rows for one session (possibly stale or empty).
|
|
554
|
+
* @param events - the stored events with `seq >= baseSeq`, in seq order.
|
|
555
|
+
* @param baseSeq - the seq `events` starts at (its first event's seq when non-empty).
|
|
556
|
+
* @returns the snapshot cut at the supplied log end (`asOfSeq` is the last
|
|
557
|
+
* supplied event's seq, `baseSeq - 1` for an empty tail) plus the
|
|
558
|
+
* refreshed checkpoint rows at that cut, ready for a durable write-back.
|
|
559
|
+
*/
|
|
560
|
+
restore(checkpoint: ProjectionCheckpoint, events: readonly SessionEvent[], baseSeq: number): {
|
|
561
|
+
snapshot: ProjectionSnapshot;
|
|
562
|
+
checkpoint: ProjectionCheckpoint;
|
|
563
|
+
};
|
|
564
|
+
/** Fold one unit from init over `events`, producing a cell watermarked at the last folded event. */
|
|
565
|
+
private buildCell;
|
|
566
|
+
/** Read (or lazily build, folding the full in-memory log) one unit's cell. */
|
|
567
|
+
private cellFor;
|
|
568
|
+
/** Eager drive: pass one committed event through every registered unit; notify on changed references. */
|
|
569
|
+
private drive;
|
|
570
|
+
}
|
|
571
|
+
//#endregion
|
|
572
|
+
//#region src/host/headers.d.ts
|
|
573
|
+
/** The unit's persisted state (plain JSON, bounded). */
|
|
574
|
+
interface HeadersState {
|
|
575
|
+
headers: HeaderRecord[];
|
|
576
|
+
}
|
|
577
|
+
//#endregion
|
|
578
|
+
//#region src/host/index.d.ts
|
|
579
|
+
declare const name = "dsh-context";
|
|
580
|
+
/** Required services: the session-projection registry that drives the unit. */
|
|
581
|
+
declare const inject: string[];
|
|
582
|
+
declare function apply(ctx: Context, config: Config): void;
|
|
583
|
+
//#endregion
|
|
584
|
+
export { type Category, Config, type ContextEventRecord, type ContextHeaders, type ContextTimeline, type HeaderRecord, type HeaderTool, type HeadersState, type RequestRecord, type Snapshot, type SurfaceNode, type TimelineState, apply, inject, name };
|