@juspay/neurolink 9.68.17 → 9.68.19

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.
@@ -1,135 +1,64 @@
1
- import { createMistral } from "@ai-sdk/mistral";
2
- import { BaseProvider } from "../core/baseProvider.js";
3
- import { DEFAULT_MAX_STEPS } from "../core/constants.js";
4
- import { streamAnalyticsCollector } from "../core/streamAnalytics.js";
5
- import { isNeuroLink } from "../neurolink.js";
6
- import { createProxyFetch } from "../proxy/proxyFetch.js";
7
- import { AuthenticationError, NetworkError, ProviderError, RateLimitError, } from "../types/index.js";
8
- import { emitToolEndFromStepFinish } from "../utils/toolEndEmitter.js";
1
+ import { MistralModels } from "../constants/enums.js";
2
+ import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../types/index.js";
9
3
  import { logger } from "../utils/logger.js";
4
+ import { redactUrlCredentials } from "../utils/logSanitize.js";
10
5
  import { createMistralConfig, getProviderModel, validateApiKey, } from "../utils/providerConfig.js";
11
- import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
12
- import { resolveToolChoice } from "../utils/toolChoice.js";
13
- import { toAnalyticsStreamResult } from "./providerTypeUtils.js";
14
- import { stepCountIs } from "../utils/tool.js";
15
- import { streamText } from "../utils/generation.js";
16
- // Configuration helpers - now using consolidated utility
6
+ import { TimeoutError } from "../utils/timeout.js";
7
+ import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
8
+ const MISTRAL_DEFAULT_BASE_URL = "https://api.mistral.ai/v1";
17
9
  const getMistralApiKey = () => {
18
10
  return validateApiKey(createMistralConfig());
19
11
  };
20
12
  const getDefaultMistralModel = () => {
21
- // Default to vision-capable Mistral Small 2506 (June 2025) with multimodal support
22
- return getProviderModel("MISTRAL_MODEL", "mistral-small-2506");
13
+ // Vision-capable Mistral Small (June 2025) with multimodal support.
14
+ return getProviderModel("MISTRAL_MODEL", MistralModels.MISTRAL_SMALL_2506);
23
15
  };
24
16
  /**
25
- * Mistral AI Provider v2 - BaseProvider Implementation
26
- * Supports official AI-SDK integration with all Mistral models
17
+ * Mistral AI Provider direct HTTP, no AI SDK.
18
+ *
19
+ * OpenAI-compatible chat completions at api.mistral.ai/v1. All request/stream/
20
+ * tool-loop orchestration lives in `OpenAIChatCompletionsProvider`; this class
21
+ * only declares configuration and the provider-specific error mapping.
22
+ *
23
+ * Mistral's `/chat/completions` accepts `response_format: { type:
24
+ * "json_schema" }` on current models (mistral-small-2506 and newer), so no
25
+ * structured-output downgrade is needed — the base client's default
26
+ * pass-through is correct.
27
+ *
28
+ * @see https://docs.mistral.ai/api/
27
29
  */
