@plasius/ai-speech 0.1.1 → 0.1.3

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/CHANGELOG.md CHANGED
@@ -18,9 +18,39 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
18
18
  - **Security**
19
19
  - (placeholder)
20
20
 
21
+ ## [0.1.3] - 2026-06-14
22
+
23
+ - **Added**
24
+ - (placeholder)
25
+
26
+ - **Changed**
27
+ - (placeholder)
28
+
29
+ - **Fixed**
30
+ - (placeholder)
31
+
32
+ - **Security**
33
+ - (placeholder)
34
+
35
+ ## [0.1.2] - 2026-06-14
36
+
37
+ - **Added**
38
+ - rollout-control, player-address, cache-planning, and voice-tier contracts for tracked TTS caching work
39
+
40
+ - **Changed**
41
+ - the package descriptor now points at the tracked TTS cache feature flag instead of the original scaffold placeholder
42
+
43
+ - **Fixed**
44
+ - default player-address rendering now redacts user/account identifiers before cache planning
45
+
46
+ - **Security**
47
+ - near-text reuse is now fail-closed for private, moderated, or identifier-bearing speech unless the caller narrows scope safely
48
+
21
49
  ## [0.1.1] - 2026-05-13
22
50
 
23
51
  - Added initial public package scaffold with governance, legal, docs, build, test, and pack-check baselines.
24
52
 
25
53
 
26
54
  [0.1.1]: https://github.com/Plasius-LTD/ai-speech/releases/tag/v0.1.1
55
+ [0.1.2]: https://github.com/Plasius-LTD/ai-speech/releases/tag/v0.1.2
56
+ [0.1.3]: https://github.com/Plasius-LTD/ai-speech/releases/tag/v0.1.3
package/README.md CHANGED
@@ -4,7 +4,14 @@ Speech orchestration, TTS cache contracts, STT/TTS routing, and voice tier polic
4
4
 
5
5
  ## Scope
6
6
 
7
- This package is part of the layered `@plasius/ai-*` package family. It is intentionally bootstrapped with a small public contract surface so implementation can evolve behind tracked Feature/Story/Task work.
7
+ This package defines public contracts for:
8
+
9
+ - rollout-controlled TTS cache planning
10
+ - cache-safe player address rendering
11
+ - development, standard, and premium-character voice tier routing
12
+ - cache telemetry payloads
13
+
14
+ It does not fetch remote feature flags, talk to providers, or store cache entries directly. Host applications and provider adapters remain responsible for runtime I/O.
8
15
 
9
16
  ## Install
10
17
 
@@ -15,11 +22,78 @@ npm install @plasius/ai-speech
15
22
  ## Usage
16
23
 
17
24
  ```ts
18
- import { packageDescriptor } from "@plasius/ai-speech";
25
+ import {
26
+ AI_SPEECH_FEATURE_FLAGS,
27
+ planAiSpeechCache,
28
+ renderAiSpeechText,
29
+ resolveAiSpeechVoiceTier,
30
+ } from "@plasius/ai-speech";
31
+
32
+ const rendered = renderAiSpeechText({
33
+ textTemplate: "Welcome back, {playerAddress}.",
34
+ playerAddress: {
35
+ source: "user-name",
36
+ rawValue: "Captain Zephod",
37
+ },
38
+ });
19
39
 
20
- console.log(packageDescriptor.packageName);
40
+ const cachePlan = planAiSpeechCache({
41
+ utteranceClass: "player-address",
42
+ textTemplate: "Welcome back, {playerAddress}.",
43
+ playerAddress: {
44
+ source: "user-name",
45
+ rawValue: "Captain Zephod",
46
+ },
47
+ voice: {
48
+ providerId: "openai",
49
+ modelId: "gpt-tts-1",
50
+ voiceId: "narrator",
51
+ locale: "en-GB",
52
+ format: "mp3",
53
+ pronunciationVersion: "2026-05",
54
+ cachePermission: "exact-and-near",
55
+ },
56
+ actorScopeKey: "user-42",
57
+ featureFlags: {
58
+ [AI_SPEECH_FEATURE_FLAGS.ttsCache]: true,
59
+ [AI_SPEECH_FEATURE_FLAGS.ttsNearReuse]: true,
60
+ },
61
+ });
62
+
63
+ const voiceTier = resolveAiSpeechVoiceTier({
64
+ environment: "production",
65
+ requestedTier: "premium-character",
66
+ featureFlags: {
67
+ [AI_SPEECH_FEATURE_FLAGS.premiumCharacters]: false,
68
+ },
69
+ });
70
+
71
+ console.log(rendered.renderText);
72
+ console.log(cachePlan.mode);
73
+ console.log(voiceTier.resolvedTier);
21
74
  ```
22
75
 
76
+ ## Rollout Controls
77
+
78
+ - `ai.tts.cache.enabled`: master switch for exact-cache planning
79
+ - `ai.tts.near-reuse.enabled`: enables near-text reuse only for approved low-risk utterance classes
80
+ - `ai.tts.premium-characters.enabled`: enables premium character voices in production routes
81
+
82
+ Default behavior is fail-closed when the caller does not provide a remote flag snapshot.
83
+
84
+ ## Cache Safety Rules
85
+
86
+ - User names, account handles, and user-renamed character names are redacted to a generic label such as `Player`.
87
+ - Near-text reuse is blocked for redacted identifiers, personal data, moderation notices, and private player context.
88
+ - Sensitive speech can only produce an actor-scoped exact cache plan when the caller provides an explicit `actorScopeKey`.
89
+ - Provider adapters must declare whether they allow no cache, exact-only cache, or exact-plus-near cache decisions.
90
+
91
+ ## Rollback
92
+
93
+ - Disable `ai.tts.near-reuse.enabled` to force safe callers back to exact-cache planning.
94
+ - Disable `ai.tts.cache.enabled` to bypass cache planning entirely.
95
+ - Disable `ai.tts.premium-characters.enabled` to downgrade premium character requests to standard voices.
96
+
23
97
  ## Development
24
98
 
