playkit-sdk 1.1.4-beta.3 → 1.2.0-beta

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.1.4-beta.3
2
+ * playkit-sdk v1.2.0-beta
3
3
  * PlayKit SDK for JavaScript
4
4
  * @license SEE LICENSE IN LICENSE
5
5
  */
@@ -24,6 +24,54 @@ class PlayKitError extends Error {
24
24
  }
25
25
  }
26
26
 
27
+ /**
28
+ * Chat and text generation types
29
+ */
30
+ /**
31
+ * Helper to convert NpcAction to ChatTool
32
+ */
33
+ function npcActionToTool(action) {
34
+ var _a;
35
+ const properties = {};
36
+ const required = [];
37
+ for (const param of action.parameters || []) {
38
+ const propDef = { description: param.description };
39
+ switch (param.type) {
40
+ case 'string':
41
+ propDef.type = 'string';
42
+ break;
43
+ case 'number':
44
+ propDef.type = 'number';
45
+ break;
46
+ case 'boolean':
47
+ propDef.type = 'boolean';
48
+ break;
49
+ case 'stringEnum':
50
+ propDef.type = 'string';
51
+ if ((_a = param.enumOptions) === null || _a === void 0 ? void 0 : _a.length) {
52
+ propDef.enum = param.enumOptions;
53
+ }
54
+ break;
55
+ }
56
+ properties[param.name] = propDef;
57
+ if (param.required !== false) {
58
+ required.push(param.name);
59
+ }
60
+ }
61
+ return {
62
+ type: 'function',
63
+ function: {
64
+ name: action.actionName,
65
+ description: action.description,
66
+ parameters: {
67
+ type: 'object',
68
+ properties,
69
+ required,
70
+ },
71
+ },
72
+ };
73
+ }
74
+
27
75
  /**
28
76
  * Token storage with encryption using Web Crypto API
29
77
  * Stores tokens in localStorage with AES-128-GCM encryption
@@ -2460,30 +2508,199 @@ class ChatProvider {
2460
2508
  throw new PlayKitError(error instanceof Error ? error.message : 'Unknown error', 'CHAT_STREAM_ERROR');
2461
2509
  }
2462
2510
  }
2511
+ /**
2512
+ * Make a chat completion request with tools (non-streaming)
2513
+ */
2514
+ async chatCompletionWithTools(chatConfig) {
2515
+ var _a, _b;
2516
+ const token = this.authManager.getToken();
2517
+ if (!token) {
2518
+ throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
2519
+ }
2520
+ const model = chatConfig.model || this.config.defaultChatModel || 'gpt-4o-mini';
2521
+ const endpoint = `/ai/${this.config.gameId}/v1/chat`;
2522
+ const requestBody = {
2523
+ model,
2524
+ messages: chatConfig.messages,
2525
+ temperature: (_a = chatConfig.temperature) !== null && _a !== void 0 ? _a : 0.7,
2526
+ stream: false,
2527
+ max_tokens: chatConfig.maxTokens || null,
2528
+ seed: chatConfig.seed || null,
2529
+ stop: chatConfig.stop || null,
2530
+ top_p: chatConfig.topP || null,
2531
+ };
2532
+ // Add tools if provided
2533
+ if ((_b = chatConfig.tools) === null || _b === void 0 ? void 0 : _b.length) {
2534
+ requestBody.tools = chatConfig.tools;
2535
+ }
2536
+ if (chatConfig.tool_choice) {
2537
+ requestBody.tool_choice = chatConfig.tool_choice;
2538
+ }
2539
+ try {
2540
+ const response = await fetch(`${this.baseURL}${endpoint}`, {
2541
+ method: 'POST',
2542
+ headers: {
2543
+ Authorization: `Bearer ${token}`,
2544
+ 'Content-Type': 'application/json',
2545
+ },
2546
+ body: JSON.stringify(requestBody),
2547
+ });
2548
+ if (!response.ok) {
2549
+ const error = await response.json().catch(() => ({ message: 'Chat request failed' }));
2550
+ const playKitError = new PlayKitError(error.message || 'Chat request failed', error.code, response.status);
2551
+ if (error.code === 'INSUFFICIENT_CREDITS' || response.status === 402) {
2552
+ if (this.playerClient) {
2553
+ await this.playerClient.handleInsufficientCredits(playKitError);
2554
+ }
2555
+ }
2556
+ throw playKitError;
2557
+ }
2558
+ const result = await response.json();
2559
+ if (this.playerClient) {
2560
+ this.playerClient.checkBalanceAfterApiCall().catch(() => { });
2561
+ }
2562
+ return result;
2563
+ }
2564
+ catch (error) {
2565
+ if (error instanceof PlayKitError) {
2566
+ throw error;
2567
+ }
2568
+ throw new PlayKitError(error instanceof Error ? error.message : 'Unknown error', 'CHAT_ERROR');
2569
+ }
2570
+ }
2571
+ /**
2572
+ * Make a streaming chat completion request with tools
2573
+ */
2574
+ async chatCompletionWithToolsStream(chatConfig) {
2575
+ var _a, _b;
2576
+ const token = this.authManager.getToken();
2577
+ if (!token) {
2578
+ throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
2579
+ }
2580
+ const model = chatConfig.model || this.config.defaultChatModel || 'gpt-4o-mini';
2581
+ const endpoint = `/ai/${this.config.gameId}/v1/chat`;
2582
+ const requestBody = {
2583
+ model,
2584
+ messages: chatConfig.messages,
2585
+ temperature: (_a = chatConfig.temperature) !== null && _a !== void 0 ? _a : 0.7,
2586
+ stream: true,
2587
+ max_tokens: chatConfig.maxTokens || null,
2588
+ seed: chatConfig.seed || null,
2589
+ stop: chatConfig.stop || null,
2590
+ top_p: chatConfig.topP || null,
2591
+ };
2592
+ // Add tools if provided
2593
+ if ((_b = chatConfig.tools) === null || _b === void 0 ? void 0 : _b.length) {
2594
+ requestBody.tools = chatConfig.tools;
2595
+ }
2596
+ if (chatConfig.tool_choice) {
2597
+ requestBody.tool_choice = chatConfig.tool_choice;
2598
+ }
2599
+ try {
2600
+ const response = await fetch(`${this.baseURL}${endpoint}`, {
2601
+ method: 'POST',
2602
+ headers: {
2603
+ Authorization: `Bearer ${token}`,
2604
+ 'Content-Type': 'application/json',
2605
+ },
2606
+ body: JSON.stringify(requestBody),
2607
+ });
2608
+ if (!response.ok) {
2609
+ const error = await response.json().catch(() => ({ message: 'Chat stream request failed' }));
2610
+ const playKitError = new PlayKitError(error.message || 'Chat stream request failed', error.code, response.status);
2611
+ if (error.code === 'INSUFFICIENT_CREDITS' || response.status === 402) {
2612
+ if (this.playerClient) {
2613
+ await this.playerClient.handleInsufficientCredits(playKitError);
2614
+ }
2615
+ }
2616
+ throw playKitError;
2617
+ }
2618
+ if (!response.body) {
2619
+ throw new PlayKitError('Response body is null', 'NO_RESPONSE_BODY');
2620
+ }
2621
+ if (this.playerClient) {
2622
+ this.playerClient.checkBalanceAfterApiCall().catch(() => { });
2623
+ }
2624
+ return response.body.getReader();
2625
+ }
2626
+ catch (error) {
2627
+ if (error instanceof PlayKitError) {
2628
+ throw error;
2629
+ }
2630
+ throw new PlayKitError(error instanceof Error ? error.message : 'Unknown error', 'CHAT_STREAM_ERROR');
2631
+ }
2632
+ }
2463
2633
  /**
2464
2634
  * Generate structured output using JSON schema
2635
+ * Uses the /chat endpoint with response_format for structured output
2465
2636
  */
