@plasius/ai-speech 0.1.5 → 0.1.7

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,6 +18,34 @@ 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.7] - 2026-07-12
22
+
23
+ - **Added**
24
+ - Player System audio contracts for narrated responses, localized cues, repeating warnings, priority/ducking policy, and combat-safe delivery under `isekai.player-system.audio.enabled`.
25
+
26
+ - **Changed**
27
+ - (none)
28
+
29
+ - **Fixed**
30
+ - (none)
31
+
32
+ - **Security**
33
+ - Audio contracts use opaque utterance/cue identifiers and bounded warning delivery; raw speech text and provider credentials remain outside the package.
34
+
35
+ ## [0.1.6] - 2026-06-28
36
+
37
+ - **Added**
38
+ - (placeholder)
39
+
40
+ - **Changed**
41
+ - Refreshed development dependency baselines to `@types/node@26.0.1`, `@typescript-eslint/*@8.62.0`, `eslint@10.6.0`, `globals@17.7.0`, and `vitest@4.1.9`.
42
+
43
+ - **Fixed**
44
+ - (placeholder)
45
+
46
+ - **Security**
47
+ - (placeholder)
48
+
21
49
  ## [0.1.5] - 2026-06-22
22
50
 
23
51
  - **Added**
@@ -54,3 +82,5 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
54
82
  [0.1.1]: https://github.com/Plasius-LTD/ai-speech/releases/tag/v0.1.1
55
83
  [0.1.4]: https://github.com/Plasius-LTD/ai-speech/releases/tag/v0.1.4
56
84
  [0.1.5]: https://github.com/Plasius-LTD/ai-speech/releases/tag/v0.1.5
85
+ [0.1.6]: https://github.com/Plasius-LTD/ai-speech/releases/tag/v0.1.6
86
+ [0.1.7]: https://github.com/Plasius-LTD/ai-speech/releases/tag/v0.1.7
package/README.md CHANGED
@@ -10,6 +10,8 @@ This package defines public contracts for:
10
10
  - cache-safe player address rendering
11
11
  - development, standard, and premium-character voice tier routing
12
12
  - cache telemetry payloads
13
+ - Player System narrated responses, localized one-shot cues, and repeating warnings
14
+ - priority, ducking, suppression, and combat-safe delivery policy
13
15
 
14
16
  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.
15
17
 
@@ -81,6 +83,49 @@ console.log(voiceTier.resolvedTier);
81
83
 
82
84
  Default behavior is fail-closed when the caller does not provide a remote flag snapshot.
83
85
 
86
+ ## Player System audio contracts
87
+
88
+ Player System audio is enabled by the remotely evaluated
89
+ `isekai.player-system.audio.enabled` flag. The package keeps the three delivery
90
+ channels distinct so hosts can apply separate UX and accessibility behavior:
91
+
92
+ - `narrated-response` uses an opaque utterance ID and locale; raw speech text
93
+ stays with the host/provider pipeline.
94
+ - `localized-cue` uses a stable cue ID, locale, and one of the exported families
95
+ `status`, `tutorial`, `mission`, `mcc`, or `warning`.
96
+ - `repeating-warning` requires bounded repeat intervals and per-minute limits.
97
+
98
+ Contracts carry `critical`, `high`, `normal`, or `low` priority plus an explicit
99
+ ducking mode. Critical audio bypasses ducking. Combat-safe policy suppresses
100
+ normal and low priority audio, allows high and critical audio, and can condense
101
+ or defer narration according to the contract. Rollout-disabled, muted, duplicate,
102
+ and explicitly suppressed dispatches fail closed with a reason code.
103
+
104
+ ```ts
105
+ import {
106
+ AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID,
107
+ createAiSpeechNarratedResponse,
108
+ resolveAiSpeechAudioPolicy,
109
+ } from "@plasius/ai-speech";
110
+
111
+ const narration = createAiSpeechNarratedResponse({
112
+ id: "tutorial-combat-hint",
113
+ utteranceId: "tutorial-combat-hint-v1",
114
+ locale: "en-GB",
115
+ priority: "high",
116
+ ducking: "music",
117
+ combatSafeDelivery: "condensed",
118
+ });
119
+
120
+ const decision = resolveAiSpeechAudioPolicy({
121
+ contract: narration,
122
+ focusMode: "combat-safe",
123
+ featureFlags: {
124
+ [AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID]: true,
125
+ },
126
+ });
127
+ ```
128
+
84
129
  ## Cache Safety Rules
85
130
 
86
131
  - User names, account handles, and user-renamed character names are redacted to a generic label such as `Player`.
package/dist/index.cjs CHANGED
@@ -20,6 +20,12 @@ 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_AUDIO_CHANNELS: () => AI_SPEECH_AUDIO_CHANNELS,
24
+ AI_SPEECH_AUDIO_COMBAT_SAFE_DELIVERIES: () => AI_SPEECH_AUDIO_COMBAT_SAFE_DELIVERIES,
25
+ AI_SPEECH_AUDIO_CUE_FAMILIES: () => AI_SPEECH_AUDIO_CUE_FAMILIES,
26
+ AI_SPEECH_AUDIO_DUCKING_MODES: () => AI_SPEECH_AUDIO_DUCKING_MODES,
27
+ AI_SPEECH_AUDIO_FOCUS_MODES: () => AI_SPEECH_AUDIO_FOCUS_MODES,
28
+ AI_SPEECH_AUDIO_PRIORITIES: () => AI_SPEECH_AUDIO_PRIORITIES,
23
29
  AI_SPEECH_CACHE_MODES: () => AI_SPEECH_CACHE_MODES,
24
30
  AI_SPEECH_CACHE_SCOPES: () => AI_SPEECH_CACHE_SCOPES,
25
31
  AI_SPEECH_DEFAULT_PLAYER_LABEL: () => AI_SPEECH_DEFAULT_PLAYER_LABEL,
