@juspay/neurolink 11.23.2 → 11.23.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,6 +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
16
  const OPENAI_DEFAULT_BASE_URL = "https://api.openai.com/v1";
16
17
  /**
17
18
  * Resolve the effective OpenAI base URL from optional credential / env
@@ -113,6 +114,23 @@ export class OpenAIProvider extends OpenAIChatCompletionsProvider {
113
114
  ? ctx.message
114
115
  : "Invalid OpenAI API key. Please check your OPENAI_API_KEY environment variable.",
115
116
  },
117
+ // MUST precede the 429 rule below. OpenAI returns 429 for two
118
+ // unrelated conditions: transient throttling, and a permanently
119
+ // exhausted quota (out of credit, or a spend cap reached). Matching on
120
+ // the status code alone collapses them, and the resulting advice —
121
+ // "try again later" — is the one thing that can never resolve the
122
+ // second. The signal is the error TYPE, not the wording.
123
+ {
124
+ // One definition of "permanent billing state", in the helper. The
125
+ // earlier cut also tested `errorType === "insufficient_quota"` here,
126
+ // which is precisely the helper's own first branch — two places to
127
+ // keep in step for no benefit.
128
+ match: (ctx) => isOpenAIQuotaExhaustedError(ctx.error),
129
+ errorClass: ProviderError,
130
+ message: "OpenAI quota exhausted — this will not resolve by retrying. " +
131
+ "Check your plan and billing details at " +
132
+ "https://platform.openai.com/account/billing",
133
+ },
116
134
  {
117
135
  match: (ctx) => ctx.statusCode === 429 ||
118
136
  errorType === "rate_limit_error" ||
@@ -52,6 +52,24 @@ 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
+ * An OpenAI-wire `insufficient_quota` error: the account is out of credit or
57
+ * has hit a spend cap.
58
+ *
59
+ * Exported and shared with the OpenAI error table so the retry predicate and
60
+ * the message a caller sees cannot disagree about what a permanent billing
61
+ * state looks like. The type is NOT a field on the error — OpenAI puts it
62
+ * inside the JSON response body, which the SDK keeps as a string — so any
63
+ * caller that needs it has to parse, and none should do so on its own.
64
+ *
65
+ * Deliberately keyed on the error TYPE, never on the word "quota". OpenAI and
66
+ * xAI send 429 + `type: "insufficient_quota"` for a permanent billing state,
67
+ * but other providers use "quota" for ordinary throttling — Google's 429 reads
68
+ * "Quota exceeded for quota metric ...", which IS retryable and must stay that
69
+ * way. Matching free text here would make Gemini throttling non-retryable and
70
+ * quietly remove a layer of resilience.
71
+ */
72
+ export declare function isOpenAIQuotaExhaustedError(error: unknown): boolean;
55
73
  export declare function isRetryableProviderError(error: unknown): boolean;
56
74
  /**
57
75
  * Extract the HTTP status code from an AI SDK error, if available.
@@ -105,7 +105,53 @@ export function extractRetryAfterMsFromError(error) {
105
105
  }
106
106
  return undefined;
107
107
  }
108
+ /**
109
+ * An OpenAI-wire `insufficient_quota` error: the account is out of credit or
110
+ * has hit a spend cap.
111
+ *
112
+ * Exported and shared with the OpenAI error table so the retry predicate and
113
+ * the message a caller sees cannot disagree about what a permanent billing
114
+ * state looks like. The type is NOT a field on the error — OpenAI puts it
115
+ * inside the JSON response body, which the SDK keeps as a string — so any
116
+ * caller that needs it has to parse, and none should do so on its own.
117
+ *
118
+ * Deliberately keyed on the error TYPE, never on the word "quota". OpenAI and
119
+ * xAI send 429 + `type: "insufficient_quota"` for a permanent billing state,
120
+ * but other providers use "quota" for ordinary throttling — Google's 429 reads
121
+ * "Quota exceeded for quota metric ...", which IS retryable and must stay that
122
+ * way. Matching free text here would make Gemini throttling non-retryable and
123
+ * quietly remove a layer of resilience.
124
+ */
125
+ export function isOpenAIQuotaExhaustedError(error) {
126
+ if (typeof error !== "object" || error === null) {
127
+ return false;
128
+ }
129
+ const err = error;
130
+ if (err.type === "insufficient_quota") {
131
+ return true;
132
+ }
133
+ // The SDK keeps the raw payload as a string; the type lives inside it.
134
+ if (typeof err.responseBody === "string") {
135
+ try {
136
+ const parsed = JSON.parse(err.responseBody);
137
+ const inner = parsed?.error;
138
+ return inner?.type === "insufficient_quota";
139
+ }
140
+ catch {
141
+ return false;
142
+ }
143
+ }
144
+ return false;
145
+ }
108
146
  export function isRetryableProviderError(error) {
147
+ // Before every other branch, including the SDK's own flag: a 429 carrying
148
+ // `insufficient_quota` is marked retryable by the AI SDK because it only
149
+ // looks at the status code. Retrying it burns the full ladder — 3 attempts
150
+ // and ~20s of backoff, since no Retry-After accompanies these — on a state
151
+ // that cannot change until someone tops up the account.
152
+ if (isOpenAIQuotaExhaustedError(error)) {
153
+ return false;
154
+ }
109
155
  // Preferred path: use the AI SDK's own branded type check + isRetryable flag
110
156
  if (APICallError.isInstance(error)) {
111
157
  return error.isRetryable;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "11.23.2",
3
+ "version": "11.23.3",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
6
6
  "author": {