playkit-sdk 1.2.13 → 1.4.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * playkit-sdk v1.2.13
2
+ * playkit-sdk v1.4.0-beta.1
3
3
  * PlayKit SDK for JavaScript
4
4
  * @license SEE LICENSE IN LICENSE
5
5
  */
@@ -825,6 +825,15 @@ class TokenStorage {
825
825
  }
826
826
  }
827
827
 
828
+ const SDK_TYPE = 'Javascript';
829
+ const SDK_VERSION = '"1.4.0-beta.1"';
830
+ function getSDKHeaders() {
831
+ return {
832
+ 'X-SDK-Type': SDK_TYPE,
833
+ 'X-SDK-Version': SDK_VERSION,
834
+ };
835
+ }
836
+
828
837
  /**
829
838
  * Authentication Flow Manager
830
839
  * Manages the headless authentication flow with automatic UI
@@ -929,8 +938,8 @@ class AuthFlowManager extends EventEmitter {
929
938
  constructor(baseURL) {
930
939
  super();
931
940
  this.currentSessionId = null;
932
- this.uiContainer = null;
933
- this.isSuccess = false;
941
+ this._uiContainer = null;
942
+ this._isSuccess = false;
934
943
  this.currentLanguage = 'en';
935
944
  // UI Elements
936
945
  this.modal = null;
@@ -1011,84 +1020,84 @@ class AuthFlowManager extends EventEmitter {
1011
1020
  // Create modal container
1012
1021
  this.modal = document.createElement('div');
1013
1022
  this.modal.className = 'playkit-auth-modal';
1014
- this.modal.innerHTML = `
1015
- <div class="playkit-auth-overlay"></div>
1016
- <div class="playkit-auth-container">
1017
- <!-- Identifier Panel -->
1018
- <div class="playkit-auth-panel" id="playkit-identifier-panel">
1019
- <div class="playkit-auth-header">
1020
- <h2>${this.t('signIn')}</h2>
1021
- <p>${this.t('signInSubtitle')}</p>
1022
- </div>
1023
-
1024
- <div class="playkit-auth-toggle">
1025
- <label class="playkit-toggle-option">
1026
- <input type="radio" name="auth-type" value="email" checked>
1027
- <span>${this.t('email')}</span>
1028
- </label>
1029
- <label class="playkit-toggle-option">
1030
- <input type="radio" name="auth-type" value="phone">
1031
- <span>${this.t('phone')}</span>
1032
- </label>
1033
- </div>
1034
-
1035
- <div class="playkit-auth-input-group">
1036
- <div class="playkit-input-wrapper">
1037
- <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">
1038
- <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>
1039
- <polyline points="22,6 12,13 2,6"></polyline>
1040
- </svg>
1041
- <input
1042
- type="text"
1043
- id="playkit-identifier-input"
1044
- placeholder="${this.t('emailPlaceholder')}"
1045
- autocomplete="off"
1046
- >
1047
- </div>
1048
- </div>
1049
-
1050
- <button class="playkit-auth-button" id="playkit-send-code-btn">
1051
- ${this.t('sendCode')}
1052
- </button>
1053
-
1054
- <div class="playkit-auth-error" id="playkit-error-text"></div>
1055
- </div>
1056
-
1057
- <!-- Verification Panel -->
1058
- <div class="playkit-auth-panel" id="playkit-verification-panel" style="display: none;">
1059
- <div class="playkit-auth-header">
1060
- <button class="playkit-back-button" id="playkit-back-btn">
1061
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1062
- <path d="M19 12H5M12 19l-7-7 7-7"/>
1063
- </svg>
1064
- </button>
1065
- <h2>${this.t('enterCode')}</h2>
1066
- <p>${this.t('enterCodeSubtitle')} <span id="playkit-identifier-display"></span></p>
1067
- </div>
1068
-
1069
- <div class="playkit-auth-input-group">
1070
- <div class="playkit-code-inputs">
1071
- <input type="number" maxlength="1" class="playkit-code-input" data-index="0">
1072
- <input type="number" maxlength="1" class="playkit-code-input" data-index="1">
1073
- <input type="number" maxlength="1" class="playkit-code-input" data-index="2">
1074
- <input type="number" maxlength="1" class="playkit-code-input" data-index="3">
1075
- <input type="number" maxlength="1" class="playkit-code-input" data-index="4">
1076
- <input type="number" maxlength="1" class="playkit-code-input" data-index="5">
1077
- </div>
1078
- </div>
1079
-
1080
- <button class="playkit-auth-button" id="playkit-verify-btn">
1081
- ${this.t('verify')}
1082
- </button>
1083
-
1084
- <div class="playkit-auth-error" id="playkit-verify-error-text"></div>
1085
- </div>
1086
-
1087
- <!-- Loading Overlay -->
1088
- <div class="playkit-loading-overlay" id="playkit-loading-overlay" style="display: none;">
1089
- <div class="playkit-spinner"></div>
1090
- </div>
1091
- </div>
1023
+ this.modal.innerHTML = `
1024
+ <div class="playkit-auth-overlay"></div>
1025
+ <div class="playkit-auth-container">
1026
+ <!-- Identifier Panel -->
1027
+ <div class="playkit-auth-panel" id="playkit-identifier-panel">
1028
+ <div class="playkit-auth-header">
1029
+ <h2>${this.t('signIn')}</h2>
1030
+ <p>${this.t('signInSubtitle')}</p>
1031
+ </div>
1032
+
1033
+ <div class="playkit-auth-toggle">
1034
+ <label class="playkit-toggle-option">
1035
+ <input type="radio" name="auth-type" value="email" checked>
1036
+ <span>${this.t('email')}</span>
1037
+ </label>
1038
+ <label class="playkit-toggle-option">
1039
+ <input type="radio" name="auth-type" value="phone">
1040
+ <span>${this.t('phone')}</span>
1041
+ </label>
1042
+ </div>
1043
+
1044
+ <div class="playkit-auth-input-group">
1045
+ <div class="playkit-input-wrapper">
1046
+ <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">
1047
+ <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>
1048
+ <polyline points="22,6 12,13 2,6"></polyline>
1049
+ </svg>
1050
+ <input
1051
+ type="text"
1052
+ id="playkit-identifier-input"
1053
+ placeholder="${this.t('emailPlaceholder')}"
1054
+ autocomplete="off"
1055
+ >
1056
+ </div>
1057
+ </div>
1058
+
1059
+ <button class="playkit-auth-button" id="playkit-send-code-btn">
1060
+ ${this.t('sendCode')}
1061
+ </button>
1062
+
1063
+ <div class="playkit-auth-error" id="playkit-error-text"></div>
1064
+ </div>
1065
+
1066
+ <!-- Verification Panel -->
1067
+ <div class="playkit-auth-panel" id="playkit-verification-panel" style="display: none;">
1068
+ <div class="playkit-auth-header">
1069
+ <button class="playkit-back-button" id="playkit-back-btn">
1070
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1071
+ <path d="M19 12H5M12 19l-7-7 7-7"/>
1072
+ </svg>
1073
+ </button>
1074
+ <h2>${this.t('enterCode')}</h2>
1075
+ <p>${this.t('enterCodeSubtitle')} <span id="playkit-identifier-display"></span></p>
1076
+ </div>
1077
+
1078
+ <div class="playkit-auth-input-group">
1079
+ <div class="playkit-code-inputs">
1080
+ <input type="number" maxlength="1" class="playkit-code-input" data-index="0">
1081
+ <input type="number" maxlength="1" class="playkit-code-input" data-index="1">
1082
+ <input type="number" maxlength="1" class="playkit-code-input" data-index="2">
1083
+ <input type="number" maxlength="1" class="playkit-code-input" data-index="3">
1084
+ <input type="number" maxlength="1" class="playkit-code-input" data-index="4">
1085
+ <input type="number" maxlength="1" class="playkit-code-input" data-index="5">
1086
+ </div>
1087
+ </div>
1088
+
1089
+ <button class="playkit-auth-button" id="playkit-verify-btn">
1090
+ ${this.t('verify')}
1091
+ </button>
1092
+
1093
+ <div class="playkit-auth-error" id="playkit-verify-error-text"></div>
1094
+ </div>
1095
+
1096
+ <!-- Loading Overlay -->
1097
+ <div class="playkit-loading-overlay" id="playkit-loading-overlay" style="display: none;">
1098
+ <div class="playkit-spinner"></div>
1099
+ </div>
1100
+ </div>
1092
1101
  `;
1093
1102
  // Add styles and load VanillaOTP
1094
1103
  this.addStyles();
@@ -1123,274 +1132,274 @@ class AuthFlowManager extends EventEmitter {
1123
1132
  return;
1124
1133
  const style = document.createElement('style');
1125
1134
  style.id = styleId;
1126
- style.textContent = `
1127
- .playkit-auth-modal {
1128
- position: fixed;
1129
- top: 0;
1130
- left: 0;
1131
- right: 0;
1132
- bottom: 0;
1133
- z-index: 999999;
1134
- display: flex;
1135
- justify-content: center;
1136
- align-items: center;
1137
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
1138
- }
1139
-
1140
- .playkit-auth-overlay {
1141
- position: absolute;
1142
- top: 0;
1143
- left: 0;
1144
- right: 0;
1145
- bottom: 0;
1146
- background: rgba(0, 0, 0, 0.8);
1147
- }
1148
-
1149
- .playkit-auth-container {
1150
- position: relative;
1151
- background: #fff;
1152
- border: 1px solid rgba(0, 0, 0, 0.1);
1153
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
1154
- width: 90%;
1155
- max-width: 320px;
1156
- overflow: hidden;
1157
- }
1158
-
1159
- .playkit-auth-panel {
1160
- padding: 24px;
1161
- }
1162
-
1163
- .playkit-auth-header {
1164
- text-align: center;
1165
- margin-bottom: 20px;
1166
- position: relative;
1167
- }
1168
-
1169
- .playkit-auth-header h2 {
1170
- margin: 0 0 8px 0;
1171
- font-size: 14px;
1172
- font-weight: 600;
1173
- color: #171717;
1174
- }
1175
-
1176
- .playkit-auth-header p {
1177
- margin: 0;
1178
- font-size: 14px;
1179
- color: #666;
1180
- line-height: 1.5;
1181
- }
1182
-
1183
- .playkit-back-button {
1184
- position: absolute;
1185
- left: 0;
1186
- top: 0;
1187
- background: transparent;
1188
- border: none;
1189
- cursor: pointer;
1190
- padding: 4px;
1191
- color: #666;
1192
- transition: background-color 0.2s ease, color 0.2s ease;
1193
- }
1194
-
1195
- .playkit-back-button:hover {
1196
- background: #f5f5f5;
1197
- color: #171717;
1198
- }
1199
-
1200
- .playkit-auth-toggle {
1201
- display: flex;
1202
- background: #f5f5f5;
1203
- padding: 2px;
1204
- margin-bottom: 20px;
1205
- gap: 2px;
1206
- }
1207
-
1208
- .playkit-toggle-option {
1209
- flex: 1;
1210
- display: flex;
1211
- justify-content: center;
1212
- align-items: center;
1213
- padding: 10px 16px;
1214
- cursor: pointer;
1215
- transition: background-color 0.2s ease;
1216
- }
1217
-
1218
- .playkit-toggle-option input {
1219
- display: none;
1220
- }
1221
-
1222
- .playkit-toggle-option span {
1223
- font-size: 14px;
1224
- font-weight: 500;
1225
- color: #666;
1226
- transition: color 0.2s ease;
1227
- }
1228
-
1229
- .playkit-toggle-option input:checked + span {
1230
- color: #fff;
1231
- }
1232
-
1233
- .playkit-toggle-option:has(input:checked) {
1234
- background: #171717;
1235
- }
1236
-
1237
- .playkit-auth-input-group {
1238
- margin-bottom: 20px;
1239
- }
1240
-
1241
- .playkit-input-wrapper {
1242
- position: relative;
1243
- display: flex;
1244
- align-items: center;
1245
- }
1246
-
1247
- .playkit-input-icon {
1248
- position: absolute;
1249
- left: 12px;
1250
- color: #999;
1251
- pointer-events: none;
1252
- }
1253
-
1254
- .playkit-input-wrapper input {
1255
- width: 100%;
1256
- padding: 10px 12px 10px 44px;
1257
- border: 1px solid #e5e7eb;
1258
- font-size: 14px;
1259
- transition: border-color 0.2s ease;
1260
- box-sizing: border-box;
1261
- background: #fff;
1262
- }
1263
-
1264
- .playkit-input-wrapper input:hover {
1265
- border-color: #d4d4d4;
1266
- }
1267
-
1268
- .playkit-input-wrapper input:focus {
1269
- outline: none;
1270
- border-color: #171717;
1271
- }
1272
-
1273
- .playkit-code-inputs {
1274
- display: flex;
1275
- gap: 8px;
1276
- justify-content: center;
1277
- }
1278
-
1279
- .playkit-code-input {
1280
- width: 40px !important;
1281
- height: 48px;
1282
- text-align: center;
1283
- font-size: 20px;
1284
- font-weight: 600;
1285
- border: 1px solid #e5e7eb !important;
1286
- padding: 0 !important;
1287
- transition: border-color 0.2s ease;
1288
- background: #fff;
1289
- -moz-appearance: textfield;
1290
- }
1291
-
1292
- .playkit-code-input::-webkit-outer-spin-button,
1293
- .playkit-code-input::-webkit-inner-spin-button {
1294
- -webkit-appearance: none;
1295
- margin: 0;
1296
- }
1297
-
1298
- .playkit-code-input:hover {
1299
- border-color: #d4d4d4 !important;
1300
- }
1301
-
1302
- .playkit-code-input:focus {
1303
- outline: none;
1304
- border-color: #171717 !important;
1305
- }
1306
-
1307
- .playkit-auth-button {
1308
- width: 100%;
1309
- padding: 10px 16px;
1310
- background: #171717;
1311
- color: white;
1312
- border: none;
1313
- font-size: 14px;
1314
- font-weight: 500;
1315
- cursor: pointer;
1316
- transition: background 0.2s ease;
1317
- }
1318
-
1319
- .playkit-auth-button:hover:not(:disabled) {
1320
- background: #404040;
1321
- }
1322
-
1323
- .playkit-auth-button:active:not(:disabled) {
1324
- background: #0a0a0a;
1325
- }
1326
-
1327
- .playkit-auth-button:disabled {
1328
- background: #e5e7eb;
1329
- color: #999;
1330
- cursor: not-allowed;
1331
- }
1332
-
1333
- .playkit-auth-error {
1334
- margin-top: 16px;
1335
- padding: 12px 16px;
1336
- background: #fef2f2;
1337
- border: 1px solid #fecaca;
1338
- color: #dc2626;
1339
- font-size: 13px;
1340
- text-align: left;
1341
- display: none;
1342
- }
1343
-
1344
- .playkit-auth-error.show {
1345
- display: block;
1346
- }
1347
-
1348
- .playkit-loading-overlay {
1349
- position: absolute;
1350
- top: 0;
1351
- left: 0;
1352
- right: 0;
1353
- bottom: 0;
1354
- background: rgba(255, 255, 255, 0.96);
1355
- display: flex;
1356
- justify-content: center;
1357
- align-items: center;
1358
- }
1359
-
1360
- .playkit-spinner {
1361
- width: 24px;
1362
- height: 24px;
1363
- border: 2px solid #e5e7eb;
1364
- border-top: 2px solid #171717;
1365
- border-radius: 50%;
1366
- animation: playkit-spin 1s linear infinite;
1367
- }
1368
-
1369
- @keyframes playkit-spin {
1370
- 0% { transform: rotate(0deg); }
1371
- 100% { transform: rotate(360deg); }
1372
- }
1373
-
1374
- @media (max-width: 480px) {
1375
- .playkit-auth-container {
1376
- width: 95%;
1377
- max-width: none;
1378
- }
1379
-
1380
- .playkit-auth-panel {
1381
- padding: 20px;
1382
- }
1383
-
1384
- .playkit-code-input {
1385
- width: 36px !important;
1386
- height: 44px;
1387
- font-size: 18px;
1388
- }
1389
-
1390
- .playkit-code-inputs {
1391
- gap: 6px;
1392
- }
1393
- }
1135
+ style.textContent = `
1136
+ .playkit-auth-modal {
1137
+ position: fixed;
1138
+ top: 0;
1139
+ left: 0;
1140
+ right: 0;
1141
+ bottom: 0;
1142
+ z-index: 999999;
1143
+ display: flex;
1144
+ justify-content: center;
1145
+ align-items: center;
1146
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
1147
+ }
1148
+
1149
+ .playkit-auth-overlay {
1150
+ position: absolute;
1151
+ top: 0;
1152
+ left: 0;
1153
+ right: 0;
1154
+ bottom: 0;
1155
+ background: rgba(0, 0, 0, 0.8);
1156
+ }
1157
+
1158
+ .playkit-auth-container {
1159
+ position: relative;
1160
+ background: #fff;
1161
+ border: 1px solid rgba(0, 0, 0, 0.1);
1162
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
1163
+ width: 90%;
1164
+ max-width: 320px;
1165
+ overflow: hidden;
1166
+ }
1167
+
1168
+ .playkit-auth-panel {
1169
+ padding: 24px;
1170
+ }
1171
+
1172
+ .playkit-auth-header {
1173
+ text-align: center;
1174
+ margin-bottom: 20px;
1175
+ position: relative;
1176
+ }
1177
+
1178
+ .playkit-auth-header h2 {
1179
+ margin: 0 0 8px 0;
1180
+ font-size: 14px;
1181
+ font-weight: 600;
1182
+ color: #171717;
1183
+ }
1184
+
1185
+ .playkit-auth-header p {
1186
+ margin: 0;
1187
+ font-size: 14px;
1188
+ color: #666;
1189
+ line-height: 1.5;
1190
+ }
1191
+
1192
+ .playkit-back-button {
1193
+ position: absolute;
1194
+ left: 0;
1195
+ top: 0;
1196
+ background: transparent;
1197
+ border: none;
1198
+ cursor: pointer;
1199
+ padding: 4px;
1200
+ color: #666;
1201
+ transition: background-color 0.2s ease, color 0.2s ease;
1202
+ }
1203
+
1204
+ .playkit-back-button:hover {
1205
+ background: #f5f5f5;
1206
+ color: #171717;
1207
+ }
1208
+
1209
+ .playkit-auth-toggle {
1210
+ display: flex;
1211
+ background: #f5f5f5;
1212
+ padding: 2px;
1213
+ margin-bottom: 20px;
1214
+ gap: 2px;
1215
+ }
1216
+
1217
+ .playkit-toggle-option {
1218
+ flex: 1;
1219
+ display: flex;
1220
+ justify-content: center;
1221
+ align-items: center;
1222
+ padding: 10px 16px;
1223
+ cursor: pointer;
1224
+ transition: background-color 0.2s ease;
1225
+ }
1226
+
1227
+ .playkit-toggle-option input {
1228
+ display: none;
1229
+ }
1230
+
1231
+ .playkit-toggle-option span {
1232
+ font-size: 14px;
1233
+ font-weight: 500;
1234
+ color: #666;
1235
+ transition: color 0.2s ease;
1236
+ }
1237
+
1238
+ .playkit-toggle-option input:checked + span {
1239
+ color: #fff;
1240
+ }
1241
+
1242
+ .playkit-toggle-option:has(input:checked) {
1243
+ background: #171717;
1244
+ }
1245
+
1246
+ .playkit-auth-input-group {
1247
+ margin-bottom: 20px;
1248
+ }
1249
+
1250
+ .playkit-input-wrapper {
1251
+ position: relative;
1252
+ display: flex;
1253
+ align-items: center;
1254
+ }
1255
+
1256
+ .playkit-input-icon {
1257
+ position: absolute;
1258
+ left: 12px;
1259
+ color: #999;
1260
+ pointer-events: none;
1261
+ }
1262
+
1263
+ .playkit-input-wrapper input {
1264
+ width: 100%;
1265
+ padding: 10px 12px 10px 44px;
1266
+ border: 1px solid #e5e7eb;
1267
+ font-size: 14px;
1268
+ transition: border-color 0.2s ease;
1269
+ box-sizing: border-box;
1270
+ background: #fff;
1271
+ }
1272
+
1273
+ .playkit-input-wrapper input:hover {
1274
+ border-color: #d4d4d4;
1275
+ }
1276
+
1277
+ .playkit-input-wrapper input:focus {
1278
+ outline: none;
1279
+ border-color: #171717;
1280
+ }
1281
+
1282
+ .playkit-code-inputs {
1283
+ display: flex;
1284
+ gap: 8px;
1285
+ justify-content: center;
1286
+ }
1287
+
1288
+ .playkit-code-input {
1289
+ width: 40px !important;
1290
+ height: 48px;
1291
+ text-align: center;
1292
+ font-size: 20px;
1293
+ font-weight: 600;
1294
+ border: 1px solid #e5e7eb !important;
1295
+ padding: 0 !important;
1296
+ transition: border-color 0.2s ease;
1297
+ background: #fff;
1298
+ -moz-appearance: textfield;
1299
+ }
1300
+
1301
+ .playkit-code-input::-webkit-outer-spin-button,
1302
+ .playkit-code-input::-webkit-inner-spin-button {
1303
+ -webkit-appearance: none;
1304
+ margin: 0;
1305
+ }
1306
+
1307
+ .playkit-code-input:hover {
1308
+ border-color: #d4d4d4 !important;
1309
+ }
1310
+
1311
+ .playkit-code-input:focus {
1312
+ outline: none;
1313
+ border-color: #171717 !important;
1314
+ }
1315
+
1316
+ .playkit-auth-button {
1317
+ width: 100%;
1318
+ padding: 10px 16px;
1319
+ background: #171717;
1320
+ color: white;
1321
+ border: none;
1322
+ font-size: 14px;
1323
+ font-weight: 500;
1324
+ cursor: pointer;
1325
+ transition: background 0.2s ease;
1326
+ }
1327
+
1328
+ .playkit-auth-button:hover:not(:disabled) {
1329
+ background: #404040;
1330
+ }
1331
+
1332
+ .playkit-auth-button:active:not(:disabled) {
1333
+ background: #0a0a0a;
1334
+ }
1335
+
1336
+ .playkit-auth-button:disabled {
1337
+ background: #e5e7eb;
1338
+ color: #999;
1339
+ cursor: not-allowed;
1340
+ }
1341
+
1342
+ .playkit-auth-error {
1343
+ margin-top: 16px;
1344
+ padding: 12px 16px;
1345
+ background: #fef2f2;
1346
+ border: 1px solid #fecaca;
1347
+ color: #dc2626;
1348
+ font-size: 13px;
1349
+ text-align: left;
1350
+ display: none;
1351
+ }
1352
+
1353
+ .playkit-auth-error.show {
1354
+ display: block;
1355
+ }
1356
+
1357
+ .playkit-loading-overlay {
1358
+ position: absolute;
1359
+ top: 0;
1360
+ left: 0;
1361
+ right: 0;
1362
+ bottom: 0;
1363
+ background: rgba(255, 255, 255, 0.96);
1364
+ display: flex;
1365
+ justify-content: center;
1366
+ align-items: center;
1367
+ }
1368
+
1369
+ .playkit-spinner {
1370
+ width: 24px;
1371
+ height: 24px;
1372
+ border: 2px solid #e5e7eb;
1373
+ border-top: 2px solid #171717;
1374
+ border-radius: 50%;
1375
+ animation: playkit-spin 1s linear infinite;
1376
+ }
1377
+
1378
+ @keyframes playkit-spin {
1379
+ 0% { transform: rotate(0deg); }
1380
+ 100% { transform: rotate(360deg); }
1381
+ }
1382
+
1383
+ @media (max-width: 480px) {
1384
+ .playkit-auth-container {
1385
+ width: 95%;
1386
+ max-width: none;
1387
+ }
1388
+
1389
+ .playkit-auth-panel {
1390
+ padding: 20px;
1391
+ }
1392
+
1393
+ .playkit-code-input {
1394
+ width: 36px !important;
1395
+ height: 44px;
1396
+ font-size: 18px;
1397
+ }
1398
+
1399
+ .playkit-code-inputs {
1400
+ gap: 6px;
1401
+ }
1402
+ }
1394
1403
  `;
1395
1404
  document.head.appendChild(style);
1396
1405
  }
@@ -1411,14 +1420,14 @@ class AuthFlowManager extends EventEmitter {
1411
1420
  : this.t('phonePlaceholder');
1412
1421
  // Update icon
1413
1422
  if (isEmail) {
1414
- identifierIcon.innerHTML = `
1415
- <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>
1416
- <polyline points="22,6 12,13 2,6"></polyline>
1423
+ identifierIcon.innerHTML = `
1424
+ <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>
1425
+ <polyline points="22,6 12,13 2,6"></polyline>
1417
1426
  `;
1418
1427
  }
1419
1428
  else {
1420
- identifierIcon.innerHTML = `
1421
- <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>
1429
+ identifierIcon.innerHTML = `
1430
+ <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>
1422
1431
  `;
1423
1432
  }
1424
1433
  };
@@ -1439,7 +1448,7 @@ class AuthFlowManager extends EventEmitter {
1439
1448
  this.otpInstance = new window.VanillaOTP(codeInputsContainer);
1440
1449
  // Auto-submit when all 6 digits entered
1441
1450
  const codeInputs = (_d = this.modal) === null || _d === void 0 ? void 0 : _d.querySelectorAll('.playkit-code-input');
1442
- codeInputs === null || codeInputs === void 0 ? void 0 : codeInputs.forEach((input, index) => {
1451
+ codeInputs === null || codeInputs === void 0 ? void 0 : codeInputs.forEach((input, _index) => {
1443
1452
  input.addEventListener('input', () => {
1444
1453
  // Check if all inputs are filled
1445
1454
  const allFilled = Array.from(codeInputs).every(inp => inp.value.length === 1);
@@ -1531,7 +1540,7 @@ class AuthFlowManager extends EventEmitter {
1531
1540
  async sendVerificationCode(identifier, type) {
1532
1541
  const response = await fetch(`${this.baseURL}/api/auth/send-code`, {
1533
1542
  method: 'POST',
1534
- headers: { 'Content-Type': 'application/json' },
1543
+ headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
1535
1544
  body: JSON.stringify({ identifier, type }),
1536
1545
  });
1537
1546
  if (!response.ok) {
@@ -1553,7 +1562,7 @@ class AuthFlowManager extends EventEmitter {
1553
1562
  }
1554
1563
  const response = await fetch(`${this.baseURL}/api/auth/verify-code`, {
1555
1564
  method: 'POST',
1556
- headers: { 'Content-Type': 'application/json' },
1565
+ headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
1557
1566
  body: JSON.stringify({
1558
1567
  sessionId: this.currentSessionId,
1559
1568
  code,
@@ -1574,7 +1583,9 @@ class AuthFlowManager extends EventEmitter {
1574
1583
  async setDefaultAuthTypeByRegion() {
1575
1584
  var _a;
1576
1585
  try {
1577
- const response = await fetch(`${this.baseURL}/api/reachability`);
1586
+ const response = await fetch(`${this.baseURL}/api/reachability`, {
1587
+ headers: Object.assign({}, getSDKHeaders()),
1588
+ });
1578
1589
  if (response.ok) {
1579
1590
  const data = await response.json();
1580
1591
  if (data.region === 'CN') {
@@ -1831,76 +1842,76 @@ class DeviceAuthFlowManager extends EventEmitter {
1831
1842
  // Create modal overlay - dark bg-black/80 style
1832
1843
  const overlay = document.createElement('div');
1833
1844
  overlay.id = 'playkit-login-modal';
1834
- overlay.style.cssText = `
1835
- position: fixed;
1836
- top: 0;
1837
- left: 0;
1838
- right: 0;
1839
- bottom: 0;
1840
- background: rgba(0, 0, 0, 0.8);
1841
- display: flex;
1842
- align-items: center;
1843
- justify-content: center;
1844
- z-index: 999999;
1845
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
1845
+ overlay.style.cssText = `
1846
+ position: fixed;
1847
+ top: 0;
1848
+ left: 0;
1849
+ right: 0;
1850
+ bottom: 0;
1851
+ background: rgba(0, 0, 0, 0.8);
1852
+ display: flex;
1853
+ align-items: center;
1854
+ justify-content: center;
1855
+ z-index: 999999;
1856
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
1846
1857
  `;
1847
1858
  // Create modal card - square corners, shadow-xl style
1848
1859
  const card = document.createElement('div');
1849
- card.style.cssText = `
1850
- background: #fff;
1851
- border: 1px solid rgba(0, 0, 0, 0.1);
1852
- padding: 24px;
1853
- max-width: 320px;
1854
- width: 90%;
1855
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
1856
- text-align: center;
1860
+ card.style.cssText = `
1861
+ background: #fff;
1862
+ border: 1px solid rgba(0, 0, 0, 0.1);
1863
+ padding: 24px;
1864
+ max-width: 320px;
1865
+ width: 90%;
1866
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
1867
+ text-align: center;
1857
1868
  `;
1858
1869
  // Subtitle / status text
1859
1870
  const subtitle = document.createElement('p');
1860
1871
  subtitle.id = 'playkit-modal-subtitle';
1861
1872
  subtitle.textContent = this.t('loginWithPlayKit');
1862
- subtitle.style.cssText = `
1863
- margin: 0 0 20px;
1864
- font-size: 14px;
1865
- color: #666;
1873
+ subtitle.style.cssText = `
1874
+ margin: 0 0 20px;
1875
+ font-size: 14px;
1876
+ color: #666;
1866
1877
  `;
1867
1878
  card.appendChild(subtitle);
1868
1879
  // Loading spinner (hidden initially)
1869
1880
  const spinner = document.createElement('div');
1870
1881
  spinner.id = 'playkit-modal-spinner';
1871
- spinner.style.cssText = `
1872
- display: none;
1873
- width: 24px;
1874
- height: 24px;
1875
- margin: 0 auto 16px;
1876
- border: 2px solid #e5e7eb;
1877
- border-top-color: #171717;
1878
- border-radius: 50%;
1879
- animation: playkit-spin 1s linear infinite;
1882
+ spinner.style.cssText = `
1883
+ display: none;
1884
+ width: 24px;
1885
+ height: 24px;
1886
+ margin: 0 auto 16px;
1887
+ border: 2px solid #e5e7eb;
1888
+ border-top-color: #171717;
1889
+ border-radius: 50%;
1890
+ animation: playkit-spin 1s linear infinite;
1880
1891
  `;
1881
1892
  card.appendChild(spinner);
1882
1893
  // Add keyframes for spinner
1883
1894
  const style = document.createElement('style');
1884
- style.textContent = `
1885
- @keyframes playkit-spin {
1886
- to { transform: rotate(360deg); }
1887
- }
1895
+ style.textContent = `
1896
+ @keyframes playkit-spin {
1897
+ to { transform: rotate(360deg); }
1898
+ }
1888
1899
  `;
1889
1900
  document.head.appendChild(style);
1890
1901
  // Login button - square corners, simple dark style
1891
1902
  const loginBtn = document.createElement('button');
1892
1903
  loginBtn.id = 'playkit-modal-login-btn';
1893
1904
  loginBtn.textContent = this.t('loginToPlay');
1894
- loginBtn.style.cssText = `
1895
- width: 100%;
1896
- padding: 10px 16px;
1897
- font-size: 14px;
1898
- font-weight: 500;
1899
- color: white;
1900
- background: #171717;
1901
- border: none;
1902
- cursor: pointer;
1903
- transition: background 0.2s ease;
1905
+ loginBtn.style.cssText = `
1906
+ width: 100%;
1907
+ padding: 10px 16px;
1908
+ font-size: 14px;
1909
+ font-weight: 500;
1910
+ color: white;
1911
+ background: #171717;
1912
+ border: none;
1913
+ cursor: pointer;
1914
+ transition: background 0.2s ease;
1904
1915
  `;
1905
1916
  loginBtn.onmouseenter = () => {
1906
1917
  loginBtn.style.background = '#404040';
@@ -1927,18 +1938,18 @@ class DeviceAuthFlowManager extends EventEmitter {
1927
1938
  const cancelBtn = document.createElement('button');
1928
1939
  cancelBtn.id = 'playkit-modal-cancel-btn';
1929
1940
  cancelBtn.textContent = this.t('cancel');
1930
- cancelBtn.style.cssText = `
1931
- display: none;
1932
- width: 100%;
1933
- margin-top: 8px;
1934
- padding: 10px 16px;
1935
- font-size: 14px;
1936
- font-weight: 500;
1937
- color: #666;
1938
- background: transparent;
1939
- border: 1px solid #e5e7eb;
1940
- cursor: pointer;
1941
- transition: all 0.2s ease;
1941
+ cancelBtn.style.cssText = `
1942
+ display: none;
1943
+ width: 100%;
1944
+ margin-top: 8px;
1945
+ padding: 10px 16px;
1946
+ font-size: 14px;
1947
+ font-weight: 500;
1948
+ color: #666;
1949
+ background: transparent;
1950
+ border: 1px solid #e5e7eb;
1951
+ cursor: pointer;
1952
+ transition: all 0.2s ease;
1942
1953
  `;
1943
1954
  cancelBtn.onmouseenter = () => {
1944
1955
  cancelBtn.style.background = '#f5f5f5';
@@ -2008,11 +2019,11 @@ class DeviceAuthFlowManager extends EventEmitter {
2008
2019
  // Create error title
2009
2020
  const errorTitle = document.createElement('h3');
2010
2021
  errorTitle.textContent = this.t(titleKey);
2011
- errorTitle.style.cssText = `
2012
- margin: 0 0 8px;
2013
- font-size: 14px;
2014
- font-weight: 600;
2015
- color: ${iconColor};
2022
+ errorTitle.style.cssText = `
2023
+ margin: 0 0 8px;
2024
+ font-size: 14px;
2025
+ font-weight: 600;
2026
+ color: ${iconColor};
2016
2027
  `;
2017
2028
  // Update subtitle with error description
2018
2029
  subtitle.textContent = this.t(descKey);
@@ -2082,9 +2093,7 @@ class DeviceAuthFlowManager extends EventEmitter {
2082
2093
  // Step 1: Initiate device auth session
2083
2094
  const initResponse = await fetch(`${this.baseURL}/api/device-auth/initiate`, {
2084
2095
  method: 'POST',
2085
- headers: {
2086
- 'Content-Type': 'application/json',
2087
- },
2096
+ headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
2088
2097
  body: JSON.stringify({
2089
2098
  game_id: this.gameId,
2090
2099
  code_challenge: codeChallenge,
@@ -2153,7 +2162,7 @@ class DeviceAuthFlowManager extends EventEmitter {
2153
2162
  return;
2154
2163
  }
2155
2164
  try {
2156
- const pollResponse = await fetch(`${this.baseURL}/api/device-auth/poll?session_id=${encodeURIComponent(session_id)}&code_verifier=${encodeURIComponent(codeVerifier)}`);
2165
+ const pollResponse = await fetch(`${this.baseURL}/api/device-auth/poll?session_id=${encodeURIComponent(session_id)}&code_verifier=${encodeURIComponent(codeVerifier)}`, { headers: Object.assign({}, getSDKHeaders()) });
2157
2166
  const pollData = await pollResponse.json();
2158
2167
  if (pollResponse.ok) {
2159
2168
  if (pollData.status === 'pending') {
@@ -2281,9 +2290,7 @@ class DeviceAuthFlowManager extends EventEmitter {
2281
2290
  // Initiate device auth session
2282
2291
  const initResponse = await fetch(`${this.baseURL}/api/device-auth/initiate`, {
2283
2292
  method: 'POST',
2284
- headers: {
2285
- 'Content-Type': 'application/json',
2286
- },
2293
+ headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
2287
2294
  body: JSON.stringify({
2288
2295
  game_id: this.gameId,
2289
2296
  code_challenge: codeChallenge,
@@ -2346,7 +2353,7 @@ class DeviceAuthFlowManager extends EventEmitter {
2346
2353
  return;
2347
2354
  }
2348
2355
  try {
2349
- const pollResponse = await fetch(`${this.baseURL}/api/device-auth/poll?session_id=${encodeURIComponent(sessionId)}&code_verifier=${encodeURIComponent(codeVerifier)}`);
2356
+ const pollResponse = await fetch(`${this.baseURL}/api/device-auth/poll?session_id=${encodeURIComponent(sessionId)}&code_verifier=${encodeURIComponent(codeVerifier)}`, { headers: Object.assign({}, getSDKHeaders()) });
2350
2357
  const pollData = await pollResponse.json();
2351
2358
  if (pollResponse.ok) {
2352
2359
  if (pollData.status === 'pending') {
@@ -2415,7 +2422,7 @@ DeviceAuthFlowManager.activeInstance = null;
2415
2422
  * Handles JWT exchange and token management
2416
2423
  */
