vocal-stack 1.0.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.
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Configuration for text sanitization
3
+ */
4
+ interface SanitizerConfig {
5
+ /**
6
+ * Rules to apply during sanitization
7
+ * @default ['markdown', 'urls', 'code-blocks', 'punctuation']
8
+ */
9
+ readonly rules?: readonly SanitizerRule[];
10
+ /**
11
+ * Custom plugins for platform-specific transformations
12
+ */
13
+ readonly plugins?: readonly SanitizerPlugin[];
14
+ /**
15
+ * Whether to preserve line breaks
16
+ * @default false
17
+ */
18
+ readonly preserveLineBreaks?: boolean;
19
+ /**
20
+ * Custom replacements for specific patterns
21
+ */
22
+ readonly customReplacements?: ReadonlyMap<string | RegExp, string>;
23
+ }
24
+ /**
25
+ * Built-in rule identifiers
26
+ */
27
+ type SanitizerRule = 'markdown' | 'urls' | 'code-blocks' | 'punctuation';
28
+ /**
29
+ * Plugin interface for custom sanitization logic
30
+ */
31
+ interface SanitizerPlugin {
32
+ readonly name: string;
33
+ readonly priority: number;
34
+ transform(text: string): string | Promise<string>;
35
+ }
36
+ /**
37
+ * Result of sanitization operation
38
+ */
39
+ interface SanitizationResult {
40
+ readonly original: string;
41
+ readonly sanitized: string;
42
+ readonly appliedRules: readonly string[];
43
+ readonly metadata: {
44
+ readonly removedCount: number;
45
+ readonly transformedCount: number;
46
+ };
47
+ }
48
+ /**
49
+ * Rule function type
50
+ */
51
+ type RuleFunction = (text: string, config: Required<SanitizerConfig>) => string;
52
+
53
+ /**
54
+ * Speech sanitizer that transforms text for TTS optimization
55
+ */
56
+ declare class SpeechSanitizer {
57
+ private readonly config;
58
+ private readonly plugins;
59
+ constructor(config?: SanitizerConfig);
60
+ /**
61
+ * Sanitize a string synchronously
62
+ */
63
+ sanitize(text: string): string;
64
+ /**
65
+ * Sanitize with detailed result metadata
66
+ */
67
+ sanitizeWithMetadata(text: string): SanitizationResult;
68
+ /**
69
+ * Sanitize a stream of text chunks (AsyncIterable)
70
+ */
71
+ sanitizeStream(input: AsyncIterable<string>): AsyncIterable<string>;
72
+ }
73
+ /**
74
+ * Convenience function for one-off sanitization
75
+ */
76
+ declare function sanitizeForSpeech(text: string, config?: SanitizerConfig): string;
77
+
78
+ /**
79
+ * Registry of built-in sanitization rules
80
+ */
81
+ declare const ruleRegistry: Map<string, RuleFunction>;
82
+
83
+ export { type RuleFunction, type SanitizationResult, type SanitizerConfig, type SanitizerPlugin, type SanitizerRule, SpeechSanitizer, ruleRegistry, sanitizeForSpeech };
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Configuration for text sanitization
3
+ */
4
+ interface SanitizerConfig {
5
+ /**
6
+ * Rules to apply during sanitization
7
+ * @default ['markdown', 'urls', 'code-blocks', 'punctuation']
8
+ */
9
+ readonly rules?: readonly SanitizerRule[];
10
+ /**
11
+ * Custom plugins for platform-specific transformations
12
+ */
13
+ readonly plugins?: readonly SanitizerPlugin[];
14
+ /**
15
+ * Whether to preserve line breaks
16
+ * @default false
17
+ */
18
+ readonly preserveLineBreaks?: boolean;
19
+ /**
20
+ * Custom replacements for specific patterns
21
+ */
22
+ readonly customReplacements?: ReadonlyMap<string | RegExp, string>;
23
+ }
24
+ /**
25
+ * Built-in rule identifiers
26
+ */
27
+ type SanitizerRule = 'markdown' | 'urls' | 'code-blocks' | 'punctuation';
28
+ /**
29
+ * Plugin interface for custom sanitization logic
30
+ */
31
+ interface SanitizerPlugin {
32
+ readonly name: string;
33
+ readonly priority: number;
34
+ transform(text: string): string | Promise<string>;
35
+ }
36
+ /**
37
+ * Result of sanitization operation
38
+ */
39
+ interface SanitizationResult {
40
+ readonly original: string;
41
+ readonly sanitized: string;
42
+ readonly appliedRules: readonly string[];
43
+ readonly metadata: {
44
+ readonly removedCount: number;
45
+ readonly transformedCount: number;
46
+ };
47
+ }
48
+ /**
49
+ * Rule function type
50
+ */
51
+ type RuleFunction = (text: string, config: Required<SanitizerConfig>) => string;
52
+
53
+ /**
54
+ * Speech sanitizer that transforms text for TTS optimization
55
+ */
56
+ declare class SpeechSanitizer {
57
+ private readonly config;
58
+ private readonly plugins;
59
+ constructor(config?: SanitizerConfig);
60
+ /**
61
+ * Sanitize a string synchronously
62
+ */
63
+ sanitize(text: string): string;
64
+ /**
65
+ * Sanitize with detailed result metadata
66
+ */
67
+ sanitizeWithMetadata(text: string): SanitizationResult;
68
+ /**
69
+ * Sanitize a stream of text chunks (AsyncIterable)
70
+ */
71
+ sanitizeStream(input: AsyncIterable<string>): AsyncIterable<string>;
72
+ }
73
+ /**
74
+ * Convenience function for one-off sanitization
75
+ */
76
+ declare function sanitizeForSpeech(text: string, config?: SanitizerConfig): string;
77
+
78
+ /**
79
+ * Registry of built-in sanitization rules
80
+ */
81
+ declare const ruleRegistry: Map<string, RuleFunction>;
82
+
83
+ export { type RuleFunction, type SanitizationResult, type SanitizerConfig, type SanitizerPlugin, type SanitizerRule, SpeechSanitizer, ruleRegistry, sanitizeForSpeech };
@@ -0,0 +1,186 @@
1
+ // src/errors.ts
2
+ var VocalStackError = class extends Error {
3
+ constructor(message, code, context) {
4
+ super(message);
5
+ this.code = code;
6
+ this.context = context;
7
+ this.name = "VocalStackError";
8
+ Error.captureStackTrace(this, this.constructor);
9
+ }
10
+ };
11
+ var SanitizerError = class extends VocalStackError {
12
+ constructor(message, context) {
13
+ super(message, "SANITIZER_ERROR", context);
14
+ this.name = "SanitizerError";
15
+ }
16
+ };
17
+
18
+ // src/sanitizer/rules/code-blocks.ts
19
+ function codeBlocksRule(text, _config) {
20
+ let result = text;
21
+ result = result.replace(/```[\s\S]*?```/g, "");
22
+ result = result.replace(/^(?: {4}|\t).+$/gm, "");
23
+ return result;
24
+ }
25
+
26
+ // src/sanitizer/rules/markdown.ts
27
+ function markdownRule(text, _config) {
28
+ let result = text;
29
+ result = result.replace(/^#{1,6}\s+/gm, "");
30
+ result = result.replace(/(\*\*|__)(.*?)\1/g, "$2");
31
+ result = result.replace(/(\*|_)(.*?)\1/g, "$2");
32
+ result = result.replace(/`([^`]+)`/g, "$1");
33
+ result = result.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
34
+ result = result.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
35
+ result = result.replace(/^>\s+/gm, "");
36
+ result = result.replace(/^[\-*_]{3,}$/gm, "");
37
+ result = result.replace(/^[\s]*[-*+]\s+/gm, "");
38
+ result = result.replace(/^[\s]*\d+\.\s+/gm, "");
39
+ result = result.replace(/~~(.*?)~~/g, "$1");
40
+ result = result.replace(/^[\s]*-\s+\[[ xX]\]\s+/gm, "");
41
+ return result;
42
+ }
43
+
44
+ // src/sanitizer/rules/punctuation.ts
45
+ function punctuationRule(text, _config) {
46
+ let result = text;
47
+ result = result.replace(/!{2,}/g, "!");
48
+ result = result.replace(/\?{2,}/g, "?");
49
+ result = result.replace(/\.{3,}/g, ".");
50
+ result = result.replace(/-{2,}/g, " ");
51
+ result = result.replace(/[—–]/g, " ");
52
+ result = result.replace(/[()[\]{}]/g, "");
53
+ result = result.replace(/,{2,}/g, ",");
54
+ result = result.replace(/[;:]/g, ",");
55
+ result = result.replace(/["'"'`]/g, "");
56
+ result = result.replace(/[*_]/g, "");
57
+ result = result.replace(/[/\\]/g, " ");
58
+ result = result.replace(/[|&]/g, " ");
59
+ result = result.replace(/[@#$%]/g, "");
60
+ return result;
61
+ }
62
+
63
+ // src/sanitizer/rules/urls.ts
64
+ function urlsRule(text, _config) {
65
+ let result = text;
66
+ const urlWithProtocol = /\b(https?:\/\/|ftp:\/\/|www\.)[^\s<>"{}|\\^`[\]]+/gi;
67
+ const emailPattern = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g;
68
+ result = result.replace(urlWithProtocol, "");
69
+ result = result.replace(emailPattern, "");
70
+ result = result.replace(/\b[a-z0-9-]+\.[a-z]{2,}\b/gi, "");
71
+ return result;
72
+ }
73
+
74
+ // src/sanitizer/rules/index.ts
75
+ var ruleRegistry = /* @__PURE__ */ new Map([
76
+ ["markdown", markdownRule],
77
+ ["urls", urlsRule],
78
+ ["code-blocks", codeBlocksRule],
79
+ ["punctuation", punctuationRule]
80
+ ]);
81
+
82
+ // src/sanitizer/sanitizer.ts
83
+ var SpeechSanitizer = class {
84
+ config;
85
+ plugins;
86
+ constructor(config = {}) {
87
+ this.config = {
88
+ rules: config.rules ?? ["markdown", "urls", "code-blocks", "punctuation"],
89
+ plugins: config.plugins ?? [],
90
+ preserveLineBreaks: config.preserveLineBreaks ?? false,
91
+ customReplacements: config.customReplacements ?? /* @__PURE__ */ new Map()
92
+ };
93
+ this.plugins = [...this.config.plugins].sort((a, b) => a.priority - b.priority);
94
+ }
95
+ /**
96
+ * Sanitize a string synchronously
97
+ */
98
+ sanitize(text) {
99
+ if (!text || text.trim().length === 0) {
100
+ return "";
101
+ }
102
+ let result = text;
103
+ try {
104
+ for (const rule of this.config.rules) {
105
+ const ruleFunction = ruleRegistry.get(rule);
106
+ if (ruleFunction) {
107
+ result = ruleFunction(result, this.config);
108
+ }
109
+ }
110
+ for (const plugin of this.plugins) {
111
+ const transformed = plugin.transform(result);
112
+ if (transformed instanceof Promise) {
113
+ throw new SanitizerError(
114
+ `Plugin ${plugin.name} returned a Promise in sync sanitize(). Use sanitizeAsync() instead.`
115
+ );
116
+ }
117
+ result = transformed;
118
+ }
119
+ for (const [pattern, replacement] of this.config.customReplacements) {
120
+ if (typeof pattern === "string") {
121
+ result = result.replaceAll(pattern, replacement);
122
+ } else {
123
+ result = result.replace(pattern, replacement);
124
+ }
125
+ }
126
+ if (this.config.preserveLineBreaks) {
127
+ result = result.replace(/ +/g, " ").replace(/^\s+|\s+$/gm, "");
128
+ } else {
129
+ result = result.replace(/\s+/g, " ").trim();
130
+ }
131
+ return result;
132
+ } catch (error) {
133
+ if (error instanceof SanitizerError) {
134
+ throw error;
135
+ }
136
+ throw new SanitizerError("Failed to sanitize text", {
137
+ originalText: text.substring(0, 100),
138
+ error
139
+ });
140
+ }
141
+ }
142
+ /**
143
+ * Sanitize with detailed result metadata
144
+ */
145
+ sanitizeWithMetadata(text) {
146
+ const original = text;
147
+ const sanitized = this.sanitize(text);
148
+ return {
149
+ original,
150
+ sanitized,
151
+ appliedRules: this.config.rules,
152
+ metadata: {
153
+ removedCount: original.length - sanitized.length,
154
+ transformedCount: this.config.rules.length + this.plugins.length
155
+ }
156
+ };
157
+ }
158
+ /**
159
+ * Sanitize a stream of text chunks (AsyncIterable)
160
+ */
161
+ async *sanitizeStream(input) {
162
+ let buffer = "";
163
+ const sentenceBoundary = /[.!?]\s+/;
164
+ for await (const chunk of input) {
165
+ buffer += chunk;
166
+ const sentences = buffer.split(sentenceBoundary);
167
+ buffer = sentences.pop() ?? "";
168
+ for (const sentence of sentences) {
169
+ if (sentence.trim()) {
170
+ yield `${this.sanitize(sentence)} `;
171
+ }
172
+ }
173
+ }
174
+ if (buffer.trim()) {
175
+ yield this.sanitize(buffer);
176
+ }
177
+ }
178
+ };
179
+ function sanitizeForSpeech(text, config) {
180
+ const sanitizer = new SpeechSanitizer(config);
181
+ return sanitizer.sanitize(text);
182
+ }
183
+
184
+ export { SpeechSanitizer, ruleRegistry, sanitizeForSpeech };
185
+ //# sourceMappingURL=index.js.map
186
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/errors.ts","../../src/sanitizer/rules/code-blocks.ts","../../src/sanitizer/rules/markdown.ts","../../src/sanitizer/rules/punctuation.ts","../../src/sanitizer/rules/urls.ts","../../src/sanitizer/rules/index.ts","../../src/sanitizer/sanitizer.ts"],"names":[],"mappings":";AAGO,IAAM,eAAA,GAAN,cAA8B,KAAA,CAAM;AAAA,EACzC,WAAA,CACE,OAAA,EACgB,IAAA,EACA,OAAA,EAChB;AACA,IAAA,KAAA,CAAM,OAAO,CAAA;AAHG,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AACA,IAAA,IAAA,CAAA,OAAA,GAAA,OAAA;AAGhB,IAAA,IAAA,CAAK,IAAA,GAAO,iBAAA;AACZ,IAAA,KAAA,CAAM,iBAAA,CAAkB,IAAA,EAAM,IAAA,CAAK,WAAW,CAAA;AAAA,EAChD;AACF,CAAA;AAKO,IAAM,cAAA,GAAN,cAA6B,eAAA,CAAgB;AAAA,EAClD,WAAA,CAAY,SAAiB,OAAA,EAAmC;AAC9D,IAAA,KAAA,CAAM,OAAA,EAAS,mBAAmB,OAAO,CAAA;AACzC,IAAA,IAAA,CAAK,IAAA,GAAO,gBAAA;AAAA,EACd;AACF,CAAA;;;AClBO,SAAS,cAAA,CAAe,MAAc,OAAA,EAA4C;AACvF,EAAA,IAAI,MAAA,GAAS,IAAA;AAIb,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,iBAAA,EAAmB,EAAE,CAAA;AAG7C,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,mBAAA,EAAqB,EAAE,CAAA;AAI/C,EAAA,OAAO,MAAA;AACT;;;ACbO,SAAS,YAAA,CAAa,MAAc,OAAA,EAA4C;AACrF,EAAA,IAAI,MAAA,GAAS,IAAA;AAGb,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,cAAA,EAAgB,EAAE,CAAA;AAG1C,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,mBAAA,EAAqB,IAAI,CAAA;AACjD,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,gBAAA,EAAkB,IAAI,CAAA;AAG9C,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,YAAA,EAAc,IAAI,CAAA;AAG1C,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,wBAAA,EAA0B,IAAI,CAAA;AAGtD,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,yBAAA,EAA2B,IAAI,CAAA;AAGvD,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,SAAA,EAAW,EAAE,CAAA;AAGrC,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,gBAAA,EAAkB,EAAE,CAAA;AAG5C,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,kBAAA,EAAoB,EAAE,CAAA;AAC9C,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,kBAAA,EAAoB,EAAE,CAAA;AAG9C,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,YAAA,EAAc,IAAI,CAAA;AAG1C,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,0BAAA,EAA4B,EAAE,CAAA;AAEtD,EAAA,OAAO,MAAA;AACT;;;ACpCO,SAAS,eAAA,CAAgB,MAAc,OAAA,EAA4C;AACxF,EAAA,IAAI,MAAA,GAAS,IAAA;AAGb,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,QAAA,EAAU,GAAG,CAAA;AAGrC,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,SAAA,EAAW,GAAG,CAAA;AAGtC,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,SAAA,EAAW,GAAG,CAAA;AAGtC,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,QAAA,EAAU,GAAG,CAAA;AAGrC,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,OAAA,EAAS,GAAG,CAAA;AAGpC,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,YAAA,EAAc,EAAE,CAAA;AAGxC,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,QAAA,EAAU,GAAG,CAAA;AAGrC,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,OAAA,EAAS,GAAG,CAAA;AAGpC,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,UAAA,EAAY,EAAE,CAAA;AAGtC,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,OAAA,EAAS,EAAE,CAAA;AAGnC,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,QAAA,EAAU,GAAG,CAAA;AAGrC,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,OAAA,EAAS,GAAG,CAAA;AAGpC,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,SAAA,EAAW,EAAE,CAAA;AAErC,EAAA,OAAO,MAAA;AACT;;;AC3CO,SAAS,QAAA,CAAS,MAAc,OAAA,EAA4C;AACjF,EAAA,IAAI,MAAA,GAAS,IAAA;AAGb,EAAA,MAAM,eAAA,GAAkB,qDAAA;AAGxB,EAAA,MAAM,YAAA,GAAe,sDAAA;AAGrB,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,eAAA,EAAiB,EAAE,CAAA;AAG3C,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,YAAA,EAAc,EAAE,CAAA;AAGxC,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,6BAAA,EAA+B,EAAE,CAAA;AAEzD,EAAA,OAAO,MAAA;AACT;;;ACfO,IAAM,YAAA,uBAAmB,GAAA,CAA0B;AAAA,EACxD,CAAC,YAAY,YAAY,CAAA;AAAA,EACzB,CAAC,QAAQ,QAAQ,CAAA;AAAA,EACjB,CAAC,eAAe,cAAc,CAAA;AAAA,EAC9B,CAAC,eAAe,eAAe;AACjC,CAAC;;;ACPM,IAAM,kBAAN,MAAsB;AAAA,EACV,MAAA;AAAA,EACA,OAAA;AAAA,EAEjB,WAAA,CAAY,MAAA,GAA0B,EAAC,EAAG;AACxC,IAAA,IAAA,CAAK,MAAA,GAAS;AAAA,MACZ,OAAO,MAAA,CAAO,KAAA,IAAS,CAAC,UAAA,EAAY,MAAA,EAAQ,eAAe,aAAa,CAAA;AAAA,MACxE,OAAA,EAAS,MAAA,CAAO,OAAA,IAAW,EAAC;AAAA,MAC5B,kBAAA,EAAoB,OAAO,kBAAA,IAAsB,KAAA;AAAA,MACjD,kBAAA,EAAoB,MAAA,CAAO,kBAAA,oBAAsB,IAAI,GAAA;AAAI,KAC3D;AACA,IAAA,IAAA,CAAK,OAAA,GAAU,CAAC,GAAG,IAAA,CAAK,OAAO,OAAO,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,CAAE,QAAA,GAAW,EAAE,QAAQ,CAAA;AAAA,EAChF;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,IAAA,EAAsB;AAC7B,IAAA,IAAI,CAAC,IAAA,IAAQ,IAAA,CAAK,IAAA,EAAK,CAAE,WAAW,CAAA,EAAG;AACrC,MAAA,OAAO,EAAA;AAAA,IACT;AAEA,IAAA,IAAI,MAAA,GAAS,IAAA;AAEb,IAAA,IAAI;AAEF,MAAA,KAAA,MAAW,IAAA,IAAQ,IAAA,CAAK,MAAA,CAAO,KAAA,EAAO;AACpC,QAAA,MAAM,YAAA,GAAe,YAAA,CAAa,GAAA,CAAI,IAAI,CAAA;AAC1C,QAAA,IAAI,YAAA,EAAc;AAChB,UAAA,MAAA,GAAS,YAAA,CAAa,MAAA,EAAQ,IAAA,CAAK,MAAM,CAAA;AAAA,QAC3C;AAAA,MACF;AAGA,MAAA,KAAA,MAAW,MAAA,IAAU,KAAK,OAAA,EAAS;AACjC,QAAA,MAAM,WAAA,GAAc,MAAA,CAAO,SAAA,CAAU,MAAM,CAAA;AAC3C,QAAA,IAAI,uBAAuB,OAAA,EAAS;AAClC,UAAA,MAAM,IAAI,cAAA;AAAA,YACR,CAAA,OAAA,EAAU,OAAO,IAAI,CAAA,oEAAA;AAAA,WACvB;AAAA,QACF;AACA,QAAA,MAAA,GAAS,WAAA;AAAA,MACX;AAGA,MAAA,KAAA,MAAW,CAAC,OAAA,EAAS,WAAW,CAAA,IAAK,IAAA,CAAK,OAAO,kBAAA,EAAoB;AACnE,QAAA,IAAI,OAAO,YAAY,QAAA,EAAU;AAC/B,UAAA,MAAA,GAAS,MAAA,CAAO,UAAA,CAAW,OAAA,EAAS,WAAW,CAAA;AAAA,QACjD,CAAA,MAAO;AACL,UAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,OAAA,EAAS,WAAW,CAAA;AAAA,QAC9C;AAAA,MACF;AAGA,MAAA,IAAI,IAAA,CAAK,OAAO,kBAAA,EAAoB;AAElC,QAAA,MAAA,GAAS,OAAO,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,eAAe,EAAE,CAAA;AAAA,MAC/D,CAAA,MAAO;AAEL,QAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,MAAA,EAAQ,GAAG,EAAE,IAAA,EAAK;AAAA,MAC5C;AAEA,MAAA,OAAO,MAAA;AAAA,IACT,SAAS,KAAA,EAAO;AACd,MAAA,IAAI,iBAAiB,cAAA,EAAgB;AACnC,QAAA,MAAM,KAAA;AAAA,MACR;AACA,MAAA,MAAM,IAAI,eAAe,yBAAA,EAA2B;AAAA,QAClD,YAAA,EAAc,IAAA,CAAK,SAAA,CAAU,CAAA,EAAG,GAAG,CAAA;AAAA,QACnC;AAAA,OACD,CAAA;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,qBAAqB,IAAA,EAAkC;AACrD,IAAA,MAAM,QAAA,GAAW,IAAA;AACjB,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,QAAA,CAAS,IAAI,CAAA;AAEpC,IAAA,OAAO;AAAA,MACL,QAAA;AAAA,MACA,SAAA;AAAA,MACA,YAAA,EAAc,KAAK,MAAA,CAAO,KAAA;AAAA,MAC1B,QAAA,EAAU;AAAA,QACR,YAAA,EAAc,QAAA,CAAS,MAAA,GAAS,SAAA,CAAU,MAAA;AAAA,QAC1C,kBAAkB,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,MAAA,GAAS,KAAK,OAAA,CAAQ;AAAA;AAC5D,KACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,eAAe,KAAA,EAAqD;AACzE,IAAA,IAAI,MAAA,GAAS,EAAA;AACb,IAAA,MAAM,gBAAA,GAAmB,UAAA;AAEzB,IAAA,WAAA,MAAiB,SAAS,KAAA,EAAO;AAC/B,MAAA,MAAA,IAAU,KAAA;AAGV,MAAA,MAAM,SAAA,GAAY,MAAA,CAAO,KAAA,CAAM,gBAAgB,CAAA;AAC/C,MAAA,MAAA,GAAS,SAAA,CAAU,KAAI,IAAK,EAAA;AAE5B,MAAA,KAAA,MAAW,YAAY,SAAA,EAAW;AAChC,QAAA,IAAI,QAAA,CAAS,MAAK,EAAG;AACnB,UAAA,MAAM,CAAA,EAAG,IAAA,CAAK,QAAA,CAAS,QAAQ,CAAC,CAAA,CAAA,CAAA;AAAA,QAClC;AAAA,MACF;AAAA,IACF;AAGA,IAAA,IAAI,MAAA,CAAO,MAAK,EAAG;AACjB,MAAA,MAAM,IAAA,CAAK,SAAS,MAAM,CAAA;AAAA,IAC5B;AAAA,EACF;AACF;AAKO,SAAS,iBAAA,CAAkB,MAAc,MAAA,EAAkC;AAChF,EAAA,MAAM,SAAA,GAAY,IAAI,eAAA,CAAgB,MAAM,CAAA;AAC5C,EAAA,OAAO,SAAA,CAAU,SAAS,IAAI,CAAA;AAChC","file":"index.js","sourcesContent":["/**\n * Base error class for all vocal-stack errors\n */\nexport class VocalStackError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly context?: Record<string, unknown>\n ) {\n super(message);\n this.name = 'VocalStackError';\n Error.captureStackTrace(this, this.constructor);\n }\n}\n\n/**\n * Error thrown during text sanitization\n */\nexport class SanitizerError extends VocalStackError {\n constructor(message: string, context?: Record<string, unknown>) {\n super(message, 'SANITIZER_ERROR', context);\n this.name = 'SanitizerError';\n }\n}\n\n/**\n * Error thrown during flow control operations\n */\nexport class FlowControlError extends VocalStackError {\n constructor(message: string, context?: Record<string, unknown>) {\n super(message, 'FLOW_CONTROL_ERROR', context);\n this.name = 'FlowControlError';\n }\n}\n\n/**\n * Error thrown during monitoring operations\n */\nexport class MonitorError extends VocalStackError {\n constructor(message: string, context?: Record<string, unknown>) {\n super(message, 'MONITOR_ERROR', context);\n this.name = 'MonitorError';\n }\n}\n","import type { SanitizerConfig } from '../types';\n\n/**\n * Remove code blocks and inline code to make text speakable\n */\nexport function codeBlocksRule(text: string, _config: Required<SanitizerConfig>): string {\n let result = text;\n\n // Remove fenced code blocks (```code```)\n // Match both with and without language specifier\n result = result.replace(/```[\\s\\S]*?```/g, '');\n\n // Remove indented code blocks (4 spaces or 1 tab at line start)\n result = result.replace(/^(?: {4}|\\t).+$/gm, '');\n\n // Note: Inline code (`code`) is handled by markdown rule\n\n return result;\n}\n","import type { SanitizerConfig } from '../types';\n\n/**\n * Strip markdown syntax to make text speakable\n */\nexport function markdownRule(text: string, _config: Required<SanitizerConfig>): string {\n let result = text;\n\n // Remove headers (##, ###, etc.)\n result = result.replace(/^#{1,6}\\s+/gm, '');\n\n // Remove bold/italic (**text**, *text*, __text__, _text_)\n result = result.replace(/(\\*\\*|__)(.*?)\\1/g, '$2');\n result = result.replace(/(\\*|_)(.*?)\\1/g, '$2');\n\n // Remove inline code (`code`)\n result = result.replace(/`([^`]+)`/g, '$1');\n\n // Remove links but keep text [text](url) -> text\n result = result.replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, '$1');\n\n // Remove images ![alt](url) -> alt or empty\n result = result.replace(/!\\[([^\\]]*)\\]\\([^)]+\\)/g, '$1');\n\n // Remove blockquotes (>)\n result = result.replace(/^>\\s+/gm, '');\n\n // Remove horizontal rules (---, ***, ___)\n result = result.replace(/^[\\-*_]{3,}$/gm, '');\n\n // Remove list markers (-, *, 1., etc.)\n result = result.replace(/^[\\s]*[-*+]\\s+/gm, '');\n result = result.replace(/^[\\s]*\\d+\\.\\s+/gm, '');\n\n // Remove strikethrough (~~text~~)\n result = result.replace(/~~(.*?)~~/g, '$1');\n\n // Remove task list markers (- [ ], - [x])\n result = result.replace(/^[\\s]*-\\s+\\[[ xX]\\]\\s+/gm, '');\n\n return result;\n}\n","import type { SanitizerConfig } from '../types';\n\n/**\n * Normalize punctuation for better TTS handling\n */\nexport function punctuationRule(text: string, _config: Required<SanitizerConfig>): string {\n let result = text;\n\n // Replace multiple exclamation marks with single\n result = result.replace(/!{2,}/g, '!');\n\n // Replace multiple question marks with single\n result = result.replace(/\\?{2,}/g, '?');\n\n // Replace ellipsis variations with single space or period\n result = result.replace(/\\.{3,}/g, '.');\n\n // Remove multiple dashes/hyphens\n result = result.replace(/-{2,}/g, ' ');\n\n // Replace em dash and en dash with space\n result = result.replace(/[—–]/g, ' ');\n\n // Remove parentheses and brackets but keep content\n result = result.replace(/[()[\\]{}]/g, '');\n\n // Replace multiple commas with single\n result = result.replace(/,{2,}/g, ',');\n\n // Remove semicolons and colons (can be disruptive in speech)\n result = result.replace(/[;:]/g, ',');\n\n // Remove quotation marks\n result = result.replace(/[\"'\"'`]/g, '');\n\n // Remove asterisks and underscores (from markdown remnants)\n result = result.replace(/[*_]/g, '');\n\n // Remove forward slashes and backslashes\n result = result.replace(/[/\\\\]/g, ' ');\n\n // Remove pipes and ampersands\n result = result.replace(/[|&]/g, ' ');\n\n // Remove @ # $ % symbols\n result = result.replace(/[@#$%]/g, '');\n\n return result;\n}\n","import type { SanitizerConfig } from '../types';\n\n/**\n * Remove or replace URLs to make text speakable\n */\nexport function urlsRule(text: string, _config: Required<SanitizerConfig>): string {\n let result = text;\n\n // Match URLs with protocol (http://, https://, ftp://, etc.)\n const urlWithProtocol = /\\b(https?:\\/\\/|ftp:\\/\\/|www\\.)[^\\s<>\"{}|\\\\^`[\\]]+/gi;\n\n // Match email addresses\n const emailPattern = /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b/g;\n\n // Remove URLs with protocol\n result = result.replace(urlWithProtocol, '');\n\n // Remove email addresses\n result = result.replace(emailPattern, '');\n\n // Remove standalone domain-like patterns (example.com)\n result = result.replace(/\\b[a-z0-9-]+\\.[a-z]{2,}\\b/gi, '');\n\n return result;\n}\n","import type { RuleFunction } from '../types';\nimport { codeBlocksRule } from './code-blocks';\nimport { markdownRule } from './markdown';\nimport { punctuationRule } from './punctuation';\nimport { urlsRule } from './urls';\n\n/**\n * Registry of built-in sanitization rules\n */\nexport const ruleRegistry = new Map<string, RuleFunction>([\n ['markdown', markdownRule],\n ['urls', urlsRule],\n ['code-blocks', codeBlocksRule],\n ['punctuation', punctuationRule],\n]);\n","import { SanitizerError } from '../errors';\nimport { ruleRegistry } from './rules';\nimport type { SanitizationResult, SanitizerConfig, SanitizerPlugin } from './types';\n\n/**\n * Speech sanitizer that transforms text for TTS optimization\n */\nexport class SpeechSanitizer {\n private readonly config: Required<SanitizerConfig>;\n private readonly plugins: readonly SanitizerPlugin[];\n\n constructor(config: SanitizerConfig = {}) {\n this.config = {\n rules: config.rules ?? ['markdown', 'urls', 'code-blocks', 'punctuation'],\n plugins: config.plugins ?? [],\n preserveLineBreaks: config.preserveLineBreaks ?? false,\n customReplacements: config.customReplacements ?? new Map(),\n };\n this.plugins = [...this.config.plugins].sort((a, b) => a.priority - b.priority);\n }\n\n /**\n * Sanitize a string synchronously\n */\n sanitize(text: string): string {\n if (!text || text.trim().length === 0) {\n return '';\n }\n\n let result = text;\n\n try {\n // Apply built-in rules\n for (const rule of this.config.rules) {\n const ruleFunction = ruleRegistry.get(rule);\n if (ruleFunction) {\n result = ruleFunction(result, this.config);\n }\n }\n\n // Apply plugins (sync only in this version)\n for (const plugin of this.plugins) {\n const transformed = plugin.transform(result);\n if (transformed instanceof Promise) {\n throw new SanitizerError(\n `Plugin ${plugin.name} returned a Promise in sync sanitize(). Use sanitizeAsync() instead.`\n );\n }\n result = transformed;\n }\n\n // Apply custom replacements\n for (const [pattern, replacement] of this.config.customReplacements) {\n if (typeof pattern === 'string') {\n result = result.replaceAll(pattern, replacement);\n } else {\n result = result.replace(pattern, replacement);\n }\n }\n\n // Clean up whitespace\n if (this.config.preserveLineBreaks) {\n // Collapse multiple spaces on same line but keep line breaks\n result = result.replace(/ +/g, ' ').replace(/^\\s+|\\s+$/gm, '');\n } else {\n // Collapse all whitespace including line breaks\n result = result.replace(/\\s+/g, ' ').trim();\n }\n\n return result;\n } catch (error) {\n if (error instanceof SanitizerError) {\n throw error;\n }\n throw new SanitizerError('Failed to sanitize text', {\n originalText: text.substring(0, 100),\n error,\n });\n }\n }\n\n /**\n * Sanitize with detailed result metadata\n */\n sanitizeWithMetadata(text: string): SanitizationResult {\n const original = text;\n const sanitized = this.sanitize(text);\n\n return {\n original,\n sanitized,\n appliedRules: this.config.rules as string[],\n metadata: {\n removedCount: original.length - sanitized.length,\n transformedCount: this.config.rules.length + this.plugins.length,\n },\n };\n }\n\n /**\n * Sanitize a stream of text chunks (AsyncIterable)\n */\n async *sanitizeStream(input: AsyncIterable<string>): AsyncIterable<string> {\n let buffer = '';\n const sentenceBoundary = /[.!?]\\s+/;\n\n for await (const chunk of input) {\n buffer += chunk;\n\n // Process complete sentences\n const sentences = buffer.split(sentenceBoundary);\n buffer = sentences.pop() ?? ''; // Keep incomplete sentence\n\n for (const sentence of sentences) {\n if (sentence.trim()) {\n yield `${this.sanitize(sentence)} `;\n }\n }\n }\n\n // Process remaining buffer\n if (buffer.trim()) {\n yield this.sanitize(buffer);\n }\n }\n}\n\n/**\n * Convenience function for one-off sanitization\n */\nexport function sanitizeForSpeech(text: string, config?: SanitizerConfig): string {\n const sanitizer = new SpeechSanitizer(config);\n return sanitizer.sanitize(text);\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,90 @@
1
+ {
2
+ "name": "vocal-stack",
3
+ "version": "1.0.0",
4
+ "description": "High-performance utility library for Voice AI agents - text sanitization, flow control, and latency monitoring",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.mjs",
10
+ "require": "./dist/index.js"
11
+ },
12
+ "./sanitizer": {
13
+ "types": "./dist/sanitizer/index.d.ts",
14
+ "import": "./dist/sanitizer/index.mjs",
15
+ "require": "./dist/sanitizer/index.js"
16
+ },
17
+ "./flow": {
18
+ "types": "./dist/flow/index.d.ts",
19
+ "import": "./dist/flow/index.mjs",
20
+ "require": "./dist/flow/index.js"
21
+ },
22
+ "./monitor": {
23
+ "types": "./dist/monitor/index.d.ts",
24
+ "import": "./dist/monitor/index.mjs",
25
+ "require": "./dist/monitor/index.js"
26
+ }
27
+ },
28
+ "main": "./dist/index.js",
29
+ "module": "./dist/index.mjs",
30
+ "types": "./dist/index.d.ts",
31
+ "files": [
32
+ "dist",
33
+ "README.md",
34
+ "LICENSE"
35
+ ],
36
+ "scripts": {
37
+ "dev": "tsup --watch",
38
+ "build": "tsup",
39
+ "test": "vitest run",
40
+ "test:watch": "vitest",
41
+ "test:coverage": "vitest run --coverage",
42
+ "lint": "biome check .",
43
+ "lint:fix": "biome check --write .",
44
+ "format": "biome format --write .",
45
+ "typecheck": "tsc --noEmit",
46
+ "prepublishOnly": "npm run build && npm run test && npm run typecheck",
47
+ "changeset": "changeset",
48
+ "version": "changeset version",
49
+ "release": "npm run build && changeset publish"
50
+ },
51
+ "keywords": [
52
+ "voice-ai",
53
+ "tts",
54
+ "llm",
55
+ "streaming",
56
+ "speech",
57
+ "conversational-ai",
58
+ "realtime",
59
+ "latency",
60
+ "voice-agents",
61
+ "text-to-speech",
62
+ "ai-agents",
63
+ "openai",
64
+ "elevenlabs"
65
+ ],
66
+ "author": "",
67
+ "license": "MIT",
68
+ "repository": {
69
+ "type": "git",
70
+ "url": ""
71
+ },
72
+ "bugs": {
73
+ "url": ""
74
+ },
75
+ "homepage": "",
76
+ "engines": {
77
+ "node": ">=18.0.0"
78
+ },
79
+ "devDependencies": {
80
+ "@biomejs/biome": "^1.9.4",
81
+ "@changesets/cli": "^2.27.10",
82
+ "@types/node": "^22.10.5",
83
+ "@vitest/coverage-v8": "^2.1.8",
84
+ "tsup": "^8.3.5",
85
+ "typescript": "^5.7.2",
86
+ "vitest": "^2.1.8"
87
+ },
88
+ "peerDependencies": {},
89
+ "dependencies": {}
90
+ }