@juspay/neurolink 11.0.0 → 11.1.0

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.
Files changed (103) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/browser/neurolink.min.js +524 -524
  3. package/dist/cli/commands/setup.d.ts +4 -0
  4. package/dist/cli/commands/setup.js +19 -33
  5. package/dist/cli/factories/commandFactory.d.ts +1157 -1
  6. package/dist/cli/factories/commandFactory.js +20 -53
  7. package/dist/factories/providerDescriptors.d.ts +18 -0
  8. package/dist/factories/providerDescriptors.js +536 -0
  9. package/dist/factories/providerFactory.d.ts +25 -14
  10. package/dist/factories/providerFactory.js +46 -23
  11. package/dist/factories/providerRegistry.js +31 -30
  12. package/dist/index.d.ts +10 -1
  13. package/dist/index.js +13 -3
  14. package/dist/lib/factories/providerDescriptors.d.ts +18 -0
  15. package/dist/lib/factories/providerDescriptors.js +537 -0
  16. package/dist/lib/factories/providerFactory.d.ts +25 -14
  17. package/dist/lib/factories/providerFactory.js +46 -23
  18. package/dist/lib/factories/providerRegistry.js +31 -30
  19. package/dist/lib/index.d.ts +10 -1
  20. package/dist/lib/index.js +13 -3
  21. package/dist/lib/neurolink.js +25 -37
  22. package/dist/lib/providers/amazonBedrock/client.js +34 -16
  23. package/dist/lib/providers/anthropic/client.js +39 -34
  24. package/dist/lib/providers/azureOpenai.js +17 -15
  25. package/dist/lib/providers/cloudflare.js +12 -21
  26. package/dist/lib/providers/cohere.js +31 -25
  27. package/dist/lib/providers/deepseek.js +23 -26
  28. package/dist/lib/providers/fireworks.js +12 -21
  29. package/dist/lib/providers/googleAiStudio/client.d.ts +2 -0
  30. package/dist/lib/providers/googleAiStudio/client.js +39 -17
  31. package/dist/lib/providers/googleVertex/client.js +107 -99
  32. package/dist/lib/providers/groq.js +19 -22
  33. package/dist/lib/providers/huggingFace/client.js +26 -24
  34. package/dist/lib/providers/litellm/client.js +54 -39
  35. package/dist/lib/providers/llamaCpp.js +21 -18
  36. package/dist/lib/providers/lmStudio.js +22 -19
  37. package/dist/lib/providers/mistral.js +12 -24
  38. package/dist/lib/providers/nvidiaNim/client.js +37 -34
  39. package/dist/lib/providers/ollama/client.js +50 -35
  40. package/dist/lib/providers/openAI/client.js +37 -40
  41. package/dist/lib/providers/openRouter/client.js +53 -47
  42. package/dist/lib/providers/openaiChatCompletionsBase.js +40 -16
  43. package/dist/lib/providers/openaiCompatible/client.js +36 -32
  44. package/dist/lib/providers/perplexity.js +12 -21
  45. package/dist/lib/providers/togetherAi.js +12 -21
  46. package/dist/lib/providers/xai.js +17 -26
  47. package/dist/lib/server/errors.d.ts +1 -1
  48. package/dist/lib/server/errors.js +2 -2
  49. package/dist/lib/server/index.d.ts +2 -2
  50. package/dist/lib/server/index.js +5 -3
  51. package/dist/lib/server/middleware/rateLimit.d.ts +0 -4
  52. package/dist/lib/server/middleware/rateLimit.js +0 -4
  53. package/dist/lib/types/errors.d.ts +35 -0
  54. package/dist/lib/types/providers.d.ts +64 -0
  55. package/dist/lib/utils/errorClassifier.d.ts +30 -0
  56. package/dist/lib/utils/errorClassifier.js +94 -0
  57. package/dist/lib/utils/fileDetector.js +6 -43
  58. package/dist/lib/utils/providerHealth.d.ts +42 -7
  59. package/dist/lib/utils/providerHealth.js +115 -122
  60. package/dist/lib/utils/providerUtils.js +21 -63
  61. package/dist/neurolink.js +25 -37
  62. package/dist/providers/amazonBedrock/client.js +34 -16
  63. package/dist/providers/amazonSagemaker.d.ts +1 -1
  64. package/dist/providers/anthropic/client.js +39 -34
  65. package/dist/providers/azureOpenai.js +17 -15
  66. package/dist/providers/cloudflare.js +12 -21
  67. package/dist/providers/cohere.js +31 -25
  68. package/dist/providers/deepseek.js +23 -26
  69. package/dist/providers/fireworks.js +12 -21
  70. package/dist/providers/googleAiStudio/client.d.ts +2 -0
  71. package/dist/providers/googleAiStudio/client.js +39 -17
  72. package/dist/providers/googleVertex/client.js +107 -99
  73. package/dist/providers/groq.js +19 -22
  74. package/dist/providers/huggingFace/client.js +26 -24
  75. package/dist/providers/litellm/client.js +54 -39
  76. package/dist/providers/llamaCpp.js +21 -18
  77. package/dist/providers/lmStudio.js +22 -19
  78. package/dist/providers/mistral.js +12 -24
  79. package/dist/providers/nvidiaNim/client.js +37 -34
  80. package/dist/providers/ollama/client.js +50 -35
  81. package/dist/providers/openAI/client.js +37 -40
  82. package/dist/providers/openRouter/client.js +53 -47
  83. package/dist/providers/openaiChatCompletionsBase.js +40 -16
  84. package/dist/providers/openaiCompatible/client.js +36 -32
  85. package/dist/providers/perplexity.js +12 -21
  86. package/dist/providers/sagemaker/language-model.d.ts +2 -2
  87. package/dist/providers/togetherAi.js +12 -21
  88. package/dist/providers/xai.js +17 -26
  89. package/dist/server/errors.d.ts +1 -1
  90. package/dist/server/errors.js +2 -2
  91. package/dist/server/index.d.ts +2 -2
  92. package/dist/server/index.js +5 -3
  93. package/dist/server/middleware/rateLimit.d.ts +0 -4
  94. package/dist/server/middleware/rateLimit.js +0 -4
  95. package/dist/types/errors.d.ts +35 -0
  96. package/dist/types/providers.d.ts +64 -0
  97. package/dist/utils/errorClassifier.d.ts +30 -0
  98. package/dist/utils/errorClassifier.js +93 -0
  99. package/dist/utils/fileDetector.js +6 -43
  100. package/dist/utils/providerHealth.d.ts +42 -7
  101. package/dist/utils/providerHealth.js +115 -122
  102. package/dist/utils/providerUtils.js +21 -63
  103. package/package.json +15 -70
