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
  */
@@ -371,6 +371,54 @@
371
371
  }
372
372
  }
373
373
 
374
+ /**
375
+ * Chat and text generation types
376
+ */
377
+ /**
378
+ * Helper to convert NpcAction to ChatTool
379
+ */
380
+ function npcActionToTool(action) {
381
+ var _a;
382
+ const properties = {};
383
+ const required = [];
384
+ for (const param of action.parameters || []) {
385
+ const propDef = { description: param.description };
386
+ switch (param.type) {
387
+ case 'string':
388
+ propDef.type = 'string';
389
+ break;
390
+ case 'number':
391
+ propDef.type = 'number';
392
+ break;
393
+ case 'boolean':
394
+ propDef.type = 'boolean';
395
+ break;
396
+ case 'stringEnum':
397
+ propDef.type = 'string';
398
+ if ((_a = param.enumOptions) === null || _a === void 0 ? void 0 : _a.length) {
399
+ propDef.enum = param.enumOptions;
400
+ }
401
+ break;
402
+ }
403
+ properties[param.name] = propDef;
404
+ if (param.required !== false) {
405
+ required.push(param.name);
406
+ }
407
+ }
408
+ return {
409
+ type: 'function',
410
+ function: {
411
+ name: action.actionName,
412
+ description: action.description,
413
+ parameters: {
414
+ type: 'object',
415
+ properties,
416
+ required,
417
+ },
418
+ },
419
+ };
420
+ }
421
+
374
422
  /**
375
423
  * Token storage with encryption using Web Crypto API
376
424
  * Stores tokens in localStorage with AES-128-GCM encryption
@@ -2807,30 +2855,199 @@
2807
2855
  throw new PlayKitError(error instanceof Error ? error.message : 'Unknown error', 'CHAT_STREAM_ERROR');
2808
2856
  }
2809
2857
  }
2858
+ /**
2859
+ * Make a chat completion request with tools (non-streaming)
2860
+ */
2861
+ async chatCompletionWithTools(chatConfig) {
2862
+ var _a, _b;
2863
+ const token = this.authManager.getToken();
2864
+ if (!token) {
2865
+ throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
2866
+ }
2867
+ const model = chatConfig.model || this.config.defaultChatModel || 'gpt-4o-mini';
2868
+ const endpoint = `/ai/${this.config.gameId}/v1/chat`;
2869
+ const requestBody = {
2870
+ model,
2871
+ messages: chatConfig.messages,
2872
+ temperature: (_a = chatConfig.temperature) !== null && _a !== void 0 ? _a : 0.7,
2873
+ stream: false,
2874
+ max_tokens: chatConfig.maxTokens || null,
2875
+ seed: chatConfig.seed || null,
2876
+ stop: chatConfig.stop || null,
2877
+ top_p: chatConfig.topP || null,
2878
+ };
2879
+ // Add tools if provided
2880
+ if ((_b = chatConfig.tools) === null || _b === void 0 ? void 0 : _b.length) {
2881
+ requestBody.tools = chatConfig.tools;
2882
+ }
2883
+ if (chatConfig.tool_choice) {
2884
+ requestBody.tool_choice = chatConfig.tool_choice;
2885
+ }
2886
+ try {
2887
+ const response = await fetch(`${this.baseURL}${endpoint}`, {
2888
+ method: 'POST',
2889
+ headers: {
2890
+ Authorization: `Bearer ${token}`,
2891
+ 'Content-Type': 'application/json',
2892
+ },
2893
+ body: JSON.stringify(requestBody),
2894
+ });
2895
+ if (!response.ok) {
2896
+ const error = await response.json().catch(() => ({ message: 'Chat request failed' }));
2897
+ const playKitError = new PlayKitError(error.message || 'Chat request failed', error.code, response.status);
2898
+ if (error.code === 'INSUFFICIENT_CREDITS' || response.status === 402) {
2899
+ if (this.playerClient) {
2900
+ await this.playerClient.handleInsufficientCredits(playKitError);
2901
+ }
2902
+ }
2903
+ throw playKitError;
2904
+ }
2905
+ const result = await response.json();
2906
+ if (this.playerClient) {
2907
+ this.playerClient.checkBalanceAfterApiCall().catch(() => { });
2908
+ }
2909
+ return result;
2910
+ }
2911
+ catch (error) {
2912
+ if (error instanceof PlayKitError) {
2913
+ throw error;
2914
+ }
2915
+ throw new PlayKitError(error instanceof Error ? error.message : 'Unknown error', 'CHAT_ERROR');
2916
+ }
2917
+ }
2918
+ /**
2919
+ * Make a streaming chat completion request with tools
2920
+ */
2921
+ async chatCompletionWithToolsStream(chatConfig) {
2922
+ var _a, _b;
2923
+ const token = this.authManager.getToken();
2924
+ if (!token) {
2925
+ throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
2926
+ }
2927
+ const model = chatConfig.model || this.config.defaultChatModel || 'gpt-4o-mini';
2928
+ const endpoint = `/ai/${this.config.gameId}/v1/chat`;
2929
+ const requestBody = {
2930
+ model,
2931
+ messages: chatConfig.messages,
2932
+ temperature: (_a = chatConfig.temperature) !== null && _a !== void 0 ? _a : 0.7,
2933
+ stream: true,
2934
+ max_tokens: chatConfig.maxTokens || null,
2935
+ seed: chatConfig.seed || null,
2936
+ stop: chatConfig.stop || null,
2937
+ top_p: chatConfig.topP || null,
2938
+ };
2939
+ // Add tools if provided
2940
+ if ((_b = chatConfig.tools) === null || _b === void 0 ? void 0 : _b.length) {
2941
+ requestBody.tools = chatConfig.tools;
2942
+ }
2943
+ if (chatConfig.tool_choice) {
2944
+ requestBody.tool_choice = chatConfig.tool_choice;
2945
+ }
2946
+ try {
2947
+ const response = await fetch(`${this.baseURL}${endpoint}`, {
2948
+ method: 'POST',
2949
+ headers: {
2950
+ Authorization: `Bearer ${token}`,
2951
+ 'Content-Type': 'application/json',
2952
+ },
2953
+ body: JSON.stringify(requestBody),
2954
+ });
2955
+ if (!response.ok) {
2956
+ const error = await response.json().catch(() => ({ message: 'Chat stream request failed' }));
2957
+ const playKitError = new PlayKitError(error.message || 'Chat stream request failed', error.code, response.status);
2958
+ if (error.code === 'INSUFFICIENT_CREDITS' || response.status === 402) {
2959
+ if (this.playerClient) {
2960
+ await this.playerClient.handleInsufficientCredits(playKitError);
2961
+ }
2962
+ }
2963
+ throw playKitError;
2964
+ }
2965
+ if (!response.body) {
2966
+ throw new PlayKitError('Response body is null', 'NO_RESPONSE_BODY');
2967
+ }
2968
+ if (this.playerClient) {
2969
+ this.playerClient.checkBalanceAfterApiCall().catch(() => { });
2970
+ }
2971
+ return response.body.getReader();
2972
+ }
2973
+ catch (error) {
2974
+ if (error instanceof PlayKitError) {
2975
+ throw error;
2976
+ }
2977
+ throw new PlayKitError(error instanceof Error ? error.message : 'Unknown error', 'CHAT_STREAM_ERROR');
2978
+ }
2979
+ }
2810
2980
  /**
2811
2981
  * Generate structured output using JSON schema
2982
+ * Uses the /chat endpoint with response_format for structured output
2812
2983
  */
