@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 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 response = await fetch(url, {
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
- // One final bulletproof regex wash to strip (smiles) and *laughs* just in case the LLM disobeys
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. **Deduplicate & Consolidate:** Remove duplicate hobbies, traits, boundaries, and preferences. Combine related points into concise descriptors.
1147
- 2. **Update Facts:** If the new events contain updated basic info (like new realName, different occupation), update it. Otherwise keep the existing info.
1148
- 3. **Keep it Clean:** Maximum 15 items per array.
1149
- 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.
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",
@@ -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: {
@@ -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 fetch(`${this.backendApiUrl}/api/v1/cyber-soul/llm-models/template?${qs.toString()}`, {
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 fetch(template.apiUrl, {
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@space3-npm/cybersoul-client",
3
- "version": "1.4.2",
3
+ "version": "1.4.4",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",