@@ -13,6 +13,7 @@ import { createOAuthFetch } from "../../proxy/oauthFetch.js";
13
13
  import { createProxyFetch } from "../../proxy/proxyFetch.js";
14
14
  import { getCapturedLimitSnapshot, getCapturedResponseHeaders, logClaudeLimitSnapshot, runInLimitCaptureScope, setLimitSpanAttributes, withLimitCapture, wrapFetchWithLimitCapture, } from "./rateLimitCapture.js";
15
15
  import { AuthenticationError, NetworkError, ProviderError, RateLimitError, } from "../../types/index.js";
16
+ import { classifyProviderError } from "../../utils/errorClassifier.js";
16
17
  import { logger } from "../../utils/logger.js";
17
18
  import { ANTHROPIC_ELISION_NOTE, planAnthropicLoopReclaim, previewAnthropicToolResultText, } from "../../context/anthropicLoopGuard.js";
18
19
  import { getAvailableInputTokens } from "../../constants/contextWindows.js";
@@ -22,13 +23,14 @@ import { ANTHROPIC_MAX_CACHE_BREAKPOINTS, applyAnthropicHistoryCacheBreakpoints,
22
23
  import { calculateCost } from "../../utils/pricing.js";
23
24
  import { resolveDeferredTool } from "../../tools/toolDiscovery.js";
24
25
  import { createAnthropicConfig, getProviderModel, validateApiKey, } from "../../utils/providerConfig.js";
25
- import { composeAbortSignals, createTimeoutController, mergeAbortSignals, TimeoutError, } from "../../utils/timeout.js";
26
+ import { composeAbortSignals, createTimeoutController, mergeAbortSignals, } from "../../utils/timeout.js";
26
27
  import { resolveToolChoice } from "../../utils/toolChoice.js";
27
28
  import { emitToolEndFromStepFinish } from "../../utils/toolEndEmitter.js";
28
29
  import { NoOutputGeneratedError } from "../../utils/generationErrors.js";
29
30
  import { buildNoOutputSentinel, stampNoOutputSpan, } from "../../utils/noOutputSentinel.js";
30
31
  import { convertZodToJsonSchema } from "../../utils/schemaConversion.js";
31
32
  import { resolveClaudeMaxTokens } from "../../utils/tokenLimits.js";
33
+ import { withProviderRetry } from "../../utils/providerRetry.js";
32
34
  import { toAnthropicImageBlock, fileToAnthropicBlock, } from "../anthropicImageBlocks.js";
33
35
  import { resolveSamplingParams } from "../../models/modelRegistry.js";
34
36
  import { createChunkQueue, createDeferredAnalytics, stringifyToolInput, } from "../openaiChatCompletionsClient.js";
@@ -1315,37 +1317,40 @@ export class AnthropicProvider extends BaseProvider {
1315
1317
  return delegatingModel;
1316
1318
  }
1317
1319
  formatProviderError(error) {
1318
- if (error instanceof TimeoutError) {
1319
- return new NetworkError(`Request timed out after ${error.timeout}ms`, this.providerName);
1320
- }
1321
- const errorRecord = error;
1322
- const message = typeof errorRecord?.message === "string"
1323
- ? errorRecord.message
1324
- : "Unknown error";
1325
- if (message.includes("API_KEY_INVALID") ||
1326
- message.includes("Invalid API key")) {
1327
- return new AuthenticationError("Invalid Anthropic API key. Please check your ANTHROPIC_API_KEY environment variable.", this.providerName);
1328
- }
1329
- if (message.includes("rate limit") ||
1330
- message.includes("too_many_requests") ||
1331
- message.includes("429")) {
1332
- return new RateLimitError("Anthropic rate limit exceeded. Please try again later.", this.providerName);
1333
- }
1334
- if (message.includes("ECONNRESET") ||
1335
- message.includes("ENOTFOUND") ||
1336
- message.includes("ECONNREFUSED") ||
1337
- message.includes("network") ||
1338
- message.includes("connection")) {
1339
- return new NetworkError(`Connection error: ${message}`, this.providerName);
1340
- }
1341
- if (message.includes("500") ||
1342
- message.includes("502") ||
1343
- message.includes("503") ||
1344
- message.includes("504") ||
1345
- message.includes("server error")) {
1346
- return new ProviderError(`Server error: ${message}`, this.providerName);
1347
- }
1348
- return new ProviderError(`Anthropic error: ${message}`, this.providerName);
1320
+ const rules = [
1321
+ {
1322
+ // Plan 02's mocked-contract work documented a pre-existing gap: the
1323
+ // Anthropic SDK formats auth failures as a bare "401 <msg>" string,
1324
+ // which the message-text regex alone does not catch — mirrors
1325
+ // Vertex's statusCode === 401 fallback in this same task.
1326
+ match: (ctx) => ctx.statusCode === 401 ||
1327
+ /API_KEY_INVALID|Invalid API key/i.test(ctx.message),
1328
+ errorClass: AuthenticationError,
1329
+ message: "Invalid Anthropic API key. Please check your ANTHROPIC_API_KEY environment variable.",
1330
+ },
1331
+ {
1332
+ match: (ctx) => ctx.statusCode === 429 ||
1333
+ /rate limit|too_many_requests|429/i.test(ctx.message),
1334
+ errorClass: RateLimitError,
1335
+ message: "Anthropic rate limit exceeded. Please try again later.",
1336
+ },
1337
+ {
1338
+ match: (ctx) => /ECONNRESET|ENOTFOUND|ECONNREFUSED|network|connection/i.test(ctx.message),
1339
+ errorClass: NetworkError,
1340
+ message: (ctx) => `Connection error: ${ctx.message}`,
1341
+ },
1342
+ {
1343
+ match: (ctx) => /500|502|503|504|server error/i.test(ctx.message),
1344
+ errorClass: ProviderError,
1345
+ message: (ctx) => `Server error: ${ctx.message}`,
1346
+ },
1347
+ {
1348
+ match: () => true,
1349
+ errorClass: ProviderError,
1350
+ message: (ctx) => `Anthropic error: ${ctx.message}`,
1351
+ },
1352
+ ];
1353
+ return classifyProviderError(error, rules, this.providerName, this.modelName);
1349
1354
  }
1350
1355
  // executeGenerate removed - BaseProvider handles all generation with tools
1351
1356
  /**
@@ -1662,9 +1667,9 @@ export class AnthropicProvider extends BaseProvider {
1662
1667
  ...(anthropicToolChoice ? { tool_choice: anthropicToolChoice } : {}),
1663
1668
  ...(thinking ? { thinking } : {}),
1664
1669
  };
1665
- const events = await client.messages.create(params, {
1670
+ const events = await withProviderRetry(() => client.messages.create(params, {
1666
1671
  signal: abortSignal ?? undefined,
1667
- });
1672
+ }), trace.getActiveSpan() ?? undefined, `${this.providerName} stream`);
1668
1673
  // Per-step accumulators, keyed by content-block index so blocks are
1669
1674
  // replayed to the conversation in order (thinking blocks must be
1670
1675
  // passed back with their signatures when tool use continues a turn).
@@ -1,8 +1,8 @@
1
1
  import { APIVersions } from "../constants/enums.js";
2
- import { AuthenticationError, NetworkError, ProviderError, } from "../types/index.js";
2
+ import { AuthenticationError } from "../types/index.js";
3
3
  import { logger } from "../utils/logger.js";
4
+ import { classifyProviderError, DEFAULT_ERROR_RULES, } from "../utils/errorClassifier.js";
4
5
  import { createAzureAPIKeyConfig, createAzureEndpointConfig, validateApiKey, } from "../utils/providerConfig.js";
5
- import { TimeoutError } from "../utils/timeout.js";
6
6
  import { transformParamsForLogging } from "../utils/transformationUtils.js";
7
7
  import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
8
8
  import { requiresMaxCompletionTokens } from "./openaiChatCompletionsClient.js";
@@ -158,19 +158,21 @@ export class AzureOpenAIProvider extends OpenAIChatCompletionsProvider {
158
158
  return this.azureDeployment;
159
159
  }
160
160
  formatProviderError(error) {
161
- if (error instanceof TimeoutError) {
162
- return new NetworkError(`Request timed out: ${error.message}`, "azure");
163
- }
164
- const errorObj = error;
165
- if (errorObj?.message &&
166
- typeof errorObj.message === "string" &&
167
- errorObj.message.includes("401")) {
168
- return new AuthenticationError("Invalid Azure OpenAI API key or endpoint.", "azure");
169
- }
170
- const message = errorObj?.message && typeof errorObj.message === "string"
171
- ? errorObj.message
172
- : "Unknown error";
173
- return new ProviderError(`Azure OpenAI error: ${message}`, "azure");
161
+ // RULING 1 fix: previously only a raw "401" substring was special-cased —
162
+ // everything else (429, 404, network errors, 5xx) fell through to one
163
+ // generic ProviderError. The 401 branch below is preserved byte-for-byte
164
+ // (message text + the raw-substring match style); DEFAULT_ERROR_RULES now
165
+ // gives 429/404/network/5xx their correct classifications instead of the
166
+ // old undifferentiated catch-all.
167
+ const rules = [
168
+ {
169
+ match: (ctx) => /401/.test(ctx.message),
170
+ errorClass: AuthenticationError,
171
+ message: "Invalid Azure OpenAI API key or endpoint.",
172
+ },
173
+ ...DEFAULT_ERROR_RULES,
174
+ ];
175
+ return classifyProviderError(error, rules, "azure", this.modelName);
174
176
  }
175
177
  // ===========================================================================
176
178
  // New overridable hooks (provided by the base-enhancement branch)
@@ -1,9 +1,9 @@
1
1
  import { CloudflareModels } from "../constants/enums.js";
2
- import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../types/index.js";
2
+ import { AuthenticationError } from "../types/index.js";
3
3
  import { logger } from "../utils/logger.js";
4
4
  import { redactUrlCredentials } from "../utils/logSanitize.js";
5
+ import { classifyProviderError, DEFAULT_ERROR_RULES, } from "../utils/errorClassifier.js";
5
6
  import { createCloudflareConfig, getProviderModel, validateApiKey, } from "../utils/providerConfig.js";
6
- import { TimeoutError } from "../utils/timeout.js";
7
7
  import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
8
8
  /**
9
9
  * Cloudflare Workers AI exposes an OpenAI-compatible endpoint scoped per
@@ -68,25 +68,16 @@ export class CloudflareProvider extends OpenAIChatCompletionsProvider {
68
68
  ];
69
69
  }
70
70
  formatProviderError(error) {
71
- if (error instanceof TimeoutError) {
72
- return new NetworkError(`Request timed out: ${error.message}`, "cloudflare");
73
- }
74
- const errorRecord = error;
75
- const message = typeof errorRecord?.message === "string"
76
- ? errorRecord.message
77
- : "Unknown error";
78
- if (message.includes("Invalid API key") ||
79
- message.includes("Authentication") ||
80
- message.includes("401")) {
81
- return new AuthenticationError("Invalid Cloudflare API key. Use a token with Workers AI Read+Write scope. Get one at https://dash.cloudflare.com/profile/api-tokens", "cloudflare");
82
- }
83
- if (message.includes("rate limit") || message.includes("429")) {
84
- return new RateLimitError("Cloudflare Workers AI rate limit exceeded. Free-tier neurons reset daily.", "cloudflare");
85
- }
86
- if (message.includes("model_not_found") || message.includes("404")) {
87
- return new InvalidModelError(`Cloudflare model '${this.modelName}' not found. Browse https://developers.cloudflare.com/workers-ai/models/`, "cloudflare");
88
- }
89
- return new ProviderError(`Cloudflare Workers AI error: ${message}`, "cloudflare");
71
+ const rules = [
72
+ {
73
+ match: (ctx) => ctx.statusCode === 401 ||
74
+ /Invalid API key|Authentication/i.test(ctx.message),
75
+ errorClass: AuthenticationError,
76
+ message: "Invalid Cloudflare API key. Use a token with Workers AI Read+Write scope. Get one at https://dash.cloudflare.com/profile/api-tokens",
77
+ },
78
+ ...DEFAULT_ERROR_RULES,
79
+ ];
80
+ return classifyProviderError(error, rules, "cloudflare", this.modelName);
90
81
  }
91
82
  }
92
83
  //# sourceMappingURL=cloudflare.js.map
@@ -1,10 +1,11 @@
1
1
  import { CohereModels } from "../constants/enums.js";
2
- import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../types/index.js";
2
+ import { AuthenticationError, InvalidModelError, ProviderError, RateLimitError, } from "../types/index.js";
3
+ import { classifyProviderError, DEFAULT_ERROR_RULES, } from "../utils/errorClassifier.js";
3
4
  import { logger } from "../utils/logger.js";
4
5
  import { redactUrlCredentials } from "../utils/logSanitize.js";
5
6
  import { createProxyFetch } from "../proxy/proxyFetch.js";
6
7
  import { createCohereConfig, getProviderModel, validateApiKey, } from "../utils/providerConfig.js";
7
- import { createTimeoutController, TimeoutError } from "../utils/timeout.js";
8
+ import { createTimeoutController } from "../utils/timeout.js";
8
9
  import { stripTrailingSlash } from "./openaiChatCompletionsClient.js";
9
10
  import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
10
11
  const COHERE_DEFAULT_BASE_URL = "https://api.cohere.com/compatibility/v1";
@@ -59,29 +60,34 @@ export class CohereProvider extends OpenAIChatCompletionsProvider {
59
60
  ];
60
61
  }
61
62
  formatProviderError(error) {
62
- if (error instanceof TimeoutError) {
63
- return new NetworkError(`Request timed out: ${error.message}`, "cohere");
64
- }
65
- const errorRecord = error;
66
- const message = typeof errorRecord?.message === "string"
67
- ? errorRecord.message
68
- : "Unknown error";
69
- if (message.includes("invalid api token") ||
70
- message.includes("Authentication") ||
71
- message.includes("401") ||
72
- message.includes("invalid_api_token")) {
73
- return new AuthenticationError("Invalid Cohere API key. Check COHERE_API_KEY. Get one at https://dashboard.cohere.com/api-keys", "cohere");
74
- }
75
- if (message.includes("rate limit") || message.includes("429")) {
76
- return new RateLimitError("Cohere rate limit exceeded. Back off and retry.", "cohere");
77
- }
78
- if (message.includes("model_not_found") || message.includes("404")) {
79
- return new InvalidModelError(`Cohere model '${this.modelName}' not found. Use command-r, command-r-plus, or command-r7b-12-2024.`, "cohere");
80
- }
81
- if (message.includes("trial limit") || message.includes("trial_limit")) {
82
- return new ProviderError("Cohere trial usage limit exceeded. Upgrade at https://dashboard.cohere.com/billing.", "cohere");
83
- }
84
- return new ProviderError(`Cohere error: ${message}`, "cohere");
63
+ const rules = [
64
+ // "invalid api token" is checked lowercase (case-sensitive substring, as
65
+ // in the original) — deliberately NOT normalized to match the family's
66
+ // more common "Invalid API key" capitalization, since Cohere's actual
67
+ // wire message differs.
68
+ {
69
+ match: (ctx) => /invalid api token|Authentication|401|invalid_api_token/.test(ctx.message),
70
+ errorClass: AuthenticationError,
71
+ message: "Invalid Cohere API key. Check COHERE_API_KEY. Get one at https://dashboard.cohere.com/api-keys",
72
+ },
73
+ {
74
+ match: (ctx) => /rate limit|429/.test(ctx.message),
75
+ errorClass: RateLimitError,
76
+ message: "Cohere rate limit exceeded. Back off and retry.",
77
+ },
78
+ {
79
+ match: (ctx) => /model_not_found|404/.test(ctx.message),
80
+ errorClass: InvalidModelError,
81
+ message: () => `Cohere model '${this.modelName}' not found. Use command-r, command-r-plus, or command-r7b-12-2024.`,
82
+ },
83
+ {
84
+ match: (ctx) => /trial limit|trial_limit/.test(ctx.message),
85
+ errorClass: ProviderError,
86
+ message: "Cohere trial usage limit exceeded. Upgrade at https://dashboard.cohere.com/billing.",
87
+ },
88
+ ...DEFAULT_ERROR_RULES,
89
+ ];
90
+ return classifyProviderError(error, rules, "cohere", this.modelName);
85
91
  }
86
92
  async validateConfiguration() {
87
93
  return (typeof this.config.apiKey === "string" &&
@@ -1,9 +1,9 @@
1
1
  import { DeepSeekModels } from "../constants/enums.js";
2
- import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../types/index.js";
2
+ import { AuthenticationError, InvalidModelError, ProviderError, } from "../types/index.js";
3
3
  import { logger } from "../utils/logger.js";
4
4
  import { redactUrlCredentials } from "../utils/logSanitize.js";
5
+ import { classifyProviderError, DEFAULT_ERROR_RULES, } from "../utils/errorClassifier.js";
5
6
  import { createDeepSeekConfig, getProviderModel, validateApiKey, } from "../utils/providerConfig.js";
6
- import { TimeoutError } from "../utils/timeout.js";
7
7
  import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
8
8
  const DEEPSEEK_BASE_URL = "https://api.deepseek.com";
9
9
  const getDeepSeekApiKey = () => {
@@ -69,30 +69,27 @@ export class DeepSeekProvider extends OpenAIChatCompletionsProvider {
69
69
  return getDefaultDeepSeekModel();
70
70
  }
71
71
  formatProviderError(error) {
72
- if (error instanceof TimeoutError) {
73
- return new NetworkError(`Request timed out: ${error.message}`, "deepseek");
74
- }
75
- const errorRecord = error;
76
- const message = typeof errorRecord?.message === "string"
77
- ? errorRecord.message
78
- : "Unknown error";
79
- if (message.includes("Invalid API key") ||
80
- message.includes("Authentication") ||
81
- message.includes("401")) {
82
- return new AuthenticationError("Invalid DeepSeek API key. Please check your DEEPSEEK_API_KEY environment variable.", "deepseek");
83
- }
84
- if (message.includes("rate limit") || message.includes("429")) {
85
- return new RateLimitError("DeepSeek rate limit exceeded", "deepseek");
86
- }
87
- if (message.includes("Insufficient Balance") ||
88
- message.includes("insufficient_balance") ||
89
- message.includes("402")) {
90
- return new ProviderError("DeepSeek account has insufficient balance. Top up at https://platform.deepseek.com/usage", "deepseek");
91
- }
92
- if (message.includes("model_not_found") || message.includes("404")) {
93
- return new InvalidModelError(`DeepSeek model '${this.modelName}' not found. Use 'deepseek-chat' or 'deepseek-reasoner'.`, "deepseek");
94
- }
95
- return new ProviderError(`DeepSeek error: ${message}`, "deepseek");
72
+ const rules = [
73
+ {
74
+ match: (ctx) => ctx.statusCode === 401 ||
75
+ /Invalid API key|Authentication/i.test(ctx.message),
76
+ errorClass: AuthenticationError,
77
+ message: "Invalid DeepSeek API key. Please check your DEEPSEEK_API_KEY environment variable.",
78
+ },
79
+ {
80
+ match: (ctx) => ctx.statusCode === 402 ||
81
+ /Insufficient Balance|insufficient_balance/i.test(ctx.message),
82
+ errorClass: ProviderError,
83
+ message: "DeepSeek account has insufficient balance. Top up at https://platform.deepseek.com/usage",
84
+ },
85
+ {
86
+ match: (ctx) => /model_not_found/i.test(ctx.message),
87
+ errorClass: InvalidModelError,
88
+ message: (ctx) => `DeepSeek model '${ctx.modelName}' not found. Use 'deepseek-chat' or 'deepseek-reasoner'.`,
89
+ },
90
+ ...DEFAULT_ERROR_RULES,
91
+ ];
92
+ return classifyProviderError(error, rules, "deepseek", this.modelName);
96
93
  }
97
94
  // ===========================================================================
98
95
  // Optional hooks — provider-specific quirks
@@ -1,9 +1,9 @@
1
1
  import { FireworksModels } from "../constants/enums.js";
2
- import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../types/index.js";
2
+ import { AuthenticationError } from "../types/index.js";
3
3
  import { logger } from "../utils/logger.js";
4
4
  import { redactUrlCredentials } from "../utils/logSanitize.js";
5
+ import { classifyProviderError, DEFAULT_ERROR_RULES, } from "../utils/errorClassifier.js";
5
6
  import { createFireworksConfig, getProviderModel, validateApiKey, } from "../utils/providerConfig.js";
6
- import { TimeoutError } from "../utils/timeout.js";
7
7
  import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
8
8
  const FIREWORKS_DEFAULT_BASE_URL = "https://api.fireworks.ai/inference/v1";
9
9
  const getFireworksApiKey = () => validateApiKey(createFireworksConfig());
@@ -58,25 +58,16 @@ export class FireworksProvider extends OpenAIChatCompletionsProvider {
58
58
  ];
59
59
  }
60
60
  formatProviderError(error) {
61
- if (error instanceof TimeoutError) {
62
- return new NetworkError(`Request timed out: ${error.message}`, "fireworks");
63
- }
64
- const errorRecord = error;
65
- const message = typeof errorRecord?.message === "string"
66
- ? errorRecord.message
67
- : "Unknown error";
68
- if (message.includes("Invalid API key") ||
69
- message.includes("Authentication") ||
70
- message.includes("401")) {
71
- return new AuthenticationError("Invalid Fireworks API key. Get one at https://fireworks.ai/account/api-keys", "fireworks");
72
- }
73
- if (message.includes("rate limit") || message.includes("429")) {
74
- return new RateLimitError("Fireworks rate limit exceeded. Back off and retry.", "fireworks");
75
- }
76
- if (message.includes("model_not_found") || message.includes("404")) {
77
- return new InvalidModelError(`Fireworks model '${this.modelName}' not found. Browse https://fireworks.ai/models`, "fireworks");
78
- }
79
- return new ProviderError(`Fireworks error: ${message}`, "fireworks");
61
+ const rules = [
62
+ {
63
+ match: (ctx) => ctx.statusCode === 401 ||
64
+ /Invalid API key|Authentication/i.test(ctx.message),
65
+ errorClass: AuthenticationError,
66
+ message: "Invalid Fireworks API key. Get one at https://fireworks.ai/account/api-keys",
67
+ },
68
+ ...DEFAULT_ERROR_RULES,
69
+ ];
70
+ return classifyProviderError(error, rules, "fireworks", this.modelName);
80
71
  }
81
72
  }
82
73
  //# sourceMappingURL=fireworks.js.map
@@ -6,6 +6,7 @@ export declare class GoogleAIStudioProvider extends BaseProvider {
6
6
  private credentials?;
7
7
  constructor(modelName?: string, sdk?: unknown, credentials?: {
8
8
  apiKey?: string;
9
+ baseURL?: string;
9
10
  });
10
11
  getProviderName(): AIProviderName;
11
12
  getDefaultModel(): string;
@@ -86,5 +87,6 @@ export declare class GoogleAIStudioProvider extends BaseProvider {
86
87
  */
87
88
  embedMany(texts: string[], modelName?: string): Promise<number[][]>;
88
89
  private getApiKey;
90
+ private getBaseURL;
89
91
  }
