@pie-players/pie-assessment-toolkit 0.3.9 → 0.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +5 -0
  2. package/dist/components/PieAssessmentToolkit.custom-element.js +142 -85
  3. package/dist/context/assessment-toolkit-context.d.ts +8 -8
  4. package/dist/context/assessment-toolkit-context.d.ts.map +1 -1
  5. package/dist/index.d.ts +4 -4
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +2 -2
  8. package/dist/services/HighlightCoordinator.d.ts +2 -2
  9. package/dist/services/HighlightCoordinator.d.ts.map +1 -1
  10. package/dist/services/HighlightCoordinator.js +1 -1
  11. package/dist/services/HighlightCoordinator.js.map +1 -1
  12. package/dist/services/I18nService.d.ts +3 -3
  13. package/dist/services/I18nService.d.ts.map +1 -1
  14. package/dist/services/PNPToolResolver.d.ts +7 -7
  15. package/dist/services/PNPToolResolver.js +6 -6
  16. package/dist/services/TTSService.d.ts +21 -9
  17. package/dist/services/TTSService.d.ts.map +1 -1
  18. package/dist/services/TTSService.js +172 -173
  19. package/dist/services/TTSService.js.map +1 -1
  20. package/dist/services/ThemeProvider.d.ts +2 -2
  21. package/dist/services/ThemeProvider.d.ts.map +1 -1
  22. package/dist/services/ToolCoordinator.d.ts +2 -2
  23. package/dist/services/ToolCoordinator.d.ts.map +1 -1
  24. package/dist/services/ToolRegistry.d.ts +7 -7
  25. package/dist/services/ToolRegistry.d.ts.map +1 -1
  26. package/dist/services/ToolkitCoordinator.d.ts +19 -3
  27. package/dist/services/ToolkitCoordinator.d.ts.map +1 -1
  28. package/dist/services/ToolkitCoordinator.js +228 -214
  29. package/dist/services/ToolkitCoordinator.js.map +1 -1
  30. package/dist/services/interfaces.d.ts +20 -20
  31. package/dist/services/interfaces.d.ts.map +1 -1
  32. package/dist/services/interfaces.js +1 -1
  33. package/dist/services/interfaces.js.map +1 -1
  34. package/dist/services/pnp-provenance.d.ts +7 -7
  35. package/dist/services/pnp-provenance.js +1 -1
  36. package/dist/services/tool-providers/DesmosToolProvider.d.ts +3 -3
  37. package/dist/services/tool-providers/DesmosToolProvider.d.ts.map +1 -1
  38. package/dist/services/tool-providers/DesmosToolProvider.js +1 -1
  39. package/dist/services/tool-providers/TTSToolProvider.d.ts +3 -3
  40. package/dist/services/tool-providers/TTSToolProvider.d.ts.map +1 -1
  41. package/dist/services/tool-providers/TTSToolProvider.js +1 -1
  42. package/dist/services/tool-providers/ToolProviderApi.d.ts +26 -0
  43. package/dist/services/tool-providers/ToolProviderApi.d.ts.map +1 -0
  44. package/dist/services/tool-providers/ToolProviderApi.js +8 -0
  45. package/dist/services/tool-providers/ToolProviderApi.js.map +1 -0
  46. package/dist/services/tool-providers/ToolProviderRegistry.d.ts +3 -3
  47. package/dist/services/tool-providers/ToolProviderRegistry.d.ts.map +1 -1
  48. package/dist/services/tool-providers/ToolProviderRegistry.js.map +1 -1
  49. package/dist/services/tool-providers/index.d.ts +1 -1
  50. package/dist/services/tool-providers/index.d.ts.map +1 -1
  51. package/dist/services/tts/browser-provider.d.ts.map +1 -1
  52. package/dist/services/tts/browser-provider.js +6 -37
  53. package/dist/services/tts/browser-provider.js.map +1 -1
  54. package/dist/services/tts/text-segmentation.d.ts +10 -0
  55. package/dist/services/tts/text-segmentation.d.ts.map +1 -0
  56. package/dist/services/tts/text-segmentation.js +38 -0
  57. package/dist/services/tts/text-segmentation.js.map +1 -0
  58. package/dist/services/tts-runtime-config.d.ts +31 -0
  59. package/dist/services/tts-runtime-config.d.ts.map +1 -0
  60. package/dist/services/tts-runtime-config.js +54 -0
  61. package/dist/services/tts-runtime-config.js.map +1 -0
  62. package/dist/tools/registrations/tts.d.ts.map +1 -1
  63. package/dist/tools/registrations/tts.js +6 -57
  64. package/dist/tools/registrations/tts.js.map +1 -1
  65. package/package.json +7 -7
  66. package/dist/services/tool-providers/IToolProvider.d.ts +0 -136
  67. package/dist/services/tool-providers/IToolProvider.d.ts.map +0 -1
  68. package/dist/services/tool-providers/IToolProvider.js +0 -12
  69. package/dist/services/tool-providers/IToolProvider.js.map +0 -1
