@juspay/neurolink 12.11.3 → 12.12.1

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.
Files changed (52) hide show
  1. package/CHANGELOG.md +2 -3
  2. package/dist/browser/neurolink.min.js +533 -581
  3. package/dist/constants/enums.d.ts +13 -0
  4. package/dist/constants/enums.js +14 -0
  5. package/dist/core/baseProvider.d.ts +71 -3
  6. package/dist/core/baseProvider.js +152 -44
  7. package/dist/core/modules/GenerationHandler.d.ts +22 -24
  8. package/dist/core/modules/GenerationHandler.js +28 -463
  9. package/dist/core/nativeGenerateLoop.d.ts +35 -0
  10. package/dist/core/nativeGenerateLoop.js +299 -0
  11. package/dist/files/fileTools.d.ts +5 -5
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.js +4 -0
  14. package/dist/middleware/builtin/guardrails.d.ts +0 -5
  15. package/dist/middleware/builtin/guardrails.js +33 -5
  16. package/dist/middleware/factory.js +1 -1
  17. package/dist/middleware/wrapLanguageModel.d.ts +18 -0
  18. package/dist/middleware/wrapLanguageModel.js +53 -0
  19. package/dist/processors/media/AudioProcessor.js +46 -11
  20. package/dist/providers/amazonSagemaker.d.ts +17 -1
  21. package/dist/providers/amazonSagemaker.js +110 -0
  22. package/dist/providers/anthropic/client.d.ts +11 -0
  23. package/dist/providers/anthropic/client.js +148 -1
  24. package/dist/providers/catalog/index.generated.d.ts +1 -1
  25. package/dist/providers/catalog/index.generated.js +3 -0
  26. package/dist/providers/catalog/loader.js +1 -0
  27. package/dist/providers/catalog/mancer.json +192 -0
  28. package/dist/providers/configuredOpenAICompat.d.ts +11 -0
  29. package/dist/providers/configuredOpenAICompat.js +16 -0
  30. package/dist/providers/googleVertex/client.d.ts +0 -9
  31. package/dist/providers/googleVertex/client.js +0 -33
  32. package/dist/providers/openaiChatCompletionsBase.d.ts +21 -4
  33. package/dist/providers/openaiChatCompletionsBase.js +220 -0
  34. package/dist/providers/providerTypeUtils.d.ts +1 -2
  35. package/dist/providers/providerTypeUtils.js +5 -1
  36. package/dist/types/aiCompat.d.ts +485 -0
  37. package/dist/types/aiCompat.js +17 -0
  38. package/dist/types/conversation.d.ts +1 -1
  39. package/dist/types/generate.d.ts +52 -0
  40. package/dist/types/middleware.d.ts +3 -6
  41. package/dist/types/providerCatalog.generated.d.ts +2 -2
  42. package/dist/types/providers.d.ts +14 -1
  43. package/dist/types/tools.d.ts +2 -2
  44. package/dist/utils/generationErrors.d.ts +78 -6
  45. package/dist/utils/generationErrors.js +114 -6
  46. package/dist/utils/nativeSingleShot.d.ts +3 -0
  47. package/dist/utils/nativeSingleShot.js +83 -0
  48. package/dist/utils/tool.d.ts +30 -5
  49. package/dist/utils/tool.js +43 -5
  50. package/package.json +3 -6
  51. package/dist/utils/generation.d.ts +0 -8
  52. 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",
@@ -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
- private recordPerformanceMetrics;
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
- private runGenerateWithModelFallback;
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 { generateText } from "../utils/generation.js";
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 generateText({
1509
- model,
1510
- system: options.systemPrompt,
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.totalUsage ?? formattedResult.usage);
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 hasValidTurnTimeout = typeof options.turnTimeoutMs === "number" &&
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?: typeof generateText;
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
- * Execute the generation with AI SDK
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
- executeGeneration(model: LanguageModel, messages: ModelMessage[], tools: Record<string, Tool>, options: TextGenerationOptions): Promise<Awaited<ReturnType<typeof generateText>>>;
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: Awaited<ReturnType<typeof generateText>>): void;
125
+ logGenerationComplete(generateResult: GenerateTextResult<Record<string, Tool>, unknown>): void;
113
126
  /**
114
127
  * Extract tool information from generation result
115
128
  */
116
- extractToolInformation(generateResult: Awaited<ReturnType<typeof generateText>>): {
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
  */