90
92
  export default GoogleAIStudioProvider;
@@ -6,6 +6,7 @@ import { ATTR, tracers, withClientSpan, withClientStreamSpan, withSpan, } from "
6
6
  import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../../types/index.js";
7
7
  import { ERROR_CODES, NeuroLinkError } from "../../utils/errorHandling.js";
8
8
  import { logger } from "../../utils/logger.js";
9
+ import { isToolsSchemaExclusionInForce } from "../../core/modules/structuredOutputPolicy.js";
9
10
  import { GEMINI_ELISION_NOTE, planGeminiLoopReclaim, previewGeminiToolResponseText, } from "../../context/geminiLoopGuard.js";
10
11
  import { getAvailableInputTokens, getContextWindowSize, } from "../../constants/contextWindows.js";
11
12
  import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../../utils/timeout.js";
@@ -18,7 +19,7 @@ import { createProxyFetch } from "../../proxy/proxyFetch.js";
18
19
  // Google AI Live API types now imported from ../types/providerSpecific.js
19
20
  // Import proper types for multimodal message handling
20
21
  // Create Google GenAI client
21
- async function createGoogleGenAIClient(apiKey) {
22
+ async function createGoogleGenAIClient(apiKey, baseURL) {
22
23
  const mod = await import("@google/genai");
23
24
  const ctor = mod.GoogleGenAI;
24
25
  if (!ctor) {
@@ -32,11 +33,17 @@ async function createGoogleGenAIClient(apiKey) {
32
33
  });
33
34
  }
34
35
  const Ctor = ctor;
35
- // Include httpOptions with proxy fetch for corporate network support
36
+ // Include httpOptions with proxy fetch for corporate network support.
37
+ // baseUrl is only included when resolved — verified against
38
+ // @google/genai's ApiClient (dist/node/index.cjs) that it falls back to
39
+ // its own default whenever httpOptions.baseUrl is undefined, so omitting
40
+ // the key and passing `baseUrl: undefined` behave identically; the key is
41
+ // still omitted outright for a cleaner outbound config object.
36
42
  return new Ctor({
37
43
  apiKey,
38
44
  httpOptions: {
39
45
  fetch: createProxyFetch(),
46
+ ...(baseURL ? { baseUrl: baseURL } : {}),
40
47
  },
41
48
  });
42
49
  }
