@seanhogg/builderforce-memory 2026.6.18

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 (163) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +582 -0
  3. package/dist/agent/SSMAgent.d.ts +146 -0
  4. package/dist/agent/SSMAgent.d.ts.map +1 -0
  5. package/dist/agent/SSMAgent.js +231 -0
  6. package/dist/agent/SSMAgent.js.map +1 -0
  7. package/dist/agent/index.d.ts +3 -0
  8. package/dist/agent/index.d.ts.map +1 -0
  9. package/dist/agent/index.js +2 -0
  10. package/dist/agent/index.js.map +1 -0
  11. package/dist/bridges/AnthropicBridge.d.ts +47 -0
  12. package/dist/bridges/AnthropicBridge.d.ts.map +1 -0
  13. package/dist/bridges/AnthropicBridge.js +120 -0
  14. package/dist/bridges/AnthropicBridge.js.map +1 -0
  15. package/dist/bridges/CachingBridge.d.ts +44 -0
  16. package/dist/bridges/CachingBridge.d.ts.map +1 -0
  17. package/dist/bridges/CachingBridge.js +62 -0
  18. package/dist/bridges/CachingBridge.js.map +1 -0
  19. package/dist/bridges/FetchBridge.d.ts +30 -0
  20. package/dist/bridges/FetchBridge.d.ts.map +1 -0
  21. package/dist/bridges/FetchBridge.js +24 -0
  22. package/dist/bridges/FetchBridge.js.map +1 -0
  23. package/dist/bridges/OpenAIBridge.d.ts +33 -0
  24. package/dist/bridges/OpenAIBridge.d.ts.map +1 -0
  25. package/dist/bridges/OpenAIBridge.js +110 -0
  26. package/dist/bridges/OpenAIBridge.js.map +1 -0
  27. package/dist/bridges/ResponseCache.d.ts +65 -0
  28. package/dist/bridges/ResponseCache.d.ts.map +1 -0
  29. package/dist/bridges/ResponseCache.js +97 -0
  30. package/dist/bridges/ResponseCache.js.map +1 -0
  31. package/dist/bridges/SemanticCachingBridge.d.ts +31 -0
  32. package/dist/bridges/SemanticCachingBridge.d.ts.map +1 -0
  33. package/dist/bridges/SemanticCachingBridge.js +44 -0
  34. package/dist/bridges/SemanticCachingBridge.js.map +1 -0
  35. package/dist/bridges/TransformerBridge.d.ts +35 -0
  36. package/dist/bridges/TransformerBridge.d.ts.map +1 -0
  37. package/dist/bridges/TransformerBridge.js +10 -0
  38. package/dist/bridges/TransformerBridge.js.map +1 -0
  39. package/dist/bridges/index.d.ts +14 -0
  40. package/dist/bridges/index.d.ts.map +1 -0
  41. package/dist/bridges/index.js +7 -0
  42. package/dist/bridges/index.js.map +1 -0
  43. package/dist/cache/FetchSemanticCacheBackend.d.ts +40 -0
  44. package/dist/cache/FetchSemanticCacheBackend.d.ts.map +1 -0
  45. package/dist/cache/FetchSemanticCacheBackend.js +61 -0
  46. package/dist/cache/FetchSemanticCacheBackend.js.map +1 -0
  47. package/dist/cache/SemanticCache.d.ts +105 -0
  48. package/dist/cache/SemanticCache.d.ts.map +1 -0
  49. package/dist/cache/SemanticCache.js +130 -0
  50. package/dist/cache/SemanticCache.js.map +1 -0
  51. package/dist/cache/index.d.ts +5 -0
  52. package/dist/cache/index.d.ts.map +1 -0
  53. package/dist/cache/index.js +3 -0
  54. package/dist/cache/index.js.map +1 -0
  55. package/dist/distillation/DistillationEngine.d.ts +107 -0
  56. package/dist/distillation/DistillationEngine.d.ts.map +1 -0
  57. package/dist/distillation/DistillationEngine.js +152 -0
  58. package/dist/distillation/DistillationEngine.js.map +1 -0
  59. package/dist/distillation/index.d.ts +3 -0
  60. package/dist/distillation/index.d.ts.map +1 -0
  61. package/dist/distillation/index.js +2 -0
  62. package/dist/distillation/index.js.map +1 -0
  63. package/dist/errors/SSMError.d.ts +14 -0
  64. package/dist/errors/SSMError.d.ts.map +1 -0
  65. package/dist/errors/SSMError.js +18 -0
  66. package/dist/errors/SSMError.js.map +1 -0
  67. package/dist/errors/index.d.ts +3 -0
  68. package/dist/errors/index.d.ts.map +1 -0
  69. package/dist/errors/index.js +2 -0
  70. package/dist/errors/index.js.map +1 -0
  71. package/dist/index.d.ts +65 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +59 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/memory/MemoryStore.d.ts +152 -0
  76. package/dist/memory/MemoryStore.d.ts.map +1 -0
  77. package/dist/memory/MemoryStore.js +290 -0
  78. package/dist/memory/MemoryStore.js.map +1 -0
  79. package/dist/memory/index.d.ts +3 -0
  80. package/dist/memory/index.d.ts.map +1 -0
  81. package/dist/memory/index.js +2 -0
  82. package/dist/memory/index.js.map +1 -0
  83. package/dist/router/InferenceRouter.d.ts +92 -0
  84. package/dist/router/InferenceRouter.d.ts.map +1 -0
  85. package/dist/router/InferenceRouter.js +113 -0
  86. package/dist/router/InferenceRouter.js.map +1 -0
  87. package/dist/router/index.d.ts +3 -0
  88. package/dist/router/index.d.ts.map +1 -0
  89. package/dist/router/index.js +2 -0
  90. package/dist/router/index.js.map +1 -0
  91. package/dist/runtime/SSMRuntime.d.ts +167 -0
  92. package/dist/runtime/SSMRuntime.d.ts.map +1 -0
  93. package/dist/runtime/SSMRuntime.js +199 -0
  94. package/dist/runtime/SSMRuntime.js.map +1 -0
  95. package/dist/runtime/index.d.ts +3 -0
  96. package/dist/runtime/index.d.ts.map +1 -0
  97. package/dist/runtime/index.js +2 -0
  98. package/dist/runtime/index.js.map +1 -0
  99. package/dist/session/errors.d.ts +10 -0
  100. package/dist/session/errors.d.ts.map +1 -0
  101. package/dist/session/errors.js +14 -0
  102. package/dist/session/errors.js.map +1 -0
  103. package/dist/session/index.d.ts +11 -0
  104. package/dist/session/index.d.ts.map +1 -0
  105. package/dist/session/index.js +7 -0
  106. package/dist/session/index.js.map +1 -0
  107. package/dist/session/persistence.d.ts +14 -0
  108. package/dist/session/persistence.d.ts.map +1 -0
  109. package/dist/session/persistence.js +100 -0
  110. package/dist/session/persistence.js.map +1 -0
  111. package/dist/session/presets.d.ts +31 -0
  112. package/dist/session/presets.d.ts.map +1 -0
  113. package/dist/session/presets.js +91 -0
  114. package/dist/session/presets.js.map +1 -0
  115. package/dist/session/session.d.ts +186 -0
  116. package/dist/session/session.d.ts.map +1 -0
  117. package/dist/session/session.js +358 -0
  118. package/dist/session/session.js.map +1 -0
  119. package/dist/session/streaming.d.ts +13 -0
  120. package/dist/session/streaming.d.ts.map +1 -0
  121. package/dist/session/streaming.js +74 -0
  122. package/dist/session/streaming.js.map +1 -0
  123. package/dist/session/tokenizer.d.ts +18 -0
  124. package/dist/session/tokenizer.d.ts.map +1 -0
  125. package/dist/session/tokenizer.js +11 -0
  126. package/dist/session/tokenizer.js.map +1 -0
  127. package/dist/similarity/index.d.ts +19 -0
  128. package/dist/similarity/index.d.ts.map +1 -0
  129. package/dist/similarity/index.js +42 -0
  130. package/dist/similarity/index.js.map +1 -0
  131. package/package.json +120 -0
  132. package/src/agent/SSMAgent.ts +327 -0
  133. package/src/agent/index.ts +2 -0
  134. package/src/bridges/AnthropicBridge.ts +166 -0
  135. package/src/bridges/CachingBridge.ts +79 -0
  136. package/src/bridges/FetchBridge.ts +41 -0
  137. package/src/bridges/OpenAIBridge.ts +143 -0
  138. package/src/bridges/ResponseCache.ts +131 -0
  139. package/src/bridges/SemanticCachingBridge.ts +60 -0
  140. package/src/bridges/TransformerBridge.ts +38 -0
  141. package/src/bridges/index.ts +13 -0
  142. package/src/cache/FetchSemanticCacheBackend.ts +79 -0
  143. package/src/cache/SemanticCache.ts +196 -0
  144. package/src/cache/index.ts +9 -0
  145. package/src/distillation/DistillationEngine.ts +248 -0
  146. package/src/distillation/index.ts +2 -0
  147. package/src/errors/SSMError.ts +26 -0
  148. package/src/errors/index.ts +2 -0
  149. package/src/index.ts +128 -0
  150. package/src/memory/MemoryStore.ts +408 -0
  151. package/src/memory/index.ts +2 -0
  152. package/src/router/InferenceRouter.ts +201 -0
  153. package/src/router/index.ts +2 -0
  154. package/src/runtime/SSMRuntime.ts +309 -0
  155. package/src/runtime/index.ts +2 -0
  156. package/src/session/errors.ts +24 -0
  157. package/src/session/index.ts +25 -0
  158. package/src/session/persistence.ts +142 -0
  159. package/src/session/presets.ts +122 -0
  160. package/src/session/session.ts +657 -0
  161. package/src/session/streaming.ts +97 -0
  162. package/src/session/tokenizer.ts +18 -0
  163. package/src/similarity/index.ts +42 -0
