@space3-npm/cybersoul-client 1.4.12 → 1.4.13

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 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
@@ -515,6 +515,35 @@ ${isProactive
515
515
  .replace(/\s+/g, " ")
516
516
  .trim();
517
517
  }
518
+ /**
519
+ * Build the in-band `mediaError` envelope from the first typed media
520
+ * failure captured during `interact()` / `proactiveInteract()`. Keeps
521
+ * the conversion in one place so both call sites stay consistent and
522
+ * the SDK never re-throws on a partial media failure once the text
523
+ * reply is already in flight.
524
+ */
525
+ buildMediaError(err, affected) {
526
+ if (err instanceof CyberSoulInsufficientPointsError) {
527
+ return {
528
+ kind: "insufficient-points",
529
+ code: err.code,
530
+ message: err.message,
531
+ affected,
532
+ };
533
+ }
534
+ if (err instanceof CyberSoulWalletError) {
535
+ return {
536
+ kind: "wallet",
537
+ message: err.message,
538
+ affected,
539
+ };
540
+ }
541
+ return {
542
+ kind: "unknown",
543
+ message: err.message,
544
+ affected,
545
+ };
546
+ }
518
547
  formatHistoryEntries(history, userName, agentName, promptDirective = "") {
519
548
  const contextLines = [];
520
549
  for (let i = 0; i < history.length; i++) {
@@ -712,6 +741,27 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
712
741
  let finalAudioUrl = undefined;
713
742
  let finalAudioMediaId = undefined;
714
743
  let finalDurationSec = undefined;
744
+ // Partial-failure capture: text was already produced and emitted
745
+ // via [onTextReady], so a wallet / insufficient-points failure on
746
+ // image or voice MUST NOT abort the whole turn. We collect the
747
+ // affected modalities + first typed error and surface them in-band
748
+ // through `InteractResponse.mediaError`. The caller (MessageBus /
749
+ // UI) decides how to message the user without losing the reply.
750
+ const mediaErrorAffected = [];
751
+ let firstMediaError = null;
752
+ const captureMediaError = (modality, e) => {
753
+ if (!(e instanceof CyberSoulError))
754
+ return;
755
+ if (!(e instanceof CyberSoulInsufficientPointsError) &&
756
+ !(e instanceof CyberSoulWalletError)) {
757
+ return;
758
+ }
759
+ if (!mediaErrorAffected.includes(modality)) {
760
+ mediaErrorAffected.push(modality);
761
+ }
762
+ if (!firstMediaError)
763
+ firstMediaError = e;
764
+ };
715
765
  // Output Event Trigger
716
766
  if (parsedIntent.triggerEvent) {
717
767
  mediaTasks.push(this.apiFetch("/api/v1/cyber-soul/characters/ondemand-event", {
@@ -748,8 +798,7 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
748
798
  })
749
799
  .catch((e) => {
750
800
  console.error("[CyberSoulClient] Image generation failed:", e);
751
- if (e.code === 'INSUFFICIENT_POINTS' || e.code === 'WALLET_DEDUCTION_ERROR')
752
- throw e;
801
+ captureMediaError("image", e);
753
802
  }));
754
803
  }
755
804
  const shouldGenerateVoice = types.includes(InteractRequestType.VOICE) &&
@@ -773,8 +822,7 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
773
822
  })
774
823
  .catch((e) => {
775
824
  console.error("[CyberSoulClient] Voice generation failed:", e);
776
- if (e.code === 'INSUFFICIENT_POINTS' || e.code === 'WALLET_DEDUCTION_ERROR')
777
- throw e;
825
+ captureMediaError("voice", e);
778
826
  }));
779
827
  }
780
828
  // Wait for image/voice gens to return successfully
@@ -784,6 +832,9 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
784
832
  // This adds at most ~1 small request to the critical path; in
785
833
  // practice the PATCH usually resolves before media generation.
786
834
  const persistedDynamicContext = (await persistedStatePromise) ?? undefined;
835
+ const mediaError = firstMediaError
836
+ ? this.buildMediaError(firstMediaError, mediaErrorAffected)
837
+ : undefined;
787
838
  return {
788
839
  status: "success",
789
840
  textResponse: resolvedTextResponse || "...",
@@ -799,6 +850,7 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
799
850
  userAnalysis: parsedIntent.userAnalysis,
800
851
  isEndTurn: parsedIntent.isEndTurn,
801
852
  persistedDynamicContext,
853
+ mediaError,
802
854
  };
803
855
  }
804
856
  catch (error) {
@@ -1040,6 +1092,8 @@ If "shouldSkipProactive" is false, "textResponse" is required and "stateUpdate"
1040
1092
  }
1041
1093
  let finalImageUrl;
1042
1094
  let finalImageMediaId;
1095
+ let proactiveMediaError = null;
1096
+ const proactiveAffected = [];
1043
1097
  if (parsedIntent.imageParams) {
1044
1098
  try {
1045
1099
  const res = await this.generatePrimitive("image", parsedIntent.imageParams);
@@ -1048,9 +1102,17 @@ If "shouldSkipProactive" is false, "textResponse" is required and "stateUpdate"
1048
1102
  }
1049
1103
  catch (e) {
1050
1104
  console.error("[CyberSoulClient] Proactive Image generation failed:", e);
1105
+ if (e instanceof CyberSoulInsufficientPointsError ||
1106
+ e instanceof CyberSoulWalletError) {
1107
+ proactiveMediaError = e;
1108
+ proactiveAffected.push("image");
1109
+ }
1051
1110
  }
1052
1111
  }
1053
1112
  const persistedDynamicContext = (await persistedStatePromise) ?? undefined;
1113
+ const proactiveMediaErrorEnv = proactiveMediaError
1114
+ ? this.buildMediaError(proactiveMediaError, proactiveAffected)
1115
+ : undefined;
1054
1116
  return {
1055
1117
  status: "success",
1056
1118
  textResponse: parsedIntent.textResponse,
@@ -1059,6 +1121,7 @@ If "shouldSkipProactive" is false, "textResponse" is required and "stateUpdate"
1059
1121
  imageMediaId: finalImageMediaId,
1060
1122
  stateUpdate: parsedIntent.stateUpdate,
1061
1123
  persistedDynamicContext,
1124
+ mediaError: proactiveMediaErrorEnv,
1062
1125
  };
1063
1126
  }
1064
1127
  catch (error) {
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@space3-npm/cybersoul-client",
3
- "version": "1.4.12",
3
+ "version": "1.4.13",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",