@juspay/neurolink 9.68.11 → 9.68.13

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,29 +1,47 @@
1
1
  import type { AIProviderName } from "../constants/enums.js";
2
- import { BaseProvider } from "../core/baseProvider.js";
3
- import type { NeurolinkCredentials, StreamOptions, StreamResult, ValidationSchema } from "../types/index.js";
4
- import type { LanguageModel } from "../types/index.js";
2
+ import type { NeurolinkCredentials, OpenAICompatResponseFormat } from "../types/index.js";
3
+ import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
5
4
  /**
6
- * DeepSeek Provider
7
- * OpenAI-compatible chat completions; supports deepseek-chat (V3) and
8
- * deepseek-reasoner (R1, exposes reasoning_content).
5
+ * DeepSeek Provider — direct HTTP, no AI SDK.
6
+ *
7
+ * OpenAI-compatible chat completions at api.deepseek.com (deepseek-chat /
8
+ * deepseek-reasoner). All request/stream/tool-loop orchestration lives in
9
+ * `OpenAIChatCompletionsProvider`; this class declares configuration and
10
+ * provider-specific quirks:
11
+ *
12
+ * 1. Structured-output downgrade — DeepSeek rejects `response_format:
13
+ * { type: "json_schema" }` ("This response_format type is unavailable
14
+ * now"), so `adjustResponseFormat` downgrades it to `json_object` —
15
+ * matching the `supportsStructuredOutputs: false` behaviour of the
16
+ * `@ai-sdk/openai-compatible` path this migration replaced. The base
17
+ * client injects the literal "json" word the API requires for that mode.
18
+ *
19
+ * 2. Reasoning support — the opt-in `thinking` request param (non-reasoner
20
+ * chat models) and `reasoning_content` surfacing (deepseek-reasoner / R1)
21
+ * both require plumbing the thinking signal and the reasoning delta
22
+ * through the native base client. That plumbing isn't in place yet, so
23
+ * neither is wired here; it is tracked as a base-client follow-up. All
24
+ * other behavior is preserved.
25
+ *
26
+ * @see https://api-docs.deepseek.com
9
27
  */
