@juspay/neurolink 12.45.3 → 12.46.1

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.
@@ -17,6 +17,7 @@ export const MEDIA_HANDLER_CATALOG = [
17
17
  { kind: "tts", name: "elevenlabs", aliases: ["elevenlabs-tts"] },
18
18
  { kind: "tts", name: "azure-tts" },
19
19
  { kind: "tts", name: "fish-audio" },
20
+ { kind: "tts", name: "sixtydb" },
20
21
  { kind: "tts", name: "cartesia" },
21
22
  // --- STT ---
22
23
  { kind: "stt", name: "whisper", aliases: ["openai-stt"] },
package/dist/index.d.ts CHANGED
@@ -68,7 +68,7 @@ export { AVATAR_ERROR_CODES, AvatarError } from "./utils/avatarProcessor.js";
68
68
  export { MusicProcessor } from "./utils/musicProcessor.js";
69
69
  export { MUSIC_ERROR_CODES, MusicError } from "./utils/musicProcessor.js";
70
70
  export { STTProcessor } from "./utils/sttProcessor.js";
71
- export { AzureTTS, AzureTTSHandler, CartesiaTTS, CartesiaTTSHandler, ElevenLabsTTS, ElevenLabsTTSHandler, FishAudioTTS, FishAudioTTSHandler, GoogleTTSHandler, OpenAITTS, OpenAITTSHandler, AzureSTT, AzureSTTHandler, DeepgramSTT, DeepgramSTTHandler, GoogleSTT, GoogleSTTHandler, OpenAISTT, OpenAISTTHandler, WhisperSTT, WhisperSTTHandler, BaseRealtimeHandler, GeminiLive, GeminiLiveHandler, OpenAIRealtime, OpenAIRealtimeHandler, RealtimeProcessor, RealtimeError, STTError, VoiceError, registerDefaultRealtimeHandlers, registerDefaultSTTHandlers, registerDefaultTTSHandlers, } from "./voice/index.js";
71
+ export { AzureTTS, AzureTTSHandler, CartesiaTTS, CartesiaTTSHandler, ElevenLabsTTS, ElevenLabsTTSHandler, FishAudioTTS, FishAudioTTSHandler, SixtyDBTTS, SixtyDBTTSHandler, GoogleTTSHandler, OpenAITTS, OpenAITTSHandler, AzureSTT, AzureSTTHandler, DeepgramSTT, DeepgramSTTHandler, GoogleSTT, GoogleSTTHandler, OpenAISTT, OpenAISTTHandler, WhisperSTT, WhisperSTTHandler, BaseRealtimeHandler, GeminiLive, GeminiLiveHandler, OpenAIRealtime, OpenAIRealtimeHandler, RealtimeProcessor, RealtimeError, STTError, VoiceError, registerDefaultRealtimeHandlers, registerDefaultSTTHandlers, registerDefaultTTSHandlers, } from "./voice/index.js";
72
72
  export { BeatovenMusic, BeatovenMusicHandler, ElevenLabsMusic, ElevenLabsMusicHandler, LyriaMusic, LyriaMusicHandler, registerDefaultMusicHandlers, ReplicateMusic, ReplicateMusicHandler, } from "./music/index.js";
73
73
  export { DIDAvatar, DIDAvatarHandler, HeyGenAvatar, HeyGenAvatarHandler, registerDefaultAvatarHandlers, ReplicateAvatar, ReplicateAvatarHandler, } from "./avatar/index.js";
74
74
  export { isVertexVideoConfigured, KlingVideoHandler, registerDefaultVideoHandlers, ReplicateVideoHandler, RunwayVideoHandler, VertexVideoHandler, } from "./adapters/video/index.js";
package/dist/index.js CHANGED
@@ -129,7 +129,7 @@ export { STTProcessor } from "./utils/sttProcessor.js";
129
129
  // TTS, STT, Realtime handlers + RealtimeProcessor
130
130
  export {
131
131
  // TTS
132
- AzureTTS, AzureTTSHandler, CartesiaTTS, CartesiaTTSHandler, ElevenLabsTTS, ElevenLabsTTSHandler, FishAudioTTS, FishAudioTTSHandler, GoogleTTSHandler, OpenAITTS, OpenAITTSHandler,
132
+ AzureTTS, AzureTTSHandler, CartesiaTTS, CartesiaTTSHandler, ElevenLabsTTS, ElevenLabsTTSHandler, FishAudioTTS, FishAudioTTSHandler, SixtyDBTTS, SixtyDBTTSHandler, GoogleTTSHandler, OpenAITTS, OpenAITTSHandler,
133
133
  // STT
134
134
  AzureSTT, AzureSTTHandler, DeepgramSTT, DeepgramSTTHandler, GoogleSTT, GoogleSTTHandler, OpenAISTT, OpenAISTTHandler, WhisperSTT, WhisperSTTHandler,
135
135
  // Realtime
@@ -1432,7 +1432,12 @@ export class AnthropicProvider extends BaseProvider {
1432
1432
  message: (ctx) => `Connection error: ${ctx.message}`,
1433
1433
  },
1434
1434
  {
1435
- match: (ctx) => /500|502|503|504|server error/i.test(ctx.message),
1435
+ // Match the shared 5xx predicate: HTTP status or a bounded phrase,
1436
+ // rather than digits embedded in an unrelated value.
1437
+ match: (ctx) => (ctx.statusCode !== undefined &&
1438
+ ctx.statusCode >= 500 &&
1439
+ ctx.statusCode <= 599) ||
1440
+ /server error|bad gateway|service unavailable|gateway timeout|\berror\b\D{0,12}\b5\d\d\b|\b5\d\d\b\D{0,12}\berror\b|\bstatus(?:\s*code)?\b\D{0,12}\b5\d\d\b/i.test(ctx.message),
1436
1441
  errorClass: ProviderError,
1437
1442
  message: (ctx) => `Server error: ${ctx.message}`,
1438
1443
  },
@@ -373,7 +373,8 @@
373
373
  "message": "HuggingFace model '{model}' not found.\n\nSuggestions:\n1. Check model name spelling\n2. Ensure an Inference Provider serves the model (https://huggingface.co/inference/models)\n3. For tool calling, use: meta-llama/Llama-3.3-70B-Instruct, zai-org/GLM-5 or Qwen/Qwen3.5-397B-A17B"
374
374
  },
375
375
  {
376
- "pattern": "function|tool",
376
+ "pattern": "\\b(?:(?:tool|function)s?(?:\\s*/\\s*(?:tool|function)s?)?[ _-]?call(?:s|ing)?|tool_choice|tools? (?:is|are) not supported|(?:does|do) not support tools?)\\b",
377
+ "patternStatuses": [400, 422],
377
378
  "class": "provider",
378
379
  "message": "HuggingFace tool calling error: {message}\n\nNotes:\n1. Ensure you're using a tool-capable model, e.g. meta-llama/Llama-3.3-70B-Instruct, zai-org/GLM-5 or Qwen/Qwen3.5-397B-A17B\n2. Check that your model supports function calling\n3. Verify tool schema format is correct"
379
380
  }
@@ -66,7 +66,11 @@ function buildErrorRules(entry) {
66
66
  const needsContext = rule.message.includes("{model}") || rule.message.includes("{message}");
67
67
  return {
68
68
  match: (ctx) => (rule.status !== undefined && ctx.statusCode === rule.status) ||
69
- (regex !== undefined && regex.test(ctx.message)),
69
+ (regex !== undefined &&
70
+ (rule.patternStatuses === undefined ||
71
+ (ctx.statusCode !== undefined &&
72
+ rule.patternStatuses.includes(ctx.statusCode))) &&
73
+ regex.test(ctx.message)),
70
74
  errorClass: ERROR_CLASS_MAP[rule.class],
71
75
  message: needsContext
72
76
  ? (ctx) => interpolate(rule.message, entry, ctx.modelName, ctx.message)
@@ -71,6 +71,7 @@ export declare const providerCatalogJsonSchema: z.ZodObject<{
71
71
  errorRules: z.ZodArray<z.ZodObject<{
72
72
  status: z.ZodOptional<z.ZodNumber>;
73
73
  pattern: z.ZodOptional<z.ZodString>;
74
+ patternStatuses: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
74
75
  class: z.ZodEnum<{
75
76
  network: "network";
76
77
  provider: "provider";
@@ -111,6 +111,7 @@ const catalogErrorRuleJsonSchema = z
111
111
  .strictObject({
112
112
  status: z.number().optional(),
113
113
  pattern: z.string().optional(),
114
+ patternStatuses: z.array(z.number().int()).min(1).optional(),
114
115
  class: catalogErrorRuleClassSchema,
115
116
  message: z.string(),
116
117
  })
@@ -122,6 +123,13 @@ const catalogErrorRuleJsonSchema = z
122
123
  message: "errorRules entry requires status or pattern",
123
124
  });
124
125
  }
126
+ if (rule.patternStatuses !== undefined && rule.pattern === undefined) {
127
+ ctx.addIssue({
128
+ code: "custom",
129
+ path: ["patternStatuses"],
130
+ message: "errorRules patternStatuses requires pattern",
131
+ });
132
+ }
125
133
  if (rule.pattern !== undefined) {
126
134
  try {
127
135
  new RegExp(rule.pattern, "i");
@@ -5,8 +5,8 @@ import { createProxyFetch } from "../proxy/proxyFetch.js";
5
5
  import { AuthenticationError, ProviderError, RateLimitError, } from "../types/index.js";
6
6
  import { logger } from "../utils/logger.js";
7
7
  import { createIdeogramConfig, getProviderModel, validateApiKey, } from "../utils/providerConfig.js";
8
- import { MAX_IMAGE_BYTES, readBoundedBuffer } from "../utils/sizeGuard.js";
9
- import { assertSafeUrl } from "../utils/ssrfGuard.js";
8
+ import { MAX_IMAGE_BYTES } from "../utils/sizeGuard.js";
9
+ import { safeDownload } from "../utils/safeFetch.js";
10
10
  const IDEOGRAM_DEFAULT_BASE_URL = "https://api.ideogram.ai";
11
11
  const REQUEST_TIMEOUT_MS = 120_000;
12
12
  const getIdeogramApiKey = () => validateApiKey(createIdeogramConfig());
@@ -134,34 +134,13 @@ export class IdeogramProvider extends BaseProvider {
134
134
  if (!url) {
135
135
  throw new Error("Ideogram returned no image URL");
136
136
  }
137
- // Guard the API-returned URL before fetching (provider-returned URLs
138
- // carry the same SSRF risk as caller-supplied ones) — matches Recraft's
139
- // equivalent download path.
140
- await assertSafeUrl(url);
141
- // Download the image and convert to base64 to match the imageOutput
142
- // contract used by other image-gen providers. Apply a 60s timeout so the
143
- // download cannot hang indefinitely.
144
- const dlController = new AbortController();
145
- const dlTimeoutId = setTimeout(() => dlController.abort(), 60_000);
146
- let dl;
147
- try {
148
- dl = await this.proxyFetch(url, { signal: dlController.signal });
149
- }
150
- catch (err) {
151
- if (err instanceof Error && err.name === "AbortError") {
152
- throw new Error("Ideogram image download timed out after 60s", {
153
- cause: err,
154
- });
155
- }
156
- throw err;
157
- }
158
- finally {
159
- clearTimeout(dlTimeoutId);
160
- }
161
- if (!dl.ok) {
162
- throw new Error(`Failed to download Ideogram image: ${dl.status}`);
163
- }
164
- const buffer = await readBoundedBuffer(dl, MAX_IMAGE_BYTES, "Ideogram image");
137
+ // Resolve and validate once, dial only those addresses, and refuse
138
+ // redirects so a changing DNS answer cannot steer this download.
139
+ const buffer = await safeDownload(url, {
140
+ label: "Ideogram image",
141
+ maxBytes: MAX_IMAGE_BYTES,
142
+ timeoutMs: 60_000,
143
+ });
165
144
  const base64 = buffer.toString("base64");
166
145
  const generationTimeMs = Date.now() - startTime;
167
146
  logger.info(`[IdeogramProvider] Generated image (${buffer.length} bytes) in ${generationTimeMs}ms — model ${this.modelName}`);
@@ -57,7 +57,8 @@ export class LlamaCppProvider extends OpenAIChatCompletionsProvider {
57
57
  buildLocalUnreachableErrorRule(error, () => `llama.cpp server not reachable at ${redactUrlCredentials(this.config.baseURL)}. ` +
58
58
  "Start it with: ./llama-server -m model.gguf --port 8080"),
59
59
  {
60
- match: (ctx) => /400/.test(ctx.message),
60
+ match: (ctx) => ctx.statusCode === 400 ||
61
+ (ctx.statusCode === undefined && /\b400\b/.test(ctx.message)),
61
62
  errorClass: ProviderError,
62
63
  message: "llama.cpp rejected the request. Common cause: model doesn't support tools (start llama-server with --jinja for tool support).",
63
64
  },
@@ -12,7 +12,7 @@ import { assertSafeUrl } from "../../utils/ssrfGuard.js";
12
12
  import { createTimeoutController } from "../../utils/timeout.js";
13
13
  import { stripTrailingSlash } from "../openaiChatCompletionsClient.js";
14
14
  import { OpenAIChatCompletionsProvider } from "../openaiChatCompletionsBase.js";
15
- import { isOpenAIQuotaExhaustedError } from "../../utils/providerRetry.js";
15
+ import { isOpenAIQuotaExhaustedError, readOpenAIBodyErrorType, } from "../../utils/providerRetry.js";
16
16
  const OPENAI_DEFAULT_BASE_URL = "https://api.openai.com/v1";
17
17
  /**
18
18
  * Resolve the effective OpenAI base URL from optional credential / env
@@ -91,13 +91,12 @@ export class OpenAIProvider extends OpenAIChatCompletionsProvider {
91
91
  return getOpenAIModel();
92
92
  }
93
93
  formatProviderError(error) {
94
- // `type` isn't part of `ProviderErrorContext` (it's an OpenAI-specific
95
- // error-body field), so it's read directly off the raw error here and
96
- // captured by the rule closures below.
94
+ // Chat errors retain the JSON in responseBody; embedding errors stamp
95
+ // type onto the raw error. Keep both paths available to the rule closures.
97
96
  const errorObj = error;
98
97
  const errorType = errorObj?.type && typeof errorObj.type === "string"
99
98
  ? errorObj.type
100
- : undefined;
99
+ : readOpenAIBodyErrorType(errorObj?.responseBody);
101
100
  const rules = [
102
101
  // Curator P1-1 / Reviewer Finding #4: only the explicit auth markers
103
102
  // map to AuthenticationError. Earlier we treated every
@@ -1005,6 +1005,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
1005
1005
  // endpoint, the one it was written for, is a Tier-2 catalog provider on
1006
1006
  // this very base class, so without this the recovery would simply not
1007
1007
  // happen for it.
1008
+ let promptSideFallbackRan = false;
1008
1009
  let loop;
1009
1010
  try {
1010
1011
  loop = await runLoop(conversation, responseFormat);
@@ -1018,6 +1019,8 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
1018
1019
  throw error;
1019
1020
  }
1020
1021
  logger.warn(`[${this.providerName}] provider rejected response_format — retrying with the schema in the system prompt`, { provider: this.providerName, model: modelId });
1022
+ // Re-asking after this fallback would bill the same request again.
1023
+ promptSideFallbackRan = true;
1021
1024
  loop = await runLoop(appendJsonSchemaInstruction(conversation, responseFormat.schema), undefined);
1022
1025
  }
1023
1026
  // The vendor can also IGNORE `response_format` and answer in prose without
@@ -1027,7 +1030,8 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
1027
1030
  // above was reached; the native loop has no such parser, so the silent
1028
1031
  // case sailed through and handed the caller prose. Same recovery, keyed on
1029
1032
  // the result rather than on an exception.
1030
- if (responseFormat !== undefined &&
1033
+ if (!promptSideFallbackRan &&
1034
+ responseFormat !== undefined &&
1031
1035
  options.schema !== undefined &&
1032
1036
  !yieldsSchemaValidObject(loop.text, options.schema)) {
1033
1037
  logger.warn(`[${this.providerName}] response_format did not yield a schema-valid object — retrying with the schema in the system prompt`, { provider: this.providerName, model: modelId });
@@ -1821,12 +1825,10 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
1821
1825
  // `loopPromise` is undefined when a middleware blocked the request
1822
1826
  // before `doStream` ran, in which case there is no loop to surface.
1823
1827
  await loopPromise;
1824
- // Record the terminal finish reason on the metadata object this
1825
- // provider returns by reference (`result.metadata`). A rejected
1826
- // `loopPromise` throws out of this `await` straight into the `catch`
1827
- // below, so reaching this line means `runStreamLoop` returned
1828
- // normally and already called `resolveFinish` with the wire value —
1829
- // `finishPromise` is settled, not pending.
1828
+ // Native loops settle the finish promise before returning. A
1829
+ // middleware can close its synthetic stream without calling doStream,
1830
+ // leaving no loop to settle it. A promise settles once, so an explicit
1831
+ // finish part already observed still wins.
1830
1832
  //
1831
1833
  // `rawFinishReason` is the verbatim vendor value (e.g. "stop",
1832
1834
  // "length", "tool_calls", "content_filter") and is accurate the
@@ -1834,6 +1836,9 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
1834
1836
  // post-stream work follows. The graded `finishReason` is set later,
1835
1837
  // once that post-stream work (the structured-output re-ask below)
1836
1838
  // has actually succeeded — see the assignment after that block.
1839
+ if (!loopPromise) {
1840
+ resolveFinish("stop");
1841
+ }
1837
1842
  streamMetadata.rawFinishReason = await finishPromise;
1838
1843
  // Structured output for a `stream({ schema })` turn. Runs HERE —
1839
1844
  // after the stream is fully drained — so a tool-free re-ask (when the
@@ -454,11 +454,23 @@ const SCHEMA_NODES = [
454
454
  "then",
455
455
  "else",
456
456
  ];
457
- // OpenAI's strict mode does not accept these composition keywords. A schema
457
+ // OpenAI's strict mode does not accept these keywords. A schema
458
458
  // carrying one cannot be sent with `strict: true` at all, so it is not merely
459
459
  // "not yet normalised" — it must drop to non-strict, where the schema is
460
460
  // honoured as written.
461
- const STRICT_UNSUPPORTED = ["allOf", "not", "if", "then", "else"];
461
+ const STRICT_UNSUPPORTED = [
462
+ "allOf",
463
+ "oneOf",
464
+ "not",
465
+ "if",
466
+ "then",
467
+ "else",
468
+ "dependentRequired",
469
+ "dependentSchemas",
470
+ "patternProperties",
471
+ "uniqueItems",
472
+ "prefixItems",
473
+ ];
462
474
  const mapValues = (obj, fn) => Object.fromEntries(Object.entries((obj ?? {})).map(([k, v]) => [
463
475
  k,
464
476
  fn(v),
@@ -500,7 +512,7 @@ const withClosedObjects = (node) => {
500
512
  /**
501
513
  * True when the schema can legally be sent with `strict: true`: every object
502
514
  * node lists all of its properties as required, every object is closed, and
503
- * no composition keyword OpenAI rejects appears anywhere.
515
+ * no unsupported keyword or tuple-valued items appears anywhere.
504
516
  *
505
517
  * Deliberately conservative — a false negative costs only the stronger
506
518
  * guarantee, while a false positive costs the whole request.
@@ -516,6 +528,10 @@ const satisfiesStrictRequired = (node) => {
516
528
  if (STRICT_UNSUPPORTED.some((k) => k in rec)) {
517
529
  return false;
518
530
  }
531
+ // Draft-07 tuples have an items array; strict mode accepts one items schema.
532
+ if (Array.isArray(rec.items)) {
533
+ return false;
534
+ }
519
535
  if (rec.type === "object") {
520
536
  if (rec.additionalProperties !== false) {
521
537
  return false;
@@ -532,6 +548,8 @@ const satisfiesStrictRequired = (node) => {
532
548
  const nodesOk = SCHEMA_NODES.filter((k) => k in rec).every((k) => satisfiesStrictRequired(rec[k]));
533
549
  return mapsOk && nodesOk;
534
550
  };
551
+ const hasObjectRoot = (schema) => schema.type === "object" && !("anyOf" in schema) && !("oneOf" in schema);
552
+ const isStrictLegal = (schema) => hasObjectRoot(schema) && satisfiesStrictRequired(schema);
535
553
  export const v3ResponseFormatToOpenAI = (rf) => {
536
554
  if (rf.type === "text") {
537
555
  return { type: "text" };
@@ -539,7 +557,7 @@ export const v3ResponseFormatToOpenAI = (rf) => {
539
557
  if (!rf.schema) {
540
558
  return { type: "json_object" };
541
559
  }
542
- // Mutate as little as possible, in this order:
560
+ // Mutate as little as possible (root must be an object, without anyOf/oneOf):
543
561
  //
544
562
  // 1. already strict-legal -> send it UNTOUCHED with strict: true
545
563
  // 2. legal once closed -> send the closed copy with strict: true
@@ -559,9 +577,9 @@ export const v3ResponseFormatToOpenAI = (rf) => {
559
577
  // what it did before this change.
560
578
  const original = rf.schema;
561
579
  const closed = withClosedObjects(original);
562
- const schema = satisfiesStrictRequired(original)
580
+ const schema = isStrictLegal(original)
563
581
  ? original
564
- : satisfiesStrictRequired(closed)
582
+ : isStrictLegal(closed)
565
583
  ? closed
566
584
  : original;
567
585
  return {
@@ -570,7 +588,7 @@ export const v3ResponseFormatToOpenAI = (rf) => {
570
588
  name: rf.name ?? "response",
571
589
  schema: schema,
572
590
  ...(rf.description ? { description: rf.description } : {}),
573
- strict: satisfiesStrictRequired(schema),
591
+ strict: isStrictLegal(schema),
574
592
  },
575
593
  };
576
594
  };
@@ -5,8 +5,8 @@ import { createProxyFetch } from "../proxy/proxyFetch.js";
5
5
  import { AuthenticationError, InvalidModelError, ProviderError, RateLimitError, } from "../types/index.js";
6
6
  import { logger } from "../utils/logger.js";
7
7
  import { createRecraftConfig, getProviderModel, validateApiKey, } from "../utils/providerConfig.js";
8
- import { MAX_IMAGE_BYTES, readBoundedBuffer } from "../utils/sizeGuard.js";
9
- import { assertSafeUrl } from "../utils/ssrfGuard.js";
8
+ import { MAX_IMAGE_BYTES } from "../utils/sizeGuard.js";
9
+ import { safeDownload } from "../utils/safeFetch.js";
10
10
  import { detectImageMimeType } from "../utils/imageDetection.js";
11
11
  const RECRAFT_DEFAULT_BASE_URL = "https://external.api.recraft.ai/v1";
12
12
  const REQUEST_TIMEOUT_MS = 120_000;
@@ -141,32 +141,13 @@ export class RecraftProvider extends BaseProvider {
141
141
  base64 = entry.b64_json;
142
142
  }
143
143
  else if (entry.url) {
144
- // Guard the API-returned URL before fetching (provider-returned URLs
145
- // carry the same SSRF risk as caller-supplied ones).
146
- await assertSafeUrl(entry.url);
147
- // Fallback URL download — apply a 60s timeout so it cannot hang indefinitely.
148
- const dlController = new AbortController();
149
- const dlTimeoutId = setTimeout(() => dlController.abort(), 60_000);
150
- let dl;
151
- try {
152
- dl = await this.proxyFetch(entry.url, { signal: dlController.signal });
153
- }
154
- catch (err) {
155
- if (err instanceof Error && err.name === "AbortError") {
156
- throw new Error("Recraft image download timed out after 60s", {
157
- cause: err,
158
- });
159
- }
160
- throw err;
161
- }
162
- finally {
163
- clearTimeout(dlTimeoutId);
164
- }
165
- if (!dl.ok) {
166
- throw new Error(`Failed to download Recraft image: ${dl.status}`);
167
- }
168
- const dlBuf = await readBoundedBuffer(dl, MAX_IMAGE_BYTES, "Recraft image");
169
- base64 = dlBuf.toString("base64");
144
+ // See ideogram.ts: pin validated addresses and refuse redirects.
145
+ const buffer = await safeDownload(entry.url, {
146
+ label: "Recraft image",
147
+ maxBytes: MAX_IMAGE_BYTES,
148
+ timeoutMs: 60_000,
149
+ });
150
+ base64 = buffer.toString("base64");
170
151
  }
171
152
  else {
172
153
  throw new Error("Recraft response missing both b64_json and url");
@@ -56,6 +56,8 @@ export type CatalogErrorRuleClass = "authentication" | "rate-limit" | "invalid-m
56
56
  export type CatalogErrorRuleJson = {
57
57
  status?: number;
58
58
  pattern?: string;
59
+ /** Restrict the pattern to these HTTP statuses; status matching stays independent. */
60
+ patternStatuses?: number[];
59
61
  class: CatalogErrorRuleClass;
60
62
  message: string;
61
63
  };
@@ -24,7 +24,7 @@ export type TTSQuality = "standard" | "hd";
24
24
  * provider names registered via `TTSProcessor.registerHandler()` while
25
25
  * still surfacing the built-in choices in editor autocomplete.
26
26
  */
27
- export type TTSProviderName = "google-ai" | "vertex" | "openai-tts" | "elevenlabs" | "elevenlabs-tts" | "azure-tts" | "fish-audio" | "cartesia" | (string & {});
27
+ export type TTSProviderName = "google-ai" | "vertex" | "openai-tts" | "elevenlabs" | "elevenlabs-tts" | "azure-tts" | "fish-audio" | "sixtydb" | "cartesia" | (string & {});
28
28
  /**
29
29
  * TTS configuration options
30
30
  */
@@ -28,7 +28,7 @@ export type VoiceProviderType = "tts" | "stt" | "realtime";
28
28
  /**
29
29
  * Voice provider name union type
30
30
  */
31
- export type VoiceProviderName = "google-tts" | "elevenlabs" | "openai-tts" | "azure-tts" | "sarvam" | "murf" | "playai" | "speechify" | "cartesia" | "deepgram" | "gladia" | "whisper" | "assemblyai" | "google-stt" | "azure-stt" | "openai-realtime" | "gemini-live";
31
+ export type VoiceProviderName = "google-tts" | "elevenlabs" | "openai-tts" | "sixtydb" | "azure-tts" | "sarvam" | "murf" | "playai" | "speechify" | "cartesia" | "deepgram" | "gladia" | "whisper" | "assemblyai" | "google-stt" | "azure-stt" | "openai-realtime" | "gemini-live";
32
32
  /**
33
33
  * Base voice provider configuration
34
34
  */
@@ -52,6 +52,11 @@ export declare function duckTypedStatusCode(error: unknown): number | undefined;
52
52
  * hand-rolled OpenAI-compatible client's record). Shared with baseProvider.
53
53
  */
54
54
  export declare function extractRetryAfterMsFromError(error: unknown): number | undefined;
55
+ /**
56
+ * OpenAI's chat transport keeps the type inside the JSON response body,
57
+ * rather than stamping it onto the raw error.
58
+ */
59
+ export declare function readOpenAIBodyErrorType(responseBody: unknown): string | undefined;
55
60
  /**
56
61
  * An OpenAI-wire `insufficient_quota` error: the account is out of credit or
57
62
  * has hit a spend cap.
@@ -105,6 +105,23 @@ export function extractRetryAfterMsFromError(error) {
105
105
  }
106
106
  return undefined;
107
107
  }
108
+ /**
109
+ * OpenAI's chat transport keeps the type inside the JSON response body,
110
+ * rather than stamping it onto the raw error.
111
+ */
112
+ export function readOpenAIBodyErrorType(responseBody) {
113
+ if (typeof responseBody !== "string") {
114
+ return undefined;
115
+ }
116
+ try {
117
+ const parsed = JSON.parse(responseBody);
118
+ const inner = parsed?.error;
119
+ return typeof inner?.type === "string" ? inner.type : undefined;
120
+ }
121
+ catch {
122
+ return undefined;
123
+ }
124
+ }
108
125
  /**
109
126
  * An OpenAI-wire `insufficient_quota` error: the account is out of credit or
110
127
  * has hit a spend cap.
@@ -130,18 +147,7 @@ export function isOpenAIQuotaExhaustedError(error) {
130
147
  if (err.type === "insufficient_quota") {
131
148
  return true;
132
149
  }
133
- // The SDK keeps the raw payload as a string; the type lives inside it.
134
- if (typeof err.responseBody === "string") {
135
- try {
136
- const parsed = JSON.parse(err.responseBody);
137
- const inner = parsed?.error;
138
- return inner?.type === "insufficient_quota";
139
- }
140
- catch {
141
- return false;
142
- }
143
- }
144
- return false;
150
+ return readOpenAIBodyErrorType(err.responseBody) === "insufficient_quota";
145
151
  }
146
152
  /**
147
153
  * True when `error` is an abort, or wraps one at any depth via `cause`.
@@ -25,6 +25,7 @@ export { AUDIO_SIGNATURES, calculateDuration, convertAudioFormat, createPcmBuffe
25
25
  export { asyncIterableToStream, ChunkedAudioStream, StreamHandler, StreamMerger, StreamSplitter, streamToAsyncIterable, } from "./stream-handler.js";
26
26
  export { GoogleTTSHandler } from "../adapters/tts/googleTTSHandler.js";
27
27
  export { AzureTTS, AzureTTS as AzureTTSHandler } from "./providers/AzureTTS.js";
28
+ export { SixtyDBTTS, SixtyDBTTS as SixtyDBTTSHandler, } from "./providers/SixtyDBTTS.js";
28
29
  export { CartesiaTTS, CartesiaTTS as CartesiaTTSHandler, } from "./providers/CartesiaTTS.js";
29
30
  export { ElevenLabsTTS, ElevenLabsTTS as ElevenLabsTTSHandler, } from "./providers/ElevenLabsTTS.js";
30
31
  export { FishAudioTTS, FishAudioTTS as FishAudioTTSHandler, } from "./providers/FishAudioTTS.js";
@@ -51,6 +51,7 @@ export { asyncIterableToStream, ChunkedAudioStream, StreamHandler, StreamMerger,
51
51
  // ============================================================================
52
52
  export { GoogleTTSHandler } from "../adapters/tts/googleTTSHandler.js";
53
53
  export { AzureTTS, AzureTTS as AzureTTSHandler } from "./providers/AzureTTS.js";
54
+ export { SixtyDBTTS, SixtyDBTTS as SixtyDBTTSHandler, } from "./providers/SixtyDBTTS.js";
54
55
  export { CartesiaTTS, CartesiaTTS as CartesiaTTSHandler, } from "./providers/CartesiaTTS.js";
55
56
  export { ElevenLabsTTS, ElevenLabsTTS as ElevenLabsTTSHandler, } from "./providers/ElevenLabsTTS.js";
56
57
  export { FishAudioTTS, FishAudioTTS as FishAudioTTSHandler, } from "./providers/FishAudioTTS.js";
@@ -74,6 +75,7 @@ import { AzureTTS } from "./providers/AzureTTS.js";
74
75
  import { CartesiaTTS } from "./providers/CartesiaTTS.js";
75
76
  import { ElevenLabsTTS } from "./providers/ElevenLabsTTS.js";
76
77
  import { FishAudioTTS } from "./providers/FishAudioTTS.js";
78
+ import { SixtyDBTTS } from "./providers/SixtyDBTTS.js";
77
79
  import { OpenAITTS } from "./providers/OpenAITTS.js";
78
80
  import { AzureSTT } from "./providers/AzureSTT.js";
79
81
  import { DeepgramSTT } from "./providers/DeepgramSTT.js";
@@ -90,6 +92,7 @@ const TTS_HANDLER_FACTORIES = {
90
92
  elevenlabs: () => new ElevenLabsTTS(),
91
93
  "azure-tts": () => new AzureTTS(),
92
94
  "fish-audio": () => new FishAudioTTS(),
95
+ sixtydb: () => new SixtyDBTTS(),
93
96
  cartesia: () => new CartesiaTTS(),
94
97
  };
95
98
  const TTS_HANDLER_CANDIDATES = MEDIA_HANDLER_CATALOG.filter((entry) => entry.kind === "tts").map((entry) => {
@@ -0,0 +1,23 @@
1
+ import type { TTSHandler, TTSOptions, TTSResult, TTSVoice } from "../../types/index.js";
2
+ /** Buffered synthesis; NeuroLink's processor handles sentence streaming. */
3
+ export declare class SixtyDBTTS implements TTSHandler {
4
+ readonly maxTextLength = 5000;
5
+ private readonly apiKey;
6
+ private voices?;
7
+ private readonly baseUrl;
8
+ /**
9
+ * Explicit endpoint supports deployments using their own API proxy.
10
+ * HTTP is accepted only for a loopback host (offline tests dial
11
+ * http://127.0.0.1:<port>); every other override must be HTTPS, since
12
+ * requests carry `Authorization: Bearer <key>` (CodeRabbit finding on
13
+ * the PR this was taken over from, applied here). The loopback list
14
+ * matches proxyReplay.ts's own HTTPS-except-loopback check, including
15
+ * "localhost" for a deployer's local proxy, not just the IP forms.
16
+ */
17
+ constructor(apiKey?: string, baseUrl?: string);
18
+ isConfigured(): boolean;
19
+ private requireKey;
20
+ private request;
21
+ synthesize(text: string, options?: TTSOptions): Promise<TTSResult>;
22
+ getVoices(languageCode?: string): Promise<TTSVoice[]>;
23
+ }