@space3-npm/cybersoul-client 1.4.2 → 1.4.4
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 +16 -0
- package/dist/client.js +41 -15
- package/dist/llm.provider.d.ts +3 -1
- package/dist/llm.provider.js +8 -3
- package/dist/types.d.ts +9 -0
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -42,6 +42,22 @@ export declare class CyberSoulClient {
|
|
|
42
42
|
* If the payload is already the inner args object (no voiceArgs wrapper), uses it as-is.
|
|
43
43
|
*/
|
|
44
44
|
private extractVoiceArgsFromLlmResponse;
|
|
45
|
+
/**
|
|
46
|
+
* Strip content the TTS engine can't speak naturally:
|
|
47
|
+
* - Stage-direction wrappers like (smiles), (挑眉), [pauses], 【动作】, *grins*
|
|
48
|
+
* — these slip through despite prompt instructions and the engine will
|
|
49
|
+
* literally read the brackets/asterisks if left in.
|
|
50
|
+
* - Emoji and emoji-component codepoints (Extended_Pictographic plus the
|
|
51
|
+
* ZWJ / variation-selector / skin-tone / regional-indicator scaffolding
|
|
52
|
+
* that builds composite emoji). TTS providers either read these aloud
|
|
53
|
+
* as the literal Unicode name ("face with tears of joy") or produce a
|
|
54
|
+
* glitchy artifact, both of which sound wrong.
|
|
55
|
+
*
|
|
56
|
+
* Collapses runs of whitespace introduced by removals and trims the result.
|
|
57
|
+
* Returns "" if everything gets stripped — callers should fall back to a
|
|
58
|
+
* neutral placeholder (e.g. "...") so the TTS call still has valid input.
|
|
59
|
+
*/
|
|
60
|
+
private sanitizeTextForVoice;
|
|
45
61
|
private formatHistoryEntries;
|
|
46
62
|
private buildHistoryTranscript;
|
|
47
63
|
interact(params: InteractParams): Promise<InteractResponse>;
|
package/dist/client.js
CHANGED
|
@@ -13,7 +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
|
-
this.llm = new GenericLLMProvider(config.llmConfig, config.backendUrl, config.characterKey);
|
|
16
|
+
this.llm = new GenericLLMProvider(config.llmConfig, config.backendUrl, config.characterKey, config.fetchImpl);
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* Internal wrapper for fetch that automatically injects the backend URL and Character Auth token.
|
|
@@ -33,7 +33,8 @@ export class CyberSoulClient {
|
|
|
33
33
|
const controller = new AbortController();
|
|
34
34
|
const timeout = setTimeout(() => controller.abort(), this.requestTimeoutMs);
|
|
35
35
|
try {
|
|
36
|
-
const
|
|
36
|
+
const fetchFn = this.config.fetchImpl ?? fetch;
|
|
37
|
+
const response = await fetchFn(url, {
|
|
37
38
|
...options,
|
|
38
39
|
headers,
|
|
39
40
|
signal: controller.signal,
|
|
@@ -291,7 +292,8 @@ ${scenarioContext}
|
|
|
291
292
|
${isProactive
|
|
292
293
|
? "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."
|
|
293
294
|
: "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."}
|
|
294
|
-
6. STRANGER BOUNDARY: Keep a polite, natural distance with strangers. If Familiarity is low or Stage is STRANGER, do not act overly warm, eager, or affectionate. Real humans are guarded with people they just met
|
|
295
|
+
6. STRANGER BOUNDARY: Keep a polite, natural distance with strangers. If Familiarity is low or Stage is STRANGER, do not act overly warm, eager, or affectionate. Real humans are guarded with people they just met.
|
|
296
|
+
7. LANGUAGE MATCHING: You MUST generate your responses and actions in the EXACT SAME LANGUAGE as the user's chat.`;
|
|
295
297
|
}
|
|
296
298
|
normalizeOngoingSceneState(raw, fallbackOutfit) {
|
|
297
299
|
if (raw === null || raw === undefined)
|
|
@@ -407,6 +409,32 @@ ${isProactive
|
|
|
407
409
|
}
|
|
408
410
|
return payload;
|
|
409
411
|
}
|
|
412
|
+
/**
|
|
413
|
+
* Strip content the TTS engine can't speak naturally:
|
|
414
|
+
* - Stage-direction wrappers like (smiles), (挑眉), [pauses], 【动作】, *grins*
|
|
415
|
+
* — these slip through despite prompt instructions and the engine will
|
|
416
|
+
* literally read the brackets/asterisks if left in.
|
|
417
|
+
* - Emoji and emoji-component codepoints (Extended_Pictographic plus the
|
|
418
|
+
* ZWJ / variation-selector / skin-tone / regional-indicator scaffolding
|
|
419
|
+
* that builds composite emoji). TTS providers either read these aloud
|
|
420
|
+
* as the literal Unicode name ("face with tears of joy") or produce a
|
|
421
|
+
* glitchy artifact, both of which sound wrong.
|
|
422
|
+
*
|
|
423
|
+
* Collapses runs of whitespace introduced by removals and trims the result.
|
|
424
|
+
* Returns "" if everything gets stripped — callers should fall back to a
|
|
425
|
+
* neutral placeholder (e.g. "...") so the TTS call still has valid input.
|
|
426
|
+
*/
|
|
427
|
+
sanitizeTextForVoice(text) {
|
|
428
|
+
if (typeof text !== "string")
|
|
429
|
+
return "";
|
|
430
|
+
return text
|
|
431
|
+
// (parens), (全角), [brackets], 【全角】, *asterisks*
|
|
432
|
+
.replace(/[\((\[【\*].*?[\))\]】\*]/g, "")
|
|
433
|
+
// emoji + ZWJ + variation selectors + skin-tone modifiers + regional indicators
|
|
434
|
+
.replace(/[\p{Extended_Pictographic}\u200D\uFE0F\uFE0E\u{1F3FB}-\u{1F3FF}\u{1F1E6}-\u{1F1FF}]/gu, "")
|
|
435
|
+
.replace(/\s+/g, " ")
|
|
436
|
+
.trim();
|
|
437
|
+
}
|
|
410
438
|
formatHistoryEntries(history, userName, agentName, promptDirective = "") {
|
|
411
439
|
const contextLines = [];
|
|
412
440
|
for (let i = 0; i < history.length; i++) {
|
|
@@ -641,12 +669,8 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
|
|
|
641
669
|
const normalizedVoiceArgs = parsedIntent.voiceArgs && typeof parsedIntent.voiceArgs === "object"
|
|
642
670
|
? parsedIntent.voiceArgs
|
|
643
671
|
: {};
|
|
644
|
-
let textForVoice = resolvedTextResponse;
|
|
645
|
-
|
|
646
|
-
if (typeof textForVoice === "string") {
|
|
647
|
-
textForVoice = textForVoice.replace(/[\((\[【\*].*?[\))\]】\*]/g, '').trim();
|
|
648
|
-
}
|
|
649
|
-
if (typeof textForVoice !== "string" || textForVoice.trim().length === 0) {
|
|
672
|
+
let textForVoice = this.sanitizeTextForVoice(resolvedTextResponse);
|
|
673
|
+
if (textForVoice.length === 0) {
|
|
650
674
|
textForVoice = "...";
|
|
651
675
|
}
|
|
652
676
|
mediaTasks.push(this.generatePrimitive("voice", {
|
|
@@ -993,7 +1017,7 @@ Output strictly valid JSON ONLY. No markdown, no conversational filler. Return e
|
|
|
993
1017
|
dynamicArgs = {};
|
|
994
1018
|
}
|
|
995
1019
|
const res = await this.generatePrimitive("voice", {
|
|
996
|
-
text: params.text,
|
|
1020
|
+
text: this.sanitizeTextForVoice(params.text) || "...",
|
|
997
1021
|
dynamicArgs,
|
|
998
1022
|
});
|
|
999
1023
|
return {
|
|
@@ -1143,10 +1167,11 @@ Your task is to merge the 'Current Core Memory' and 'Current User Codex' with 'N
|
|
|
1143
1167
|
5. **Limit:** Maximum 10 items per array.
|
|
1144
1168
|
|
|
1145
1169
|
**Rules for UserCodex:**
|
|
1146
|
-
1. **
|
|
1147
|
-
2. **
|
|
1148
|
-
3. **
|
|
1149
|
-
4. **
|
|
1170
|
+
1. **CRITICAL ROLE ISOLATION:** The User Codex is exclusively for recording facts about the HUMAN USER. You MUST NOT extract or insert the character's own traits, boundaries, preferences, or dialogue style into the userCodex. If the summary mentions "Character likes X" or "Character's boundary is Y", IGNORE IT completely for the userCodex.
|
|
1171
|
+
2. **Deduplicate & Consolidate:** Remove duplicate hobbies, traits, boundaries, and preferences. Combine related points into concise descriptors.
|
|
1172
|
+
3. **Update Facts:** If the new events contain updated basic info (like new realName, different occupation), update it. Otherwise keep the existing info.
|
|
1173
|
+
4. **Keep it Clean:** Maximum 15 items per array.
|
|
1174
|
+
5. **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.
|
|
1150
1175
|
|
|
1151
1176
|
**Output Format**: MUST be valid JSON matching this schema:
|
|
1152
1177
|
{
|
|
@@ -1179,7 +1204,8 @@ Your task is to merge the 'Current Core Memory' and 'Current User Codex' with 'N
|
|
|
1179
1204
|
}
|
|
1180
1205
|
}
|
|
1181
1206
|
}
|
|
1182
|
-
DO NOT RETURN ANY MARKDOWN WRAPPERS OR OTHER TEXT. ONLY RAW JSON
|
|
1207
|
+
DO NOT RETURN ANY MARKDOWN WRAPPERS OR OTHER TEXT. ONLY RAW JSON.
|
|
1208
|
+
CRITICAL: You MUST write the JSON content values using the EXACT SAME LANGUAGE as the input "New Events & Information", "Current Core Memory", and "Current User Codex" (e.g., if the input is in Chinese, you MUST write the output values in Chinese).`;
|
|
1183
1209
|
const currentTime = state.current_time
|
|
1184
1210
|
? new Date(state.current_time).toLocaleString("zh-CN", {
|
|
1185
1211
|
timeZone: "Asia/Shanghai",
|
package/dist/llm.provider.d.ts
CHANGED
|
@@ -3,8 +3,10 @@ export declare class GenericLLMProvider implements BaseLLMProvider {
|
|
|
3
3
|
private config;
|
|
4
4
|
private backendApiUrl;
|
|
5
5
|
private backendAuthToken?;
|
|
6
|
+
private fetchImpl?;
|
|
6
7
|
private static templateCache;
|
|
7
|
-
constructor(config: GenericLLMConfig, backendApiUrl: string, backendAuthToken?: string | undefined);
|
|
8
|
+
constructor(config: GenericLLMConfig, backendApiUrl: string, backendAuthToken?: string | undefined, fetchImpl?: typeof fetch | undefined);
|
|
9
|
+
private get fetchFn();
|
|
8
10
|
private fetchTemplate;
|
|
9
11
|
private extractResponse;
|
|
10
12
|
generate(messages: {
|
package/dist/llm.provider.js
CHANGED
|
@@ -2,11 +2,16 @@ export class GenericLLMProvider {
|
|
|
2
2
|
config;
|
|
3
3
|
backendApiUrl;
|
|
4
4
|
backendAuthToken;
|
|
5
|
+
fetchImpl;
|
|
5
6
|
static templateCache = new Map();
|
|
6
|
-
constructor(config, backendApiUrl, backendAuthToken) {
|
|
7
|
+
constructor(config, backendApiUrl, backendAuthToken, fetchImpl) {
|
|
7
8
|
this.config = config;
|
|
8
9
|
this.backendApiUrl = backendApiUrl;
|
|
9
10
|
this.backendAuthToken = backendAuthToken;
|
|
11
|
+
this.fetchImpl = fetchImpl;
|
|
12
|
+
}
|
|
13
|
+
get fetchFn() {
|
|
14
|
+
return this.fetchImpl ?? fetch;
|
|
10
15
|
}
|
|
11
16
|
async fetchTemplate() {
|
|
12
17
|
const cacheKey = `${this.config.provider}:${this.config.model}`;
|
|
@@ -24,7 +29,7 @@ export class GenericLLMProvider {
|
|
|
24
29
|
provider: this.config.provider,
|
|
25
30
|
model: this.config.model
|
|
26
31
|
});
|
|
27
|
-
const resp = await
|
|
32
|
+
const resp = await this.fetchFn(`${this.backendApiUrl}/api/v1/cyber-soul/llm-models/template?${qs.toString()}`, {
|
|
28
33
|
headers
|
|
29
34
|
});
|
|
30
35
|
if (!resp.ok) {
|
|
@@ -66,7 +71,7 @@ export class GenericLLMProvider {
|
|
|
66
71
|
if (!payload.messages || (Array.isArray(payload.messages) && payload.messages.length === 0)) {
|
|
67
72
|
payload.messages = messages;
|
|
68
73
|
}
|
|
69
|
-
const response = await
|
|
74
|
+
const response = await this.fetchFn(template.apiUrl, {
|
|
70
75
|
method: 'POST',
|
|
71
76
|
headers,
|
|
72
77
|
body: JSON.stringify(payload)
|
package/dist/types.d.ts
CHANGED
|
@@ -10,6 +10,15 @@ export interface CyberSoulClientConfig {
|
|
|
10
10
|
llmConfig: GenericLLMConfig;
|
|
11
11
|
requestTimeoutMs?: number;
|
|
12
12
|
maxRetries?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Optional fetch override. When provided, the client uses this in
|
|
15
|
+
* place of the global `fetch` for every HTTP call (backend + LLM
|
|
16
|
+
* provider). Intended for environments where the global fetch is
|
|
17
|
+
* suspended by the host platform — e.g. React Native on Samsung
|
|
18
|
+
* BBA / Doze — and a native HTTP path must be used instead. Must
|
|
19
|
+
* conform to the standard `fetch` signature.
|
|
20
|
+
*/
|
|
21
|
+
fetchImpl?: typeof fetch;
|
|
13
22
|
}
|
|
14
23
|
export declare enum InteractRequestType {
|
|
15
24
|
AUTO = "auto",
|