@space3-npm/cybersoul-client 1.4.12 → 1.4.15
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 +8 -0
- package/dist/client.js +83 -8
- package/dist/llm.provider.js +4 -1
- package/dist/types.d.ts +27 -0
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -70,6 +70,14 @@ export declare class CyberSoulClient {
|
|
|
70
70
|
* neutral placeholder (e.g. "...") so the TTS call still has valid input.
|
|
71
71
|
*/
|
|
72
72
|
private sanitizeTextForVoice;
|
|
73
|
+
/**
|
|
74
|
+
* Build the in-band `mediaError` envelope from the first typed media
|
|
75
|
+
* failure captured during `interact()` / `proactiveInteract()`. Keeps
|
|
76
|
+
* the conversion in one place so both call sites stay consistent and
|
|
77
|
+
* the SDK never re-throws on a partial media failure once the text
|
|
78
|
+
* reply is already in flight.
|
|
79
|
+
*/
|
|
80
|
+
private buildMediaError;
|
|
73
81
|
private formatHistoryEntries;
|
|
74
82
|
private buildHistoryTranscript;
|
|
75
83
|
interact(params: InteractParams): Promise<InteractResponse>;
|
package/dist/client.js
CHANGED
|
@@ -34,7 +34,11 @@ export class CyberSoulClient {
|
|
|
34
34
|
const controller = new AbortController();
|
|
35
35
|
const timeout = setTimeout(() => controller.abort(), this.requestTimeoutMs);
|
|
36
36
|
try {
|
|
37
|
-
|
|
37
|
+
// NOTE: When no custom fetchImpl is provided, fall back to the global
|
|
38
|
+
// `fetch` bound to `globalThis`. Browsers throw "Illegal invocation"
|
|
39
|
+
// if the global `fetch` is invoked while detached from its Window
|
|
40
|
+
// receiver (e.g. via a captured reference).
|
|
41
|
+
const fetchFn = this.config.fetchImpl ?? fetch.bind(globalThis);
|
|
38
42
|
const response = await fetchFn(url, {
|
|
39
43
|
...options,
|
|
40
44
|
headers,
|
|
@@ -515,6 +519,35 @@ ${isProactive
|
|
|
515
519
|
.replace(/\s+/g, " ")
|
|
516
520
|
.trim();
|
|
517
521
|
}
|
|
522
|
+
/**
|
|
523
|
+
* Build the in-band `mediaError` envelope from the first typed media
|
|
524
|
+
* failure captured during `interact()` / `proactiveInteract()`. Keeps
|
|
525
|
+
* the conversion in one place so both call sites stay consistent and
|
|
526
|
+
* the SDK never re-throws on a partial media failure once the text
|
|
527
|
+
* reply is already in flight.
|
|
528
|
+
*/
|
|
529
|
+
buildMediaError(err, affected) {
|
|
530
|
+
if (err instanceof CyberSoulInsufficientPointsError) {
|
|
531
|
+
return {
|
|
532
|
+
kind: "insufficient-points",
|
|
533
|
+
code: err.code,
|
|
534
|
+
message: err.message,
|
|
535
|
+
affected,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
if (err instanceof CyberSoulWalletError) {
|
|
539
|
+
return {
|
|
540
|
+
kind: "wallet",
|
|
541
|
+
message: err.message,
|
|
542
|
+
affected,
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
return {
|
|
546
|
+
kind: "unknown",
|
|
547
|
+
message: err.message,
|
|
548
|
+
affected,
|
|
549
|
+
};
|
|
550
|
+
}
|
|
518
551
|
formatHistoryEntries(history, userName, agentName, promptDirective = "") {
|
|
519
552
|
const contextLines = [];
|
|
520
553
|
for (let i = 0; i < history.length; i++) {
|
|
@@ -712,6 +745,27 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
|
|
|
712
745
|
let finalAudioUrl = undefined;
|
|
713
746
|
let finalAudioMediaId = undefined;
|
|
714
747
|
let finalDurationSec = undefined;
|
|
748
|
+
// Partial-failure capture: text was already produced and emitted
|
|
749
|
+
// via [onTextReady], so a wallet / insufficient-points failure on
|
|
750
|
+
// image or voice MUST NOT abort the whole turn. We collect the
|
|
751
|
+
// affected modalities + first typed error and surface them in-band
|
|
752
|
+
// through `InteractResponse.mediaError`. The caller (MessageBus /
|
|
753
|
+
// UI) decides how to message the user without losing the reply.
|
|
754
|
+
const mediaErrorAffected = [];
|
|
755
|
+
let firstMediaError = null;
|
|
756
|
+
const captureMediaError = (modality, e) => {
|
|
757
|
+
if (!(e instanceof CyberSoulError))
|
|
758
|
+
return;
|
|
759
|
+
if (!(e instanceof CyberSoulInsufficientPointsError) &&
|
|
760
|
+
!(e instanceof CyberSoulWalletError)) {
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
if (!mediaErrorAffected.includes(modality)) {
|
|
764
|
+
mediaErrorAffected.push(modality);
|
|
765
|
+
}
|
|
766
|
+
if (!firstMediaError)
|
|
767
|
+
firstMediaError = e;
|
|
768
|
+
};
|
|
715
769
|
// Output Event Trigger
|
|
716
770
|
if (parsedIntent.triggerEvent) {
|
|
717
771
|
mediaTasks.push(this.apiFetch("/api/v1/cyber-soul/characters/ondemand-event", {
|
|
@@ -747,9 +801,11 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
|
|
|
747
801
|
finalImageMediaId = res.id;
|
|
748
802
|
})
|
|
749
803
|
.catch((e) => {
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
804
|
+
if (!(e instanceof CyberSoulInsufficientPointsError) &&
|
|
805
|
+
!(e instanceof CyberSoulWalletError)) {
|
|
806
|
+
console.error("[CyberSoulClient] Image generation failed:", e);
|
|
807
|
+
}
|
|
808
|
+
captureMediaError("image", e);
|
|
753
809
|
}));
|
|
754
810
|
}
|
|
755
811
|
const shouldGenerateVoice = types.includes(InteractRequestType.VOICE) &&
|
|
@@ -772,9 +828,11 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
|
|
|
772
828
|
finalDurationSec = res.duration_sec;
|
|
773
829
|
})
|
|
774
830
|
.catch((e) => {
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
831
|
+
if (!(e instanceof CyberSoulInsufficientPointsError) &&
|
|
832
|
+
!(e instanceof CyberSoulWalletError)) {
|
|
833
|
+
console.error("[CyberSoulClient] Voice generation failed:", e);
|
|
834
|
+
}
|
|
835
|
+
captureMediaError("voice", e);
|
|
778
836
|
}));
|
|
779
837
|
}
|
|
780
838
|
// Wait for image/voice gens to return successfully
|
|
@@ -784,6 +842,9 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
|
|
|
784
842
|
// This adds at most ~1 small request to the critical path; in
|
|
785
843
|
// practice the PATCH usually resolves before media generation.
|
|
786
844
|
const persistedDynamicContext = (await persistedStatePromise) ?? undefined;
|
|
845
|
+
const mediaError = firstMediaError
|
|
846
|
+
? this.buildMediaError(firstMediaError, mediaErrorAffected)
|
|
847
|
+
: undefined;
|
|
787
848
|
return {
|
|
788
849
|
status: "success",
|
|
789
850
|
textResponse: resolvedTextResponse || "...",
|
|
@@ -799,6 +860,7 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
|
|
|
799
860
|
userAnalysis: parsedIntent.userAnalysis,
|
|
800
861
|
isEndTurn: parsedIntent.isEndTurn,
|
|
801
862
|
persistedDynamicContext,
|
|
863
|
+
mediaError,
|
|
802
864
|
};
|
|
803
865
|
}
|
|
804
866
|
catch (error) {
|
|
@@ -1040,6 +1102,8 @@ If "shouldSkipProactive" is false, "textResponse" is required and "stateUpdate"
|
|
|
1040
1102
|
}
|
|
1041
1103
|
let finalImageUrl;
|
|
1042
1104
|
let finalImageMediaId;
|
|
1105
|
+
let proactiveMediaError = null;
|
|
1106
|
+
const proactiveAffected = [];
|
|
1043
1107
|
if (parsedIntent.imageParams) {
|
|
1044
1108
|
try {
|
|
1045
1109
|
const res = await this.generatePrimitive("image", parsedIntent.imageParams);
|
|
@@ -1047,10 +1111,20 @@ If "shouldSkipProactive" is false, "textResponse" is required and "stateUpdate"
|
|
|
1047
1111
|
finalImageMediaId = res.id;
|
|
1048
1112
|
}
|
|
1049
1113
|
catch (e) {
|
|
1050
|
-
|
|
1114
|
+
if (e instanceof CyberSoulInsufficientPointsError ||
|
|
1115
|
+
e instanceof CyberSoulWalletError) {
|
|
1116
|
+
proactiveMediaError = e;
|
|
1117
|
+
proactiveAffected.push("image");
|
|
1118
|
+
}
|
|
1119
|
+
else {
|
|
1120
|
+
console.error("[CyberSoulClient] Proactive Image generation failed:", e);
|
|
1121
|
+
}
|
|
1051
1122
|
}
|
|
1052
1123
|
}
|
|
1053
1124
|
const persistedDynamicContext = (await persistedStatePromise) ?? undefined;
|
|
1125
|
+
const proactiveMediaErrorEnv = proactiveMediaError
|
|
1126
|
+
? this.buildMediaError(proactiveMediaError, proactiveAffected)
|
|
1127
|
+
: undefined;
|
|
1054
1128
|
return {
|
|
1055
1129
|
status: "success",
|
|
1056
1130
|
textResponse: parsedIntent.textResponse,
|
|
@@ -1059,6 +1133,7 @@ If "shouldSkipProactive" is false, "textResponse" is required and "stateUpdate"
|
|
|
1059
1133
|
imageMediaId: finalImageMediaId,
|
|
1060
1134
|
stateUpdate: parsedIntent.stateUpdate,
|
|
1061
1135
|
persistedDynamicContext,
|
|
1136
|
+
mediaError: proactiveMediaErrorEnv,
|
|
1062
1137
|
};
|
|
1063
1138
|
}
|
|
1064
1139
|
catch (error) {
|
package/dist/llm.provider.js
CHANGED
|
@@ -11,7 +11,10 @@ export class GenericLLMProvider {
|
|
|
11
11
|
this.fetchImpl = fetchImpl;
|
|
12
12
|
}
|
|
13
13
|
get fetchFn() {
|
|
14
|
-
|
|
14
|
+
// Bind to `globalThis` so the global `fetch` is not invoked detached
|
|
15
|
+
// from its Window receiver (which throws "Illegal invocation" in
|
|
16
|
+
// Chromium-based browsers).
|
|
17
|
+
return this.fetchImpl ?? fetch.bind(globalThis);
|
|
15
18
|
}
|
|
16
19
|
async fetchTemplate() {
|
|
17
20
|
const cacheKey = `${this.config.provider}:${this.config.model}`;
|
package/dist/types.d.ts
CHANGED
|
@@ -72,6 +72,11 @@ export interface ProactiveResponse {
|
|
|
72
72
|
stateUpdate?: DispatcherIntent["stateUpdate"];
|
|
73
73
|
/** Server-authoritative post-write snapshot (see PersistedDynamicContext). */
|
|
74
74
|
persistedDynamicContext?: PersistedDynamicContext;
|
|
75
|
+
/** Partial-failure descriptor: text was generated successfully but one or
|
|
76
|
+
* more media calls (image/voice) failed. Surfaced in-band so the caller
|
|
77
|
+
* can still render the text reply and explain the missing media
|
|
78
|
+
* without losing the conversation. See [InteractMediaError]. */
|
|
79
|
+
mediaError?: InteractMediaError;
|
|
75
80
|
error?: string;
|
|
76
81
|
}
|
|
77
82
|
export interface InteractParams {
|
|
@@ -134,8 +139,30 @@ export interface InteractResponse {
|
|
|
134
139
|
isEndTurn?: boolean;
|
|
135
140
|
/** Server-authoritative post-write snapshot (see PersistedDynamicContext). */
|
|
136
141
|
persistedDynamicContext?: PersistedDynamicContext;
|
|
142
|
+
/** Partial-failure descriptor: text was generated successfully but one or
|
|
143
|
+
* more media calls (image/voice) failed. Surfaced in-band so the caller
|
|
144
|
+
* can still render the text reply and explain the missing media
|
|
145
|
+
* without losing the conversation. See [InteractMediaError]. */
|
|
146
|
+
mediaError?: InteractMediaError;
|
|
137
147
|
error?: string;
|
|
138
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Describes a partial-failure during an [interact] / [proactiveInteract]
|
|
151
|
+
* call: the text reply was generated and returned, but image and/or
|
|
152
|
+
* voice generation failed (usually because the user ran out of points
|
|
153
|
+
* mid-turn). Surfaced in-band on the success envelope so callers can
|
|
154
|
+
* render the text response without losing it to an exception.
|
|
155
|
+
*/
|
|
156
|
+
export interface InteractMediaError {
|
|
157
|
+
/** Coarse kind so UIs can map to a single user-facing message. */
|
|
158
|
+
kind: "insufficient-points" | "wallet" | "unknown";
|
|
159
|
+
/** Backend machine code when available (e.g. "INSUFFICIENT_POINTS"). */
|
|
160
|
+
code?: string;
|
|
161
|
+
/** Raw error message, for logs / diagnostics. */
|
|
162
|
+
message?: string;
|
|
163
|
+
/** Which media generation calls were affected. */
|
|
164
|
+
affected: Array<"image" | "voice">;
|
|
165
|
+
}
|
|
139
166
|
export interface OngoingSceneState {
|
|
140
167
|
scene: string;
|
|
141
168
|
outfit: string;
|