28
- export class MistralProvider extends BaseProvider {
29
- model;
30
+ export class MistralProvider extends OpenAIChatCompletionsProvider {
30
31
  constructor(modelName, sdk, _region, credentials) {
31
- // Type guard for NeuroLink parameter validation
32
- const validatedNeurolink = isNeuroLink(sdk) ? sdk : undefined;
33
- super(modelName, "mistral", validatedNeurolink);
34
- // Initialize Mistral model with API key validation and proxy support
35
- const apiKey = credentials?.apiKey ?? getMistralApiKey();
36
- const mistral = createMistral({
37
- apiKey: apiKey,
38
- fetch: createProxyFetch(),
39
- });
40
- this.model = mistral(this.modelName);
41
- logger.debug("Mistral Provider v2 initialized", {
32
+ // Trim the override before applying precedence. A blank/whitespace
33
+ // `credentials.apiKey` must NOT bypass `getMistralApiKey()` that would
34
+ // build a client with an unusable bearer token and fail at request time
35
+ // with a confusing 401 instead of at construction time.
36
+ const overrideApiKey = credentials?.apiKey?.trim();
37
+ const apiKey = overrideApiKey && overrideApiKey.length > 0
38
+ ? overrideApiKey
39
+ : getMistralApiKey();
40
+ // Treat blank/whitespace overrides as unset so an empty
41
+ // `credentials.baseURL` or `MISTRAL_BASE_URL=` cannot silently override
42
+ // the default with "" (mirrors the apiKey precedence above).
43
+ const baseURL = credentials?.baseURL?.trim() ||
44
+ process.env.MISTRAL_BASE_URL?.trim() ||
45
+ MISTRAL_DEFAULT_BASE_URL;
46
+ super("mistral", modelName, sdk, { baseURL, apiKey });
47
+ logger.debug("Mistral Provider initialized", {
42
48
  modelName: this.modelName,
43
49
  providerName: this.providerName,
50
+ baseURL: redactUrlCredentials(this.config.baseURL),
44
51
  });
45
52
  }
46
- // generate() method is inherited from BaseProvider; this provider uses the base implementation for generation with tools
47
- async executeStream(options, _analysisSchema) {
48
- this.validateStreamOptions(options);
49
- const startTime = Date.now();
50
- const timeout = this.getTimeout(options);
51
- const timeoutController = createTimeoutController(timeout, this.providerName, "stream");
52
- try {
53
- // Get tools - options.tools is pre-merged by BaseProvider.stream()
54
- const shouldUseTools = !options.disableTools && this.supportsTools();
55
- const tools = shouldUseTools
56
- ? options.tools || (await this.getAllTools())
57
- : {};
58
- // Build message array from options with multimodal support
59
- // Using protected helper from BaseProvider to eliminate code duplication
60
- const messages = await this.buildMessagesForStream(options);
61
- const model = await this.getAISDKModelWithMiddleware(options); // This is where network connection happens!
62
- // Reviewer follow-up: capture upstream provider errors via onError
63
- // so the post-stream NoOutput sentinel carries the real cause.
64
- let capturedProviderError;
65
- const result = await streamText({
66
- model,
67
- messages: messages,
68
- temperature: options.temperature,
69
- maxOutputTokens: options.maxTokens, // No default limit - unlimited unless specified
70
- tools,
71
- stopWhen: stepCountIs(options.maxSteps || DEFAULT_MAX_STEPS),
72
- toolChoice: resolveToolChoice(options, tools, shouldUseTools),
73
- abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
74
- experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
75
- experimental_repairToolCall: this.getToolCallRepairFn(options),
76
- onError: (event) => {
77
- capturedProviderError = event.error;
78
- logger.error("Mistral: Stream error", {
79
- error: event.error instanceof Error
80
- ? event.error.message
81
- : String(event.error),
82
- });
83
- },
84
- onStepFinish: ({ toolCalls, toolResults }) => {
85
- emitToolEndFromStepFinish(this.neurolink?.getEventEmitter(), toolResults);
86
- this.handleToolExecutionStorage(toolCalls, toolResults, options, new Date()).catch((error) => {
87
- logger.warn("[MistralProvider] Failed to store tool executions", {
88
- provider: this.providerName,
89
- error: error instanceof Error ? error.message : String(error),
90
- });
91
- });
92
- },
93
- });
94
- timeoutController?.cleanup();
95
- // Transform string stream to content object stream using BaseProvider method
96
- const transformedStream = this.createTextStream(result, () => capturedProviderError);
97
- // Create analytics promise that resolves after stream completion
98
- const analyticsPromise = streamAnalyticsCollector.createAnalytics(this.providerName, this.modelName, toAnalyticsStreamResult(result), Date.now() - startTime, {
99
- requestId: `mistral-stream-${Date.now()}`,
100
- streamingMode: true,
101
- });
102
- return {
103
- stream: transformedStream,
104
- provider: this.providerName,
105
- model: this.modelName,
106
- analytics: analyticsPromise,
107
- metadata: {
108
- startTime,
109
- streamId: `mistral-${Date.now()}`,
110
- },
111
- };
112
- }
113
- catch (error) {
114
- timeoutController?.cleanup();
115
- throw this.handleProviderError(error);
116
- }
117
- }
118
- // ===================
119
- // ABSTRACT METHOD IMPLEMENTATIONS
120
- // ===================
53
+ // ===========================================================================
54
+ // Abstract hooks (required)
55
+ // ===========================================================================
121
56
  getProviderName() {
122
- return this.providerName;
57
+ return "mistral";
123
58
  }
124
59
  getDefaultModel() {
125
60
  return getDefaultMistralModel();
126
61
  }
127
- /**
128
- * Returns the Vercel AI SDK model instance for Mistral
129
- */
130
- getAISDKModel() {
131
- return this.model;
132
- }
133
62
  formatProviderError(error) {
134
63
  if (error instanceof TimeoutError) {
135
64
  return new NetworkError(`Request timed out: ${error.message}`, "mistral");
@@ -139,35 +68,31 @@ export class MistralProvider extends BaseProvider {
139
68
  ? errorRecord.message
140
69
  : "Unknown error";
141
70
  if (message.includes("API_KEY_INVALID") ||
142
- message.includes("Invalid API key")) {
71
+ message.includes("Invalid API key") ||
72
+ message.includes("Unauthorized") ||
73
+ message.includes("401")) {
143
74
  return new AuthenticationError("Invalid Mistral API key. Please check your MISTRAL_API_KEY environment variable.", "mistral");
144
75
  }
145
- if (message.includes("Rate limit exceeded")) {
76
+ if (message.includes("rate limit") ||
77
+ message.includes("Rate limit") ||
78
+ message.includes("429")) {
146
79
  return new RateLimitError("Mistral rate limit exceeded", "mistral");
147
80
  }
81
+ if (message.includes("model_not_found") || message.includes("404")) {
82
+ return new InvalidModelError(`Mistral model '${this.modelName}' not found.`, "mistral");
83
+ }
148
84
  return new ProviderError(`Mistral error: ${message}`, "mistral");
149
85
  }
150
- /**
151
- * Validate provider configuration
152
- */
153
- async validateConfiguration() {
154
- try {
155
- getMistralApiKey();
156
- return true;
157
- }
158
- catch {
159
- return false;
160
- }
86
+ // ===========================================================================
87
+ // Optional hooks
88
+ // ===========================================================================
89
+ getFallbackModelName() {
90
+ return MistralModels.MISTRAL_SMALL_2506;
161
91
  }
162
- /**
163
- * Get provider-specific configuration
164
- */
165
- getConfiguration() {
166
- return {
167
- provider: this.providerName,
168
- model: this.modelName,
169
- defaultModel: getDefaultMistralModel(),
170
- };
92
+ getFallbackModels() {
93
+ return [
94
+ MistralModels.MISTRAL_SMALL_2506,
95
+ MistralModels.MISTRAL_LARGE_LATEST,
96
+ ];
171
97
  }
172
98
  }
173
- export default MistralProvider;
@@ -1,60 +1,75 @@
1
- import type { ZodType } from "zod";
2
1
  import { AIProviderName } from "../constants/enums.js";
3
- import { BaseProvider } from "../core/baseProvider.js";
4
- import type { StreamOptions, StreamResult } from "../types/index.js";
5
- import type { LanguageModel, Schema } from "../types/index.js";
2
+ import type { NeurolinkCredentials } from "../types/index.js";
3
+ import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
6
4
  /**
7
- * OpenRouter Provider - BaseProvider Implementation
8
- * Provides access to 300+ models from 60+ providers via OpenRouter unified gateway
5
+ * OpenRouter Provider direct HTTP, no AI SDK.
6
+ *
7
+ * OpenAI-compatible unified gateway to 300+ models from 60+ providers. All
8
+ * request/stream/tool-loop orchestration lives in
9
+ * `OpenAIChatCompletionsProvider`; this class declares configuration plus the
10
+ * OpenRouter-specific behaviour:
11
+ *
12
+ * 1. Attribution headers — optional `HTTP-Referer` / `X-Title` (from
13
+ * `OPENROUTER_REFERER` / `OPENROUTER_APP_NAME`) are merged into every
14
+ * request via `getAuthHeaders` so usage shows up on the openrouter.ai
15
+ * activity dashboard.
16
+ * 2. Per-model tool gating — OpenRouter proxies many models with varying
17
+ * tool support, so `supportsTools()` consults a cached capability set
18
+ * (populated by `cacheModelCapabilities()`) and falls back to a
19
+ * conservative known-capable pattern list.
20
+ * 3. Dynamic model discovery — `getAvailableModels()` fetches the live
21
+ * `/models` list (10-minute cache) with a hardcoded fallback.
22
+ *
23
+ * @see https://openrouter.ai/docs
9
24
  */
10
- export declare class OpenRouterProvider extends BaseProvider {
11
- private model;
12
- private openRouterClient;
13
- private config;
25
+ export declare class OpenRouterProvider extends OpenAIChatCompletionsProvider {
26
+ private readonly referer?;
27
+ private readonly appName?;
14
28
  private static modelsCache;
15
29
  private static modelsCacheTime;
16
30
  private static readonly MODELS_CACHE_DURATION;
17
31
  private static toolCapableModels;
18
32
  private static capabilitiesCached;
19
- constructor(modelName?: string, sdk?: unknown, _region?: string, credentials?: {
20
- apiKey?: string;
21
- baseURL?: string;
22
- });
33
+ constructor(modelName?: string, sdk?: unknown, _region?: string, credentials?: NeurolinkCredentials["openrouter"]);
23
34
  protected getProviderName(): AIProviderName;
24
35
  protected getDefaultModel(): string;
36
+ protected formatProviderError(error: unknown): Error;
37
+ protected getFallbackModelName(): string;
25
38
  /**
26
- * Returns the Vercel AI SDK model instance for OpenRouter
39
+ * Attribution headers are merged into every request alongside the bearer
40
+ * token so OpenRouter can attribute usage on its activity dashboard.
27
41
  */
28
- protected getAISDKModel(): LanguageModel;
29
- formatProviderError(error: unknown): Error;
42
+ protected getAuthHeaders(): Record<string, string>;
30
43
  /**
31
- * OpenRouter supports tools for compatible models
32
- * Checks cached model capabilities or uses known patterns as fallback
44
+ * OpenRouter proxies models with varying tool support. Use cached
45
+ * capabilities when available (populated by `cacheModelCapabilities()`),
46
+ * otherwise fall back to a conservative known-capable pattern list and
47
+ * disable tools for unknown models.
33
48
  */
34
49
  supportsTools(): boolean;
35
50
  /**
36
- * Provider-specific streaming implementation
37
- * Note: This is only used when tools are disabled
51
+ * Models/capabilities endpoint, derived from the configured `baseURL` so a
52
+ * custom OpenRouter-compatible gateway is honoured for discovery too.
38
53
  */
39
- protected executeStream(options: StreamOptions, analysisSchema?: ZodType | Schema<unknown>): Promise<StreamResult>;
54
+ private getModelsUrl;
40
55
  /**
41
- * Get available models from OpenRouter API
42
- * Dynamically fetches from /api/v1/models endpoint with caching and fallback
56
+ * Get available models from the OpenRouter `/models` endpoint, with a
57
+ * 10-minute cache and a hardcoded fallback when the fetch fails.
43
58
  */
44
59
  getAvailableModels(): Promise<string[]>;
45
60
  /**
46
- * Fetch available models from OpenRouter API /api/v1/models endpoint
61
+ * Fetch available models from the OpenRouter `/models` endpoint.
47
62
  * @private
48
63
  */
49
64
  private fetchModelsFromAPI;
50
65
  /**
51
- * Type guard to validate the models API response structure
66
+ * Type guard to validate the models API response structure.
52
67
  * @private
53
68
  */
54
69
  private isValidModelsResponse;
55
70
  /**
56
- * Fetch and cache model capabilities from OpenRouter API
57
- * Call this to enable accurate tool support detection
71
+ * Fetch and cache model capabilities from the OpenRouter `/models` endpoint.
72
+ * Call this to enable accurate per-model tool support detection.
58
73
  */
59
74
  cacheModelCapabilities(): Promise<void>;
60
75
  }