@space3-npm/cybersoul-client 1.2.9 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CyberSoulClientConfig, InteractParams, ProactiveParams, ProactiveResponse, OndemandEventParams, OndemandEventResponse, DispatcherIntent, InteractResponse, CharacterState, CoreMemory, UserCodex } from "./types.js";
1
+ import { CyberSoulClientConfig, InteractParams, ProactiveParams, ProactiveResponse, OndemandEventParams, OndemandEventResponse, DispatcherIntent, InteractResponse, CharacterState, CoreMemory, UserCodex, LikedPicture } from "./types.js";
2
2
  export declare class CyberSoulClient {
3
3
  private config;
4
4
  private llm;
@@ -103,7 +103,7 @@ export declare class CyberSoulClient {
103
103
  /**
104
104
  * Save the recent story moment to the character's backend database to be picked up by the core memory consolidation.
105
105
  */
106
- saveMoment(summary: string, date: string, time: string): Promise<void>;
106
+ saveMoment(summary: string, date: string, time: string, likedPictures?: LikedPicture[]): Promise<void>;
107
107
  /**
108
108
  * Consolidate Core Memory and User Codex using edge LLM logic and sync to remote DB
109
109
  */
package/dist/client.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { InteractRequestType, } from "./types.js";
2
2
  import { robustJsonParse } from "./utils/json.utils.js";
3
- import { MinimaxProvider } from "./providers/minimax.provider.js";
3
+ import { GenericLLMProvider } from "./llm.provider.js";
4
4
  export class CyberSoulClient {
5
5
  config;
6
6
  llm;
@@ -13,12 +13,7 @@ export class CyberSoulClient {
13
13
  this.requestTimeoutMs = config.requestTimeoutMs ?? 120000;
14
14
  this.maxRetries = Math.max(0, config.maxRetries ?? 1);
15
15
  // Setup Provider
16
- if (config.llmConfig.provider === "minimax") {
17
- this.llm = new MinimaxProvider(config.llmConfig);
18
- }
19
- else {
20
- throw new Error(`Unsupported LLM provider: ${config.llmConfig.provider}`);
21
- }
16
+ this.llm = new GenericLLMProvider(config.llmConfig, config.backendUrl, config.characterKey);
22
17
  }
23
18
  /**
24
19
  * Internal wrapper for fetch that automatically injects the backend URL and Character Auth token.
@@ -319,7 +314,7 @@ ${isProactive
319
314
  }`;
320
315
  }
321
316
  getOutfitSelectionPrompt() {
322
- return `When generating a triggerEvent, you MUST provide a suitable 'triggerEvent.outfitId' if the VERY LAST USER MESSAGE explicitly asks for an outfit change, OR if the new activity implies a context/location shift that conflicts with the current outfit (e.g., currently in SLEEPWEAR at home but going outside). Otherwise, keep it null. When changing outfits, match it to the event's activity, environment, and relationship stage (e.g., DAILY, INTIMATE, SLEEPWEAR).`;
317
+ return `When generating a triggerEvent, you MUST provide a suitable 'triggerEvent.outfitId' if the VERY LAST USER MESSAGE explicitly asks for an outfit change, OR if the new activity implies a context/location shift that conflicts with the current outfit (e.g., currently in SLEEPWEAR at home but going outside). Otherwise, keep it null. When changing outfits, match it to the event's activity, environment, and relationship stage (e.g., CASUAL, COSTUME, INTIMATE, SLEEPWEAR, etc.).`;
323
318
  }
324
319
  getTriggerEventPolicyPrompt() {
325
320
  return `- Include 'triggerEvent' only if the VERY LAST USER MESSAGE proposes a new activity/hangout, explicitly requests an outfit change, or proposes intimate/romantic actions; ignore older history. ${this.getOutfitSelectionPrompt()}`;
@@ -420,7 +415,7 @@ ${isProactive
420
415
  - 'textResponse' is ALWAYS REQUIRED.
421
416
  - The modalities you are ALLOWED to dynamically include: ${requestedOthers.length > 0 ? requestedOthers.join(", ") : "None (Only text is allowed)"}. Do not include other modalities.`;
422
417
  if (requestedOthers.includes(InteractRequestType.IMAGE)) {
423
- modalitiesInstruction += `\n - Include 'imageParams' for visual/photo requests or key visual moments during active events; explicitly describe current clothing/exposure in image fields.`;
418
+ modalitiesInstruction += `\n - Include 'imageParams' for visual/photo requests or key visual moments during active events; explicitly describe current clothing/exposure in image fields. CRITICAL POLICY: Guard your privacy! If the user feels like a stranger (low Familiarity) AND your Mood/Temperature is cool/distant (< 50), ALWAYS set 'imageParams' to null and naturally decline. If your Temperature is warm/high (>= 50), you may choose to share a picture if requested or if it fits naturally, even if you just met.`;
424
419
  }
425
420
  else {
426
421
  modalitiesInstruction += `\n - ALWAYS set 'imageParams' to null. If the user explicitly asks for a picture, FIRMLY decline naturally in your 'textResponse' (e.g., say you absolutely cannot right now). NEVER pretend to send one, and NEVER give in no matter how many times they ask.`;
@@ -474,12 +469,15 @@ USER ANALYSIS WORKFLOW:
474
469
 
475
470
  For 'isEndTurn', use true only when the interaction naturally concludes (confirmation/bye, event ending, or clear hard scene shift); otherwise false.
476
471
 
472
+ If the user explicitly praises, loves, or stars the VERY LAST picture you sent (not general appearance, but the recent photo itself), set 'likePreviousPicture' to true in the JSON, otherwise false.
473
+
477
474
  Voice direction for voiceArgs: ${this.getVoiceDirectorInstruction(state)}
478
475
 
479
476
  Output JSON Schema:
480
477
  {
481
478
  "actionText": "(Scene descriptions, physical actions, expressions, inner feelings) ONLY. Never include spoken dialogue here.",
482
479
  "textResponse": "Spoken dialogue ONLY. Never include actions or parentheses.",
480
+ "likePreviousPicture": false,
483
481
  "stateUpdate": { "temperatureDelta": 1, "userNickname": "How character addresses user", "agentNickname": "How user addresses character", "talkingStyle": "Current speaking style", "ongoingScene": { "scene": "Current physical scene/activity", "outfit": "Current outfit wording; use 'naked' when applicable" } },
484
482
  "giftOutfit": { "descriptionText": "Concise description of the newly acquired outfit to add into wardrobe." },
485
483
  "userAnalysis": { "newFactsLearned": [{ "category": "realName|occupation|age|gender|hobby|trait|communicationStyle|boundary|preference", "value": "explicit new user fact from VERY LAST USER MESSAGE" }] },
@@ -609,6 +607,7 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
609
607
  actionText: parsedIntent.actionText || "",
610
608
  imageUrl: finalImageUrl,
611
609
  audioUrl: finalAudioUrl,
610
+ likePreviousPicture: parsedIntent.likePreviousPicture,
612
611
  durationSec: finalDurationSec,
613
612
  triggeredEvent: parsedIntent.triggerEvent || undefined,
614
613
  stateUpdate: parsedIntent.stateUpdate,
@@ -769,7 +768,7 @@ CRITICAL: Output MUST be ONLY valid JSON with no markdown block wrappers. Do NOT
769
768
  // Determine modalities (reusing logic from interact)
770
769
  let modalitiesInstruction = "You are initiating conversation without a preceding user message.\\n";
771
770
  if (requestedOthers.includes(InteractRequestType.IMAGE)) {
772
- modalitiesInstruction += " - Include 'imageParams' for visual/photo requests or key visual moments; explicitly describe current clothing.\\n";
771
+ modalitiesInstruction += " - Include 'imageParams' for visual/photo moments. CRITICAL POLICY: NEVER send pictures to strangers! If Stage is STRANGER or COLD, or Familiarity is very low (< 10), ALWAYS set 'imageParams' to null.\\n";
773
772
  }
774
773
  else {
775
774
  modalitiesInstruction += " - ALWAYS set 'imageParams' to null.\\n";
@@ -1006,13 +1005,14 @@ Output requirements:
1006
1005
  /**
1007
1006
  * Save the recent story moment to the character's backend database to be picked up by the core memory consolidation.
1008
1007
  */
1009
- async saveMoment(summary, date, time) {
1008
+ async saveMoment(summary, date, time, likedPictures) {
1010
1009
  const res = await this.apiFetch("/api/v1/cyber-soul/characters/moments", {
1011
1010
  method: "POST",
1012
1011
  body: JSON.stringify({
1013
1012
  summary,
1014
1013
  date,
1015
1014
  time,
1015
+ likedPictures,
1016
1016
  }),
1017
1017
  });
1018
1018
  if (!res.ok) {
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export * from './types.js';
2
2
  export * from './client.js';
3
- export * from './providers/minimax.provider.js';
3
+ export * from './llm.provider.js';
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  export * from './types.js';
2
2
  export * from './client.js';
3
- export * from './providers/minimax.provider.js';
3
+ export * from './llm.provider.js';
@@ -0,0 +1,14 @@
1
+ import { BaseLLMProvider, GenericLLMConfig } from './types.js';
2
+ export declare class GenericLLMProvider implements BaseLLMProvider {
3
+ private config;
4
+ private backendApiUrl;
5
+ private backendAuthToken?;
6
+ private static templateCache;
7
+ constructor(config: GenericLLMConfig, backendApiUrl: string, backendAuthToken?: string | undefined);
8
+ private fetchTemplate;
9
+ private extractResponse;
10
+ generate(messages: {
11
+ role: string;
12
+ content: string;
13
+ }[], maxTokens?: number, temperature?: number): Promise<string>;
14
+ }
@@ -0,0 +1,85 @@
1
+ export class GenericLLMProvider {
2
+ config;
3
+ backendApiUrl;
4
+ backendAuthToken;
5
+ static templateCache = new Map();
6
+ constructor(config, backendApiUrl, backendAuthToken) {
7
+ this.config = config;
8
+ this.backendApiUrl = backendApiUrl;
9
+ this.backendAuthToken = backendAuthToken;
10
+ }
11
+ async fetchTemplate() {
12
+ const cacheKey = `${this.config.provider}:${this.config.model}`;
13
+ if (GenericLLMProvider.templateCache.has(cacheKey)) {
14
+ return GenericLLMProvider.templateCache.get(cacheKey);
15
+ }
16
+ // Need an auth token to call the backend APIs
17
+ const headers = {
18
+ 'Content-Type': 'application/json'
19
+ };
20
+ if (this.backendAuthToken) {
21
+ headers['Authorization'] = `Bearer ${this.backendAuthToken}`;
22
+ }
23
+ const qs = new URLSearchParams({
24
+ provider: this.config.provider,
25
+ model: this.config.model
26
+ });
27
+ const resp = await fetch(`${this.backendApiUrl}/api/v1/cyber-soul/llm-models/template?${qs.toString()}`, {
28
+ headers
29
+ });
30
+ if (!resp.ok) {
31
+ throw new Error(`Failed to fetch LLM generic template: ${resp.status}`);
32
+ }
33
+ const template = await resp.json();
34
+ GenericLLMProvider.templateCache.set(cacheKey, template);
35
+ return template;
36
+ }
37
+ extractResponse(data, responsePath) {
38
+ const parts = responsePath.split('.');
39
+ let cursor = data;
40
+ for (const part of parts) {
41
+ if (cursor === undefined || cursor === null)
42
+ return '';
43
+ cursor = cursor[part];
44
+ }
45
+ if (typeof cursor !== 'string') {
46
+ throw new Error(`Extraction resulted in non-string type: ${typeof cursor}`);
47
+ }
48
+ return cursor;
49
+ }
50
+ async generate(messages, maxTokens = 1500, temperature = 0.7) {
51
+ const template = await this.fetchTemplate();
52
+ const headers = { ...template.headersTemplate };
53
+ if (this.config.apiKey) {
54
+ for (const key of Object.keys(headers)) {
55
+ if (typeof headers[key] === 'string') {
56
+ headers[key] = headers[key].replace('{{apiKey}}', this.config.apiKey);
57
+ }
58
+ }
59
+ }
60
+ const payload = { ...template.basePayload };
61
+ if (this.config.customSettings) {
62
+ Object.assign(payload, this.config.customSettings);
63
+ }
64
+ // We only explicitly map messages. Parameters like temperature or max_tokens
65
+ // should be defined in basePayload, customSettings, or configured via the UI.
66
+ if (!payload.messages || (Array.isArray(payload.messages) && payload.messages.length === 0)) {
67
+ payload.messages = messages;
68
+ }
69
+ const response = await fetch(template.apiUrl, {
70
+ method: 'POST',
71
+ headers,
72
+ body: JSON.stringify(payload)
73
+ });
74
+ if (!response.ok) {
75
+ throw new Error(`Generic API returned status: ${response.status}`);
76
+ }
77
+ const data = await response.json();
78
+ try {
79
+ return this.extractResponse(data, template.responsePath || 'choices.0.message.content');
80
+ }
81
+ catch (e) {
82
+ throw new Error(`Failed to extract response. Error: ${e?.message}`);
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,14 @@
1
+ import { BaseLLMProvider, GenericLLMConfig } from '../types.js';
2
+ export declare class GenericLLMProvider implements BaseLLMProvider {
3
+ private config;
4
+ private backendApiUrl;
5
+ private backendAuthToken?;
6
+ private static templateCache;
7
+ constructor(config: GenericLLMConfig, backendApiUrl: string, backendAuthToken?: string | undefined);
8
+ private fetchTemplate;
9
+ private extractResponse;
10
+ generate(messages: {
11
+ role: string;
12
+ content: string;
13
+ }[], maxTokens?: number, temperature?: number): Promise<string>;
14
+ }
@@ -0,0 +1,85 @@
1
+ export class GenericLLMProvider {
2
+ config;
3
+ backendApiUrl;
4
+ backendAuthToken;
5
+ static templateCache = new Map();
6
+ constructor(config, backendApiUrl, backendAuthToken) {
7
+ this.config = config;
8
+ this.backendApiUrl = backendApiUrl;
9
+ this.backendAuthToken = backendAuthToken;
10
+ }
11
+ async fetchTemplate() {
12
+ const cacheKey = `${this.config.provider}:${this.config.model}`;
13
+ if (GenericLLMProvider.templateCache.has(cacheKey)) {
14
+ return GenericLLMProvider.templateCache.get(cacheKey);
15
+ }
16
+ // Need an auth token to call the backend APIs
17
+ const headers = {
18
+ 'Content-Type': 'application/json'
19
+ };
20
+ if (this.backendAuthToken) {
21
+ headers['Authorization'] = `Bearer ${this.backendAuthToken}`;
22
+ }
23
+ const qs = new URLSearchParams({
24
+ provider: this.config.provider,
25
+ model: this.config.model || ''
26
+ });
27
+ const resp = await fetch(`${this.backendApiUrl}/api/v1/cyber-soul/llm-models/template?${qs.toString()}`, {
28
+ headers
29
+ });
30
+ if (!resp.ok) {
31
+ throw new Error(`Failed to fetch LLM generic template: ${resp.status}`);
32
+ }
33
+ const template = await resp.json();
34
+ GenericLLMProvider.templateCache.set(cacheKey, template);
35
+ return template;
36
+ }
37
+ extractResponse(data, responsePath) {
38
+ const parts = responsePath.split('.');
39
+ let cursor = data;
40
+ for (const part of parts) {
41
+ if (cursor === undefined || cursor === null)
42
+ return '';
43
+ cursor = cursor[part];
44
+ }
45
+ if (typeof cursor !== 'string') {
46
+ throw new Error(`Extraction resulted in non-string type: ${typeof cursor}`);
47
+ }
48
+ return cursor;
49
+ }
50
+ async generate(messages, maxTokens = 1500, temperature = 0.7) {
51
+ if (!this.config.apiKey) {
52
+ throw new Error("Missing Generic Provider API Key");
53
+ }
54
+ const template = await this.fetchTemplate();
55
+ const headers = { ...template.headersTemplate };
56
+ for (const key of Object.keys(headers)) {
57
+ if (typeof headers[key] === 'string') {
58
+ headers[key] = headers[key].replace('{{apiKey}}', this.config.apiKey);
59
+ }
60
+ }
61
+ const payload = { ...template.basePayload };
62
+ if (this.config.customSettings) {
63
+ Object.assign(payload, this.config.customSettings);
64
+ }
65
+ // We only explicitly map messages. Parameters like temperature or max_tokens
66
+ // should be defined in basePayload, customSettings, or configured via the UI.
67
+ if (!payload.messages)
68
+ payload.messages = messages;
69
+ const response = await fetch(template.apiUrl, {
70
+ method: 'POST',
71
+ headers,
72
+ body: JSON.stringify(payload)
73
+ });
74
+ if (!response.ok) {
75
+ throw new Error(`Generic API returned status: ${response.status}`);
76
+ }
77
+ const data = await response.json();
78
+ try {
79
+ return this.extractResponse(data, template.responsePath || 'choices.0.message.content');
80
+ }
81
+ catch (e) {
82
+ throw new Error(`Failed to extract response. Error: ${e?.message}`);
83
+ }
84
+ }
85
+ }
package/dist/types.d.ts CHANGED
@@ -1,12 +1,13 @@
1
- export interface LLMConfig {
2
- provider: "minimax";
1
+ export interface GenericLLMConfig {
2
+ provider: string;
3
3
  apiKey: string;
4
4
  model: string;
5
+ customSettings?: Record<string, any>;
5
6
  }
6
7
  export interface CyberSoulClientConfig {
7
8
  characterKey: string;
8
9
  backendUrl: string;
9
- llmConfig: LLMConfig;
10
+ llmConfig: GenericLLMConfig;
10
11
  requestTimeoutMs?: number;
11
12
  maxRetries?: number;
12
13
  }
@@ -61,10 +62,21 @@ export interface OndemandEventResponse {
61
62
  scheduledDateStr?: string;
62
63
  error?: string;
63
64
  }
65
+ export declare enum WardrobeCategory {
66
+ CASUAL = "CASUAL",
67
+ FORMAL = "FORMAL",
68
+ WORKWEAR = "WORKWEAR",
69
+ SPORTSWEAR = "SPORTSWEAR",
70
+ SWIMWEAR = "SWIMWEAR",
71
+ COSTUME = "COSTUME",
72
+ SLEEPWEAR = "SLEEPWEAR",
73
+ INTIMATE = "INTIMATE",
74
+ DAILY = "DAILY"
75
+ }
64
76
  export interface WardrobeItem {
65
77
  id: string;
66
78
  itemName: string;
67
- category: string;
79
+ category: WardrobeCategory;
68
80
  promptModifier: string;
69
81
  }
70
82
  export interface InteractResponse {
@@ -73,6 +85,7 @@ export interface InteractResponse {
73
85
  actionText?: string;
74
86
  imageUrl?: string;
75
87
  audioUrl?: string;
88
+ likePreviousPicture?: boolean;
76
89
  durationSec?: number;
77
90
  triggeredEvent?: {
78
91
  eventTitle?: string;
@@ -93,6 +106,7 @@ export interface DispatcherIntent {
93
106
  textResponse?: string;
94
107
  actionText?: string;
95
108
  imageParams?: any;
109
+ likePreviousPicture?: boolean;
96
110
  voiceArgs?: VoiceArgs | null;
97
111
  giftOutfit?: {
98
112
  descriptionText: string;
@@ -262,3 +276,8 @@ export interface ICharacterProfile {
262
276
  visualCustomConfig?: Record<string, Record<string, unknown>>;
263
277
  [key: string]: unknown;
264
278
  }
279
+ export interface LikedPicture {
280
+ url: string;
281
+ date: string;
282
+ mediaId?: string;
283
+ }
package/dist/types.js CHANGED
@@ -5,3 +5,15 @@ export var InteractRequestType;
5
5
  InteractRequestType["IMAGE"] = "image";
6
6
  InteractRequestType["VOICE"] = "voice";
7
7
  })(InteractRequestType || (InteractRequestType = {}));
8
+ export var WardrobeCategory;
9
+ (function (WardrobeCategory) {
10
+ WardrobeCategory["CASUAL"] = "CASUAL";
11
+ WardrobeCategory["FORMAL"] = "FORMAL";
12
+ WardrobeCategory["WORKWEAR"] = "WORKWEAR";
13
+ WardrobeCategory["SPORTSWEAR"] = "SPORTSWEAR";
14
+ WardrobeCategory["SWIMWEAR"] = "SWIMWEAR";
15
+ WardrobeCategory["COSTUME"] = "COSTUME";
16
+ WardrobeCategory["SLEEPWEAR"] = "SLEEPWEAR";
17
+ WardrobeCategory["INTIMATE"] = "INTIMATE";
18
+ WardrobeCategory["DAILY"] = "DAILY";
19
+ })(WardrobeCategory || (WardrobeCategory = {}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@space3-npm/cybersoul-client",
3
- "version": "1.2.9",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",