@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/json.ts ADDED
@@ -0,0 +1,169 @@
1
+ export type JsonParseResult =
2
+ | { ok: true; value: unknown; rawJson: string; warnings: string[] }
3
+ | { ok: false; error: string; rawJson: string; warnings: string[] };
4
+
5
+ type BalancedJsonCandidate = {
6
+ rawJson: string;
7
+ hadPrefixOrSuffix: boolean;
8
+ };
9
+
10
+ /**
11
+ * Some models wrap JSON in Markdown or thinking tags despite instructions. WebLLM JSON mode should
12
+ * not do this, but these helpers make output handling robust across different models/settings.
13
+ * (Schema validation lives in schema-validator.ts — one validator for the whole SDK.)
14
+ */
15
+ export function normalizeModelText(text: string): string {
16
+ const trimmed = text.trim();
17
+ const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
18
+ const withoutFence = fenced?.[1] ? fenced[1].trim() : trimmed;
19
+ return withoutFence.replace(/<think>[\s\S]*?<\/think>/gi, "").trim();
20
+ }
21
+
22
+ export function extractJsonCandidate(text: string): string {
23
+ const cleaned = normalizeModelText(text);
24
+ return findFirstBalancedJson(cleaned)?.rawJson ?? cleaned;
25
+ }
26
+
27
+ export function parseJsonFromModel(text: string): JsonParseResult {
28
+ const cleaned = normalizeModelText(text);
29
+ const warnings: string[] = [];
30
+
31
+ try {
32
+ return { ok: true, value: JSON.parse(cleaned), rawJson: cleaned, warnings };
33
+ } catch {
34
+ // Continue below. A common small-model failure is valid JSON followed by repeated
35
+ // text. In that case we parse the first complete JSON value and warn the caller.
36
+ }
37
+
38
+ const balanced = findFirstBalancedJson(cleaned);
39
+ if (balanced) {
40
+ if (balanced.hadPrefixOrSuffix) {
41
+ warnings.push(
42
+ "Parsed the first complete JSON value, but the model also produced prefix/suffix text. Tighten generation parameters or lower max tokens if this repeats.",
43
+ );
44
+ }
45
+
46
+ try {
47
+ return {
48
+ ok: true,
49
+ value: JSON.parse(balanced.rawJson),
50
+ rawJson: balanced.rawJson,
51
+ warnings,
52
+ };
53
+ } catch (error) {
54
+ return {
55
+ ok: false,
56
+ rawJson: balanced.rawJson,
57
+ warnings,
58
+ error: error instanceof Error ? error.message : String(error),
59
+ };
60
+ }
61
+ }
62
+
63
+ return {
64
+ ok: false,
65
+ rawJson: cleaned,
66
+ warnings,
67
+ error: "No complete balanced JSON object or array was found.",
68
+ };
69
+ }
70
+
71
+ export function prettyJson(value: unknown): string {
72
+ return JSON.stringify(value, null, 2);
73
+ }
74
+
75
+ export function detectLikelyRepetition(text: string): string[] {
76
+ const warnings: string[] = [];
77
+ const normalized = text.toLowerCase().replace(/\s+/g, " ").trim();
78
+ if (!normalized) return warnings;
79
+
80
+ const words = normalized.split(" ").filter((word) => word.length > 2);
81
+ const ngramCounts = new Map<string, number>();
82
+ for (let i = 0; i <= words.length - 3; i += 1) {
83
+ const ngram = words.slice(i, i + 3).join(" ");
84
+ ngramCounts.set(ngram, (ngramCounts.get(ngram) ?? 0) + 1);
85
+ }
86
+
87
+ const repeated = Array.from(ngramCounts.entries()).find(([, count]) => count >= 3);
88
+ if (repeated) {
89
+ warnings.push(`Likely repetition detected: "${repeated[0]}" appeared ${repeated[1]} times.`);
90
+ }
91
+
92
+ const fragments = normalized
93
+ .split(/[{}\[\],]/)
94
+ .map((line) => line.trim())
95
+ .filter((line) => line.length > 20);
96
+ const fragmentCounts = new Map<string, number>();
97
+ for (const fragment of fragments) {
98
+ fragmentCounts.set(fragment, (fragmentCounts.get(fragment) ?? 0) + 1);
99
+ }
100
+ const repeatedFragment = Array.from(fragmentCounts.entries()).find(([, count]) => count >= 3);
101
+ if (repeatedFragment) {
102
+ warnings.push("Repeated JSON fragments detected in the raw output.");
103
+ }
104
+
105
+ return warnings;
106
+ }
107
+
108
+ function findFirstBalancedJson(text: string): BalancedJsonCandidate | null {
109
+ const start = findJsonStart(text);
110
+ if (start < 0) return null;
111
+
112
+ const stack: string[] = [];
113
+ let inString = false;
114
+ let escaped = false;
115
+
116
+ for (let i = start; i < text.length; i += 1) {
117
+ const char = text[i];
118
+
119
+ if (inString) {
120
+ if (escaped) {
121
+ escaped = false;
122
+ } else if (char === "\\") {
123
+ escaped = true;
124
+ } else if (char === '"') {
125
+ inString = false;
126
+ }
127
+ continue;
128
+ }
129
+
130
+ if (char === '"') {
131
+ inString = true;
132
+ continue;
133
+ }
134
+
135
+ if (char === "{") {
136
+ stack.push("}");
137
+ continue;
138
+ }
139
+
140
+ if (char === "[") {
141
+ stack.push("]");
142
+ continue;
143
+ }
144
+
145
+ if (char === "}" || char === "]") {
146
+ const expected = stack.pop();
147
+ if (expected !== char) return null;
148
+ if (stack.length === 0) {
149
+ const rawJson = text.slice(start, i + 1).trim();
150
+ const suffix = text.slice(i + 1).trim();
151
+ return {
152
+ rawJson,
153
+ hadPrefixOrSuffix: start > 0 || suffix.length > 0,
154
+ };
155
+ }
156
+ }
157
+ }
158
+
159
+ return null;
160
+ }
161
+
162
+ function findJsonStart(text: string): number {
163
+ const objectStart = text.indexOf("{");
164
+ const arrayStart = text.indexOf("[");
165
+
166
+ if (objectStart === -1) return arrayStart;
167
+ if (arrayStart === -1) return objectStart;
168
+ return Math.min(objectStart, arrayStart);
169
+ }
package/src/metrics.ts ADDED
@@ -0,0 +1,63 @@
1
+ import type { GenStats } from "./multimodal.js";
2
+ import type { RuntimeStats } from "./inference.js";
3
+ import type { RunRecord } from "./history.js";
4
+ import { getModelPreset } from "./models.js";
5
+ import { vendorForModel } from "./history.js";
6
+
7
+ /** Build a RuntimeStats record from multimodal generation telemetry (tokens + first-token time). */
8
+ export function buildMultimodalStats(gen: GenStats, modelId: string): RuntimeStats {
9
+ const elapsedMs = Math.max(0, gen.endedMs - gen.startedMs);
10
+ const completionTokens = gen.completionTokens > 0 ? gen.completionTokens : undefined;
11
+ const ttftS = gen.firstTokenMs !== undefined ? Math.max(0, (gen.firstTokenMs - gen.startedMs) / 1000) : undefined;
12
+ const decodeSeconds = gen.firstTokenMs !== undefined ? Math.max(0.001, (gen.endedMs - gen.firstTokenMs) / 1000) : elapsedMs / 1000;
13
+ const measured = completionTokens !== undefined && elapsedMs > 0 ? completionTokens / (elapsedMs / 1000) : undefined;
14
+ return {
15
+ rawText: "",
16
+ model: modelId,
17
+ backend: "transformers-js",
18
+ finishReason: "stop",
19
+ measuredElapsedMs: elapsedMs,
20
+ completionTokens,
21
+ totalTokens: completionTokens,
22
+ measuredCompletionTokensPerSecond: measured,
23
+ extra: {
24
+ e2e_latency_s: elapsedMs / 1000,
25
+ decode_tokens_per_s: completionTokens !== undefined ? completionTokens / decodeSeconds : undefined,
26
+ time_to_first_token_s: ttftS,
27
+ },
28
+ };
29
+ }
30
+
31
+ /**
32
+ * Map a generation's RuntimeStats to a persistable RunRecord, attributing the run to the model that
33
+ * produced the stats (not whatever is currently selected). Returns null for stats without a model
34
+ * (error placeholders) so failed runs never pollute history.
35
+ */
36
+ export function runRecordFromStats(caseKey: string, stats: RuntimeStats): RunRecord | null {
37
+ if (!stats.model || stats.model === "n/a") return null;
38
+ const preset = getModelPreset(stats.model);
39
+ const extra = stats.extra;
40
+ return {
41
+ ts: Date.now(),
42
+ model: preset.shortName,
43
+ vendor: vendorForModel(preset),
44
+ backend: stats.backend === "transformers-js" ? "transformers-js" : "webllm",
45
+ case: caseKey,
46
+ promptTokens: stats.promptTokens ?? 0,
47
+ completionTokens: stats.completionTokens ?? 0,
48
+ decode: extra?.decode_tokens_per_s ?? stats.measuredCompletionTokensPerSecond ?? 0,
49
+ ttft: extra?.time_to_first_token_s ?? 0,
50
+ totalMs: stats.measuredElapsedMs,
51
+ measured: stats.measuredCompletionTokensPerSecond ?? 0,
52
+ };
53
+ }
54
+
55
+ /**
56
+ * Word-count proxy for non-generative transcription runs: ASR pipelines (Whisper/Moonshine) produce
57
+ * no token telemetry, so callers may log `{ completionTokens: wordCount }` instead. Exposed as an
58
+ * explicit helper so the unit substitution is a visible policy, not a silent one.
59
+ */
60
+ export function wordCountGenStats(text: string, startedMs: number, endedMs: number): GenStats {
61
+ const words = text ? text.trim().split(/\s+/).filter(Boolean).length : 0;
62
+ return { completionTokens: words, startedMs, endedMs };
63
+ }