realtime-avatar 0.7.1 → 0.7.3

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.
@@ -43,7 +43,7 @@ function isTransient(cause) {
43
43
 
44
44
  // ../http-client/src/client.ts
45
45
  var DEFAULT_BASE_URL = "https://realtimeavatar.ai/api/v1";
46
- var SDK_VERSION = "0.7.1";
46
+ var SDK_VERSION = "0.7.3";
47
47
  var RealtimeAvatar = class {
48
48
  #apiKey;
49
49
  #baseUrl;
@@ -1,4 +1,4 @@
1
- import { RealtimeAvatar, isQueued, RealtimeAvatarHttpError } from './chunk-NQJK3QD7.js';
1
+ import { RealtimeAvatar, isQueued, RealtimeAvatarHttpError } from './chunk-2V333KRZ.js';
2
2
 
3
3
  // ../proxy/src/config.ts
4
4
  var json = (body, status = 200) => new Response(JSON.stringify(body), {
package/dist/express.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-QC5BVIUO.js';
2
- import './chunk-NQJK3QD7.js';
1
+ import { createProxyHandler } from './chunk-OMOEUVO5.js';
2
+ import './chunk-2V333KRZ.js';
3
3
 
4
4
  // ../proxy/src/express.ts
5
5
  function realtimeAvatarExpress(config) {
package/dist/hono.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-QC5BVIUO.js';
2
- import './chunk-NQJK3QD7.js';
1
+ import { createProxyHandler } from './chunk-OMOEUVO5.js';
2
+ import './chunk-2V333KRZ.js';
3
3
 
4
4
  // ../proxy/src/hono.ts
5
5
  function realtimeAvatarHono(config) {
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, isQueued, verifyTranscript } from './chunk-NQJK3QD7.js';
1
+ export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, isQueued, verifyTranscript } from './chunk-2V333KRZ.js';
package/dist/nextjs.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-QC5BVIUO.js';
2
- import './chunk-NQJK3QD7.js';
1
+ import { createProxyHandler } from './chunk-OMOEUVO5.js';
2
+ import './chunk-2V333KRZ.js';
3
3
 
4
4
  // ../proxy/src/nextjs.ts
5
5
  function createRealtimeAvatarRoute(config) {
@@ -131,6 +131,43 @@ function timeoutSetter(fn, ms) {
131
131
  }
132
132
 
133
133
  // ../client/src/errors.ts
134
+ var RealtimeAvatarApiError = class _RealtimeAvatarApiError extends Error {
135
+ constructor(message, status, body, response, metadata = {}) {
136
+ super(message);
137
+ this.status = status;
138
+ this.body = body;
139
+ this.response = response;
140
+ this.name = "RealtimeAvatarApiError";
141
+ this.code = metadata.code;
142
+ this.retryable = metadata.retryable;
143
+ this.billingUrl = metadata.billingUrl;
144
+ this.rawMessage = metadata.rawMessage ?? null;
145
+ }
146
+ status;
147
+ body;
148
+ response;
149
+ code;
150
+ retryable;
151
+ billingUrl;
152
+ /** Debug detail returned by the API. Do not render directly in user-facing UI. */
153
+ rawMessage;
154
+ get isBillingRequired() {
155
+ return this.status === 402 || this.code === "insufficient_credits" || this.code === "spend_limit_exceeded";
156
+ }
157
+ static async fromResponse(response) {
158
+ const contentType = response.headers.get("content-type") ?? "";
159
+ const body = contentType.includes("application/json") ? await response.clone().json().catch(() => null) : await response.clone().text().catch(() => "");
160
+ const rawMessage = (extractErrorMessage(body) ?? response.statusText) || null;
161
+ const metadata = extractErrorMetadata(body, response.status, rawMessage);
162
+ return new _RealtimeAvatarApiError(
163
+ metadata.message,
164
+ response.status,
165
+ body,
166
+ response,
167
+ metadata
168
+ );
169
+ }
170
+ };
134
171
  var RealtimeAvatarCapacityError = class extends Error {
135
172
  constructor(message, busy) {
136
173
  super(message);
@@ -151,6 +188,107 @@ var RealtimeAvatarCapacityError = class extends Error {
151
188
  return this.busy.recommended_retry_ms;
152
189
  }
153
190
  };
191
+ function extractErrorMessage(body) {
192
+ if (!body || typeof body !== "object") return typeof body === "string" && body ? body : null;
193
+ const record = body;
194
+ const detail = readMessageValue(record.detail);
195
+ if (detail) return detail;
196
+ const error = readMessageValue(record.error);
197
+ if (error) return error;
198
+ const message = readMessageValue(record.message);
199
+ if (message) return message;
200
+ return null;
201
+ }
202
+ function normalizeRealtimeAvatarError(input) {
203
+ const status = input.status ?? 0;
204
+ const rawCode = input.code?.trim() || null;
205
+ const rawMessage = input.message?.trim() || "";
206
+ const blob = `${rawCode ?? ""} ${rawMessage}`.toLowerCase();
207
+ if (rawCode === "insufficient_credits" || rawCode === "spend_limit_exceeded" || status === 402) {
208
+ return {
209
+ code: rawCode ?? "billing_required",
210
+ message: rawMessage && !hasInternalDetail(rawMessage) ? rawMessage : "Add credits to continue using realtime avatars.",
211
+ retryable: false
212
+ };
213
+ }
214
+ if (status === 401) {
215
+ return { code: "unauthorized", message: "Authentication is required to use Realtime Avatar.", retryable: false };
216
+ }
217
+ if (status === 403) {
218
+ return { code: "forbidden", message: "This API key does not have access to that action.", retryable: false };
219
+ }
220
+ if (status === 404 || blob.includes("unknown avatar_id") || blob.includes("avatar not found")) {
221
+ return { code: "not_found", message: "We could not find that avatar or resource.", retryable: false };
222
+ }
223
+ if (status === 409) {
224
+ return { code: "conflict", message: "That request is already in progress. Try again in a moment.", retryable: true };
225
+ }
226
+ if (status === 429) {
227
+ return { code: "rate_limited", message: "Too many realtime requests. Please slow down and retry.", retryable: true };
228
+ }
229
+ if (isWarmingMessage(blob)) {
230
+ return { code: "service_unavailable", message: "Realtime Avatar is warming up. Try again in a moment.", retryable: true };
231
+ }
232
+ if (status === 400 || status === 422) {
233
+ return { code: "invalid_request", message: userSafeMessage(rawMessage) ?? "Check the request and try again.", retryable: false };
234
+ }
235
+ if (status >= 500) {
236
+ return { code: "service_unavailable", message: "Realtime Avatar is temporarily unavailable. Try again in a moment.", retryable: true };
237
+ }
238
+ return {
239
+ code: isPublicErrorCode(rawCode) ? rawCode : "request_failed",
240
+ message: userSafeMessage(rawMessage) ?? "Realtime Avatar request failed. Try again.",
241
+ retryable: status === 0 || status >= 500
242
+ };
243
+ }
244
+ function extractErrorMetadata(body, status, rawMessage) {
245
+ if (!body || typeof body !== "object") {
246
+ const normalized2 = normalizeRealtimeAvatarError({ status, message: rawMessage });
247
+ return { ...normalized2, rawMessage };
248
+ }
249
+ const record = body;
250
+ const code = typeof record.code === "string" ? record.code : null;
251
+ const normalized = normalizeRealtimeAvatarError({ status, code, message: rawMessage });
252
+ return {
253
+ code: normalized.code,
254
+ retryable: typeof record.retryable === "boolean" ? record.retryable : normalized.retryable,
255
+ ...typeof record.billingUrl === "string" ? { billingUrl: record.billingUrl } : {},
256
+ rawMessage,
257
+ message: normalized.message
258
+ };
259
+ }
260
+ var PUBLIC_ERROR_CODES = /* @__PURE__ */ new Set([
261
+ "billing_required",
262
+ "invalid_request",
263
+ "unauthorized",
264
+ "forbidden",
265
+ "not_found",
266
+ "conflict",
267
+ "rate_limited",
268
+ "service_unavailable",
269
+ "request_failed",
270
+ "network_error"
271
+ ]);
272
+ function isPublicErrorCode(code) {
273
+ return Boolean(code && PUBLIC_ERROR_CODES.has(code));
274
+ }
275
+ function readMessageValue(value) {
276
+ if (typeof value === "string") return value.trim() || null;
277
+ if (!value || typeof value !== "object") return null;
278
+ const record = value;
279
+ return readMessageValue(record.message) ?? readMessageValue(record.error) ?? readMessageValue(record.detail);
280
+ }
281
+ function userSafeMessage(message) {
282
+ if (!message || hasInternalDetail(message)) return null;
283
+ return message.length > 180 ? `${message.slice(0, 177)}...` : message;
284
+ }
285
+ function isWarmingMessage(blob) {
286
+ return blob.includes("warming") || blob.includes("not ready") || blob.includes("starting up");
287
+ }
288
+ var MACHINE_SHAPED = /[_=<>{}\[\]|\\$`]|\w+\.\w+\.\w|\S:\d|\w\(\)|^\s*(?:at|File)\b/mu;
289
+ function hasInternalDetail(message) {
290
+ return MACHINE_SHAPED.test(message);
291
+ }
154
292
  function splitCallTranscript(all, localIdentity) {
155
293
  if (!localIdentity) return { user: [], agent: all };
156
294
  const user = [];
@@ -1697,6 +1835,7 @@ function useSessionLifecycle(input) {
1697
1835
  if (!active || connected || connectWatchdogMs <= 0) return;
1698
1836
  if (grantState.status !== "ready" || !grantState.grant) return;
1699
1837
  if (recovery.kind === "failed" || recovery.kind === "ended") return;
1838
+ if (recovery.kind === "in-place-reconnecting") return;
1700
1839
  const timer = window.setTimeout(() => {
1701
1840
  releaseRef.current("disconnected");
1702
1841
  setRecovery({ kind: "reconnecting", attempt: attemptRef.current });
@@ -2389,8 +2528,7 @@ function createProxyClient(options) {
2389
2528
  return { status: "busy", busy };
2390
2529
  }
2391
2530
  if (!response.ok) {
2392
- const detail = await response.text().catch(() => "");
2393
- throw new Error(`realtime-avatar: proxy refused the call (${response.status}) ${detail}`.trim());
2531
+ throw await RealtimeAvatarApiError.fromResponse(response);
2394
2532
  }
2395
2533
  return { status: "ready", grant: await response.json() };
2396
2534
  },
package/dist/react.js CHANGED
@@ -90,6 +90,43 @@ function timeoutSetter(fn, ms) {
90
90
  }
91
91
 
92
92
  // ../client/src/errors.ts
93
+ var RealtimeAvatarApiError = class _RealtimeAvatarApiError extends Error {
94
+ constructor(message, status, body, response, metadata = {}) {
95
+ super(message);
96
+ this.status = status;
97
+ this.body = body;
98
+ this.response = response;
99
+ this.name = "RealtimeAvatarApiError";
100
+ this.code = metadata.code;
101
+ this.retryable = metadata.retryable;
102
+ this.billingUrl = metadata.billingUrl;
103
+ this.rawMessage = metadata.rawMessage ?? null;
104
+ }
105
+ status;
106
+ body;
107
+ response;
108
+ code;
109
+ retryable;
110
+ billingUrl;
111
+ /** Debug detail returned by the API. Do not render directly in user-facing UI. */
112
+ rawMessage;
113
+ get isBillingRequired() {
114
+ return this.status === 402 || this.code === "insufficient_credits" || this.code === "spend_limit_exceeded";
115
+ }
116
+ static async fromResponse(response) {
117
+ const contentType = response.headers.get("content-type") ?? "";
118
+ const body = contentType.includes("application/json") ? await response.clone().json().catch(() => null) : await response.clone().text().catch(() => "");
119
+ const rawMessage = (extractErrorMessage(body) ?? response.statusText) || null;
120
+ const metadata = extractErrorMetadata(body, response.status, rawMessage);
121
+ return new _RealtimeAvatarApiError(
122
+ metadata.message,
123
+ response.status,
124
+ body,
125
+ response,
126
+ metadata
127
+ );
128
+ }
129
+ };
93
130
  var RealtimeAvatarCapacityError = class extends Error {
94
131
  constructor(message, busy) {
95
132
  super(message);
@@ -110,6 +147,107 @@ var RealtimeAvatarCapacityError = class extends Error {
110
147
  return this.busy.recommended_retry_ms;
111
148
  }
112
149
  };
150
+ function extractErrorMessage(body) {
151
+ if (!body || typeof body !== "object") return typeof body === "string" && body ? body : null;
152
+ const record = body;
153
+ const detail = readMessageValue(record.detail);
154
+ if (detail) return detail;
155
+ const error = readMessageValue(record.error);
156
+ if (error) return error;
157
+ const message = readMessageValue(record.message);
158
+ if (message) return message;
159
+ return null;
160
+ }
161
+ function normalizeRealtimeAvatarError(input) {
162
+ const status = input.status ?? 0;
163
+ const rawCode = input.code?.trim() || null;
164
+ const rawMessage = input.message?.trim() || "";
165
+ const blob = `${rawCode ?? ""} ${rawMessage}`.toLowerCase();
166
+ if (rawCode === "insufficient_credits" || rawCode === "spend_limit_exceeded" || status === 402) {
167
+ return {
168
+ code: rawCode ?? "billing_required",
169
+ message: rawMessage && !hasInternalDetail(rawMessage) ? rawMessage : "Add credits to continue using realtime avatars.",
170
+ retryable: false
171
+ };
172
+ }
173
+ if (status === 401) {
174
+ return { code: "unauthorized", message: "Authentication is required to use Realtime Avatar.", retryable: false };
175
+ }
176
+ if (status === 403) {
177
+ return { code: "forbidden", message: "This API key does not have access to that action.", retryable: false };
178
+ }
179
+ if (status === 404 || blob.includes("unknown avatar_id") || blob.includes("avatar not found")) {
180
+ return { code: "not_found", message: "We could not find that avatar or resource.", retryable: false };
181
+ }
182
+ if (status === 409) {
183
+ return { code: "conflict", message: "That request is already in progress. Try again in a moment.", retryable: true };
184
+ }
185
+ if (status === 429) {
186
+ return { code: "rate_limited", message: "Too many realtime requests. Please slow down and retry.", retryable: true };
187
+ }
188
+ if (isWarmingMessage(blob)) {
189
+ return { code: "service_unavailable", message: "Realtime Avatar is warming up. Try again in a moment.", retryable: true };
190
+ }
191
+ if (status === 400 || status === 422) {
192
+ return { code: "invalid_request", message: userSafeMessage(rawMessage) ?? "Check the request and try again.", retryable: false };
193
+ }
194
+ if (status >= 500) {
195
+ return { code: "service_unavailable", message: "Realtime Avatar is temporarily unavailable. Try again in a moment.", retryable: true };
196
+ }
197
+ return {
198
+ code: isPublicErrorCode(rawCode) ? rawCode : "request_failed",
199
+ message: userSafeMessage(rawMessage) ?? "Realtime Avatar request failed. Try again.",
200
+ retryable: status === 0 || status >= 500
201
+ };
202
+ }
203
+ function extractErrorMetadata(body, status, rawMessage) {
204
+ if (!body || typeof body !== "object") {
205
+ const normalized2 = normalizeRealtimeAvatarError({ status, message: rawMessage });
206
+ return { ...normalized2, rawMessage };
207
+ }
208
+ const record = body;
209
+ const code = typeof record.code === "string" ? record.code : null;
210
+ const normalized = normalizeRealtimeAvatarError({ status, code, message: rawMessage });
211
+ return {
212
+ code: normalized.code,
213
+ retryable: typeof record.retryable === "boolean" ? record.retryable : normalized.retryable,
214
+ ...typeof record.billingUrl === "string" ? { billingUrl: record.billingUrl } : {},
215
+ rawMessage,
216
+ message: normalized.message
217
+ };
218
+ }
219
+ var PUBLIC_ERROR_CODES = /* @__PURE__ */ new Set([
220
+ "billing_required",
221
+ "invalid_request",
222
+ "unauthorized",
223
+ "forbidden",
224
+ "not_found",
225
+ "conflict",
226
+ "rate_limited",
227
+ "service_unavailable",
228
+ "request_failed",
229
+ "network_error"
230
+ ]);
231
+ function isPublicErrorCode(code) {
232
+ return Boolean(code && PUBLIC_ERROR_CODES.has(code));
233
+ }
234
+ function readMessageValue(value) {
235
+ if (typeof value === "string") return value.trim() || null;
236
+ if (!value || typeof value !== "object") return null;
237
+ const record = value;
238
+ return readMessageValue(record.message) ?? readMessageValue(record.error) ?? readMessageValue(record.detail);
239
+ }
240
+ function userSafeMessage(message) {
241
+ if (!message || hasInternalDetail(message)) return null;
242
+ return message.length > 180 ? `${message.slice(0, 177)}...` : message;
243
+ }
244
+ function isWarmingMessage(blob) {
245
+ return blob.includes("warming") || blob.includes("not ready") || blob.includes("starting up");
246
+ }
247
+ var MACHINE_SHAPED = /[_=<>{}\[\]|\\$`]|\w+\.\w+\.\w|\S:\d|\w\(\)|^\s*(?:at|File)\b/mu;
248
+ function hasInternalDetail(message) {
249
+ return MACHINE_SHAPED.test(message);
250
+ }
113
251
  function splitCallTranscript(all, localIdentity) {
114
252
  if (!localIdentity) return { user: [], agent: all };
115
253
  const user = [];
@@ -1920,6 +2058,7 @@ function useSessionLifecycle(input) {
1920
2058
  if (!active || connected || connectWatchdogMs <= 0) return;
1921
2059
  if (grantState.status !== "ready" || !grantState.grant) return;
1922
2060
  if (recovery.kind === "failed" || recovery.kind === "ended") return;
2061
+ if (recovery.kind === "in-place-reconnecting") return;
1923
2062
  const timer = window.setTimeout(() => {
1924
2063
  releaseRef.current("disconnected");
1925
2064
  setRecovery({ kind: "reconnecting", attempt: attemptRef.current });
@@ -2694,8 +2833,7 @@ function createProxyClient(options) {
2694
2833
  return { status: "busy", busy };
2695
2834
  }
2696
2835
  if (!response.ok) {
2697
- const detail = await response.text().catch(() => "");
2698
- throw new Error(`realtime-avatar: proxy refused the call (${response.status}) ${detail}`.trim());
2836
+ throw await RealtimeAvatarApiError.fromResponse(response);
2699
2837
  }
2700
2838
  return { status: "ready", grant: await response.json() };
2701
2839
  },
package/dist/server.js CHANGED
@@ -1 +1 @@
1
- export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, isQueued, verifyTranscript } from './chunk-NQJK3QD7.js';
1
+ export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, isQueued, verifyTranscript } from './chunk-2V333KRZ.js';
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-QC5BVIUO.js';
2
- import './chunk-NQJK3QD7.js';
1
+ import { createProxyHandler } from './chunk-OMOEUVO5.js';
2
+ import './chunk-2V333KRZ.js';
3
3
 
4
4
  // src/tanstack-start.ts
5
5
  function realtimeAvatarServerRoute(config) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "realtime-avatar",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "description": "Realtime Avatar SDK. `realtime-avatar` and the route adapters hold your API key and are server-only; `realtime-avatar/react`, `/browser` and `/tools` never can — importing a server entry into a browser build throws at bundle time.",
5
5
  "license": "MIT",
6
6
  "author": "The Influence Company",