2813
- async generateStructured(schemaName, prompt, model, temperature) {
2984
+ async generateStructured(schemaName, prompt, model, temperature, schema, schemaDescription) {
2814
2985
  var _a;
2986
+ const token = this.authManager.getToken();
2987
+ if (!token) {
2988
+ throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
2989
+ }
2990
+ const modelToUse = model || this.config.defaultChatModel || 'gpt-4o-mini';
2991
+ const endpoint = `/ai/${this.config.gameId}/v1/chat`;
2815
2992
  const messages = [{ role: 'user', content: prompt }];
2816
- const chatConfig = {
2993
+ const requestBody = {
2994
+ model: modelToUse,
2817
2995
  messages,
2818
- model: model || this.config.defaultChatModel,
2819
2996
  temperature: temperature !== null && temperature !== void 0 ? temperature : 0.7,
2997
+ stream: false,
2820
2998
  };
2821
- // Add schema information to the request
2822
- // (Implementation depends on how the API handles structured output)
2823
- const response = await this.chatCompletion(chatConfig);
2824
- // Parse the response content as JSON
2825
- const content = (_a = response.choices[0]) === null || _a === void 0 ? void 0 : _a.message.content;
2826
- if (!content) {
2827
- throw new PlayKitError('No content in response', 'NO_CONTENT');
2999
+ // Add response_format with json_schema if schema is provided
3000
+ if (schema) {
3001
+ requestBody.response_format = {
3002
+ type: 'json_schema',
3003
+ json_schema: {
3004
+ name: schemaName,
3005
+ description: schemaDescription || '',
3006
+ schema: schema,
3007
+ strict: true,
3008
+ },
3009
+ };
2828
3010
  }
2829
3011
  try {
2830
- return JSON.parse(content);
3012
+ const response = await fetch(`${this.baseURL}${endpoint}`, {
3013
+ method: 'POST',
3014
+ headers: {
3015
+ Authorization: `Bearer ${token}`,
3016
+ 'Content-Type': 'application/json',
3017
+ },
3018
+ body: JSON.stringify(requestBody),
3019
+ });
3020
+ if (!response.ok) {
3021
+ const error = await response.json().catch(() => ({ message: 'Structured generation failed' }));
3022
+ const playKitError = new PlayKitError(error.message || 'Structured generation failed', error.code, response.status);
3023
+ if (error.code === 'INSUFFICIENT_CREDITS' || response.status === 402) {
3024
+ if (this.playerClient) {
3025
+ await this.playerClient.handleInsufficientCredits(playKitError);
3026
+ }
3027
+ }
3028
+ throw playKitError;
3029
+ }
3030
+ const result = await response.json();
3031
+ if (this.playerClient) {
3032
+ this.playerClient.checkBalanceAfterApiCall().catch(() => { });
3033
+ }
3034
+ // Parse the response content as JSON
3035
+ const content = (_a = result.choices[0]) === null || _a === void 0 ? void 0 : _a.message.content;
3036
+ if (!content) {
3037
+ throw new PlayKitError('No content in response', 'NO_CONTENT');
3038
+ }
3039
+ try {
3040
+ return JSON.parse(content);
3041
+ }
3042
+ catch (parseError) {
3043
+ throw new PlayKitError('Failed to parse structured output', 'PARSE_ERROR');
3044
+ }
2831
3045
  }
2832
3046
  catch (error) {
2833
- throw new PlayKitError('Failed to parse structured output', 'PARSE_ERROR');
3047
+ if (error instanceof PlayKitError) {
3048
+ throw error;
3049
+ }
3050
+ throw new PlayKitError(error instanceof Error ? error.message : 'Unknown error', 'STRUCTURED_GENERATION_ERROR');
2834
3051
  }
2835
3052
  }
2836
3053
  }
@@ -3186,6 +3403,56 @@
3186
3403
  onComplete,
3187
3404
  });