2466
- async generateStructured(schemaName, prompt, model, temperature) {
2637
+ async generateStructured(schemaName, prompt, model, temperature, schema, schemaDescription) {
2467
2638
  var _a;
2639
+ const token = this.authManager.getToken();
2640
+ if (!token) {
2641
+ throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
2642
+ }
2643
+ const modelToUse = model || this.config.defaultChatModel || 'gpt-4o-mini';
2644
+ const endpoint = `/ai/${this.config.gameId}/v1/chat`;
2468
2645
  const messages = [{ role: 'user', content: prompt }];
2469
- const chatConfig = {
2646
+ const requestBody = {
2647
+ model: modelToUse,
2470
2648
  messages,
2471
- model: model || this.config.defaultChatModel,
2472
2649
  temperature: temperature !== null && temperature !== void 0 ? temperature : 0.7,
2650
+ stream: false,
2473
2651
  };
2474
- // Add schema information to the request
2475
- // (Implementation depends on how the API handles structured output)
2476
- const response = await this.chatCompletion(chatConfig);
2477
- // Parse the response content as JSON
2478
- const content = (_a = response.choices[0]) === null || _a === void 0 ? void 0 : _a.message.content;
2479
- if (!content) {
2480
- throw new PlayKitError('No content in response', 'NO_CONTENT');
2652
+ // Add response_format with json_schema if schema is provided
2653
+ if (schema) {
2654
+ requestBody.response_format = {
2655
+ type: 'json_schema',
2656
+ json_schema: {
2657
+ name: schemaName,
2658
+ description: schemaDescription || '',
2659
+ schema: schema,
2660
+ strict: true,
2661
+ },
2662
+ };
2481
2663
  }
2482
2664
  try {
2483
- return JSON.parse(content);
2665
+ const response = await fetch(`${this.baseURL}${endpoint}`, {
2666
+ method: 'POST',
2667
+ headers: {
2668
+ Authorization: `Bearer ${token}`,
2669
+ 'Content-Type': 'application/json',
2670
+ },
2671
+ body: JSON.stringify(requestBody),
2672
+ });
2673
+ if (!response.ok) {
2674
+ const error = await response.json().catch(() => ({ message: 'Structured generation failed' }));
2675
+ const playKitError = new PlayKitError(error.message || 'Structured generation failed', error.code, response.status);
2676
+ if (error.code === 'INSUFFICIENT_CREDITS' || response.status === 402) {
2677
+ if (this.playerClient) {
2678
+ await this.playerClient.handleInsufficientCredits(playKitError);
2679
+ }
2680
+ }
2681
+ throw playKitError;
2682
+ }
2683
+ const result = await response.json();
2684
+ if (this.playerClient) {
2685
+ this.playerClient.checkBalanceAfterApiCall().catch(() => { });
2686
+ }
2687
+ // Parse the response content as JSON
2688
+ const content = (_a = result.choices[0]) === null || _a === void 0 ? void 0 : _a.message.content;
2689
+ if (!content) {
2690
+ throw new PlayKitError('No content in response', 'NO_CONTENT');
2691
+ }
2692
+ try {
2693
+ return JSON.parse(content);
2694
+ }
2695
+ catch (parseError) {
2696
+ throw new PlayKitError('Failed to parse structured output', 'PARSE_ERROR');
2697
+ }
2484
2698
  }
2485
2699
  catch (error) {
2486
- throw new PlayKitError('Failed to parse structured output', 'PARSE_ERROR');
2700
+ if (error instanceof PlayKitError) {
2701
+ throw error;
2702
+ }
2703
+ throw new PlayKitError(error instanceof Error ? error.message : 'Unknown error', 'STRUCTURED_GENERATION_ERROR');
2487
2704
  }
2488
2705
  }
2489
2706
  }
@@ -2839,6 +3056,56 @@ class ChatClient {
2839
3056
  onComplete,
2840
3057
  });
2841
3058
  }
