@simulatte/doppler 0.1.3 → 0.1.4

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/README.md CHANGED
@@ -15,14 +15,14 @@ npm install @simulatte/doppler
15
15
  ```js
16
16
  import { doppler } from '@simulatte/doppler';
17
17
 
18
- const model = await doppler.load('gemma-3-1b');
18
+ const model = await doppler.load('gemma3-270m');
19
19
 
20
20
  for await (const token of model.generate('Hello, world')) {
21
21
  process.stdout.write(token);
22
22
  }
23
23
  ```
24
24
 
25
- Tokens stream from a native `AsyncGenerator`. See [more examples](#more-examples) below or the full [API contract](docs/doppler-api-contract.md).
25
+ Registry IDs resolve to hosted RDRR artifacts from `Clocksmith/rdrr` by default. Tokens stream from a native `AsyncGenerator`. See [more examples](#more-examples) below or the full [API contract](docs/doppler-api-contract.md).
26
26
 
27
27
  ## Why Doppler
28
28
 
@@ -56,6 +56,11 @@ Snapshot artifacts:
56
56
  // Non-streaming
57
57
  const text = await model.generateText('Explain WebGPU in one sentence');
58
58
 
59
+ // Load with progress logging
60
+ const modelWithProgress = await doppler.load('gemma3-270m', {
61
+ onProgress: ({ message }) => console.log(`[doppler] ${message}`),
62
+ });
63
+
59
64
  // Chat
60
65
  const reply = await model.chatText([
61
66
  { role: 'user', content: 'Write a dispatch that outruns its own light cone' },
@@ -65,7 +70,7 @@ const reply = await model.chatText([
65
70
  await model.loadLoRA('oneshift-twoshift-redshift-blueshift');
66
71
 
67
72
  // Convenience shorthand (caches model automatically)
68
- for await (const token of doppler('Hello', { model: 'gemma-3-1b' })) {
73
+ for await (const token of doppler('Hello', { model: 'gemma3-270m' })) {
69
74
  process.stdout.write(token);
70
75
  }
71
76
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simulatte/doppler",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Browser-native WebGPU inference engine for local intent and inference loops",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -14,6 +14,8 @@
14
14
  "manifest-refresh": "node tools/refresh-converted-manifest.js",
15
15
  "debug": "node tools/doppler-cli.js debug",
16
16
  "bench": "node tools/doppler-cli.js bench",
17
+ "lean:check": "./lean/check.sh",
18
+ "lean:execution-contract": "node tools/lean-execution-contract.js",
17
19
  "bench:chart": "node ./benchmarks/vendors/compare-chart.js",
18
20
  "bench:chart:readme": "node ./benchmarks/vendors/compare-chart.js --preset readme-evidence",
19
21
  "bench:architecture:chart": "node ./benchmarks/vendors/generate-architecture-overview-svg.js",
@@ -0,0 +1,80 @@
1
+ import type { RDRRManifest } from '../formats/rdrr/index.js';
2
+ import type { GenerateOptions, KVCacheSnapshot } from '../generation/index.js';
3
+ import type { ChatMessage } from '../inference/pipelines/text/chat-format.js';
4
+ import type { LoRAManifest } from '../adapters/lora-loader.js';
5
+
6
+ export interface DopplerLoadProgress {
7
+ phase: 'resolve' | 'manifest' | 'load' | 'ready';
8
+ percent: number;
9
+ message: string;
10
+ }
11
+
12
+ export interface DopplerLoadOptions {
13
+ onProgress?: (event: DopplerLoadProgress) => void;
14
+ runtimeConfig?: Record<string, unknown>;
15
+ }
16
+
17
+ export interface DopplerCallOptions extends GenerateOptions {
18
+ model: string | { url: string };
19
+ onProgress?: (event: DopplerLoadProgress) => void;
20
+ }
21
+
22
+ export interface DopplerChatResponse {
23
+ content: string;
24
+ usage: {
25
+ promptTokens: number;
26
+ completionTokens: number;
27
+ totalTokens: number;
28
+ };
29
+ }
30
+
31
+ export interface DopplerModel {
32
+ generate(prompt: string, options?: GenerateOptions): AsyncGenerator<string, void, void>;
33
+ generateText(prompt: string, options?: GenerateOptions): Promise<string>;
34
+ chat(messages: ChatMessage[], options?: GenerateOptions): AsyncGenerator<string, void, void>;
35
+ chatText(messages: ChatMessage[], options?: GenerateOptions): Promise<DopplerChatResponse>;
36
+ loadLoRA(adapter: string | LoRAManifest): Promise<void>;
37
+ unloadLoRA(): Promise<void>;
38
+ unload(): Promise<void>;
39
+ readonly activeLoRA: string | null;
40
+ readonly loaded: boolean;
41
+ readonly modelId: string;
42
+ readonly manifest: RDRRManifest;
43
+ readonly deviceInfo: Record<string, unknown> | null;
44
+ readonly advanced: {
45
+ prefillKV(prompt: string, options?: GenerateOptions): Promise<KVCacheSnapshot>;
46
+ generateWithPrefixKV(
47
+ prefix: KVCacheSnapshot,
48
+ prompt: string,
49
+ options?: GenerateOptions
50
+ ): AsyncGenerator<string, void, void>;
51
+ };
52
+ }
53
+
54
+ export interface DopplerNamespace {
55
+ (prompt: string, options: DopplerCallOptions): AsyncGenerator<string, void, void>;
56
+ load(
57
+ model: string | { url: string } | { manifest: RDRRManifest; baseUrl?: string },
58
+ options?: DopplerLoadOptions
59
+ ): Promise<DopplerModel>;
60
+ text(prompt: string, options: DopplerCallOptions): Promise<string>;
61
+ chat(messages: ChatMessage[], options: DopplerCallOptions): AsyncGenerator<string, void, void>;
62
+ chatText(messages: ChatMessage[], options: DopplerCallOptions): Promise<DopplerChatResponse>;
63
+ evict(model: string | { url: string }): Promise<boolean>;
64
+ evictAll(): Promise<void>;
65
+ listModels(): Promise<string[]>;
66
+ }
67
+
68
+ export declare function load(
69
+ model: string | { url: string } | { manifest: RDRRManifest; baseUrl?: string },
70
+ options?: DopplerLoadOptions
71
+ ): Promise<DopplerModel>;
72
+
73
+ export declare function createDefaultNodeLoadProgressLogger(): (event: DopplerLoadProgress) => void;
74
+
75
+ export declare function resolveLoadProgressHandlers(options?: DopplerLoadOptions): {
76
+ userProgress: ((event: DopplerLoadProgress) => void) | null;
77
+ pipelineProgress: ((event: DopplerLoadProgress) => void) | null;
78
+ };
79
+
80
+ export declare const doppler: DopplerNamespace;
@@ -0,0 +1,298 @@
1
+ import { loadLoRAFromManifest, loadLoRAFromUrl } from '../adapters/lora-loader.js';
2
+ import { log } from '../debug/index.js';
3
+ import { getManifestUrl, parseManifest } from '../formats/rdrr/index.js';
4
+ import { createPipeline } from '../generation/index.js';
5
+ import { getKernelCapabilities } from '../gpu/device.js';
6
+ import { formatChatMessages } from '../inference/pipelines/text/chat-format.js';
7
+ import { bootstrapNodeWebGPU } from '../tooling/node-webgpu.js';
8
+ import { buildQuickstartModelBaseUrl, listQuickstartModels, resolveQuickstartModel } from './doppler-registry.js';
9
+
10
+ const convenienceModelCache = new Map();
11
+ const inFlightLoadCache = new Map();
12
+
13
+ function isNodeRuntime() {
14
+ return typeof process !== 'undefined'
15
+ && typeof process.versions === 'object'
16
+ && typeof process.versions.node === 'string';
17
+ }
18
+
19
+ async function ensureWebGPUAvailable() {
20
+ if (typeof globalThis.navigator !== 'undefined' && globalThis.navigator?.gpu) {
21
+ return;
22
+ }
23
+ if (isNodeRuntime()) {
24
+ const result = await bootstrapNodeWebGPU();
25
+ if (result.ok && globalThis.navigator?.gpu) {
26
+ return;
27
+ }
28
+ }
29
+ throw new Error('WebGPU is unavailable. Install a Node WebGPU provider or run in a WebGPU-capable browser.');
30
+ }
31
+
32
+ function emitLoadProgress(callback, phase, percent, message) {
33
+ if (typeof callback !== 'function') return;
34
+ callback({ phase, percent, message });
35
+ }
36
+
37
+ export function createDefaultNodeLoadProgressLogger() {
38
+ return (event) => {
39
+ const message = typeof event?.message === 'string' ? event.message.trim() : '';
40
+ if (!message) return;
41
+ log.info('doppler', message);
42
+ };
43
+ }
44
+
45
+ export function resolveLoadProgressHandlers(options = {}) {
46
+ const onProgress = typeof options?.onProgress === 'function' ? options.onProgress : null;
47
+ if (onProgress) {
48
+ return {
49
+ userProgress: onProgress,
50
+ pipelineProgress: onProgress,
51
+ };
52
+ }
53
+ if (isNodeRuntime()) {
54
+ return {
55
+ userProgress: createDefaultNodeLoadProgressLogger(),
56
+ pipelineProgress: null,
57
+ };
58
+ }
59
+ return {
60
+ userProgress: null,
61
+ pipelineProgress: null,
62
+ };
63
+ }
64
+
65
+ async function fetchManifestFromBaseUrl(baseUrl) {
66
+ const response = await fetch(getManifestUrl(baseUrl));
67
+ if (!response.ok) {
68
+ throw new Error(`Failed to fetch manifest from ${baseUrl}: ${response.status}`);
69
+ }
70
+ return parseManifest(await response.text());
71
+ }
72
+
73
+ async function resolveModelSource(model) {
74
+ if (typeof model === 'string') {
75
+ const entry = await resolveQuickstartModel(model);
76
+ return {
77
+ modelId: entry.modelId,
78
+ baseUrl: buildQuickstartModelBaseUrl(entry),
79
+ manifest: null,
80
+ };
81
+ }
82
+ if (model && typeof model === 'object' && typeof model.url === 'string' && model.url.trim().length > 0) {
83
+ return {
84
+ modelId: model.url.trim(),
85
+ baseUrl: model.url.trim(),
86
+ manifest: null,
87
+ };
88
+ }
89
+ if (model && typeof model === 'object' && model.manifest && typeof model.manifest === 'object') {
90
+ const manifest = model.manifest;
91
+ const modelId = typeof manifest.modelId === 'string' && manifest.modelId.length > 0
92
+ ? manifest.modelId
93
+ : 'manifest';
94
+ return {
95
+ modelId,
96
+ baseUrl: typeof model.baseUrl === 'string' && model.baseUrl.length > 0 ? model.baseUrl : null,
97
+ manifest,
98
+ };
99
+ }
100
+ throw new Error('doppler.load expects a quickstart registry id, { url }, or { manifest, baseUrl? }.');
101
+ }
102
+
103
+ function countTokens(pipeline, text) {
104
+ if (!text || typeof text !== 'string') return 0;
105
+ try {
106
+ return pipeline?.tokenizer?.encode(text)?.length ?? 0;
107
+ } catch {
108
+ return 0;
109
+ }
110
+ }
111
+
112
+ function resolveChatPromptForUsage(pipeline, messages) {
113
+ const templateType = pipeline?.manifest?.inference?.chatTemplate?.enabled === false
114
+ ? null
115
+ : (pipeline?.manifest?.inference?.chatTemplate?.type ?? null);
116
+ try {
117
+ return formatChatMessages(messages, templateType);
118
+ } catch {
119
+ return messages.map((message) => String(message?.content ?? '')).join('\n');
120
+ }
121
+ }
122
+
123
+ async function collectText(iterable) {
124
+ let output = '';
125
+ for await (const token of iterable) {
126
+ output += token;
127
+ }
128
+ return output;
129
+ }
130
+
131
+ function createModelHandle(pipeline, resolved) {
132
+ return {
133
+ generate(prompt, options = {}) {
134
+ return pipeline.generate(prompt, options);
135
+ },
136
+ async generateText(prompt, options = {}) {
137
+ return collectText(pipeline.generate(prompt, options));
138
+ },
139
+ chat(messages, options = {}) {
140
+ return pipeline.generate(messages, options);
141
+ },
142
+ async chatText(messages, options = {}) {
143
+ const content = await collectText(pipeline.generate(messages, options));
144
+ const promptText = resolveChatPromptForUsage(pipeline, messages);
145
+ const promptTokens = countTokens(pipeline, promptText);
146
+ const completionTokens = countTokens(pipeline, content);
147
+ return {
148
+ content,
149
+ usage: {
150
+ promptTokens,
151
+ completionTokens,
152
+ totalTokens: promptTokens + completionTokens,
153
+ },
154
+ };
155
+ },
156
+ async loadLoRA(adapter) {
157
+ const lora = typeof adapter === 'string'
158
+ ? await loadLoRAFromUrl(adapter)
159
+ : await loadLoRAFromManifest(adapter);
160
+ pipeline.setLoRAAdapter(lora);
161
+ },
162
+ async unloadLoRA() {
163
+ pipeline.setLoRAAdapter(null);
164
+ },
165
+ async unload() {
166
+ await pipeline.unload();
167
+ },
168
+ get activeLoRA() {
169
+ return pipeline.getActiveLoRA()?.name ?? null;
170
+ },
171
+ get loaded() {
172
+ return pipeline.isLoaded === true;
173
+ },
174
+ get modelId() {
175
+ return resolved.modelId;
176
+ },
177
+ get manifest() {
178
+ return pipeline.manifest;
179
+ },
180
+ get deviceInfo() {
181
+ return getKernelCapabilities()?.adapterInfo ?? null;
182
+ },
183
+ advanced: {
184
+ prefillKV(prompt, options = {}) {
185
+ return pipeline.prefillKVOnly(prompt, options);
186
+ },
187
+ generateWithPrefixKV(prefix, prompt, options = {}) {
188
+ return pipeline.generateWithPrefixKV(prefix, prompt, options);
189
+ },
190
+ },
191
+ };
192
+ }
193
+
194
+ export async function load(model, options = {}) {
195
+ const { userProgress, pipelineProgress } = resolveLoadProgressHandlers(options);
196
+
197
+ emitLoadProgress(userProgress, 'resolve', 5, 'Resolving model');
198
+ const resolved = await resolveModelSource(model);
199
+ await ensureWebGPUAvailable();
200
+
201
+ emitLoadProgress(userProgress, 'manifest', 15, 'Fetching manifest');
202
+ const manifest = resolved.manifest ?? await fetchManifestFromBaseUrl(resolved.baseUrl);
203
+
204
+ emitLoadProgress(userProgress, 'load', 25, 'Loading weights');
205
+ const pipeline = await createPipeline(manifest, {
206
+ baseUrl: resolved.baseUrl ?? undefined,
207
+ runtimeConfig: options.runtimeConfig,
208
+ onProgress: pipelineProgress
209
+ ? (progress) => emitLoadProgress(
210
+ pipelineProgress,
211
+ 'load',
212
+ Math.max(25, Math.min(99, Math.round(progress.percent))),
213
+ progress.message || 'Loading weights'
214
+ )
215
+ : undefined,
216
+ });
217
+
218
+ emitLoadProgress(userProgress, 'ready', 100, 'Model ready');
219
+ return createModelHandle(pipeline, resolved);
220
+ }
221
+
222
+ async function getCachedModel(model, options = {}) {
223
+ const resolved = await resolveModelSource(model);
224
+ const cacheKey = resolved.modelId;
225
+ const cached = convenienceModelCache.get(cacheKey);
226
+ if (cached?.loaded) {
227
+ return cached;
228
+ }
229
+ if (cached && !cached.loaded) {
230
+ convenienceModelCache.delete(cacheKey);
231
+ }
232
+ if (!inFlightLoadCache.has(cacheKey)) {
233
+ inFlightLoadCache.set(cacheKey, load(model, options).then((instance) => {
234
+ convenienceModelCache.set(cacheKey, instance);
235
+ inFlightLoadCache.delete(cacheKey);
236
+ return instance;
237
+ }).catch((error) => {
238
+ inFlightLoadCache.delete(cacheKey);
239
+ throw error;
240
+ }));
241
+ }
242
+ return inFlightLoadCache.get(cacheKey);
243
+ }
244
+
245
+ async function* dopplerGenerate(prompt, options = {}) {
246
+ if (!options || typeof options !== 'object' || options.model == null) {
247
+ throw new Error('doppler() requires options.model.');
248
+ }
249
+ if (options.runtimeConfig !== undefined || options.runtimePreset !== undefined) {
250
+ throw new Error('doppler() does not accept load-affecting options. Use doppler.load(model, options) instead.');
251
+ }
252
+ const model = await getCachedModel(options.model, { onProgress: options.onProgress });
253
+ yield* model.generate(prompt, options);
254
+ }
255
+
256
+ export function doppler(prompt, options) {
257
+ return dopplerGenerate(prompt, options);
258
+ }
259
+
260
+ doppler.load = load;
261
+ doppler.text = async function text(prompt, options) {
262
+ return collectText(doppler(prompt, options));
263
+ };
264
+ doppler.chat = function chat(messages, options = {}) {
265
+ if (!options || typeof options !== 'object' || options.model == null) {
266
+ throw new Error('doppler.chat() requires options.model.');
267
+ }
268
+ return (async function* () {
269
+ const model = await getCachedModel(options.model, { onProgress: options.onProgress });
270
+ yield* model.chat(messages, options);
271
+ }());
272
+ };
273
+ doppler.chatText = async function chatText(messages, options = {}) {
274
+ if (!options || typeof options !== 'object' || options.model == null) {
275
+ throw new Error('doppler.chatText() requires options.model.');
276
+ }
277
+ const model = await getCachedModel(options.model, { onProgress: options.onProgress });
278
+ return model.chatText(messages, options);
279
+ };
280
+ doppler.evict = async function evict(model) {
281
+ const resolved = await resolveModelSource(model);
282
+ const cached = convenienceModelCache.get(resolved.modelId);
283
+ if (!cached) {
284
+ return false;
285
+ }
286
+ convenienceModelCache.delete(resolved.modelId);
287
+ await cached.unload();
288
+ return true;
289
+ };
290
+ doppler.evictAll = async function evictAll() {
291
+ const cached = [...convenienceModelCache.values()];
292
+ convenienceModelCache.clear();
293
+ await Promise.all(cached.map((entry) => entry.unload()));
294
+ };
295
+ doppler.listModels = async function listModels() {
296
+ const models = await listQuickstartModels();
297
+ return models.map((entry) => entry.modelId);
298
+ };
@@ -0,0 +1,23 @@
1
+ export interface QuickstartRegistryEntry {
2
+ modelId: string;
3
+ aliases: string[];
4
+ modes: string[];
5
+ hf: {
6
+ repoId: string;
7
+ revision: string | null;
8
+ path: string;
9
+ } | null;
10
+ }
11
+
12
+ export declare function listQuickstartModels(): Promise<Array<{
13
+ modelId: string;
14
+ aliases: string[];
15
+ modes: string[];
16
+ }>>;
17
+
18
+ export declare function resolveQuickstartModel(model: string): Promise<QuickstartRegistryEntry>;
19
+
20
+ export declare function buildQuickstartModelBaseUrl(
21
+ entry: QuickstartRegistryEntry,
22
+ options?: { cdnBasePath?: string }
23
+ ): string;
@@ -0,0 +1,88 @@
1
+ import { getCdnBasePath } from '../storage/download-types.js';
2
+ import { loadJson } from '../utils/load-json.js';
3
+
4
+ let registryPromise = null;
5
+
6
+ function normalizeEntry(entry) {
7
+ if (!entry || typeof entry !== 'object') {
8
+ return null;
9
+ }
10
+ const modelId = typeof entry.modelId === 'string' ? entry.modelId.trim() : '';
11
+ if (!modelId) {
12
+ return null;
13
+ }
14
+ return {
15
+ modelId,
16
+ aliases: Array.isArray(entry.aliases)
17
+ ? entry.aliases.filter((alias) => typeof alias === 'string' && alias.trim().length > 0)
18
+ : [],
19
+ modes: Array.isArray(entry.modes)
20
+ ? entry.modes.filter((mode) => typeof mode === 'string' && mode.trim().length > 0)
21
+ : [],
22
+ hf: entry.hf && typeof entry.hf === 'object'
23
+ ? {
24
+ repoId: typeof entry.hf.repoId === 'string' ? entry.hf.repoId.trim() : '',
25
+ revision: typeof entry.hf.revision === 'string' && entry.hf.revision.trim().length > 0
26
+ ? entry.hf.revision.trim()
27
+ : null,
28
+ path: typeof entry.hf.path === 'string' ? entry.hf.path.trim() : '',
29
+ }
30
+ : null,
31
+ };
32
+ }
33
+
34
+ async function loadRegistry() {
35
+ if (!registryPromise) {
36
+ registryPromise = loadJson(
37
+ './doppler-registry.json',
38
+ import.meta.url,
39
+ 'Failed to load Doppler quickstart registry'
40
+ ).then((raw) => {
41
+ const entries = Array.isArray(raw?.models)
42
+ ? raw.models.map(normalizeEntry).filter(Boolean)
43
+ : [];
44
+ return { models: entries };
45
+ });
46
+ }
47
+ return registryPromise;
48
+ }
49
+
50
+ export async function listQuickstartModels() {
51
+ const registry = await loadRegistry();
52
+ return registry.models.map((entry) => ({
53
+ modelId: entry.modelId,
54
+ aliases: [...entry.aliases],
55
+ modes: [...entry.modes],
56
+ }));
57
+ }
58
+
59
+ export async function resolveQuickstartModel(model) {
60
+ const requested = typeof model === 'string' ? model.trim() : '';
61
+ if (!requested) {
62
+ throw new Error('Quickstart model id is required.');
63
+ }
64
+
65
+ const registry = await loadRegistry();
66
+ const resolved = registry.models.find((entry) => (
67
+ entry.modelId === requested || entry.aliases.includes(requested)
68
+ ));
69
+ if (resolved) {
70
+ return resolved;
71
+ }
72
+
73
+ const available = registry.models.map((entry) => entry.modelId).join(', ');
74
+ throw new Error(`Unknown quickstart model "${requested}". Available: ${available}`);
75
+ }
76
+
77
+ export function buildQuickstartModelBaseUrl(entry, options = {}) {
78
+ if (!entry?.hf?.repoId || !entry?.hf?.path) {
79
+ throw new Error(`Quickstart model "${entry?.modelId ?? 'unknown'}" does not have a hosted Hugging Face source.`);
80
+ }
81
+ const cdnBasePath = typeof options.cdnBasePath === 'string' && options.cdnBasePath.length > 0
82
+ ? options.cdnBasePath
83
+ : (getCdnBasePath() || 'https://huggingface.co');
84
+ const revision = entry.hf.revision || 'main';
85
+ const base = cdnBasePath.replace(/\/$/, '');
86
+ const path = entry.hf.path.replace(/^\/+/, '');
87
+ return `${base}/${entry.hf.repoId}/resolve/${revision}/${path}`;
88
+ }
@@ -0,0 +1,39 @@
1
+ {
2
+ "version": 1,
3
+ "source": "Clocksmith/rdrr",
4
+ "models": [
5
+ {
6
+ "modelId": "gemma-3-270m-it-wq4k-ef16-hf16",
7
+ "aliases": [
8
+ "gemma3-270m",
9
+ "google/gemma-3-270m-it",
10
+ "gemma-3-270m-it-wq4k-ef16",
11
+ "gemma-3-270m-it-wq4k-ef16-hf16-f32"
12
+ ],
13
+ "modes": [
14
+ "run"
15
+ ],
16
+ "hf": {
17
+ "repoId": "Clocksmith/rdrr",
18
+ "revision": "4efe64a914892e98be50842aeb16c3b648cc68a5",
19
+ "path": "models/gemma-3-270m-it-wq4k-ef16"
20
+ }
21
+ },
22
+ {
23
+ "modelId": "google-embeddinggemma-300m-wq4k-ef16",
24
+ "aliases": [
25
+ "embeddinggemma-300m",
26
+ "google/embeddinggemma-300m",
27
+ "google-embeddinggemma-300m-wq4k-ef16"
28
+ ],
29
+ "modes": [
30
+ "embedding"
31
+ ],
32
+ "hf": {
33
+ "repoId": "Clocksmith/rdrr",
34
+ "revision": "4efe64a914892e98be50842aeb16c3b648cc68a5",
35
+ "path": "models/google-embeddinggemma-300m-wq4k-ef16"
36
+ }
37
+ }
38
+ ]
39
+ }
@@ -0,0 +1,49 @@
1
+ export interface ExecutionContractStepFacts {
2
+ id: string;
3
+ phase: 'prefill' | 'decode' | 'both';
4
+ opClass: 'attention' | 'embed' | 'norm' | 'projection' | 'residual' | 'sample' | 'other';
5
+ }
6
+
7
+ export interface ExecutionContractSessionFacts {
8
+ layout: 'contiguous' | 'paged' | 'tiered' | 'bdpa';
9
+ disableCommandBatching: boolean;
10
+ decodeBatchSize: number;
11
+ headDim: number;
12
+ kvLen: number;
13
+ coldQuantMode: 'none' | 'int8' | 'int4';
14
+ }
15
+
16
+ export interface ExecutionContractFacts {
17
+ modelId: string;
18
+ session: ExecutionContractSessionFacts;
19
+ steps: ExecutionContractStepFacts[];
20
+ }
21
+
22
+ export interface ExecutionContractCheckResult {
23
+ id: string;
24
+ ok: boolean;
25
+ }
26
+
27
+ export interface ExecutionContractValidationResult {
28
+ ok: boolean;
29
+ errors: string[];
30
+ checks: ExecutionContractCheckResult[];
31
+ }
32
+
33
+ export interface ManifestExecutionContractValidationResult extends ExecutionContractValidationResult {
34
+ facts: ExecutionContractFacts;
35
+ }
36
+
37
+ export declare function sanitizeLeanModuleName(value: unknown): string;
38
+
39
+ export declare function extractExecutionContractFacts(
40
+ manifest: Record<string, unknown>
41
+ ): ExecutionContractFacts;
42
+
43
+ export declare function validateExecutionContractFacts(
44
+ facts: ExecutionContractFacts
45
+ ): ExecutionContractValidationResult;
46
+
47
+ export declare function validateManifestExecutionContract(
48
+ manifest: Record<string, unknown>
49
+ ): ManifestExecutionContractValidationResult;