@space3-npm/cybersoul-client 1.4.13 → 1.4.16

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
@@ -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
- const fetchFn = this.config.fetchImpl ?? fetch;
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,
@@ -720,6 +724,34 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
720
724
  if (parsedIntent && (parsedIntent.stateUpdate || parsedIntent.userAnalysis)) {
721
725
  persistedStatePromise = this._updateDynamicContextInternal(parsedIntent.stateUpdate, parsedIntent.userAnalysis);
722
726
  }
727
+ // Fire `onStateReady` the moment the dynamic-context PATCH resolves
728
+ // (or immediately, when no state update was emitted). This is
729
+ // independent of media generation, so the UI can stop showing
730
+ // "updating…" on temperature / relationship stage well before the
731
+ // (potentially slow) image task finishes. Errors are swallowed:
732
+ // an authoritative snapshot is best-effort, the optimistic delta
733
+ // already applied client-side is the fallback.
734
+ if (params.onStateReady) {
735
+ const stateReadyCb = params.onStateReady;
736
+ persistedStatePromise
737
+ .then((persisted) => {
738
+ try {
739
+ stateReadyCb(persisted ?? {});
740
+ }
741
+ catch (cbErr) {
742
+ console.warn("[CyberSoulClient] onStateReady callback threw:", cbErr);
743
+ }
744
+ })
745
+ .catch(() => {
746
+ // PATCH failed; still signal LLM-phase complete with an empty snapshot.
747
+ try {
748
+ stateReadyCb({});
749
+ }
750
+ catch (cbErr) {
751
+ console.warn("[CyberSoulClient] onStateReady callback threw:", cbErr);
752
+ }
753
+ });
754
+ }
723
755
  const resolvedTextResponse = typeof parsedIntent.textResponse === "string" &&
724
756
  parsedIntent.textResponse.trim().length > 0
725
757
  ? parsedIntent.textResponse
@@ -795,9 +827,24 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
795
827
  .then((res) => {
796
828
  finalImageUrl = res.image_url;
797
829
  finalImageMediaId = res.id;
830
+ if (params.onMediaReady && finalImageUrl) {
831
+ try {
832
+ params.onMediaReady({
833
+ modality: "image",
834
+ url: finalImageUrl,
835
+ mediaId: finalImageMediaId,
836
+ });
837
+ }
838
+ catch (cbErr) {
839
+ console.warn("[CyberSoulClient] onMediaReady(image) threw:", cbErr);
840
+ }
841
+ }
798
842
  })
799
843
  .catch((e) => {
800
- console.error("[CyberSoulClient] Image generation failed:", e);
844
+ if (!(e instanceof CyberSoulInsufficientPointsError) &&
845
+ !(e instanceof CyberSoulWalletError)) {
846
+ console.error("[CyberSoulClient] Image generation failed:", e);
847
+ }
801
848
  captureMediaError("image", e);
802
849
  }));
803
850
  }
@@ -819,9 +866,25 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
819
866
  finalAudioUrl = res.audio_url;
820
867
  finalAudioMediaId = res.id;
821
868
  finalDurationSec = res.duration_sec;
869
+ if (params.onMediaReady && finalAudioUrl) {
870
+ try {
871
+ params.onMediaReady({
872
+ modality: "voice",
873
+ url: finalAudioUrl,
874
+ mediaId: finalAudioMediaId,
875
+ durationSec: finalDurationSec,
876
+ });
877
+ }
878
+ catch (cbErr) {
879
+ console.warn("[CyberSoulClient] onMediaReady(voice) threw:", cbErr);
880
+ }
881
+ }
822
882
  })
823
883
  .catch((e) => {
824
- console.error("[CyberSoulClient] Voice generation failed:", e);
884
+ if (!(e instanceof CyberSoulInsufficientPointsError) &&
885
+ !(e instanceof CyberSoulWalletError)) {
886
+ console.error("[CyberSoulClient] Voice generation failed:", e);
887
+ }
825
888
  captureMediaError("voice", e);
826
889
  }));
827
890
  }
@@ -1085,6 +1148,26 @@ If "shouldSkipProactive" is false, "textResponse" is required and "stateUpdate"
1085
1148
  if (parsedIntent.stateUpdate) {
1086
1149
  persistedStatePromise = this._updateDynamicContextInternal(parsedIntent.stateUpdate);
1087
1150
  }
1151
+ if (params.onStateReady) {
1152
+ const stateReadyCb = params.onStateReady;
1153
+ persistedStatePromise
1154
+ .then((persisted) => {
1155
+ try {
1156
+ stateReadyCb(persisted ?? {});
1157
+ }
1158
+ catch (cbErr) {
1159
+ console.warn("[CyberSoulClient] onStateReady callback threw:", cbErr);
1160
+ }
1161
+ })
1162
+ .catch(() => {
1163
+ try {
1164
+ stateReadyCb({});
1165
+ }
1166
+ catch (cbErr) {
1167
+ console.warn("[CyberSoulClient] onStateReady callback threw:", cbErr);
1168
+ }
1169
+ });
1170
+ }
1088
1171
  if (params.onTextReady) {
1089
1172
  params.onTextReady(parsedIntent.textResponse, parsedIntent.actionText, {
1090
1173
  stateUpdate: parsedIntent.stateUpdate,
@@ -1099,14 +1182,28 @@ If "shouldSkipProactive" is false, "textResponse" is required and "stateUpdate"
1099
1182
  const res = await this.generatePrimitive("image", parsedIntent.imageParams);
1100
1183
  finalImageUrl = res.image_url;
1101
1184
  finalImageMediaId = res.id;
1185
+ if (params.onMediaReady && finalImageUrl) {
1186
+ try {
1187
+ params.onMediaReady({
1188
+ modality: "image",
1189
+ url: finalImageUrl,
1190
+ mediaId: finalImageMediaId,
1191
+ });
1192
+ }
1193
+ catch (cbErr) {
1194
+ console.warn("[CyberSoulClient] onMediaReady(image) threw:", cbErr);
1195
+ }
1196
+ }
1102
1197
  }
1103
1198
  catch (e) {
1104
- console.error("[CyberSoulClient] Proactive Image generation failed:", e);
1105
1199
  if (e instanceof CyberSoulInsufficientPointsError ||
1106
1200
  e instanceof CyberSoulWalletError) {
1107
1201
  proactiveMediaError = e;
1108
1202
  proactiveAffected.push("image");
1109
1203
  }
1204
+ else {
1205
+ console.error("[CyberSoulClient] Proactive Image generation failed:", e);
1206
+ }
1110
1207
  }
1111
1208
  }
1112
1209
  const persistedDynamicContext = (await persistedStatePromise) ?? undefined;
@@ -11,7 +11,10 @@ export class GenericLLMProvider {
11
11
  this.fetchImpl = fetchImpl;
12
12
  }
13
13
  get fetchFn() {
14
- return this.fetchImpl ?? fetch;
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
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@space3-npm/cybersoul-client",
3
- "version": "1.4.13",
3
+ "version": "1.4.16",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",