@yadsh/dsh-kv-persist 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.
Files changed (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +110 -0
  3. package/compatibility.json +16 -0
  4. package/cordis.patch.yml +4 -0
  5. package/docs/dsh-kv-persist.md +3311 -0
  6. package/lib/backends/llama-cpp/backend.d.ts +20 -0
  7. package/lib/backends/llama-cpp/backend.d.ts.map +1 -0
  8. package/lib/backends/llama-cpp/backend.js +37 -0
  9. package/lib/backends/llama-cpp/backend.js.map +1 -0
  10. package/lib/backends/llama-cpp/client.d.ts +31 -0
  11. package/lib/backends/llama-cpp/client.d.ts.map +1 -0
  12. package/lib/backends/llama-cpp/client.js +120 -0
  13. package/lib/backends/llama-cpp/client.js.map +1 -0
  14. package/lib/backends/llama-cpp/discovery.d.ts +20 -0
  15. package/lib/backends/llama-cpp/discovery.d.ts.map +1 -0
  16. package/lib/backends/llama-cpp/discovery.js +33 -0
  17. package/lib/backends/llama-cpp/discovery.js.map +1 -0
  18. package/lib/backends/llama-cpp/types.d.ts +30 -0
  19. package/lib/backends/llama-cpp/types.d.ts.map +1 -0
  20. package/lib/backends/llama-cpp/types.js +9 -0
  21. package/lib/backends/llama-cpp/types.js.map +1 -0
  22. package/lib/backends/types.d.ts +52 -0
  23. package/lib/backends/types.d.ts.map +1 -0
  24. package/lib/backends/types.js +6 -0
  25. package/lib/backends/types.js.map +1 -0
  26. package/lib/config.d.ts +258 -0
  27. package/lib/config.d.ts.map +1 -0
  28. package/lib/config.js +169 -0
  29. package/lib/config.js.map +1 -0
  30. package/lib/coordinator/checkpoint-policy.d.ts +19 -0
  31. package/lib/coordinator/checkpoint-policy.d.ts.map +1 -0
  32. package/lib/coordinator/checkpoint-policy.js +40 -0
  33. package/lib/coordinator/checkpoint-policy.js.map +1 -0
  34. package/lib/coordinator/circuit-breaker.d.ts +29 -0
  35. package/lib/coordinator/circuit-breaker.d.ts.map +1 -0
  36. package/lib/coordinator/circuit-breaker.js +62 -0
  37. package/lib/coordinator/circuit-breaker.js.map +1 -0
  38. package/lib/coordinator/coordinator.d.ts +74 -0
  39. package/lib/coordinator/coordinator.d.ts.map +1 -0
  40. package/lib/coordinator/coordinator.js +542 -0
  41. package/lib/coordinator/coordinator.js.map +1 -0
  42. package/lib/coordinator/slot-lease.d.ts +22 -0
  43. package/lib/coordinator/slot-lease.d.ts.map +1 -0
  44. package/lib/coordinator/slot-lease.js +38 -0
  45. package/lib/coordinator/slot-lease.js.map +1 -0
  46. package/lib/coordinator/state-machine.d.ts +65 -0
  47. package/lib/coordinator/state-machine.d.ts.map +1 -0
  48. package/lib/coordinator/state-machine.js +40 -0
  49. package/lib/coordinator/state-machine.js.map +1 -0
  50. package/lib/errors.d.ts +82 -0
  51. package/lib/errors.d.ts.map +1 -0
  52. package/lib/errors.js +113 -0
  53. package/lib/errors.js.map +1 -0
  54. package/lib/index.d.ts +26 -0
  55. package/lib/index.d.ts.map +1 -0
  56. package/lib/index.js +17 -0
  57. package/lib/index.js.map +1 -0
  58. package/lib/logging/dsh-home.d.ts +13 -0
  59. package/lib/logging/dsh-home.d.ts.map +1 -0
  60. package/lib/logging/dsh-home.js +18 -0
  61. package/lib/logging/dsh-home.js.map +1 -0
  62. package/lib/logging/index.d.ts +10 -0
  63. package/lib/logging/index.d.ts.map +1 -0
  64. package/lib/logging/index.js +10 -0
  65. package/lib/logging/index.js.map +1 -0
  66. package/lib/logging/plugin-logger.d.ts +94 -0
  67. package/lib/logging/plugin-logger.d.ts.map +1 -0
  68. package/lib/logging/plugin-logger.js +397 -0
  69. package/lib/logging/plugin-logger.js.map +1 -0
  70. package/lib/observability/diagnostics.d.ts +37 -0
  71. package/lib/observability/diagnostics.d.ts.map +1 -0
  72. package/lib/observability/diagnostics.js +75 -0
  73. package/lib/observability/diagnostics.js.map +1 -0
  74. package/lib/observability/metrics.d.ts +38 -0
  75. package/lib/observability/metrics.d.ts.map +1 -0
  76. package/lib/observability/metrics.js +29 -0
  77. package/lib/observability/metrics.js.map +1 -0
  78. package/lib/service.d.ts +231 -0
  79. package/lib/service.d.ts.map +1 -0
  80. package/lib/service.js +232 -0
  81. package/lib/service.js.map +1 -0
  82. package/lib/snapshots/fingerprint.d.ts +53 -0
  83. package/lib/snapshots/fingerprint.d.ts.map +1 -0
  84. package/lib/snapshots/fingerprint.js +69 -0
  85. package/lib/snapshots/fingerprint.js.map +1 -0
  86. package/lib/snapshots/manifest.d.ts +51 -0
  87. package/lib/snapshots/manifest.d.ts.map +1 -0
  88. package/lib/snapshots/manifest.js +0 -0
  89. package/lib/snapshots/manifest.js.map +1 -0
  90. package/lib/snapshots/naming.d.ts +18 -0
  91. package/lib/snapshots/naming.d.ts.map +1 -0
  92. package/lib/snapshots/naming.js +33 -0
  93. package/lib/snapshots/naming.js.map +1 -0
  94. package/lib/snapshots/repository.d.ts +52 -0
  95. package/lib/snapshots/repository.d.ts.map +1 -0
  96. package/lib/snapshots/repository.js +270 -0
  97. package/lib/snapshots/repository.js.map +1 -0
  98. package/package.json +83 -0
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Public `ctx.kvPersist` service (SPEC §11, §47).
3
+ *
4
+ * A Cordis `Service` that wires the coordinator, backend, repository, and
5
+ * lifecycle listeners together. Future UI/CLI plugins can depend on this
6
+ * surface without knowing anything about llama.cpp.
7
+ *
8
+ * Members use TypeScript `private` (not `#private`): the host exposes the
9
+ * service through a Cordis accessor proxy, and `#private` branding breaks
10
+ * when methods are invoked with a proxy receiver — the same convention the
11
+ * reference plugins follow.
12
+ */
13
+ import { Context, Service } from "@deepseek-ai/cordis";
14
+ import type { KvPersistConfig, ResolvedKvPersistConfig } from "./config.js";
15
+ import type { SnapshotInvalidationReason } from "./errors.js";
16
+ import type { KvPersistenceBackend } from "./backends/types.js";
17
+ import type { RestoreOutcome, SnapshotResult } from "./coordinator/state-machine.js";
18
+ import type { KvPersistLogger } from "./observability/diagnostics.js";
19
+ import { SnapshotRepository } from "./snapshots/repository.js";
20
+ /**
21
+ * Metadata root resolution (SPEC §39): explicit config path wins, otherwise
22
+ * `<$DSH_HOME>/cache/dsh-kv-persist` (cwd fallback for dev/test).
23
+ */
24
+ export declare function resolveMetadataDir(config: ResolvedKvPersistConfig): string;
25
+ /** Overridable internals for tests (fake backend, temp repository, …). */
26
+ export interface KvPersistServiceDeps {
27
+ readonly backend?: KvPersistenceBackend;
28
+ readonly repository?: SnapshotRepository;
29
+ readonly logger?: KvPersistLogger;
30
+ readonly now?: () => number;
31
+ }
32
+ /** Status payload (SPEC §47). */
33
+ export interface KvPersistStatus {
34
+ readonly enabled: boolean;
35
+ readonly backend: {
36
+ readonly kind: "llama.cpp";
37
+ readonly state: string;
38
+ readonly endpoint: string;
39
+ };
40
+ readonly mode: "single-slot";
41
+ readonly slots: readonly {
42
+ readonly id: number;
43
+ readonly owner: string | null;
44
+ readonly state: string;
45
+ }[];
46
+ readonly snapshots: {
47
+ readonly known: number;
48
+ readonly valid: number;
49
+ readonly invalid: number;
50
+ };
51
+ readonly stats: {
52
+ readonly restores: number;
53
+ readonly restoreHits: number;
54
+ readonly coldStarts: number;
55
+ readonly saves: number;
56
+ };
57
+ }
58
+ /** Per-session state view (SPEC §11 `getSessionState`). */
59
+ export interface SessionKvState {
60
+ readonly sessionId: string;
61
+ readonly lifecycle: string;
62
+ readonly provider: string;
63
+ readonly model: string;
64
+ readonly dirtyRevision: number;
65
+ readonly persistedRevision: number;
66
+ readonly ownsSlot: boolean;
67
+ }
68
+ /** Doctor report (SPEC §34, §48-lite). */
69
+ export interface KvPersistDoctorReport {
70
+ readonly result: "READY" | "BLOCKED";
71
+ readonly checks: readonly {
72
+ readonly name: string;
73
+ readonly ok: boolean;
74
+ readonly detail: string;
75
+ }[];
76
+ }
77
+ export declare class KvPersistService extends Service {
78
+ static Config: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
79
+ enabled: import("@deepseek-ai/schemastery").default<boolean, boolean>;
80
+ backend: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
81
+ type: import("@deepseek-ai/schemastery").default<"llama.cpp", "llama.cpp">;
82
+ baseURL: import("@deepseek-ai/schemastery").default<string, string>;
83
+ apiKey: import("@deepseek-ai/schemastery").default<string, string>;
84
+ requestTimeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
85
+ }>, Schemastery.ObjectT<{
86
+ type: import("@deepseek-ai/schemastery").default<"llama.cpp", "llama.cpp">;
87
+ baseURL: import("@deepseek-ai/schemastery").default<string, string>;
88
+ apiKey: import("@deepseek-ai/schemastery").default<string, string>;
89
+ requestTimeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
90
+ }>>;
91
+ providers: import("@deepseek-ai/schemastery").default<string[], string[]>;
92
+ mode: import("@deepseek-ai/schemastery").default<"single-slot", "single-slot">;
93
+ slotId: import("@deepseek-ai/schemastery").default<number, number>;
94
+ runtimeKey: import("@deepseek-ai/schemastery").default<string, string>;
95
+ checkpoint: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
96
+ onSwitch: import("@deepseek-ai/schemastery").default<boolean, boolean>;
97
+ onShutdown: import("@deepseek-ai/schemastery").default<boolean, boolean>;
98
+ onSessionFlush: import("@deepseek-ai/schemastery").default<boolean, boolean>;
99
+ idleMs: import("@deepseek-ai/schemastery").default<number, number>;
100
+ onTurnEnd: import("@deepseek-ai/schemastery").default<boolean, boolean>;
101
+ onStepEnd: import("@deepseek-ai/schemastery").default<boolean, boolean>;
102
+ }>, Schemastery.ObjectT<{
103
+ onSwitch: import("@deepseek-ai/schemastery").default<boolean, boolean>;
104
+ onShutdown: import("@deepseek-ai/schemastery").default<boolean, boolean>;
105
+ onSessionFlush: import("@deepseek-ai/schemastery").default<boolean, boolean>;
106
+ idleMs: import("@deepseek-ai/schemastery").default<number, number>;
107
+ onTurnEnd: import("@deepseek-ai/schemastery").default<boolean, boolean>;
108
+ onStepEnd: import("@deepseek-ai/schemastery").default<boolean, boolean>;
109
+ }>>;
110
+ restore: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
111
+ enabled: import("@deepseek-ai/schemastery").default<boolean, boolean>;
112
+ verify: import("@deepseek-ai/schemastery").default<boolean, boolean>;
113
+ }>, Schemastery.ObjectT<{
114
+ enabled: import("@deepseek-ai/schemastery").default<boolean, boolean>;
115
+ verify: import("@deepseek-ai/schemastery").default<boolean, boolean>;
116
+ }>>;
117
+ failure: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
118
+ strict: import("@deepseek-ai/schemastery").default<boolean, boolean>;
119
+ maxConsecutiveFailures: import("@deepseek-ai/schemastery").default<number, number>;
120
+ cooldownMs: import("@deepseek-ai/schemastery").default<number, number>;
121
+ }>, Schemastery.ObjectT<{
122
+ strict: import("@deepseek-ai/schemastery").default<boolean, boolean>;
123
+ maxConsecutiveFailures: import("@deepseek-ai/schemastery").default<number, number>;
124
+ cooldownMs: import("@deepseek-ai/schemastery").default<number, number>;
125
+ }>>;
126
+ metadata: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
127
+ path: import("@deepseek-ai/schemastery").default<string, string>;
128
+ }>, Schemastery.ObjectT<{
129
+ path: import("@deepseek-ai/schemastery").default<string, string>;
130
+ }>>;
131
+ logging: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
132
+ level: import("@deepseek-ai/schemastery").default<"debug" | "info" | "off", "debug" | "info" | "off">;
133
+ }>, Schemastery.ObjectT<{
134
+ level: import("@deepseek-ai/schemastery").default<"debug" | "info" | "off", "debug" | "info" | "off">;
135
+ }>>;
136
+ }>, Schemastery.ObjectT<{
137
+ enabled: import("@deepseek-ai/schemastery").default<boolean, boolean>;
138
+ backend: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
139
+ type: import("@deepseek-ai/schemastery").default<"llama.cpp", "llama.cpp">;
140
+ baseURL: import("@deepseek-ai/schemastery").default<string, string>;
141
+ apiKey: import("@deepseek-ai/schemastery").default<string, string>;
142
+ requestTimeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
143
+ }>, Schemastery.ObjectT<{
144
+ type: import("@deepseek-ai/schemastery").default<"llama.cpp", "llama.cpp">;
145
+ baseURL: import("@deepseek-ai/schemastery").default<string, string>;
146
+ apiKey: import("@deepseek-ai/schemastery").default<string, string>;
147
+ requestTimeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
148
+ }>>;
149
+ providers: import("@deepseek-ai/schemastery").default<string[], string[]>;
150
+ mode: import("@deepseek-ai/schemastery").default<"single-slot", "single-slot">;
151
+ slotId: import("@deepseek-ai/schemastery").default<number, number>;
152
+ runtimeKey: import("@deepseek-ai/schemastery").default<string, string>;
153
+ checkpoint: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
154
+ onSwitch: import("@deepseek-ai/schemastery").default<boolean, boolean>;
155
+ onShutdown: import("@deepseek-ai/schemastery").default<boolean, boolean>;
156
+ onSessionFlush: import("@deepseek-ai/schemastery").default<boolean, boolean>;
157
+ idleMs: import("@deepseek-ai/schemastery").default<number, number>;
158
+ onTurnEnd: import("@deepseek-ai/schemastery").default<boolean, boolean>;
159
+ onStepEnd: import("@deepseek-ai/schemastery").default<boolean, boolean>;
160
+ }>, Schemastery.ObjectT<{
161
+ onSwitch: import("@deepseek-ai/schemastery").default<boolean, boolean>;
162
+ onShutdown: import("@deepseek-ai/schemastery").default<boolean, boolean>;
163
+ onSessionFlush: import("@deepseek-ai/schemastery").default<boolean, boolean>;
164
+ idleMs: import("@deepseek-ai/schemastery").default<number, number>;
165
+ onTurnEnd: import("@deepseek-ai/schemastery").default<boolean, boolean>;
166
+ onStepEnd: import("@deepseek-ai/schemastery").default<boolean, boolean>;
167
+ }>>;
168
+ restore: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
169
+ enabled: import("@deepseek-ai/schemastery").default<boolean, boolean>;
170
+ verify: import("@deepseek-ai/schemastery").default<boolean, boolean>;
171
+ }>, Schemastery.ObjectT<{
172
+ enabled: import("@deepseek-ai/schemastery").default<boolean, boolean>;
173
+ verify: import("@deepseek-ai/schemastery").default<boolean, boolean>;
174
+ }>>;
175
+ failure: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
176
+ strict: import("@deepseek-ai/schemastery").default<boolean, boolean>;
177
+ maxConsecutiveFailures: import("@deepseek-ai/schemastery").default<number, number>;
178
+ cooldownMs: import("@deepseek-ai/schemastery").default<number, number>;
179
+ }>, Schemastery.ObjectT<{
180
+ strict: import("@deepseek-ai/schemastery").default<boolean, boolean>;
181
+ maxConsecutiveFailures: import("@deepseek-ai/schemastery").default<number, number>;
182
+ cooldownMs: import("@deepseek-ai/schemastery").default<number, number>;
183
+ }>>;
184
+ metadata: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
185
+ path: import("@deepseek-ai/schemastery").default<string, string>;
186
+ }>, Schemastery.ObjectT<{
187
+ path: import("@deepseek-ai/schemastery").default<string, string>;
188
+ }>>;
189
+ logging: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
190
+ level: import("@deepseek-ai/schemastery").default<"debug" | "info" | "off", "debug" | "info" | "off">;
191
+ }>, Schemastery.ObjectT<{
192
+ level: import("@deepseek-ai/schemastery").default<"debug" | "info" | "off", "debug" | "info" | "off">;
193
+ }>>;
194
+ }>>;
195
+ private readonly config;
196
+ private readonly backend;
197
+ private readonly repository;
198
+ private readonly logger;
199
+ private readonly metrics;
200
+ private readonly coordinator;
201
+ constructor(ctx: Context, config?: KvPersistConfig, deps?: KvPersistServiceDeps);
202
+ /** Config access for diagnostics. */
203
+ get resolvedConfig(): ResolvedKvPersistConfig;
204
+ /** Diagnostics snapshot (SPEC §47). */
205
+ status(): Promise<KvPersistStatus>;
206
+ /** Runtime state of one session, when known. */
207
+ getSessionState(sessionId: string): SessionKvState | undefined;
208
+ /** Force a checkpoint of the session (SPEC §11 `save`). */
209
+ save(sessionId: string): Promise<SnapshotResult>;
210
+ /** Force a restore for the session (SPEC §11 `restore`). */
211
+ restore(sessionId: string): Promise<RestoreOutcome>;
212
+ /** Mark every snapshot of the session invalid, without deleting (§31). */
213
+ invalidate(sessionId: string, reason?: SnapshotInvalidationReason): Promise<void>;
214
+ /** Remove all metadata of the session (binary cleanup is server-owned). */
215
+ purge(sessionId: string): Promise<void>;
216
+ /** Checkpoint the dirty owned session now, if any (SPEC §11 `flush`). */
217
+ flush(): Promise<void>;
218
+ /** Managed-provider filter (SPEC §37): only explicit providers are coordinated. */
219
+ handles(provider: string): boolean;
220
+ /** Backend + metadata probe (SPEC §34; CLI `doctor` basis, §48). */
221
+ doctor(): Promise<KvPersistDoctorReport>;
222
+ private register;
223
+ /**
224
+ * Prepare the slot, then wrap the downstream stream (SPEC §23, §25).
225
+ * `next()` is invoked exactly once, after preparation, inside the slot
226
+ * lease; chunks pass through unchanged and unbuffered.
227
+ */
228
+ private handleStream;
229
+ private checkMetadataWritable;
230
+ }
231
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
package/lib/service.js ADDED
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Public `ctx.kvPersist` service (SPEC §11, §47).
3
+ *
4
+ * A Cordis `Service` that wires the coordinator, backend, repository, and
5
+ * lifecycle listeners together. Future UI/CLI plugins can depend on this
6
+ * surface without knowing anything about llama.cpp.
7
+ *
8
+ * Members use TypeScript `private` (not `#private`): the host exposes the
9
+ * service through a Cordis accessor proxy, and `#private` branding breaks
10
+ * when methods are invoked with a proxy receiver — the same convention the
11
+ * reference plugins follow.
12
+ */
13
+ import { mkdir, rm, writeFile } from "node:fs/promises";
14
+ import { join } from "node:path";
15
+ import { Context, Service } from "@deepseek-ai/cordis";
16
+ import { KvPersistConfigSchema, resolveKvPersistConfig, isManagedProvider } from "./config.js";
17
+ import { LlamaCppBackend } from "./backends/llama-cpp/backend.js";
18
+ import { SingleSlotCoordinator } from "./coordinator/coordinator.js";
19
+ import { createKvPersistLogger } from "./observability/diagnostics.js";
20
+ import { KvPersistMetrics } from "./observability/metrics.js";
21
+ import { SnapshotRepository } from "./snapshots/repository.js";
22
+ /**
23
+ * Metadata root resolution (SPEC §39): explicit config path wins, otherwise
24
+ * `<$DSH_HOME>/cache/dsh-kv-persist` (cwd fallback for dev/test).
25
+ */
26
+ export function resolveMetadataDir(config) {
27
+ if (config.metadataPath !== null)
28
+ return config.metadataPath;
29
+ const dshHome = process.env.DSH_HOME?.trim() ?? "";
30
+ return join(dshHome.length > 0 ? dshHome : process.cwd(), "cache", "dsh-kv-persist");
31
+ }
32
+ /**
33
+ * Register a host event whose name lives in the host's session-service
34
+ * vocabulary rather than the plugin-visible `Events` interface. The cast
35
+ * mirrors the structural lifecycle-registration pattern used across this
36
+ * monorepo (guidelines §3.3; reference: dsh-doc-impact lifecycle wiring).
37
+ */
38
+ function registerHostEvent(ctx, event, listener) {
39
+ ctx.on(event, listener);
40
+ }
41
+ export class KvPersistService extends Service {
42
+ static Config = KvPersistConfigSchema;
43
+ config;
44
+ backend;
45
+ repository;
46
+ logger;
47
+ metrics = new KvPersistMetrics();
48
+ coordinator;
49
+ constructor(ctx, config = {}, deps = {}) {
50
+ super(ctx, "kvPersist");
51
+ const resolved = resolveKvPersistConfig(config);
52
+ this.config = resolved;
53
+ this.logger =
54
+ deps.logger ??
55
+ createKvPersistLogger(ctx.logger, resolved.logLevel);
56
+ this.repository =
57
+ deps.repository ?? new SnapshotRepository(resolveMetadataDir(resolved));
58
+ this.backend =
59
+ deps.backend ??
60
+ new LlamaCppBackend({
61
+ baseURL: resolved.baseURL,
62
+ apiKey: resolved.apiKey,
63
+ requestTimeoutMs: resolved.requestTimeoutMs,
64
+ }, resolved.slotId);
65
+ this.coordinator = new SingleSlotCoordinator({
66
+ config: resolved,
67
+ backend: this.backend,
68
+ repository: this.repository,
69
+ metrics: this.metrics,
70
+ logger: this.logger,
71
+ now: deps.now,
72
+ });
73
+ this.register(ctx);
74
+ }
75
+ /** Config access for diagnostics. */
76
+ get resolvedConfig() {
77
+ return this.config;
78
+ }
79
+ // ——— public API (SPEC §11) ————————————————————————————————————————————
80
+ /** Diagnostics snapshot (SPEC §47). */
81
+ async status() {
82
+ const counts = await this.repository.counts().catch(() => ({ known: 0, valid: 0, invalid: 0 }));
83
+ const counters = this.metrics.snapshot();
84
+ const slot = this.coordinator.slot;
85
+ return {
86
+ enabled: this.config.enabled,
87
+ backend: {
88
+ kind: "llama.cpp",
89
+ state: this.coordinator.circuitState,
90
+ endpoint: this.config.baseURL,
91
+ },
92
+ mode: "single-slot",
93
+ slots: [
94
+ {
95
+ id: slot.id,
96
+ owner: slot.ownerSessionId,
97
+ state: slot.state,
98
+ },
99
+ ],
100
+ snapshots: counts,
101
+ stats: {
102
+ restores: counters.restores,
103
+ restoreHits: counters.restoreHits,
104
+ coldStarts: counters.coldPrefills,
105
+ saves: counters.saves,
106
+ },
107
+ };
108
+ }
109
+ /** Runtime state of one session, when known. */
110
+ getSessionState(sessionId) {
111
+ const runtime = this.coordinator.getSessionState(sessionId);
112
+ if (runtime === undefined)
113
+ return undefined;
114
+ return {
115
+ sessionId,
116
+ lifecycle: runtime.lifecycle,
117
+ provider: runtime.route.provider,
118
+ model: runtime.route.model,
119
+ dirtyRevision: runtime.dirtyRevision,
120
+ persistedRevision: runtime.persistedRevision,
121
+ ownsSlot: this.coordinator.slot.ownerSessionId === sessionId,
122
+ };
123
+ }
124
+ /** Force a checkpoint of the session (SPEC §11 `save`). */
125
+ save(sessionId) {
126
+ return this.coordinator.saveNow(sessionId);
127
+ }
128
+ /** Force a restore for the session (SPEC §11 `restore`). */
129
+ restore(sessionId) {
130
+ return this.coordinator.restoreNow(sessionId);
131
+ }
132
+ /** Mark every snapshot of the session invalid, without deleting (§31). */
133
+ invalidate(sessionId, reason) {
134
+ return this.coordinator.invalidate(sessionId, reason ?? "EXPLICIT");
135
+ }
136
+ /** Remove all metadata of the session (binary cleanup is server-owned). */
137
+ purge(sessionId) {
138
+ return this.coordinator.purge(sessionId);
139
+ }
140
+ /** Checkpoint the dirty owned session now, if any (SPEC §11 `flush`). */
141
+ flush() {
142
+ return this.coordinator.flushOwned("manual");
143
+ }
144
+ /** Managed-provider filter (SPEC §37): only explicit providers are coordinated. */
145
+ handles(provider) {
146
+ return this.config.enabled && isManagedProvider(this.config, provider);
147
+ }
148
+ /** Backend + metadata probe (SPEC §34; CLI `doctor` basis, §48). */
149
+ async doctor() {
150
+ const checks = [];
151
+ const probe = await this.coordinator.probeBackend();
152
+ checks.push({
153
+ name: "slots-api",
154
+ ok: probe.healthy,
155
+ detail: probe.healthy
156
+ ? `reachable, slots: [${probe.slotIds.join(", ")}]`
157
+ : (probe.error ?? "unreachable"),
158
+ });
159
+ checks.push({
160
+ name: "configured-slot",
161
+ ok: probe.slotIds.includes(this.config.slotId),
162
+ detail: `slot ${this.config.slotId} required (mode: ${this.config.mode})`,
163
+ });
164
+ checks.push(await this.checkMetadataWritable());
165
+ return {
166
+ result: checks.every((check) => check.ok) ? "READY" : "BLOCKED",
167
+ checks,
168
+ };
169
+ }
170
+ // ——— lifecycle wiring (SPEC §21, §50, §58) —————————————————————————————
171
+ register(ctx) {
172
+ // `llm/stream` is a waterfall around every streaming model call (§21).
173
+ // The listener awaits slot preparation (lazy restore, save-before-switch)
174
+ // before invoking `next()`, then returns the transparent stream wrapper.
175
+ ctx.on("llm/stream", ((options, next) => this.handleStream(options, next)), { global: true });
176
+ // Session lifecycle hooks through a structural registration (the
177
+ // `session/*` event vocabulary belongs to the host's session service).
178
+ registerHostEvent(ctx, "session/flush", (session) => this.coordinator.checkpoint(String(session.id), "session-flush").catch(() => undefined));
179
+ registerHostEvent(ctx, "session/disposed", (session) => {
180
+ void this.coordinator.handleSessionDisposed(String(session.id)).catch(() => undefined);
181
+ });
182
+ registerHostEvent(ctx, "session/event", (session, event) => {
183
+ if (event.type !== "turn/end")
184
+ return;
185
+ void this.coordinator.checkpoint(String(session.id), "turn-end").catch(() => undefined);
186
+ });
187
+ // Backend probe + shutdown checkpoint through a proper Cordis effect
188
+ // (SPEC §34, §58): the disposer runs when the owning fiber unloads.
189
+ ctx.effect(() => {
190
+ void this.coordinator.probeBackend().catch(() => undefined);
191
+ return async () => {
192
+ await this.coordinator.dispose();
193
+ // Flush and release the log file handle (no-op for injected loggers).
194
+ await this.logger.close?.();
195
+ };
196
+ }, "dsh-kv-persist.lifecycle");
197
+ }
198
+ /**
199
+ * Prepare the slot, then wrap the downstream stream (SPEC §23, §25).
200
+ * `next()` is invoked exactly once, after preparation, inside the slot
201
+ * lease; chunks pass through unchanged and unbuffered.
202
+ */
203
+ async handleStream(options, next) {
204
+ if (!this.handles(options.provider))
205
+ return next();
206
+ return this.coordinator.runSessionRequest({
207
+ sessionId: options.sessionId === undefined ? null : String(options.sessionId),
208
+ provider: options.provider,
209
+ model: options.model,
210
+ purpose: options.purpose,
211
+ next,
212
+ });
213
+ }
214
+ async checkMetadataWritable() {
215
+ const dir = resolveMetadataDir(this.config);
216
+ const probeFile = join(dir, `.probe-${process.pid}-${Date.now().toString(36)}`);
217
+ try {
218
+ await mkdir(dir, { recursive: true });
219
+ await writeFile(probeFile, "probe\n", "utf8");
220
+ await rm(probeFile, { force: true });
221
+ return { name: "metadata-dir", ok: true, detail: `${dir} is writable` };
222
+ }
223
+ catch (error) {
224
+ return {
225
+ name: "metadata-dir",
226
+ ok: false,
227
+ detail: `${dir} is not writable: ${String(error?.message ?? error)}`,
228
+ };
229
+ }
230
+ }
231
+ }
232
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Runtime fingerprints and snapshot identity (SPEC §13-§15).
3
+ *
4
+ * A snapshot is never identified by sessionId alone: the identity binds the
5
+ * server instance, provider, model, and a compatibility generation. The
6
+ * runtime fingerprint is the compatibility gate (SPEC Invariant 4) — a
7
+ * snapshot is restored only when the fingerprint matches.
8
+ */
9
+ /** Plugin snapshot schema generation; bump on incompatible manifest changes. */
10
+ export declare const SNAPSHOT_SCHEMA_GENERATION = 1;
11
+ /** Identity of one snapshot (SPEC §13). */
12
+ export interface SnapshotIdentity {
13
+ readonly sessionId: string;
14
+ readonly provider: string;
15
+ readonly model: string;
16
+ readonly backend: "llama.cpp";
17
+ /** Stable key of the server instance (v0.1: derived from the endpoint). */
18
+ readonly serverInstanceKey: string;
19
+ /** Compatibility generation (SPEC §15). */
20
+ readonly compatibilityVersion: string;
21
+ }
22
+ /** The request route a session is currently using. */
23
+ export interface SessionRoute {
24
+ readonly provider: string;
25
+ readonly model: string;
26
+ }
27
+ /** sha256 hex digest of a string. */
28
+ export declare function sha256Hex(input: string): string;
29
+ /**
30
+ * Server instance key for v0.1: one configured endpoint, keyed by its
31
+ * origin (`method://host:port`). Multiple servers arrive with v0.3.
32
+ */
33
+ export declare function deriveServerInstanceKey(baseURL: string): string;
34
+ /**
35
+ * Compatibility generation (SPEC §15): runtimeKey escape hatch + model +
36
+ * schema generation. Changing `runtimeKey` makes old snapshots invisible
37
+ * without deleting them.
38
+ */
39
+ export declare function compatibilityVersion(options: {
40
+ readonly backend: "llama.cpp";
41
+ readonly runtimeKey: string | null;
42
+ readonly model: string;
43
+ }): string;
44
+ /** Build the full snapshot identity for one (session, route) pair. */
45
+ export declare function buildSnapshotIdentity(options: {
46
+ readonly sessionId: string;
47
+ readonly route: SessionRoute;
48
+ readonly baseURL: string;
49
+ readonly runtimeKey: string | null;
50
+ }): SnapshotIdentity;
51
+ /** True when a stored identity is compatible with the requested one (SPEC §31). */
52
+ export declare function isIdentityCompatible(stored: SnapshotIdentity, requested: SnapshotIdentity): boolean;
53
+ //# sourceMappingURL=fingerprint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fingerprint.d.ts","sourceRoot":"","sources":["../../src/snapshots/fingerprint.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,gFAAgF;AAChF,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C,2CAA2C;AAC3C,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,2EAA2E;IAC3E,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,2CAA2C;IAC3C,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;CACvC;AAED,sDAAsD;AACtD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,qCAAqC;AACrC,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAQ/D;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE;IAC5C,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB,GAAG,MAAM,CAUT;AAED,sEAAsE;AACtE,wBAAgB,qBAAqB,CAAC,OAAO,EAAE;IAC7C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC,GAAG,gBAAgB,CAcnB;AAED,mFAAmF;AACnF,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,gBAAgB,EACxB,SAAS,EAAE,gBAAgB,GAC1B,OAAO,CAQT"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Runtime fingerprints and snapshot identity (SPEC §13-§15).
3
+ *
4
+ * A snapshot is never identified by sessionId alone: the identity binds the
5
+ * server instance, provider, model, and a compatibility generation. The
6
+ * runtime fingerprint is the compatibility gate (SPEC Invariant 4) — a
7
+ * snapshot is restored only when the fingerprint matches.
8
+ */
9
+ import { createHash } from "node:crypto";
10
+ import { KvInvariantError } from "../errors.js";
11
+ /** Plugin snapshot schema generation; bump on incompatible manifest changes. */
12
+ export const SNAPSHOT_SCHEMA_GENERATION = 1;
13
+ /** sha256 hex digest of a string. */
14
+ export function sha256Hex(input) {
15
+ return createHash("sha256").update(input, "utf8").digest("hex");
16
+ }
17
+ /**
18
+ * Server instance key for v0.1: one configured endpoint, keyed by its
19
+ * origin (`method://host:port`). Multiple servers arrive with v0.3.
20
+ */
21
+ export function deriveServerInstanceKey(baseURL) {
22
+ let origin;
23
+ try {
24
+ origin = new URL(baseURL).origin;
25
+ }
26
+ catch {
27
+ throw new KvInvariantError(`backend.baseURL is not a valid URL: "${baseURL}"`);
28
+ }
29
+ return `llama-${sha256Hex(origin).slice(0, 12)}`;
30
+ }
31
+ /**
32
+ * Compatibility generation (SPEC §15): runtimeKey escape hatch + model +
33
+ * schema generation. Changing `runtimeKey` makes old snapshots invisible
34
+ * without deleting them.
35
+ */
36
+ export function compatibilityVersion(options) {
37
+ return sha256Hex(JSON.stringify([
38
+ options.backend,
39
+ // null and "" mean "no explicit runtime key" and must hash identically.
40
+ options.runtimeKey ?? "",
41
+ options.model,
42
+ SNAPSHOT_SCHEMA_GENERATION,
43
+ ])).slice(0, 16);
44
+ }
45
+ /** Build the full snapshot identity for one (session, route) pair. */
46
+ export function buildSnapshotIdentity(options) {
47
+ const backend = "llama.cpp";
48
+ return {
49
+ sessionId: options.sessionId,
50
+ provider: options.route.provider,
51
+ model: options.route.model,
52
+ backend,
53
+ serverInstanceKey: deriveServerInstanceKey(options.baseURL),
54
+ compatibilityVersion: compatibilityVersion({
55
+ backend,
56
+ runtimeKey: options.runtimeKey,
57
+ model: options.route.model,
58
+ }),
59
+ };
60
+ }
61
+ /** True when a stored identity is compatible with the requested one (SPEC §31). */
62
+ export function isIdentityCompatible(stored, requested) {
63
+ return (stored.backend === requested.backend &&
64
+ stored.serverInstanceKey === requested.serverInstanceKey &&
65
+ stored.provider === requested.provider &&
66
+ stored.model === requested.model &&
67
+ stored.compatibilityVersion === requested.compatibilityVersion);
68
+ }
69
+ //# sourceMappingURL=fingerprint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../../src/snapshots/fingerprint.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,gFAAgF;AAChF,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC;AAoB5C,qCAAqC;AACrC,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAClE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAe;IACrD,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,gBAAgB,CAAC,wCAAwC,OAAO,GAAG,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,SAAS,SAAS,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;AACnD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAIpC;IACC,OAAO,SAAS,CACd,IAAI,CAAC,SAAS,CAAC;QACb,OAAO,CAAC,OAAO;QACf,wEAAwE;QACxE,OAAO,CAAC,UAAU,IAAI,EAAE;QACxB,OAAO,CAAC,KAAK;QACb,0BAA0B;KAC3B,CAAC,CACH,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACjB,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,qBAAqB,CAAC,OAKrC;IACC,MAAM,OAAO,GAAG,WAAoB,CAAC;IACrC,OAAO;QACL,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,QAAQ,EAAE,OAAO,CAAC,KAAK,CAAC,QAAQ;QAChC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,KAAK;QAC1B,OAAO;QACP,iBAAiB,EAAE,uBAAuB,CAAC,OAAO,CAAC,OAAO,CAAC;QAC3D,oBAAoB,EAAE,oBAAoB,CAAC;YACzC,OAAO;YACP,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,KAAK;SAC3B,CAAC;KACH,CAAC;AACJ,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,oBAAoB,CAClC,MAAwB,EACxB,SAA2B;IAE3B,OAAO,CACL,MAAM,CAAC,OAAO,KAAK,SAAS,CAAC,OAAO;QACpC,MAAM,CAAC,iBAAiB,KAAK,SAAS,CAAC,iBAAiB;QACxD,MAAM,CAAC,QAAQ,KAAK,SAAS,CAAC,QAAQ;QACtC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,KAAK;QAChC,MAAM,CAAC,oBAAoB,KAAK,SAAS,CAAC,oBAAoB,CAC/D,CAAC;AACJ,CAAC"}