@missionsquad/browserai 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 (146) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +229 -0
  3. package/dist/audio/clip.d.ts +51 -0
  4. package/dist/audio/clip.d.ts.map +1 -0
  5. package/dist/audio/clip.js +71 -0
  6. package/dist/audio/clip.js.map +1 -0
  7. package/dist/audio/decode.d.ts +7 -0
  8. package/dist/audio/decode.d.ts.map +1 -0
  9. package/dist/audio/decode.js +19 -0
  10. package/dist/audio/decode.js.map +1 -0
  11. package/dist/audio/pcm.d.ts +13 -0
  12. package/dist/audio/pcm.d.ts.map +1 -0
  13. package/dist/audio/pcm.js +66 -0
  14. package/dist/audio/pcm.js.map +1 -0
  15. package/dist/audio/recorder.d.ts +40 -0
  16. package/dist/audio/recorder.d.ts.map +1 -0
  17. package/dist/audio/recorder.js +127 -0
  18. package/dist/audio/recorder.js.map +1 -0
  19. package/dist/audio/streaming-player.d.ts +29 -0
  20. package/dist/audio/streaming-player.d.ts.map +1 -0
  21. package/dist/audio/streaming-player.js +82 -0
  22. package/dist/audio/streaming-player.js.map +1 -0
  23. package/dist/client.d.ts +195 -0
  24. package/dist/client.d.ts.map +1 -0
  25. package/dist/client.js +638 -0
  26. package/dist/client.js.map +1 -0
  27. package/dist/config.d.ts +81 -0
  28. package/dist/config.d.ts.map +1 -0
  29. package/dist/config.js +19 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/errors.d.ts +52 -0
  32. package/dist/errors.d.ts.map +1 -0
  33. package/dist/errors.js +74 -0
  34. package/dist/errors.js.map +1 -0
  35. package/dist/events.d.ts +18 -0
  36. package/dist/events.d.ts.map +1 -0
  37. package/dist/events.js +43 -0
  38. package/dist/events.js.map +1 -0
  39. package/dist/generation.d.ts +12 -0
  40. package/dist/generation.d.ts.map +1 -0
  41. package/dist/generation.js +30 -0
  42. package/dist/generation.js.map +1 -0
  43. package/dist/hardware.d.ts +23 -0
  44. package/dist/hardware.d.ts.map +1 -0
  45. package/dist/hardware.js +150 -0
  46. package/dist/hardware.js.map +1 -0
  47. package/dist/history.d.ts +81 -0
  48. package/dist/history.d.ts.map +1 -0
  49. package/dist/history.js +193 -0
  50. package/dist/history.js.map +1 -0
  51. package/dist/index.d.ts +51 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +50 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/inference.d.ts +89 -0
  56. package/dist/inference.d.ts.map +1 -0
  57. package/dist/inference.js +411 -0
  58. package/dist/inference.js.map +1 -0
  59. package/dist/json.d.ts +22 -0
  60. package/dist/json.d.ts.map +1 -0
  61. package/dist/json.js +145 -0
  62. package/dist/json.js.map +1 -0
  63. package/dist/metrics.d.ts +18 -0
  64. package/dist/metrics.d.ts.map +1 -0
  65. package/dist/metrics.js +59 -0
  66. package/dist/metrics.js.map +1 -0
  67. package/dist/model-cache.d.ts +49 -0
  68. package/dist/model-cache.d.ts.map +1 -0
  69. package/dist/model-cache.js +511 -0
  70. package/dist/model-cache.js.map +1 -0
  71. package/dist/model-source.d.ts +27 -0
  72. package/dist/model-source.d.ts.map +1 -0
  73. package/dist/model-source.js +110 -0
  74. package/dist/model-source.js.map +1 -0
  75. package/dist/models.d.ts +103 -0
  76. package/dist/models.d.ts.map +1 -0
  77. package/dist/models.js +1052 -0
  78. package/dist/models.js.map +1 -0
  79. package/dist/multimodal.d.ts +261 -0
  80. package/dist/multimodal.d.ts.map +1 -0
  81. package/dist/multimodal.js +506 -0
  82. package/dist/multimodal.js.map +1 -0
  83. package/dist/prompt.d.ts +13 -0
  84. package/dist/prompt.d.ts.map +1 -0
  85. package/dist/prompt.js +32 -0
  86. package/dist/prompt.js.map +1 -0
  87. package/dist/proxy-verify.d.ts +10 -0
  88. package/dist/proxy-verify.d.ts.map +1 -0
  89. package/dist/proxy-verify.js +98 -0
  90. package/dist/proxy-verify.js.map +1 -0
  91. package/dist/schema-validator.d.ts +6 -0
  92. package/dist/schema-validator.d.ts.map +1 -0
  93. package/dist/schema-validator.js +104 -0
  94. package/dist/schema-validator.js.map +1 -0
  95. package/dist/schema.d.ts +98 -0
  96. package/dist/schema.d.ts.map +1 -0
  97. package/dist/schema.js +115 -0
  98. package/dist/schema.js.map +1 -0
  99. package/dist/text-utils.d.ts +8 -0
  100. package/dist/text-utils.d.ts.map +1 -0
  101. package/dist/text-utils.js +14 -0
  102. package/dist/text-utils.js.map +1 -0
  103. package/dist/voice/helpers.d.ts +35 -0
  104. package/dist/voice/helpers.d.ts.map +1 -0
  105. package/dist/voice/helpers.js +49 -0
  106. package/dist/voice/helpers.js.map +1 -0
  107. package/dist/voice/voice-pipeline.d.ts +155 -0
  108. package/dist/voice/voice-pipeline.d.ts.map +1 -0
  109. package/dist/voice/voice-pipeline.js +407 -0
  110. package/dist/voice/voice-pipeline.js.map +1 -0
  111. package/dist/webllm.worker.d.ts +2 -0
  112. package/dist/webllm.worker.d.ts.map +1 -0
  113. package/dist/webllm.worker.js +8 -0
  114. package/dist/webllm.worker.js.map +1 -0
  115. package/package.json +62 -0
  116. package/src/audio/clip.ts +94 -0
  117. package/src/audio/decode.ts +19 -0
  118. package/src/audio/pcm.ts +62 -0
  119. package/src/audio/recorder.ts +146 -0
  120. package/src/audio/streaming-player.ts +99 -0
  121. package/src/client.ts +784 -0
  122. package/src/config.ts +104 -0
  123. package/src/errors.ts +81 -0
  124. package/src/events.ts +53 -0
  125. package/src/generation.ts +39 -0
  126. package/src/hardware.ts +193 -0
  127. package/src/history.ts +257 -0
  128. package/src/index.ts +203 -0
  129. package/src/inference.ts +579 -0
  130. package/src/json.ts +169 -0
  131. package/src/metrics.ts +63 -0
  132. package/src/model-cache.ts +637 -0
  133. package/src/model-source.ts +142 -0
  134. package/src/models.ts +1171 -0
  135. package/src/multimodal.ts +697 -0
  136. package/src/prompt.ts +33 -0
  137. package/src/proxy-verify.ts +113 -0
  138. package/src/schema-validator.ts +117 -0
  139. package/src/schema.ts +118 -0
  140. package/src/text-utils.ts +12 -0
  141. package/src/voice/helpers.ts +59 -0
  142. package/src/voice/voice-pipeline.ts +513 -0
  143. package/src/webllm.worker.ts +9 -0
  144. package/worker-template/README.md +39 -0
  145. package/worker-template/cloudflare-worker.ts +389 -0
  146. package/worker-template/wrangler.jsonc +19 -0
