@space3-npm/cybersoul-client 1.0.5 → 1.0.7

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, DispatcherIntent, InteractResponse, CharacterState, ImageGenerationParams, VoiceGenerationParams } from "./types.js";
1
+ import { CyberSoulClientConfig, InteractParams, DispatcherIntent, InteractResponse, CharacterState, CoreMemory } from "./types.js";
2
2
  export declare class CyberSoulClient {
3
3
  private config;
4
4
  private llm;
@@ -7,6 +7,9 @@ export declare class CyberSoulClient {
7
7
  * Internal wrapper for fetch that automatically injects the backend URL and Character Auth token.
8
8
  */
9
9
  private apiFetch;
10
+ private buildStateContextPrompt;
11
+ private getImageSchemaParams;
12
+ private getVoiceSchemaParams;
10
13
  /**
11
14
  * Fetches the current dynamic context and daily state.
12
15
  */
@@ -18,13 +21,19 @@ export declare class CyberSoulClient {
18
21
  /**
19
22
  * Manually generate an image of the character outside of chat flow.
20
23
  */
21
- generateImage(params: ImageGenerationParams): Promise<{
24
+ generateImage(params: {
25
+ sceneDescription: string;
26
+ interactParams?: InteractParams;
27
+ }): Promise<{
22
28
  imageUrl: string;
23
29
  }>;
24
30
  /**
25
31
  * Manually synthesize voice audio outside of chat flow.
26
32
  */
27
- generateVoice(params: VoiceGenerationParams): Promise<{
33
+ generateVoice(params: {
34
+ text: string;
35
+ interactParams?: InteractParams;
36
+ }): Promise<{
28
37
  audioUrl: string;
29
38
  durationSec?: number;
30
39
  }>;
@@ -46,4 +55,14 @@ export declare class CyberSoulClient {
46
55
  private generatePrimitive;
47
56
  private normalizeRequestTypes;
48
57
  interact(params: InteractParams): Promise<InteractResponse>;
58
+ /**
59
+ * Consolidate Core Memory using edge LLM logic and sync to remote DB
60
+ */
61
+ consolidateCoreMemory(input: {
62
+ events: string;
63
+ }): Promise<{
64
+ status: string;
65
+ coreMemory?: CoreMemory;
66
+ error?: string;
67
+ }>;
49
68
  }
package/dist/client.js CHANGED
@@ -1,4 +1,4 @@
1
- import { InteractRequestType } from "./types.js";
1
+ import { InteractRequestType, } from "./types.js";
2
2
  import { robustJsonParse } from "./utils/json.utils.js";
3
3
  import { MinimaxProvider } from "./providers/minimax.provider.js";
4
4
  export class CyberSoulClient {
@@ -26,6 +26,56 @@ export class CyberSoulClient {
26
26
  };
27
27
  return fetch(url, { ...options, headers });
28
28
  }
29
+ buildStateContextPrompt(state, localContext) {
30
+ const contextParts = [];
31
+ if (state.active_event) {
32
+ contextParts.push(`- Active Event: ${state.active_event.title} (${state.active_event.narrative_context})`);
33
+ }
34
+ if (state.next_event) {
35
+ contextParts.push(`- Next Event: ${state.next_event.title} at ${state.next_event.start_time} (in ${state.next_event.time_until_mins} mins)`);
36
+ }
37
+ if (state.active_wardrobe) {
38
+ contextParts.push(`- Wardrobe: ${state.active_wardrobe.name || state.active_wardrobe.id || "Current"}`);
39
+ }
40
+ const dyn = state.dynamic_context || {};
41
+ const stage = state.relationship_stage || "NEUTRAL";
42
+ contextParts.push(`- Relationship Info (Stage: ${stage}): You call the user '${dyn.userNickname || "User"}'. The user calls you '${dyn.agentNickname || "Agent"}'. Mood: ${dyn.talkingStyle || "Normal"}. Temp (0-100): ${dyn.temperature || 50}.`);
43
+ if (localContext) {
44
+ contextParts.push(`- Additional Context: ${localContext}`);
45
+ }
46
+ const scenarioContext = contextParts.join("\n");
47
+ return `You are ${state.name}, acting as a virtual companion.
48
+ Demographics: Age ${state.age || "unknown"}, Gender ${state.gender || "unknown"}, Occupation ${state.occupation || "unknown"}, Hobby ${state.hobby || "unknown"}
49
+ Current time: ${new Date(state.current_time || Date.now()).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" })}
50
+ Current context/schedule: ${scenarioContext}
51
+ Relationship stage: ${state.relationship_stage}
52
+ Personality Traits: ${state.personality_traits || "None"}
53
+ Interaction Boundaries: ${state.interaction_boundaries || "None"}
54
+ Communication Style: ${state.communication_style || "None"}
55
+
56
+ EMOTIONAL INERTIA RULES:
57
+ 1. You must act strictly according to the current Relationship Stage (${state.relationship_stage || "NEUTRAL"}).
58
+ 2. If the user expresses sudden high affection (e.g. "I miss you") but your stage is COLD, you MUST react with skepticism, coldness, or appropriately distanced deflection. Do NOT instantly become warm.
59
+ 3. Emotional mood changes must be slow. The 'temperatureDelta' should rarely exceed +/- 5 points per turn.`;
60
+ }
61
+ getImageSchemaParams() {
62
+ return `"imageParams": {
63
+ "mode": "structured | full-prompt (use 'full-prompt' for highly dynamic actions)",
64
+ "full_prompt": "Use only if mode is full-prompt. Highly detailed visual description in ENGLISH.",
65
+ "expression": "seductive | cute | happy | sleepy | dazed | pleased | default (Strictly choose ONE from this exact list. DO NOT invent new words like 'shy'.)",
66
+ "condition": "normal | sweaty | wet | messy | oily (Strictly choose ONE from this exact list.)",
67
+ "view_angle": "front | side | high_angle | from_below | boyfriend_view | selfie | mirror (Strictly choose ONE from this exact list.)",
68
+ "exposure": "normal | cleavage | see_through | half_naked | naked | intimate (Strictly choose ONE from this exact list.)",
69
+ "pose": "e.g., sitting on bed, leaning forward (ENGLISH ONLY)",
70
+ "scene": "e.g., cozy bedroom, morning light (ENGLISH ONLY)",
71
+ "outfit": "auto | ondemand",
72
+ "ondemandOutfit": "e.g., silk robe (ENGLISH ONLY)",
73
+ "style": "e.g., photorealistic (ENGLISH ONLY)"
74
+ }`;
75
+ }
76
+ getVoiceSchemaParams() {
77
+ return `"voiceArgs": { "style_instruction": "How the line should be spoken (Qwen3 format)", "emotion": "happy | sad | angry | fearful | disgusted | surprised | calm | fluent | whisper (Strictly choose ONE from this exact list.)" }`;
78
+ }
29
79
  /**
30
80
  * Fetches the current dynamic context and daily state.
31
81
  */
@@ -53,13 +103,63 @@ export class CyberSoulClient {
53
103
  * Manually generate an image of the character outside of chat flow.
54
104
  */
55
105
  async generateImage(params) {
56
- return this.generatePrimitive("image", params);
106
+ let imageParams = {};
107
+ const state = await this.getState();
108
+ const prompt = `${this.buildStateContextPrompt(state, params.interactParams?.localContext)}
109
+
110
+ You are an AI image prompt director. Analyze the scene description according to the character's relationship stage and emotional inertia to determine the best image generation parameters.
111
+ Output strictly valid JSON exactly matching this schema:
112
+ {
113
+ ${this.getImageSchemaParams()}
114
+ }`;
115
+ const promptMessages = [
116
+ { role: "system", content: prompt },
117
+ ...(params.interactParams?.history || []),
118
+ {
119
+ role: "user",
120
+ content: `Scene Description: "${params.sceneDescription}"`,
121
+ },
122
+ ];
123
+ const llmRes = await this.llm.generate(promptMessages, 500, 0.4);
124
+ try {
125
+ const parsedImageArgs = robustJsonParse(llmRes, "generateImage args fallback");
126
+ imageParams = parsedImageArgs.imageParams || parsedImageArgs;
127
+ }
128
+ catch (e) {
129
+ imageParams = { mode: "full-prompt", full_prompt: params.sceneDescription }; // fallback to basic prompt
130
+ }
131
+ return this.generatePrimitive("image", imageParams);
57
132
  }
58
133
  /**
59
134
  * Manually synthesize voice audio outside of chat flow.
60
135
  */
61
136
  async generateVoice(params) {
62
- return this.generatePrimitive("voice", params);
137
+ let dynamicArgs = {};
138
+ const state = await this.getState();
139
+ const prompt = `${this.buildStateContextPrompt(state, params.interactParams?.localContext)}
140
+
141
+ You are a voice acting director. Analyze the text according to the character's relationship stage and emotional inertia to determine the single best emotion and a style instruction for TTS.
142
+ Allowed emotions: "happy", "sad", "angry", "fearful", "disgusted", "surprised", "calm", "fluent", "whisper".
143
+ Output strictly valid JSON in exactly this format: {"emotion": "chosen_emotion", "style_instruction": "How the line should be spoken"}`;
144
+ const promptMessages = [
145
+ { role: "system", content: prompt },
146
+ ...(params.interactParams?.history || []),
147
+ {
148
+ role: "user",
149
+ content: `Text: "${params.text}"`,
150
+ },
151
+ ];
152
+ const llmRes = await this.llm.generate(promptMessages, 300, 0.3);
153
+ try {
154
+ dynamicArgs = robustJsonParse(llmRes, "generateVoice args fallback");
155
+ }
156
+ catch (e) {
157
+ dynamicArgs = {}; // fallback to empty
158
+ }
159
+ return this.generatePrimitive("voice", {
160
+ text: params.text,
161
+ dynamicArgs,
162
+ });
63
163
  }
64
164
  /**
65
165
  * Gift a new outfit to the character's wardrobe inventory.
@@ -143,36 +243,7 @@ export class CyberSoulClient {
143
243
  const types = this.normalizeRequestTypes(params.requestTypes);
144
244
  const isAuto = types.includes(InteractRequestType.AUTO);
145
245
  // Combine state info into a clean descriptive context
146
- const contextParts = [];
147
- if (state.active_event) {
148
- contextParts.push(`- Active Event: ${state.active_event.title} (${state.active_event.narrative_context})`);
149
- }
150
- if (state.next_event) {
151
- contextParts.push(`- Next Event: ${state.next_event.title} at ${state.next_event.start_time} (in ${state.next_event.time_until_mins} mins)`);
152
- }
153
- if (state.active_wardrobe) {
154
- contextParts.push(`- Wardrobe: ${state.active_wardrobe.name || state.active_wardrobe.id || "Current"}`);
155
- }
156
- const dyn = state.dynamic_context || {};
157
- const stage = state.relationship_stage || "NEUTRAL";
158
- contextParts.push(`- Relationship Info (Stage: ${stage}): You call the user '${dyn.userNickname || "User"}'. The user calls you '${dyn.agentNickname || "Agent"}'. Mood: ${dyn.talkingStyle || "Normal"}. Temp (0-100): ${dyn.temperature || 50}.`);
159
- if (params.localContext) {
160
- contextParts.push(`- Additional Context: ${params.localContext}`);
161
- }
162
- const scenarioContext = contextParts.join("\n");
163
- const systemPrompt = `You are ${state.name}, acting as a virtual companion.
164
- Demographics: Age ${state.age || 'unknown'}, Gender ${state.gender || 'unknown'}, Occupation ${state.occupation || 'unknown'}, Hobby ${state.hobby || 'unknown'}
165
- Current time: ${new Date(state.current_time).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" })}
166
- Current context/schedule: ${scenarioContext}
167
- Relationship stage: ${state.relationship_stage}
168
- Personality Traits: ${state.personality_traits || 'None'}
169
- Interaction Boundaries: ${state.interaction_boundaries || 'None'}
170
- Communication Style: ${state.communication_style || 'None'}
171
-
172
- EMOTIONAL INERTIA RULES:
173
- 1. You must act strictly according to the current Relationship Stage (${state.relationship_stage || 'NEUTRAL'}).
174
- 2. If the user expresses sudden high affection (e.g. "I miss you") but your stage is COLD, you MUST react with skepticism, coldness, or appropriately distanced deflection. Do NOT instantly become warm.
175
- 3. Emotional mood changes must be slow. The 'temperatureDelta' should rarely exceed +/- 5 points per turn.
246
+ const systemPrompt = `${this.buildStateContextPrompt(state, params.localContext)}
176
247
 
177
248
  The user has sent a message. You must evaluate the context and the user's message, and return a JSON object (no markdown formatting) that dictates the character's multi-modal response.
178
249
 
@@ -188,20 +259,8 @@ Output JSON Schema:
188
259
  {
189
260
  "textResponse": "The direct spoken dialogue in Chinese",
190
261
  "stateUpdate": { "temperatureDelta": "+1 to -1", "userNickname": "What you now call the user", "agentNickname": "What the user calls you", "talkingStyle": "Current mood/style of talking" },
191
- "imageParams": {
192
- "mode": "structured | full-prompt (use 'full-prompt' for highly dynamic actions)",
193
- "full_prompt": "Use only if mode is full-prompt. Highly detailed visual description in ENGLISH.",
194
- "expression": "seductive | cute | happy | sleepy | dazed | pleased | default (Strictly choose ONE from this exact list. DO NOT invent new words like 'shy'.)",
195
- "condition": "normal | sweaty | wet | messy | oily (Strictly choose ONE from this exact list.)",
196
- "view_angle": "front | side | high_angle | from_below | boyfriend_view | selfie | mirror (Strictly choose ONE from this exact list.)",
197
- "exposure": "normal | cleavage | see_through | half_naked | naked | intimate (Strictly choose ONE from this exact list.)",
198
- "pose": "e.g., sitting on bed, leaning forward (ENGLISH ONLY)",
199
- "scene": "e.g., cozy bedroom, morning light (ENGLISH ONLY)",
200
- "outfit": "auto | ondemand",
201
- "ondemandOutfit": "e.g., silk robe (ENGLISH ONLY)",
202
- "style": "e.g., photorealistic (ENGLISH ONLY)"
203
- },
204
- "voiceArgs": { "style_instruction": "How the line should be spoken (Qwen3 format)", "emotion": "e.g., happy (MiniMax format, MUST BE ENGLISH, no Chinese)" }
262
+ ${this.getImageSchemaParams()},
263
+ ${this.getVoiceSchemaParams()}
205
264
  }
206
265
  Note: If "imageParams", "voiceArgs", or "stateUpdate" are not needed, set their values to null instead of omitting the keys completely (e.g., "imageParams": null). Output MUST be ONLY valid JSON with no markdown block wrappers. CRITICAL: Ensure your JSON has exactly one root object \`{\` and ends with exactly one \`}\` without any trailing garbage or extra brackets.`;
207
266
  const promptMessages = [
@@ -241,24 +300,19 @@ Note: If "imageParams", "voiceArgs", or "stateUpdate" are not needed, set their
241
300
  let finalImageUrl = undefined;
242
301
  let finalAudioUrl = undefined;
243
302
  let finalDurationSec = undefined;
244
- const shouldGenerateImage = types.includes(InteractRequestType.IMAGE) || (isAuto && !!parsedIntent.imageParams);
303
+ const shouldGenerateImage = types.includes(InteractRequestType.IMAGE) ||
304
+ (isAuto && !!parsedIntent.imageParams);
245
305
  if (shouldGenerateImage) {
246
- mediaTasks.push(this.generatePrimitive("image", {
247
- ...parsedIntent.imageParams,
248
- ...(params.imageOverrides || {}),
249
- }).then((res) => {
306
+ mediaTasks.push(this.generatePrimitive("image", parsedIntent.imageParams).then((res) => {
250
307
  finalImageUrl = res.image_url;
251
308
  }));
252
309
  }
253
- const shouldGenerateVoice = types.includes(InteractRequestType.VOICE) || (isAuto && !!parsedIntent.voiceArgs);
310
+ const shouldGenerateVoice = types.includes(InteractRequestType.VOICE) ||
311
+ (isAuto && !!parsedIntent.voiceArgs);
254
312
  if (shouldGenerateVoice) {
255
- const dynamicArgs = {
256
- ...(parsedIntent.voiceArgs || {}),
257
- ...(params.voiceOverrides || {})
258
- };
259
313
  mediaTasks.push(this.generatePrimitive("voice", {
260
314
  text: parsedIntent.textResponse,
261
- dynamicArgs,
315
+ dynamicArgs: parsedIntent.voiceArgs || {},
262
316
  }).then((res) => {
263
317
  finalAudioUrl = res.audio_url;
264
318
  finalDurationSec = res.duration_sec;
@@ -283,4 +337,76 @@ Note: If "imageParams", "voiceArgs", or "stateUpdate" are not needed, set their
283
337
  };
284
338
  }
285
339
  }
340
+ /**
341
+ * Consolidate Core Memory using edge LLM logic and sync to remote DB
342
+ */
343
+ async consolidateCoreMemory(input) {
344
+ try {
345
+ const state = await this.getState();
346
+ const currentMemory = state.core_memory || {
347
+ relationshipStatus: "Starting out",
348
+ identityAnchors: [],
349
+ activeArcs: [],
350
+ keyEvents: [],
351
+ appointments: [],
352
+ };
353
+ const systemPrompt = `You are an AI Memory Consolidation Engine for a virtual companion.
354
+ Your task is to merge the 'Current Core Memory' with 'New Daily Events & Information' and output an updated 'Core Memory' JSON object.
355
+
356
+ **Rules:**
357
+ 1. **Condense:** Keep items brief. Remove resolving or expired story arcs.
358
+ 2. **Retain Value:** Never delete the absolute core identity or major relationship milestones.
359
+ 3. **Time-Aware:** Update or remove 'appointments' if the new events mention they occurred. If an event or appointment is time-specific, append the day/time to its description.
360
+ 4. **Limit:** Maximum 10 items per array.
361
+ 5. **Output Format**: MUST be valid JSON matching this schema:
362
+ {
363
+ "relationshipStatus": "string",
364
+ "identityAnchors": ["string"],
365
+ "activeArcs": ["string"],
366
+ "keyEvents": ["string"],
367
+ "appointments": ["string"]
368
+ }
369
+ DO NOT RETURN ANY MARKDOWN WRAPPERS OR OTHER TEXT. ONLY RAW JSON.`;
370
+ const currentTime = state.current_time
371
+ ? new Date(state.current_time).toLocaleString("zh-CN", {
372
+ timeZone: "Asia/Shanghai",
373
+ })
374
+ : new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
375
+ const prompt = `**Current Time:** ${currentTime}
376
+
377
+ **Current Core Memory:**
378
+ ${JSON.stringify(currentMemory, null, 2)}
379
+
380
+ **New Events & Information:**
381
+ ${input.events}`;
382
+ const responseText = await this.llm.generate([
383
+ { role: "system", content: systemPrompt },
384
+ { role: "user", content: prompt },
385
+ ], 1500, 0.4);
386
+ let newMemory;
387
+ try {
388
+ newMemory = robustJsonParse(responseText, "parsing core memory");
389
+ }
390
+ catch (e) {
391
+ throw new Error("LLM failed to return valid JSON payload");
392
+ }
393
+ if (!newMemory ||
394
+ !newMemory.relationshipStatus ||
395
+ !newMemory.activeArcs) {
396
+ throw new Error("LLM returned incomplete structured core memory payload");
397
+ }
398
+ const response = await this.apiFetch("/api/v1/cyber-soul/characters/core-memory", {
399
+ method: "PATCH",
400
+ body: JSON.stringify({ coreMemory: newMemory }),
401
+ });
402
+ if (!response.ok) {
403
+ throw new Error(`Failed to update core memory. Status: ${response.status}`);
404
+ }
405
+ return { status: "success", coreMemory: newMemory };
406
+ }
407
+ catch (error) {
408
+ console.error("[CyberSoulClient] consolidateCoreMemory Error:", error);
409
+ return { status: "error", error: error.message };
410
+ }
411
+ }
286
412
  }
package/dist/types.d.ts CHANGED
@@ -22,8 +22,6 @@ export interface InteractParams {
22
22
  role: string;
23
23
  content: string;
24
24
  }[];
25
- imageOverrides?: Partial<ImageGenerationParams>;
26
- voiceOverrides?: Partial<VoiceGenerationParams['dynamicArgs']>;
27
25
  onTextReady?: (textResponse: string) => void;
28
26
  }
29
27
  export interface InteractResponse {
@@ -45,12 +43,19 @@ export interface DispatcherIntent {
45
43
  talkingStyle?: string;
46
44
  };
47
45
  }
46
+ export interface CoreMemory {
47
+ relationshipStatus: string;
48
+ identityAnchors: string[];
49
+ activeArcs: string[];
50
+ keyEvents: string[];
51
+ appointments: string[];
52
+ }
48
53
  export interface CharacterState {
49
54
  current_time: string;
50
55
  active_event?: any;
51
56
  next_event?: any;
52
57
  active_wardrobe?: any;
53
- active_story_arcs?: string[];
58
+ core_memory?: CoreMemory;
54
59
  dynamic_context?: any;
55
60
  relationship_stage?: string;
56
61
  name?: string;
@@ -68,26 +73,3 @@ export interface BaseLLMProvider {
68
73
  content: string;
69
74
  }[], maxTokens?: number, temperature?: number): Promise<string>;
70
75
  }
71
- export interface ImageGenerationParams {
72
- mode: 'structured' | 'full-prompt';
73
- full_prompt?: string;
74
- expression?: string;
75
- condition?: string;
76
- pose?: string;
77
- view_angle?: string;
78
- exposure?: string;
79
- outfit?: string;
80
- scene?: string;
81
- ondemandOutfit?: string;
82
- style?: string;
83
- triggerWord?: string;
84
- appearanceBody?: string;
85
- appearanceFace?: string;
86
- }
87
- export interface VoiceGenerationParams {
88
- text: string;
89
- dynamicArgs: {
90
- style_instruction?: string;
91
- emotion?: string;
92
- };
93
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@space3-npm/cybersoul-client",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",