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