@juspay/neurolink 11.1.1 → 11.2.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/dist/auth/codexOAuth.d.ts +67 -0
- package/dist/auth/codexOAuth.js +202 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.js +4 -0
- package/dist/browser/neurolink.min.js +401 -401
- package/dist/cli/commands/auth.d.ts +27 -8
- package/dist/cli/commands/auth.js +425 -6
- package/dist/cli/commands/proxy.js +230 -5
- package/dist/cli/factories/authCommandFactory.d.ts +8 -0
- package/dist/cli/factories/authCommandFactory.js +74 -1
- package/dist/lib/auth/codexOAuth.d.ts +67 -0
- package/dist/lib/auth/codexOAuth.js +203 -0
- package/dist/lib/auth/index.d.ts +1 -0
- package/dist/lib/auth/index.js +4 -0
- package/dist/lib/providers/configuredOpenAICompat.d.ts +24 -0
- package/dist/lib/providers/configuredOpenAICompat.js +60 -0
- package/dist/lib/providers/openaiCompatCatalog.d.ts +24 -0
- package/dist/lib/providers/openaiCompatCatalog.js +272 -0
- package/dist/lib/proxy/accountCooldown.js +35 -2
- package/dist/lib/proxy/accountQuota.d.ts +29 -3
- package/dist/lib/proxy/accountQuota.js +203 -12
- package/dist/lib/proxy/accountUsage.js +15 -2
- package/dist/lib/proxy/codexAccountUsage.d.ts +26 -0
- package/dist/lib/proxy/codexAccountUsage.js +174 -0
- package/dist/lib/proxy/proxyAnalysis.js +12 -1
- package/dist/lib/proxy/proxyConfig.js +24 -0
- package/dist/lib/proxy/routingEvidence.d.ts +12 -1
- package/dist/lib/proxy/routingEvidence.js +23 -0
- package/dist/lib/proxy/runtimeConfig.js +3 -0
- package/dist/lib/server/routes/claudeProxyRoutes.d.ts +79 -5
- package/dist/lib/server/routes/claudeProxyRoutes.js +653 -72
- package/dist/lib/server/routes/codexProxyRoutes.d.ts +64 -0
- package/dist/lib/server/routes/codexProxyRoutes.js +454 -0
- package/dist/lib/types/cli.d.ts +7 -1
- package/dist/lib/types/codex.d.ts +95 -0
- package/dist/lib/types/codex.js +15 -0
- package/dist/lib/types/index.d.ts +1 -0
- package/dist/lib/types/index.js +1 -0
- package/dist/lib/types/providers.d.ts +99 -0
- package/dist/lib/types/proxy.d.ts +83 -0
- package/dist/lib/types/subscription.d.ts +13 -0
- package/dist/lib/utils/providerConfig.d.ts +23 -1
- package/dist/lib/utils/providerConfig.js +60 -0
- package/dist/providers/configuredOpenAICompat.d.ts +24 -0
- package/dist/providers/configuredOpenAICompat.js +59 -0
- package/dist/providers/openaiCompatCatalog.d.ts +24 -0
- package/dist/providers/openaiCompatCatalog.js +271 -0
- package/dist/proxy/accountCooldown.js +35 -2
- package/dist/proxy/accountQuota.d.ts +29 -3
- package/dist/proxy/accountQuota.js +203 -12
- package/dist/proxy/accountUsage.js +15 -2
- package/dist/proxy/codexAccountUsage.d.ts +26 -0
- package/dist/proxy/codexAccountUsage.js +173 -0
- package/dist/proxy/proxyAnalysis.js +12 -1
- package/dist/proxy/proxyConfig.js +24 -0
- package/dist/proxy/routingEvidence.d.ts +12 -1
- package/dist/proxy/routingEvidence.js +23 -0
- package/dist/proxy/runtimeConfig.js +3 -0
- package/dist/server/routes/claudeProxyRoutes.d.ts +79 -5
- package/dist/server/routes/claudeProxyRoutes.js +653 -72
- package/dist/server/routes/codexProxyRoutes.d.ts +64 -0
- package/dist/server/routes/codexProxyRoutes.js +453 -0
- package/dist/types/cli.d.ts +7 -1
- package/dist/types/codex.d.ts +95 -0
- package/dist/types/codex.js +14 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/providers.d.ts +99 -0
- package/dist/types/proxy.d.ts +83 -0
- package/dist/types/subscription.d.ts +13 -0
- package/dist/utils/providerConfig.d.ts +23 -1
- package/dist/utils/providerConfig.js +60 -0
- package/package.json +3 -1
package/dist/lib/auth/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export { ANTHROPIC_OAUTH_BASE_URL, DEFAULT_SCOPES, DEFAULT_REDIRECT_URI, DEFAULT
|
|
|
14
14
|
export { AnthropicOAuth } from "./anthropicOAuth.js";
|
|
15
15
|
export { OAuthError, OAuthConfigurationError, OAuthTokenExchangeError, OAuthTokenRefreshError, OAuthTokenValidationError, OAuthTokenRevocationError, OAuthCallbackServerError, } from "../types/index.js";
|
|
16
16
|
export { createAnthropicOAuth, createAnthropicOAuthConfig, hasAnthropicOAuthCredentials, startCallbackServer, stopCallbackServer, performOAuthFlow, } from "./anthropicOAuth.js";
|
|
17
|
+
export { CODEX_CLIENT_ID, CODEX_AUTH_URL, CODEX_TOKEN_URL, CODEX_REDIRECT_URI, CODEX_DEFAULT_SCOPES, CODEX_BACKEND_BASE_URL, CODEX_RESPONSES_URL, CODEX_USAGE_URL, CODEX_USER_AGENT, CODEX_ORIGINATOR, decodeCodexAccessToken, decodeCodexEmail, resolveCodexAccountId, importCodexAuthFile, refreshCodexToken, codexTokenNeedsRefresh, } from "./codexOAuth.js";
|
|
17
18
|
export { TokenStore, tokenStore, defaultTokenStore } from "./tokenStore.js";
|
|
18
19
|
export { TokenStoreError } from "../types/index.js";
|
|
19
20
|
export { AccountPool } from "./accountPool.js";
|
package/dist/lib/auth/index.js
CHANGED
|
@@ -23,6 +23,10 @@ export { OAuthError, OAuthConfigurationError, OAuthTokenExchangeError, OAuthToke
|
|
|
23
23
|
export { createAnthropicOAuth, createAnthropicOAuthConfig, hasAnthropicOAuthCredentials, startCallbackServer, stopCallbackServer, performOAuthFlow, } from "./anthropicOAuth.js";
|
|
24
24
|
// OAuth types (canonical definitions in types/subscriptionTypes.ts)
|
|
25
25
|
// =============================================================================
|
|
26
|
+
// CODEX (ChatGPT) OAUTH - subscription pool support
|
|
27
|
+
// =============================================================================
|
|
28
|
+
export { CODEX_CLIENT_ID, CODEX_AUTH_URL, CODEX_TOKEN_URL, CODEX_REDIRECT_URI, CODEX_DEFAULT_SCOPES, CODEX_BACKEND_BASE_URL, CODEX_RESPONSES_URL, CODEX_USAGE_URL, CODEX_USER_AGENT, CODEX_ORIGINATOR, decodeCodexAccessToken, decodeCodexEmail, resolveCodexAccountId, importCodexAuthFile, refreshCodexToken, codexTokenNeedsRefresh, } from "./codexOAuth.js";
|
|
29
|
+
// =============================================================================
|
|
26
30
|
// TOKEN STORE - Secure Token Storage
|
|
27
31
|
// =============================================================================
|
|
28
32
|
// Main TokenStore class and instances
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AIProviderName } from "../constants/enums.js";
|
|
2
|
+
import type { OpenAICompatCatalogEntry, OpenAICompatCredentials } from "../types/index.js";
|
|
3
|
+
import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
|
|
4
|
+
/**
|
|
5
|
+
* Generic OpenAI-compatible provider driven entirely by an
|
|
6
|
+
* OpenAICompatCatalogEntry. Replaces a hand-written subclass for any
|
|
7
|
+
* provider whose only differences from its siblings are credentials, base
|
|
8
|
+
* URL, model defaults, and error-classification rules — see
|
|
9
|
+
* OPENAI_COMPAT_CATALOG in openaiCompatCatalog.ts for the entries.
|
|
10
|
+
*
|
|
11
|
+
* If a provider needs a real hook override (adjustRequestBody,
|
|
12
|
+
* adjustBodyAfter400, getChatCompletionsURL, getAuthHeaders,
|
|
13
|
+
* suppressResponseFormatWithTools, ...) it does NOT belong in the catalog —
|
|
14
|
+
* write a dedicated subclass instead (see deepseek.ts, azureOpenai.ts).
|
|
15
|
+
*/
|
|
16
|
+
export declare class ConfiguredOpenAICompatProvider extends OpenAIChatCompletionsProvider {
|
|
17
|
+
private readonly entry;
|
|
18
|
+
constructor(entry: OpenAICompatCatalogEntry, modelName?: string, sdk?: unknown, credentials?: OpenAICompatCredentials);
|
|
19
|
+
protected getProviderName(): AIProviderName;
|
|
20
|
+
protected getDefaultModel(): string;
|
|
21
|
+
protected getFallbackModelName(): string;
|
|
22
|
+
protected getFallbackModels(): string[];
|
|
23
|
+
protected formatProviderError(error: unknown): Error;
|
|
24
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { logger } from "../utils/logger.js";
|
|
2
|
+
import { redactUrlCredentials } from "../utils/logSanitize.js";
|
|
3
|
+
import { getProviderModel, resolveOpenAICompatConfig, } from "../utils/providerConfig.js";
|
|
4
|
+
import { classifyProviderError } from "../utils/errorClassifier.js";
|
|
5
|
+
import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
|
|
6
|
+
/**
|
|
7
|
+
* Generic OpenAI-compatible provider driven entirely by an
|
|
8
|
+
* OpenAICompatCatalogEntry. Replaces a hand-written subclass for any
|
|
9
|
+
* provider whose only differences from its siblings are credentials, base
|
|
10
|
+
* URL, model defaults, and error-classification rules — see
|
|
11
|
+
* OPENAI_COMPAT_CATALOG in openaiCompatCatalog.ts for the entries.
|
|
12
|
+
*
|
|
13
|
+
* If a provider needs a real hook override (adjustRequestBody,
|
|
14
|
+
* adjustBodyAfter400, getChatCompletionsURL, getAuthHeaders,
|
|
15
|
+
* suppressResponseFormatWithTools, ...) it does NOT belong in the catalog —
|
|
16
|
+
* write a dedicated subclass instead (see deepseek.ts, azureOpenai.ts).
|
|
17
|
+
*/
|
|
18
|
+
export class ConfiguredOpenAICompatProvider extends OpenAIChatCompletionsProvider {
|
|
19
|
+
entry;
|
|
20
|
+
constructor(entry, modelName, sdk, credentials) {
|
|
21
|
+
const { apiKey, baseURL } = resolveOpenAICompatConfig(entry, credentials);
|
|
22
|
+
// BaseProvider's constructor calls `this.getDefaultModel()` /
|
|
23
|
+
// `this.getProviderName()` synchronously inside `super()`, before this
|
|
24
|
+
// class's own constructor body (or field initializers) ever run — so
|
|
25
|
+
// `this.entry` is not yet assigned at that point and those overrides
|
|
26
|
+
// would read `undefined.modelEnvVar`. `entry.providerName` is always
|
|
27
|
+
// defined, so passing it straight through makes the base constructor's
|
|
28
|
+
// `providerName || this.getProviderName()` short-circuit; resolving the
|
|
29
|
+
// model up front and always passing a truthy `modelName` does the same
|
|
30
|
+
// for `getDefaultModel()`. Both overrides remain correct for any call
|
|
31
|
+
// made after construction, once `this.entry` is set below.
|
|
32
|
+
const resolvedModelName = modelName || getProviderModel(entry.modelEnvVar, entry.defaultModel);
|
|
33
|
+
super(entry.providerName, resolvedModelName, sdk, { baseURL, apiKey });
|
|
34
|
+
this.entry = entry;
|
|
35
|
+
logger.debug(`${entry.configOptions.providerName} Provider initialized`, {
|
|
36
|
+
modelName: this.modelName,
|
|
37
|
+
providerName: this.providerName,
|
|
38
|
+
baseURL: redactUrlCredentials(this.config.baseURL),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
getProviderName() {
|
|
42
|
+
return this.entry.providerName;
|
|
43
|
+
}
|
|
44
|
+
getDefaultModel() {
|
|
45
|
+
return getProviderModel(this.entry.modelEnvVar, this.entry.defaultModel);
|
|
46
|
+
}
|
|
47
|
+
getFallbackModelName() {
|
|
48
|
+
return this.entry.fallbackModelName;
|
|
49
|
+
}
|
|
50
|
+
getFallbackModels() {
|
|
51
|
+
return this.entry.fallbackModels;
|
|
52
|
+
}
|
|
53
|
+
formatProviderError(error) {
|
|
54
|
+
// classifyProviderError handles TimeoutError internally (always maps
|
|
55
|
+
// to NetworkError, ahead of any rule table) — no local pre-check
|
|
56
|
+
// needed or wanted here; see this task's design note.
|
|
57
|
+
return classifyProviderError(error, this.entry.errorRules, this.entry.providerName, this.modelName);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=configuredOpenAICompat.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { OpenAICompatCatalogEntry } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Config-driven catalog of the 7 zero-quirk OpenAI-compatible providers.
|
|
4
|
+
* Each entry fully replaces what used to be a hand-written
|
|
5
|
+
* OpenAIChatCompletionsProvider subclass — see ConfiguredOpenAICompatProvider
|
|
6
|
+
* for the class that reads these entries, and providerRegistry.ts for the
|
|
7
|
+
* registration loop that consumes this array.
|
|
8
|
+
*
|
|
9
|
+
* `errorRules` mirrors each provider's LIVE `formatProviderError` rule array
|
|
10
|
+
* (post plan-07/wave-2 migration), not the original hand-rolled ladder these
|
|
11
|
+
* providers had when plan 05 was first drafted: every provider below now
|
|
12
|
+
* keeps only its bespoke rule(s) — auth, plus Groq's model_decommissioned and
|
|
13
|
+
* xAI's insufficient_quota — before spreading the SAME exported
|
|
14
|
+
* `DEFAULT_ERROR_RULES` constant that the live subclasses spread (never an
|
|
15
|
+
* inlined copy, so this catalog cannot drift from that table independently).
|
|
16
|
+
* See plan-05/progress.md Ruling R4 for the full rationale.
|
|
17
|
+
*
|
|
18
|
+
* To add a new zero-quirk OpenAI-compatible provider: add one entry here.
|
|
19
|
+
* Do NOT add a provider here if it needs any hook override beyond the 3
|
|
20
|
+
* mandatory ones (getProviderName/getDefaultModel/formatProviderError) —
|
|
21
|
+
* write a dedicated subclass instead (see deepseek.ts, azureOpenai.ts, and
|
|
22
|
+
* Task 14's docs task for the deciding criteria).
|
|
23
|
+
*/
|
|
24
|
+
export declare const OPENAI_COMPAT_CATALOG: readonly OpenAICompatCatalogEntry[];
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { AIProviderName } from "../constants/enums.js";
|
|
2
|
+
import { CloudflareModels, FireworksModels, GroqModels, MistralModels, PerplexityModels, TogetherAIModels, XaiModels, } from "../constants/enums.js";
|
|
3
|
+
import { AuthenticationError, InvalidModelError, ProviderError, } from "../types/index.js";
|
|
4
|
+
import { DEFAULT_ERROR_RULES } from "../utils/errorClassifier.js";
|
|
5
|
+
import { createCloudflareConfig, createFireworksConfig, createGroqConfig, createMistralConfig, createPerplexityConfig, createTogetherAIConfig, createXaiConfig, } from "../utils/providerConfig.js";
|
|
6
|
+
function buildCloudflareBaseURL(accountId) {
|
|
7
|
+
return `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/v1`;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Config-driven catalog of the 7 zero-quirk OpenAI-compatible providers.
|
|
11
|
+
* Each entry fully replaces what used to be a hand-written
|
|
12
|
+
* OpenAIChatCompletionsProvider subclass — see ConfiguredOpenAICompatProvider
|
|
13
|
+
* for the class that reads these entries, and providerRegistry.ts for the
|
|
14
|
+
* registration loop that consumes this array.
|
|
15
|
+
*
|
|
16
|
+
* `errorRules` mirrors each provider's LIVE `formatProviderError` rule array
|
|
17
|
+
* (post plan-07/wave-2 migration), not the original hand-rolled ladder these
|
|
18
|
+
* providers had when plan 05 was first drafted: every provider below now
|
|
19
|
+
* keeps only its bespoke rule(s) — auth, plus Groq's model_decommissioned and
|
|
20
|
+
* xAI's insufficient_quota — before spreading the SAME exported
|
|
21
|
+
* `DEFAULT_ERROR_RULES` constant that the live subclasses spread (never an
|
|
22
|
+
* inlined copy, so this catalog cannot drift from that table independently).
|
|
23
|
+
* See plan-05/progress.md Ruling R4 for the full rationale.
|
|
24
|
+
*
|
|
25
|
+
* To add a new zero-quirk OpenAI-compatible provider: add one entry here.
|
|
26
|
+
* Do NOT add a provider here if it needs any hook override beyond the 3
|
|
27
|
+
* mandatory ones (getProviderName/getDefaultModel/formatProviderError) —
|
|
28
|
+
* write a dedicated subclass instead (see deepseek.ts, azureOpenai.ts, and
|
|
29
|
+
* Task 14's docs task for the deciding criteria).
|
|
30
|
+
*/
|
|
31
|
+
export const OPENAI_COMPAT_CATALOG = [
|
|
32
|
+
{
|
|
33
|
+
providerName: AIProviderName.GROQ,
|
|
34
|
+
aliases: ["groq"],
|
|
35
|
+
apiKeyEnvVar: "GROQ_API_KEY",
|
|
36
|
+
baseURLEnvVar: "GROQ_BASE_URL",
|
|
37
|
+
defaultBaseURL: "https://api.groq.com/openai/v1",
|
|
38
|
+
configOptions: createGroqConfig(),
|
|
39
|
+
modelEnvVar: "GROQ_MODEL",
|
|
40
|
+
defaultModel: GroqModels.LLAMA_3_3_70B_VERSATILE,
|
|
41
|
+
registryDefaultModel: GroqModels.LLAMA_3_3_70B_VERSATILE,
|
|
42
|
+
registryDefaultModelChecksEnvVar: true,
|
|
43
|
+
fallbackModelName: GroqModels.LLAMA_3_1_8B_INSTANT,
|
|
44
|
+
fallbackModels: [
|
|
45
|
+
GroqModels.LLAMA_3_3_70B_VERSATILE,
|
|
46
|
+
GroqModels.LLAMA_3_1_8B_INSTANT,
|
|
47
|
+
GroqModels.GEMMA_2_9B_IT,
|
|
48
|
+
GroqModels.MIXTRAL_8X7B_32768,
|
|
49
|
+
GroqModels.LLAMA_3_2_90B_VISION_PREVIEW,
|
|
50
|
+
GroqModels.LLAMA_3_2_11B_VISION_PREVIEW,
|
|
51
|
+
],
|
|
52
|
+
// KNOWN GAP (not reproducible by this data-only array — flagged for PR C,
|
|
53
|
+
// see plan-05/task-A-report.md): live GroqProvider.formatProviderError()
|
|
54
|
+
// intercepts TimeoutError and returns a plain ProviderError BEFORE ever
|
|
55
|
+
// calling classifyProviderError, overriding that function's own
|
|
56
|
+
// (non-overridable) rule that TimeoutError always maps to NetworkError.
|
|
57
|
+
// ConfiguredOpenAICompatProvider.formatProviderError() has no such
|
|
58
|
+
// pre-check hook, so migrating Groq onto it as-is would silently
|
|
59
|
+
// reclassify Groq timeouts as NetworkError. This entry's errorRules
|
|
60
|
+
// still mirrors Groq's live rule array faithfully for every other error
|
|
61
|
+
// shape; the TimeoutError special case is a structural gap in
|
|
62
|
+
// ConfiguredOpenAICompatProvider, not a data error here.
|
|
63
|
+
errorRules: [
|
|
64
|
+
{
|
|
65
|
+
match: (ctx) => ctx.statusCode === 401 ||
|
|
66
|
+
/Invalid API key|Authentication|invalid_api_key/i.test(ctx.message),
|
|
67
|
+
errorClass: AuthenticationError,
|
|
68
|
+
message: "Invalid Groq API key. Check GROQ_API_KEY. Get one at https://console.groq.com/keys",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
match: (ctx) => /model_decommissioned/i.test(ctx.message),
|
|
72
|
+
errorClass: InvalidModelError,
|
|
73
|
+
message: (ctx) => `Groq model '${ctx.modelName}' was decommissioned. Pick a current model from https://console.groq.com/docs/models.`,
|
|
74
|
+
},
|
|
75
|
+
...DEFAULT_ERROR_RULES,
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
providerName: AIProviderName.XAI,
|
|
80
|
+
aliases: ["xai", "grok"],
|
|
81
|
+
apiKeyEnvVar: "XAI_API_KEY",
|
|
82
|
+
baseURLEnvVar: "XAI_BASE_URL",
|
|
83
|
+
defaultBaseURL: "https://api.x.ai/v1",
|
|
84
|
+
configOptions: createXaiConfig(),
|
|
85
|
+
modelEnvVar: "XAI_MODEL",
|
|
86
|
+
defaultModel: XaiModels.GROK_3,
|
|
87
|
+
registryDefaultModel: XaiModels.GROK_3,
|
|
88
|
+
registryDefaultModelChecksEnvVar: true,
|
|
89
|
+
fallbackModelName: XaiModels.GROK_3_MINI,
|
|
90
|
+
fallbackModels: [
|
|
91
|
+
XaiModels.GROK_3,
|
|
92
|
+
XaiModels.GROK_3_MINI,
|
|
93
|
+
XaiModels.GROK_2_LATEST,
|
|
94
|
+
XaiModels.GROK_2_VISION_LATEST,
|
|
95
|
+
XaiModels.GROK_BETA,
|
|
96
|
+
],
|
|
97
|
+
errorRules: [
|
|
98
|
+
{
|
|
99
|
+
match: (ctx) => ctx.statusCode === 401 ||
|
|
100
|
+
/Invalid API key|Authentication|invalid_api_key/i.test(ctx.message),
|
|
101
|
+
errorClass: AuthenticationError,
|
|
102
|
+
message: "Invalid xAI API key. Please check your XAI_API_KEY environment variable. Get one at https://console.x.ai/",
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
match: (ctx) => /insufficient_quota|quota exceeded/i.test(ctx.message),
|
|
106
|
+
errorClass: ProviderError,
|
|
107
|
+
message: "xAI account has insufficient quota. Top up at https://console.x.ai/",
|
|
108
|
+
},
|
|
109
|
+
...DEFAULT_ERROR_RULES,
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
providerName: AIProviderName.TOGETHER_AI,
|
|
114
|
+
aliases: ["together-ai", "together"],
|
|
115
|
+
apiKeyEnvVar: "TOGETHER_API_KEY",
|
|
116
|
+
baseURLEnvVar: "TOGETHER_BASE_URL",
|
|
117
|
+
defaultBaseURL: "https://api.together.xyz/v1",
|
|
118
|
+
configOptions: createTogetherAIConfig(),
|
|
119
|
+
modelEnvVar: "TOGETHER_MODEL",
|
|
120
|
+
defaultModel: TogetherAIModels.LLAMA_3_3_70B_INSTRUCT_TURBO,
|
|
121
|
+
registryDefaultModel: TogetherAIModels.LLAMA_3_3_70B_INSTRUCT_TURBO,
|
|
122
|
+
registryDefaultModelChecksEnvVar: true,
|
|
123
|
+
fallbackModelName: TogetherAIModels.LLAMA_3_1_8B_INSTRUCT_TURBO,
|
|
124
|
+
fallbackModels: [
|
|
125
|
+
TogetherAIModels.LLAMA_3_3_70B_INSTRUCT_TURBO,
|
|
126
|
+
TogetherAIModels.LLAMA_3_1_405B_INSTRUCT_TURBO,
|
|
127
|
+
TogetherAIModels.LLAMA_3_1_70B_INSTRUCT_TURBO,
|
|
128
|
+
TogetherAIModels.LLAMA_3_1_8B_INSTRUCT_TURBO,
|
|
129
|
+
TogetherAIModels.MIXTRAL_8X22B_INSTRUCT,
|
|
130
|
+
TogetherAIModels.QWEN_2_5_72B_INSTRUCT_TURBO,
|
|
131
|
+
TogetherAIModels.DEEPSEEK_R1,
|
|
132
|
+
TogetherAIModels.DEEPSEEK_V3,
|
|
133
|
+
],
|
|
134
|
+
errorRules: [
|
|
135
|
+
{
|
|
136
|
+
match: (ctx) => ctx.statusCode === 401 ||
|
|
137
|
+
/Invalid API key|Authentication/i.test(ctx.message),
|
|
138
|
+
errorClass: AuthenticationError,
|
|
139
|
+
message: "Invalid Together AI API key. Get one at https://api.together.xyz/settings/api-keys",
|
|
140
|
+
},
|
|
141
|
+
...DEFAULT_ERROR_RULES,
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
providerName: AIProviderName.FIREWORKS,
|
|
146
|
+
aliases: ["fireworks"],
|
|
147
|
+
apiKeyEnvVar: "FIREWORKS_API_KEY",
|
|
148
|
+
baseURLEnvVar: "FIREWORKS_BASE_URL",
|
|
149
|
+
defaultBaseURL: "https://api.fireworks.ai/inference/v1",
|
|
150
|
+
configOptions: createFireworksConfig(),
|
|
151
|
+
modelEnvVar: "FIREWORKS_MODEL",
|
|
152
|
+
defaultModel: FireworksModels.DEEPSEEK_V4_PRO,
|
|
153
|
+
registryDefaultModel: FireworksModels.DEEPSEEK_V4_PRO,
|
|
154
|
+
registryDefaultModelChecksEnvVar: true,
|
|
155
|
+
fallbackModelName: FireworksModels.DEEPSEEK_V4_PRO,
|
|
156
|
+
fallbackModels: [
|
|
157
|
+
FireworksModels.DEEPSEEK_V4_PRO,
|
|
158
|
+
FireworksModels.GLM_5P1,
|
|
159
|
+
FireworksModels.GLM_5,
|
|
160
|
+
FireworksModels.KIMI_K2P6,
|
|
161
|
+
FireworksModels.KIMI_K2P5,
|
|
162
|
+
FireworksModels.GPT_OSS_120B,
|
|
163
|
+
],
|
|
164
|
+
errorRules: [
|
|
165
|
+
{
|
|
166
|
+
match: (ctx) => ctx.statusCode === 401 ||
|
|
167
|
+
/Invalid API key|Authentication/i.test(ctx.message),
|
|
168
|
+
errorClass: AuthenticationError,
|
|
169
|
+
message: "Invalid Fireworks API key. Get one at https://fireworks.ai/account/api-keys",
|
|
170
|
+
},
|
|
171
|
+
...DEFAULT_ERROR_RULES,
|
|
172
|
+
],
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
providerName: AIProviderName.PERPLEXITY,
|
|
176
|
+
aliases: ["perplexity", "pplx"],
|
|
177
|
+
apiKeyEnvVar: "PERPLEXITY_API_KEY",
|
|
178
|
+
baseURLEnvVar: "PERPLEXITY_BASE_URL",
|
|
179
|
+
defaultBaseURL: "https://api.perplexity.ai",
|
|
180
|
+
configOptions: createPerplexityConfig(),
|
|
181
|
+
modelEnvVar: "PERPLEXITY_MODEL",
|
|
182
|
+
defaultModel: PerplexityModels.SONAR,
|
|
183
|
+
registryDefaultModel: PerplexityModels.SONAR,
|
|
184
|
+
registryDefaultModelChecksEnvVar: true,
|
|
185
|
+
// Perplexity's live class does NOT override getFallbackModelName() — it
|
|
186
|
+
// inherits the base class default "gpt-3.5-turbo". Preserved here
|
|
187
|
+
// verbatim, not "fixed" to a Perplexity model — that's a real,
|
|
188
|
+
// pre-existing quirk this plan is not authorized to change.
|
|
189
|
+
fallbackModelName: "gpt-3.5-turbo",
|
|
190
|
+
fallbackModels: [
|
|
191
|
+
PerplexityModels.SONAR,
|
|
192
|
+
PerplexityModels.SONAR_PRO,
|
|
193
|
+
PerplexityModels.SONAR_REASONING,
|
|
194
|
+
PerplexityModels.SONAR_REASONING_PRO,
|
|
195
|
+
PerplexityModels.SONAR_DEEP_RESEARCH,
|
|
196
|
+
],
|
|
197
|
+
errorRules: [
|
|
198
|
+
{
|
|
199
|
+
match: (ctx) => ctx.statusCode === 401 ||
|
|
200
|
+
/Invalid API key|Authentication/i.test(ctx.message),
|
|
201
|
+
errorClass: AuthenticationError,
|
|
202
|
+
message: "Invalid Perplexity API key. Get one at https://www.perplexity.ai/settings/api",
|
|
203
|
+
},
|
|
204
|
+
...DEFAULT_ERROR_RULES,
|
|
205
|
+
],
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
providerName: AIProviderName.MISTRAL,
|
|
209
|
+
aliases: ["mistral"],
|
|
210
|
+
apiKeyEnvVar: "MISTRAL_API_KEY",
|
|
211
|
+
baseURLEnvVar: "MISTRAL_BASE_URL",
|
|
212
|
+
defaultBaseURL: "https://api.mistral.ai/v1",
|
|
213
|
+
configOptions: createMistralConfig(),
|
|
214
|
+
modelEnvVar: "MISTRAL_MODEL",
|
|
215
|
+
defaultModel: MistralModels.MISTRAL_SMALL_2506,
|
|
216
|
+
// The one documented registry-vs-class default-model quirk (see this
|
|
217
|
+
// plan's "Design reference" section): the registry passes the bare
|
|
218
|
+
// literal MISTRAL_LARGE_LATEST with no env-var check, while
|
|
219
|
+
// MistralProvider.getDefaultModel() checks MISTRAL_MODEL and defaults to
|
|
220
|
+
// MISTRAL_SMALL_2506. Preserved exactly, not reconciled.
|
|
221
|
+
registryDefaultModel: MistralModels.MISTRAL_LARGE_LATEST,
|
|
222
|
+
registryDefaultModelChecksEnvVar: false,
|
|
223
|
+
fallbackModelName: MistralModels.MISTRAL_SMALL_2506,
|
|
224
|
+
fallbackModels: [
|
|
225
|
+
MistralModels.MISTRAL_SMALL_2506,
|
|
226
|
+
MistralModels.MISTRAL_LARGE_LATEST,
|
|
227
|
+
],
|
|
228
|
+
errorRules: [
|
|
229
|
+
{
|
|
230
|
+
match: (ctx) => ctx.statusCode === 401 ||
|
|
231
|
+
/API_KEY_INVALID|Invalid API key|Unauthorized/i.test(ctx.message),
|
|
232
|
+
errorClass: AuthenticationError,
|
|
233
|
+
message: "Invalid Mistral API key. Please check your MISTRAL_API_KEY environment variable.",
|
|
234
|
+
},
|
|
235
|
+
...DEFAULT_ERROR_RULES,
|
|
236
|
+
],
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
providerName: AIProviderName.CLOUDFLARE,
|
|
240
|
+
aliases: ["cloudflare", "workers-ai", "cf-ai"],
|
|
241
|
+
apiKeyEnvVar: "CLOUDFLARE_API_KEY",
|
|
242
|
+
computedBaseURL: {
|
|
243
|
+
envVar: "CLOUDFLARE_ACCOUNT_ID",
|
|
244
|
+
missingValueMessage: "CLOUDFLARE_ACCOUNT_ID is required (or pass credentials.cloudflare.accountId). Get the account id from https://dash.cloudflare.com/",
|
|
245
|
+
build: buildCloudflareBaseURL,
|
|
246
|
+
},
|
|
247
|
+
configOptions: createCloudflareConfig(),
|
|
248
|
+
modelEnvVar: "CLOUDFLARE_MODEL",
|
|
249
|
+
defaultModel: CloudflareModels.LLAMA_3_3_70B_FAST,
|
|
250
|
+
registryDefaultModel: CloudflareModels.LLAMA_3_3_70B_FAST,
|
|
251
|
+
registryDefaultModelChecksEnvVar: true,
|
|
252
|
+
fallbackModelName: CloudflareModels.LLAMA_3_1_8B_FAST,
|
|
253
|
+
fallbackModels: [
|
|
254
|
+
CloudflareModels.LLAMA_3_3_70B_FAST,
|
|
255
|
+
CloudflareModels.LLAMA_3_1_70B_INSTRUCT,
|
|
256
|
+
CloudflareModels.LLAMA_3_1_8B_FAST,
|
|
257
|
+
CloudflareModels.LLAMA_3_2_11B_VISION,
|
|
258
|
+
CloudflareModels.MISTRAL_7B_INSTRUCT_V0_2,
|
|
259
|
+
CloudflareModels.QWEN_1P5_14B_CHAT_AWQ,
|
|
260
|
+
],
|
|
261
|
+
errorRules: [
|
|
262
|
+
{
|
|
263
|
+
match: (ctx) => ctx.statusCode === 401 ||
|
|
264
|
+
/Invalid API key|Authentication/i.test(ctx.message),
|
|
265
|
+
errorClass: AuthenticationError,
|
|
266
|
+
message: "Invalid Cloudflare API key. Use a token with Workers AI Read+Write scope. Get one at https://dash.cloudflare.com/profile/api-tokens",
|
|
267
|
+
},
|
|
268
|
+
...DEFAULT_ERROR_RULES,
|
|
269
|
+
],
|
|
270
|
+
},
|
|
271
|
+
];
|
|
272
|
+
//# sourceMappingURL=openaiCompatCatalog.js.map
|
|
@@ -2,7 +2,8 @@ import { readFile } from "node:fs/promises";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { AsyncMutex } from "../utils/asyncMutex.js";
|
|
5
|
-
import {
|
|
5
|
+
import { logger } from "../utils/logger.js";
|
|
6
|
+
import { ACCOUNT_COOLING_REASONS, MAX_COOLDOWN_MS_BY_REASON, } from "./routingEvidence.js";
|
|
6
7
|
import { writeJsonSnapshotAtomically } from "./snapshotPersistence.js";
|
|
7
8
|
const COOLDOWN_FILE = "account-cooldowns.json";
|
|
8
9
|
const VALID_REASONS = new Set(ACCOUNT_COOLING_REASONS);
|
|
@@ -32,13 +33,45 @@ function isPersistedCooldown(value) {
|
|
|
32
33
|
typeof candidate.reason === "string" &&
|
|
33
34
|
VALID_REASONS.has(candidate.reason));
|
|
34
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Cap a persisted cooldown at what its reason can plausibly mean, measured from
|
|
38
|
+
* when it was written.
|
|
39
|
+
*
|
|
40
|
+
* Entries written before per-reason ceilings existed can hold a wildly
|
|
41
|
+
* out-of-range wait — a "session" cooldown running for days, from a single stale
|
|
42
|
+
* reset timestamp. Clamping on load heals those without operator action.
|
|
43
|
+
* Clamping rather than dropping keeps a legitimate long weekly cooldown intact.
|
|
44
|
+
*/
|
|
45
|
+
function sanitizePersistedCooldown(accountKey, entry) {
|
|
46
|
+
const ceiling = MAX_COOLDOWN_MS_BY_REASON[entry.reason];
|
|
47
|
+
if (ceiling === undefined) {
|
|
48
|
+
return entry;
|
|
49
|
+
}
|
|
50
|
+
const latest = entry.updatedAt + ceiling;
|
|
51
|
+
if (entry.coolingUntil <= latest) {
|
|
52
|
+
return entry;
|
|
53
|
+
}
|
|
54
|
+
// Announce it: an account silently parked far beyond what its reason can mean
|
|
55
|
+
// is exactly the condition that is hard to diagnose from the outside, and this
|
|
56
|
+
// runs once per process so it cannot become noise.
|
|
57
|
+
const hours = (ms) => (ms / 3_600_000).toFixed(1);
|
|
58
|
+
logger.always(`[proxy] cooldown clamp: ${accountKey} ${entry.reason} entry healed from ` +
|
|
59
|
+
`${hours(entry.coolingUntil - entry.updatedAt)}h to ` +
|
|
60
|
+
`${hours(ceiling)}h — the stored wait exceeded what "${entry.reason}" can mean`);
|
|
61
|
+
return { ...entry, coolingUntil: latest };
|
|
62
|
+
}
|
|
35
63
|
async function ensureAccountCooldownsLoaded() {
|
|
36
64
|
if (!cacheLoaded) {
|
|
37
65
|
if (!cacheLoadPromise) {
|
|
38
66
|
cacheLoadPromise = (async () => {
|
|
39
67
|
try {
|
|
40
68
|
const parsed = JSON.parse(await readFile(getCooldownFilePath(), "utf8"));
|
|
41
|
-
memoryCache = Object.fromEntries(Object.entries(parsed)
|
|
69
|
+
memoryCache = Object.fromEntries(Object.entries(parsed)
|
|
70
|
+
.filter((entry) => isPersistedCooldown(entry[1]))
|
|
71
|
+
.map(([key, entry]) => [
|
|
72
|
+
key,
|
|
73
|
+
sanitizePersistedCooldown(key, entry),
|
|
74
|
+
]));
|
|
42
75
|
}
|
|
43
76
|
catch {
|
|
44
77
|
memoryCache = {};
|
|
@@ -9,7 +9,14 @@
|
|
|
9
9
|
* updates an in-memory cache and debounces disk writes so the request/response
|
|
10
10
|
* path is never blocked by file I/O.
|
|
11
11
|
*/
|
|
12
|
-
import type { AccountQuota } from "../types/index.js";
|
|
12
|
+
import type { AccountQuota, AccountQuotaWindow } from "../types/index.js";
|
|
13
|
+
/**
|
|
14
|
+
* Collapse a wire model id to its family by dropping the snapshot date, so
|
|
15
|
+
* `claude-fable-5-20260115` and `claude-fable-5-20260320` both tag the same
|
|
16
|
+
* scoped window. Without this a window would stop matching the day Anthropic
|
|
17
|
+
* ships a new snapshot.
|
|
18
|
+
*/
|
|
19
|
+
export declare function modelFamilyToken(model: string): string;
|
|
13
20
|
/** Read and normalize Anthropic's authoritative top-level unified status. */
|
|
14
21
|
export declare function getUnifiedRateLimitStatus(headers: Headers | Record<string, string>): string | undefined;
|
|
15
22
|
/**
|
|
@@ -20,13 +27,32 @@ export declare function getUnifiedRateLimitStatus(headers: Headers | Record<stri
|
|
|
20
27
|
* fallback percentage together with an allowed overage status, which is the
|
|
21
28
|
* equivalent provider state.
|
|
22
29
|
*/
|
|
23
|
-
export declare function isQuotaOverageAvailable(quota: Pick<AccountQuota, "fallbackPercentage" | "fallbackStatus" | "overageStatus" | "overageInUse" | "upgradePaths"> | null | undefined): boolean;
|
|
30
|
+
export declare function isQuotaOverageAvailable(quota: Pick<AccountQuota, "fallbackPercentage" | "fallbackStatus" | "overageStatus" | "overageInUse" | "overageEnabled" | "overageDisabledReason" | "upgradePaths"> | null | undefined): boolean;
|
|
24
31
|
/**
|
|
25
32
|
* Parse Anthropic rate-limit / quota headers into an `AccountQuota`.
|
|
26
33
|
* Returns `null` when key headers are absent.
|
|
27
34
|
* Pure computation — no I/O, no blocking.
|
|
28
35
|
*/
|
|
29
|
-
export declare function parseQuotaHeaders(headers: Headers | Record<string, string
|
|
36
|
+
export declare function parseQuotaHeaders(headers: Headers | Record<string, string>, opts?: {
|
|
37
|
+
model?: string;
|
|
38
|
+
now?: number;
|
|
39
|
+
}): AccountQuota | null;
|
|
40
|
+
/**
|
|
41
|
+
* Merge dynamic limit windows across snapshots from different sources.
|
|
42
|
+
*
|
|
43
|
+
* The two sources see different things and neither is a superset: the usage API
|
|
44
|
+
* reports every plan bucket but only when explicitly refreshed, while response
|
|
45
|
+
* headers report only the window(s) touched by the request just served — but do
|
|
46
|
+
* so continuously. A plain overwrite in either direction loses real data, which
|
|
47
|
+
* is why a header capture used to erase the model-scoped windows a `/limits`
|
|
48
|
+
* refresh had just fetched.
|
|
49
|
+
*/
|
|
50
|
+
export declare function mergeQuotaWindows(existing: AccountQuotaWindow[] | undefined, incoming: AccountQuotaWindow[] | undefined): AccountQuotaWindow[] | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Fold a freshly observed snapshot onto the previous one for the same account,
|
|
53
|
+
* preserving dynamic windows the new snapshot does not carry.
|
|
54
|
+
*/
|
|
55
|
+
export declare function mergeQuotaSnapshot(previous: AccountQuota | undefined, incoming: AccountQuota): AccountQuota;
|
|
30
56
|
/**
|
|
31
57
|
* Initialise the quota module with a custom file path.
|
|
32
58
|
* When set, all reads/writes go to this path instead of the default
|