3188
3405
  }
3406
+ /**
3407
+ * Generate text with tool calling support (non-streaming)
3408
+ */
3409
+ async textGenerationWithTools(config) {
3410
+ const chatConfig = Object.assign(Object.assign({}, config), { model: config.model || this.model });
3411
+ const response = await this.provider.chatCompletionWithTools(chatConfig);
3412
+ const choice = response.choices[0];
3413
+ if (!choice) {
3414
+ throw new Error('No choices in response');
3415
+ }
3416
+ return {
3417
+ content: choice.message.content || '',
3418
+ model: response.model,
3419
+ finishReason: choice.finish_reason,
3420
+ usage: response.usage
3421
+ ? {
3422
+ promptTokens: response.usage.prompt_tokens,
3423
+ completionTokens: response.usage.completion_tokens,
3424
+ totalTokens: response.usage.total_tokens,
3425
+ }
3426
+ : undefined,
3427
+ id: response.id,
3428
+ created: response.created,
3429
+ tool_calls: choice.message.tool_calls,
3430
+ };
3431
+ }
3432
+ /**
3433
+ * Generate text with tool calling support (streaming)
3434
+ * Text streams first, complete result with tool_calls returned in onComplete
3435
+ */
3436
+ async textGenerationWithToolsStream(config) {
3437
+ const chatConfig = Object.assign(Object.assign({}, config), { model: config.model || this.model });
3438
+ const reader = await this.provider.chatCompletionWithToolsStream(chatConfig);
3439
+ let fullContent = '';
3440
+ let toolCalls = [];
3441
+ await StreamParser.streamWithCallbacks(reader, (chunk) => {
3442
+ fullContent += chunk;
3443
+ config.onChunk(chunk);
3444
+ }, () => {
3445
+ // On complete, provide full result
3446
+ if (config.onComplete) {
3447
+ config.onComplete({
3448
+ content: fullContent,
3449
+ model: chatConfig.model || this.model,
3450
+ finishReason: toolCalls.length > 0 ? 'tool_calls' : 'stop',
3451
+ tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
3452
+ });
3453
+ }
3454
+ }, config.onError);
3455
+ }
3189
3456
  }
