@juspay/neurolink 9.6.0 → 9.7.0
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 +6 -0
- package/dist/adapters/video/vertexVideoHandler.js +3 -3
- package/dist/cli/loop/optionsSchema.d.ts +1 -1
- package/dist/cli/loop/optionsSchema.js +4 -0
- package/dist/core/analytics.js +11 -4
- package/dist/core/baseProvider.d.ts +6 -0
- package/dist/core/baseProvider.js +83 -14
- package/dist/core/conversationMemoryManager.d.ts +13 -0
- package/dist/core/conversationMemoryManager.js +28 -0
- package/dist/core/dynamicModels.js +3 -2
- package/dist/core/modules/GenerationHandler.js +2 -0
- package/dist/core/redisConversationMemoryManager.d.ts +11 -0
- package/dist/core/redisConversationMemoryManager.js +26 -9
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/lib/adapters/video/vertexVideoHandler.js +3 -3
- package/dist/lib/core/analytics.js +11 -4
- package/dist/lib/core/baseProvider.d.ts +6 -0
- package/dist/lib/core/baseProvider.js +83 -14
- package/dist/lib/core/conversationMemoryManager.d.ts +13 -0
- package/dist/lib/core/conversationMemoryManager.js +28 -0
- package/dist/lib/core/dynamicModels.js +3 -2
- package/dist/lib/core/modules/GenerationHandler.js +2 -0
- package/dist/lib/core/redisConversationMemoryManager.d.ts +11 -0
- package/dist/lib/core/redisConversationMemoryManager.js +26 -9
- package/dist/lib/index.d.ts +4 -0
- package/dist/lib/index.js +5 -0
- package/dist/lib/mcp/httpRetryHandler.js +6 -2
- package/dist/lib/neurolink.d.ts +5 -0
- package/dist/lib/neurolink.js +160 -10
- package/dist/lib/processors/base/BaseFileProcessor.js +2 -1
- package/dist/lib/processors/errors/errorHelpers.js +12 -4
- package/dist/lib/providers/amazonBedrock.js +2 -1
- package/dist/lib/providers/anthropic.js +2 -2
- package/dist/lib/providers/anthropicBaseProvider.js +10 -4
- package/dist/lib/providers/azureOpenai.js +14 -25
- package/dist/lib/providers/googleAiStudio.d.ts +0 -34
- package/dist/lib/providers/googleAiStudio.js +124 -315
- package/dist/lib/providers/googleNativeGemini3.d.ts +119 -0
- package/dist/lib/providers/googleNativeGemini3.js +264 -0
- package/dist/lib/providers/googleVertex.d.ts +0 -40
- package/dist/lib/providers/googleVertex.js +150 -317
- package/dist/lib/providers/huggingFace.js +20 -5
- package/dist/lib/providers/litellm.js +6 -4
- package/dist/lib/providers/mistral.js +3 -2
- package/dist/lib/providers/openAI.js +2 -2
- package/dist/lib/providers/openRouter.js +8 -7
- package/dist/lib/providers/openaiCompatible.js +10 -4
- package/dist/lib/rag/resilience/RetryHandler.js +6 -2
- package/dist/lib/services/server/ai/observability/instrumentation.d.ts +24 -2
- package/dist/lib/services/server/ai/observability/instrumentation.js +12 -1
- package/dist/lib/types/generateTypes.d.ts +28 -0
- package/dist/lib/types/ragTypes.d.ts +9 -1
- package/dist/lib/types/streamTypes.d.ts +13 -0
- package/dist/lib/utils/conversationMemory.js +15 -0
- package/dist/lib/utils/errorHandling.d.ts +5 -0
- package/dist/lib/utils/errorHandling.js +19 -0
- package/dist/lib/utils/pricing.d.ts +12 -0
- package/dist/lib/utils/pricing.js +134 -0
- package/dist/lib/utils/redis.d.ts +17 -0
- package/dist/lib/utils/redis.js +105 -0
- package/dist/lib/utils/timeout.d.ts +10 -0
- package/dist/lib/utils/timeout.js +15 -0
- package/dist/mcp/httpRetryHandler.js +6 -2
- package/dist/neurolink.d.ts +5 -0
- package/dist/neurolink.js +160 -10
- package/dist/processors/base/BaseFileProcessor.js +2 -1
- package/dist/processors/errors/errorHelpers.js +12 -4
- package/dist/providers/amazonBedrock.js +2 -1
- package/dist/providers/anthropic.js +2 -2
- package/dist/providers/anthropicBaseProvider.js +10 -4
- package/dist/providers/azureOpenai.js +14 -25
- package/dist/providers/googleAiStudio.d.ts +0 -34
- package/dist/providers/googleAiStudio.js +124 -315
- package/dist/providers/googleNativeGemini3.d.ts +119 -0
- package/dist/providers/googleNativeGemini3.js +263 -0
- package/dist/providers/googleVertex.d.ts +0 -40
- package/dist/providers/googleVertex.js +150 -317
- package/dist/providers/huggingFace.js +20 -5
- package/dist/providers/litellm.js +6 -4
- package/dist/providers/mistral.js +3 -2
- package/dist/providers/openAI.js +2 -2
- package/dist/providers/openRouter.js +8 -7
- package/dist/providers/openaiCompatible.js +10 -4
- package/dist/rag/resilience/RetryHandler.js +6 -2
- package/dist/services/server/ai/observability/instrumentation.d.ts +24 -2
- package/dist/services/server/ai/observability/instrumentation.js +12 -1
- package/dist/types/generateTypes.d.ts +28 -0
- package/dist/types/ragTypes.d.ts +9 -1
- package/dist/types/streamTypes.d.ts +13 -0
- package/dist/utils/conversationMemory.js +15 -0
- package/dist/utils/errorHandling.d.ts +5 -0
- package/dist/utils/errorHandling.js +19 -0
- package/dist/utils/pricing.d.ts +12 -0
- package/dist/utils/pricing.js +133 -0
- package/dist/utils/redis.d.ts +17 -0
- package/dist/utils/redis.js +105 -0
- package/dist/utils/timeout.d.ts +10 -0
- package/dist/utils/timeout.js +15 -0
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ import { streamAnalyticsCollector } from "../core/streamAnalytics.js";
|
|
|
6
6
|
import { createProxyFetch } from "../proxy/proxyFetch.js";
|
|
7
7
|
import { logger } from "../utils/logger.js";
|
|
8
8
|
import { createMistralConfig, getProviderModel, validateApiKey, } from "../utils/providerConfig.js";
|
|
9
|
-
import { createTimeoutController, TimeoutError } from "../utils/timeout.js";
|
|
9
|
+
import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
|
|
10
10
|
// Configuration helpers - now using consolidated utility
|
|
11
11
|
const getMistralApiKey = () => {
|
|
12
12
|
return validateApiKey(createMistralConfig());
|
|
@@ -63,7 +63,8 @@ export class MistralProvider extends BaseProvider {
|
|
|
63
63
|
tools,
|
|
64
64
|
maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
|
|
65
65
|
toolChoice: shouldUseTools ? "auto" : "none",
|
|
66
|
-
abortSignal: timeoutController?.controller.signal,
|
|
66
|
+
abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
|
|
67
|
+
experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
|
|
67
68
|
onStepFinish: ({ toolCalls, toolResults }) => {
|
|
68
69
|
this.handleToolExecutionStorage(toolCalls, toolResults, options, new Date()).catch((error) => {
|
|
69
70
|
logger.warn("[MistralProvider] Failed to store tool executions", {
|
|
@@ -9,7 +9,7 @@ import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, Ra
|
|
|
9
9
|
import { logger } from "../utils/logger.js";
|
|
10
10
|
import { createOpenAIConfig, getProviderModel, validateApiKey, } from "../utils/providerConfig.js";
|
|
11
11
|
import { isZodSchema } from "../utils/schemaConversion.js";
|
|
12
|
-
import { createTimeoutController, TimeoutError } from "../utils/timeout.js";
|
|
12
|
+
import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
|
|
13
13
|
// Configuration helpers - now using consolidated utility
|
|
14
14
|
const getOpenAIApiKey = () => {
|
|
15
15
|
return validateApiKey(createOpenAIConfig());
|
|
@@ -282,7 +282,7 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
282
282
|
tools,
|
|
283
283
|
maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
|
|
284
284
|
toolChoice: shouldUseTools && Object.keys(tools).length > 0 ? "auto" : "none",
|
|
285
|
-
abortSignal: timeoutController?.controller.signal,
|
|
285
|
+
abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
|
|
286
286
|
experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
|
|
287
287
|
onStepFinish: ({ toolCalls, toolResults }) => {
|
|
288
288
|
logger.info("Tool execution completed", { toolResults, toolCalls });
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
2
|
-
import { Output, streamText } from "ai";
|
|
2
|
+
import { Output, streamText, } from "ai";
|
|
3
3
|
import { AIProviderName } from "../constants/enums.js";
|
|
4
4
|
import { BaseProvider } from "../core/baseProvider.js";
|
|
5
5
|
import { DEFAULT_MAX_STEPS } from "../core/constants.js";
|
|
6
6
|
import { streamAnalyticsCollector } from "../core/streamAnalytics.js";
|
|
7
7
|
import { createProxyFetch } from "../proxy/proxyFetch.js";
|
|
8
|
+
import { isAbortError } from "../utils/errorHandling.js";
|
|
8
9
|
import { logger } from "../utils/logger.js";
|
|
9
10
|
import { getProviderModel } from "../utils/providerConfig.js";
|
|
10
|
-
import { createTimeoutController, TimeoutError } from "../utils/timeout.js";
|
|
11
|
+
import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
|
|
11
12
|
// Constants
|
|
12
13
|
const MODELS_DISCOVERY_TIMEOUT_MS = 5000; // 5 seconds for model discovery
|
|
13
14
|
// Configuration helpers
|
|
@@ -206,8 +207,7 @@ export class OpenRouterProvider extends BaseProvider {
|
|
|
206
207
|
// BaseProvider.stream() pre-merges base tools + external tools into options.tools
|
|
207
208
|
const shouldUseTools = !options.disableTools && this.supportsTools();
|
|
208
209
|
const tools = shouldUseTools
|
|
209
|
-
? options.tools ||
|
|
210
|
-
(await this.getAllTools())
|
|
210
|
+
? options.tools || (await this.getAllTools())
|
|
211
211
|
: {};
|
|
212
212
|
logger.debug(`OpenRouter: Tools for streaming`, {
|
|
213
213
|
shouldUseTools,
|
|
@@ -229,7 +229,8 @@ export class OpenRouterProvider extends BaseProvider {
|
|
|
229
229
|
toolChoice: "auto",
|
|
230
230
|
maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
|
|
231
231
|
}),
|
|
232
|
-
abortSignal: timeoutController?.controller.signal,
|
|
232
|
+
abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
|
|
233
|
+
experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
|
|
233
234
|
onError: (event) => {
|
|
234
235
|
const error = event.error;
|
|
235
236
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -276,7 +277,7 @@ export class OpenRouterProvider extends BaseProvider {
|
|
|
276
277
|
}
|
|
277
278
|
}
|
|
278
279
|
const result = await streamText(streamOptions);
|
|
279
|
-
timeoutController?.cleanup();
|
|
280
|
+
result.text.finally(() => timeoutController?.cleanup());
|
|
280
281
|
// Transform stream to content object stream using fullStream (handles both text and tool calls)
|
|
281
282
|
const transformedStream = (async function* () {
|
|
282
283
|
// Try fullStream first (handles both text and tool calls), fallback to textStream
|
|
@@ -439,7 +440,7 @@ export class OpenRouterProvider extends BaseProvider {
|
|
|
439
440
|
}
|
|
440
441
|
catch (error) {
|
|
441
442
|
clearTimeout(timeoutId);
|
|
442
|
-
if (error
|
|
443
|
+
if (isAbortError(error)) {
|
|
443
444
|
throw new Error(`Request timed out after ${MODELS_DISCOVERY_TIMEOUT_MS / 1000} seconds`);
|
|
444
445
|
}
|
|
445
446
|
throw error;
|
|
@@ -3,7 +3,7 @@ import { streamText } from "ai";
|
|
|
3
3
|
import { AIProviderName } from "../constants/enums.js";
|
|
4
4
|
import { BaseProvider } from "../core/baseProvider.js";
|
|
5
5
|
import { logger } from "../utils/logger.js";
|
|
6
|
-
import { createTimeoutController, TimeoutError } from "../utils/timeout.js";
|
|
6
|
+
import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
|
|
7
7
|
import { streamAnalyticsCollector } from "../core/streamAnalytics.js";
|
|
8
8
|
import { createProxyFetch } from "../proxy/proxyFetch.js";
|
|
9
9
|
import { DEFAULT_MAX_STEPS } from "../core/constants.js";
|
|
@@ -159,6 +159,11 @@ export class OpenAICompatibleProvider extends BaseProvider {
|
|
|
159
159
|
const timeout = this.getTimeout(options);
|
|
160
160
|
const timeoutController = createTimeoutController(timeout, this.providerName, "stream");
|
|
161
161
|
try {
|
|
162
|
+
// Get tools - options.tools is pre-merged by BaseProvider.stream()
|
|
163
|
+
const shouldUseTools = !options.disableTools && this.supportsTools();
|
|
164
|
+
const tools = shouldUseTools
|
|
165
|
+
? options.tools || (await this.getAllTools())
|
|
166
|
+
: {};
|
|
162
167
|
// Build message array from options with multimodal support
|
|
163
168
|
// Using protected helper from BaseProvider to eliminate code duplication
|
|
164
169
|
const messages = await this.buildMessagesForStream(options);
|
|
@@ -173,9 +178,10 @@ export class OpenAICompatibleProvider extends BaseProvider {
|
|
|
173
178
|
? { temperature: options.temperature }
|
|
174
179
|
: {}),
|
|
175
180
|
maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
|
|
176
|
-
tools
|
|
177
|
-
toolChoice: "auto",
|
|
178
|
-
abortSignal: timeoutController?.controller.signal,
|
|
181
|
+
tools,
|
|
182
|
+
toolChoice: shouldUseTools ? "auto" : "none",
|
|
183
|
+
abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
|
|
184
|
+
experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
|
|
179
185
|
onStepFinish: ({ toolCalls, toolResults }) => {
|
|
180
186
|
this.handleToolExecutionStorage(toolCalls, toolResults, options, new Date()).catch((error) => {
|
|
181
187
|
logger.warn("[OpenAiCompatibleProvider] Failed to store tool executions", {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* vector queries, and LLM-based extraction.
|
|
7
7
|
*/
|
|
8
8
|
import { withRetry, } from "../../core/infrastructure/index.js";
|
|
9
|
+
import { isAbortError } from "../../utils/errorHandling.js";
|
|
9
10
|
import { logger } from "../../utils/logger.js";
|
|
10
11
|
import { EmbeddingError, isRetryableRAGError, MetadataExtractionError, RAGError, RAGErrorCodes, VectorQueryError, } from "../errors/RAGError.js";
|
|
11
12
|
/**
|
|
@@ -50,6 +51,10 @@ function sleep(ms) {
|
|
|
50
51
|
* Check if an error is retryable based on configuration
|
|
51
52
|
*/
|
|
52
53
|
export function isRetryable(error, config = DEFAULT_RAG_RETRY_CONFIG) {
|
|
54
|
+
// Never retry abort errors - the operation was intentionally cancelled
|
|
55
|
+
if (isAbortError(error)) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
53
58
|
// Use custom shouldRetry if provided
|
|
54
59
|
if (config.shouldRetry) {
|
|
55
60
|
return config.shouldRetry(error);
|
|
@@ -70,8 +75,7 @@ export function isRetryable(error, config = DEFAULT_RAG_RETRY_CONFIG) {
|
|
|
70
75
|
// Timeout errors
|
|
71
76
|
if (errorObj.name === "TimeoutError" ||
|
|
72
77
|
errorObj.code === "TIMEOUT" ||
|
|
73
|
-
errorObj.code === "ETIMEDOUT"
|
|
74
|
-
errorObj.name === "AbortError") {
|
|
78
|
+
errorObj.code === "ETIMEDOUT") {
|
|
75
79
|
return true;
|
|
76
80
|
}
|
|
77
81
|
// Network errors
|
|
@@ -15,7 +15,7 @@ import type { LangfuseConfig } from "../../../../types/observability.js";
|
|
|
15
15
|
* Extended context for Langfuse spans
|
|
16
16
|
* Supports all Langfuse trace attributes for rich observability
|
|
17
17
|
*/
|
|
18
|
-
type LangfuseContext = {
|
|
18
|
+
export type LangfuseContext = {
|
|
19
19
|
userId?: string | null;
|
|
20
20
|
sessionId?: string | null;
|
|
21
21
|
/** Conversation/thread identifier for grouping related traces */
|
|
@@ -51,6 +51,27 @@ type LangfuseContext = {
|
|
|
51
51
|
* @default undefined (uses global setting, which defaults to true)
|
|
52
52
|
*/
|
|
53
53
|
autoDetectOperationName?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Custom attributes to set on all spans within this context.
|
|
56
|
+
*
|
|
57
|
+
* These attributes are propagated to every span created within the
|
|
58
|
+
* AsyncLocalStorage context, enabling application-level context
|
|
59
|
+
* (e.g., Slack channel name, feature flag, tenant ID) to appear
|
|
60
|
+
* on all SDK-internal spans.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* await setLangfuseContext({
|
|
64
|
+
* userId: "user@email.com",
|
|
65
|
+
* customAttributes: {
|
|
66
|
+
* "app.slack.channel": "engineering",
|
|
67
|
+
* "app.tenant.id": "tenant-123",
|
|
68
|
+
* "app.feature.flag": true,
|
|
69
|
+
* }
|
|
70
|
+
* }, async () => {
|
|
71
|
+
* // All spans created here will have these attributes
|
|
72
|
+
* });
|
|
73
|
+
*/
|
|
74
|
+
customAttributes?: Record<string, string | number | boolean>;
|
|
54
75
|
};
|
|
55
76
|
/**
|
|
56
77
|
* Initialize OpenTelemetry with Langfuse span processor
|
|
@@ -140,6 +161,8 @@ export declare function setLangfuseContext<T = void>(context: {
|
|
|
140
161
|
operationName?: string | null;
|
|
141
162
|
/** Override global autoDetectOperationName for this context */
|
|
142
163
|
autoDetectOperationName?: boolean;
|
|
164
|
+
/** Custom attributes to set on all spans within this context */
|
|
165
|
+
customAttributes?: Record<string, string | number | boolean>;
|
|
143
166
|
}, callback?: () => T | Promise<T>): Promise<T | void>;
|
|
144
167
|
/**
|
|
145
168
|
* Get the current Langfuse context from AsyncLocalStorage
|
|
@@ -194,4 +217,3 @@ export declare function getSpanProcessors(): SpanProcessor[];
|
|
|
194
217
|
* @returns true if operating in external TracerProvider mode
|
|
195
218
|
*/
|
|
196
219
|
export declare function isUsingExternalTracerProvider(): boolean;
|
|
197
|
-
export {};
|
|
@@ -119,7 +119,14 @@ class ContextEnricher {
|
|
|
119
119
|
// 2. Formatted name with userId + operationName
|
|
120
120
|
// 3. userId only (legacy fallback)
|
|
121
121
|
const traceName = this.buildTraceName(context?.traceName, userId, operationName);
|
|
122
|
-
//
|
|
122
|
+
// Apply custom attributes FIRST so internal attributes always take precedence
|
|
123
|
+
// and cannot be accidentally overwritten by user-provided values
|
|
124
|
+
if (context?.customAttributes) {
|
|
125
|
+
for (const [key, value] of Object.entries(context.customAttributes)) {
|
|
126
|
+
span.setAttribute(key, value);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Set user and session attributes (internal - always override custom)
|
|
123
130
|
if (userId && userId !== "guest") {
|
|
124
131
|
span.setAttribute("user.id", userId);
|
|
125
132
|
}
|
|
@@ -657,6 +664,10 @@ export async function setLangfuseContext(context, callback) {
|
|
|
657
664
|
autoDetectOperationName: context.autoDetectOperationName !== undefined
|
|
658
665
|
? context.autoDetectOperationName
|
|
659
666
|
: currentContext.autoDetectOperationName,
|
|
667
|
+
// Custom attributes support
|
|
668
|
+
customAttributes: context.customAttributes !== undefined
|
|
669
|
+
? context.customAttributes
|
|
670
|
+
: currentContext.customAttributes,
|
|
660
671
|
};
|
|
661
672
|
if (callback) {
|
|
662
673
|
return await contextStorage.run(newContext, callback);
|
|
@@ -217,6 +217,8 @@ export type GenerateOptions = {
|
|
|
217
217
|
schema?: ValidationSchema;
|
|
218
218
|
tools?: Record<string, Tool>;
|
|
219
219
|
timeout?: number | string;
|
|
220
|
+
/** AbortSignal for external cancellation of the AI call */
|
|
221
|
+
abortSignal?: AbortSignal;
|
|
220
222
|
/**
|
|
221
223
|
* Disable tool execution (including built-in tools)
|
|
222
224
|
*
|
|
@@ -235,6 +237,17 @@ export type GenerateOptions = {
|
|
|
235
237
|
* ```
|
|
236
238
|
*/
|
|
237
239
|
disableTools?: boolean;
|
|
240
|
+
/** Include only these tools by name (whitelist). If set, only matching tools are available. */
|
|
241
|
+
toolFilter?: string[];
|
|
242
|
+
/** Exclude these tools by name (blacklist). Applied after toolFilter. */
|
|
243
|
+
excludeTools?: string[];
|
|
244
|
+
/**
|
|
245
|
+
* Skip injecting tool schemas into the system prompt.
|
|
246
|
+
* When true, tools are ONLY passed natively via the provider's `tools` parameter,
|
|
247
|
+
* avoiding duplicate tool definitions (~30K tokens savings per call).
|
|
248
|
+
* Default: false (backward compatible — tool schemas are injected into system prompt).
|
|
249
|
+
*/
|
|
250
|
+
skipToolPromptInjection?: boolean;
|
|
238
251
|
enableEvaluation?: boolean;
|
|
239
252
|
enableAnalytics?: boolean;
|
|
240
253
|
context?: StandardRecord;
|
|
@@ -389,6 +402,7 @@ export type GenerateResult = {
|
|
|
389
402
|
} | null;
|
|
390
403
|
provider?: string;
|
|
391
404
|
model?: string;
|
|
405
|
+
finishReason?: string;
|
|
392
406
|
usage?: TokenUsage;
|
|
393
407
|
responseTime?: number;
|
|
394
408
|
toolCalls?: Array<{
|
|
@@ -546,8 +560,14 @@ export type TextGenerationOptions = {
|
|
|
546
560
|
};
|
|
547
561
|
tools?: Record<string, Tool>;
|
|
548
562
|
timeout?: number | string;
|
|
563
|
+
/** AbortSignal for external cancellation of the AI call */
|
|
564
|
+
abortSignal?: AbortSignal;
|
|
549
565
|
disableTools?: boolean;
|
|
550
566
|
maxSteps?: number;
|
|
567
|
+
/** Include only these tools by name (whitelist). If set, only matching tools are available. */
|
|
568
|
+
toolFilter?: string[];
|
|
569
|
+
/** Exclude these tools by name (blacklist). Applied after toolFilter. */
|
|
570
|
+
excludeTools?: string[];
|
|
551
571
|
/**
|
|
552
572
|
* Text-to-Speech (TTS) configuration
|
|
553
573
|
*
|
|
@@ -609,6 +629,13 @@ export type TextGenerationOptions = {
|
|
|
609
629
|
* @internal Set by NeuroLink SDK — not typically used directly by consumers.
|
|
610
630
|
*/
|
|
611
631
|
fileRegistry?: unknown;
|
|
632
|
+
/**
|
|
633
|
+
* Skip injecting tool schemas into the system prompt.
|
|
634
|
+
* When true, tools are ONLY passed natively via the provider's `tools` parameter,
|
|
635
|
+
* avoiding duplicate tool definitions (~30K tokens savings per call).
|
|
636
|
+
* Default: false (backward compatible — tool schemas are injected into system prompt).
|
|
637
|
+
*/
|
|
638
|
+
skipToolPromptInjection?: boolean;
|
|
612
639
|
/**
|
|
613
640
|
* ## Extended Thinking Options
|
|
614
641
|
*
|
|
@@ -702,6 +729,7 @@ export type TextGenerationOptions = {
|
|
|
702
729
|
*/
|
|
703
730
|
export type TextGenerationResult = {
|
|
704
731
|
content: string;
|
|
732
|
+
finishReason?: string;
|
|
705
733
|
provider?: string;
|
|
706
734
|
model?: string;
|
|
707
735
|
usage?: TokenUsage;
|
|
@@ -133,7 +133,15 @@ export type RAGRetryConfig = {
|
|
|
133
133
|
backoffMultiplier: number;
|
|
134
134
|
/** Whether to add jitter (default: true) */
|
|
135
135
|
jitter: boolean;
|
|
136
|
-
/**
|
|
136
|
+
/**
|
|
137
|
+
* Custom function to determine if error is retryable.
|
|
138
|
+
*
|
|
139
|
+
* Note: In `isRetryable()`, this callback is invoked *before* the built-in
|
|
140
|
+
* abort-error check. If you provide a custom `shouldRetry`, it should
|
|
141
|
+
* explicitly handle abort errors (e.g. return `false` for them) when
|
|
142
|
+
* cancellation correctness is required. Otherwise an aborted operation
|
|
143
|
+
* could be retried instead of propagating immediately.
|
|
144
|
+
*/
|
|
137
145
|
shouldRetry?: (error: Error) => boolean;
|
|
138
146
|
/** Retryable error codes */
|
|
139
147
|
retryableErrorCodes?: string[];
|
|
@@ -321,8 +321,21 @@ export type StreamOptions = {
|
|
|
321
321
|
schema?: ValidationSchema;
|
|
322
322
|
tools?: Record<string, Tool>;
|
|
323
323
|
timeout?: number | string;
|
|
324
|
+
/** AbortSignal for external cancellation of the AI call */
|
|
325
|
+
abortSignal?: AbortSignal;
|
|
324
326
|
disableTools?: boolean;
|
|
325
327
|
maxSteps?: number;
|
|
328
|
+
/** Include only these tools by name (whitelist). If set, only matching tools are available. */
|
|
329
|
+
toolFilter?: string[];
|
|
330
|
+
/** Exclude these tools by name (blacklist). Applied after toolFilter. */
|
|
331
|
+
excludeTools?: string[];
|
|
332
|
+
/**
|
|
333
|
+
* Skip injecting tool schemas into the system prompt.
|
|
334
|
+
* When true, tools are ONLY passed natively via the provider's `tools` parameter,
|
|
335
|
+
* avoiding duplicate tool definitions (~30K tokens savings per call).
|
|
336
|
+
* Default: false (backward compatible — tool schemas are injected into system prompt).
|
|
337
|
+
*/
|
|
338
|
+
skipToolPromptInjection?: boolean;
|
|
326
339
|
enableEvaluation?: boolean;
|
|
327
340
|
enableAnalytics?: boolean;
|
|
328
341
|
context?: UnknownRecord;
|
|
@@ -104,6 +104,21 @@ export async function storeConversationTurn(conversationMemory, originalOptions,
|
|
|
104
104
|
}
|
|
105
105
|
const userMessage = originalOptions.originalPrompt || originalOptions.prompt || "";
|
|
106
106
|
const aiResponse = result.content ?? "";
|
|
107
|
+
// Guard: skip storing conversation turn if AI response is empty AND no tools were used.
|
|
108
|
+
// Empty assistant messages cause "text content blocks must be non-empty" errors
|
|
109
|
+
// when loaded as conversation history on the next interaction.
|
|
110
|
+
// However, tool-only turns (empty text but tools were invoked) must still be stored
|
|
111
|
+
// to preserve tool-calling conversation history.
|
|
112
|
+
const hasToolActivity = (result.toolsUsed && result.toolsUsed.length > 0) ||
|
|
113
|
+
(result.toolExecutions && result.toolExecutions.length > 0);
|
|
114
|
+
if (!aiResponse.trim() && !hasToolActivity) {
|
|
115
|
+
logger.warn("[conversationMemoryUtils] Skipping conversation turn storage — AI response is empty and no tool activity", {
|
|
116
|
+
sessionId,
|
|
117
|
+
userId,
|
|
118
|
+
userMessageLength: userMessage.length,
|
|
119
|
+
});
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
107
122
|
let providerDetails;
|
|
108
123
|
if (result.provider && result.model) {
|
|
109
124
|
providerDetails = {
|
|
@@ -244,6 +244,11 @@ export declare class CircuitBreaker {
|
|
|
244
244
|
getState(): "closed" | "open" | "half-open";
|
|
245
245
|
getFailureCount(): number;
|
|
246
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* Detect AbortError from any source (DOMException, plain Error, or message-based).
|
|
249
|
+
* Used to short-circuit retry/fallback loops when an abort signal fires.
|
|
250
|
+
*/
|
|
251
|
+
export declare function isAbortError(error: unknown): boolean;
|
|
247
252
|
/**
|
|
248
253
|
* Error handler that decides whether to retry based on error type
|
|
249
254
|
*/
|
|
@@ -820,6 +820,25 @@ export class CircuitBreaker {
|
|
|
820
820
|
return this.failures;
|
|
821
821
|
}
|
|
822
822
|
}
|
|
823
|
+
/**
|
|
824
|
+
* Detect AbortError from any source (DOMException, plain Error, or message-based).
|
|
825
|
+
* Used to short-circuit retry/fallback loops when an abort signal fires.
|
|
826
|
+
*/
|
|
827
|
+
export function isAbortError(error) {
|
|
828
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
829
|
+
return true;
|
|
830
|
+
}
|
|
831
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
832
|
+
return true;
|
|
833
|
+
}
|
|
834
|
+
if (error instanceof Error &&
|
|
835
|
+
(error.message === "This operation was aborted" ||
|
|
836
|
+
error.message === "The operation was aborted" ||
|
|
837
|
+
error.message?.includes("The user aborted a request"))) {
|
|
838
|
+
return true;
|
|
839
|
+
}
|
|
840
|
+
return false;
|
|
841
|
+
}
|
|
823
842
|
/**
|
|
824
843
|
* Error handler that decides whether to retry based on error type
|
|
825
844
|
*/
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TokenUsage } from "../types/analytics.js";
|
|
2
|
+
/**
|
|
3
|
+
* Calculate the dollar cost of a generate/stream call based on token usage.
|
|
4
|
+
* Returns 0 if the provider/model combination is not in the pricing table.
|
|
5
|
+
*/
|
|
6
|
+
export declare function calculateCost(provider: string, model: string, usage: TokenUsage): number;
|
|
7
|
+
/**
|
|
8
|
+
* Check if pricing is available for a provider/model combination.
|
|
9
|
+
* Checks the rate table directly instead of computing a cost,
|
|
10
|
+
* so even very cheap models (e.g. gemini-1.5-flash) are detected correctly.
|
|
11
|
+
*/
|
|
12
|
+
export declare function hasPricing(provider: string, model: string): boolean;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-token pricing data (USD per token). Updated Feb 2026.
|
|
3
|
+
* Sources:
|
|
4
|
+
* - Anthropic: https://www.anthropic.com/pricing
|
|
5
|
+
* - OpenAI: https://openai.com/api/pricing
|
|
6
|
+
* - Google: https://ai.google.dev/pricing
|
|
7
|
+
*
|
|
8
|
+
* Note: Not all supported providers have pricing data. Missing providers
|
|
9
|
+
* (Bedrock, Azure, Mistral, etc.) will return 0 from calculateCost().
|
|
10
|
+
*/
|
|
11
|
+
const PRICING = {
|
|
12
|
+
// Anthropic (direct API)
|
|
13
|
+
anthropic: {
|
|
14
|
+
"claude-sonnet-4-5-20250929": {
|
|
15
|
+
input: 3.0 / 1_000_000,
|
|
16
|
+
output: 15.0 / 1_000_000,
|
|
17
|
+
cacheRead: 0.3 / 1_000_000,
|
|
18
|
+
cacheCreation: 3.75 / 1_000_000,
|
|
19
|
+
},
|
|
20
|
+
"claude-opus-4-6": {
|
|
21
|
+
input: 15.0 / 1_000_000,
|
|
22
|
+
output: 75.0 / 1_000_000,
|
|
23
|
+
cacheRead: 1.5 / 1_000_000,
|
|
24
|
+
cacheCreation: 18.75 / 1_000_000,
|
|
25
|
+
},
|
|
26
|
+
"claude-haiku-4-5-20251001": {
|
|
27
|
+
input: 0.8 / 1_000_000,
|
|
28
|
+
output: 4.0 / 1_000_000,
|
|
29
|
+
cacheRead: 0.08 / 1_000_000,
|
|
30
|
+
cacheCreation: 1.0 / 1_000_000,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
// Google Vertex AI (same models, same pricing)
|
|
34
|
+
vertex: {
|
|
35
|
+
"claude-sonnet-4-5@20250929": {
|
|
36
|
+
input: 3.0 / 1_000_000,
|
|
37
|
+
output: 15.0 / 1_000_000,
|
|
38
|
+
cacheRead: 0.3 / 1_000_000,
|
|
39
|
+
cacheCreation: 3.75 / 1_000_000,
|
|
40
|
+
},
|
|
41
|
+
"claude-opus-4-6": {
|
|
42
|
+
input: 15.0 / 1_000_000,
|
|
43
|
+
output: 75.0 / 1_000_000,
|
|
44
|
+
cacheRead: 1.5 / 1_000_000,
|
|
45
|
+
cacheCreation: 18.75 / 1_000_000,
|
|
46
|
+
},
|
|
47
|
+
"claude-haiku-4-5@20251001": {
|
|
48
|
+
input: 0.8 / 1_000_000,
|
|
49
|
+
output: 4.0 / 1_000_000,
|
|
50
|
+
cacheRead: 0.08 / 1_000_000,
|
|
51
|
+
cacheCreation: 1.0 / 1_000_000,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
// OpenAI
|
|
55
|
+
openai: {
|
|
56
|
+
"gpt-4o": { input: 2.5 / 1_000_000, output: 10.0 / 1_000_000 },
|
|
57
|
+
"gpt-4o-mini": { input: 0.15 / 1_000_000, output: 0.6 / 1_000_000 },
|
|
58
|
+
"gpt-4-turbo": { input: 10.0 / 1_000_000, output: 30.0 / 1_000_000 },
|
|
59
|
+
o1: { input: 15.0 / 1_000_000, output: 60.0 / 1_000_000 },
|
|
60
|
+
"o1-mini": { input: 1.1 / 1_000_000, output: 4.4 / 1_000_000 },
|
|
61
|
+
},
|
|
62
|
+
// Google (Gemini)
|
|
63
|
+
google: {
|
|
64
|
+
"gemini-2.0-flash": { input: 0.1 / 1_000_000, output: 0.4 / 1_000_000 },
|
|
65
|
+
"gemini-2.0-pro": { input: 1.25 / 1_000_000, output: 10.0 / 1_000_000 },
|
|
66
|
+
"gemini-1.5-pro": { input: 1.25 / 1_000_000, output: 5.0 / 1_000_000 },
|
|
67
|
+
"gemini-1.5-flash": { input: 0.075 / 1_000_000, output: 0.3 / 1_000_000 },
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Map of normalized provider aliases to canonical PRICING keys.
|
|
72
|
+
* After stripping non-alpha characters, e.g. "google-ai" becomes "googleai".
|
|
73
|
+
*/
|
|
74
|
+
const PROVIDER_ALIASES = {
|
|
75
|
+
googleai: "google",
|
|
76
|
+
googleaistudio: "google",
|
|
77
|
+
anthropic: "anthropic",
|
|
78
|
+
openai: "openai",
|
|
79
|
+
vertex: "vertex",
|
|
80
|
+
google: "google",
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Look up per-token rates for a provider/model combination.
|
|
84
|
+
* Normalises the provider name via aliases, then tries an exact model match
|
|
85
|
+
* followed by a longest-prefix match so that e.g. "gpt-4o-2024-08-06"
|
|
86
|
+
* resolves to the "gpt-4o" entry without a false hit on "gpt-4".
|
|
87
|
+
*
|
|
88
|
+
* @returns The rate entry, or undefined when the combination is unknown.
|
|
89
|
+
*/
|
|
90
|
+
function findRates(provider, model) {
|
|
91
|
+
const stripped = provider.toLowerCase().replace(/[^a-z]/g, "");
|
|
92
|
+
const normalizedProvider = PROVIDER_ALIASES[stripped] ?? stripped;
|
|
93
|
+
const providerPricing = PRICING[normalizedProvider] || PRICING[provider];
|
|
94
|
+
if (!providerPricing) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
// Exact match
|
|
98
|
+
if (providerPricing[model]) {
|
|
99
|
+
return providerPricing[model];
|
|
100
|
+
}
|
|
101
|
+
// Longest-prefix match
|
|
102
|
+
const sortedKeys = Object.keys(providerPricing).sort((a, b) => b.length - a.length);
|
|
103
|
+
const key = sortedKeys.find((k) => model.startsWith(k));
|
|
104
|
+
return key ? providerPricing[key] : undefined;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Calculate the dollar cost of a generate/stream call based on token usage.
|
|
108
|
+
* Returns 0 if the provider/model combination is not in the pricing table.
|
|
109
|
+
*/
|
|
110
|
+
export function calculateCost(provider, model, usage) {
|
|
111
|
+
const rates = findRates(provider, model);
|
|
112
|
+
if (!rates) {
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
let cost = 0;
|
|
116
|
+
cost += (usage.input || 0) * rates.input;
|
|
117
|
+
cost += (usage.output || 0) * rates.output;
|
|
118
|
+
if (usage.cacheReadTokens && rates.cacheRead) {
|
|
119
|
+
cost += usage.cacheReadTokens * rates.cacheRead;
|
|
120
|
+
}
|
|
121
|
+
if (usage.cacheCreationTokens && rates.cacheCreation) {
|
|
122
|
+
cost += usage.cacheCreationTokens * rates.cacheCreation;
|
|
123
|
+
}
|
|
124
|
+
return Math.round(cost * 1_000_000) / 1_000_000; // Round to 6 decimal places
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Check if pricing is available for a provider/model combination.
|
|
128
|
+
* Checks the rate table directly instead of computing a cost,
|
|
129
|
+
* so even very cheap models (e.g. gemini-1.5-flash) are detected correctly.
|
|
130
|
+
*/
|
|
131
|
+
export function hasPricing(provider, model) {
|
|
132
|
+
return findRates(provider, model) !== undefined;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=pricing.js.map
|
|
@@ -5,6 +5,23 @@
|
|
|
5
5
|
import { createClient } from "redis";
|
|
6
6
|
import type { RedisStorageConfig, RedisConversationObject } from "../types/conversation.js";
|
|
7
7
|
type RedisClient = ReturnType<typeof createClient>;
|
|
8
|
+
/**
|
|
9
|
+
* Get a pooled Redis connection. Multiple callers with the same host:port:db
|
|
10
|
+
* share a single connection, reducing connection count.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getPooledRedisClient(config: Required<RedisStorageConfig>): Promise<RedisClient>;
|
|
13
|
+
/**
|
|
14
|
+
* Release a pooled Redis connection. Only closes when refCount reaches 0.
|
|
15
|
+
*/
|
|
16
|
+
export declare function releasePooledRedisClient(config: Required<RedisStorageConfig>): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Get stats about the connection pool
|
|
19
|
+
*/
|
|
20
|
+
export declare function getPoolStats(): Array<{
|
|
21
|
+
key: string;
|
|
22
|
+
refCount: number;
|
|
23
|
+
isOpen: boolean;
|
|
24
|
+
}>;
|
|
8
25
|
/**
|
|
9
26
|
* Creates a Redis client with the provided configuration
|
|
10
27
|
*/
|