package/src/history.ts ADDED
@@ -0,0 +1,257 @@
1
+ import type { ModelPreset } from "./models.js";
2
+ import type { StorageLike } from "./config.js";
3
+
4
+ /**
5
+ * Run-history persistence + aggregation. Ported from small-ai with the storage injected: pass any
6
+ * `StorageLike` (or `null` for in-memory only); the default is `globalThis.localStorage` when present.
7
+ */
8
+
9
+ /** Suggested task keys for logged runs (hosts may use their own strings). */
10
+ export type RunCaseKey = "json" | "chat" | "summarize" | "classify" | "qa" | "voice" | "vision";
11
+
12
+ export type ModelVendor =
13
+ | "qwen"
14
+ | "google"
15
+ | "meta"
16
+ | "openai"
17
+ | "ibm"
18
+ | "microsoft"
19
+ | "nvidia"
20
+ | "liquid"
21
+ | "huggingface"
22
+ | "mistral"
23
+ | "other";
24
+
25
+ /**
26
+ * One logged generation. This is the persisted shape, so every field is a primitive and the
27
+ * set is intentionally minimal: exactly what comparison views need to rebuild their aggregates
28
+ * (decode/ttft by model, run log) without storing full prompts/outputs. Storing raw text or
29
+ * per-token arrays would blow up the storage footprint, so those are excluded.
30
+ */
31
+ export type RunRecord = {
32
+ /** Epoch milliseconds. */
33
+ ts: number;
34
+ /** Model short name, e.g. "Qwen3.5 0.8B". */
35
+ model: string;
36
+ /** Derived vendor bucket for filtering/labels. */
37
+ vendor: ModelVendor;
38
+ /** Backend used for the run. */
39
+ backend: "webllm" | "transformers-js";
40
+ /** Task key ({@link RunCaseKey} suggested; any non-empty string accepted). */
41
+ case: string;
42
+ promptTokens: number;
43
+ completionTokens: number;
44
+ /** Decode speed in tokens/sec. */
45
+ decode: number;
46
+ /** Time to first token in seconds. */
47
+ ttft: number;
48
+ /** Measured wall-clock time in milliseconds. */
49
+ totalMs: number;
50
+ /** Measured completion tokens/sec over wall-clock time. */
51
+ measured: number;
52
+ };
53
+
54
+ export type ModelAggregate = {
55
+ model: string;
56
+ vendor: ModelVendor;
57
+ runs: number;
58
+ decode: number;
59
+ ttft: number;
60
+ totalMs: number;
61
+ measured: number;
62
+ };
63
+
64
+ export const DEFAULT_HISTORY_KEY = "browserai:v1:history";
65
+ export const DEFAULT_HISTORY_MAX_RECORDS = 200;
66
+
67
+ const VALID_VENDORS = new Set<ModelVendor>([
68
+ "qwen",
69
+ "google",
70
+ "meta",
71
+ "openai",
72
+ "ibm",
73
+ "microsoft",
74
+ "nvidia",
75
+ "liquid",
76
+ "huggingface",
77
+ "mistral",
78
+ "other",
79
+ ]);
80
+
81
+ export type RunHistoryOptions = {
82
+ /** Persistence. Defaults to `globalThis.localStorage` when available; `null` = in-memory only. */
83
+ storage?: StorageLike | null;
84
+ key?: string;
85
+ maxRecords?: number;
86
+ };
87
+
88
+ function defaultStorage(): StorageLike | null {
89
+ try {
90
+ // `localStorage` can throw in private-mode / sandboxed contexts; history becomes ephemeral.
91
+ return (globalThis as { localStorage?: StorageLike }).localStorage ?? null;
92
+ } catch {
93
+ return null;
94
+ }
95
+ }
96
+
97
+ function isFiniteNumber(value: unknown): value is number {
98
+ return typeof value === "number" && Number.isFinite(value);
99
+ }
100
+
101
+ function coerceRecord(value: unknown): RunRecord | null {
102
+ if (!value || typeof value !== "object") return null;
103
+ const record = value as Record<string, unknown>;
104
+ const numericKeys = ["ts", "promptTokens", "completionTokens", "decode", "ttft", "totalMs", "measured"] as const;
105
+ for (const key of numericKeys) {
106
+ if (!isFiniteNumber(record[key])) return null;
107
+ }
108
+ if (typeof record.model !== "string" || typeof record.case !== "string" || record.case.length === 0) return null;
109
+
110
+ return {
111
+ ts: record.ts as number,
112
+ model: record.model,
113
+ vendor: VALID_VENDORS.has(record.vendor as ModelVendor) ? (record.vendor as ModelVendor) : "other",
114
+ backend: record.backend === "transformers-js" ? "transformers-js" : "webllm",
115
+ case: record.case,
116
+ promptTokens: record.promptTokens as number,
117
+ completionTokens: record.completionTokens as number,
118
+ decode: record.decode as number,
119
+ ttft: record.ttft as number,
120
+ totalMs: record.totalMs as number,
121
+ measured: record.measured as number,
122
+ };
123
+ }
124
+
125
+ /**
126
+ * A persisted, capped run log (newest first). All storage errors are swallowed: quota failures and
127
+ * unavailable storage degrade to in-memory history rather than breaking generation flows.
128
+ */
129
+ export class RunHistoryStore {
130
+ readonly #storage: StorageLike | null;
131
+ readonly #key: string;
132
+ readonly #maxRecords: number;
133
+ #records: RunRecord[];
134
+
135
+ constructor(options: RunHistoryOptions = {}) {
136
+ this.#storage = options.storage === undefined ? defaultStorage() : options.storage;
137
+ this.#key = options.key ?? DEFAULT_HISTORY_KEY;
138
+ this.#maxRecords = options.maxRecords ?? DEFAULT_HISTORY_MAX_RECORDS;
139
+ this.#records = this.#load();
140
+ }
141
+
142
+ /** The in-memory records, newest first. */
143
+ get records(): readonly RunRecord[] {
144
+ return this.#records;
145
+ }
146
+
147
+ /** Prepend a record and persist. Returns the updated list. */
148
+ append(record: RunRecord): readonly RunRecord[] {
149
+ this.#records = [record, ...this.#records].slice(0, this.#maxRecords);
150
+ this.#persist(this.#records);
151
+ return this.#records;
152
+ }
153
+
154
+ /** Overwrite the history (e.g. after deleting selected rows). */
155
+ replace(records: RunRecord[]): void {
156
+ this.#records = records.slice(0, this.#maxRecords);
157
+ this.#persist(this.#records);
158
+ }
159
+
160
+ clear(): void {
161
+ this.#records = [];
162
+ try {
163
+ this.#storage?.removeItem(this.#key);
164
+ } catch {
165
+ // Clearing is best-effort.
166
+ }
167
+ }
168
+
169
+ aggregateByModel(caseFilter: string | "all" = "all"): ModelAggregate[] {
170
+ return aggregateByModel(this.#records, caseFilter);
171
+ }
172
+
173
+ #load(): RunRecord[] {
174
+ if (!this.#storage) return [];
175
+ try {
176
+ const raw = this.#storage.getItem(this.#key);
177
+ if (!raw) return [];
178
+ const parsed = JSON.parse(raw);
179
+ if (!Array.isArray(parsed)) return [];
180
+ return parsed.map(coerceRecord).filter((record): record is RunRecord => record !== null);
181
+ } catch {
182
+ return [];
183
+ }
184
+ }
185
+
186
+ #persist(records: RunRecord[]): void {
187
+ if (!this.#storage) return;
188
+ try {
189
+ this.#storage.setItem(this.#key, JSON.stringify(records));
190
+ } catch {
191
+ // Quota errors are non-fatal; the list still lives in memory for this session.
192
+ }
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Derive a vendor bucket from a preset. Uses explicit id/name matching rather than the repo owner
198
+ * so re-hosted artifacts (e.g. onnx-community mirrors) still map to the true model vendor.
199
+ */
200
+ export function vendorForModel(preset: ModelPreset): ModelVendor {
201
+ const haystack = `${preset.id} ${preset.shortName}`.toLowerCase();
202
+ if (haystack.includes("qwen")) return "qwen";
203
+ if (haystack.includes("gemma")) return "google";
204
+ if (haystack.includes("llama") || haystack.includes("hermes")) return "meta";
205
+ if (haystack.includes("whisper")) return "openai";
206
+ if (haystack.includes("granite")) return "ibm";
207
+ if (haystack.includes("phi")) return "microsoft";
208
+ if (haystack.includes("nemotron") || haystack.includes("parakeet")) return "nvidia";
209
+ if (haystack.includes("lfm") || haystack.includes("liquid")) return "liquid";
210
+ if (haystack.includes("smollm") || haystack.includes("smolvlm")) return "huggingface";
211
+ if (haystack.includes("mistral") || haystack.includes("ministral") || haystack.includes("voxtral")) return "mistral";
212
+ if (haystack.includes("olmo")) return "other";
213
+ if (haystack.includes("minicpm")) return "other";
214
+ return "other";
215
+ }
216
+
217
+ export const VENDOR_LABELS: Record<ModelVendor, string> = {
218
+ qwen: "Qwen",
219
+ google: "Google",
220
+ meta: "Meta",
221
+ openai: "OpenAI",
222
+ ibm: "IBM",
223
+ microsoft: "Microsoft",
224
+ nvidia: "NVIDIA",
225
+ liquid: "Liquid",
226
+ huggingface: "HuggingFace",
227
+ mistral: "Mistral",
228
+ other: "Other",
229
+ };
230
+
231
+ /**
232
+ * Group runs by model (optionally filtered by case) and average the per-run metrics.
233
+ */
234
+ export function aggregateByModel(history: readonly RunRecord[], caseFilter: string | "all"): ModelAggregate[] {
235
+ const buckets = new Map<string, { vendor: ModelVendor; runs: number; decode: number; ttft: number; totalMs: number; measured: number }>();
236
+
237
+ for (const record of history) {
238
+ if (caseFilter !== "all" && record.case !== caseFilter) continue;
239
+ const bucket = buckets.get(record.model) ?? { vendor: record.vendor, runs: 0, decode: 0, ttft: 0, totalMs: 0, measured: 0 };
240
+ bucket.runs += 1;
241
+ bucket.decode += record.decode;
242
+ bucket.ttft += record.ttft;
243
+ bucket.totalMs += record.totalMs;
244
+ bucket.measured += record.measured;
245
+ buckets.set(record.model, bucket);
246
+ }
247
+
248
+ return Array.from(buckets.entries()).map(([model, bucket]) => ({
249
+ model,
250
+ vendor: bucket.vendor,
251
+ runs: bucket.runs,
252
+ decode: bucket.decode / bucket.runs,
253
+ ttft: bucket.ttft / bucket.runs,
254
+ totalMs: bucket.totalMs / bucket.runs,
255
+ measured: bucket.measured / bucket.runs,
256
+ }));
257
+ }
package/src/index.ts ADDED
@@ -0,0 +1,203 @@
1
+ /**
2
+ * @missionsquad/browserai — run small open AI models entirely in the browser with WebGPU.
3
+ *
4
+ * Core entry points:
5
+ * - {@link BrowserAI} — model catalog + capability-slot manager + inference façade.
6
+ * - {@link VoicePipeline} — headless press-to-talk voice assistant (mic → STT → LLM → TTS).
7
+ */
8
+
9
+ // Client
10
+ export { BrowserAI, buildModelCacheTarget, normalizeContextLength } from "./client.js";
11
+ export type { BrowserAIEvents, GenerateTextOptions, LoadOptions, LoadProgress, LoadedModel, StreamingTtsSession, TtsOverrides } from "./client.js";
12
+
13
+ // Configuration
14
+ export { DEFAULT_CONTEXT_LENGTH, resolveConfig } from "./config.js";
15
+ export type { BrowserAIConfig, HistoryOptions, KokoroOptions, ResolvedConfig, StorageLike, TtsDefaults, WebLLMOptions } from "./config.js";
16
+
17
+ // Errors
18
+ export {
19
+ BrowserAIError,
20
+ formatError,
21
+ MicrophoneError,
22
+ MissingModelError,
23
+ ModelLoadError,
24
+ ProxyVerificationError,
25
+ UnknownModelError,
26
+ VoiceTurnError,
27
+ WebGPUUnavailableError,
28
+ } from "./errors.js";
29
+
30
+ // Events
31
+ export { TypedEmitter } from "./events.js";
32
+ export type { EventMap, Listener } from "./events.js";
33
+
34
+ // Model catalog
35
+ export {
36
+ applyCuratedModelRecordOverrides,
37
+ CUSTOM_MODEL_RECORD_IDS,
38
+ CUSTOM_MODEL_RECORDS,
39
+ formatMB,
40
+ getModelPreset,
41
+ KOKORO_MODEL_ID,
42
+ KOKORO_VOICES,
43
+ MODALITY_ICONS,
44
+ MODALITY_LABELS,
45
+ MODEL_PRESETS,
46
+ MODEL_RECORD_OVERRIDES,
47
+ modelRepoLabel,
48
+ modelSlots,
49
+ presetModalityIn,
50
+ presetModalityOut,
51
+ presetTasks,
52
+ SLOT_ORDER,
53
+ SUPERTONIC_2_MODEL_ID,
54
+ SUPERTONIC_MODEL_ID,
55
+ SUPERTONIC_VOICES,
56
+ TASK_ICONS,
57
+ TASK_LABELS,
58
+ ttsVoicesForModel,
59
+ } from "./models.js";
60
+ export type { Modality, ModelBackend, ModelPreset, ModelTask, MultimodalRuntime, RuntimePreset, TtsVoice } from "./models.js";
61
+
62
+ // Generation parameters + JSON mode
63
+ export { buildResponseFormat, clampNumber, parseOptionalInteger, runtimeParamsFromPreset } from "./generation.js";
64
+ export type { JsonMode, RuntimeParameters } from "./generation.js";
65
+
66
+ // Text inference
67
+ export { buildRuntimeStatsText, collectGenericStats, EMPTY_RESPONSE, generate, normalizeLatencyBreakdown } from "./inference.js";
68
+ export type {
69
+ ChatMessage,
70
+ EngineHandles,
71
+ GenerateOptions,
72
+ GenerationResult,
73
+ LatencyBreakdown,
74
+ RuntimeStats,
75
+ TransformersModule,
76
+ TransformersTextGenerationPipeline,
77
+ TransformersTokenizer,
78
+ WebLLMUsageExtra,
79
+ } from "./inference.js";
80
+
81
+ // Multimodal runtimes (STT / TTS / VLM / Gemma)
82
+ export {
83
+ capSrc,
84
+ DEFAULT_KOKORO_CDN,
85
+ describeImage,
86
+ disposeMultimodal,
87
+ generateGemmaText,
88
+ loadMultimodal,
89
+ planGemmaAudioWindows,
90
+ runAudioLm,
91
+ runGemma,
92
+ runVoxtralRealtime,
93
+ supertonicVoiceUrl,
94
+ supportsStreamingTts,
95
+ synthesize,
96
+ synthesizeStream,
97
+ transcribe,
98
+ } from "./multimodal.js";
99
+ export type {
100
+ AudioWindow,
101
+ GenStats,
102
+ KokoroModule,
103
+ LoadMultimodalOptions,
104
+ MultimodalDevice,
105
+ MultimodalHandle,
106
+ MultimodalTransformers,
107
+ ProgressCallback,
108
+ RawAudio,
109
+ SttResult,
110
+ TextTurn,
111
+ TtsTextSplitter,
112
+ VlmResult,
113
+ } from "./multimodal.js";
114
+
115
+ // JSON helpers + schema validation
116
+ export { detectLikelyRepetition, extractJsonCandidate, normalizeModelText, parseJsonFromModel, prettyJson } from "./json.js";
117
+ export type { JsonParseResult } from "./json.js";
118
+ export { validateAgainstJsonSchema } from "./schema-validator.js";
119
+ export { DEFAULT_EXTRACTION_SCHEMA, stringifySchema, stripSchemaMetadata } from "./schema.js";
120
+ export { buildExtractionMessages, DEFAULT_EXTRACTION_INSTRUCTIONS } from "./prompt.js";
121
+
122
+ // Model source (direct vs proxy) + proxy verification
123
+ export {
124
+ applyContextOverride,
125
+ buildProxiedAppConfig,
126
+ buildWebLLMAppConfig,
127
+ modelSourceDescription,
128
+ proxyHuggingFaceUrl,
129
+ proxyModelLibraryUrl,
130
+ proxyModelRecord,
131
+ proxyRawGithubUrl,
132
+ TRANSFORMERS_CACHE_KEY,
133
+ withCustomModelRecords,
134
+ } from "./model-source.js";
135
+ export type { BrowserCacheBackend, ModelSourceMode } from "./model-source.js";
136
+ export { splitHuggingFaceRepoId, verifyTransformersProxy, verifyWebLLMProxy } from "./proxy-verify.js";
137
+
138
+ // Hardware probing
139
+ export { collectHardwareSnapshot, formatBytes, formatHardwareSnapshot, modelSafetyHint } from "./hardware.js";
140
+ export type { HardwareSnapshot } from "./hardware.js";
141
+
142
+ // Model artifact cache management
143
+ export {
144
+ deleteOneModelArtifactsFromBrowserStorage,
145
+ deleteTransformersModelArtifactsFromBrowserStorage,
146
+ deleteTransformersModelFromBrowserCache,
147
+ deleteWebLLMModelArtifactsFromBrowserStorage,
148
+ estimateOriginStorage,
149
+ getCachedModelStatus,
150
+ hasTransformersModelInBrowserCache,
151
+ matchesModelCacheTarget,
152
+ TRANSFORMERS_CACHE_SCOPE,
153
+ WEBLLM_CACHE_SCOPES,
154
+ } from "./model-cache.js";
155
+ export type {
156
+ CacheCleanupResult,
157
+ CacheCleanupScopeResult,
158
+ CachedModelStatus,
159
+ IndividualModelCacheStatus,
160
+ IndividualModelCleanupResult,
161
+ ModelCacheTarget,
162
+ StorageEstimateSnapshot,
163
+ } from "./model-cache.js";
164
+
165
+ // Metrics + run history
166
+ export { buildMultimodalStats, runRecordFromStats, wordCountGenStats } from "./metrics.js";
167
+ export {
168
+ aggregateByModel,
169
+ DEFAULT_HISTORY_KEY,
170
+ DEFAULT_HISTORY_MAX_RECORDS,
171
+ RunHistoryStore,
172
+ VENDOR_LABELS,
173
+ vendorForModel,
174
+ } from "./history.js";
175
+ export type { ModelAggregate, ModelVendor, RunCaseKey, RunHistoryOptions, RunRecord } from "./history.js";
176
+
177
+ // Audio utilities
178
+ export { AUDIO_SR, encodeWavBlob, pitchShiftBuffer } from "./audio/pcm.js";
179
+ export { decodeAudioTo16kMono } from "./audio/decode.js";
180
+ export { MicRecorder } from "./audio/recorder.js";
181
+ export type { MicRecorderOptions } from "./audio/recorder.js";
182
+ export { createStreamingAudioPlayer } from "./audio/streaming-player.js";
183
+ export type { StreamingAudioPlayer, StreamingAudioPlayerOptions } from "./audio/streaming-player.js";
184
+ export { clipFromRawAudio, clipPlaybackRate, playClipThroughElement, releaseClip, repitchClip, synthesizeClip } from "./audio/clip.js";
185
+ export type { SynthClip, SynthesizeClipOptions } from "./audio/clip.js";
186
+
187
+ // Text utilities
188
+ export { stripThinkTags } from "./text-utils.js";
189
+
190
+ // Voice pipeline
191
+ export {
192
+ capPipelineHistory,
193
+ missingPipelineModels,
194
+ PIPELINE_HISTORY_MAX_TURNS,
195
+ PIPELINE_RECOMMENDED,
196
+ PIPELINE_SYSTEM_PROMPT,
197
+ PIPELINE_TASKS,
198
+ shouldSpeakReply,
199
+ streamedDelta,
200
+ } from "./voice/helpers.js";
201
+ export type { PipelineTask } from "./voice/helpers.js";
202
+ export { VoicePipeline } from "./voice/voice-pipeline.js";
203
+ export type { TurnDiscardReason, VoicePipelineEvents, VoicePipelineOptions, VoicePipelineStage } from "./voice/voice-pipeline.js";