@@ -30,6 +36,7 @@ __export(index_exports, {
30
36
  AI_SPEECH_NEAR_REUSE_SAFE_CLASSES: () => AI_SPEECH_NEAR_REUSE_SAFE_CLASSES,
31
37
  AI_SPEECH_PACKAGE: () => AI_SPEECH_PACKAGE,
32
38
  AI_SPEECH_PLAYER_ADDRESS_SOURCES: () => AI_SPEECH_PLAYER_ADDRESS_SOURCES,
39
+ AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID: () => AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID,
33
40
  AI_SPEECH_PROVIDER_CACHE_PERMISSIONS: () => AI_SPEECH_PROVIDER_CACHE_PERMISSIONS,
34
41
  AI_SPEECH_ROLLOUTS: () => AI_SPEECH_ROLLOUTS,
35
42
  AI_SPEECH_ROLLOUT_EVALUATORS: () => AI_SPEECH_ROLLOUT_EVALUATORS,
@@ -37,13 +44,17 @@ __export(index_exports, {
37
44
  AI_SPEECH_UTTERANCE_CLASSES: () => AI_SPEECH_UTTERANCE_CLASSES,
38
45
  AI_SPEECH_VOICE_TIERS: () => AI_SPEECH_VOICE_TIERS,
39
46
  createAiSpeechCacheKey: () => createAiSpeechCacheKey,
47
+ createAiSpeechLocalizedCue: () => createAiSpeechLocalizedCue,
48
+ createAiSpeechNarratedResponse: () => createAiSpeechNarratedResponse,
40
49
  createAiSpeechNearReuseFingerprint: () => createAiSpeechNearReuseFingerprint,
50
+ createAiSpeechRepeatingWarning: () => createAiSpeechRepeatingWarning,
41
51
  isAiSpeechFeatureEnabled: () => isAiSpeechFeatureEnabled,
42
52
  isAiSpeechNearReuseSafeClass: () => isAiSpeechNearReuseSafeClass,
43
53
  normalizeAiSpeechText: () => normalizeAiSpeechText,
44
54
  packageDescriptor: () => packageDescriptor,
45
55
  planAiSpeechCache: () => planAiSpeechCache,
46
56
  renderAiSpeechText: () => renderAiSpeechText,
57
+ resolveAiSpeechAudioPolicy: () => resolveAiSpeechAudioPolicy,
47
58
  resolveAiSpeechPlayerAddress: () => resolveAiSpeechPlayerAddress,
48
59
  resolveAiSpeechRolloutDecision: () => resolveAiSpeechRolloutDecision,
49
60
  resolveAiSpeechVoiceTier: () => resolveAiSpeechVoiceTier
@@ -377,6 +388,171 @@ function planAiSpeechCache(input) {
377
388
  reasonCodes: ["near-reuse-eligible"]
378
389
  };
379
390
  }
391
+ var AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID = "isekai.player-system.audio.enabled";
392
+ var AI_SPEECH_AUDIO_CHANNELS = [
393
+ "narrated-response",
394
+ "localized-cue",
395
+ "repeating-warning"
396
+ ];
397
+ var AI_SPEECH_AUDIO_PRIORITIES = [
398
+ "critical",
399
+ "high",
400
+ "normal",
401
+ "low"
402
+ ];
403
+ var AI_SPEECH_AUDIO_DUCKING_MODES = [
404
+ "none",
405
+ "music",
406
+ "non-critical",
407
+ "lower-priority"
408
+ ];
409
+ var AI_SPEECH_AUDIO_CUE_FAMILIES = [
410
+ "status",
411
+ "tutorial",
412
+ "mission",
413
+ "mcc",
414
+ "warning"
415
+ ];
416
+ var AI_SPEECH_AUDIO_FOCUS_MODES = [
417
+ "ambient",
418
+ "focused",
419
+ "combat-safe"
420
+ ];
421
+ var AI_SPEECH_AUDIO_COMBAT_SAFE_DELIVERIES = [
422
+ "full",
423
+ "condensed",
424
+ "deferred",
425
+ "suppressed"
426
+ ];
427
+ function requireAudioEnum(value, allowed, label) {
428
+ if (!allowed.includes(value)) {
429
+ throw new Error(`${label} is not supported: ${value}.`);
430
+ }
431
+ return value;
432
+ }
433
+ function requireBoundedAudioNumber(value, minimum, maximum, label) {
434
+ if (!Number.isFinite(value) || value < minimum || value > maximum) {
435
+ throw new Error(
436
+ `${label} must be between ${minimum} and ${maximum}, inclusive.`
437
+ );
438
+ }
439
+ return value;
440
+ }
441
+ function createAiSpeechAudioContractBase(input) {
442
+ return {
443
+ id: requireNonEmptyString(input.id, "Audio contract id"),
444
+ featureFlagId: AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID,
445
+ priority: requireAudioEnum(
446
+ input.priority,
447
+ AI_SPEECH_AUDIO_PRIORITIES,
448
+ "Audio priority"
449
+ ),
450
+ ducking: requireAudioEnum(
451
+ input.ducking,
452
+ AI_SPEECH_AUDIO_DUCKING_MODES,
453
+ "Audio ducking mode"
454
+ ),
455
+ combatSafeDelivery: requireAudioEnum(
456
+ input.combatSafeDelivery,
457
+ AI_SPEECH_AUDIO_COMBAT_SAFE_DELIVERIES,
458
+ "Combat-safe audio delivery"
459
+ )
460
+ };
461
+ }
462
+ function createAiSpeechNarratedResponse(input) {
463
+ return Object.freeze({
464
+ ...createAiSpeechAudioContractBase(input),
465
+ channel: "narrated-response",
466
+ utteranceId: requireNonEmptyString(input.utteranceId, "Utterance id"),
467
+ locale: requireNonEmptyString(input.locale, "Locale")
468
+ });
469
+ }
470
+ function createAiSpeechLocalizedCue(input) {
471
+ return Object.freeze({
472
+ ...createAiSpeechAudioContractBase(input),
473
+ channel: "localized-cue",
474
+ cueFamily: requireAudioEnum(
475
+ input.cueFamily,
476
+ AI_SPEECH_AUDIO_CUE_FAMILIES,
477
+ "Audio cue family"
478
+ ),
479
+ cueId: requireNonEmptyString(input.cueId, "Cue id"),
480
+ locale: requireNonEmptyString(input.locale, "Locale")
481
+ });
482
+ }
483
+ function createAiSpeechRepeatingWarning(input) {
484
+ return Object.freeze({
485
+ ...createAiSpeechAudioContractBase(input),
486
+ channel: "repeating-warning",
487
+ warningId: requireNonEmptyString(input.warningId, "Warning id"),
488
+ intervalSeconds: requireBoundedAudioNumber(
489
+ input.intervalSeconds,
490
+ 1,
491
+ 3600,
492
+ "Warning intervalSeconds"
493
+ ),
494
+ maximumOccurrencesPerMinute: requireBoundedAudioNumber(
495
+ input.maximumOccurrencesPerMinute,
496
+ 1,
497
+ 60,
498
+ "Warning maximumOccurrencesPerMinute"
499
+ )
500
+ });
501
+ }
502
+ function isPlayerSystemAudioEnabled(featureFlags = {}) {
503
+ return featureFlags[AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID] === true;
504
+ }
505
+ function resolveAiSpeechAudioPolicy(input) {
506
+ const { contract } = input;
507
+ if (!isPlayerSystemAudioEnabled(input.featureFlags)) {
508
+ return {
509
+ deliver: false,
510
+ mode: "deferred",
511
+ ducking: "none",
512
+ reasonCodes: ["player-system-audio-rollout-disabled"]
513
+ };
514
+ }
515
+ if (input.masterMuted || input.userMuted) {
516
+ return {
517
+ deliver: false,
518
+ mode: "deferred",
519
+ ducking: "none",
520
+ reasonCodes: [input.masterMuted ? "master-muted" : "user-muted"]
521
+ };
522
+ }
523
+ if (input.activeContractIds?.includes(contract.id)) {
524
+ return {
525
+ deliver: false,
526
+ mode: "deferred",
527
+ ducking: "none",
528
+ reasonCodes: ["duplicate-audio-contract-suppressed"]
529
+ };
530
+ }
531
+ if (input.focusMode === "combat-safe" && contract.priority !== "critical" && contract.priority !== "high") {
532
+ return {
533
+ deliver: false,
534
+ mode: "deferred",
535
+ ducking: "none",
536
+ reasonCodes: ["combat-safe-priority-suppressed"]
537
+ };
538
+ }
539
+ if (contract.combatSafeDelivery === "suppressed") {
540
+ return {
541
+ deliver: false,
542
+ mode: "deferred",
543
+ ducking: "none",
544
+ reasonCodes: ["audio-contract-suppressed"]
545
+ };
546
+ }
547
+ const mode = input.focusMode === "combat-safe" ? contract.combatSafeDelivery === "condensed" ? "condensed" : contract.combatSafeDelivery === "deferred" ? "deferred" : "full" : "full";
548
+ const ducking = contract.priority === "critical" ? "none" : contract.ducking;
549
+ return {
550
+ deliver: mode !== "deferred",
551
+ mode,
552
+ ducking,
553
+ reasonCodes: contract.priority === "critical" ? ["critical-priority-bypasses-ducking"] : []
554
+ };
555
+ }
380
556
  var packageDescriptor = Object.freeze({
381
557
  packageName: AI_SPEECH_PACKAGE,
382
558
  featureFlagId: AI_SPEECH_FEATURE_FLAG_ID,
@@ -385,6 +561,12 @@ var packageDescriptor = Object.freeze({
385
561
  });
386
562
  // Annotate the CommonJS export names for ESM import in node:
387
563
  0 && (module.exports = {
564
+ AI_SPEECH_AUDIO_CHANNELS,
565
+ AI_SPEECH_AUDIO_COMBAT_SAFE_DELIVERIES,
566
+ AI_SPEECH_AUDIO_CUE_FAMILIES,
567
+ AI_SPEECH_AUDIO_DUCKING_MODES,
568
+ AI_SPEECH_AUDIO_FOCUS_MODES,
569
+ AI_SPEECH_AUDIO_PRIORITIES,
388
570
  AI_SPEECH_CACHE_MODES,
389
571
  AI_SPEECH_CACHE_SCOPES,
390
572
  AI_SPEECH_DEFAULT_PLAYER_LABEL,
@@ -395,6 +577,7 @@ var packageDescriptor = Object.freeze({
395
577
  AI_SPEECH_NEAR_REUSE_SAFE_CLASSES,
396
578
  AI_SPEECH_PACKAGE,
397
579
  AI_SPEECH_PLAYER_ADDRESS_SOURCES,
580
+ AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID,
398
581
  AI_SPEECH_PROVIDER_CACHE_PERMISSIONS,
399
582
  AI_SPEECH_ROLLOUTS,
400
583
  AI_SPEECH_ROLLOUT_EVALUATORS,
@@ -402,13 +585,17 @@ var packageDescriptor = Object.freeze({
402
585
  AI_SPEECH_UTTERANCE_CLASSES,
403
586
  AI_SPEECH_VOICE_TIERS,
404
587
  createAiSpeechCacheKey,
588
+ createAiSpeechLocalizedCue,
589
+ createAiSpeechNarratedResponse,
405
590
  createAiSpeechNearReuseFingerprint,
591
+ createAiSpeechRepeatingWarning,
406
592
  isAiSpeechFeatureEnabled,
407
593
  isAiSpeechNearReuseSafeClass,
408
594
  normalizeAiSpeechText,
409
595
  packageDescriptor,
410
596
  planAiSpeechCache,
411
597
  renderAiSpeechText,
598
+ resolveAiSpeechAudioPolicy,
412
599
  resolveAiSpeechPlayerAddress,
413
600
  resolveAiSpeechRolloutDecision,
414
601
  resolveAiSpeechVoiceTier
@@ -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\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":[]}
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 AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID =\n \"isekai.player-system.audio.enabled\" as const;\n\nexport const AI_SPEECH_AUDIO_CHANNELS = [\n \"narrated-response\",\n \"localized-cue\",\n \"repeating-warning\",\n] as const;\n\nexport type AiSpeechAudioChannel = (typeof AI_SPEECH_AUDIO_CHANNELS)[number];\n\nexport const AI_SPEECH_AUDIO_PRIORITIES = [\n \"critical\",\n \"high\",\n \"normal\",\n \"low\",\n] as const;\n\nexport type AiSpeechAudioPriority =\n (typeof AI_SPEECH_AUDIO_PRIORITIES)[number];\n\nexport const AI_SPEECH_AUDIO_DUCKING_MODES = [\n \"none\",\n \"music\",\n \"non-critical\",\n \"lower-priority\",\n] as const;\n\nexport type AiSpeechAudioDuckingMode =\n (typeof AI_SPEECH_AUDIO_DUCKING_MODES)[number];\n\nexport const AI_SPEECH_AUDIO_CUE_FAMILIES = [\n \"status\",\n \"tutorial\",\n \"mission\",\n \"mcc\",\n \"warning\",\n] as const;\n\nexport type AiSpeechAudioCueFamily =\n (typeof AI_SPEECH_AUDIO_CUE_FAMILIES)[number];\n\nexport const AI_SPEECH_AUDIO_FOCUS_MODES = [\n \"ambient\",\n \"focused\",\n \"combat-safe\",\n] as const;\n\nexport type AiSpeechAudioFocusMode =\n (typeof AI_SPEECH_AUDIO_FOCUS_MODES)[number];\n\nexport const AI_SPEECH_AUDIO_COMBAT_SAFE_DELIVERIES = [\n \"full\",\n \"condensed\",\n \"deferred\",\n \"suppressed\",\n] as const;\n\nexport type AiSpeechAudioCombatSafeDelivery =\n (typeof AI_SPEECH_AUDIO_COMBAT_SAFE_DELIVERIES)[number];\n\nexport interface AiSpeechAudioContractBase {\n readonly id: string;\n readonly featureFlagId: typeof AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID;\n readonly priority: AiSpeechAudioPriority;\n readonly ducking: AiSpeechAudioDuckingMode;\n readonly combatSafeDelivery: AiSpeechAudioCombatSafeDelivery;\n}\n\nexport interface AiSpeechNarratedResponseContract\n extends AiSpeechAudioContractBase {\n readonly channel: \"narrated-response\";\n readonly utteranceId: string;\n readonly locale: string;\n}\n\nexport interface AiSpeechLocalizedCueContract extends AiSpeechAudioContractBase {\n readonly channel: \"localized-cue\";\n readonly cueFamily: AiSpeechAudioCueFamily;\n readonly cueId: string;\n readonly locale: string;\n}\n\nexport interface AiSpeechRepeatingWarningContract\n extends AiSpeechAudioContractBase {\n readonly channel: \"repeating-warning\";\n readonly warningId: string;\n readonly intervalSeconds: number;\n readonly maximumOccurrencesPerMinute: number;\n}\n\nexport type AiSpeechAudioContract =\n | AiSpeechNarratedResponseContract\n | AiSpeechLocalizedCueContract\n | AiSpeechRepeatingWarningContract;\n\nexport interface AiSpeechAudioContractBaseInput {\n readonly id: string;\n readonly priority: AiSpeechAudioPriority;\n readonly ducking: AiSpeechAudioDuckingMode;\n readonly combatSafeDelivery: AiSpeechAudioCombatSafeDelivery;\n}\n\nexport interface CreateAiSpeechNarratedResponseInput\n extends AiSpeechAudioContractBaseInput {\n readonly utteranceId: string;\n readonly locale: string;\n}\n\nexport interface CreateAiSpeechLocalizedCueInput\n extends AiSpeechAudioContractBaseInput {\n readonly cueFamily: AiSpeechAudioCueFamily;\n readonly cueId: string;\n readonly locale: string;\n}\n\nexport interface CreateAiSpeechRepeatingWarningInput\n extends AiSpeechAudioContractBaseInput {\n readonly warningId: string;\n readonly intervalSeconds: number;\n readonly maximumOccurrencesPerMinute: number;\n}\n\nfunction requireAudioEnum<T extends string>(\n value: T,\n allowed: readonly T[],\n label: string\n): T {\n if (!allowed.includes(value)) {\n throw new Error(`${label} is not supported: ${value}.`);\n }\n\n return value;\n}\n\nfunction requireBoundedAudioNumber(\n value: number,\n minimum: number,\n maximum: number,\n label: string\n): number {\n if (!Number.isFinite(value) || value < minimum || value > maximum) {\n throw new Error(\n `${label} must be between ${minimum} and ${maximum}, inclusive.`\n );\n }\n\n return value;\n}\n\nfunction createAiSpeechAudioContractBase(\n input: AiSpeechAudioContractBaseInput\n): AiSpeechAudioContractBase {\n return {\n id: requireNonEmptyString(input.id, \"Audio contract id\"),\n featureFlagId: AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID,\n priority: requireAudioEnum(\n input.priority,\n AI_SPEECH_AUDIO_PRIORITIES,\n \"Audio priority\"\n ),\n ducking: requireAudioEnum(\n input.ducking,\n AI_SPEECH_AUDIO_DUCKING_MODES,\n \"Audio ducking mode\"\n ),\n combatSafeDelivery: requireAudioEnum(\n input.combatSafeDelivery,\n AI_SPEECH_AUDIO_COMBAT_SAFE_DELIVERIES,\n \"Combat-safe audio delivery\"\n ),\n };\n}\n\nexport function createAiSpeechNarratedResponse(\n input: CreateAiSpeechNarratedResponseInput\n): AiSpeechNarratedResponseContract {\n return Object.freeze({\n ...createAiSpeechAudioContractBase(input),\n channel: \"narrated-response\" as const,\n utteranceId: requireNonEmptyString(input.utteranceId, \"Utterance id\"),\n locale: requireNonEmptyString(input.locale, \"Locale\"),\n });\n}\n\nexport function createAiSpeechLocalizedCue(\n input: CreateAiSpeechLocalizedCueInput\n): AiSpeechLocalizedCueContract {\n return Object.freeze({\n ...createAiSpeechAudioContractBase(input),\n channel: \"localized-cue\" as const,\n cueFamily: requireAudioEnum(\n input.cueFamily,\n AI_SPEECH_AUDIO_CUE_FAMILIES,\n \"Audio cue family\"\n ),\n cueId: requireNonEmptyString(input.cueId, \"Cue id\"),\n locale: requireNonEmptyString(input.locale, \"Locale\"),\n });\n}\n\nexport function createAiSpeechRepeatingWarning(\n input: CreateAiSpeechRepeatingWarningInput\n): AiSpeechRepeatingWarningContract {\n return Object.freeze({\n ...createAiSpeechAudioContractBase(input),\n channel: \"repeating-warning\" as const,\n warningId: requireNonEmptyString(input.warningId, \"Warning id\"),\n intervalSeconds: requireBoundedAudioNumber(\n input.intervalSeconds,\n 1,\n 3600,\n \"Warning intervalSeconds\"\n ),\n maximumOccurrencesPerMinute: requireBoundedAudioNumber(\n input.maximumOccurrencesPerMinute,\n 1,\n 60,\n \"Warning maximumOccurrencesPerMinute\"\n ),\n });\n}\n\nexport interface ResolveAiSpeechAudioPolicyInput {\n readonly contract: AiSpeechAudioContract;\n readonly focusMode: AiSpeechAudioFocusMode;\n readonly featureFlags?: AiSpeechFeatureFlagSnapshot;\n readonly masterMuted?: boolean;\n readonly userMuted?: boolean;\n readonly activeContractIds?: readonly string[];\n}\n\nexport interface AiSpeechAudioPolicyDecision {\n readonly deliver: boolean;\n readonly mode: \"full\" | \"condensed\" | \"deferred\";\n readonly ducking: AiSpeechAudioDuckingMode;\n readonly reasonCodes: readonly string[];\n}\n\nfunction isPlayerSystemAudioEnabled(\n featureFlags: AiSpeechFeatureFlagSnapshot = {}\n): boolean {\n return featureFlags[AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID] === true;\n}\n\nexport function resolveAiSpeechAudioPolicy(\n input: ResolveAiSpeechAudioPolicyInput\n): AiSpeechAudioPolicyDecision {\n const { contract } = input;\n\n if (!isPlayerSystemAudioEnabled(input.featureFlags)) {\n return {\n deliver: false,\n mode: \"deferred\",\n ducking: \"none\",\n reasonCodes: [\"player-system-audio-rollout-disabled\"],\n };\n }\n\n if (input.masterMuted || input.userMuted) {\n return {\n deliver: false,\n mode: \"deferred\",\n ducking: \"none\",\n reasonCodes: [input.masterMuted ? \"master-muted\" : \"user-muted\"],\n };\n }\n\n if (input.activeContractIds?.includes(contract.id)) {\n return {\n deliver: false,\n mode: \"deferred\",\n ducking: \"none\",\n reasonCodes: [\"duplicate-audio-contract-suppressed\"],\n };\n }\n\n if (\n input.focusMode === \"combat-safe\" &&\n contract.priority !== \"critical\" &&\n contract.priority !== \"high\"\n ) {\n return {\n deliver: false,\n mode: \"deferred\",\n ducking: \"none\",\n reasonCodes: [\"combat-safe-priority-suppressed\"],\n };\n }\n\n if (contract.combatSafeDelivery === \"suppressed\") {\n return {\n deliver: false,\n mode: \"deferred\",\n ducking: \"none\",\n reasonCodes: [\"audio-contract-suppressed\"],\n };\n }\n\n const mode =\n input.focusMode === \"combat-safe\"\n ? contract.combatSafeDelivery === \"condensed\"\n ? \"condensed\"\n : contract.combatSafeDelivery === \"deferred\"\n ? \"deferred\"\n : \"full\"\n : \"full\";\n const ducking = contract.priority === \"critical\" ? \"none\" : contract.ducking;\n\n return {\n deliver: mode !== \"deferred\",\n mode,\n ducking,\n reasonCodes:\n contract.priority === \"critical\"\n ? [\"critical-priority-bypasses-ducking\"]\n : [],\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":[]}
package/dist/index.d.cts CHANGED
@@ -145,6 +145,82 @@ declare function createAiSpeechCacheKey(input: AiSpeechCacheKeyInput): string;
145
145
  declare function createAiSpeechNearReuseFingerprint(value: string): string;
146
146
  declare function isAiSpeechNearReuseSafeClass(utteranceClass: AiSpeechUtteranceClass): boolean;
147
147
  declare function planAiSpeechCache(input: PlanAiSpeechCacheInput): AiSpeechCachePlan;
148
+ declare const AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID: "isekai.player-system.audio.enabled";
149
+ declare const AI_SPEECH_AUDIO_CHANNELS: readonly ["narrated-response", "localized-cue", "repeating-warning"];
150
+ type AiSpeechAudioChannel = (typeof AI_SPEECH_AUDIO_CHANNELS)[number];
151
+ declare const AI_SPEECH_AUDIO_PRIORITIES: readonly ["critical", "high", "normal", "low"];
152
+ type AiSpeechAudioPriority = (typeof AI_SPEECH_AUDIO_PRIORITIES)[number];
153
+ declare const AI_SPEECH_AUDIO_DUCKING_MODES: readonly ["none", "music", "non-critical", "lower-priority"];
154
+ type AiSpeechAudioDuckingMode = (typeof AI_SPEECH_AUDIO_DUCKING_MODES)[number];
155
+ declare const AI_SPEECH_AUDIO_CUE_FAMILIES: readonly ["status", "tutorial", "mission", "mcc", "warning"];
156
+ type AiSpeechAudioCueFamily = (typeof AI_SPEECH_AUDIO_CUE_FAMILIES)[number];
157
+ declare const AI_SPEECH_AUDIO_FOCUS_MODES: readonly ["ambient", "focused", "combat-safe"];
158
+ type AiSpeechAudioFocusMode = (typeof AI_SPEECH_AUDIO_FOCUS_MODES)[number];
159
+ declare const AI_SPEECH_AUDIO_COMBAT_SAFE_DELIVERIES: readonly ["full", "condensed", "deferred", "suppressed"];
160
+ type AiSpeechAudioCombatSafeDelivery = (typeof AI_SPEECH_AUDIO_COMBAT_SAFE_DELIVERIES)[number];
161
+ interface AiSpeechAudioContractBase {
162
+ readonly id: string;
163
+ readonly featureFlagId: typeof AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID;
164
+ readonly priority: AiSpeechAudioPriority;
165
+ readonly ducking: AiSpeechAudioDuckingMode;
166
+ readonly combatSafeDelivery: AiSpeechAudioCombatSafeDelivery;
167
+ }
168
+ interface AiSpeechNarratedResponseContract extends AiSpeechAudioContractBase {
169
+ readonly channel: "narrated-response";
170
+ readonly utteranceId: string;
171
+ readonly locale: string;
172
+ }
173
+ interface AiSpeechLocalizedCueContract extends AiSpeechAudioContractBase {
174
+ readonly channel: "localized-cue";
175
+ readonly cueFamily: AiSpeechAudioCueFamily;
176
+ readonly cueId: string;
177
+ readonly locale: string;
178
+ }
179
+ interface AiSpeechRepeatingWarningContract extends AiSpeechAudioContractBase {
180
+ readonly channel: "repeating-warning";
181
+ readonly warningId: string;
182
+ readonly intervalSeconds: number;
183
+ readonly maximumOccurrencesPerMinute: number;
184
+ }
185
+ type AiSpeechAudioContract = AiSpeechNarratedResponseContract | AiSpeechLocalizedCueContract | AiSpeechRepeatingWarningContract;
186
+ interface AiSpeechAudioContractBaseInput {
187
+ readonly id: string;
188
+ readonly priority: AiSpeechAudioPriority;
189
+ readonly ducking: AiSpeechAudioDuckingMode;
190
+ readonly combatSafeDelivery: AiSpeechAudioCombatSafeDelivery;
191
+ }
192
+ interface CreateAiSpeechNarratedResponseInput extends AiSpeechAudioContractBaseInput {
193
+ readonly utteranceId: string;
194
+ readonly locale: string;
195
+ }
196
+ interface CreateAiSpeechLocalizedCueInput extends AiSpeechAudioContractBaseInput {
197
+ readonly cueFamily: AiSpeechAudioCueFamily;
198
+ readonly cueId: string;
199
+ readonly locale: string;
200
+ }
201
+ interface CreateAiSpeechRepeatingWarningInput extends AiSpeechAudioContractBaseInput {
202
+ readonly warningId: string;
203
+ readonly intervalSeconds: number;
204
+ readonly maximumOccurrencesPerMinute: number;
205
+ }
206
+ declare function createAiSpeechNarratedResponse(input: CreateAiSpeechNarratedResponseInput): AiSpeechNarratedResponseContract;
207
+ declare function createAiSpeechLocalizedCue(input: CreateAiSpeechLocalizedCueInput): AiSpeechLocalizedCueContract;
208
+ declare function createAiSpeechRepeatingWarning(input: CreateAiSpeechRepeatingWarningInput): AiSpeechRepeatingWarningContract;
209
+ interface ResolveAiSpeechAudioPolicyInput {
210
+ readonly contract: AiSpeechAudioContract;
211
+ readonly focusMode: AiSpeechAudioFocusMode;
212
+ readonly featureFlags?: AiSpeechFeatureFlagSnapshot;
213
+ readonly masterMuted?: boolean;
214
+ readonly userMuted?: boolean;
215
+ readonly activeContractIds?: readonly string[];
216
+ }
217
+ interface AiSpeechAudioPolicyDecision {
218
+ readonly deliver: boolean;
219
+ readonly mode: "full" | "condensed" | "deferred";
220
+ readonly ducking: AiSpeechAudioDuckingMode;
221
+ readonly reasonCodes: readonly string[];
222
+ }
223
+ declare function resolveAiSpeechAudioPolicy(input: ResolveAiSpeechAudioPolicyInput): AiSpeechAudioPolicyDecision;
148
224
  declare const packageDescriptor: AiPackageDescriptor;
149
225
 
150
- export { AI_SPEECH_CACHE_MODES, AI_SPEECH_CACHE_SCOPES, AI_SPEECH_DEFAULT_PLAYER_LABEL, AI_SPEECH_ENVIRONMENTS, AI_SPEECH_ENV_PREFIX, AI_SPEECH_FEATURE_FLAGS, AI_SPEECH_FEATURE_FLAG_ID, AI_SPEECH_NEAR_REUSE_SAFE_CLASSES, AI_SPEECH_PACKAGE, AI_SPEECH_PLAYER_ADDRESS_SOURCES, AI_SPEECH_PROVIDER_CACHE_PERMISSIONS, AI_SPEECH_ROLLOUTS, AI_SPEECH_ROLLOUT_EVALUATORS, AI_SPEECH_ROLLOUT_FALLBACK_MODES, AI_SPEECH_UTTERANCE_CLASSES, AI_SPEECH_VOICE_TIERS, type AiPackageDescriptor, type AiSpeechCacheKeyInput, type AiSpeechCacheMode, type AiSpeechCachePlan, type AiSpeechCacheScope, type AiSpeechCacheTelemetry, type AiSpeechEnvironment, type AiSpeechFeatureFlagKey, type AiSpeechFeatureFlagSnapshot, type AiSpeechPlayerAddressDecision, type AiSpeechPlayerAddressInput, type AiSpeechPlayerAddressSource, type AiSpeechProviderCachePermission, type AiSpeechRenderTextInput, type AiSpeechRenderTextResult, type AiSpeechRolloutControl, type AiSpeechRolloutDecision, type AiSpeechRolloutEvaluator, type AiSpeechRolloutFallbackMode, type AiSpeechUtteranceClass, type AiSpeechVoiceProfileRef, type AiSpeechVoiceTier, type AiSpeechVoiceTierDecision, type PlanAiSpeechCacheInput, type ResolveAiSpeechVoiceTierInput, createAiSpeechCacheKey, createAiSpeechNearReuseFingerprint, isAiSpeechFeatureEnabled, isAiSpeechNearReuseSafeClass, normalizeAiSpeechText, packageDescriptor, planAiSpeechCache, renderAiSpeechText, resolveAiSpeechPlayerAddress, resolveAiSpeechRolloutDecision, resolveAiSpeechVoiceTier };
226
+ export { AI_SPEECH_AUDIO_CHANNELS, AI_SPEECH_AUDIO_COMBAT_SAFE_DELIVERIES, AI_SPEECH_AUDIO_CUE_FAMILIES, AI_SPEECH_AUDIO_DUCKING_MODES, AI_SPEECH_AUDIO_FOCUS_MODES, AI_SPEECH_AUDIO_PRIORITIES, AI_SPEECH_CACHE_MODES, AI_SPEECH_CACHE_SCOPES, AI_SPEECH_DEFAULT_PLAYER_LABEL, AI_SPEECH_ENVIRONMENTS, AI_SPEECH_ENV_PREFIX, AI_SPEECH_FEATURE_FLAGS, AI_SPEECH_FEATURE_FLAG_ID, AI_SPEECH_NEAR_REUSE_SAFE_CLASSES, AI_SPEECH_PACKAGE, AI_SPEECH_PLAYER_ADDRESS_SOURCES, AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID, AI_SPEECH_PROVIDER_CACHE_PERMISSIONS, AI_SPEECH_ROLLOUTS, AI_SPEECH_ROLLOUT_EVALUATORS, AI_SPEECH_ROLLOUT_FALLBACK_MODES, AI_SPEECH_UTTERANCE_CLASSES, AI_SPEECH_VOICE_TIERS, type AiPackageDescriptor, type AiSpeechAudioChannel, type AiSpeechAudioCombatSafeDelivery, type AiSpeechAudioContract, type AiSpeechAudioContractBase, type AiSpeechAudioContractBaseInput, type AiSpeechAudioCueFamily, type AiSpeechAudioDuckingMode, type AiSpeechAudioFocusMode, type AiSpeechAudioPolicyDecision, type AiSpeechAudioPriority, type AiSpeechCacheKeyInput, type AiSpeechCacheMode, type AiSpeechCachePlan, type AiSpeechCacheScope, type AiSpeechCacheTelemetry, type AiSpeechEnvironment, type AiSpeechFeatureFlagKey, type AiSpeechFeatureFlagSnapshot, type AiSpeechLocalizedCueContract, type AiSpeechNarratedResponseContract, type AiSpeechPlayerAddressDecision, type AiSpeechPlayerAddressInput, type AiSpeechPlayerAddressSource, type AiSpeechProviderCachePermission, type AiSpeechRenderTextInput, type AiSpeechRenderTextResult, type AiSpeechRepeatingWarningContract, type AiSpeechRolloutControl, type AiSpeechRolloutDecision, type AiSpeechRolloutEvaluator, type AiSpeechRolloutFallbackMode, type AiSpeechUtteranceClass, type AiSpeechVoiceProfileRef, type AiSpeechVoiceTier, type AiSpeechVoiceTierDecision, type CreateAiSpeechLocalizedCueInput, type CreateAiSpeechNarratedResponseInput, type CreateAiSpeechRepeatingWarningInput, type PlanAiSpeechCacheInput, type ResolveAiSpeechAudioPolicyInput, type ResolveAiSpeechVoiceTierInput, createAiSpeechCacheKey, createAiSpeechLocalizedCue, createAiSpeechNarratedResponse, createAiSpeechNearReuseFingerprint, createAiSpeechRepeatingWarning, isAiSpeechFeatureEnabled, isAiSpeechNearReuseSafeClass, normalizeAiSpeechText, packageDescriptor, planAiSpeechCache, renderAiSpeechText, resolveAiSpeechAudioPolicy, resolveAiSpeechPlayerAddress, resolveAiSpeechRolloutDecision, resolveAiSpeechVoiceTier };
package/dist/index.d.ts CHANGED
@@ -145,6 +145,82 @@ declare function createAiSpeechCacheKey(input: AiSpeechCacheKeyInput): string;
145
145
  declare function createAiSpeechNearReuseFingerprint(value: string): string;
146
146
  declare function isAiSpeechNearReuseSafeClass(utteranceClass: AiSpeechUtteranceClass): boolean;
147
147
  declare function planAiSpeechCache(input: PlanAiSpeechCacheInput): AiSpeechCachePlan;
148
+ declare const AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID: "isekai.player-system.audio.enabled";
149
+ declare const AI_SPEECH_AUDIO_CHANNELS: readonly ["narrated-response", "localized-cue", "repeating-warning"];
150
+ type AiSpeechAudioChannel = (typeof AI_SPEECH_AUDIO_CHANNELS)[number];
151
+ declare const AI_SPEECH_AUDIO_PRIORITIES: readonly ["critical", "high", "normal", "low"];
152
+ type AiSpeechAudioPriority = (typeof AI_SPEECH_AUDIO_PRIORITIES)[number];
153
+ declare const AI_SPEECH_AUDIO_DUCKING_MODES: readonly ["none", "music", "non-critical", "lower-priority"];
154
+ type AiSpeechAudioDuckingMode = (typeof AI_SPEECH_AUDIO_DUCKING_MODES)[number];
155
+ declare const AI_SPEECH_AUDIO_CUE_FAMILIES: readonly ["status", "tutorial", "mission", "mcc", "warning"];
156
+ type AiSpeechAudioCueFamily = (typeof AI_SPEECH_AUDIO_CUE_FAMILIES)[number];
157
+ declare const AI_SPEECH_AUDIO_FOCUS_MODES: readonly ["ambient", "focused", "combat-safe"];
158
+ type AiSpeechAudioFocusMode = (typeof AI_SPEECH_AUDIO_FOCUS_MODES)[number];
159
+ declare const AI_SPEECH_AUDIO_COMBAT_SAFE_DELIVERIES: readonly ["full", "condensed", "deferred", "suppressed"];
160
+ type AiSpeechAudioCombatSafeDelivery = (typeof AI_SPEECH_AUDIO_COMBAT_SAFE_DELIVERIES)[number];
161
+ interface AiSpeechAudioContractBase {
162
+ readonly id: string;
163
+ readonly featureFlagId: typeof AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID;
164
+ readonly priority: AiSpeechAudioPriority;
165
+ readonly ducking: AiSpeechAudioDuckingMode;
166
+ readonly combatSafeDelivery: AiSpeechAudioCombatSafeDelivery;
167
+ }
168
+ interface AiSpeechNarratedResponseContract extends AiSpeechAudioContractBase {
169
+ readonly channel: "narrated-response";
170
+ readonly utteranceId: string;
171
+ readonly locale: string;
172
+ }
173
+ interface AiSpeechLocalizedCueContract extends AiSpeechAudioContractBase {
174
+ readonly channel: "localized-cue";
175
+ readonly cueFamily: AiSpeechAudioCueFamily;
176
+ readonly cueId: string;
177
+ readonly locale: string;
178
+ }
179
+ interface AiSpeechRepeatingWarningContract extends AiSpeechAudioContractBase {
180
+ readonly channel: "repeating-warning";
181
+ readonly warningId: string;
182
+ readonly intervalSeconds: number;
183
+ readonly maximumOccurrencesPerMinute: number;
184
+ }
185
+ type AiSpeechAudioContract = AiSpeechNarratedResponseContract | AiSpeechLocalizedCueContract | AiSpeechRepeatingWarningContract;
186
+ interface AiSpeechAudioContractBaseInput {
187
+ readonly id: string;
188
+ readonly priority: AiSpeechAudioPriority;
189
+ readonly ducking: AiSpeechAudioDuckingMode;
190
+ readonly combatSafeDelivery: AiSpeechAudioCombatSafeDelivery;
191
+ }
192
+ interface CreateAiSpeechNarratedResponseInput extends AiSpeechAudioContractBaseInput {
193
+ readonly utteranceId: string;
194
+ readonly locale: string;
195
+ }
196
+ interface CreateAiSpeechLocalizedCueInput extends AiSpeechAudioContractBaseInput {
197
+ readonly cueFamily: AiSpeechAudioCueFamily;
198
+ readonly cueId: string;
199
+ readonly locale: string;
200
+ }
201
+ interface CreateAiSpeechRepeatingWarningInput extends AiSpeechAudioContractBaseInput {
202
+ readonly warningId: string;
203
+ readonly intervalSeconds: number;
204
+ readonly maximumOccurrencesPerMinute: number;
205
+ }
206
+ declare function createAiSpeechNarratedResponse(input: CreateAiSpeechNarratedResponseInput): AiSpeechNarratedResponseContract;
207
+ declare function createAiSpeechLocalizedCue(input: CreateAiSpeechLocalizedCueInput): AiSpeechLocalizedCueContract;
208
+ declare function createAiSpeechRepeatingWarning(input: CreateAiSpeechRepeatingWarningInput): AiSpeechRepeatingWarningContract;
209
+ interface ResolveAiSpeechAudioPolicyInput {
210
+ readonly contract: AiSpeechAudioContract;
211
+ readonly focusMode: AiSpeechAudioFocusMode;
212
+ readonly featureFlags?: AiSpeechFeatureFlagSnapshot;
213
+ readonly masterMuted?: boolean;
214
+ readonly userMuted?: boolean;
215
+ readonly activeContractIds?: readonly string[];
216
+ }
217
+ interface AiSpeechAudioPolicyDecision {
218
+ readonly deliver: boolean;
219
+ readonly mode: "full" | "condensed" | "deferred";
220
+ readonly ducking: AiSpeechAudioDuckingMode;
221
+ readonly reasonCodes: readonly string[];
222
+ }
223
+ declare function resolveAiSpeechAudioPolicy(input: ResolveAiSpeechAudioPolicyInput): AiSpeechAudioPolicyDecision;
148
224
  declare const packageDescriptor: AiPackageDescriptor;
149
225
 
150
- export { AI_SPEECH_CACHE_MODES, AI_SPEECH_CACHE_SCOPES, AI_SPEECH_DEFAULT_PLAYER_LABEL, AI_SPEECH_ENVIRONMENTS, AI_SPEECH_ENV_PREFIX, AI_SPEECH_FEATURE_FLAGS, AI_SPEECH_FEATURE_FLAG_ID, AI_SPEECH_NEAR_REUSE_SAFE_CLASSES, AI_SPEECH_PACKAGE, AI_SPEECH_PLAYER_ADDRESS_SOURCES, AI_SPEECH_PROVIDER_CACHE_PERMISSIONS, AI_SPEECH_ROLLOUTS, AI_SPEECH_ROLLOUT_EVALUATORS, AI_SPEECH_ROLLOUT_FALLBACK_MODES, AI_SPEECH_UTTERANCE_CLASSES, AI_SPEECH_VOICE_TIERS, type AiPackageDescriptor, type AiSpeechCacheKeyInput, type AiSpeechCacheMode, type AiSpeechCachePlan, type AiSpeechCacheScope, type AiSpeechCacheTelemetry, type AiSpeechEnvironment, type AiSpeechFeatureFlagKey, type AiSpeechFeatureFlagSnapshot, type AiSpeechPlayerAddressDecision, type AiSpeechPlayerAddressInput, type AiSpeechPlayerAddressSource, type AiSpeechProviderCachePermission, type AiSpeechRenderTextInput, type AiSpeechRenderTextResult, type AiSpeechRolloutControl, type AiSpeechRolloutDecision, type AiSpeechRolloutEvaluator, type AiSpeechRolloutFallbackMode, type AiSpeechUtteranceClass, type AiSpeechVoiceProfileRef, type AiSpeechVoiceTier, type AiSpeechVoiceTierDecision, type PlanAiSpeechCacheInput, type ResolveAiSpeechVoiceTierInput, createAiSpeechCacheKey, createAiSpeechNearReuseFingerprint, isAiSpeechFeatureEnabled, isAiSpeechNearReuseSafeClass, normalizeAiSpeechText, packageDescriptor, planAiSpeechCache, renderAiSpeechText, resolveAiSpeechPlayerAddress, resolveAiSpeechRolloutDecision, resolveAiSpeechVoiceTier };
226
+ export { AI_SPEECH_AUDIO_CHANNELS, AI_SPEECH_AUDIO_COMBAT_SAFE_DELIVERIES, AI_SPEECH_AUDIO_CUE_FAMILIES, AI_SPEECH_AUDIO_DUCKING_MODES, AI_SPEECH_AUDIO_FOCUS_MODES, AI_SPEECH_AUDIO_PRIORITIES, AI_SPEECH_CACHE_MODES, AI_SPEECH_CACHE_SCOPES, AI_SPEECH_DEFAULT_PLAYER_LABEL, AI_SPEECH_ENVIRONMENTS, AI_SPEECH_ENV_PREFIX, AI_SPEECH_FEATURE_FLAGS, AI_SPEECH_FEATURE_FLAG_ID, AI_SPEECH_NEAR_REUSE_SAFE_CLASSES, AI_SPEECH_PACKAGE, AI_SPEECH_PLAYER_ADDRESS_SOURCES, AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID, AI_SPEECH_PROVIDER_CACHE_PERMISSIONS, AI_SPEECH_ROLLOUTS, AI_SPEECH_ROLLOUT_EVALUATORS, AI_SPEECH_ROLLOUT_FALLBACK_MODES, AI_SPEECH_UTTERANCE_CLASSES, AI_SPEECH_VOICE_TIERS, type AiPackageDescriptor, type AiSpeechAudioChannel, type AiSpeechAudioCombatSafeDelivery, type AiSpeechAudioContract, type AiSpeechAudioContractBase, type AiSpeechAudioContractBaseInput, type AiSpeechAudioCueFamily, type AiSpeechAudioDuckingMode, type AiSpeechAudioFocusMode, type AiSpeechAudioPolicyDecision, type AiSpeechAudioPriority, type AiSpeechCacheKeyInput, type AiSpeechCacheMode, type AiSpeechCachePlan, type AiSpeechCacheScope, type AiSpeechCacheTelemetry, type AiSpeechEnvironment, type AiSpeechFeatureFlagKey, type AiSpeechFeatureFlagSnapshot, type AiSpeechLocalizedCueContract, type AiSpeechNarratedResponseContract, type AiSpeechPlayerAddressDecision, type AiSpeechPlayerAddressInput, type AiSpeechPlayerAddressSource, type AiSpeechProviderCachePermission, type AiSpeechRenderTextInput, type AiSpeechRenderTextResult, type AiSpeechRepeatingWarningContract, type AiSpeechRolloutControl, type AiSpeechRolloutDecision, type AiSpeechRolloutEvaluator, type AiSpeechRolloutFallbackMode, type AiSpeechUtteranceClass, type AiSpeechVoiceProfileRef, type AiSpeechVoiceTier, type AiSpeechVoiceTierDecision, type CreateAiSpeechLocalizedCueInput, type CreateAiSpeechNarratedResponseInput, type CreateAiSpeechRepeatingWarningInput, type PlanAiSpeechCacheInput, type ResolveAiSpeechAudioPolicyInput, type ResolveAiSpeechVoiceTierInput, createAiSpeechCacheKey, createAiSpeechLocalizedCue, createAiSpeechNarratedResponse, createAiSpeechNearReuseFingerprint, createAiSpeechRepeatingWarning, isAiSpeechFeatureEnabled, isAiSpeechNearReuseSafeClass, normalizeAiSpeechText, packageDescriptor, planAiSpeechCache, renderAiSpeechText, resolveAiSpeechAudioPolicy, resolveAiSpeechPlayerAddress, resolveAiSpeechRolloutDecision, resolveAiSpeechVoiceTier };
package/dist/index.js CHANGED
@@ -327,6 +327,171 @@ function planAiSpeechCache(input) {
327
327
  reasonCodes: ["near-reuse-eligible"]
328
328
  };
329
329
  }
330
+ var AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID = "isekai.player-system.audio.enabled";
331
+ var AI_SPEECH_AUDIO_CHANNELS = [
332
+ "narrated-response",
333
+ "localized-cue",
334
+ "repeating-warning"
335
+ ];
336
+ var AI_SPEECH_AUDIO_PRIORITIES = [
337
+ "critical",
338
+ "high",
339
+ "normal",
340
+ "low"
341
+ ];
342
+ var AI_SPEECH_AUDIO_DUCKING_MODES = [
343
+ "none",
344
+ "music",
345
+ "non-critical",
346
+ "lower-priority"
347
+ ];
348
+ var AI_SPEECH_AUDIO_CUE_FAMILIES = [
349
+ "status",
350
+ "tutorial",
351
+ "mission",
352
+ "mcc",
353
+ "warning"
354
+ ];
355
+ var AI_SPEECH_AUDIO_FOCUS_MODES = [
356
+ "ambient",
357
+ "focused",
358
+ "combat-safe"
359
+ ];
360
+ var AI_SPEECH_AUDIO_COMBAT_SAFE_DELIVERIES = [
361
+ "full",
362
+ "condensed",
363
+ "deferred",
364
+ "suppressed"
365
+ ];
366
+ function requireAudioEnum(value, allowed, label) {
367
+ if (!allowed.includes(value)) {
368
+ throw new Error(`${label} is not supported: ${value}.`);
369
+ }
370
+ return value;
371
+ }
372
+ function requireBoundedAudioNumber(value, minimum, maximum, label) {
373
+ if (!Number.isFinite(value) || value < minimum || value > maximum) {
374
+ throw new Error(
375
+ `${label} must be between ${minimum} and ${maximum}, inclusive.`
376
+ );
377
+ }
378
+ return value;
379
+ }
380
+ function createAiSpeechAudioContractBase(input) {
381
+ return {
382
+ id: requireNonEmptyString(input.id, "Audio contract id"),
383
+ featureFlagId: AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID,
384
+ priority: requireAudioEnum(
385
+ input.priority,
386
+ AI_SPEECH_AUDIO_PRIORITIES,
387
+ "Audio priority"
388
+ ),
389
+ ducking: requireAudioEnum(
390
+ input.ducking,
391
+ AI_SPEECH_AUDIO_DUCKING_MODES,
392
+ "Audio ducking mode"
393
+ ),
394
+ combatSafeDelivery: requireAudioEnum(
395
+ input.combatSafeDelivery,
396
+ AI_SPEECH_AUDIO_COMBAT_SAFE_DELIVERIES,
397
+ "Combat-safe audio delivery"
398
+ )
399
+ };
400
+ }
401
+ function createAiSpeechNarratedResponse(input) {
402
+ return Object.freeze({
403
+ ...createAiSpeechAudioContractBase(input),
404
+ channel: "narrated-response",
405
+ utteranceId: requireNonEmptyString(input.utteranceId, "Utterance id"),
406
+ locale: requireNonEmptyString(input.locale, "Locale")
407
+ });
408
+ }
409
+ function createAiSpeechLocalizedCue(input) {
410
+ return Object.freeze({
411
+ ...createAiSpeechAudioContractBase(input),
412
+ channel: "localized-cue",
413
+ cueFamily: requireAudioEnum(
414
+ input.cueFamily,
415
+ AI_SPEECH_AUDIO_CUE_FAMILIES,
416
+ "Audio cue family"
417
+ ),
418
+ cueId: requireNonEmptyString(input.cueId, "Cue id"),
419
+ locale: requireNonEmptyString(input.locale, "Locale")
420
+ });
421
+ }
422
+ function createAiSpeechRepeatingWarning(input) {
423
+ return Object.freeze({
424
+ ...createAiSpeechAudioContractBase(input),
425
+ channel: "repeating-warning",
426
+ warningId: requireNonEmptyString(input.warningId, "Warning id"),
427
+ intervalSeconds: requireBoundedAudioNumber(
428
+ input.intervalSeconds,
429
+ 1,
430
+ 3600,
431
+ "Warning intervalSeconds"
432
+ ),
433
+ maximumOccurrencesPerMinute: requireBoundedAudioNumber(
434
+ input.maximumOccurrencesPerMinute,
435
+ 1,
436
+ 60,
437
+ "Warning maximumOccurrencesPerMinute"
438
+ )
439
+ });
440
+ }
441
+ function isPlayerSystemAudioEnabled(featureFlags = {}) {
442
+ return featureFlags[AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID] === true;
443
+ }
444
+ function resolveAiSpeechAudioPolicy(input) {
445
+ const { contract } = input;
446
+ if (!isPlayerSystemAudioEnabled(input.featureFlags)) {
447
+ return {
448
+ deliver: false,
449
+ mode: "deferred",
450
+ ducking: "none",
451
+ reasonCodes: ["player-system-audio-rollout-disabled"]
452
+ };
453
+ }
454
+ if (input.masterMuted || input.userMuted) {
455
+ return {
456
+ deliver: false,
457
+ mode: "deferred",
458
+ ducking: "none",
459
+ reasonCodes: [input.masterMuted ? "master-muted" : "user-muted"]
460
+ };
461
+ }
462
+ if (input.activeContractIds?.includes(contract.id)) {
463
+ return {
464
+ deliver: false,
465
+ mode: "deferred",
466
+ ducking: "none",
467
+ reasonCodes: ["duplicate-audio-contract-suppressed"]
468
+ };
469
+ }
470
+ if (input.focusMode === "combat-safe" && contract.priority !== "critical" && contract.priority !== "high") {
471
+ return {
472
+ deliver: false,
473
+ mode: "deferred",
474
+ ducking: "none",
475
+ reasonCodes: ["combat-safe-priority-suppressed"]
476
+ };
477
+ }
478
+ if (contract.combatSafeDelivery === "suppressed") {
479
+ return {
480
+ deliver: false,
481
+ mode: "deferred",
482
+ ducking: "none",
483
+ reasonCodes: ["audio-contract-suppressed"]
484
+ };
485
+ }
486
+ const mode = input.focusMode === "combat-safe" ? contract.combatSafeDelivery === "condensed" ? "condensed" : contract.combatSafeDelivery === "deferred" ? "deferred" : "full" : "full";
487
+ const ducking = contract.priority === "critical" ? "none" : contract.ducking;
488
+ return {
489
+ deliver: mode !== "deferred",
490
+ mode,
491
+ ducking,
492
+ reasonCodes: contract.priority === "critical" ? ["critical-priority-bypasses-ducking"] : []
493
+ };
494
+ }
330
495
  var packageDescriptor = Object.freeze({
331
496
  packageName: AI_SPEECH_PACKAGE,
332
497
  featureFlagId: AI_SPEECH_FEATURE_FLAG_ID,
@@ -334,6 +499,12 @@ var packageDescriptor = Object.freeze({
334
499
  summary: "Speech orchestration, TTS cache contracts, STT/TTS routing, and voice tier policy for Plasius AI."
335
500
  });
336
501
  export {
502
+ AI_SPEECH_AUDIO_CHANNELS,
503
+ AI_SPEECH_AUDIO_COMBAT_SAFE_DELIVERIES,
504
+ AI_SPEECH_AUDIO_CUE_FAMILIES,
505
+ AI_SPEECH_AUDIO_DUCKING_MODES,
506
+ AI_SPEECH_AUDIO_FOCUS_MODES,
507
+ AI_SPEECH_AUDIO_PRIORITIES,
337
508
  AI_SPEECH_CACHE_MODES,
338
509
  AI_SPEECH_CACHE_SCOPES,
339
510
  AI_SPEECH_DEFAULT_PLAYER_LABEL,
@@ -344,6 +515,7 @@ export {
344
515
  AI_SPEECH_NEAR_REUSE_SAFE_CLASSES,
345
516
  AI_SPEECH_PACKAGE,
346
517
  AI_SPEECH_PLAYER_ADDRESS_SOURCES,
518
+ AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID,
347
519
  AI_SPEECH_PROVIDER_CACHE_PERMISSIONS,
348
520
  AI_SPEECH_ROLLOUTS,
349
521
  AI_SPEECH_ROLLOUT_EVALUATORS,
@@ -351,13 +523,17 @@ export {
351
523
  AI_SPEECH_UTTERANCE_CLASSES,
352
524
  AI_SPEECH_VOICE_TIERS,
353
525
  createAiSpeechCacheKey,
526
+ createAiSpeechLocalizedCue,
527
+ createAiSpeechNarratedResponse,
354
528
  createAiSpeechNearReuseFingerprint,
529
+ createAiSpeechRepeatingWarning,
355
530
  isAiSpeechFeatureEnabled,
356
531
  isAiSpeechNearReuseSafeClass,
357
532
  normalizeAiSpeechText,
358
533
  packageDescriptor,
359
534
  planAiSpeechCache,
360
535
  renderAiSpeechText,
536
+ resolveAiSpeechAudioPolicy,
361
537
  resolveAiSpeechPlayerAddress,
362
538
  resolveAiSpeechRolloutDecision,
363
539
  resolveAiSpeechVoiceTier
package/dist/index.js.map CHANGED
@@ -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\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":[]}
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 AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID =\n \"isekai.player-system.audio.enabled\" as const;\n\nexport const AI_SPEECH_AUDIO_CHANNELS = [\n \"narrated-response\",\n \"localized-cue\",\n \"repeating-warning\",\n] as const;\n\nexport type AiSpeechAudioChannel = (typeof AI_SPEECH_AUDIO_CHANNELS)[number];\n\nexport const AI_SPEECH_AUDIO_PRIORITIES = [\n \"critical\",\n \"high\",\n \"normal\",\n \"low\",\n] as const;\n\nexport type AiSpeechAudioPriority =\n (typeof AI_SPEECH_AUDIO_PRIORITIES)[number];\n\nexport const AI_SPEECH_AUDIO_DUCKING_MODES = [\n \"none\",\n \"music\",\n \"non-critical\",\n \"lower-priority\",\n] as const;\n\nexport type AiSpeechAudioDuckingMode =\n (typeof AI_SPEECH_AUDIO_DUCKING_MODES)[number];\n\nexport const AI_SPEECH_AUDIO_CUE_FAMILIES = [\n \"status\",\n \"tutorial\",\n \"mission\",\n \"mcc\",\n \"warning\",\n] as const;\n\nexport type AiSpeechAudioCueFamily =\n (typeof AI_SPEECH_AUDIO_CUE_FAMILIES)[number];\n\nexport const AI_SPEECH_AUDIO_FOCUS_MODES = [\n \"ambient\",\n \"focused\",\n \"combat-safe\",\n] as const;\n\nexport type AiSpeechAudioFocusMode =\n (typeof AI_SPEECH_AUDIO_FOCUS_MODES)[number];\n\nexport const AI_SPEECH_AUDIO_COMBAT_SAFE_DELIVERIES = [\n \"full\",\n \"condensed\",\n \"deferred\",\n \"suppressed\",\n] as const;\n\nexport type AiSpeechAudioCombatSafeDelivery =\n (typeof AI_SPEECH_AUDIO_COMBAT_SAFE_DELIVERIES)[number];\n\nexport interface AiSpeechAudioContractBase {\n readonly id: string;\n readonly featureFlagId: typeof AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID;\n readonly priority: AiSpeechAudioPriority;\n readonly ducking: AiSpeechAudioDuckingMode;\n readonly combatSafeDelivery: AiSpeechAudioCombatSafeDelivery;\n}\n\nexport interface AiSpeechNarratedResponseContract\n extends AiSpeechAudioContractBase {\n readonly channel: \"narrated-response\";\n readonly utteranceId: string;\n readonly locale: string;\n}\n\nexport interface AiSpeechLocalizedCueContract extends AiSpeechAudioContractBase {\n readonly channel: \"localized-cue\";\n readonly cueFamily: AiSpeechAudioCueFamily;\n readonly cueId: string;\n readonly locale: string;\n}\n\nexport interface AiSpeechRepeatingWarningContract\n extends AiSpeechAudioContractBase {\n readonly channel: \"repeating-warning\";\n readonly warningId: string;\n readonly intervalSeconds: number;\n readonly maximumOccurrencesPerMinute: number;\n}\n\nexport type AiSpeechAudioContract =\n | AiSpeechNarratedResponseContract\n | AiSpeechLocalizedCueContract\n | AiSpeechRepeatingWarningContract;\n\nexport interface AiSpeechAudioContractBaseInput {\n readonly id: string;\n readonly priority: AiSpeechAudioPriority;\n readonly ducking: AiSpeechAudioDuckingMode;\n readonly combatSafeDelivery: AiSpeechAudioCombatSafeDelivery;\n}\n\nexport interface CreateAiSpeechNarratedResponseInput\n extends AiSpeechAudioContractBaseInput {\n readonly utteranceId: string;\n readonly locale: string;\n}\n\nexport interface CreateAiSpeechLocalizedCueInput\n extends AiSpeechAudioContractBaseInput {\n readonly cueFamily: AiSpeechAudioCueFamily;\n readonly cueId: string;\n readonly locale: string;\n}\n\nexport interface CreateAiSpeechRepeatingWarningInput\n extends AiSpeechAudioContractBaseInput {\n readonly warningId: string;\n readonly intervalSeconds: number;\n readonly maximumOccurrencesPerMinute: number;\n}\n\nfunction requireAudioEnum<T extends string>(\n value: T,\n allowed: readonly T[],\n label: string\n): T {\n if (!allowed.includes(value)) {\n throw new Error(`${label} is not supported: ${value}.`);\n }\n\n return value;\n}\n\nfunction requireBoundedAudioNumber(\n value: number,\n minimum: number,\n maximum: number,\n label: string\n): number {\n if (!Number.isFinite(value) || value < minimum || value > maximum) {\n throw new Error(\n `${label} must be between ${minimum} and ${maximum}, inclusive.`\n );\n }\n\n return value;\n}\n\nfunction createAiSpeechAudioContractBase(\n input: AiSpeechAudioContractBaseInput\n): AiSpeechAudioContractBase {\n return {\n id: requireNonEmptyString(input.id, \"Audio contract id\"),\n featureFlagId: AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID,\n priority: requireAudioEnum(\n input.priority,\n AI_SPEECH_AUDIO_PRIORITIES,\n \"Audio priority\"\n ),\n ducking: requireAudioEnum(\n input.ducking,\n AI_SPEECH_AUDIO_DUCKING_MODES,\n \"Audio ducking mode\"\n ),\n combatSafeDelivery: requireAudioEnum(\n input.combatSafeDelivery,\n AI_SPEECH_AUDIO_COMBAT_SAFE_DELIVERIES,\n \"Combat-safe audio delivery\"\n ),\n };\n}\n\nexport function createAiSpeechNarratedResponse(\n input: CreateAiSpeechNarratedResponseInput\n): AiSpeechNarratedResponseContract {\n return Object.freeze({\n ...createAiSpeechAudioContractBase(input),\n channel: \"narrated-response\" as const,\n utteranceId: requireNonEmptyString(input.utteranceId, \"Utterance id\"),\n locale: requireNonEmptyString(input.locale, \"Locale\"),\n });\n}\n\nexport function createAiSpeechLocalizedCue(\n input: CreateAiSpeechLocalizedCueInput\n): AiSpeechLocalizedCueContract {\n return Object.freeze({\n ...createAiSpeechAudioContractBase(input),\n channel: \"localized-cue\" as const,\n cueFamily: requireAudioEnum(\n input.cueFamily,\n AI_SPEECH_AUDIO_CUE_FAMILIES,\n \"Audio cue family\"\n ),\n cueId: requireNonEmptyString(input.cueId, \"Cue id\"),\n locale: requireNonEmptyString(input.locale, \"Locale\"),\n });\n}\n\nexport function createAiSpeechRepeatingWarning(\n input: CreateAiSpeechRepeatingWarningInput\n): AiSpeechRepeatingWarningContract {\n return Object.freeze({\n ...createAiSpeechAudioContractBase(input),\n channel: \"repeating-warning\" as const,\n warningId: requireNonEmptyString(input.warningId, \"Warning id\"),\n intervalSeconds: requireBoundedAudioNumber(\n input.intervalSeconds,\n 1,\n 3600,\n \"Warning intervalSeconds\"\n ),\n maximumOccurrencesPerMinute: requireBoundedAudioNumber(\n input.maximumOccurrencesPerMinute,\n 1,\n 60,\n \"Warning maximumOccurrencesPerMinute\"\n ),\n });\n}\n\nexport interface ResolveAiSpeechAudioPolicyInput {\n readonly contract: AiSpeechAudioContract;\n readonly focusMode: AiSpeechAudioFocusMode;\n readonly featureFlags?: AiSpeechFeatureFlagSnapshot;\n readonly masterMuted?: boolean;\n readonly userMuted?: boolean;\n readonly activeContractIds?: readonly string[];\n}\n\nexport interface AiSpeechAudioPolicyDecision {\n readonly deliver: boolean;\n readonly mode: \"full\" | \"condensed\" | \"deferred\";\n readonly ducking: AiSpeechAudioDuckingMode;\n readonly reasonCodes: readonly string[];\n}\n\nfunction isPlayerSystemAudioEnabled(\n featureFlags: AiSpeechFeatureFlagSnapshot = {}\n): boolean {\n return featureFlags[AI_SPEECH_PLAYER_SYSTEM_AUDIO_FLAG_ID] === true;\n}\n\nexport function resolveAiSpeechAudioPolicy(\n input: ResolveAiSpeechAudioPolicyInput\n): AiSpeechAudioPolicyDecision {\n const { contract } = input;\n\n if (!isPlayerSystemAudioEnabled(input.featureFlags)) {\n return {\n deliver: false,\n mode: \"deferred\",\n ducking: \"none\",\n reasonCodes: [\"player-system-audio-rollout-disabled\"],\n };\n }\n\n if (input.masterMuted || input.userMuted) {\n return {\n deliver: false,\n mode: \"deferred\",\n ducking: \"none\",\n reasonCodes: [input.masterMuted ? \"master-muted\" : \"user-muted\"],\n };\n }\n\n if (input.activeContractIds?.includes(contract.id)) {\n return {\n deliver: false,\n mode: \"deferred\",\n ducking: \"none\",\n reasonCodes: [\"duplicate-audio-contract-suppressed\"],\n };\n }\n\n if (\n input.focusMode === \"combat-safe\" &&\n contract.priority !== \"critical\" &&\n contract.priority !== \"high\"\n ) {\n return {\n deliver: false,\n mode: \"deferred\",\n ducking: \"none\",\n reasonCodes: [\"combat-safe-priority-suppressed\"],\n };\n }\n\n if (contract.combatSafeDelivery === \"suppressed\") {\n return {\n deliver: false,\n mode: \"deferred\",\n ducking: \"none\",\n reasonCodes: [\"audio-contract-suppressed\"],\n };\n }\n\n const mode =\n input.focusMode === \"combat-safe\"\n ? contract.combatSafeDelivery === \"condensed\"\n ? \"condensed\"\n : contract.combatSafeDelivery === \"deferred\"\n ? \"deferred\"\n : \"full\"\n : \"full\";\n const ducking = contract.priority === \"critical\" ? \"none\" : contract.ducking;\n\n return {\n deliver: mode !== \"deferred\",\n mode,\n ducking,\n reasonCodes:\n contract.priority === \"critical\"\n ? [\"critical-priority-bypasses-ducking\"]\n : [],\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":[]}
@@ -0,0 +1,52 @@
1
+ # ADR-0003: Player System Audio Contract Boundary
2
+
3
+ - Date: 2026-07-12
4
+ - Status: Accepted
5
+
6
+ ## Context
7
+
8
+ The Player System needs stable speech-domain contracts for narrated responses,
9
+ localized one-shot cues, and repeating warnings. If each host defines these
10
+ channels and combat behavior independently, priority, ducking, suppression, and
11
+ fallback behavior will drift between the site and package consumers.
12
+
13
+ The package must remain provider- and runtime-independent. Raw narration text,
14
+ provider credentials, audio assets, playback engines, and feature-flag fetching
15
+ must remain outside the public contract package.
16
+
17
+ ## Decision
18
+
19
+ `@plasius/ai-speech` owns immutable metadata contracts for the three audio
20
+ channels and deterministic policy evaluation over caller-supplied state. The
21
+ contracts use opaque IDs and locales, reuse the Player System priority
22
+ vocabulary, require explicit ducking and combat-safe delivery behavior, and
23
+ bound repeating-warning intervals and frequencies.
24
+
25
+ The policy is fail-closed when `isekai.player-system.audio.enabled` is absent or
26
+ false, when audio is muted, when a dispatch is duplicated, or when combat-safe
27
+ mode suppresses its priority. Critical audio bypasses ducking; normal and low
28
+ priority audio are suppressed during combat-safe mode; narration may be
29
+ condensed or deferred according to its contract.
30
+
31
+ Hosts own feature-flag evaluation, translations, asset resolution, playback,
32
+ captions, and non-audio accessibility fallbacks.
33
+
34
+ ## Alternatives considered
35
+
36
+ - Keep the audio channel model in each host: rejected because consumers would
37
+ reinterpret priority and combat-safe rules independently.
38
+ - Store raw narration text in the package contract: rejected because it would
39
+ increase privacy and telemetry exposure without helping playback adapters.
40
+ - Depend on a playback engine or provider SDK: rejected because native, server,
41
+ browser, and test consumers need the same portable contract surface.
42
+
43
+ ## Impact and validation
44
+
45
+ - Public consumers can construct and evaluate deterministic, bounded dispatch
46
+ metadata without runtime dependencies.
47
+ - The package remains compatible with existing TTS cache and voice-tier APIs.
48
+ - Unit tests cover channel separation, validation bounds, rollout fail-closed
49
+ behavior, combat-safe reduction, critical ducking bypass, duplicate
50
+ suppression, and mute handling.
51
+ - Release remains the normal package CI and approved `cd.yml` path; no provider
52
+ or runtime deployment is introduced by this decision.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plasius/ai-speech",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Speech orchestration, TTS cache contracts, STT/TTS routing, and voice tier policy for Plasius AI.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -64,16 +64,16 @@
64
64
  "homepage": "https://github.com/Plasius-LTD/ai-speech#readme",
65
65
  "devDependencies": {
66
66
  "@eslint/js": "^10.0.1",
67
- "@types/node": "^25.7.0",
68
- "@typescript-eslint/eslint-plugin": "^8.59.3",
69
- "@typescript-eslint/parser": "^8.59.3",
70
- "@vitest/coverage-v8": "^4.1.6",
71
- "eslint": "^10.3.0",
72
- "globals": "^17.6.0",
67
+ "@types/node": "^26.0.1",
68
+ "@typescript-eslint/eslint-plugin": "^8.62.0",
69
+ "@typescript-eslint/parser": "^8.62.0",
70
+ "@vitest/coverage-v8": "^4.1.9",
71
+ "eslint": "^10.6.0",
72
+ "globals": "^17.7.0",
73
73
  "rimraf": "^6.1.3",
74
74
  "tsup": "^8.5.1",
75
75
  "typescript": "^6.0.3",
76
- "vitest": "^4.1.6"
76
+ "vitest": "^4.1.9"
77
77
  },
78
78
  "publishConfig": {
79
79
  "access": "public"