10
- export declare class DeepSeekProvider extends BaseProvider {
11
- private model;
12
- private apiKey;
13
- private baseURL;
28
+ export declare class DeepSeekProvider extends OpenAIChatCompletionsProvider {
14
29
  constructor(modelName?: string, sdk?: unknown, _region?: string, credentials?: NeurolinkCredentials["deepseek"]);
15
- protected executeStream(options: StreamOptions, _analysisSchema?: ValidationSchema): Promise<StreamResult>;
16
- private executeStreamInner;
17
30
  protected getProviderName(): AIProviderName;
18
31
  protected getDefaultModel(): string;
19
- protected getAISDKModel(): LanguageModel;
20
32
  protected formatProviderError(error: unknown): Error;
21
- validateConfiguration(): Promise<boolean>;
22
- getConfiguration(): {
23
- provider: AIProviderName;
24
- model: string;
25
- defaultModel: string;
26
- baseURL: string;
27
- };
33
+ protected getFallbackModelName(): string;
34
+ protected getFallbackModels(): string[];
35
+ /**
36
+ * DeepSeek's /chat/completions rejects `response_format: { type:
37
+ * "json_schema" }` outright ("This response_format type is unavailable
38
+ * now"). The `@ai-sdk/openai-compatible` provider this migration replaced
39
+ * ran with `supportsStructuredOutputs: false`, which downgraded structured-
40
+ * output requests to `{ type: "json_object" }`. Replicate that downgrade so
41
+ * `generate({ schema })` keeps working. (DeepSeek's json_object mode also
42
+ * requires the word "json" somewhere in the messages; the base client's
43
+ * `ensureJsonWordInBody` injects a minimal instruction when the prompt
44
+ * lacks it.)
45
+ */
46
+ protected adjustResponseFormat(rf: OpenAICompatResponseFormat | undefined, _modelId: string): OpenAICompatResponseFormat | undefined;
28
47
  }
29
- export default DeepSeekProvider;
@@ -1,50 +1,10 @@
1
- import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
2
1
  import { DeepSeekModels } from "../constants/enums.js";
3
- import { BaseProvider } from "../core/baseProvider.js";
4
- import { DEFAULT_MAX_STEPS } from "../core/constants.js";
5
- import { streamAnalyticsCollector } from "../core/streamAnalytics.js";
6
- import { isNeuroLink } from "../neurolink.js";
7
- import { createProxyFetch, maskProxyUrl } from "../proxy/proxyFetch.js";
8
- import { tracers, ATTR, withClientStreamSpan } from "../telemetry/index.js";
9
2
  import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../types/index.js";
10
3
  import { logger } from "../utils/logger.js";
11
4
  import { createDeepSeekConfig, getProviderModel, validateApiKey, } from "../utils/providerConfig.js";
12
- import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
13
- import { emitToolEndFromStepFinish } from "../utils/toolEndEmitter.js";
14
- import { resolveToolChoice } from "../utils/toolChoice.js";
15
- import { toAnalyticsStreamResult } from "./providerTypeUtils.js";
16
- import { stepCountIs } from "../utils/tool.js";
17
- import { streamText } from "../utils/generation.js";
18
- const makeLoggingFetch = (provider) => {
19
- const base = createProxyFetch();
20
- return (async (input, init) => {
21
- const url = typeof input === "string"
22
- ? input
23
- : input instanceof URL
24
- ? input.toString()
25
- : input.url;
26
- const reqSize = init?.body && typeof init.body === "string" ? init.body.length : 0;
27
- const response = await base(input, init);
28
- if (!response.ok) {
29
- // Don't fall back to the raw URL — that would defeat the redaction.
30
- const safeUrl = maskProxyUrl(url) ?? "<redacted>";
31
- if (process.env.NEUROLINK_DEBUG_HTTP === "1") {
32
- const clone = response.clone();
33
- const body = await clone.text().catch(() => "<unreadable>");
34
- logger.warn(`[${provider}] upstream ${response.status}`, {
35
- url: safeUrl,
36
- body: body.slice(0, 800),
37
- reqSize,
38
- });
39
- }
40
- else {
41
- logger.warn(`[${provider}] upstream ${response.status} url=${safeUrl} reqSize=${reqSize}`);
42
- }
43
- }
44
- return response;
45
- });
46
- };
47
- const DEEPSEEK_DEFAULT_BASE_URL = "https://api.deepseek.com";
5
+ import { TimeoutError } from "../utils/timeout.js";
6
+ import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
7
+ const DEEPSEEK_BASE_URL = "https://api.deepseek.com";
48
8
  const getDeepSeekApiKey = () => {
49
9
  return validateApiKey(createDeepSeekConfig());
50
10
  };
@@ -52,184 +12,61 @@ const getDefaultDeepSeekModel = () => {
52
12
  return getProviderModel("DEEPSEEK_MODEL", DeepSeekModels.DEEPSEEK_CHAT);
53
13
  };
54
14
  /**
55
- * DeepSeek Provider
56
- * OpenAI-compatible chat completions; supports deepseek-chat (V3) and
57
- * deepseek-reasoner (R1, exposes reasoning_content).
15
+ * DeepSeek Provider — direct HTTP, no AI SDK.
16
+ *
17
+ * OpenAI-compatible chat completions at api.deepseek.com (deepseek-chat /
18
+ * deepseek-reasoner). All request/stream/tool-loop orchestration lives in
19
+ * `OpenAIChatCompletionsProvider`; this class declares configuration and
20
+ * provider-specific quirks:
21
+ *
22
+ * 1. Structured-output downgrade — DeepSeek rejects `response_format:
23
+ * { type: "json_schema" }` ("This response_format type is unavailable
24
+ * now"), so `adjustResponseFormat` downgrades it to `json_object` —
25
+ * matching the `supportsStructuredOutputs: false` behaviour of the
26
+ * `@ai-sdk/openai-compatible` path this migration replaced. The base
27
+ * client injects the literal "json" word the API requires for that mode.
28
+ *
29
+ * 2. Reasoning support — the opt-in `thinking` request param (non-reasoner
30
+ * chat models) and `reasoning_content` surfacing (deepseek-reasoner / R1)
31
+ * both require plumbing the thinking signal and the reasoning delta
32
+ * through the native base client. That plumbing isn't in place yet, so
33
+ * neither is wired here; it is tracked as a base-client follow-up. All
34
+ * other behavior is preserved.
35
+ *
36
+ * @see https://api-docs.deepseek.com
58
37
  */
59
- export class DeepSeekProvider extends BaseProvider {
60
- model;
61
- apiKey;
62
- baseURL;
38
+ export class DeepSeekProvider extends OpenAIChatCompletionsProvider {
63
39
  constructor(modelName, sdk, _region, credentials) {
64
- const validatedNeurolink = isNeuroLink(sdk) ? sdk : undefined;
65
- super(modelName, "deepseek", validatedNeurolink);
66
40
  // Trim the override before applying precedence. A blank/whitespace
67
- // `credentials.apiKey` should NOT bypass `getDeepSeekApiKey()` — that
68
- // would build a client with an unusable bearer token and fail at request
69
- // time with a confusing 401 instead of at construction time.
41
+ // `credentials.apiKey` must NOT bypass `getDeepSeekApiKey()` — that
42
+ // would build a client with an unusable bearer token and fail at
43
+ // request time with a confusing 401 instead of at construction time.
70
44
  const overrideApiKey = credentials?.apiKey?.trim();
71
- this.apiKey =
72
- overrideApiKey && overrideApiKey.length > 0
73
- ? overrideApiKey
74
- : getDeepSeekApiKey();
75
- this.baseURL =
76
- credentials?.baseURL ??
77
- process.env.DEEPSEEK_BASE_URL ??
78
- DEEPSEEK_DEFAULT_BASE_URL;
79
- // We deliberately use `@ai-sdk/openai-compatible` rather than
80
- // `@ai-sdk/openai`. Two upstream behaviors of `@ai-sdk/openai` break us:
81
- // 1. It always sends `response_format: { type: "json_schema" }` when a
82
- // schema is provided. DeepSeek's API rejects that with the literal
83
- // message "This response_format type is unavailable now".
84
- // 2. It does not parse the `reasoning_content` field that
85
- // `deepseek-reasoner` emits, so chain-of-thought is silently dropped.
86
- // `@ai-sdk/openai-compatible` honors `supportsStructuredOutputs: false`
87
- // (falls back to `{ type: "json_object" }` and injects the schema into
88
- // the prompt) and parses both `choice.message.reasoning_content` and
89
- // `delta.reasoning_content` into the SDK-standard `reasoning` part.
90
- const deepseek = createOpenAICompatible({
91
- name: "deepseek",
92
- apiKey: this.apiKey,
93
- baseURL: this.baseURL,
94
- fetch: makeLoggingFetch("deepseek"),
95
- supportsStructuredOutputs: false,
96
- includeUsage: true,
97
- // DeepSeek's `response_format: { type: "json_object" }` requires the
98
- // prompt to literally contain the word "json" — otherwise the API
99
- // rejects with: "Prompt must contain the word 'json' in some form to
100
- // use 'response_format' of type 'json_object'." The OpenAI-compatible
101
- // SDK fallback path (used because supportsStructuredOutputs is false)
102
- // does not inject this guidance itself, so we prepend a system
103
- // message when it's missing. No-op for non-JSON requests.
104
- transformRequestBody: (body) => {
105
- const rf = body
106
- .response_format;
107
- if (rf?.type !== "json_object") {
108
- return body;
109
- }
110
- const messages = body
111
- .messages;
112
- if (!Array.isArray(messages)) {
113
- return body;
114
- }
115
- const containsJsonWord = messages.some((m) => {
116
- const c = m?.content;
117
- if (typeof c === "string") {
118
- return /\bjson\b/i.test(c);
119
- }
120
- if (Array.isArray(c)) {
121
- return c.some((part) => typeof part?.text === "string" &&
122
- /\bjson\b/i.test(part.text));
123
- }
124
- return false;
125
- });
126
- if (containsJsonWord) {
127
- return body;
128
- }
129
- return {
130
- ...body,
131
- messages: [
132
- {
133
- role: "system",
134
- content: "Respond with valid JSON that satisfies the requested schema. Output JSON only — no prose, no markdown fencing.",
135
- },
136
- ...messages,
137
- ],
138
- };
139
- },
140
- });
141
- this.model = deepseek.chatModel(this.modelName);
45
+ const apiKey = overrideApiKey && overrideApiKey.length > 0
46
+ ? overrideApiKey
47
+ : getDeepSeekApiKey();
48
+ // Treat blank/whitespace overrides as unset so an empty
49
+ // `credentials.baseURL` or `DEEPSEEK_BASE_URL=` cannot silently override
50
+ // the default with "" (mirrors the apiKey precedence above).
51
+ const baseURL = credentials?.baseURL?.trim() ||
52
+ process.env.DEEPSEEK_BASE_URL?.trim() ||
53
+ DEEPSEEK_BASE_URL;
54
+ super("deepseek", modelName, sdk, { baseURL, apiKey });
142
55
  logger.debug("DeepSeek Provider initialized", {
143
56
  modelName: this.modelName,
144
57
  providerName: this.providerName,
145
- baseURL: this.baseURL,
58
+ baseURL: this.config.baseURL,
146
59
  });
147
60
  }
148
- async executeStream(options, _analysisSchema) {
149
- return withClientStreamSpan({
150
- name: "neurolink.provider.stream",
151
- tracer: tracers.provider,
152
- attributes: {
153
- [ATTR.GEN_AI_SYSTEM]: "deepseek",
154
- [ATTR.GEN_AI_MODEL]: this.modelName,
155
- [ATTR.GEN_AI_OPERATION]: "stream",
156
- [ATTR.NL_STREAM_MODE]: true,
157
- },
158
- }, async () => this.executeStreamInner(options), (r) => r.stream, (r, wrapped) => ({ ...r, stream: wrapped }));
159
- }
160
- async executeStreamInner(options) {
161
- this.validateStreamOptions(options);
162
- const startTime = Date.now();
163
- const timeout = this.getTimeout(options);
164
- const timeoutController = createTimeoutController(timeout, this.providerName, "stream");
165
- try {
166
- const shouldUseTools = !options.disableTools && this.supportsTools();
167
- const tools = shouldUseTools
168
- ? options.tools || (await this.getAllTools())
169
- : {};
170
- const messages = await this.buildMessagesForStream(options);
171
- const model = await this.getAISDKModelWithMiddleware(options);
172
- const isReasoner = this.modelName === DeepSeekModels.DEEPSEEK_REASONER;
173
- const result = await streamText({
174
- model,
175
- messages,
176
- temperature: options.temperature,
177
- maxOutputTokens: options.maxTokens,
178
- tools,
179
- stopWhen: stepCountIs(options.maxSteps || DEFAULT_MAX_STEPS),
180
- toolChoice: resolveToolChoice(options, tools, shouldUseTools),
181
- abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
182
- // DeepSeek's `thinking` mode is opt-in for chat models — only enable
183
- // when the caller explicitly asks for it via `thinkingConfig.enabled`.
184
- // Forcing it on every chat call would trigger extended reasoning for
185
- // simple prompts (and ignore reasoner models which control it natively).
186
- providerOptions: !isReasoner && options.thinkingConfig?.enabled
187
- ? {
188
- openai: {
189
- thinking: { type: "enabled" },
190
- },
191
- }
192
- : undefined,
193
- experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
194
- experimental_repairToolCall: this.getToolCallRepairFn(options),
195
- onStepFinish: ({ toolCalls, toolResults }) => {
196
- emitToolEndFromStepFinish(this.neurolink?.getEventEmitter(), toolResults);
197
- this.handleToolExecutionStorage(toolCalls, toolResults, options, new Date()).catch((error) => {
198
- logger.warn("[DeepSeekProvider] Failed to store tool executions", {
199
- provider: this.providerName,
200
- error: error instanceof Error ? error.message : String(error),
201
- });
202
- });
203
- },
204
- });
205
- timeoutController?.cleanup();
206
- const transformedStream = this.createTextStream(result);
207
- const analyticsPromise = streamAnalyticsCollector.createAnalytics(this.providerName, this.modelName, toAnalyticsStreamResult(result), Date.now() - startTime, {
208
- requestId: `deepseek-stream-${Date.now()}`,
209
- streamingMode: true,
210
- });
211
- return {
212
- stream: transformedStream,
213
- provider: this.providerName,
214
- model: this.modelName,
215
- analytics: analyticsPromise,
216
- metadata: { startTime, streamId: `deepseek-${Date.now()}` },
217
- };
218
- }
219
- catch (error) {
220
- timeoutController?.cleanup();
221
- throw this.handleProviderError(error);
222
- }
223
- }
61
+ // ===========================================================================
62
+ // Abstract hooks (required)
63
+ // ===========================================================================
224
64
  getProviderName() {
225
- return this.providerName;
65
+ return "deepseek";
226
66
  }
227
67
  getDefaultModel() {
228
68
  return getDefaultDeepSeekModel();
229
69
  }
230
- getAISDKModel() {
231
- return this.model;
232
- }
233
70
  formatProviderError(error) {
234
71
  if (error instanceof TimeoutError) {
235
72
  return new NetworkError(`Request timed out: ${error.message}`, "deepseek");
@@ -256,17 +93,31 @@ export class DeepSeekProvider extends BaseProvider {
256
93
  }
257
94
  return new ProviderError(`DeepSeek error: ${message}`, "deepseek");
258
95
  }
259
- async validateConfiguration() {
260
- return typeof this.apiKey === "string" && this.apiKey.trim().length > 0;
96
+ // ===========================================================================
97
+ // Optional hooks provider-specific quirks
98
+ // ===========================================================================
99
+ getFallbackModelName() {
100
+ return DeepSeekModels.DEEPSEEK_CHAT;
261
101
  }
262
- getConfiguration() {
263
- return {
264
- provider: this.providerName,
265
- model: this.modelName,
266
- defaultModel: getDefaultDeepSeekModel(),
267
- baseURL: this.baseURL,
268
- };
102
+ getFallbackModels() {
103
+ return [DeepSeekModels.DEEPSEEK_CHAT, DeepSeekModels.DEEPSEEK_REASONER];
104
+ }
105
+ /**
106
+ * DeepSeek's /chat/completions rejects `response_format: { type:
107
+ * "json_schema" }` outright ("This response_format type is unavailable
108
+ * now"). The `@ai-sdk/openai-compatible` provider this migration replaced
109
+ * ran with `supportsStructuredOutputs: false`, which downgraded structured-
110
+ * output requests to `{ type: "json_object" }`. Replicate that downgrade so
111
+ * `generate({ schema })` keeps working. (DeepSeek's json_object mode also
112
+ * requires the word "json" somewhere in the messages; the base client's
113
+ * `ensureJsonWordInBody` injects a minimal instruction when the prompt
114
+ * lacks it.)
115
+ */
116
+ adjustResponseFormat(rf, _modelId) {
117
+ if (rf?.type === "json_schema") {
118
+ return { type: "json_object" };
119
+ }
120
+ return rf;
269
121
  }
270
122
  }
271
- export default DeepSeekProvider;
272
123
  //# sourceMappingURL=deepseek.js.map
@@ -1,33 +1,23 @@
1
1
  import type { AIProviderName } from "../constants/enums.js";
2
- import { BaseProvider } from "../core/baseProvider.js";
3
- import type { NeurolinkCredentials, StreamOptions, StreamResult, ValidationSchema } from "../types/index.js";
4
- import type { LanguageModel } from "../types/index.js";
2
+ import type { NeurolinkCredentials } from "../types/index.js";
3
+ import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
5
4
  /**
6
- * Perplexity Provider
5
+ * Perplexity Provider — direct HTTP, no AI SDK.
7
6
  *
8
7
  * Sonar models with built-in web grounding. OpenAI-compatible chat
9
8
  * completions at api.perplexity.ai. Best for queries that need fresh
10
9
  * web context (search-augmented answers + citations).
11
10
  *
11
+ * All request/stream/tool-loop orchestration lives in
12
+ * `OpenAIChatCompletionsProvider`; this class only declares configuration
13
+ * and provider-specific error mapping.
14
+ *
12
15
  * @see https://docs.perplexity.ai/api-reference/chat-completions
13
16
  */
14
- export declare class PerplexityProvider extends BaseProvider {
15
- private model;
16
- private apiKey;
17
- private baseURL;
17
+ export declare class PerplexityProvider extends OpenAIChatCompletionsProvider {
18
18
  constructor(modelName?: string, sdk?: unknown, _region?: string, credentials?: NeurolinkCredentials["perplexity"]);
19
- protected executeStream(options: StreamOptions, _analysisSchema?: ValidationSchema): Promise<StreamResult>;
20
- private executeStreamInner;
21
19
  protected getProviderName(): AIProviderName;
22
20
  protected getDefaultModel(): string;
23
- protected getAISDKModel(): LanguageModel;
21
+ protected getFallbackModels(): string[];
24
22
  protected formatProviderError(error: unknown): Error;
25
- validateConfiguration(): Promise<boolean>;
26
- getConfiguration(): {
27
- provider: AIProviderName;
28
- model: string;
29
- defaultModel: string;
30
- baseURL: string;
31
- };
32
23
  }
33
- export default PerplexityProvider;
@@ -1,147 +1,55 @@
1
- import { createOpenAI } from "@ai-sdk/openai";
2
1
  import { PerplexityModels } from "../constants/enums.js";
3
- import { BaseProvider } from "../core/baseProvider.js";
4
- import { DEFAULT_MAX_STEPS } from "../core/constants.js";
5
- import { streamAnalyticsCollector } from "../core/streamAnalytics.js";
6
- import { isNeuroLink } from "../neurolink.js";
7
- import { createLoggingFetch } from "../utils/loggingFetch.js";
8
- import { tracers, ATTR, withClientStreamSpan } from "../telemetry/index.js";
9
2
  import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../types/index.js";
10
3
  import { logger } from "../utils/logger.js";
11
4
  import { createPerplexityConfig, getProviderModel, validateApiKey, } from "../utils/providerConfig.js";
12
- import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
13
- import { emitToolEndFromStepFinish } from "../utils/toolEndEmitter.js";
14
- import { resolveToolChoice } from "../utils/toolChoice.js";
15
- import { toAnalyticsStreamResult } from "./providerTypeUtils.js";
16
- import { stepCountIs } from "../utils/tool.js";
17
- import { streamText } from "../utils/generation.js";
5
+ import { TimeoutError } from "../utils/timeout.js";
6
+ import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
18
7
  const PERPLEXITY_DEFAULT_BASE_URL = "https://api.perplexity.ai";
19
8
  const getPerplexityApiKey = () => validateApiKey(createPerplexityConfig());
20
9
  const getDefaultPerplexityModel = () => getProviderModel("PERPLEXITY_MODEL", PerplexityModels.SONAR);
21
10
  /**
22
- * Perplexity Provider
11
+ * Perplexity Provider — direct HTTP, no AI SDK.
23
12
  *
24
13
  * Sonar models with built-in web grounding. OpenAI-compatible chat
25
14
  * completions at api.perplexity.ai. Best for queries that need fresh
26
15
  * web context (search-augmented answers + citations).
27
16
  *
17
+ * All request/stream/tool-loop orchestration lives in
18
+ * `OpenAIChatCompletionsProvider`; this class only declares configuration
19
+ * and provider-specific error mapping.
20
+ *
28
21
  * @see https://docs.perplexity.ai/api-reference/chat-completions
29
22
  */
30
- export class PerplexityProvider extends BaseProvider {
31
- model;
32
- apiKey;
33
- baseURL;
23
+ export class PerplexityProvider extends OpenAIChatCompletionsProvider {
34
24
  constructor(modelName, sdk, _region, credentials) {
35
- const validatedNeurolink = isNeuroLink(sdk) ? sdk : undefined;
36
- super(modelName, "perplexity", validatedNeurolink);
37
25
  const overrideApiKey = credentials?.apiKey?.trim();
38
- this.apiKey =
39
- overrideApiKey && overrideApiKey.length > 0
40
- ? overrideApiKey
41
- : getPerplexityApiKey();
42
- this.baseURL =
43
- credentials?.baseURL ??
44
- process.env.PERPLEXITY_BASE_URL ??
45
- PERPLEXITY_DEFAULT_BASE_URL;
46
- const perplexity = createOpenAI({
47
- apiKey: this.apiKey,
48
- baseURL: this.baseURL,
49
- fetch: createLoggingFetch("perplexity"),
50
- });
51
- this.model = perplexity.chat(this.modelName);
26
+ const apiKey = overrideApiKey && overrideApiKey.length > 0
27
+ ? overrideApiKey
28
+ : getPerplexityApiKey();
29
+ const baseURL = credentials?.baseURL?.trim() ||
30
+ process.env.PERPLEXITY_BASE_URL?.trim() ||
31
+ PERPLEXITY_DEFAULT_BASE_URL;
32
+ super("perplexity", modelName, sdk, { baseURL, apiKey });
52
33
  logger.debug("Perplexity Provider initialized", {
53
34
  modelName: this.modelName,
54
35
  providerName: this.providerName,
55
- baseURL: this.baseURL,
36
+ baseURL: this.config.baseURL,
56
37
  });
57
38
  }
58
- async executeStream(options, _analysisSchema) {
59
- return withClientStreamSpan({
60
- name: "neurolink.provider.stream",
61
- tracer: tracers.provider,
62
- attributes: {
63
- [ATTR.GEN_AI_SYSTEM]: "perplexity",
64
- [ATTR.GEN_AI_MODEL]: this.modelName,
65
- [ATTR.GEN_AI_OPERATION]: "stream",
66
- [ATTR.NL_STREAM_MODE]: true,
67
- },
68
- }, async () => this.executeStreamInner(options), (r) => r.stream, (r, wrapped) => ({ ...r, stream: wrapped }));
69
- }
70
- async executeStreamInner(options) {
71
- this.validateStreamOptions(options);
72
- // Resolve per-call credentials first, then fall back to instance-level.
73
- const perCallCreds = options.credentials?.perplexity;
74
- const effectiveApiKey = perCallCreds?.apiKey?.trim() || this.apiKey;
75
- const effectiveBaseURL = perCallCreds?.baseURL || this.baseURL;
76
- const startTime = Date.now();
77
- const timeout = this.getTimeout(options);
78
- const timeoutController = createTimeoutController(timeout, this.providerName, "stream");
79
- try {
80
- // Perplexity Sonar's tool support is limited; default to disabled when
81
- // not explicitly requested by the caller. The web-grounding signal is
82
- // baked into the model itself, not exposed as tool calls.
83
- const shouldUseTools = !options.disableTools && this.supportsTools();
84
- const tools = shouldUseTools
85
- ? options.tools || (await this.getAllTools())
86
- : {};
87
- const messages = await this.buildMessagesForStream(options);
88
- // When per-call credentials differ from instance, build a fresh client.
89
- const hasDifferentCreds = effectiveApiKey !== this.apiKey || effectiveBaseURL !== this.baseURL;
90
- const model = hasDifferentCreds
91
- ? createOpenAI({
92
- apiKey: effectiveApiKey,
93
- baseURL: effectiveBaseURL,
94
- fetch: createLoggingFetch("perplexity"),
95
- }).chat(this.modelName)
96
- : await this.getAISDKModelWithMiddleware(options);
97
- const result = await streamText({
98
- model,
99
- messages,
100
- temperature: options.temperature,
101
- maxOutputTokens: options.maxTokens,
102
- tools,
103
- stopWhen: stepCountIs(options.maxSteps || DEFAULT_MAX_STEPS),
104
- toolChoice: resolveToolChoice(options, tools, shouldUseTools),
105
- abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
106
- experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
107
- experimental_repairToolCall: this.getToolCallRepairFn(options),
108
- onStepFinish: ({ toolCalls, toolResults }) => {
109
- emitToolEndFromStepFinish(this.neurolink?.getEventEmitter(), toolResults);
110
- this.handleToolExecutionStorage(toolCalls, toolResults, options, new Date()).catch((error) => {
111
- logger.warn("[PerplexityProvider] Failed to store tool executions", {
112
- provider: this.providerName,
113
- error: error instanceof Error ? error.message : String(error),
114
- });
115
- });
116
- },
117
- });
118
- timeoutController?.cleanup();
119
- const transformedStream = this.createTextStream(result);
120
- const analyticsPromise = streamAnalyticsCollector.createAnalytics(this.providerName, this.modelName, toAnalyticsStreamResult(result), Date.now() - startTime, {
121
- requestId: `perplexity-stream-${Date.now()}`,
122
- streamingMode: true,
123
- });
124
- return {
125
- stream: transformedStream,
126
- provider: this.providerName,
127
- model: this.modelName,
128
- analytics: analyticsPromise,
129
- metadata: { startTime, streamId: `perplexity-${Date.now()}` },
130
- };
131
- }
132
- catch (error) {
133
- timeoutController?.cleanup();
134
- throw this.handleProviderError(error);
135
- }
136
- }
137
39
  getProviderName() {
138
- return this.providerName;
40
+ return "perplexity";
139
41
  }
140
42
  getDefaultModel() {
141
43
  return getDefaultPerplexityModel();
142
44
  }
143
- getAISDKModel() {
144
- return this.model;
45
+ getFallbackModels() {
46
+ return [
47
+ PerplexityModels.SONAR,
48
+ PerplexityModels.SONAR_PRO,
49
+ PerplexityModels.SONAR_REASONING,
50
+ PerplexityModels.SONAR_REASONING_PRO,
51
+ PerplexityModels.SONAR_DEEP_RESEARCH,
52
+ ];
145
53
  }
146
54
  formatProviderError(error) {
147
55
  if (error instanceof TimeoutError) {
@@ -164,17 +72,5 @@ export class PerplexityProvider extends BaseProvider {
164
72
  }
165
73
  return new ProviderError(`Perplexity error: ${message}`, "perplexity");
166
74
  }
167
- async validateConfiguration() {
168
- return typeof this.apiKey === "string" && this.apiKey.trim().length > 0;
169
- }
170
- getConfiguration() {
171
- return {
172
- provider: this.providerName,
173
- model: this.modelName,
174
- defaultModel: getDefaultPerplexityModel(),
175
- baseURL: this.baseURL,
176
- };
177
- }
178
75
  }
179
- export default PerplexityProvider;
180
76
  //# sourceMappingURL=perplexity.js.map