@@ -256,7 +263,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
256
263
  // Use the @google/genai client for image generation
257
264
  let client;
258
265
  try {
259
- client = await createGoogleGenAIClient(apiKey);
266
+ client = await createGoogleGenAIClient(apiKey, this.getBaseURL());
260
267
  }
261
268
  catch {
262
269
  throw new AuthenticationError("Missing '@google/genai'. Install with: npm install @google/genai", this.providerName);
@@ -563,7 +570,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
563
570
  // generate() does, or attached files are dropped on this path alone.
564
571
  await this.preprocessNativeFileInput(options);
565
572
  // Structured output (analysisSchema, JSON format, or schema) is incompatible with tools on Gemini.
566
- const wantsStructuredOutput = analysisSchema || options.output?.format === "json" || options.schema;
573
+ const wantsStructuredOutput = Boolean(analysisSchema || options.output?.format === "json" || options.schema);
567
574
  // Tool filter (a0269210): trust options.tools — caller (BaseProvider.stream)
568
575
  // already merged MCP/built-in tools with user tools and applied any
569
576
  // enabledToolNames filter. Re-attaching getAllTools() here would clobber
@@ -575,12 +582,10 @@ export class GoogleAIStudioProvider extends BaseProvider {
575
582
  ...options,
576
583
  tools: optionTools,
577
584
  };
578
- // Check for tools + JSON schema conflict (Gemini limitation)
585
+ // Tools + JSON conflict (Gemini), via the shared predicate.
579
586
  const wantsJsonOutput = options.output?.format === "json" || options.schema;
580
- if (wantsJsonOutput &&
581
- mergedOptions.tools &&
582
- Object.keys(mergedOptions.tools).length > 0 &&
583
- !mergedOptions.disableTools) {
587
+ const exclusionInForce = isToolsSchemaExclusionInForce(this.providerName, modelName, !mergedOptions.disableTools, Object.keys(mergedOptions.tools ?? {}).length);
588
+ if (wantsJsonOutput && exclusionInForce) {
584
589
  logger.warn("[GoogleAIStudio] Gemini does not support tools and JSON schema output simultaneously. Disabling tools for this request.");
585
590
  mergedOptions = { ...mergedOptions, disableTools: true, tools: {} };
586
591
  }
@@ -618,7 +623,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
618
623
  const timeoutController = createTimeoutController(timeout, this.providerName, "stream");
619
624
  try {
620
625
  const apiKey = this.getApiKey();
621
- const client = await createGoogleGenAIClient(apiKey);
626
+ const client = await createGoogleGenAIClient(apiKey, this.getBaseURL());
622
627
  logger.debug("[GoogleAIStudio] Using native @google/genai for Gemini 3", {
623
628
  model: modelName,
624
629
  hasTools: !!options.tools && Object.keys(options.tools).length > 0,
@@ -956,7 +961,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
956
961
  const timeoutController = createTimeoutController(timeout, this.providerName, "generate");
957
962
  try {
958
963
  const apiKey = this.getApiKey();
959
- const client = await createGoogleGenAIClient(apiKey);
964
+ const client = await createGoogleGenAIClient(apiKey, this.getBaseURL());
960
965
  logger.debug("[GoogleAIStudio] Using native @google/genai for Gemini 3 generate", {
961
966
  model: modelName,
962
967
  hasTools: !!options.tools && Object.keys(options.tools).length > 0,
@@ -985,7 +990,16 @@ export class GoogleAIStudioProvider extends BaseProvider {
985
990
  let originalNameMap = new Map();
986
991
  let declarationsResult;
987
992
  const shouldUseTools = !options.disableTools;
988
- if (shouldUseTools) {
993
+ // Structured output (JSON format or schema) is incompatible with
994
+ // tools on Gemini — routed through the shared predicate so this
995
+ // decision matches stream()'s orchestrator (previously this path
996
+ // had no proactive check and silently dropped the schema instead).
997
+ const wantsNativeJsonRequested = Boolean(options.output?.format === "json" || options.schema);
998
+ const exclusionInForce = isToolsSchemaExclusionInForce(this.providerName, modelName, shouldUseTools, Object.keys(options.tools || {}).length);
999
+ if (wantsNativeJsonRequested && exclusionInForce) {
1000
+ logger.warn("[GoogleAIStudio] Gemini does not support tools and JSON schema output simultaneously. Disabling tools for this request (generate()).");
1001
+ }
1002
+ if (shouldUseTools && !exclusionInForce) {
989
1003
  const tools = options.tools || {};
990
1004
  if (Object.keys(tools).length > 0) {
991
1005
  const result = buildNativeToolDeclarations(tools);
@@ -1002,8 +1016,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
1002
1016
  // Native JSON / schema enforcement (generate path). Mirrors the
1003
1017
  // stream block above; only set when no tools are being sent
1004
1018
  // because Gemini cannot combine function calling with JSON mime.
1005
- const wantsNativeJson = !toolsConfig &&
1006
- (options.output?.format === "json" || !!options.schema);
1019
+ const wantsNativeJson = !toolsConfig && wantsNativeJsonRequested;
1007
1020
  const nativeResponseSchema = wantsNativeJson && options.schema
1008
1021
  ? buildGeminiResponseSchema(options.schema)
1009
1022
  : undefined;
@@ -1332,7 +1345,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
1332
1345
  // Dynamic import to avoid hard dependency unless audio streaming is used
1333
1346
  let client;
1334
1347
  try {
1335
- client = await createGoogleGenAIClient(apiKey);
1348
+ client = await createGoogleGenAIClient(apiKey, this.getBaseURL());
1336
1349
  }
1337
1350
  catch {
1338
1351
  throw new AuthenticationError("Missing '@google/genai'. Install with: pnpm add @google/genai", this.providerName);
@@ -1522,7 +1535,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
1522
1535
  });
1523
1536
  try {
1524
1537
  const apiKey = this.getApiKey();
1525
- const client = await createGoogleGenAIClient(apiKey);
1538
+ const client = await createGoogleGenAIClient(apiKey, this.getBaseURL());
1526
1539
  const result = await client.models.embedContent({
1527
1540
  model: embeddingModelName,
1528
1541
  contents: [text],
@@ -1562,7 +1575,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
1562
1575
  });
1563
1576
  try {
1564
1577
  const apiKey = this.getApiKey();
1565
- const client = await createGoogleGenAIClient(apiKey);
1578
+ const client = await createGoogleGenAIClient(apiKey, this.getBaseURL());
1566
1579
  const result = await client.models.embedContent({
1567
1580
  model: embeddingModelName,
1568
1581
  contents: texts,
@@ -1594,6 +1607,15 @@ export class GoogleAIStudioProvider extends BaseProvider {
1594
1607
  }
1595
1608
  return apiKey;
1596
1609
  }
1610
+ // Mirrors mistral.ts's baseURL precedence (credentials override, then env,
1611
+ // then unset — the SDK's own default applies when unset). Blank/whitespace
1612
+ // values are treated as unset so an empty override can't accidentally
1613
+ // clobber the default.
1614
+ getBaseURL() {
1615
+ const baseURL = this.credentials?.baseURL?.trim() ||
1616
+ process.env.GOOGLE_AI_BASE_URL?.trim();
1617
+ return baseURL && baseURL.length > 0 ? baseURL : undefined;
1618
+ }
1597
1619
  }
1598
1620
  export default GoogleAIStudioProvider;
1599
1621
  //# sourceMappingURL=client.js.map