@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.
- package/CHANGELOG.md +4 -0
- package/dist/browser/neurolink.min.js +408 -422
- package/dist/lib/providers/deepseek.d.ts +39 -21
- package/dist/lib/providers/deepseek.js +68 -217
- package/dist/lib/providers/perplexity.d.ts +9 -19
- package/dist/lib/providers/perplexity.js +25 -129
- package/dist/providers/deepseek.d.ts +39 -21
- package/dist/providers/deepseek.js +68 -217
- package/dist/providers/perplexity.d.ts +9 -19
- package/dist/providers/perplexity.js +25 -129
- package/package.json +1 -1
|
@@ -1,29 +1,47 @@
|
|
|
1
1
|
import type { AIProviderName } from "../constants/enums.js";
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
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
|
-
*
|
|
8
|
-
*
|
|
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
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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 {
|
|
13
|
-
import {
|
|
14
|
-
|
|
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
|
-
*
|
|
57
|
-
*
|
|
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
|
|
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`
|
|
68
|
-
// would build a client with an unusable bearer token and fail at
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
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,16 +93,30 @@ export class DeepSeekProvider extends BaseProvider {
|
|
|
256
93
|
}
|
|
257
94
|
return new ProviderError(`DeepSeek error: ${message}`, "deepseek");
|
|
258
95
|
}
|
|
259
|
-
|
|
260
|
-
|
|
96
|
+
// ===========================================================================
|
|
97
|
+
// Optional hooks — provider-specific quirks
|
|
98
|
+
// ===========================================================================
|
|
99
|
+
getFallbackModelName() {
|
|
100
|
+
return DeepSeekModels.DEEPSEEK_CHAT;
|
|
261
101
|
}
|
|
262
|
-
|
|
263
|
-
return
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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;
|
|
@@ -1,33 +1,23 @@
|
|
|
1
1
|
import type { AIProviderName } from "../constants/enums.js";
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
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
|
|
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
|
|
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 {
|
|
13
|
-
import {
|
|
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
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
40
|
+
return "perplexity";
|
|
139
41
|
}
|
|
140
42
|
getDefaultModel() {
|
|
141
43
|
return getDefaultPerplexityModel();
|
|
142
44
|
}
|
|
143
|
-
|
|
144
|
-
return
|
|
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,16 +72,4 @@ 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;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "9.68.
|
|
3
|
+
"version": "9.68.13",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "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 applications with 21+ providers: OpenAI, Anthropic, Google AI Studio, Google Vertex, AWS Bedrock, Azure OpenAI, Mistral, LiteLLM, SageMaker, Hugging Face, Ollama, OpenAI-compatible, OpenRouter, DeepSeek, NVIDIA NIM, LM Studio, llama.cpp, plus voice (OpenAI TTS, ElevenLabs, Deepgram, Azure Speech).",
|
|
6
6
|
"author": {
|