@space3-npm/cybersoul-client 1.2.9 → 1.3.1
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 +2 -2
- package/dist/client.js +26 -22
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/llm.provider.d.ts +14 -0
- package/dist/llm.provider.js +85 -0
- package/dist/providers/generic-llm.provider.d.ts +14 -0
- package/dist/providers/generic-llm.provider.js +85 -0
- package/dist/types.d.ts +24 -4
- package/dist/types.js +12 -0
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CyberSoulClientConfig, InteractParams, ProactiveParams, ProactiveResponse, OndemandEventParams, OndemandEventResponse, DispatcherIntent, InteractResponse, CharacterState, CoreMemory, UserCodex } from "./types.js";
|
|
1
|
+
import { CyberSoulClientConfig, InteractParams, ProactiveParams, ProactiveResponse, OndemandEventParams, OndemandEventResponse, DispatcherIntent, InteractResponse, CharacterState, CoreMemory, UserCodex, LikedPicture } from "./types.js";
|
|
2
2
|
export declare class CyberSoulClient {
|
|
3
3
|
private config;
|
|
4
4
|
private llm;
|
|
@@ -103,7 +103,7 @@ export declare class CyberSoulClient {
|
|
|
103
103
|
/**
|
|
104
104
|
* Save the recent story moment to the character's backend database to be picked up by the core memory consolidation.
|
|
105
105
|
*/
|
|
106
|
-
saveMoment(summary: string, date: string, time: string): Promise<void>;
|
|
106
|
+
saveMoment(summary: string, date: string, time: string, likedPictures?: LikedPicture[]): Promise<void>;
|
|
107
107
|
/**
|
|
108
108
|
* Consolidate Core Memory and User Codex using edge LLM logic and sync to remote DB
|
|
109
109
|
*/
|
package/dist/client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { InteractRequestType, } from "./types.js";
|
|
2
2
|
import { robustJsonParse } from "./utils/json.utils.js";
|
|
3
|
-
import {
|
|
3
|
+
import { GenericLLMProvider } from "./llm.provider.js";
|
|
4
4
|
export class CyberSoulClient {
|
|
5
5
|
config;
|
|
6
6
|
llm;
|
|
@@ -13,12 +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
|
-
|
|
17
|
-
this.llm = new MinimaxProvider(config.llmConfig);
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
20
|
-
throw new Error(`Unsupported LLM provider: ${config.llmConfig.provider}`);
|
|
21
|
-
}
|
|
16
|
+
this.llm = new GenericLLMProvider(config.llmConfig, config.backendUrl, config.characterKey);
|
|
22
17
|
}
|
|
23
18
|
/**
|
|
24
19
|
* Internal wrapper for fetch that automatically injects the backend URL and Character Auth token.
|
|
@@ -165,9 +160,10 @@ export class CyberSoulClient {
|
|
|
165
160
|
const temperature = dyn.temperature ?? 50;
|
|
166
161
|
const contextParts = [];
|
|
167
162
|
// [1] CORE IDENTITY & PHYSICAL CONTEXT
|
|
163
|
+
const appearanceStr = state.appearance ? `\nAppearance: ${state.appearance}` : "";
|
|
168
164
|
contextParts.push(`[CORE IDENTITY]
|
|
169
165
|
Name: ${state.name}
|
|
170
|
-
Demographics: Age ${state.age || "unknown"}, Gender ${state.gender || "unknown"}, Occupation ${state.occupation || "unknown"}
|
|
166
|
+
Demographics: Age ${state.age || "unknown"}, Gender ${state.gender || "unknown"}, Occupation ${state.occupation || "unknown"}${appearanceStr}
|
|
171
167
|
Hobby: ${state.hobby || "unknown"}
|
|
172
168
|
Personality Traits: ${state.personality_traits || "None"}
|
|
173
169
|
Communication Style: ${state.communication_style || "None"}
|
|
@@ -235,21 +231,23 @@ Comm Style: ${psychological?.communicationStyle || "Unknown"}
|
|
|
235
231
|
Hobbies: ${(psychological?.hobbies || []).join(", ") || "Unknown"}
|
|
236
232
|
Traits/Boundaries: ${(psychological?.traits || []).join(", ") || "Unknown"} / ${(psychological?.boundaries || []).join(", ") || "Unknown"}
|
|
237
233
|
Preferences/Habits: ${(psychological?.preferences || []).join(", ") || "Unknown"}`);
|
|
238
|
-
// CURIOSITY DRIVE: Find what's missing
|
|
239
|
-
// Paradox avoidance: A cold
|
|
234
|
+
// CURIOSITY DRIVE: Find what's missing to build familiarity (which unlocks deeper relationship stages).
|
|
235
|
+
// Only do this actively if relationship is warming up. Paradox avoidance: A cold character shouldn't cheerfully fish for hobbies.
|
|
240
236
|
if (temperature >= 40 && stage !== "COLD" && stage !== "STRANGER") {
|
|
241
237
|
const missingFacts = [];
|
|
242
238
|
if (!basicInfo?.occupation)
|
|
243
|
-
missingFacts.push("their
|
|
239
|
+
missingFacts.push("their profession, career, or what they do for a living");
|
|
244
240
|
if (!psychological?.hobbies || psychological.hobbies.length === 0)
|
|
245
|
-
missingFacts.push("their hobbies or what they do for fun");
|
|
246
|
-
if (!basicInfo?.
|
|
247
|
-
missingFacts.push("
|
|
241
|
+
missingFacts.push("their hobbies, passions, or what they do for fun");
|
|
242
|
+
if (!basicInfo?.gender)
|
|
243
|
+
missingFacts.push("their gender (if it's obvious from context, confidently deduce and implicitly refer to it; if not, playfully guess or tease it out)");
|
|
244
|
+
if (!basicInfo?.age)
|
|
245
|
+
missingFacts.push("their age or generation (e.g., teasing about their age)");
|
|
248
246
|
if (!psychological?.traits || psychological.traits.length === 0)
|
|
249
|
-
missingFacts.push("their personality traits");
|
|
247
|
+
missingFacts.push("their personality traits (by putting them in interesting hypothetical situations)");
|
|
250
248
|
if (missingFacts.length > 0) {
|
|
251
|
-
contextParts.push(`\n[
|
|
252
|
-
|
|
249
|
+
contextParts.push(`\n[CURIOSITY DRIVE & CONNECTION]
|
|
250
|
+
To unlock deeper relationship stages, you need to understand them better. Whenever natural in conversation, creatively and subtly steer the interaction to find out about: ${missingFacts.slice(0, 2).join(" and ")}.`);
|
|
253
251
|
}
|
|
254
252
|
}
|
|
255
253
|
}
|
|
@@ -306,7 +304,7 @@ ${isProactive
|
|
|
306
304
|
return `"imageParams": null`;
|
|
307
305
|
return `"imageParams": {
|
|
308
306
|
"mode": "structured | full-prompt (use 'full-prompt' for highly dynamic actions)",
|
|
309
|
-
"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
|
|
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 precisely with the character's current Wardrobe and exposure state. Explicitly describe the character's exact clothing (or specify naked/half-naked if applicable). Ensure basic appearance (makeup, body shape, hair, facial features, etc.) aligns exactly with the character's foundational appearance profile.",
|
|
310
308
|
"expression": "seductive | cute | happy | sleepy | dazed | pleased | default (Strictly choose ONE from this exact list. DO NOT invent new words like 'shy'.)",
|
|
311
309
|
"condition": "normal | sweaty | wet | messy | oily (Strictly choose ONE from this exact list.)",
|
|
312
310
|
"view_angle": "front | side | high_angle | from_below | boyfriend_view | selfie | mirror (Strictly choose ONE from this exact list.)",
|
|
@@ -319,7 +317,7 @@ ${isProactive
|
|
|
319
317
|
}`;
|
|
320
318
|
}
|
|
321
319
|
getOutfitSelectionPrompt() {
|
|
322
|
-
return `When generating a triggerEvent, you MUST provide a suitable 'triggerEvent.outfitId' if the VERY LAST USER MESSAGE explicitly asks for an outfit change, OR if the new activity implies a context/location shift that conflicts with the current outfit (e.g., currently in SLEEPWEAR at home but going outside). Otherwise, keep it null. When changing outfits, match it to the event's activity, environment, and relationship stage (e.g.,
|
|
320
|
+
return `When generating a triggerEvent, you MUST provide a suitable 'triggerEvent.outfitId' if the VERY LAST USER MESSAGE explicitly asks for an outfit change, OR if the new activity implies a context/location shift that conflicts with the current outfit (e.g., currently in SLEEPWEAR at home but going outside). Otherwise, keep it null. When changing outfits, match it to the event's activity, environment, and relationship stage (e.g., CASUAL, COSTUME, INTIMATE, SLEEPWEAR, etc.).`;
|
|
323
321
|
}
|
|
324
322
|
getTriggerEventPolicyPrompt() {
|
|
325
323
|
return `- Include 'triggerEvent' only if the VERY LAST USER MESSAGE proposes a new activity/hangout, explicitly requests an outfit change, or proposes intimate/romantic actions; ignore older history. ${this.getOutfitSelectionPrompt()}`;
|
|
@@ -420,7 +418,7 @@ ${isProactive
|
|
|
420
418
|
- 'textResponse' is ALWAYS REQUIRED.
|
|
421
419
|
- The modalities you are ALLOWED to dynamically include: ${requestedOthers.length > 0 ? requestedOthers.join(", ") : "None (Only text is allowed)"}. Do not include other modalities.`;
|
|
422
420
|
if (requestedOthers.includes(InteractRequestType.IMAGE)) {
|
|
423
|
-
modalitiesInstruction += `\n - Include 'imageParams' for visual/photo requests or key visual moments during active events; explicitly describe current clothing/exposure in image fields.`;
|
|
421
|
+
modalitiesInstruction += `\n - Include 'imageParams' for visual/photo requests or key visual moments during active events; explicitly describe current clothing/exposure in image fields. CRITICAL POLICY: Guard your privacy! If the user feels like a stranger (low Familiarity) AND your Mood/Temperature is cool/distant (< 50), ALWAYS set 'imageParams' to null and naturally decline. If your Temperature is warm/high (>= 50), you may choose to share a picture if requested or if it fits naturally, even if you just met.`;
|
|
424
422
|
}
|
|
425
423
|
else {
|
|
426
424
|
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.`;
|
|
@@ -466,6 +464,7 @@ For 'ongoingScene.outfit': decide based on the current active wardrobe by defaul
|
|
|
466
464
|
USER ANALYSIS WORKFLOW:
|
|
467
465
|
- Extract from VERY LAST USER MESSAGE only.
|
|
468
466
|
- Add only explicit new user facts from this turn (no inference).
|
|
467
|
+
- Exclude transient, temporary, or time-sensitive activities (e.g., "I am working on a release today", "I'm eating dinner"). Do not map short-term actions into permanent categories like 'occupation' or 'hobby'.
|
|
469
468
|
- For 'preference', only capture explicit statements (e.g., "I like/love/dislike/hate...").
|
|
470
469
|
- For 'boundary', only capture explicit rejections or limitations (e.g., "Don't talk about X", "I won't do Y").
|
|
471
470
|
- Categories: 'realName', 'occupation', 'age', 'gender', 'hobby', 'trait', 'communicationStyle', 'boundary', 'preference'.
|
|
@@ -474,12 +473,15 @@ USER ANALYSIS WORKFLOW:
|
|
|
474
473
|
|
|
475
474
|
For 'isEndTurn', use true only when the interaction naturally concludes (confirmation/bye, event ending, or clear hard scene shift); otherwise false.
|
|
476
475
|
|
|
476
|
+
If the user explicitly praises, loves, or stars the VERY LAST picture you sent (not general appearance, but the recent photo itself), set 'likePreviousPicture' to true in the JSON, otherwise false.
|
|
477
|
+
|
|
477
478
|
Voice direction for voiceArgs: ${this.getVoiceDirectorInstruction(state)}
|
|
478
479
|
|
|
479
480
|
Output JSON Schema:
|
|
480
481
|
{
|
|
481
482
|
"actionText": "(Scene descriptions, physical actions, expressions, inner feelings) ONLY. Never include spoken dialogue here.",
|
|
482
483
|
"textResponse": "Spoken dialogue ONLY. Never include actions or parentheses.",
|
|
484
|
+
"likePreviousPicture": false,
|
|
483
485
|
"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" } },
|
|
484
486
|
"giftOutfit": { "descriptionText": "Concise description of the newly acquired outfit to add into wardrobe." },
|
|
485
487
|
"userAnalysis": { "newFactsLearned": [{ "category": "realName|occupation|age|gender|hobby|trait|communicationStyle|boundary|preference", "value": "explicit new user fact from VERY LAST USER MESSAGE" }] },
|
|
@@ -609,6 +611,7 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
|
|
|
609
611
|
actionText: parsedIntent.actionText || "",
|
|
610
612
|
imageUrl: finalImageUrl,
|
|
611
613
|
audioUrl: finalAudioUrl,
|
|
614
|
+
likePreviousPicture: parsedIntent.likePreviousPicture,
|
|
612
615
|
durationSec: finalDurationSec,
|
|
613
616
|
triggeredEvent: parsedIntent.triggerEvent || undefined,
|
|
614
617
|
stateUpdate: parsedIntent.stateUpdate,
|
|
@@ -769,7 +772,7 @@ CRITICAL: Output MUST be ONLY valid JSON with no markdown block wrappers. Do NOT
|
|
|
769
772
|
// Determine modalities (reusing logic from interact)
|
|
770
773
|
let modalitiesInstruction = "You are initiating conversation without a preceding user message.\\n";
|
|
771
774
|
if (requestedOthers.includes(InteractRequestType.IMAGE)) {
|
|
772
|
-
modalitiesInstruction += " - Include 'imageParams' for visual/photo
|
|
775
|
+
modalitiesInstruction += " - Include 'imageParams' for visual/photo moments. CRITICAL POLICY: NEVER send pictures to strangers! If Stage is STRANGER or COLD, or Familiarity is very low (< 10), ALWAYS set 'imageParams' to null.\\n";
|
|
773
776
|
}
|
|
774
777
|
else {
|
|
775
778
|
modalitiesInstruction += " - ALWAYS set 'imageParams' to null.\\n";
|
|
@@ -1006,13 +1009,14 @@ Output requirements:
|
|
|
1006
1009
|
/**
|
|
1007
1010
|
* Save the recent story moment to the character's backend database to be picked up by the core memory consolidation.
|
|
1008
1011
|
*/
|
|
1009
|
-
async saveMoment(summary, date, time) {
|
|
1012
|
+
async saveMoment(summary, date, time, likedPictures) {
|
|
1010
1013
|
const res = await this.apiFetch("/api/v1/cyber-soul/characters/moments", {
|
|
1011
1014
|
method: "POST",
|
|
1012
1015
|
body: JSON.stringify({
|
|
1013
1016
|
summary,
|
|
1014
1017
|
date,
|
|
1015
1018
|
time,
|
|
1019
|
+
likedPictures,
|
|
1016
1020
|
}),
|
|
1017
1021
|
});
|
|
1018
1022
|
if (!res.ok) {
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseLLMProvider, GenericLLMConfig } from './types.js';
|
|
2
|
+
export declare class GenericLLMProvider implements BaseLLMProvider {
|
|
3
|
+
private config;
|
|
4
|
+
private backendApiUrl;
|
|
5
|
+
private backendAuthToken?;
|
|
6
|
+
private static templateCache;
|
|
7
|
+
constructor(config: GenericLLMConfig, backendApiUrl: string, backendAuthToken?: string | undefined);
|
|
8
|
+
private fetchTemplate;
|
|
9
|
+
private extractResponse;
|
|
10
|
+
generate(messages: {
|
|
11
|
+
role: string;
|
|
12
|
+
content: string;
|
|
13
|
+
}[], maxTokens?: number, temperature?: number): Promise<string>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export class GenericLLMProvider {
|
|
2
|
+
config;
|
|
3
|
+
backendApiUrl;
|
|
4
|
+
backendAuthToken;
|
|
5
|
+
static templateCache = new Map();
|
|
6
|
+
constructor(config, backendApiUrl, backendAuthToken) {
|
|
7
|
+
this.config = config;
|
|
8
|
+
this.backendApiUrl = backendApiUrl;
|
|
9
|
+
this.backendAuthToken = backendAuthToken;
|
|
10
|
+
}
|
|
11
|
+
async fetchTemplate() {
|
|
12
|
+
const cacheKey = `${this.config.provider}:${this.config.model}`;
|
|
13
|
+
if (GenericLLMProvider.templateCache.has(cacheKey)) {
|
|
14
|
+
return GenericLLMProvider.templateCache.get(cacheKey);
|
|
15
|
+
}
|
|
16
|
+
// Need an auth token to call the backend APIs
|
|
17
|
+
const headers = {
|
|
18
|
+
'Content-Type': 'application/json'
|
|
19
|
+
};
|
|
20
|
+
if (this.backendAuthToken) {
|
|
21
|
+
headers['Authorization'] = `Bearer ${this.backendAuthToken}`;
|
|
22
|
+
}
|
|
23
|
+
const qs = new URLSearchParams({
|
|
24
|
+
provider: this.config.provider,
|
|
25
|
+
model: this.config.model
|
|
26
|
+
});
|
|
27
|
+
const resp = await fetch(`${this.backendApiUrl}/api/v1/cyber-soul/llm-models/template?${qs.toString()}`, {
|
|
28
|
+
headers
|
|
29
|
+
});
|
|
30
|
+
if (!resp.ok) {
|
|
31
|
+
throw new Error(`Failed to fetch LLM generic template: ${resp.status}`);
|
|
32
|
+
}
|
|
33
|
+
const template = await resp.json();
|
|
34
|
+
GenericLLMProvider.templateCache.set(cacheKey, template);
|
|
35
|
+
return template;
|
|
36
|
+
}
|
|
37
|
+
extractResponse(data, responsePath) {
|
|
38
|
+
const parts = responsePath.split('.');
|
|
39
|
+
let cursor = data;
|
|
40
|
+
for (const part of parts) {
|
|
41
|
+
if (cursor === undefined || cursor === null)
|
|
42
|
+
return '';
|
|
43
|
+
cursor = cursor[part];
|
|
44
|
+
}
|
|
45
|
+
if (typeof cursor !== 'string') {
|
|
46
|
+
throw new Error(`Extraction resulted in non-string type: ${typeof cursor}`);
|
|
47
|
+
}
|
|
48
|
+
return cursor;
|
|
49
|
+
}
|
|
50
|
+
async generate(messages, maxTokens = 1500, temperature = 0.7) {
|
|
51
|
+
const template = await this.fetchTemplate();
|
|
52
|
+
const headers = { ...template.headersTemplate };
|
|
53
|
+
if (this.config.apiKey) {
|
|
54
|
+
for (const key of Object.keys(headers)) {
|
|
55
|
+
if (typeof headers[key] === 'string') {
|
|
56
|
+
headers[key] = headers[key].replace('{{apiKey}}', this.config.apiKey);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const payload = { ...template.basePayload };
|
|
61
|
+
if (this.config.customSettings) {
|
|
62
|
+
Object.assign(payload, this.config.customSettings);
|
|
63
|
+
}
|
|
64
|
+
// We only explicitly map messages. Parameters like temperature or max_tokens
|
|
65
|
+
// should be defined in basePayload, customSettings, or configured via the UI.
|
|
66
|
+
if (!payload.messages || (Array.isArray(payload.messages) && payload.messages.length === 0)) {
|
|
67
|
+
payload.messages = messages;
|
|
68
|
+
}
|
|
69
|
+
const response = await fetch(template.apiUrl, {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers,
|
|
72
|
+
body: JSON.stringify(payload)
|
|
73
|
+
});
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
throw new Error(`Generic API returned status: ${response.status}`);
|
|
76
|
+
}
|
|
77
|
+
const data = await response.json();
|
|
78
|
+
try {
|
|
79
|
+
return this.extractResponse(data, template.responsePath || 'choices.0.message.content');
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
throw new Error(`Failed to extract response. Error: ${e?.message}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseLLMProvider, GenericLLMConfig } from '../types.js';
|
|
2
|
+
export declare class GenericLLMProvider implements BaseLLMProvider {
|
|
3
|
+
private config;
|
|
4
|
+
private backendApiUrl;
|
|
5
|
+
private backendAuthToken?;
|
|
6
|
+
private static templateCache;
|
|
7
|
+
constructor(config: GenericLLMConfig, backendApiUrl: string, backendAuthToken?: string | undefined);
|
|
8
|
+
private fetchTemplate;
|
|
9
|
+
private extractResponse;
|
|
10
|
+
generate(messages: {
|
|
11
|
+
role: string;
|
|
12
|
+
content: string;
|
|
13
|
+
}[], maxTokens?: number, temperature?: number): Promise<string>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export class GenericLLMProvider {
|
|
2
|
+
config;
|
|
3
|
+
backendApiUrl;
|
|
4
|
+
backendAuthToken;
|
|
5
|
+
static templateCache = new Map();
|
|
6
|
+
constructor(config, backendApiUrl, backendAuthToken) {
|
|
7
|
+
this.config = config;
|
|
8
|
+
this.backendApiUrl = backendApiUrl;
|
|
9
|
+
this.backendAuthToken = backendAuthToken;
|
|
10
|
+
}
|
|
11
|
+
async fetchTemplate() {
|
|
12
|
+
const cacheKey = `${this.config.provider}:${this.config.model}`;
|
|
13
|
+
if (GenericLLMProvider.templateCache.has(cacheKey)) {
|
|
14
|
+
return GenericLLMProvider.templateCache.get(cacheKey);
|
|
15
|
+
}
|
|
16
|
+
// Need an auth token to call the backend APIs
|
|
17
|
+
const headers = {
|
|
18
|
+
'Content-Type': 'application/json'
|
|
19
|
+
};
|
|
20
|
+
if (this.backendAuthToken) {
|
|
21
|
+
headers['Authorization'] = `Bearer ${this.backendAuthToken}`;
|
|
22
|
+
}
|
|
23
|
+
const qs = new URLSearchParams({
|
|
24
|
+
provider: this.config.provider,
|
|
25
|
+
model: this.config.model || ''
|
|
26
|
+
});
|
|
27
|
+
const resp = await fetch(`${this.backendApiUrl}/api/v1/cyber-soul/llm-models/template?${qs.toString()}`, {
|
|
28
|
+
headers
|
|
29
|
+
});
|
|
30
|
+
if (!resp.ok) {
|
|
31
|
+
throw new Error(`Failed to fetch LLM generic template: ${resp.status}`);
|
|
32
|
+
}
|
|
33
|
+
const template = await resp.json();
|
|
34
|
+
GenericLLMProvider.templateCache.set(cacheKey, template);
|
|
35
|
+
return template;
|
|
36
|
+
}
|
|
37
|
+
extractResponse(data, responsePath) {
|
|
38
|
+
const parts = responsePath.split('.');
|
|
39
|
+
let cursor = data;
|
|
40
|
+
for (const part of parts) {
|
|
41
|
+
if (cursor === undefined || cursor === null)
|
|
42
|
+
return '';
|
|
43
|
+
cursor = cursor[part];
|
|
44
|
+
}
|
|
45
|
+
if (typeof cursor !== 'string') {
|
|
46
|
+
throw new Error(`Extraction resulted in non-string type: ${typeof cursor}`);
|
|
47
|
+
}
|
|
48
|
+
return cursor;
|
|
49
|
+
}
|
|
50
|
+
async generate(messages, maxTokens = 1500, temperature = 0.7) {
|
|
51
|
+
if (!this.config.apiKey) {
|
|
52
|
+
throw new Error("Missing Generic Provider API Key");
|
|
53
|
+
}
|
|
54
|
+
const template = await this.fetchTemplate();
|
|
55
|
+
const headers = { ...template.headersTemplate };
|
|
56
|
+
for (const key of Object.keys(headers)) {
|
|
57
|
+
if (typeof headers[key] === 'string') {
|
|
58
|
+
headers[key] = headers[key].replace('{{apiKey}}', this.config.apiKey);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const payload = { ...template.basePayload };
|
|
62
|
+
if (this.config.customSettings) {
|
|
63
|
+
Object.assign(payload, this.config.customSettings);
|
|
64
|
+
}
|
|
65
|
+
// We only explicitly map messages. Parameters like temperature or max_tokens
|
|
66
|
+
// should be defined in basePayload, customSettings, or configured via the UI.
|
|
67
|
+
if (!payload.messages)
|
|
68
|
+
payload.messages = messages;
|
|
69
|
+
const response = await fetch(template.apiUrl, {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers,
|
|
72
|
+
body: JSON.stringify(payload)
|
|
73
|
+
});
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
throw new Error(`Generic API returned status: ${response.status}`);
|
|
76
|
+
}
|
|
77
|
+
const data = await response.json();
|
|
78
|
+
try {
|
|
79
|
+
return this.extractResponse(data, template.responsePath || 'choices.0.message.content');
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
throw new Error(`Failed to extract response. Error: ${e?.message}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
export interface
|
|
2
|
-
provider:
|
|
1
|
+
export interface GenericLLMConfig {
|
|
2
|
+
provider: string;
|
|
3
3
|
apiKey: string;
|
|
4
4
|
model: string;
|
|
5
|
+
customSettings?: Record<string, any>;
|
|
5
6
|
}
|
|
6
7
|
export interface CyberSoulClientConfig {
|
|
7
8
|
characterKey: string;
|
|
8
9
|
backendUrl: string;
|
|
9
|
-
llmConfig:
|
|
10
|
+
llmConfig: GenericLLMConfig;
|
|
10
11
|
requestTimeoutMs?: number;
|
|
11
12
|
maxRetries?: number;
|
|
12
13
|
}
|
|
@@ -61,10 +62,21 @@ export interface OndemandEventResponse {
|
|
|
61
62
|
scheduledDateStr?: string;
|
|
62
63
|
error?: string;
|
|
63
64
|
}
|
|
65
|
+
export declare enum WardrobeCategory {
|
|
66
|
+
CASUAL = "CASUAL",
|
|
67
|
+
FORMAL = "FORMAL",
|
|
68
|
+
WORKWEAR = "WORKWEAR",
|
|
69
|
+
SPORTSWEAR = "SPORTSWEAR",
|
|
70
|
+
SWIMWEAR = "SWIMWEAR",
|
|
71
|
+
COSTUME = "COSTUME",
|
|
72
|
+
SLEEPWEAR = "SLEEPWEAR",
|
|
73
|
+
INTIMATE = "INTIMATE",
|
|
74
|
+
DAILY = "DAILY"
|
|
75
|
+
}
|
|
64
76
|
export interface WardrobeItem {
|
|
65
77
|
id: string;
|
|
66
78
|
itemName: string;
|
|
67
|
-
category:
|
|
79
|
+
category: WardrobeCategory;
|
|
68
80
|
promptModifier: string;
|
|
69
81
|
}
|
|
70
82
|
export interface InteractResponse {
|
|
@@ -73,6 +85,7 @@ export interface InteractResponse {
|
|
|
73
85
|
actionText?: string;
|
|
74
86
|
imageUrl?: string;
|
|
75
87
|
audioUrl?: string;
|
|
88
|
+
likePreviousPicture?: boolean;
|
|
76
89
|
durationSec?: number;
|
|
77
90
|
triggeredEvent?: {
|
|
78
91
|
eventTitle?: string;
|
|
@@ -93,6 +106,7 @@ export interface DispatcherIntent {
|
|
|
93
106
|
textResponse?: string;
|
|
94
107
|
actionText?: string;
|
|
95
108
|
imageParams?: any;
|
|
109
|
+
likePreviousPicture?: boolean;
|
|
96
110
|
voiceArgs?: VoiceArgs | null;
|
|
97
111
|
giftOutfit?: {
|
|
98
112
|
descriptionText: string;
|
|
@@ -199,6 +213,7 @@ export interface CharacterState {
|
|
|
199
213
|
occupation?: string;
|
|
200
214
|
hobby?: string;
|
|
201
215
|
personality_traits?: string;
|
|
216
|
+
appearance?: string;
|
|
202
217
|
interaction_boundaries?: string;
|
|
203
218
|
communication_style?: string;
|
|
204
219
|
user_codex?: UserCodex;
|
|
@@ -262,3 +277,8 @@ export interface ICharacterProfile {
|
|
|
262
277
|
visualCustomConfig?: Record<string, Record<string, unknown>>;
|
|
263
278
|
[key: string]: unknown;
|
|
264
279
|
}
|
|
280
|
+
export interface LikedPicture {
|
|
281
|
+
url: string;
|
|
282
|
+
date: string;
|
|
283
|
+
mediaId?: string;
|
|
284
|
+
}
|
package/dist/types.js
CHANGED
|
@@ -5,3 +5,15 @@ export var InteractRequestType;
|
|
|
5
5
|
InteractRequestType["IMAGE"] = "image";
|
|
6
6
|
InteractRequestType["VOICE"] = "voice";
|
|
7
7
|
})(InteractRequestType || (InteractRequestType = {}));
|
|
8
|
+
export var WardrobeCategory;
|
|
9
|
+
(function (WardrobeCategory) {
|
|
10
|
+
WardrobeCategory["CASUAL"] = "CASUAL";
|
|
11
|
+
WardrobeCategory["FORMAL"] = "FORMAL";
|
|
12
|
+
WardrobeCategory["WORKWEAR"] = "WORKWEAR";
|
|
13
|
+
WardrobeCategory["SPORTSWEAR"] = "SPORTSWEAR";
|
|
14
|
+
WardrobeCategory["SWIMWEAR"] = "SWIMWEAR";
|
|
15
|
+
WardrobeCategory["COSTUME"] = "COSTUME";
|
|
16
|
+
WardrobeCategory["SLEEPWEAR"] = "SLEEPWEAR";
|
|
17
|
+
WardrobeCategory["INTIMATE"] = "INTIMATE";
|
|
18
|
+
WardrobeCategory["DAILY"] = "DAILY";
|
|
19
|
+
})(WardrobeCategory || (WardrobeCategory = {}));
|