2417
2424
  // @ts-ignore - replaced at build time
2418
- const DEFAULT_BASE_URL$5 = "https://api.playkit.ai";
2425
+ const DEFAULT_BASE_URL$6 = "https://api.playkit.ai";
2419
2426
  const JWT_EXCHANGE_ENDPOINT = '/api/external/exchange-jwt';
2420
2427
  const TOKEN_REFRESH_ENDPOINT = '/api/auth/refresh';
2421
2428
  class AuthManager extends EventEmitter {
@@ -2433,7 +2440,7 @@ class AuthManager extends EventEmitter {
2433
2440
  this.storage = new TokenStorage({
2434
2441
  mode: config.mode === 'server' ? 'server' : 'browser',
2435
2442
  });
2436
- this.baseURL = config.baseURL || DEFAULT_BASE_URL$5;
2443
+ this.baseURL = config.baseURL || DEFAULT_BASE_URL$6;
2437
2444
  this.authState = {
2438
2445
  isAuthenticated: false,
2439
2446
  };
@@ -2615,10 +2622,7 @@ class AuthManager extends EventEmitter {
2615
2622
  try {
2616
2623
  const response = await fetch(`${this.baseURL}${JWT_EXCHANGE_ENDPOINT}`, {
2617
2624
  method: 'POST',
2618
- headers: {
2619
- Authorization: `Bearer ${jwt}`,
2620
- 'Content-Type': 'application/json',
2621
- },
2625
+ headers: Object.assign({ Authorization: `Bearer ${jwt}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
2622
2626
  body: JSON.stringify({ gameId: this.config.gameId }),
2623
2627
  });
2624
2628
  if (!response.ok) {
@@ -2968,9 +2972,7 @@ class AuthManager extends EventEmitter {
2968
2972
  this.logger.debug('Refreshing access token');
2969
2973
  const response = await fetch(`${this.baseURL}${TOKEN_REFRESH_ENDPOINT}`, {
2970
2974
  method: 'POST',
2971
- headers: {
2972
- 'Content-Type': 'application/json',
2973
- },
2975
+ headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
2974
2976
  body: JSON.stringify({
2975
2977
  refresh_token: this.authState.refreshToken,
2976
2978
  }),
@@ -3073,7 +3075,7 @@ const translations = {
3073
3075
  * RechargeManager handles the recharge modal UI and recharge window opening
3074
3076
  */
3075
3077
  class RechargeManager extends EventEmitter {
3076
- constructor(playerToken, rechargePortalUrl = 'https://playkit.ai/recharge', gameId) {
3078
+ constructor(playerToken, rechargePortalUrl = 'https://players.playkit.ai/recharge', gameId) {
3077
3079
  super();
3078
3080
  this.modalContainer = null;
3079
3081
  this.styleElement = null;
@@ -3176,220 +3178,220 @@ class RechargeManager extends EventEmitter {
3176
3178
  return;
3177
3179
  }
3178
3180
  this.styleElement = document.createElement('style');
3179
- this.styleElement.textContent = `
3180
- .playkit-recharge-overlay {
3181
- position: fixed;
3182
- top: 0;
3183
- left: 0;
3184
- right: 0;
3185
- bottom: 0;
3186
- background: rgba(0, 0, 0, 0.8);
3187
- display: flex;
3188
- justify-content: center;
3189
- align-items: center;
3190
- z-index: 999999;
3191
- animation: playkit-recharge-fadeIn 0.2s ease-out;
3192
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3193
- }
3194
-
3195
- @keyframes playkit-recharge-fadeIn {
3196
- from {
3197
- opacity: 0;
3198
- }
3199
- to {
3200
- opacity: 1;
3201
- }
3202
- }
3203
-
3204
- .playkit-recharge-modal {
3205
- background: #fff;
3206
- border: 1px solid rgba(0, 0, 0, 0.1);
3207
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
3208
- padding: 24px;
3209
- max-width: 320px;
3210
- width: 90%;
3211
- position: relative;
3212
- text-align: center;
3213
- }
3214
-
3215
- .playkit-recharge-title {
3216
- font-size: 14px;
3217
- font-weight: 600;
3218
- color: #171717;
3219
- margin: 0 0 8px 0;
3220
- text-align: center;
3221
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3222
- }
3223
-
3224
- .playkit-recharge-message {
3225
- font-size: 14px;
3226
- color: #666;
3227
- margin: 0 0 20px 0;
3228
- text-align: center;
3229
- line-height: 1.5;
3230
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3231
- }
3232
-
3233
- .playkit-recharge-balance {
3234
- background: #f5f5f5;
3235
- border: 1px solid #e5e7eb;
3236
- padding: 16px;
3237
- margin: 0 0 20px 0;
3238
- text-align: center;
3239
- }
3240
-
3241
- .playkit-recharge-balance-label {
3242
- font-size: 12px;
3243
- color: #666;
3244
- margin: 0 0 8px 0;
3245
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3246
- }
3247
-
3248
- .playkit-recharge-balance-value {
3249
- font-size: 24px;
3250
- font-weight: bold;
3251
- color: #171717;
3252
- margin: 0;
3253
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3254
- }
3255
-
3256
- .playkit-recharge-balance-unit {
3257
- font-size: 14px;
3258
- color: #666;
3259
- margin-left: 4px;
3260
- }
3261
-
3262
- .playkit-recharge-buttons {
3263
- display: flex;
3264
- flex-direction: column;
3265
- gap: 8px;
3266
- }
3267
-
3268
- .playkit-recharge-button {
3269
- width: 100%;
3270
- padding: 10px 16px;
3271
- border: none;
3272
- font-size: 14px;
3273
- font-weight: 500;
3274
- cursor: pointer;
3275
- transition: all 0.2s ease;
3276
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3277
- }
3278
-
3279
- .playkit-recharge-button-primary {
3280
- background: #171717;
3281
- color: white;
3282
- }
3283
-
3284
- .playkit-recharge-button-primary:hover {
3285
- background: #404040;
3286
- }
3287
-
3288
- .playkit-recharge-button-primary:active {
3289
- background: #0a0a0a;
3290
- }
3291
-
3292
- .playkit-recharge-button-secondary {
3293
- background: transparent;
3294
- color: #666;
3295
- border: 1px solid #e5e7eb;
3296
- }
3297
-
3298
- .playkit-recharge-button-secondary:hover {
3299
- background: #f5f5f5;
3300
- border-color: #d4d4d4;
3301
- }
3302
-
3303
- .playkit-recharge-button-secondary:active {
3304
- background: #e5e5e5;
3305
- }
3306
-
3307
- @media (max-width: 480px) {
3308
- .playkit-recharge-modal {
3309
- padding: 20px;
3310
- }
3311
- }
3312
-
3313
- /* Daily Refresh Toast Styles */
3314
- .playkit-daily-refresh-toast {
3315
- position: fixed;
3316
- top: 20px;
3317
- right: 20px;
3318
- background: #fff;
3319
- border: 1px solid rgba(0, 0, 0, 0.1);
3320
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.1);
3321
- padding: 16px 20px;
3322
- min-width: 240px;
3323
- max-width: 320px;
3324
- z-index: 999998;
3325
- animation: playkit-toast-slideIn 0.3s ease-out;
3326
- display: flex;
3327
- align-items: flex-start;
3328
- gap: 12px;
3329
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3330
- }
3331
-
3332
- .playkit-daily-refresh-toast.hiding {
3333
- animation: playkit-toast-fadeOut 0.3s ease-out forwards;
3334
- }
3335
-
3336
- @keyframes playkit-toast-slideIn {
3337
- from {
3338
- transform: translateX(100%);
3339
- opacity: 0;
3340
- }
3341
- to {
3342
- transform: translateX(0);
3343
- opacity: 1;
3344
- }
3345
- }
3346
-
3347
- @keyframes playkit-toast-fadeOut {
3348
- from {
3349
- transform: translateX(0);
3350
- opacity: 1;
3351
- }
3352
- to {
3353
- transform: translateX(100%);
3354
- opacity: 0;
3355
- }
3356
- }
3357
-
3358
- .playkit-toast-icon {
3359
- width: 24px;
3360
- height: 24px;
3361
- background: #171717;
3362
- border-radius: 50%;
3363
- display: flex;
3364
- align-items: center;
3365
- justify-content: center;
3366
- flex-shrink: 0;
3367
- }
3368
-
3369
- .playkit-toast-icon svg {
3370
- width: 14px;
3371
- height: 14px;
3372
- color: #ffffff;
3373
- }
3374
-
3375
- .playkit-toast-message {
3376
- flex: 1;
3377
- font-size: 14px;
3378
- font-weight: 500;
3379
- color: #171717;
3380
- line-height: 1.4;
3381
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3382
- }
3383
-
3384
- @media (max-width: 480px) {
3385
- .playkit-daily-refresh-toast {
3386
- top: 10px;
3387
- right: 10px;
3388
- left: 10px;
3389
- min-width: auto;
3390
- max-width: none;
3391
- }
3392
- }
3181
+ this.styleElement.textContent = `
3182
+ .playkit-recharge-overlay {
3183
+ position: fixed;
3184
+ top: 0;
3185
+ left: 0;
3186
+ right: 0;
3187
+ bottom: 0;
3188
+ background: rgba(0, 0, 0, 0.8);
3189
+ display: flex;
3190
+ justify-content: center;
3191
+ align-items: center;
3192
+ z-index: 999999;
3193
+ animation: playkit-recharge-fadeIn 0.2s ease-out;
3194
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3195
+ }
3196
+
3197
+ @keyframes playkit-recharge-fadeIn {
3198
+ from {
3199
+ opacity: 0;
3200
+ }
3201
+ to {
3202
+ opacity: 1;
3203
+ }
3204
+ }
3205
+
3206
+ .playkit-recharge-modal {
3207
+ background: #fff;
3208
+ border: 1px solid rgba(0, 0, 0, 0.1);
3209
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
3210
+ padding: 24px;
3211
+ max-width: 320px;
3212
+ width: 90%;
3213
+ position: relative;
3214
+ text-align: center;
3215
+ }
3216
+
3217
+ .playkit-recharge-title {
3218
+ font-size: 14px;
3219
+ font-weight: 600;
3220
+ color: #171717;
3221
+ margin: 0 0 8px 0;
3222
+ text-align: center;
3223
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3224
+ }
3225
+
3226
+ .playkit-recharge-message {
3227
+ font-size: 14px;
3228
+ color: #666;
3229
+ margin: 0 0 20px 0;
3230
+ text-align: center;
3231
+ line-height: 1.5;
3232
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3233
+ }
3234
+
3235
+ .playkit-recharge-balance {
3236
+ background: #f5f5f5;
3237
+ border: 1px solid #e5e7eb;
3238
+ padding: 16px;
3239
+ margin: 0 0 20px 0;
3240
+ text-align: center;
3241
+ }
3242
+
3243
+ .playkit-recharge-balance-label {
3244
+ font-size: 12px;
3245
+ color: #666;
3246
+ margin: 0 0 8px 0;
3247
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3248
+ }
3249
+
3250
+ .playkit-recharge-balance-value {
3251
+ font-size: 24px;
3252
+ font-weight: bold;
3253
+ color: #171717;
3254
+ margin: 0;
3255
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3256
+ }
3257
+
3258
+ .playkit-recharge-balance-unit {
3259
+ font-size: 14px;
3260
+ color: #666;
3261
+ margin-left: 4px;
3262
+ }
3263
+
3264
+ .playkit-recharge-buttons {
3265
+ display: flex;
3266
+ flex-direction: column;
3267
+ gap: 8px;
3268
+ }
3269
+
3270
+ .playkit-recharge-button {
3271
+ width: 100%;
3272
+ padding: 10px 16px;
3273
+ border: none;
3274
+ font-size: 14px;
3275
+ font-weight: 500;
3276
+ cursor: pointer;
3277
+ transition: all 0.2s ease;
3278
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3279
+ }
3280
+
3281
+ .playkit-recharge-button-primary {
3282
+ background: #171717;
3283
+ color: white;
3284
+ }
3285
+
3286
+ .playkit-recharge-button-primary:hover {
3287
+ background: #404040;
3288
+ }
3289
+
3290
+ .playkit-recharge-button-primary:active {
3291
+ background: #0a0a0a;
3292
+ }
3293
+
3294
+ .playkit-recharge-button-secondary {
3295
+ background: transparent;
3296
+ color: #666;
3297
+ border: 1px solid #e5e7eb;
3298
+ }
3299
+
3300
+ .playkit-recharge-button-secondary:hover {
3301
+ background: #f5f5f5;
3302
+ border-color: #d4d4d4;
3303
+ }
3304
+
3305
+ .playkit-recharge-button-secondary:active {
3306
+ background: #e5e5e5;
3307
+ }
3308
+
3309
+ @media (max-width: 480px) {
3310
+ .playkit-recharge-modal {
3311
+ padding: 20px;
3312
+ }
3313
+ }
3314
+
3315
+ /* Daily Refresh Toast Styles */
3316
+ .playkit-daily-refresh-toast {
3317
+ position: fixed;
3318
+ top: 20px;
3319
+ right: 20px;
3320
+ background: #fff;
3321
+ border: 1px solid rgba(0, 0, 0, 0.1);
3322
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.1);
3323
+ padding: 16px 20px;
3324
+ min-width: 240px;
3325
+ max-width: 320px;
3326
+ z-index: 999998;
3327
+ animation: playkit-toast-slideIn 0.3s ease-out;
3328
+ display: flex;
3329
+ align-items: flex-start;
3330
+ gap: 12px;
3331
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3332
+ }
3333
+
3334
+ .playkit-daily-refresh-toast.hiding {
3335
+ animation: playkit-toast-fadeOut 0.3s ease-out forwards;
3336
+ }
3337
+
3338
+ @keyframes playkit-toast-slideIn {
3339
+ from {
3340
+ transform: translateX(100%);
3341
+ opacity: 0;
3342
+ }
3343
+ to {
3344
+ transform: translateX(0);
3345
+ opacity: 1;
3346
+ }
3347
+ }
3348
+
3349
+ @keyframes playkit-toast-fadeOut {
3350
+ from {
3351
+ transform: translateX(0);
3352
+ opacity: 1;
3353
+ }
3354
+ to {
3355
+ transform: translateX(100%);
3356
+ opacity: 0;
3357
+ }
3358
+ }
3359
+
3360
+ .playkit-toast-icon {
3361
+ width: 24px;
3362
+ height: 24px;
3363
+ background: #171717;
3364
+ border-radius: 50%;
3365
+ display: flex;
3366
+ align-items: center;
3367
+ justify-content: center;
3368
+ flex-shrink: 0;
3369
+ }
3370
+
3371
+ .playkit-toast-icon svg {
3372
+ width: 14px;
3373
+ height: 14px;
3374
+ color: #ffffff;
3375
+ }
3376
+
3377
+ .playkit-toast-message {
3378
+ flex: 1;
3379
+ font-size: 14px;
3380
+ font-weight: 500;
3381
+ color: #171717;
3382
+ line-height: 1.4;
3383
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3384
+ }
3385
+
3386
+ @media (max-width: 480px) {
3387
+ .playkit-daily-refresh-toast {
3388
+ top: 10px;
3389
+ right: 10px;
3390
+ left: 10px;
3391
+ min-width: auto;
3392
+ max-width: none;
3393
+ }
3394
+ }
3393
3395
  `;
3394
3396
  document.head.appendChild(this.styleElement);
3395
3397
  }
@@ -3526,7 +3528,8 @@ class RechargeManager extends EventEmitter {
3526
3528
  /**
3527
3529
  * Player client for managing player information and credits
3528
3530
  */
3529
- const DEFAULT_BASE_URL$4 = 'https://playkit.ai';
3531
+ // @ts-ignore - replaced at build time
3532
+ const DEFAULT_BASE_URL$5 = "https://api.playkit.ai";
3530
3533
  const PLAYER_INFO_ENDPOINT = '/api/external/player-info';
3531
3534
  const SET_NICKNAME_ENDPOINT = '/api/external/set-game-player-nickname';
3532
3535
  class PlayerClient extends EventEmitter {
@@ -3538,13 +3541,13 @@ class PlayerClient extends EventEmitter {
3538
3541
  this.balanceCheckInterval = null;
3539
3542
  this.logger = Logger.getLogger('PlayerClient');
3540
3543
  this.authManager = authManager;
3541
- this.baseURL = config.baseURL || DEFAULT_BASE_URL$4;
3544
+ this.baseURL = config.baseURL || DEFAULT_BASE_URL$5;
3542
3545
  this.gameId = config.gameId;
3543
3546
  this.rechargeConfig = {
3544
3547
  autoShowBalanceModal: (_a = rechargeConfig.autoShowBalanceModal) !== null && _a !== void 0 ? _a : true,
3545
3548
  balanceCheckInterval: (_b = rechargeConfig.balanceCheckInterval) !== null && _b !== void 0 ? _b : 30000,
3546
3549
  checkBalanceAfterApiCall: (_c = rechargeConfig.checkBalanceAfterApiCall) !== null && _c !== void 0 ? _c : true,
3547
- rechargePortalUrl: rechargeConfig.rechargePortalUrl || 'https://playkit.ai/recharge',
3550
+ rechargePortalUrl: rechargeConfig.rechargePortalUrl || 'https://players.playkit.ai/recharge',
3548
3551
  showDailyRefreshToast: (_d = rechargeConfig.showDailyRefreshToast) !== null && _d !== void 0 ? _d : true,
3549
3552
  };
3550
3553
  }
@@ -3559,9 +3562,7 @@ class PlayerClient extends EventEmitter {
3559
3562
  }
3560
3563
  try {
3561
3564
  // Build headers with X-Game-Id to support Global Developer Token
3562
- const headers = {
3563
- Authorization: `Bearer ${token}`,
3564
- };
3565
+ const headers = Object.assign({ Authorization: `Bearer ${token}` }, getSDKHeaders());
3565
3566
  if (this.gameId) {
3566
3567
  headers['X-Game-Id'] = this.gameId;
3567
3568
  }
@@ -3661,10 +3662,7 @@ class PlayerClient extends EventEmitter {
3661
3662
  try {
3662
3663
  const response = await fetch(`${this.baseURL}${SET_NICKNAME_ENDPOINT}`, {
3663
3664
  method: 'POST',
3664
- headers: {
3665
- Authorization: `Bearer ${token}`,
3666
- 'Content-Type': 'application/json',
3667
- },
3665
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
3668
3666
  body: JSON.stringify({ nickname: trimmed }),
3669
3667
  });
3670
3668
  if (!response.ok) {
@@ -3814,15 +3812,89 @@ class PlayerClient extends EventEmitter {
3814
3812
  }
3815
3813
  }
3816
3814
 
3815
+ const VALID_PART_TYPES = new Set([
3816
+ 'text',
3817
+ 'image',
3818
+ 'image_url',
3819
+ 'file',
3820
+ 'audio',
3821
+ 'input_audio',
3822
+ ]);
3823
+ function describePart(part) {
3824
+ if (part === null)
3825
+ return 'null';
3826
+ if (typeof part !== 'object')
3827
+ return typeof part;
3828
+ const keys = Object.keys(part).slice(0, 5).join(',');
3829
+ return `{${keys}}`;
3830
+ }
3831
+ /**
3832
+ * Validate that `messages` matches the SDK's `Message[]` runtime contract before
3833
+ * shipping to the chat API. Throws `PlayKitError('INVALID_MESSAGES')` when a
3834
+ * caller has wrapped a Message[] inside one user message's `content` (the
3835
+ * `[{role:'user', content: [{role,...}, ...]}]` anti-pattern that bypasses the
3836
+ * `MessageContentPart` type at runtime).
3837
+ *
3838
+ * Does NOT auto-flatten — silently guessing system/user roles would mask bugs.
3839
+ */
3840
+ function assertValidMessages(messages) {
3841
+ if (!Array.isArray(messages)) {
3842
+ throw new PlayKitError('messages must be an array of Message', 'INVALID_MESSAGES');
3843
+ }
3844
+ for (let i = 0; i < messages.length; i++) {
3845
+ const msg = messages[i];
3846
+ if (!msg || typeof msg !== 'object') {
3847
+ throw new PlayKitError(`messages[${i}] must be an object with {role, content}`, 'INVALID_MESSAGES');
3848
+ }
3849
+ const content = msg.content;
3850
+ if (typeof content === 'string' || content == null)
3851
+ continue;
3852
+ if (!Array.isArray(content)) {
3853
+ throw new PlayKitError(`messages[${i}].content must be a string or an array of content parts (got ${typeof content})`, 'INVALID_MESSAGES');
3854
+ }
3855
+ for (let j = 0; j < content.length; j++) {
3856
+ const part = content[j];
3857
+ if (!part || typeof part !== 'object') {
3858
+ throw new PlayKitError(`messages[${i}].content[${j}] must be a content part object (got ${typeof part})`, 'INVALID_MESSAGES');
3859
+ }
3860
+ const hasType = typeof part.type === 'string' && VALID_PART_TYPES.has(part.type);
3861
+ if (!hasType) {
3862
+ if ('role' in part && 'content' in part) {
3863
+ throw new PlayKitError(`messages[${i}].content[${j}] is shaped like a Message (has role/content) ` +
3864
+ `but content parts must be {type:'text'|'image'|'image_url'|'file'|'audio'|'input_audio',...}. ` +
3865
+ `Did you mean to pass that array as messages directly? ` +
3866
+ `e.g. \`messages: theArray\` instead of \`messages: [{role:'user', content: theArray}]\`. ` +
3867
+ `Got part ${describePart(part)}`, 'INVALID_MESSAGES');
3868
+ }
3869
+ throw new PlayKitError(`messages[${i}].content[${j}] is missing a recognized 'type' field ` +
3870
+ `(expected one of text|image|image_url|file|audio|input_audio). Got part ${describePart(part)}`, 'INVALID_MESSAGES');
3871
+ }
3872
+ }
3873
+ }
3874
+ }
3875
+
3817
3876
  /**
3818
3877
  * Chat provider for HTTP communication with chat API
3819
3878
  */
3820
- const DEFAULT_BASE_URL$3 = 'https://playkit.ai';
3879
+ /**
3880
+ * Helper to extract string from MessageContent
3881
+ */
3882
+ function contentToString$1(content) {
3883
+ if (!content)
3884
+ return '';
3885
+ if (typeof content === 'string')
3886
+ return content;
3887
+ // For array of content parts, extract text parts
3888
+ const textParts = content.filter(part => part.type === 'text');
3889
+ return textParts.map(part => part.text).join('');
3890
+ }
3891
+ // @ts-ignore - replaced at build time
3892
+ const DEFAULT_BASE_URL$4 = "https://api.playkit.ai";
3821
3893
  class ChatProvider {
3822
3894
  constructor(authManager, config) {
3823
3895
  this.authManager = authManager;
3824
3896
  this.config = config;
3825
- this.baseURL = config.baseURL || DEFAULT_BASE_URL$3;
3897
+ this.baseURL = config.baseURL || DEFAULT_BASE_URL$4;
3826
3898
  }
3827
3899
  /**
3828
3900
  * Set player client for balance checking
@@ -3835,6 +3907,7 @@ class ChatProvider {
3835
3907
  */
3836
3908
  async chatCompletion(chatConfig) {
3837
3909
  var _a;
3910
+ assertValidMessages(chatConfig.messages);
3838
3911
  // Ensure token is valid, auto-refresh if needed (browser mode only)
3839
3912
  await this.authManager.ensureValidToken();
3840
3913
  const token = this.authManager.getToken();
@@ -3856,10 +3929,7 @@ class ChatProvider {
3856
3929
  try {
3857
3930
  const response = await fetch(`${this.baseURL}${endpoint}`, {
3858
3931
  method: 'POST',
3859
- headers: {
3860
- Authorization: `Bearer ${token}`,
3861
- 'Content-Type': 'application/json',
3862
- },
3932
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
3863
3933
  body: JSON.stringify(requestBody),
3864
3934
  });
3865
3935
  if (!response.ok) {
@@ -3894,6 +3964,7 @@ class ChatProvider {
3894
3964
  */
3895
3965
  async chatCompletionStream(chatConfig) {
3896
3966
  var _a;
3967
+ assertValidMessages(chatConfig.messages);
3897
3968
  // Ensure token is valid, auto-refresh if needed (browser mode only)
3898
3969
  await this.authManager.ensureValidToken();
3899
3970
  const token = this.authManager.getToken();
@@ -3915,10 +3986,7 @@ class ChatProvider {
3915
3986
  try {
3916
3987
  const response = await fetch(`${this.baseURL}${endpoint}`, {
3917
3988
  method: 'POST',
3918
- headers: {
3919
- Authorization: `Bearer ${token}`,
3920
- 'Content-Type': 'application/json',
3921
- },
3989
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
3922
3990
  body: JSON.stringify(requestBody),
3923
3991
  });
3924
3992
  if (!response.ok) {
@@ -3955,6 +4023,7 @@ class ChatProvider {
3955
4023
  */
3956
4024
  async chatCompletionWithTools(chatConfig) {
3957
4025
  var _a, _b;
4026
+ assertValidMessages(chatConfig.messages);
3958
4027
  const token = this.authManager.getToken();
3959
4028
  if (!token) {
3960
4029
  throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
@@ -3981,10 +4050,7 @@ class ChatProvider {
3981
4050
  try {
3982
4051
  const response = await fetch(`${this.baseURL}${endpoint}`, {
3983
4052
  method: 'POST',
3984
- headers: {
3985
- Authorization: `Bearer ${token}`,
3986
- 'Content-Type': 'application/json',
3987
- },
4053
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
3988
4054
  body: JSON.stringify(requestBody),
3989
4055
  });
3990
4056
  if (!response.ok) {
@@ -4015,6 +4081,7 @@ class ChatProvider {
4015
4081
  */
4016
4082
  async chatCompletionWithToolsStream(chatConfig) {
4017
4083
  var _a, _b;
4084
+ assertValidMessages(chatConfig.messages);
4018
4085
  const token = this.authManager.getToken();
4019
4086
  if (!token) {
4020
4087
  throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
@@ -4041,10 +4108,7 @@ class ChatProvider {
4041
4108
  try {
4042
4109
  const response = await fetch(`${this.baseURL}${endpoint}`, {
4043
4110
  method: 'POST',
4044
- headers: {
4045
- Authorization: `Bearer ${token}`,
4046
- 'Content-Type': 'application/json',
4047
- },
4111
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
4048
4112
  body: JSON.stringify(requestBody),
4049
4113
  });
4050
4114
  if (!response.ok) {
@@ -4114,10 +4178,7 @@ class ChatProvider {
4114
4178
  try {
4115
4179
  const response = await fetch(`${this.baseURL}${endpoint}`, {
4116
4180
  method: 'POST',
4117
- headers: {
4118
- Authorization: `Bearer ${token}`,
4119
- 'Content-Type': 'application/json',
4120
- },
4181
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
4121
4182
  body: JSON.stringify(requestBody),
4122
4183
  });
4123
4184
  if (!response.ok) {
@@ -4135,11 +4196,12 @@ class ChatProvider {
4135
4196
  this.playerClient.checkBalanceAfterApiCall().catch(() => { });
4136
4197
  }
4137
4198
  // Parse the response content as JSON
4138
- const content = (_a = result.choices[0]) === null || _a === void 0 ? void 0 : _a.message.content;
4139
- if (!content) {
4199
+ const rawContent = (_a = result.choices[0]) === null || _a === void 0 ? void 0 : _a.message.content;
4200
+ if (!rawContent) {
4140
4201
  throw new PlayKitError('No content in response', 'NO_CONTENT');
4141
4202
  }
4142
4203
  try {
4204
+ const content = contentToString$1(rawContent);
4143
4205
  return JSON.parse(content);
4144
4206
  }
4145
4207
  catch (parseError) {
@@ -4158,12 +4220,13 @@ class ChatProvider {
4158
4220
  /**
4159
4221
  * Image generation provider for HTTP communication with image API
4160
4222
  */
4161
- const DEFAULT_BASE_URL$2 = 'https://playkit.ai';
4223
+ // @ts-ignore - replaced at build time
4224
+ const DEFAULT_BASE_URL$3 = "https://api.playkit.ai";
4162
4225
  class ImageProvider {
4163
4226
  constructor(authManager, config) {
4164
4227
  this.authManager = authManager;
4165
4228
  this.config = config;
4166
- this.baseURL = config.baseURL || DEFAULT_BASE_URL$2;
4229
+ this.baseURL = config.baseURL || DEFAULT_BASE_URL$3;
4167
4230
  }
4168
4231
  /**
4169
4232
  * Set player client for balance checking
@@ -4211,10 +4274,7 @@ class ImageProvider {
4211
4274
  try {
4212
4275
  const response = await fetch(`${this.baseURL}${endpoint}`, {
4213
4276
  method: 'POST',
4214
- headers: {
4215
- Authorization: `Bearer ${token}`,
4216
- 'Content-Type': 'application/json',
4217
- },
4277
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
4218
4278
  body: JSON.stringify(requestBody),
4219
4279
  });
4220
4280
  if (!response.ok) {
@@ -4249,12 +4309,13 @@ class ImageProvider {
4249
4309
  /**
4250
4310
  * Transcription provider for HTTP communication with audio transcription API
4251
4311
  */
4252
- const DEFAULT_BASE_URL$1 = 'https://playkit.ai';
4312
+ // @ts-ignore - replaced at build time
4313
+ const DEFAULT_BASE_URL$2 = "https://api.playkit.ai";
4253
4314
  class TranscriptionProvider {
4254
4315
  constructor(authManager, config) {
4255
4316
  this.authManager = authManager;
4256
4317
  this.config = config;
4257
- this.baseURL = config.baseURL || DEFAULT_BASE_URL$1;
4318
+ this.baseURL = config.baseURL || DEFAULT_BASE_URL$2;
4258
4319
  }
4259
4320
  /**
4260
4321
  * Set player client for balance checking
@@ -4314,10 +4375,7 @@ class TranscriptionProvider {
4314
4375
  try {
4315
4376
  const response = await fetch(`${this.baseURL}${endpoint}`, {
4316
4377
  method: 'POST',
4317
- headers: {
4318
- Authorization: `Bearer ${token}`,
4319
- 'Content-Type': 'application/json',
4320
- },
4378
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
4321
4379
  body: JSON.stringify(requestBody),
4322
4380
  });
4323
4381
  if (!response.ok) {
@@ -4351,48 +4409,158 @@ class TranscriptionProvider {
4351
4409
  }
4352
4410
  }
4353
4411
 
4354
- /******************************************************************************
4355
- Copyright (c) Microsoft Corporation.
4356
-
4357
- Permission to use, copy, modify, and/or distribute this software for any
4358
- purpose with or without fee is hereby granted.
4359
-
4360
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
4361
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
4362
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
4363
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
4364
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
4365
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
4366
- PERFORMANCE OF THIS SOFTWARE.
4367
- ***************************************************************************** */
4368
- /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
4369
-
4370
-
4371
- function __values(o) {
4372
- var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
4373
- if (m) return m.call(o);
4374
- if (o && typeof o.length === "number") return {
4375
- next: function () {
4376
- if (o && i >= o.length) o = void 0;
4377
- return { value: o && o[i++], done: !o };
4378
- }
4379
- };
4380
- throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
4381
- }
4382
-
4383
- function __await(v) {
4384
- return this instanceof __await ? (this.v = v, this) : new __await(v);
4385
- }
4386
-
4387
- function __asyncGenerator(thisArg, _arguments, generator) {
4388
- if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
4389
- var g = generator.apply(thisArg, _arguments || []), i, q = [];
4390
- return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
4391
- function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
4392
- function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
4393
- function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
4394
- function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
4395
- function fulfill(value) { resume("next", value); }
4412
+ /**
4413
+ * TTS provider for HTTP communication with the text-to-speech API
4414
+ */
4415
+ // @ts-ignore - replaced at build time
4416
+ const DEFAULT_BASE_URL$1 = "https://api.playkit.ai";
4417
+ class TTSProvider {
4418
+ constructor(authManager, config) {
4419
+ this.authManager = authManager;
4420
+ this.config = config;
4421
+ this.baseURL = config.baseURL || DEFAULT_BASE_URL$1;
4422
+ }
4423
+ /**
4424
+ * Set player client for balance checking
4425
+ */
4426
+ setPlayerClient(playerClient) {
4427
+ this.playerClient = playerClient;
4428
+ }
4429
+ /**
4430
+ * Synthesize text into speech audio
4431
+ */
4432
+ async synthesize(ttsConfig) {
4433
+ // Ensure token is valid, auto-refresh if needed (browser mode only)
4434
+ await this.authManager.ensureValidToken();
4435
+ const token = this.authManager.getToken();
4436
+ if (!token) {
4437
+ throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
4438
+ }
4439
+ const model = ttsConfig.model || this.config.defaultTTSModel || 'default-tts-model';
4440
+ const endpoint = `/ai/${this.config.gameId}/v2/audio/speech`;
4441
+ const requestBody = {
4442
+ model,
4443
+ text: ttsConfig.text,
4444
+ };
4445
+ // Add optional parameters (only when defined)
4446
+ if (ttsConfig.voice !== undefined) {
4447
+ requestBody.voice = ttsConfig.voice;
4448
+ }
4449
+ if (ttsConfig.speed !== undefined) {
4450
+ requestBody.speed = ttsConfig.speed;
4451
+ }
4452
+ if (ttsConfig.vol !== undefined) {
4453
+ requestBody.vol = ttsConfig.vol;
4454
+ }
4455
+ if (ttsConfig.pitch !== undefined) {
4456
+ requestBody.pitch = ttsConfig.pitch;
4457
+ }
4458
+ if (ttsConfig.emotion !== undefined) {
4459
+ requestBody.emotion = ttsConfig.emotion;
4460
+ }
4461
+ if (ttsConfig.languageBoost !== undefined) {
4462
+ requestBody.language_boost = ttsConfig.languageBoost;
4463
+ }
4464
+ if (ttsConfig.format !== undefined) {
4465
+ requestBody.response_format = ttsConfig.format;
4466
+ }
4467
+ if (ttsConfig.voiceSetting !== undefined) {
4468
+ requestBody.voice_setting = ttsConfig.voiceSetting;
4469
+ }
4470
+ if (ttsConfig.audioSetting !== undefined) {
4471
+ requestBody.audio_setting = ttsConfig.audioSetting;
4472
+ }
4473
+ try {
4474
+ const response = await fetch(`${this.baseURL}${endpoint}`, {
4475
+ method: 'POST',
4476
+ headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
4477
+ body: JSON.stringify(requestBody),
4478
+ });
4479
+ if (!response.ok) {
4480
+ const error = await response.json().catch(() => ({ message: 'Speech synthesis failed' }));
4481
+ const playKitError = new PlayKitError(error.message || 'Speech synthesis failed', error.code, response.status);
4482
+ // Check for insufficient credits error
4483
+ if (error.code === 'INSUFFICIENT_CREDITS' ||
4484
+ error.code === 'PLAYER_INSUFFICIENT_CREDIT' ||
4485
+ response.status === 402) {
4486
+ if (this.playerClient) {
4487
+ await this.playerClient.handleInsufficientCredits(playKitError);
4488
+ }
4489
+ }
4490
+ throw playKitError;
4491
+ }
4492
+ // SUCCESS: response is raw audio bytes, NOT JSON.
4493
+ const audio = await response.arrayBuffer();
4494
+ const contentType = response.headers.get('Content-Type');
4495
+ const usageHeader = response.headers.get('X-Usage-Characters');
4496
+ const audioLengthHeader = response.headers.get('X-Audio-Length-Ms');
4497
+ const result = {
4498
+ audio,
4499
+ format: contentType || ttsConfig.format || 'mp3',
4500
+ usageCharacters: Number(usageHeader) || 0,
4501
+ };
4502
+ if (audioLengthHeader !== null) {
4503
+ result.audioLengthMs = Number(audioLengthHeader) || 0;
4504
+ }
4505
+ // Check balance after successful API call
4506
+ if (this.playerClient) {
4507
+ this.playerClient.checkBalanceAfterApiCall().catch(() => {
4508
+ // Silently fail
4509
+ });
4510
+ }
4511
+ return result;
4512
+ }
4513
+ catch (error) {
4514
+ if (error instanceof PlayKitError) {
4515
+ throw error;
4516
+ }
4517
+ throw new PlayKitError(error instanceof Error ? error.message : 'Unknown error', 'TTS_ERROR');
4518
+ }
4519
+ }
4520
+ }
4521
+
4522
+ /******************************************************************************
4523
+ Copyright (c) Microsoft Corporation.
4524
+
4525
+ Permission to use, copy, modify, and/or distribute this software for any
4526
+ purpose with or without fee is hereby granted.
4527
+
4528
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
4529
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
4530
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
4531
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
4532
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
4533
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
4534
+ PERFORMANCE OF THIS SOFTWARE.
4535
+ ***************************************************************************** */
4536
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
4537
+
4538
+
4539
+ function __values(o) {
4540
+ var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
4541
+ if (m) return m.call(o);
4542
+ if (o && typeof o.length === "number") return {
4543
+ next: function () {
4544
+ if (o && i >= o.length) o = void 0;
4545
+ return { value: o && o[i++], done: !o };
4546
+ }
4547
+ };
4548
+ throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
4549
+ }
4550
+
4551
+ function __await(v) {
4552
+ return this instanceof __await ? (this.v = v, this) : new __await(v);
4553
+ }
4554
+
4555
+ function __asyncGenerator(thisArg, _arguments, generator) {
4556
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
4557
+ var g = generator.apply(thisArg, _arguments || []), i, q = [];
4558
+ return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
4559
+ function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
4560
+ function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
4561
+ function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
4562
+ function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
4563
+ function fulfill(value) { resume("next", value); }
4396
4564
  function reject(value) { resume("throw", value); }
4397
4565
  function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
4398
4566
  }
@@ -4463,9 +4631,18 @@ class StreamParser {
4463
4631
  if (text) {
4464
4632
  yield yield __await(text);
4465
4633
  }
4466
- if (parsed.type === 'done' || parsed.finish_reason) {
4634
+ // Stream termination events
4635
+ if (parsed.type === 'done' || parsed.type === 'finish' || parsed.finish_reason) {
4636
+ return yield __await(void 0);
4637
+ }
4638
+ if (parsed.type === 'abort') {
4639
+ // Server-side timeout or cancellation — treat as end of stream
4467
4640
  return yield __await(void 0);
4468
4641
  }
4642
+ if (parsed.type === 'error') {
4643
+ // Server-side error event — throw to trigger onError callback
4644
+ throw new Error(parsed.errorText || parsed.error || 'Stream error');
4645
+ }
4469
4646
  }
4470
4647
  catch (error) {
4471
4648
  // If JSON parse fails, treat as plain text
@@ -4564,6 +4741,18 @@ class StreamParser {
4564
4741
  /**
4565
4742
  * Chat client for AI text generation
4566
4743
  */
4744
+ /**
4745
+ * Helper to extract string from MessageContent
4746
+ */
4747
+ function contentToString(content) {
4748
+ if (!content)
4749
+ return '';
4750
+ if (typeof content === 'string')
4751
+ return content;
4752
+ // For array of content parts, extract text parts
4753
+ const textParts = content.filter(part => part.type === 'text');
4754
+ return textParts.map(part => part.text).join('');
4755
+ }
4567
4756
  class ChatClient {
4568
4757
  constructor(provider, model) {
4569
4758
  this.schemaLibrary = null;
@@ -4613,7 +4802,7 @@ class ChatClient {
4613
4802
  throw new Error('No choices in response');
4614
4803
  }
4615
4804
  return {
4616
- content: choice.message.content,
4805
+ content: contentToString(choice.message.content),
4617
4806
  model: response.model,
4618
4807
  finishReason: choice.finish_reason,
4619
4808
  usage: response.usage
@@ -4684,9 +4873,10 @@ class ChatClient {
4684
4873
  }
4685
4874
  // Extract user message content from the last user message
4686
4875
  const lastUserMessage = [...messages].reverse().find(m => m.role === 'user');
4687
- const prompt = (lastUserMessage === null || lastUserMessage === void 0 ? void 0 : lastUserMessage.content) || '';
4876
+ const prompt = contentToString(lastUserMessage === null || lastUserMessage === void 0 ? void 0 : lastUserMessage.content);
4688
4877
  // Build system message from messages array
4689
- const systemMessage = (_a = messages.find(m => m.role === 'system')) === null || _a === void 0 ? void 0 : _a.content;
4878
+ const systemMessageContent = (_a = messages.find(m => m.role === 'system')) === null || _a === void 0 ? void 0 : _a.content;
4879
+ const systemMessage = contentToString(systemMessageContent) || undefined;
4690
4880
  return this.generateStructuredWithSchema(schemaEntry.schema, prompt, Object.assign({ schemaName, schemaDescription: schemaEntry.description, systemMessage }, options));
4691
4881
  }
4692
4882
  /**
@@ -4777,7 +4967,7 @@ class ChatClient {
4777
4967
  throw new Error('No choices in response');
4778
4968
  }
4779
4969
  return {
4780
- content: choice.message.content || '',
4970
+ content: contentToString(choice.message.content),
4781
4971
  model: response.model,
4782
4972
  finishReason: choice.finish_reason,
4783
4973
  usage: response.usage
@@ -4841,7 +5031,7 @@ class GeneratedImageImpl {
4841
5031
  return new Promise((resolve, reject) => {
4842
5032
  const img = new Image();
4843
5033
  img.onload = () => resolve(img);
4844
- img.onerror = (e) => reject(new Error('Failed to load image'));
5034
+ img.onerror = (_e) => reject(new Error('Failed to load image'));
4845
5035
  img.src = this.toDataURL();
4846
5036
  });
4847
5037
  }
@@ -4855,13 +5045,14 @@ class ImageClient {
4855
5045
  * Generate a single image
4856
5046
  */
4857
5047
  async generateImage(config) {
5048
+ var _a;
4858
5049
  const imageConfig = Object.assign(Object.assign({}, config), { model: config.model || this.model, n: 1 });
4859
5050
  const response = await this.provider.generateImages(imageConfig);
4860
5051
  const imageData = response.data[0];
4861
5052
  if (!imageData || !imageData.b64_json) {
4862
5053
  throw new Error('No image data in response');
4863
5054
  }
4864
- return new GeneratedImageImpl(imageData.b64_json, config.prompt, imageData.revised_prompt, config.size, imageData.b64_json_original, imageData.transparent_success);
5055
+ 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);
4865
5056
  }
4866
5057
  /**
4867
5058
  * Generate multiple images
@@ -4870,10 +5061,11 @@ class ImageClient {
4870
5061
  const imageConfig = Object.assign(Object.assign({}, config), { model: config.model || this.model, n: config.n || 1 });
4871
5062
  const response = await this.provider.generateImages(imageConfig);
4872
5063
  return response.data.map((imageData) => {
5064
+ var _a;
4873
5065
  if (!imageData.b64_json) {
4874
5066
  throw new Error('No image data in response');
4875
5067
  }
4876
- return new GeneratedImageImpl(imageData.b64_json, config.prompt, imageData.revised_prompt, config.size, imageData.b64_json_original, imageData.transparent_success);
5068
+ 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);
4877
5069
  });
4878
5070
  }
4879
5071
  /**
@@ -4992,1019 +5184,1191 @@ class TranscriptionClient {
4992
5184
  }
4993
5185
 
4994
5186
  /**
4995
- * NPC Client for simplified conversation management
4996
- * Automatically handles conversation history
4997
- *
4998
- * Key Features:
4999
- * - Call talk() for all interactions - actions are handled automatically
5000
- * - Memory system for persistent NPC context
5001
- * - Reply prediction for suggesting player responses
5002
- * - Automatic conversation history management
5187
+ * High-level client for text-to-speech synthesis
5003
5188
  */
5004
- class NPCClient extends EventEmitter {
5005
- constructor(chatClient, config) {
5006
- var _a, _b, _c;
5007
- super();
5008
- this._isTalking = false;
5009
- this.logger = Logger.getLogger('NPCClient');
5010
- this.chatClient = chatClient;
5011
- // Support both characterDesign and legacy systemPrompt
5012
- 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.';
5013
- this.temperature = (_a = config === null || config === void 0 ? void 0 : config.temperature) !== null && _a !== void 0 ? _a : 0.7;
5014
- this.maxHistoryLength = (config === null || config === void 0 ? void 0 : config.maxHistoryLength) || 50;
5015
- this.generateReplyPrediction = (_b = config === null || config === void 0 ? void 0 : config.generateReplyPrediction) !== null && _b !== void 0 ? _b : false;
5016
- this.predictionCount = Math.max(2, Math.min(6, (_c = config === null || config === void 0 ? void 0 : config.predictionCount) !== null && _c !== void 0 ? _c : 4));
5017
- this.fastModel = config === null || config === void 0 ? void 0 : config.fastModel;
5018
- this.history = [];
5019
- this.memories = new Map();
5189
+ /**
5190
+ * Resolve an audio format/content-type string into a valid MIME type.
5191
+ * The provider's `result.format` may be a full MIME (e.g. 'audio/mpeg' from
5192
+ * the Content-Type header) or a bare token (e.g. 'mp3' from the fallback).
5193
+ * Full MIME strings pass through; bare tokens are mapped.
5194
+ */
5195
+ function contentTypeFor(format) {
5196
+ if (format.includes('/')) {
5197
+ return format;
5198
+ }
5199
+ switch (format.toLowerCase()) {
5200
+ case 'mp3':
5201
+ return 'audio/mpeg';
5202
+ case 'wav':
5203
+ return 'audio/wav';
5204
+ case 'ogg':
5205
+ return 'audio/ogg';
5206
+ case 'flac':
5207
+ return 'audio/flac';
5208
+ case 'aac':
5209
+ return 'audio/aac';
5210
+ case 'pcm':
5211
+ return 'audio/pcm';
5212
+ default:
5213
+ return 'audio/mpeg';
5020
5214
  }
5021
- // ===== State Properties =====
5022
- /**
5023
- * Whether the NPC is currently processing a request
5024
- */
5025
- get isTalking() {
5026
- return this._isTalking;
5215
+ }
5216
+ class TTSClient {
5217
+ constructor(provider, model) {
5218
+ this.provider = provider;
5219
+ this.model = model || 'default-tts-model';
5027
5220
  }
5028
- // ===== Character Design & Memory System =====
5029
5221
  /**
5030
- * Set the character design for the NPC.
5031
- * The system prompt is composed of CharacterDesign + all Memories.
5222
+ * Get the current model name
5032
5223
  */
5033
- setCharacterDesign(design) {
5034
- this.characterDesign = design;
5224
+ get modelName() {
5225
+ return this.model;
5035
5226
  }
5036
5227
  /**
5037
- * Get the current character design
5228
+ * Synthesize text into speech audio
5229
+ * @param config - Full TTS configuration
5230
+ * @returns TTS result containing raw audio bytes and usage metadata
5038
5231
  */
5039
- getCharacterDesign() {
5040
- return this.characterDesign;
5232
+ async synthesize(config) {
5233
+ return this.provider.synthesize(Object.assign(Object.assign({}, config), { model: config.model || this.model }));
5041
5234
  }
5042
5235
  /**
5043
- * @deprecated Use setCharacterDesign instead.
5044
- * This method is kept for backwards compatibility.
5236
+ * Synthesize text into speech and return it as a Blob (browser-friendly)
5237
+ * @param config - Full TTS configuration
5238
+ * @returns Audio Blob with the appropriate MIME type
5045
5239
  */
5046
- setSystemPrompt(prompt) {
5047
- this.logger.warn('setSystemPrompt is deprecated. Use setCharacterDesign instead.');
5048
- this.setCharacterDesign(prompt);
5240
+ async synthesizeToBlob(config) {
5241
+ const result = await this.synthesize(config);
5242
+ return new Blob([result.audio], { type: contentTypeFor(result.format) });
5049
5243
  }
5050
5244
  /**
5051
- * @deprecated Use getCharacterDesign instead.
5052
- * This method is kept for backwards compatibility.
5245
+ * Synthesize text into speech and return an object URL (browser only)
5246
+ * @param config - Full TTS configuration
5247
+ * @returns An object URL that can be assigned to an <audio> element
5248
+ * @throws PlayKitError if URL.createObjectURL is unavailable (e.g. Node.js)
5053
5249
  */
5054
- getSystemPrompt() {
5055
- return this.buildSystemPrompt();
5250
+ async synthesizeToObjectURL(config) {
5251
+ if (typeof URL === 'undefined' || typeof URL.createObjectURL !== 'function') {
5252
+ throw new PlayKitError('URL.createObjectURL is not available in this environment. ' +
5253
+ 'Use synthesize() to access the raw audio bytes instead.', 'TTS_OBJECT_URL_UNAVAILABLE');
5254
+ }
5255
+ const blob = await this.synthesizeToBlob(config);
5256
+ return URL.createObjectURL(blob);
5257
+ }
5258
+ }
5259
+
5260
+ /**
5261
+ * Global AI Context Manager for managing NPC conversations and player context.
5262
+ *
5263
+ * Features:
5264
+ * - Player description management
5265
+ * - NPC conversation tracking
5266
+ * - Automatic conversation compaction (AutoCompact)
5267
+ */
5268
+ /**
5269
+ * Global AI Context Manager
5270
+ * Manages NPC conversations and player context across the application
5271
+ */
5272
+ class AIContextManager extends EventEmitter {
5273
+ constructor(config) {
5274
+ var _a, _b, _c, _d, _e;
5275
+ super();
5276
+ this.playerDescription = null;
5277
+ this.playerPrompt = null;
5278
+ this.playerMemories = new Map();
5279
+ this.npcStates = new Map();
5280
+ this.autoCompactTimer = null;
5281
+ this.chatClientFactory = null;
5282
+ this.logger = Logger.getLogger('AIContextManager');
5283
+ this.config = {
5284
+ enableAutoCompact: (_a = config === null || config === void 0 ? void 0 : config.enableAutoCompact) !== null && _a !== void 0 ? _a : false,
5285
+ autoCompactMinMessages: (_b = config === null || config === void 0 ? void 0 : config.autoCompactMinMessages) !== null && _b !== void 0 ? _b : 20,
5286
+ autoCompactTimeoutSeconds: (_c = config === null || config === void 0 ? void 0 : config.autoCompactTimeoutSeconds) !== null && _c !== void 0 ? _c : 300,
5287
+ autoCompactCheckInterval: (_d = config === null || config === void 0 ? void 0 : config.autoCompactCheckInterval) !== null && _d !== void 0 ? _d : 60000,
5288
+ fastModel: (_e = config === null || config === void 0 ? void 0 : config.fastModel) !== null && _e !== void 0 ? _e : '',
5289
+ };
5290
+ // Start auto-compact check if enabled
5291
+ if (this.config.enableAutoCompact) {
5292
+ this.startAutoCompactCheck();
5293
+ }
5056
5294
  }
5295
+ // ===== Singleton Pattern =====
5057
5296
  /**
5058
- * Set or update a memory for the NPC.
5059
- * Memories are appended to the character design to form the system prompt.
5060
- * Set memoryContent to null or empty to remove the memory.
5061
- * @param memoryName The name/key of the memory
5062
- * @param memoryContent The content of the memory. Null or empty to remove.
5297
+ * Get the singleton instance of AIContextManager
5298
+ * Creates a new instance if one doesn't exist
5063
5299
  */
5064
- setMemory(memoryName, memoryContent) {
5065
- if (!memoryName) {
5066
- this.logger.warn('Memory name cannot be empty');
5067
- return;
5068
- }
5069
- if (!memoryContent) {
5070
- // Remove memory if content is null or empty
5071
- if (this.memories.has(memoryName)) {
5072
- this.memories.delete(memoryName);
5073
- this.emit('memory_removed', memoryName);
5074
- }
5075
- }
5076
- else {
5077
- // Add or update memory
5078
- this.memories.set(memoryName, memoryContent);
5079
- this.emit('memory_set', memoryName, memoryContent);
5300
+ static getInstance(config) {
5301
+ if (!AIContextManager._instance) {
5302
+ AIContextManager._instance = new AIContextManager(config);
5080
5303
  }
5304
+ return AIContextManager._instance;
5081
5305
  }
5082
5306
  /**
5083
- * Get a specific memory by name.
5084
- * @param memoryName The name of the memory to retrieve
5085
- * @returns The memory content, or undefined if not found
5307
+ * Reset the singleton instance (useful for testing)
5086
5308
  */
5087
- getMemory(memoryName) {
5088
- return this.memories.get(memoryName);
5309
+ static resetInstance() {
5310
+ if (AIContextManager._instance) {
5311
+ AIContextManager._instance.destroy();
5312
+ AIContextManager._instance = null;
5313
+ }
5089
5314
  }
5315
+ // ===== Configuration =====
5090
5316
  /**
5091
- * Get all memory names currently stored.
5092
- * @returns Array of memory names
5317
+ * Set the chat client factory for creating chat clients for summarization
5318
+ * Required for compaction to work
5093
5319
  */
5094
- getMemoryNames() {
5095
- return Array.from(this.memories.keys());
5320
+ setChatClientFactory(factory) {
5321
+ this.chatClientFactory = factory;
5096
5322
  }
5097
5323
  /**
5098
- * Clear all memories (but keep character design).
5324
+ * Update configuration
5099
5325
  */
5100
- clearMemories() {
5101
- this.memories.clear();
5102
- this.emit('memories_cleared');
5326
+ setConfig(config) {
5327
+ const wasAutoCompactEnabled = this.config.enableAutoCompact;
5328
+ this.config = Object.assign(Object.assign({}, this.config), config);
5329
+ // Handle auto-compact state change
5330
+ if (config.enableAutoCompact !== undefined) {
5331
+ if (config.enableAutoCompact && !wasAutoCompactEnabled) {
5332
+ this.startAutoCompactCheck();
5333
+ }
5334
+ else if (!config.enableAutoCompact && wasAutoCompactEnabled) {
5335
+ this.stopAutoCompactCheck();
5336
+ }
5337
+ }
5103
5338
  }
5339
+ // ===== Player Description =====
5104
5340
  /**
5105
- * Build the complete system prompt from CharacterDesign + Memories.
5341
+ * Set the player's description for AI context.
5342
+ * Used when generating reply predictions and for NPC context.
5343
+ * @param description Description of the player character
5106
5344
  */
5107
- buildSystemPrompt() {
5108
- const parts = [];
5109
- if (this.characterDesign) {
5110
- parts.push(this.characterDesign);
5111
- }
5112
- if (this.memories.size > 0) {
5113
- const memoryStrings = Array.from(this.memories.entries())
5114
- .map(([name, content]) => `[${name}]: ${content}`);
5115
- parts.push('Memories:\n' + memoryStrings.join('\n'));
5116
- }
5117
- return parts.join('\n\n');
5345
+ setPlayerDescription(description) {
5346
+ this.playerDescription = description;
5347
+ this.emit('playerDescriptionChanged', description);
5118
5348
  }
5119
- // ===== Reply Prediction =====
5120
5349
  /**
5121
- * Enable or disable automatic reply prediction
5350
+ * Get the current player description.
5351
+ * @returns The player description, or null if not set
5122
5352
  */
5123
- setGenerateReplyPrediction(enabled) {
5124
- this.generateReplyPrediction = enabled;
5353
+ getPlayerDescription() {
5354
+ return this.playerDescription;
5125
5355
  }
5126
5356
  /**
5127
- * Set the number of predictions to generate
5357
+ * Clear the player description.
5128
5358
  */
5129
- setPredictionCount(count) {
5130
- this.predictionCount = Math.max(2, Math.min(6, count));
5359
+ clearPlayerDescription() {
5360
+ this.playerDescription = null;
5361
+ this.emit('playerDescriptionChanged', null);
5131
5362
  }
5363
+ // ===== Player Prompt & Memory (for Reply Prediction) =====
5132
5364
  /**
5133
- * Manually generate reply predictions based on current conversation.
5134
- * Uses the fast model for quick generation.
5135
- * @param count Number of predictions to generate (default: uses predictionCount property)
5136
- * @returns Array of predicted player replies, or empty array on failure
5365
+ * Set the player's character prompt/persona.
5366
+ * This defines how the player character speaks and behaves.
5367
+ * Used when generating reply predictions to match the player's tone.
5368
+ * @param prompt The player character's persona/prompt
5137
5369
  */
5138
- async generateReplyPredictions(count) {
5139
- var _a;
5140
- const predictionNum = count !== null && count !== void 0 ? count : this.predictionCount;
5141
- if (this.history.length < 2) {
5142
- this.logger.info('Not enough conversation history to generate predictions');
5143
- return [];
5370
+ setPlayerPrompt(prompt) {
5371
+ this.playerPrompt = prompt;
5372
+ }
5373
+ /**
5374
+ * Get the current player prompt.
5375
+ * @returns The player prompt, or null if not set
5376
+ */
5377
+ getPlayerPrompt() {
5378
+ return this.playerPrompt;
5379
+ }
5380
+ /**
5381
+ * Set or update a memory for the player character.
5382
+ * Memories are appended to the player prompt to form the full player context.
5383
+ * Set memoryContent to null or empty to remove the memory.
5384
+ * @param memoryName The name/key of the memory
5385
+ * @param memoryContent The content of the memory. Null or empty to remove.
5386
+ */
5387
+ setPlayerMemory(memoryName, memoryContent) {
5388
+ if (!memoryName) {
5389
+ this.logger.warn('Memory name cannot be empty');
5390
+ return;
5144
5391
  }
5145
- try {
5146
- // Get last NPC message
5147
- const lastNpcMessage = (_a = [...this.history]
5148
- .reverse()
5149
- .find(m => m.role === 'assistant')) === null || _a === void 0 ? void 0 : _a.content;
5150
- if (!lastNpcMessage) {
5151
- this.logger.info('No NPC message found to generate predictions from');
5152
- return [];
5153
- }
5154
- // Build recent history (last 6 non-system messages)
5155
- const recentHistory = this.history
5156
- .filter(m => m.role !== 'system')
5157
- .slice(-6)
5158
- .map(m => `${m.role}: ${m.content}`);
5159
- // Build prompt for prediction generation
5160
- const prompt = `Based on the conversation history below, generate exactly ${predictionNum} natural and contextually appropriate responses that the player might say next.
5161
-
5162
- Context:
5163
- - This is a conversation between a player and an NPC in a game
5164
- - The NPC just said: "${lastNpcMessage}"
5165
-
5166
- Conversation history:
5167
- ${recentHistory.join('\n')}
5168
-
5169
- Requirements:
5170
- 1. Each response should be 1-2 sentences maximum
5171
- 2. Responses should be diverse in tone and intent
5172
- 3. Include a mix of questions, statements, and action-oriented responses
5173
- 4. Responses should feel natural for a player character
5174
-
5175
- Output ONLY a JSON array of ${predictionNum} strings, nothing else:
5176
- ["response1", "response2", "response3", "response4"]`;
5177
- const result = await this.chatClient.textGeneration({
5178
- messages: [{ role: 'user', content: prompt }],
5179
- temperature: 0.8,
5180
- model: this.fastModel,
5181
- });
5182
- if (!result.content) {
5183
- this.logger.warn('Failed to generate predictions: empty response');
5184
- return [];
5185
- }
5186
- // Parse JSON response
5187
- const predictions = this.parsePredictionsFromJson(result.content, predictionNum);
5188
- if (predictions.length > 0) {
5189
- this.emit('replyPredictions', predictions);
5190
- }
5191
- return predictions;
5392
+ if (!memoryContent) {
5393
+ // Remove memory if content is null or empty
5394
+ this.playerMemories.delete(memoryName);
5192
5395
  }
5193
- catch (error) {
5194
- this.logger.error('Error generating predictions:', error);
5195
- return [];
5396
+ else {
5397
+ // Add or update memory
5398
+ this.playerMemories.set(memoryName, memoryContent);
5196
5399
  }
5197
5400
  }
5198
5401
  /**
5199
- * Parse predictions from JSON array response
5402
+ * Get a specific player memory by name.
5403
+ * @param memoryName The name of the memory to retrieve
5404
+ * @returns The memory content, or undefined if not found
5200
5405
  */
5201
- parsePredictionsFromJson(response, expectedCount) {
5202
- try {
5203
- // Try to find JSON array in response
5204
- const startIndex = response.indexOf('[');
5205
- const endIndex = response.lastIndexOf(']');
5206
- if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
5207
- this.logger.warn('Could not find JSON array in prediction response');
5208
- return this.extractPredictionsFromText(response, expectedCount);
5209
- }
5210
- const jsonArray = response.substring(startIndex, endIndex + 1);
5211
- const parsed = JSON.parse(jsonArray);
5212
- if (Array.isArray(parsed)) {
5213
- return parsed
5214
- .filter(item => typeof item === 'string' && item.trim())
5215
- .slice(0, expectedCount);
5216
- }
5217
- return [];
5406
+ getPlayerMemory(memoryName) {
5407
+ return this.playerMemories.get(memoryName);
5408
+ }
5409
+ /**
5410
+ * Get all player memory names currently stored.
5411
+ * @returns Array of memory names
5412
+ */
5413
+ getPlayerMemoryNames() {
5414
+ return Array.from(this.playerMemories.keys());
5415
+ }
5416
+ /**
5417
+ * Clear all player memories (but keep player prompt).
5418
+ */
5419
+ clearPlayerMemories() {
5420
+ this.playerMemories.clear();
5421
+ }
5422
+ /**
5423
+ * Build the complete player context from PlayerPrompt + PlayerMemories.
5424
+ * Used by NPCClient for generating reply predictions.
5425
+ * @returns The combined player context string, or null if no context is set
5426
+ */
5427
+ buildPlayerContext() {
5428
+ const parts = [];
5429
+ if (this.playerPrompt) {
5430
+ parts.push(this.playerPrompt);
5218
5431
  }
5219
- catch (error) {
5220
- this.logger.warn('Failed to parse predictions JSON:', error);
5221
- return this.extractPredictionsFromText(response, expectedCount);
5432
+ if (this.playerMemories.size > 0) {
5433
+ const memoryStrings = Array.from(this.playerMemories.entries())
5434
+ .map(([name, content]) => `[${name}]: ${content}`);
5435
+ parts.push('Player Memories:\n' + memoryStrings.join('\n'));
5436
+ }
5437
+ if (parts.length === 0) {
5438
+ return null;
5222
5439
  }
5440
+ return parts.join('\n\n');
5223
5441
  }
5442
+ // ===== NPC Tracking =====
5224
5443
  /**
5225
- * Fallback: Extract predictions from text when JSON parsing fails
5444
+ * Register an NPC for context management.
5445
+ * @param npc The NPC client to register
5226
5446
  */
5227
- extractPredictionsFromText(response, expectedCount) {
5228
- const predictions = [];
5229
- const lines = response.split(/[\n\r]+/).filter(line => line.trim());
5230
- for (const line of lines) {
5231
- let cleaned = line.trim();
5232
- // Skip empty lines and JSON brackets
5233
- if (!cleaned || cleaned === '[' || cleaned === ']')
5234
- continue;
5235
- // Remove common prefixes like "1.", "- ", etc.
5236
- if (/^\d+\./.test(cleaned)) {
5237
- cleaned = cleaned.replace(/^\d+\.\s*/, '');
5238
- }
5239
- else if (cleaned.startsWith('- ')) {
5240
- cleaned = cleaned.substring(2);
5241
- }
5242
- // Remove surrounding quotes
5243
- if (cleaned.startsWith('"') && cleaned.endsWith('"')) {
5244
- cleaned = cleaned.slice(1, -1);
5245
- }
5246
- // Remove trailing comma
5247
- if (cleaned.endsWith(',')) {
5248
- cleaned = cleaned.slice(0, -1).trim();
5249
- }
5250
- if (cleaned && predictions.length < expectedCount) {
5251
- predictions.push(cleaned);
5252
- }
5447
+ registerNpc(npc) {
5448
+ if (!npc)
5449
+ return;
5450
+ if (!this.npcStates.has(npc)) {
5451
+ this.npcStates.set(npc, {
5452
+ lastConversationTime: new Date(),
5453
+ isCompacted: false,
5454
+ compactionCount: 0,
5455
+ });
5253
5456
  }
5254
- return predictions;
5255
5457
  }
5256
5458
  /**
5257
- * Internal method to trigger prediction generation after NPC response
5459
+ * Unregister an NPC (call when NPC is destroyed/removed).
5460
+ * @param npc The NPC client to unregister
5258
5461
  */
5259
- async triggerReplyPrediction() {
5260
- if (!this.generateReplyPrediction)
5462
+ unregisterNpc(npc) {
5463
+ if (!npc)
5261
5464
  return;
5262
- // Fire and forget - don't block the main response
5263
- this.generateReplyPredictions().catch(err => {
5264
- this.logger.error('Background prediction generation failed:', err);
5265
- });
5465
+ this.npcStates.delete(npc);
5266
5466
  }
5267
- // ===== Main API - Talk Methods =====
5268
5467
  /**
5269
- * Talk to the NPC (non-streaming)
5468
+ * Record that a conversation occurred with an NPC.
5469
+ * Called after each Talk() exchange.
5470
+ * @param npc The NPC client that had a conversation
5270
5471
  */
5271
- async talk(message) {
5272
- this._isTalking = true;
5273
- try {
5274
- // Add user message to history
5275
- const userMessage = { role: 'user', content: message };
5276
- this.history.push(userMessage);
5277
- // Build messages array with system prompt
5278
- const messages = [
5279
- { role: 'system', content: this.buildSystemPrompt() },
5280
- ...this.history,
5281
- ];
5282
- // Generate response
5283
- const result = await this.chatClient.textGeneration({
5284
- messages,
5285
- temperature: this.temperature,
5286
- });
5287
- // Add assistant response to history
5288
- const assistantMessage = { role: 'assistant', content: result.content };
5289
- this.history.push(assistantMessage);
5290
- // Trim history if needed
5291
- this.trimHistory();
5292
- this.emit('response', result.content);
5293
- // Trigger reply prediction generation (fire and forget)
5294
- this.triggerReplyPrediction();
5295
- return result.content;
5296
- }
5297
- finally {
5298
- this._isTalking = false;
5472
+ recordConversation(npc) {
5473
+ if (!npc)
5474
+ return;
5475
+ if (!this.npcStates.has(npc)) {
5476
+ this.registerNpc(npc);
5299
5477
  }
5478
+ const state = this.npcStates.get(npc);
5479
+ state.lastConversationTime = new Date();
5480
+ state.isCompacted = false; // Reset compaction flag on new conversation
5300
5481
  }
5301
5482
  /**
5302
- * Talk to the NPC with streaming
5483
+ * Get all registered NPCs
5303
5484
  */
5304
- async talkStream(message, onChunk, onComplete) {
5305
- this._isTalking = true;
5306
- try {
5307
- // Add user message to history
5308
- const userMessage = { role: 'user', content: message };
5309
- this.history.push(userMessage);
5310
- // Build messages array with system prompt
5311
- const messages = [
5312
- { role: 'system', content: this.buildSystemPrompt() },
5313
- ...this.history,
5314
- ];
5315
- // Generate response
5316
- await this.chatClient.textGenerationStream({
5317
- messages,
5318
- temperature: this.temperature,
5319
- onChunk,
5320
- onComplete: (fullText) => {
5321
- this._isTalking = false;
5322
- // Add assistant response to history
5323
- const assistantMessage = { role: 'assistant', content: fullText };
5324
- this.history.push(assistantMessage);
5325
- // Trim history if needed
5326
- this.trimHistory();
5327
- this.emit('response', fullText);
5328
- // Trigger reply prediction generation (fire and forget)
5329
- this.triggerReplyPrediction();
5330
- if (onComplete) {
5331
- onComplete(fullText);
5332
- }
5333
- },
5334
- });
5335
- }
5336
- catch (error) {
5337
- this._isTalking = false;
5338
- throw error;
5339
- }
5485
+ getRegisteredNpcs() {
5486
+ return Array.from(this.npcStates.keys());
5340
5487
  }
5341
5488
  /**
5342
- * Talk with structured output
5343
- * @deprecated Use talkWithActions instead for NPC decision-making with actions
5489
+ * Get the conversation state for an NPC
5344
5490
  */
5345
- async talkStructured(message, schemaName) {
5346
- this.logger.warn('talkStructured is deprecated. Use talkWithActions instead for NPC decision-making with actions.');
5347
- // Add user message to history
5348
- const userMessage = { role: 'user', content: message };
5349
- this.history.push(userMessage);
5350
- // Generate structured response
5351
- const result = await this.chatClient.generateStructured({
5352
- schemaName,
5353
- prompt: message,
5354
- messages: [{ role: 'system', content: this.buildSystemPrompt() }, ...this.history],
5355
- temperature: this.temperature,
5356
- });
5357
- // Add a text representation to history
5358
- const assistantMessage = {
5359
- role: 'assistant',
5360
- content: JSON.stringify(result),
5361
- };
5362
- this.history.push(assistantMessage);
5363
- this.trimHistory();
5364
- return result;
5491
+ getNpcState(npc) {
5492
+ return this.npcStates.get(npc);
5365
5493
  }
5494
+ // ===== Auto Compaction =====
5366
5495
  /**
5367
- * Talk to the NPC with available actions (non-streaming)
5368
- * @param message The message to send
5369
- * @param actions List of actions the NPC can perform
5370
- * @returns Response containing text and any action calls
5496
+ * Check if an NPC is eligible for compaction.
5497
+ * @param npc The NPC to check
5498
+ * @returns True if eligible for compaction
5371
5499
  */
5372
- async talkWithActions(message, actions) {
5373
- this._isTalking = true;
5500
+ isEligibleForCompaction(npc) {
5501
+ if (!npc)
5502
+ return false;
5503
+ const state = this.npcStates.get(npc);
5504
+ if (!state)
5505
+ return false;
5506
+ // Check if already compacted since last conversation
5507
+ if (state.isCompacted)
5508
+ return false;
5509
+ // Check message count
5510
+ const history = npc.getHistory();
5511
+ const nonSystemMessages = history.filter(m => m.role !== 'system').length;
5512
+ if (nonSystemMessages < this.config.autoCompactMinMessages)
5513
+ return false;
5514
+ // Check time since last conversation
5515
+ const timeSinceLastConversation = (Date.now() - state.lastConversationTime.getTime()) / 1000;
5516
+ if (timeSinceLastConversation < this.config.autoCompactTimeoutSeconds)
5517
+ return false;
5518
+ return true;
5519
+ }
5520
+ /**
5521
+ * Manually trigger conversation compaction for a specific NPC.
5522
+ * Summarizes the conversation history and stores it as a memory.
5523
+ * @param npc The NPC to compact
5524
+ * @returns True if compaction succeeded
5525
+ */
5526
+ async compactConversation(npc) {
5527
+ if (!npc) {
5528
+ this.logger.warn('Cannot compact: NPC is null');
5529
+ return false;
5530
+ }
5531
+ if (!this.chatClientFactory) {
5532
+ this.logger.error('Cannot compact: No chat client factory set. Call setChatClientFactory() first.');
5533
+ return false;
5534
+ }
5535
+ const history = npc.getHistory();
5536
+ const nonSystemMessages = history.filter(m => m.role !== 'system');
5537
+ if (nonSystemMessages.length < 2) {
5538
+ this.logger.info('Skipping compaction: not enough messages');
5539
+ return false;
5540
+ }
5374
5541
  try {
5375
- // Add user message to history
5376
- const userMessage = { role: 'user', content: message };
5377
- this.history.push(userMessage);
5378
- // Convert NpcActions to ChatTools
5379
- const tools = actions
5380
- .filter(a => a && a.enabled !== false)
5381
- .map(a => npcActionToTool(a));
5382
- // Build messages array with system prompt
5383
- const messages = [
5384
- { role: 'system', content: this.buildSystemPrompt() },
5385
- ...this.history,
5386
- ];
5387
- // Generate response with tools
5388
- const result = await this.chatClient.textGenerationWithTools({
5389
- messages,
5390
- temperature: this.temperature,
5391
- tools,
5392
- tool_choice: 'auto',
5542
+ this.logger.info(`Starting compaction (${nonSystemMessages.length} messages)`);
5543
+ // Build conversation text for summarization
5544
+ const conversationText = nonSystemMessages
5545
+ .map(m => `${m.role}: ${m.content}`)
5546
+ .join('\n');
5547
+ // Create summarization prompt
5548
+ const summaryPrompt = `Summarize the following conversation concisely. Focus on:
5549
+ 1. Key topics discussed
5550
+ 2. Important information exchanged
5551
+ 3. Any decisions or commitments made
5552
+ 4. The emotional tone
5553
+
5554
+ Keep the summary under 200 words. Write in third person.
5555
+
5556
+ Conversation:
5557
+ ${conversationText}`;
5558
+ // Use chat client for summarization
5559
+ const chatClient = this.chatClientFactory();
5560
+ const result = await chatClient.textGeneration({
5561
+ messages: [{ role: 'user', content: summaryPrompt }],
5562
+ temperature: 0.5,
5563
+ model: this.config.fastModel || undefined,
5393
5564
  });
5394
- // Build response
5395
- const response = {
5396
- text: result.content || '',
5397
- actionCalls: [],
5398
- hasActions: false,
5399
- };
5400
- // Extract tool calls if any
5401
- if (result.tool_calls) {
5402
- response.actionCalls = result.tool_calls.map(tc => ({
5403
- id: tc.id,
5404
- actionName: tc.function.name,
5405
- arguments: this.parseToolArguments(tc.function.arguments),
5406
- }));
5407
- response.hasActions = response.actionCalls.length > 0;
5565
+ if (!result.content) {
5566
+ const error = 'Empty response from summarization';
5567
+ this.logger.error(`Compaction failed: ${error}`);
5568
+ this.emit('compactionFailed', npc, error);
5569
+ return false;
5408
5570
  }
5409
- // Add assistant response to history
5410
- const assistantMessage = {
5411
- role: 'assistant',
5412
- content: response.text,
5413
- tool_calls: result.tool_calls,
5414
- };
5415
- this.history.push(assistantMessage);
5416
- this.trimHistory();
5417
- this.emit('response', response.text);
5418
- if (response.hasActions) {
5419
- this.emit('actions', response.actionCalls);
5571
+ // Clear history and add summary as memory
5572
+ npc.clearHistory();
5573
+ npc.setMemory('PreviousConversationSummary', result.content);
5574
+ // Update state
5575
+ const state = this.npcStates.get(npc);
5576
+ if (state) {
5577
+ state.isCompacted = true;
5578
+ state.compactionCount++;
5420
5579
  }
5421
- // Trigger reply prediction generation (fire and forget)
5422
- this.triggerReplyPrediction();
5423
- return response;
5580
+ this.logger.info(`Compaction completed. Summary: ${result.content.substring(0, 100)}...`);
5581
+ this.emit('npcCompacted', npc);
5582
+ return true;
5424
5583
  }
5425
- finally {
5426
- this._isTalking = false;
5584
+ catch (error) {
5585
+ const errorMessage = error instanceof Error ? error.message : String(error);
5586
+ this.logger.error(`Compaction error: ${errorMessage}`);
5587
+ this.emit('compactionFailed', npc, errorMessage);
5588
+ return false;
5427
5589
  }
5428
5590
  }
5429
5591
  /**
5430
- * Talk to the NPC with actions (streaming)
5431
- * Text streams first, action calls are returned in onComplete
5592
+ * Compact all registered NPCs that meet the eligibility criteria.
5593
+ * @returns Number of NPCs successfully compacted
5432
5594
  */
5433
- async talkWithActionsStream(message, actions, onChunk, onComplete) {
5434
- this._isTalking = true;
5435
- try {
5436
- // Add user message to history
5437
- const userMessage = { role: 'user', content: message };
5438
- this.history.push(userMessage);
5439
- // Convert NpcActions to ChatTools
5440
- const tools = actions
5441
- .filter(a => a && a.enabled !== false)
5442
- .map(a => npcActionToTool(a));
5443
- // Build messages array with system prompt
5444
- const messages = [
5445
- { role: 'system', content: this.buildSystemPrompt() },
5446
- ...this.history,
5447
- ];
5448
- // Generate response with tools (streaming)
5449
- await this.chatClient.textGenerationWithToolsStream({
5450
- messages,
5451
- temperature: this.temperature,
5452
- tools,
5453
- tool_choice: 'auto',
5454
- onChunk,
5455
- onComplete: (result) => {
5456
- this._isTalking = false;
5457
- // Build response
5458
- const response = {
5459
- text: result.content || '',
5460
- actionCalls: [],
5461
- hasActions: false,
5462
- };
5463
- // Extract tool calls if any
5464
- if (result.tool_calls) {
5465
- response.actionCalls = result.tool_calls.map(tc => ({
5466
- id: tc.id,
5467
- actionName: tc.function.name,
5468
- arguments: this.parseToolArguments(tc.function.arguments),
5469
- }));
5470
- response.hasActions = response.actionCalls.length > 0;
5471
- }
5472
- // Add assistant response to history
5473
- const assistantMessage = {
5474
- role: 'assistant',
5475
- content: response.text,
5476
- tool_calls: result.tool_calls,
5477
- };
5478
- this.history.push(assistantMessage);
5479
- this.trimHistory();
5480
- this.emit('response', response.text);
5481
- if (response.hasActions) {
5482
- this.emit('actions', response.actionCalls);
5483
- }
5484
- // Trigger reply prediction generation (fire and forget)
5485
- this.triggerReplyPrediction();
5486
- if (onComplete) {
5487
- onComplete(response);
5488
- }
5489
- },
5490
- });
5595
+ async compactAllEligible() {
5596
+ const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
5597
+ if (eligibleNpcs.length === 0) {
5598
+ return 0;
5491
5599
  }
5492
- catch (error) {
5493
- this._isTalking = false;
5494
- throw error;
5600
+ this.logger.info(`Compacting ${eligibleNpcs.length} eligible NPCs`);
5601
+ let successCount = 0;
5602
+ for (const npc of eligibleNpcs) {
5603
+ const success = await this.compactConversation(npc);
5604
+ if (success)
5605
+ successCount++;
5495
5606
  }
5607
+ return successCount;
5496
5608
  }
5497
- // ===== Action Results Reporting =====
5609
+ // ===== Auto Compact Timer =====
5498
5610
  /**
5499
- * Report action results back to the conversation
5500
- * Call this after executing actions to let the NPC know the results
5611
+ * Start the auto-compact check timer
5501
5612
  */
5502
- reportActionResults(results) {
5503
- for (const [callId, result] of Object.entries(results)) {
5504
- this.history.push({
5505
- role: 'tool',
5506
- tool_call_id: callId,
5507
- content: result,
5508
- });
5613
+ startAutoCompactCheck() {
5614
+ if (this.autoCompactTimer) {
5615
+ this.stopAutoCompactCheck();
5509
5616
  }
5617
+ this.autoCompactTimer = setInterval(() => {
5618
+ this.runAutoCompactCheck();
5619
+ }, this.config.autoCompactCheckInterval);
5510
5620
  }
5511
5621
  /**
5512
- * Report a single action result
5622
+ * Stop the auto-compact check timer
5513
5623
  */
5514
- reportActionResult(callId, result) {
5515
- this.history.push({
5516
- role: 'tool',
5517
- tool_call_id: callId,
5518
- content: result,
5519
- });
5624
+ stopAutoCompactCheck() {
5625
+ if (this.autoCompactTimer) {
5626
+ clearInterval(this.autoCompactTimer);
5627
+ this.autoCompactTimer = null;
5628
+ }
5520
5629
  }
5521
5630
  /**
5522
- * Parse tool arguments from JSON string
5631
+ * Run a single auto-compact check
5523
5632
  */
5524
- parseToolArguments(args) {
5525
- try {
5526
- return JSON.parse(args);
5527
- }
5528
- catch (_a) {
5529
- return {};
5633
+ async runAutoCompactCheck() {
5634
+ if (!this.config.enableAutoCompact)
5635
+ return;
5636
+ const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
5637
+ for (const npc of eligibleNpcs) {
5638
+ // Fire and forget - don't block
5639
+ this.compactConversation(npc).catch(err => {
5640
+ this.logger.error('Auto-compact error:', err);
5641
+ });
5530
5642
  }
5531
5643
  }
5532
- // ===== Conversation History Management =====
5644
+ // ===== Lifecycle =====
5533
5645
  /**
5534
- * Get conversation history
5646
+ * Enable auto-compaction
5535
5647
  */
5536
- getHistory() {
5537
- return [...this.history];
5648
+ enableAutoCompact() {
5649
+ this.config.enableAutoCompact = true;
5650
+ this.startAutoCompactCheck();
5538
5651
  }
5539
5652
  /**
5540
- * Get the number of messages in history
5653
+ * Disable auto-compaction
5541
5654
  */
5542
- getHistoryLength() {
5543
- return this.history.length;
5655
+ disableAutoCompact() {
5656
+ this.config.enableAutoCompact = false;
5657
+ this.stopAutoCompactCheck();
5544
5658
  }
5545
5659
  /**
5546
- * Clear conversation history.
5547
- * The character design and memories will be preserved.
5660
+ * Clean up resources
5548
5661
  */
5549
- clearHistory() {
5662
+ destroy() {
5663
+ this.stopAutoCompactCheck();
5664
+ this.npcStates.clear();
5665
+ this.playerDescription = null;
5666
+ this.playerPrompt = null;
5667
+ this.playerMemories.clear();
5668
+ this.removeAllListeners();
5669
+ }
5670
+ }
5671
+ AIContextManager._instance = null;
5672
+ /**
5673
+ * Default AIContextManager instance
5674
+ * Can be used as a global context manager
5675
+ */
5676
+ const defaultContextManager = AIContextManager.getInstance();
5677
+
5678
+ /**
5679
+ * NPC Client for simplified conversation management
5680
+ * Automatically handles conversation history
5681
+ *
5682
+ * Key Features:
5683
+ * - Call talk() for all interactions - actions are handled automatically
5684
+ * - Memory system for persistent NPC context
5685
+ * - Reply prediction for suggesting player responses
5686
+ * - Automatic conversation history management
5687
+ */
5688
+ class NPCClient extends EventEmitter {
5689
+ constructor(chatClient, config) {
5690
+ var _a, _b, _c;
5691
+ super();
5692
+ this._isTalking = false;
5693
+ this.logger = Logger.getLogger('NPCClient');
5694
+ this.chatClient = chatClient;
5695
+ // Support both characterDesign and legacy systemPrompt
5696
+ 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.';
5697
+ this.temperature = (_a = config === null || config === void 0 ? void 0 : config.temperature) !== null && _a !== void 0 ? _a : 0.7;
5698
+ this.maxHistoryLength = (config === null || config === void 0 ? void 0 : config.maxHistoryLength) || 50;
5699
+ this.generateReplyPrediction = (_b = config === null || config === void 0 ? void 0 : config.generateReplyPrediction) !== null && _b !== void 0 ? _b : false;
5700
+ this.predictionCount = Math.max(2, Math.min(6, (_c = config === null || config === void 0 ? void 0 : config.predictionCount) !== null && _c !== void 0 ? _c : 4));
5701
+ this.fastModel = config === null || config === void 0 ? void 0 : config.fastModel;
5550
5702
  this.history = [];
5551
- this.emit('history_cleared');
5703
+ this.memories = new Map();
5552
5704
  }
5705
+ // ===== State Properties =====
5553
5706
  /**
5554
- * Revert the last exchange (user message and assistant response) from history.
5555
- * @returns true if reverted, false if not enough history
5707
+ * Whether the NPC is currently processing a request
5556
5708
  */
5557
- revertHistory() {
5558
- let lastAssistantIndex = -1;
5559
- let lastUserIndex = -1;
5560
- for (let i = this.history.length - 1; i >= 0; i--) {
5561
- if (this.history[i].role === 'assistant' && lastAssistantIndex === -1) {
5562
- lastAssistantIndex = i;
5563
- }
5564
- else if (this.history[i].role === 'user' && lastAssistantIndex !== -1 && lastUserIndex === -1) {
5565
- lastUserIndex = i;
5566
- break;
5709
+ get isTalking() {
5710
+ return this._isTalking;
5711
+ }
5712
+ // ===== Character Design & Memory System =====
5713
+ /**
5714
+ * Set the character design for the NPC.
5715
+ * The system prompt is composed of CharacterDesign + all Memories.
5716
+ */
5717
+ setCharacterDesign(design) {
5718
+ this.characterDesign = design;
5719
+ }
5720
+ /**
5721
+ * Get the current character design
5722
+ */
5723
+ getCharacterDesign() {
5724
+ return this.characterDesign;
5725
+ }
5726
+ /**
5727
+ * @deprecated Use setCharacterDesign instead.
5728
+ * This method is kept for backwards compatibility.
5729
+ */
5730
+ setSystemPrompt(prompt) {
5731
+ this.logger.warn('setSystemPrompt is deprecated. Use setCharacterDesign instead.');
5732
+ this.setCharacterDesign(prompt);
5733
+ }
5734
+ /**
5735
+ * @deprecated Use getCharacterDesign instead.
5736
+ * This method is kept for backwards compatibility.
5737
+ */
5738
+ getSystemPrompt() {
5739
+ return this.buildSystemPrompt();
5740
+ }
5741
+ /**
5742
+ * Set or update a memory for the NPC.
5743
+ * Memories are appended to the character design to form the system prompt.
5744
+ * Set memoryContent to null or empty to remove the memory.
5745
+ * @param memoryName The name/key of the memory
5746
+ * @param memoryContent The content of the memory. Null or empty to remove.
5747
+ */
5748
+ setMemory(memoryName, memoryContent) {
5749
+ if (!memoryName) {
5750
+ this.logger.warn('Memory name cannot be empty');
5751
+ return;
5752
+ }
5753
+ if (!memoryContent) {
5754
+ // Remove memory if content is null or empty
5755
+ if (this.memories.has(memoryName)) {
5756
+ this.memories.delete(memoryName);
5757
+ this.emit('memory_removed', memoryName);
5567
5758
  }
5568
5759
  }
5569
- if (lastAssistantIndex !== -1 && lastUserIndex !== -1) {
5570
- // Remove in reverse order to maintain indices
5571
- this.history.splice(lastAssistantIndex, 1);
5572
- this.history.splice(lastUserIndex, 1);
5573
- this.emit('history_reverted');
5574
- return true;
5760
+ else {
5761
+ // Add or update memory
5762
+ this.memories.set(memoryName, memoryContent);
5763
+ this.emit('memory_set', memoryName, memoryContent);
5575
5764
  }
5576
- return false;
5577
5765
  }
5578
5766
  /**
5579
- * Revert (remove) the last N chat messages from history
5580
- * @param count Number of messages to remove
5581
- * @returns Number of messages actually removed
5767
+ * Get a specific memory by name.
5768
+ * @param memoryName The name of the memory to retrieve
5769
+ * @returns The memory content, or undefined if not found
5582
5770
  */
5583
- revertChatMessages(count) {
5584
- if (count <= 0)
5585
- return 0;
5586
- const messagesToRemove = Math.min(count, this.history.length);
5587
- const originalCount = this.history.length;
5588
- this.history = this.history.slice(0, -messagesToRemove);
5589
- const actuallyRemoved = originalCount - this.history.length;
5590
- if (actuallyRemoved > 0) {
5591
- this.emit('history_reverted', actuallyRemoved);
5771
+ getMemory(memoryName) {
5772
+ return this.memories.get(memoryName);
5773
+ }
5774
+ /**
5775
+ * Get all memory names currently stored.
5776
+ * @returns Array of memory names
5777
+ */
5778
+ getMemoryNames() {
5779
+ return Array.from(this.memories.keys());
5780
+ }
5781
+ /**
5782
+ * Clear all memories (but keep character design).
5783
+ */
5784
+ clearMemories() {
5785
+ this.memories.clear();
5786
+ this.emit('memories_cleared');
5787
+ }
5788
+ /**
5789
+ * Build the complete system prompt from CharacterDesign + Memories.
5790
+ */
5791
+ buildSystemPrompt() {
5792
+ const parts = [];
5793
+ if (this.characterDesign) {
5794
+ parts.push(this.characterDesign);
5795
+ }
5796
+ if (this.memories.size > 0) {
5797
+ const memoryStrings = Array.from(this.memories.entries())
5798
+ .map(([name, content]) => `[${name}]: ${content}`);
5799
+ parts.push('Memories:\n' + memoryStrings.join('\n'));
5592
5800
  }
5593
- return actuallyRemoved;
5801
+ return parts.join('\n\n');
5594
5802
  }
5803
+ // ===== Reply Prediction =====
5595
5804
  /**
5596
- * Revert to a specific point in history
5597
- * @deprecated Use revertHistory() or revertChatMessages() instead
5805
+ * Enable or disable automatic reply prediction
5598
5806
  */
5599
- revertToMessage(index) {
5600
- if (index >= 0 && index < this.history.length) {
5601
- this.history = this.history.slice(0, index + 1);
5602
- this.emit('history_reverted', index);
5603
- }
5807
+ setGenerateReplyPrediction(enabled) {
5808
+ this.generateReplyPrediction = enabled;
5604
5809
  }
5605
5810
  /**
5606
- * Append a message to history manually
5811
+ * Set the number of predictions to generate
5607
5812
  */
5608
- appendMessage(message) {
5609
- this.history.push(message);
5610
- this.trimHistory();
5813
+ setPredictionCount(count) {
5814
+ this.predictionCount = Math.max(2, Math.min(6, count));
5611
5815
  }
5612
5816
  /**
5613
- * Alias for appendMessage (Unity SDK compatibility)
5817
+ * Manually generate reply predictions based on current conversation.
5818
+ * Uses the fast model for quick generation.
5819
+ * @param tempPrompt Optional temporary prompt to influence the prediction style/tone
5820
+ * @param count Number of predictions to generate (default: uses predictionCount property)
5821
+ * @returns Array of predicted player replies, or empty array on failure
5614
5822
  */
5615
- appendChatMessage(role, content) {
5616
- if (!role || !content) {
5617
- this.logger.warn('Role and content cannot be empty');
5618
- return;
5823
+ async generateReplyPredictions(tempPrompt, count) {
5824
+ var _a;
5825
+ const predictionNum = count !== null && count !== void 0 ? count : this.predictionCount;
5826
+ if (this.history.length < 2) {
5827
+ this.logger.info('Not enough conversation history to generate predictions');
5828
+ return [];
5829
+ }
5830
+ try {
5831
+ // Get last NPC message
5832
+ const lastNpcMessage = (_a = [...this.history]
5833
+ .reverse()
5834
+ .find(m => m.role === 'assistant')) === null || _a === void 0 ? void 0 : _a.content;
5835
+ if (!lastNpcMessage) {
5836
+ this.logger.info('No NPC message found to generate predictions from');
5837
+ return [];
5838
+ }
5839
+ // Build recent history (last 6 non-system messages)
5840
+ const recentHistory = this.history
5841
+ .filter(m => m.role !== 'system')
5842
+ .slice(-6)
5843
+ .map(m => `${m.role}: ${m.content}`);
5844
+ // Get player context from AIContextManager
5845
+ const contextManager = AIContextManager.getInstance();
5846
+ const playerContext = contextManager.buildPlayerContext();
5847
+ // Build player character section
5848
+ let playerCharacterSection = '';
5849
+ if (playerContext || tempPrompt) {
5850
+ playerCharacterSection = '\nPlayer Character:\n';
5851
+ if (playerContext) {
5852
+ playerCharacterSection += playerContext + '\n';
5853
+ }
5854
+ if (tempPrompt) {
5855
+ playerCharacterSection += `Additional guidance: ${tempPrompt}\n`;
5856
+ }
5857
+ }
5858
+ // Build prompt for prediction generation
5859
+ const prompt = `Based on the conversation history below, generate exactly ${predictionNum} natural and contextually appropriate responses that the player might say next.
5860
+
5861
+ Context:
5862
+ - This is a conversation between a player and an NPC in a game
5863
+ - The NPC just said: "${lastNpcMessage}"
5864
+ ${playerCharacterSection}
5865
+ Conversation history:
5866
+ ${recentHistory.join('\n')}
5867
+
5868
+ Requirements:
5869
+ 1. Each response should be 1-2 sentences maximum
5870
+ 2. Responses should be diverse in tone and intent
5871
+ 3. Include a mix of questions, statements, and action-oriented responses
5872
+ 4. Responses should feel natural for the player character${playerContext || tempPrompt ? ' and match their personality/tone' : ''}
5873
+
5874
+ Output ONLY a JSON array of ${predictionNum} strings, nothing else:
5875
+ ["response1", "response2", "response3", "response4"]`;
5876
+ const result = await this.chatClient.textGeneration({
5877
+ messages: [{ role: 'user', content: prompt }],
5878
+ temperature: 0.8,
5879
+ model: this.fastModel,
5880
+ });
5881
+ if (!result.content) {
5882
+ this.logger.warn('Failed to generate predictions: empty response');
5883
+ return [];
5884
+ }
5885
+ // Parse JSON response
5886
+ const predictions = this.parsePredictionsFromJson(result.content, predictionNum);
5887
+ if (predictions.length > 0) {
5888
+ this.emit('replyPredictions', predictions);
5889
+ }
5890
+ return predictions;
5891
+ }
5892
+ catch (error) {
5893
+ this.logger.error('Error generating predictions:', error);
5894
+ return [];
5619
5895
  }
5620
- this.appendMessage({ role: role, content });
5621
5896
  }
5622
5897
  /**
5623
- * Trim history to max length
5898
+ * Parse predictions from JSON array response
5624
5899
  */
5625
- trimHistory() {
5626
- if (this.history.length > this.maxHistoryLength) {
5627
- // Keep the most recent messages
5628
- this.history = this.history.slice(-this.maxHistoryLength);
5900
+ parsePredictionsFromJson(response, expectedCount) {
5901
+ try {
5902
+ // Try to find JSON array in response
5903
+ const startIndex = response.indexOf('[');
5904
+ const endIndex = response.lastIndexOf(']');
5905
+ if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
5906
+ this.logger.warn('Could not find JSON array in prediction response');
5907
+ return this.extractPredictionsFromText(response, expectedCount);
5908
+ }
5909
+ const jsonArray = response.substring(startIndex, endIndex + 1);
5910
+ const parsed = JSON.parse(jsonArray);
5911
+ if (Array.isArray(parsed)) {
5912
+ return parsed
5913
+ .filter(item => typeof item === 'string' && item.trim())
5914
+ .slice(0, expectedCount);
5915
+ }
5916
+ return [];
5917
+ }
5918
+ catch (error) {
5919
+ this.logger.warn('Failed to parse predictions JSON:', error);
5920
+ return this.extractPredictionsFromText(response, expectedCount);
5629
5921
  }
5630
5922
  }
5631
- // ===== Save/Load =====
5632
5923
  /**
5633
- * Save the current conversation history to a serializable format.
5634
- * Includes characterDesign, memories, and history.
5924
+ * Fallback: Extract predictions from text when JSON parsing fails
5635
5925
  */
5636
- saveHistory() {
5637
- const saveData = {
5638
- characterDesign: this.characterDesign,
5639
- memories: Array.from(this.memories.entries()).map(([name, content]) => ({ name, content })),
5640
- history: this.history,
5641
- };
5642
- return JSON.stringify(saveData);
5926
+ extractPredictionsFromText(response, expectedCount) {
5927
+ const predictions = [];
5928
+ const lines = response.split(/[\n\r]+/).filter(line => line.trim());
5929
+ for (const line of lines) {
5930
+ let cleaned = line.trim();
5931
+ // Skip empty lines and JSON brackets
5932
+ if (!cleaned || cleaned === '[' || cleaned === ']')
5933
+ continue;
5934
+ // Remove common prefixes like "1.", "- ", etc.
5935
+ if (/^\d+\./.test(cleaned)) {
5936
+ cleaned = cleaned.replace(/^\d+\.\s*/, '');
5937
+ }
5938
+ else if (cleaned.startsWith('- ')) {
5939
+ cleaned = cleaned.substring(2);
5940
+ }
5941
+ // Remove surrounding quotes
5942
+ if (cleaned.startsWith('"') && cleaned.endsWith('"')) {
5943
+ cleaned = cleaned.slice(1, -1);
5944
+ }
5945
+ // Remove trailing comma
5946
+ if (cleaned.endsWith(',')) {
5947
+ cleaned = cleaned.slice(0, -1).trim();
5948
+ }
5949
+ if (cleaned && predictions.length < expectedCount) {
5950
+ predictions.push(cleaned);
5951
+ }
5952
+ }
5953
+ return predictions;
5643
5954
  }
5644
5955
  /**
5645
- * Load conversation history from serialized data.
5646
- * Restores characterDesign, memories, and history.
5956
+ * Internal method to trigger prediction generation after NPC response
5647
5957
  */
5648
- loadHistory(saveData) {
5958
+ async triggerReplyPrediction() {
5959
+ if (!this.generateReplyPrediction)
5960
+ return;
5961
+ // Fire and forget - don't block the main response
5962
+ this.generateReplyPredictions().catch(err => {
5963
+ this.logger.error('Background prediction generation failed:', err);
5964
+ });
5965
+ }
5966
+ // ===== Main API - Talk Methods =====
5967
+ /**
5968
+ * Talk to the NPC (non-streaming)
5969
+ */
5970
+ async talk(message) {
5971
+ this._isTalking = true;
5649
5972
  try {
5650
- const data = JSON.parse(saveData);
5651
- // Load character design (with backwards compatibility for old systemPrompt field)
5652
- this.characterDesign = data.characterDesign || data.systemPrompt || this.characterDesign;
5653
- // Load memories
5654
- this.memories.clear();
5655
- if (data.memories && Array.isArray(data.memories)) {
5656
- for (const memory of data.memories) {
5657
- if (memory.name && memory.content) {
5658
- this.memories.set(memory.name, memory.content);
5659
- }
5660
- }
5661
- }
5662
- // Load history (skip system messages as they'll be rebuilt from characterDesign + memories)
5663
- this.history = (data.history || []).filter(m => m.role !== 'system');
5664
- this.emit('history_loaded');
5665
- return true;
5666
- }
5667
- catch (error) {
5668
- this.logger.error('Failed to load history:', error);
5669
- return false;
5973
+ // Add user message to history
5974
+ const userMessage = { role: 'user', content: message };
5975
+ this.history.push(userMessage);
5976
+ // Build messages array with system prompt
5977
+ const messages = [
5978
+ { role: 'system', content: this.buildSystemPrompt() },
5979
+ ...this.history,
5980
+ ];
5981
+ // Generate response
5982
+ const result = await this.chatClient.textGeneration({
5983
+ messages,
5984
+ temperature: this.temperature,
5985
+ });
5986
+ // Add assistant response to history
5987
+ const assistantMessage = { role: 'assistant', content: result.content };
5988
+ this.history.push(assistantMessage);
5989
+ // Trim history if needed
5990
+ this.trimHistory();
5991
+ this.emit('response', result.content);
5992
+ // Trigger reply prediction generation (fire and forget)
5993
+ this.triggerReplyPrediction();
5994
+ return result.content;
5670
5995
  }
5671
- }
5672
- }
5673
-
5674
- /**
5675
- * Global AI Context Manager for managing NPC conversations and player context.
5676
- *
5677
- * Features:
5678
- * - Player description management
5679
- * - NPC conversation tracking
5680
- * - Automatic conversation compaction (AutoCompact)
5681
- */
5682
- /**
5683
- * Global AI Context Manager
5684
- * Manages NPC conversations and player context across the application
5685
- */
5686
- class AIContextManager extends EventEmitter {
5687
- constructor(config) {
5688
- var _a, _b, _c, _d, _e;
5689
- super();
5690
- this.playerDescription = null;
5691
- this.npcStates = new Map();
5692
- this.autoCompactTimer = null;
5693
- this.chatClientFactory = null;
5694
- this.logger = Logger.getLogger('AIContextManager');
5695
- this.config = {
5696
- enableAutoCompact: (_a = config === null || config === void 0 ? void 0 : config.enableAutoCompact) !== null && _a !== void 0 ? _a : false,
5697
- autoCompactMinMessages: (_b = config === null || config === void 0 ? void 0 : config.autoCompactMinMessages) !== null && _b !== void 0 ? _b : 20,
5698
- autoCompactTimeoutSeconds: (_c = config === null || config === void 0 ? void 0 : config.autoCompactTimeoutSeconds) !== null && _c !== void 0 ? _c : 300,
5699
- autoCompactCheckInterval: (_d = config === null || config === void 0 ? void 0 : config.autoCompactCheckInterval) !== null && _d !== void 0 ? _d : 60000,
5700
- fastModel: (_e = config === null || config === void 0 ? void 0 : config.fastModel) !== null && _e !== void 0 ? _e : '',
5701
- };
5702
- // Start auto-compact check if enabled
5703
- if (this.config.enableAutoCompact) {
5704
- this.startAutoCompactCheck();
5996
+ finally {
5997
+ this._isTalking = false;
5705
5998
  }
5706
5999
  }
5707
- // ===== Singleton Pattern =====
5708
6000
  /**
5709
- * Get the singleton instance of AIContextManager
5710
- * Creates a new instance if one doesn't exist
6001
+ * Talk to the NPC with streaming
5711
6002
  */
5712
- static getInstance(config) {
5713
- if (!AIContextManager._instance) {
5714
- AIContextManager._instance = new AIContextManager(config);
6003
+ async talkStream(message, onChunk, onComplete) {
6004
+ this._isTalking = true;
6005
+ try {
6006
+ // Add user message to history
6007
+ const userMessage = { role: 'user', content: message };
6008
+ this.history.push(userMessage);
6009
+ // Build messages array with system prompt
6010
+ const messages = [
6011
+ { role: 'system', content: this.buildSystemPrompt() },
6012
+ ...this.history,
6013
+ ];
6014
+ // Generate response
6015
+ await this.chatClient.textGenerationStream({
6016
+ messages,
6017
+ temperature: this.temperature,
6018
+ onChunk,
6019
+ onComplete: (fullText) => {
6020
+ this._isTalking = false;
6021
+ // Add assistant response to history
6022
+ const assistantMessage = { role: 'assistant', content: fullText };
6023
+ this.history.push(assistantMessage);
6024
+ // Trim history if needed
6025
+ this.trimHistory();
6026
+ this.emit('response', fullText);
6027
+ // Trigger reply prediction generation (fire and forget)
6028
+ this.triggerReplyPrediction();
6029
+ if (onComplete) {
6030
+ onComplete(fullText);
6031
+ }
6032
+ },
6033
+ });
5715
6034
  }
5716
- return AIContextManager._instance;
5717
- }
5718
- /**
5719
- * Reset the singleton instance (useful for testing)
5720
- */
5721
- static resetInstance() {
5722
- if (AIContextManager._instance) {
5723
- AIContextManager._instance.destroy();
5724
- AIContextManager._instance = null;
6035
+ catch (error) {
6036
+ this._isTalking = false;
6037
+ throw error;
5725
6038
  }
5726
6039
  }
5727
- // ===== Configuration =====
5728
6040
  /**
5729
- * Set the chat client factory for creating chat clients for summarization
5730
- * Required for compaction to work
6041
+ * Talk with structured output
6042
+ * @deprecated Use talkWithActions instead for NPC decision-making with actions
5731
6043
  */
5732
- setChatClientFactory(factory) {
5733
- this.chatClientFactory = factory;
6044
+ async talkStructured(message, schemaName) {
6045
+ this.logger.warn('talkStructured is deprecated. Use talkWithActions instead for NPC decision-making with actions.');
6046
+ // Add user message to history
6047
+ const userMessage = { role: 'user', content: message };
6048
+ this.history.push(userMessage);
6049
+ // Generate structured response
6050
+ const result = await this.chatClient.generateStructured({
6051
+ schemaName,
6052
+ prompt: message,
6053
+ messages: [{ role: 'system', content: this.buildSystemPrompt() }, ...this.history],
6054
+ temperature: this.temperature,
6055
+ });
6056
+ // Add a text representation to history
6057
+ const assistantMessage = {
6058
+ role: 'assistant',
6059
+ content: JSON.stringify(result),
6060
+ };
6061
+ this.history.push(assistantMessage);
6062
+ this.trimHistory();
6063
+ return result;
5734
6064
  }
5735
6065
  /**
5736
- * Update configuration
6066
+ * Talk to the NPC with available actions (non-streaming)
6067
+ * @param message The message to send
6068
+ * @param actions List of actions the NPC can perform
6069
+ * @returns Response containing text and any action calls
5737
6070
  */
5738
- setConfig(config) {
5739
- const wasAutoCompactEnabled = this.config.enableAutoCompact;
5740
- this.config = Object.assign(Object.assign({}, this.config), config);
5741
- // Handle auto-compact state change
5742
- if (config.enableAutoCompact !== undefined) {
5743
- if (config.enableAutoCompact && !wasAutoCompactEnabled) {
5744
- this.startAutoCompactCheck();
6071
+ async talkWithActions(message, actions) {
6072
+ this._isTalking = true;
6073
+ try {
6074
+ // Add user message to history
6075
+ const userMessage = { role: 'user', content: message };
6076
+ this.history.push(userMessage);
6077
+ // Convert NpcActions to ChatTools
6078
+ const tools = actions
6079
+ .filter(a => a && a.enabled !== false)
6080
+ .map(a => npcActionToTool(a));
6081
+ // Build messages array with system prompt
6082
+ const messages = [
6083
+ { role: 'system', content: this.buildSystemPrompt() },
6084
+ ...this.history,
6085
+ ];
6086
+ // Generate response with tools
6087
+ const result = await this.chatClient.textGenerationWithTools({
6088
+ messages,
6089
+ temperature: this.temperature,
6090
+ tools,
6091
+ tool_choice: 'auto',
6092
+ });
6093
+ // Build response
6094
+ const response = {
6095
+ text: result.content || '',
6096
+ actionCalls: [],
6097
+ hasActions: false,
6098
+ };
6099
+ // Extract tool calls if any
6100
+ if (result.tool_calls) {
6101
+ response.actionCalls = result.tool_calls.map(tc => ({
6102
+ id: tc.id,
6103
+ actionName: tc.function.name,
6104
+ arguments: this.parseToolArguments(tc.function.arguments),
6105
+ }));
6106
+ response.hasActions = response.actionCalls.length > 0;
5745
6107
  }
5746
- else if (!config.enableAutoCompact && wasAutoCompactEnabled) {
5747
- this.stopAutoCompactCheck();
6108
+ // Add assistant response to history
6109
+ const assistantMessage = {
6110
+ role: 'assistant',
6111
+ content: response.text,
6112
+ tool_calls: result.tool_calls,
6113
+ };
6114
+ this.history.push(assistantMessage);
6115
+ this.trimHistory();
6116
+ this.emit('response', response.text);
6117
+ if (response.hasActions) {
6118
+ this.emit('actions', response.actionCalls);
5748
6119
  }
6120
+ // Trigger reply prediction generation (fire and forget)
6121
+ this.triggerReplyPrediction();
6122
+ return response;
6123
+ }
6124
+ finally {
6125
+ this._isTalking = false;
5749
6126
  }
5750
- }
5751
- // ===== Player Description =====
5752
- /**
5753
- * Set the player's description for AI context.
5754
- * Used when generating reply predictions and for NPC context.
5755
- * @param description Description of the player character
5756
- */
5757
- setPlayerDescription(description) {
5758
- this.playerDescription = description;
5759
- this.emit('playerDescriptionChanged', description);
5760
- }
5761
- /**
5762
- * Get the current player description.
5763
- * @returns The player description, or null if not set
5764
- */
5765
- getPlayerDescription() {
5766
- return this.playerDescription;
5767
6127
  }
5768
6128
  /**
5769
- * Clear the player description.
6129
+ * Talk to the NPC with actions (streaming)
6130
+ * Text streams first, action calls are returned in onComplete
5770
6131
  */
5771
- clearPlayerDescription() {
5772
- this.playerDescription = null;
5773
- this.emit('playerDescriptionChanged', null);
6132
+ async talkWithActionsStream(message, actions, onChunk, onComplete) {
6133
+ this._isTalking = true;
6134
+ try {
6135
+ // Add user message to history
6136
+ const userMessage = { role: 'user', content: message };
6137
+ this.history.push(userMessage);
6138
+ // Convert NpcActions to ChatTools
6139
+ const tools = actions
6140
+ .filter(a => a && a.enabled !== false)
6141
+ .map(a => npcActionToTool(a));
6142
+ // Build messages array with system prompt
6143
+ const messages = [
6144
+ { role: 'system', content: this.buildSystemPrompt() },
6145
+ ...this.history,
6146
+ ];
6147
+ // Generate response with tools (streaming)
6148
+ await this.chatClient.textGenerationWithToolsStream({
6149
+ messages,
6150
+ temperature: this.temperature,
6151
+ tools,
6152
+ tool_choice: 'auto',
6153
+ onChunk,
6154
+ onComplete: (result) => {
6155
+ this._isTalking = false;
6156
+ // Build response
6157
+ const response = {
6158
+ text: result.content || '',
6159
+ actionCalls: [],
6160
+ hasActions: false,
6161
+ };
6162
+ // Extract tool calls if any
6163
+ if (result.tool_calls) {
6164
+ response.actionCalls = result.tool_calls.map(tc => ({
6165
+ id: tc.id,
6166
+ actionName: tc.function.name,
6167
+ arguments: this.parseToolArguments(tc.function.arguments),
6168
+ }));
6169
+ response.hasActions = response.actionCalls.length > 0;
6170
+ }
6171
+ // Add assistant response to history
6172
+ const assistantMessage = {
6173
+ role: 'assistant',
6174
+ content: response.text,
6175
+ tool_calls: result.tool_calls,
6176
+ };
6177
+ this.history.push(assistantMessage);
6178
+ this.trimHistory();
6179
+ this.emit('response', response.text);
6180
+ if (response.hasActions) {
6181
+ this.emit('actions', response.actionCalls);
6182
+ }
6183
+ // Trigger reply prediction generation (fire and forget)
6184
+ this.triggerReplyPrediction();
6185
+ if (onComplete) {
6186
+ onComplete(response);
6187
+ }
6188
+ },
6189
+ });
6190
+ }
6191
+ catch (error) {
6192
+ this._isTalking = false;
6193
+ throw error;
6194
+ }
5774
6195
  }
5775
- // ===== NPC Tracking =====
6196
+ // ===== Action Results Reporting =====
5776
6197
  /**
5777
- * Register an NPC for context management.
5778
- * @param npc The NPC client to register
6198
+ * Report action results back to the conversation
6199
+ * Call this after executing actions to let the NPC know the results
5779
6200
  */
5780
- registerNpc(npc) {
5781
- if (!npc)
5782
- return;
5783
- if (!this.npcStates.has(npc)) {
5784
- this.npcStates.set(npc, {
5785
- lastConversationTime: new Date(),
5786
- isCompacted: false,
5787
- compactionCount: 0,
6201
+ reportActionResults(results) {
6202
+ for (const [callId, result] of Object.entries(results)) {
6203
+ this.history.push({
6204
+ role: 'tool',
6205
+ tool_call_id: callId,
6206
+ content: result,
5788
6207
  });
5789
6208
  }
5790
6209
  }
5791
6210
  /**
5792
- * Unregister an NPC (call when NPC is destroyed/removed).
5793
- * @param npc The NPC client to unregister
6211
+ * Report a single action result
5794
6212
  */
5795
- unregisterNpc(npc) {
5796
- if (!npc)
5797
- return;
5798
- this.npcStates.delete(npc);
6213
+ reportActionResult(callId, result) {
6214
+ this.history.push({
6215
+ role: 'tool',
6216
+ tool_call_id: callId,
6217
+ content: result,
6218
+ });
5799
6219
  }
5800
6220
  /**
5801
- * Record that a conversation occurred with an NPC.
5802
- * Called after each Talk() exchange.
5803
- * @param npc The NPC client that had a conversation
6221
+ * Parse tool arguments from JSON string
5804
6222
  */
5805
- recordConversation(npc) {
5806
- if (!npc)
5807
- return;
5808
- if (!this.npcStates.has(npc)) {
5809
- this.registerNpc(npc);
6223
+ parseToolArguments(args) {
6224
+ try {
6225
+ return JSON.parse(args);
6226
+ }
6227
+ catch (_a) {
6228
+ return {};
5810
6229
  }
5811
- const state = this.npcStates.get(npc);
5812
- state.lastConversationTime = new Date();
5813
- state.isCompacted = false; // Reset compaction flag on new conversation
5814
6230
  }
6231
+ // ===== Conversation History Management =====
5815
6232
  /**
5816
- * Get all registered NPCs
6233
+ * Get conversation history
5817
6234
  */
5818
- getRegisteredNpcs() {
5819
- return Array.from(this.npcStates.keys());
6235
+ getHistory() {
6236
+ return [...this.history];
5820
6237
  }
5821
6238
  /**
5822
- * Get the conversation state for an NPC
6239
+ * Get the number of messages in history
5823
6240
  */
5824
- getNpcState(npc) {
5825
- return this.npcStates.get(npc);
6241
+ getHistoryLength() {
6242
+ return this.history.length;
5826
6243
  }
5827
- // ===== Auto Compaction =====
5828
6244
  /**
5829
- * Check if an NPC is eligible for compaction.
5830
- * @param npc The NPC to check
5831
- * @returns True if eligible for compaction
6245
+ * Clear conversation history.
6246
+ * The character design and memories will be preserved.
5832
6247
  */
5833
- isEligibleForCompaction(npc) {
5834
- if (!npc)
5835
- return false;
5836
- const state = this.npcStates.get(npc);
5837
- if (!state)
5838
- return false;
5839
- // Check if already compacted since last conversation
5840
- if (state.isCompacted)
5841
- return false;
5842
- // Check message count
5843
- const history = npc.getHistory();
5844
- const nonSystemMessages = history.filter(m => m.role !== 'system').length;
5845
- if (nonSystemMessages < this.config.autoCompactMinMessages)
5846
- return false;
5847
- // Check time since last conversation
5848
- const timeSinceLastConversation = (Date.now() - state.lastConversationTime.getTime()) / 1000;
5849
- if (timeSinceLastConversation < this.config.autoCompactTimeoutSeconds)
5850
- return false;
5851
- return true;
6248
+ clearHistory() {
6249
+ this.history = [];
6250
+ this.emit('history_cleared');
5852
6251
  }
5853
6252
  /**
5854
- * Manually trigger conversation compaction for a specific NPC.
5855
- * Summarizes the conversation history and stores it as a memory.
5856
- * @param npc The NPC to compact
5857
- * @returns True if compaction succeeded
6253
+ * Revert the last exchange (user message and assistant response) from history.
6254
+ * @returns true if reverted, false if not enough history
5858
6255
  */
5859
- async compactConversation(npc) {
5860
- if (!npc) {
5861
- this.logger.warn('Cannot compact: NPC is null');
5862
- return false;
5863
- }
5864
- if (!this.chatClientFactory) {
5865
- this.logger.error('Cannot compact: No chat client factory set. Call setChatClientFactory() first.');
5866
- return false;
5867
- }
5868
- const history = npc.getHistory();
5869
- const nonSystemMessages = history.filter(m => m.role !== 'system');
5870
- if (nonSystemMessages.length < 2) {
5871
- this.logger.info('Skipping compaction: not enough messages');
5872
- return false;
5873
- }
5874
- try {
5875
- this.logger.info(`Starting compaction (${nonSystemMessages.length} messages)`);
5876
- // Build conversation text for summarization
5877
- const conversationText = nonSystemMessages
5878
- .map(m => `${m.role}: ${m.content}`)
5879
- .join('\n');
5880
- // Create summarization prompt
5881
- const summaryPrompt = `Summarize the following conversation concisely. Focus on:
5882
- 1. Key topics discussed
5883
- 2. Important information exchanged
5884
- 3. Any decisions or commitments made
5885
- 4. The emotional tone
5886
-
5887
- Keep the summary under 200 words. Write in third person.
5888
-
5889
- Conversation:
5890
- ${conversationText}`;
5891
- // Use chat client for summarization
5892
- const chatClient = this.chatClientFactory();
5893
- const result = await chatClient.textGeneration({
5894
- messages: [{ role: 'user', content: summaryPrompt }],
5895
- temperature: 0.5,
5896
- model: this.config.fastModel || undefined,
5897
- });
5898
- if (!result.content) {
5899
- const error = 'Empty response from summarization';
5900
- this.logger.error(`Compaction failed: ${error}`);
5901
- this.emit('compactionFailed', npc, error);
5902
- return false;
6256
+ revertHistory() {
6257
+ let lastAssistantIndex = -1;
6258
+ let lastUserIndex = -1;
6259
+ for (let i = this.history.length - 1; i >= 0; i--) {
6260
+ if (this.history[i].role === 'assistant' && lastAssistantIndex === -1) {
6261
+ lastAssistantIndex = i;
5903
6262
  }
5904
- // Clear history and add summary as memory
5905
- npc.clearHistory();
5906
- npc.setMemory('PreviousConversationSummary', result.content);
5907
- // Update state
5908
- const state = this.npcStates.get(npc);
5909
- if (state) {
5910
- state.isCompacted = true;
5911
- state.compactionCount++;
6263
+ else if (this.history[i].role === 'user' && lastAssistantIndex !== -1 && lastUserIndex === -1) {
6264
+ lastUserIndex = i;
6265
+ break;
5912
6266
  }
5913
- this.logger.info(`Compaction completed. Summary: ${result.content.substring(0, 100)}...`);
5914
- this.emit('npcCompacted', npc);
5915
- return true;
5916
6267
  }
5917
- catch (error) {
5918
- const errorMessage = error instanceof Error ? error.message : String(error);
5919
- this.logger.error(`Compaction error: ${errorMessage}`);
5920
- this.emit('compactionFailed', npc, errorMessage);
5921
- return false;
6268
+ if (lastAssistantIndex !== -1 && lastUserIndex !== -1) {
6269
+ // Remove in reverse order to maintain indices
6270
+ this.history.splice(lastAssistantIndex, 1);
6271
+ this.history.splice(lastUserIndex, 1);
6272
+ this.emit('history_reverted');
6273
+ return true;
5922
6274
  }
6275
+ return false;
5923
6276
  }
5924
6277
  /**
5925
- * Compact all registered NPCs that meet the eligibility criteria.
5926
- * @returns Number of NPCs successfully compacted
6278
+ * Revert (remove) the last N chat messages from history
6279
+ * @param count Number of messages to remove
6280
+ * @returns Number of messages actually removed
5927
6281
  */
5928
- async compactAllEligible() {
5929
- const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
5930
- if (eligibleNpcs.length === 0) {
6282
+ revertChatMessages(count) {
6283
+ if (count <= 0)
5931
6284
  return 0;
6285
+ const messagesToRemove = Math.min(count, this.history.length);
6286
+ const originalCount = this.history.length;
6287
+ this.history = this.history.slice(0, -messagesToRemove);
6288
+ const actuallyRemoved = originalCount - this.history.length;
6289
+ if (actuallyRemoved > 0) {
6290
+ this.emit('history_reverted', actuallyRemoved);
5932
6291
  }
5933
- this.logger.info(`Compacting ${eligibleNpcs.length} eligible NPCs`);
5934
- let successCount = 0;
5935
- for (const npc of eligibleNpcs) {
5936
- const success = await this.compactConversation(npc);
5937
- if (success)
5938
- successCount++;
5939
- }
5940
- return successCount;
6292
+ return actuallyRemoved;
5941
6293
  }
5942
- // ===== Auto Compact Timer =====
5943
6294
  /**
5944
- * Start the auto-compact check timer
6295
+ * Revert to a specific point in history
6296
+ * @deprecated Use revertHistory() or revertChatMessages() instead
5945
6297
  */
5946
- startAutoCompactCheck() {
5947
- if (this.autoCompactTimer) {
5948
- this.stopAutoCompactCheck();
6298
+ revertToMessage(index) {
6299
+ if (index >= 0 && index < this.history.length) {
6300
+ this.history = this.history.slice(0, index + 1);
6301
+ this.emit('history_reverted', index);
5949
6302
  }
5950
- this.autoCompactTimer = setInterval(() => {
5951
- this.runAutoCompactCheck();
5952
- }, this.config.autoCompactCheckInterval);
5953
6303
  }
5954
6304
  /**
5955
- * Stop the auto-compact check timer
6305
+ * Append a message to history manually
5956
6306
  */
5957
- stopAutoCompactCheck() {
5958
- if (this.autoCompactTimer) {
5959
- clearInterval(this.autoCompactTimer);
5960
- this.autoCompactTimer = null;
5961
- }
6307
+ appendMessage(message) {
6308
+ this.history.push(message);
6309
+ this.trimHistory();
5962
6310
  }
5963
6311
  /**
5964
- * Run a single auto-compact check
6312
+ * Alias for appendMessage (Unity SDK compatibility)
5965
6313
  */
5966
- async runAutoCompactCheck() {
5967
- if (!this.config.enableAutoCompact)
6314
+ appendChatMessage(role, content) {
6315
+ if (!role || !content) {
6316
+ this.logger.warn('Role and content cannot be empty');
5968
6317
  return;
5969
- const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
5970
- for (const npc of eligibleNpcs) {
5971
- // Fire and forget - don't block
5972
- this.compactConversation(npc).catch(err => {
5973
- this.logger.error('Auto-compact error:', err);
5974
- });
5975
6318
  }
6319
+ this.appendMessage({ role: role, content });
5976
6320
  }
5977
- // ===== Lifecycle =====
5978
6321
  /**
5979
- * Enable auto-compaction
6322
+ * Trim history to max length
5980
6323
  */
5981
- enableAutoCompact() {
5982
- this.config.enableAutoCompact = true;
5983
- this.startAutoCompactCheck();
6324
+ trimHistory() {
6325
+ if (this.history.length > this.maxHistoryLength) {
6326
+ // Keep the most recent messages
6327
+ this.history = this.history.slice(-this.maxHistoryLength);
6328
+ }
5984
6329
  }
6330
+ // ===== Save/Load =====
5985
6331
  /**
5986
- * Disable auto-compaction
6332
+ * Save the current conversation history to a serializable format.
6333
+ * Includes characterDesign, memories, and history.
5987
6334
  */
5988
- disableAutoCompact() {
5989
- this.config.enableAutoCompact = false;
5990
- this.stopAutoCompactCheck();
6335
+ saveHistory() {
6336
+ const saveData = {
6337
+ characterDesign: this.characterDesign,
6338
+ memories: Array.from(this.memories.entries()).map(([name, content]) => ({ name, content })),
6339
+ history: this.history,
6340
+ };
6341
+ return JSON.stringify(saveData);
5991
6342
  }
5992
6343
  /**
5993
- * Clean up resources
6344
+ * Load conversation history from serialized data.
6345
+ * Restores characterDesign, memories, and history.
5994
6346
  */
5995
- destroy() {
5996
- this.stopAutoCompactCheck();
5997
- this.npcStates.clear();
5998
- this.playerDescription = null;
5999
- this.removeAllListeners();
6347
+ loadHistory(saveData) {
6348
+ try {
6349
+ const data = JSON.parse(saveData);
6350
+ // Load character design (with backwards compatibility for old systemPrompt field)
6351
+ this.characterDesign = data.characterDesign || data.systemPrompt || this.characterDesign;
6352
+ // Load memories
6353
+ this.memories.clear();
6354
+ if (data.memories && Array.isArray(data.memories)) {
6355
+ for (const memory of data.memories) {
6356
+ if (memory.name && memory.content) {
6357
+ this.memories.set(memory.name, memory.content);
6358
+ }
6359
+ }
6360
+ }
6361
+ // Load history (skip system messages as they'll be rebuilt from characterDesign + memories)
6362
+ this.history = (data.history || []).filter(m => m.role !== 'system');
6363
+ this.emit('history_loaded');
6364
+ return true;
6365
+ }
6366
+ catch (error) {
6367
+ this.logger.error('Failed to load history:', error);
6368
+ return false;
6369
+ }
6000
6370
  }
6001
6371
  }
6002
- AIContextManager._instance = null;
6003
- /**
6004
- * Default AIContextManager instance
6005
- * Can be used as a global context manager
6006
- */
6007
- const defaultContextManager = AIContextManager.getInstance();
6008
6372
 
6009
6373
  /**
6010
6374
  * Schema Library for managing JSON schemas for AI structured output generation
@@ -6216,10 +6580,12 @@ class PlayKitSDK extends EventEmitter {
6216
6580
  this.chatProvider = new ChatProvider(this.authManager, this.config);
6217
6581
  this.imageProvider = new ImageProvider(this.authManager, this.config);
6218
6582
  this.transcriptionProvider = new TranscriptionProvider(this.authManager, this.config);
6583
+ this.ttsProvider = new TTSProvider(this.authManager, this.config);
6219
6584
  // Connect providers to player client for balance checking
6220
6585
  this.chatProvider.setPlayerClient(this.playerClient);
6221
6586
  this.imageProvider.setPlayerClient(this.playerClient);
6222
6587
  this.transcriptionProvider.setPlayerClient(this.playerClient);
6588
+ this.ttsProvider.setPlayerClient(this.playerClient);
6223
6589
  // Initialize AI context manager
6224
6590
  this.contextManager = new AIContextManager(this.config.aiContext);
6225
6591
  // Set chat client factory for compaction
@@ -6363,20 +6729,20 @@ class PlayKitSDK extends EventEmitter {
6363
6729
  // Create indicator element
6364
6730
  this.devTokenIndicator = document.createElement('div');
6365
6731
  this.devTokenIndicator.textContent = 'DeveloperToken';
6366
- this.devTokenIndicator.style.cssText = `
6367
- position: fixed;
6368
- top: 10px;
6369
- left: 10px;
6370
- background-color: #dc2626;
6371
- color: white;
6372
- padding: 4px 12px;
6373
- border-radius: 4px;
6374
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
6375
- font-size: 12px;
6376
- font-weight: 600;
6377
- z-index: 999999;
6378
- pointer-events: none;
6379
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
6732
+ this.devTokenIndicator.style.cssText = `
6733
+ position: fixed;
6734
+ top: 10px;
6735
+ left: 10px;
6736
+ background-color: #dc2626;
6737
+ color: white;
6738
+ padding: 4px 12px;
6739
+ border-radius: 4px;
6740
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
6741
+ font-size: 12px;
6742
+ font-weight: 600;
6743
+ z-index: 999999;
6744
+ pointer-events: none;
6745
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
6380
6746
  `;
6381
6747
  document.body.appendChild(this.devTokenIndicator);
6382
6748
  }
@@ -6468,6 +6834,14 @@ class PlayKitSDK extends EventEmitter {
6468
6834
  this.ensureInitialized();
6469
6835
  return new TranscriptionClient(this.transcriptionProvider, model || this.config.defaultTranscriptionModel);
6470
6836
  }
6837
+ /**
6838
+ * Create a TTS client for text-to-speech
6839
+ * @param model - TTS model to use (default: 'default-tts-model')
6840
+ */
6841
+ createTTSClient(model) {
6842
+ this.ensureInitialized();
6843
+ return new TTSClient(this.ttsProvider, model || this.config.defaultTTSModel);
6844
+ }
6471
6845
  /**
6472
6846
  * Create an NPC client
6473
6847
  * Automatically registers with AIContextManager
@@ -6765,9 +7139,7 @@ class TokenValidator {
6765
7139
  */
6766
7140
  async validateToken(token, gameId) {
6767
7141
  var _a, _b;
6768
- const headers = {
6769
- 'Authorization': `Bearer ${token}`,
6770
- };
7142
+ const headers = Object.assign({ 'Authorization': `Bearer ${token}` }, getSDKHeaders());
6771
7143
  if (gameId) {
6772
7144
  headers['X-Game-Id'] = gameId;
6773
7145
  }
@@ -6797,9 +7169,7 @@ class TokenValidator {
6797
7169
  */
6798
7170
  async verifyToken(token, gameId) {
6799
7171
  var _a, _b;
6800
- const headers = {
6801
- 'Authorization': `Bearer ${token}`,
6802
- };
7172
+ const headers = Object.assign({ 'Authorization': `Bearer ${token}` }, getSDKHeaders());
6803
7173
  if (gameId) {
6804
7174
  headers['X-Game-Id'] = gameId;
6805
7175
  }
@@ -6862,5 +7232,5 @@ class TokenValidator {
6862
7232
  */
6863
7233
  const defaultTokenValidator = new TokenValidator();
6864
7234
 
6865
- export { AIContextManager, AuthFlowManager, AuthManager, BrowserStorage, BufferLogHandler, CallbackLogHandler, ChatClient, DeviceAuthFlowManager, ImageClient, LogLevel, Logger, MemoryStorage, NPCClient, PlayKitSDK, PlayerClient, RechargeManager, SchemaLibrary, StreamParser, TokenStorage, TokenValidator, TranscriptionClient, createMultimodalMessage, createStorage, createTextMessage, PlayKitSDK as default, defaultContextManager, defaultSchemaLibrary, defaultTokenValidator, isLocalStorageAvailable };
7235
+ export { AIContextManager, AuthFlowManager, AuthManager, BrowserStorage, BufferLogHandler, CallbackLogHandler, ChatClient, DeviceAuthFlowManager, ImageClient, LogLevel, Logger, MemoryStorage, NPCClient, PlayKitSDK, PlayerClient, RechargeManager, SchemaLibrary, StreamParser, TTSClient, TokenStorage, TokenValidator, TranscriptionClient, createMultimodalMessage, createStorage, createTextMessage, PlayKitSDK as default, defaultContextManager, defaultSchemaLibrary, defaultTokenValidator, isLocalStorageAvailable };
6866
7236
  //# sourceMappingURL=playkit-sdk.esm.js.map