@space3-npm/cybersoul-client 1.0.0
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/LICENSE +21 -0
- package/README.md +94 -0
- package/dist/client.d.ts +10 -0
- package/dist/client.js +207 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/providers/minimax.provider.d.ts +9 -0
- package/dist/providers/minimax.provider.js +45 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.js +1 -0
- package/dist/utils/json.utils.d.ts +1 -0
- package/dist/utils/json.utils.js +67 -0
- package/package.json +28 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Space3 Digital Media Tech Studio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# cybersoul-client
|
|
2
|
+
|
|
3
|
+
`cybersoul-client` is the official TypeScript client SDK for interacting with the Cyber-Soul backend service. It acts as the "Brain" orchestrator on the client side, converting your local text inputs into rich, multi-modal outputs (text, voice, and image) driven by an LLM architecture.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Cyber-Soul Service transforms static text-based virtual companions into fully realized, multi-modal emotional personas. The `cybersoul-client` sits between your user-facing application (or OpenClaw agent) and the Cyber-Soul backend infrastructure.
|
|
8
|
+
|
|
9
|
+
### The Role of the SDK in the Architecture
|
|
10
|
+
|
|
11
|
+
1. **Context Management & Synchronization**: The client synchronizes the character's remote state (relationship stage, active daily events, current wardrobe) and merges it with local context.
|
|
12
|
+
2. **Local LLM Execution (BYOK)**: It securely utilizes the developer's provided LLM API key (e.g., MiniMax) locally. This keeps the heaviest cognitive lifting decoupled from the core database backend, letting the user control the underlying intelligence layer.
|
|
13
|
+
3. **Intent Parsing**: The LLM evaluates the chat context and returns a structured JSON intent determining what modalities are necessary (e.g., if a user asked for a photo, the LLM sets `imageParams`; if the relationship progressed, it creates a `stateUpdate`).
|
|
14
|
+
4. **Primitive Orchestration**: The SDK orchestrates low-level generation operations in parallel against the Cyber-Soul backend:
|
|
15
|
+
- Evaluates `stateUpdate` and fires a `PATCH` request to persist emotional inertia remotely.
|
|
16
|
+
- Generates images via `/api/v1/cyber-soul/image/generate`.
|
|
17
|
+
- Synthesizes speech via `/api/v1/cyber-soul/voice/generate`.
|
|
18
|
+
5. **Consolidated Delivery**: Once all operations complete, it returns a clean, unified payload ready for UI rendering.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
You can install the SDK locally or via npm (when published):
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install cybersoul-client
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
Initialize the client with your Character Key (provided by your Cyber-Soul Dashboard) and your local LLM configuration:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { CyberSoulClient } from 'cybersoul-client';
|
|
34
|
+
|
|
35
|
+
const client = new CyberSoulClient({
|
|
36
|
+
characterKey: 'YOUR_CHARACTER_KEY_HASH', // Ties requests to your specific Cyber-Soul persona
|
|
37
|
+
backendUrl: 'http://localhost:3002', // The Cyber-Soul core service URL
|
|
38
|
+
llmConfig: {
|
|
39
|
+
provider: 'minimax',
|
|
40
|
+
apiKey: 'YOUR_MINIMAX_API_KEY',
|
|
41
|
+
model: 'MiniMax-M2.7'
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Interact with your Cyber-Soul
|
|
46
|
+
const response = await client.interact({
|
|
47
|
+
userMessage: "Good morning! Can you send me a quick selfie before work?",
|
|
48
|
+
requestTypes: ['auto'], // Can force 'text', 'image', or 'voice'. 'auto' lets the LLM decide.
|
|
49
|
+
history: [
|
|
50
|
+
{ role: 'user', content: 'See you tomorrow!' },
|
|
51
|
+
{ role: 'assistant', content: 'Goodnight! Sweet dreams.' }
|
|
52
|
+
],
|
|
53
|
+
onTextReady: (text) => {
|
|
54
|
+
// Fired immediately as soon as the text intent is parsed from the LLM,
|
|
55
|
+
// before waiting for heavy media generations (voice/image) to complete.
|
|
56
|
+
console.log("Instant text response:", text);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (response.status === 'success') {
|
|
61
|
+
console.log("Final text:", response.textResponse);
|
|
62
|
+
if (response.imageUrl) console.log("Image attached:", response.imageUrl);
|
|
63
|
+
if (response.audioUrl) console.log("Audio attached:", response.audioUrl, "Duration:", response.durationSec);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## How It Works Under The Hood
|
|
68
|
+
|
|
69
|
+
```mermaid
|
|
70
|
+
flowchart TD
|
|
71
|
+
App[User App / Chatbot] -- "interact(msg)" --> SDK[cybersoul-client SDK]
|
|
72
|
+
SDK -- "1. Sync Context" --> CSBackend[Cyber-Soul Backend]
|
|
73
|
+
CSBackend -. "State Profile (Wardrobe, Mood)" .-> SDK
|
|
74
|
+
SDK -- "2. Build System Prompt & Run LLM" --> LLM[Local LLM / provider]
|
|
75
|
+
LLM -. "JSON Intent (Text, Media args)" .-> SDK
|
|
76
|
+
|
|
77
|
+
SDK -- "3. Trigger text ready callback" --> App
|
|
78
|
+
SDK -- "4. Commit state updates" --> CSBackend
|
|
79
|
+
|
|
80
|
+
subgraph ParallelMedia [Parallel Media Generation]
|
|
81
|
+
SDK -- "5a. Fetch Image Primitive" --> GenImage[Backend: /image/generate]
|
|
82
|
+
SDK -- "5b. Fetch Audio Primitive" --> GenAudio[Backend: /voice/generate]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
GenImage -. "Image URL" .-> SDK
|
|
86
|
+
GenAudio -. "Audio URL" .-> SDK
|
|
87
|
+
SDK -- "6. Consolidated Promise resolves" --> App
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Usage in Custom Applications (OpenClaw / Frontend)
|
|
91
|
+
|
|
92
|
+
When plugging this SDK into an existing bot framework like OpenClaw, you will generally bootstrap the system once with your config files, then call `.interact()` on every new message event.
|
|
93
|
+
|
|
94
|
+
Because `interact` acts as an all-in-one state-machine adapter, it isolates multi-modal state tracking—you simply provide the raw user string and the array of recent history, and the SDK takes care of API generation, prompt construction, and relationship temperature updates implicitly.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { CyberSoulClientConfig, InteractParams, InteractResponse } from "./types";
|
|
2
|
+
export declare class CyberSoulClient {
|
|
3
|
+
private config;
|
|
4
|
+
private llm;
|
|
5
|
+
constructor(config: CyberSoulClientConfig);
|
|
6
|
+
private fetchRemoteState;
|
|
7
|
+
private updateDynamicContext;
|
|
8
|
+
private generatePrimitive;
|
|
9
|
+
interact(params: InteractParams): Promise<InteractResponse>;
|
|
10
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { robustJsonParse } from "./utils/json.utils";
|
|
2
|
+
import { MinimaxProvider } from "./providers/minimax.provider";
|
|
3
|
+
export class CyberSoulClient {
|
|
4
|
+
config;
|
|
5
|
+
llm;
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.config = config;
|
|
8
|
+
// Setup Provider
|
|
9
|
+
if (config.llmConfig.provider === "minimax") {
|
|
10
|
+
this.llm = new MinimaxProvider(config.llmConfig);
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
throw new Error(`Unsupported LLM provider: ${config.llmConfig.provider}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async fetchRemoteState() {
|
|
17
|
+
const url = `${this.config.backendUrl}/api/v1/cyber-soul/state`;
|
|
18
|
+
const res = await fetch(url, {
|
|
19
|
+
headers: { Authorization: `Bearer ${this.config.characterKey}` },
|
|
20
|
+
});
|
|
21
|
+
if (!res.ok)
|
|
22
|
+
throw new Error("Failed to fetch character state");
|
|
23
|
+
const json = await res.json();
|
|
24
|
+
return json.data;
|
|
25
|
+
}
|
|
26
|
+
async updateDynamicContext(stateUpdate) {
|
|
27
|
+
if (!stateUpdate)
|
|
28
|
+
return;
|
|
29
|
+
// Map TS schema intent (temperatureDelta) to match Backend payload schema (temperature)
|
|
30
|
+
const payload = { ...stateUpdate };
|
|
31
|
+
if (payload.temperatureDelta !== undefined) {
|
|
32
|
+
payload.temperature = payload.temperatureDelta;
|
|
33
|
+
delete payload.temperatureDelta;
|
|
34
|
+
}
|
|
35
|
+
const url = `${this.config.backendUrl}/api/v1/cyber-soul/characters/dynamic-context`;
|
|
36
|
+
await fetch(url, {
|
|
37
|
+
method: "PATCH",
|
|
38
|
+
headers: {
|
|
39
|
+
Authorization: `Bearer ${this.config.characterKey}`,
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify(payload),
|
|
43
|
+
}).catch((e) => console.error("Failed to update dynamic context", e)); // non-blocking error handler
|
|
44
|
+
}
|
|
45
|
+
async generatePrimitive(type, payload) {
|
|
46
|
+
const url = `${this.config.backendUrl}/api/v1/cyber-soul/${type}/generate`;
|
|
47
|
+
const res = await fetch(url, {
|
|
48
|
+
method: "POST",
|
|
49
|
+
headers: {
|
|
50
|
+
Authorization: `Bearer ${this.config.characterKey}`,
|
|
51
|
+
"Content-Type": "application/json",
|
|
52
|
+
},
|
|
53
|
+
body: JSON.stringify(payload),
|
|
54
|
+
});
|
|
55
|
+
if (!res.ok)
|
|
56
|
+
throw new Error(`Failed to generate ${type}`);
|
|
57
|
+
return res.json();
|
|
58
|
+
}
|
|
59
|
+
async interact(params) {
|
|
60
|
+
try {
|
|
61
|
+
// 1. Sync remote context
|
|
62
|
+
const state = await this.fetchRemoteState();
|
|
63
|
+
// 2. Build local Prompt
|
|
64
|
+
const types = params.requestTypes && params.requestTypes.length > 0 ? params.requestTypes : ["auto"];
|
|
65
|
+
const isAuto = types.includes("auto");
|
|
66
|
+
// Combine state info into a clean descriptive context
|
|
67
|
+
const contextParts = [];
|
|
68
|
+
if (state.active_event) {
|
|
69
|
+
contextParts.push(`- Active Event: ${state.active_event.title} (${state.active_event.narrative_context})`);
|
|
70
|
+
}
|
|
71
|
+
if (state.next_event) {
|
|
72
|
+
contextParts.push(`- Next Event: ${state.next_event.title} at ${state.next_event.start_time} (in ${state.next_event.time_until_mins} mins)`);
|
|
73
|
+
}
|
|
74
|
+
if (state.active_wardrobe) {
|
|
75
|
+
contextParts.push(`- Wardrobe: ${state.active_wardrobe.name || state.active_wardrobe.id || "Current"}`);
|
|
76
|
+
}
|
|
77
|
+
const dyn = state.dynamic_context || {};
|
|
78
|
+
const stage = state.relationship_stage || "NEUTRAL";
|
|
79
|
+
contextParts.push(`- Relationship Info (Stage: ${stage}): You call the user '${dyn.userNickname || "User"}'. The user calls you '${dyn.agentNickname || "Agent"}'. Mood: ${dyn.talkingStyle || "Normal"}. Temp (0-100): ${dyn.temperature || 50}.`);
|
|
80
|
+
if (params.localContext) {
|
|
81
|
+
contextParts.push(`- Additional Context: ${params.localContext}`);
|
|
82
|
+
}
|
|
83
|
+
const scenarioContext = contextParts.join("\n");
|
|
84
|
+
const systemPrompt = `You are ${state.name}, acting as a virtual companion.
|
|
85
|
+
Demographics: Age ${state.age || 'unknown'}, Gender ${state.gender || 'unknown'}, Occupation ${state.occupation || 'unknown'}
|
|
86
|
+
Current time: ${new Date(state.current_time).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" })}
|
|
87
|
+
Current context/schedule: ${scenarioContext}
|
|
88
|
+
Relationship stage: ${state.relationship_stage}
|
|
89
|
+
Personality Traits: ${state.personality_traits || 'None'}
|
|
90
|
+
Interaction Boundaries: ${state.interaction_boundaries || 'None'}
|
|
91
|
+
Communication Style: ${state.communication_style || 'None'}
|
|
92
|
+
|
|
93
|
+
EMOTIONAL INERTIA RULES:
|
|
94
|
+
1. You must act strictly according to the current Relationship Stage (${state.relationship_stage || 'NEUTRAL'}).
|
|
95
|
+
2. If the user expresses sudden high affection (e.g. "I miss you") but your stage is COLD, you MUST react with skepticism, coldness, or appropriately distanced deflection. Do NOT instantly become warm.
|
|
96
|
+
3. Emotional mood changes must be slow. The 'temperatureDelta' should rarely exceed +/- 5 points per turn.
|
|
97
|
+
|
|
98
|
+
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.
|
|
99
|
+
|
|
100
|
+
${isAuto
|
|
101
|
+
? `Analyze the user's message to determine the appropriate response modalities (text, image, voice).
|
|
102
|
+
- Always include 'textResponse'.
|
|
103
|
+
- If the user explicitly asks to see a photo, look at you, or describing an action that warrants a photo, include 'imageParams'.
|
|
104
|
+
- If the user wants to hear you, or if appropriate for a voice message, include 'voiceArgs'.`
|
|
105
|
+
: `Requested types to fulfill: ${(params.requestTypes ?? []).join(", ")}`}
|
|
106
|
+
If the user's message shifts the emotional mood, establishes new nicknames, or warrants a relationship temperature change, you MUST include a 'stateUpdate' block. Temperature goes from 0 (cold/angry) to 100 (obsessively in love).
|
|
107
|
+
|
|
108
|
+
Output JSON Schema:
|
|
109
|
+
{
|
|
110
|
+
"textResponse": "The direct spoken dialogue in Chinese",
|
|
111
|
+
"stateUpdate": { "temperatureDelta": "+1 to -1", "userNickname": "What you now call the user", "agentNickname": "What the user calls you", "talkingStyle": "Current mood/style of talking" },
|
|
112
|
+
"imageParams": {
|
|
113
|
+
"mode": "structured | full-prompt (use 'full-prompt' for highly dynamic actions)",
|
|
114
|
+
"full_prompt": "Use only if mode is full-prompt. Highly detailed visual description in ENGLISH.",
|
|
115
|
+
"expression": "seductive | cute | happy | sleepy | dazed | pleased | default (Strictly choose ONE from this exact list. DO NOT invent new words like 'shy'.)",
|
|
116
|
+
"condition": "normal | sweaty | wet | messy | oily (Strictly choose ONE from this exact list.)",
|
|
117
|
+
"view_angle": "front | side | high_angle | from_below | boyfriend_view | selfie | mirror (Strictly choose ONE from this exact list.)",
|
|
118
|
+
"exposure": "normal | cleavage | see_through | half_naked | naked | intimate (Strictly choose ONE from this exact list.)",
|
|
119
|
+
"pose": "e.g., sitting on bed, leaning forward (ENGLISH ONLY)",
|
|
120
|
+
"scene": "e.g., cozy bedroom, morning light (ENGLISH ONLY)",
|
|
121
|
+
"outfit": "auto | ondemand",
|
|
122
|
+
"ondemandOutfit": "e.g., silk robe (ENGLISH ONLY)",
|
|
123
|
+
"style": "e.g., photorealistic (ENGLISH ONLY)"
|
|
124
|
+
},
|
|
125
|
+
"voiceArgs": { "style_instruction": "How the line should be spoken (Qwen3 format)", "emotion": "e.g., happy (MiniMax format, MUST BE ENGLISH, no Chinese)" }
|
|
126
|
+
}
|
|
127
|
+
Note: If "imageParams", "voiceArgs", or "stateUpdate" are not needed, set their values to null instead of omitting the keys completely (e.g., "imageParams": null). Output MUST be ONLY valid JSON with no markdown block wrappers. CRITICAL: Ensure your JSON has exactly one root object \`{\` and ends with exactly one \`}\` without any trailing garbage or extra brackets.`;
|
|
128
|
+
const promptMessages = [
|
|
129
|
+
{ role: "system", content: systemPrompt },
|
|
130
|
+
...(params.history || []),
|
|
131
|
+
{
|
|
132
|
+
role: "user",
|
|
133
|
+
content: params.userMessage +
|
|
134
|
+
"\n\n**CRITICAL REMINDER**: You MUST output your final response exactly in the JSON format specified in the system prompt. DO NOT output plain text dialogue directly. For 'imageParams', ALL values MUST be in ENGLISH ONLY without exception, and you MUST use the exact English enum strings provided.",
|
|
135
|
+
},
|
|
136
|
+
];
|
|
137
|
+
// 3. Local Execute LLM
|
|
138
|
+
const rawLlmResponse = await this.llm.generate(promptMessages, 1500, 0.7);
|
|
139
|
+
console.log("[CyberSoulClient] Raw LLM Response:", rawLlmResponse);
|
|
140
|
+
let parsedIntent;
|
|
141
|
+
try {
|
|
142
|
+
parsedIntent = robustJsonParse(rawLlmResponse, "Dispatcher fallback");
|
|
143
|
+
}
|
|
144
|
+
catch (e) {
|
|
145
|
+
console.warn("[CyberSoulClient] JSON parse failed, falling back to raw text:", e);
|
|
146
|
+
// Fallback robust mode - just text if completely broken
|
|
147
|
+
parsedIntent = {
|
|
148
|
+
textResponse: rawLlmResponse.replace(/^[\`\s]+|[\`\s]+$/g, "").trim(),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
console.log("[CyberSoulClient] Parsed Intent:", parsedIntent);
|
|
152
|
+
// 4. Update Backend State async
|
|
153
|
+
if (parsedIntent.stateUpdate) {
|
|
154
|
+
this.updateDynamicContext(parsedIntent.stateUpdate);
|
|
155
|
+
}
|
|
156
|
+
// Fire text ready callback if provided
|
|
157
|
+
if (params.onTextReady && parsedIntent.textResponse) {
|
|
158
|
+
params.onTextReady(parsedIntent.textResponse);
|
|
159
|
+
}
|
|
160
|
+
// 5. Build Final Media Calls parallel
|
|
161
|
+
const mediaTasks = [];
|
|
162
|
+
let finalImageUrl = undefined;
|
|
163
|
+
let finalAudioUrl = undefined;
|
|
164
|
+
let finalDurationSec = undefined;
|
|
165
|
+
const shouldGenerateImage = types.includes("image") || (isAuto && !!parsedIntent.imageParams);
|
|
166
|
+
if (shouldGenerateImage) {
|
|
167
|
+
mediaTasks.push(this.generatePrimitive("image", {
|
|
168
|
+
...parsedIntent.imageParams,
|
|
169
|
+
...(params.imageOverrides || {}),
|
|
170
|
+
}).then((res) => {
|
|
171
|
+
finalImageUrl = res.image_url;
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
174
|
+
const shouldGenerateVoice = types.includes("voice") || (isAuto && !!parsedIntent.voiceArgs);
|
|
175
|
+
if (shouldGenerateVoice) {
|
|
176
|
+
const dynamicArgs = { ...(parsedIntent.voiceArgs || {}) };
|
|
177
|
+
if (params.voiceStyleOverride) {
|
|
178
|
+
dynamicArgs.style_instruction = params.voiceStyleOverride;
|
|
179
|
+
}
|
|
180
|
+
mediaTasks.push(this.generatePrimitive("voice", {
|
|
181
|
+
text: parsedIntent.textResponse,
|
|
182
|
+
dynamicArgs,
|
|
183
|
+
}).then((res) => {
|
|
184
|
+
finalAudioUrl = res.audio_url;
|
|
185
|
+
finalDurationSec = res.duration_sec;
|
|
186
|
+
}));
|
|
187
|
+
}
|
|
188
|
+
// Wait for image/voice gens to return successfully
|
|
189
|
+
await Promise.all(mediaTasks);
|
|
190
|
+
return {
|
|
191
|
+
status: "success",
|
|
192
|
+
textResponse: parsedIntent.textResponse || "...",
|
|
193
|
+
imageUrl: finalImageUrl,
|
|
194
|
+
audioUrl: finalAudioUrl,
|
|
195
|
+
durationSec: finalDurationSec,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
console.error("[CyberSoulClient] Interface Error: ", error);
|
|
200
|
+
return {
|
|
201
|
+
status: "error",
|
|
202
|
+
textResponse: "System Error...",
|
|
203
|
+
error: error.message,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { LLMConfig, BaseLLMProvider } from '../types';
|
|
2
|
+
export declare class MinimaxProvider implements BaseLLMProvider {
|
|
3
|
+
private config;
|
|
4
|
+
constructor(config: LLMConfig);
|
|
5
|
+
generate(messages: {
|
|
6
|
+
role: string;
|
|
7
|
+
content: string;
|
|
8
|
+
}[], maxTokens?: number, temperature?: number): Promise<string>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export class MinimaxProvider {
|
|
2
|
+
config;
|
|
3
|
+
constructor(config) {
|
|
4
|
+
this.config = config;
|
|
5
|
+
}
|
|
6
|
+
async generate(messages, maxTokens = 1500, temperature = 0.7) {
|
|
7
|
+
if (!this.config.apiKey) {
|
|
8
|
+
throw new Error("Missing MiniMax API Key");
|
|
9
|
+
}
|
|
10
|
+
// Ensure we handle correct minimax URL
|
|
11
|
+
const host = 'https://api.minimaxi.com';
|
|
12
|
+
const payload = {
|
|
13
|
+
model: this.config.model || 'MiniMax-M2.7',
|
|
14
|
+
messages,
|
|
15
|
+
temperature,
|
|
16
|
+
max_tokens: maxTokens,
|
|
17
|
+
tokens_to_generate: maxTokens
|
|
18
|
+
};
|
|
19
|
+
const response = await fetch(`${host}/v1/text/chatcompletion_v2`, {
|
|
20
|
+
method: 'POST',
|
|
21
|
+
headers: {
|
|
22
|
+
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
23
|
+
'Content-Type': 'application/json'
|
|
24
|
+
},
|
|
25
|
+
body: JSON.stringify(payload)
|
|
26
|
+
});
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
throw new Error(`MiniMax API returned status: ${response.status}`);
|
|
29
|
+
}
|
|
30
|
+
const data = await response.json();
|
|
31
|
+
console.log("[MinimaxProvider] API Response Payload:", data);
|
|
32
|
+
if (data?.base_resp?.status_code && data.base_resp.status_code !== 0) {
|
|
33
|
+
throw new Error(`MiniMax SDK Error [${data.base_resp.status_code}]: ${data.base_resp.status_msg}`);
|
|
34
|
+
}
|
|
35
|
+
const choices = data?.choices || [];
|
|
36
|
+
if (choices.length > 0) {
|
|
37
|
+
const msg = choices[0].message;
|
|
38
|
+
if (!msg || typeof msg.content !== 'string') {
|
|
39
|
+
throw new Error("Invalid response format from MiniMax API: missing message content.");
|
|
40
|
+
}
|
|
41
|
+
return msg.content;
|
|
42
|
+
}
|
|
43
|
+
throw new Error("MiniMax API returned no choices.");
|
|
44
|
+
}
|
|
45
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface LLMConfig {
|
|
2
|
+
provider: 'minimax' | 'openai';
|
|
3
|
+
apiKey: string;
|
|
4
|
+
model: string;
|
|
5
|
+
}
|
|
6
|
+
export interface CyberSoulClientConfig {
|
|
7
|
+
characterKey: string;
|
|
8
|
+
backendUrl: string;
|
|
9
|
+
llmConfig: LLMConfig;
|
|
10
|
+
}
|
|
11
|
+
export interface InteractParams {
|
|
12
|
+
userMessage: string;
|
|
13
|
+
localContext?: string;
|
|
14
|
+
requestTypes?: string[];
|
|
15
|
+
history?: {
|
|
16
|
+
role: string;
|
|
17
|
+
content: string;
|
|
18
|
+
}[];
|
|
19
|
+
imageOverrides?: any;
|
|
20
|
+
voiceStyleOverride?: string;
|
|
21
|
+
onTextReady?: (textResponse: string) => void;
|
|
22
|
+
}
|
|
23
|
+
export interface InteractResponse {
|
|
24
|
+
status: 'success' | 'error';
|
|
25
|
+
textResponse: string;
|
|
26
|
+
imageUrl?: string;
|
|
27
|
+
audioUrl?: string;
|
|
28
|
+
durationSec?: number;
|
|
29
|
+
error?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface DispatcherIntent {
|
|
32
|
+
textResponse?: string;
|
|
33
|
+
imageParams?: any;
|
|
34
|
+
voiceArgs?: any;
|
|
35
|
+
stateUpdate?: {
|
|
36
|
+
temperatureDelta?: string | number;
|
|
37
|
+
userNickname?: string;
|
|
38
|
+
agentNickname?: string;
|
|
39
|
+
talkingStyle?: string;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export interface BaseLLMProvider {
|
|
43
|
+
generate(messages: {
|
|
44
|
+
role: string;
|
|
45
|
+
content: string;
|
|
46
|
+
}[], maxTokens?: number, temperature?: number): Promise<string>;
|
|
47
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function robustJsonParse<T>(jsonString: string, contextMessage?: string): T;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export function robustJsonParse(jsonString, contextMessage = 'throwing original error') {
|
|
2
|
+
let cleanJson = jsonString.trim();
|
|
3
|
+
const jsonMatch = cleanJson.match(/```json\n?([\s\S]*?)\n?```/i) || cleanJson.match(/```\n?([\s\S]*?)\n?```/);
|
|
4
|
+
if (jsonMatch) {
|
|
5
|
+
cleanJson = jsonMatch[1].trim();
|
|
6
|
+
}
|
|
7
|
+
cleanJson = cleanJson.replace(/,(\s*[}\]])/g, '$1');
|
|
8
|
+
cleanJson = cleanJson.replace(/,\s*$/, '');
|
|
9
|
+
// Extract the first complete JSON object by brace counting if it looks like there's trailing garbage
|
|
10
|
+
function extractFirstJsonObject(str) {
|
|
11
|
+
let braceCount = 0;
|
|
12
|
+
let startIdx = str.indexOf('{');
|
|
13
|
+
if (startIdx === -1)
|
|
14
|
+
return str;
|
|
15
|
+
let inString = false;
|
|
16
|
+
let escape = false;
|
|
17
|
+
for (let i = startIdx; i < str.length; i++) {
|
|
18
|
+
const char = str[i];
|
|
19
|
+
if (!escape && char === '"') {
|
|
20
|
+
inString = !inString;
|
|
21
|
+
}
|
|
22
|
+
escape = (char === '\\' && !escape);
|
|
23
|
+
if (!inString) {
|
|
24
|
+
if (char === '{')
|
|
25
|
+
braceCount++;
|
|
26
|
+
else if (char === '}') {
|
|
27
|
+
braceCount--;
|
|
28
|
+
if (braceCount === 0) {
|
|
29
|
+
return str.substring(startIdx, i + 1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return str;
|
|
35
|
+
}
|
|
36
|
+
let parsed;
|
|
37
|
+
try {
|
|
38
|
+
parsed = JSON.parse(cleanJson);
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
if (e instanceof SyntaxError) {
|
|
42
|
+
const extracted = extractFirstJsonObject(cleanJson);
|
|
43
|
+
if (extracted !== cleanJson) {
|
|
44
|
+
try {
|
|
45
|
+
parsed = JSON.parse(extracted);
|
|
46
|
+
return parsed;
|
|
47
|
+
}
|
|
48
|
+
catch (innerE) {
|
|
49
|
+
// ignore and let fallback chain continue
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
parsed = JSON.parse(cleanJson + '}');
|
|
55
|
+
}
|
|
56
|
+
catch (e2) {
|
|
57
|
+
try {
|
|
58
|
+
parsed = JSON.parse(cleanJson + '}}');
|
|
59
|
+
}
|
|
60
|
+
catch (e3) {
|
|
61
|
+
console.warn(`Failed to parse Dispatcher Intent: ${contextMessage}. Falling back to plain text.`);
|
|
62
|
+
throw e;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return parsed;
|
|
67
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@space3-npm/cybersoul-client",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module", "main": "dist/index.js", "module": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"prepare": "npm run build",
|
|
12
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"cybersoul",
|
|
16
|
+
"sdk",
|
|
17
|
+
"ai",
|
|
18
|
+
"multimodal"
|
|
19
|
+
],
|
|
20
|
+
"author": "Space3 Digital Media Tech Studio",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"description": "Cyber-Soul multimodal character interaction SDK by Space3 Digital Media Tech Studio",
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^25.6.0",
|
|
25
|
+
"ts-node": "^10.9.2",
|
|
26
|
+
"typescript": "^6.0.2"
|
|
27
|
+
}
|
|
28
|
+
}
|