3059
+ /**
3060
+ * Generate text with tool calling support (non-streaming)
3061
+ */
3062
+ async textGenerationWithTools(config) {
3063
+ const chatConfig = Object.assign(Object.assign({}, config), { model: config.model || this.model });
3064
+ const response = await this.provider.chatCompletionWithTools(chatConfig);
3065
+ const choice = response.choices[0];
3066
+ if (!choice) {
3067
+ throw new Error('No choices in response');
3068
+ }
3069
+ return {
3070
+ content: choice.message.content || '',
3071
+ model: response.model,
3072
+ finishReason: choice.finish_reason,
3073
+ usage: response.usage
3074
+ ? {
3075
+ promptTokens: response.usage.prompt_tokens,
3076
+ completionTokens: response.usage.completion_tokens,
3077
+ totalTokens: response.usage.total_tokens,
3078
+ }
3079
+ : undefined,
3080
+ id: response.id,
3081
+ created: response.created,
3082
+ tool_calls: choice.message.tool_calls,
3083
+ };
3084
+ }
3085
+ /**
3086
+ * Generate text with tool calling support (streaming)
3087
+ * Text streams first, complete result with tool_calls returned in onComplete
3088
+ */
3089
+ async textGenerationWithToolsStream(config) {
3090
+ const chatConfig = Object.assign(Object.assign({}, config), { model: config.model || this.model });
3091
+ const reader = await this.provider.chatCompletionWithToolsStream(chatConfig);
3092
+ let fullContent = '';
3093
+ let toolCalls = [];
3094
+ await StreamParser.streamWithCallbacks(reader, (chunk) => {
3095
+ fullContent += chunk;
3096
+ config.onChunk(chunk);
3097
+ }, () => {
3098
+ // On complete, provide full result
3099
+ if (config.onComplete) {
3100
+ config.onComplete({
3101
+ content: fullContent,
3102
+ model: chatConfig.model || this.model,
3103
+ finishReason: toolCalls.length > 0 ? 'tool_calls' : 'stop',
3104
+ tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
3105
+ });
3106
+ }
3107
+ }, config.onError);
3108
+ }
2842
3109
  }