25
99
  ```bash
package/dist/index.cjs CHANGED
@@ -20,15 +20,363 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ AI_SPEECH_CACHE_MODES: () => AI_SPEECH_CACHE_MODES,
24
+ AI_SPEECH_CACHE_SCOPES: () => AI_SPEECH_CACHE_SCOPES,
25
+ AI_SPEECH_DEFAULT_PLAYER_LABEL: () => AI_SPEECH_DEFAULT_PLAYER_LABEL,
26
+ AI_SPEECH_ENVIRONMENTS: () => AI_SPEECH_ENVIRONMENTS,
23
27
  AI_SPEECH_ENV_PREFIX: () => AI_SPEECH_ENV_PREFIX,
28
+ AI_SPEECH_FEATURE_FLAGS: () => AI_SPEECH_FEATURE_FLAGS,
24
29
  AI_SPEECH_FEATURE_FLAG_ID: () => AI_SPEECH_FEATURE_FLAG_ID,
30
+ AI_SPEECH_NEAR_REUSE_SAFE_CLASSES: () => AI_SPEECH_NEAR_REUSE_SAFE_CLASSES,
25
31
  AI_SPEECH_PACKAGE: () => AI_SPEECH_PACKAGE,
26
- packageDescriptor: () => packageDescriptor
32
+ AI_SPEECH_PLAYER_ADDRESS_SOURCES: () => AI_SPEECH_PLAYER_ADDRESS_SOURCES,
33
+ AI_SPEECH_PROVIDER_CACHE_PERMISSIONS: () => AI_SPEECH_PROVIDER_CACHE_PERMISSIONS,
34
+ AI_SPEECH_ROLLOUTS: () => AI_SPEECH_ROLLOUTS,
35
+ AI_SPEECH_ROLLOUT_EVALUATORS: () => AI_SPEECH_ROLLOUT_EVALUATORS,
36
+ AI_SPEECH_ROLLOUT_FALLBACK_MODES: () => AI_SPEECH_ROLLOUT_FALLBACK_MODES,
37
+ AI_SPEECH_UTTERANCE_CLASSES: () => AI_SPEECH_UTTERANCE_CLASSES,
38
+ AI_SPEECH_VOICE_TIERS: () => AI_SPEECH_VOICE_TIERS,
39
+ createAiSpeechCacheKey: () => createAiSpeechCacheKey,
40
+ createAiSpeechNearReuseFingerprint: () => createAiSpeechNearReuseFingerprint,
41
+ isAiSpeechFeatureEnabled: () => isAiSpeechFeatureEnabled,
42
+ isAiSpeechNearReuseSafeClass: () => isAiSpeechNearReuseSafeClass,
43
+ normalizeAiSpeechText: () => normalizeAiSpeechText,
44
+ packageDescriptor: () => packageDescriptor,
45
+ planAiSpeechCache: () => planAiSpeechCache,
46
+ renderAiSpeechText: () => renderAiSpeechText,
47
+ resolveAiSpeechPlayerAddress: () => resolveAiSpeechPlayerAddress,
48
+ resolveAiSpeechRolloutDecision: () => resolveAiSpeechRolloutDecision,
49
+ resolveAiSpeechVoiceTier: () => resolveAiSpeechVoiceTier
27
50
  });
28
51
  module.exports = __toCommonJS(index_exports);
52
+ function requireNonEmptyString(value, label) {
53
+ const trimmed = value.trim();
54
+ if (trimmed.length === 0) {
55
+ throw new Error(`${label} must be a non-empty string.`);
56
+ }
57
+ return trimmed;
58
+ }
59
+ function normalizeWhitespace(value) {
60
+ return value.replace(/\s+/gu, " ").trim();
61
+ }
29
62
  var AI_SPEECH_PACKAGE = "@plasius/ai-speech";
30
- var AI_SPEECH_FEATURE_FLAG_ID = "ai.speech.enabled";
31
63
  var AI_SPEECH_ENV_PREFIX = "AI_SPEECH";
64
+ var AI_SPEECH_FEATURE_FLAGS = {
65
+ ttsCache: "ai.tts.cache.enabled",
66
+ ttsNearReuse: "ai.tts.near-reuse.enabled",
67
+ premiumCharacters: "ai.tts.premium-characters.enabled"
68
+ };
69
+ var AI_SPEECH_FEATURE_FLAG_ID = AI_SPEECH_FEATURE_FLAGS.ttsCache;
70
+ var AI_SPEECH_ROLLOUT_EVALUATORS = [
71
+ "remote-flag-service",
72
+ "host-application",
73
+ "break-glass-env"
74
+ ];
75
+ var AI_SPEECH_ROLLOUT_FALLBACK_MODES = [
76
+ "fail-closed",
77
+ "fail-open"
78
+ ];
79
+ var AI_SPEECH_ROLLOUTS = Object.freeze({
80
+ ttsCache: Object.freeze({
81
+ featureFlag: AI_SPEECH_FEATURE_FLAGS.ttsCache,
82
+ evaluator: "remote-flag-service",
83
+ defaultEnabled: false,
84
+ fallbackMode: "fail-closed"
85
+ }),
86
+ ttsNearReuse: Object.freeze({
87
+ featureFlag: AI_SPEECH_FEATURE_FLAGS.ttsNearReuse,
88
+ evaluator: "remote-flag-service",
89
+ defaultEnabled: false,
90
+ fallbackMode: "fail-closed"
91
+ }),
92
+ premiumCharacters: Object.freeze({
93
+ featureFlag: AI_SPEECH_FEATURE_FLAGS.premiumCharacters,
94
+ evaluator: "remote-flag-service",
95
+ defaultEnabled: false,
96
+ fallbackMode: "fail-closed"
97
+ })
98
+ });
99
+ function resolveAiSpeechRolloutDecision(control, snapshot = {}) {
100
+ const resolved = snapshot[control.featureFlag];
101
+ if (typeof resolved === "boolean") {
102
+ return {
103
+ ...control,
104
+ enabled: resolved,
105
+ source: "snapshot"
106
+ };
107
+ }
108
+ return {
109
+ ...control,
110
+ enabled: control.defaultEnabled,
111
+ source: "default"
112
+ };
113
+ }
114
+ function isAiSpeechFeatureEnabled(featureFlag, snapshot = {}) {
115
+ const control = Object.values(AI_SPEECH_ROLLOUTS).find(
116
+ (candidate) => candidate.featureFlag === featureFlag
117
+ );
118
+ if (!control) {
119
+ return false;
120
+ }
121
+ return resolveAiSpeechRolloutDecision(control, snapshot).enabled;
122
+ }
123
+ var AI_SPEECH_VOICE_TIERS = [
124
+ "development",
125
+ "standard",
126
+ "premium-character"
127
+ ];
128
+ var AI_SPEECH_ENVIRONMENTS = [
129
+ "development",
130
+ "preview",
131
+ "production"
132
+ ];
133
+ function resolveAiSpeechVoiceTier(input) {
134
+ const requestedTier = input.requestedTier ?? (input.environment === "development" ? "development" : "standard");
135
+ if (requestedTier === "premium-character" && !isAiSpeechFeatureEnabled(
136
+ AI_SPEECH_FEATURE_FLAGS.premiumCharacters,
137
+ input.featureFlags
138
+ )) {
139
+ return {
140
+ requestedTier,
141
+ resolvedTier: "standard",
142
+ fallbackTier: "standard",
143
+ reasonCodes: ["premium-character-rollout-disabled"]
144
+ };
145
+ }
146
+ if (requestedTier === "development" && input.environment !== "development") {
147
+ return {
148
+ requestedTier,
149
+ resolvedTier: "standard",
150
+ fallbackTier: "standard",
151
+ reasonCodes: ["development-voices-limited-to-development-environments"]
152
+ };
153
+ }
154
+ return {
155
+ requestedTier,
156
+ resolvedTier: requestedTier,
157
+ reasonCodes: []
158
+ };
159
+ }
160
+ var AI_SPEECH_PLAYER_ADDRESS_SOURCES = [
161
+ "generic-player",
162
+ "class-title",
163
+ "faction-title",
164
+ "authored-character",
165
+ "user-name",
166
+ "account-handle",
167
+ "user-renamed-character"
168
+ ];
169
+ var AI_SPEECH_DEFAULT_PLAYER_LABEL = "Player";
170
+ function resolveAiSpeechPlayerAddress(input) {
171
+ const fallbackLabel = normalizeWhitespace(input.fallbackLabel ?? "") || AI_SPEECH_DEFAULT_PLAYER_LABEL;
172
+ const rawValue = normalizeWhitespace(input.rawValue ?? "");
173
+ switch (input.source) {
174
+ case "generic-player":
175
+ return {
176
+ source: input.source,
177
+ renderText: fallbackLabel,
178
+ exactReuseAllowed: true,
179
+ nearReuseAllowed: true,
180
+ redacted: false,
181
+ reasonCodes: ["generic-player-address"]
182
+ };
183
+ case "class-title":
184
+ case "faction-title":
185
+ return {
186
+ source: input.source,
187
+ renderText: rawValue || fallbackLabel,
188
+ exactReuseAllowed: true,
189
+ nearReuseAllowed: true,
190
+ redacted: false,
191
+ reasonCodes: rawValue ? [] : ["player-address-fell-back-to-generic-label"]
192
+ };
193
+ case "authored-character":
194
+ return {
195
+ source: input.source,
196
+ renderText: rawValue || fallbackLabel,
197
+ exactReuseAllowed: true,
198
+ nearReuseAllowed: true,
199
+ redacted: false,
200
+ reasonCodes: rawValue ? [] : ["player-address-fell-back-to-generic-label"]
201
+ };
202
+ case "user-name":
203
+ case "account-handle":
204
+ case "user-renamed-character":
205
+ return {
206
+ source: input.source,
207
+ renderText: fallbackLabel,
208
+ exactReuseAllowed: true,
209
+ nearReuseAllowed: false,
210
+ redacted: true,
211
+ reasonCodes: ["player-identifier-redacted-from-render-text"]
212
+ };
213
+ }
214
+ }
215
+ function renderAiSpeechText(input) {
216
+ const textTemplate = requireNonEmptyString(
217
+ input.textTemplate,
218
+ "Speech text template"
219
+ );
220
+ const hasPlayerPlaceholder = textTemplate.includes("{playerAddress}");
221
+ if (hasPlayerPlaceholder && !input.playerAddress) {
222
+ throw new Error(
223
+ "Speech text template requires playerAddress when using the {playerAddress} placeholder."
224
+ );
225
+ }
226
+ const playerAddress = input.playerAddress ? resolveAiSpeechPlayerAddress(input.playerAddress) : void 0;
227
+ const renderText = normalizeWhitespace(
228
+ hasPlayerPlaceholder && playerAddress ? textTemplate.replace(/\{playerAddress\}/gu, playerAddress.renderText) : textTemplate
229
+ );
230
+ return {
231
+ renderText,
232
+ normalizedRenderText: normalizeAiSpeechText(renderText),
233
+ playerAddress
234
+ };
235
+ }
236
+ function normalizeAiSpeechText(value) {
237
+ return requireNonEmptyString(
238
+ normalizeWhitespace(value).toLocaleLowerCase("en-GB"),
239
+ "Speech text"
240
+ );
241
+ }
242
+ var AI_SPEECH_PROVIDER_CACHE_PERMISSIONS = [
243
+ "forbidden",
244
+ "exact-only",
245
+ "exact-and-near"
246
+ ];
247
+ var AI_SPEECH_CACHE_MODES = [
248
+ "disabled",
249
+ "no-cache",
250
+ "exact",
251
+ "near"
252
+ ];
253
+ var AI_SPEECH_CACHE_SCOPES = ["none", "actor", "global"];
254
+ var AI_SPEECH_UTTERANCE_CLASSES = [
255
+ "system-generic",
256
+ "game-npc-dialogue",
257
+ "game-bark",
258
+ "player-address",
259
+ "moderation-notice",
260
+ "private-response"
261
+ ];
262
+ var AI_SPEECH_NEAR_REUSE_SAFE_CLASSES = [
263
+ "system-generic",
264
+ "game-npc-dialogue",
265
+ "game-bark",
266
+ "player-address"
267
+ ];
268
+ var AI_SPEECH_NEAR_REUSE_SAFE_CLASS_SET = new Set(AI_SPEECH_NEAR_REUSE_SAFE_CLASSES);
269
+ function createAiSpeechCacheKey(input) {
270
+ const scopeDiscriminator = normalizeWhitespace(input.scopeDiscriminator ?? "");
271
+ return [
272
+ requireNonEmptyString(input.providerId, "Provider id"),
273
+ requireNonEmptyString(input.modelId, "Model id"),
274
+ requireNonEmptyString(input.voiceId, "Voice id"),
275
+ requireNonEmptyString(input.locale, "Locale"),
276
+ normalizeWhitespace(input.styleId ?? "") || "_",
277
+ requireNonEmptyString(input.format, "Format"),
278
+ requireNonEmptyString(
279
+ input.pronunciationVersion,
280
+ "Pronunciation version"
281
+ ),
282
+ scopeDiscriminator || "global",
283
+ normalizeAiSpeechText(input.normalizedRenderText)
284
+ ].map((segment) => encodeURIComponent(segment)).join("::");
285
+ }
286
+ function createAiSpeechNearReuseFingerprint(value) {
287
+ const normalized = normalizeAiSpeechText(value).replace(/[^a-z0-9\s]/gu, " ").replace(/\s+/gu, " ").trim();
288
+ return requireNonEmptyString(normalized, "Near-reuse fingerprint");
289
+ }
290
+ function isAiSpeechNearReuseSafeClass(utteranceClass) {
291
+ return AI_SPEECH_NEAR_REUSE_SAFE_CLASS_SET.has(utteranceClass);
292
+ }
293
+ function planAiSpeechCache(input) {
294
+ const rendered = renderAiSpeechText({
295
+ textTemplate: input.textTemplate,
296
+ playerAddress: input.playerAddress
297
+ });
298
+ const enabledFeatureFlags = [];
299
+ const cacheEnabled = isAiSpeechFeatureEnabled(
300
+ AI_SPEECH_FEATURE_FLAGS.ttsCache,
301
+ input.featureFlags
302
+ );
303
+ if (!cacheEnabled) {
304
+ return {
305
+ mode: "disabled",
306
+ sharingScope: "none",
307
+ renderText: rendered.renderText,
308
+ normalizedRenderText: rendered.normalizedRenderText,
309
+ enabledFeatureFlags,
310
+ playerAddress: rendered.playerAddress,
311
+ reasonCodes: ["tts-cache-rollout-disabled"]
312
+ };
313
+ }
314
+ enabledFeatureFlags.push(AI_SPEECH_FEATURE_FLAGS.ttsCache);
315
+ if (input.voice.cachePermission === "forbidden") {
316
+ return {
317
+ mode: "no-cache",
318
+ sharingScope: "none",
319
+ renderText: rendered.renderText,
320
+ normalizedRenderText: rendered.normalizedRenderText,
321
+ enabledFeatureFlags,
322
+ playerAddress: rendered.playerAddress,
323
+ reasonCodes: ["provider-forbids-tts-caching"]
324
+ };
325
+ }
326
+ const containsSensitiveContent = Boolean(input.containsPersonalData) || Boolean(input.containsPrivateContext) || Boolean(input.containsModerationNotice) || Boolean(rendered.playerAddress?.redacted);
327
+ const actorScopeKey = normalizeWhitespace(input.actorScopeKey ?? "");
328
+ const actorScopeRequired = containsSensitiveContent || input.utteranceClass === "private-response";
329
+ if (actorScopeRequired && !actorScopeKey) {
330
+ return {
331
+ mode: "no-cache",
332
+ sharingScope: "none",
333
+ renderText: rendered.renderText,
334
+ normalizedRenderText: rendered.normalizedRenderText,
335
+ enabledFeatureFlags,
336
+ playerAddress: rendered.playerAddress,
337
+ reasonCodes: ["actor-scope-key-required-for-sensitive-cache-entry"]
338
+ };
339
+ }
340
+ const exactKey = createAiSpeechCacheKey({
341
+ ...input.voice,
342
+ normalizedRenderText: rendered.normalizedRenderText,
343
+ scopeDiscriminator: actorScopeRequired ? actorScopeKey : void 0
344
+ });
345
+ const nearReuseEnabled = input.voice.cachePermission === "exact-and-near" && isAiSpeechFeatureEnabled(
346
+ AI_SPEECH_FEATURE_FLAGS.ttsNearReuse,
347
+ input.featureFlags
348
+ ) && isAiSpeechNearReuseSafeClass(input.utteranceClass) && !containsSensitiveContent && (rendered.playerAddress?.nearReuseAllowed ?? true);
349
+ if (!nearReuseEnabled) {
350
+ return {
351
+ mode: "exact",
352
+ sharingScope: actorScopeRequired ? "actor" : "global",
353
+ renderText: rendered.renderText,
354
+ normalizedRenderText: rendered.normalizedRenderText,
355
+ exactKey,
356
+ enabledFeatureFlags,
357
+ playerAddress: rendered.playerAddress,
358
+ reasonCodes: containsSensitiveContent ? ["exact-cache-only-for-sensitive-or-redacted-content"] : ["near-reuse-disabled-or-ineligible"]
359
+ };
360
+ }
361
+ enabledFeatureFlags.push(AI_SPEECH_FEATURE_FLAGS.ttsNearReuse);
362
+ const nearKey = createAiSpeechCacheKey({
363
+ ...input.voice,
364
+ normalizedRenderText: createAiSpeechNearReuseFingerprint(
365
+ rendered.normalizedRenderText
366
+ )
367
+ });
368
+ return {
369
+ mode: "near",
370
+ sharingScope: "global",
371
+ renderText: rendered.renderText,
372
+ normalizedRenderText: rendered.normalizedRenderText,
373
+ exactKey,
374
+ nearKey,
375
+ enabledFeatureFlags,
376
+ playerAddress: rendered.playerAddress,
377
+ reasonCodes: ["near-reuse-eligible"]
378
+ };
379
+ }
32
380
  var packageDescriptor = Object.freeze({
33
381
  packageName: AI_SPEECH_PACKAGE,
34
382
  featureFlagId: AI_SPEECH_FEATURE_FLAG_ID,
@@ -37,9 +385,32 @@ var packageDescriptor = Object.freeze({
37
385
  });
38
386
  // Annotate the CommonJS export names for ESM import in node:
39
387
  0 && (module.exports = {
388
+ AI_SPEECH_CACHE_MODES,
389
+ AI_SPEECH_CACHE_SCOPES,
390
+ AI_SPEECH_DEFAULT_PLAYER_LABEL,
391
+ AI_SPEECH_ENVIRONMENTS,
40
392
  AI_SPEECH_ENV_PREFIX,
393
+ AI_SPEECH_FEATURE_FLAGS,
41
394
  AI_SPEECH_FEATURE_FLAG_ID,
395
+ AI_SPEECH_NEAR_REUSE_SAFE_CLASSES,
42
396
  AI_SPEECH_PACKAGE,
43
- packageDescriptor
397
+ AI_SPEECH_PLAYER_ADDRESS_SOURCES,
398
+ AI_SPEECH_PROVIDER_CACHE_PERMISSIONS,
399
+ AI_SPEECH_ROLLOUTS,
400
+ AI_SPEECH_ROLLOUT_EVALUATORS,
401
+ AI_SPEECH_ROLLOUT_FALLBACK_MODES,
402
+ AI_SPEECH_UTTERANCE_CLASSES,
403
+ AI_SPEECH_VOICE_TIERS,
404
+ createAiSpeechCacheKey,
405
+ createAiSpeechNearReuseFingerprint,
406
+ isAiSpeechFeatureEnabled,
407
+ isAiSpeechNearReuseSafeClass,
408
+ normalizeAiSpeechText,
409
+ packageDescriptor,
410
+ planAiSpeechCache,
411
+ renderAiSpeechText,
412
+ resolveAiSpeechPlayerAddress,
413
+ resolveAiSpeechRolloutDecision,
414
+ resolveAiSpeechVoiceTier
44
415
  });
45
416
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export interface AiPackageDescriptor {\n readonly packageName: string;\n readonly featureFlagId: string;\n readonly envPrefix: string;\n readonly summary: string;\n}\n\nexport const AI_SPEECH_PACKAGE = \"@plasius/ai-speech\";\nexport const AI_SPEECH_FEATURE_FLAG_ID = \"ai.speech.enabled\";\nexport const AI_SPEECH_ENV_PREFIX = \"AI_SPEECH\";\n\nexport const packageDescriptor: AiPackageDescriptor = Object.freeze({\n packageName: AI_SPEECH_PACKAGE,\n featureFlagId: AI_SPEECH_FEATURE_FLAG_ID,\n envPrefix: AI_SPEECH_ENV_PREFIX,\n summary: \"Speech orchestration, TTS cache contracts, STT/TTS routing, and voice tier policy for Plasius AI.\",\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOO,IAAM,oBAAoB;AAC1B,IAAM,4BAA4B;AAClC,IAAM,uBAAuB;AAE7B,IAAM,oBAAyC,OAAO,OAAO;AAAA,EAClE,aAAa;AAAA,EACb,eAAe;AAAA,EACf,WAAW;AAAA,EACX,SAAS;AACX,CAAC;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export interface AiPackageDescriptor {\n readonly packageName: string;\n readonly featureFlagId: string;\n readonly envPrefix: string;\n readonly summary: string;\n}\n\nfunction requireNonEmptyString(value: string, label: string): string {\n const trimmed = value.trim();\n if (trimmed.length === 0) {\n throw new Error(`${label} must be a non-empty string.`);\n }\n\n return trimmed;\n}\n\nfunction normalizeWhitespace(value: string): string {\n return value.replace(/\\s+/gu, \" \").trim();\n}\n\nexport const AI_SPEECH_PACKAGE = \"@plasius/ai-speech\";\nexport const AI_SPEECH_ENV_PREFIX = \"AI_SPEECH\";\n\nexport const AI_SPEECH_FEATURE_FLAGS = {\n ttsCache: \"ai.tts.cache.enabled\",\n ttsNearReuse: \"ai.tts.near-reuse.enabled\",\n premiumCharacters: \"ai.tts.premium-characters.enabled\",\n} as const;\n\nexport type AiSpeechFeatureFlagKey =\n (typeof AI_SPEECH_FEATURE_FLAGS)[keyof typeof AI_SPEECH_FEATURE_FLAGS];\n\nexport type AiSpeechFeatureFlagSnapshot = Readonly<\n Record<string, boolean | undefined>\n>;\n\nexport const AI_SPEECH_FEATURE_FLAG_ID = AI_SPEECH_FEATURE_FLAGS.ttsCache;\n\nexport const AI_SPEECH_ROLLOUT_EVALUATORS = [\n \"remote-flag-service\",\n \"host-application\",\n \"break-glass-env\",\n] as const;\n\nexport type AiSpeechRolloutEvaluator =\n (typeof AI_SPEECH_ROLLOUT_EVALUATORS)[number];\n\nexport const AI_SPEECH_ROLLOUT_FALLBACK_MODES = [\n \"fail-closed\",\n \"fail-open\",\n] as const;\n\nexport type AiSpeechRolloutFallbackMode =\n (typeof AI_SPEECH_ROLLOUT_FALLBACK_MODES)[number];\n\nexport interface AiSpeechRolloutControl {\n readonly featureFlag: AiSpeechFeatureFlagKey;\n readonly evaluator: AiSpeechRolloutEvaluator;\n readonly defaultEnabled: boolean;\n readonly fallbackMode: AiSpeechRolloutFallbackMode;\n}\n\nexport interface AiSpeechRolloutDecision extends AiSpeechRolloutControl {\n readonly enabled: boolean;\n readonly source: \"snapshot\" | \"default\";\n}\n\nexport const AI_SPEECH_ROLLOUTS = Object.freeze({\n ttsCache: Object.freeze<AiSpeechRolloutControl>({\n featureFlag: AI_SPEECH_FEATURE_FLAGS.ttsCache,\n evaluator: \"remote-flag-service\",\n defaultEnabled: false,\n fallbackMode: \"fail-closed\",\n }),\n ttsNearReuse: Object.freeze<AiSpeechRolloutControl>({\n featureFlag: AI_SPEECH_FEATURE_FLAGS.ttsNearReuse,\n evaluator: \"remote-flag-service\",\n defaultEnabled: false,\n fallbackMode: \"fail-closed\",\n }),\n premiumCharacters: Object.freeze<AiSpeechRolloutControl>({\n featureFlag: AI_SPEECH_FEATURE_FLAGS.premiumCharacters,\n evaluator: \"remote-flag-service\",\n defaultEnabled: false,\n fallbackMode: \"fail-closed\",\n }),\n});\n\nexport function resolveAiSpeechRolloutDecision(\n control: AiSpeechRolloutControl,\n snapshot: AiSpeechFeatureFlagSnapshot = {}\n): AiSpeechRolloutDecision {\n const resolved = snapshot[control.featureFlag];\n if (typeof resolved === \"boolean\") {\n return {\n ...control,\n enabled: resolved,\n source: \"snapshot\",\n };\n }\n\n return {\n ...control,\n enabled: control.defaultEnabled,\n source: \"default\",\n };\n}\n\nexport function isAiSpeechFeatureEnabled(\n featureFlag: AiSpeechFeatureFlagKey,\n snapshot: AiSpeechFeatureFlagSnapshot = {}\n): boolean {\n const control = Object.values(AI_SPEECH_ROLLOUTS).find(\n (candidate) => candidate.featureFlag === featureFlag\n );\n\n if (!control) {\n return false;\n }\n\n return resolveAiSpeechRolloutDecision(control, snapshot).enabled;\n}\n\nexport const AI_SPEECH_VOICE_TIERS = [\n \"development\",\n \"standard\",\n \"premium-character\",\n] as const;\n\nexport type AiSpeechVoiceTier = (typeof AI_SPEECH_VOICE_TIERS)[number];\n\nexport const AI_SPEECH_ENVIRONMENTS = [\n \"development\",\n \"preview\",\n \"production\",\n] as const;\n\nexport type AiSpeechEnvironment = (typeof AI_SPEECH_ENVIRONMENTS)[number];\n\nexport interface AiSpeechVoiceTierDecision {\n readonly requestedTier: AiSpeechVoiceTier;\n readonly resolvedTier: AiSpeechVoiceTier;\n readonly fallbackTier?: AiSpeechVoiceTier;\n readonly reasonCodes: readonly string[];\n}\n\nexport interface ResolveAiSpeechVoiceTierInput {\n readonly environment: AiSpeechEnvironment;\n readonly requestedTier?: AiSpeechVoiceTier;\n readonly featureFlags?: AiSpeechFeatureFlagSnapshot;\n}\n\nexport function resolveAiSpeechVoiceTier(\n input: ResolveAiSpeechVoiceTierInput\n): AiSpeechVoiceTierDecision {\n const requestedTier =\n input.requestedTier ??\n (input.environment === \"development\" ? \"development\" : \"standard\");\n\n if (\n requestedTier === \"premium-character\" &&\n !isAiSpeechFeatureEnabled(\n AI_SPEECH_FEATURE_FLAGS.premiumCharacters,\n input.featureFlags\n )\n ) {\n return {\n requestedTier,\n resolvedTier: \"standard\",\n fallbackTier: \"standard\",\n reasonCodes: [\"premium-character-rollout-disabled\"],\n };\n }\n\n if (\n requestedTier === \"development\" &&\n input.environment !== \"development\"\n ) {\n return {\n requestedTier,\n resolvedTier: \"standard\",\n fallbackTier: \"standard\",\n reasonCodes: [\"development-voices-limited-to-development-environments\"],\n };\n }\n\n return {\n requestedTier,\n resolvedTier: requestedTier,\n reasonCodes: [],\n };\n}\n\nexport const AI_SPEECH_PLAYER_ADDRESS_SOURCES = [\n \"generic-player\",\n \"class-title\",\n \"faction-title\",\n \"authored-character\",\n \"user-name\",\n \"account-handle\",\n \"user-renamed-character\",\n] as const;\n\nexport type AiSpeechPlayerAddressSource =\n (typeof AI_SPEECH_PLAYER_ADDRESS_SOURCES)[number];\n\nexport const AI_SPEECH_DEFAULT_PLAYER_LABEL = \"Player\";\n\nexport interface AiSpeechPlayerAddressInput {\n readonly source: AiSpeechPlayerAddressSource;\n readonly rawValue?: string;\n readonly fallbackLabel?: string;\n}\n\nexport interface AiSpeechPlayerAddressDecision {\n readonly source: AiSpeechPlayerAddressSource;\n readonly renderText: string;\n readonly exactReuseAllowed: boolean;\n readonly nearReuseAllowed: boolean;\n readonly redacted: boolean;\n readonly reasonCodes: readonly string[];\n}\n\nexport function resolveAiSpeechPlayerAddress(\n input: AiSpeechPlayerAddressInput\n): AiSpeechPlayerAddressDecision {\n const fallbackLabel =\n normalizeWhitespace(input.fallbackLabel ?? \"\") ||\n AI_SPEECH_DEFAULT_PLAYER_LABEL;\n const rawValue = normalizeWhitespace(input.rawValue ?? \"\");\n\n switch (input.source) {\n case \"generic-player\":\n return {\n source: input.source,\n renderText: fallbackLabel,\n exactReuseAllowed: true,\n nearReuseAllowed: true,\n redacted: false,\n reasonCodes: [\"generic-player-address\"],\n };\n case \"class-title\":\n case \"faction-title\":\n return {\n source: input.source,\n renderText: rawValue || fallbackLabel,\n exactReuseAllowed: true,\n nearReuseAllowed: true,\n redacted: false,\n reasonCodes: rawValue ? [] : [\"player-address-fell-back-to-generic-label\"],\n };\n case \"authored-character\":\n return {\n source: input.source,\n renderText: rawValue || fallbackLabel,\n exactReuseAllowed: true,\n nearReuseAllowed: true,\n redacted: false,\n reasonCodes: rawValue ? [] : [\"player-address-fell-back-to-generic-label\"],\n };\n case \"user-name\":\n case \"account-handle\":\n case \"user-renamed-character\":\n return {\n source: input.source,\n renderText: fallbackLabel,\n exactReuseAllowed: true,\n nearReuseAllowed: false,\n redacted: true,\n reasonCodes: [\"player-identifier-redacted-from-render-text\"],\n };\n }\n}\n\nexport interface AiSpeechRenderTextInput {\n readonly textTemplate: string;\n readonly playerAddress?: AiSpeechPlayerAddressInput;\n}\n\nexport interface AiSpeechRenderTextResult {\n readonly renderText: string;\n readonly normalizedRenderText: string;\n readonly playerAddress?: AiSpeechPlayerAddressDecision;\n}\n\nexport function renderAiSpeechText(\n input: AiSpeechRenderTextInput\n): AiSpeechRenderTextResult {\n const textTemplate = requireNonEmptyString(\n input.textTemplate,\n \"Speech text template\"\n );\n const hasPlayerPlaceholder = textTemplate.includes(\"{playerAddress}\");\n\n if (hasPlayerPlaceholder && !input.playerAddress) {\n throw new Error(\n \"Speech text template requires playerAddress when using the {playerAddress} placeholder.\"\n );\n }\n\n const playerAddress = input.playerAddress\n ? resolveAiSpeechPlayerAddress(input.playerAddress)\n : undefined;\n const renderText = normalizeWhitespace(\n hasPlayerPlaceholder && playerAddress\n ? textTemplate.replace(/\\{playerAddress\\}/gu, playerAddress.renderText)\n : textTemplate\n );\n\n return {\n renderText,\n normalizedRenderText: normalizeAiSpeechText(renderText),\n playerAddress,\n };\n}\n\nexport function normalizeAiSpeechText(value: string): string {\n return requireNonEmptyString(\n normalizeWhitespace(value).toLocaleLowerCase(\"en-GB\"),\n \"Speech text\"\n );\n}\n\nexport const AI_SPEECH_PROVIDER_CACHE_PERMISSIONS = [\n \"forbidden\",\n \"exact-only\",\n \"exact-and-near\",\n] as const;\n\nexport type AiSpeechProviderCachePermission =\n (typeof AI_SPEECH_PROVIDER_CACHE_PERMISSIONS)[number];\n\nexport const AI_SPEECH_CACHE_MODES = [\n \"disabled\",\n \"no-cache\",\n \"exact\",\n \"near\",\n] as const;\n\nexport type AiSpeechCacheMode = (typeof AI_SPEECH_CACHE_MODES)[number];\n\nexport const AI_SPEECH_CACHE_SCOPES = [\"none\", \"actor\", \"global\"] as const;\n\nexport type AiSpeechCacheScope = (typeof AI_SPEECH_CACHE_SCOPES)[number];\n\nexport const AI_SPEECH_UTTERANCE_CLASSES = [\n \"system-generic\",\n \"game-npc-dialogue\",\n \"game-bark\",\n \"player-address\",\n \"moderation-notice\",\n \"private-response\",\n] as const;\n\nexport type AiSpeechUtteranceClass =\n (typeof AI_SPEECH_UTTERANCE_CLASSES)[number];\n\nexport const AI_SPEECH_NEAR_REUSE_SAFE_CLASSES = [\n \"system-generic\",\n \"game-npc-dialogue\",\n \"game-bark\",\n \"player-address\",\n] as const satisfies readonly AiSpeechUtteranceClass[];\n\nconst AI_SPEECH_NEAR_REUSE_SAFE_CLASS_SET: ReadonlySet<AiSpeechUtteranceClass> =\n new Set(AI_SPEECH_NEAR_REUSE_SAFE_CLASSES);\n\nexport interface AiSpeechCacheKeyInput {\n readonly providerId: string;\n readonly modelId: string;\n readonly voiceId: string;\n readonly locale: string;\n readonly format: string;\n readonly pronunciationVersion: string;\n readonly normalizedRenderText: string;\n readonly styleId?: string;\n readonly scopeDiscriminator?: string;\n}\n\nexport interface AiSpeechVoiceProfileRef {\n readonly providerId: string;\n readonly modelId: string;\n readonly voiceId: string;\n readonly locale: string;\n readonly format: string;\n readonly pronunciationVersion: string;\n readonly styleId?: string;\n readonly cachePermission: AiSpeechProviderCachePermission;\n readonly tier?: AiSpeechVoiceTier;\n readonly profileId?: string;\n}\n\nexport interface AiSpeechCacheTelemetry {\n readonly cacheHits: number;\n readonly nearCacheHits: number;\n readonly cacheMisses: number;\n readonly charactersSaved: number;\n readonly estimatedCostSavedUsd?: number;\n readonly voiceProfileIds?: readonly string[];\n}\n\nexport interface PlanAiSpeechCacheInput {\n readonly utteranceClass: AiSpeechUtteranceClass;\n readonly textTemplate: string;\n readonly playerAddress?: AiSpeechPlayerAddressInput;\n readonly voice: AiSpeechVoiceProfileRef;\n readonly featureFlags?: AiSpeechFeatureFlagSnapshot;\n readonly actorScopeKey?: string;\n readonly containsPersonalData?: boolean;\n readonly containsPrivateContext?: boolean;\n readonly containsModerationNotice?: boolean;\n}\n\nexport interface AiSpeechCachePlan {\n readonly mode: AiSpeechCacheMode;\n readonly sharingScope: AiSpeechCacheScope;\n readonly renderText: string;\n readonly normalizedRenderText: string;\n readonly exactKey?: string;\n readonly nearKey?: string;\n readonly enabledFeatureFlags: readonly AiSpeechFeatureFlagKey[];\n readonly playerAddress?: AiSpeechPlayerAddressDecision;\n readonly reasonCodes: readonly string[];\n}\n\nexport function createAiSpeechCacheKey(input: AiSpeechCacheKeyInput): string {\n const scopeDiscriminator = normalizeWhitespace(input.scopeDiscriminator ?? \"\");\n return [\n requireNonEmptyString(input.providerId, \"Provider id\"),\n requireNonEmptyString(input.modelId, \"Model id\"),\n requireNonEmptyString(input.voiceId, \"Voice id\"),\n requireNonEmptyString(input.locale, \"Locale\"),\n normalizeWhitespace(input.styleId ?? \"\") || \"_\",\n requireNonEmptyString(input.format, \"Format\"),\n requireNonEmptyString(\n input.pronunciationVersion,\n \"Pronunciation version\"\n ),\n scopeDiscriminator || \"global\",\n normalizeAiSpeechText(input.normalizedRenderText),\n ]\n .map((segment) => encodeURIComponent(segment))\n .join(\"::\");\n}\n\nexport function createAiSpeechNearReuseFingerprint(value: string): string {\n const normalized = normalizeAiSpeechText(value)\n .replace(/[^a-z0-9\\s]/gu, \" \")\n .replace(/\\s+/gu, \" \")\n .trim();\n\n return requireNonEmptyString(normalized, \"Near-reuse fingerprint\");\n}\n\nexport function isAiSpeechNearReuseSafeClass(\n utteranceClass: AiSpeechUtteranceClass\n): boolean {\n return AI_SPEECH_NEAR_REUSE_SAFE_CLASS_SET.has(utteranceClass);\n}\n\nexport function planAiSpeechCache(\n input: PlanAiSpeechCacheInput\n): AiSpeechCachePlan {\n const rendered = renderAiSpeechText({\n textTemplate: input.textTemplate,\n playerAddress: input.playerAddress,\n });\n const enabledFeatureFlags: AiSpeechFeatureFlagKey[] = [];\n const cacheEnabled = isAiSpeechFeatureEnabled(\n AI_SPEECH_FEATURE_FLAGS.ttsCache,\n input.featureFlags\n );\n\n if (!cacheEnabled) {\n return {\n mode: \"disabled\",\n sharingScope: \"none\",\n renderText: rendered.renderText,\n normalizedRenderText: rendered.normalizedRenderText,\n enabledFeatureFlags,\n playerAddress: rendered.playerAddress,\n reasonCodes: [\"tts-cache-rollout-disabled\"],\n };\n }\n\n enabledFeatureFlags.push(AI_SPEECH_FEATURE_FLAGS.ttsCache);\n\n if (input.voice.cachePermission === \"forbidden\") {\n return {\n mode: \"no-cache\",\n sharingScope: \"none\",\n renderText: rendered.renderText,\n normalizedRenderText: rendered.normalizedRenderText,\n enabledFeatureFlags,\n playerAddress: rendered.playerAddress,\n reasonCodes: [\"provider-forbids-tts-caching\"],\n };\n }\n\n const containsSensitiveContent =\n Boolean(input.containsPersonalData) ||\n Boolean(input.containsPrivateContext) ||\n Boolean(input.containsModerationNotice) ||\n Boolean(rendered.playerAddress?.redacted);\n const actorScopeKey = normalizeWhitespace(input.actorScopeKey ?? \"\");\n\n const actorScopeRequired =\n containsSensitiveContent || input.utteranceClass === \"private-response\";\n\n if (actorScopeRequired && !actorScopeKey) {\n return {\n mode: \"no-cache\",\n sharingScope: \"none\",\n renderText: rendered.renderText,\n normalizedRenderText: rendered.normalizedRenderText,\n enabledFeatureFlags,\n playerAddress: rendered.playerAddress,\n reasonCodes: [\"actor-scope-key-required-for-sensitive-cache-entry\"],\n };\n }\n\n const exactKey = createAiSpeechCacheKey({\n ...input.voice,\n normalizedRenderText: rendered.normalizedRenderText,\n scopeDiscriminator: actorScopeRequired ? actorScopeKey : undefined,\n });\n\n const nearReuseEnabled =\n input.voice.cachePermission === \"exact-and-near\" &&\n isAiSpeechFeatureEnabled(\n AI_SPEECH_FEATURE_FLAGS.ttsNearReuse,\n input.featureFlags\n ) &&\n isAiSpeechNearReuseSafeClass(input.utteranceClass) &&\n !containsSensitiveContent &&\n (rendered.playerAddress?.nearReuseAllowed ?? true);\n\n if (!nearReuseEnabled) {\n return {\n mode: \"exact\",\n sharingScope: actorScopeRequired ? \"actor\" : \"global\",\n renderText: rendered.renderText,\n normalizedRenderText: rendered.normalizedRenderText,\n exactKey,\n enabledFeatureFlags,\n playerAddress: rendered.playerAddress,\n reasonCodes: containsSensitiveContent\n ? [\"exact-cache-only-for-sensitive-or-redacted-content\"]\n : [\"near-reuse-disabled-or-ineligible\"],\n };\n }\n\n enabledFeatureFlags.push(AI_SPEECH_FEATURE_FLAGS.ttsNearReuse);\n\n const nearKey = createAiSpeechCacheKey({\n ...input.voice,\n normalizedRenderText: createAiSpeechNearReuseFingerprint(\n rendered.normalizedRenderText\n ),\n });\n\n return {\n mode: \"near\",\n sharingScope: \"global\",\n renderText: rendered.renderText,\n normalizedRenderText: rendered.normalizedRenderText,\n exactKey,\n nearKey,\n enabledFeatureFlags,\n playerAddress: rendered.playerAddress,\n reasonCodes: [\"near-reuse-eligible\"],\n };\n}\n\nexport const packageDescriptor: AiPackageDescriptor = Object.freeze({\n packageName: AI_SPEECH_PACKAGE,\n featureFlagId: AI_SPEECH_FEATURE_FLAG_ID,\n envPrefix: AI_SPEECH_ENV_PREFIX,\n summary:\n \"Speech orchestration, TTS cache contracts, STT/TTS routing, and voice tier policy for Plasius AI.\",\n});\n"],"mappings":"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","names":[]}