@juspay/neurolink 12.11.2 → 12.12.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 +3 -3
- package/dist/browser/neurolink.min.js +533 -581
- package/dist/constants/enums.d.ts +13 -0
- package/dist/constants/enums.js +14 -0
- package/dist/core/baseProvider.d.ts +71 -3
- package/dist/core/baseProvider.js +152 -44
- package/dist/core/modules/GenerationHandler.d.ts +22 -24
- package/dist/core/modules/GenerationHandler.js +28 -463
- package/dist/core/nativeGenerateLoop.d.ts +35 -0
- package/dist/core/nativeGenerateLoop.js +261 -0
- package/dist/files/fileTools.d.ts +5 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -0
- package/dist/mcp/toolRegistry.js +7 -0
- package/dist/middleware/builtin/guardrails.d.ts +0 -5
- package/dist/middleware/builtin/guardrails.js +33 -5
- package/dist/middleware/factory.js +1 -1
- package/dist/middleware/wrapLanguageModel.d.ts +18 -0
- package/dist/middleware/wrapLanguageModel.js +53 -0
- package/dist/neurolink.d.ts +7 -0
- package/dist/neurolink.js +61 -11
- package/dist/processors/media/AudioProcessor.js +46 -11
- package/dist/providers/amazonSagemaker.d.ts +17 -1
- package/dist/providers/amazonSagemaker.js +110 -0
- package/dist/providers/anthropic/client.d.ts +11 -0
- package/dist/providers/anthropic/client.js +148 -1
- package/dist/providers/catalog/index.generated.d.ts +1 -1
- package/dist/providers/catalog/index.generated.js +3 -0
- package/dist/providers/catalog/loader.js +1 -0
- package/dist/providers/catalog/mancer.json +192 -0
- package/dist/providers/configuredOpenAICompat.d.ts +11 -0
- package/dist/providers/configuredOpenAICompat.js +16 -0
- package/dist/providers/googleVertex/client.d.ts +0 -9
- package/dist/providers/googleVertex/client.js +0 -33
- package/dist/providers/openaiChatCompletionsBase.d.ts +21 -1
- package/dist/providers/openaiChatCompletionsBase.js +178 -0
- package/dist/providers/providerTypeUtils.d.ts +1 -2
- package/dist/providers/providerTypeUtils.js +5 -1
- package/dist/types/aiCompat.d.ts +485 -0
- package/dist/types/aiCompat.js +17 -0
- package/dist/types/conversation.d.ts +1 -1
- package/dist/types/generate.d.ts +52 -0
- package/dist/types/middleware.d.ts +3 -6
- package/dist/types/providerCatalog.generated.d.ts +2 -2
- package/dist/types/providers.d.ts +14 -1
- package/dist/types/tools.d.ts +25 -2
- package/dist/utils/errorHandling.d.ts +20 -3
- package/dist/utils/errorHandling.js +22 -5
- package/dist/utils/generationErrors.d.ts +78 -6
- package/dist/utils/generationErrors.js +114 -6
- package/dist/utils/mcpDefaults.d.ts +1 -1
- package/dist/utils/mcpDefaults.js +4 -1
- package/dist/utils/nativeSingleShot.d.ts +3 -0
- package/dist/utils/nativeSingleShot.js +83 -0
- package/dist/utils/tool.d.ts +30 -5
- package/dist/utils/tool.js +43 -5
- package/package.json +3 -6
- package/dist/utils/generation.d.ts +0 -8
- package/dist/utils/generation.js +0 -8
|
@@ -26,6 +26,7 @@ export declare enum AIProviderName {
|
|
|
26
26
|
GROQ = "groq",
|
|
27
27
|
INCEPTION_LABS = "inception-labs",
|
|
28
28
|
IO_INTELLIGENCE = "io-intelligence",
|
|
29
|
+
MANCER = "mancer",
|
|
29
30
|
MISTRAL = "mistral",
|
|
30
31
|
PERPLEXITY = "perplexity",
|
|
31
32
|
SAMBANOVA = "sambanova",
|
|
@@ -919,6 +920,18 @@ export declare enum IoIntelligenceModels {
|
|
|
919
920
|
OPENAI_GPT_OSS_20B = "openai/gpt-oss-20b",
|
|
920
921
|
META_LLAMA_LLAMA_3_3_70B_INSTRUCT = "meta-llama/Llama-3.3-70B-Instruct"
|
|
921
922
|
}
|
|
923
|
+
export declare enum MancerModels {
|
|
924
|
+
MYTHOMAX = "mythomax",
|
|
925
|
+
DEEPSEEK_V4_FLASH = "deepseek-v4-flash",
|
|
926
|
+
DEEPSEEK_V4_FLASH_0731 = "deepseek-v4-flash-0731",
|
|
927
|
+
MYTHOLITE = "mytholite",
|
|
928
|
+
REMM_SLERP = "remm-slerp",
|
|
929
|
+
MAGNUM_72B_V4 = "magnum-72b-v4",
|
|
930
|
+
GLM_4_7 = "glm-4.7",
|
|
931
|
+
GPT_OSS_120B = "gpt-oss-120b",
|
|
932
|
+
WEAVER_ALPHA = "weaver-alpha",
|
|
933
|
+
DANS_PE_1_3_24B = "dans-pe-1.3-24b"
|
|
934
|
+
}
|
|
922
935
|
export declare enum MistralModels {
|
|
923
936
|
MISTRAL_LARGE_LATEST = "mistral-large-latest",
|
|
924
937
|
MISTRAL_LARGE_2512 = "mistral-large-2512",
|
package/dist/constants/enums.js
CHANGED
|
@@ -31,6 +31,7 @@ export var AIProviderName;
|
|
|
31
31
|
AIProviderName["GROQ"] = "groq";
|
|
32
32
|
AIProviderName["INCEPTION_LABS"] = "inception-labs";
|
|
33
33
|
AIProviderName["IO_INTELLIGENCE"] = "io-intelligence";
|
|
34
|
+
AIProviderName["MANCER"] = "mancer";
|
|
34
35
|
AIProviderName["MISTRAL"] = "mistral";
|
|
35
36
|
AIProviderName["PERPLEXITY"] = "perplexity";
|
|
36
37
|
AIProviderName["SAMBANOVA"] = "sambanova";
|
|
@@ -1175,6 +1176,19 @@ export var IoIntelligenceModels;
|
|
|
1175
1176
|
IoIntelligenceModels["OPENAI_GPT_OSS_20B"] = "openai/gpt-oss-20b";
|
|
1176
1177
|
IoIntelligenceModels["META_LLAMA_LLAMA_3_3_70B_INSTRUCT"] = "meta-llama/Llama-3.3-70B-Instruct";
|
|
1177
1178
|
})(IoIntelligenceModels || (IoIntelligenceModels = {}));
|
|
1179
|
+
export var MancerModels;
|
|
1180
|
+
(function (MancerModels) {
|
|
1181
|
+
MancerModels["MYTHOMAX"] = "mythomax";
|
|
1182
|
+
MancerModels["DEEPSEEK_V4_FLASH"] = "deepseek-v4-flash";
|
|
1183
|
+
MancerModels["DEEPSEEK_V4_FLASH_0731"] = "deepseek-v4-flash-0731";
|
|
1184
|
+
MancerModels["MYTHOLITE"] = "mytholite";
|
|
1185
|
+
MancerModels["REMM_SLERP"] = "remm-slerp";
|
|
1186
|
+
MancerModels["MAGNUM_72B_V4"] = "magnum-72b-v4";
|
|
1187
|
+
MancerModels["GLM_4_7"] = "glm-4.7";
|
|
1188
|
+
MancerModels["GPT_OSS_120B"] = "gpt-oss-120b";
|
|
1189
|
+
MancerModels["WEAVER_ALPHA"] = "weaver-alpha";
|
|
1190
|
+
MancerModels["DANS_PE_1_3_24B"] = "dans-pe-1.3-24b";
|
|
1191
|
+
})(MancerModels || (MancerModels = {}));
|
|
1178
1192
|
export var MistralModels;
|
|
1179
1193
|
(function (MistralModels) {
|
|
1180
1194
|
MistralModels["MISTRAL_LARGE_LATEST"] = "mistral-large-latest";
|
|
@@ -3,7 +3,7 @@ import type { EvaluationData } from "../index.js";
|
|
|
3
3
|
import type { NeuroLink } from "../neurolink.js";
|
|
4
4
|
import type { UnknownRecord, MiddlewareFactoryOptions, StreamOptions, StreamResult, AIProvider, AnalyticsData, EnhancedGenerateResult, TextGenerationOptions, TextGenerationResult, ValidationSchema } from "../types/index.js";
|
|
5
5
|
import { TelemetryHandler } from "./modules/TelemetryHandler.js";
|
|
6
|
-
import type { LanguageModel, ModelMessage, Tool, ToolCallRepairFunction, ToolSet } from "../types/index.js";
|
|
6
|
+
import type { LanguageModel, ModelMessage, RawUsageObject, Tool, ToolCallRepairFunction, ToolSet } from "../types/index.js";
|
|
7
7
|
export declare abstract class BaseProvider implements AIProvider {
|
|
8
8
|
protected modelName: string;
|
|
9
9
|
protected readonly providerName: AIProviderName;
|
|
@@ -196,7 +196,7 @@ export declare abstract class BaseProvider implements AIProvider {
|
|
|
196
196
|
/**
|
|
197
197
|
* Record performance metrics - delegated to TelemetryHandler
|
|
198
198
|
*/
|
|
199
|
-
|
|
199
|
+
protected recordPerformanceMetrics(usage: RawUsageObject | undefined, responseTime: number): Promise<void>;
|
|
200
200
|
/**
|
|
201
201
|
* Extract tool information from generation result - delegated to GenerationHandler
|
|
202
202
|
*/
|
|
@@ -262,7 +262,13 @@ export declare abstract class BaseProvider implements AIProvider {
|
|
|
262
262
|
* lifecycle callback has fired and before a chunk has reached the consumer,
|
|
263
263
|
* so there is no observable output to replay.
|
|
264
264
|
*/
|
|
265
|
-
|
|
265
|
+
/**
|
|
266
|
+
* Protected rather than private so a provider with a native generate path can
|
|
267
|
+
* reuse it. Overriding `generate()` otherwise skips this wrapper silently,
|
|
268
|
+
* and a retired default model stops degrading to the next live one in the
|
|
269
|
+
* catalog — which is exactly what the provider contract gate checks.
|
|
270
|
+
*/
|
|
271
|
+
protected runGenerateWithModelFallback(attempt: () => Promise<EnhancedGenerateResult | null>, callerOwnsFallback: boolean): Promise<EnhancedGenerateResult | null>;
|
|
266
272
|
/**
|
|
267
273
|
* Model ids to try when this provider rejects its current model as invalid.
|
|
268
274
|
* Empty by default; catalog-driven providers return their `fallbacks`.
|
|
@@ -276,6 +282,30 @@ export declare abstract class BaseProvider implements AIProvider {
|
|
|
276
282
|
protected handleDirectTTSSynthesis(options: TextGenerationOptions, startTime: number): Promise<EnhancedGenerateResult>;
|
|
277
283
|
private handleVideoFrameGeneration;
|
|
278
284
|
private executeStandardGenerateFlow;
|
|
285
|
+
/**
|
|
286
|
+
* Close out a turn produced by a provider's own native generate loop.
|
|
287
|
+
*
|
|
288
|
+
* The native loops bypass `executeGeneration`, and with it every post-call
|
|
289
|
+
* step the standard path runs. Each one that was missed had to be found
|
|
290
|
+
* separately — `onFinish` stopped firing, TTS synthesis silently produced no
|
|
291
|
+
* audio, and OTEL saw no usage for any native provider. Routing all of them
|
|
292
|
+
* through one method is what stops the next native path from rediscovering
|
|
293
|
+
* the same list.
|
|
294
|
+
*
|
|
295
|
+
* Order matters and mirrors the standard path: metrics are recorded before
|
|
296
|
+
* synthesis so telemetry sees the model's own usage, and `enhanceResult`
|
|
297
|
+
* runs last so analytics and evaluation observe the final content.
|
|
298
|
+
*/
|
|
299
|
+
protected finalizeNativeGenerate(result: EnhancedGenerateResult, options: TextGenerationOptions, startTime: number): Promise<EnhancedGenerateResult>;
|
|
300
|
+
/**
|
|
301
|
+
* Invoke a caller's `onFinish` for a native turn.
|
|
302
|
+
*
|
|
303
|
+
* On the standard path `onFinish` is converted into lifecycle middleware and
|
|
304
|
+
* applied by wrapping the model — a step the native loops skip, so they have
|
|
305
|
+
* to call it themselves. Failures are logged and swallowed: a caller's
|
|
306
|
+
* callback must not be able to fail their generation.
|
|
307
|
+
*/
|
|
308
|
+
protected fireGenerateOnFinish(options: TextGenerationOptions, result: EnhancedGenerateResult | null, startTime: number): void;
|
|
279
309
|
protected synthesizeAIResponseIfNeeded(enhancedResult: EnhancedGenerateResult, options: TextGenerationOptions): Promise<EnhancedGenerateResult>;
|
|
280
310
|
/**
|
|
281
311
|
* Build the public TTS failure detail.
|
|
@@ -393,7 +423,45 @@ export declare abstract class BaseProvider implements AIProvider {
|
|
|
393
423
|
* REQUIRED: Every provider MUST implement this method
|
|
394
424
|
* Returns the Vercel AI SDK model instance for this provider
|
|
395
425
|
*/
|
|
426
|
+
/**
|
|
427
|
+
* Run one whole turn under the caller's timeout contract.
|
|
428
|
+
*
|
|
429
|
+
* Every generate path must go through this, including the native loops that
|
|
430
|
+
* bypass `executeGeneration`. When the Anthropic native path was first added
|
|
431
|
+
* it built its own call and never composed a timer, so `timeout: 1000`
|
|
432
|
+
* against a three-second upstream simply returned the late response — the
|
|
433
|
+
* turn budget silently stopped existing for that provider.
|
|
434
|
+
*
|
|
435
|
+
* An explicit, valid `turnTimeoutMs` is the caller's whole-turn contract and
|
|
436
|
+
* owns this hard abort; `timeout` then keeps its per-model-call meaning (it
|
|
437
|
+
* reaches the model layer via `providerOptions.neurolink`). Before that
|
|
438
|
+
* split, `timeout` alone bounded the ENTIRE multi-step loop, so a caller
|
|
439
|
+
* asking for a 40-minute turn of 5-minute calls was killed at 5 minutes
|
|
440
|
+
* flat — mid-loop, dressed as "Request was aborted.".
|
|
441
|
+
*
|
|
442
|
+
* `descriptorGenerateMs` only ever RAISES the 3-minute floor, never lowers
|
|
443
|
+
* it: several descriptors carry aspirational sub-180s numbers (openai 30s,
|
|
444
|
+
* bedrock 45s) that were never enforced, and enforcing them now would break
|
|
445
|
+
* long-running generations that have always been allowed.
|
|
446
|
+
*/
|
|
447
|
+
protected withTurnTimeout<T>(options: TextGenerationOptions, descriptorGenerateMs: number | undefined, run: (timedOptions: TextGenerationOptions) => Promise<T>): Promise<T>;
|
|
448
|
+
/**
|
|
449
|
+
* The per-provider generate budget from the descriptor, for native paths
|
|
450
|
+
* that call `withTurnTimeout` directly.
|
|
451
|
+
*/
|
|
452
|
+
protected getDescriptorGenerateMs(): number | undefined;
|
|
396
453
|
protected abstract getAISDKModel(): LanguageModel | Promise<LanguageModel>;
|
|
454
|
+
/**
|
|
455
|
+
* Public handle on this provider's model object.
|
|
456
|
+
*
|
|
457
|
+
* `getAISDKModel()` is protected because only the generation pipeline should
|
|
458
|
+
* drive it. The browser bundle needs the same handle to back its public
|
|
459
|
+
* provider factories without reaching into provider internals, so this is the
|
|
460
|
+
* one sanctioned way out. The union return lets providers that resolve their
|
|
461
|
+
* model synchronously (Anthropic) and asynchronously (the OpenAI-compatible
|
|
462
|
+
* family) both satisfy it.
|
|
463
|
+
*/
|
|
464
|
+
getModel(): LanguageModel | Promise<LanguageModel>;
|
|
397
465
|
/**
|
|
398
466
|
* Get AI SDK model with middleware applied
|
|
399
467
|
* This method wraps the base model with any configured middleware
|
|
@@ -46,7 +46,7 @@ import { StreamHandler } from "./modules/StreamHandler.js";
|
|
|
46
46
|
import { TelemetryHandler } from "./modules/TelemetryHandler.js";
|
|
47
47
|
import { ToolsManager } from "./modules/ToolsManager.js";
|
|
48
48
|
import { Utilities } from "./modules/Utilities.js";
|
|
49
|
-
import {
|
|
49
|
+
import { generateOnceNative } from "../utils/nativeSingleShot.js";
|
|
50
50
|
import { extractTokenUsage } from "../utils/tokenUtils.js";
|
|
51
51
|
/**
|
|
52
52
|
* Read the consumer-facing lifecycle callbacks buried inside a request's
|
|
@@ -1325,6 +1325,12 @@ export class BaseProvider {
|
|
|
1325
1325
|
* lifecycle callback has fired and before a chunk has reached the consumer,
|
|
1326
1326
|
* so there is no observable output to replay.
|
|
1327
1327
|
*/
|
|
1328
|
+
/**
|
|
1329
|
+
* Protected rather than private so a provider with a native generate path can
|
|
1330
|
+
* reuse it. Overriding `generate()` otherwise skips this wrapper silently,
|
|
1331
|
+
* and a retired default model stops degrading to the next live one in the
|
|
1332
|
+
* catalog — which is exactly what the provider contract gate checks.
|
|
1333
|
+
*/
|
|
1328
1334
|
async runGenerateWithModelFallback(attempt, callerOwnsFallback) {
|
|
1329
1335
|
const requestedModel = this.modelName;
|
|
1330
1336
|
try {
|
|
@@ -1505,17 +1511,15 @@ export class BaseProvider {
|
|
|
1505
1511
|
userTextLength: userText.length,
|
|
1506
1512
|
analysisLength: videoAnalysisResult.length,
|
|
1507
1513
|
});
|
|
1508
|
-
const formattedResult = await
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
messages: [{ role: "user", content: formattingPrompt }],
|
|
1514
|
+
const formattedResult = await generateOnceNative(model, {
|
|
1515
|
+
...(options.systemPrompt ? { system: options.systemPrompt } : {}),
|
|
1516
|
+
prompt: formattingPrompt,
|
|
1512
1517
|
maxOutputTokens: options.maxTokens || 8192,
|
|
1513
1518
|
temperature: 0.3,
|
|
1514
|
-
abortSignal: options.abortSignal,
|
|
1515
|
-
experimental_telemetry: this.telemetryHandler?.getTelemetryConfig(options, "generate"),
|
|
1519
|
+
...(options.abortSignal ? { abortSignal: options.abortSignal } : {}),
|
|
1516
1520
|
});
|
|
1517
1521
|
formattedContent = formattedResult.text;
|
|
1518
|
-
usage = extractTokenUsage(formattedResult.
|
|
1522
|
+
usage = extractTokenUsage(formattedResult.usage);
|
|
1519
1523
|
logger.debug("[VideoAnalysis] Claude formatting complete", {
|
|
1520
1524
|
formattedLength: formattedContent.length,
|
|
1521
1525
|
usage,
|
|
@@ -1557,42 +1561,7 @@ export class BaseProvider {
|
|
|
1557
1561
|
// Before this, `timeout` alone bounded the ENTIRE multi-step loop, so a
|
|
1558
1562
|
// caller asking for a 40-minute turn of 5-minute calls was killed at 5
|
|
1559
1563
|
// minutes flat — mid-loop, dressed as "Request was aborted.".
|
|
1560
|
-
const
|
|
1561
|
-
Number.isFinite(options.turnTimeoutMs) &&
|
|
1562
|
-
options.turnTimeoutMs > 0;
|
|
1563
|
-
const effectiveTimeout = hasValidTurnTimeout
|
|
1564
|
-
? options.turnTimeoutMs
|
|
1565
|
-
: (options.timeout ?? Math.max(descriptorGenerateMs ?? 0, 180_000));
|
|
1566
|
-
const timeoutController = createTimeoutController(effectiveTimeout, this.providerName, "generate");
|
|
1567
|
-
const composedSignal = composeAbortSignals(options.abortSignal, timeoutController?.controller.signal);
|
|
1568
|
-
const composedOptions = composedSignal
|
|
1569
|
-
? { ...options, abortSignal: composedSignal }
|
|
1570
|
-
: options;
|
|
1571
|
-
let generateResult;
|
|
1572
|
-
try {
|
|
1573
|
-
generateResult = await this.executeGeneration(model, messages, tools, composedOptions);
|
|
1574
|
-
}
|
|
1575
|
-
catch (error) {
|
|
1576
|
-
// When OUR timer fired, provider SDKs typically normalize the abort
|
|
1577
|
-
// into their own generic cancel shape (e.g. Anthropic's
|
|
1578
|
-
// APIUserAbortError, "Request was aborted.") and discard the signal's
|
|
1579
|
-
// reason. The TimeoutError on the signal is the honest identity —
|
|
1580
|
-
// rethrow it so logs and abort classification see a timeout, not a
|
|
1581
|
-
// caller cancel. A genuine caller abort (their signal fired) keeps its
|
|
1582
|
-
// original shape even if our timer also expired in the race window.
|
|
1583
|
-
const reason = timeoutController?.controller.signal.aborted
|
|
1584
|
-
? timeoutController.controller.signal.reason
|
|
1585
|
-
: undefined;
|
|
1586
|
-
if (reason instanceof TimeoutError &&
|
|
1587
|
-
isAbortError(error) &&
|
|
1588
|
-
options.abortSignal?.aborted !== true) {
|
|
1589
|
-
throw reason;
|
|
1590
|
-
}
|
|
1591
|
-
throw error;
|
|
1592
|
-
}
|
|
1593
|
-
finally {
|
|
1594
|
-
timeoutController?.cleanup();
|
|
1595
|
-
}
|
|
1564
|
+
const generateResult = await this.withTurnTimeout(options, descriptorGenerateMs, (timedOptions) => this.executeGeneration(model, messages, tools, timedOptions));
|
|
1596
1565
|
this.analyzeAIResponse(generateResult);
|
|
1597
1566
|
this.logGenerationComplete(generateResult);
|
|
1598
1567
|
const responseTime = Date.now() - startTime;
|
|
@@ -1613,6 +1582,62 @@ export class BaseProvider {
|
|
|
1613
1582
|
const finalResult = await this.enhanceResult(enhancedResult, options, startTime);
|
|
1614
1583
|
return finalResult;
|
|
1615
1584
|
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Close out a turn produced by a provider's own native generate loop.
|
|
1587
|
+
*
|
|
1588
|
+
* The native loops bypass `executeGeneration`, and with it every post-call
|
|
1589
|
+
* step the standard path runs. Each one that was missed had to be found
|
|
1590
|
+
* separately — `onFinish` stopped firing, TTS synthesis silently produced no
|
|
1591
|
+
* audio, and OTEL saw no usage for any native provider. Routing all of them
|
|
1592
|
+
* through one method is what stops the next native path from rediscovering
|
|
1593
|
+
* the same list.
|
|
1594
|
+
*
|
|
1595
|
+
* Order matters and mirrors the standard path: metrics are recorded before
|
|
1596
|
+
* synthesis so telemetry sees the model's own usage, and `enhanceResult`
|
|
1597
|
+
* runs last so analytics and evaluation observe the final content.
|
|
1598
|
+
*/
|
|
1599
|
+
async finalizeNativeGenerate(result, options, startTime) {
|
|
1600
|
+
// onFinish is NOT fired here. `applyGenerateLifecycleMiddleware` folds it
|
|
1601
|
+
// into `options.middleware.middlewareConfig.lifecycle`, and the native
|
|
1602
|
+
// paths now wrap their model, so the lifecycle middleware fires it.
|
|
1603
|
+
// Firing it here too would deliver every callback twice.
|
|
1604
|
+
await this.recordPerformanceMetrics(result.usage, Date.now() - startTime);
|
|
1605
|
+
const synthesized = await this.synthesizeAIResponseIfNeeded(result, options);
|
|
1606
|
+
return this.enhanceResult(synthesized, options, startTime);
|
|
1607
|
+
}
|
|
1608
|
+
/**
|
|
1609
|
+
* Invoke a caller's `onFinish` for a native turn.
|
|
1610
|
+
*
|
|
1611
|
+
* On the standard path `onFinish` is converted into lifecycle middleware and
|
|
1612
|
+
* applied by wrapping the model — a step the native loops skip, so they have
|
|
1613
|
+
* to call it themselves. Failures are logged and swallowed: a caller's
|
|
1614
|
+
* callback must not be able to fail their generation.
|
|
1615
|
+
*/
|
|
1616
|
+
fireGenerateOnFinish(options, result, startTime) {
|
|
1617
|
+
const onFinish = options
|
|
1618
|
+
.onFinish;
|
|
1619
|
+
if (typeof onFinish !== "function") {
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
try {
|
|
1623
|
+
const usage = result?.usage;
|
|
1624
|
+
const cb = onFinish({
|
|
1625
|
+
text: result?.content || "",
|
|
1626
|
+
usage: usage
|
|
1627
|
+
? {
|
|
1628
|
+
promptTokens: usage.input ?? 0,
|
|
1629
|
+
completionTokens: usage.output ?? 0,
|
|
1630
|
+
}
|
|
1631
|
+
: undefined,
|
|
1632
|
+
duration: Date.now() - startTime,
|
|
1633
|
+
finishReason: result?.finishReason ?? "stop",
|
|
1634
|
+
});
|
|
1635
|
+
Promise.resolve(cb).catch((err) => logger.warn(`[${this.providerName}] onFinish callback rejected: ${err instanceof Error ? err.message : String(err)}`));
|
|
1636
|
+
}
|
|
1637
|
+
catch (err) {
|
|
1638
|
+
logger.warn(`[${this.providerName}] onFinish callback threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1616
1641
|
async synthesizeAIResponseIfNeeded(enhancedResult, options) {
|
|
1617
1642
|
if (!options.tts?.enabled || !options.tts?.useAiResponse) {
|
|
1618
1643
|
return enhancedResult;
|
|
@@ -1901,6 +1926,89 @@ export class BaseProvider {
|
|
|
1901
1926
|
metadata,
|
|
1902
1927
|
};
|
|
1903
1928
|
}
|
|
1929
|
+
/**
|
|
1930
|
+
* REQUIRED: Every provider MUST implement this method
|
|
1931
|
+
* Returns the Vercel AI SDK model instance for this provider
|
|
1932
|
+
*/
|
|
1933
|
+
/**
|
|
1934
|
+
* Run one whole turn under the caller's timeout contract.
|
|
1935
|
+
*
|
|
1936
|
+
* Every generate path must go through this, including the native loops that
|
|
1937
|
+
* bypass `executeGeneration`. When the Anthropic native path was first added
|
|
1938
|
+
* it built its own call and never composed a timer, so `timeout: 1000`
|
|
1939
|
+
* against a three-second upstream simply returned the late response — the
|
|
1940
|
+
* turn budget silently stopped existing for that provider.
|
|
1941
|
+
*
|
|
1942
|
+
* An explicit, valid `turnTimeoutMs` is the caller's whole-turn contract and
|
|
1943
|
+
* owns this hard abort; `timeout` then keeps its per-model-call meaning (it
|
|
1944
|
+
* reaches the model layer via `providerOptions.neurolink`). Before that
|
|
1945
|
+
* split, `timeout` alone bounded the ENTIRE multi-step loop, so a caller
|
|
1946
|
+
* asking for a 40-minute turn of 5-minute calls was killed at 5 minutes
|
|
1947
|
+
* flat — mid-loop, dressed as "Request was aborted.".
|
|
1948
|
+
*
|
|
1949
|
+
* `descriptorGenerateMs` only ever RAISES the 3-minute floor, never lowers
|
|
1950
|
+
* it: several descriptors carry aspirational sub-180s numbers (openai 30s,
|
|
1951
|
+
* bedrock 45s) that were never enforced, and enforcing them now would break
|
|
1952
|
+
* long-running generations that have always been allowed.
|
|
1953
|
+
*/
|
|
1954
|
+
async withTurnTimeout(options, descriptorGenerateMs, run) {
|
|
1955
|
+
const hasValidTurnTimeout = typeof options.turnTimeoutMs === "number" &&
|
|
1956
|
+
Number.isFinite(options.turnTimeoutMs) &&
|
|
1957
|
+
options.turnTimeoutMs > 0;
|
|
1958
|
+
const effectiveTimeout = hasValidTurnTimeout
|
|
1959
|
+
? options.turnTimeoutMs
|
|
1960
|
+
: (options.timeout ?? Math.max(descriptorGenerateMs ?? 0, 180_000));
|
|
1961
|
+
const timeoutController = createTimeoutController(effectiveTimeout, this.providerName, "generate");
|
|
1962
|
+
const composedSignal = composeAbortSignals(options.abortSignal, timeoutController?.controller.signal);
|
|
1963
|
+
const timedOptions = composedSignal
|
|
1964
|
+
? { ...options, abortSignal: composedSignal }
|
|
1965
|
+
: options;
|
|
1966
|
+
try {
|
|
1967
|
+
return await run(timedOptions);
|
|
1968
|
+
}
|
|
1969
|
+
catch (error) {
|
|
1970
|
+
// When OUR timer fired, provider SDKs typically normalize the abort
|
|
1971
|
+
// into their own generic cancel shape (e.g. Anthropic's
|
|
1972
|
+
// APIUserAbortError, "Request was aborted.") and discard the signal's
|
|
1973
|
+
// reason. The TimeoutError on the signal is the honest identity —
|
|
1974
|
+
// rethrow it so logs and abort classification see a timeout, not a
|
|
1975
|
+
// caller cancel. A genuine caller abort (their signal fired) keeps its
|
|
1976
|
+
// original shape even if our timer also expired in the race window.
|
|
1977
|
+
const reason = timeoutController?.controller.signal.aborted
|
|
1978
|
+
? timeoutController.controller.signal.reason
|
|
1979
|
+
: undefined;
|
|
1980
|
+
if (reason instanceof TimeoutError &&
|
|
1981
|
+
isAbortError(error) &&
|
|
1982
|
+
options.abortSignal?.aborted !== true) {
|
|
1983
|
+
throw reason;
|
|
1984
|
+
}
|
|
1985
|
+
throw error;
|
|
1986
|
+
}
|
|
1987
|
+
finally {
|
|
1988
|
+
timeoutController?.cleanup();
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
/**
|
|
1992
|
+
* The per-provider generate budget from the descriptor, for native paths
|
|
1993
|
+
* that call `withTurnTimeout` directly.
|
|
1994
|
+
*/
|
|
1995
|
+
getDescriptorGenerateMs() {
|
|
1996
|
+
return PROVIDER_DESCRIPTORS_BY_NAME.get(this.providerName)?.timeouts
|
|
1997
|
+
?.generateMs;
|
|
1998
|
+
}
|
|
1999
|
+
/**
|
|
2000
|
+
* Public handle on this provider's model object.
|
|
2001
|
+
*
|
|
2002
|
+
* `getAISDKModel()` is protected because only the generation pipeline should
|
|
2003
|
+
* drive it. The browser bundle needs the same handle to back its public
|
|
2004
|
+
* provider factories without reaching into provider internals, so this is the
|
|
2005
|
+
* one sanctioned way out. The union return lets providers that resolve their
|
|
2006
|
+
* model synchronously (Anthropic) and asynchronously (the OpenAI-compatible
|
|
2007
|
+
* family) both satisfy it.
|
|
2008
|
+
*/
|
|
2009
|
+
getModel() {
|
|
2010
|
+
return this.getAISDKModel();
|
|
2011
|
+
}
|
|
1904
2012
|
/**
|
|
1905
2013
|
* Get AI SDK model with middleware applied
|
|
1906
2014
|
* This method wraps the base model with any configured middleware
|
|
@@ -12,9 +12,8 @@
|
|
|
12
12
|
*
|
|
13
13
|
* @module core/modules/GenerationHandler
|
|
14
14
|
*/
|
|
15
|
-
import type { AIProviderName, EnhancedGenerateResult, NeuroLinkEvents, StandardRecord, TextGenerationOptions, ToolExecutionRecord, TypedEventEmitter } from "../../types/index.js";
|
|
15
|
+
import type { GenerateTextResult, AIProviderName, EnhancedGenerateResult, NeuroLinkEvents, StandardRecord, TextGenerationOptions, ToolExecutionRecord, TypedEventEmitter } from "../../types/index.js";
|
|
16
16
|
import type { LanguageModel, ModelMessage, Tool } from "../../types/index.js";
|
|
17
|
-
import { generateText } from "../../utils/generation.js";
|
|
18
17
|
/**
|
|
19
18
|
* Turn budget + wrap-up deadline (parity with the googleVertex native loops).
|
|
20
19
|
* A deadline is engaged only when the caller expressed one: turnTimeoutMs
|
|
@@ -39,6 +38,9 @@ export declare function resolveTurnBudget(options: TextGenerationOptions, turnSt
|
|
|
39
38
|
wrapupLeadMs: number;
|
|
40
39
|
turnDeadline: number | undefined;
|
|
41
40
|
};
|
|
41
|
+
/**
|
|
42
|
+
* GenerationHandler class - Handles text generation operations for AI providers
|
|
43
|
+
*/
|
|
42
44
|
export declare class GenerationHandler {
|
|
43
45
|
private readonly providerName;
|
|
44
46
|
private readonly modelName;
|
|
@@ -81,17 +83,28 @@ export declare class GenerationHandler {
|
|
|
81
83
|
*/
|
|
82
84
|
deps?: {
|
|
83
85
|
getEmitterFn?: () => TypedEventEmitter<NeuroLinkEvents> | undefined;
|
|
84
|
-
generateTextFn?:
|
|
86
|
+
generateTextFn?: (options: Record<string, unknown>) => Promise<GenerateTextResult<Record<string, Tool>, unknown>>;
|
|
85
87
|
});
|
|
86
88
|
/**
|
|
87
89
|
* Helper method to call generateText with optional structured output
|
|
88
90
|
* @private
|
|
89
91
|
*/
|
|
90
|
-
private callGenerateText;
|
|
91
92
|
/**
|
|
92
|
-
*
|
|
93
|
+
* The ai-package generate loop.
|
|
94
|
+
*
|
|
95
|
+
* Unreachable: every text provider now implements a native generate() and
|
|
96
|
+
* none of them return here. That was established by trapping the seam —
|
|
97
|
+
* replacing the ai package's generateText with a throwing stub left the full
|
|
98
|
+
* provider matrix passing and zero cells reaching it — and non-text request
|
|
99
|
+
* kinds return from runGenerateInActiveContext before this handler is
|
|
100
|
+
* consulted.
|
|
101
|
+
*
|
|
102
|
+
* Kept as an explicit failure rather than deleted outright so a provider
|
|
103
|
+
* added without a native generate() fails loudly here instead of silently
|
|
104
|
+
* reintroducing a dependency on the removed package.
|
|
93
105
|
*/
|
|
94
|
-
|
|
106
|
+
private callGenerateText;
|
|
107
|
+
executeGeneration(model: LanguageModel, messages: ModelMessage[], tools: Record<string, Tool>, options: TextGenerationOptions): Promise<GenerateTextResult<Record<string, Tool>, unknown>>;
|
|
95
108
|
/**
|
|
96
109
|
* Extract cache metrics from provider metadata (e.g. Anthropic's providerMetadata.anthropic)
|
|
97
110
|
* The AI SDK's LanguageModelUsage only has inputTokens/outputTokens.
|
|
@@ -109,11 +122,11 @@ export declare class GenerationHandler {
|
|
|
109
122
|
/**
|
|
110
123
|
* Log generation completion information
|
|
111
124
|
*/
|
|
112
|
-
logGenerationComplete(generateResult:
|
|
125
|
+
logGenerationComplete(generateResult: GenerateTextResult<Record<string, Tool>, unknown>): void;
|
|
113
126
|
/**
|
|
114
127
|
* Extract tool information from generation result
|
|
115
128
|
*/
|
|
116
|
-
extractToolInformation(generateResult:
|
|
129
|
+
extractToolInformation(generateResult: GenerateTextResult<Record<string, Tool>, unknown>): {
|
|
117
130
|
toolsUsed: string[];
|
|
118
131
|
toolExecutions: Array<{
|
|
119
132
|
name: string;
|
|
@@ -124,22 +137,7 @@ export declare class GenerationHandler {
|
|
|
124
137
|
/**
|
|
125
138
|
* Format the enhanced result
|
|
126
139
|
*/
|
|
127
|
-
|
|
128
|
-
* One re-ask with `toolChoice: "none"` when a tool loop ends on a
|
|
129
|
-
* `tool-calls` finish that carries neither a tool call nor any text.
|
|
130
|
-
*
|
|
131
|
-
* io.net's Llama endpoint does exactly this on the step after a tool result
|
|
132
|
-
* when the caller asked for JSON: the model's JSON-shaped answer trips the
|
|
133
|
-
* vendor's tool-call parser, which drops it and reports
|
|
134
|
-
* `finish_reason: tool_calls` with `content: null` and no `tool_calls`.
|
|
135
|
-
* The AI-SDK loop has nothing to execute and stops, so the caller gets an
|
|
136
|
-
* empty turn although the tool ran. Replaying that request with
|
|
137
|
-
* `tool_choice: "none"` (or no tool list) returns the answer — verified on
|
|
138
|
-
* the wire, 4/4 — so the recovery is one bounded extra step that keeps the
|
|
139
|
-
* executed tool steps and usage in the returned result.
|
|
140
|
-
*/
|
|
141
|
-
private recoverEmptyToolCallsFinish;
|
|
142
|
-
formatEnhancedResult(generateResult: Awaited<ReturnType<typeof generateText>>, tools: Record<string, Tool>, toolsUsed: string[], toolExecutions: ToolExecutionRecord[], options: TextGenerationOptions): EnhancedGenerateResult;
|
|
140
|
+
formatEnhancedResult(generateResult: GenerateTextResult<Record<string, Tool>, unknown>, tools: Record<string, Tool>, toolsUsed: string[], toolExecutions: ToolExecutionRecord[], options: TextGenerationOptions): EnhancedGenerateResult;
|
|
143
141
|
/**
|
|
144
142
|
* Analyze AI response structure and log detailed debugging information
|
|
145
143
|
*/
|