2843
3110
 
2844
3111
  /**
@@ -2988,8 +3255,10 @@ class NPCClient extends EventEmitter {
2988
3255
  }
2989
3256
  /**
2990
3257
  * Talk with structured output
3258
+ * @deprecated Use talkWithActions instead for NPC decision-making with actions
2991
3259
  */
2992
3260
  async talkStructured(message, schemaName) {
3261
+ console.warn('[NPCClient] talkStructured is deprecated. Use talkWithActions instead for NPC decision-making with actions.');
2993
3262
  // Add user message to history
2994
3263
  const userMessage = { role: 'user', content: message };
2995
3264
  this.history.push(userMessage);
@@ -3009,6 +3278,153 @@ class NPCClient extends EventEmitter {
3009
3278
  this.trimHistory();
3010
3279
  return result;
3011
3280
  }
3281
+ /**
3282
+ * Talk to the NPC with available actions (non-streaming)
3283
+ * @param message The message to send
3284
+ * @param actions List of actions the NPC can perform
3285
+ * @returns Response containing text and any action calls
3286
+ */
3287
+ async talkWithActions(message, actions) {
3288
+ // Add user message to history
3289
+ const userMessage = { role: 'user', content: message };
3290
+ this.history.push(userMessage);
3291
+ // Convert NpcActions to ChatTools
3292
+ const tools = actions
3293
+ .filter(a => a && a.enabled !== false)
3294
+ .map(a => npcActionToTool(a));
3295
+ // Build messages array with system prompt
3296
+ const messages = [
3297
+ { role: 'system', content: this.systemPrompt },
3298
+ ...this.history,
3299
+ ];
3300
+ // Generate response with tools
3301
+ const result = await this.chatClient.textGenerationWithTools({
3302
+ messages,
3303
+ temperature: this.temperature,
3304
+ tools,
3305
+ tool_choice: 'auto',
3306
+ });
3307
+ // Build response
3308
+ const response = {
3309
+ text: result.content || '',
3310
+ actionCalls: [],
3311
+ hasActions: false,
3312
+ };
3313
+ // Extract tool calls if any
3314
+ if (result.tool_calls) {
3315
+ response.actionCalls = result.tool_calls.map(tc => ({
3316
+ id: tc.id,
3317
+ actionName: tc.function.name,
3318
+ arguments: this.parseToolArguments(tc.function.arguments),
3319
+ }));
3320
+ response.hasActions = response.actionCalls.length > 0;
3321
+ }
3322
+ // Add assistant response to history
3323
+ const assistantMessage = {
3324
+ role: 'assistant',
3325
+ content: response.text,
3326
+ tool_calls: result.tool_calls,
3327
+ };
3328
+ this.history.push(assistantMessage);
3329
+ this.trimHistory();
3330
+ this.emit('response', response.text);
3331
+ if (response.hasActions) {
3332
+ this.emit('actions', response.actionCalls);
3333
+ }
3334
+ return response;
3335
+ }
3336
+ /**
3337
+ * Talk to the NPC with actions (streaming)
3338
+ * Text streams first, action calls are returned in onComplete
3339
+ */
3340
+ async talkWithActionsStream(message, actions, onChunk, onComplete) {
3341
+ // Add user message to history
3342
+ const userMessage = { role: 'user', content: message };
3343
+ this.history.push(userMessage);
3344
+ // Convert NpcActions to ChatTools
3345
+ const tools = actions
3346
+ .filter(a => a && a.enabled !== false)
3347
+ .map(a => npcActionToTool(a));
3348
+ // Build messages array with system prompt
3349
+ const messages = [
3350
+ { role: 'system', content: this.systemPrompt },
3351
+ ...this.history,
3352
+ ];
3353
+ // Generate response with tools (streaming)
3354
+ await this.chatClient.textGenerationWithToolsStream({
3355
+ messages,
3356
+ temperature: this.temperature,
3357
+ tools,
3358
+ tool_choice: 'auto',
3359
+ onChunk,
3360
+ onComplete: (result) => {
3361
+ // Build response
3362
+ const response = {
3363
+ text: result.content || '',
3364
+ actionCalls: [],
3365
+ hasActions: false,
3366
+ };
3367
+ // Extract tool calls if any
3368
+ if (result.tool_calls) {
3369
+ response.actionCalls = result.tool_calls.map(tc => ({
3370
+ id: tc.id,
3371
+ actionName: tc.function.name,
3372
+ arguments: this.parseToolArguments(tc.function.arguments),
3373
+ }));
3374
+ response.hasActions = response.actionCalls.length > 0;
3375
+ }
3376
+ // Add assistant response to history
3377
+ const assistantMessage = {
3378
+ role: 'assistant',
3379
+ content: response.text,
3380
+ tool_calls: result.tool_calls,
3381
+ };
3382
+ this.history.push(assistantMessage);
3383
+ this.trimHistory();
3384
+ this.emit('response', response.text);
3385
+ if (response.hasActions) {
3386
+ this.emit('actions', response.actionCalls);
3387
+ }
3388
+ if (onComplete) {
3389
+ onComplete(response);
3390
+ }
3391
+ },
3392
+ });
3393
+ }
3394
+ /**
3395
+ * Report action results back to the conversation
3396
+ * Call this after executing actions to let the NPC know the results
3397
+ */
3398
+ reportActionResults(results) {
3399
+ for (const [callId, result] of Object.entries(results)) {
3400
+ this.history.push({
3401
+ role: 'tool',
3402
+ tool_call_id: callId,
3403
+ content: result,
3404
+ });
3405
+ }
3406
+ }
3407
+ /**
3408
+ * Report a single action result
3409
+ */
3410
+ reportActionResult(callId, result) {
3411
+ this.history.push({
3412
+ role: 'tool',
3413
+ tool_call_id: callId,
3414
+ content: result,
3415
+ });
3416
+ }
3417
+ /**
3418
+ * Parse tool arguments from JSON string
3419
+ */
3420
+ parseToolArguments(args) {
3421
+ try {
3422
+ return JSON.parse(args);
3423
+ }
3424
+ catch (_a) {
3425
+ return {};
3426
+ }
3427
+ }
3012
3428
  /**
3013
3429
  * Get conversation history
3014
3430
  */
@@ -3152,12 +3568,27 @@ class PlayKitSDK extends EventEmitter {
3152
3568
  }
3153
3569
  }