@@ -0,0 +1,309 @@
1
+ /**
2
+ * SSMRuntime – core runtime class for SSM.js.
3
+ *
4
+ * Wraps a MambaSession and adds:
5
+ * - Hybrid inference routing (SSM ↔ transformer bridge)
6
+ * - Unified generate / stream API
7
+ * - Perplexity-aware routing (InferenceRouter auto mode)
8
+ * - Thin save/load pass-throughs for MemoryStore integration
9
+ *
10
+ * GPU lifecycle is owned entirely by this class via the underlying session.
11
+ */
12
+
13
+ import {
14
+ MambaSession,
15
+ type MambaSessionOptions,
16
+ type CompleteOptions,
17
+ type AdaptOptions,
18
+ type AdaptResult,
19
+ type SaveOptions,
20
+ type LoadOptions,
21
+ type CreateCallbacks,
22
+ } from '../session/index.js';
23
+
24
+ import { InferenceRouter, type RoutingStrategy, type RoutingAuditEntry } from '../router/InferenceRouter.js';
25
+ import type { DistillationLog } from '../distillation/DistillationEngine.js';
26
+ import type { TransformerBridge, BridgeGenerateOptions } from '../bridges/TransformerBridge.js';
27
+ import { SSMError } from '../errors/SSMError.js';
28
+
29
+ // ── Public types ──────────────────────────────────────────────────────────────
30
+
31
+ export type GenerateOptions = CompleteOptions & {
32
+ /**
33
+ * Options forwarded to the transformer bridge when the router selects it.
34
+ * Ignored when SSM is selected.
35
+ */
36
+ bridgeOpts?: BridgeGenerateOptions;
37
+
38
+ /**
39
+ * Stable system / context prefix, kept separate from the volatile `input`.
40
+ *
41
+ * Splitting it out is what makes transformer-side prompt caching possible:
42
+ * on the SSM path it is prepended to `input` (reproducing the single-string
43
+ * trained prompt format, so SSM behaviour is unchanged), while on the
44
+ * transformer path it is sent as the cacheable `system` field rather than
45
+ * being concatenated into the user message. A bridge can then cache it
46
+ * across turns; concatenating it into the per-turn input could never cache.
47
+ */
48
+ system?: string;
49
+ };
50
+
51
+ export interface SSMRuntimeOptions {
52
+ /**
53
+ * Options forwarded verbatim to MambaSession.create().
54
+ * Controls model size, SSM variant, tokenizer, and checkpoint URL.
55
+ */
56
+ session : MambaSessionOptions;
57
+
58
+ /**
59
+ * Optional transformer bridge. When absent, all requests go to the SSM
60
+ * and distillation is unavailable.
61
+ */
62
+ bridge? : TransformerBridge;
63
+
64
+ /**
65
+ * Routing strategy when a bridge is present.
66
+ * Default: 'auto'
67
+ */
68
+ routingStrategy? : RoutingStrategy;
69
+
70
+ /**
71
+ * Character length above which auto-routing prefers the transformer.
72
+ * Default: 1200
73
+ */
74
+ longInputThreshold? : number;
75
+
76
+ /**
77
+ * SSM perplexity above which auto-routing prefers the transformer.
78
+ * Default: 80
79
+ */
80
+ perplexityThreshold? : number;
81
+
82
+ /**
83
+ * MambaSession.create() progress callbacks.
84
+ */
85
+ callbacks? : CreateCallbacks;
86
+ }
87
+
88
+ // ── SSMRuntime ────────────────────────────────────────────────────────────────
89
+
90
+ export class SSMRuntime {
91
+ private readonly _session : MambaSession;
92
+ private readonly _bridge : TransformerBridge | undefined;
93
+ private readonly _router : InferenceRouter;
94
+ private _destroyed = false;
95
+
96
+ private constructor(
97
+ session : MambaSession,
98
+ bridge : TransformerBridge | undefined,
99
+ router : InferenceRouter,
100
+ ) {
101
+ this._session = session;
102
+ this._bridge = bridge;
103
+ this._router = router;
104
+ }
105
+
106
+ // ── Factory ───────────────────────────────────────────────────────────────
107
+
108
+ /**
109
+ * Creates and initialises a new SSMRuntime.
110
+ *
111
+ * Delegates to MambaSession.create() — can throw SessionError for
112
+ * GPU / tokenizer failures.
113
+ */
114
+ static async create(opts: SSMRuntimeOptions): Promise<SSMRuntime> {
115
+ const session = await MambaSession.create(opts.session, opts.callbacks);
116
+
117
+ const router = new InferenceRouter({
118
+ strategy : opts.routingStrategy,
119
+ longInputThreshold : opts.longInputThreshold,
120
+ perplexityThreshold : opts.perplexityThreshold,
121
+ hasBridge : !!opts.bridge,
122
+ // Pass perplexity probe as callback — avoids circular import
123
+ perplexityProbe : opts.bridge
124
+ ? (text) => session.evaluate(text)
125
+ : undefined,
126
+ });
127
+
128
+ return new SSMRuntime(session, opts.bridge, router);
129
+ }
130
+
131
+ // ── Inference ─────────────────────────────────────────────────────────────
132
+
133
+ /**
134
+ * Generates a full response for the given input.
135
+ * Routes to SSM or transformer bridge based on the configured strategy.
136
+ */
137
+ async generate(input: string, opts: GenerateOptions = {}): Promise<string> {
138
+ this._checkAlive();
139
+
140
+ const decision = await this._router.route(input);
141
+
142
+ if (decision.target === 'transformer' && this._bridge) {
143
+ return this._bridge.generate(input, this._bridgeOptsWithSystem(opts));
144
+ }
145
+
146
+ // SSM path — extract only CompleteOptions fields
147
+ const { bridgeOpts: _b, system, ...completeOpts } = opts;
148
+ return this._session.complete(this._ssmInput(input, system), completeOpts);
149
+ }
150
+
151
+ /**
152
+ * Streaming token generation.
153
+ *
154
+ * Routing note: streaming always uses the SSM path for consistent
155
+ * latency characteristics. Use `generate()` if transformer streaming
156
+ * is needed (bridgeOpts.stream, handled by the bridge's own `stream()` method).
157
+ */
158
+ async *stream(input: string, opts: GenerateOptions = {}): AsyncIterable<string> {
159
+ this._checkAlive();
160
+ const { bridgeOpts: _b, system, ...completeOpts } = opts;
161
+ yield* this._session.completeStream(this._ssmInput(input, system), completeOpts);
162
+ }
163
+
164
+ /**
165
+ * Hybrid streaming: routes to SSM or transformer bridge stream.
166
+ * Falls back to `generate()` for bridges that don't support streaming.
167
+ */
168
+ async *streamHybrid(input: string, opts: GenerateOptions = {}): AsyncIterable<string> {
169
+ this._checkAlive();
170
+
171
+ const decision = await this._router.route(input);
172
+
173
+ if (decision.target === 'transformer' && this._bridge) {
174
+ const bridgeOpts = this._bridgeOptsWithSystem(opts);
175
+ if (this._bridge.supportsStreaming && this._bridge.stream) {
176
+ yield* this._bridge.stream(input, bridgeOpts);
177
+ } else {
178
+ yield await this._bridge.generate(input, bridgeOpts);
179
+ }
180
+ return;
181
+ }
182
+
183
+ const { bridgeOpts: _b, system, ...completeOpts } = opts;
184
+ yield* this._session.completeStream(this._ssmInput(input, system), completeOpts);
185
+ }
186
+
187
+ // ── Prompt-prefix helpers ─────────────────────────────────────────────────
188
+
189
+ /**
190
+ * Combines the stable `system` prefix with the volatile `input` for the SSM
191
+ * path, reproducing the single-string format the model was trained on.
192
+ */
193
+ private _ssmInput(input: string, system: string | undefined): string {
194
+ return system ? `${system}\n${input}` : input;
195
+ }
196
+
197
+ /**
198
+ * Maps the top-level `system` prefix onto the bridge's `systemPrompt` so the
199
+ * transformer receives it as a cacheable `system` field. An explicit
200
+ * `bridgeOpts.systemPrompt` wins if the caller set one.
201
+ */
202
+ private _bridgeOptsWithSystem(opts: GenerateOptions): BridgeGenerateOptions {
203
+ return {
204
+ ...opts.bridgeOpts,
205
+ systemPrompt: opts.bridgeOpts?.systemPrompt ?? opts.system,
206
+ };
207
+ }
208
+
209
+ // ── Adaptation ────────────────────────────────────────────────────────────
210
+
211
+ /**
212
+ * Fine-tunes the SSM on the provided text.
213
+ * Pass-through to MambaSession.adapt().
214
+ */
215
+ async adapt(data: string, opts?: AdaptOptions): Promise<AdaptResult> {
216
+ this._checkAlive();
217
+ return this._session.adapt(data, opts);
218
+ }
219
+
220
+ /**
221
+ * Evaluates SSM perplexity on the provided text.
222
+ * Exposed for power users and InferenceRouter's perplexity probe.
223
+ */
224
+ async evaluate(text: string): Promise<number> {
225
+ this._checkAlive();
226
+ return this._session.evaluate(text);
227
+ }
228
+
229
+ /**
230
+ * Returns an L2-normalised SSM embedding for `text`.
231
+ * Pass-through to MambaSession.embed(). Consumed by MemoryStore.recallSimilar
232
+ * for SSM-based semantic search.
233
+ */
234
+ async embed(text: string): Promise<Float32Array> {
235
+ this._checkAlive();
236
+ return this._session.embed(text);
237
+ }
238
+
239
+ // ── Persistence ───────────────────────────────────────────────────────────
240
+
241
+ /**
242
+ * Saves SSM weights. Pass-through to MambaSession.save().
243
+ * Accepts a `key` option used by MemoryStore to save under a custom key.
244
+ */
245
+ async save(opts?: SaveOptions & { key?: string }): Promise<void> {
246
+ this._checkAlive();
247
+ await this._session.save(opts);
248
+ }
249
+
250
+ /**
251
+ * Loads SSM weights. Pass-through to MambaSession.load().
252
+ * Returns false when no checkpoint exists.
253
+ */
254
+ async load(opts?: LoadOptions & { key?: string }): Promise<boolean> {
255
+ this._checkAlive();
256
+ return this._session.load(opts) as Promise<boolean>;
257
+ }
258
+
259
+ // ── Accessors ─────────────────────────────────────────────────────────────
260
+
261
+ /**
262
+ * Returns a copy of the in-memory routing audit log.
263
+ * Delegates to the internal InferenceRouter.
264
+ */
265
+ getRoutingAuditLog(): RoutingAuditEntry[] {
266
+ return this._router.getAuditLog();
267
+ }
268
+
269
+ /**
270
+ * Returns the distillation log if a DistillationEngine has been attached.
271
+ * Returns an empty array when no distillation engine is available.
272
+ *
273
+ * Note: DistillationEngine is a separate object — attach it by calling
274
+ * `new DistillationEngine(runtime, bridge)` and keeping a reference.
275
+ * This method is a stub for future inline engine support.
276
+ */
277
+ getDistillationLog(): DistillationLog[] {
278
+ return [];
279
+ }
280
+
281
+ /** The transformer bridge attached to this runtime, if any. */
282
+ get bridge(): TransformerBridge | undefined { return this._bridge; }
283
+
284
+ /** Whether this runtime has been destroyed. */
285
+ get destroyed(): boolean { return this._destroyed; }
286
+
287
+ /**
288
+ * Escape hatch to the underlying MambaSession internals.
289
+ * Use sparingly — the SSMRuntime API covers the common cases.
290
+ */
291
+ get internals() { return this._session.internals; }
292
+
293
+ // ── Lifecycle ─────────────────────────────────────────────────────────────
294
+
295
+ /**
296
+ * Destroys the underlying MambaSession and releases all GPU resources.
297
+ * After calling this, all methods throw SSMError('RUNTIME_DESTROYED').
298
+ */
299
+ destroy(): void {
300
+ this._session.destroy();
301
+ this._destroyed = true;
302
+ }
303
+
304
+ private _checkAlive(): void {
305
+ if (this._destroyed) {
306
+ throw new SSMError('RUNTIME_DESTROYED', 'SSMRuntime has been destroyed.');
307
+ }
308
+ }
309
+ }
@@ -0,0 +1,2 @@
1
+ export { SSMRuntime } from './SSMRuntime.js';
2
+ export type { SSMRuntimeOptions, GenerateOptions } from './SSMRuntime.js';
@@ -0,0 +1,24 @@
1
+ /**
2
+ * errors.ts – Typed error class for the MambaSession layer.
3
+ */
4
+
5
+ export type SessionErrorCode =
6
+ | 'GPU_UNAVAILABLE' // navigator.gpu not present or adapter request failed
7
+ | 'TOKENIZER_LOAD_FAILED' // vocab.json or merges.txt could not be fetched/parsed
8
+ | 'CHECKPOINT_FETCH_FAILED' // checkpoint URL returned non-OK response after retries
9
+ | 'CHECKPOINT_INVALID' // loadWeights threw (bad magic, version, or size mismatch)
10
+ | 'INPUT_TOO_SHORT' // adapt() input encodes to fewer than 2 tokens
11
+ | 'STORAGE_UNAVAILABLE' // IndexedDB or File System Access API not available
12
+ | 'SESSION_DESTROYED' // method called after destroy()
13
+ | 'UNKNOWN'; // unexpected error (original in .cause)
14
+
15
+ export class SessionError extends Error {
16
+ constructor(
17
+ public readonly code: SessionErrorCode,
18
+ message: string,
19
+ public readonly cause?: unknown,
20
+ ) {
21
+ super(message);
22
+ this.name = 'SessionError';
23
+ }
24
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * session/index.ts – barrel export for the MambaSession layer.
3
+ */
4
+
5
+ export { MambaSession } from './session.js';
6
+ export { SessionError } from './errors.js';
7
+ export { MODEL_PRESETS, resolveLayerSchedule, resolveModelConfig } from './presets.js';
8
+
9
+ export type { SessionErrorCode } from './errors.js';
10
+ export type { LayerSchedulePreset } from './presets.js';
11
+ export type {
12
+ MambaSessionOptions,
13
+ CompleteOptions,
14
+ AdaptOptions,
15
+ AdaptResult,
16
+ SaveOptions,
17
+ LoadOptions,
18
+ StorageTarget,
19
+ CreateProgressEvent,
20
+ CreateStage,
21
+ CreateCallbacks,
22
+ SessionInternals,
23
+ GpuMode,
24
+ } from './session.js';
25
+ export type { Tokenizer } from './tokenizer.js';
@@ -0,0 +1,142 @@
1
+ /**
2
+ * persistence.ts – Storage helpers for the MambaSession layer.
3
+ *
4
+ * Supports three storage targets:
5
+ * - IndexedDB (browser default)
6
+ * - Download (Blob URL trigger)
7
+ * - File System Access API
8
+ */
9
+
10
+ import { SessionError } from './errors.js';
11
+
12
+ const DB_NAME = 'ssmjs-session';
13
+ const DB_VERSION = 1;
14
+ const STORE_NAME = 'checkpoints';
15
+
16
+ // ── IndexedDB helpers ─────────────────────────────────────────────────────────
17
+
18
+ function openDB(idb?: IDBFactory): Promise<IDBDatabase> {
19
+ return new Promise((resolve, reject) => {
20
+ const factory = idb ?? (typeof indexedDB !== 'undefined' ? indexedDB : undefined);
21
+ if (!factory) {
22
+ reject(new SessionError(
23
+ 'STORAGE_UNAVAILABLE',
24
+ 'IndexedDB is not available in this environment. Pass an idbFactory option (e.g. from fake-indexeddb) for Node.js support.',
25
+ ));
26
+ return;
27
+ }
28
+
29
+ const req = factory.open(DB_NAME, DB_VERSION);
30
+
31
+ req.onupgradeneeded = (e) => {
32
+ const db = (e.target as IDBOpenDBRequest).result;
33
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
34
+ db.createObjectStore(STORE_NAME);
35
+ }
36
+ };
37
+
38
+ req.onsuccess = () => resolve(req.result);
39
+ req.onerror = () => reject(new SessionError(
40
+ 'STORAGE_UNAVAILABLE',
41
+ `Failed to open IndexedDB database "${DB_NAME}": ${req.error?.message ?? 'unknown error'}`,
42
+ req.error,
43
+ ));
44
+ });
45
+ }
46
+
47
+ export async function saveToIndexedDB(key: string, buffer: ArrayBuffer, idb?: IDBFactory): Promise<void> {
48
+ const db = await openDB(idb);
49
+ return new Promise((resolve, reject) => {
50
+ const tx = db.transaction(STORE_NAME, 'readwrite');
51
+ const store = tx.objectStore(STORE_NAME);
52
+ const req = store.put(buffer, key);
53
+
54
+ req.onsuccess = () => resolve();
55
+ req.onerror = () => reject(new SessionError(
56
+ 'STORAGE_UNAVAILABLE',
57
+ `Failed to write checkpoint to IndexedDB (key="${key}"): ${req.error?.message ?? 'unknown error'}`,
58
+ req.error,
59
+ ));
60
+ tx.oncomplete = () => db.close();
61
+ });
62
+ }
63
+
64
+ export async function loadFromIndexedDB(key: string, idb?: IDBFactory): Promise<ArrayBuffer | undefined> {
65
+ const db = await openDB(idb);
66
+ return new Promise((resolve, reject) => {
67
+ const tx = db.transaction(STORE_NAME, 'readonly');
68
+ const store = tx.objectStore(STORE_NAME);
69
+ const req = store.get(key);
70
+
71
+ req.onsuccess = () => {
72
+ db.close();
73
+ resolve(req.result as ArrayBuffer | undefined);
74
+ };
75
+ req.onerror = () => reject(new SessionError(
76
+ 'STORAGE_UNAVAILABLE',
77
+ `Failed to read checkpoint from IndexedDB (key="${key}"): ${req.error?.message ?? 'unknown error'}`,
78
+ req.error,
79
+ ));
80
+ });
81
+ }
82
+
83
+ /** Milliseconds a download Blob URL is kept alive before being revoked. */
84
+ const DOWNLOAD_URL_TTL_MS = 10_000;
85
+
86
+ // ── Download helper ───────────────────────────────────────────────────────────
87
+
88
+ export async function triggerDownload(filename: string, buffer: ArrayBuffer): Promise<void> {
89
+ const blob = new Blob([buffer], { type: 'application/octet-stream' });
90
+ const url = URL.createObjectURL(blob);
91
+
92
+ const anchor = document.createElement('a');
93
+ anchor.href = url;
94
+ anchor.download = filename;
95
+ anchor.style.display = 'none';
96
+
97
+ document.body.appendChild(anchor);
98
+ anchor.click();
99
+ document.body.removeChild(anchor);
100
+
101
+ // Release the object URL after the TTL to allow the download to start
102
+ setTimeout(() => URL.revokeObjectURL(url), DOWNLOAD_URL_TTL_MS);
103
+ }
104
+
105
+ // ── File System Access API helpers ────────────────────────────────────────────
106
+
107
+ export async function saveViaFileSystemAPI(filename: string, buffer: ArrayBuffer): Promise<void> {
108
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
109
+ const win = window as any;
110
+ if (typeof win.showSaveFilePicker !== 'function') {
111
+ throw new SessionError(
112
+ 'STORAGE_UNAVAILABLE',
113
+ 'File System Access API (showSaveFilePicker) is not available in this browser.',
114
+ );
115
+ }
116
+
117
+ const handle = await win.showSaveFilePicker({
118
+ suggestedName: filename,
119
+ types: [{ description: 'MambaSession Checkpoint', accept: { 'application/octet-stream': ['.bin'] } }],
120
+ });
121
+ const writable = await handle.createWritable();
122
+ await writable.write(buffer);
123
+ await writable.close();
124
+ }
125
+
126
+ export async function loadViaFileSystemAPI(): Promise<ArrayBuffer> {
127
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
128
+ const win = window as any;
129
+ if (typeof win.showOpenFilePicker !== 'function') {
130
+ throw new SessionError(
131
+ 'STORAGE_UNAVAILABLE',
132
+ 'File System Access API (showOpenFilePicker) is not available in this browser.',
133
+ );
134
+ }
135
+
136
+ const [handle] = await win.showOpenFilePicker({
137
+ types: [{ description: 'MambaSession Checkpoint', accept: { 'application/octet-stream': ['.bin'] } }],
138
+ multiple: false,
139
+ });
140
+ const file = await handle.getFile();
141
+ return file.arrayBuffer();
142
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * presets.ts – Model size presets and config resolver for the MambaSession layer.
3
+ */
4
+
5
+ import type { HybridMambaModelConfig, LayerSpec, LayerType } from '@seanhogg/builderforce-memory-engine';
6
+ import type { MambaSessionOptions } from './session.js';
7
+
8
+ // ── Model size presets ────────────────────────────────────────────────────────
9
+
10
+ /**
11
+ * Pre-defined model size presets.
12
+ * nHeads is used by Mamba-2/3 and Attention layers; ignored for Mamba-1.
13
+ */
14
+ export const MODEL_PRESETS: Record<string, Partial<HybridMambaModelConfig>> = {
15
+ nano : { dModel: 128, numLayers: 4, dState: 16, dConv: 4, expand: 2, nHeads: 4 },
16
+ small : { dModel: 256, numLayers: 6, dState: 16, dConv: 4, expand: 2, nHeads: 8 },
17
+ medium: { dModel: 512, numLayers: 8, dState: 16, dConv: 4, expand: 2, nHeads: 8 },
18
+ large : { dModel: 768, numLayers: 12, dState: 16, dConv: 4, expand: 2, nHeads: 12 },
19
+ };
20
+
21
+ const DEFAULT_PRESET = 'nano';
22
+
23
+ // ── Layer schedule presets ────────────────────────────────────────────────────
24
+
25
+ export type LayerSchedulePreset = 'jamba' | 'zamba';
26
+
27
+ /**
28
+ * Resolve a layer schedule from a preset name, explicit array, or undefined.
29
+ *
30
+ * 'jamba' — Jamba-style: every 4th layer (index 3, 7, 11…) is attention, rest mamba2
31
+ * 'zamba' — Zamba-style: every 6th layer (index 5, 11…) is attention, rest mamba3
32
+ */
33
+ export function resolveLayerSchedule(
34
+ schedule : LayerSpec[] | LayerSchedulePreset | undefined,
35
+ numLayers : number,
36
+ defaultType: LayerType,
37
+ ): LayerSpec[] {
38
+ if (!schedule) {
39
+ return Array.from({ length: numLayers }, () => ({ type: defaultType }));
40
+ }
41
+
42
+ if (schedule === 'jamba') {
43
+ return Array.from({ length: numLayers }, (_, i) => ({
44
+ type: (i % 4 === 3 ? 'attention' : 'mamba2') as LayerType,
45
+ }));
46
+ }
47
+
48
+ if (schedule === 'zamba') {
49
+ return Array.from({ length: numLayers }, (_, i) => ({
50
+ type: (i % 6 === 5 ? 'attention' : 'mamba3') as LayerType,
51
+ }));
52
+ }
53
+
54
+ return schedule;
55
+ }
56
+
57
+ // ── Config resolution ─────────────────────────────────────────────────────────
58
+
59
+ /**
60
+ * Resolves a fully-populated HybridMambaModelConfig from session options and
61
+ * the actual tokenizer vocab size.
62
+ *
63
+ * Resolution order:
64
+ * 1. Preset fields (default: 'nano')
65
+ * 2. modelConfig overrides (only applied when modelSize === 'custom')
66
+ * 3. vocabSize from the tokenizer
67
+ * 4. mambaVersion → default layer type for schedule resolution
68
+ * 5. layerSchedule → per-layer type array (preset string or explicit array)
69
+ */
70
+ export function resolveModelConfig(
71
+ options : MambaSessionOptions,
72
+ vocabSize: number,
73
+ ): HybridMambaModelConfig {
74
+ const presetName = options.modelSize === 'custom' || options.modelSize == null
75
+ ? DEFAULT_PRESET
76
+ : options.modelSize;
77
+
78
+ const preset = MODEL_PRESETS[presetName] ?? MODEL_PRESETS[DEFAULT_PRESET]!;
79
+
80
+ const overrides: Partial<HybridMambaModelConfig> =
81
+ options.modelSize === 'custom' && options.modelConfig
82
+ ? options.modelConfig
83
+ : {};
84
+
85
+ const dModel = overrides.dModel ?? preset.dModel ?? 128;
86
+ const numLayers = overrides.numLayers ?? preset.numLayers ?? 4;
87
+ const dState = overrides.dState ?? preset.dState ?? 16;
88
+ const dConv = overrides.dConv ?? preset.dConv ?? 4;
89
+ const expand = overrides.expand ?? preset.expand ?? 2;
90
+ const nHeads = overrides.nHeads ?? preset.nHeads ?? 4;
91
+ const nGroups = overrides.nGroups ?? preset.nGroups ?? 1;
92
+ const chunkLen = overrides.chunkLen ?? preset.chunkLen ?? 256;
93
+ const mimoGroup = overrides.mimoGroup ?? preset.mimoGroup ?? 1;
94
+ const eosId = overrides.eosId ?? preset.eosId ?? -1;
95
+
96
+ // Validate nHeads divides dModel for multi-head blocks
97
+ if (dModel % nHeads !== 0) {
98
+ throw new Error(
99
+ `resolveModelConfig: dModel (${dModel}) must be divisible by nHeads (${nHeads}).`
100
+ );
101
+ }
102
+
103
+ // Layer schedule
104
+ const defaultType: LayerType = options.mambaVersion ?? 'mamba1';
105
+ const layers = resolveLayerSchedule(options.layerSchedule, numLayers, defaultType);
106
+
107
+ return {
108
+ vocabSize,
109
+ dModel,
110
+ numLayers,
111
+ dState,
112
+ dConv,
113
+ expand,
114
+ nHeads,
115
+ nGroups,
116
+ chunkLen,
117
+ mimoGroup,
118
+ eosId,
119
+ layers,
120
+ seed: options.seed,
121
+ };
122
+ }