@space3-npm/cybersoul-client 1.4.28 → 1.5.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/dist/api/cyberSoulApi.d.ts +198 -0
- package/dist/api/cyberSoulApi.js +427 -0
- package/dist/client.d.ts +127 -82
- package/dist/client.js +626 -1523
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/prompts/promptBuilders.d.ts +136 -0
- package/dist/prompts/promptBuilders.js +756 -0
- package/dist/prompts/types.d.ts +71 -0
- package/dist/prompts/types.js +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/utils/error.utils.d.ts +13 -0
- package/dist/utils/error.utils.js +41 -0
- package/dist/utils/history.utils.d.ts +40 -0
- package/dist/utils/history.utils.js +82 -0
- package/dist/utils/image.utils.d.ts +14 -0
- package/dist/utils/image.utils.js +26 -0
- package/dist/utils/requestTypes.utils.d.ts +16 -0
- package/dist/utils/requestTypes.utils.js +33 -0
- package/dist/utils/state.utils.d.ts +57 -0
- package/dist/utils/state.utils.js +103 -0
- package/dist/utils/time.utils.d.ts +65 -0
- package/dist/utils/time.utils.js +119 -0
- package/dist/utils/voice.utils.d.ts +38 -0
- package/dist/utils/voice.utils.js +62 -0
- package/package.json +1 -1
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { CharacterState, DispatcherIntent, PersistedDynamicContext, SupportedLLMModel, WardrobeItem, LikedPicture, CoreMemory, UserCodex } from "../types.js";
|
|
2
|
+
import { CyberSoulError } from "../errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for the [CyberSoulApi] HTTP layer. Mirrors the relevant
|
|
5
|
+
* subset of [CyberSoulClientConfig] so the API module can be reused
|
|
6
|
+
* independently of the orchestration layer.
|
|
7
|
+
*/
|
|
8
|
+
export interface CyberSoulApiConfig {
|
|
9
|
+
backendUrl: string;
|
|
10
|
+
characterKey: string;
|
|
11
|
+
requestTimeoutMs?: number;
|
|
12
|
+
maxRetries?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Optional fetch override. When provided, the API layer uses this in
|
|
15
|
+
* place of the global `fetch` for every HTTP call. Intended for
|
|
16
|
+
* environments where the global fetch is suspended by the host
|
|
17
|
+
* platform — e.g. React Native on Samsung BBA / Doze — and a native
|
|
18
|
+
* HTTP path must be used instead. Must conform to the standard
|
|
19
|
+
* `fetch` signature.
|
|
20
|
+
*/
|
|
21
|
+
fetchImpl?: typeof fetch;
|
|
22
|
+
}
|
|
23
|
+
/** Result of a successful image generation call. */
|
|
24
|
+
export interface GeneratedImage {
|
|
25
|
+
image_url: string;
|
|
26
|
+
id: string;
|
|
27
|
+
}
|
|
28
|
+
/** Result of a successful voice generation call. */
|
|
29
|
+
export interface GeneratedVoice {
|
|
30
|
+
audio_url: string;
|
|
31
|
+
id: string;
|
|
32
|
+
duration_sec?: number;
|
|
33
|
+
}
|
|
34
|
+
/** Payload accepted by PATCH /characters/dynamic-context. */
|
|
35
|
+
export interface DynamicContextPatchPayload {
|
|
36
|
+
temperature?: number;
|
|
37
|
+
temperatureAbsolute?: number;
|
|
38
|
+
ongoingScene?: {
|
|
39
|
+
scene: string;
|
|
40
|
+
outfit: string;
|
|
41
|
+
} | null;
|
|
42
|
+
userNickname?: string;
|
|
43
|
+
agentNickname?: string;
|
|
44
|
+
talkingStyle?: string;
|
|
45
|
+
userAnalysis?: DispatcherIntent["userAnalysis"];
|
|
46
|
+
}
|
|
47
|
+
/** Payload accepted by POST /characters/ondemand-event. */
|
|
48
|
+
export interface OndemandEventPayload {
|
|
49
|
+
eventTitle?: string;
|
|
50
|
+
eventDescription: string;
|
|
51
|
+
durationMins?: number;
|
|
52
|
+
outfitId?: string;
|
|
53
|
+
scheduledStartTimeStr?: string;
|
|
54
|
+
scheduledDateStr?: string;
|
|
55
|
+
}
|
|
56
|
+
/** Payload accepted by POST /characters/moments. */
|
|
57
|
+
export interface SaveMomentPayload {
|
|
58
|
+
summary: string;
|
|
59
|
+
date: string;
|
|
60
|
+
time: string;
|
|
61
|
+
likedPictures?: LikedPicture[];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Encapsulates every backend HTTP call used by the SDK. Owns the
|
|
65
|
+
* transport (timeout, retry, typed error mapping) and exposes one typed
|
|
66
|
+
* method per endpoint so the orchestration layer in `client.ts` never
|
|
67
|
+
* touches `fetch` or status codes directly.
|
|
68
|
+
*
|
|
69
|
+
* Error contract:
|
|
70
|
+
* - Transport-level failures are wrapped as [CyberSoulNetworkError] /
|
|
71
|
+
* [CyberSoulTimeoutError].
|
|
72
|
+
* - Backend typed failures (402 / wallet / sensitive-content / auth)
|
|
73
|
+
* are surfaced as the dedicated subclasses of [CyberSoulError] so
|
|
74
|
+
* callers can branch on `instanceof` instead of string-sniffing.
|
|
75
|
+
*/
|
|
76
|
+
export declare class CyberSoulApi {
|
|
77
|
+
private backendUrl;
|
|
78
|
+
private characterKey;
|
|
79
|
+
private requestTimeoutMs;
|
|
80
|
+
private maxRetries;
|
|
81
|
+
private fetchImpl?;
|
|
82
|
+
constructor(config: CyberSoulApiConfig);
|
|
83
|
+
/**
|
|
84
|
+
* Internal wrapper for fetch that injects the backend URL and the
|
|
85
|
+
* Character Auth token, applies per-request timeout, and retries
|
|
86
|
+
* transient server-side failures (HTTP >= 500) for idempotent
|
|
87
|
+
* methods (GET / HEAD).
|
|
88
|
+
*
|
|
89
|
+
* Exposed as `public` so callers that need a raw `Response` (e.g. to
|
|
90
|
+
* branch on status without paying for an exception) can reuse the
|
|
91
|
+
* same transport path. Most consumers should prefer the typed
|
|
92
|
+
* helpers below.
|
|
93
|
+
*/
|
|
94
|
+
apiFetch(endpoint: string, options?: RequestInit): Promise<Response>;
|
|
95
|
+
/**
|
|
96
|
+
* GET /api/v1/cyber-soul/state. Returns the full character state
|
|
97
|
+
* (identity, dynamic context, codex, memory, active event/wardrobe).
|
|
98
|
+
*
|
|
99
|
+
* 401/403 → [CyberSoulAuthError] (character may have been deleted).
|
|
100
|
+
* Other non-2xx → [CyberSoulApiError].
|
|
101
|
+
*/
|
|
102
|
+
getState(): Promise<CharacterState>;
|
|
103
|
+
/**
|
|
104
|
+
* GET /api/v1/cyber-soul/wardrobe. Returns the raw wardrobe items;
|
|
105
|
+
* the caller is responsible for any caching / formatting. Resolves
|
|
106
|
+
* to an empty array when the backend returns no items or a non-2xx
|
|
107
|
+
* (failures are swallowed because wardrobe is best-effort context
|
|
108
|
+
* for prompt assembly — a missing list must not abort a chat turn).
|
|
109
|
+
*/
|
|
110
|
+
getWardrobe(): Promise<WardrobeItem[]>;
|
|
111
|
+
/**
|
|
112
|
+
* POST /api/v1/cyber-soul/{type}/generate. Generates an image or a
|
|
113
|
+
* voice clip. Backend typed failures are surfaced as dedicated
|
|
114
|
+
* subclasses of [CyberSoulError] so callers can branch precisely:
|
|
115
|
+
* - 402 / INSUFFICIENT_POINTS → [CyberSoulInsufficientPointsError]
|
|
116
|
+
* - WALLET_DEDUCTION_ERROR → [CyberSoulWalletError]
|
|
117
|
+
* - E005 (sensitive content) → [CyberSoulSensitiveContentError]
|
|
118
|
+
* - 401 / 403 → [CyberSoulAuthError]
|
|
119
|
+
* - anything else → [CyberSoulApiError] (with legacy
|
|
120
|
+
* duck-typed `code` preserved)
|
|
121
|
+
*/
|
|
122
|
+
generatePrimitive(type: "image" | "voice", payload: any): Promise<GeneratedImage & GeneratedVoice>;
|
|
123
|
+
/**
|
|
124
|
+
* PATCH /api/v1/cyber-soul/characters/dynamic-context.
|
|
125
|
+
*
|
|
126
|
+
* The server applies stage-based dampening, familiarity soft-caps,
|
|
127
|
+
* hard floor, and stage re-evaluation, then returns the
|
|
128
|
+
* *authoritative* persisted `temperature` and `relationshipStage`.
|
|
129
|
+
*
|
|
130
|
+
* Returns the post-write snapshot, or `null` when there's nothing
|
|
131
|
+
* to send / the request fails (failure is non-fatal for a chat
|
|
132
|
+
* turn — callers must treat `null` as "no fresh snapshot").
|
|
133
|
+
*
|
|
134
|
+
* The caller is responsible for any payload normalization (e.g.
|
|
135
|
+
* mapping `temperatureDelta` → `temperature`); this method sends
|
|
136
|
+
* `payload` as-is.
|
|
137
|
+
*/
|
|
138
|
+
patchDynamicContext(payload: DynamicContextPatchPayload): Promise<PersistedDynamicContext | null>;
|
|
139
|
+
/**
|
|
140
|
+
* PATCH /api/v1/cyber-soul/characters/dynamic-context with an exact
|
|
141
|
+
* absolute temperature. Used by chat recall, where inverse deltas are
|
|
142
|
+
* not accurate once the backend has applied dampening, caps, and
|
|
143
|
+
* stage re-evaluation. The value is clamped to [0,100] with one
|
|
144
|
+
* decimal of precision before being sent.
|
|
145
|
+
*
|
|
146
|
+
* Returns `null` on transport failure or invalid input — strict,
|
|
147
|
+
* no silent fallback. Caller should treat `null` as "restore did
|
|
148
|
+
* not succeed" and surface the inconsistency.
|
|
149
|
+
*/
|
|
150
|
+
restoreDynamicContextTemperature(temperatureAbsolute: number): Promise<PersistedDynamicContext | null>;
|
|
151
|
+
/**
|
|
152
|
+
* POST /api/v1/cyber-soul/characters/ondemand-event. Schedules an
|
|
153
|
+
* on-demand event. Throws when the backend rejects the schedule.
|
|
154
|
+
*/
|
|
155
|
+
triggerOndemandEvent(payload: OndemandEventPayload): Promise<void>;
|
|
156
|
+
/**
|
|
157
|
+
* POST /api/v1/cyber-soul/characters/gift-outfit. Adds a new outfit
|
|
158
|
+
* (or several — the backend may expand a single description into
|
|
159
|
+
* multiple items) to the wardrobe inventory. Returns the number of
|
|
160
|
+
* items created, or `undefined` when the server didn't report a
|
|
161
|
+
* count (never fabricated).
|
|
162
|
+
*/
|
|
163
|
+
giftOutfit(descriptionText: string): Promise<number | undefined>;
|
|
164
|
+
/**
|
|
165
|
+
* POST /api/v1/cyber-soul/characters/bootstrap. Bootstraps a
|
|
166
|
+
* character profile from OpenClaw workspace files.
|
|
167
|
+
*/
|
|
168
|
+
bootstrapCharacter(workspaceFiles: Record<string, string>): Promise<void>;
|
|
169
|
+
/**
|
|
170
|
+
* POST /api/v1/cyber-soul/daily-script/generate. Triggers the
|
|
171
|
+
* backend to generate the daily script/plan for the character.
|
|
172
|
+
*/
|
|
173
|
+
generateDailyScript(): Promise<void>;
|
|
174
|
+
/**
|
|
175
|
+
* GET /api/v1/cyber-soul/llm-models. Lists the public LLM models the
|
|
176
|
+
* backend currently supports, including each model's
|
|
177
|
+
* `customConfigDefinition` schema for `customSettings`.
|
|
178
|
+
*/
|
|
179
|
+
listLLMModels(): Promise<SupportedLLMModel[]>;
|
|
180
|
+
/**
|
|
181
|
+
* POST /api/v1/cyber-soul/characters/moments. Saves a story moment
|
|
182
|
+
* so it can be picked up by the core-memory consolidation pass.
|
|
183
|
+
*/
|
|
184
|
+
saveMoment(payload: SaveMomentPayload): Promise<void>;
|
|
185
|
+
/**
|
|
186
|
+
* PATCH /api/v1/cyber-soul/characters/core-memory. Replaces the
|
|
187
|
+
* consolidated core memory and user codex in one shot.
|
|
188
|
+
*/
|
|
189
|
+
updateCoreMemory(payload: {
|
|
190
|
+
coreMemory: CoreMemory;
|
|
191
|
+
userCodex: UserCodex;
|
|
192
|
+
}): Promise<void>;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Convenience type guard re-exported so callers don't need to import
|
|
196
|
+
* from `errors.js` just to branch on a caught value.
|
|
197
|
+
*/
|
|
198
|
+
export declare function isCyberSoulError(e: unknown): e is CyberSoulError;
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import { CyberSoulApiError, CyberSoulAuthError, CyberSoulError, CyberSoulInsufficientPointsError, CyberSoulNetworkError, CyberSoulSensitiveContentError, CyberSoulTimeoutError, CyberSoulWalletError, } from "../errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Encapsulates every backend HTTP call used by the SDK. Owns the
|
|
4
|
+
* transport (timeout, retry, typed error mapping) and exposes one typed
|
|
5
|
+
* method per endpoint so the orchestration layer in `client.ts` never
|
|
6
|
+
* touches `fetch` or status codes directly.
|
|
7
|
+
*
|
|
8
|
+
* Error contract:
|
|
9
|
+
* - Transport-level failures are wrapped as [CyberSoulNetworkError] /
|
|
10
|
+
* [CyberSoulTimeoutError].
|
|
11
|
+
* - Backend typed failures (402 / wallet / sensitive-content / auth)
|
|
12
|
+
* are surfaced as the dedicated subclasses of [CyberSoulError] so
|
|
13
|
+
* callers can branch on `instanceof` instead of string-sniffing.
|
|
14
|
+
*/
|
|
15
|
+
export class CyberSoulApi {
|
|
16
|
+
backendUrl;
|
|
17
|
+
characterKey;
|
|
18
|
+
requestTimeoutMs;
|
|
19
|
+
maxRetries;
|
|
20
|
+
fetchImpl;
|
|
21
|
+
constructor(config) {
|
|
22
|
+
this.backendUrl = config.backendUrl;
|
|
23
|
+
this.characterKey = config.characterKey;
|
|
24
|
+
this.requestTimeoutMs = config.requestTimeoutMs ?? 120000;
|
|
25
|
+
this.maxRetries = Math.max(0, config.maxRetries ?? 1);
|
|
26
|
+
this.fetchImpl = config.fetchImpl;
|
|
27
|
+
}
|
|
28
|
+
/* -------------------------------------------------------------------- */
|
|
29
|
+
/* Transport */
|
|
30
|
+
/* -------------------------------------------------------------------- */
|
|
31
|
+
/**
|
|
32
|
+
* Internal wrapper for fetch that injects the backend URL and the
|
|
33
|
+
* Character Auth token, applies per-request timeout, and retries
|
|
34
|
+
* transient server-side failures (HTTP >= 500) for idempotent
|
|
35
|
+
* methods (GET / HEAD).
|
|
36
|
+
*
|
|
37
|
+
* Exposed as `public` so callers that need a raw `Response` (e.g. to
|
|
38
|
+
* branch on status without paying for an exception) can reuse the
|
|
39
|
+
* same transport path. Most consumers should prefer the typed
|
|
40
|
+
* helpers below.
|
|
41
|
+
*/
|
|
42
|
+
async apiFetch(endpoint, options = {}) {
|
|
43
|
+
const url = `${this.backendUrl}${endpoint}`;
|
|
44
|
+
const headers = {
|
|
45
|
+
Authorization: `Bearer ${this.characterKey}`,
|
|
46
|
+
"Content-Type": "application/json",
|
|
47
|
+
...(options.headers || {}),
|
|
48
|
+
};
|
|
49
|
+
const method = (options.method || "GET").toUpperCase();
|
|
50
|
+
const isIdempotent = method === "GET" || method === "HEAD";
|
|
51
|
+
const retryLimit = isIdempotent ? this.maxRetries : 0;
|
|
52
|
+
let lastError;
|
|
53
|
+
for (let attempt = 0; attempt <= retryLimit; attempt++) {
|
|
54
|
+
const controller = new AbortController();
|
|
55
|
+
const timeout = setTimeout(() => controller.abort(), this.requestTimeoutMs);
|
|
56
|
+
try {
|
|
57
|
+
// NOTE: When no custom fetchImpl is provided, fall back to the global
|
|
58
|
+
// `fetch` bound to `globalThis`. Browsers throw "Illegal invocation"
|
|
59
|
+
// if the global `fetch` is invoked while detached from its Window
|
|
60
|
+
// receiver (e.g. via a captured reference).
|
|
61
|
+
const fetchFn = this.fetchImpl ?? fetch.bind(globalThis);
|
|
62
|
+
const response = await fetchFn(url, {
|
|
63
|
+
...options,
|
|
64
|
+
headers,
|
|
65
|
+
signal: controller.signal,
|
|
66
|
+
});
|
|
67
|
+
// Retry transient server-side failures only for idempotent methods.
|
|
68
|
+
if (response.status >= 500 && attempt < retryLimit) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
return response;
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
75
|
+
lastError = new CyberSoulTimeoutError(endpoint, method, this.requestTimeoutMs);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
lastError = new CyberSoulNetworkError(endpoint, method, error instanceof Error
|
|
79
|
+
? `Network request failed: ${method} ${endpoint}: ${error.message}`
|
|
80
|
+
: `Network request failed: ${method} ${endpoint}`, { cause: error });
|
|
81
|
+
}
|
|
82
|
+
if (attempt >= retryLimit) {
|
|
83
|
+
throw lastError;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
clearTimeout(timeout);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Defensive: the loop above either returns a Response, throws the
|
|
91
|
+
// wrapped network error, or continues to the next attempt. Reaching
|
|
92
|
+
// this point means the retry budget was exhausted without ever
|
|
93
|
+
// populating `lastError` (logically unreachable, but TypeScript
|
|
94
|
+
// cannot prove that).
|
|
95
|
+
throw lastError instanceof Error
|
|
96
|
+
? lastError
|
|
97
|
+
: new CyberSoulNetworkError(endpoint, method, `Request failed unexpectedly: ${method} ${endpoint}`);
|
|
98
|
+
}
|
|
99
|
+
/* -------------------------------------------------------------------- */
|
|
100
|
+
/* State & wardrobe */
|
|
101
|
+
/* -------------------------------------------------------------------- */
|
|
102
|
+
/**
|
|
103
|
+
* GET /api/v1/cyber-soul/state. Returns the full character state
|
|
104
|
+
* (identity, dynamic context, codex, memory, active event/wardrobe).
|
|
105
|
+
*
|
|
106
|
+
* 401/403 → [CyberSoulAuthError] (character may have been deleted).
|
|
107
|
+
* Other non-2xx → [CyberSoulApiError].
|
|
108
|
+
*/
|
|
109
|
+
async getState() {
|
|
110
|
+
const endpoint = "/api/v1/cyber-soul/state";
|
|
111
|
+
const res = await this.apiFetch(endpoint);
|
|
112
|
+
if (!res.ok) {
|
|
113
|
+
let body;
|
|
114
|
+
try {
|
|
115
|
+
body = await res.json();
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
body = undefined;
|
|
119
|
+
}
|
|
120
|
+
const detail = (body && typeof body === "object" && "error" in body
|
|
121
|
+
? String(body.error)
|
|
122
|
+
: undefined) ?? `HTTP ${res.status}`;
|
|
123
|
+
if (res.status === 401 || res.status === 403) {
|
|
124
|
+
throw new CyberSoulAuthError(endpoint, "GET", res.status, `Character credential rejected by backend (${detail}). The character may have been deleted.`, body);
|
|
125
|
+
}
|
|
126
|
+
throw new CyberSoulApiError(endpoint, "GET", res.status, `Failed to fetch character state: ${detail}`, body);
|
|
127
|
+
}
|
|
128
|
+
const json = await res.json();
|
|
129
|
+
return json.data;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* GET /api/v1/cyber-soul/wardrobe. Returns the raw wardrobe items;
|
|
133
|
+
* the caller is responsible for any caching / formatting. Resolves
|
|
134
|
+
* to an empty array when the backend returns no items or a non-2xx
|
|
135
|
+
* (failures are swallowed because wardrobe is best-effort context
|
|
136
|
+
* for prompt assembly — a missing list must not abort a chat turn).
|
|
137
|
+
*/
|
|
138
|
+
async getWardrobe() {
|
|
139
|
+
const wardrobeRes = await this.apiFetch("/api/v1/cyber-soul/wardrobe");
|
|
140
|
+
if (!wardrobeRes.ok)
|
|
141
|
+
return [];
|
|
142
|
+
let payload = {};
|
|
143
|
+
try {
|
|
144
|
+
payload = await wardrobeRes.json();
|
|
145
|
+
}
|
|
146
|
+
catch (e) {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
return Array.isArray(payload?.data) ? payload.data : [];
|
|
150
|
+
}
|
|
151
|
+
/* -------------------------------------------------------------------- */
|
|
152
|
+
/* Primitive media generation */
|
|
153
|
+
/* -------------------------------------------------------------------- */
|
|
154
|
+
/**
|
|
155
|
+
* POST /api/v1/cyber-soul/{type}/generate. Generates an image or a
|
|
156
|
+
* voice clip. Backend typed failures are surfaced as dedicated
|
|
157
|
+
* subclasses of [CyberSoulError] so callers can branch precisely:
|
|
158
|
+
* - 402 / INSUFFICIENT_POINTS → [CyberSoulInsufficientPointsError]
|
|
159
|
+
* - WALLET_DEDUCTION_ERROR → [CyberSoulWalletError]
|
|
160
|
+
* - E005 (sensitive content) → [CyberSoulSensitiveContentError]
|
|
161
|
+
* - 401 / 403 → [CyberSoulAuthError]
|
|
162
|
+
* - anything else → [CyberSoulApiError] (with legacy
|
|
163
|
+
* duck-typed `code` preserved)
|
|
164
|
+
*/
|
|
165
|
+
async generatePrimitive(type, payload) {
|
|
166
|
+
const endpoint = `/api/v1/cyber-soul/${type}/generate`;
|
|
167
|
+
const res = await this.apiFetch(endpoint, {
|
|
168
|
+
method: "POST",
|
|
169
|
+
body: JSON.stringify(payload),
|
|
170
|
+
});
|
|
171
|
+
if (!res.ok) {
|
|
172
|
+
let errData;
|
|
173
|
+
try {
|
|
174
|
+
errData = await res.json();
|
|
175
|
+
}
|
|
176
|
+
catch (e) { }
|
|
177
|
+
const msg = errData?.message || errData?.error || `Status ${res.status}`;
|
|
178
|
+
const code = errData?.code || "UNKNOWN_ERROR";
|
|
179
|
+
const detailedMessage = `Failed to generate ${type}: ${msg}`;
|
|
180
|
+
if (res.status === 402 || code === "INSUFFICIENT_POINTS") {
|
|
181
|
+
throw new CyberSoulInsufficientPointsError(endpoint, "POST", res.status, detailedMessage, errData, code);
|
|
182
|
+
}
|
|
183
|
+
if (code === "WALLET_DEDUCTION_ERROR") {
|
|
184
|
+
throw new CyberSoulWalletError(endpoint, "POST", res.status, detailedMessage, errData, code);
|
|
185
|
+
}
|
|
186
|
+
if (code === "E005") {
|
|
187
|
+
throw new CyberSoulSensitiveContentError(endpoint, "POST", res.status, detailedMessage, errData, code);
|
|
188
|
+
}
|
|
189
|
+
if (res.status === 401 || res.status === 403) {
|
|
190
|
+
throw new CyberSoulAuthError(endpoint, "POST", res.status, detailedMessage, errData);
|
|
191
|
+
}
|
|
192
|
+
const apiErr = new CyberSoulApiError(endpoint, "POST", res.status, detailedMessage, errData);
|
|
193
|
+
// Preserve the legacy duck-typed `code` field so existing callers
|
|
194
|
+
// that branch on `e.code` (including this SDK's own `interact()`
|
|
195
|
+
// mediaTasks catch block) keep working unchanged.
|
|
196
|
+
apiErr.code = code;
|
|
197
|
+
throw apiErr;
|
|
198
|
+
}
|
|
199
|
+
return res.json();
|
|
200
|
+
}
|
|
201
|
+
/* -------------------------------------------------------------------- */
|
|
202
|
+
/* Dynamic context */
|
|
203
|
+
/* -------------------------------------------------------------------- */
|
|
204
|
+
/**
|
|
205
|
+
* PATCH /api/v1/cyber-soul/characters/dynamic-context.
|
|
206
|
+
*
|
|
207
|
+
* The server applies stage-based dampening, familiarity soft-caps,
|
|
208
|
+
* hard floor, and stage re-evaluation, then returns the
|
|
209
|
+
* *authoritative* persisted `temperature` and `relationshipStage`.
|
|
210
|
+
*
|
|
211
|
+
* Returns the post-write snapshot, or `null` when there's nothing
|
|
212
|
+
* to send / the request fails (failure is non-fatal for a chat
|
|
213
|
+
* turn — callers must treat `null` as "no fresh snapshot").
|
|
214
|
+
*
|
|
215
|
+
* The caller is responsible for any payload normalization (e.g.
|
|
216
|
+
* mapping `temperatureDelta` → `temperature`); this method sends
|
|
217
|
+
* `payload` as-is.
|
|
218
|
+
*/
|
|
219
|
+
async patchDynamicContext(payload) {
|
|
220
|
+
if (payload.temperature === undefined &&
|
|
221
|
+
payload.temperatureAbsolute === undefined &&
|
|
222
|
+
payload.ongoingScene === undefined &&
|
|
223
|
+
payload.userNickname === undefined &&
|
|
224
|
+
payload.agentNickname === undefined &&
|
|
225
|
+
payload.talkingStyle === undefined &&
|
|
226
|
+
payload.userAnalysis === undefined) {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
let res;
|
|
230
|
+
try {
|
|
231
|
+
res = await this.apiFetch("/api/v1/cyber-soul/characters/dynamic-context", {
|
|
232
|
+
method: "PATCH",
|
|
233
|
+
body: JSON.stringify(payload),
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
catch (e) {
|
|
237
|
+
console.error("Failed to update dynamic context", e);
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
if (!res.ok) {
|
|
241
|
+
console.error(`Failed to update dynamic context: HTTP ${res.status}`);
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
const body = (await res.json());
|
|
246
|
+
const temperature = typeof body.dynamicContext?.temperature === "number" &&
|
|
247
|
+
Number.isFinite(body.dynamicContext.temperature)
|
|
248
|
+
? body.dynamicContext.temperature
|
|
249
|
+
: undefined;
|
|
250
|
+
const relationshipStage = typeof body.relationshipStage === "string"
|
|
251
|
+
? body.relationshipStage
|
|
252
|
+
: undefined;
|
|
253
|
+
if (temperature === undefined && relationshipStage === undefined) {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
return { temperature, relationshipStage };
|
|
257
|
+
}
|
|
258
|
+
catch (e) {
|
|
259
|
+
console.error("Failed to parse dynamic-context PATCH response", e);
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* PATCH /api/v1/cyber-soul/characters/dynamic-context with an exact
|
|
265
|
+
* absolute temperature. Used by chat recall, where inverse deltas are
|
|
266
|
+
* not accurate once the backend has applied dampening, caps, and
|
|
267
|
+
* stage re-evaluation. The value is clamped to [0,100] with one
|
|
268
|
+
* decimal of precision before being sent.
|
|
269
|
+
*
|
|
270
|
+
* Returns `null` on transport failure or invalid input — strict,
|
|
271
|
+
* no silent fallback. Caller should treat `null` as "restore did
|
|
272
|
+
* not succeed" and surface the inconsistency.
|
|
273
|
+
*/
|
|
274
|
+
async restoreDynamicContextTemperature(temperatureAbsolute) {
|
|
275
|
+
if (!Number.isFinite(temperatureAbsolute))
|
|
276
|
+
return null;
|
|
277
|
+
const normalizedAbsolute = Math.max(0, Math.min(100, Math.round(temperatureAbsolute * 10) / 10));
|
|
278
|
+
try {
|
|
279
|
+
const res = await this.apiFetch("/api/v1/cyber-soul/characters/dynamic-context", {
|
|
280
|
+
method: "PATCH",
|
|
281
|
+
body: JSON.stringify({ temperatureAbsolute: normalizedAbsolute }),
|
|
282
|
+
});
|
|
283
|
+
if (!res.ok)
|
|
284
|
+
return null;
|
|
285
|
+
const payload = (await res.json());
|
|
286
|
+
if (payload?.status !== "success")
|
|
287
|
+
return null;
|
|
288
|
+
if (typeof payload.dynamicContext?.temperature !== "number")
|
|
289
|
+
return null;
|
|
290
|
+
if (!Number.isFinite(payload.dynamicContext.temperature))
|
|
291
|
+
return null;
|
|
292
|
+
return {
|
|
293
|
+
temperature: payload.dynamicContext.temperature,
|
|
294
|
+
relationshipStage: typeof payload.relationshipStage === "string"
|
|
295
|
+
? payload.relationshipStage
|
|
296
|
+
: undefined,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
catch (e) {
|
|
300
|
+
console.error("restoreDynamicContextTemperature failed", e);
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/* -------------------------------------------------------------------- */
|
|
305
|
+
/* Events, wardrobe writes, lifecycle */
|
|
306
|
+
/* -------------------------------------------------------------------- */
|
|
307
|
+
/**
|
|
308
|
+
* POST /api/v1/cyber-soul/characters/ondemand-event. Schedules an
|
|
309
|
+
* on-demand event. Throws when the backend rejects the schedule.
|
|
310
|
+
*/
|
|
311
|
+
async triggerOndemandEvent(payload) {
|
|
312
|
+
const res = await this.apiFetch("/api/v1/cyber-soul/characters/ondemand-event", {
|
|
313
|
+
method: "POST",
|
|
314
|
+
body: JSON.stringify(payload),
|
|
315
|
+
});
|
|
316
|
+
if (!res.ok) {
|
|
317
|
+
throw new Error("Backend failed to schedule the on-demand event");
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* POST /api/v1/cyber-soul/characters/gift-outfit. Adds a new outfit
|
|
322
|
+
* (or several — the backend may expand a single description into
|
|
323
|
+
* multiple items) to the wardrobe inventory. Returns the number of
|
|
324
|
+
* items created, or `undefined` when the server didn't report a
|
|
325
|
+
* count (never fabricated).
|
|
326
|
+
*/
|
|
327
|
+
async giftOutfit(descriptionText) {
|
|
328
|
+
const res = await this.apiFetch("/api/v1/cyber-soul/characters/gift-outfit", {
|
|
329
|
+
method: "POST",
|
|
330
|
+
body: JSON.stringify({ text: descriptionText }),
|
|
331
|
+
});
|
|
332
|
+
if (!res.ok)
|
|
333
|
+
throw new Error("Failed to gift outfit");
|
|
334
|
+
try {
|
|
335
|
+
const body = (await res.json());
|
|
336
|
+
return typeof body.count === "number" && Number.isFinite(body.count)
|
|
337
|
+
? body.count
|
|
338
|
+
: undefined;
|
|
339
|
+
}
|
|
340
|
+
catch {
|
|
341
|
+
// The gift already succeeded server-side (res.ok); a missing/
|
|
342
|
+
// unparseable count is non-fatal — report "unknown" rather than
|
|
343
|
+
// fabricating a number.
|
|
344
|
+
return undefined;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* POST /api/v1/cyber-soul/characters/bootstrap. Bootstraps a
|
|
349
|
+
* character profile from OpenClaw workspace files.
|
|
350
|
+
*/
|
|
351
|
+
async bootstrapCharacter(workspaceFiles) {
|
|
352
|
+
const res = await this.apiFetch("/api/v1/cyber-soul/characters/bootstrap", {
|
|
353
|
+
method: "POST",
|
|
354
|
+
body: JSON.stringify({ workspace_files: workspaceFiles }),
|
|
355
|
+
});
|
|
356
|
+
if (!res.ok)
|
|
357
|
+
throw new Error("Failed to bootstrap character");
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* POST /api/v1/cyber-soul/daily-script/generate. Triggers the
|
|
361
|
+
* backend to generate the daily script/plan for the character.
|
|
362
|
+
*/
|
|
363
|
+
async generateDailyScript() {
|
|
364
|
+
const res = await this.apiFetch("/api/v1/cyber-soul/daily-script/generate", {
|
|
365
|
+
method: "POST",
|
|
366
|
+
});
|
|
367
|
+
if (!res.ok)
|
|
368
|
+
throw new Error("Failed to generate daily script");
|
|
369
|
+
}
|
|
370
|
+
/* -------------------------------------------------------------------- */
|
|
371
|
+
/* LLM models */
|
|
372
|
+
/* -------------------------------------------------------------------- */
|
|
373
|
+
/**
|
|
374
|
+
* GET /api/v1/cyber-soul/llm-models. Lists the public LLM models the
|
|
375
|
+
* backend currently supports, including each model's
|
|
376
|
+
* `customConfigDefinition` schema for `customSettings`.
|
|
377
|
+
*/
|
|
378
|
+
async listLLMModels() {
|
|
379
|
+
const res = await this.apiFetch("/api/v1/cyber-soul/llm-models");
|
|
380
|
+
if (!res.ok) {
|
|
381
|
+
throw new Error(`Failed to list supported LLMs: ${res.status}`);
|
|
382
|
+
}
|
|
383
|
+
const body = (await res.json());
|
|
384
|
+
if (Array.isArray(body))
|
|
385
|
+
return body;
|
|
386
|
+
if (body && typeof body === "object" && Array.isArray(body.data)) {
|
|
387
|
+
return body.data;
|
|
388
|
+
}
|
|
389
|
+
throw new Error("Unexpected response shape from /llm-models");
|
|
390
|
+
}
|
|
391
|
+
/* -------------------------------------------------------------------- */
|
|
392
|
+
/* Memory pipeline */
|
|
393
|
+
/* -------------------------------------------------------------------- */
|
|
394
|
+
/**
|
|
395
|
+
* POST /api/v1/cyber-soul/characters/moments. Saves a story moment
|
|
396
|
+
* so it can be picked up by the core-memory consolidation pass.
|
|
397
|
+
*/
|
|
398
|
+
async saveMoment(payload) {
|
|
399
|
+
const res = await this.apiFetch("/api/v1/cyber-soul/characters/moments", {
|
|
400
|
+
method: "POST",
|
|
401
|
+
body: JSON.stringify(payload),
|
|
402
|
+
});
|
|
403
|
+
if (!res.ok) {
|
|
404
|
+
throw new Error("Failed to save character moment.");
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* PATCH /api/v1/cyber-soul/characters/core-memory. Replaces the
|
|
409
|
+
* consolidated core memory and user codex in one shot.
|
|
410
|
+
*/
|
|
411
|
+
async updateCoreMemory(payload) {
|
|
412
|
+
const res = await this.apiFetch("/api/v1/cyber-soul/characters/core-memory", {
|
|
413
|
+
method: "PATCH",
|
|
414
|
+
body: JSON.stringify(payload),
|
|
415
|
+
});
|
|
416
|
+
if (!res.ok) {
|
|
417
|
+
throw new Error(`Failed to update core memory. Status: ${res.status}`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Convenience type guard re-exported so callers don't need to import
|
|
423
|
+
* from `errors.js` just to branch on a caught value.
|
|
424
|
+
*/
|
|
425
|
+
export function isCyberSoulError(e) {
|
|
426
|
+
return e instanceof CyberSoulError;
|
|
427
|
+
}
|