playkit-sdk 1.2.12 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,13 @@
1
1
  /**
2
- * playkit-sdk v1.2.12
2
+ * playkit-sdk v1.3.0
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.3.0"';
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') {
@@ -2777,6 +2784,8 @@
2777
2784
  this.logger = Logger.getLogger('AuthManager');
2778
2785
  /** Shared promise for current device auth flow - allows multiple callers to await the same result */
2779
2786
  this.currentDeviceAuthFlowPromise = null;
2787
+ /** Shared promise for current auth flow (startAuthFlow) - allows multiple callers to await the same result */
2788
+ this.currentAuthFlowPromise = null;
2780
2789
  this.config = config;
2781
2790
  // Create TokenStorage with appropriate mode for server vs browser environment
2782
2791
  this.storage = new TokenStorage({
@@ -2885,12 +2894,27 @@
2885
2894
  * @deprecated 'headless' authentication is deprecated and will be removed in v2.0. Use 'device' instead.
2886
2895
  */
2887
2896
  async startAuthFlow(authMethod = 'device') {
2888
- var _a, _b;
2889
- if (this.authFlowManager || this.deviceAuthFlowManager) {
2890
- // Already in progress
2891
- this.logger.warn('Auth flow already in progress, ignoring duplicate call');
2892
- return;
2897
+ // If a flow is already in progress, return the shared promise so all callers await the same result
2898
+ if (this.currentAuthFlowPromise) {
2899
+ this.logger.debug('Auth flow already in progress, waiting for existing flow');
2900
+ return this.currentAuthFlowPromise;
2901
+ }
2902
+ // Store the flow promise so subsequent calls can await the same result
2903
+ const flowPromise = this.executeAuthFlow(authMethod);
2904
+ this.currentAuthFlowPromise = flowPromise;
2905
+ try {
2906
+ return await flowPromise;
2907
+ }
2908
+ finally {
2909
+ this.currentAuthFlowPromise = null;
2893
2910
  }
2911
+ }
2912
+ /**
2913
+ * Internal method that executes the actual auth flow
2914
+ * @private
2915
+ */
2916
+ async executeAuthFlow(authMethod = 'device') {
2917
+ var _a, _b;
2894
2918
  // Deprecation warning for headless auth
2895
2919
  if (authMethod === 'headless') {
2896
2920
  this.logger.warn('"headless" authentication is deprecated and will be removed in v2.0. ' +
@@ -2949,10 +2973,7 @@
2949
2973
  try {
2950
2974
  const response = await fetch(`${this.baseURL}${JWT_EXCHANGE_ENDPOINT}`, {
2951
2975
  method: 'POST',
2952
- headers: {
2953
- Authorization: `Bearer ${jwt}`,
2954
- 'Content-Type': 'application/json',
2955
- },
2976
+ headers: Object.assign({ Authorization: `Bearer ${jwt}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
2956
2977
  body: JSON.stringify({ gameId: this.config.gameId }),
2957
2978
  });
2958
2979
  if (!response.ok) {
@@ -3302,9 +3323,7 @@
3302
3323
  this.logger.debug('Refreshing access token');
3303
3324
  const response = await fetch(`${this.baseURL}${TOKEN_REFRESH_ENDPOINT}`, {
3304
3325
  method: 'POST',
3305
- headers: {
3306
- 'Content-Type': 'application/json',
3307
- },
3326
+ headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
3308
3327
  body: JSON.stringify({
3309
3328
  refresh_token: this.authState.refreshToken,
3310
3329
  }),
@@ -3407,7 +3426,7 @@
3407
3426
  * RechargeManager handles the recharge modal UI and recharge window opening
3408
3427
  */
3409
3428
  class RechargeManager extends EventEmitter {
3410
- constructor(playerToken, rechargePortalUrl = 'https://playkit.ai/recharge', gameId) {
3429
+ constructor(playerToken, rechargePortalUrl = 'https://players.playkit.ai/recharge', gameId) {
3411
3430
  super();
3412
3431
  this.modalContainer = null;
3413
3432
  this.styleElement = null;
@@ -3510,220 +3529,220 @@
3510
3529
  return;
3511
3530
  }
3512
3531
  this.styleElement = document.createElement('style');
3513
- this.styleElement.textContent = `
3514
- .playkit-recharge-overlay {
3515
- position: fixed;
3516
- top: 0;
3517
- left: 0;
3518
- right: 0;
3519
- bottom: 0;
3520
- background: rgba(0, 0, 0, 0.8);
3521
- display: flex;
3522
- justify-content: center;
3523
- align-items: center;
3524
- z-index: 999999;
3525
- animation: playkit-recharge-fadeIn 0.2s ease-out;
3526
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3527
- }
3528
-
3529
- @keyframes playkit-recharge-fadeIn {
3530
- from {
3531
- opacity: 0;
3532
- }
3533
- to {
3534
- opacity: 1;
3535
- }
3536
- }
3537
-
3538
- .playkit-recharge-modal {
3539
- background: #fff;
3540
- border: 1px solid rgba(0, 0, 0, 0.1);
3541
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
3542
- padding: 24px;
3543
- max-width: 320px;
3544
- width: 90%;
3545
- position: relative;
3546
- text-align: center;
3547
- }
3548
-
3549
- .playkit-recharge-title {
3550
- font-size: 14px;
3551
- font-weight: 600;
3552
- color: #171717;
3553
- margin: 0 0 8px 0;
3554
- text-align: center;
3555
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3556
- }
3557
-
3558
- .playkit-recharge-message {
3559
- font-size: 14px;
3560
- color: #666;
3561
- margin: 0 0 20px 0;
3562
- text-align: center;
3563
- line-height: 1.5;
3564
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3565
- }
3566
-
3567
- .playkit-recharge-balance {
3568
- background: #f5f5f5;
3569
- border: 1px solid #e5e7eb;
3570
- padding: 16px;
3571
- margin: 0 0 20px 0;
3572
- text-align: center;
3573
- }
3574
-
3575
- .playkit-recharge-balance-label {
3576
- font-size: 12px;
3577
- color: #666;
3578
- margin: 0 0 8px 0;
3579
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3580
- }
3581
-
3582
- .playkit-recharge-balance-value {
3583
- font-size: 24px;
3584
- font-weight: bold;
3585
- color: #171717;
3586
- margin: 0;
3587
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3588
- }
3589
-
3590
- .playkit-recharge-balance-unit {
3591
- font-size: 14px;
3592
- color: #666;
3593
- margin-left: 4px;
3594
- }
3595
-
3596
- .playkit-recharge-buttons {
3597
- display: flex;
3598
- flex-direction: column;
3599
- gap: 8px;
3600
- }
3601
-
3602
- .playkit-recharge-button {
3603
- width: 100%;
3604
- padding: 10px 16px;
3605
- border: none;
3606
- font-size: 14px;
3607
- font-weight: 500;
3608
- cursor: pointer;
3609
- transition: all 0.2s ease;
3610
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3611
- }
3612
-
3613
- .playkit-recharge-button-primary {
3614
- background: #171717;
3615
- color: white;
3616
- }
3617
-
3618
- .playkit-recharge-button-primary:hover {
3619
- background: #404040;
3620
- }
3621
-
3622
- .playkit-recharge-button-primary:active {
3623
- background: #0a0a0a;
3624
- }
3625
-
3626
- .playkit-recharge-button-secondary {
3627
- background: transparent;
3628
- color: #666;
3629
- border: 1px solid #e5e7eb;
3630
- }
3631
-
3632
- .playkit-recharge-button-secondary:hover {
3633
- background: #f5f5f5;
3634
- border-color: #d4d4d4;
3635
- }
3636
-
3637
- .playkit-recharge-button-secondary:active {
3638
- background: #e5e5e5;
3639
- }
3640
-
3641
- @media (max-width: 480px) {
3642
- .playkit-recharge-modal {
3643
- padding: 20px;
3644
- }
3645
- }
3646
-
3647
- /* Daily Refresh Toast Styles */
3648
- .playkit-daily-refresh-toast {
3649
- position: fixed;
3650
- top: 20px;
3651
- right: 20px;
3652
- background: #fff;
3653
- border: 1px solid rgba(0, 0, 0, 0.1);
3654
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.1);
3655
- padding: 16px 20px;
3656
- min-width: 240px;
3657
- max-width: 320px;
3658
- z-index: 999998;
3659
- animation: playkit-toast-slideIn 0.3s ease-out;
3660
- display: flex;
3661
- align-items: flex-start;
3662
- gap: 12px;
3663
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3664
- }
3665
-
3666
- .playkit-daily-refresh-toast.hiding {
3667
- animation: playkit-toast-fadeOut 0.3s ease-out forwards;
3668
- }
3669
-
3670
- @keyframes playkit-toast-slideIn {
3671
- from {
3672
- transform: translateX(100%);
3673
- opacity: 0;
3674
- }
3675
- to {
3676
- transform: translateX(0);
3677
- opacity: 1;
3678
- }
3679
- }
3680
-
3681
- @keyframes playkit-toast-fadeOut {
3682
- from {
3683
- transform: translateX(0);
3684
- opacity: 1;
3685
- }
3686
- to {
3687
- transform: translateX(100%);
3688
- opacity: 0;
3689
- }
3690
- }
3691
-
3692
- .playkit-toast-icon {
3693
- width: 24px;
3694
- height: 24px;
3695
- background: #171717;
3696
- border-radius: 50%;
3697
- display: flex;
3698
- align-items: center;
3699
- justify-content: center;
3700
- flex-shrink: 0;
3701
- }
3702
-
3703
- .playkit-toast-icon svg {
3704
- width: 14px;
3705
- height: 14px;
3706
- color: #ffffff;
3707
- }
3708
-
3709
- .playkit-toast-message {
3710
- flex: 1;
3711
- font-size: 14px;
3712
- font-weight: 500;
3713
- color: #171717;
3714
- line-height: 1.4;
3715
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3716
- }
3717
-
3718
- @media (max-width: 480px) {
3719
- .playkit-daily-refresh-toast {
3720
- top: 10px;
3721
- right: 10px;
3722
- left: 10px;
3723
- min-width: auto;
3724
- max-width: none;
3725
- }
3726
- }
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
+ }
3727
3746
  `;
3728
3747
  document.head.appendChild(this.styleElement);
3729
3748
  }
@@ -3860,7 +3879,8 @@
3860
3879
  /**
3861
3880
  * Player client for managing player information and credits
3862
3881
  */
3863
- const DEFAULT_BASE_URL$4 = 'https://playkit.ai';
3882
+ // @ts-ignore - replaced at build time
3883
+ const DEFAULT_BASE_URL$4 = "https://api.playkit.ai";
3864
3884
  const PLAYER_INFO_ENDPOINT = '/api/external/player-info';
3865
3885
  const SET_NICKNAME_ENDPOINT = '/api/external/set-game-player-nickname';
3866
3886
  class PlayerClient extends EventEmitter {
@@ -3878,7 +3898,7 @@
3878
3898
  autoShowBalanceModal: (_a = rechargeConfig.autoShowBalanceModal) !== null && _a !== void 0 ? _a : true,
3879
3899
  balanceCheckInterval: (_b = rechargeConfig.balanceCheckInterval) !== null && _b !== void 0 ? _b : 30000,
3880
3900
  checkBalanceAfterApiCall: (_c = rechargeConfig.checkBalanceAfterApiCall) !== null && _c !== void 0 ? _c : true,
3881
- rechargePortalUrl: rechargeConfig.rechargePortalUrl || 'https://playkit.ai/recharge',
3901
+ rechargePortalUrl: rechargeConfig.rechargePortalUrl || 'https://players.playkit.ai/recharge',
3882
3902
  showDailyRefreshToast: (_d = rechargeConfig.showDailyRefreshToast) !== null && _d !== void 0 ? _d : true,
3883
3903
  };
3884
3904
  }
@@ -3893,9 +3913,7 @@
3893
3913
  }
3894
3914
  try {
3895
3915
  // Build headers with X-Game-Id to support Global Developer Token
3896
- const headers = {
3897
- Authorization: `Bearer ${token}`,
3898
- };
3916
+ const headers = Object.assign({ Authorization: `Bearer ${token}` }, getSDKHeaders());
3899
3917
  if (this.gameId) {
3900
3918
  headers['X-Game-Id'] = this.gameId;
3901
3919
  }
@@ -3995,10 +4013,7 @@
3995
4013
  try {
3996
4014
  const response = await fetch(`${this.baseURL}${SET_NICKNAME_ENDPOINT}`, {
3997
4015
  method: 'POST',
3998
- headers: {
3999
- Authorization: `Bearer ${token}`,
4000
- 'Content-Type': 'application/json',
4001
- },
4016
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
4002
4017
  body: JSON.stringify({ nickname: trimmed }),
4003
4018
  });
4004
4019
  if (!response.ok) {
@@ -4148,10 +4163,84 @@
4148
4163
  }
4149
4164
  }
4150
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
+
4151
4227
  /**
4152
4228
  * Chat provider for HTTP communication with chat API
4153
4229
  */
4154
- 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$3 = "https://api.playkit.ai";
4155
4244
  class ChatProvider {
4156
4245
  constructor(authManager, config) {
4157
4246
  this.authManager = authManager;
@@ -4169,6 +4258,7 @@
4169
4258
  */
4170
4259
  async chatCompletion(chatConfig) {
4171
4260
  var _a;
4261
+ assertValidMessages(chatConfig.messages);
4172
4262
  // Ensure token is valid, auto-refresh if needed (browser mode only)
4173
4263
  await this.authManager.ensureValidToken();
4174
4264
  const token = this.authManager.getToken();
@@ -4190,10 +4280,7 @@
4190
4280
  try {
4191
4281
  const response = await fetch(`${this.baseURL}${endpoint}`, {
4192
4282
  method: 'POST',
4193
- headers: {
4194
- Authorization: `Bearer ${token}`,
4195
- 'Content-Type': 'application/json',
4196
- },
4283
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
4197
4284
  body: JSON.stringify(requestBody),
4198
4285
  });
4199
4286
  if (!response.ok) {
@@ -4228,6 +4315,7 @@
4228
4315
  */
4229
4316
  async chatCompletionStream(chatConfig) {
4230
4317
  var _a;
4318
+ assertValidMessages(chatConfig.messages);
4231
4319
  // Ensure token is valid, auto-refresh if needed (browser mode only)
4232
4320
  await this.authManager.ensureValidToken();
4233
4321
  const token = this.authManager.getToken();
@@ -4249,10 +4337,7 @@
4249
4337
  try {
4250
4338
  const response = await fetch(`${this.baseURL}${endpoint}`, {
4251
4339
  method: 'POST',
4252
- headers: {
4253
- Authorization: `Bearer ${token}`,
4254
- 'Content-Type': 'application/json',
4255
- },
4340
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
4256
4341
  body: JSON.stringify(requestBody),
4257
4342
  });
4258
4343
  if (!response.ok) {
@@ -4289,6 +4374,7 @@
4289
4374
  */
4290
4375
  async chatCompletionWithTools(chatConfig) {
4291
4376
  var _a, _b;
4377
+ assertValidMessages(chatConfig.messages);
4292
4378
  const token = this.authManager.getToken();
4293
4379
  if (!token) {
4294
4380
  throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
@@ -4315,10 +4401,7 @@
4315
4401
  try {
4316
4402
  const response = await fetch(`${this.baseURL}${endpoint}`, {
4317
4403
  method: 'POST',
4318
- headers: {
4319
- Authorization: `Bearer ${token}`,
4320
- 'Content-Type': 'application/json',
4321
- },
4404
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
4322
4405
  body: JSON.stringify(requestBody),
4323
4406
  });
4324
4407
  if (!response.ok) {
@@ -4349,6 +4432,7 @@
4349
4432
  */
4350
4433
  async chatCompletionWithToolsStream(chatConfig) {
4351
4434
  var _a, _b;
4435
+ assertValidMessages(chatConfig.messages);
4352
4436
  const token = this.authManager.getToken();
4353
4437
  if (!token) {
4354
4438
  throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
@@ -4375,10 +4459,7 @@
4375
4459
  try {
4376
4460
  const response = await fetch(`${this.baseURL}${endpoint}`, {
4377
4461
  method: 'POST',
4378
- headers: {
4379
- Authorization: `Bearer ${token}`,
4380
- 'Content-Type': 'application/json',
4381
- },
4462
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
4382
4463
  body: JSON.stringify(requestBody),
4383
4464
  });
4384
4465
  if (!response.ok) {
@@ -4448,10 +4529,7 @@
4448
4529
  try {
4449
4530
  const response = await fetch(`${this.baseURL}${endpoint}`, {
4450
4531
  method: 'POST',
4451
- headers: {
4452
- Authorization: `Bearer ${token}`,
4453
- 'Content-Type': 'application/json',
4454
- },
4532
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
4455
4533
  body: JSON.stringify(requestBody),
4456
4534
  });
4457
4535
  if (!response.ok) {
@@ -4469,11 +4547,12 @@
4469
4547
  this.playerClient.checkBalanceAfterApiCall().catch(() => { });
4470
4548
  }
4471
4549
  // Parse the response content as JSON
4472
- const content = (_a = result.choices[0]) === null || _a === void 0 ? void 0 : _a.message.content;
4473
- if (!content) {
4550
+ const rawContent = (_a = result.choices[0]) === null || _a === void 0 ? void 0 : _a.message.content;
4551
+ if (!rawContent) {
4474
4552
  throw new PlayKitError('No content in response', 'NO_CONTENT');
4475
4553
  }
4476
4554
  try {
4555
+ const content = contentToString$1(rawContent);
4477
4556
  return JSON.parse(content);
4478
4557
  }
4479
4558
  catch (parseError) {
@@ -4492,7 +4571,8 @@
4492
4571
  /**
4493
4572
  * Image generation provider for HTTP communication with image API
4494
4573
  */
4495
- const DEFAULT_BASE_URL$2 = 'https://playkit.ai';
4574
+ // @ts-ignore - replaced at build time
4575
+ const DEFAULT_BASE_URL$2 = "https://api.playkit.ai";
4496
4576
  class ImageProvider {
4497
4577
  constructor(authManager, config) {
4498
4578
  this.authManager = authManager;
@@ -4545,10 +4625,7 @@
4545
4625
  try {
4546
4626
  const response = await fetch(`${this.baseURL}${endpoint}`, {
4547
4627
  method: 'POST',
4548
- headers: {
4549
- Authorization: `Bearer ${token}`,
4550
- 'Content-Type': 'application/json',
4551
- },
4628
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
4552
4629
  body: JSON.stringify(requestBody),
4553
4630
  });
4554
4631
  if (!response.ok) {
@@ -4583,7 +4660,8 @@
4583
4660
  /**
4584
4661
  * Transcription provider for HTTP communication with audio transcription API
4585
4662
  */
4586
- const DEFAULT_BASE_URL$1 = 'https://playkit.ai';
4663
+ // @ts-ignore - replaced at build time
4664
+ const DEFAULT_BASE_URL$1 = "https://api.playkit.ai";
4587
4665
  class TranscriptionProvider {
4588
4666
  constructor(authManager, config) {
4589
4667
  this.authManager = authManager;
@@ -4648,10 +4726,7 @@
4648
4726
  try {
4649
4727
  const response = await fetch(`${this.baseURL}${endpoint}`, {
4650
4728
  method: 'POST',
4651
- headers: {
4652
- Authorization: `Bearer ${token}`,
4653
- 'Content-Type': 'application/json',
4654
- },
4729
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
4655
4730
  body: JSON.stringify(requestBody),
4656
4731
  });
4657
4732
  if (!response.ok) {
@@ -4797,9 +4872,18 @@
4797
4872
  if (text) {
4798
4873
  yield yield __await(text);
4799
4874
  }
4800
- if (parsed.type === 'done' || parsed.finish_reason) {
4875
+ // Stream termination events
4876
+ if (parsed.type === 'done' || parsed.type === 'finish' || parsed.finish_reason) {
4877
+ return yield __await(void 0);
4878
+ }
4879
+ if (parsed.type === 'abort') {
4880
+ // Server-side timeout or cancellation — treat as end of stream
4801
4881
  return yield __await(void 0);
4802
4882
  }
4883
+ if (parsed.type === 'error') {
4884
+ // Server-side error event — throw to trigger onError callback
4885
+ throw new Error(parsed.errorText || parsed.error || 'Stream error');
4886
+ }
4803
4887
  }
4804
4888
  catch (error) {
4805
4889
  // If JSON parse fails, treat as plain text
@@ -4898,6 +4982,18 @@
4898
4982
  /**
4899
4983
  * Chat client for AI text generation
4900
4984
  */
4985
+ /**
4986
+ * Helper to extract string from MessageContent
4987
+ */
4988
+ function contentToString(content) {
4989
+ if (!content)
4990
+ return '';
4991
+ if (typeof content === 'string')
4992
+ return content;
4993
+ // For array of content parts, extract text parts
4994
+ const textParts = content.filter(part => part.type === 'text');
4995
+ return textParts.map(part => part.text).join('');
4996
+ }
4901
4997
  class ChatClient {
4902
4998
  constructor(provider, model) {
4903
4999
  this.schemaLibrary = null;
@@ -4947,7 +5043,7 @@
4947
5043
  throw new Error('No choices in response');
4948
5044
  }
4949
5045
  return {
4950
- content: choice.message.content,
5046
+ content: contentToString(choice.message.content),
4951
5047
  model: response.model,
4952
5048
  finishReason: choice.finish_reason,
4953
5049
  usage: response.usage
@@ -5018,9 +5114,10 @@
5018
5114
  }
5019
5115
  // Extract user message content from the last user message
5020
5116
  const lastUserMessage = [...messages].reverse().find(m => m.role === 'user');
5021
- const prompt = (lastUserMessage === null || lastUserMessage === void 0 ? void 0 : lastUserMessage.content) || '';
5117
+ const prompt = contentToString(lastUserMessage === null || lastUserMessage === void 0 ? void 0 : lastUserMessage.content);
5022
5118
  // Build system message from messages array
5023
- const systemMessage = (_a = messages.find(m => m.role === 'system')) === null || _a === void 0 ? void 0 : _a.content;
5119
+ const systemMessageContent = (_a = messages.find(m => m.role === 'system')) === null || _a === void 0 ? void 0 : _a.content;
5120
+ const systemMessage = contentToString(systemMessageContent) || undefined;
5024
5121
  return this.generateStructuredWithSchema(schemaEntry.schema, prompt, Object.assign({ schemaName, schemaDescription: schemaEntry.description, systemMessage }, options));
5025
5122
  }
5026
5123
  /**
@@ -5111,7 +5208,7 @@
5111
5208
  throw new Error('No choices in response');
5112
5209
  }
5113
5210
  return {
5114
- content: choice.message.content || '',
5211
+ content: contentToString(choice.message.content),
5115
5212
  model: response.model,
5116
5213
  finishReason: choice.finish_reason,
5117
5214
  usage: response.usage
@@ -5175,7 +5272,7 @@
5175
5272
  return new Promise((resolve, reject) => {
5176
5273
  const img = new Image();
5177
5274
  img.onload = () => resolve(img);
5178
- img.onerror = (e) => reject(new Error('Failed to load image'));
5275
+ img.onerror = (_e) => reject(new Error('Failed to load image'));
5179
5276
  img.src = this.toDataURL();
5180
5277
  });
5181
5278
  }
@@ -5189,13 +5286,14 @@
5189
5286
  * Generate a single image
5190
5287
  */
5191
5288
  async generateImage(config) {
5289
+ var _a;
5192
5290
  const imageConfig = Object.assign(Object.assign({}, config), { model: config.model || this.model, n: 1 });
5193
5291
  const response = await this.provider.generateImages(imageConfig);
5194
5292
  const imageData = response.data[0];
5195
5293
  if (!imageData || !imageData.b64_json) {
5196
5294
  throw new Error('No image data in response');
5197
5295
  }
5198
- return new GeneratedImageImpl(imageData.b64_json, config.prompt, imageData.revised_prompt, config.size, imageData.b64_json_original, imageData.transparent_success);
5296
+ 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);
5199
5297
  }
5200
5298
  /**
5201
5299
  * Generate multiple images
@@ -5204,10 +5302,11 @@
5204
5302
  const imageConfig = Object.assign(Object.assign({}, config), { model: config.model || this.model, n: config.n || 1 });
5205
5303
  const response = await this.provider.generateImages(imageConfig);
5206
5304
  return response.data.map((imageData) => {
5305
+ var _a;
5207
5306
  if (!imageData.b64_json) {
5208
5307
  throw new Error('No image data in response');
5209
5308
  }
5210
- return new GeneratedImageImpl(imageData.b64_json, config.prompt, imageData.revised_prompt, config.size, imageData.b64_json_original, imageData.transparent_success);
5309
+ 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);
5211
5310
  });
5212
5311
  }
5213
5312
  /**
@@ -5326,1019 +5425,1117 @@
5326
5425
  }
5327
5426
 
5328
5427
  /**
5329
- * NPC Client for simplified conversation management
5330
- * Automatically handles conversation history
5428
+ * Global AI Context Manager for managing NPC conversations and player context.
5331
5429
  *
5332
- * Key Features:
5333
- * - Call talk() for all interactions - actions are handled automatically
5334
- * - Memory system for persistent NPC context
5335
- * - Reply prediction for suggesting player responses
5336
- * - Automatic conversation history management
5430
+ * Features:
5431
+ * - Player description management
5432
+ * - NPC conversation tracking
5433
+ * - Automatic conversation compaction (AutoCompact)
5337
5434
  */
5338
- class NPCClient extends EventEmitter {
5339
- constructor(chatClient, config) {
5340
- var _a, _b, _c;
5435
+ /**
5436
+ * Global AI Context Manager
5437
+ * Manages NPC conversations and player context across the application
5438
+ */
5439
+ class AIContextManager extends EventEmitter {
5440
+ constructor(config) {
5441
+ var _a, _b, _c, _d, _e;
5341
5442
  super();
5342
- this._isTalking = false;
5343
- this.logger = Logger.getLogger('NPCClient');
5344
- this.chatClient = chatClient;
5345
- // Support both characterDesign and legacy systemPrompt
5346
- 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.';
5347
- this.temperature = (_a = config === null || config === void 0 ? void 0 : config.temperature) !== null && _a !== void 0 ? _a : 0.7;
5348
- this.maxHistoryLength = (config === null || config === void 0 ? void 0 : config.maxHistoryLength) || 50;
5349
- this.generateReplyPrediction = (_b = config === null || config === void 0 ? void 0 : config.generateReplyPrediction) !== null && _b !== void 0 ? _b : false;
5350
- this.predictionCount = Math.max(2, Math.min(6, (_c = config === null || config === void 0 ? void 0 : config.predictionCount) !== null && _c !== void 0 ? _c : 4));
5351
- this.fastModel = config === null || config === void 0 ? void 0 : config.fastModel;
5352
- this.history = [];
5353
- this.memories = new Map();
5443
+ this.playerDescription = null;
5444
+ this.playerPrompt = null;
5445
+ this.playerMemories = new Map();
5446
+ this.npcStates = new Map();
5447
+ this.autoCompactTimer = null;
5448
+ this.chatClientFactory = null;
5449
+ this.logger = Logger.getLogger('AIContextManager');
5450
+ this.config = {
5451
+ enableAutoCompact: (_a = config === null || config === void 0 ? void 0 : config.enableAutoCompact) !== null && _a !== void 0 ? _a : false,
5452
+ autoCompactMinMessages: (_b = config === null || config === void 0 ? void 0 : config.autoCompactMinMessages) !== null && _b !== void 0 ? _b : 20,
5453
+ autoCompactTimeoutSeconds: (_c = config === null || config === void 0 ? void 0 : config.autoCompactTimeoutSeconds) !== null && _c !== void 0 ? _c : 300,
5454
+ autoCompactCheckInterval: (_d = config === null || config === void 0 ? void 0 : config.autoCompactCheckInterval) !== null && _d !== void 0 ? _d : 60000,
5455
+ fastModel: (_e = config === null || config === void 0 ? void 0 : config.fastModel) !== null && _e !== void 0 ? _e : '',
5456
+ };
5457
+ // Start auto-compact check if enabled
5458
+ if (this.config.enableAutoCompact) {
5459
+ this.startAutoCompactCheck();
5460
+ }
5354
5461
  }
5355
- // ===== State Properties =====
5462
+ // ===== Singleton Pattern =====
5356
5463
  /**
5357
- * Whether the NPC is currently processing a request
5464
+ * Get the singleton instance of AIContextManager
5465
+ * Creates a new instance if one doesn't exist
5358
5466
  */
5359
- get isTalking() {
5360
- return this._isTalking;
5467
+ static getInstance(config) {
5468
+ if (!AIContextManager._instance) {
5469
+ AIContextManager._instance = new AIContextManager(config);
5470
+ }
5471
+ return AIContextManager._instance;
5361
5472
  }
5362
- // ===== Character Design & Memory System =====
5363
5473
  /**
5364
- * Set the character design for the NPC.
5365
- * The system prompt is composed of CharacterDesign + all Memories.
5474
+ * Reset the singleton instance (useful for testing)
5366
5475
  */
5367
- setCharacterDesign(design) {
5368
- this.characterDesign = design;
5476
+ static resetInstance() {
5477
+ if (AIContextManager._instance) {
5478
+ AIContextManager._instance.destroy();
5479
+ AIContextManager._instance = null;
5480
+ }
5369
5481
  }
5482
+ // ===== Configuration =====
5370
5483
  /**
5371
- * Get the current character design
5484
+ * Set the chat client factory for creating chat clients for summarization
5485
+ * Required for compaction to work
5372
5486
  */
5373
- getCharacterDesign() {
5374
- return this.characterDesign;
5487
+ setChatClientFactory(factory) {
5488
+ this.chatClientFactory = factory;
5375
5489
  }
5376
5490
  /**
5377
- * @deprecated Use setCharacterDesign instead.
5378
- * This method is kept for backwards compatibility.
5491
+ * Update configuration
5379
5492
  */
5380
- setSystemPrompt(prompt) {
5381
- this.logger.warn('setSystemPrompt is deprecated. Use setCharacterDesign instead.');
5382
- this.setCharacterDesign(prompt);
5493
+ setConfig(config) {
5494
+ const wasAutoCompactEnabled = this.config.enableAutoCompact;
5495
+ this.config = Object.assign(Object.assign({}, this.config), config);
5496
+ // Handle auto-compact state change
5497
+ if (config.enableAutoCompact !== undefined) {
5498
+ if (config.enableAutoCompact && !wasAutoCompactEnabled) {
5499
+ this.startAutoCompactCheck();
5500
+ }
5501
+ else if (!config.enableAutoCompact && wasAutoCompactEnabled) {
5502
+ this.stopAutoCompactCheck();
5503
+ }
5504
+ }
5383
5505
  }
5506
+ // ===== Player Description =====
5384
5507
  /**
5385
- * @deprecated Use getCharacterDesign instead.
5386
- * This method is kept for backwards compatibility.
5508
+ * Set the player's description for AI context.
5509
+ * Used when generating reply predictions and for NPC context.
5510
+ * @param description Description of the player character
5387
5511
  */
5388
- getSystemPrompt() {
5389
- return this.buildSystemPrompt();
5512
+ setPlayerDescription(description) {
5513
+ this.playerDescription = description;
5514
+ this.emit('playerDescriptionChanged', description);
5390
5515
  }
5391
5516
  /**
5392
- * Set or update a memory for the NPC.
5393
- * Memories are appended to the character design to form the system prompt.
5517
+ * Get the current player description.
5518
+ * @returns The player description, or null if not set
5519
+ */
5520
+ getPlayerDescription() {
5521
+ return this.playerDescription;
5522
+ }
5523
+ /**
5524
+ * Clear the player description.
5525
+ */
5526
+ clearPlayerDescription() {
5527
+ this.playerDescription = null;
5528
+ this.emit('playerDescriptionChanged', null);
5529
+ }
5530
+ // ===== Player Prompt & Memory (for Reply Prediction) =====
5531
+ /**
5532
+ * Set the player's character prompt/persona.
5533
+ * This defines how the player character speaks and behaves.
5534
+ * Used when generating reply predictions to match the player's tone.
5535
+ * @param prompt The player character's persona/prompt
5536
+ */
5537
+ setPlayerPrompt(prompt) {
5538
+ this.playerPrompt = prompt;
5539
+ }
5540
+ /**
5541
+ * Get the current player prompt.
5542
+ * @returns The player prompt, or null if not set
5543
+ */
5544
+ getPlayerPrompt() {
5545
+ return this.playerPrompt;
5546
+ }
5547
+ /**
5548
+ * Set or update a memory for the player character.
5549
+ * Memories are appended to the player prompt to form the full player context.
5394
5550
  * Set memoryContent to null or empty to remove the memory.
5395
5551
  * @param memoryName The name/key of the memory
5396
5552
  * @param memoryContent The content of the memory. Null or empty to remove.
5397
5553
  */
5398
- setMemory(memoryName, memoryContent) {
5554
+ setPlayerMemory(memoryName, memoryContent) {
5399
5555
  if (!memoryName) {
5400
5556
  this.logger.warn('Memory name cannot be empty');
5401
5557
  return;
5402
5558
  }
5403
5559
  if (!memoryContent) {
5404
5560
  // Remove memory if content is null or empty
5405
- if (this.memories.has(memoryName)) {
5406
- this.memories.delete(memoryName);
5407
- this.emit('memory_removed', memoryName);
5408
- }
5561
+ this.playerMemories.delete(memoryName);
5409
5562
  }
5410
5563
  else {
5411
5564
  // Add or update memory
5412
- this.memories.set(memoryName, memoryContent);
5413
- this.emit('memory_set', memoryName, memoryContent);
5565
+ this.playerMemories.set(memoryName, memoryContent);
5414
5566
  }
5415
5567
  }
5416
5568
  /**
5417
- * Get a specific memory by name.
5569
+ * Get a specific player memory by name.
5418
5570
  * @param memoryName The name of the memory to retrieve
5419
5571
  * @returns The memory content, or undefined if not found
5420
5572
  */
5421
- getMemory(memoryName) {
5422
- return this.memories.get(memoryName);
5573
+ getPlayerMemory(memoryName) {
5574
+ return this.playerMemories.get(memoryName);
5423
5575
  }
5424
5576
  /**
5425
- * Get all memory names currently stored.
5577
+ * Get all player memory names currently stored.
5426
5578
  * @returns Array of memory names
5427
5579
  */
5428
- getMemoryNames() {
5429
- return Array.from(this.memories.keys());
5580
+ getPlayerMemoryNames() {
5581
+ return Array.from(this.playerMemories.keys());
5430
5582
  }
5431
5583
  /**
5432
- * Clear all memories (but keep character design).
5584
+ * Clear all player memories (but keep player prompt).
5433
5585
  */
5434
- clearMemories() {
5435
- this.memories.clear();
5436
- this.emit('memories_cleared');
5586
+ clearPlayerMemories() {
5587
+ this.playerMemories.clear();
5437
5588
  }
5438
5589
  /**
5439
- * Build the complete system prompt from CharacterDesign + Memories.
5590
+ * Build the complete player context from PlayerPrompt + PlayerMemories.
5591
+ * Used by NPCClient for generating reply predictions.
5592
+ * @returns The combined player context string, or null if no context is set
5440
5593
  */
5441
- buildSystemPrompt() {
5594
+ buildPlayerContext() {
5442
5595
  const parts = [];
5443
- if (this.characterDesign) {
5444
- parts.push(this.characterDesign);
5596
+ if (this.playerPrompt) {
5597
+ parts.push(this.playerPrompt);
5445
5598
  }
5446
- if (this.memories.size > 0) {
5447
- const memoryStrings = Array.from(this.memories.entries())
5599
+ if (this.playerMemories.size > 0) {
5600
+ const memoryStrings = Array.from(this.playerMemories.entries())
5448
5601
  .map(([name, content]) => `[${name}]: ${content}`);
5449
- parts.push('Memories:\n' + memoryStrings.join('\n'));
5602
+ parts.push('Player Memories:\n' + memoryStrings.join('\n'));
5603
+ }
5604
+ if (parts.length === 0) {
5605
+ return null;
5450
5606
  }
5451
5607
  return parts.join('\n\n');
5452
5608
  }
5453
- // ===== Reply Prediction =====
5609
+ // ===== NPC Tracking =====
5454
5610
  /**
5455
- * Enable or disable automatic reply prediction
5611
+ * Register an NPC for context management.
5612
+ * @param npc The NPC client to register
5456
5613
  */
5457
- setGenerateReplyPrediction(enabled) {
5458
- this.generateReplyPrediction = enabled;
5614
+ registerNpc(npc) {
5615
+ if (!npc)
5616
+ return;
5617
+ if (!this.npcStates.has(npc)) {
5618
+ this.npcStates.set(npc, {
5619
+ lastConversationTime: new Date(),
5620
+ isCompacted: false,
5621
+ compactionCount: 0,
5622
+ });
5623
+ }
5459
5624
  }
5460
5625
  /**
5461
- * Set the number of predictions to generate
5626
+ * Unregister an NPC (call when NPC is destroyed/removed).
5627
+ * @param npc The NPC client to unregister
5462
5628
  */
5463
- setPredictionCount(count) {
5464
- this.predictionCount = Math.max(2, Math.min(6, count));
5629
+ unregisterNpc(npc) {
5630
+ if (!npc)
5631
+ return;
5632
+ this.npcStates.delete(npc);
5465
5633
  }
5466
5634
  /**
5467
- * Manually generate reply predictions based on current conversation.
5468
- * Uses the fast model for quick generation.
5469
- * @param count Number of predictions to generate (default: uses predictionCount property)
5470
- * @returns Array of predicted player replies, or empty array on failure
5635
+ * Record that a conversation occurred with an NPC.
5636
+ * Called after each Talk() exchange.
5637
+ * @param npc The NPC client that had a conversation
5471
5638
  */
5472
- async generateReplyPredictions(count) {
5473
- var _a;
5474
- const predictionNum = count !== null && count !== void 0 ? count : this.predictionCount;
5475
- if (this.history.length < 2) {
5476
- this.logger.info('Not enough conversation history to generate predictions');
5477
- return [];
5478
- }
5479
- try {
5480
- // Get last NPC message
5481
- const lastNpcMessage = (_a = [...this.history]
5482
- .reverse()
5483
- .find(m => m.role === 'assistant')) === null || _a === void 0 ? void 0 : _a.content;
5484
- if (!lastNpcMessage) {
5485
- this.logger.info('No NPC message found to generate predictions from');
5486
- return [];
5487
- }
5488
- // Build recent history (last 6 non-system messages)
5489
- const recentHistory = this.history
5490
- .filter(m => m.role !== 'system')
5491
- .slice(-6)
5492
- .map(m => `${m.role}: ${m.content}`);
5493
- // Build prompt for prediction generation
5494
- const prompt = `Based on the conversation history below, generate exactly ${predictionNum} natural and contextually appropriate responses that the player might say next.
5495
-
5496
- Context:
5497
- - This is a conversation between a player and an NPC in a game
5498
- - The NPC just said: "${lastNpcMessage}"
5499
-
5500
- Conversation history:
5501
- ${recentHistory.join('\n')}
5502
-
5503
- Requirements:
5504
- 1. Each response should be 1-2 sentences maximum
5505
- 2. Responses should be diverse in tone and intent
5506
- 3. Include a mix of questions, statements, and action-oriented responses
5507
- 4. Responses should feel natural for a player character
5508
-
5509
- Output ONLY a JSON array of ${predictionNum} strings, nothing else:
5510
- ["response1", "response2", "response3", "response4"]`;
5511
- const result = await this.chatClient.textGeneration({
5512
- messages: [{ role: 'user', content: prompt }],
5513
- temperature: 0.8,
5514
- model: this.fastModel,
5515
- });
5516
- if (!result.content) {
5517
- this.logger.warn('Failed to generate predictions: empty response');
5518
- return [];
5519
- }
5520
- // Parse JSON response
5521
- const predictions = this.parsePredictionsFromJson(result.content, predictionNum);
5522
- if (predictions.length > 0) {
5523
- this.emit('replyPredictions', predictions);
5524
- }
5525
- return predictions;
5526
- }
5527
- catch (error) {
5528
- this.logger.error('Error generating predictions:', error);
5529
- return [];
5639
+ recordConversation(npc) {
5640
+ if (!npc)
5641
+ return;
5642
+ if (!this.npcStates.has(npc)) {
5643
+ this.registerNpc(npc);
5530
5644
  }
5645
+ const state = this.npcStates.get(npc);
5646
+ state.lastConversationTime = new Date();
5647
+ state.isCompacted = false; // Reset compaction flag on new conversation
5531
5648
  }
5532
5649
  /**
5533
- * Parse predictions from JSON array response
5650
+ * Get all registered NPCs
5534
5651
  */
5535
- parsePredictionsFromJson(response, expectedCount) {
5536
- try {
5537
- // Try to find JSON array in response
5538
- const startIndex = response.indexOf('[');
5539
- const endIndex = response.lastIndexOf(']');
5540
- if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
5541
- this.logger.warn('Could not find JSON array in prediction response');
5542
- return this.extractPredictionsFromText(response, expectedCount);
5543
- }
5544
- const jsonArray = response.substring(startIndex, endIndex + 1);
5545
- const parsed = JSON.parse(jsonArray);
5546
- if (Array.isArray(parsed)) {
5547
- return parsed
5548
- .filter(item => typeof item === 'string' && item.trim())
5549
- .slice(0, expectedCount);
5550
- }
5551
- return [];
5552
- }
5553
- catch (error) {
5554
- this.logger.warn('Failed to parse predictions JSON:', error);
5555
- return this.extractPredictionsFromText(response, expectedCount);
5556
- }
5652
+ getRegisteredNpcs() {
5653
+ return Array.from(this.npcStates.keys());
5557
5654
  }
5558
5655
  /**
5559
- * Fallback: Extract predictions from text when JSON parsing fails
5656
+ * Get the conversation state for an NPC
5560
5657
  */
5561
- extractPredictionsFromText(response, expectedCount) {
5562
- const predictions = [];
5563
- const lines = response.split(/[\n\r]+/).filter(line => line.trim());
5564
- for (const line of lines) {
5565
- let cleaned = line.trim();
5566
- // Skip empty lines and JSON brackets
5567
- if (!cleaned || cleaned === '[' || cleaned === ']')
5568
- continue;
5569
- // Remove common prefixes like "1.", "- ", etc.
5570
- if (/^\d+\./.test(cleaned)) {
5571
- cleaned = cleaned.replace(/^\d+\.\s*/, '');
5572
- }
5573
- else if (cleaned.startsWith('- ')) {
5574
- cleaned = cleaned.substring(2);
5575
- }
5576
- // Remove surrounding quotes
5577
- if (cleaned.startsWith('"') && cleaned.endsWith('"')) {
5578
- cleaned = cleaned.slice(1, -1);
5579
- }
5580
- // Remove trailing comma
5581
- if (cleaned.endsWith(',')) {
5582
- cleaned = cleaned.slice(0, -1).trim();
5583
- }
5584
- if (cleaned && predictions.length < expectedCount) {
5585
- predictions.push(cleaned);
5586
- }
5587
- }
5588
- return predictions;
5658
+ getNpcState(npc) {
5659
+ return this.npcStates.get(npc);
5589
5660
  }
5661
+ // ===== Auto Compaction =====
5590
5662
  /**
5591
- * Internal method to trigger prediction generation after NPC response
5663
+ * Check if an NPC is eligible for compaction.
5664
+ * @param npc The NPC to check
5665
+ * @returns True if eligible for compaction
5592
5666
  */
5593
- async triggerReplyPrediction() {
5594
- if (!this.generateReplyPrediction)
5595
- return;
5596
- // Fire and forget - don't block the main response
5597
- this.generateReplyPredictions().catch(err => {
5598
- this.logger.error('Background prediction generation failed:', err);
5599
- });
5667
+ isEligibleForCompaction(npc) {
5668
+ if (!npc)
5669
+ return false;
5670
+ const state = this.npcStates.get(npc);
5671
+ if (!state)
5672
+ return false;
5673
+ // Check if already compacted since last conversation
5674
+ if (state.isCompacted)
5675
+ return false;
5676
+ // Check message count
5677
+ const history = npc.getHistory();
5678
+ const nonSystemMessages = history.filter(m => m.role !== 'system').length;
5679
+ if (nonSystemMessages < this.config.autoCompactMinMessages)
5680
+ return false;
5681
+ // Check time since last conversation
5682
+ const timeSinceLastConversation = (Date.now() - state.lastConversationTime.getTime()) / 1000;
5683
+ if (timeSinceLastConversation < this.config.autoCompactTimeoutSeconds)
5684
+ return false;
5685
+ return true;
5600
5686
  }
5601
- // ===== Main API - Talk Methods =====
5602
5687
  /**
5603
- * Talk to the NPC (non-streaming)
5688
+ * Manually trigger conversation compaction for a specific NPC.
5689
+ * Summarizes the conversation history and stores it as a memory.
5690
+ * @param npc The NPC to compact
5691
+ * @returns True if compaction succeeded
5604
5692
  */
5605
- async talk(message) {
5606
- this._isTalking = true;
5607
- try {
5608
- // Add user message to history
5609
- const userMessage = { role: 'user', content: message };
5610
- this.history.push(userMessage);
5611
- // Build messages array with system prompt
5612
- const messages = [
5613
- { role: 'system', content: this.buildSystemPrompt() },
5614
- ...this.history,
5615
- ];
5616
- // Generate response
5617
- const result = await this.chatClient.textGeneration({
5618
- messages,
5619
- temperature: this.temperature,
5620
- });
5621
- // Add assistant response to history
5622
- const assistantMessage = { role: 'assistant', content: result.content };
5623
- this.history.push(assistantMessage);
5624
- // Trim history if needed
5625
- this.trimHistory();
5626
- this.emit('response', result.content);
5627
- // Trigger reply prediction generation (fire and forget)
5628
- this.triggerReplyPrediction();
5629
- return result.content;
5630
- }
5631
- finally {
5632
- this._isTalking = false;
5693
+ async compactConversation(npc) {
5694
+ if (!npc) {
5695
+ this.logger.warn('Cannot compact: NPC is null');
5696
+ return false;
5633
5697
  }
5634
- }
5635
- /**
5636
- * Talk to the NPC with streaming
5637
- */
5638
- async talkStream(message, onChunk, onComplete) {
5639
- this._isTalking = true;
5640
- try {
5641
- // Add user message to history
5642
- const userMessage = { role: 'user', content: message };
5643
- this.history.push(userMessage);
5644
- // Build messages array with system prompt
5645
- const messages = [
5646
- { role: 'system', content: this.buildSystemPrompt() },
5647
- ...this.history,
5648
- ];
5649
- // Generate response
5650
- await this.chatClient.textGenerationStream({
5651
- messages,
5652
- temperature: this.temperature,
5653
- onChunk,
5654
- onComplete: (fullText) => {
5655
- this._isTalking = false;
5656
- // Add assistant response to history
5657
- const assistantMessage = { role: 'assistant', content: fullText };
5658
- this.history.push(assistantMessage);
5659
- // Trim history if needed
5660
- this.trimHistory();
5661
- this.emit('response', fullText);
5662
- // Trigger reply prediction generation (fire and forget)
5663
- this.triggerReplyPrediction();
5664
- if (onComplete) {
5665
- onComplete(fullText);
5666
- }
5667
- },
5668
- });
5698
+ if (!this.chatClientFactory) {
5699
+ this.logger.error('Cannot compact: No chat client factory set. Call setChatClientFactory() first.');
5700
+ return false;
5669
5701
  }
5670
- catch (error) {
5671
- this._isTalking = false;
5672
- throw error;
5702
+ const history = npc.getHistory();
5703
+ const nonSystemMessages = history.filter(m => m.role !== 'system');
5704
+ if (nonSystemMessages.length < 2) {
5705
+ this.logger.info('Skipping compaction: not enough messages');
5706
+ return false;
5673
5707
  }
5674
- }
5675
- /**
5676
- * Talk with structured output
5677
- * @deprecated Use talkWithActions instead for NPC decision-making with actions
5678
- */
5679
- async talkStructured(message, schemaName) {
5680
- this.logger.warn('talkStructured is deprecated. Use talkWithActions instead for NPC decision-making with actions.');
5681
- // Add user message to history
5682
- const userMessage = { role: 'user', content: message };
5683
- this.history.push(userMessage);
5684
- // Generate structured response
5685
- const result = await this.chatClient.generateStructured({
5686
- schemaName,
5687
- prompt: message,
5688
- messages: [{ role: 'system', content: this.buildSystemPrompt() }, ...this.history],
5689
- temperature: this.temperature,
5690
- });
5691
- // Add a text representation to history
5692
- const assistantMessage = {
5693
- role: 'assistant',
5694
- content: JSON.stringify(result),
5695
- };
5696
- this.history.push(assistantMessage);
5697
- this.trimHistory();
5698
- return result;
5699
- }
5700
- /**
5701
- * Talk to the NPC with available actions (non-streaming)
5702
- * @param message The message to send
5703
- * @param actions List of actions the NPC can perform
5704
- * @returns Response containing text and any action calls
5705
- */
5706
- async talkWithActions(message, actions) {
5707
- this._isTalking = true;
5708
5708
  try {
5709
- // Add user message to history
5710
- const userMessage = { role: 'user', content: message };
5711
- this.history.push(userMessage);
5712
- // Convert NpcActions to ChatTools
5713
- const tools = actions
5714
- .filter(a => a && a.enabled !== false)
5715
- .map(a => npcActionToTool(a));
5716
- // Build messages array with system prompt
5717
- const messages = [
5718
- { role: 'system', content: this.buildSystemPrompt() },
5719
- ...this.history,
5720
- ];
5721
- // Generate response with tools
5722
- const result = await this.chatClient.textGenerationWithTools({
5723
- messages,
5724
- temperature: this.temperature,
5725
- tools,
5726
- tool_choice: 'auto',
5709
+ this.logger.info(`Starting compaction (${nonSystemMessages.length} messages)`);
5710
+ // Build conversation text for summarization
5711
+ const conversationText = nonSystemMessages
5712
+ .map(m => `${m.role}: ${m.content}`)
5713
+ .join('\n');
5714
+ // Create summarization prompt
5715
+ const summaryPrompt = `Summarize the following conversation concisely. Focus on:
5716
+ 1. Key topics discussed
5717
+ 2. Important information exchanged
5718
+ 3. Any decisions or commitments made
5719
+ 4. The emotional tone
5720
+
5721
+ Keep the summary under 200 words. Write in third person.
5722
+
5723
+ Conversation:
5724
+ ${conversationText}`;
5725
+ // Use chat client for summarization
5726
+ const chatClient = this.chatClientFactory();
5727
+ const result = await chatClient.textGeneration({
5728
+ messages: [{ role: 'user', content: summaryPrompt }],
5729
+ temperature: 0.5,
5730
+ model: this.config.fastModel || undefined,
5727
5731
  });
5728
- // Build response
5729
- const response = {
5730
- text: result.content || '',
5731
- actionCalls: [],
5732
- hasActions: false,
5733
- };
5734
- // Extract tool calls if any
5735
- if (result.tool_calls) {
5736
- response.actionCalls = result.tool_calls.map(tc => ({
5737
- id: tc.id,
5738
- actionName: tc.function.name,
5739
- arguments: this.parseToolArguments(tc.function.arguments),
5740
- }));
5741
- response.hasActions = response.actionCalls.length > 0;
5742
- }
5743
- // Add assistant response to history
5744
- const assistantMessage = {
5745
- role: 'assistant',
5746
- content: response.text,
5747
- tool_calls: result.tool_calls,
5748
- };
5749
- this.history.push(assistantMessage);
5750
- this.trimHistory();
5751
- this.emit('response', response.text);
5752
- if (response.hasActions) {
5753
- this.emit('actions', response.actionCalls);
5732
+ if (!result.content) {
5733
+ const error = 'Empty response from summarization';
5734
+ this.logger.error(`Compaction failed: ${error}`);
5735
+ this.emit('compactionFailed', npc, error);
5736
+ return false;
5754
5737
  }
5755
- // Trigger reply prediction generation (fire and forget)
5756
- this.triggerReplyPrediction();
5757
- return response;
5758
- }
5759
- finally {
5760
- this._isTalking = false;
5761
- }
5762
- }
5763
- /**
5764
- * Talk to the NPC with actions (streaming)
5765
- * Text streams first, action calls are returned in onComplete
5766
- */
5767
- async talkWithActionsStream(message, actions, onChunk, onComplete) {
5768
- this._isTalking = true;
5769
- try {
5770
- // Add user message to history
5771
- const userMessage = { role: 'user', content: message };
5772
- this.history.push(userMessage);
5773
- // Convert NpcActions to ChatTools
5774
- const tools = actions
5775
- .filter(a => a && a.enabled !== false)
5776
- .map(a => npcActionToTool(a));
5777
- // Build messages array with system prompt
5778
- const messages = [
5779
- { role: 'system', content: this.buildSystemPrompt() },
5780
- ...this.history,
5781
- ];
5782
- // Generate response with tools (streaming)
5783
- await this.chatClient.textGenerationWithToolsStream({
5784
- messages,
5785
- temperature: this.temperature,
5786
- tools,
5787
- tool_choice: 'auto',
5788
- onChunk,
5789
- onComplete: (result) => {
5790
- this._isTalking = false;
5791
- // Build response
5792
- const response = {
5793
- text: result.content || '',
5794
- actionCalls: [],
5795
- hasActions: false,
5796
- };
5797
- // Extract tool calls if any
5798
- if (result.tool_calls) {
5799
- response.actionCalls = result.tool_calls.map(tc => ({
5800
- id: tc.id,
5801
- actionName: tc.function.name,
5802
- arguments: this.parseToolArguments(tc.function.arguments),
5803
- }));
5804
- response.hasActions = response.actionCalls.length > 0;
5805
- }
5806
- // Add assistant response to history
5807
- const assistantMessage = {
5808
- role: 'assistant',
5809
- content: response.text,
5810
- tool_calls: result.tool_calls,
5811
- };
5812
- this.history.push(assistantMessage);
5813
- this.trimHistory();
5814
- this.emit('response', response.text);
5815
- if (response.hasActions) {
5816
- this.emit('actions', response.actionCalls);
5817
- }
5818
- // Trigger reply prediction generation (fire and forget)
5819
- this.triggerReplyPrediction();
5820
- if (onComplete) {
5821
- onComplete(response);
5822
- }
5823
- },
5824
- });
5738
+ // Clear history and add summary as memory
5739
+ npc.clearHistory();
5740
+ npc.setMemory('PreviousConversationSummary', result.content);
5741
+ // Update state
5742
+ const state = this.npcStates.get(npc);
5743
+ if (state) {
5744
+ state.isCompacted = true;
5745
+ state.compactionCount++;
5746
+ }
5747
+ this.logger.info(`Compaction completed. Summary: ${result.content.substring(0, 100)}...`);
5748
+ this.emit('npcCompacted', npc);
5749
+ return true;
5825
5750
  }
5826
5751
  catch (error) {
5827
- this._isTalking = false;
5828
- throw error;
5752
+ const errorMessage = error instanceof Error ? error.message : String(error);
5753
+ this.logger.error(`Compaction error: ${errorMessage}`);
5754
+ this.emit('compactionFailed', npc, errorMessage);
5755
+ return false;
5829
5756
  }
5830
5757
  }
5831
- // ===== Action Results Reporting =====
5832
5758
  /**
5833
- * Report action results back to the conversation
5834
- * Call this after executing actions to let the NPC know the results
5759
+ * Compact all registered NPCs that meet the eligibility criteria.
5760
+ * @returns Number of NPCs successfully compacted
5835
5761
  */
5836
- reportActionResults(results) {
5837
- for (const [callId, result] of Object.entries(results)) {
5838
- this.history.push({
5839
- role: 'tool',
5840
- tool_call_id: callId,
5841
- content: result,
5842
- });
5762
+ async compactAllEligible() {
5763
+ const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
5764
+ if (eligibleNpcs.length === 0) {
5765
+ return 0;
5766
+ }
5767
+ this.logger.info(`Compacting ${eligibleNpcs.length} eligible NPCs`);
5768
+ let successCount = 0;
5769
+ for (const npc of eligibleNpcs) {
5770
+ const success = await this.compactConversation(npc);
5771
+ if (success)
5772
+ successCount++;
5843
5773
  }
5774
+ return successCount;
5844
5775
  }
5776
+ // ===== Auto Compact Timer =====
5845
5777
  /**
5846
- * Report a single action result
5778
+ * Start the auto-compact check timer
5847
5779
  */
5848
- reportActionResult(callId, result) {
5849
- this.history.push({
5850
- role: 'tool',
5851
- tool_call_id: callId,
5852
- content: result,
5853
- });
5780
+ startAutoCompactCheck() {
5781
+ if (this.autoCompactTimer) {
5782
+ this.stopAutoCompactCheck();
5783
+ }
5784
+ this.autoCompactTimer = setInterval(() => {
5785
+ this.runAutoCompactCheck();
5786
+ }, this.config.autoCompactCheckInterval);
5854
5787
  }
5855
5788
  /**
5856
- * Parse tool arguments from JSON string
5789
+ * Stop the auto-compact check timer
5857
5790
  */
5858
- parseToolArguments(args) {
5859
- try {
5860
- return JSON.parse(args);
5791
+ stopAutoCompactCheck() {
5792
+ if (this.autoCompactTimer) {
5793
+ clearInterval(this.autoCompactTimer);
5794
+ this.autoCompactTimer = null;
5861
5795
  }
5862
- catch (_a) {
5863
- return {};
5796
+ }
5797
+ /**
5798
+ * Run a single auto-compact check
5799
+ */
5800
+ async runAutoCompactCheck() {
5801
+ if (!this.config.enableAutoCompact)
5802
+ return;
5803
+ const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
5804
+ for (const npc of eligibleNpcs) {
5805
+ // Fire and forget - don't block
5806
+ this.compactConversation(npc).catch(err => {
5807
+ this.logger.error('Auto-compact error:', err);
5808
+ });
5864
5809
  }
5865
5810
  }
5866
- // ===== Conversation History Management =====
5811
+ // ===== Lifecycle =====
5867
5812
  /**
5868
- * Get conversation history
5813
+ * Enable auto-compaction
5869
5814
  */
5870
- getHistory() {
5871
- return [...this.history];
5815
+ enableAutoCompact() {
5816
+ this.config.enableAutoCompact = true;
5817
+ this.startAutoCompactCheck();
5872
5818
  }
5873
5819
  /**
5874
- * Get the number of messages in history
5820
+ * Disable auto-compaction
5875
5821
  */
5876
- getHistoryLength() {
5877
- return this.history.length;
5822
+ disableAutoCompact() {
5823
+ this.config.enableAutoCompact = false;
5824
+ this.stopAutoCompactCheck();
5878
5825
  }
5879
5826
  /**
5880
- * Clear conversation history.
5881
- * The character design and memories will be preserved.
5827
+ * Clean up resources
5882
5828
  */
5883
- clearHistory() {
5829
+ destroy() {
5830
+ this.stopAutoCompactCheck();
5831
+ this.npcStates.clear();
5832
+ this.playerDescription = null;
5833
+ this.playerPrompt = null;
5834
+ this.playerMemories.clear();
5835
+ this.removeAllListeners();
5836
+ }
5837
+ }
5838
+ AIContextManager._instance = null;
5839
+ /**
5840
+ * Default AIContextManager instance
5841
+ * Can be used as a global context manager
5842
+ */
5843
+ const defaultContextManager = AIContextManager.getInstance();
5844
+
5845
+ /**
5846
+ * NPC Client for simplified conversation management
5847
+ * Automatically handles conversation history
5848
+ *
5849
+ * Key Features:
5850
+ * - Call talk() for all interactions - actions are handled automatically
5851
+ * - Memory system for persistent NPC context
5852
+ * - Reply prediction for suggesting player responses
5853
+ * - Automatic conversation history management
5854
+ */
5855
+ class NPCClient extends EventEmitter {
5856
+ constructor(chatClient, config) {
5857
+ var _a, _b, _c;
5858
+ super();
5859
+ this._isTalking = false;
5860
+ this.logger = Logger.getLogger('NPCClient');
5861
+ this.chatClient = chatClient;
5862
+ // Support both characterDesign and legacy systemPrompt
5863
+ 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.';
5864
+ this.temperature = (_a = config === null || config === void 0 ? void 0 : config.temperature) !== null && _a !== void 0 ? _a : 0.7;
5865
+ this.maxHistoryLength = (config === null || config === void 0 ? void 0 : config.maxHistoryLength) || 50;
5866
+ this.generateReplyPrediction = (_b = config === null || config === void 0 ? void 0 : config.generateReplyPrediction) !== null && _b !== void 0 ? _b : false;
5867
+ this.predictionCount = Math.max(2, Math.min(6, (_c = config === null || config === void 0 ? void 0 : config.predictionCount) !== null && _c !== void 0 ? _c : 4));
5868
+ this.fastModel = config === null || config === void 0 ? void 0 : config.fastModel;
5884
5869
  this.history = [];
5885
- this.emit('history_cleared');
5870
+ this.memories = new Map();
5886
5871
  }
5872
+ // ===== State Properties =====
5887
5873
  /**
5888
- * Revert the last exchange (user message and assistant response) from history.
5889
- * @returns true if reverted, false if not enough history
5874
+ * Whether the NPC is currently processing a request
5890
5875
  */
5891
- revertHistory() {
5892
- let lastAssistantIndex = -1;
5893
- let lastUserIndex = -1;
5894
- for (let i = this.history.length - 1; i >= 0; i--) {
5895
- if (this.history[i].role === 'assistant' && lastAssistantIndex === -1) {
5896
- lastAssistantIndex = i;
5897
- }
5898
- else if (this.history[i].role === 'user' && lastAssistantIndex !== -1 && lastUserIndex === -1) {
5899
- lastUserIndex = i;
5900
- break;
5876
+ get isTalking() {
5877
+ return this._isTalking;
5878
+ }
5879
+ // ===== Character Design & Memory System =====
5880
+ /**
5881
+ * Set the character design for the NPC.
5882
+ * The system prompt is composed of CharacterDesign + all Memories.
5883
+ */
5884
+ setCharacterDesign(design) {
5885
+ this.characterDesign = design;
5886
+ }
5887
+ /**
5888
+ * Get the current character design
5889
+ */
5890
+ getCharacterDesign() {
5891
+ return this.characterDesign;
5892
+ }
5893
+ /**
5894
+ * @deprecated Use setCharacterDesign instead.
5895
+ * This method is kept for backwards compatibility.
5896
+ */
5897
+ setSystemPrompt(prompt) {
5898
+ this.logger.warn('setSystemPrompt is deprecated. Use setCharacterDesign instead.');
5899
+ this.setCharacterDesign(prompt);
5900
+ }
5901
+ /**
5902
+ * @deprecated Use getCharacterDesign instead.
5903
+ * This method is kept for backwards compatibility.
5904
+ */
5905
+ getSystemPrompt() {
5906
+ return this.buildSystemPrompt();
5907
+ }
5908
+ /**
5909
+ * Set or update a memory for the NPC.
5910
+ * Memories are appended to the character design to form the system prompt.
5911
+ * Set memoryContent to null or empty to remove the memory.
5912
+ * @param memoryName The name/key of the memory
5913
+ * @param memoryContent The content of the memory. Null or empty to remove.
5914
+ */
5915
+ setMemory(memoryName, memoryContent) {
5916
+ if (!memoryName) {
5917
+ this.logger.warn('Memory name cannot be empty');
5918
+ return;
5919
+ }
5920
+ if (!memoryContent) {
5921
+ // Remove memory if content is null or empty
5922
+ if (this.memories.has(memoryName)) {
5923
+ this.memories.delete(memoryName);
5924
+ this.emit('memory_removed', memoryName);
5901
5925
  }
5902
5926
  }
5903
- if (lastAssistantIndex !== -1 && lastUserIndex !== -1) {
5904
- // Remove in reverse order to maintain indices
5905
- this.history.splice(lastAssistantIndex, 1);
5906
- this.history.splice(lastUserIndex, 1);
5907
- this.emit('history_reverted');
5908
- return true;
5927
+ else {
5928
+ // Add or update memory
5929
+ this.memories.set(memoryName, memoryContent);
5930
+ this.emit('memory_set', memoryName, memoryContent);
5909
5931
  }
5910
- return false;
5911
5932
  }
5912
5933
  /**
5913
- * Revert (remove) the last N chat messages from history
5914
- * @param count Number of messages to remove
5915
- * @returns Number of messages actually removed
5934
+ * Get a specific memory by name.
5935
+ * @param memoryName The name of the memory to retrieve
5936
+ * @returns The memory content, or undefined if not found
5916
5937
  */
5917
- revertChatMessages(count) {
5918
- if (count <= 0)
5919
- return 0;
5920
- const messagesToRemove = Math.min(count, this.history.length);
5921
- const originalCount = this.history.length;
5922
- this.history = this.history.slice(0, -messagesToRemove);
5923
- const actuallyRemoved = originalCount - this.history.length;
5924
- if (actuallyRemoved > 0) {
5925
- this.emit('history_reverted', actuallyRemoved);
5938
+ getMemory(memoryName) {
5939
+ return this.memories.get(memoryName);
5940
+ }
5941
+ /**
5942
+ * Get all memory names currently stored.
5943
+ * @returns Array of memory names
5944
+ */
5945
+ getMemoryNames() {
5946
+ return Array.from(this.memories.keys());
5947
+ }
5948
+ /**
5949
+ * Clear all memories (but keep character design).
5950
+ */
5951
+ clearMemories() {
5952
+ this.memories.clear();
5953
+ this.emit('memories_cleared');
5954
+ }
5955
+ /**
5956
+ * Build the complete system prompt from CharacterDesign + Memories.
5957
+ */
5958
+ buildSystemPrompt() {
5959
+ const parts = [];
5960
+ if (this.characterDesign) {
5961
+ parts.push(this.characterDesign);
5926
5962
  }
5927
- return actuallyRemoved;
5963
+ if (this.memories.size > 0) {
5964
+ const memoryStrings = Array.from(this.memories.entries())
5965
+ .map(([name, content]) => `[${name}]: ${content}`);
5966
+ parts.push('Memories:\n' + memoryStrings.join('\n'));
5967
+ }
5968
+ return parts.join('\n\n');
5969
+ }
5970
+ // ===== Reply Prediction =====
5971
+ /**
5972
+ * Enable or disable automatic reply prediction
5973
+ */
5974
+ setGenerateReplyPrediction(enabled) {
5975
+ this.generateReplyPrediction = enabled;
5928
5976
  }
5929
5977
  /**
5930
- * Revert to a specific point in history
5931
- * @deprecated Use revertHistory() or revertChatMessages() instead
5978
+ * Set the number of predictions to generate
5932
5979
  */
5933
- revertToMessage(index) {
5934
- if (index >= 0 && index < this.history.length) {
5935
- this.history = this.history.slice(0, index + 1);
5936
- this.emit('history_reverted', index);
5937
- }
5980
+ setPredictionCount(count) {
5981
+ this.predictionCount = Math.max(2, Math.min(6, count));
5938
5982
  }
5939
5983
  /**
5940
- * Append a message to history manually
5984
+ * Manually generate reply predictions based on current conversation.
5985
+ * Uses the fast model for quick generation.
5986
+ * @param tempPrompt Optional temporary prompt to influence the prediction style/tone
5987
+ * @param count Number of predictions to generate (default: uses predictionCount property)
5988
+ * @returns Array of predicted player replies, or empty array on failure
5941
5989
  */
5942
- appendMessage(message) {
5943
- this.history.push(message);
5944
- this.trimHistory();
5990
+ async generateReplyPredictions(tempPrompt, count) {
5991
+ var _a;
5992
+ const predictionNum = count !== null && count !== void 0 ? count : this.predictionCount;
5993
+ if (this.history.length < 2) {
5994
+ this.logger.info('Not enough conversation history to generate predictions');
5995
+ return [];
5996
+ }
5997
+ try {
5998
+ // Get last NPC message
5999
+ const lastNpcMessage = (_a = [...this.history]
6000
+ .reverse()
6001
+ .find(m => m.role === 'assistant')) === null || _a === void 0 ? void 0 : _a.content;
6002
+ if (!lastNpcMessage) {
6003
+ this.logger.info('No NPC message found to generate predictions from');
6004
+ return [];
6005
+ }
6006
+ // Build recent history (last 6 non-system messages)
6007
+ const recentHistory = this.history
6008
+ .filter(m => m.role !== 'system')
6009
+ .slice(-6)
6010
+ .map(m => `${m.role}: ${m.content}`);
6011
+ // Get player context from AIContextManager
6012
+ const contextManager = AIContextManager.getInstance();
6013
+ const playerContext = contextManager.buildPlayerContext();
6014
+ // Build player character section
6015
+ let playerCharacterSection = '';
6016
+ if (playerContext || tempPrompt) {
6017
+ playerCharacterSection = '\nPlayer Character:\n';
6018
+ if (playerContext) {
6019
+ playerCharacterSection += playerContext + '\n';
6020
+ }
6021
+ if (tempPrompt) {
6022
+ playerCharacterSection += `Additional guidance: ${tempPrompt}\n`;
6023
+ }
6024
+ }
6025
+ // Build prompt for prediction generation
6026
+ const prompt = `Based on the conversation history below, generate exactly ${predictionNum} natural and contextually appropriate responses that the player might say next.
6027
+
6028
+ Context:
6029
+ - This is a conversation between a player and an NPC in a game
6030
+ - The NPC just said: "${lastNpcMessage}"
6031
+ ${playerCharacterSection}
6032
+ Conversation history:
6033
+ ${recentHistory.join('\n')}
6034
+
6035
+ Requirements:
6036
+ 1. Each response should be 1-2 sentences maximum
6037
+ 2. Responses should be diverse in tone and intent
6038
+ 3. Include a mix of questions, statements, and action-oriented responses
6039
+ 4. Responses should feel natural for the player character${playerContext || tempPrompt ? ' and match their personality/tone' : ''}
6040
+
6041
+ Output ONLY a JSON array of ${predictionNum} strings, nothing else:
6042
+ ["response1", "response2", "response3", "response4"]`;
6043
+ const result = await this.chatClient.textGeneration({
6044
+ messages: [{ role: 'user', content: prompt }],
6045
+ temperature: 0.8,
6046
+ model: this.fastModel,
6047
+ });
6048
+ if (!result.content) {
6049
+ this.logger.warn('Failed to generate predictions: empty response');
6050
+ return [];
6051
+ }
6052
+ // Parse JSON response
6053
+ const predictions = this.parsePredictionsFromJson(result.content, predictionNum);
6054
+ if (predictions.length > 0) {
6055
+ this.emit('replyPredictions', predictions);
6056
+ }
6057
+ return predictions;
6058
+ }
6059
+ catch (error) {
6060
+ this.logger.error('Error generating predictions:', error);
6061
+ return [];
6062
+ }
5945
6063
  }
5946
6064
  /**
5947
- * Alias for appendMessage (Unity SDK compatibility)
6065
+ * Parse predictions from JSON array response
5948
6066
  */
5949
- appendChatMessage(role, content) {
5950
- if (!role || !content) {
5951
- this.logger.warn('Role and content cannot be empty');
5952
- return;
6067
+ parsePredictionsFromJson(response, expectedCount) {
6068
+ try {
6069
+ // Try to find JSON array in response
6070
+ const startIndex = response.indexOf('[');
6071
+ const endIndex = response.lastIndexOf(']');
6072
+ if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
6073
+ this.logger.warn('Could not find JSON array in prediction response');
6074
+ return this.extractPredictionsFromText(response, expectedCount);
6075
+ }
6076
+ const jsonArray = response.substring(startIndex, endIndex + 1);
6077
+ const parsed = JSON.parse(jsonArray);
6078
+ if (Array.isArray(parsed)) {
6079
+ return parsed
6080
+ .filter(item => typeof item === 'string' && item.trim())
6081
+ .slice(0, expectedCount);
6082
+ }
6083
+ return [];
6084
+ }
6085
+ catch (error) {
6086
+ this.logger.warn('Failed to parse predictions JSON:', error);
6087
+ return this.extractPredictionsFromText(response, expectedCount);
5953
6088
  }
5954
- this.appendMessage({ role: role, content });
5955
6089
  }
5956
6090
  /**
5957
- * Trim history to max length
6091
+ * Fallback: Extract predictions from text when JSON parsing fails
5958
6092
  */
5959
- trimHistory() {
5960
- if (this.history.length > this.maxHistoryLength) {
5961
- // Keep the most recent messages
5962
- this.history = this.history.slice(-this.maxHistoryLength);
6093
+ extractPredictionsFromText(response, expectedCount) {
6094
+ const predictions = [];
6095
+ const lines = response.split(/[\n\r]+/).filter(line => line.trim());
6096
+ for (const line of lines) {
6097
+ let cleaned = line.trim();
6098
+ // Skip empty lines and JSON brackets
6099
+ if (!cleaned || cleaned === '[' || cleaned === ']')
6100
+ continue;
6101
+ // Remove common prefixes like "1.", "- ", etc.
6102
+ if (/^\d+\./.test(cleaned)) {
6103
+ cleaned = cleaned.replace(/^\d+\.\s*/, '');
6104
+ }
6105
+ else if (cleaned.startsWith('- ')) {
6106
+ cleaned = cleaned.substring(2);
6107
+ }
6108
+ // Remove surrounding quotes
6109
+ if (cleaned.startsWith('"') && cleaned.endsWith('"')) {
6110
+ cleaned = cleaned.slice(1, -1);
6111
+ }
6112
+ // Remove trailing comma
6113
+ if (cleaned.endsWith(',')) {
6114
+ cleaned = cleaned.slice(0, -1).trim();
6115
+ }
6116
+ if (cleaned && predictions.length < expectedCount) {
6117
+ predictions.push(cleaned);
6118
+ }
5963
6119
  }
6120
+ return predictions;
5964
6121
  }
5965
- // ===== Save/Load =====
5966
6122
  /**
5967
- * Save the current conversation history to a serializable format.
5968
- * Includes characterDesign, memories, and history.
6123
+ * Internal method to trigger prediction generation after NPC response
5969
6124
  */
5970
- saveHistory() {
5971
- const saveData = {
5972
- characterDesign: this.characterDesign,
5973
- memories: Array.from(this.memories.entries()).map(([name, content]) => ({ name, content })),
5974
- history: this.history,
5975
- };
5976
- return JSON.stringify(saveData);
6125
+ async triggerReplyPrediction() {
6126
+ if (!this.generateReplyPrediction)
6127
+ return;
6128
+ // Fire and forget - don't block the main response
6129
+ this.generateReplyPredictions().catch(err => {
6130
+ this.logger.error('Background prediction generation failed:', err);
6131
+ });
5977
6132
  }
6133
+ // ===== Main API - Talk Methods =====
5978
6134
  /**
5979
- * Load conversation history from serialized data.
5980
- * Restores characterDesign, memories, and history.
6135
+ * Talk to the NPC (non-streaming)
5981
6136
  */
5982
- loadHistory(saveData) {
6137
+ async talk(message) {
6138
+ this._isTalking = true;
5983
6139
  try {
5984
- const data = JSON.parse(saveData);
5985
- // Load character design (with backwards compatibility for old systemPrompt field)
5986
- this.characterDesign = data.characterDesign || data.systemPrompt || this.characterDesign;
5987
- // Load memories
5988
- this.memories.clear();
5989
- if (data.memories && Array.isArray(data.memories)) {
5990
- for (const memory of data.memories) {
5991
- if (memory.name && memory.content) {
5992
- this.memories.set(memory.name, memory.content);
5993
- }
5994
- }
5995
- }
5996
- // Load history (skip system messages as they'll be rebuilt from characterDesign + memories)
5997
- this.history = (data.history || []).filter(m => m.role !== 'system');
5998
- this.emit('history_loaded');
5999
- return true;
6000
- }
6001
- catch (error) {
6002
- this.logger.error('Failed to load history:', error);
6003
- return false;
6140
+ // Add user message to history
6141
+ const userMessage = { role: 'user', content: message };
6142
+ this.history.push(userMessage);
6143
+ // Build messages array with system prompt
6144
+ const messages = [
6145
+ { role: 'system', content: this.buildSystemPrompt() },
6146
+ ...this.history,
6147
+ ];
6148
+ // Generate response
6149
+ const result = await this.chatClient.textGeneration({
6150
+ messages,
6151
+ temperature: this.temperature,
6152
+ });
6153
+ // Add assistant response to history
6154
+ const assistantMessage = { role: 'assistant', content: result.content };
6155
+ this.history.push(assistantMessage);
6156
+ // Trim history if needed
6157
+ this.trimHistory();
6158
+ this.emit('response', result.content);
6159
+ // Trigger reply prediction generation (fire and forget)
6160
+ this.triggerReplyPrediction();
6161
+ return result.content;
6004
6162
  }
6005
- }
6006
- }
6007
-
6008
- /**
6009
- * Global AI Context Manager for managing NPC conversations and player context.
6010
- *
6011
- * Features:
6012
- * - Player description management
6013
- * - NPC conversation tracking
6014
- * - Automatic conversation compaction (AutoCompact)
6015
- */
6016
- /**
6017
- * Global AI Context Manager
6018
- * Manages NPC conversations and player context across the application
6019
- */
6020
- class AIContextManager extends EventEmitter {
6021
- constructor(config) {
6022
- var _a, _b, _c, _d, _e;
6023
- super();
6024
- this.playerDescription = null;
6025
- this.npcStates = new Map();
6026
- this.autoCompactTimer = null;
6027
- this.chatClientFactory = null;
6028
- this.logger = Logger.getLogger('AIContextManager');
6029
- this.config = {
6030
- enableAutoCompact: (_a = config === null || config === void 0 ? void 0 : config.enableAutoCompact) !== null && _a !== void 0 ? _a : false,
6031
- autoCompactMinMessages: (_b = config === null || config === void 0 ? void 0 : config.autoCompactMinMessages) !== null && _b !== void 0 ? _b : 20,
6032
- autoCompactTimeoutSeconds: (_c = config === null || config === void 0 ? void 0 : config.autoCompactTimeoutSeconds) !== null && _c !== void 0 ? _c : 300,
6033
- autoCompactCheckInterval: (_d = config === null || config === void 0 ? void 0 : config.autoCompactCheckInterval) !== null && _d !== void 0 ? _d : 60000,
6034
- fastModel: (_e = config === null || config === void 0 ? void 0 : config.fastModel) !== null && _e !== void 0 ? _e : '',
6035
- };
6036
- // Start auto-compact check if enabled
6037
- if (this.config.enableAutoCompact) {
6038
- this.startAutoCompactCheck();
6163
+ finally {
6164
+ this._isTalking = false;
6039
6165
  }
6040
6166
  }
6041
- // ===== Singleton Pattern =====
6042
6167
  /**
6043
- * Get the singleton instance of AIContextManager
6044
- * Creates a new instance if one doesn't exist
6168
+ * Talk to the NPC with streaming
6045
6169
  */
6046
- static getInstance(config) {
6047
- if (!AIContextManager._instance) {
6048
- AIContextManager._instance = new AIContextManager(config);
6170
+ async talkStream(message, onChunk, onComplete) {
6171
+ this._isTalking = true;
6172
+ try {
6173
+ // Add user message to history
6174
+ const userMessage = { role: 'user', content: message };
6175
+ this.history.push(userMessage);
6176
+ // Build messages array with system prompt
6177
+ const messages = [
6178
+ { role: 'system', content: this.buildSystemPrompt() },
6179
+ ...this.history,
6180
+ ];
6181
+ // Generate response
6182
+ await this.chatClient.textGenerationStream({
6183
+ messages,
6184
+ temperature: this.temperature,
6185
+ onChunk,
6186
+ onComplete: (fullText) => {
6187
+ this._isTalking = false;
6188
+ // Add assistant response to history
6189
+ const assistantMessage = { role: 'assistant', content: fullText };
6190
+ this.history.push(assistantMessage);
6191
+ // Trim history if needed
6192
+ this.trimHistory();
6193
+ this.emit('response', fullText);
6194
+ // Trigger reply prediction generation (fire and forget)
6195
+ this.triggerReplyPrediction();
6196
+ if (onComplete) {
6197
+ onComplete(fullText);
6198
+ }
6199
+ },
6200
+ });
6049
6201
  }
6050
- return AIContextManager._instance;
6051
- }
6052
- /**
6053
- * Reset the singleton instance (useful for testing)
6054
- */
6055
- static resetInstance() {
6056
- if (AIContextManager._instance) {
6057
- AIContextManager._instance.destroy();
6058
- AIContextManager._instance = null;
6202
+ catch (error) {
6203
+ this._isTalking = false;
6204
+ throw error;
6059
6205
  }
6060
6206
  }
6061
- // ===== Configuration =====
6062
6207
  /**
6063
- * Set the chat client factory for creating chat clients for summarization
6064
- * Required for compaction to work
6208
+ * Talk with structured output
6209
+ * @deprecated Use talkWithActions instead for NPC decision-making with actions
6065
6210
  */
6066
- setChatClientFactory(factory) {
6067
- this.chatClientFactory = factory;
6211
+ async talkStructured(message, schemaName) {
6212
+ this.logger.warn('talkStructured is deprecated. Use talkWithActions instead for NPC decision-making with actions.');
6213
+ // Add user message to history
6214
+ const userMessage = { role: 'user', content: message };
6215
+ this.history.push(userMessage);
6216
+ // Generate structured response
6217
+ const result = await this.chatClient.generateStructured({
6218
+ schemaName,
6219
+ prompt: message,
6220
+ messages: [{ role: 'system', content: this.buildSystemPrompt() }, ...this.history],
6221
+ temperature: this.temperature,
6222
+ });
6223
+ // Add a text representation to history
6224
+ const assistantMessage = {
6225
+ role: 'assistant',
6226
+ content: JSON.stringify(result),
6227
+ };
6228
+ this.history.push(assistantMessage);
6229
+ this.trimHistory();
6230
+ return result;
6068
6231
  }
6069
6232
  /**
6070
- * Update configuration
6233
+ * Talk to the NPC with available actions (non-streaming)
6234
+ * @param message The message to send
6235
+ * @param actions List of actions the NPC can perform
6236
+ * @returns Response containing text and any action calls
6071
6237
  */
6072
- setConfig(config) {
6073
- const wasAutoCompactEnabled = this.config.enableAutoCompact;
6074
- this.config = Object.assign(Object.assign({}, this.config), config);
6075
- // Handle auto-compact state change
6076
- if (config.enableAutoCompact !== undefined) {
6077
- if (config.enableAutoCompact && !wasAutoCompactEnabled) {
6078
- this.startAutoCompactCheck();
6238
+ async talkWithActions(message, actions) {
6239
+ this._isTalking = true;
6240
+ try {
6241
+ // Add user message to history
6242
+ const userMessage = { role: 'user', content: message };
6243
+ this.history.push(userMessage);
6244
+ // Convert NpcActions to ChatTools
6245
+ const tools = actions
6246
+ .filter(a => a && a.enabled !== false)
6247
+ .map(a => npcActionToTool(a));
6248
+ // Build messages array with system prompt
6249
+ const messages = [
6250
+ { role: 'system', content: this.buildSystemPrompt() },
6251
+ ...this.history,
6252
+ ];
6253
+ // Generate response with tools
6254
+ const result = await this.chatClient.textGenerationWithTools({
6255
+ messages,
6256
+ temperature: this.temperature,
6257
+ tools,
6258
+ tool_choice: 'auto',
6259
+ });
6260
+ // Build response
6261
+ const response = {
6262
+ text: result.content || '',
6263
+ actionCalls: [],
6264
+ hasActions: false,
6265
+ };
6266
+ // Extract tool calls if any
6267
+ if (result.tool_calls) {
6268
+ response.actionCalls = result.tool_calls.map(tc => ({
6269
+ id: tc.id,
6270
+ actionName: tc.function.name,
6271
+ arguments: this.parseToolArguments(tc.function.arguments),
6272
+ }));
6273
+ response.hasActions = response.actionCalls.length > 0;
6079
6274
  }
6080
- else if (!config.enableAutoCompact && wasAutoCompactEnabled) {
6081
- this.stopAutoCompactCheck();
6275
+ // Add assistant response to history
6276
+ const assistantMessage = {
6277
+ role: 'assistant',
6278
+ content: response.text,
6279
+ tool_calls: result.tool_calls,
6280
+ };
6281
+ this.history.push(assistantMessage);
6282
+ this.trimHistory();
6283
+ this.emit('response', response.text);
6284
+ if (response.hasActions) {
6285
+ this.emit('actions', response.actionCalls);
6082
6286
  }
6287
+ // Trigger reply prediction generation (fire and forget)
6288
+ this.triggerReplyPrediction();
6289
+ return response;
6290
+ }
6291
+ finally {
6292
+ this._isTalking = false;
6083
6293
  }
6084
- }
6085
- // ===== Player Description =====
6086
- /**
6087
- * Set the player's description for AI context.
6088
- * Used when generating reply predictions and for NPC context.
6089
- * @param description Description of the player character
6090
- */
6091
- setPlayerDescription(description) {
6092
- this.playerDescription = description;
6093
- this.emit('playerDescriptionChanged', description);
6094
- }
6095
- /**
6096
- * Get the current player description.
6097
- * @returns The player description, or null if not set
6098
- */
6099
- getPlayerDescription() {
6100
- return this.playerDescription;
6101
6294
  }
6102
6295
  /**
6103
- * Clear the player description.
6296
+ * Talk to the NPC with actions (streaming)
6297
+ * Text streams first, action calls are returned in onComplete
6104
6298
  */
6105
- clearPlayerDescription() {
6106
- this.playerDescription = null;
6107
- this.emit('playerDescriptionChanged', null);
6299
+ async talkWithActionsStream(message, actions, onChunk, onComplete) {
6300
+ this._isTalking = true;
6301
+ try {
6302
+ // Add user message to history
6303
+ const userMessage = { role: 'user', content: message };
6304
+ this.history.push(userMessage);
6305
+ // Convert NpcActions to ChatTools
6306
+ const tools = actions
6307
+ .filter(a => a && a.enabled !== false)
6308
+ .map(a => npcActionToTool(a));
6309
+ // Build messages array with system prompt
6310
+ const messages = [
6311
+ { role: 'system', content: this.buildSystemPrompt() },
6312
+ ...this.history,
6313
+ ];
6314
+ // Generate response with tools (streaming)
6315
+ await this.chatClient.textGenerationWithToolsStream({
6316
+ messages,
6317
+ temperature: this.temperature,
6318
+ tools,
6319
+ tool_choice: 'auto',
6320
+ onChunk,
6321
+ onComplete: (result) => {
6322
+ this._isTalking = false;
6323
+ // Build response
6324
+ const response = {
6325
+ text: result.content || '',
6326
+ actionCalls: [],
6327
+ hasActions: false,
6328
+ };
6329
+ // Extract tool calls if any
6330
+ if (result.tool_calls) {
6331
+ response.actionCalls = result.tool_calls.map(tc => ({
6332
+ id: tc.id,
6333
+ actionName: tc.function.name,
6334
+ arguments: this.parseToolArguments(tc.function.arguments),
6335
+ }));
6336
+ response.hasActions = response.actionCalls.length > 0;
6337
+ }
6338
+ // Add assistant response to history
6339
+ const assistantMessage = {
6340
+ role: 'assistant',
6341
+ content: response.text,
6342
+ tool_calls: result.tool_calls,
6343
+ };
6344
+ this.history.push(assistantMessage);
6345
+ this.trimHistory();
6346
+ this.emit('response', response.text);
6347
+ if (response.hasActions) {
6348
+ this.emit('actions', response.actionCalls);
6349
+ }
6350
+ // Trigger reply prediction generation (fire and forget)
6351
+ this.triggerReplyPrediction();
6352
+ if (onComplete) {
6353
+ onComplete(response);
6354
+ }
6355
+ },
6356
+ });
6357
+ }
6358
+ catch (error) {
6359
+ this._isTalking = false;
6360
+ throw error;
6361
+ }
6108
6362
  }
6109
- // ===== NPC Tracking =====
6363
+ // ===== Action Results Reporting =====
6110
6364
  /**
6111
- * Register an NPC for context management.
6112
- * @param npc The NPC client to register
6365
+ * Report action results back to the conversation
6366
+ * Call this after executing actions to let the NPC know the results
6113
6367
  */
6114
- registerNpc(npc) {
6115
- if (!npc)
6116
- return;
6117
- if (!this.npcStates.has(npc)) {
6118
- this.npcStates.set(npc, {
6119
- lastConversationTime: new Date(),
6120
- isCompacted: false,
6121
- compactionCount: 0,
6368
+ reportActionResults(results) {
6369
+ for (const [callId, result] of Object.entries(results)) {
6370
+ this.history.push({
6371
+ role: 'tool',
6372
+ tool_call_id: callId,
6373
+ content: result,
6122
6374
  });
6123
6375
  }
6124
6376
  }
6125
6377
  /**
6126
- * Unregister an NPC (call when NPC is destroyed/removed).
6127
- * @param npc The NPC client to unregister
6378
+ * Report a single action result
6128
6379
  */
6129
- unregisterNpc(npc) {
6130
- if (!npc)
6131
- return;
6132
- this.npcStates.delete(npc);
6380
+ reportActionResult(callId, result) {
6381
+ this.history.push({
6382
+ role: 'tool',
6383
+ tool_call_id: callId,
6384
+ content: result,
6385
+ });
6133
6386
  }
6134
6387
  /**
6135
- * Record that a conversation occurred with an NPC.
6136
- * Called after each Talk() exchange.
6137
- * @param npc The NPC client that had a conversation
6388
+ * Parse tool arguments from JSON string
6138
6389
  */
6139
- recordConversation(npc) {
6140
- if (!npc)
6141
- return;
6142
- if (!this.npcStates.has(npc)) {
6143
- this.registerNpc(npc);
6390
+ parseToolArguments(args) {
6391
+ try {
6392
+ return JSON.parse(args);
6393
+ }
6394
+ catch (_a) {
6395
+ return {};
6144
6396
  }
6145
- const state = this.npcStates.get(npc);
6146
- state.lastConversationTime = new Date();
6147
- state.isCompacted = false; // Reset compaction flag on new conversation
6148
6397
  }
6398
+ // ===== Conversation History Management =====
6149
6399
  /**
6150
- * Get all registered NPCs
6400
+ * Get conversation history
6151
6401
  */
6152
- getRegisteredNpcs() {
6153
- return Array.from(this.npcStates.keys());
6402
+ getHistory() {
6403
+ return [...this.history];
6154
6404
  }
6155
6405
  /**
6156
- * Get the conversation state for an NPC
6406
+ * Get the number of messages in history
6157
6407
  */
6158
- getNpcState(npc) {
6159
- return this.npcStates.get(npc);
6408
+ getHistoryLength() {
6409
+ return this.history.length;
6160
6410
  }
6161
- // ===== Auto Compaction =====
6162
6411
  /**
6163
- * Check if an NPC is eligible for compaction.
6164
- * @param npc The NPC to check
6165
- * @returns True if eligible for compaction
6412
+ * Clear conversation history.
6413
+ * The character design and memories will be preserved.
6166
6414
  */
6167
- isEligibleForCompaction(npc) {
6168
- if (!npc)
6169
- return false;
6170
- const state = this.npcStates.get(npc);
6171
- if (!state)
6172
- return false;
6173
- // Check if already compacted since last conversation
6174
- if (state.isCompacted)
6175
- return false;
6176
- // Check message count
6177
- const history = npc.getHistory();
6178
- const nonSystemMessages = history.filter(m => m.role !== 'system').length;
6179
- if (nonSystemMessages < this.config.autoCompactMinMessages)
6180
- return false;
6181
- // Check time since last conversation
6182
- const timeSinceLastConversation = (Date.now() - state.lastConversationTime.getTime()) / 1000;
6183
- if (timeSinceLastConversation < this.config.autoCompactTimeoutSeconds)
6184
- return false;
6185
- return true;
6415
+ clearHistory() {
6416
+ this.history = [];
6417
+ this.emit('history_cleared');
6186
6418
  }
6187
6419
  /**
6188
- * Manually trigger conversation compaction for a specific NPC.
6189
- * Summarizes the conversation history and stores it as a memory.
6190
- * @param npc The NPC to compact
6191
- * @returns True if compaction succeeded
6420
+ * Revert the last exchange (user message and assistant response) from history.
6421
+ * @returns true if reverted, false if not enough history
6192
6422
  */
6193
- async compactConversation(npc) {
6194
- if (!npc) {
6195
- this.logger.warn('Cannot compact: NPC is null');
6196
- return false;
6197
- }
6198
- if (!this.chatClientFactory) {
6199
- this.logger.error('Cannot compact: No chat client factory set. Call setChatClientFactory() first.');
6200
- return false;
6201
- }
6202
- const history = npc.getHistory();
6203
- const nonSystemMessages = history.filter(m => m.role !== 'system');
6204
- if (nonSystemMessages.length < 2) {
6205
- this.logger.info('Skipping compaction: not enough messages');
6206
- return false;
6207
- }
6208
- try {
6209
- this.logger.info(`Starting compaction (${nonSystemMessages.length} messages)`);
6210
- // Build conversation text for summarization
6211
- const conversationText = nonSystemMessages
6212
- .map(m => `${m.role}: ${m.content}`)
6213
- .join('\n');
6214
- // Create summarization prompt
6215
- const summaryPrompt = `Summarize the following conversation concisely. Focus on:
6216
- 1. Key topics discussed
6217
- 2. Important information exchanged
6218
- 3. Any decisions or commitments made
6219
- 4. The emotional tone
6220
-
6221
- Keep the summary under 200 words. Write in third person.
6222
-
6223
- Conversation:
6224
- ${conversationText}`;
6225
- // Use chat client for summarization
6226
- const chatClient = this.chatClientFactory();
6227
- const result = await chatClient.textGeneration({
6228
- messages: [{ role: 'user', content: summaryPrompt }],
6229
- temperature: 0.5,
6230
- model: this.config.fastModel || undefined,
6231
- });
6232
- if (!result.content) {
6233
- const error = 'Empty response from summarization';
6234
- this.logger.error(`Compaction failed: ${error}`);
6235
- this.emit('compactionFailed', npc, error);
6236
- return false;
6423
+ revertHistory() {
6424
+ let lastAssistantIndex = -1;
6425
+ let lastUserIndex = -1;
6426
+ for (let i = this.history.length - 1; i >= 0; i--) {
6427
+ if (this.history[i].role === 'assistant' && lastAssistantIndex === -1) {
6428
+ lastAssistantIndex = i;
6237
6429
  }
6238
- // Clear history and add summary as memory
6239
- npc.clearHistory();
6240
- npc.setMemory('PreviousConversationSummary', result.content);
6241
- // Update state
6242
- const state = this.npcStates.get(npc);
6243
- if (state) {
6244
- state.isCompacted = true;
6245
- state.compactionCount++;
6430
+ else if (this.history[i].role === 'user' && lastAssistantIndex !== -1 && lastUserIndex === -1) {
6431
+ lastUserIndex = i;
6432
+ break;
6246
6433
  }
6247
- this.logger.info(`Compaction completed. Summary: ${result.content.substring(0, 100)}...`);
6248
- this.emit('npcCompacted', npc);
6249
- return true;
6250
6434
  }
6251
- catch (error) {
6252
- const errorMessage = error instanceof Error ? error.message : String(error);
6253
- this.logger.error(`Compaction error: ${errorMessage}`);
6254
- this.emit('compactionFailed', npc, errorMessage);
6255
- return false;
6435
+ if (lastAssistantIndex !== -1 && lastUserIndex !== -1) {
6436
+ // Remove in reverse order to maintain indices
6437
+ this.history.splice(lastAssistantIndex, 1);
6438
+ this.history.splice(lastUserIndex, 1);
6439
+ this.emit('history_reverted');
6440
+ return true;
6256
6441
  }
6442
+ return false;
6257
6443
  }
6258
6444
  /**
6259
- * Compact all registered NPCs that meet the eligibility criteria.
6260
- * @returns Number of NPCs successfully compacted
6445
+ * Revert (remove) the last N chat messages from history
6446
+ * @param count Number of messages to remove
6447
+ * @returns Number of messages actually removed
6261
6448
  */
6262
- async compactAllEligible() {
6263
- const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
6264
- if (eligibleNpcs.length === 0) {
6449
+ revertChatMessages(count) {
6450
+ if (count <= 0)
6265
6451
  return 0;
6452
+ const messagesToRemove = Math.min(count, this.history.length);
6453
+ const originalCount = this.history.length;
6454
+ this.history = this.history.slice(0, -messagesToRemove);
6455
+ const actuallyRemoved = originalCount - this.history.length;
6456
+ if (actuallyRemoved > 0) {
6457
+ this.emit('history_reverted', actuallyRemoved);
6266
6458
  }
6267
- this.logger.info(`Compacting ${eligibleNpcs.length} eligible NPCs`);
6268
- let successCount = 0;
6269
- for (const npc of eligibleNpcs) {
6270
- const success = await this.compactConversation(npc);
6271
- if (success)
6272
- successCount++;
6273
- }
6274
- return successCount;
6459
+ return actuallyRemoved;
6275
6460
  }
6276
- // ===== Auto Compact Timer =====
6277
6461
  /**
6278
- * Start the auto-compact check timer
6462
+ * Revert to a specific point in history
6463
+ * @deprecated Use revertHistory() or revertChatMessages() instead
6279
6464
  */
6280
- startAutoCompactCheck() {
6281
- if (this.autoCompactTimer) {
6282
- this.stopAutoCompactCheck();
6465
+ revertToMessage(index) {
6466
+ if (index >= 0 && index < this.history.length) {
6467
+ this.history = this.history.slice(0, index + 1);
6468
+ this.emit('history_reverted', index);
6283
6469
  }
6284
- this.autoCompactTimer = setInterval(() => {
6285
- this.runAutoCompactCheck();
6286
- }, this.config.autoCompactCheckInterval);
6287
6470
  }
6288
6471
  /**
6289
- * Stop the auto-compact check timer
6472
+ * Append a message to history manually
6290
6473
  */
6291
- stopAutoCompactCheck() {
6292
- if (this.autoCompactTimer) {
6293
- clearInterval(this.autoCompactTimer);
6294
- this.autoCompactTimer = null;
6295
- }
6474
+ appendMessage(message) {
6475
+ this.history.push(message);
6476
+ this.trimHistory();
6296
6477
  }
6297
6478
  /**
6298
- * Run a single auto-compact check
6479
+ * Alias for appendMessage (Unity SDK compatibility)
6299
6480
  */
6300
- async runAutoCompactCheck() {
6301
- if (!this.config.enableAutoCompact)
6481
+ appendChatMessage(role, content) {
6482
+ if (!role || !content) {
6483
+ this.logger.warn('Role and content cannot be empty');
6302
6484
  return;
6303
- const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
6304
- for (const npc of eligibleNpcs) {
6305
- // Fire and forget - don't block
6306
- this.compactConversation(npc).catch(err => {
6307
- this.logger.error('Auto-compact error:', err);
6308
- });
6309
6485
  }
6486
+ this.appendMessage({ role: role, content });
6310
6487
  }
6311
- // ===== Lifecycle =====
6312
6488
  /**
6313
- * Enable auto-compaction
6489
+ * Trim history to max length
6314
6490
  */
6315
- enableAutoCompact() {
6316
- this.config.enableAutoCompact = true;
6317
- this.startAutoCompactCheck();
6491
+ trimHistory() {
6492
+ if (this.history.length > this.maxHistoryLength) {
6493
+ // Keep the most recent messages
6494
+ this.history = this.history.slice(-this.maxHistoryLength);
6495
+ }
6318
6496
  }
6497
+ // ===== Save/Load =====
6319
6498
  /**
6320
- * Disable auto-compaction
6499
+ * Save the current conversation history to a serializable format.
6500
+ * Includes characterDesign, memories, and history.
6321
6501
  */
6322
- disableAutoCompact() {
6323
- this.config.enableAutoCompact = false;
6324
- this.stopAutoCompactCheck();
6502
+ saveHistory() {
6503
+ const saveData = {
6504
+ characterDesign: this.characterDesign,
6505
+ memories: Array.from(this.memories.entries()).map(([name, content]) => ({ name, content })),
6506
+ history: this.history,
6507
+ };
6508
+ return JSON.stringify(saveData);
6325
6509
  }
6326
6510
  /**
6327
- * Clean up resources
6511
+ * Load conversation history from serialized data.
6512
+ * Restores characterDesign, memories, and history.
6328
6513
  */
6329
- destroy() {
6330
- this.stopAutoCompactCheck();
6331
- this.npcStates.clear();
6332
- this.playerDescription = null;
6333
- this.removeAllListeners();
6514
+ loadHistory(saveData) {
6515
+ try {
6516
+ const data = JSON.parse(saveData);
6517
+ // Load character design (with backwards compatibility for old systemPrompt field)
6518
+ this.characterDesign = data.characterDesign || data.systemPrompt || this.characterDesign;
6519
+ // Load memories
6520
+ this.memories.clear();
6521
+ if (data.memories && Array.isArray(data.memories)) {
6522
+ for (const memory of data.memories) {
6523
+ if (memory.name && memory.content) {
6524
+ this.memories.set(memory.name, memory.content);
6525
+ }
6526
+ }
6527
+ }
6528
+ // Load history (skip system messages as they'll be rebuilt from characterDesign + memories)
6529
+ this.history = (data.history || []).filter(m => m.role !== 'system');
6530
+ this.emit('history_loaded');
6531
+ return true;
6532
+ }
6533
+ catch (error) {
6534
+ this.logger.error('Failed to load history:', error);
6535
+ return false;
6536
+ }
6334
6537
  }
6335
6538
  }
6336
- AIContextManager._instance = null;
6337
- /**
6338
- * Default AIContextManager instance
6339
- * Can be used as a global context manager
6340
- */
6341
- const defaultContextManager = AIContextManager.getInstance();
6342
6539
 
6343
6540
  /**
6344
6541
  * Schema Library for managing JSON schemas for AI structured output generation
@@ -6697,20 +6894,20 @@ ${conversationText}`;
6697
6894
  // Create indicator element
6698
6895
  this.devTokenIndicator = document.createElement('div');
6699
6896
  this.devTokenIndicator.textContent = 'DeveloperToken';
6700
- this.devTokenIndicator.style.cssText = `
6701
- position: fixed;
6702
- top: 10px;
6703
- left: 10px;
6704
- background-color: #dc2626;
6705
- color: white;
6706
- padding: 4px 12px;
6707
- border-radius: 4px;
6708
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
6709
- font-size: 12px;
6710
- font-weight: 600;
6711
- z-index: 999999;
6712
- pointer-events: none;
6713
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
6897
+ this.devTokenIndicator.style.cssText = `
6898
+ position: fixed;
6899
+ top: 10px;
6900
+ left: 10px;
6901
+ background-color: #dc2626;
6902
+ color: white;
6903
+ padding: 4px 12px;
6904
+ border-radius: 4px;
6905
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
6906
+ font-size: 12px;
6907
+ font-weight: 600;
6908
+ z-index: 999999;
6909
+ pointer-events: none;
6910
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
6714
6911
  `;
6715
6912
  document.body.appendChild(this.devTokenIndicator);
6716
6913
  }
@@ -6873,7 +7070,7 @@ ${conversationText}`;
6873
7070
  */
6874
7071
  setDebug(enabled) {
6875
7072
  this.config.debug = enabled;
6876
- Logger.setGlobalLevel(enabled ? exports.LogLevel.DEBUG : exports.LogLevel.WARN);
7073
+ Logger.setGlobalLevel(enabled ? LogLevel.DEBUG : LogLevel.WARN);
6877
7074
  }
6878
7075
  /**
6879
7076
  * Configure the logging system
@@ -6895,7 +7092,7 @@ ${conversationText}`;
6895
7092
  initializeLogging(config) {
6896
7093
  // Handle legacy debug option for backwards compatibility
6897
7094
  if (config.debug !== undefined && config.logging === undefined) {
6898
- Logger.setGlobalLevel(config.debug ? exports.LogLevel.DEBUG : exports.LogLevel.WARN);
7095
+ Logger.setGlobalLevel(config.debug ? LogLevel.DEBUG : LogLevel.WARN);
6899
7096
  }
6900
7097
  // Apply new logging config
6901
7098
  if (config.logging) {
@@ -7099,9 +7296,7 @@ ${conversationText}`;
7099
7296
  */
7100
7297
  async validateToken(token, gameId) {
7101
7298
  var _a, _b;
7102
- const headers = {
7103
- 'Authorization': `Bearer ${token}`,
7104
- };
7299
+ const headers = Object.assign({ 'Authorization': `Bearer ${token}` }, getSDKHeaders());
7105
7300
  if (gameId) {
7106
7301
  headers['X-Game-Id'] = gameId;
7107
7302
  }
@@ -7131,9 +7326,7 @@ ${conversationText}`;
7131
7326
  */
7132
7327
  async verifyToken(token, gameId) {
7133
7328
  var _a, _b;
7134
- const headers = {
7135
- 'Authorization': `Bearer ${token}`,
7136
- };
7329
+ const headers = Object.assign({ 'Authorization': `Bearer ${token}` }, getSDKHeaders());
7137
7330
  if (gameId) {
7138
7331
  headers['X-Game-Id'] = gameId;
7139
7332
  }
@@ -7196,36 +7389,61 @@ ${conversationText}`;
7196
7389
  */
7197
7390
  const defaultTokenValidator = new TokenValidator();
7198
7391
 
7199
- exports.AIContextManager = AIContextManager;
7200
- exports.AuthFlowManager = AuthFlowManager;
7201
- exports.AuthManager = AuthManager;
7202
- exports.BrowserStorage = BrowserStorage;
7203
- exports.BufferLogHandler = BufferLogHandler;
7204
- exports.CallbackLogHandler = CallbackLogHandler;
7205
- exports.ChatClient = ChatClient;
7206
- exports.DeviceAuthFlowManager = DeviceAuthFlowManager;
7207
- exports.ImageClient = ImageClient;
7208
- exports.Logger = Logger;
7209
- exports.MemoryStorage = MemoryStorage;
7210
- exports.NPCClient = NPCClient;
7211
- exports.PlayKitSDK = PlayKitSDK;
7212
- exports.PlayerClient = PlayerClient;
7213
- exports.RechargeManager = RechargeManager;
7214
- exports.SchemaLibrary = SchemaLibrary;
7215
- exports.StreamParser = StreamParser;
7216
- exports.TokenStorage = TokenStorage;
7217
- exports.TokenValidator = TokenValidator;
7218
- exports.TranscriptionClient = TranscriptionClient;
7219
- exports.createMultimodalMessage = createMultimodalMessage;
7220
- exports.createStorage = createStorage;
7221
- exports.createTextMessage = createTextMessage;
7222
- exports.default = PlayKitSDK;
7223
- exports.defaultContextManager = defaultContextManager;
7224
- exports.defaultSchemaLibrary = defaultSchemaLibrary;
7225
- exports.defaultTokenValidator = defaultTokenValidator;
7226
- exports.isLocalStorageAvailable = isLocalStorageAvailable;
7227
-
7228
- Object.defineProperty(exports, '__esModule', { value: true });
7392
+ /**
7393
+ * PlayKit SDK for JavaScript
7394
+ * AI integration for web-based games
7395
+ */
7396
+ // Main SDK
7397
+
7398
+ var namespace = /*#__PURE__*/Object.freeze({
7399
+ __proto__: null,
7400
+ AIContextManager: AIContextManager,
7401
+ AuthFlowManager: AuthFlowManager,
7402
+ AuthManager: AuthManager,
7403
+ BrowserStorage: BrowserStorage,
7404
+ BufferLogHandler: BufferLogHandler,
7405
+ CallbackLogHandler: CallbackLogHandler,
7406
+ ChatClient: ChatClient,
7407
+ DeviceAuthFlowManager: DeviceAuthFlowManager,
7408
+ ImageClient: ImageClient,
7409
+ get LogLevel () { return LogLevel; },
7410
+ Logger: Logger,
7411
+ MemoryStorage: MemoryStorage,
7412
+ NPCClient: NPCClient,
7413
+ PlayKitSDK: PlayKitSDK,
7414
+ PlayerClient: PlayerClient,
7415
+ RechargeManager: RechargeManager,
7416
+ SchemaLibrary: SchemaLibrary,
7417
+ StreamParser: StreamParser,
7418
+ TokenStorage: TokenStorage,
7419
+ TokenValidator: TokenValidator,
7420
+ TranscriptionClient: TranscriptionClient,
7421
+ createMultimodalMessage: createMultimodalMessage,
7422
+ createStorage: createStorage,
7423
+ createTextMessage: createTextMessage,
7424
+ default: PlayKitSDK,
7425
+ defaultContextManager: defaultContextManager,
7426
+ defaultSchemaLibrary: defaultSchemaLibrary,
7427
+ defaultTokenValidator: defaultTokenValidator,
7428
+ isLocalStorageAvailable: isLocalStorageAvailable
7429
+ });
7430
+
7431
+ /**
7432
+ * UMD bundle entry.
7433
+ *
7434
+ * Goal: make `window.PlayKitSDK` directly the constructor class while still
7435
+ * exposing every named export as a property on it.
7436
+ *
7437
+ * Result:
7438
+ * new window.PlayKitSDK(cfg) // recommended
7439
+ * new window.PlayKitSDK.PlayKitSDK(cfg) // legacy v1.x form, still works
7440
+ * window.PlayKitSDK.ChatClient // named exports preserved
7441
+ */
7442
+ Object.assign(PlayKitSDK, namespace);
7443
+ PlayKitSDK.PlayKitSDK = PlayKitSDK;
7444
+ PlayKitSDK.default = PlayKitSDK;
7445
+
7446
+ return PlayKitSDK;
7229
7447
 
7230
7448
  }));
7231
7449
  //# sourceMappingURL=playkit-sdk.umd.js.map