@@ -21,6 +21,7 @@ import { HighlightCoordinator } from "./HighlightCoordinator.js";
21
21
  import { ToolCoordinator } from "./ToolCoordinator.js";
22
22
  import { TTSService } from "./TTSService.js";
23
23
  import { BrowserTTSProvider } from "./tts/browser-provider.js";
24
+ import { buildRuntimeTTSConfig, resolveTTSBackend, } from "./tts-runtime-config.js";
24
25
  import { ToolProviderRegistry } from "./tool-providers/index.js";
25
26
  import { createPackagedToolRegistry } from "./createDefaultToolRegistry.js";
26
27
  const SECTION_ITEM_EVENT_TYPES = [
@@ -376,9 +377,38 @@ export class ToolkitCoordinator {
376
377
  return this.sectionControllers.get(this.getSectionControllerMapKey(key));
377
378
  }
378
379
  subscribeSectionEvents(args) {
379
- let controllerEntry;
380
+ const controllerEntry = this.resolveSectionSubscriptionEntry(args);
381
+ if (!controllerEntry)
382
+ return () => { };
383
+ const { mapKey, controller } = controllerEntry;
384
+ const subscribe = controller.subscribe;
385
+ if (!subscribe) {
386
+ const resolvedKey = this.sectionControllerKeys.get(mapKey);
387
+ const sectionLabel = resolvedKey?.sectionId || args.sectionId || "<unknown>";
388
+ const attemptLabel = resolvedKey?.attemptId || args.attemptId || "<default>";
389
+ throw new Error(`[ToolkitCoordinator] subscribeSectionEvents could not subscribe: resolved controller for section "${sectionLabel}" attempt "${attemptLabel}" does not expose subscribe().`);
390
+ }
391
+ const listenerId = this.getOrCreateSectionEventListenerId(args.listener);
392
+ const subscriptionKey = `${mapKey}::${listenerId}`;
393
+ const previousSubscription = this.sectionEventSubscriptions.get(subscriptionKey);
394
+ previousSubscription?.();
395
+ const shouldDeliverEvent = this.buildSectionEventPredicate(args);
396
+ const unsubscribeController = subscribe.call(controller, (event) => {
397
+ if (!shouldDeliverEvent(event))
398
+ return;
399
+ args.listener(event);
400
+ });
401
+ const replayEvent = this.buildLoadingCompleteReplayEvent(controller);
402
+ if (replayEvent && shouldDeliverEvent(replayEvent)) {
403
+ args.listener(replayEvent);
404
+ }
405
+ const detach = this.createSectionEventDetachHandler(subscriptionKey, unsubscribeController);
406
+ this.sectionEventSubscriptions.set(subscriptionKey, detach);
407
+ return detach;
408
+ }
409
+ resolveSectionSubscriptionEntry(args) {
380
410
  try {
381
- controllerEntry = this.resolveSectionControllerForSubscription({
411
+ return this.resolveSectionControllerForSubscription({
382
412
  sectionId: args.sectionId,
383
413
  attemptId: args.attemptId,
384
414
  });
@@ -390,55 +420,30 @@ export class ToolkitCoordinator {
390
420
  message.includes("subscribeSectionEvents is ambiguous for section");
391
421
  if (isAmbiguousSectionWithoutAttempt) {
392
422
  console.warn(message);
393
- return () => { };
423
+ return null;
394
424
  }
395
425
  throw error;
396
426
  }
397
- const controller = controllerEntry.controller;
398
- const subscribe = controller.subscribe;
399
- if (!subscribe) {
400
- const resolvedKey = this.sectionControllerKeys.get(controllerEntry.mapKey);
401
- const sectionLabel = resolvedKey?.sectionId || args.sectionId || "<unknown>";
402
- const attemptLabel = resolvedKey?.attemptId || args.attemptId || "<default>";
403
- throw new Error(`[ToolkitCoordinator] subscribeSectionEvents could not subscribe: resolved controller for section "${sectionLabel}" attempt "${attemptLabel}" does not expose subscribe().`);
404
- }
405
- const { mapKey } = controllerEntry;
406
- let listenerId = this.sectionEventListenerIds.get(args.listener);
427
+ }
428
+ getOrCreateSectionEventListenerId(listener) {
429
+ let listenerId = this.sectionEventListenerIds.get(listener);
407
430
  if (!listenerId) {
408
431
  listenerId = this.nextSectionEventListenerId++;
409
- this.sectionEventListenerIds.set(args.listener, listenerId);
432
+ this.sectionEventListenerIds.set(listener, listenerId);
410
433
  }
411
- const subscriptionKey = `${mapKey}::${listenerId}`;
412
- const previousSubscription = this.sectionEventSubscriptions.get(subscriptionKey);
413
- previousSubscription?.();
414
- const eventTypeFilter = args.eventTypes
415
- ? new Set(args.eventTypes)
416
- : null;
434
+ return listenerId;
435
+ }
436
+ buildSectionEventPredicate(args) {
437
+ const eventTypeFilter = args.eventTypes ? new Set(args.eventTypes) : null;
417
438
  const itemIdFilter = args.itemIds ? new Set(args.itemIds) : null;
418
- const shouldDeliverEvent = (event) => {
439
+ return (event) => {
419
440
  if (eventTypeFilter || itemIdFilter) {
420
441
  const eventType = event?.type || null;
421
442
  if (eventTypeFilter && (!eventType || !eventTypeFilter.has(eventType))) {
422
443
  return false;
423
444
  }
424
445
  if (itemIdFilter) {
425
- const eventItemCandidates = new Set();
426
- if ("itemId" in event && typeof event.itemId === "string") {
427
- eventItemCandidates.add(event.itemId);
428
- }
429
- if ("canonicalItemId" in event &&
430
- typeof event.canonicalItemId === "string") {
431
- eventItemCandidates.add(event.canonicalItemId);
432
- }
433
- if ("currentItemId" in event &&
434
- typeof event.currentItemId === "string") {
435
- eventItemCandidates.add(event.currentItemId);
436
- }
437
- if ("previousItemId" in event &&
438
- typeof event.previousItemId === "string") {
439
- eventItemCandidates.add(event.previousItemId);
440
- }
441
- const hasMatchingItem = Array.from(eventItemCandidates).some((itemId) => itemIdFilter.has(itemId));
446
+ const hasMatchingItem = Array.from(this.collectEventItemIds(event)).some((itemId) => itemIdFilter.has(itemId));
442
447
  if (!hasMatchingItem) {
443
448
  return false;
444
449
  }
@@ -446,26 +451,44 @@ export class ToolkitCoordinator {
446
451
  }
447
452
  return true;
448
453
  };
449
- const unsubscribeController = subscribe.call(controller, (event) => {
450
- if (!shouldDeliverEvent(event))
451
- return;
452
- args.listener(event);
453
- });
454
- const runtimeState = controller.getRuntimeState?.();
455
- if (runtimeState?.loadingComplete &&
456
- runtimeState.totalRegistered > 0 &&
457
- runtimeState.totalLoaded >= runtimeState.totalRegistered) {
458
- const loadingCompleteEvent = {
459
- type: "section-loading-complete",
460
- totalRegistered: runtimeState.totalRegistered,
461
- totalLoaded: runtimeState.totalLoaded,
462
- currentItemIndex: runtimeState.currentItemIndex,
463
- timestamp: Date.now(),
464
- };
465
- if (shouldDeliverEvent(loadingCompleteEvent)) {
466
- args.listener(loadingCompleteEvent);
467
- }
454
+ }
455
+ collectEventItemIds(event) {
456
+ const itemIds = new Set();
457
+ if ("itemId" in event && typeof event.itemId === "string") {
458
+ itemIds.add(event.itemId);
459
+ }
460
+ if ("canonicalItemId" in event && typeof event.canonicalItemId === "string") {
461
+ itemIds.add(event.canonicalItemId);
462
+ }
463
+ if ("currentItemId" in event && typeof event.currentItemId === "string") {
464
+ itemIds.add(event.currentItemId);
468
465
  }
466
+ if ("previousItemId" in event && typeof event.previousItemId === "string") {
467
+ itemIds.add(event.previousItemId);
468
+ }
469
+ return itemIds;
470
+ }
471
+ buildLoadingCompleteReplayEvent(controller) {
472
+ const runtimeState = controller.getRuntimeState?.();
473
+ if (runtimeState?.loadingComplete !== true)
474
+ return null;
475
+ const totalRegistered = typeof runtimeState.totalRegistered === "number" &&
476
+ Number.isFinite(runtimeState.totalRegistered)
477
+ ? Math.max(0, runtimeState.totalRegistered)
478
+ : 0;
479
+ const totalLoaded = typeof runtimeState.totalLoaded === "number" &&
480
+ Number.isFinite(runtimeState.totalLoaded)
481
+ ? Math.max(0, runtimeState.totalLoaded)
482
+ : totalRegistered;
483
+ return {
484
+ type: "section-loading-complete",
485
+ totalRegistered,
486
+ totalLoaded,
487
+ currentItemIndex: runtimeState.currentItemIndex,
488
+ timestamp: Date.now(),
489
+ };
490
+ }
491
+ createSectionEventDetachHandler(subscriptionKey, unsubscribeController) {
469
492
  const detach = () => {
470
493
  const current = this.sectionEventSubscriptions.get(subscriptionKey);
471
494
  if (current !== detach) {
@@ -474,7 +497,6 @@ export class ToolkitCoordinator {
474
497
  this.sectionEventSubscriptions.delete(subscriptionKey);
475
498
  unsubscribeController();
476
499
  };
477
- this.sectionEventSubscriptions.set(subscriptionKey, detach);
478
500
  return detach;
479
501
  }
480
502
  /**
@@ -574,67 +596,101 @@ export class ToolkitCoordinator {
574
596
  attemptId: args.attemptId,
575
597
  };
576
598
  const mapKey = this.getSectionControllerMapKey(key);
577
- const existingController = this.sectionControllers.get(mapKey);
578
- if (existingController) {
579
- this.sectionControllerKeys.set(mapKey, key);
580
- if (args.updateExisting !== false) {
581
- // Existing controllers keep their in-memory session state across
582
- // input refresh; updateInput should rebuild composition/runtime view
583
- // without resetting responses.
584
- await existingController.updateInput?.(args.input);
585
- }
599
+ const existingController = await this.resolveExistingSectionController({
600
+ mapKey,
601
+ key,
602
+ input: args.input,
603
+ updateExisting: args.updateExisting,
604
+ });
605
+ if (existingController)
586
606
  return existingController;
587
- }
588
607
  const existingPromise = this.sectionControllerInitPromises.get(mapKey);
589
608
  if (existingPromise)
590
609
  return existingPromise;
591
- const initPromise = (async () => {
592
- const context = {
593
- key,
594
- coordinator: this,
595
- input: args.input,
596
- };
597
- const persistence = await this.resolveSectionPersistence(context);
598
- const defaults = {
599
- createDefaultController: args.createDefaultController,
600
- };
601
- const controller = (await this.hooks.createSectionController?.(context, defaults)) ??
602
- (await defaults.createDefaultController());
603
- await controller.configureSessionPersistence?.({
604
- strategy: persistence,
605
- context,
606
- });
607
- await controller.initialize?.(args.input);
608
- await controller.hydrate?.();
609
- this.sectionControllers.set(mapKey, controller);
610
- this.sectionControllerKeys.set(mapKey, key);
611
- this.emitSectionControllerLifecycle({
612
- type: "ready",
613
- key,
614
- controller,
615
- });
616
- await this.hooks.onSectionControllerReady?.(context, controller);
617
- await this.emitTelemetry("section-controller-ready", {
618
- assessmentId: key.assessmentId,
619
- sectionId: key.sectionId,
620
- attemptId: key.attemptId,
621
- });
622
- return controller;
623
- })().catch((err) => {
624
- this.handleError(err, {
625
- phase: "section-controller-init",
626
- details: {
627
- sectionId: args.sectionId,
628
- attemptId: args.attemptId,
629
- },
630
- });
610
+ const initPromise = this.initializeNewSectionController({
611
+ args,
612
+ key,
613
+ mapKey,
614
+ })
615
+ .catch((err) => {
616
+ this.handleSectionControllerInitError(err, args);
631
617
  throw err;
632
- }).finally(() => {
618
+ })
619
+ .finally(() => {
633
620
  this.sectionControllerInitPromises.delete(mapKey);
634
621
  });
635
622
  this.sectionControllerInitPromises.set(mapKey, initPromise);
636
623
  return initPromise;
637
624
  }
625
+ async resolveExistingSectionController(args) {
626
+ const existingController = this.sectionControllers.get(args.mapKey);
627
+ if (!existingController)
628
+ return undefined;
629
+ this.sectionControllerKeys.set(args.mapKey, args.key);
630
+ if (args.updateExisting !== false) {
631
+ // Existing controllers keep their in-memory session state across
632
+ // input refresh; updateInput should rebuild composition/runtime view
633
+ // without resetting responses.
634
+ await existingController.updateInput?.(args.input);
635
+ }
636
+ return existingController;
637
+ }
638
+ createSectionControllerContext(args) {
639
+ return {
640
+ key: args.key,
641
+ coordinator: this,
642
+ input: args.input,
643
+ };
644
+ }
645
+ async initializeNewSectionController(args) {
646
+ const context = this.createSectionControllerContext({
647
+ key: args.key,
648
+ input: args.args.input,
649
+ });
650
+ const persistence = await this.resolveSectionPersistence(context);
651
+ const defaults = {
652
+ createDefaultController: args.args.createDefaultController,
653
+ };
654
+ const controller = (await this.hooks.createSectionController?.(context, defaults)) ??
655
+ (await defaults.createDefaultController());
656
+ await controller.configureSessionPersistence?.({
657
+ strategy: persistence,
658
+ context,
659
+ });
660
+ await controller.initialize?.(args.args.input);
661
+ await controller.hydrate?.();
662
+ await this.finalizeSectionControllerReady({
663
+ mapKey: args.mapKey,
664
+ key: args.key,
665
+ context,
666
+ controller,
667
+ });
668
+ return controller;
669
+ }
670
+ async finalizeSectionControllerReady(args) {
671
+ this.sectionControllers.set(args.mapKey, args.controller);
672
+ this.sectionControllerKeys.set(args.mapKey, args.key);
673
+ this.emitSectionControllerLifecycle({
674
+ type: "ready",
675
+ key: args.key,
676
+ controller: args.controller,
677
+ });
678
+ await this.hooks.onSectionControllerReady?.(args.context, args.controller);
679
+ await this.emitTelemetry("section-controller-ready", {
680
+ assessmentId: args.key.assessmentId,
681
+ sectionId: args.key.sectionId,
682
+ attemptId: args.key.attemptId,
683
+ });
684
+ }
685
+ handleSectionControllerInitError(err, args) {
686
+ this.handleError(err, {
687
+ phase: "section-controller-init",
688
+ details: {
689
+ sectionId: args.sectionId,
690
+ attemptId: args.attemptId,
691
+ },
692
+ });
693
+ }
638
694
  async disposeSectionController(args) {
639
695
  const key = {
640
696
  assessmentId: this.assessmentId,
@@ -645,26 +701,17 @@ export class ToolkitCoordinator {
645
701
  const controller = this.sectionControllers.get(mapKey);
646
702
  if (!controller)
647
703
  return;
648
- const subscriptionPrefix = `${mapKey}::`;
649
- for (const subscriptionKey of Array.from(this.sectionEventSubscriptions.keys())) {
650
- if (!subscriptionKey.startsWith(subscriptionPrefix))
651
- continue;
652
- this.sectionEventSubscriptions.get(subscriptionKey)?.();
653
- }
654
- const context = {
704
+ this.detachSectionEventSubscriptionsForMapKey(mapKey);
705
+ const context = this.createSectionControllerContext({
655
706
  key,
656
- coordinator: this,
657
- };
707
+ input: undefined,
708
+ });
658
709
  try {
659
- if (args.persistBeforeDispose !== false) {
660
- await controller.persist?.();
661
- }
662
- await controller.dispose?.();
663
- await this.hooks.onSectionControllerDispose?.(context, controller);
664
- await this.emitTelemetry("section-controller-disposed", {
665
- assessmentId: key.assessmentId,
666
- sectionId: key.sectionId,
667
- attemptId: key.attemptId,
710
+ await this.runSectionControllerDisposePipeline({
711
+ key,
712
+ context,
713
+ controller,
714
+ persistBeforeDispose: args.persistBeforeDispose,
668
715
  });
669
716
  }
670
717
  catch (err) {
@@ -677,19 +724,47 @@ export class ToolkitCoordinator {
677
724
  });
678
725
  }
679
726
  finally {
680
- this.sectionControllers.delete(mapKey);
681
- this.sectionControllerKeys.delete(mapKey);
682
- this.emitSectionControllerLifecycle({
683
- type: "disposed",
727
+ await this.finalizeSectionControllerDispose({
728
+ mapKey,
684
729
  key,
730
+ context,
731
+ clearPersistence: args.clearPersistence,
685
732
  });
686
- if (args.clearPersistence) {
687
- const strategy = this.sectionPersistenceStrategies.get(mapKey);
688
- await strategy?.clearSession?.(context);
689
- }
690
- this.sectionPersistenceStrategies.delete(mapKey);
691
733
  }
692
734
  }
735
+ detachSectionEventSubscriptionsForMapKey(mapKey) {
736
+ const subscriptionPrefix = `${mapKey}::`;
737
+ for (const subscriptionKey of Array.from(this.sectionEventSubscriptions.keys())) {
738
+ if (!subscriptionKey.startsWith(subscriptionPrefix))
739
+ continue;
740
+ this.sectionEventSubscriptions.get(subscriptionKey)?.();
741
+ }
742
+ }
743
+ async runSectionControllerDisposePipeline(args) {
744
+ if (args.persistBeforeDispose !== false) {
745
+ await args.controller.persist?.();
746
+ }
747
+ await args.controller.dispose?.();
748
+ await this.hooks.onSectionControllerDispose?.(args.context, args.controller);
749
+ await this.emitTelemetry("section-controller-disposed", {
750
+ assessmentId: args.key.assessmentId,
751
+ sectionId: args.key.sectionId,
752
+ attemptId: args.key.attemptId,
753
+ });
754
+ }
755
+ async finalizeSectionControllerDispose(args) {
756
+ this.sectionControllers.delete(args.mapKey);
757
+ this.sectionControllerKeys.delete(args.mapKey);
758
+ this.emitSectionControllerLifecycle({
759
+ type: "disposed",
760
+ key: args.key,
761
+ });
762
+ if (args.clearPersistence) {
763
+ const strategy = this.sectionPersistenceStrategies.get(args.mapKey);
764
+ await strategy?.clearSession?.(args.context);
765
+ }
766
+ this.sectionPersistenceStrategies.delete(args.mapKey);
767
+ }
693
768
  /**
694
769
  * Initialize TTS service with provider
695
770
  */
@@ -706,92 +781,24 @@ export class ToolkitCoordinator {
706
781
  async _initializeTTS(config) {
707
782
  if (this.ttsInitialized)
708
783
  return;
784
+ const resolvedToolConfig = this.resolveTTSToolConfig(config);
785
+ const resolvedBackend = resolveTTSBackend(resolvedToolConfig);
786
+ const runtimeTTSConfig = buildRuntimeTTSConfig(resolvedToolConfig);
709
787
  await this.hooks.onBeforeTTSInit?.({
710
788
  phase: "tts-init",
711
789
  details: {
712
- backend: config?.backend,
790
+ backend: resolvedBackend,
713
791
  },
714
792
  });
715
793
  await this.emitTelemetry("tts-init-start", {
716
- backend: config?.backend ??
717
- this.config.tools?.providers?.tts
718
- ?.backend ??
719
- "browser",
794
+ backend: resolvedBackend,
720
795
  });
721
- const resolvedToolConfig = config ||
722
- (this.config.tools?.providers?.tts || {});
723
- const resolvedBackend = resolvedToolConfig.backend || "browser";
724
- const runtimeProvider = resolvedToolConfig.serverProvider ||
725
- resolvedToolConfig.provider ||
726
- (resolvedBackend === "polly" || resolvedBackend === "google"
727
- ? resolvedBackend
728
- : undefined);
729
- const transportMode = resolvedToolConfig.transportMode ||
730
- (runtimeProvider === "custom"
731
- ? "custom"
732
- : "pie");
733
- const runtimeTTSConfig = {
734
- voice: resolvedToolConfig.defaultVoice,
735
- rate: resolvedToolConfig.rate,
736
- pitch: resolvedToolConfig.pitch,
737
- providerOptions: {
738
- ...(resolvedToolConfig.language
739
- ? {
740
- locale: resolvedToolConfig.language,
741
- }
742
- : {}),
743
- ...(resolvedBackend === "polly" && resolvedToolConfig.engine
744
- ? {
745
- engine: resolvedToolConfig.engine,
746
- }
747
- : {}),
748
- ...(resolvedBackend === "polly" &&
749
- typeof resolvedToolConfig.sampleRate === "number"
750
- ? {
751
- sampleRate: resolvedToolConfig.sampleRate,
752
- }
753
- : {}),
754
- ...(resolvedBackend === "polly" && resolvedToolConfig.format
755
- ? {
756
- format: resolvedToolConfig.format,
757
- }
758
- : {}),
759
- ...(resolvedBackend === "polly"
760
- ? {
761
- speechMarkTypes: resolvedToolConfig.speechMarksMode === "word+sentence"
762
- ? ["word", "sentence"]
763
- : ["word"],
764
- }
765
- : {}),
766
- ...(transportMode === "custom" &&
767
- typeof resolvedToolConfig.cache === "boolean"
768
- ? { cache: resolvedToolConfig.cache }
769
- : {}),
770
- ...(transportMode === "custom" && resolvedToolConfig.speedRate
771
- ? { speedRate: resolvedToolConfig.speedRate }
772
- : {}),
773
- ...(transportMode === "custom" && resolvedToolConfig.lang_id
774
- ? { lang_id: resolvedToolConfig.lang_id }
775
- : {}),
776
- },
777
- apiEndpoint: resolvedToolConfig.apiEndpoint,
778
- provider: runtimeProvider,
779
- language: resolvedToolConfig.language,
780
- transportMode,
781
- endpointMode: resolvedToolConfig.endpointMode,
782
- endpointValidationMode: resolvedToolConfig.endpointValidationMode,
783
- includeAuthOnAssetFetch: resolvedToolConfig.includeAuthOnAssetFetch,
784
- validateEndpoint: resolvedToolConfig.validateEndpoint,
785
- };
786
796
  // Try to use TTS provider from registry if available
787
797
  if (this.toolProviderRegistry.has("tts")) {
788
798
  try {
789
799
  const ttsProvider = await this.ensureProviderReady("tts");
790
800
  const providerInstance = await ttsProvider.createInstance();
791
- await this.ttsService.initialize(providerInstance, runtimeTTSConfig);
792
- this.ttsService.setCatalogResolver(this.catalogResolver);
793
- this.ttsInitialized = true;
794
- await this.hooks.onTTSReady?.();
801
+ await this.initializeTTSService(providerInstance, runtimeTTSConfig);
795
802
  await this.emitTelemetry("tts-init-success", {
796
803
  provider: "registry",
797
804
  });
@@ -805,10 +812,7 @@ export class ToolkitCoordinator {
805
812
  // Fallback to browser provider
806
813
  const provider = new BrowserTTSProvider();
807
814
  try {
808
- await this.ttsService.initialize(provider, runtimeTTSConfig);
809
- this.ttsService.setCatalogResolver(this.catalogResolver);
810
- this.ttsInitialized = true;
811
- await this.hooks.onTTSReady?.();
815
+ await this.initializeTTSService(provider, runtimeTTSConfig);
812
816
  await this.emitTelemetry("tts-init-success", {
813
817
  provider: "browser-fallback",
814
818
  });
@@ -823,6 +827,16 @@ export class ToolkitCoordinator {
823
827
  throw normalized;
824
828
  }
825
829
  }
830
+ resolveTTSToolConfig(config) {
831
+ return (config ||
832
+ (this.config.tools?.providers?.tts || {}));
833
+ }
834
+ async initializeTTSService(provider, config) {
835
+ await this.ttsService.initialize(provider, config);
836
+ this.ttsService.setCatalogResolver(this.catalogResolver);
837
+ this.ttsInitialized = true;
838
+ await this.hooks.onTTSReady?.();
839
+ }
826
840
  /**
827
841
  * Get all services as a bundle for section player convenience.
828
842
  * Section player can extract and pass services to child components.