3190
3457
 
3191
3458
  /**
@@ -3335,8 +3602,10 @@
3335
3602
  }
3336
3603
  /**
3337
3604
  * Talk with structured output
3605
+ * @deprecated Use talkWithActions instead for NPC decision-making with actions
3338
3606
  */
3339
3607
  async talkStructured(message, schemaName) {
3608
+ console.warn('[NPCClient] talkStructured is deprecated. Use talkWithActions instead for NPC decision-making with actions.');
3340
3609
  // Add user message to history
3341
3610
  const userMessage = { role: 'user', content: message };
3342
3611
  this.history.push(userMessage);
@@ -3356,6 +3625,153 @@
3356
3625
  this.trimHistory();
3357
3626
  return result;
3358
3627
  }
3628
+ /**
3629
+ * Talk to the NPC with available actions (non-streaming)
3630
+ * @param message The message to send
3631
+ * @param actions List of actions the NPC can perform
3632
+ * @returns Response containing text and any action calls
3633
+ */
3634
+ async talkWithActions(message, actions) {
3635
+ // Add user message to history
3636
+ const userMessage = { role: 'user', content: message };
3637
+ this.history.push(userMessage);
3638
+ // Convert NpcActions to ChatTools
3639
+ const tools = actions
3640
+ .filter(a => a && a.enabled !== false)
3641
+ .map(a => npcActionToTool(a));
3642
+ // Build messages array with system prompt
3643
+ const messages = [
3644
+ { role: 'system', content: this.systemPrompt },
3645
+ ...this.history,
3646
+ ];
3647
+ // Generate response with tools
3648
+ const result = await this.chatClient.textGenerationWithTools({
3649
+ messages,
3650
+ temperature: this.temperature,
3651
+ tools,
3652
+ tool_choice: 'auto',
3653
+ });
3654
+ // Build response
3655
+ const response = {
3656
+ text: result.content || '',
3657
+ actionCalls: [],
3658
+ hasActions: false,
3659
+ };
3660
+ // Extract tool calls if any
3661
+ if (result.tool_calls) {
3662
+ response.actionCalls = result.tool_calls.map(tc => ({
3663
+ id: tc.id,
3664
+ actionName: tc.function.name,
3665
+ arguments: this.parseToolArguments(tc.function.arguments),
3666
+ }));
3667
+ response.hasActions = response.actionCalls.length > 0;
3668
+ }
3669
+ // Add assistant response to history
3670
+ const assistantMessage = {
3671
+ role: 'assistant',
3672
+ content: response.text,
3673
+ tool_calls: result.tool_calls,
3674
+ };
3675
+ this.history.push(assistantMessage);
3676
+ this.trimHistory();
3677
+ this.emit('response', response.text);
3678
+ if (response.hasActions) {
3679
+ this.emit('actions', response.actionCalls);
3680
+ }
3681
+ return response;
3682
+ }
3683
+ /**
3684
+ * Talk to the NPC with actions (streaming)
3685
+ * Text streams first, action calls are returned in onComplete
3686
+ */
3687
+ async talkWithActionsStream(message, actions, onChunk, onComplete) {
3688
+ // Add user message to history
3689
+ const userMessage = { role: 'user', content: message };
3690
+ this.history.push(userMessage);
3691
+ // Convert NpcActions to ChatTools
3692
+ const tools = actions
3693
+ .filter(a => a && a.enabled !== false)
3694
+ .map(a => npcActionToTool(a));
3695
+ // Build messages array with system prompt
3696
+ const messages = [
3697
+ { role: 'system', content: this.systemPrompt },
3698
+ ...this.history,
3699
+ ];
3700
+ // Generate response with tools (streaming)
3701
+ await this.chatClient.textGenerationWithToolsStream({
3702
+ messages,
3703
+ temperature: this.temperature,
3704
+ tools,
3705
+ tool_choice: 'auto',
3706
+ onChunk,
3707
+ onComplete: (result) => {
3708
+ // Build response
3709
+ const response = {
3710
+ text: result.content || '',
3711
+ actionCalls: [],
3712
+ hasActions: false,
3713
+ };
3714
+ // Extract tool calls if any
3715
+ if (result.tool_calls) {
3716
+ response.actionCalls = result.tool_calls.map(tc => ({
3717
+ id: tc.id,
3718
+ actionName: tc.function.name,
3719
+ arguments: this.parseToolArguments(tc.function.arguments),
3720
+ }));
3721
+ response.hasActions = response.actionCalls.length > 0;
3722
+ }
3723
+ // Add assistant response to history
3724
+ const assistantMessage = {
3725
+ role: 'assistant',
3726
+ content: response.text,
3727
+ tool_calls: result.tool_calls,
3728
+ };
3729
+ this.history.push(assistantMessage);
3730
+ this.trimHistory();
3731
+ this.emit('response', response.text);
3732
+ if (response.hasActions) {
3733
+ this.emit('actions', response.actionCalls);
3734
+ }
3735
+ if (onComplete) {
3736
+ onComplete(response);
3737
+ }
3738
+ },
3739
+ });
3740
+ }
3741
+ /**
3742
+ * Report action results back to the conversation
3743
+ * Call this after executing actions to let the NPC know the results
3744
+ */
3745
+ reportActionResults(results) {
3746
+ for (const [callId, result] of Object.entries(results)) {
3747
+ this.history.push({
3748
+ role: 'tool',
3749
+ tool_call_id: callId,
3750
+ content: result,
3751
+ });
3752
+ }
3753
+ }
3754
+ /**
3755
+ * Report a single action result
3756
+ */
3757
+ reportActionResult(callId, result) {
3758
+ this.history.push({
3759
+ role: 'tool',
3760
+ tool_call_id: callId,
3761
+ content: result,
3762
+ });
3763
+ }
3764
+ /**
3765
+ * Parse tool arguments from JSON string
3766
+ */
3767
+ parseToolArguments(args) {
3768
+ try {
3769
+ return JSON.parse(args);
3770
+ }
3771
+ catch (_a) {
3772
+ return {};
3773
+ }
3774
+ }
3359
3775
  /**
3360
3776
  * Get conversation history
3361
3777
  */