3154
3570
  catch (error) {
3155
- // If token is invalid, logout and re-throw error
3571
+ // If token is invalid, logout and restart auth flow
3156
3572
  if (this.config.debug) {
3157
3573
  console.error('[PlayKitSDK] Token validation failed:', error);
3158
3574
  }
3159
3575
  await this.authManager.logout();
3160
- throw new Error('Token validation failed: ' + (error instanceof Error ? error.message : String(error)));
3576
+ // Auto-restart login flow in browser environment
3577
+ if (typeof window !== 'undefined') {
3578
+ if (this.config.debug) {
3579
+ console.log('[PlayKitSDK] Restarting authentication flow...');
3580
+ }
3581
+ const useExternalAuth = this.config.authMethod === 'external-auth';
3582
+ await this.authManager.startAuthFlow(useExternalAuth);
3583
+ // Retry getting player info after re-authentication
3584
+ await this.playerClient.getPlayerInfo();
3585
+ if (this.config.debug) {
3586
+ console.log('[PlayKitSDK] Re-authentication successful, token validated');
3587
+ }
3588
+ }
3589
+ else {
3590
+ throw new Error('Token validation failed: ' + (error instanceof Error ? error.message : String(error)));
3591
+ }
3161
3592
  }
3162
3593
  }
3163
3594
  this.emit('ready');