@juspay/neurolink 9.64.0 → 9.65.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 +12 -0
- package/README.md +18 -17
- package/dist/adapters/providerImageAdapter.js +29 -1
- package/dist/adapters/replicate/auth.d.ts +19 -0
- package/dist/adapters/replicate/auth.js +32 -0
- package/dist/adapters/replicate/predictionLifecycle.d.ts +46 -0
- package/dist/adapters/replicate/predictionLifecycle.js +283 -0
- package/dist/adapters/video/klingVideoHandler.d.ts +37 -0
- package/dist/adapters/video/klingVideoHandler.js +305 -0
- package/dist/adapters/video/replicateVideoHandler.d.ts +29 -0
- package/dist/adapters/video/replicateVideoHandler.js +157 -0
- package/dist/adapters/video/runwayVideoHandler.d.ts +32 -0
- package/dist/adapters/video/runwayVideoHandler.js +316 -0
- package/dist/adapters/video/vertexVideoHandler.d.ts +19 -1
- package/dist/adapters/video/vertexVideoHandler.js +33 -9
- package/dist/agent/directTools.js +11 -3
- package/dist/autoresearch/runner.js +8 -2
- package/dist/avatar/index.d.ts +13 -0
- package/dist/avatar/index.js +13 -0
- package/dist/avatar/providers/DIDAvatar.d.ts +49 -0
- package/dist/avatar/providers/DIDAvatar.js +501 -0
- package/dist/avatar/providers/HeyGenAvatar.d.ts +30 -0
- package/dist/avatar/providers/HeyGenAvatar.js +337 -0
- package/dist/avatar/providers/ReplicateAvatar.d.ts +36 -0
- package/dist/avatar/providers/ReplicateAvatar.js +267 -0
- package/dist/browser/neurolink.min.js +624 -601
- package/dist/cli/commands/mcp.js +29 -0
- package/dist/cli/commands/proxy.js +24 -5
- package/dist/cli/factories/commandFactory.d.ts +11 -1
- package/dist/cli/factories/commandFactory.js +291 -38
- package/dist/constants/contextWindows.js +101 -0
- package/dist/constants/enums.d.ts +273 -2
- package/dist/constants/enums.js +290 -1
- package/dist/constants/videoErrors.d.ts +4 -0
- package/dist/constants/videoErrors.js +4 -0
- package/dist/core/baseProvider.d.ts +22 -2
- package/dist/core/baseProvider.js +217 -11
- package/dist/core/constants.d.ts +12 -0
- package/dist/core/constants.js +72 -1
- package/dist/evaluation/index.d.ts +2 -0
- package/dist/evaluation/index.js +4 -0
- package/dist/factories/providerFactory.js +7 -1
- package/dist/factories/providerRegistry.js +202 -5
- package/dist/features/ppt/contentPlanner.js +42 -14
- package/dist/index.d.ts +9 -1
- package/dist/index.js +16 -1
- package/dist/lib/adapters/providerImageAdapter.js +29 -1
- package/dist/lib/adapters/replicate/auth.d.ts +19 -0
- package/dist/lib/adapters/replicate/auth.js +33 -0
- package/dist/lib/adapters/replicate/predictionLifecycle.d.ts +46 -0
- package/dist/lib/adapters/replicate/predictionLifecycle.js +284 -0
- package/dist/lib/adapters/video/klingVideoHandler.d.ts +37 -0
- package/dist/lib/adapters/video/klingVideoHandler.js +306 -0
- package/dist/lib/adapters/video/replicateVideoHandler.d.ts +29 -0
- package/dist/lib/adapters/video/replicateVideoHandler.js +158 -0
- package/dist/lib/adapters/video/runwayVideoHandler.d.ts +32 -0
- package/dist/lib/adapters/video/runwayVideoHandler.js +317 -0
- package/dist/lib/adapters/video/vertexVideoHandler.d.ts +19 -1
- package/dist/lib/adapters/video/vertexVideoHandler.js +33 -9
- package/dist/lib/agent/directTools.js +11 -3
- package/dist/lib/autoresearch/runner.js +8 -2
- package/dist/lib/avatar/index.d.ts +13 -0
- package/dist/lib/avatar/index.js +14 -0
- package/dist/lib/avatar/providers/DIDAvatar.d.ts +49 -0
- package/dist/lib/avatar/providers/DIDAvatar.js +502 -0
- package/dist/lib/avatar/providers/HeyGenAvatar.d.ts +30 -0
- package/dist/lib/avatar/providers/HeyGenAvatar.js +338 -0
- package/dist/lib/avatar/providers/ReplicateAvatar.d.ts +36 -0
- package/dist/lib/avatar/providers/ReplicateAvatar.js +268 -0
- package/dist/lib/constants/contextWindows.js +101 -0
- package/dist/lib/constants/enums.d.ts +273 -2
- package/dist/lib/constants/enums.js +290 -1
- package/dist/lib/constants/videoErrors.d.ts +4 -0
- package/dist/lib/constants/videoErrors.js +4 -0
- package/dist/lib/core/baseProvider.d.ts +22 -2
- package/dist/lib/core/baseProvider.js +217 -11
- package/dist/lib/core/constants.d.ts +12 -0
- package/dist/lib/core/constants.js +72 -1
- package/dist/lib/evaluation/index.d.ts +2 -0
- package/dist/lib/evaluation/index.js +4 -0
- package/dist/lib/factories/providerFactory.js +7 -1
- package/dist/lib/factories/providerRegistry.js +202 -5
- package/dist/lib/features/ppt/contentPlanner.js +42 -14
- package/dist/lib/index.d.ts +9 -1
- package/dist/lib/index.js +16 -1
- package/dist/lib/middleware/builtin/lifecycle.js +39 -9
- package/dist/lib/music/index.d.ts +13 -0
- package/dist/lib/music/index.js +14 -0
- package/dist/lib/music/providers/BeatovenMusic.d.ts +31 -0
- package/dist/lib/music/providers/BeatovenMusic.js +334 -0
- package/dist/lib/music/providers/ElevenLabsMusic.d.ts +30 -0
- package/dist/lib/music/providers/ElevenLabsMusic.js +169 -0
- package/dist/lib/music/providers/LyriaMusic.d.ts +29 -0
- package/dist/lib/music/providers/LyriaMusic.js +173 -0
- package/dist/lib/music/providers/ReplicateMusic.d.ts +31 -0
- package/dist/lib/music/providers/ReplicateMusic.js +262 -0
- package/dist/lib/neurolink.d.ts +30 -0
- package/dist/lib/neurolink.js +323 -77
- package/dist/lib/providers/amazonBedrock.d.ts +10 -0
- package/dist/lib/providers/amazonBedrock.js +94 -39
- package/dist/lib/providers/anthropic.js +55 -7
- package/dist/lib/providers/anthropicBaseProvider.js +1 -1
- package/dist/lib/providers/azureOpenai.js +66 -17
- package/dist/lib/providers/cloudflare.d.ts +35 -0
- package/dist/lib/providers/cloudflare.js +174 -0
- package/dist/lib/providers/cohere.d.ts +52 -0
- package/dist/lib/providers/cohere.js +253 -0
- package/dist/lib/providers/deepseek.js +72 -17
- package/dist/lib/providers/fireworks.d.ts +33 -0
- package/dist/lib/providers/fireworks.js +164 -0
- package/dist/lib/providers/googleAiStudio.js +126 -10
- package/dist/lib/providers/googleNativeGemini3.d.ts +26 -6
- package/dist/lib/providers/googleNativeGemini3.js +276 -29
- package/dist/lib/providers/googleVertex.js +639 -181
- package/dist/lib/providers/groq.d.ts +33 -0
- package/dist/lib/providers/groq.js +181 -0
- package/dist/lib/providers/huggingFace.js +9 -8
- package/dist/lib/providers/ideogram.d.ts +34 -0
- package/dist/lib/providers/ideogram.js +184 -0
- package/dist/lib/providers/index.d.ts +13 -0
- package/dist/lib/providers/index.js +13 -0
- package/dist/lib/providers/jina.d.ts +59 -0
- package/dist/lib/providers/jina.js +218 -0
- package/dist/lib/providers/llamaCpp.js +14 -46
- package/dist/lib/providers/lmStudio.js +14 -47
- package/dist/lib/providers/mistral.js +7 -7
- package/dist/lib/providers/nvidiaNim.js +160 -19
- package/dist/lib/providers/ollama.js +7 -7
- package/dist/lib/providers/openAI.d.ts +22 -1
- package/dist/lib/providers/openAI.js +181 -0
- package/dist/lib/providers/openRouter.js +35 -23
- package/dist/lib/providers/openaiCompatible.js +9 -8
- package/dist/lib/providers/perplexity.d.ts +33 -0
- package/dist/lib/providers/perplexity.js +179 -0
- package/dist/lib/providers/recraft.d.ts +34 -0
- package/dist/lib/providers/recraft.js +197 -0
- package/dist/lib/providers/replicate.d.ts +75 -0
- package/dist/lib/providers/replicate.js +403 -0
- package/dist/lib/providers/stability.d.ts +37 -0
- package/dist/lib/providers/stability.js +191 -0
- package/dist/lib/providers/togetherAi.d.ts +33 -0
- package/dist/lib/providers/togetherAi.js +176 -0
- package/dist/lib/providers/voyage.d.ts +47 -0
- package/dist/lib/providers/voyage.js +177 -0
- package/dist/lib/providers/xai.d.ts +33 -0
- package/dist/lib/providers/xai.js +172 -0
- package/dist/lib/telemetry/index.d.ts +1 -1
- package/dist/lib/telemetry/index.js +1 -1
- package/dist/lib/telemetry/tracers.d.ts +19 -0
- package/dist/lib/telemetry/tracers.js +19 -0
- package/dist/lib/telemetry/withSpan.d.ts +35 -0
- package/dist/lib/telemetry/withSpan.js +103 -0
- package/dist/lib/types/avatar.d.ts +143 -0
- package/dist/lib/types/avatar.js +20 -0
- package/dist/lib/types/cli.d.ts +6 -0
- package/dist/lib/types/conversation.d.ts +16 -0
- package/dist/lib/types/generate.d.ts +62 -5
- package/dist/lib/types/index.d.ts +5 -0
- package/dist/lib/types/index.js +7 -0
- package/dist/lib/types/middleware.d.ts +27 -0
- package/dist/lib/types/multimodal.d.ts +35 -2
- package/dist/lib/types/music.d.ts +165 -0
- package/dist/lib/types/music.js +21 -0
- package/dist/lib/types/providers.d.ts +144 -1
- package/dist/lib/types/replicate.d.ts +67 -0
- package/dist/lib/types/replicate.js +10 -0
- package/dist/lib/types/safeFetch.d.ts +15 -0
- package/dist/lib/types/safeFetch.js +7 -0
- package/dist/lib/types/stream.d.ts +2 -1
- package/dist/lib/types/tools.d.ts +13 -0
- package/dist/lib/types/video.d.ts +89 -0
- package/dist/lib/types/video.js +15 -0
- package/dist/lib/utils/avatarProcessor.d.ts +68 -0
- package/dist/lib/utils/avatarProcessor.js +172 -0
- package/dist/lib/utils/cloneOptions.d.ts +36 -0
- package/dist/lib/utils/cloneOptions.js +62 -0
- package/dist/lib/utils/lifecycleCallbacks.d.ts +51 -8
- package/dist/lib/utils/lifecycleCallbacks.js +82 -26
- package/dist/lib/utils/lifecycleTimeout.d.ts +25 -0
- package/dist/lib/utils/lifecycleTimeout.js +39 -0
- package/dist/lib/utils/logSanitize.d.ts +49 -0
- package/dist/lib/utils/logSanitize.js +170 -0
- package/dist/lib/utils/loggingFetch.d.ts +29 -0
- package/dist/lib/utils/loggingFetch.js +60 -0
- package/dist/lib/utils/messageBuilder.js +43 -25
- package/dist/lib/utils/modelChoices.js +236 -3
- package/dist/lib/utils/musicProcessor.d.ts +67 -0
- package/dist/lib/utils/musicProcessor.js +189 -0
- package/dist/lib/utils/optionsConversion.js +3 -2
- package/dist/lib/utils/parameterValidation.js +14 -4
- package/dist/lib/utils/pricing.js +193 -0
- package/dist/lib/utils/providerConfig.d.ts +55 -0
- package/dist/lib/utils/providerConfig.js +224 -0
- package/dist/lib/utils/safeFetch.d.ts +26 -0
- package/dist/lib/utils/safeFetch.js +83 -0
- package/dist/lib/utils/sizeGuard.d.ts +34 -0
- package/dist/lib/utils/sizeGuard.js +45 -0
- package/dist/lib/utils/ssrfGuard.d.ts +52 -0
- package/dist/lib/utils/ssrfGuard.js +411 -0
- package/dist/lib/utils/videoProcessor.d.ts +60 -0
- package/dist/lib/utils/videoProcessor.js +201 -0
- package/dist/lib/voice/providers/FishAudioTTS.d.ts +27 -0
- package/dist/lib/voice/providers/FishAudioTTS.js +183 -0
- package/dist/lib/workflow/core/ensembleExecutor.js +26 -9
- package/dist/middleware/builtin/lifecycle.js +39 -9
- package/dist/music/index.d.ts +13 -0
- package/dist/music/index.js +13 -0
- package/dist/music/providers/BeatovenMusic.d.ts +31 -0
- package/dist/music/providers/BeatovenMusic.js +333 -0
- package/dist/music/providers/ElevenLabsMusic.d.ts +30 -0
- package/dist/music/providers/ElevenLabsMusic.js +168 -0
- package/dist/music/providers/LyriaMusic.d.ts +29 -0
- package/dist/music/providers/LyriaMusic.js +172 -0
- package/dist/music/providers/ReplicateMusic.d.ts +31 -0
- package/dist/music/providers/ReplicateMusic.js +261 -0
- package/dist/neurolink.d.ts +30 -0
- package/dist/neurolink.js +323 -77
- package/dist/providers/amazonBedrock.d.ts +10 -0
- package/dist/providers/amazonBedrock.js +94 -39
- package/dist/providers/anthropic.js +55 -7
- package/dist/providers/anthropicBaseProvider.js +1 -1
- package/dist/providers/azureOpenai.js +66 -17
- package/dist/providers/cloudflare.d.ts +35 -0
- package/dist/providers/cloudflare.js +173 -0
- package/dist/providers/cohere.d.ts +52 -0
- package/dist/providers/cohere.js +252 -0
- package/dist/providers/deepseek.js +72 -17
- package/dist/providers/fireworks.d.ts +33 -0
- package/dist/providers/fireworks.js +163 -0
- package/dist/providers/googleAiStudio.js +126 -10
- package/dist/providers/googleNativeGemini3.d.ts +26 -6
- package/dist/providers/googleNativeGemini3.js +276 -29
- package/dist/providers/googleVertex.js +639 -181
- package/dist/providers/groq.d.ts +33 -0
- package/dist/providers/groq.js +180 -0
- package/dist/providers/huggingFace.js +9 -8
- package/dist/providers/ideogram.d.ts +34 -0
- package/dist/providers/ideogram.js +183 -0
- package/dist/providers/index.d.ts +13 -0
- package/dist/providers/index.js +13 -0
- package/dist/providers/jina.d.ts +59 -0
- package/dist/providers/jina.js +217 -0
- package/dist/providers/llamaCpp.js +14 -46
- package/dist/providers/lmStudio.js +14 -47
- package/dist/providers/mistral.js +7 -7
- package/dist/providers/nvidiaNim.js +160 -19
- package/dist/providers/ollama.js +7 -7
- package/dist/providers/openAI.d.ts +22 -1
- package/dist/providers/openAI.js +181 -0
- package/dist/providers/openRouter.js +35 -23
- package/dist/providers/openaiCompatible.js +9 -8
- package/dist/providers/perplexity.d.ts +33 -0
- package/dist/providers/perplexity.js +178 -0
- package/dist/providers/recraft.d.ts +34 -0
- package/dist/providers/recraft.js +196 -0
- package/dist/providers/replicate.d.ts +75 -0
- package/dist/providers/replicate.js +402 -0
- package/dist/providers/stability.d.ts +37 -0
- package/dist/providers/stability.js +190 -0
- package/dist/providers/togetherAi.d.ts +33 -0
- package/dist/providers/togetherAi.js +175 -0
- package/dist/providers/voyage.d.ts +47 -0
- package/dist/providers/voyage.js +176 -0
- package/dist/providers/xai.d.ts +33 -0
- package/dist/providers/xai.js +171 -0
- package/dist/telemetry/index.d.ts +1 -1
- package/dist/telemetry/index.js +1 -1
- package/dist/telemetry/tracers.d.ts +19 -0
- package/dist/telemetry/tracers.js +19 -0
- package/dist/telemetry/withSpan.d.ts +35 -0
- package/dist/telemetry/withSpan.js +103 -0
- package/dist/types/avatar.d.ts +143 -0
- package/dist/types/avatar.js +19 -0
- package/dist/types/cli.d.ts +6 -0
- package/dist/types/conversation.d.ts +16 -0
- package/dist/types/generate.d.ts +62 -5
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +7 -0
- package/dist/types/middleware.d.ts +27 -0
- package/dist/types/multimodal.d.ts +35 -2
- package/dist/types/music.d.ts +165 -0
- package/dist/types/music.js +20 -0
- package/dist/types/providers.d.ts +144 -1
- package/dist/types/replicate.d.ts +67 -0
- package/dist/types/replicate.js +9 -0
- package/dist/types/safeFetch.d.ts +15 -0
- package/dist/types/safeFetch.js +6 -0
- package/dist/types/stream.d.ts +2 -1
- package/dist/types/tools.d.ts +13 -0
- package/dist/types/video.d.ts +89 -0
- package/dist/types/video.js +14 -0
- package/dist/utils/avatarProcessor.d.ts +68 -0
- package/dist/utils/avatarProcessor.js +171 -0
- package/dist/utils/cloneOptions.d.ts +36 -0
- package/dist/utils/cloneOptions.js +61 -0
- package/dist/utils/lifecycleCallbacks.d.ts +51 -8
- package/dist/utils/lifecycleCallbacks.js +82 -26
- package/dist/utils/lifecycleTimeout.d.ts +25 -0
- package/dist/utils/lifecycleTimeout.js +38 -0
- package/dist/utils/logSanitize.d.ts +49 -0
- package/dist/utils/logSanitize.js +169 -0
- package/dist/utils/loggingFetch.d.ts +29 -0
- package/dist/utils/loggingFetch.js +59 -0
- package/dist/utils/messageBuilder.js +43 -25
- package/dist/utils/modelChoices.js +236 -3
- package/dist/utils/musicProcessor.d.ts +67 -0
- package/dist/utils/musicProcessor.js +188 -0
- package/dist/utils/optionsConversion.js +3 -2
- package/dist/utils/parameterValidation.js +14 -4
- package/dist/utils/pricing.js +193 -0
- package/dist/utils/providerConfig.d.ts +55 -0
- package/dist/utils/providerConfig.js +224 -0
- package/dist/utils/safeFetch.d.ts +26 -0
- package/dist/utils/safeFetch.js +82 -0
- package/dist/utils/sizeGuard.d.ts +34 -0
- package/dist/utils/sizeGuard.js +44 -0
- package/dist/utils/ssrfGuard.d.ts +52 -0
- package/dist/utils/ssrfGuard.js +410 -0
- package/dist/utils/videoProcessor.d.ts +60 -0
- package/dist/utils/videoProcessor.js +200 -0
- package/dist/voice/providers/FishAudioTTS.d.ts +27 -0
- package/dist/voice/providers/FishAudioTTS.js +182 -0
- package/dist/workflow/core/ensembleExecutor.js +26 -9
- package/package.json +32 -5
|
@@ -5,6 +5,7 @@ import { BaseProvider } from "../core/baseProvider.js";
|
|
|
5
5
|
import { DEFAULT_MAX_STEPS } from "../core/constants.js";
|
|
6
6
|
import { streamAnalyticsCollector } from "../core/streamAnalytics.js";
|
|
7
7
|
import { createProxyFetch } from "../proxy/proxyFetch.js";
|
|
8
|
+
import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../types/index.js";
|
|
8
9
|
import { isAbortError } from "../utils/errorHandling.js";
|
|
9
10
|
import { emitToolEndFromStepFinish } from "../utils/toolEndEmitter.js";
|
|
10
11
|
import { logger } from "../utils/logger.js";
|
|
@@ -37,11 +38,15 @@ const getOpenRouterConfig = () => {
|
|
|
37
38
|
* - 'google/gemini-2.5-flash'
|
|
38
39
|
* - 'meta-llama/llama-3-70b-instruct'
|
|
39
40
|
*
|
|
40
|
-
*
|
|
41
|
+
* The previous default `anthropic/claude-3-5-sonnet` was retired by OpenRouter
|
|
42
|
+
* in late 2025 and now returns "No endpoints found for model" for every
|
|
43
|
+
* caller. Default bumped to the current Anthropic mainline (Claude Sonnet
|
|
44
|
+
* 4.5) so callers without an `OPENROUTER_MODEL` env var don't hit a dead
|
|
45
|
+
* model. Must stay aligned with the registry default in
|
|
46
|
+
* `src/lib/factories/providerRegistry.ts` and `PROVIDER_DEFAULTS` in
|
|
47
|
+
* `src/lib/utils/modelChoices.ts`.
|
|
41
48
|
*
|
|
42
|
-
*
|
|
43
|
-
* because OpenRouter sunset the Claude 3.5 Sonnet endpoint upstream — every
|
|
44
|
-
* call against the old default returned `No endpoints found` 404s.
|
|
49
|
+
* You can override the default by setting the OPENROUTER_MODEL environment variable.
|
|
45
50
|
*/
|
|
46
51
|
const getDefaultOpenRouterModel = () => {
|
|
47
52
|
return getProviderModel("OPENROUTER_MODEL", "anthropic/claude-sonnet-4.5");
|
|
@@ -111,62 +116,62 @@ export class OpenRouterProvider extends BaseProvider {
|
|
|
111
116
|
}
|
|
112
117
|
formatProviderError(error) {
|
|
113
118
|
if (error instanceof TimeoutError) {
|
|
114
|
-
return new
|
|
119
|
+
return new NetworkError(`Request timed out: ${error.message}`, "openrouter");
|
|
115
120
|
}
|
|
116
121
|
// Check for timeout by error name and message as fallback
|
|
117
122
|
const errorRecord = error;
|
|
118
123
|
if (errorRecord?.name === "TimeoutError" ||
|
|
119
124
|
(typeof errorRecord?.message === "string" &&
|
|
120
125
|
errorRecord.message.includes("Timeout"))) {
|
|
121
|
-
return new
|
|
126
|
+
return new NetworkError(`Request timed out: ${errorRecord?.message || "Unknown timeout"}`, "openrouter");
|
|
122
127
|
}
|
|
123
128
|
if (typeof errorRecord?.message === "string") {
|
|
124
129
|
if (errorRecord.message.includes("ECONNREFUSED") ||
|
|
125
130
|
errorRecord.message.includes("Failed to fetch")) {
|
|
126
|
-
return new
|
|
131
|
+
return new NetworkError("OpenRouter API not available. Please check your network connection and try again.", "openrouter");
|
|
127
132
|
}
|
|
128
133
|
if (errorRecord.message.includes("API_KEY_INVALID") ||
|
|
129
134
|
errorRecord.message.includes("Invalid API key") ||
|
|
130
135
|
errorRecord.message.includes("invalid_api_key") ||
|
|
131
136
|
errorRecord.message.includes("Unauthorized")) {
|
|
132
|
-
return new
|
|
133
|
-
"Get your key at https://openrouter.ai/keys");
|
|
137
|
+
return new AuthenticationError("Invalid OpenRouter API key. Please check your OPENROUTER_API_KEY environment variable. " +
|
|
138
|
+
"Get your key at https://openrouter.ai/keys", "openrouter");
|
|
134
139
|
}
|
|
135
140
|
if (errorRecord.message.includes("rate limit")) {
|
|
136
|
-
return new
|
|
141
|
+
return new RateLimitError("OpenRouter rate limit exceeded. Please try again later or upgrade your account at https://openrouter.ai/credits", "openrouter");
|
|
137
142
|
}
|
|
138
143
|
if (errorRecord.message.includes("model") &&
|
|
139
144
|
errorRecord.message.includes("not found")) {
|
|
140
|
-
return new
|
|
141
|
-
"Browse available models at https://openrouter.ai/models");
|
|
145
|
+
return new InvalidModelError(`Model '${this.modelName}' not available on OpenRouter. ` +
|
|
146
|
+
"Browse available models at https://openrouter.ai/models", "openrouter");
|
|
142
147
|
}
|
|
143
148
|
if (errorRecord.message.includes("insufficient_credits")) {
|
|
144
|
-
return new
|
|
149
|
+
return new ProviderError("Insufficient OpenRouter credits. Add credits at https://openrouter.ai/credits", "openrouter");
|
|
145
150
|
}
|
|
146
151
|
// "No endpoints found" — model temporarily unavailable or unsupported parameters
|
|
147
152
|
// This is distinct from tool errors: it can happen on any request when the
|
|
148
153
|
// model has no available providers on OpenRouter (e.g., free-tier model down).
|
|
149
154
|
if (errorRecord.message.includes("No endpoints found")) {
|
|
150
|
-
return new
|
|
155
|
+
return new InvalidModelError(`No endpoints found for model '${this.modelName}' on OpenRouter. ` +
|
|
151
156
|
"The model may be temporarily unavailable or does not support the requested parameters. " +
|
|
152
|
-
"Try a different model or check availability at https://openrouter.ai/models");
|
|
157
|
+
"Try a different model or check availability at https://openrouter.ai/models", "openrouter");
|
|
153
158
|
}
|
|
154
159
|
// Tool/function calling errors
|
|
155
160
|
if (errorRecord.message.includes("tool use") ||
|
|
156
161
|
errorRecord.message.includes("tool_use") ||
|
|
157
162
|
errorRecord.message.includes("function_call") ||
|
|
158
163
|
errorRecord.message.includes("tools are not supported")) {
|
|
159
|
-
return new
|
|
164
|
+
return new ProviderError(`Model '${this.modelName}' does not support tool calling. ` +
|
|
160
165
|
"Use a tool-capable model like:\n" +
|
|
161
166
|
" • google/gemini-2.0-flash-exp:free (free)\n" +
|
|
162
167
|
" • meta-llama/llama-3.3-70b-instruct:free (free)\n" +
|
|
163
|
-
" • anthropic/claude-3-
|
|
168
|
+
" • anthropic/claude-3.7-sonnet (paid)\n" +
|
|
164
169
|
" • openai/gpt-4o (paid)\n" +
|
|
165
170
|
"Or use --disableTools flag. " +
|
|
166
|
-
"See all tool-capable models at https://openrouter.ai/models?supported_parameters=tools");
|
|
171
|
+
"See all tool-capable models at https://openrouter.ai/models?supported_parameters=tools", "openrouter");
|
|
167
172
|
}
|
|
168
173
|
}
|
|
169
|
-
return new
|
|
174
|
+
return new ProviderError(`OpenRouter error: ${errorRecord?.message || "Unknown error"}`, "openrouter");
|
|
170
175
|
}
|
|
171
176
|
/**
|
|
172
177
|
* OpenRouter supports tools for compatible models
|
|
@@ -211,7 +216,7 @@ export class OpenRouterProvider extends BaseProvider {
|
|
|
211
216
|
// For unknown models, warn and disable tools (safe default)
|
|
212
217
|
logger.warn("OpenRouter: Unknown model tool capability, disabling tools", {
|
|
213
218
|
model: modelName,
|
|
214
|
-
suggestion: "Use a known tool-capable model like anthropic/claude-3-
|
|
219
|
+
suggestion: "Use a known tool-capable model like anthropic/claude-3.7-sonnet, openai/gpt-4o, or google/gemini-2.0-flash-exp:free",
|
|
215
220
|
});
|
|
216
221
|
return false;
|
|
217
222
|
}
|
|
@@ -255,7 +260,12 @@ export class OpenRouterProvider extends BaseProvider {
|
|
|
255
260
|
messages: messages,
|
|
256
261
|
temperature: options.temperature,
|
|
257
262
|
maxRetries: 0, // Disable SDK retries - let caller handle rate limit retries with delays
|
|
258
|
-
|
|
263
|
+
// AI SDK v6 renamed `maxTokens` to `maxOutputTokens` — using the old
|
|
264
|
+
// name here is a silent no-op, so OpenRouter sees no cap and applies
|
|
265
|
+
// the model's full output max (typically 64K+ tokens) to its pre-bill
|
|
266
|
+
// affordability check. That trips "This request requires more credits"
|
|
267
|
+
// even on cheap models when the account balance is low.
|
|
268
|
+
...(options.maxTokens && { maxOutputTokens: options.maxTokens }),
|
|
259
269
|
...(shouldUseTools &&
|
|
260
270
|
Object.keys(tools).length > 0 && {
|
|
261
271
|
tools,
|
|
@@ -456,10 +466,12 @@ export class OpenRouterProvider extends BaseProvider {
|
|
|
456
466
|
error: error instanceof Error ? error.message : String(error),
|
|
457
467
|
});
|
|
458
468
|
}
|
|
459
|
-
// Fallback to hardcoded list if API fetch fails
|
|
469
|
+
// Fallback to hardcoded list if API fetch fails. Aligned with
|
|
470
|
+
// `getDefaultOpenRouterModel()` — `anthropic/claude-3-5-sonnet` was
|
|
471
|
+
// retired by OpenRouter late 2025 and would return a dead model here.
|
|
460
472
|
const fallbackModels = [
|
|
461
473
|
// Anthropic Claude models
|
|
462
|
-
"anthropic/claude-3-
|
|
474
|
+
"anthropic/claude-3.7-sonnet",
|
|
463
475
|
"anthropic/claude-3-5-haiku",
|
|
464
476
|
"anthropic/claude-3-opus",
|
|
465
477
|
// OpenAI models
|
|
@@ -4,6 +4,7 @@ import { BaseProvider } from "../core/baseProvider.js";
|
|
|
4
4
|
import { DEFAULT_MAX_STEPS } from "../core/constants.js";
|
|
5
5
|
import { streamAnalyticsCollector } from "../core/streamAnalytics.js";
|
|
6
6
|
import { createProxyFetch } from "../proxy/proxyFetch.js";
|
|
7
|
+
import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../types/index.js";
|
|
7
8
|
import { emitToolEndFromStepFinish } from "../utils/toolEndEmitter.js";
|
|
8
9
|
import { logger } from "../utils/logger.js";
|
|
9
10
|
import { buildNoOutputSentinel, detectPostStreamNoOutput, stampNoOutputSpan, } from "../utils/noOutputSentinel.js";
|
|
@@ -127,36 +128,36 @@ export class OpenAICompatibleProvider extends BaseProvider {
|
|
|
127
128
|
}
|
|
128
129
|
formatProviderError(error) {
|
|
129
130
|
if (error instanceof TimeoutError) {
|
|
130
|
-
return new
|
|
131
|
+
return new NetworkError(`Request timed out: ${error.message}`, "openai-compatible");
|
|
131
132
|
}
|
|
132
133
|
// Check for timeout by error name and message as fallback
|
|
133
134
|
const errorRecord = error;
|
|
134
135
|
if (errorRecord?.name === "TimeoutError" ||
|
|
135
136
|
(typeof errorRecord?.message === "string" &&
|
|
136
137
|
errorRecord.message.includes("Timeout"))) {
|
|
137
|
-
return new
|
|
138
|
+
return new NetworkError(`Request timed out: ${errorRecord?.message || "Unknown timeout"}`, "openai-compatible");
|
|
138
139
|
}
|
|
139
140
|
if (typeof errorRecord?.message === "string") {
|
|
140
141
|
if (errorRecord.message.includes("ECONNREFUSED") ||
|
|
141
142
|
errorRecord.message.includes("Failed to fetch")) {
|
|
142
|
-
return new
|
|
143
|
+
return new NetworkError(`OpenAI Compatible endpoint not available. Please check your OPENAI_COMPATIBLE_BASE_URL: ${this.config.baseURL}`, "openai-compatible");
|
|
143
144
|
}
|
|
144
145
|
if (errorRecord.message.includes("API_KEY_INVALID") ||
|
|
145
146
|
errorRecord.message.includes("Invalid API key") ||
|
|
146
147
|
errorRecord.message.includes("Unauthorized")) {
|
|
147
|
-
return new
|
|
148
|
+
return new AuthenticationError("Invalid OpenAI Compatible API key. Please check your OPENAI_COMPATIBLE_API_KEY environment variable.", "openai-compatible");
|
|
148
149
|
}
|
|
149
150
|
if (errorRecord.message.includes("rate limit")) {
|
|
150
|
-
return new
|
|
151
|
+
return new RateLimitError("OpenAI Compatible rate limit exceeded. Please try again later.", "openai-compatible");
|
|
151
152
|
}
|
|
152
153
|
if (errorRecord.message.includes("model") &&
|
|
153
154
|
(errorRecord.message.includes("not found") ||
|
|
154
155
|
errorRecord.message.includes("does not exist"))) {
|
|
155
|
-
return new
|
|
156
|
-
"Please check available models or use getAvailableModels() to see supported models.");
|
|
156
|
+
return new InvalidModelError(`Model '${this.modelName}' not available on OpenAI Compatible endpoint. ` +
|
|
157
|
+
"Please check available models or use getAvailableModels() to see supported models.", "openai-compatible");
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
|
-
return new
|
|
160
|
+
return new ProviderError(`OpenAI Compatible error: ${errorRecord?.message || "Unknown error"}`, "openai-compatible");
|
|
160
161
|
}
|
|
161
162
|
/**
|
|
162
163
|
* OpenAI Compatible endpoints support tools for compatible models
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type LanguageModel } from "ai";
|
|
2
|
+
import type { AIProviderName } from "../constants/enums.js";
|
|
3
|
+
import { BaseProvider } from "../core/baseProvider.js";
|
|
4
|
+
import type { NeurolinkCredentials, StreamOptions, StreamResult, ValidationSchema } from "../types/index.js";
|
|
5
|
+
/**
|
|
6
|
+
* Perplexity Provider
|
|
7
|
+
*
|
|
8
|
+
* Sonar models with built-in web grounding. OpenAI-compatible chat
|
|
9
|
+
* completions at api.perplexity.ai. Best for queries that need fresh
|
|
10
|
+
* web context (search-augmented answers + citations).
|
|
11
|
+
*
|
|
12
|
+
* @see https://docs.perplexity.ai/api-reference/chat-completions
|
|
13
|
+
*/
|
|
14
|
+
export declare class PerplexityProvider extends BaseProvider {
|
|
15
|
+
private model;
|
|
16
|
+
private apiKey;
|
|
17
|
+
private baseURL;
|
|
18
|
+
constructor(modelName?: string, sdk?: unknown, _region?: string, credentials?: NeurolinkCredentials["perplexity"]);
|
|
19
|
+
protected executeStream(options: StreamOptions, _analysisSchema?: ValidationSchema): Promise<StreamResult>;
|
|
20
|
+
private executeStreamInner;
|
|
21
|
+
protected getProviderName(): AIProviderName;
|
|
22
|
+
protected getDefaultModel(): string;
|
|
23
|
+
protected getAISDKModel(): LanguageModel;
|
|
24
|
+
protected formatProviderError(error: unknown): Error;
|
|
25
|
+
validateConfiguration(): Promise<boolean>;
|
|
26
|
+
getConfiguration(): {
|
|
27
|
+
provider: AIProviderName;
|
|
28
|
+
model: string;
|
|
29
|
+
defaultModel: string;
|
|
30
|
+
baseURL: string;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export default PerplexityProvider;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
2
|
+
import { stepCountIs, streamText } from "ai";
|
|
3
|
+
import { PerplexityModels } from "../constants/enums.js";
|
|
4
|
+
import { BaseProvider } from "../core/baseProvider.js";
|
|
5
|
+
import { DEFAULT_MAX_STEPS } from "../core/constants.js";
|
|
6
|
+
import { streamAnalyticsCollector } from "../core/streamAnalytics.js";
|
|
7
|
+
import { isNeuroLink } from "../neurolink.js";
|
|
8
|
+
import { createLoggingFetch } from "../utils/loggingFetch.js";
|
|
9
|
+
import { tracers, ATTR, withClientStreamSpan } from "../telemetry/index.js";
|
|
10
|
+
import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../types/index.js";
|
|
11
|
+
import { logger } from "../utils/logger.js";
|
|
12
|
+
import { createPerplexityConfig, getProviderModel, validateApiKey, } from "../utils/providerConfig.js";
|
|
13
|
+
import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
|
|
14
|
+
import { emitToolEndFromStepFinish } from "../utils/toolEndEmitter.js";
|
|
15
|
+
import { resolveToolChoice } from "../utils/toolChoice.js";
|
|
16
|
+
import { toAnalyticsStreamResult } from "./providerTypeUtils.js";
|
|
17
|
+
const PERPLEXITY_DEFAULT_BASE_URL = "https://api.perplexity.ai";
|
|
18
|
+
const getPerplexityApiKey = () => validateApiKey(createPerplexityConfig());
|
|
19
|
+
const getDefaultPerplexityModel = () => getProviderModel("PERPLEXITY_MODEL", PerplexityModels.SONAR);
|
|
20
|
+
/**
|
|
21
|
+
* Perplexity Provider
|
|
22
|
+
*
|
|
23
|
+
* Sonar models with built-in web grounding. OpenAI-compatible chat
|
|
24
|
+
* completions at api.perplexity.ai. Best for queries that need fresh
|
|
25
|
+
* web context (search-augmented answers + citations).
|
|
26
|
+
*
|
|
27
|
+
* @see https://docs.perplexity.ai/api-reference/chat-completions
|
|
28
|
+
*/
|
|
29
|
+
export class PerplexityProvider extends BaseProvider {
|
|
30
|
+
model;
|
|
31
|
+
apiKey;
|
|
32
|
+
baseURL;
|
|
33
|
+
constructor(modelName, sdk, _region, credentials) {
|
|
34
|
+
const validatedNeurolink = isNeuroLink(sdk) ? sdk : undefined;
|
|
35
|
+
super(modelName, "perplexity", validatedNeurolink);
|
|
36
|
+
const overrideApiKey = credentials?.apiKey?.trim();
|
|
37
|
+
this.apiKey =
|
|
38
|
+
overrideApiKey && overrideApiKey.length > 0
|
|
39
|
+
? overrideApiKey
|
|
40
|
+
: getPerplexityApiKey();
|
|
41
|
+
this.baseURL =
|
|
42
|
+
credentials?.baseURL ??
|
|
43
|
+
process.env.PERPLEXITY_BASE_URL ??
|
|
44
|
+
PERPLEXITY_DEFAULT_BASE_URL;
|
|
45
|
+
const perplexity = createOpenAI({
|
|
46
|
+
apiKey: this.apiKey,
|
|
47
|
+
baseURL: this.baseURL,
|
|
48
|
+
fetch: createLoggingFetch("perplexity"),
|
|
49
|
+
});
|
|
50
|
+
this.model = perplexity.chat(this.modelName);
|
|
51
|
+
logger.debug("Perplexity Provider initialized", {
|
|
52
|
+
modelName: this.modelName,
|
|
53
|
+
providerName: this.providerName,
|
|
54
|
+
baseURL: this.baseURL,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
async executeStream(options, _analysisSchema) {
|
|
58
|
+
return withClientStreamSpan({
|
|
59
|
+
name: "neurolink.provider.stream",
|
|
60
|
+
tracer: tracers.provider,
|
|
61
|
+
attributes: {
|
|
62
|
+
[ATTR.GEN_AI_SYSTEM]: "perplexity",
|
|
63
|
+
[ATTR.GEN_AI_MODEL]: this.modelName,
|
|
64
|
+
[ATTR.GEN_AI_OPERATION]: "stream",
|
|
65
|
+
[ATTR.NL_STREAM_MODE]: true,
|
|
66
|
+
},
|
|
67
|
+
}, async () => this.executeStreamInner(options), (r) => r.stream, (r, wrapped) => ({ ...r, stream: wrapped }));
|
|
68
|
+
}
|
|
69
|
+
async executeStreamInner(options) {
|
|
70
|
+
this.validateStreamOptions(options);
|
|
71
|
+
// Resolve per-call credentials first, then fall back to instance-level.
|
|
72
|
+
const perCallCreds = options.credentials?.perplexity;
|
|
73
|
+
const effectiveApiKey = perCallCreds?.apiKey?.trim() || this.apiKey;
|
|
74
|
+
const effectiveBaseURL = perCallCreds?.baseURL || this.baseURL;
|
|
75
|
+
const startTime = Date.now();
|
|
76
|
+
const timeout = this.getTimeout(options);
|
|
77
|
+
const timeoutController = createTimeoutController(timeout, this.providerName, "stream");
|
|
78
|
+
try {
|
|
79
|
+
// Perplexity Sonar's tool support is limited; default to disabled when
|
|
80
|
+
// not explicitly requested by the caller. The web-grounding signal is
|
|
81
|
+
// baked into the model itself, not exposed as tool calls.
|
|
82
|
+
const shouldUseTools = !options.disableTools && this.supportsTools();
|
|
83
|
+
const tools = shouldUseTools
|
|
84
|
+
? options.tools || (await this.getAllTools())
|
|
85
|
+
: {};
|
|
86
|
+
const messages = await this.buildMessagesForStream(options);
|
|
87
|
+
// When per-call credentials differ from instance, build a fresh client.
|
|
88
|
+
const hasDifferentCreds = effectiveApiKey !== this.apiKey || effectiveBaseURL !== this.baseURL;
|
|
89
|
+
const model = hasDifferentCreds
|
|
90
|
+
? createOpenAI({
|
|
91
|
+
apiKey: effectiveApiKey,
|
|
92
|
+
baseURL: effectiveBaseURL,
|
|
93
|
+
fetch: createLoggingFetch("perplexity"),
|
|
94
|
+
}).chat(this.modelName)
|
|
95
|
+
: await this.getAISDKModelWithMiddleware(options);
|
|
96
|
+
const result = await streamText({
|
|
97
|
+
model,
|
|
98
|
+
messages,
|
|
99
|
+
temperature: options.temperature,
|
|
100
|
+
maxOutputTokens: options.maxTokens,
|
|
101
|
+
tools,
|
|
102
|
+
stopWhen: stepCountIs(options.maxSteps || DEFAULT_MAX_STEPS),
|
|
103
|
+
toolChoice: resolveToolChoice(options, tools, shouldUseTools),
|
|
104
|
+
abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
|
|
105
|
+
experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
|
|
106
|
+
experimental_repairToolCall: this.getToolCallRepairFn(options),
|
|
107
|
+
onStepFinish: ({ toolCalls, toolResults }) => {
|
|
108
|
+
emitToolEndFromStepFinish(this.neurolink?.getEventEmitter(), toolResults);
|
|
109
|
+
this.handleToolExecutionStorage(toolCalls, toolResults, options, new Date()).catch((error) => {
|
|
110
|
+
logger.warn("[PerplexityProvider] Failed to store tool executions", {
|
|
111
|
+
provider: this.providerName,
|
|
112
|
+
error: error instanceof Error ? error.message : String(error),
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
timeoutController?.cleanup();
|
|
118
|
+
const transformedStream = this.createTextStream(result);
|
|
119
|
+
const analyticsPromise = streamAnalyticsCollector.createAnalytics(this.providerName, this.modelName, toAnalyticsStreamResult(result), Date.now() - startTime, {
|
|
120
|
+
requestId: `perplexity-stream-${Date.now()}`,
|
|
121
|
+
streamingMode: true,
|
|
122
|
+
});
|
|
123
|
+
return {
|
|
124
|
+
stream: transformedStream,
|
|
125
|
+
provider: this.providerName,
|
|
126
|
+
model: this.modelName,
|
|
127
|
+
analytics: analyticsPromise,
|
|
128
|
+
metadata: { startTime, streamId: `perplexity-${Date.now()}` },
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
timeoutController?.cleanup();
|
|
133
|
+
throw this.handleProviderError(error);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
getProviderName() {
|
|
137
|
+
return this.providerName;
|
|
138
|
+
}
|
|
139
|
+
getDefaultModel() {
|
|
140
|
+
return getDefaultPerplexityModel();
|
|
141
|
+
}
|
|
142
|
+
getAISDKModel() {
|
|
143
|
+
return this.model;
|
|
144
|
+
}
|
|
145
|
+
formatProviderError(error) {
|
|
146
|
+
if (error instanceof TimeoutError) {
|
|
147
|
+
return new NetworkError(`Request timed out: ${error.message}`, "perplexity");
|
|
148
|
+
}
|
|
149
|
+
const errorRecord = error;
|
|
150
|
+
const message = typeof errorRecord?.message === "string"
|
|
151
|
+
? errorRecord.message
|
|
152
|
+
: "Unknown error";
|
|
153
|
+
if (message.includes("Invalid API key") ||
|
|
154
|
+
message.includes("Authentication") ||
|
|
155
|
+
message.includes("401")) {
|
|
156
|
+
return new AuthenticationError("Invalid Perplexity API key. Get one at https://www.perplexity.ai/settings/api", "perplexity");
|
|
157
|
+
}
|
|
158
|
+
if (message.includes("rate limit") || message.includes("429")) {
|
|
159
|
+
return new RateLimitError("Perplexity rate limit exceeded. Back off and retry.", "perplexity");
|
|
160
|
+
}
|
|
161
|
+
if (message.includes("model_not_found") || message.includes("404")) {
|
|
162
|
+
return new InvalidModelError(`Perplexity model '${this.modelName}' not found. Use sonar, sonar-pro, sonar-reasoning, sonar-reasoning-pro, or sonar-deep-research.`, "perplexity");
|
|
163
|
+
}
|
|
164
|
+
return new ProviderError(`Perplexity error: ${message}`, "perplexity");
|
|
165
|
+
}
|
|
166
|
+
async validateConfiguration() {
|
|
167
|
+
return typeof this.apiKey === "string" && this.apiKey.trim().length > 0;
|
|
168
|
+
}
|
|
169
|
+
getConfiguration() {
|
|
170
|
+
return {
|
|
171
|
+
provider: this.providerName,
|
|
172
|
+
model: this.modelName,
|
|
173
|
+
defaultModel: getDefaultPerplexityModel(),
|
|
174
|
+
baseURL: this.baseURL,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
export default PerplexityProvider;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { LanguageModel } from "ai";
|
|
2
|
+
import type { AIProviderName } from "../constants/enums.js";
|
|
3
|
+
import { BaseProvider } from "../core/baseProvider.js";
|
|
4
|
+
import type { EnhancedGenerateResult, NeurolinkCredentials, StreamOptions, StreamResult, TextGenerationOptions, ValidationSchema } from "../types/index.js";
|
|
5
|
+
/**
|
|
6
|
+
* Recraft Provider — image generation with vector / illustration focus.
|
|
7
|
+
*
|
|
8
|
+
* Hits external.api.recraft.ai/v1/images/generations (OpenAI-compat
|
|
9
|
+
* shape). Returns either url or b64_json; we convert URL → base64 for
|
|
10
|
+
* the uniform imageOutput contract.
|
|
11
|
+
*
|
|
12
|
+
* @see https://www.recraft.ai/docs
|
|
13
|
+
*/
|
|
14
|
+
export declare class RecraftProvider extends BaseProvider {
|
|
15
|
+
private readonly apiKey;
|
|
16
|
+
private readonly baseURL;
|
|
17
|
+
private readonly proxyFetch;
|
|
18
|
+
constructor(modelName?: string, sdk?: unknown, _region?: string, credentials?: NeurolinkCredentials["recraft"]);
|
|
19
|
+
protected getProviderName(): AIProviderName;
|
|
20
|
+
protected getDefaultModel(): string;
|
|
21
|
+
supportsTools(): boolean;
|
|
22
|
+
protected getAISDKModel(): LanguageModel;
|
|
23
|
+
protected executeStream(_options: StreamOptions, _analysisSchema?: ValidationSchema): Promise<StreamResult>;
|
|
24
|
+
protected formatProviderError(error: unknown): Error;
|
|
25
|
+
protected executeImageGeneration(options: TextGenerationOptions): Promise<EnhancedGenerateResult>;
|
|
26
|
+
validateConfiguration(): Promise<boolean>;
|
|
27
|
+
getConfiguration(): {
|
|
28
|
+
provider: AIProviderName;
|
|
29
|
+
model: string;
|
|
30
|
+
defaultModel: string;
|
|
31
|
+
baseURL: string;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export default RecraftProvider;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { RecraftModels } from "../constants/enums.js";
|
|
2
|
+
import { BaseProvider } from "../core/baseProvider.js";
|
|
3
|
+
import { isNeuroLink } from "../neurolink.js";
|
|
4
|
+
import { createProxyFetch } from "../proxy/proxyFetch.js";
|
|
5
|
+
import { AuthenticationError, InvalidModelError, ProviderError, RateLimitError, } from "../types/index.js";
|
|
6
|
+
import { logger } from "../utils/logger.js";
|
|
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";
|
|
10
|
+
const RECRAFT_DEFAULT_BASE_URL = "https://external.api.recraft.ai/v1";
|
|
11
|
+
const REQUEST_TIMEOUT_MS = 120_000;
|
|
12
|
+
const getRecraftApiKey = () => validateApiKey(createRecraftConfig());
|
|
13
|
+
const getDefaultRecraftModel = () => getProviderModel("RECRAFT_MODEL", RecraftModels.RECRAFT_V3);
|
|
14
|
+
/**
|
|
15
|
+
* Recraft Provider — image generation with vector / illustration focus.
|
|
16
|
+
*
|
|
17
|
+
* Hits external.api.recraft.ai/v1/images/generations (OpenAI-compat
|
|
18
|
+
* shape). Returns either url or b64_json; we convert URL → base64 for
|
|
19
|
+
* the uniform imageOutput contract.
|
|
20
|
+
*
|
|
21
|
+
* @see https://www.recraft.ai/docs
|
|
22
|
+
*/
|
|
23
|
+
export class RecraftProvider extends BaseProvider {
|
|
24
|
+
apiKey;
|
|
25
|
+
baseURL;
|
|
26
|
+
proxyFetch;
|
|
27
|
+
constructor(modelName, sdk, _region, credentials) {
|
|
28
|
+
const validatedNeurolink = isNeuroLink(sdk) ? sdk : undefined;
|
|
29
|
+
super(modelName, "recraft", validatedNeurolink);
|
|
30
|
+
const overrideKey = credentials?.apiKey?.trim();
|
|
31
|
+
this.apiKey =
|
|
32
|
+
overrideKey && overrideKey.length > 0 ? overrideKey : getRecraftApiKey();
|
|
33
|
+
this.baseURL =
|
|
34
|
+
credentials?.baseURL ??
|
|
35
|
+
process.env.RECRAFT_BASE_URL ??
|
|
36
|
+
RECRAFT_DEFAULT_BASE_URL;
|
|
37
|
+
this.proxyFetch = createProxyFetch();
|
|
38
|
+
logger.debug("Recraft Provider initialized (image-gen only)", {
|
|
39
|
+
modelName: this.modelName,
|
|
40
|
+
baseURL: this.baseURL,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
getProviderName() {
|
|
44
|
+
return this.providerName;
|
|
45
|
+
}
|
|
46
|
+
getDefaultModel() {
|
|
47
|
+
return getDefaultRecraftModel();
|
|
48
|
+
}
|
|
49
|
+
supportsTools() {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
getAISDKModel() {
|
|
53
|
+
throw new Error("Recraft is an image-generation-only provider; chat completions are not available.");
|
|
54
|
+
}
|
|
55
|
+
async executeStream(_options, _analysisSchema) {
|
|
56
|
+
throw new Error("Recraft is an image-generation-only provider; streaming chat is not available.");
|
|
57
|
+
}
|
|
58
|
+
formatProviderError(error) {
|
|
59
|
+
const message = error instanceof Error
|
|
60
|
+
? error.message
|
|
61
|
+
: typeof error === "string"
|
|
62
|
+
? error
|
|
63
|
+
: "Unknown error";
|
|
64
|
+
if (message.includes("401") ||
|
|
65
|
+
message.toLowerCase().includes("unauthorized")) {
|
|
66
|
+
return new AuthenticationError("Invalid Recraft API key. Get one at https://www.recraft.ai/api", "recraft");
|
|
67
|
+
}
|
|
68
|
+
if (message.includes("429") ||
|
|
69
|
+
message.toLowerCase().includes("rate limit")) {
|
|
70
|
+
return new RateLimitError("Recraft rate limit exceeded. Back off and retry.", "recraft");
|
|
71
|
+
}
|
|
72
|
+
if (message.includes("404") || message.includes("model_not_found")) {
|
|
73
|
+
return new InvalidModelError(`Recraft model '${this.modelName}' not found. Use recraftv3, recraftv3-svg, or recraftv2.`, "recraft");
|
|
74
|
+
}
|
|
75
|
+
return new ProviderError(`Recraft error: ${message}`, "recraft");
|
|
76
|
+
}
|
|
77
|
+
async executeImageGeneration(options) {
|
|
78
|
+
const startTime = Date.now();
|
|
79
|
+
// Resolve per-call credentials first, then fall back to instance-level.
|
|
80
|
+
const perCallCreds = options.credentials?.recraft;
|
|
81
|
+
const effectiveApiKey = perCallCreds?.apiKey?.trim() || this.apiKey;
|
|
82
|
+
const effectiveBaseURL = perCallCreds?.baseURL || this.baseURL;
|
|
83
|
+
const prompt = options.prompt ?? options.input?.text ?? "";
|
|
84
|
+
if (!prompt.trim()) {
|
|
85
|
+
throw new Error("Recraft image generation requires a prompt (input.text or prompt)");
|
|
86
|
+
}
|
|
87
|
+
const extras = options;
|
|
88
|
+
const body = {
|
|
89
|
+
model: options.model ?? this.modelName,
|
|
90
|
+
prompt,
|
|
91
|
+
n: 1,
|
|
92
|
+
response_format: "b64_json",
|
|
93
|
+
};
|
|
94
|
+
if (extras.negativePrompt) {
|
|
95
|
+
body.negative_prompt = extras.negativePrompt;
|
|
96
|
+
}
|
|
97
|
+
if (extras.style) {
|
|
98
|
+
body.style = extras.style;
|
|
99
|
+
}
|
|
100
|
+
if (extras.styleId) {
|
|
101
|
+
body.style_id = extras.styleId;
|
|
102
|
+
}
|
|
103
|
+
if (extras.size) {
|
|
104
|
+
body.size = extras.size;
|
|
105
|
+
}
|
|
106
|
+
const controller = new AbortController();
|
|
107
|
+
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
108
|
+
let response;
|
|
109
|
+
try {
|
|
110
|
+
response = await this.proxyFetch(`${effectiveBaseURL}/images/generations`, {
|
|
111
|
+
method: "POST",
|
|
112
|
+
headers: {
|
|
113
|
+
Authorization: `Bearer ${effectiveApiKey}`,
|
|
114
|
+
"Content-Type": "application/json",
|
|
115
|
+
},
|
|
116
|
+
body: JSON.stringify(body),
|
|
117
|
+
signal: controller.signal,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
122
|
+
throw this.formatProviderError(new Error(`Recraft image-gen request timed out after ${REQUEST_TIMEOUT_MS / 1000}s`));
|
|
123
|
+
}
|
|
124
|
+
throw this.formatProviderError(err);
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
clearTimeout(timeoutId);
|
|
128
|
+
}
|
|
129
|
+
if (!response.ok) {
|
|
130
|
+
const text = await response.text();
|
|
131
|
+
throw this.formatProviderError(new Error(`Recraft image-gen failed: ${response.status} — ${text}`));
|
|
132
|
+
}
|
|
133
|
+
const data = (await response.json());
|
|
134
|
+
const entry = data.data?.[0];
|
|
135
|
+
if (!entry) {
|
|
136
|
+
throw new Error("Recraft returned no image data");
|
|
137
|
+
}
|
|
138
|
+
let base64;
|
|
139
|
+
if (entry.b64_json) {
|
|
140
|
+
base64 = entry.b64_json;
|
|
141
|
+
}
|
|
142
|
+
else if (entry.url) {
|
|
143
|
+
// Guard the API-returned URL before fetching (provider-returned URLs
|
|
144
|
+
// carry the same SSRF risk as caller-supplied ones).
|
|
145
|
+
await assertSafeUrl(entry.url);
|
|
146
|
+
// Fallback URL download — apply a 60s timeout so it cannot hang indefinitely.
|
|
147
|
+
const dlController = new AbortController();
|
|
148
|
+
const dlTimeoutId = setTimeout(() => dlController.abort(), 60_000);
|
|
149
|
+
let dl;
|
|
150
|
+
try {
|
|
151
|
+
dl = await this.proxyFetch(entry.url, { signal: dlController.signal });
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
155
|
+
throw new Error("Recraft image download timed out after 60s", {
|
|
156
|
+
cause: err,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
throw err;
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
clearTimeout(dlTimeoutId);
|
|
163
|
+
}
|
|
164
|
+
if (!dl.ok) {
|
|
165
|
+
throw new Error(`Failed to download Recraft image: ${dl.status}`);
|
|
166
|
+
}
|
|
167
|
+
const dlBuf = await readBoundedBuffer(dl, MAX_IMAGE_BYTES, "Recraft image");
|
|
168
|
+
base64 = dlBuf.toString("base64");
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
throw new Error("Recraft response missing both b64_json and url");
|
|
172
|
+
}
|
|
173
|
+
const generationTimeMs = Date.now() - startTime;
|
|
174
|
+
logger.info(`[RecraftProvider] Generated image (${base64.length} base64 chars) in ${generationTimeMs}ms — model ${this.modelName}`);
|
|
175
|
+
return {
|
|
176
|
+
content: prompt,
|
|
177
|
+
provider: this.providerName,
|
|
178
|
+
model: this.modelName,
|
|
179
|
+
// output: 1000 = sentinel for per-image pricing (see pricing.ts)
|
|
180
|
+
usage: { input: 0, output: 1000, total: 1000 },
|
|
181
|
+
imageOutput: { base64 },
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
async validateConfiguration() {
|
|
185
|
+
return typeof this.apiKey === "string" && this.apiKey.trim().length > 0;
|
|
186
|
+
}
|
|
187
|
+
getConfiguration() {
|
|
188
|
+
return {
|
|
189
|
+
provider: this.providerName,
|
|
190
|
+
model: this.modelName,
|
|
191
|
+
defaultModel: getDefaultRecraftModel(),
|
|
192
|
+
baseURL: this.baseURL,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
export default RecraftProvider;
|