@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.
- package/CHANGELOG.md +2 -2
- package/README.md +1 -1
- package/dist/browser/neurolink.min.js +405 -405
- package/dist/factories/mediaHandlerCatalog.js +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/providers/anthropic/client.js +6 -1
- package/dist/providers/catalog/huggingface.json +2 -1
- package/dist/providers/catalog/loader.js +5 -1
- package/dist/providers/catalog/schema.d.ts +1 -0
- package/dist/providers/catalog/schema.js +8 -0
- package/dist/providers/ideogram.js +9 -30
- package/dist/providers/llamaCpp.js +2 -1
- package/dist/providers/openAI/client.js +4 -5
- package/dist/providers/openaiChatCompletionsBase.js +12 -7
- package/dist/providers/openaiChatCompletionsClient.js +25 -7
- package/dist/providers/recraft.js +9 -28
- package/dist/types/providerCatalog.d.ts +2 -0
- package/dist/types/tts.d.ts +1 -1
- package/dist/types/voice.d.ts +1 -1
- package/dist/utils/providerRetry.d.ts +5 -0
- package/dist/utils/providerRetry.js +18 -12
- package/dist/voice/index.d.ts +1 -0
- package/dist/voice/index.js +3 -0
- package/dist/voice/providers/SixtyDBTTS.d.ts +23 -0
- package/dist/voice/providers/SixtyDBTTS.js +390 -0
- package/docs-site/static/search-index.json +542 -532
- package/package.json +2 -1
|
@@ -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
|
-
|
|
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|
|
|
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 &&
|
|
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
|
|
9
|
-
import {
|
|
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
|
-
//
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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) =>
|
|
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
|
-
//
|
|
95
|
-
//
|
|
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
|
-
:
|
|
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 (
|
|
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
|
-
//
|
|
1825
|
-
//
|
|
1826
|
-
//
|
|
1827
|
-
//
|
|
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
|
|
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 = [
|
|
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
|
|
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,
|
|
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 =
|
|
580
|
+
const schema = isStrictLegal(original)
|
|
563
581
|
? original
|
|
564
|
-
:
|
|
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:
|
|
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
|
|
9
|
-
import {
|
|
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
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
};
|
package/dist/types/tts.d.ts
CHANGED
|
@@ -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
|
*/
|
package/dist/types/voice.d.ts
CHANGED
|
@@ -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
|
-
|
|
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`.
|
package/dist/voice/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/voice/index.js
CHANGED
|
@@ -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
|
+
}
|