@space3-npm/cybersoul-client 1.2.7 → 1.2.9
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 +6 -1
- package/dist/client.js +159 -15
- package/dist/types.d.ts +17 -0
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CyberSoulClientConfig, InteractParams, OndemandEventParams, OndemandEventResponse, DispatcherIntent, InteractResponse, CharacterState, CoreMemory, UserCodex } from "./types.js";
|
|
1
|
+
import { CyberSoulClientConfig, InteractParams, ProactiveParams, ProactiveResponse, OndemandEventParams, OndemandEventResponse, DispatcherIntent, InteractResponse, CharacterState, CoreMemory, UserCodex } from "./types.js";
|
|
2
2
|
export declare class CyberSoulClient {
|
|
3
3
|
private config;
|
|
4
4
|
private llm;
|
|
@@ -47,6 +47,11 @@ export declare class CyberSoulClient {
|
|
|
47
47
|
* Evaluates and triggers an on-demand event, intelligently deciding if an outfit change is needed.
|
|
48
48
|
*/
|
|
49
49
|
ondemandEvent(params: OndemandEventParams): Promise<OndemandEventResponse>;
|
|
50
|
+
/**
|
|
51
|
+
* Generates a proactive message when the user hasn't responded.
|
|
52
|
+
* Safely prevents spamming, and adjusts its approach based on relationship dynamics.
|
|
53
|
+
*/
|
|
54
|
+
proactiveInteract(params: ProactiveParams): Promise<ProactiveResponse>;
|
|
50
55
|
/**
|
|
51
56
|
* Manually generate an image of the character outside of chat flow.
|
|
52
57
|
*/
|
package/dist/client.js
CHANGED
|
@@ -159,7 +159,7 @@ export class CyberSoulClient {
|
|
|
159
159
|
}
|
|
160
160
|
return normalized;
|
|
161
161
|
}
|
|
162
|
-
buildStateContextPrompt(state, localContext) {
|
|
162
|
+
buildStateContextPrompt(state, localContext, isProactive = false) {
|
|
163
163
|
const dyn = state.dynamic_context || {};
|
|
164
164
|
const stage = state.relationship_stage || "NEUTRAL";
|
|
165
165
|
const temperature = dyn.temperature ?? 50;
|
|
@@ -197,10 +197,10 @@ Current time: ${new Date(currentTimeMs).toLocaleString("zh-CN", { timeZone: "Asi
|
|
|
197
197
|
if (state.active_event) {
|
|
198
198
|
contextParts.push(`Active Event: ${state.active_event.title} (${state.active_event.narrative_context})`);
|
|
199
199
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
200
|
+
if (localContext) {
|
|
201
|
+
contextParts.push(`Additional Context: ${localContext}`);
|
|
202
|
+
}
|
|
203
|
+
if (state.next_event) {
|
|
204
204
|
contextParts.push(`Next Event: ${state.next_event.title} at ${state.next_event.start_time} (in ${state.next_event.time_until_mins} mins)`);
|
|
205
205
|
}
|
|
206
206
|
if (state.active_wardrobe) {
|
|
@@ -269,7 +269,9 @@ ${scenarioContext}
|
|
|
269
269
|
2. IDENTITY VS MOOD: Familiarity determines what you know; Temperature determines how you feel. If Familiarity is high but Temperature is low, be distant and cold. Do not act warm just because you know them well.
|
|
270
270
|
3. CONVERSATIONAL VERBOSITY: If Temperature is low (< 40) or Stage is STRANGER/COLD, keep answers brief and short. An angry or distant person does not write long paragraphs. Even when Temperature is high, ALWAYS mirror the user's verbosity. If the user sends a short message, reply with a proportionately short message (1-2 sentences). Do not monologize or write long paragraphs unless the user writes one first.
|
|
271
271
|
4. EMOTIONAL INERTIA: React strictly according to current Temperature. Deflect sudden user affection if you are currently COLD. Mood shifts MUST be slow ('temperatureDelta' +/- 5 max per turn).
|
|
272
|
-
|
|
272
|
+
${isProactive
|
|
273
|
+
? "5. REAL-TIME PACING: You are initiating the conversation because the user hasn't replied recently. Transition naturally from your last message or start a new topic seamlessly. Ensure everything happens in a single real-time moment."
|
|
274
|
+
: "5. REAL-TIME PACING: Write ONLY your immediate, split-second reaction to the user's exact last message. Do NOT narrate actions over a span of time (e.g., waiting, hearing steps, then walking to the door). Ensure everything happens in a single real-time moment."}`;
|
|
273
275
|
}
|
|
274
276
|
normalizeOngoingSceneState(raw, fallbackOutfit) {
|
|
275
277
|
if (raw === null || raw === undefined)
|
|
@@ -561,9 +563,15 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
|
|
|
561
563
|
mode: "full-prompt",
|
|
562
564
|
full_prompt: resolvedTextResponse,
|
|
563
565
|
};
|
|
564
|
-
mediaTasks.push(this.generatePrimitive("image", imagePayload)
|
|
566
|
+
mediaTasks.push(this.generatePrimitive("image", imagePayload)
|
|
567
|
+
.then((res) => {
|
|
565
568
|
finalImageUrl = res.image_url;
|
|
566
|
-
})
|
|
569
|
+
})
|
|
570
|
+
.catch((e) => {
|
|
571
|
+
console.error("[CyberSoulClient] Image generation failed:", e);
|
|
572
|
+
if (e.code === 'INSUFFICIENT_POINTS' || e.code === 'WALLET_DEDUCTION_ERROR')
|
|
573
|
+
throw e;
|
|
574
|
+
}));
|
|
567
575
|
}
|
|
568
576
|
const shouldGenerateVoice = types.includes(InteractRequestType.VOICE) &&
|
|
569
577
|
(!isAuto || !!parsedIntent.voiceArgs);
|
|
@@ -582,10 +590,16 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
|
|
|
582
590
|
mediaTasks.push(this.generatePrimitive("voice", {
|
|
583
591
|
text: textForVoice,
|
|
584
592
|
dynamicArgs: normalizedVoiceArgs,
|
|
585
|
-
})
|
|
593
|
+
})
|
|
594
|
+
.then((res) => {
|
|
586
595
|
finalAudioUrl = res.audio_url;
|
|
587
596
|
finalDurationSec = res.duration_sec;
|
|
588
|
-
})
|
|
597
|
+
})
|
|
598
|
+
.catch((e) => {
|
|
599
|
+
console.error("[CyberSoulClient] Voice generation failed:", e);
|
|
600
|
+
if (e.code === 'INSUFFICIENT_POINTS' || e.code === 'WALLET_DEDUCTION_ERROR')
|
|
601
|
+
throw e;
|
|
602
|
+
}));
|
|
589
603
|
}
|
|
590
604
|
// Wait for image/voice gens to return successfully
|
|
591
605
|
await Promise.all(mediaTasks);
|
|
@@ -697,6 +711,135 @@ CRITICAL: Output MUST be ONLY valid JSON with no markdown block wrappers. Do NOT
|
|
|
697
711
|
};
|
|
698
712
|
}
|
|
699
713
|
}
|
|
714
|
+
/**
|
|
715
|
+
* Generates a proactive message when the user hasn't responded.
|
|
716
|
+
* Safely prevents spamming, and adjusts its approach based on relationship dynamics.
|
|
717
|
+
*/
|
|
718
|
+
async proactiveInteract(params) {
|
|
719
|
+
try {
|
|
720
|
+
// 1. Cold Interaction Protection (Logic-based fallback)
|
|
721
|
+
const history = params.history || [];
|
|
722
|
+
const maxUnreplied = params.maxUnreplied ?? 2;
|
|
723
|
+
let consecutiveProactive = 0;
|
|
724
|
+
// Start from the most recent message
|
|
725
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
726
|
+
const msg = history[i];
|
|
727
|
+
if (msg.role === 'user') {
|
|
728
|
+
break; // User responded, streak broken
|
|
729
|
+
}
|
|
730
|
+
if (msg.role === 'assistant') {
|
|
731
|
+
consecutiveProactive++;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
if (consecutiveProactive >= maxUnreplied) {
|
|
735
|
+
return {
|
|
736
|
+
status: "skipped",
|
|
737
|
+
reason: `User is busy. ${consecutiveProactive} consecutive proactive messages ignored.`
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
// 2. Fetch current character state
|
|
741
|
+
const [state, availableOutfits] = await Promise.all([
|
|
742
|
+
this.fetchRemoteState(),
|
|
743
|
+
this.getWardrobePromptStr()
|
|
744
|
+
]);
|
|
745
|
+
// 3. Evaluate behavioral approach based on relationship and personality
|
|
746
|
+
const dyn = state.dynamic_context || {};
|
|
747
|
+
const stage = state.relationship_stage || "ACQUAINTANCE";
|
|
748
|
+
const temperature = dyn.temperature ?? 50;
|
|
749
|
+
const userTraits = state.user_codex?.psychological?.traits?.join(", ") || "";
|
|
750
|
+
let interrogationStrategy = "Do not ask 'are you there?' or 'why aren't you answering?'. Just share your current status, a passing thought, complain whimsically, or tease the user naturally like a real partner.";
|
|
751
|
+
if (stage === "INTIMATE" || stage === "PARTNER" || (stage === "WARM" && temperature > 70)) {
|
|
752
|
+
if (temperature > 70) {
|
|
753
|
+
interrogationStrategy = "Because you are deeply intimate and highly affectionate, you MISS them. You MAY organically 'interrogate' or pout playfully about why they are ignoring you (e.g., 'Are you too busy for me?', 'Still ignoring your girl?'). Act like a real, slightly needy/attached partner.";
|
|
754
|
+
}
|
|
755
|
+
else if (temperature < 40) {
|
|
756
|
+
interrogationStrategy = "Because you are intimate but currently feeling cold/angry, you notice they are ignoring you. You MAY be passive-aggressive or cross-examine them coldly (e.g., 'So we're just not talking today?', 'Fine, keep ignoring me.').";
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
else if (stage === "COLD" || stage === "STRANGER") {
|
|
760
|
+
interrogationStrategy = "You are distant. Do NOT double-text with neediness. If you must speak, make it a detached observation or a cold administrative remark.";
|
|
761
|
+
}
|
|
762
|
+
// History/Context awareness prompt
|
|
763
|
+
const historyAwarenessPrompt = `CRITICAL CONTEXT AWARENESS: Read the CHAT HISTORY above carefully. Remember that YOU sent the last message. Your new message MUST feel organically connected to the flow of what you two were previously talking about, or naturally bring up a known event/topic from your [CORE MEMORY]. Do not sound like a robot reading a log.`;
|
|
764
|
+
// 4. Build a Proactive-specific System Prompt
|
|
765
|
+
const baseContext = this.buildStateContextPrompt(state, params.localContext, true);
|
|
766
|
+
const types = this.normalizeRequestTypes(params.requestTypes);
|
|
767
|
+
const isAuto = types.includes(InteractRequestType.AUTO);
|
|
768
|
+
const requestedOthers = types.filter((t) => t !== InteractRequestType.AUTO && t !== InteractRequestType.TEXT);
|
|
769
|
+
// Determine modalities (reusing logic from interact)
|
|
770
|
+
let modalitiesInstruction = "You are initiating conversation without a preceding user message.\\n";
|
|
771
|
+
if (requestedOthers.includes(InteractRequestType.IMAGE)) {
|
|
772
|
+
modalitiesInstruction += " - Include 'imageParams' for visual/photo requests or key visual moments; explicitly describe current clothing.\\n";
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
775
|
+
modalitiesInstruction += " - ALWAYS set 'imageParams' to null.\\n";
|
|
776
|
+
}
|
|
777
|
+
const systemPrompt = `${baseContext}
|
|
778
|
+
|
|
779
|
+
[PROACTIVE INITIATION TASK]
|
|
780
|
+
The user has NOT spoken to you recently. You sent the last message in the chat history, and they haven't replied. You are deciding to follow up proactively.
|
|
781
|
+
${interrogationStrategy}
|
|
782
|
+
${historyAwarenessPrompt}
|
|
783
|
+
Consider the user's known traits (${userTraits}) when choosing how to act. Need to keep it strictly under 2-3 sentences max.
|
|
784
|
+
|
|
785
|
+
Available Wardrobe Outfits:
|
|
786
|
+
${availableOutfits}
|
|
787
|
+
|
|
788
|
+
${modalitiesInstruction}
|
|
789
|
+
You MUST output ONLY a valid JSON object matching exactly this structure:
|
|
790
|
+
{
|
|
791
|
+
"actionText": "(Scene descriptions, physical actions, expressions, inner feelings) ONLY.",
|
|
792
|
+
"textResponse": "Spoken dialogue ONLY.",
|
|
793
|
+
"stateUpdate": { "temperatureDelta": 1, "ongoingScene": { "scene": "...", "outfit": "..." } },
|
|
794
|
+
${this.getImageSchemaParams(requestedOthers.includes(InteractRequestType.IMAGE))},
|
|
795
|
+
"voiceArgs": null
|
|
796
|
+
}`;
|
|
797
|
+
const transcript = this.buildHistoryTranscript(params.history, state);
|
|
798
|
+
const promptMessages = [
|
|
799
|
+
{ role: "system", content: systemPrompt },
|
|
800
|
+
{
|
|
801
|
+
role: "user",
|
|
802
|
+
content: `${transcript}\n[TRIGGER PROACTIVE MESSAGE]\nBased on your active event and environment, send a new message to the user.\n\nCRITICAL: Output ONLY valid JSON matching the schema. DO NOT wrap the JSON in \`\`\`json.`
|
|
803
|
+
}
|
|
804
|
+
];
|
|
805
|
+
// 5. Generate with LLM using a confident temperature
|
|
806
|
+
const rawLlmResponse = await this.llm.generate(promptMessages, 800, 0.7);
|
|
807
|
+
let parsedIntent;
|
|
808
|
+
try {
|
|
809
|
+
parsedIntent = robustJsonParse(rawLlmResponse, "Proactive fallback");
|
|
810
|
+
}
|
|
811
|
+
catch (e) {
|
|
812
|
+
parsedIntent = { textResponse: rawLlmResponse.replace(/^[\`\s]+|[\`\s]+$/g, "").trim() };
|
|
813
|
+
}
|
|
814
|
+
// Update Remote state if needed
|
|
815
|
+
if (parsedIntent.stateUpdate) {
|
|
816
|
+
this._updateDynamicContextInternal(parsedIntent.stateUpdate).catch(e => console.error(e));
|
|
817
|
+
}
|
|
818
|
+
// Handle Optional Media (Image only for proactive to save compute normally, but you can extend)
|
|
819
|
+
let finalImageUrl = undefined;
|
|
820
|
+
if (requestedOthers.includes(InteractRequestType.IMAGE) || !!parsedIntent.imageParams) {
|
|
821
|
+
const imagePayload = parsedIntent.imageParams || { mode: "full-prompt", full_prompt: parsedIntent.textResponse };
|
|
822
|
+
try {
|
|
823
|
+
const res = await this.generatePrimitive("image", imagePayload);
|
|
824
|
+
finalImageUrl = res.image_url;
|
|
825
|
+
}
|
|
826
|
+
catch (e) {
|
|
827
|
+
console.error("[CyberSoulClient] Proactive Image generation failed:", e);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
return {
|
|
831
|
+
status: "success",
|
|
832
|
+
textResponse: parsedIntent.textResponse,
|
|
833
|
+
actionText: parsedIntent.actionText,
|
|
834
|
+
imageUrl: finalImageUrl,
|
|
835
|
+
stateUpdate: parsedIntent.stateUpdate
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
catch (error) {
|
|
839
|
+
console.error("[CyberSoulClient] Proactive Interact Error: ", error);
|
|
840
|
+
return { status: "error", error: error.message };
|
|
841
|
+
}
|
|
842
|
+
}
|
|
700
843
|
/**
|
|
701
844
|
* Manually generate an image of the character outside of chat flow.
|
|
702
845
|
*/
|
|
@@ -913,6 +1056,7 @@ Your task is to merge the 'Current Core Memory' and 'Current User Codex' with 'N
|
|
|
913
1056
|
1. **Deduplicate & Consolidate:** Remove duplicate hobbies, traits, boundaries, and preferences. Combine related points into concise descriptors.
|
|
914
1057
|
2. **Update Facts:** If the new events contain updated basic info (like new realName, different occupation), update it. Otherwise keep the existing info.
|
|
915
1058
|
3. **Keep it Clean:** Maximum 15 items per array.
|
|
1059
|
+
4. **CRITICAL Anti-Destruction Rule:** NEVER use placeholder values like 'string'. If a fact is not mentioned and is absent from Current User Codex, OMIT the key entirely. If a fact ALREADY EXISTS in the Current User Codex, you MUST retain it in your output. DO NOT reset existing arrays or strings to empty.
|
|
916
1060
|
|
|
917
1061
|
**Output Format**: MUST be valid JSON matching this schema:
|
|
918
1062
|
{
|
|
@@ -931,15 +1075,15 @@ Your task is to merge the 'Current Core Memory' and 'Current User Codex' with 'N
|
|
|
931
1075
|
},
|
|
932
1076
|
"userCodex": {
|
|
933
1077
|
"basicInfo": {
|
|
934
|
-
"realName": "string",
|
|
935
|
-
"occupation": "string",
|
|
936
|
-
"age": "string",
|
|
937
|
-
"gender": "string"
|
|
1078
|
+
"realName": "string (optional, omit if unknown)",
|
|
1079
|
+
"occupation": "string (optional, omit if unknown)",
|
|
1080
|
+
"age": "string (optional, omit if unknown)",
|
|
1081
|
+
"gender": "string (optional, omit if unknown)"
|
|
938
1082
|
},
|
|
939
1083
|
"psychological": {
|
|
940
1084
|
"hobbies": ["string"],
|
|
941
1085
|
"traits": ["string"],
|
|
942
|
-
"communicationStyle": "string",
|
|
1086
|
+
"communicationStyle": "string (optional, omit if unknown)",
|
|
943
1087
|
"boundaries": ["string"],
|
|
944
1088
|
"preferences": ["string"]
|
|
945
1089
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -21,6 +21,23 @@ export interface HistoryEntry {
|
|
|
21
21
|
content: string;
|
|
22
22
|
actionText?: string;
|
|
23
23
|
mediaHint?: string;
|
|
24
|
+
isProactive?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface ProactiveParams {
|
|
27
|
+
history?: HistoryEntry[];
|
|
28
|
+
maxUnreplied?: number;
|
|
29
|
+
requestTypes?: InteractRequestType[];
|
|
30
|
+
localContext?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ProactiveResponse {
|
|
33
|
+
status: "success" | "skipped" | "error";
|
|
34
|
+
reason?: string;
|
|
35
|
+
textResponse?: string;
|
|
36
|
+
actionText?: string;
|
|
37
|
+
imageUrl?: string;
|
|
38
|
+
audioUrl?: string;
|
|
39
|
+
stateUpdate?: DispatcherIntent["stateUpdate"];
|
|
40
|
+
error?: string;
|
|
24
41
|
}
|
|
25
42
|
export interface InteractParams {
|
|
26
43
|
userMessage: string;
|