@space3-npm/cybersoul-client 1.2.3 → 1.2.5
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.js +79 -33
- package/dist/types.d.ts +2 -1
- package/dist/utils/json.utils.d.ts +1 -1
- package/dist/utils/json.utils.js +45 -1
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -142,15 +142,22 @@ export class CyberSoulClient {
|
|
|
142
142
|
}).catch((e) => console.error("Failed to update dynamic context", e)); // non-blocking error handler
|
|
143
143
|
}
|
|
144
144
|
normalizeRequestTypes(requestTypes) {
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
let normalized = requestTypes;
|
|
146
|
+
if (!normalized || normalized.length === 0) {
|
|
147
|
+
normalized = [InteractRequestType.AUTO, InteractRequestType.TEXT];
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
normalized = [...normalized];
|
|
151
|
+
}
|
|
152
|
+
if (!normalized.includes(InteractRequestType.TEXT)) {
|
|
153
|
+
normalized.push(InteractRequestType.TEXT);
|
|
147
154
|
}
|
|
148
155
|
const validRequestTypes = new Set(Object.values(InteractRequestType));
|
|
149
|
-
const invalidRequestTypes =
|
|
156
|
+
const invalidRequestTypes = normalized.filter((type) => !validRequestTypes.has(type));
|
|
150
157
|
if (invalidRequestTypes.length > 0) {
|
|
151
158
|
throw new Error(`Invalid requestTypes: ${invalidRequestTypes.join(", ")}. Allowed values: ${Object.values(InteractRequestType).join(", ")}`);
|
|
152
159
|
}
|
|
153
|
-
return
|
|
160
|
+
return normalized;
|
|
154
161
|
}
|
|
155
162
|
buildStateContextPrompt(state, localContext) {
|
|
156
163
|
const dyn = state.dynamic_context || {};
|
|
@@ -178,7 +185,7 @@ Current time: ${new Date(currentTimeMs).toLocaleString("zh-CN", { timeZone: "Asi
|
|
|
178
185
|
let isOutdated = false;
|
|
179
186
|
if (dyn.lastInteractionAt) {
|
|
180
187
|
const elapsedHours = (currentTimeMs - new Date(dyn.lastInteractionAt).getTime()) / (1000 * 60 * 60);
|
|
181
|
-
if (elapsedHours >
|
|
188
|
+
if (elapsedHours > 1) {
|
|
182
189
|
isOutdated = true;
|
|
183
190
|
contextParts.push(`${lastKnownSceneLine}\n[CRITICAL SCENE SHIFT]: It has been ${elapsedHours.toFixed(1)} hours since the last discussion. The 'Last Known Scene' is now strictly OUTDATED. You MUST abandon the previous scene context entirely and transition to a new scene appropriate for the 'Current time' and 'Active Event'. DO NOT continue the old actions or environment!`);
|
|
184
191
|
}
|
|
@@ -190,15 +197,15 @@ Current time: ${new Date(currentTimeMs).toLocaleString("zh-CN", { timeZone: "Asi
|
|
|
190
197
|
if (state.active_event) {
|
|
191
198
|
contextParts.push(`Active Event: ${state.active_event.title} (${state.active_event.narrative_context})`);
|
|
192
199
|
}
|
|
193
|
-
if (
|
|
200
|
+
/* if (localContext) {
|
|
201
|
+
contextParts.push(`Additional Context: ${localContext}`);
|
|
202
|
+
}
|
|
203
|
+
*/ if (state.next_event) {
|
|
194
204
|
contextParts.push(`Next Event: ${state.next_event.title} at ${state.next_event.start_time} (in ${state.next_event.time_until_mins} mins)`);
|
|
195
205
|
}
|
|
196
206
|
if (state.active_wardrobe) {
|
|
197
207
|
contextParts.push(`Wardrobe: ${state.active_wardrobe.name || state.active_wardrobe.id || "Current"}`);
|
|
198
208
|
}
|
|
199
|
-
if (localContext) {
|
|
200
|
-
contextParts.push(`Additional Context: ${localContext}`);
|
|
201
|
-
}
|
|
202
209
|
if (state.core_memory) {
|
|
203
210
|
let memoryLines = ["[CORE MEMORY]"];
|
|
204
211
|
const mem = state.core_memory;
|
|
@@ -226,7 +233,8 @@ Occupation: ${basicInfo?.occupation || "Unknown"}
|
|
|
226
233
|
Age/Gender: ${basicInfo?.age || "Unknown"} / ${basicInfo?.gender || "Unknown"}
|
|
227
234
|
Comm Style: ${psychological?.communicationStyle || "Unknown"}
|
|
228
235
|
Hobbies: ${(psychological?.hobbies || []).join(", ") || "Unknown"}
|
|
229
|
-
Traits/Boundaries: ${(psychological?.traits || []).join(", ") || "Unknown"} / ${(psychological?.boundaries || []).join(", ") || "Unknown"}
|
|
236
|
+
Traits/Boundaries: ${(psychological?.traits || []).join(", ") || "Unknown"} / ${(psychological?.boundaries || []).join(", ") || "Unknown"}
|
|
237
|
+
Preferences/Habits: ${(psychological?.preferences || []).join(", ") || "Unknown"}`);
|
|
230
238
|
// CURIOSITY DRIVE: Find what's missing, but ONLY IF we are on generally warm speaking terms
|
|
231
239
|
// Paradox avoidance: A cold/angry character shouldn't enthusiastically fish for hobbies.
|
|
232
240
|
if (temperature >= 40 && stage !== "COLD" && stage !== "STRANGER") {
|
|
@@ -291,7 +299,9 @@ ${scenarioContext}
|
|
|
291
299
|
}
|
|
292
300
|
return undefined;
|
|
293
301
|
}
|
|
294
|
-
getImageSchemaParams() {
|
|
302
|
+
getImageSchemaParams(allowed) {
|
|
303
|
+
if (!allowed)
|
|
304
|
+
return `"imageParams": null`;
|
|
295
305
|
return `"imageParams": {
|
|
296
306
|
"mode": "structured | full-prompt (use 'full-prompt' for highly dynamic actions)",
|
|
297
307
|
"full_prompt": "Use only if mode is full-prompt. Highly detailed visual description in ENGLISH. CRITICAL: MUST use a strict first-person perspective exclusively from the USER's eyes. DO NOT describe the user (e.g., 'a man', 'the driver') as visible in the scene because the camera IS the user. Start with 'POV: '. Describe ONLY the character looking back at the camera and their immediate surroundings. MUST align with the character's current Active exposure state or Wardrobe depends on the scene. Explicitly describe the character's exact clothing (or specify naked/half-naked if applicable).",
|
|
@@ -334,7 +344,9 @@ ${scenarioContext}
|
|
|
334
344
|
* Returns the JSON schema snippet for voiceArgs to embed in the LLM output schema.
|
|
335
345
|
* Built from dynamic_params when available, otherwise falls back to static defaults.
|
|
336
346
|
*/
|
|
337
|
-
getVoiceSchemaFromState(state) {
|
|
347
|
+
getVoiceSchemaFromState(state, allowed) {
|
|
348
|
+
if (!allowed)
|
|
349
|
+
return `"voiceArgs": null`;
|
|
338
350
|
const dynamicParams = state.voice_model?.dynamic_params;
|
|
339
351
|
if (dynamicParams && dynamicParams.length > 0) {
|
|
340
352
|
return this.buildVoiceSchemaFromDynamicParams(dynamicParams);
|
|
@@ -390,6 +402,43 @@ ${scenarioContext}
|
|
|
390
402
|
// 2. Build local Prompt
|
|
391
403
|
const types = this.normalizeRequestTypes(params.requestTypes);
|
|
392
404
|
const isAuto = types.includes(InteractRequestType.AUTO);
|
|
405
|
+
const requestedOthers = types.filter((t) => t !== InteractRequestType.AUTO && t !== InteractRequestType.TEXT);
|
|
406
|
+
let modalitiesInstruction = "";
|
|
407
|
+
if (isAuto) {
|
|
408
|
+
modalitiesInstruction = `Analyze the user's message and optionally decide to use allowed modalities: ${requestedOthers.join(", ") || "none"}.
|
|
409
|
+
- 'textResponse' is ALWAYS REQUIRED.
|
|
410
|
+
- The modalities you are ALLOWED to dynamically include: ${requestedOthers.length > 0 ? requestedOthers.join(", ") : "None (Only text is allowed)"}. Do not include other modalities.`;
|
|
411
|
+
if (requestedOthers.includes(InteractRequestType.IMAGE)) {
|
|
412
|
+
modalitiesInstruction += `\n - Include 'imageParams' for visual/photo requests or key visual moments during active events; explicitly describe current clothing/exposure in image fields.`;
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
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.`;
|
|
416
|
+
}
|
|
417
|
+
if (requestedOthers.includes(InteractRequestType.VOICE)) {
|
|
418
|
+
modalitiesInstruction += `\n - Include 'voiceArgs' ONLY if the complicated tone/emotion is hard to express via pure text, or if the user explicitly requests to hear your voice. Otherwise, set it to null.`;
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
modalitiesInstruction += `\n - ALWAYS set 'voiceArgs' to null.`;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
modalitiesInstruction = `You MUST return the requested modalities: ${requestedOthers.join(", ") || "only text"}.
|
|
426
|
+
- 'textResponse' is ALWAYS REQUIRED.`;
|
|
427
|
+
if (requestedOthers.includes(InteractRequestType.IMAGE)) {
|
|
428
|
+
modalitiesInstruction += `\n - 'imageParams' is REQUIRED. Include it and explicitly describe current clothing/exposure in image fields.`;
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
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.`;
|
|
432
|
+
}
|
|
433
|
+
if (requestedOthers.includes(InteractRequestType.VOICE)) {
|
|
434
|
+
modalitiesInstruction += `\n - 'voiceArgs' is REQUIRED. Include it.`;
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
modalitiesInstruction += `\n - ALWAYS set 'voiceArgs' to null.`;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
modalitiesInstruction += `\n - Include 'triggerEvent' only if the VERY LAST USER MESSAGE proposes a new activity/hangout; ignore older history.
|
|
441
|
+
- Outfit acquisition (VERY LAST USER MESSAGE only): set giftOutfit for gift/buy/add-clothes intent; otherwise null. giftOutfit format: { "descriptionText": "short outfit description" }.`;
|
|
393
442
|
// Combine state info into a clean descriptive context
|
|
394
443
|
const systemPrompt = `${this.buildStateContextPrompt(state, params.localContext)}
|
|
395
444
|
Available Wardrobe Outfits (For event triggers):
|
|
@@ -397,14 +446,7 @@ ${availableOutfits}
|
|
|
397
446
|
|
|
398
447
|
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.
|
|
399
448
|
|
|
400
|
-
${
|
|
401
|
-
? `Analyze the user's message and decide response modalities (text, image, voice).
|
|
402
|
-
- Always include 'textResponse'.
|
|
403
|
-
- Include 'imageParams' for visual/photo requests or key visual moments during active events; explicitly describe current clothing/exposure in image fields.
|
|
404
|
-
- Include 'voiceArgs' ONLY if the complicated tone/emotion is hard to express via pure text, or if the user explicitly requests to hear your voice. Otherwise, set it to null.
|
|
405
|
-
- Include 'triggerEvent' only if the VERY LAST USER MESSAGE proposes a new activity/hangout; ignore older history.
|
|
406
|
-
- Outfit acquisition (VERY LAST USER MESSAGE only): set giftOutfit for gift/buy/add-clothes intent; otherwise null. giftOutfit format: { "descriptionText": "short outfit description" }.`
|
|
407
|
-
: `Requested types to fulfill: ${types.join(", ")}`}
|
|
449
|
+
${modalitiesInstruction}
|
|
408
450
|
Every turn adjusts trust: positive +1, negative -1, neutral 0. Always include 'stateUpdate' with integer 'temperatureDelta' (range guidance: 0 cold to 100 obsessive).
|
|
409
451
|
|
|
410
452
|
Always return 'stateUpdate.ongoingScene' as an object with both keys: { "scene": string, "outfit": string }.
|
|
@@ -413,7 +455,9 @@ For 'ongoingScene.outfit': decide based on the current active wardrobe by defaul
|
|
|
413
455
|
USER ANALYSIS WORKFLOW:
|
|
414
456
|
- Extract from VERY LAST USER MESSAGE only.
|
|
415
457
|
- Add only explicit new user facts from this turn (no inference).
|
|
416
|
-
-
|
|
458
|
+
- For 'preference', only capture explicit statements (e.g., "I like/love/dislike/hate...").
|
|
459
|
+
- For 'boundary', only capture explicit rejections or limitations (e.g., "Don't talk about X", "I won't do Y").
|
|
460
|
+
- Categories: 'realName', 'occupation', 'age', 'gender', 'hobby', 'trait', 'communicationStyle', 'boundary', 'preference'.
|
|
417
461
|
- Keep nicknames in stateUpdate; do not place them in newFactsLearned.
|
|
418
462
|
- If no new fact is explicit, set userAnalysis to null.
|
|
419
463
|
|
|
@@ -427,13 +471,13 @@ Output JSON Schema:
|
|
|
427
471
|
"textResponse": "Spoken dialogue ONLY. Never include actions or parentheses.",
|
|
428
472
|
"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" } },
|
|
429
473
|
"giftOutfit": { "descriptionText": "Concise description of the newly acquired outfit to add into wardrobe." },
|
|
430
|
-
"userAnalysis": { "newFactsLearned": [{ "category": "realName|occupation|age|gender|hobby|trait|communicationStyle|boundary", "value": "explicit new user fact from VERY LAST USER MESSAGE" }] },
|
|
474
|
+
"userAnalysis": { "newFactsLearned": [{ "category": "realName|occupation|age|gender|hobby|trait|communicationStyle|boundary|preference", "value": "explicit new user fact from VERY LAST USER MESSAGE" }] },
|
|
431
475
|
"isEndTurn": false,
|
|
432
476
|
"triggerEvent": {
|
|
433
477
|
${this.getEventSchemaParams(state.dynamic_context?.userNickname)}
|
|
434
478
|
},
|
|
435
|
-
${this.getImageSchemaParams()},
|
|
436
|
-
${this.getVoiceSchemaFromState(state)}
|
|
479
|
+
${this.getImageSchemaParams(requestedOthers.includes(InteractRequestType.IMAGE))},
|
|
480
|
+
${this.getVoiceSchemaFromState(state, requestedOthers.includes(InteractRequestType.VOICE))}
|
|
437
481
|
}
|
|
438
482
|
Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent", "giftOutfit", or "userAnalysis" are not needed, set them to null. "stateUpdate" cannot be null. Return valid raw JSON only.`;
|
|
439
483
|
const transcript = this.buildHistoryTranscript(params.history, state);
|
|
@@ -452,7 +496,7 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
|
|
|
452
496
|
// console.debug("[CyberSoulClient] Raw LLM Response:", rawLlmResponse);
|
|
453
497
|
let parsedIntent;
|
|
454
498
|
try {
|
|
455
|
-
parsedIntent = robustJsonParse(rawLlmResponse, "Dispatcher fallback");
|
|
499
|
+
parsedIntent = robustJsonParse(rawLlmResponse, "Dispatcher fallback", { textResponse: "", actionText: "", isEndTurn: false });
|
|
456
500
|
}
|
|
457
501
|
catch (e) {
|
|
458
502
|
console.warn("[CyberSoulClient] JSON parse failed, falling back to raw text:", e);
|
|
@@ -499,8 +543,8 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
|
|
|
499
543
|
parsedIntent.giftOutfit.descriptionText.trim().length > 0) {
|
|
500
544
|
mediaTasks.push(this.giftOutfit(parsedIntent.giftOutfit.descriptionText.trim()).catch((e) => console.error("[CyberSoulClient] Auto giftOutfit failed:", e)));
|
|
501
545
|
}
|
|
502
|
-
const shouldGenerateImage = types.includes(InteractRequestType.IMAGE)
|
|
503
|
-
(isAuto
|
|
546
|
+
const shouldGenerateImage = types.includes(InteractRequestType.IMAGE) &&
|
|
547
|
+
(!isAuto || !!parsedIntent.imageParams);
|
|
504
548
|
if (shouldGenerateImage) {
|
|
505
549
|
const imagePayload = parsedIntent.imageParams && typeof parsedIntent.imageParams === "object"
|
|
506
550
|
? parsedIntent.imageParams
|
|
@@ -512,8 +556,8 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
|
|
|
512
556
|
finalImageUrl = res.image_url;
|
|
513
557
|
}).catch(e => console.error("[CyberSoulClient] Image generation failed:", e)));
|
|
514
558
|
}
|
|
515
|
-
const shouldGenerateVoice = types.includes(InteractRequestType.VOICE)
|
|
516
|
-
(isAuto
|
|
559
|
+
const shouldGenerateVoice = types.includes(InteractRequestType.VOICE) &&
|
|
560
|
+
(!isAuto || !!parsedIntent.voiceArgs);
|
|
517
561
|
if (shouldGenerateVoice) {
|
|
518
562
|
const normalizedVoiceArgs = parsedIntent.voiceArgs && typeof parsedIntent.voiceArgs === "object"
|
|
519
563
|
? parsedIntent.voiceArgs
|
|
@@ -654,7 +698,7 @@ CRITICAL: Output MUST be ONLY valid JSON with no markdown block wrappers. Do NOT
|
|
|
654
698
|
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.
|
|
655
699
|
Output strictly valid JSON ONLY. No markdown, no conversational filler. Return exactly matching this schema:
|
|
656
700
|
{
|
|
657
|
-
${this.getImageSchemaParams()}
|
|
701
|
+
${this.getImageSchemaParams(true)}
|
|
658
702
|
}`;
|
|
659
703
|
const transcript = this.buildHistoryTranscript(params.interactParams?.history, state);
|
|
660
704
|
const promptMessages = [
|
|
@@ -689,7 +733,7 @@ Output strictly valid JSON ONLY. No markdown, no conversational filler. Return e
|
|
|
689
733
|
You are a voice acting director. ${this.getVoiceDirectorInstruction(state)}
|
|
690
734
|
Output strictly valid JSON ONLY. No markdown, no conversational filler. Return exactly matching this schema:
|
|
691
735
|
{
|
|
692
|
-
${this.getVoiceSchemaFromState(state)}
|
|
736
|
+
${this.getVoiceSchemaFromState(state, true)}
|
|
693
737
|
}`;
|
|
694
738
|
const transcript = this.buildHistoryTranscript(params.interactParams?.history, state);
|
|
695
739
|
const promptMessages = [
|
|
@@ -842,6 +886,7 @@ Output requirements:
|
|
|
842
886
|
traits: [],
|
|
843
887
|
communicationStyle: "",
|
|
844
888
|
boundaries: [],
|
|
889
|
+
preferences: [],
|
|
845
890
|
}
|
|
846
891
|
};
|
|
847
892
|
const systemPrompt = `You are an AI Memory Consolidation Engine for a virtual companion.
|
|
@@ -855,7 +900,7 @@ Your task is to merge the 'Current Core Memory' and 'Current User Codex' with 'N
|
|
|
855
900
|
5. **Limit:** Maximum 10 items per array.
|
|
856
901
|
|
|
857
902
|
**Rules for UserCodex:**
|
|
858
|
-
1. **Deduplicate & Consolidate:** Remove duplicate hobbies, traits, and
|
|
903
|
+
1. **Deduplicate & Consolidate:** Remove duplicate hobbies, traits, boundaries, and preferences. Combine related points into concise descriptors.
|
|
859
904
|
2. **Update Facts:** If the new events contain updated basic info (like new realName, different occupation), update it. Otherwise keep the existing info.
|
|
860
905
|
3. **Keep it Clean:** Maximum 15 items per array.
|
|
861
906
|
|
|
@@ -885,7 +930,8 @@ Your task is to merge the 'Current Core Memory' and 'Current User Codex' with 'N
|
|
|
885
930
|
"hobbies": ["string"],
|
|
886
931
|
"traits": ["string"],
|
|
887
932
|
"communicationStyle": "string",
|
|
888
|
-
"boundaries": ["string"]
|
|
933
|
+
"boundaries": ["string"],
|
|
934
|
+
"preferences": ["string"]
|
|
889
935
|
}
|
|
890
936
|
}
|
|
891
937
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -82,7 +82,7 @@ export interface DispatcherIntent {
|
|
|
82
82
|
} | null;
|
|
83
83
|
userAnalysis?: {
|
|
84
84
|
newFactsLearned: {
|
|
85
|
-
category: "realName" | "occupation" | "age" | "gender" | "hobby" | "trait" | "communicationStyle" | "boundary";
|
|
85
|
+
category: "realName" | "occupation" | "age" | "gender" | "hobby" | "trait" | "communicationStyle" | "boundary" | "preference";
|
|
86
86
|
value: string;
|
|
87
87
|
}[];
|
|
88
88
|
};
|
|
@@ -129,6 +129,7 @@ export interface UserCodex {
|
|
|
129
129
|
traits: string[];
|
|
130
130
|
communicationStyle: string;
|
|
131
131
|
boundaries: string[];
|
|
132
|
+
preferences?: string[];
|
|
132
133
|
};
|
|
133
134
|
familiarityScore?: number;
|
|
134
135
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function robustJsonParse<T>(jsonString: string, contextMessage?: string): T;
|
|
1
|
+
export declare function robustJsonParse<T>(jsonString: string, contextMessage?: string, fallbackTemplate?: Record<string, any>): T;
|
package/dist/utils/json.utils.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export function robustJsonParse(jsonString, contextMessage = 'throwing original error') {
|
|
1
|
+
export function robustJsonParse(jsonString, contextMessage = 'throwing original error', fallbackTemplate) {
|
|
2
2
|
let cleanJson = jsonString.trim();
|
|
3
3
|
// 0. Inject missing colons between string keys and string values (e.g. "key""value" -> "key":"value")
|
|
4
4
|
// Only insert the colon if we match a likely key (alphanumeric/hyphen) followed by quotes, handling smart quotes.
|
|
@@ -119,6 +119,50 @@ export function robustJsonParse(jsonString, contextMessage = 'throwing original
|
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
+
// FINAL FALLBACK: Regex extraction of requested fields if fallbackTemplate is provided
|
|
123
|
+
if (fallbackTemplate) {
|
|
124
|
+
console.warn(`[robustJsonParse] Regex fallback using template for: ${contextMessage}`);
|
|
125
|
+
const extractedObj = { ...fallbackTemplate };
|
|
126
|
+
let extractedAny = false;
|
|
127
|
+
for (const key of Object.keys(fallbackTemplate)) {
|
|
128
|
+
// 1. Try to extract string values handling escaped characters like \" and \n
|
|
129
|
+
const stringMatch = cleanJson.match(new RegExp(`"${key}"\\s*:\\s*"((?:[^"\\\\]|\\\\.)*)"`));
|
|
130
|
+
if (stringMatch) {
|
|
131
|
+
try {
|
|
132
|
+
extractedObj[key] = JSON.parse(`"${stringMatch[1]}"`);
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
extractedObj[key] = stringMatch[1];
|
|
136
|
+
}
|
|
137
|
+
extractedAny = true;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
// 2. Try to extract booleans, numbers, or null
|
|
141
|
+
const primitiveMatch = cleanJson.match(new RegExp(`"${key}"\\s*:\\s*([a-zA-Z0-9_.-]+)`));
|
|
142
|
+
if (primitiveMatch) {
|
|
143
|
+
const val = primitiveMatch[1];
|
|
144
|
+
if (val === 'true') {
|
|
145
|
+
extractedObj[key] = true;
|
|
146
|
+
extractedAny = true;
|
|
147
|
+
}
|
|
148
|
+
else if (val === 'false') {
|
|
149
|
+
extractedObj[key] = false;
|
|
150
|
+
extractedAny = true;
|
|
151
|
+
}
|
|
152
|
+
else if (val === 'null') {
|
|
153
|
+
extractedObj[key] = null;
|
|
154
|
+
extractedAny = true;
|
|
155
|
+
}
|
|
156
|
+
else if (!isNaN(Number(val))) {
|
|
157
|
+
extractedObj[key] = Number(val);
|
|
158
|
+
extractedAny = true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (extractedAny) {
|
|
163
|
+
return extractedObj;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
122
166
|
console.warn(`Failed to parse Dispatcher Intent: ${contextMessage}. Falling back to plain text.`);
|
|
123
167
|
throw e;
|
|
124
168
|
}
|