@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio
262 lines (261 loc) • 8.51 kB
JavaScript
/**
* Base error class for all NeuroLink-specific errors.
* This allows for easy identification of errors thrown by the SDK.
*/
export class BaseError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
}
/**
* Thrown when a provider encounters a generic error.
*/
export class ProviderError extends BaseError {
provider;
constructor(message, provider) {
super(provider ? `[${provider}] ${message}` : message);
this.provider = provider;
}
}
/**
* Thrown for authentication-related errors, such as invalid or missing API keys.
*/
export class AuthenticationError extends ProviderError {
constructor(message, provider) {
super(message, provider);
}
}
/**
* Thrown for authorization errors, where the user does not have permission.
*/
export class AuthorizationError extends ProviderError {
constructor(message, provider) {
super(message, provider);
}
}
/**
* Thrown for network-related issues, such as connectivity problems or timeouts.
*/
export class NetworkError extends ProviderError {
constructor(message, provider) {
super(message, provider);
}
}
/**
* Thrown when an API rate limit has been exceeded.
*/
export class RateLimitError extends ProviderError {
constructor(message, provider) {
super(message, provider);
}
}
/**
* Thrown when a specified model is not found or is invalid for the provider.
*/
export class InvalidModelError extends ProviderError {
constructor(message, provider) {
super(message, provider);
}
}
// =============================================================================
// OAUTH ERROR CLASSES
// =============================================================================
/**
* Base class for OAuth-specific errors
*/
export class OAuthError extends BaseError {
code;
constructor(message, code) {
super(message);
this.code = code;
this.name = "OAuthError";
}
}
/**
* Thrown when OAuth configuration is invalid or missing
*/
export class OAuthConfigurationError extends OAuthError {
constructor(message) {
super(message, "CONFIGURATION_ERROR");
this.name = "OAuthConfigurationError";
}
}
/**
* Thrown when authorization code exchange fails
*/
export class OAuthTokenExchangeError extends OAuthError {
statusCode;
constructor(message, statusCode) {
super(message, "TOKEN_EXCHANGE_ERROR");
this.statusCode = statusCode;
this.name = "OAuthTokenExchangeError";
}
}
/**
* Thrown when token refresh fails
*/
export class OAuthTokenRefreshError extends OAuthError {
statusCode;
constructor(message, statusCode) {
super(message, "TOKEN_REFRESH_ERROR");
this.statusCode = statusCode;
this.name = "OAuthTokenRefreshError";
}
}
/**
* Thrown when token validation fails
*/
export class OAuthTokenValidationError extends OAuthError {
constructor(message) {
super(message, "TOKEN_VALIDATION_ERROR");
this.name = "OAuthTokenValidationError";
}
}
/**
* Thrown when token revocation fails
*/
export class OAuthTokenRevocationError extends OAuthError {
statusCode;
constructor(message, statusCode) {
super(message, "TOKEN_REVOCATION_ERROR");
this.statusCode = statusCode;
this.name = "OAuthTokenRevocationError";
}
}
/**
* Thrown when callback server operations fail
*/
export class OAuthCallbackServerError extends OAuthError {
constructor(message) {
super(message, "CALLBACK_SERVER_ERROR");
this.name = "OAuthCallbackServerError";
}
}
// =============================================================================
// TOKEN STORE ERROR
// =============================================================================
/**
* Token storage error for authentication-related failures
*/
export class TokenStoreError extends BaseError {
code;
constructor(message, code = "STORAGE_ERROR") {
super(message);
this.code = code;
this.name = "TokenStoreError";
}
}
// =============================================================================
// MODEL ACCESS ERROR
// =============================================================================
/**
* Error thrown when model access is denied based on subscription tier
*/
export class ModelAccessError extends BaseError {
model;
tier;
requiredTier;
constructor(model, tier, requiredTier) {
super(`Model "${model}" is not available for tier "${tier}". ` +
`Required tier: "${requiredTier}" or higher.`);
this.name = "ModelAccessError";
this.model = model;
this.tier = tier;
this.requiredTier = requiredTier;
}
}
/**
* Curator P1-1: thrown when a provider rejects a request because the
* caller's team / API key is not whitelisted for the requested model.
*
* LiteLLM's `team not allowed to access model. This team can only access
* models=['glm-latest', 'kimi-latest', ...]` is the canonical example —
* the list is parsed off the error body so callers / fallback orchestrators
* can choose a whitelisted alternative without scraping strings.
*/
export class ModelAccessDeniedError extends ProviderError {
requestedModel;
allowedModels;
code = "MODEL_ACCESS_DENIED";
constructor(message, options = {}) {
super(message, options.provider);
this.name = "ModelAccessDeniedError";
this.requestedModel = options.requestedModel;
this.allowedModels = options.allowedModels;
}
}
/** Maximum body length we'll attempt to parse. Real provider error
* bodies are well under 10 KB; longer inputs are either truncated
* log output or a deliberate ReDoS attempt. */
const MAX_ALLOWED_MODELS_INPUT = 10_000;
/**
* Parse the `allowed_models` array out of a provider error message body.
* Currently targets the LiteLLM team-whitelist response shape:
*
* "team not allowed to access model. This team can only access
* models=['glm-latest', 'kimi-latest', 'open-large']"
*
* Implementation note: deliberately uses `indexOf`/`slice` instead of a
* single `/models\s*=\s*\[([^\]]*)\]/` regex. CodeQL flagged the latter
* as `js/polynomial-redos` because the `[^\]]*` greedy quantifier on
* library-supplied input can be exploited by a crafted long string. The
* indexOf/slice path is O(n) with no backtracking and we additionally
* cap the input length.
*
* Returns undefined when no list is found.
*/
export function parseAllowedModels(message) {
if (typeof message !== "string" || message.length === 0) {
return undefined;
}
if (message.length > MAX_ALLOWED_MODELS_INPUT) {
return undefined;
}
// Locate `models` keyword case-insensitively, then walk forward to
// confirm `=` and `[` markers — no regex backtracking.
const lower = message.toLowerCase();
let idx = lower.indexOf("models", 0);
while (idx !== -1) {
let cursor = idx + "models".length;
// Skip whitespace
while (cursor < message.length && /\s/.test(message[cursor])) {
cursor++;
}
if (message[cursor] !== "=") {
idx = lower.indexOf("models", idx + 1);
continue;
}
cursor++;
while (cursor < message.length && /\s/.test(message[cursor])) {
cursor++;
}
if (message[cursor] !== "[") {
idx = lower.indexOf("models", idx + 1);
continue;
}
const open = cursor;
const close = message.indexOf("]", open + 1);
if (close === -1) {
return undefined;
}
const inside = message.slice(open + 1, close);
const items = inside
.split(",")
.map((s) => s.trim().replace(/^['"]|['"]$/g, ""))
.filter((s) => s.length > 0);
return items.length > 0 ? items : undefined;
}
return undefined;
}
/**
* Returns true when `message` looks like a model-access-denied response
* (LiteLLM "team not allowed", generic "not allowed to access model",
* or "team can only access models=[...]").
*/
export function isModelAccessDeniedMessage(message) {
const lower = message.toLowerCase();
return ((lower.includes("team") && lower.includes("not allowed")) ||
lower.includes("team can only access") ||
/not\s+allowed\s+to\s+access\s+(this\s+)?model/i.test(message));
}