@space3-npm/cybersoul-client 1.4.11 → 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
@@ -1,7 +1,7 @@
1
1
  import { InteractRequestType, } from "./types.js";
2
2
  import { robustJsonParse } from "./utils/json.utils.js";
3
3
  import { GenericLLMProvider } from "./llm.provider.js";
4
- import { CyberSoulApiError, CyberSoulAuthError, CyberSoulInsufficientPointsError, CyberSoulNetworkError, CyberSoulTimeoutError, CyberSoulWalletError, } from "./errors.js";
4
+ import { CyberSoulApiError, CyberSoulAuthError, CyberSoulError, CyberSoulInsufficientPointsError, CyberSoulNetworkError, CyberSoulTimeoutError, CyberSoulWalletError, } from "./errors.js";
5
5
  export class CyberSoulClient {
6
6
  config;
7
7
  llm;
@@ -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,9 +850,18 @@ 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) {
857
+ // Typed SDK errors (insufficient points, wallet failure, auth, etc.)
858
+ // are part of the public contract — let callers branch on
859
+ // `instanceof` instead of string-sniffing a generic status:"error"
860
+ // envelope. Only truly-unexpected throws fall back to the legacy
861
+ // envelope so we don't break callers that don't yet handle throws.
862
+ if (error instanceof CyberSoulError) {
863
+ throw error;
864
+ }
805
865
  console.error("[CyberSoulClient] Interface Error: ", error);
806
866
  return {
807
867
  status: "error",
@@ -1032,6 +1092,8 @@ If "shouldSkipProactive" is false, "textResponse" is required and "stateUpdate"
1032
1092
  }
1033
1093
  let finalImageUrl;
1034
1094
  let finalImageMediaId;
1095
+ let proactiveMediaError = null;
1096
+ const proactiveAffected = [];
1035
1097
  if (parsedIntent.imageParams) {
1036
1098
  try {
1037
1099
  const res = await this.generatePrimitive("image", parsedIntent.imageParams);
@@ -1040,9 +1102,17 @@ If "shouldSkipProactive" is false, "textResponse" is required and "stateUpdate"
1040
1102
  }
1041
1103
  catch (e) {
1042
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
+ }
1043
1110
  }
1044
1111
  }
1045
1112
  const persistedDynamicContext = (await persistedStatePromise) ?? undefined;
1113
+ const proactiveMediaErrorEnv = proactiveMediaError
1114
+ ? this.buildMediaError(proactiveMediaError, proactiveAffected)
1115
+ : undefined;
1046
1116
  return {
1047
1117
  status: "success",
1048
1118
  textResponse: parsedIntent.textResponse,
@@ -1051,9 +1121,14 @@ If "shouldSkipProactive" is false, "textResponse" is required and "stateUpdate"
1051
1121
  imageMediaId: finalImageMediaId,
1052
1122
  stateUpdate: parsedIntent.stateUpdate,
1053
1123
  persistedDynamicContext,
1124
+ mediaError: proactiveMediaErrorEnv,
1054
1125
  };
1055
1126
  }
1056
1127
  catch (error) {
1128
+ // Mirror `interact()`: preserve typed SDK errors for the caller.
1129
+ if (error instanceof CyberSoulError) {
1130
+ throw error;
1131
+ }
1057
1132
  console.error("[CyberSoulClient] Proactive Interact Error: ", error);
1058
1133
  return { status: "error", error: error.message };
1059
1134
  }
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.11",
3
+ "version": "1.4.13",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",