@@ -3499,12 +3915,27 @@
3499
3915
  }
3500
3916
  }
3501
3917
  catch (error) {
3502
- // If token is invalid, logout and re-throw error
3918
+ // If token is invalid, logout and restart auth flow
3503
3919
  if (this.config.debug) {
3504
3920
  console.error('[PlayKitSDK] Token validation failed:', error);
3505
3921
  }
3506
3922
  await this.authManager.logout();
3507
- throw new Error('Token validation failed: ' + (error instanceof Error ? error.message : String(error)));
3923
+ // Auto-restart login flow in browser environment
3924
+ if (typeof window !== 'undefined') {
3925
+ if (this.config.debug) {
3926
+ console.log('[PlayKitSDK] Restarting authentication flow...');
3927
+ }
3928
+ const useExternalAuth = this.config.authMethod === 'external-auth';
3929
+ await this.authManager.startAuthFlow(useExternalAuth);
3930
+ // Retry getting player info after re-authentication
3931
+ await this.playerClient.getPlayerInfo();
3932
+ if (this.config.debug) {
3933
+ console.log('[PlayKitSDK] Re-authentication successful, token validated');
3934
+ }
3935
+ }
3936
+ else {
3937
+ throw new Error('Token validation failed: ' + (error instanceof Error ? error.message : String(error)));
3938
+ }
3508
3939
  }
3509
3940
  }
3510
3941
  this.emit('ready');