@space3-npm/cybersoul-client 1.4.15 → 1.4.17

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.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, CyberSoulError, CyberSoulInsufficientPointsError, CyberSoulNetworkError, CyberSoulTimeoutError, CyberSoulWalletError, } from "./errors.js";
4
+ import { CyberSoulApiError, CyberSoulAuthError, CyberSoulError, CyberSoulInsufficientPointsError, CyberSoulNetworkError, CyberSoulSensitiveContentError, CyberSoulTimeoutError, CyberSoulWalletError, } from "./errors.js";
5
5
  export class CyberSoulClient {
6
6
  config;
7
7
  llm;
@@ -146,6 +146,9 @@ export class CyberSoulClient {
146
146
  if (code === "WALLET_DEDUCTION_ERROR") {
147
147
  throw new CyberSoulWalletError(endpoint, "POST", res.status, detailedMessage, errData, code);
148
148
  }
149
+ if (code === "E005") {
150
+ throw new CyberSoulSensitiveContentError(endpoint, "POST", res.status, detailedMessage, errData, code);
151
+ }
149
152
  if (res.status === 401 || res.status === 403) {
150
153
  throw new CyberSoulAuthError(endpoint, "POST", res.status, detailedMessage, errData);
151
154
  }
@@ -542,6 +545,14 @@ ${isProactive
542
545
  affected,
543
546
  };
544
547
  }
548
+ if (err instanceof CyberSoulSensitiveContentError) {
549
+ return {
550
+ kind: "sensitive-content",
551
+ code: err.code,
552
+ message: err.message,
553
+ affected,
554
+ };
555
+ }
545
556
  return {
546
557
  kind: "unknown",
547
558
  message: err.message,
@@ -724,6 +735,34 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
724
735
  if (parsedIntent && (parsedIntent.stateUpdate || parsedIntent.userAnalysis)) {
725
736
  persistedStatePromise = this._updateDynamicContextInternal(parsedIntent.stateUpdate, parsedIntent.userAnalysis);
726
737
  }
738
+ // Fire `onStateReady` the moment the dynamic-context PATCH resolves
739
+ // (or immediately, when no state update was emitted). This is
740
+ // independent of media generation, so the UI can stop showing
741
+ // "updating…" on temperature / relationship stage well before the
742
+ // (potentially slow) image task finishes. Errors are swallowed:
743
+ // an authoritative snapshot is best-effort, the optimistic delta
744
+ // already applied client-side is the fallback.
745
+ if (params.onStateReady) {
746
+ const stateReadyCb = params.onStateReady;
747
+ persistedStatePromise
748
+ .then((persisted) => {
749
+ try {
750
+ stateReadyCb(persisted ?? {});
751
+ }
752
+ catch (cbErr) {
753
+ console.warn("[CyberSoulClient] onStateReady callback threw:", cbErr);
754
+ }
755
+ })
756
+ .catch(() => {
757
+ // PATCH failed; still signal LLM-phase complete with an empty snapshot.
758
+ try {
759
+ stateReadyCb({});
760
+ }
761
+ catch (cbErr) {
762
+ console.warn("[CyberSoulClient] onStateReady callback threw:", cbErr);
763
+ }
764
+ });
765
+ }
727
766
  const resolvedTextResponse = typeof parsedIntent.textResponse === "string" &&
728
767
  parsedIntent.textResponse.trim().length > 0
729
768
  ? parsedIntent.textResponse
@@ -757,7 +796,8 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
757
796
  if (!(e instanceof CyberSoulError))
758
797
  return;
759
798
  if (!(e instanceof CyberSoulInsufficientPointsError) &&
760
- !(e instanceof CyberSoulWalletError)) {
799
+ !(e instanceof CyberSoulWalletError) &&
800
+ !(e instanceof CyberSoulSensitiveContentError)) {
761
801
  return;
762
802
  }
763
803
  if (!mediaErrorAffected.includes(modality)) {
@@ -799,10 +839,23 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
799
839
  .then((res) => {
800
840
  finalImageUrl = res.image_url;
801
841
  finalImageMediaId = res.id;
842
+ if (params.onMediaReady && finalImageUrl) {
843
+ try {
844
+ params.onMediaReady({
845
+ modality: "image",
846
+ url: finalImageUrl,
847
+ mediaId: finalImageMediaId,
848
+ });
849
+ }
850
+ catch (cbErr) {
851
+ console.warn("[CyberSoulClient] onMediaReady(image) threw:", cbErr);
852
+ }
853
+ }
802
854
  })
803
855
  .catch((e) => {
804
856
  if (!(e instanceof CyberSoulInsufficientPointsError) &&
805
- !(e instanceof CyberSoulWalletError)) {
857
+ !(e instanceof CyberSoulWalletError) &&
858
+ !(e instanceof CyberSoulSensitiveContentError)) {
806
859
  console.error("[CyberSoulClient] Image generation failed:", e);
807
860
  }
808
861
  captureMediaError("image", e);
@@ -826,10 +879,24 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
826
879
  finalAudioUrl = res.audio_url;
827
880
  finalAudioMediaId = res.id;
828
881
  finalDurationSec = res.duration_sec;
882
+ if (params.onMediaReady && finalAudioUrl) {
883
+ try {
884
+ params.onMediaReady({
885
+ modality: "voice",
886
+ url: finalAudioUrl,
887
+ mediaId: finalAudioMediaId,
888
+ durationSec: finalDurationSec,
889
+ });
890
+ }
891
+ catch (cbErr) {
892
+ console.warn("[CyberSoulClient] onMediaReady(voice) threw:", cbErr);
893
+ }
894
+ }
829
895
  })
830
896
  .catch((e) => {
831
897
  if (!(e instanceof CyberSoulInsufficientPointsError) &&
832
- !(e instanceof CyberSoulWalletError)) {
898
+ !(e instanceof CyberSoulWalletError) &&
899
+ !(e instanceof CyberSoulSensitiveContentError)) {
833
900
  console.error("[CyberSoulClient] Voice generation failed:", e);
834
901
  }
835
902
  captureMediaError("voice", e);
@@ -1095,6 +1162,26 @@ If "shouldSkipProactive" is false, "textResponse" is required and "stateUpdate"
1095
1162
  if (parsedIntent.stateUpdate) {
1096
1163
  persistedStatePromise = this._updateDynamicContextInternal(parsedIntent.stateUpdate);
1097
1164
  }
1165
+ if (params.onStateReady) {
1166
+ const stateReadyCb = params.onStateReady;
1167
+ persistedStatePromise
1168
+ .then((persisted) => {
1169
+ try {
1170
+ stateReadyCb(persisted ?? {});
1171
+ }
1172
+ catch (cbErr) {
1173
+ console.warn("[CyberSoulClient] onStateReady callback threw:", cbErr);
1174
+ }
1175
+ })
1176
+ .catch(() => {
1177
+ try {
1178
+ stateReadyCb({});
1179
+ }
1180
+ catch (cbErr) {
1181
+ console.warn("[CyberSoulClient] onStateReady callback threw:", cbErr);
1182
+ }
1183
+ });
1184
+ }
1098
1185
  if (params.onTextReady) {
1099
1186
  params.onTextReady(parsedIntent.textResponse, parsedIntent.actionText, {
1100
1187
  stateUpdate: parsedIntent.stateUpdate,
@@ -1109,10 +1196,23 @@ If "shouldSkipProactive" is false, "textResponse" is required and "stateUpdate"
1109
1196
  const res = await this.generatePrimitive("image", parsedIntent.imageParams);
1110
1197
  finalImageUrl = res.image_url;
1111
1198
  finalImageMediaId = res.id;
1199
+ if (params.onMediaReady && finalImageUrl) {
1200
+ try {
1201
+ params.onMediaReady({
1202
+ modality: "image",
1203
+ url: finalImageUrl,
1204
+ mediaId: finalImageMediaId,
1205
+ });
1206
+ }
1207
+ catch (cbErr) {
1208
+ console.warn("[CyberSoulClient] onMediaReady(image) threw:", cbErr);
1209
+ }
1210
+ }
1112
1211
  }
1113
1212
  catch (e) {
1114
1213
  if (e instanceof CyberSoulInsufficientPointsError ||
1115
- e instanceof CyberSoulWalletError) {
1214
+ e instanceof CyberSoulWalletError ||
1215
+ e instanceof CyberSoulSensitiveContentError) {
1116
1216
  proactiveMediaError = e;
1117
1217
  proactiveAffected.push("image");
1118
1218
  }
package/dist/errors.d.ts CHANGED
@@ -80,3 +80,13 @@ export declare class CyberSoulWalletError extends CyberSoulApiError {
80
80
  readonly code: string;
81
81
  constructor(endpoint: string, method: string, status: number, message: string, body?: unknown, code?: string);
82
82
  }
83
+ /**
84
+ * The backend rejected an image/voice generation request because the
85
+ * prompt (or the model's output) was flagged as sensitive / unsafe
86
+ * (backend code `E005`). The user can recover by sending a different
87
+ * prompt — there's nothing to retry automatically.
88
+ */
89
+ export declare class CyberSoulSensitiveContentError extends CyberSoulApiError {
90
+ readonly code: string;
91
+ constructor(endpoint: string, method: string, status: number, message: string, body?: unknown, code?: string);
92
+ }
package/dist/errors.js CHANGED
@@ -108,3 +108,16 @@ export class CyberSoulWalletError extends CyberSoulApiError {
108
108
  this.code = code;
109
109
  }
110
110
  }
111
+ /**
112
+ * The backend rejected an image/voice generation request because the
113
+ * prompt (or the model's output) was flagged as sensitive / unsafe
114
+ * (backend code `E005`). The user can recover by sending a different
115
+ * prompt — there's nothing to retry automatically.
116
+ */
117
+ export class CyberSoulSensitiveContentError extends CyberSoulApiError {
118
+ code;
119
+ constructor(endpoint, method, status, message, body, code = "E005") {
120
+ super(endpoint, method, status, message, body, "sensitive-content");
121
+ this.code = code;
122
+ }
123
+ }
package/dist/types.d.ts CHANGED
@@ -53,12 +53,37 @@ export interface PersistedDynamicContext {
53
53
  /** Persisted relationship stage label after re-evaluation. */
54
54
  relationshipStage?: string;
55
55
  }
56
+ /**
57
+ * Payload delivered by [InteractParams.onMediaReady] when an individual
58
+ * media task (image/voice) finishes. Fires from inside the SDK's
59
+ * per-modality `.then()` so callers can render the bubble the moment
60
+ * that modality is ready, instead of waiting for the slowest one to
61
+ * finish. The aggregated `InteractResponse` is still returned at the
62
+ * end and carries the same URLs (no double-render needed if the caller
63
+ * tracks per-modality state).
64
+ */
65
+ export interface MediaReadyPayload {
66
+ modality: "image" | "voice";
67
+ url: string;
68
+ mediaId?: string;
69
+ /** Voice only — TTS duration in seconds when known. */
70
+ durationSec?: number;
71
+ }
56
72
  export interface ProactiveParams {
57
73
  history?: HistoryEntry[];
58
74
  maxUnreplied?: number;
59
75
  requestTypes?: InteractRequestType[];
60
76
  localContext?: string;
61
77
  onTextReady?: (textResponse: string, actionText?: string, metadata?: InteractMetadata) => void;
78
+ /**
79
+ * Fires when the server-authoritative PATCH /dynamic-context resolves,
80
+ * before media generation completes. Lets the UI update the live
81
+ * temperature / relationship stage immediately instead of waiting for
82
+ * the (potentially slow) image task.
83
+ */
84
+ onStateReady?: (persisted: PersistedDynamicContext) => void;
85
+ /** Fires per modality as each media task settles successfully. */
86
+ onMediaReady?: (payload: MediaReadyPayload) => void;
62
87
  }
63
88
  export interface ProactiveResponse {
64
89
  status: "success" | "skipped" | "error";
@@ -85,6 +110,17 @@ export interface InteractParams {
85
110
  requestTypes?: InteractRequestType[];
86
111
  history?: HistoryEntry[];
87
112
  onTextReady?: (textResponse: string, actionText?: string, metadata?: InteractMetadata) => void;
113
+ /**
114
+ * Fires when the server-authoritative PATCH /dynamic-context resolves,
115
+ * before media generation completes. Lets the UI update the live
116
+ * temperature / relationship stage immediately instead of waiting for
117
+ * the (potentially slow) image task. When the turn has no
118
+ * `stateUpdate`, this still fires with an empty object so callers can
119
+ * use it as a generic "LLM phase done" signal.
120
+ */
121
+ onStateReady?: (persisted: PersistedDynamicContext) => void;
122
+ /** Fires per modality as each media task settles successfully. */
123
+ onMediaReady?: (payload: MediaReadyPayload) => void;
88
124
  }
89
125
  export interface OndemandEventParams {
90
126
  eventDescription: string;
@@ -155,7 +191,7 @@ export interface InteractResponse {
155
191
  */
156
192
  export interface InteractMediaError {
157
193
  /** Coarse kind so UIs can map to a single user-facing message. */
158
- kind: "insufficient-points" | "wallet" | "unknown";
194
+ kind: "insufficient-points" | "wallet" | "sensitive-content" | "unknown";
159
195
  /** Backend machine code when available (e.g. "INSUFFICIENT_POINTS"). */
160
196
  code?: string;
161
197
  /** Raw error message, for logs / diagnostics. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@space3-npm/cybersoul-client",
3
- "version": "1.4.15",
3
+ "version": "1.4.17",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",