playkit-sdk 1.2.13 → 1.4.0-beta.1

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.
@@ -1,13 +1,13 @@
1
1
  /**
2
- * playkit-sdk v1.2.13
2
+ * playkit-sdk v1.4.0-beta.1
3
3
  * PlayKit SDK for JavaScript
4
4
  * @license SEE LICENSE IN LICENSE
5
5
  */
6
6
  (function (global, factory) {
7
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
8
- typeof define === 'function' && define.amd ? define(['exports'], factory) :
9
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.PlayKitSDK = {}));
10
- })(this, (function (exports) { 'use strict';
7
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
8
+ typeof define === 'function' && define.amd ? define(factory) :
9
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.PlayKitSDK = factory());
10
+ })(this, (function () { 'use strict';
11
11
 
12
12
  function getDefaultExportFromCjs (x) {
13
13
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
@@ -467,7 +467,7 @@
467
467
  * Log level enumeration
468
468
  * Lower values = higher priority
469
469
  */
470
- exports.LogLevel = void 0;
470
+ var LogLevel;
471
471
  (function (LogLevel) {
472
472
  /** Disable all logging */
473
473
  LogLevel[LogLevel["NONE"] = 0] = "NONE";
@@ -479,7 +479,7 @@
479
479
  LogLevel[LogLevel["INFO"] = 3] = "INFO";
480
480
  /** Debug level - all logs */
481
481
  LogLevel[LogLevel["DEBUG"] = 4] = "DEBUG";
482
- })(exports.LogLevel || (exports.LogLevel = {}));
482
+ })(LogLevel || (LogLevel = {}));
483
483
  /**
484
484
  * PlayKit Logger
485
485
  *
@@ -603,7 +603,7 @@
603
603
  * Useful for testing
604
604
  */
605
605
  static reset() {
606
- Logger.globalLevel = exports.LogLevel.WARN;
606
+ Logger.globalLevel = LogLevel.WARN;
607
607
  Logger.consoleEnabled = true;
608
608
  Logger.handlers = [];
609
609
  Logger.instances.clear();
@@ -631,7 +631,7 @@
631
631
  * @param data Additional data to log
632
632
  */
633
633
  debug(message, ...data) {
634
- this.log(exports.LogLevel.DEBUG, 'debug', message, data);
634
+ this.log(LogLevel.DEBUG, 'debug', message, data);
635
635
  }
636
636
  /**
637
637
  * Log an info message
@@ -639,7 +639,7 @@
639
639
  * @param data Additional data to log
640
640
  */
641
641
  info(message, ...data) {
642
- this.log(exports.LogLevel.INFO, 'info', message, data);
642
+ this.log(LogLevel.INFO, 'info', message, data);
643
643
  }
644
644
  /**
645
645
  * Log a warning message
@@ -647,7 +647,7 @@
647
647
  * @param data Additional data to log
648
648
  */
649
649
  warn(message, ...data) {
650
- this.log(exports.LogLevel.WARN, 'warn', message, data);
650
+ this.log(LogLevel.WARN, 'warn', message, data);
651
651
  }
652
652
  /**
653
653
  * Log an error message
@@ -655,7 +655,7 @@
655
655
  * @param data Additional data to log
656
656
  */
657
657
  error(message, ...data) {
658
- this.log(exports.LogLevel.ERROR, 'error', message, data);
658
+ this.log(LogLevel.ERROR, 'error', message, data);
659
659
  }
660
660
  /**
661
661
  * Internal log method
@@ -713,7 +713,7 @@
713
713
  }
714
714
  }
715
715
  }
716
- Logger.globalLevel = exports.LogLevel.WARN;
716
+ Logger.globalLevel = LogLevel.WARN;
717
717
  Logger.handlers = [];
718
718
  Logger.consoleEnabled = true;
719
719
  Logger.instances = new Map();
@@ -1176,6 +1176,15 @@
1176
1176
  }
1177
1177
  }
1178
1178
 
1179
+ const SDK_TYPE = 'Javascript';
1180
+ const SDK_VERSION = '"1.4.0-beta.1"';
1181
+ function getSDKHeaders() {
1182
+ return {
1183
+ 'X-SDK-Type': SDK_TYPE,
1184
+ 'X-SDK-Version': SDK_VERSION,
1185
+ };
1186
+ }
1187
+
1179
1188
  /**
1180
1189
  * Authentication Flow Manager
1181
1190
  * Manages the headless authentication flow with automatic UI
@@ -1280,8 +1289,8 @@
1280
1289
  constructor(baseURL) {
1281
1290
  super();
1282
1291
  this.currentSessionId = null;
1283
- this.uiContainer = null;
1284
- this.isSuccess = false;
1292
+ this._uiContainer = null;
1293
+ this._isSuccess = false;
1285
1294
  this.currentLanguage = 'en';
1286
1295
  // UI Elements
1287
1296
  this.modal = null;
@@ -1362,84 +1371,84 @@
1362
1371
  // Create modal container
1363
1372
  this.modal = document.createElement('div');
1364
1373
  this.modal.className = 'playkit-auth-modal';
1365
- this.modal.innerHTML = `
1366
- <div class="playkit-auth-overlay"></div>
1367
- <div class="playkit-auth-container">
1368
- <!-- Identifier Panel -->
1369
- <div class="playkit-auth-panel" id="playkit-identifier-panel">
1370
- <div class="playkit-auth-header">
1371
- <h2>${this.t('signIn')}</h2>
1372
- <p>${this.t('signInSubtitle')}</p>
1373
- </div>
1374
-
1375
- <div class="playkit-auth-toggle">
1376
- <label class="playkit-toggle-option">
1377
- <input type="radio" name="auth-type" value="email" checked>
1378
- <span>${this.t('email')}</span>
1379
- </label>
1380
- <label class="playkit-toggle-option">
1381
- <input type="radio" name="auth-type" value="phone">
1382
- <span>${this.t('phone')}</span>
1383
- </label>
1384
- </div>
1385
-
1386
- <div class="playkit-auth-input-group">
1387
- <div class="playkit-input-wrapper">
1388
- <svg class="playkit-input-icon" id="playkit-identifier-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1389
- <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
1390
- <polyline points="22,6 12,13 2,6"></polyline>
1391
- </svg>
1392
- <input
1393
- type="text"
1394
- id="playkit-identifier-input"
1395
- placeholder="${this.t('emailPlaceholder')}"
1396
- autocomplete="off"
1397
- >
1398
- </div>
1399
- </div>
1400
-
1401
- <button class="playkit-auth-button" id="playkit-send-code-btn">
1402
- ${this.t('sendCode')}
1403
- </button>
1404
-
1405
- <div class="playkit-auth-error" id="playkit-error-text"></div>
1406
- </div>
1407
-
1408
- <!-- Verification Panel -->
1409
- <div class="playkit-auth-panel" id="playkit-verification-panel" style="display: none;">
1410
- <div class="playkit-auth-header">
1411
- <button class="playkit-back-button" id="playkit-back-btn">
1412
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1413
- <path d="M19 12H5M12 19l-7-7 7-7"/>
1414
- </svg>
1415
- </button>
1416
- <h2>${this.t('enterCode')}</h2>
1417
- <p>${this.t('enterCodeSubtitle')} <span id="playkit-identifier-display"></span></p>
1418
- </div>
1419
-
1420
- <div class="playkit-auth-input-group">
1421
- <div class="playkit-code-inputs">
1422
- <input type="number" maxlength="1" class="playkit-code-input" data-index="0">
1423
- <input type="number" maxlength="1" class="playkit-code-input" data-index="1">
1424
- <input type="number" maxlength="1" class="playkit-code-input" data-index="2">
1425
- <input type="number" maxlength="1" class="playkit-code-input" data-index="3">
1426
- <input type="number" maxlength="1" class="playkit-code-input" data-index="4">
1427
- <input type="number" maxlength="1" class="playkit-code-input" data-index="5">
1428
- </div>
1429
- </div>
1430
-
1431
- <button class="playkit-auth-button" id="playkit-verify-btn">
1432
- ${this.t('verify')}
1433
- </button>
1434
-
1435
- <div class="playkit-auth-error" id="playkit-verify-error-text"></div>
1436
- </div>
1437
-
1438
- <!-- Loading Overlay -->
1439
- <div class="playkit-loading-overlay" id="playkit-loading-overlay" style="display: none;">
1440
- <div class="playkit-spinner"></div>
1441
- </div>
1442
- </div>
1374
+ this.modal.innerHTML = `
1375
+ <div class="playkit-auth-overlay"></div>
1376
+ <div class="playkit-auth-container">
1377
+ <!-- Identifier Panel -->
1378
+ <div class="playkit-auth-panel" id="playkit-identifier-panel">
1379
+ <div class="playkit-auth-header">
1380
+ <h2>${this.t('signIn')}</h2>
1381
+ <p>${this.t('signInSubtitle')}</p>
1382
+ </div>
1383
+
1384
+ <div class="playkit-auth-toggle">
1385
+ <label class="playkit-toggle-option">
1386
+ <input type="radio" name="auth-type" value="email" checked>
1387
+ <span>${this.t('email')}</span>
1388
+ </label>
1389
+ <label class="playkit-toggle-option">
1390
+ <input type="radio" name="auth-type" value="phone">
1391
+ <span>${this.t('phone')}</span>
1392
+ </label>
1393
+ </div>
1394
+
1395
+ <div class="playkit-auth-input-group">
1396
+ <div class="playkit-input-wrapper">
1397
+ <svg class="playkit-input-icon" id="playkit-identifier-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1398
+ <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
1399
+ <polyline points="22,6 12,13 2,6"></polyline>
1400
+ </svg>
1401
+ <input
1402
+ type="text"
1403
+ id="playkit-identifier-input"
1404
+ placeholder="${this.t('emailPlaceholder')}"
1405
+ autocomplete="off"
1406
+ >
1407
+ </div>
1408
+ </div>
1409
+
1410
+ <button class="playkit-auth-button" id="playkit-send-code-btn">
1411
+ ${this.t('sendCode')}
1412
+ </button>
1413
+
1414
+ <div class="playkit-auth-error" id="playkit-error-text"></div>
1415
+ </div>
1416
+
1417
+ <!-- Verification Panel -->
1418
+ <div class="playkit-auth-panel" id="playkit-verification-panel" style="display: none;">
1419
+ <div class="playkit-auth-header">
1420
+ <button class="playkit-back-button" id="playkit-back-btn">
1421
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1422
+ <path d="M19 12H5M12 19l-7-7 7-7"/>
1423
+ </svg>
1424
+ </button>
1425
+ <h2>${this.t('enterCode')}</h2>
1426
+ <p>${this.t('enterCodeSubtitle')} <span id="playkit-identifier-display"></span></p>
1427
+ </div>
1428
+
1429
+ <div class="playkit-auth-input-group">
1430
+ <div class="playkit-code-inputs">
1431
+ <input type="number" maxlength="1" class="playkit-code-input" data-index="0">
1432
+ <input type="number" maxlength="1" class="playkit-code-input" data-index="1">
1433
+ <input type="number" maxlength="1" class="playkit-code-input" data-index="2">
1434
+ <input type="number" maxlength="1" class="playkit-code-input" data-index="3">
1435
+ <input type="number" maxlength="1" class="playkit-code-input" data-index="4">
1436
+ <input type="number" maxlength="1" class="playkit-code-input" data-index="5">
1437
+ </div>
1438
+ </div>
1439
+
1440
+ <button class="playkit-auth-button" id="playkit-verify-btn">
1441
+ ${this.t('verify')}
1442
+ </button>
1443
+
1444
+ <div class="playkit-auth-error" id="playkit-verify-error-text"></div>
1445
+ </div>
1446
+
1447
+ <!-- Loading Overlay -->
1448
+ <div class="playkit-loading-overlay" id="playkit-loading-overlay" style="display: none;">
1449
+ <div class="playkit-spinner"></div>
1450
+ </div>
1451
+ </div>
1443
1452
  `;
1444
1453
  // Add styles and load VanillaOTP
1445
1454
  this.addStyles();
@@ -1474,274 +1483,274 @@
1474
1483
  return;
1475
1484
  const style = document.createElement('style');
1476
1485
  style.id = styleId;
1477
- style.textContent = `
1478
- .playkit-auth-modal {
1479
- position: fixed;
1480
- top: 0;
1481
- left: 0;
1482
- right: 0;
1483
- bottom: 0;
1484
- z-index: 999999;
1485
- display: flex;
1486
- justify-content: center;
1487
- align-items: center;
1488
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
1489
- }
1490
-
1491
- .playkit-auth-overlay {
1492
- position: absolute;
1493
- top: 0;
1494
- left: 0;
1495
- right: 0;
1496
- bottom: 0;
1497
- background: rgba(0, 0, 0, 0.8);
1498
- }
1499
-
1500
- .playkit-auth-container {
1501
- position: relative;
1502
- background: #fff;
1503
- border: 1px solid rgba(0, 0, 0, 0.1);
1504
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
1505
- width: 90%;
1506
- max-width: 320px;
1507
- overflow: hidden;
1508
- }
1509
-
1510
- .playkit-auth-panel {
1511
- padding: 24px;
1512
- }
1513
-
1514
- .playkit-auth-header {
1515
- text-align: center;
1516
- margin-bottom: 20px;
1517
- position: relative;
1518
- }
1519
-
1520
- .playkit-auth-header h2 {
1521
- margin: 0 0 8px 0;
1522
- font-size: 14px;
1523
- font-weight: 600;
1524
- color: #171717;
1525
- }
1526
-
1527
- .playkit-auth-header p {
1528
- margin: 0;
1529
- font-size: 14px;
1530
- color: #666;
1531
- line-height: 1.5;
1532
- }
1533
-
1534
- .playkit-back-button {
1535
- position: absolute;
1536
- left: 0;
1537
- top: 0;
1538
- background: transparent;
1539
- border: none;
1540
- cursor: pointer;
1541
- padding: 4px;
1542
- color: #666;
1543
- transition: background-color 0.2s ease, color 0.2s ease;
1544
- }
1545
-
1546
- .playkit-back-button:hover {
1547
- background: #f5f5f5;
1548
- color: #171717;
1549
- }
1550
-
1551
- .playkit-auth-toggle {
1552
- display: flex;
1553
- background: #f5f5f5;
1554
- padding: 2px;
1555
- margin-bottom: 20px;
1556
- gap: 2px;
1557
- }
1558
-
1559
- .playkit-toggle-option {
1560
- flex: 1;
1561
- display: flex;
1562
- justify-content: center;
1563
- align-items: center;
1564
- padding: 10px 16px;
1565
- cursor: pointer;
1566
- transition: background-color 0.2s ease;
1567
- }
1568
-
1569
- .playkit-toggle-option input {
1570
- display: none;
1571
- }
1572
-
1573
- .playkit-toggle-option span {
1574
- font-size: 14px;
1575
- font-weight: 500;
1576
- color: #666;
1577
- transition: color 0.2s ease;
1578
- }
1579
-
1580
- .playkit-toggle-option input:checked + span {
1581
- color: #fff;
1582
- }
1583
-
1584
- .playkit-toggle-option:has(input:checked) {
1585
- background: #171717;
1586
- }
1587
-
1588
- .playkit-auth-input-group {
1589
- margin-bottom: 20px;
1590
- }
1591
-
1592
- .playkit-input-wrapper {
1593
- position: relative;
1594
- display: flex;
1595
- align-items: center;
1596
- }
1597
-
1598
- .playkit-input-icon {
1599
- position: absolute;
1600
- left: 12px;
1601
- color: #999;
1602
- pointer-events: none;
1603
- }
1604
-
1605
- .playkit-input-wrapper input {
1606
- width: 100%;
1607
- padding: 10px 12px 10px 44px;
1608
- border: 1px solid #e5e7eb;
1609
- font-size: 14px;
1610
- transition: border-color 0.2s ease;
1611
- box-sizing: border-box;
1612
- background: #fff;
1613
- }
1614
-
1615
- .playkit-input-wrapper input:hover {
1616
- border-color: #d4d4d4;
1617
- }
1618
-
1619
- .playkit-input-wrapper input:focus {
1620
- outline: none;
1621
- border-color: #171717;
1622
- }
1623
-
1624
- .playkit-code-inputs {
1625
- display: flex;
1626
- gap: 8px;
1627
- justify-content: center;
1628
- }
1629
-
1630
- .playkit-code-input {
1631
- width: 40px !important;
1632
- height: 48px;
1633
- text-align: center;
1634
- font-size: 20px;
1635
- font-weight: 600;
1636
- border: 1px solid #e5e7eb !important;
1637
- padding: 0 !important;
1638
- transition: border-color 0.2s ease;
1639
- background: #fff;
1640
- -moz-appearance: textfield;
1641
- }
1642
-
1643
- .playkit-code-input::-webkit-outer-spin-button,
1644
- .playkit-code-input::-webkit-inner-spin-button {
1645
- -webkit-appearance: none;
1646
- margin: 0;
1647
- }
1648
-
1649
- .playkit-code-input:hover {
1650
- border-color: #d4d4d4 !important;
1651
- }
1652
-
1653
- .playkit-code-input:focus {
1654
- outline: none;
1655
- border-color: #171717 !important;
1656
- }
1657
-
1658
- .playkit-auth-button {
1659
- width: 100%;
1660
- padding: 10px 16px;
1661
- background: #171717;
1662
- color: white;
1663
- border: none;
1664
- font-size: 14px;
1665
- font-weight: 500;
1666
- cursor: pointer;
1667
- transition: background 0.2s ease;
1668
- }
1669
-
1670
- .playkit-auth-button:hover:not(:disabled) {
1671
- background: #404040;
1672
- }
1673
-
1674
- .playkit-auth-button:active:not(:disabled) {
1675
- background: #0a0a0a;
1676
- }
1677
-
1678
- .playkit-auth-button:disabled {
1679
- background: #e5e7eb;
1680
- color: #999;
1681
- cursor: not-allowed;
1682
- }
1683
-
1684
- .playkit-auth-error {
1685
- margin-top: 16px;
1686
- padding: 12px 16px;
1687
- background: #fef2f2;
1688
- border: 1px solid #fecaca;
1689
- color: #dc2626;
1690
- font-size: 13px;
1691
- text-align: left;
1692
- display: none;
1693
- }
1694
-
1695
- .playkit-auth-error.show {
1696
- display: block;
1697
- }
1698
-
1699
- .playkit-loading-overlay {
1700
- position: absolute;
1701
- top: 0;
1702
- left: 0;
1703
- right: 0;
1704
- bottom: 0;
1705
- background: rgba(255, 255, 255, 0.96);
1706
- display: flex;
1707
- justify-content: center;
1708
- align-items: center;
1709
- }
1710
-
1711
- .playkit-spinner {
1712
- width: 24px;
1713
- height: 24px;
1714
- border: 2px solid #e5e7eb;
1715
- border-top: 2px solid #171717;
1716
- border-radius: 50%;
1717
- animation: playkit-spin 1s linear infinite;
1718
- }
1719
-
1720
- @keyframes playkit-spin {
1721
- 0% { transform: rotate(0deg); }
1722
- 100% { transform: rotate(360deg); }
1723
- }
1724
-
1725
- @media (max-width: 480px) {
1726
- .playkit-auth-container {
1727
- width: 95%;
1728
- max-width: none;
1729
- }
1730
-
1731
- .playkit-auth-panel {
1732
- padding: 20px;
1733
- }
1734
-
1735
- .playkit-code-input {
1736
- width: 36px !important;
1737
- height: 44px;
1738
- font-size: 18px;
1739
- }
1740
-
1741
- .playkit-code-inputs {
1742
- gap: 6px;
1743
- }
1744
- }
1486
+ style.textContent = `
1487
+ .playkit-auth-modal {
1488
+ position: fixed;
1489
+ top: 0;
1490
+ left: 0;
1491
+ right: 0;
1492
+ bottom: 0;
1493
+ z-index: 999999;
1494
+ display: flex;
1495
+ justify-content: center;
1496
+ align-items: center;
1497
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
1498
+ }
1499
+
1500
+ .playkit-auth-overlay {
1501
+ position: absolute;
1502
+ top: 0;
1503
+ left: 0;
1504
+ right: 0;
1505
+ bottom: 0;
1506
+ background: rgba(0, 0, 0, 0.8);
1507
+ }
1508
+
1509
+ .playkit-auth-container {
1510
+ position: relative;
1511
+ background: #fff;
1512
+ border: 1px solid rgba(0, 0, 0, 0.1);
1513
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
1514
+ width: 90%;
1515
+ max-width: 320px;
1516
+ overflow: hidden;
1517
+ }
1518
+
1519
+ .playkit-auth-panel {
1520
+ padding: 24px;
1521
+ }
1522
+
1523
+ .playkit-auth-header {
1524
+ text-align: center;
1525
+ margin-bottom: 20px;
1526
+ position: relative;
1527
+ }
1528
+
1529
+ .playkit-auth-header h2 {
1530
+ margin: 0 0 8px 0;
1531
+ font-size: 14px;
1532
+ font-weight: 600;
1533
+ color: #171717;
1534
+ }
1535
+
1536
+ .playkit-auth-header p {
1537
+ margin: 0;
1538
+ font-size: 14px;
1539
+ color: #666;
1540
+ line-height: 1.5;
1541
+ }
1542
+
1543
+ .playkit-back-button {
1544
+ position: absolute;
1545
+ left: 0;
1546
+ top: 0;
1547
+ background: transparent;
1548
+ border: none;
1549
+ cursor: pointer;
1550
+ padding: 4px;
1551
+ color: #666;
1552
+ transition: background-color 0.2s ease, color 0.2s ease;
1553
+ }
1554
+
1555
+ .playkit-back-button:hover {
1556
+ background: #f5f5f5;
1557
+ color: #171717;
1558
+ }
1559
+
1560
+ .playkit-auth-toggle {
1561
+ display: flex;
1562
+ background: #f5f5f5;
1563
+ padding: 2px;
1564
+ margin-bottom: 20px;
1565
+ gap: 2px;
1566
+ }
1567
+
1568
+ .playkit-toggle-option {
1569
+ flex: 1;
1570
+ display: flex;
1571
+ justify-content: center;
1572
+ align-items: center;
1573
+ padding: 10px 16px;
1574
+ cursor: pointer;
1575
+ transition: background-color 0.2s ease;
1576
+ }
1577
+
1578
+ .playkit-toggle-option input {
1579
+ display: none;
1580
+ }
1581
+
1582
+ .playkit-toggle-option span {
1583
+ font-size: 14px;
1584
+ font-weight: 500;
1585
+ color: #666;
1586
+ transition: color 0.2s ease;
1587
+ }
1588
+
1589
+ .playkit-toggle-option input:checked + span {
1590
+ color: #fff;
1591
+ }
1592
+
1593
+ .playkit-toggle-option:has(input:checked) {
1594
+ background: #171717;
1595
+ }
1596
+
1597
+ .playkit-auth-input-group {
1598
+ margin-bottom: 20px;
1599
+ }
1600
+
1601
+ .playkit-input-wrapper {
1602
+ position: relative;
1603
+ display: flex;
1604
+ align-items: center;
1605
+ }
1606
+
1607
+ .playkit-input-icon {
1608
+ position: absolute;
1609
+ left: 12px;
1610
+ color: #999;
1611
+ pointer-events: none;
1612
+ }
1613
+
1614
+ .playkit-input-wrapper input {
1615
+ width: 100%;
1616
+ padding: 10px 12px 10px 44px;
1617
+ border: 1px solid #e5e7eb;
1618
+ font-size: 14px;
1619
+ transition: border-color 0.2s ease;
1620
+ box-sizing: border-box;
1621
+ background: #fff;
1622
+ }
1623
+
1624
+ .playkit-input-wrapper input:hover {
1625
+ border-color: #d4d4d4;
1626
+ }
1627
+
1628
+ .playkit-input-wrapper input:focus {
1629
+ outline: none;
1630
+ border-color: #171717;
1631
+ }
1632
+
1633
+ .playkit-code-inputs {
1634
+ display: flex;
1635
+ gap: 8px;
1636
+ justify-content: center;
1637
+ }
1638
+
1639
+ .playkit-code-input {
1640
+ width: 40px !important;
1641
+ height: 48px;
1642
+ text-align: center;
1643
+ font-size: 20px;
1644
+ font-weight: 600;
1645
+ border: 1px solid #e5e7eb !important;
1646
+ padding: 0 !important;
1647
+ transition: border-color 0.2s ease;
1648
+ background: #fff;
1649
+ -moz-appearance: textfield;
1650
+ }
1651
+
1652
+ .playkit-code-input::-webkit-outer-spin-button,
1653
+ .playkit-code-input::-webkit-inner-spin-button {
1654
+ -webkit-appearance: none;
1655
+ margin: 0;
1656
+ }
1657
+
1658
+ .playkit-code-input:hover {
1659
+ border-color: #d4d4d4 !important;
1660
+ }
1661
+
1662
+ .playkit-code-input:focus {
1663
+ outline: none;
1664
+ border-color: #171717 !important;
1665
+ }
1666
+
1667
+ .playkit-auth-button {
1668
+ width: 100%;
1669
+ padding: 10px 16px;
1670
+ background: #171717;
1671
+ color: white;
1672
+ border: none;
1673
+ font-size: 14px;
1674
+ font-weight: 500;
1675
+ cursor: pointer;
1676
+ transition: background 0.2s ease;
1677
+ }
1678
+
1679
+ .playkit-auth-button:hover:not(:disabled) {
1680
+ background: #404040;
1681
+ }
1682
+
1683
+ .playkit-auth-button:active:not(:disabled) {
1684
+ background: #0a0a0a;
1685
+ }
1686
+
1687
+ .playkit-auth-button:disabled {
1688
+ background: #e5e7eb;
1689
+ color: #999;
1690
+ cursor: not-allowed;
1691
+ }
1692
+
1693
+ .playkit-auth-error {
1694
+ margin-top: 16px;
1695
+ padding: 12px 16px;
1696
+ background: #fef2f2;
1697
+ border: 1px solid #fecaca;
1698
+ color: #dc2626;
1699
+ font-size: 13px;
1700
+ text-align: left;
1701
+ display: none;
1702
+ }
1703
+
1704
+ .playkit-auth-error.show {
1705
+ display: block;
1706
+ }
1707
+
1708
+ .playkit-loading-overlay {
1709
+ position: absolute;
1710
+ top: 0;
1711
+ left: 0;
1712
+ right: 0;
1713
+ bottom: 0;
1714
+ background: rgba(255, 255, 255, 0.96);
1715
+ display: flex;
1716
+ justify-content: center;
1717
+ align-items: center;
1718
+ }
1719
+
1720
+ .playkit-spinner {
1721
+ width: 24px;
1722
+ height: 24px;
1723
+ border: 2px solid #e5e7eb;
1724
+ border-top: 2px solid #171717;
1725
+ border-radius: 50%;
1726
+ animation: playkit-spin 1s linear infinite;
1727
+ }
1728
+
1729
+ @keyframes playkit-spin {
1730
+ 0% { transform: rotate(0deg); }
1731
+ 100% { transform: rotate(360deg); }
1732
+ }
1733
+
1734
+ @media (max-width: 480px) {
1735
+ .playkit-auth-container {
1736
+ width: 95%;
1737
+ max-width: none;
1738
+ }
1739
+
1740
+ .playkit-auth-panel {
1741
+ padding: 20px;
1742
+ }
1743
+
1744
+ .playkit-code-input {
1745
+ width: 36px !important;
1746
+ height: 44px;
1747
+ font-size: 18px;
1748
+ }
1749
+
1750
+ .playkit-code-inputs {
1751
+ gap: 6px;
1752
+ }
1753
+ }
1745
1754
  `;
1746
1755
  document.head.appendChild(style);
1747
1756
  }
@@ -1762,14 +1771,14 @@
1762
1771
  : this.t('phonePlaceholder');
1763
1772
  // Update icon
1764
1773
  if (isEmail) {
1765
- identifierIcon.innerHTML = `
1766
- <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
1767
- <polyline points="22,6 12,13 2,6"></polyline>
1774
+ identifierIcon.innerHTML = `
1775
+ <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
1776
+ <polyline points="22,6 12,13 2,6"></polyline>
1768
1777
  `;
1769
1778
  }
1770
1779
  else {
1771
- identifierIcon.innerHTML = `
1772
- <path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>
1780
+ identifierIcon.innerHTML = `
1781
+ <path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>
1773
1782
  `;
1774
1783
  }
1775
1784
  };
@@ -1790,7 +1799,7 @@
1790
1799
  this.otpInstance = new window.VanillaOTP(codeInputsContainer);
1791
1800
  // Auto-submit when all 6 digits entered
1792
1801
  const codeInputs = (_d = this.modal) === null || _d === void 0 ? void 0 : _d.querySelectorAll('.playkit-code-input');
1793
- codeInputs === null || codeInputs === void 0 ? void 0 : codeInputs.forEach((input, index) => {
1802
+ codeInputs === null || codeInputs === void 0 ? void 0 : codeInputs.forEach((input, _index) => {
1794
1803
  input.addEventListener('input', () => {
1795
1804
  // Check if all inputs are filled
1796
1805
  const allFilled = Array.from(codeInputs).every(inp => inp.value.length === 1);
@@ -1882,7 +1891,7 @@
1882
1891
  async sendVerificationCode(identifier, type) {
1883
1892
  const response = await fetch(`${this.baseURL}/api/auth/send-code`, {
1884
1893
  method: 'POST',
1885
- headers: { 'Content-Type': 'application/json' },
1894
+ headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
1886
1895
  body: JSON.stringify({ identifier, type }),
1887
1896
  });
1888
1897
  if (!response.ok) {
@@ -1904,7 +1913,7 @@
1904
1913
  }
1905
1914
  const response = await fetch(`${this.baseURL}/api/auth/verify-code`, {
1906
1915
  method: 'POST',
1907
- headers: { 'Content-Type': 'application/json' },
1916
+ headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
1908
1917
  body: JSON.stringify({
1909
1918
  sessionId: this.currentSessionId,
1910
1919
  code,
@@ -1925,7 +1934,9 @@
1925
1934
  async setDefaultAuthTypeByRegion() {
1926
1935
  var _a;
1927
1936
  try {
1928
- const response = await fetch(`${this.baseURL}/api/reachability`);
1937
+ const response = await fetch(`${this.baseURL}/api/reachability`, {
1938
+ headers: Object.assign({}, getSDKHeaders()),
1939
+ });
1929
1940
  if (response.ok) {
1930
1941
  const data = await response.json();
1931
1942
  if (data.region === 'CN') {
@@ -2182,76 +2193,76 @@
2182
2193
  // Create modal overlay - dark bg-black/80 style
2183
2194
  const overlay = document.createElement('div');
2184
2195
  overlay.id = 'playkit-login-modal';
2185
- overlay.style.cssText = `
2186
- position: fixed;
2187
- top: 0;
2188
- left: 0;
2189
- right: 0;
2190
- bottom: 0;
2191
- background: rgba(0, 0, 0, 0.8);
2192
- display: flex;
2193
- align-items: center;
2194
- justify-content: center;
2195
- z-index: 999999;
2196
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
2196
+ overlay.style.cssText = `
2197
+ position: fixed;
2198
+ top: 0;
2199
+ left: 0;
2200
+ right: 0;
2201
+ bottom: 0;
2202
+ background: rgba(0, 0, 0, 0.8);
2203
+ display: flex;
2204
+ align-items: center;
2205
+ justify-content: center;
2206
+ z-index: 999999;
2207
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
2197
2208
  `;
2198
2209
  // Create modal card - square corners, shadow-xl style
2199
2210
  const card = document.createElement('div');
2200
- card.style.cssText = `
2201
- background: #fff;
2202
- border: 1px solid rgba(0, 0, 0, 0.1);
2203
- padding: 24px;
2204
- max-width: 320px;
2205
- width: 90%;
2206
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
2207
- text-align: center;
2211
+ card.style.cssText = `
2212
+ background: #fff;
2213
+ border: 1px solid rgba(0, 0, 0, 0.1);
2214
+ padding: 24px;
2215
+ max-width: 320px;
2216
+ width: 90%;
2217
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
2218
+ text-align: center;
2208
2219
  `;
2209
2220
  // Subtitle / status text
2210
2221
  const subtitle = document.createElement('p');
2211
2222
  subtitle.id = 'playkit-modal-subtitle';
2212
2223
  subtitle.textContent = this.t('loginWithPlayKit');
2213
- subtitle.style.cssText = `
2214
- margin: 0 0 20px;
2215
- font-size: 14px;
2216
- color: #666;
2224
+ subtitle.style.cssText = `
2225
+ margin: 0 0 20px;
2226
+ font-size: 14px;
2227
+ color: #666;
2217
2228
  `;
2218
2229
  card.appendChild(subtitle);
2219
2230
  // Loading spinner (hidden initially)
2220
2231
  const spinner = document.createElement('div');
2221
2232
  spinner.id = 'playkit-modal-spinner';
2222
- spinner.style.cssText = `
2223
- display: none;
2224
- width: 24px;
2225
- height: 24px;
2226
- margin: 0 auto 16px;
2227
- border: 2px solid #e5e7eb;
2228
- border-top-color: #171717;
2229
- border-radius: 50%;
2230
- animation: playkit-spin 1s linear infinite;
2233
+ spinner.style.cssText = `
2234
+ display: none;
2235
+ width: 24px;
2236
+ height: 24px;
2237
+ margin: 0 auto 16px;
2238
+ border: 2px solid #e5e7eb;
2239
+ border-top-color: #171717;
2240
+ border-radius: 50%;
2241
+ animation: playkit-spin 1s linear infinite;
2231
2242
  `;
2232
2243
  card.appendChild(spinner);
2233
2244
  // Add keyframes for spinner
2234
2245
  const style = document.createElement('style');
2235
- style.textContent = `
2236
- @keyframes playkit-spin {
2237
- to { transform: rotate(360deg); }
2238
- }
2246
+ style.textContent = `
2247
+ @keyframes playkit-spin {
2248
+ to { transform: rotate(360deg); }
2249
+ }
2239
2250
  `;
2240
2251
  document.head.appendChild(style);
2241
2252
  // Login button - square corners, simple dark style
2242
2253
  const loginBtn = document.createElement('button');
2243
2254
  loginBtn.id = 'playkit-modal-login-btn';
2244
2255
  loginBtn.textContent = this.t('loginToPlay');
2245
- loginBtn.style.cssText = `
2246
- width: 100%;
2247
- padding: 10px 16px;
2248
- font-size: 14px;
2249
- font-weight: 500;
2250
- color: white;
2251
- background: #171717;
2252
- border: none;
2253
- cursor: pointer;
2254
- transition: background 0.2s ease;
2256
+ loginBtn.style.cssText = `
2257
+ width: 100%;
2258
+ padding: 10px 16px;
2259
+ font-size: 14px;
2260
+ font-weight: 500;
2261
+ color: white;
2262
+ background: #171717;
2263
+ border: none;
2264
+ cursor: pointer;
2265
+ transition: background 0.2s ease;
2255
2266
  `;
2256
2267
  loginBtn.onmouseenter = () => {
2257
2268
  loginBtn.style.background = '#404040';
@@ -2278,18 +2289,18 @@
2278
2289
  const cancelBtn = document.createElement('button');
2279
2290
  cancelBtn.id = 'playkit-modal-cancel-btn';
2280
2291
  cancelBtn.textContent = this.t('cancel');
2281
- cancelBtn.style.cssText = `
2282
- display: none;
2283
- width: 100%;
2284
- margin-top: 8px;
2285
- padding: 10px 16px;
2286
- font-size: 14px;
2287
- font-weight: 500;
2288
- color: #666;
2289
- background: transparent;
2290
- border: 1px solid #e5e7eb;
2291
- cursor: pointer;
2292
- transition: all 0.2s ease;
2292
+ cancelBtn.style.cssText = `
2293
+ display: none;
2294
+ width: 100%;
2295
+ margin-top: 8px;
2296
+ padding: 10px 16px;
2297
+ font-size: 14px;
2298
+ font-weight: 500;
2299
+ color: #666;
2300
+ background: transparent;
2301
+ border: 1px solid #e5e7eb;
2302
+ cursor: pointer;
2303
+ transition: all 0.2s ease;
2293
2304
  `;
2294
2305
  cancelBtn.onmouseenter = () => {
2295
2306
  cancelBtn.style.background = '#f5f5f5';
@@ -2359,11 +2370,11 @@
2359
2370
  // Create error title
2360
2371
  const errorTitle = document.createElement('h3');
2361
2372
  errorTitle.textContent = this.t(titleKey);
2362
- errorTitle.style.cssText = `
2363
- margin: 0 0 8px;
2364
- font-size: 14px;
2365
- font-weight: 600;
2366
- color: ${iconColor};
2373
+ errorTitle.style.cssText = `
2374
+ margin: 0 0 8px;
2375
+ font-size: 14px;
2376
+ font-weight: 600;
2377
+ color: ${iconColor};
2367
2378
  `;
2368
2379
  // Update subtitle with error description
2369
2380
  subtitle.textContent = this.t(descKey);
@@ -2433,9 +2444,7 @@
2433
2444
  // Step 1: Initiate device auth session
2434
2445
  const initResponse = await fetch(`${this.baseURL}/api/device-auth/initiate`, {
2435
2446
  method: 'POST',
2436
- headers: {
2437
- 'Content-Type': 'application/json',
2438
- },
2447
+ headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
2439
2448
  body: JSON.stringify({
2440
2449
  game_id: this.gameId,
2441
2450
  code_challenge: codeChallenge,
@@ -2504,7 +2513,7 @@
2504
2513
  return;
2505
2514
  }
2506
2515
  try {
2507
- const pollResponse = await fetch(`${this.baseURL}/api/device-auth/poll?session_id=${encodeURIComponent(session_id)}&code_verifier=${encodeURIComponent(codeVerifier)}`);
2516
+ const pollResponse = await fetch(`${this.baseURL}/api/device-auth/poll?session_id=${encodeURIComponent(session_id)}&code_verifier=${encodeURIComponent(codeVerifier)}`, { headers: Object.assign({}, getSDKHeaders()) });
2508
2517
  const pollData = await pollResponse.json();
2509
2518
  if (pollResponse.ok) {
2510
2519
  if (pollData.status === 'pending') {
@@ -2632,9 +2641,7 @@
2632
2641
  // Initiate device auth session
2633
2642
  const initResponse = await fetch(`${this.baseURL}/api/device-auth/initiate`, {
2634
2643
  method: 'POST',
2635
- headers: {
2636
- 'Content-Type': 'application/json',
2637
- },
2644
+ headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
2638
2645
  body: JSON.stringify({
2639
2646
  game_id: this.gameId,
2640
2647
  code_challenge: codeChallenge,
@@ -2697,7 +2704,7 @@
2697
2704
  return;
2698
2705
  }
2699
2706
  try {
2700
- const pollResponse = await fetch(`${this.baseURL}/api/device-auth/poll?session_id=${encodeURIComponent(sessionId)}&code_verifier=${encodeURIComponent(codeVerifier)}`);
2707
+ const pollResponse = await fetch(`${this.baseURL}/api/device-auth/poll?session_id=${encodeURIComponent(sessionId)}&code_verifier=${encodeURIComponent(codeVerifier)}`, { headers: Object.assign({}, getSDKHeaders()) });
2701
2708
  const pollData = await pollResponse.json();
2702
2709
  if (pollResponse.ok) {
2703
2710
  if (pollData.status === 'pending') {
@@ -2766,7 +2773,7 @@
2766
2773
  * Handles JWT exchange and token management
2767
2774
  */
2768
2775
  // @ts-ignore - replaced at build time
2769
- const DEFAULT_BASE_URL$5 = "https://api.playkit.ai";
2776
+ const DEFAULT_BASE_URL$6 = "https://api.playkit.ai";
2770
2777
  const JWT_EXCHANGE_ENDPOINT = '/api/external/exchange-jwt';
2771
2778
  const TOKEN_REFRESH_ENDPOINT = '/api/auth/refresh';
2772
2779
  class AuthManager extends EventEmitter {
@@ -2784,7 +2791,7 @@
2784
2791
  this.storage = new TokenStorage({
2785
2792
  mode: config.mode === 'server' ? 'server' : 'browser',
2786
2793
  });
2787
- this.baseURL = config.baseURL || DEFAULT_BASE_URL$5;
2794
+ this.baseURL = config.baseURL || DEFAULT_BASE_URL$6;
2788
2795
  this.authState = {
2789
2796
  isAuthenticated: false,
2790
2797
  };
@@ -2966,10 +2973,7 @@
2966
2973
  try {
2967
2974
  const response = await fetch(`${this.baseURL}${JWT_EXCHANGE_ENDPOINT}`, {
2968
2975
  method: 'POST',
2969
- headers: {
2970
- Authorization: `Bearer ${jwt}`,
2971
- 'Content-Type': 'application/json',
2972
- },
2976
+ headers: Object.assign({ Authorization: `Bearer ${jwt}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
2973
2977
  body: JSON.stringify({ gameId: this.config.gameId }),
2974
2978
  });
2975
2979
  if (!response.ok) {
@@ -3319,9 +3323,7 @@
3319
3323
  this.logger.debug('Refreshing access token');
3320
3324
  const response = await fetch(`${this.baseURL}${TOKEN_REFRESH_ENDPOINT}`, {
3321
3325
  method: 'POST',
3322
- headers: {
3323
- 'Content-Type': 'application/json',
3324
- },
3326
+ headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
3325
3327
  body: JSON.stringify({
3326
3328
  refresh_token: this.authState.refreshToken,
3327
3329
  }),
@@ -3424,7 +3426,7 @@
3424
3426
  * RechargeManager handles the recharge modal UI and recharge window opening
3425
3427
  */
3426
3428
  class RechargeManager extends EventEmitter {
3427
- constructor(playerToken, rechargePortalUrl = 'https://playkit.ai/recharge', gameId) {
3429
+ constructor(playerToken, rechargePortalUrl = 'https://players.playkit.ai/recharge', gameId) {
3428
3430
  super();
3429
3431
  this.modalContainer = null;
3430
3432
  this.styleElement = null;
@@ -3527,220 +3529,220 @@
3527
3529
  return;
3528
3530
  }
3529
3531
  this.styleElement = document.createElement('style');
3530
- this.styleElement.textContent = `
3531
- .playkit-recharge-overlay {
3532
- position: fixed;
3533
- top: 0;
3534
- left: 0;
3535
- right: 0;
3536
- bottom: 0;
3537
- background: rgba(0, 0, 0, 0.8);
3538
- display: flex;
3539
- justify-content: center;
3540
- align-items: center;
3541
- z-index: 999999;
3542
- animation: playkit-recharge-fadeIn 0.2s ease-out;
3543
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3544
- }
3545
-
3546
- @keyframes playkit-recharge-fadeIn {
3547
- from {
3548
- opacity: 0;
3549
- }
3550
- to {
3551
- opacity: 1;
3552
- }
3553
- }
3554
-
3555
- .playkit-recharge-modal {
3556
- background: #fff;
3557
- border: 1px solid rgba(0, 0, 0, 0.1);
3558
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
3559
- padding: 24px;
3560
- max-width: 320px;
3561
- width: 90%;
3562
- position: relative;
3563
- text-align: center;
3564
- }
3565
-
3566
- .playkit-recharge-title {
3567
- font-size: 14px;
3568
- font-weight: 600;
3569
- color: #171717;
3570
- margin: 0 0 8px 0;
3571
- text-align: center;
3572
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3573
- }
3574
-
3575
- .playkit-recharge-message {
3576
- font-size: 14px;
3577
- color: #666;
3578
- margin: 0 0 20px 0;
3579
- text-align: center;
3580
- line-height: 1.5;
3581
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3582
- }
3583
-
3584
- .playkit-recharge-balance {
3585
- background: #f5f5f5;
3586
- border: 1px solid #e5e7eb;
3587
- padding: 16px;
3588
- margin: 0 0 20px 0;
3589
- text-align: center;
3590
- }
3591
-
3592
- .playkit-recharge-balance-label {
3593
- font-size: 12px;
3594
- color: #666;
3595
- margin: 0 0 8px 0;
3596
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3597
- }
3598
-
3599
- .playkit-recharge-balance-value {
3600
- font-size: 24px;
3601
- font-weight: bold;
3602
- color: #171717;
3603
- margin: 0;
3604
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3605
- }
3606
-
3607
- .playkit-recharge-balance-unit {
3608
- font-size: 14px;
3609
- color: #666;
3610
- margin-left: 4px;
3611
- }
3612
-
3613
- .playkit-recharge-buttons {
3614
- display: flex;
3615
- flex-direction: column;
3616
- gap: 8px;
3617
- }
3618
-
3619
- .playkit-recharge-button {
3620
- width: 100%;
3621
- padding: 10px 16px;
3622
- border: none;
3623
- font-size: 14px;
3624
- font-weight: 500;
3625
- cursor: pointer;
3626
- transition: all 0.2s ease;
3627
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3628
- }
3629
-
3630
- .playkit-recharge-button-primary {
3631
- background: #171717;
3632
- color: white;
3633
- }
3634
-
3635
- .playkit-recharge-button-primary:hover {
3636
- background: #404040;
3637
- }
3638
-
3639
- .playkit-recharge-button-primary:active {
3640
- background: #0a0a0a;
3641
- }
3642
-
3643
- .playkit-recharge-button-secondary {
3644
- background: transparent;
3645
- color: #666;
3646
- border: 1px solid #e5e7eb;
3647
- }
3648
-
3649
- .playkit-recharge-button-secondary:hover {
3650
- background: #f5f5f5;
3651
- border-color: #d4d4d4;
3652
- }
3653
-
3654
- .playkit-recharge-button-secondary:active {
3655
- background: #e5e5e5;
3656
- }
3657
-
3658
- @media (max-width: 480px) {
3659
- .playkit-recharge-modal {
3660
- padding: 20px;
3661
- }
3662
- }
3663
-
3664
- /* Daily Refresh Toast Styles */
3665
- .playkit-daily-refresh-toast {
3666
- position: fixed;
3667
- top: 20px;
3668
- right: 20px;
3669
- background: #fff;
3670
- border: 1px solid rgba(0, 0, 0, 0.1);
3671
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.1);
3672
- padding: 16px 20px;
3673
- min-width: 240px;
3674
- max-width: 320px;
3675
- z-index: 999998;
3676
- animation: playkit-toast-slideIn 0.3s ease-out;
3677
- display: flex;
3678
- align-items: flex-start;
3679
- gap: 12px;
3680
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3681
- }
3682
-
3683
- .playkit-daily-refresh-toast.hiding {
3684
- animation: playkit-toast-fadeOut 0.3s ease-out forwards;
3685
- }
3686
-
3687
- @keyframes playkit-toast-slideIn {
3688
- from {
3689
- transform: translateX(100%);
3690
- opacity: 0;
3691
- }
3692
- to {
3693
- transform: translateX(0);
3694
- opacity: 1;
3695
- }
3696
- }
3697
-
3698
- @keyframes playkit-toast-fadeOut {
3699
- from {
3700
- transform: translateX(0);
3701
- opacity: 1;
3702
- }
3703
- to {
3704
- transform: translateX(100%);
3705
- opacity: 0;
3706
- }
3707
- }
3708
-
3709
- .playkit-toast-icon {
3710
- width: 24px;
3711
- height: 24px;
3712
- background: #171717;
3713
- border-radius: 50%;
3714
- display: flex;
3715
- align-items: center;
3716
- justify-content: center;
3717
- flex-shrink: 0;
3718
- }
3719
-
3720
- .playkit-toast-icon svg {
3721
- width: 14px;
3722
- height: 14px;
3723
- color: #ffffff;
3724
- }
3725
-
3726
- .playkit-toast-message {
3727
- flex: 1;
3728
- font-size: 14px;
3729
- font-weight: 500;
3730
- color: #171717;
3731
- line-height: 1.4;
3732
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3733
- }
3734
-
3735
- @media (max-width: 480px) {
3736
- .playkit-daily-refresh-toast {
3737
- top: 10px;
3738
- right: 10px;
3739
- left: 10px;
3740
- min-width: auto;
3741
- max-width: none;
3742
- }
3743
- }
3532
+ this.styleElement.textContent = `
3533
+ .playkit-recharge-overlay {
3534
+ position: fixed;
3535
+ top: 0;
3536
+ left: 0;
3537
+ right: 0;
3538
+ bottom: 0;
3539
+ background: rgba(0, 0, 0, 0.8);
3540
+ display: flex;
3541
+ justify-content: center;
3542
+ align-items: center;
3543
+ z-index: 999999;
3544
+ animation: playkit-recharge-fadeIn 0.2s ease-out;
3545
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3546
+ }
3547
+
3548
+ @keyframes playkit-recharge-fadeIn {
3549
+ from {
3550
+ opacity: 0;
3551
+ }
3552
+ to {
3553
+ opacity: 1;
3554
+ }
3555
+ }
3556
+
3557
+ .playkit-recharge-modal {
3558
+ background: #fff;
3559
+ border: 1px solid rgba(0, 0, 0, 0.1);
3560
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
3561
+ padding: 24px;
3562
+ max-width: 320px;
3563
+ width: 90%;
3564
+ position: relative;
3565
+ text-align: center;
3566
+ }
3567
+
3568
+ .playkit-recharge-title {
3569
+ font-size: 14px;
3570
+ font-weight: 600;
3571
+ color: #171717;
3572
+ margin: 0 0 8px 0;
3573
+ text-align: center;
3574
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3575
+ }
3576
+
3577
+ .playkit-recharge-message {
3578
+ font-size: 14px;
3579
+ color: #666;
3580
+ margin: 0 0 20px 0;
3581
+ text-align: center;
3582
+ line-height: 1.5;
3583
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3584
+ }
3585
+
3586
+ .playkit-recharge-balance {
3587
+ background: #f5f5f5;
3588
+ border: 1px solid #e5e7eb;
3589
+ padding: 16px;
3590
+ margin: 0 0 20px 0;
3591
+ text-align: center;
3592
+ }
3593
+
3594
+ .playkit-recharge-balance-label {
3595
+ font-size: 12px;
3596
+ color: #666;
3597
+ margin: 0 0 8px 0;
3598
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3599
+ }
3600
+
3601
+ .playkit-recharge-balance-value {
3602
+ font-size: 24px;
3603
+ font-weight: bold;
3604
+ color: #171717;
3605
+ margin: 0;
3606
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3607
+ }
3608
+
3609
+ .playkit-recharge-balance-unit {
3610
+ font-size: 14px;
3611
+ color: #666;
3612
+ margin-left: 4px;
3613
+ }
3614
+
3615
+ .playkit-recharge-buttons {
3616
+ display: flex;
3617
+ flex-direction: column;
3618
+ gap: 8px;
3619
+ }
3620
+
3621
+ .playkit-recharge-button {
3622
+ width: 100%;
3623
+ padding: 10px 16px;
3624
+ border: none;
3625
+ font-size: 14px;
3626
+ font-weight: 500;
3627
+ cursor: pointer;
3628
+ transition: all 0.2s ease;
3629
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3630
+ }
3631
+
3632
+ .playkit-recharge-button-primary {
3633
+ background: #171717;
3634
+ color: white;
3635
+ }
3636
+
3637
+ .playkit-recharge-button-primary:hover {
3638
+ background: #404040;
3639
+ }
3640
+
3641
+ .playkit-recharge-button-primary:active {
3642
+ background: #0a0a0a;
3643
+ }
3644
+
3645
+ .playkit-recharge-button-secondary {
3646
+ background: transparent;
3647
+ color: #666;
3648
+ border: 1px solid #e5e7eb;
3649
+ }
3650
+
3651
+ .playkit-recharge-button-secondary:hover {
3652
+ background: #f5f5f5;
3653
+ border-color: #d4d4d4;
3654
+ }
3655
+
3656
+ .playkit-recharge-button-secondary:active {
3657
+ background: #e5e5e5;
3658
+ }
3659
+
3660
+ @media (max-width: 480px) {
3661
+ .playkit-recharge-modal {
3662
+ padding: 20px;
3663
+ }
3664
+ }
3665
+
3666
+ /* Daily Refresh Toast Styles */
3667
+ .playkit-daily-refresh-toast {
3668
+ position: fixed;
3669
+ top: 20px;
3670
+ right: 20px;
3671
+ background: #fff;
3672
+ border: 1px solid rgba(0, 0, 0, 0.1);
3673
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.1);
3674
+ padding: 16px 20px;
3675
+ min-width: 240px;
3676
+ max-width: 320px;
3677
+ z-index: 999998;
3678
+ animation: playkit-toast-slideIn 0.3s ease-out;
3679
+ display: flex;
3680
+ align-items: flex-start;
3681
+ gap: 12px;
3682
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3683
+ }
3684
+
3685
+ .playkit-daily-refresh-toast.hiding {
3686
+ animation: playkit-toast-fadeOut 0.3s ease-out forwards;
3687
+ }
3688
+
3689
+ @keyframes playkit-toast-slideIn {
3690
+ from {
3691
+ transform: translateX(100%);
3692
+ opacity: 0;
3693
+ }
3694
+ to {
3695
+ transform: translateX(0);
3696
+ opacity: 1;
3697
+ }
3698
+ }
3699
+
3700
+ @keyframes playkit-toast-fadeOut {
3701
+ from {
3702
+ transform: translateX(0);
3703
+ opacity: 1;
3704
+ }
3705
+ to {
3706
+ transform: translateX(100%);
3707
+ opacity: 0;
3708
+ }
3709
+ }
3710
+
3711
+ .playkit-toast-icon {
3712
+ width: 24px;
3713
+ height: 24px;
3714
+ background: #171717;
3715
+ border-radius: 50%;
3716
+ display: flex;
3717
+ align-items: center;
3718
+ justify-content: center;
3719
+ flex-shrink: 0;
3720
+ }
3721
+
3722
+ .playkit-toast-icon svg {
3723
+ width: 14px;
3724
+ height: 14px;
3725
+ color: #ffffff;
3726
+ }
3727
+
3728
+ .playkit-toast-message {
3729
+ flex: 1;
3730
+ font-size: 14px;
3731
+ font-weight: 500;
3732
+ color: #171717;
3733
+ line-height: 1.4;
3734
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3735
+ }
3736
+
3737
+ @media (max-width: 480px) {
3738
+ .playkit-daily-refresh-toast {
3739
+ top: 10px;
3740
+ right: 10px;
3741
+ left: 10px;
3742
+ min-width: auto;
3743
+ max-width: none;
3744
+ }
3745
+ }
3744
3746
  `;
3745
3747
  document.head.appendChild(this.styleElement);
3746
3748
  }
@@ -3877,7 +3879,8 @@
3877
3879
  /**
3878
3880
  * Player client for managing player information and credits
3879
3881
  */
3880
- const DEFAULT_BASE_URL$4 = 'https://playkit.ai';
3882
+ // @ts-ignore - replaced at build time
3883
+ const DEFAULT_BASE_URL$5 = "https://api.playkit.ai";
3881
3884
  const PLAYER_INFO_ENDPOINT = '/api/external/player-info';
3882
3885
  const SET_NICKNAME_ENDPOINT = '/api/external/set-game-player-nickname';
3883
3886
  class PlayerClient extends EventEmitter {
@@ -3889,13 +3892,13 @@
3889
3892
  this.balanceCheckInterval = null;
3890
3893
  this.logger = Logger.getLogger('PlayerClient');
3891
3894
  this.authManager = authManager;
3892
- this.baseURL = config.baseURL || DEFAULT_BASE_URL$4;
3895
+ this.baseURL = config.baseURL || DEFAULT_BASE_URL$5;
3893
3896
  this.gameId = config.gameId;
3894
3897
  this.rechargeConfig = {
3895
3898
  autoShowBalanceModal: (_a = rechargeConfig.autoShowBalanceModal) !== null && _a !== void 0 ? _a : true,
3896
3899
  balanceCheckInterval: (_b = rechargeConfig.balanceCheckInterval) !== null && _b !== void 0 ? _b : 30000,
3897
3900
  checkBalanceAfterApiCall: (_c = rechargeConfig.checkBalanceAfterApiCall) !== null && _c !== void 0 ? _c : true,
3898
- rechargePortalUrl: rechargeConfig.rechargePortalUrl || 'https://playkit.ai/recharge',
3901
+ rechargePortalUrl: rechargeConfig.rechargePortalUrl || 'https://players.playkit.ai/recharge',
3899
3902
  showDailyRefreshToast: (_d = rechargeConfig.showDailyRefreshToast) !== null && _d !== void 0 ? _d : true,
3900
3903
  };
3901
3904
  }
@@ -3910,9 +3913,7 @@
3910
3913
  }
3911
3914
  try {
3912
3915
  // Build headers with X-Game-Id to support Global Developer Token
3913
- const headers = {
3914
- Authorization: `Bearer ${token}`,
3915
- };
3916
+ const headers = Object.assign({ Authorization: `Bearer ${token}` }, getSDKHeaders());
3916
3917
  if (this.gameId) {
3917
3918
  headers['X-Game-Id'] = this.gameId;
3918
3919
  }
@@ -4012,10 +4013,7 @@
4012
4013
  try {
4013
4014
  const response = await fetch(`${this.baseURL}${SET_NICKNAME_ENDPOINT}`, {
4014
4015
  method: 'POST',
4015
- headers: {
4016
- Authorization: `Bearer ${token}`,
4017
- 'Content-Type': 'application/json',
4018
- },
4016
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
4019
4017
  body: JSON.stringify({ nickname: trimmed }),
4020
4018
  });
4021
4019
  if (!response.ok) {
@@ -4165,15 +4163,89 @@
4165
4163
  }
4166
4164
  }
4167
4165
 
4166
+ const VALID_PART_TYPES = new Set([
4167
+ 'text',
4168
+ 'image',
4169
+ 'image_url',
4170
+ 'file',
4171
+ 'audio',
4172
+ 'input_audio',
4173
+ ]);
4174
+ function describePart(part) {
4175
+ if (part === null)
4176
+ return 'null';
4177
+ if (typeof part !== 'object')
4178
+ return typeof part;
4179
+ const keys = Object.keys(part).slice(0, 5).join(',');
4180
+ return `{${keys}}`;
4181
+ }
4182
+ /**
4183
+ * Validate that `messages` matches the SDK's `Message[]` runtime contract before
4184
+ * shipping to the chat API. Throws `PlayKitError('INVALID_MESSAGES')` when a
4185
+ * caller has wrapped a Message[] inside one user message's `content` (the
4186
+ * `[{role:'user', content: [{role,...}, ...]}]` anti-pattern that bypasses the
4187
+ * `MessageContentPart` type at runtime).
4188
+ *
4189
+ * Does NOT auto-flatten — silently guessing system/user roles would mask bugs.
4190
+ */
4191
+ function assertValidMessages(messages) {
4192
+ if (!Array.isArray(messages)) {
4193
+ throw new PlayKitError('messages must be an array of Message', 'INVALID_MESSAGES');
4194
+ }
4195
+ for (let i = 0; i < messages.length; i++) {
4196
+ const msg = messages[i];
4197
+ if (!msg || typeof msg !== 'object') {
4198
+ throw new PlayKitError(`messages[${i}] must be an object with {role, content}`, 'INVALID_MESSAGES');
4199
+ }
4200
+ const content = msg.content;
4201
+ if (typeof content === 'string' || content == null)
4202
+ continue;
4203
+ if (!Array.isArray(content)) {
4204
+ throw new PlayKitError(`messages[${i}].content must be a string or an array of content parts (got ${typeof content})`, 'INVALID_MESSAGES');
4205
+ }
4206
+ for (let j = 0; j < content.length; j++) {
4207
+ const part = content[j];
4208
+ if (!part || typeof part !== 'object') {
4209
+ throw new PlayKitError(`messages[${i}].content[${j}] must be a content part object (got ${typeof part})`, 'INVALID_MESSAGES');
4210
+ }
4211
+ const hasType = typeof part.type === 'string' && VALID_PART_TYPES.has(part.type);
4212
+ if (!hasType) {
4213
+ if ('role' in part && 'content' in part) {
4214
+ throw new PlayKitError(`messages[${i}].content[${j}] is shaped like a Message (has role/content) ` +
4215
+ `but content parts must be {type:'text'|'image'|'image_url'|'file'|'audio'|'input_audio',...}. ` +
4216
+ `Did you mean to pass that array as messages directly? ` +
4217
+ `e.g. \`messages: theArray\` instead of \`messages: [{role:'user', content: theArray}]\`. ` +
4218
+ `Got part ${describePart(part)}`, 'INVALID_MESSAGES');
4219
+ }
4220
+ throw new PlayKitError(`messages[${i}].content[${j}] is missing a recognized 'type' field ` +
4221
+ `(expected one of text|image|image_url|file|audio|input_audio). Got part ${describePart(part)}`, 'INVALID_MESSAGES');
4222
+ }
4223
+ }
4224
+ }
4225
+ }
4226
+
4168
4227
  /**
4169
4228
  * Chat provider for HTTP communication with chat API
4170
4229
  */
4171
- const DEFAULT_BASE_URL$3 = 'https://playkit.ai';
4230
+ /**
4231
+ * Helper to extract string from MessageContent
4232
+ */
4233
+ function contentToString$1(content) {
4234
+ if (!content)
4235
+ return '';
4236
+ if (typeof content === 'string')
4237
+ return content;
4238
+ // For array of content parts, extract text parts
4239
+ const textParts = content.filter(part => part.type === 'text');
4240
+ return textParts.map(part => part.text).join('');
4241
+ }
4242
+ // @ts-ignore - replaced at build time
4243
+ const DEFAULT_BASE_URL$4 = "https://api.playkit.ai";
4172
4244
  class ChatProvider {
4173
4245
  constructor(authManager, config) {
4174
4246
  this.authManager = authManager;
4175
4247
  this.config = config;
4176
- this.baseURL = config.baseURL || DEFAULT_BASE_URL$3;
4248
+ this.baseURL = config.baseURL || DEFAULT_BASE_URL$4;
4177
4249
  }
4178
4250
  /**
4179
4251
  * Set player client for balance checking
@@ -4186,6 +4258,7 @@
4186
4258
  */
4187
4259
  async chatCompletion(chatConfig) {
4188
4260
  var _a;
4261
+ assertValidMessages(chatConfig.messages);
4189
4262
  // Ensure token is valid, auto-refresh if needed (browser mode only)
4190
4263
  await this.authManager.ensureValidToken();
4191
4264
  const token = this.authManager.getToken();
@@ -4207,10 +4280,7 @@
4207
4280
  try {
4208
4281
  const response = await fetch(`${this.baseURL}${endpoint}`, {
4209
4282
  method: 'POST',
4210
- headers: {
4211
- Authorization: `Bearer ${token}`,
4212
- 'Content-Type': 'application/json',
4213
- },
4283
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
4214
4284
  body: JSON.stringify(requestBody),
4215
4285
  });
4216
4286
  if (!response.ok) {
@@ -4245,6 +4315,7 @@
4245
4315
  */
4246
4316
  async chatCompletionStream(chatConfig) {
4247
4317
  var _a;
4318
+ assertValidMessages(chatConfig.messages);
4248
4319
  // Ensure token is valid, auto-refresh if needed (browser mode only)
4249
4320
  await this.authManager.ensureValidToken();
4250
4321
  const token = this.authManager.getToken();
@@ -4266,10 +4337,7 @@
4266
4337
  try {
4267
4338
  const response = await fetch(`${this.baseURL}${endpoint}`, {
4268
4339
  method: 'POST',
4269
- headers: {
4270
- Authorization: `Bearer ${token}`,
4271
- 'Content-Type': 'application/json',
4272
- },
4340
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
4273
4341
  body: JSON.stringify(requestBody),
4274
4342
  });
4275
4343
  if (!response.ok) {
@@ -4306,6 +4374,7 @@
4306
4374
  */
4307
4375
  async chatCompletionWithTools(chatConfig) {
4308
4376
  var _a, _b;
4377
+ assertValidMessages(chatConfig.messages);
4309
4378
  const token = this.authManager.getToken();
4310
4379
  if (!token) {
4311
4380
  throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
@@ -4332,10 +4401,7 @@
4332
4401
  try {
4333
4402
  const response = await fetch(`${this.baseURL}${endpoint}`, {
4334
4403
  method: 'POST',
4335
- headers: {
4336
- Authorization: `Bearer ${token}`,
4337
- 'Content-Type': 'application/json',
4338
- },
4404
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
4339
4405
  body: JSON.stringify(requestBody),
4340
4406
  });
4341
4407
  if (!response.ok) {
@@ -4366,6 +4432,7 @@
4366
4432
  */
4367
4433
  async chatCompletionWithToolsStream(chatConfig) {
4368
4434
  var _a, _b;
4435
+ assertValidMessages(chatConfig.messages);
4369
4436
  const token = this.authManager.getToken();
4370
4437
  if (!token) {
4371
4438
  throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
@@ -4392,10 +4459,7 @@
4392
4459
  try {
4393
4460
  const response = await fetch(`${this.baseURL}${endpoint}`, {
4394
4461
  method: 'POST',
4395
- headers: {
4396
- Authorization: `Bearer ${token}`,
4397
- 'Content-Type': 'application/json',
4398
- },
4462
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
4399
4463
  body: JSON.stringify(requestBody),
4400
4464
  });
4401
4465
  if (!response.ok) {
@@ -4465,10 +4529,7 @@
4465
4529
  try {
4466
4530
  const response = await fetch(`${this.baseURL}${endpoint}`, {
4467
4531
  method: 'POST',
4468
- headers: {
4469
- Authorization: `Bearer ${token}`,
4470
- 'Content-Type': 'application/json',
4471
- },
4532
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
4472
4533
  body: JSON.stringify(requestBody),
4473
4534
  });
4474
4535
  if (!response.ok) {
@@ -4486,11 +4547,12 @@
4486
4547
  this.playerClient.checkBalanceAfterApiCall().catch(() => { });
4487
4548
  }
4488
4549
  // Parse the response content as JSON
4489
- const content = (_a = result.choices[0]) === null || _a === void 0 ? void 0 : _a.message.content;
4490
- if (!content) {
4550
+ const rawContent = (_a = result.choices[0]) === null || _a === void 0 ? void 0 : _a.message.content;
4551
+ if (!rawContent) {
4491
4552
  throw new PlayKitError('No content in response', 'NO_CONTENT');
4492
4553
  }
4493
4554
  try {
4555
+ const content = contentToString$1(rawContent);
4494
4556
  return JSON.parse(content);
4495
4557
  }
4496
4558
  catch (parseError) {
@@ -4509,12 +4571,13 @@
4509
4571
  /**
4510
4572
  * Image generation provider for HTTP communication with image API
4511
4573
  */
4512
- const DEFAULT_BASE_URL$2 = 'https://playkit.ai';
4574
+ // @ts-ignore - replaced at build time
4575
+ const DEFAULT_BASE_URL$3 = "https://api.playkit.ai";
4513
4576
  class ImageProvider {
4514
4577
  constructor(authManager, config) {
4515
4578
  this.authManager = authManager;
4516
4579
  this.config = config;
4517
- this.baseURL = config.baseURL || DEFAULT_BASE_URL$2;
4580
+ this.baseURL = config.baseURL || DEFAULT_BASE_URL$3;
4518
4581
  }
4519
4582
  /**
4520
4583
  * Set player client for balance checking
@@ -4562,10 +4625,7 @@
4562
4625
  try {
4563
4626
  const response = await fetch(`${this.baseURL}${endpoint}`, {
4564
4627
  method: 'POST',
4565
- headers: {
4566
- Authorization: `Bearer ${token}`,
4567
- 'Content-Type': 'application/json',
4568
- },
4628
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
4569
4629
  body: JSON.stringify(requestBody),
4570
4630
  });
4571
4631
  if (!response.ok) {
@@ -4600,12 +4660,13 @@
4600
4660
  /**
4601
4661
  * Transcription provider for HTTP communication with audio transcription API
4602
4662
  */
4603
- const DEFAULT_BASE_URL$1 = 'https://playkit.ai';
4663
+ // @ts-ignore - replaced at build time
4664
+ const DEFAULT_BASE_URL$2 = "https://api.playkit.ai";
4604
4665
  class TranscriptionProvider {
4605
4666
  constructor(authManager, config) {
4606
4667
  this.authManager = authManager;
4607
4668
  this.config = config;
4608
- this.baseURL = config.baseURL || DEFAULT_BASE_URL$1;
4669
+ this.baseURL = config.baseURL || DEFAULT_BASE_URL$2;
4609
4670
  }
4610
4671
  /**
4611
4672
  * Set player client for balance checking
@@ -4665,10 +4726,7 @@
4665
4726
  try {
4666
4727
  const response = await fetch(`${this.baseURL}${endpoint}`, {
4667
4728
  method: 'POST',
4668
- headers: {
4669
- Authorization: `Bearer ${token}`,
4670
- 'Content-Type': 'application/json',
4671
- },
4729
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
4672
4730
  body: JSON.stringify(requestBody),
4673
4731
  });
4674
4732
  if (!response.ok) {
@@ -4702,73 +4760,183 @@
4702
4760
  }
4703
4761
  }
4704
4762
 
4705
- /******************************************************************************
4706
- Copyright (c) Microsoft Corporation.
4707
-
4708
- Permission to use, copy, modify, and/or distribute this software for any
4709
- purpose with or without fee is hereby granted.
4710
-
4711
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
4712
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
4713
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
4714
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
4715
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
4716
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
4717
- PERFORMANCE OF THIS SOFTWARE.
4718
- ***************************************************************************** */
4719
- /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
4720
-
4721
-
4722
- function __values(o) {
4723
- var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
4724
- if (m) return m.call(o);
4725
- if (o && typeof o.length === "number") return {
4726
- next: function () {
4727
- if (o && i >= o.length) o = void 0;
4728
- return { value: o && o[i++], done: !o };
4729
- }
4730
- };
4731
- throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
4732
- }
4733
-
4734
- function __await(v) {
4735
- return this instanceof __await ? (this.v = v, this) : new __await(v);
4736
- }
4737
-
4738
- function __asyncGenerator(thisArg, _arguments, generator) {
4739
- if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
4740
- var g = generator.apply(thisArg, _arguments || []), i, q = [];
4741
- return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
4742
- function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
4743
- function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
4744
- function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
4745
- function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
4746
- function fulfill(value) { resume("next", value); }
4747
- function reject(value) { resume("throw", value); }
4748
- function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
4749
- }
4750
-
4751
- function __asyncValues(o) {
4752
- if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
4753
- var m = o[Symbol.asyncIterator], i;
4754
- return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
4755
- function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
4756
- function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
4757
- }
4758
-
4759
- typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
4760
- var e = new Error(message);
4761
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
4762
- };
4763
-
4764
- /**
4765
- * Server-Sent Events (SSE) stream parser
4766
- * Handles parsing of streaming text responses
4767
- */
4768
4763
  /**
4769
- * Create a cross-platform text decoder
4764
+ * TTS provider for HTTP communication with the text-to-speech API
4770
4765
  */
4771
- function createDecoder() {
4766
+ // @ts-ignore - replaced at build time
4767
+ const DEFAULT_BASE_URL$1 = "https://api.playkit.ai";
4768
+ class TTSProvider {
4769
+ constructor(authManager, config) {
4770
+ this.authManager = authManager;
4771
+ this.config = config;
4772
+ this.baseURL = config.baseURL || DEFAULT_BASE_URL$1;
4773
+ }
4774
+ /**
4775
+ * Set player client for balance checking
4776
+ */
4777
+ setPlayerClient(playerClient) {
4778
+ this.playerClient = playerClient;
4779
+ }
4780
+ /**
4781
+ * Synthesize text into speech audio
4782
+ */
4783
+ async synthesize(ttsConfig) {
4784
+ // Ensure token is valid, auto-refresh if needed (browser mode only)
4785
+ await this.authManager.ensureValidToken();
4786
+ const token = this.authManager.getToken();
4787
+ if (!token) {
4788
+ throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
4789
+ }
4790
+ const model = ttsConfig.model || this.config.defaultTTSModel || 'default-tts-model';
4791
+ const endpoint = `/ai/${this.config.gameId}/v2/audio/speech`;
4792
+ const requestBody = {
4793
+ model,
4794
+ text: ttsConfig.text,
4795
+ };
4796
+ // Add optional parameters (only when defined)
4797
+ if (ttsConfig.voice !== undefined) {
4798
+ requestBody.voice = ttsConfig.voice;
4799
+ }
4800
+ if (ttsConfig.speed !== undefined) {
4801
+ requestBody.speed = ttsConfig.speed;
4802
+ }
4803
+ if (ttsConfig.vol !== undefined) {
4804
+ requestBody.vol = ttsConfig.vol;
4805
+ }
4806
+ if (ttsConfig.pitch !== undefined) {
4807
+ requestBody.pitch = ttsConfig.pitch;
4808
+ }
4809
+ if (ttsConfig.emotion !== undefined) {
4810
+ requestBody.emotion = ttsConfig.emotion;
4811
+ }
4812
+ if (ttsConfig.languageBoost !== undefined) {
4813
+ requestBody.language_boost = ttsConfig.languageBoost;
4814
+ }
4815
+ if (ttsConfig.format !== undefined) {
4816
+ requestBody.response_format = ttsConfig.format;
4817
+ }
4818
+ if (ttsConfig.voiceSetting !== undefined) {
4819
+ requestBody.voice_setting = ttsConfig.voiceSetting;
4820
+ }
4821
+ if (ttsConfig.audioSetting !== undefined) {
4822
+ requestBody.audio_setting = ttsConfig.audioSetting;
4823
+ }
4824
+ try {
4825
+ const response = await fetch(`${this.baseURL}${endpoint}`, {
4826
+ method: 'POST',
4827
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
4828
+ body: JSON.stringify(requestBody),
4829
+ });
4830
+ if (!response.ok) {
4831
+ const error = await response.json().catch(() => ({ message: 'Speech synthesis failed' }));
4832
+ const playKitError = new PlayKitError(error.message || 'Speech synthesis failed', error.code, response.status);
4833
+ // Check for insufficient credits error
4834
+ if (error.code === 'INSUFFICIENT_CREDITS' ||
4835
+ error.code === 'PLAYER_INSUFFICIENT_CREDIT' ||
4836
+ response.status === 402) {
4837
+ if (this.playerClient) {
4838
+ await this.playerClient.handleInsufficientCredits(playKitError);
4839
+ }
4840
+ }
4841
+ throw playKitError;
4842
+ }
4843
+ // SUCCESS: response is raw audio bytes, NOT JSON.
4844
+ const audio = await response.arrayBuffer();
4845
+ const contentType = response.headers.get('Content-Type');
4846
+ const usageHeader = response.headers.get('X-Usage-Characters');
4847
+ const audioLengthHeader = response.headers.get('X-Audio-Length-Ms');
4848
+ const result = {
4849
+ audio,
4850
+ format: contentType || ttsConfig.format || 'mp3',
4851
+ usageCharacters: Number(usageHeader) || 0,
4852
+ };
4853
+ if (audioLengthHeader !== null) {
4854
+ result.audioLengthMs = Number(audioLengthHeader) || 0;
4855
+ }
4856
+ // Check balance after successful API call
4857
+ if (this.playerClient) {
4858
+ this.playerClient.checkBalanceAfterApiCall().catch(() => {
4859
+ // Silently fail
4860
+ });
4861
+ }
4862
+ return result;
4863
+ }
4864
+ catch (error) {
4865
+ if (error instanceof PlayKitError) {
4866
+ throw error;
4867
+ }
4868
+ throw new PlayKitError(error instanceof Error ? error.message : 'Unknown error', 'TTS_ERROR');
4869
+ }
4870
+ }
4871
+ }
4872
+
4873
+ /******************************************************************************
4874
+ Copyright (c) Microsoft Corporation.
4875
+
4876
+ Permission to use, copy, modify, and/or distribute this software for any
4877
+ purpose with or without fee is hereby granted.
4878
+
4879
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
4880
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
4881
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
4882
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
4883
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
4884
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
4885
+ PERFORMANCE OF THIS SOFTWARE.
4886
+ ***************************************************************************** */
4887
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
4888
+
4889
+
4890
+ function __values(o) {
4891
+ var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
4892
+ if (m) return m.call(o);
4893
+ if (o && typeof o.length === "number") return {
4894
+ next: function () {
4895
+ if (o && i >= o.length) o = void 0;
4896
+ return { value: o && o[i++], done: !o };
4897
+ }
4898
+ };
4899
+ throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
4900
+ }
4901
+
4902
+ function __await(v) {
4903
+ return this instanceof __await ? (this.v = v, this) : new __await(v);
4904
+ }
4905
+
4906
+ function __asyncGenerator(thisArg, _arguments, generator) {
4907
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
4908
+ var g = generator.apply(thisArg, _arguments || []), i, q = [];
4909
+ return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
4910
+ function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
4911
+ function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
4912
+ function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
4913
+ function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
4914
+ function fulfill(value) { resume("next", value); }
4915
+ function reject(value) { resume("throw", value); }
4916
+ function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
4917
+ }
4918
+
4919
+ function __asyncValues(o) {
4920
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
4921
+ var m = o[Symbol.asyncIterator], i;
4922
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
4923
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
4924
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
4925
+ }
4926
+
4927
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
4928
+ var e = new Error(message);
4929
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
4930
+ };
4931
+
4932
+ /**
4933
+ * Server-Sent Events (SSE) stream parser
4934
+ * Handles parsing of streaming text responses
4935
+ */
4936
+ /**
4937
+ * Create a cross-platform text decoder
4938
+ */
4939
+ function createDecoder() {
4772
4940
  if (typeof TextDecoder !== 'undefined') {
4773
4941
  return new TextDecoder();
4774
4942
  }
@@ -4814,9 +4982,18 @@
4814
4982
  if (text) {
4815
4983
  yield yield __await(text);
4816
4984
  }
4817
- if (parsed.type === 'done' || parsed.finish_reason) {
4985
+ // Stream termination events
4986
+ if (parsed.type === 'done' || parsed.type === 'finish' || parsed.finish_reason) {
4987
+ return yield __await(void 0);
4988
+ }
4989
+ if (parsed.type === 'abort') {
4990
+ // Server-side timeout or cancellation — treat as end of stream
4818
4991
  return yield __await(void 0);
4819
4992
  }
4993
+ if (parsed.type === 'error') {
4994
+ // Server-side error event — throw to trigger onError callback
4995
+ throw new Error(parsed.errorText || parsed.error || 'Stream error');
4996
+ }
4820
4997
  }
4821
4998
  catch (error) {
4822
4999
  // If JSON parse fails, treat as plain text
@@ -4915,6 +5092,18 @@
4915
5092
  /**
4916
5093
  * Chat client for AI text generation
4917
5094
  */
5095
+ /**
5096
+ * Helper to extract string from MessageContent
5097
+ */
5098
+ function contentToString(content) {
5099
+ if (!content)
5100
+ return '';
5101
+ if (typeof content === 'string')
5102
+ return content;
5103
+ // For array of content parts, extract text parts
5104
+ const textParts = content.filter(part => part.type === 'text');
5105
+ return textParts.map(part => part.text).join('');
5106
+ }
4918
5107
  class ChatClient {
4919
5108
  constructor(provider, model) {
4920
5109
  this.schemaLibrary = null;
@@ -4964,7 +5153,7 @@
4964
5153
  throw new Error('No choices in response');
4965
5154
  }
4966
5155
  return {
4967
- content: choice.message.content,
5156
+ content: contentToString(choice.message.content),
4968
5157
  model: response.model,
4969
5158
  finishReason: choice.finish_reason,
4970
5159
  usage: response.usage
@@ -5035,9 +5224,10 @@
5035
5224
  }
5036
5225
  // Extract user message content from the last user message
5037
5226
  const lastUserMessage = [...messages].reverse().find(m => m.role === 'user');
5038
- const prompt = (lastUserMessage === null || lastUserMessage === void 0 ? void 0 : lastUserMessage.content) || '';
5227
+ const prompt = contentToString(lastUserMessage === null || lastUserMessage === void 0 ? void 0 : lastUserMessage.content);
5039
5228
  // Build system message from messages array
5040
- const systemMessage = (_a = messages.find(m => m.role === 'system')) === null || _a === void 0 ? void 0 : _a.content;
5229
+ const systemMessageContent = (_a = messages.find(m => m.role === 'system')) === null || _a === void 0 ? void 0 : _a.content;
5230
+ const systemMessage = contentToString(systemMessageContent) || undefined;
5041
5231
  return this.generateStructuredWithSchema(schemaEntry.schema, prompt, Object.assign({ schemaName, schemaDescription: schemaEntry.description, systemMessage }, options));
5042
5232
  }
5043
5233
  /**
@@ -5128,7 +5318,7 @@
5128
5318
  throw new Error('No choices in response');
5129
5319
  }
5130
5320
  return {
5131
- content: choice.message.content || '',
5321
+ content: contentToString(choice.message.content),
5132
5322
  model: response.model,
5133
5323
  finishReason: choice.finish_reason,
5134
5324
  usage: response.usage
@@ -5192,7 +5382,7 @@
5192
5382
  return new Promise((resolve, reject) => {
5193
5383
  const img = new Image();
5194
5384
  img.onload = () => resolve(img);
5195
- img.onerror = (e) => reject(new Error('Failed to load image'));
5385
+ img.onerror = (_e) => reject(new Error('Failed to load image'));
5196
5386
  img.src = this.toDataURL();
5197
5387
  });
5198
5388
  }
@@ -5206,13 +5396,14 @@
5206
5396
  * Generate a single image
5207
5397
  */
5208
5398
  async generateImage(config) {
5399
+ var _a;
5209
5400
  const imageConfig = Object.assign(Object.assign({}, config), { model: config.model || this.model, n: 1 });
5210
5401
  const response = await this.provider.generateImages(imageConfig);
5211
5402
  const imageData = response.data[0];
5212
5403
  if (!imageData || !imageData.b64_json) {
5213
5404
  throw new Error('No image data in response');
5214
5405
  }
5215
- return new GeneratedImageImpl(imageData.b64_json, config.prompt, imageData.revised_prompt, config.size, imageData.b64_json_original, imageData.transparent_success);
5406
+ return new GeneratedImageImpl(imageData.b64_json, config.prompt, (_a = imageData.revised_prompt) !== null && _a !== void 0 ? _a : config.prompt, config.size, imageData.b64_json_original, imageData.transparent_success);
5216
5407
  }
5217
5408
  /**
5218
5409
  * Generate multiple images
@@ -5221,10 +5412,11 @@
5221
5412
  const imageConfig = Object.assign(Object.assign({}, config), { model: config.model || this.model, n: config.n || 1 });
5222
5413
  const response = await this.provider.generateImages(imageConfig);
5223
5414
  return response.data.map((imageData) => {
5415
+ var _a;
5224
5416
  if (!imageData.b64_json) {
5225
5417
  throw new Error('No image data in response');
5226
5418
  }
5227
- return new GeneratedImageImpl(imageData.b64_json, config.prompt, imageData.revised_prompt, config.size, imageData.b64_json_original, imageData.transparent_success);
5419
+ return new GeneratedImageImpl(imageData.b64_json, config.prompt, (_a = imageData.revised_prompt) !== null && _a !== void 0 ? _a : config.prompt, config.size, imageData.b64_json_original, imageData.transparent_success);
5228
5420
  });
5229
5421
  }
5230
5422
  /**
@@ -5343,1019 +5535,1191 @@
5343
5535
  }
5344
5536
 
5345
5537
  /**
5346
- * NPC Client for simplified conversation management
5347
- * Automatically handles conversation history
5348
- *
5349
- * Key Features:
5350
- * - Call talk() for all interactions - actions are handled automatically
5351
- * - Memory system for persistent NPC context
5352
- * - Reply prediction for suggesting player responses
5353
- * - Automatic conversation history management
5538
+ * High-level client for text-to-speech synthesis
5354
5539
  */
5355
- class NPCClient extends EventEmitter {
5356
- constructor(chatClient, config) {
5357
- var _a, _b, _c;
5358
- super();
5359
- this._isTalking = false;
5360
- this.logger = Logger.getLogger('NPCClient');
5361
- this.chatClient = chatClient;
5362
- // Support both characterDesign and legacy systemPrompt
5363
- this.characterDesign = (config === null || config === void 0 ? void 0 : config.characterDesign) || (config === null || config === void 0 ? void 0 : config.systemPrompt) || 'You are a helpful assistant.';
5364
- this.temperature = (_a = config === null || config === void 0 ? void 0 : config.temperature) !== null && _a !== void 0 ? _a : 0.7;
5365
- this.maxHistoryLength = (config === null || config === void 0 ? void 0 : config.maxHistoryLength) || 50;
5366
- this.generateReplyPrediction = (_b = config === null || config === void 0 ? void 0 : config.generateReplyPrediction) !== null && _b !== void 0 ? _b : false;
5367
- this.predictionCount = Math.max(2, Math.min(6, (_c = config === null || config === void 0 ? void 0 : config.predictionCount) !== null && _c !== void 0 ? _c : 4));
5368
- this.fastModel = config === null || config === void 0 ? void 0 : config.fastModel;
5369
- this.history = [];
5370
- this.memories = new Map();
5540
+ /**
5541
+ * Resolve an audio format/content-type string into a valid MIME type.
5542
+ * The provider's `result.format` may be a full MIME (e.g. 'audio/mpeg' from
5543
+ * the Content-Type header) or a bare token (e.g. 'mp3' from the fallback).
5544
+ * Full MIME strings pass through; bare tokens are mapped.
5545
+ */
5546
+ function contentTypeFor(format) {
5547
+ if (format.includes('/')) {
5548
+ return format;
5549
+ }
5550
+ switch (format.toLowerCase()) {
5551
+ case 'mp3':
5552
+ return 'audio/mpeg';
5553
+ case 'wav':
5554
+ return 'audio/wav';
5555
+ case 'ogg':
5556
+ return 'audio/ogg';
5557
+ case 'flac':
5558
+ return 'audio/flac';
5559
+ case 'aac':
5560
+ return 'audio/aac';
5561
+ case 'pcm':
5562
+ return 'audio/pcm';
5563
+ default:
5564
+ return 'audio/mpeg';
5565
+ }
5566
+ }
5567
+ class TTSClient {
5568
+ constructor(provider, model) {
5569
+ this.provider = provider;
5570
+ this.model = model || 'default-tts-model';
5371
5571
  }
5372
- // ===== State Properties =====
5373
5572
  /**
5374
- * Whether the NPC is currently processing a request
5573
+ * Get the current model name
5375
5574
  */
5376
- get isTalking() {
5377
- return this._isTalking;
5575
+ get modelName() {
5576
+ return this.model;
5378
5577
  }
5379
- // ===== Character Design & Memory System =====
5380
5578
  /**
5381
- * Set the character design for the NPC.
5382
- * The system prompt is composed of CharacterDesign + all Memories.
5579
+ * Synthesize text into speech audio
5580
+ * @param config - Full TTS configuration
5581
+ * @returns TTS result containing raw audio bytes and usage metadata
5383
5582
  */
5384
- setCharacterDesign(design) {
5385
- this.characterDesign = design;
5583
+ async synthesize(config) {
5584
+ return this.provider.synthesize(Object.assign(Object.assign({}, config), { model: config.model || this.model }));
5386
5585
  }
5387
5586
  /**
5388
- * Get the current character design
5587
+ * Synthesize text into speech and return it as a Blob (browser-friendly)
5588
+ * @param config - Full TTS configuration
5589
+ * @returns Audio Blob with the appropriate MIME type
5389
5590
  */
5390
- getCharacterDesign() {
5391
- return this.characterDesign;
5591
+ async synthesizeToBlob(config) {
5592
+ const result = await this.synthesize(config);
5593
+ return new Blob([result.audio], { type: contentTypeFor(result.format) });
5392
5594
  }
5393
5595
  /**
5394
- * @deprecated Use setCharacterDesign instead.
5395
- * This method is kept for backwards compatibility.
5596
+ * Synthesize text into speech and return an object URL (browser only)
5597
+ * @param config - Full TTS configuration
5598
+ * @returns An object URL that can be assigned to an <audio> element
5599
+ * @throws PlayKitError if URL.createObjectURL is unavailable (e.g. Node.js)
5396
5600
  */
5397
- setSystemPrompt(prompt) {
5398
- this.logger.warn('setSystemPrompt is deprecated. Use setCharacterDesign instead.');
5399
- this.setCharacterDesign(prompt);
5601
+ async synthesizeToObjectURL(config) {
5602
+ if (typeof URL === 'undefined' || typeof URL.createObjectURL !== 'function') {
5603
+ throw new PlayKitError('URL.createObjectURL is not available in this environment. ' +
5604
+ 'Use synthesize() to access the raw audio bytes instead.', 'TTS_OBJECT_URL_UNAVAILABLE');
5605
+ }
5606
+ const blob = await this.synthesizeToBlob(config);
5607
+ return URL.createObjectURL(blob);
5608
+ }
5609
+ }
5610
+
5611
+ /**
5612
+ * Global AI Context Manager for managing NPC conversations and player context.
5613
+ *
5614
+ * Features:
5615
+ * - Player description management
5616
+ * - NPC conversation tracking
5617
+ * - Automatic conversation compaction (AutoCompact)
5618
+ */
5619
+ /**
5620
+ * Global AI Context Manager
5621
+ * Manages NPC conversations and player context across the application
5622
+ */
5623
+ class AIContextManager extends EventEmitter {
5624
+ constructor(config) {
5625
+ var _a, _b, _c, _d, _e;
5626
+ super();
5627
+ this.playerDescription = null;
5628
+ this.playerPrompt = null;
5629
+ this.playerMemories = new Map();
5630
+ this.npcStates = new Map();
5631
+ this.autoCompactTimer = null;
5632
+ this.chatClientFactory = null;
5633
+ this.logger = Logger.getLogger('AIContextManager');
5634
+ this.config = {
5635
+ enableAutoCompact: (_a = config === null || config === void 0 ? void 0 : config.enableAutoCompact) !== null && _a !== void 0 ? _a : false,
5636
+ autoCompactMinMessages: (_b = config === null || config === void 0 ? void 0 : config.autoCompactMinMessages) !== null && _b !== void 0 ? _b : 20,
5637
+ autoCompactTimeoutSeconds: (_c = config === null || config === void 0 ? void 0 : config.autoCompactTimeoutSeconds) !== null && _c !== void 0 ? _c : 300,
5638
+ autoCompactCheckInterval: (_d = config === null || config === void 0 ? void 0 : config.autoCompactCheckInterval) !== null && _d !== void 0 ? _d : 60000,
5639
+ fastModel: (_e = config === null || config === void 0 ? void 0 : config.fastModel) !== null && _e !== void 0 ? _e : '',
5640
+ };
5641
+ // Start auto-compact check if enabled
5642
+ if (this.config.enableAutoCompact) {
5643
+ this.startAutoCompactCheck();
5644
+ }
5400
5645
  }
5646
+ // ===== Singleton Pattern =====
5401
5647
  /**
5402
- * @deprecated Use getCharacterDesign instead.
5403
- * This method is kept for backwards compatibility.
5648
+ * Get the singleton instance of AIContextManager
5649
+ * Creates a new instance if one doesn't exist
5404
5650
  */
5405
- getSystemPrompt() {
5406
- return this.buildSystemPrompt();
5651
+ static getInstance(config) {
5652
+ if (!AIContextManager._instance) {
5653
+ AIContextManager._instance = new AIContextManager(config);
5654
+ }
5655
+ return AIContextManager._instance;
5407
5656
  }
5408
5657
  /**
5409
- * Set or update a memory for the NPC.
5410
- * Memories are appended to the character design to form the system prompt.
5411
- * Set memoryContent to null or empty to remove the memory.
5412
- * @param memoryName The name/key of the memory
5413
- * @param memoryContent The content of the memory. Null or empty to remove.
5658
+ * Reset the singleton instance (useful for testing)
5414
5659
  */
5415
- setMemory(memoryName, memoryContent) {
5416
- if (!memoryName) {
5417
- this.logger.warn('Memory name cannot be empty');
5418
- return;
5660
+ static resetInstance() {
5661
+ if (AIContextManager._instance) {
5662
+ AIContextManager._instance.destroy();
5663
+ AIContextManager._instance = null;
5419
5664
  }
5420
- if (!memoryContent) {
5421
- // Remove memory if content is null or empty
5422
- if (this.memories.has(memoryName)) {
5423
- this.memories.delete(memoryName);
5424
- this.emit('memory_removed', memoryName);
5665
+ }
5666
+ // ===== Configuration =====
5667
+ /**
5668
+ * Set the chat client factory for creating chat clients for summarization
5669
+ * Required for compaction to work
5670
+ */
5671
+ setChatClientFactory(factory) {
5672
+ this.chatClientFactory = factory;
5673
+ }
5674
+ /**
5675
+ * Update configuration
5676
+ */
5677
+ setConfig(config) {
5678
+ const wasAutoCompactEnabled = this.config.enableAutoCompact;
5679
+ this.config = Object.assign(Object.assign({}, this.config), config);
5680
+ // Handle auto-compact state change
5681
+ if (config.enableAutoCompact !== undefined) {
5682
+ if (config.enableAutoCompact && !wasAutoCompactEnabled) {
5683
+ this.startAutoCompactCheck();
5425
5684
  }
5685
+ else if (!config.enableAutoCompact && wasAutoCompactEnabled) {
5686
+ this.stopAutoCompactCheck();
5687
+ }
5688
+ }
5689
+ }
5690
+ // ===== Player Description =====
5691
+ /**
5692
+ * Set the player's description for AI context.
5693
+ * Used when generating reply predictions and for NPC context.
5694
+ * @param description Description of the player character
5695
+ */
5696
+ setPlayerDescription(description) {
5697
+ this.playerDescription = description;
5698
+ this.emit('playerDescriptionChanged', description);
5699
+ }
5700
+ /**
5701
+ * Get the current player description.
5702
+ * @returns The player description, or null if not set
5703
+ */
5704
+ getPlayerDescription() {
5705
+ return this.playerDescription;
5706
+ }
5707
+ /**
5708
+ * Clear the player description.
5709
+ */
5710
+ clearPlayerDescription() {
5711
+ this.playerDescription = null;
5712
+ this.emit('playerDescriptionChanged', null);
5713
+ }
5714
+ // ===== Player Prompt & Memory (for Reply Prediction) =====
5715
+ /**
5716
+ * Set the player's character prompt/persona.
5717
+ * This defines how the player character speaks and behaves.
5718
+ * Used when generating reply predictions to match the player's tone.
5719
+ * @param prompt The player character's persona/prompt
5720
+ */
5721
+ setPlayerPrompt(prompt) {
5722
+ this.playerPrompt = prompt;
5723
+ }
5724
+ /**
5725
+ * Get the current player prompt.
5726
+ * @returns The player prompt, or null if not set
5727
+ */
5728
+ getPlayerPrompt() {
5729
+ return this.playerPrompt;
5730
+ }
5731
+ /**
5732
+ * Set or update a memory for the player character.
5733
+ * Memories are appended to the player prompt to form the full player context.
5734
+ * Set memoryContent to null or empty to remove the memory.
5735
+ * @param memoryName The name/key of the memory
5736
+ * @param memoryContent The content of the memory. Null or empty to remove.
5737
+ */
5738
+ setPlayerMemory(memoryName, memoryContent) {
5739
+ if (!memoryName) {
5740
+ this.logger.warn('Memory name cannot be empty');
5741
+ return;
5742
+ }
5743
+ if (!memoryContent) {
5744
+ // Remove memory if content is null or empty
5745
+ this.playerMemories.delete(memoryName);
5426
5746
  }
5427
5747
  else {
5428
5748
  // Add or update memory
5429
- this.memories.set(memoryName, memoryContent);
5430
- this.emit('memory_set', memoryName, memoryContent);
5749
+ this.playerMemories.set(memoryName, memoryContent);
5431
5750
  }
5432
5751
  }
5433
5752
  /**
5434
- * Get a specific memory by name.
5753
+ * Get a specific player memory by name.
5435
5754
  * @param memoryName The name of the memory to retrieve
5436
5755
  * @returns The memory content, or undefined if not found
5437
5756
  */
5438
- getMemory(memoryName) {
5439
- return this.memories.get(memoryName);
5757
+ getPlayerMemory(memoryName) {
5758
+ return this.playerMemories.get(memoryName);
5440
5759
  }
5441
5760
  /**
5442
- * Get all memory names currently stored.
5761
+ * Get all player memory names currently stored.
5443
5762
  * @returns Array of memory names
5444
5763
  */
5445
- getMemoryNames() {
5446
- return Array.from(this.memories.keys());
5764
+ getPlayerMemoryNames() {
5765
+ return Array.from(this.playerMemories.keys());
5447
5766
  }
5448
5767
  /**
5449
- * Clear all memories (but keep character design).
5768
+ * Clear all player memories (but keep player prompt).
5450
5769
  */
5451
- clearMemories() {
5452
- this.memories.clear();
5453
- this.emit('memories_cleared');
5770
+ clearPlayerMemories() {
5771
+ this.playerMemories.clear();
5454
5772
  }
5455
5773
  /**
5456
- * Build the complete system prompt from CharacterDesign + Memories.
5774
+ * Build the complete player context from PlayerPrompt + PlayerMemories.
5775
+ * Used by NPCClient for generating reply predictions.
5776
+ * @returns The combined player context string, or null if no context is set
5457
5777
  */
5458
- buildSystemPrompt() {
5778
+ buildPlayerContext() {
5459
5779
  const parts = [];
5460
- if (this.characterDesign) {
5461
- parts.push(this.characterDesign);
5780
+ if (this.playerPrompt) {
5781
+ parts.push(this.playerPrompt);
5462
5782
  }
5463
- if (this.memories.size > 0) {
5464
- const memoryStrings = Array.from(this.memories.entries())
5783
+ if (this.playerMemories.size > 0) {
5784
+ const memoryStrings = Array.from(this.playerMemories.entries())
5465
5785
  .map(([name, content]) => `[${name}]: ${content}`);
5466
- parts.push('Memories:\n' + memoryStrings.join('\n'));
5786
+ parts.push('Player Memories:\n' + memoryStrings.join('\n'));
5787
+ }
5788
+ if (parts.length === 0) {
5789
+ return null;
5467
5790
  }
5468
5791
  return parts.join('\n\n');
5469
5792
  }
5470
- // ===== Reply Prediction =====
5471
- /**
5472
- * Enable or disable automatic reply prediction
5473
- */
5474
- setGenerateReplyPrediction(enabled) {
5475
- this.generateReplyPrediction = enabled;
5476
- }
5793
+ // ===== NPC Tracking =====
5477
5794
  /**
5478
- * Set the number of predictions to generate
5795
+ * Register an NPC for context management.
5796
+ * @param npc The NPC client to register
5479
5797
  */
5480
- setPredictionCount(count) {
5481
- this.predictionCount = Math.max(2, Math.min(6, count));
5798
+ registerNpc(npc) {
5799
+ if (!npc)
5800
+ return;
5801
+ if (!this.npcStates.has(npc)) {
5802
+ this.npcStates.set(npc, {
5803
+ lastConversationTime: new Date(),
5804
+ isCompacted: false,
5805
+ compactionCount: 0,
5806
+ });
5807
+ }
5482
5808
  }
5483
5809
  /**
5484
- * Manually generate reply predictions based on current conversation.
5485
- * Uses the fast model for quick generation.
5486
- * @param count Number of predictions to generate (default: uses predictionCount property)
5487
- * @returns Array of predicted player replies, or empty array on failure
5810
+ * Unregister an NPC (call when NPC is destroyed/removed).
5811
+ * @param npc The NPC client to unregister
5488
5812
  */
5489
- async generateReplyPredictions(count) {
5490
- var _a;
5491
- const predictionNum = count !== null && count !== void 0 ? count : this.predictionCount;
5492
- if (this.history.length < 2) {
5493
- this.logger.info('Not enough conversation history to generate predictions');
5494
- return [];
5495
- }
5496
- try {
5497
- // Get last NPC message
5498
- const lastNpcMessage = (_a = [...this.history]
5499
- .reverse()
5500
- .find(m => m.role === 'assistant')) === null || _a === void 0 ? void 0 : _a.content;
5501
- if (!lastNpcMessage) {
5502
- this.logger.info('No NPC message found to generate predictions from');
5503
- return [];
5504
- }
5505
- // Build recent history (last 6 non-system messages)
5506
- const recentHistory = this.history
5507
- .filter(m => m.role !== 'system')
5508
- .slice(-6)
5509
- .map(m => `${m.role}: ${m.content}`);
5510
- // Build prompt for prediction generation
5511
- const prompt = `Based on the conversation history below, generate exactly ${predictionNum} natural and contextually appropriate responses that the player might say next.
5512
-
5513
- Context:
5514
- - This is a conversation between a player and an NPC in a game
5515
- - The NPC just said: "${lastNpcMessage}"
5516
-
5517
- Conversation history:
5518
- ${recentHistory.join('\n')}
5519
-
5520
- Requirements:
5521
- 1. Each response should be 1-2 sentences maximum
5522
- 2. Responses should be diverse in tone and intent
5523
- 3. Include a mix of questions, statements, and action-oriented responses
5524
- 4. Responses should feel natural for a player character
5525
-
5526
- Output ONLY a JSON array of ${predictionNum} strings, nothing else:
5527
- ["response1", "response2", "response3", "response4"]`;
5528
- const result = await this.chatClient.textGeneration({
5529
- messages: [{ role: 'user', content: prompt }],
5530
- temperature: 0.8,
5531
- model: this.fastModel,
5532
- });
5533
- if (!result.content) {
5534
- this.logger.warn('Failed to generate predictions: empty response');
5535
- return [];
5536
- }
5537
- // Parse JSON response
5538
- const predictions = this.parsePredictionsFromJson(result.content, predictionNum);
5539
- if (predictions.length > 0) {
5540
- this.emit('replyPredictions', predictions);
5541
- }
5542
- return predictions;
5543
- }
5544
- catch (error) {
5545
- this.logger.error('Error generating predictions:', error);
5546
- return [];
5547
- }
5813
+ unregisterNpc(npc) {
5814
+ if (!npc)
5815
+ return;
5816
+ this.npcStates.delete(npc);
5548
5817
  }
5549
5818
  /**
5550
- * Parse predictions from JSON array response
5819
+ * Record that a conversation occurred with an NPC.
5820
+ * Called after each Talk() exchange.
5821
+ * @param npc The NPC client that had a conversation
5551
5822
  */
5552
- parsePredictionsFromJson(response, expectedCount) {
5553
- try {
5554
- // Try to find JSON array in response
5555
- const startIndex = response.indexOf('[');
5556
- const endIndex = response.lastIndexOf(']');
5557
- if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
5558
- this.logger.warn('Could not find JSON array in prediction response');
5559
- return this.extractPredictionsFromText(response, expectedCount);
5560
- }
5561
- const jsonArray = response.substring(startIndex, endIndex + 1);
5562
- const parsed = JSON.parse(jsonArray);
5563
- if (Array.isArray(parsed)) {
5564
- return parsed
5565
- .filter(item => typeof item === 'string' && item.trim())
5566
- .slice(0, expectedCount);
5567
- }
5568
- return [];
5569
- }
5570
- catch (error) {
5571
- this.logger.warn('Failed to parse predictions JSON:', error);
5572
- return this.extractPredictionsFromText(response, expectedCount);
5823
+ recordConversation(npc) {
5824
+ if (!npc)
5825
+ return;
5826
+ if (!this.npcStates.has(npc)) {
5827
+ this.registerNpc(npc);
5573
5828
  }
5829
+ const state = this.npcStates.get(npc);
5830
+ state.lastConversationTime = new Date();
5831
+ state.isCompacted = false; // Reset compaction flag on new conversation
5574
5832
  }
5575
5833
  /**
5576
- * Fallback: Extract predictions from text when JSON parsing fails
5834
+ * Get all registered NPCs
5577
5835
  */
5578
- extractPredictionsFromText(response, expectedCount) {
5579
- const predictions = [];
5580
- const lines = response.split(/[\n\r]+/).filter(line => line.trim());
5581
- for (const line of lines) {
5582
- let cleaned = line.trim();
5583
- // Skip empty lines and JSON brackets
5584
- if (!cleaned || cleaned === '[' || cleaned === ']')
5585
- continue;
5586
- // Remove common prefixes like "1.", "- ", etc.
5587
- if (/^\d+\./.test(cleaned)) {
5588
- cleaned = cleaned.replace(/^\d+\.\s*/, '');
5589
- }
5590
- else if (cleaned.startsWith('- ')) {
5591
- cleaned = cleaned.substring(2);
5592
- }
5593
- // Remove surrounding quotes
5594
- if (cleaned.startsWith('"') && cleaned.endsWith('"')) {
5595
- cleaned = cleaned.slice(1, -1);
5596
- }
5597
- // Remove trailing comma
5598
- if (cleaned.endsWith(',')) {
5599
- cleaned = cleaned.slice(0, -1).trim();
5600
- }
5601
- if (cleaned && predictions.length < expectedCount) {
5602
- predictions.push(cleaned);
5603
- }
5604
- }
5605
- return predictions;
5836
+ getRegisteredNpcs() {
5837
+ return Array.from(this.npcStates.keys());
5606
5838
  }
5607
5839
  /**
5608
- * Internal method to trigger prediction generation after NPC response
5840
+ * Get the conversation state for an NPC
5609
5841
  */
5610
- async triggerReplyPrediction() {
5611
- if (!this.generateReplyPrediction)
5612
- return;
5613
- // Fire and forget - don't block the main response
5614
- this.generateReplyPredictions().catch(err => {
5615
- this.logger.error('Background prediction generation failed:', err);
5616
- });
5842
+ getNpcState(npc) {
5843
+ return this.npcStates.get(npc);
5617
5844
  }
5618
- // ===== Main API - Talk Methods =====
5845
+ // ===== Auto Compaction =====
5619
5846
  /**
5620
- * Talk to the NPC (non-streaming)
5847
+ * Check if an NPC is eligible for compaction.
5848
+ * @param npc The NPC to check
5849
+ * @returns True if eligible for compaction
5621
5850
  */
5622
- async talk(message) {
5623
- this._isTalking = true;
5624
- try {
5625
- // Add user message to history
5626
- const userMessage = { role: 'user', content: message };
5627
- this.history.push(userMessage);
5628
- // Build messages array with system prompt
5629
- const messages = [
5630
- { role: 'system', content: this.buildSystemPrompt() },
5631
- ...this.history,
5632
- ];
5633
- // Generate response
5634
- const result = await this.chatClient.textGeneration({
5635
- messages,
5636
- temperature: this.temperature,
5637
- });
5638
- // Add assistant response to history
5639
- const assistantMessage = { role: 'assistant', content: result.content };
5640
- this.history.push(assistantMessage);
5641
- // Trim history if needed
5642
- this.trimHistory();
5643
- this.emit('response', result.content);
5644
- // Trigger reply prediction generation (fire and forget)
5645
- this.triggerReplyPrediction();
5646
- return result.content;
5647
- }
5648
- finally {
5649
- this._isTalking = false;
5650
- }
5851
+ isEligibleForCompaction(npc) {
5852
+ if (!npc)
5853
+ return false;
5854
+ const state = this.npcStates.get(npc);
5855
+ if (!state)
5856
+ return false;
5857
+ // Check if already compacted since last conversation
5858
+ if (state.isCompacted)
5859
+ return false;
5860
+ // Check message count
5861
+ const history = npc.getHistory();
5862
+ const nonSystemMessages = history.filter(m => m.role !== 'system').length;
5863
+ if (nonSystemMessages < this.config.autoCompactMinMessages)
5864
+ return false;
5865
+ // Check time since last conversation
5866
+ const timeSinceLastConversation = (Date.now() - state.lastConversationTime.getTime()) / 1000;
5867
+ if (timeSinceLastConversation < this.config.autoCompactTimeoutSeconds)
5868
+ return false;
5869
+ return true;
5651
5870
  }
5652
5871
  /**
5653
- * Talk to the NPC with streaming
5872
+ * Manually trigger conversation compaction for a specific NPC.
5873
+ * Summarizes the conversation history and stores it as a memory.
5874
+ * @param npc The NPC to compact
5875
+ * @returns True if compaction succeeded
5654
5876
  */
5655
- async talkStream(message, onChunk, onComplete) {
5656
- this._isTalking = true;
5657
- try {
5658
- // Add user message to history
5659
- const userMessage = { role: 'user', content: message };
5660
- this.history.push(userMessage);
5661
- // Build messages array with system prompt
5662
- const messages = [
5663
- { role: 'system', content: this.buildSystemPrompt() },
5664
- ...this.history,
5665
- ];
5666
- // Generate response
5667
- await this.chatClient.textGenerationStream({
5668
- messages,
5669
- temperature: this.temperature,
5670
- onChunk,
5671
- onComplete: (fullText) => {
5672
- this._isTalking = false;
5673
- // Add assistant response to history
5674
- const assistantMessage = { role: 'assistant', content: fullText };
5675
- this.history.push(assistantMessage);
5676
- // Trim history if needed
5677
- this.trimHistory();
5678
- this.emit('response', fullText);
5679
- // Trigger reply prediction generation (fire and forget)
5680
- this.triggerReplyPrediction();
5681
- if (onComplete) {
5682
- onComplete(fullText);
5683
- }
5684
- },
5685
- });
5877
+ async compactConversation(npc) {
5878
+ if (!npc) {
5879
+ this.logger.warn('Cannot compact: NPC is null');
5880
+ return false;
5686
5881
  }
5687
- catch (error) {
5688
- this._isTalking = false;
5689
- throw error;
5882
+ if (!this.chatClientFactory) {
5883
+ this.logger.error('Cannot compact: No chat client factory set. Call setChatClientFactory() first.');
5884
+ return false;
5885
+ }
5886
+ const history = npc.getHistory();
5887
+ const nonSystemMessages = history.filter(m => m.role !== 'system');
5888
+ if (nonSystemMessages.length < 2) {
5889
+ this.logger.info('Skipping compaction: not enough messages');
5890
+ return false;
5690
5891
  }
5691
- }
5692
- /**
5693
- * Talk with structured output
5694
- * @deprecated Use talkWithActions instead for NPC decision-making with actions
5695
- */
5696
- async talkStructured(message, schemaName) {
5697
- this.logger.warn('talkStructured is deprecated. Use talkWithActions instead for NPC decision-making with actions.');
5698
- // Add user message to history
5699
- const userMessage = { role: 'user', content: message };
5700
- this.history.push(userMessage);
5701
- // Generate structured response
5702
- const result = await this.chatClient.generateStructured({
5703
- schemaName,
5704
- prompt: message,
5705
- messages: [{ role: 'system', content: this.buildSystemPrompt() }, ...this.history],
5706
- temperature: this.temperature,
5707
- });
5708
- // Add a text representation to history
5709
- const assistantMessage = {
5710
- role: 'assistant',
5711
- content: JSON.stringify(result),
5712
- };
5713
- this.history.push(assistantMessage);
5714
- this.trimHistory();
5715
- return result;
5716
- }
5717
- /**
5718
- * Talk to the NPC with available actions (non-streaming)
5719
- * @param message The message to send
5720
- * @param actions List of actions the NPC can perform
5721
- * @returns Response containing text and any action calls
5722
- */
5723
- async talkWithActions(message, actions) {
5724
- this._isTalking = true;
5725
5892
  try {
5726
- // Add user message to history
5727
- const userMessage = { role: 'user', content: message };
5728
- this.history.push(userMessage);
5729
- // Convert NpcActions to ChatTools
5730
- const tools = actions
5731
- .filter(a => a && a.enabled !== false)
5732
- .map(a => npcActionToTool(a));
5733
- // Build messages array with system prompt
5734
- const messages = [
5735
- { role: 'system', content: this.buildSystemPrompt() },
5736
- ...this.history,
5737
- ];
5738
- // Generate response with tools
5739
- const result = await this.chatClient.textGenerationWithTools({
5740
- messages,
5741
- temperature: this.temperature,
5742
- tools,
5743
- tool_choice: 'auto',
5893
+ this.logger.info(`Starting compaction (${nonSystemMessages.length} messages)`);
5894
+ // Build conversation text for summarization
5895
+ const conversationText = nonSystemMessages
5896
+ .map(m => `${m.role}: ${m.content}`)
5897
+ .join('\n');
5898
+ // Create summarization prompt
5899
+ const summaryPrompt = `Summarize the following conversation concisely. Focus on:
5900
+ 1. Key topics discussed
5901
+ 2. Important information exchanged
5902
+ 3. Any decisions or commitments made
5903
+ 4. The emotional tone
5904
+
5905
+ Keep the summary under 200 words. Write in third person.
5906
+
5907
+ Conversation:
5908
+ ${conversationText}`;
5909
+ // Use chat client for summarization
5910
+ const chatClient = this.chatClientFactory();
5911
+ const result = await chatClient.textGeneration({
5912
+ messages: [{ role: 'user', content: summaryPrompt }],
5913
+ temperature: 0.5,
5914
+ model: this.config.fastModel || undefined,
5744
5915
  });
5745
- // Build response
5746
- const response = {
5747
- text: result.content || '',
5748
- actionCalls: [],
5749
- hasActions: false,
5750
- };
5751
- // Extract tool calls if any
5752
- if (result.tool_calls) {
5753
- response.actionCalls = result.tool_calls.map(tc => ({
5754
- id: tc.id,
5755
- actionName: tc.function.name,
5756
- arguments: this.parseToolArguments(tc.function.arguments),
5757
- }));
5758
- response.hasActions = response.actionCalls.length > 0;
5916
+ if (!result.content) {
5917
+ const error = 'Empty response from summarization';
5918
+ this.logger.error(`Compaction failed: ${error}`);
5919
+ this.emit('compactionFailed', npc, error);
5920
+ return false;
5759
5921
  }
5760
- // Add assistant response to history
5761
- const assistantMessage = {
5762
- role: 'assistant',
5763
- content: response.text,
5764
- tool_calls: result.tool_calls,
5765
- };
5766
- this.history.push(assistantMessage);
5767
- this.trimHistory();
5768
- this.emit('response', response.text);
5769
- if (response.hasActions) {
5770
- this.emit('actions', response.actionCalls);
5922
+ // Clear history and add summary as memory
5923
+ npc.clearHistory();
5924
+ npc.setMemory('PreviousConversationSummary', result.content);
5925
+ // Update state
5926
+ const state = this.npcStates.get(npc);
5927
+ if (state) {
5928
+ state.isCompacted = true;
5929
+ state.compactionCount++;
5771
5930
  }
5772
- // Trigger reply prediction generation (fire and forget)
5773
- this.triggerReplyPrediction();
5774
- return response;
5775
- }
5776
- finally {
5777
- this._isTalking = false;
5778
- }
5779
- }
5780
- /**
5781
- * Talk to the NPC with actions (streaming)
5782
- * Text streams first, action calls are returned in onComplete
5783
- */
5784
- async talkWithActionsStream(message, actions, onChunk, onComplete) {
5785
- this._isTalking = true;
5786
- try {
5787
- // Add user message to history
5788
- const userMessage = { role: 'user', content: message };
5789
- this.history.push(userMessage);
5790
- // Convert NpcActions to ChatTools
5791
- const tools = actions
5792
- .filter(a => a && a.enabled !== false)
5793
- .map(a => npcActionToTool(a));
5794
- // Build messages array with system prompt
5795
- const messages = [
5796
- { role: 'system', content: this.buildSystemPrompt() },
5797
- ...this.history,
5798
- ];
5799
- // Generate response with tools (streaming)
5800
- await this.chatClient.textGenerationWithToolsStream({
5801
- messages,
5802
- temperature: this.temperature,
5803
- tools,
5804
- tool_choice: 'auto',
5805
- onChunk,
5806
- onComplete: (result) => {
5807
- this._isTalking = false;
5808
- // Build response
5809
- const response = {
5810
- text: result.content || '',
5811
- actionCalls: [],
5812
- hasActions: false,
5813
- };
5814
- // Extract tool calls if any
5815
- if (result.tool_calls) {
5816
- response.actionCalls = result.tool_calls.map(tc => ({
5817
- id: tc.id,
5818
- actionName: tc.function.name,
5819
- arguments: this.parseToolArguments(tc.function.arguments),
5820
- }));
5821
- response.hasActions = response.actionCalls.length > 0;
5822
- }
5823
- // Add assistant response to history
5824
- const assistantMessage = {
5825
- role: 'assistant',
5826
- content: response.text,
5827
- tool_calls: result.tool_calls,
5828
- };
5829
- this.history.push(assistantMessage);
5830
- this.trimHistory();
5831
- this.emit('response', response.text);
5832
- if (response.hasActions) {
5833
- this.emit('actions', response.actionCalls);
5834
- }
5835
- // Trigger reply prediction generation (fire and forget)
5836
- this.triggerReplyPrediction();
5837
- if (onComplete) {
5838
- onComplete(response);
5839
- }
5840
- },
5841
- });
5931
+ this.logger.info(`Compaction completed. Summary: ${result.content.substring(0, 100)}...`);
5932
+ this.emit('npcCompacted', npc);
5933
+ return true;
5842
5934
  }
5843
5935
  catch (error) {
5844
- this._isTalking = false;
5845
- throw error;
5936
+ const errorMessage = error instanceof Error ? error.message : String(error);
5937
+ this.logger.error(`Compaction error: ${errorMessage}`);
5938
+ this.emit('compactionFailed', npc, errorMessage);
5939
+ return false;
5846
5940
  }
5847
5941
  }
5848
- // ===== Action Results Reporting =====
5849
5942
  /**
5850
- * Report action results back to the conversation
5851
- * Call this after executing actions to let the NPC know the results
5943
+ * Compact all registered NPCs that meet the eligibility criteria.
5944
+ * @returns Number of NPCs successfully compacted
5852
5945
  */
5853
- reportActionResults(results) {
5854
- for (const [callId, result] of Object.entries(results)) {
5855
- this.history.push({
5856
- role: 'tool',
5857
- tool_call_id: callId,
5858
- content: result,
5859
- });
5946
+ async compactAllEligible() {
5947
+ const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
5948
+ if (eligibleNpcs.length === 0) {
5949
+ return 0;
5950
+ }
5951
+ this.logger.info(`Compacting ${eligibleNpcs.length} eligible NPCs`);
5952
+ let successCount = 0;
5953
+ for (const npc of eligibleNpcs) {
5954
+ const success = await this.compactConversation(npc);
5955
+ if (success)
5956
+ successCount++;
5860
5957
  }
5958
+ return successCount;
5861
5959
  }
5960
+ // ===== Auto Compact Timer =====
5862
5961
  /**
5863
- * Report a single action result
5962
+ * Start the auto-compact check timer
5864
5963
  */
5865
- reportActionResult(callId, result) {
5866
- this.history.push({
5867
- role: 'tool',
5868
- tool_call_id: callId,
5869
- content: result,
5870
- });
5964
+ startAutoCompactCheck() {
5965
+ if (this.autoCompactTimer) {
5966
+ this.stopAutoCompactCheck();
5967
+ }
5968
+ this.autoCompactTimer = setInterval(() => {
5969
+ this.runAutoCompactCheck();
5970
+ }, this.config.autoCompactCheckInterval);
5871
5971
  }
5872
5972
  /**
5873
- * Parse tool arguments from JSON string
5973
+ * Stop the auto-compact check timer
5874
5974
  */
5875
- parseToolArguments(args) {
5876
- try {
5877
- return JSON.parse(args);
5975
+ stopAutoCompactCheck() {
5976
+ if (this.autoCompactTimer) {
5977
+ clearInterval(this.autoCompactTimer);
5978
+ this.autoCompactTimer = null;
5878
5979
  }
5879
- catch (_a) {
5880
- return {};
5980
+ }
5981
+ /**
5982
+ * Run a single auto-compact check
5983
+ */
5984
+ async runAutoCompactCheck() {
5985
+ if (!this.config.enableAutoCompact)
5986
+ return;
5987
+ const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
5988
+ for (const npc of eligibleNpcs) {
5989
+ // Fire and forget - don't block
5990
+ this.compactConversation(npc).catch(err => {
5991
+ this.logger.error('Auto-compact error:', err);
5992
+ });
5881
5993
  }
5882
5994
  }
5883
- // ===== Conversation History Management =====
5995
+ // ===== Lifecycle =====
5884
5996
  /**
5885
- * Get conversation history
5997
+ * Enable auto-compaction
5886
5998
  */
5887
- getHistory() {
5888
- return [...this.history];
5999
+ enableAutoCompact() {
6000
+ this.config.enableAutoCompact = true;
6001
+ this.startAutoCompactCheck();
5889
6002
  }
5890
6003
  /**
5891
- * Get the number of messages in history
6004
+ * Disable auto-compaction
5892
6005
  */
5893
- getHistoryLength() {
5894
- return this.history.length;
6006
+ disableAutoCompact() {
6007
+ this.config.enableAutoCompact = false;
6008
+ this.stopAutoCompactCheck();
5895
6009
  }
5896
6010
  /**
5897
- * Clear conversation history.
5898
- * The character design and memories will be preserved.
6011
+ * Clean up resources
5899
6012
  */
5900
- clearHistory() {
6013
+ destroy() {
6014
+ this.stopAutoCompactCheck();
6015
+ this.npcStates.clear();
6016
+ this.playerDescription = null;
6017
+ this.playerPrompt = null;
6018
+ this.playerMemories.clear();
6019
+ this.removeAllListeners();
6020
+ }
6021
+ }
6022
+ AIContextManager._instance = null;
6023
+ /**
6024
+ * Default AIContextManager instance
6025
+ * Can be used as a global context manager
6026
+ */
6027
+ const defaultContextManager = AIContextManager.getInstance();
6028
+
6029
+ /**
6030
+ * NPC Client for simplified conversation management
6031
+ * Automatically handles conversation history
6032
+ *
6033
+ * Key Features:
6034
+ * - Call talk() for all interactions - actions are handled automatically
6035
+ * - Memory system for persistent NPC context
6036
+ * - Reply prediction for suggesting player responses
6037
+ * - Automatic conversation history management
6038
+ */
6039
+ class NPCClient extends EventEmitter {
6040
+ constructor(chatClient, config) {
6041
+ var _a, _b, _c;
6042
+ super();
6043
+ this._isTalking = false;
6044
+ this.logger = Logger.getLogger('NPCClient');
6045
+ this.chatClient = chatClient;
6046
+ // Support both characterDesign and legacy systemPrompt
6047
+ this.characterDesign = (config === null || config === void 0 ? void 0 : config.characterDesign) || (config === null || config === void 0 ? void 0 : config.systemPrompt) || 'You are a helpful assistant.';
6048
+ this.temperature = (_a = config === null || config === void 0 ? void 0 : config.temperature) !== null && _a !== void 0 ? _a : 0.7;
6049
+ this.maxHistoryLength = (config === null || config === void 0 ? void 0 : config.maxHistoryLength) || 50;
6050
+ this.generateReplyPrediction = (_b = config === null || config === void 0 ? void 0 : config.generateReplyPrediction) !== null && _b !== void 0 ? _b : false;
6051
+ this.predictionCount = Math.max(2, Math.min(6, (_c = config === null || config === void 0 ? void 0 : config.predictionCount) !== null && _c !== void 0 ? _c : 4));
6052
+ this.fastModel = config === null || config === void 0 ? void 0 : config.fastModel;
5901
6053
  this.history = [];
5902
- this.emit('history_cleared');
6054
+ this.memories = new Map();
5903
6055
  }
6056
+ // ===== State Properties =====
5904
6057
  /**
5905
- * Revert the last exchange (user message and assistant response) from history.
5906
- * @returns true if reverted, false if not enough history
6058
+ * Whether the NPC is currently processing a request
5907
6059
  */
5908
- revertHistory() {
5909
- let lastAssistantIndex = -1;
5910
- let lastUserIndex = -1;
5911
- for (let i = this.history.length - 1; i >= 0; i--) {
5912
- if (this.history[i].role === 'assistant' && lastAssistantIndex === -1) {
5913
- lastAssistantIndex = i;
5914
- }
5915
- else if (this.history[i].role === 'user' && lastAssistantIndex !== -1 && lastUserIndex === -1) {
5916
- lastUserIndex = i;
5917
- break;
6060
+ get isTalking() {
6061
+ return this._isTalking;
6062
+ }
6063
+ // ===== Character Design & Memory System =====
6064
+ /**
6065
+ * Set the character design for the NPC.
6066
+ * The system prompt is composed of CharacterDesign + all Memories.
6067
+ */
6068
+ setCharacterDesign(design) {
6069
+ this.characterDesign = design;
6070
+ }
6071
+ /**
6072
+ * Get the current character design
6073
+ */
6074
+ getCharacterDesign() {
6075
+ return this.characterDesign;
6076
+ }
6077
+ /**
6078
+ * @deprecated Use setCharacterDesign instead.
6079
+ * This method is kept for backwards compatibility.
6080
+ */
6081
+ setSystemPrompt(prompt) {
6082
+ this.logger.warn('setSystemPrompt is deprecated. Use setCharacterDesign instead.');
6083
+ this.setCharacterDesign(prompt);
6084
+ }
6085
+ /**
6086
+ * @deprecated Use getCharacterDesign instead.
6087
+ * This method is kept for backwards compatibility.
6088
+ */
6089
+ getSystemPrompt() {
6090
+ return this.buildSystemPrompt();
6091
+ }
6092
+ /**
6093
+ * Set or update a memory for the NPC.
6094
+ * Memories are appended to the character design to form the system prompt.
6095
+ * Set memoryContent to null or empty to remove the memory.
6096
+ * @param memoryName The name/key of the memory
6097
+ * @param memoryContent The content of the memory. Null or empty to remove.
6098
+ */
6099
+ setMemory(memoryName, memoryContent) {
6100
+ if (!memoryName) {
6101
+ this.logger.warn('Memory name cannot be empty');
6102
+ return;
6103
+ }
6104
+ if (!memoryContent) {
6105
+ // Remove memory if content is null or empty
6106
+ if (this.memories.has(memoryName)) {
6107
+ this.memories.delete(memoryName);
6108
+ this.emit('memory_removed', memoryName);
5918
6109
  }
5919
6110
  }
5920
- if (lastAssistantIndex !== -1 && lastUserIndex !== -1) {
5921
- // Remove in reverse order to maintain indices
5922
- this.history.splice(lastAssistantIndex, 1);
5923
- this.history.splice(lastUserIndex, 1);
5924
- this.emit('history_reverted');
5925
- return true;
6111
+ else {
6112
+ // Add or update memory
6113
+ this.memories.set(memoryName, memoryContent);
6114
+ this.emit('memory_set', memoryName, memoryContent);
5926
6115
  }
5927
- return false;
5928
6116
  }
5929
6117
  /**
5930
- * Revert (remove) the last N chat messages from history
5931
- * @param count Number of messages to remove
5932
- * @returns Number of messages actually removed
6118
+ * Get a specific memory by name.
6119
+ * @param memoryName The name of the memory to retrieve
6120
+ * @returns The memory content, or undefined if not found
5933
6121
  */
5934
- revertChatMessages(count) {
5935
- if (count <= 0)
5936
- return 0;
5937
- const messagesToRemove = Math.min(count, this.history.length);
5938
- const originalCount = this.history.length;
5939
- this.history = this.history.slice(0, -messagesToRemove);
5940
- const actuallyRemoved = originalCount - this.history.length;
5941
- if (actuallyRemoved > 0) {
5942
- this.emit('history_reverted', actuallyRemoved);
6122
+ getMemory(memoryName) {
6123
+ return this.memories.get(memoryName);
6124
+ }
6125
+ /**
6126
+ * Get all memory names currently stored.
6127
+ * @returns Array of memory names
6128
+ */
6129
+ getMemoryNames() {
6130
+ return Array.from(this.memories.keys());
6131
+ }
6132
+ /**
6133
+ * Clear all memories (but keep character design).
6134
+ */
6135
+ clearMemories() {
6136
+ this.memories.clear();
6137
+ this.emit('memories_cleared');
6138
+ }
6139
+ /**
6140
+ * Build the complete system prompt from CharacterDesign + Memories.
6141
+ */
6142
+ buildSystemPrompt() {
6143
+ const parts = [];
6144
+ if (this.characterDesign) {
6145
+ parts.push(this.characterDesign);
5943
6146
  }
5944
- return actuallyRemoved;
6147
+ if (this.memories.size > 0) {
6148
+ const memoryStrings = Array.from(this.memories.entries())
6149
+ .map(([name, content]) => `[${name}]: ${content}`);
6150
+ parts.push('Memories:\n' + memoryStrings.join('\n'));
6151
+ }
6152
+ return parts.join('\n\n');
6153
+ }
6154
+ // ===== Reply Prediction =====
6155
+ /**
6156
+ * Enable or disable automatic reply prediction
6157
+ */
6158
+ setGenerateReplyPrediction(enabled) {
6159
+ this.generateReplyPrediction = enabled;
5945
6160
  }
5946
6161
  /**
5947
- * Revert to a specific point in history
5948
- * @deprecated Use revertHistory() or revertChatMessages() instead
6162
+ * Set the number of predictions to generate
5949
6163
  */
5950
- revertToMessage(index) {
5951
- if (index >= 0 && index < this.history.length) {
5952
- this.history = this.history.slice(0, index + 1);
5953
- this.emit('history_reverted', index);
5954
- }
6164
+ setPredictionCount(count) {
6165
+ this.predictionCount = Math.max(2, Math.min(6, count));
5955
6166
  }
5956
6167
  /**
5957
- * Append a message to history manually
6168
+ * Manually generate reply predictions based on current conversation.
6169
+ * Uses the fast model for quick generation.
6170
+ * @param tempPrompt Optional temporary prompt to influence the prediction style/tone
6171
+ * @param count Number of predictions to generate (default: uses predictionCount property)
6172
+ * @returns Array of predicted player replies, or empty array on failure
5958
6173
  */
5959
- appendMessage(message) {
5960
- this.history.push(message);
5961
- this.trimHistory();
6174
+ async generateReplyPredictions(tempPrompt, count) {
6175
+ var _a;
6176
+ const predictionNum = count !== null && count !== void 0 ? count : this.predictionCount;
6177
+ if (this.history.length < 2) {
6178
+ this.logger.info('Not enough conversation history to generate predictions');
6179
+ return [];
6180
+ }
6181
+ try {
6182
+ // Get last NPC message
6183
+ const lastNpcMessage = (_a = [...this.history]
6184
+ .reverse()
6185
+ .find(m => m.role === 'assistant')) === null || _a === void 0 ? void 0 : _a.content;
6186
+ if (!lastNpcMessage) {
6187
+ this.logger.info('No NPC message found to generate predictions from');
6188
+ return [];
6189
+ }
6190
+ // Build recent history (last 6 non-system messages)
6191
+ const recentHistory = this.history
6192
+ .filter(m => m.role !== 'system')
6193
+ .slice(-6)
6194
+ .map(m => `${m.role}: ${m.content}`);
6195
+ // Get player context from AIContextManager
6196
+ const contextManager = AIContextManager.getInstance();
6197
+ const playerContext = contextManager.buildPlayerContext();
6198
+ // Build player character section
6199
+ let playerCharacterSection = '';
6200
+ if (playerContext || tempPrompt) {
6201
+ playerCharacterSection = '\nPlayer Character:\n';
6202
+ if (playerContext) {
6203
+ playerCharacterSection += playerContext + '\n';
6204
+ }
6205
+ if (tempPrompt) {
6206
+ playerCharacterSection += `Additional guidance: ${tempPrompt}\n`;
6207
+ }
6208
+ }
6209
+ // Build prompt for prediction generation
6210
+ const prompt = `Based on the conversation history below, generate exactly ${predictionNum} natural and contextually appropriate responses that the player might say next.
6211
+
6212
+ Context:
6213
+ - This is a conversation between a player and an NPC in a game
6214
+ - The NPC just said: "${lastNpcMessage}"
6215
+ ${playerCharacterSection}
6216
+ Conversation history:
6217
+ ${recentHistory.join('\n')}
6218
+
6219
+ Requirements:
6220
+ 1. Each response should be 1-2 sentences maximum
6221
+ 2. Responses should be diverse in tone and intent
6222
+ 3. Include a mix of questions, statements, and action-oriented responses
6223
+ 4. Responses should feel natural for the player character${playerContext || tempPrompt ? ' and match their personality/tone' : ''}
6224
+
6225
+ Output ONLY a JSON array of ${predictionNum} strings, nothing else:
6226
+ ["response1", "response2", "response3", "response4"]`;
6227
+ const result = await this.chatClient.textGeneration({
6228
+ messages: [{ role: 'user', content: prompt }],
6229
+ temperature: 0.8,
6230
+ model: this.fastModel,
6231
+ });
6232
+ if (!result.content) {
6233
+ this.logger.warn('Failed to generate predictions: empty response');
6234
+ return [];
6235
+ }
6236
+ // Parse JSON response
6237
+ const predictions = this.parsePredictionsFromJson(result.content, predictionNum);
6238
+ if (predictions.length > 0) {
6239
+ this.emit('replyPredictions', predictions);
6240
+ }
6241
+ return predictions;
6242
+ }
6243
+ catch (error) {
6244
+ this.logger.error('Error generating predictions:', error);
6245
+ return [];
6246
+ }
5962
6247
  }
5963
6248
  /**
5964
- * Alias for appendMessage (Unity SDK compatibility)
6249
+ * Parse predictions from JSON array response
5965
6250
  */
5966
- appendChatMessage(role, content) {
5967
- if (!role || !content) {
5968
- this.logger.warn('Role and content cannot be empty');
5969
- return;
6251
+ parsePredictionsFromJson(response, expectedCount) {
6252
+ try {
6253
+ // Try to find JSON array in response
6254
+ const startIndex = response.indexOf('[');
6255
+ const endIndex = response.lastIndexOf(']');
6256
+ if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
6257
+ this.logger.warn('Could not find JSON array in prediction response');
6258
+ return this.extractPredictionsFromText(response, expectedCount);
6259
+ }
6260
+ const jsonArray = response.substring(startIndex, endIndex + 1);
6261
+ const parsed = JSON.parse(jsonArray);
6262
+ if (Array.isArray(parsed)) {
6263
+ return parsed
6264
+ .filter(item => typeof item === 'string' && item.trim())
6265
+ .slice(0, expectedCount);
6266
+ }
6267
+ return [];
6268
+ }
6269
+ catch (error) {
6270
+ this.logger.warn('Failed to parse predictions JSON:', error);
6271
+ return this.extractPredictionsFromText(response, expectedCount);
5970
6272
  }
5971
- this.appendMessage({ role: role, content });
5972
6273
  }
5973
6274
  /**
5974
- * Trim history to max length
6275
+ * Fallback: Extract predictions from text when JSON parsing fails
5975
6276
  */
5976
- trimHistory() {
5977
- if (this.history.length > this.maxHistoryLength) {
5978
- // Keep the most recent messages
5979
- this.history = this.history.slice(-this.maxHistoryLength);
6277
+ extractPredictionsFromText(response, expectedCount) {
6278
+ const predictions = [];
6279
+ const lines = response.split(/[\n\r]+/).filter(line => line.trim());
6280
+ for (const line of lines) {
6281
+ let cleaned = line.trim();
6282
+ // Skip empty lines and JSON brackets
6283
+ if (!cleaned || cleaned === '[' || cleaned === ']')
6284
+ continue;
6285
+ // Remove common prefixes like "1.", "- ", etc.
6286
+ if (/^\d+\./.test(cleaned)) {
6287
+ cleaned = cleaned.replace(/^\d+\.\s*/, '');
6288
+ }
6289
+ else if (cleaned.startsWith('- ')) {
6290
+ cleaned = cleaned.substring(2);
6291
+ }
6292
+ // Remove surrounding quotes
6293
+ if (cleaned.startsWith('"') && cleaned.endsWith('"')) {
6294
+ cleaned = cleaned.slice(1, -1);
6295
+ }
6296
+ // Remove trailing comma
6297
+ if (cleaned.endsWith(',')) {
6298
+ cleaned = cleaned.slice(0, -1).trim();
6299
+ }
6300
+ if (cleaned && predictions.length < expectedCount) {
6301
+ predictions.push(cleaned);
6302
+ }
5980
6303
  }
6304
+ return predictions;
5981
6305
  }
5982
- // ===== Save/Load =====
5983
6306
  /**
5984
- * Save the current conversation history to a serializable format.
5985
- * Includes characterDesign, memories, and history.
6307
+ * Internal method to trigger prediction generation after NPC response
5986
6308
  */
5987
- saveHistory() {
5988
- const saveData = {
5989
- characterDesign: this.characterDesign,
5990
- memories: Array.from(this.memories.entries()).map(([name, content]) => ({ name, content })),
5991
- history: this.history,
5992
- };
5993
- return JSON.stringify(saveData);
6309
+ async triggerReplyPrediction() {
6310
+ if (!this.generateReplyPrediction)
6311
+ return;
6312
+ // Fire and forget - don't block the main response
6313
+ this.generateReplyPredictions().catch(err => {
6314
+ this.logger.error('Background prediction generation failed:', err);
6315
+ });
5994
6316
  }
6317
+ // ===== Main API - Talk Methods =====
5995
6318
  /**
5996
- * Load conversation history from serialized data.
5997
- * Restores characterDesign, memories, and history.
6319
+ * Talk to the NPC (non-streaming)
5998
6320
  */
5999
- loadHistory(saveData) {
6321
+ async talk(message) {
6322
+ this._isTalking = true;
6000
6323
  try {
6001
- const data = JSON.parse(saveData);
6002
- // Load character design (with backwards compatibility for old systemPrompt field)
6003
- this.characterDesign = data.characterDesign || data.systemPrompt || this.characterDesign;
6004
- // Load memories
6005
- this.memories.clear();
6006
- if (data.memories && Array.isArray(data.memories)) {
6007
- for (const memory of data.memories) {
6008
- if (memory.name && memory.content) {
6009
- this.memories.set(memory.name, memory.content);
6010
- }
6011
- }
6012
- }
6013
- // Load history (skip system messages as they'll be rebuilt from characterDesign + memories)
6014
- this.history = (data.history || []).filter(m => m.role !== 'system');
6015
- this.emit('history_loaded');
6016
- return true;
6017
- }
6018
- catch (error) {
6019
- this.logger.error('Failed to load history:', error);
6020
- return false;
6324
+ // Add user message to history
6325
+ const userMessage = { role: 'user', content: message };
6326
+ this.history.push(userMessage);
6327
+ // Build messages array with system prompt
6328
+ const messages = [
6329
+ { role: 'system', content: this.buildSystemPrompt() },
6330
+ ...this.history,
6331
+ ];
6332
+ // Generate response
6333
+ const result = await this.chatClient.textGeneration({
6334
+ messages,
6335
+ temperature: this.temperature,
6336
+ });
6337
+ // Add assistant response to history
6338
+ const assistantMessage = { role: 'assistant', content: result.content };
6339
+ this.history.push(assistantMessage);
6340
+ // Trim history if needed
6341
+ this.trimHistory();
6342
+ this.emit('response', result.content);
6343
+ // Trigger reply prediction generation (fire and forget)
6344
+ this.triggerReplyPrediction();
6345
+ return result.content;
6021
6346
  }
6022
- }
6023
- }
6024
-
6025
- /**
6026
- * Global AI Context Manager for managing NPC conversations and player context.
6027
- *
6028
- * Features:
6029
- * - Player description management
6030
- * - NPC conversation tracking
6031
- * - Automatic conversation compaction (AutoCompact)
6032
- */
6033
- /**
6034
- * Global AI Context Manager
6035
- * Manages NPC conversations and player context across the application
6036
- */
6037
- class AIContextManager extends EventEmitter {
6038
- constructor(config) {
6039
- var _a, _b, _c, _d, _e;
6040
- super();
6041
- this.playerDescription = null;
6042
- this.npcStates = new Map();
6043
- this.autoCompactTimer = null;
6044
- this.chatClientFactory = null;
6045
- this.logger = Logger.getLogger('AIContextManager');
6046
- this.config = {
6047
- enableAutoCompact: (_a = config === null || config === void 0 ? void 0 : config.enableAutoCompact) !== null && _a !== void 0 ? _a : false,
6048
- autoCompactMinMessages: (_b = config === null || config === void 0 ? void 0 : config.autoCompactMinMessages) !== null && _b !== void 0 ? _b : 20,
6049
- autoCompactTimeoutSeconds: (_c = config === null || config === void 0 ? void 0 : config.autoCompactTimeoutSeconds) !== null && _c !== void 0 ? _c : 300,
6050
- autoCompactCheckInterval: (_d = config === null || config === void 0 ? void 0 : config.autoCompactCheckInterval) !== null && _d !== void 0 ? _d : 60000,
6051
- fastModel: (_e = config === null || config === void 0 ? void 0 : config.fastModel) !== null && _e !== void 0 ? _e : '',
6052
- };
6053
- // Start auto-compact check if enabled
6054
- if (this.config.enableAutoCompact) {
6055
- this.startAutoCompactCheck();
6347
+ finally {
6348
+ this._isTalking = false;
6056
6349
  }
6057
6350
  }
6058
- // ===== Singleton Pattern =====
6059
6351
  /**
6060
- * Get the singleton instance of AIContextManager
6061
- * Creates a new instance if one doesn't exist
6352
+ * Talk to the NPC with streaming
6062
6353
  */
6063
- static getInstance(config) {
6064
- if (!AIContextManager._instance) {
6065
- AIContextManager._instance = new AIContextManager(config);
6354
+ async talkStream(message, onChunk, onComplete) {
6355
+ this._isTalking = true;
6356
+ try {
6357
+ // Add user message to history
6358
+ const userMessage = { role: 'user', content: message };
6359
+ this.history.push(userMessage);
6360
+ // Build messages array with system prompt
6361
+ const messages = [
6362
+ { role: 'system', content: this.buildSystemPrompt() },
6363
+ ...this.history,
6364
+ ];
6365
+ // Generate response
6366
+ await this.chatClient.textGenerationStream({
6367
+ messages,
6368
+ temperature: this.temperature,
6369
+ onChunk,
6370
+ onComplete: (fullText) => {
6371
+ this._isTalking = false;
6372
+ // Add assistant response to history
6373
+ const assistantMessage = { role: 'assistant', content: fullText };
6374
+ this.history.push(assistantMessage);
6375
+ // Trim history if needed
6376
+ this.trimHistory();
6377
+ this.emit('response', fullText);
6378
+ // Trigger reply prediction generation (fire and forget)
6379
+ this.triggerReplyPrediction();
6380
+ if (onComplete) {
6381
+ onComplete(fullText);
6382
+ }
6383
+ },
6384
+ });
6066
6385
  }
6067
- return AIContextManager._instance;
6068
- }
6069
- /**
6070
- * Reset the singleton instance (useful for testing)
6071
- */
6072
- static resetInstance() {
6073
- if (AIContextManager._instance) {
6074
- AIContextManager._instance.destroy();
6075
- AIContextManager._instance = null;
6386
+ catch (error) {
6387
+ this._isTalking = false;
6388
+ throw error;
6076
6389
  }
6077
6390
  }
6078
- // ===== Configuration =====
6079
6391
  /**
6080
- * Set the chat client factory for creating chat clients for summarization
6081
- * Required for compaction to work
6392
+ * Talk with structured output
6393
+ * @deprecated Use talkWithActions instead for NPC decision-making with actions
6082
6394
  */
6083
- setChatClientFactory(factory) {
6084
- this.chatClientFactory = factory;
6395
+ async talkStructured(message, schemaName) {
6396
+ this.logger.warn('talkStructured is deprecated. Use talkWithActions instead for NPC decision-making with actions.');
6397
+ // Add user message to history
6398
+ const userMessage = { role: 'user', content: message };
6399
+ this.history.push(userMessage);
6400
+ // Generate structured response
6401
+ const result = await this.chatClient.generateStructured({
6402
+ schemaName,
6403
+ prompt: message,
6404
+ messages: [{ role: 'system', content: this.buildSystemPrompt() }, ...this.history],
6405
+ temperature: this.temperature,
6406
+ });
6407
+ // Add a text representation to history
6408
+ const assistantMessage = {
6409
+ role: 'assistant',
6410
+ content: JSON.stringify(result),
6411
+ };
6412
+ this.history.push(assistantMessage);
6413
+ this.trimHistory();
6414
+ return result;
6085
6415
  }
6086
6416
  /**
6087
- * Update configuration
6417
+ * Talk to the NPC with available actions (non-streaming)
6418
+ * @param message The message to send
6419
+ * @param actions List of actions the NPC can perform
6420
+ * @returns Response containing text and any action calls
6088
6421
  */
6089
- setConfig(config) {
6090
- const wasAutoCompactEnabled = this.config.enableAutoCompact;
6091
- this.config = Object.assign(Object.assign({}, this.config), config);
6092
- // Handle auto-compact state change
6093
- if (config.enableAutoCompact !== undefined) {
6094
- if (config.enableAutoCompact && !wasAutoCompactEnabled) {
6095
- this.startAutoCompactCheck();
6422
+ async talkWithActions(message, actions) {
6423
+ this._isTalking = true;
6424
+ try {
6425
+ // Add user message to history
6426
+ const userMessage = { role: 'user', content: message };
6427
+ this.history.push(userMessage);
6428
+ // Convert NpcActions to ChatTools
6429
+ const tools = actions
6430
+ .filter(a => a && a.enabled !== false)
6431
+ .map(a => npcActionToTool(a));
6432
+ // Build messages array with system prompt
6433
+ const messages = [
6434
+ { role: 'system', content: this.buildSystemPrompt() },
6435
+ ...this.history,
6436
+ ];
6437
+ // Generate response with tools
6438
+ const result = await this.chatClient.textGenerationWithTools({
6439
+ messages,
6440
+ temperature: this.temperature,
6441
+ tools,
6442
+ tool_choice: 'auto',
6443
+ });
6444
+ // Build response
6445
+ const response = {
6446
+ text: result.content || '',
6447
+ actionCalls: [],
6448
+ hasActions: false,
6449
+ };
6450
+ // Extract tool calls if any
6451
+ if (result.tool_calls) {
6452
+ response.actionCalls = result.tool_calls.map(tc => ({
6453
+ id: tc.id,
6454
+ actionName: tc.function.name,
6455
+ arguments: this.parseToolArguments(tc.function.arguments),
6456
+ }));
6457
+ response.hasActions = response.actionCalls.length > 0;
6096
6458
  }
6097
- else if (!config.enableAutoCompact && wasAutoCompactEnabled) {
6098
- this.stopAutoCompactCheck();
6459
+ // Add assistant response to history
6460
+ const assistantMessage = {
6461
+ role: 'assistant',
6462
+ content: response.text,
6463
+ tool_calls: result.tool_calls,
6464
+ };
6465
+ this.history.push(assistantMessage);
6466
+ this.trimHistory();
6467
+ this.emit('response', response.text);
6468
+ if (response.hasActions) {
6469
+ this.emit('actions', response.actionCalls);
6099
6470
  }
6471
+ // Trigger reply prediction generation (fire and forget)
6472
+ this.triggerReplyPrediction();
6473
+ return response;
6474
+ }
6475
+ finally {
6476
+ this._isTalking = false;
6100
6477
  }
6101
- }
6102
- // ===== Player Description =====
6103
- /**
6104
- * Set the player's description for AI context.
6105
- * Used when generating reply predictions and for NPC context.
6106
- * @param description Description of the player character
6107
- */
6108
- setPlayerDescription(description) {
6109
- this.playerDescription = description;
6110
- this.emit('playerDescriptionChanged', description);
6111
- }
6112
- /**
6113
- * Get the current player description.
6114
- * @returns The player description, or null if not set
6115
- */
6116
- getPlayerDescription() {
6117
- return this.playerDescription;
6118
6478
  }
6119
6479
  /**
6120
- * Clear the player description.
6480
+ * Talk to the NPC with actions (streaming)
6481
+ * Text streams first, action calls are returned in onComplete
6121
6482
  */
6122
- clearPlayerDescription() {
6123
- this.playerDescription = null;
6124
- this.emit('playerDescriptionChanged', null);
6483
+ async talkWithActionsStream(message, actions, onChunk, onComplete) {
6484
+ this._isTalking = true;
6485
+ try {
6486
+ // Add user message to history
6487
+ const userMessage = { role: 'user', content: message };
6488
+ this.history.push(userMessage);
6489
+ // Convert NpcActions to ChatTools
6490
+ const tools = actions
6491
+ .filter(a => a && a.enabled !== false)
6492
+ .map(a => npcActionToTool(a));
6493
+ // Build messages array with system prompt
6494
+ const messages = [
6495
+ { role: 'system', content: this.buildSystemPrompt() },
6496
+ ...this.history,
6497
+ ];
6498
+ // Generate response with tools (streaming)
6499
+ await this.chatClient.textGenerationWithToolsStream({
6500
+ messages,
6501
+ temperature: this.temperature,
6502
+ tools,
6503
+ tool_choice: 'auto',
6504
+ onChunk,
6505
+ onComplete: (result) => {
6506
+ this._isTalking = false;
6507
+ // Build response
6508
+ const response = {
6509
+ text: result.content || '',
6510
+ actionCalls: [],
6511
+ hasActions: false,
6512
+ };
6513
+ // Extract tool calls if any
6514
+ if (result.tool_calls) {
6515
+ response.actionCalls = result.tool_calls.map(tc => ({
6516
+ id: tc.id,
6517
+ actionName: tc.function.name,
6518
+ arguments: this.parseToolArguments(tc.function.arguments),
6519
+ }));
6520
+ response.hasActions = response.actionCalls.length > 0;
6521
+ }
6522
+ // Add assistant response to history
6523
+ const assistantMessage = {
6524
+ role: 'assistant',
6525
+ content: response.text,
6526
+ tool_calls: result.tool_calls,
6527
+ };
6528
+ this.history.push(assistantMessage);
6529
+ this.trimHistory();
6530
+ this.emit('response', response.text);
6531
+ if (response.hasActions) {
6532
+ this.emit('actions', response.actionCalls);
6533
+ }
6534
+ // Trigger reply prediction generation (fire and forget)
6535
+ this.triggerReplyPrediction();
6536
+ if (onComplete) {
6537
+ onComplete(response);
6538
+ }
6539
+ },
6540
+ });
6541
+ }
6542
+ catch (error) {
6543
+ this._isTalking = false;
6544
+ throw error;
6545
+ }
6125
6546
  }
6126
- // ===== NPC Tracking =====
6547
+ // ===== Action Results Reporting =====
6127
6548
  /**
6128
- * Register an NPC for context management.
6129
- * @param npc The NPC client to register
6549
+ * Report action results back to the conversation
6550
+ * Call this after executing actions to let the NPC know the results
6130
6551
  */
6131
- registerNpc(npc) {
6132
- if (!npc)
6133
- return;
6134
- if (!this.npcStates.has(npc)) {
6135
- this.npcStates.set(npc, {
6136
- lastConversationTime: new Date(),
6137
- isCompacted: false,
6138
- compactionCount: 0,
6552
+ reportActionResults(results) {
6553
+ for (const [callId, result] of Object.entries(results)) {
6554
+ this.history.push({
6555
+ role: 'tool',
6556
+ tool_call_id: callId,
6557
+ content: result,
6139
6558
  });
6140
6559
  }
6141
6560
  }
6142
6561
  /**
6143
- * Unregister an NPC (call when NPC is destroyed/removed).
6144
- * @param npc The NPC client to unregister
6562
+ * Report a single action result
6145
6563
  */
6146
- unregisterNpc(npc) {
6147
- if (!npc)
6148
- return;
6149
- this.npcStates.delete(npc);
6564
+ reportActionResult(callId, result) {
6565
+ this.history.push({
6566
+ role: 'tool',
6567
+ tool_call_id: callId,
6568
+ content: result,
6569
+ });
6150
6570
  }
6151
6571
  /**
6152
- * Record that a conversation occurred with an NPC.
6153
- * Called after each Talk() exchange.
6154
- * @param npc The NPC client that had a conversation
6572
+ * Parse tool arguments from JSON string
6155
6573
  */
6156
- recordConversation(npc) {
6157
- if (!npc)
6158
- return;
6159
- if (!this.npcStates.has(npc)) {
6160
- this.registerNpc(npc);
6574
+ parseToolArguments(args) {
6575
+ try {
6576
+ return JSON.parse(args);
6577
+ }
6578
+ catch (_a) {
6579
+ return {};
6161
6580
  }
6162
- const state = this.npcStates.get(npc);
6163
- state.lastConversationTime = new Date();
6164
- state.isCompacted = false; // Reset compaction flag on new conversation
6165
6581
  }
6582
+ // ===== Conversation History Management =====
6166
6583
  /**
6167
- * Get all registered NPCs
6584
+ * Get conversation history
6168
6585
  */
6169
- getRegisteredNpcs() {
6170
- return Array.from(this.npcStates.keys());
6586
+ getHistory() {
6587
+ return [...this.history];
6171
6588
  }
6172
6589
  /**
6173
- * Get the conversation state for an NPC
6590
+ * Get the number of messages in history
6174
6591
  */
6175
- getNpcState(npc) {
6176
- return this.npcStates.get(npc);
6592
+ getHistoryLength() {
6593
+ return this.history.length;
6177
6594
  }
6178
- // ===== Auto Compaction =====
6179
6595
  /**
6180
- * Check if an NPC is eligible for compaction.
6181
- * @param npc The NPC to check
6182
- * @returns True if eligible for compaction
6596
+ * Clear conversation history.
6597
+ * The character design and memories will be preserved.
6183
6598
  */
6184
- isEligibleForCompaction(npc) {
6185
- if (!npc)
6186
- return false;
6187
- const state = this.npcStates.get(npc);
6188
- if (!state)
6189
- return false;
6190
- // Check if already compacted since last conversation
6191
- if (state.isCompacted)
6192
- return false;
6193
- // Check message count
6194
- const history = npc.getHistory();
6195
- const nonSystemMessages = history.filter(m => m.role !== 'system').length;
6196
- if (nonSystemMessages < this.config.autoCompactMinMessages)
6197
- return false;
6198
- // Check time since last conversation
6199
- const timeSinceLastConversation = (Date.now() - state.lastConversationTime.getTime()) / 1000;
6200
- if (timeSinceLastConversation < this.config.autoCompactTimeoutSeconds)
6201
- return false;
6202
- return true;
6599
+ clearHistory() {
6600
+ this.history = [];
6601
+ this.emit('history_cleared');
6203
6602
  }
6204
6603
  /**
6205
- * Manually trigger conversation compaction for a specific NPC.
6206
- * Summarizes the conversation history and stores it as a memory.
6207
- * @param npc The NPC to compact
6208
- * @returns True if compaction succeeded
6604
+ * Revert the last exchange (user message and assistant response) from history.
6605
+ * @returns true if reverted, false if not enough history
6209
6606
  */
6210
- async compactConversation(npc) {
6211
- if (!npc) {
6212
- this.logger.warn('Cannot compact: NPC is null');
6213
- return false;
6214
- }
6215
- if (!this.chatClientFactory) {
6216
- this.logger.error('Cannot compact: No chat client factory set. Call setChatClientFactory() first.');
6217
- return false;
6218
- }
6219
- const history = npc.getHistory();
6220
- const nonSystemMessages = history.filter(m => m.role !== 'system');
6221
- if (nonSystemMessages.length < 2) {
6222
- this.logger.info('Skipping compaction: not enough messages');
6223
- return false;
6224
- }
6225
- try {
6226
- this.logger.info(`Starting compaction (${nonSystemMessages.length} messages)`);
6227
- // Build conversation text for summarization
6228
- const conversationText = nonSystemMessages
6229
- .map(m => `${m.role}: ${m.content}`)
6230
- .join('\n');
6231
- // Create summarization prompt
6232
- const summaryPrompt = `Summarize the following conversation concisely. Focus on:
6233
- 1. Key topics discussed
6234
- 2. Important information exchanged
6235
- 3. Any decisions or commitments made
6236
- 4. The emotional tone
6237
-
6238
- Keep the summary under 200 words. Write in third person.
6239
-
6240
- Conversation:
6241
- ${conversationText}`;
6242
- // Use chat client for summarization
6243
- const chatClient = this.chatClientFactory();
6244
- const result = await chatClient.textGeneration({
6245
- messages: [{ role: 'user', content: summaryPrompt }],
6246
- temperature: 0.5,
6247
- model: this.config.fastModel || undefined,
6248
- });
6249
- if (!result.content) {
6250
- const error = 'Empty response from summarization';
6251
- this.logger.error(`Compaction failed: ${error}`);
6252
- this.emit('compactionFailed', npc, error);
6253
- return false;
6607
+ revertHistory() {
6608
+ let lastAssistantIndex = -1;
6609
+ let lastUserIndex = -1;
6610
+ for (let i = this.history.length - 1; i >= 0; i--) {
6611
+ if (this.history[i].role === 'assistant' && lastAssistantIndex === -1) {
6612
+ lastAssistantIndex = i;
6254
6613
  }
6255
- // Clear history and add summary as memory
6256
- npc.clearHistory();
6257
- npc.setMemory('PreviousConversationSummary', result.content);
6258
- // Update state
6259
- const state = this.npcStates.get(npc);
6260
- if (state) {
6261
- state.isCompacted = true;
6262
- state.compactionCount++;
6614
+ else if (this.history[i].role === 'user' && lastAssistantIndex !== -1 && lastUserIndex === -1) {
6615
+ lastUserIndex = i;
6616
+ break;
6263
6617
  }
6264
- this.logger.info(`Compaction completed. Summary: ${result.content.substring(0, 100)}...`);
6265
- this.emit('npcCompacted', npc);
6266
- return true;
6267
6618
  }
6268
- catch (error) {
6269
- const errorMessage = error instanceof Error ? error.message : String(error);
6270
- this.logger.error(`Compaction error: ${errorMessage}`);
6271
- this.emit('compactionFailed', npc, errorMessage);
6272
- return false;
6619
+ if (lastAssistantIndex !== -1 && lastUserIndex !== -1) {
6620
+ // Remove in reverse order to maintain indices
6621
+ this.history.splice(lastAssistantIndex, 1);
6622
+ this.history.splice(lastUserIndex, 1);
6623
+ this.emit('history_reverted');
6624
+ return true;
6273
6625
  }
6626
+ return false;
6274
6627
  }
6275
6628
  /**
6276
- * Compact all registered NPCs that meet the eligibility criteria.
6277
- * @returns Number of NPCs successfully compacted
6629
+ * Revert (remove) the last N chat messages from history
6630
+ * @param count Number of messages to remove
6631
+ * @returns Number of messages actually removed
6278
6632
  */
6279
- async compactAllEligible() {
6280
- const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
6281
- if (eligibleNpcs.length === 0) {
6633
+ revertChatMessages(count) {
6634
+ if (count <= 0)
6282
6635
  return 0;
6636
+ const messagesToRemove = Math.min(count, this.history.length);
6637
+ const originalCount = this.history.length;
6638
+ this.history = this.history.slice(0, -messagesToRemove);
6639
+ const actuallyRemoved = originalCount - this.history.length;
6640
+ if (actuallyRemoved > 0) {
6641
+ this.emit('history_reverted', actuallyRemoved);
6283
6642
  }
6284
- this.logger.info(`Compacting ${eligibleNpcs.length} eligible NPCs`);
6285
- let successCount = 0;
6286
- for (const npc of eligibleNpcs) {
6287
- const success = await this.compactConversation(npc);
6288
- if (success)
6289
- successCount++;
6290
- }
6291
- return successCount;
6643
+ return actuallyRemoved;
6292
6644
  }
6293
- // ===== Auto Compact Timer =====
6294
6645
  /**
6295
- * Start the auto-compact check timer
6646
+ * Revert to a specific point in history
6647
+ * @deprecated Use revertHistory() or revertChatMessages() instead
6296
6648
  */
6297
- startAutoCompactCheck() {
6298
- if (this.autoCompactTimer) {
6299
- this.stopAutoCompactCheck();
6649
+ revertToMessage(index) {
6650
+ if (index >= 0 && index < this.history.length) {
6651
+ this.history = this.history.slice(0, index + 1);
6652
+ this.emit('history_reverted', index);
6300
6653
  }
6301
- this.autoCompactTimer = setInterval(() => {
6302
- this.runAutoCompactCheck();
6303
- }, this.config.autoCompactCheckInterval);
6304
6654
  }
6305
6655
  /**
6306
- * Stop the auto-compact check timer
6656
+ * Append a message to history manually
6307
6657
  */
6308
- stopAutoCompactCheck() {
6309
- if (this.autoCompactTimer) {
6310
- clearInterval(this.autoCompactTimer);
6311
- this.autoCompactTimer = null;
6312
- }
6658
+ appendMessage(message) {
6659
+ this.history.push(message);
6660
+ this.trimHistory();
6313
6661
  }
6314
6662
  /**
6315
- * Run a single auto-compact check
6663
+ * Alias for appendMessage (Unity SDK compatibility)
6316
6664
  */
6317
- async runAutoCompactCheck() {
6318
- if (!this.config.enableAutoCompact)
6665
+ appendChatMessage(role, content) {
6666
+ if (!role || !content) {
6667
+ this.logger.warn('Role and content cannot be empty');
6319
6668
  return;
6320
- const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
6321
- for (const npc of eligibleNpcs) {
6322
- // Fire and forget - don't block
6323
- this.compactConversation(npc).catch(err => {
6324
- this.logger.error('Auto-compact error:', err);
6325
- });
6326
6669
  }
6670
+ this.appendMessage({ role: role, content });
6327
6671
  }
6328
- // ===== Lifecycle =====
6329
6672
  /**
6330
- * Enable auto-compaction
6673
+ * Trim history to max length
6331
6674
  */
6332
- enableAutoCompact() {
6333
- this.config.enableAutoCompact = true;
6334
- this.startAutoCompactCheck();
6675
+ trimHistory() {
6676
+ if (this.history.length > this.maxHistoryLength) {
6677
+ // Keep the most recent messages
6678
+ this.history = this.history.slice(-this.maxHistoryLength);
6679
+ }
6335
6680
  }
6681
+ // ===== Save/Load =====
6336
6682
  /**
6337
- * Disable auto-compaction
6683
+ * Save the current conversation history to a serializable format.
6684
+ * Includes characterDesign, memories, and history.
6338
6685
  */
6339
- disableAutoCompact() {
6340
- this.config.enableAutoCompact = false;
6341
- this.stopAutoCompactCheck();
6686
+ saveHistory() {
6687
+ const saveData = {
6688
+ characterDesign: this.characterDesign,
6689
+ memories: Array.from(this.memories.entries()).map(([name, content]) => ({ name, content })),
6690
+ history: this.history,
6691
+ };
6692
+ return JSON.stringify(saveData);
6342
6693
  }
6343
6694
  /**
6344
- * Clean up resources
6695
+ * Load conversation history from serialized data.
6696
+ * Restores characterDesign, memories, and history.
6345
6697
  */
6346
- destroy() {
6347
- this.stopAutoCompactCheck();
6348
- this.npcStates.clear();
6349
- this.playerDescription = null;
6350
- this.removeAllListeners();
6698
+ loadHistory(saveData) {
6699
+ try {
6700
+ const data = JSON.parse(saveData);
6701
+ // Load character design (with backwards compatibility for old systemPrompt field)
6702
+ this.characterDesign = data.characterDesign || data.systemPrompt || this.characterDesign;
6703
+ // Load memories
6704
+ this.memories.clear();
6705
+ if (data.memories && Array.isArray(data.memories)) {
6706
+ for (const memory of data.memories) {
6707
+ if (memory.name && memory.content) {
6708
+ this.memories.set(memory.name, memory.content);
6709
+ }
6710
+ }
6711
+ }
6712
+ // Load history (skip system messages as they'll be rebuilt from characterDesign + memories)
6713
+ this.history = (data.history || []).filter(m => m.role !== 'system');
6714
+ this.emit('history_loaded');
6715
+ return true;
6716
+ }
6717
+ catch (error) {
6718
+ this.logger.error('Failed to load history:', error);
6719
+ return false;
6720
+ }
6351
6721
  }
6352
6722
  }
6353
- AIContextManager._instance = null;
6354
- /**
6355
- * Default AIContextManager instance
6356
- * Can be used as a global context manager
6357
- */
6358
- const defaultContextManager = AIContextManager.getInstance();
6359
6723
 
6360
6724
  /**
6361
6725
  * Schema Library for managing JSON schemas for AI structured output generation
@@ -6567,10 +6931,12 @@ ${conversationText}`;
6567
6931
  this.chatProvider = new ChatProvider(this.authManager, this.config);
6568
6932
  this.imageProvider = new ImageProvider(this.authManager, this.config);
6569
6933
  this.transcriptionProvider = new TranscriptionProvider(this.authManager, this.config);
6934
+ this.ttsProvider = new TTSProvider(this.authManager, this.config);
6570
6935
  // Connect providers to player client for balance checking
6571
6936
  this.chatProvider.setPlayerClient(this.playerClient);
6572
6937
  this.imageProvider.setPlayerClient(this.playerClient);
6573
6938
  this.transcriptionProvider.setPlayerClient(this.playerClient);
6939
+ this.ttsProvider.setPlayerClient(this.playerClient);
6574
6940
  // Initialize AI context manager
6575
6941
  this.contextManager = new AIContextManager(this.config.aiContext);
6576
6942
  // Set chat client factory for compaction
@@ -6714,20 +7080,20 @@ ${conversationText}`;
6714
7080
  // Create indicator element
6715
7081
  this.devTokenIndicator = document.createElement('div');
6716
7082
  this.devTokenIndicator.textContent = 'DeveloperToken';
6717
- this.devTokenIndicator.style.cssText = `
6718
- position: fixed;
6719
- top: 10px;
6720
- left: 10px;
6721
- background-color: #dc2626;
6722
- color: white;
6723
- padding: 4px 12px;
6724
- border-radius: 4px;
6725
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
6726
- font-size: 12px;
6727
- font-weight: 600;
6728
- z-index: 999999;
6729
- pointer-events: none;
6730
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
7083
+ this.devTokenIndicator.style.cssText = `
7084
+ position: fixed;
7085
+ top: 10px;
7086
+ left: 10px;
7087
+ background-color: #dc2626;
7088
+ color: white;
7089
+ padding: 4px 12px;
7090
+ border-radius: 4px;
7091
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
7092
+ font-size: 12px;
7093
+ font-weight: 600;
7094
+ z-index: 999999;
7095
+ pointer-events: none;
7096
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
6731
7097
  `;
6732
7098
  document.body.appendChild(this.devTokenIndicator);
6733
7099
  }
@@ -6819,6 +7185,14 @@ ${conversationText}`;
6819
7185
  this.ensureInitialized();
6820
7186
  return new TranscriptionClient(this.transcriptionProvider, model || this.config.defaultTranscriptionModel);
6821
7187
  }
7188
+ /**
7189
+ * Create a TTS client for text-to-speech
7190
+ * @param model - TTS model to use (default: 'default-tts-model')
7191
+ */
7192
+ createTTSClient(model) {
7193
+ this.ensureInitialized();
7194
+ return new TTSClient(this.ttsProvider, model || this.config.defaultTTSModel);
7195
+ }
6822
7196
  /**
6823
7197
  * Create an NPC client
6824
7198
  * Automatically registers with AIContextManager
@@ -6890,7 +7264,7 @@ ${conversationText}`;
6890
7264
  */
6891
7265
  setDebug(enabled) {
6892
7266
  this.config.debug = enabled;
6893
- Logger.setGlobalLevel(enabled ? exports.LogLevel.DEBUG : exports.LogLevel.WARN);
7267
+ Logger.setGlobalLevel(enabled ? LogLevel.DEBUG : LogLevel.WARN);
6894
7268
  }
6895
7269
  /**
6896
7270
  * Configure the logging system
@@ -6912,7 +7286,7 @@ ${conversationText}`;
6912
7286
  initializeLogging(config) {
6913
7287
  // Handle legacy debug option for backwards compatibility
6914
7288
  if (config.debug !== undefined && config.logging === undefined) {
6915
- Logger.setGlobalLevel(config.debug ? exports.LogLevel.DEBUG : exports.LogLevel.WARN);
7289
+ Logger.setGlobalLevel(config.debug ? LogLevel.DEBUG : LogLevel.WARN);
6916
7290
  }
6917
7291
  // Apply new logging config
6918
7292
  if (config.logging) {
@@ -7116,9 +7490,7 @@ ${conversationText}`;
7116
7490
  */
7117
7491
  async validateToken(token, gameId) {
7118
7492
  var _a, _b;
7119
- const headers = {
7120
- 'Authorization': `Bearer ${token}`,
7121
- };
7493
+ const headers = Object.assign({ 'Authorization': `Bearer ${token}` }, getSDKHeaders());
7122
7494
  if (gameId) {
7123
7495
  headers['X-Game-Id'] = gameId;
7124
7496
  }
@@ -7148,9 +7520,7 @@ ${conversationText}`;
7148
7520
  */
7149
7521
  async verifyToken(token, gameId) {
7150
7522
  var _a, _b;
7151
- const headers = {
7152
- 'Authorization': `Bearer ${token}`,
7153
- };
7523
+ const headers = Object.assign({ 'Authorization': `Bearer ${token}` }, getSDKHeaders());
7154
7524
  if (gameId) {
7155
7525
  headers['X-Game-Id'] = gameId;
7156
7526
  }
@@ -7213,36 +7583,62 @@ ${conversationText}`;
7213
7583
  */
7214
7584
  const defaultTokenValidator = new TokenValidator();
7215
7585
 
7216
- exports.AIContextManager = AIContextManager;
7217
- exports.AuthFlowManager = AuthFlowManager;
7218
- exports.AuthManager = AuthManager;
7219
- exports.BrowserStorage = BrowserStorage;
7220
- exports.BufferLogHandler = BufferLogHandler;
7221
- exports.CallbackLogHandler = CallbackLogHandler;
7222
- exports.ChatClient = ChatClient;
7223
- exports.DeviceAuthFlowManager = DeviceAuthFlowManager;
7224
- exports.ImageClient = ImageClient;
7225
- exports.Logger = Logger;
7226
- exports.MemoryStorage = MemoryStorage;
7227
- exports.NPCClient = NPCClient;
7228
- exports.PlayKitSDK = PlayKitSDK;
7229
- exports.PlayerClient = PlayerClient;
7230
- exports.RechargeManager = RechargeManager;
7231
- exports.SchemaLibrary = SchemaLibrary;
7232
- exports.StreamParser = StreamParser;
7233
- exports.TokenStorage = TokenStorage;
7234
- exports.TokenValidator = TokenValidator;
7235
- exports.TranscriptionClient = TranscriptionClient;
7236
- exports.createMultimodalMessage = createMultimodalMessage;
7237
- exports.createStorage = createStorage;
7238
- exports.createTextMessage = createTextMessage;
7239
- exports.default = PlayKitSDK;
7240
- exports.defaultContextManager = defaultContextManager;
7241
- exports.defaultSchemaLibrary = defaultSchemaLibrary;
7242
- exports.defaultTokenValidator = defaultTokenValidator;
7243
- exports.isLocalStorageAvailable = isLocalStorageAvailable;
7244
-
7245
- Object.defineProperty(exports, '__esModule', { value: true });
7586
+ /**
7587
+ * PlayKit SDK for JavaScript
7588
+ * AI integration for web-based games
7589
+ */
7590
+ // Main SDK
7591
+
7592
+ var namespace = /*#__PURE__*/Object.freeze({
7593
+ __proto__: null,
7594
+ AIContextManager: AIContextManager,
7595
+ AuthFlowManager: AuthFlowManager,
7596
+ AuthManager: AuthManager,
7597
+ BrowserStorage: BrowserStorage,
7598
+ BufferLogHandler: BufferLogHandler,
7599
+ CallbackLogHandler: CallbackLogHandler,
7600
+ ChatClient: ChatClient,
7601
+ DeviceAuthFlowManager: DeviceAuthFlowManager,
7602
+ ImageClient: ImageClient,
7603
+ get LogLevel () { return LogLevel; },
7604
+ Logger: Logger,
7605
+ MemoryStorage: MemoryStorage,
7606
+ NPCClient: NPCClient,
7607
+ PlayKitSDK: PlayKitSDK,
7608
+ PlayerClient: PlayerClient,
7609
+ RechargeManager: RechargeManager,
7610
+ SchemaLibrary: SchemaLibrary,
7611
+ StreamParser: StreamParser,
7612
+ TTSClient: TTSClient,
7613
+ TokenStorage: TokenStorage,
7614
+ TokenValidator: TokenValidator,
7615
+ TranscriptionClient: TranscriptionClient,
7616
+ createMultimodalMessage: createMultimodalMessage,
7617
+ createStorage: createStorage,
7618
+ createTextMessage: createTextMessage,
7619
+ default: PlayKitSDK,
7620
+ defaultContextManager: defaultContextManager,
7621
+ defaultSchemaLibrary: defaultSchemaLibrary,
7622
+ defaultTokenValidator: defaultTokenValidator,
7623
+ isLocalStorageAvailable: isLocalStorageAvailable
7624
+ });
7625
+
7626
+ /**
7627
+ * UMD bundle entry.
7628
+ *
7629
+ * Goal: make `window.PlayKitSDK` directly the constructor class while still
7630
+ * exposing every named export as a property on it.
7631
+ *
7632
+ * Result:
7633
+ * new window.PlayKitSDK(cfg) // recommended
7634
+ * new window.PlayKitSDK.PlayKitSDK(cfg) // legacy v1.x form, still works
7635
+ * window.PlayKitSDK.ChatClient // named exports preserved
7636
+ */
7637
+ Object.assign(PlayKitSDK, namespace);
7638
+ PlayKitSDK.PlayKitSDK = PlayKitSDK;
7639
+ PlayKitSDK.default = PlayKitSDK;
7640
+
7641
+ return PlayKitSDK;
7246
7642
 
7247
7643
  }));
7248
7644
  //# sourceMappingURL=playkit-sdk.umd.js.map