@juspay/neurolink 12.47.13 → 12.48.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.
Files changed (94) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/dist/auth/middleware/AuthMiddleware.d.ts +1 -1
  3. package/dist/browser/neurolink.min.js +578 -565
  4. package/dist/cli/factories/commandFactory.d.ts +7 -0
  5. package/dist/cli/factories/commandFactory.js +26 -0
  6. package/dist/cli/loop/optionsSchema.js +9 -0
  7. package/dist/context/budgetChecker.js +54 -2
  8. package/dist/context/contextDecision.js +2 -0
  9. package/dist/context/toolPairRepair.d.ts +12 -0
  10. package/dist/context/toolPairRepair.js +29 -3
  11. package/dist/core/baseProvider.d.ts +92 -3
  12. package/dist/core/baseProvider.js +254 -10
  13. package/dist/core/conversationMemoryManager.js +17 -2
  14. package/dist/core/loopEngine.js +1 -1
  15. package/dist/core/modules/MessageBuilder.js +11 -3
  16. package/dist/core/modules/TelemetryHandler.d.ts +2 -2
  17. package/dist/core/modules/ToolsManager.d.ts +35 -0
  18. package/dist/core/modules/ToolsManager.js +159 -55
  19. package/dist/core/nativeGenerateLoop.d.ts +58 -1
  20. package/dist/core/nativeGenerateLoop.js +277 -3
  21. package/dist/core/redisConversationMemoryManager.js +10 -3
  22. package/dist/core/resolveRequestKind.d.ts +24 -4
  23. package/dist/core/resolveRequestKind.js +31 -4
  24. package/dist/core/toolExecutionRecorder.d.ts +6 -0
  25. package/dist/core/toolExecutionRecorder.js +24 -2
  26. package/dist/core/toolRoutingDecision.js +1 -0
  27. package/dist/factories/mediaHandlerCatalog.js +5 -0
  28. package/dist/factories/providerDescriptors.js +15 -0
  29. package/dist/files/fileTools.d.ts +1 -1
  30. package/dist/index.d.ts +5 -2
  31. package/dist/index.js +9 -2
  32. package/dist/memory/condensationPrompt.d.ts +8 -0
  33. package/dist/memory/condensationPrompt.js +29 -0
  34. package/dist/memory/hippocampusInitializer.d.ts +14 -2
  35. package/dist/memory/hippocampusInitializer.js +64 -3
  36. package/dist/neurolink.d.ts +252 -3
  37. package/dist/neurolink.js +898 -43
  38. package/dist/providers/amazonBedrock/client.js +6 -0
  39. package/dist/providers/amazonSagemaker.js +14 -1
  40. package/dist/providers/anthropic/client.js +148 -19
  41. package/dist/providers/anthropic/loopAdapter.js +2 -2
  42. package/dist/providers/googleAiStudio/client.js +29 -14
  43. package/dist/providers/openaiChatCompletionsBase.js +103 -23
  44. package/dist/providers/systemOneDecision.d.ts +6 -0
  45. package/dist/providers/systemOneDecision.js +14 -63
  46. package/dist/providers/typesafe.d.ts +6 -0
  47. package/dist/providers/typesafe.js +6 -0
  48. package/dist/rag/retrieval/searchDecision.js +1 -0
  49. package/dist/routing/classifierRouter.js +6 -5
  50. package/dist/routing/classifierStrategies.js +8 -1
  51. package/dist/types/classifierRouter.d.ts +16 -3
  52. package/dist/types/common.d.ts +2 -0
  53. package/dist/types/config.d.ts +9 -0
  54. package/dist/types/context.d.ts +8 -1
  55. package/dist/types/conversation.d.ts +87 -0
  56. package/dist/types/decision.d.ts +210 -1
  57. package/dist/types/decision.js +9 -1
  58. package/dist/types/dispatch.d.ts +11 -4
  59. package/dist/types/generate.d.ts +118 -54
  60. package/dist/types/loopEngine.d.ts +7 -2
  61. package/dist/types/memory.d.ts +177 -9
  62. package/dist/types/openaiCompatible.d.ts +11 -3
  63. package/dist/types/stream.d.ts +13 -16
  64. package/dist/types/stt.d.ts +58 -1
  65. package/dist/types/tools.d.ts +14 -0
  66. package/dist/types/tools.js +8 -0
  67. package/dist/types/tts.d.ts +108 -0
  68. package/dist/types/voice.d.ts +41 -4
  69. package/dist/utils/decisionAnswers.d.ts +31 -1
  70. package/dist/utils/decisionAnswers.js +114 -0
  71. package/dist/utils/decisionLimits.d.ts +39 -0
  72. package/dist/utils/decisionLimits.js +116 -0
  73. package/dist/utils/logger.d.ts +7 -0
  74. package/dist/utils/logger.js +9 -0
  75. package/dist/utils/messageBuilder.d.ts +45 -1
  76. package/dist/utils/messageBuilder.js +393 -16
  77. package/dist/utils/multimodalOptionsBuilder.d.ts +1 -0
  78. package/dist/utils/multimodalOptionsBuilder.js +1 -0
  79. package/dist/utils/speechText.d.ts +41 -0
  80. package/dist/utils/speechText.js +364 -0
  81. package/dist/utils/toolResultStatus.d.ts +26 -0
  82. package/dist/utils/toolResultStatus.js +73 -0
  83. package/dist/utils/ttsProcessor.d.ts +29 -0
  84. package/dist/utils/ttsProcessor.js +84 -2
  85. package/dist/voice/index.d.ts +1 -0
  86. package/dist/voice/index.js +3 -0
  87. package/dist/voice/providers/ElevenLabsSTT.d.ts +42 -0
  88. package/dist/voice/providers/ElevenLabsSTT.js +351 -0
  89. package/dist/voice/providers/ElevenLabsTTS.d.ts +81 -1
  90. package/dist/voice/providers/ElevenLabsTTS.js +415 -74
  91. package/docs-site/static/search-index.json +48 -33
  92. package/package.json +5 -2
  93. package/dist/utils/toolEndEmitter.d.ts +0 -25
  94. package/dist/utils/toolEndEmitter.js +0 -64
@@ -210,6 +210,10 @@ export class AmazonBedrockProvider extends BaseProvider {
210
210
  });
211
211
  // Cast options to StreamOptions for multimodal processing
212
212
  const streamOptions = options;
213
+ // This provider bypasses BaseProvider's message builders, so the
214
+ // instance-level `conversationMemory.replayToolSteps` has to be folded
215
+ // in here or only the per-request value would ever apply.
216
+ this.applyToolReplayDefault(streamOptions);
213
217
  const multimodalOptions = buildMultimodalOptions(streamOptions, this.providerName, this.modelName);
214
218
  const multimodalMessages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
215
219
  // Convert to Bedrock format
@@ -923,6 +927,8 @@ export class AmazonBedrockProvider extends BaseProvider {
923
927
  hasPDFFiles: !!options.input?.pdfFiles?.length,
924
928
  pdfFileCount: options.input?.pdfFiles?.length || 0,
925
929
  });
930
+ // Same instance-default fold as the generate() path above.
931
+ this.applyToolReplayDefault(options);
926
932
  const multimodalOptions = buildMultimodalOptions(options, this.providerName, this.modelName);
927
933
  const multimodalMessages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
928
934
  // Convert to Bedrock format
@@ -6,6 +6,7 @@ import { resolveToolExecutionRecords, toolCallsFromSummaries, } from "../core/to
6
6
  import { transformToolExecutions } from "../utils/transformationUtils.js";
7
7
  import { convertZodToJsonSchema } from "../utils/schemaConversion.js";
8
8
  import { withProviderRetry } from "../utils/providerRetry.js";
9
+ import { resolveToolChoice } from "../utils/toolChoice.js";
9
10
  import { DEFAULT_MAX_STEPS } from "../core/constants.js";
10
11
  import { hasNativeDoGenerate, runNativeGenerateLoop, } from "../core/nativeGenerateLoop.js";
11
12
  import { withSpan } from "../telemetry/withSpan.js";
@@ -233,6 +234,17 @@ export class AmazonSageMakerProvider extends BaseProvider {
233
234
  ...(responseFormat ? { responseFormat } : {}),
234
235
  ...(v3Tools.length > 0 ? { tools: v3Tools } : {}),
235
236
  toolsRecord,
237
+ // Was never forwarded here, so a caller's toolChoice degraded to the
238
+ // endpoint's default on SageMaker alone; the language model already
239
+ // maps it onto `tool_choice`.
240
+ ...(v3Tools.length > 0 && options.toolChoice
241
+ ? { toolChoice: resolveToolChoice(options, toolsRecord, true) }
242
+ : {}),
243
+ ...(options.toolChoiceSteps !== undefined
244
+ ? { toolChoiceSteps: options.toolChoiceSteps }
245
+ : {}),
246
+ ...(options.prepareStep ? { prepareStep: options.prepareStep } : {}),
247
+ modelId: this.modelName,
236
248
  maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
237
249
  ...(options.maxTokens ? { maxOutputTokens: options.maxTokens } : {}),
238
250
  ...(options.temperature !== undefined
@@ -245,6 +257,7 @@ export class AmazonSageMakerProvider extends BaseProvider {
245
257
  runStep: (call) => withProviderRetry(call, undefined, "sagemaker generate", undefined, options.abortSignal).catch((err) => {
246
258
  throw this.handleProviderError(err);
247
259
  }),
260
+ onRejectedToolCall: (name, error, id) => this.emitRejectedToolCall(name, error, id),
248
261
  }, toolExecutionSummaries);
249
262
  const enhanced = {
250
263
  content: loop.text,
@@ -263,7 +276,7 @@ export class AmazonSageMakerProvider extends BaseProvider {
263
276
  toolExecutions: resolveToolExecutionRecords(options, transformToolExecutions(toolExecutionSummaries)),
264
277
  enhancedWithTools: loop.toolsUsed.length > 0,
265
278
  };
266
- return this.finalizeNativeGenerate(enhanced, options, startTime);
279
+ return this.finalizeNativeGenerate(enhanced, options, startTime, toolExecutionSummaries);
267
280
  }
268
281
  /**
269
282
  * Streaming was previously an `executeStream` override that unconditionally
@@ -26,7 +26,7 @@ import { stringifyAnthropicToolOutput } from "./toolOutput.js";
26
26
  import { createAnthropicLoopAdapter } from "./loopAdapter.js";
27
27
  import { DEFAULT_BEFORE_STEP_TIMEOUT_MS } from "../../utils/parameterValidation.js";
28
28
  import { runAgenticLoop } from "../../core/loopEngine.js";
29
- import { hasNativeDoGenerate, runNativeGenerateLoop, } from "../../core/nativeGenerateLoop.js";
29
+ import { hasNativeDoGenerate, resolveStepToolChoice, runNativeGenerateLoop, toPrepareStepRecord, } from "../../core/nativeGenerateLoop.js";
30
30
  import { withProviderRetry } from "../../utils/providerRetry.js";
31
31
  import { resolveRequestKind } from "../../core/resolveRequestKind.js";
32
32
  import { resolveToolExecutionRecords, toolCallsFromSummaries, } from "../../core/toolExecutionRecorder.js";
@@ -35,7 +35,6 @@ import { createAnthropicConfig, getProviderModel, validateApiKey, } from "../../
35
35
  import { composeAbortSignals, composeAbortSignalsScoped, createTimeoutController, mergeAbortSignals, TimeoutError, } from "../../utils/timeout.js";
36
36
  import { raceWithAbort } from "../../utils/async/index.js";
37
37
  import { normalizeResolvedToolChoice, resolveToolChoice, } from "../../utils/toolChoice.js";
38
- import { emitToolEndFromStepFinish } from "../../utils/toolEndEmitter.js";
39
38
  import { NoOutputGeneratedError } from "../../utils/generationErrors.js";
40
39
  import { buildNoOutputSentinel, stampNoOutputSpan, } from "../../utils/noOutputSentinel.js";
41
40
  import { convertZodToJsonSchema } from "../../utils/schemaConversion.js";
@@ -1613,6 +1612,11 @@ export class AnthropicProvider extends BaseProvider {
1613
1612
  ...(hasTools && options.toolChoice
1614
1613
  ? { toolChoice: resolveToolChoice(options, toolsRecord, true) }
1615
1614
  : {}),
1615
+ ...(options.toolChoiceSteps !== undefined
1616
+ ? { toolChoiceSteps: options.toolChoiceSteps }
1617
+ : {}),
1618
+ ...(options.prepareStep ? { prepareStep: options.prepareStep } : {}),
1619
+ modelId,
1616
1620
  ...(responseFormat ? { responseFormat } : {}),
1617
1621
  ...(providerOptions ? { providerOptions } : {}),
1618
1622
  maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
@@ -1627,6 +1631,7 @@ export class AnthropicProvider extends BaseProvider {
1627
1631
  runStep: (call) => withProviderRetry(call, trace.getActiveSpan() ?? undefined, "anthropic generate", undefined, options.abortSignal).catch((err) => {
1628
1632
  throw this.handleProviderError(err);
1629
1633
  }),
1634
+ onRejectedToolCall: (name, error, id) => this.emitRejectedToolCall(name, error, id),
1630
1635
  }, toolExecutionSummaries);
1631
1636
  const enhanced = {
1632
1637
  content: loop.text,
@@ -1660,7 +1665,7 @@ export class AnthropicProvider extends BaseProvider {
1660
1665
  toolExecutions: resolveToolExecutionRecords(options, transformToolExecutions(toolExecutionSummaries)),
1661
1666
  enhancedWithTools: loop.toolsUsed.length > 0,
1662
1667
  };
1663
- return this.finalizeNativeGenerate(enhanced, options, startTime);
1668
+ return this.finalizeNativeGenerate(enhanced, options, startTime, toolExecutionSummaries);
1664
1669
  }
1665
1670
  /**
1666
1671
  * Fold a captured snapshot into the provider's usage bookkeeping and log it.
@@ -1748,6 +1753,11 @@ export class AnthropicProvider extends BaseProvider {
1748
1753
  // options.tools is pre-merged by BaseProvider.stream() with base tools
1749
1754
  // (MCP/built-in) + user-provided tools (RAG, etc.)
1750
1755
  shouldUseTools = !options.disableTools && this.supportsTools();
1756
+ // Used as is: BaseProvider.stream() already event-wrapped, recorder-
1757
+ // wrapped and discovery-partitioned this record, and search_tools
1758
+ // hydrates new tools into THIS object mid-turn. A copy would lose the
1759
+ // null prototype, the non-enumerable resolver and every later
1760
+ // hydration, so a discovered tool the model then called was "not found".
1751
1761
  toolsRecord = shouldUseTools
1752
1762
  ? options.tools || (await this.getAllTools())
1753
1763
  : {};
@@ -1776,9 +1786,17 @@ export class AnthropicProvider extends BaseProvider {
1776
1786
  throw this.handleProviderError(setupErr);
1777
1787
  }
1778
1788
  const modelId = this.modelName || getDefaultAnthropicModel();
1779
- const anthropicToolChoice = shouldUseTools && anthropicTools && anthropicTools.length > 0
1780
- ? relaxForcedToolChoice(modelId, toolChoiceToAnthropic(resolveToolChoice(options, toolsRecord, shouldUseTools)))
1789
+ // The turn's choice in NeuroLink shape. Translated to the wire PER STEP
1790
+ // inside buildParams, because a forced choice applies only to the first
1791
+ // `toolChoiceSteps` steps — baked in once, it compelled a tool call on
1792
+ // every step and the turn ran until maxSteps.
1793
+ const turnToolChoice = shouldUseTools && anthropicTools && anthropicTools.length > 0
1794
+ ? resolveToolChoice(options, toolsRecord, shouldUseTools)
1781
1795
  : undefined;
1796
+ // Completed-step records handed to a caller's prepareStep hook. Filled by
1797
+ // the buildToolResultMessages wrapper below, which is the only per-step
1798
+ // hook that sees a step's calls and results together.
1799
+ const stepRecords = [];
1782
1800
  // Extended thinking: enabled when the caller supplies an explicit token
1783
1801
  // budget (mirrors the previous experimental_thinking gating). Thinking
1784
1802
  // deltas stream out on the `reasoning` chunk channel.
@@ -1803,7 +1821,6 @@ export class AnthropicProvider extends BaseProvider {
1803
1821
  },
1804
1822
  });
1805
1823
  const maxSteps = options.maxSteps || DEFAULT_MAX_STEPS;
1806
- const emitter = this.neurolink?.getEventEmitter();
1807
1824
  const channel = createStreamChannel();
1808
1825
  const { push: pushChunk } = channel;
1809
1826
  const { usagePromise, finishPromise, resolveUsage, resolveFinish } = createDeferredAnalytics();
@@ -1842,7 +1859,15 @@ export class AnthropicProvider extends BaseProvider {
1842
1859
  });
1843
1860
  let capturedProviderError;
1844
1861
  const client = this.client;
1862
+ // Every attempted call, for the no-output sentinel below; the result
1863
+ // reports only calls that returned a value, the rule the generate loop
1864
+ // and Bedrock already follow.
1845
1865
  const toolsUsed = [];
1866
+ const toolsSucceeded = [];
1867
+ // Every tool this turn ran, in the loop's own summary shape. Backs the
1868
+ // result's toolCalls / toolResults / toolExecutions getters, which read it
1869
+ // live so a consumer that drains the stream first sees the full turn.
1870
+ const streamToolSummaries = [];
1846
1871
  const streamStartTime = Date.now();
1847
1872
  // Mutable-reference contract from StreamResult.metadata: created before
1848
1873
  // the loop and filled once the turn drains, because wrapper spreads
@@ -1965,7 +1990,27 @@ export class AnthropicProvider extends BaseProvider {
1965
1990
  }
1966
1991
  return { conversation: rebuilt };
1967
1992
  };
1968
- const buildParams = (conversation) => {
1993
+ const buildParams = async (conversation, step) => {
1994
+ // Consulted whenever there is a base choice OR a hook: a hook with
1995
+ // no base choice still gets to force a tool on a given step.
1996
+ // No declared tools → no tool_choice and no hook call; Anthropic
1997
+ // rejects a tool_choice on a request without tools.
1998
+ const anthropicToolChoice = (turnToolChoice !== undefined || options.prepareStep) &&
1999
+ anthropicTools &&
2000
+ anthropicTools.length > 0
2001
+ ? // Per step, so a prepareStep-forced choice gets the same
2002
+ // Claude 5.5/5.1 relaxation as the turn's base choice.
2003
+ relaxForcedToolChoice(modelId, toolChoiceToAnthropic(await resolveStepToolChoice({
2004
+ base: turnToolChoice,
2005
+ step,
2006
+ toolChoiceSteps: options.toolChoiceSteps,
2007
+ prepareStep: options.prepareStep,
2008
+ steps: stepRecords,
2009
+ maxSteps,
2010
+ model: modelId,
2011
+ ...(abortSignal ? { abortSignal } : {}),
2012
+ })))
2013
+ : undefined;
1969
2014
  // Mid-turn discovery sync: search_tools (tools.discovery) hydrates
1970
2015
  // new tools into toolsRecord between steps; Claude only calls tools
1971
2016
  // declared in the request, so advertise them now.
@@ -2055,33 +2100,77 @@ export class AnthropicProvider extends BaseProvider {
2055
2100
  const adapter = {
2056
2101
  ...baseAdapter,
2057
2102
  buildToolResultMessages: (conversation, stepResult, toolResults, engineStep) => {
2103
+ const settledAt = new Date();
2058
2104
  for (const result of toolResults) {
2059
2105
  toolsUsed.push(result.name);
2106
+ if (!result.error) {
2107
+ toolsSucceeded.push(result.name);
2108
+ }
2109
+ streamToolSummaries.push({
2110
+ toolCallId: result.id,
2111
+ toolName: result.name,
2112
+ input: result.args,
2113
+ ...(result.error
2114
+ ? { error: result.error }
2115
+ : { output: result.output }),
2116
+ // The engine reports a step's results together, after the
2117
+ // batch has settled, so per-tool timing is not available here;
2118
+ // `toolExecutions` prefers the execution recorder's own records,
2119
+ // which do carry it.
2120
+ startTime: settledAt,
2121
+ endTime: settledAt,
2122
+ stepIndex: engineStep,
2123
+ });
2060
2124
  }
2125
+ const rawBlocks = stepResult.raw;
2126
+ stepRecords.push(toPrepareStepRecord({
2127
+ stepNumber: engineStep,
2128
+ content: rawBlocks.map((block) => block),
2129
+ text: stepResult.text,
2130
+ toolCalls: stepResult.toolCalls.map((call) => ({
2131
+ toolName: call.name,
2132
+ toolCallId: call.id,
2133
+ input: call.args,
2134
+ })),
2135
+ toolResults: toolResults.map((result) => ({
2136
+ toolName: result.name,
2137
+ toolCallId: result.id,
2138
+ output: result.error ? { error: result.error } : result.output,
2139
+ })),
2140
+ finishReason: "tool-calls",
2141
+ inputTokens: stepResult.usage.inputTokens,
2142
+ outputTokens: stepResult.usage.outputTokens,
2143
+ ...(stepResult.usage.cacheReadTokens !== undefined ||
2144
+ stepResult.usage.cacheWriteTokens !== undefined
2145
+ ? {
2146
+ cacheReadTokens: stepResult.usage.cacheReadTokens ?? 0,
2147
+ cacheWriteTokens: stepResult.usage.cacheWriteTokens ?? 0,
2148
+ }
2149
+ : {}),
2150
+ }));
2151
+ // `tool:start` / `tool:end` are emitted by the executor itself
2152
+ // (ToolsManager, external MCP, neurolink.executeTool), so nothing is
2153
+ // emitted here: a second `tool:end` per result — with a zeroed
2154
+ // responseTime — used to be, and doubled every consumer's count.
2061
2155
  const toolCallsForStorage = toolResults.map((result) => ({
2062
- type: "tool-call",
2063
2156
  toolCallId: result.id,
2064
2157
  toolName: result.name,
2065
2158
  args: result.args,
2159
+ stepIndex: engineStep,
2066
2160
  }));
2067
2161
  const toolResultsForStorage = toolResults.map((result) => result.error
2068
2162
  ? {
2069
- type: "tool-result",
2070
2163
  toolCallId: result.id,
2071
2164
  toolName: result.name,
2072
2165
  error: result.error,
2166
+ stepIndex: engineStep,
2073
2167
  }
2074
2168
  : {
2075
- type: "tool-result",
2076
2169
  toolCallId: result.id,
2077
2170
  toolName: result.name,
2078
- result: result.output,
2171
+ output: result.output,
2172
+ stepIndex: engineStep,
2079
2173
  });
2080
- emitToolEndFromStepFinish(emitter, toolResultsForStorage.map((tr) => ({
2081
- toolName: tr.toolName,
2082
- result: "result" in tr ? tr.result : undefined,
2083
- error: "error" in tr ? tr.error : undefined,
2084
- })));
2085
2174
  this.handleToolExecutionStorage(toolCallsForStorage, toolResultsForStorage, options, new Date()).catch((storageErr) => {
2086
2175
  logger.warn("[AnthropicProvider] Failed to store tool executions", {
2087
2176
  provider: this.providerName,
@@ -2344,7 +2433,7 @@ export class AnthropicProvider extends BaseProvider {
2344
2433
  }
2345
2434
  }
2346
2435
  };
2347
- return {
2436
+ const result = {
2348
2437
  stream: transformedStream(),
2349
2438
  provider: this.providerName,
2350
2439
  // Cell-3 identity fix: `this.modelName` is the raw, possibly-unset
@@ -2355,8 +2444,14 @@ export class AnthropicProvider extends BaseProvider {
2355
2444
  // build the request. Reporting the raw field previously let a
2356
2445
  // default-model turn's StreamResult claim an undefined/empty model.
2357
2446
  model: modelId,
2358
- toolCalls: [],
2359
- toolResults: [],
2447
+ // `toolCalls` / `toolResults` / `toolExecutions` are defined as live
2448
+ // getters below, over the per-turn summaries.
2449
+ // The live array: pushed as steps settle, so a consumer reading after
2450
+ // the drain sees every tool that ran and returned a value (a thrown or
2451
+ // not-found call is not "used"). This was never returned before, so
2452
+ // `toolsUsed` came back empty for every tool on this path and
2453
+ // `enhancedWithTools` was always false.
2454
+ toolsUsed: toolsSucceeded,
2360
2455
  metadata: turnMetadata,
2361
2456
  // Wire the deferred usage/finish promises into the analytics collector
2362
2457
  // (mirrors openaiChatCompletionsBase). Without this the loop computed a
@@ -2384,6 +2479,40 @@ export class AnthropicProvider extends BaseProvider {
2384
2479
  return analytics;
2385
2480
  }),
2386
2481
  };
2482
+ // Live getters, mirroring openaiChatCompletionsBase: the wrapper layers
2483
+ // (BaseProvider.stream, NeuroLink.stream) re-apply accessor descriptors
2484
+ // rather than spreading values, so these resolve when read, after the
2485
+ // background loop has run the tools — a plain value here would be the
2486
+ // empty snapshot taken before the first chunk.
2487
+ Object.defineProperty(result, "toolCalls", {
2488
+ enumerable: true,
2489
+ configurable: true,
2490
+ get: () => toolCallsFromSummaries(streamToolSummaries),
2491
+ });
2492
+ Object.defineProperty(result, "toolResults", {
2493
+ enumerable: true,
2494
+ configurable: true,
2495
+ get: () => streamToolSummaries.map((s) => ({
2496
+ toolName: s.toolName,
2497
+ id: s.toolCallId,
2498
+ status: s.error ? "failure" : "success",
2499
+ ...(s.error ? { error: s.error } : {}),
2500
+ ...(s.output !== undefined
2501
+ ? { output: s.output }
2502
+ : {}),
2503
+ })),
2504
+ });
2505
+ Object.defineProperty(result, "toolExecutions", {
2506
+ enumerable: true,
2507
+ configurable: true,
2508
+ get: () => resolveToolExecutionRecords(options, transformToolExecutions(streamToolSummaries.map((s) => ({
2509
+ toolName: s.toolName,
2510
+ input: s.input,
2511
+ output: s.error ? { error: s.error } : s.output,
2512
+ duration: s.endTime.getTime() - s.startTime.getTime(),
2513
+ })))),
2514
+ });
2515
+ return result;
2387
2516
  }
2388
2517
  async isAvailable() {
2389
2518
  try {
@@ -382,8 +382,8 @@ export function createAnthropicLoopAdapter(config) {
382
382
  execute: async (args, opts) => execute(args, opts),
383
383
  };
384
384
  },
385
- buildStepRequest(conversation, step) {
386
- return { raw: config.buildParams(conversation, step) };
385
+ async buildStepRequest(conversation, step) {
386
+ return { raw: await config.buildParams(conversation, step) };
387
387
  },
388
388
  async executeStep(request, channel, signal) {
389
389
  const params = request.raw;
@@ -8,6 +8,7 @@ import { ERROR_CODES, NeuroLinkError } from "../../utils/errorHandling.js";
8
8
  import { logger } from "../../utils/logger.js";
9
9
  import { drainDetachedPump } from "../../utils/drainDetachedPump.js";
10
10
  import { createGeminiLoopAdapter } from "../../core/geminiLoopAdapter.js";
11
+ import { isDirectTTSRequest } from "../../core/resolveRequestKind.js";
11
12
  import { runAgenticLoop } from "../../core/loopEngine.js";
12
13
  import { DEFAULT_TOOL_MAX_RETRIES, resolveToolTimeoutMs, } from "../../core/constants.js";
13
14
  import { isToolsSchemaExclusionInForce } from "../../core/modules/structuredOutputPolicy.js";
@@ -2043,7 +2044,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
2043
2044
  // TTS direct-synthesis mode: synthesise the input text directly (no LLM
2044
2045
  // call). BaseProvider.runGenerateInActiveContext does the same dispatch
2045
2046
  // — replicated here because AI Studio's override bypasses that path.
2046
- if (options.tts?.enabled && !options.tts?.useAiResponse) {
2047
+ if (isDirectTTSRequest(options.tts)) {
2047
2048
  logger.info("[GoogleAIStudio] Routing TTS direct-synthesis to handleDirectTTSSynthesis", { model: modelName });
2048
2049
  return this.handleDirectTTSSynthesis(options, Date.now());
2049
2050
  }
@@ -2393,7 +2394,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
2393
2394
  contents: [text],
2394
2395
  });
2395
2396
  const embedding = result.embeddings?.[0]?.values;
2396
- if (!embedding) {
2397
+ if (!embedding || embedding.length === 0) {
2397
2398
  throw new ProviderError("No embedding returned from Google AI", this.providerName);
2398
2399
  }
2399
2400
  logger.debug("Embedding generated successfully", {
@@ -2425,10 +2426,11 @@ export class GoogleAIStudioProvider extends BaseProvider {
2425
2426
  model: embeddingModelName,
2426
2427
  count: texts.length,
2427
2428
  });
2429
+ let rows;
2428
2430
  try {
2429
2431
  const apiKey = this.getApiKey();
2430
2432
  const client = await createGoogleGenAIClient(apiKey, this.getBaseURL());
2431
- const embeddings = [];
2433
+ rows = [];
2432
2434
  if (embeddingModelName.includes("gemini-embedding-2")) {
2433
2435
  // @google/genai 2.x reads an array of strings for these models as ONE
2434
2436
  // content with several parts, so a batch would come back as a single
@@ -2441,7 +2443,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
2441
2443
  contents: [text],
2442
2444
  })));
2443
2445
  for (const result of results) {
2444
- embeddings.push(result.embeddings?.[0]?.values || []);
2446
+ rows.push(result.embeddings?.[0]?.values);
2445
2447
  }
2446
2448
  }
2447
2449
  }
@@ -2450,20 +2452,13 @@ export class GoogleAIStudioProvider extends BaseProvider {
2450
2452
  model: embeddingModelName,
2451
2453
  contents: texts,
2452
2454
  });
2453
- embeddings.push(...(result.embeddings || []).map((e) => e.values || []));
2455
+ rows.push(...(result.embeddings || []).map((e) => e.values));
2454
2456
  }
2455
2457
  // One vector per text, or fail: a silent mismatch would pair texts with
2456
2458
  // the wrong vectors downstream.
2457
- if (embeddings.length !== texts.length) {
2458
- throw new Error(`Embedding response held ${embeddings.length} vectors for ${texts.length} texts`);
2459
+ if (rows.length !== texts.length) {
2460
+ throw new Error(`Embedding response held ${rows.length} vectors for ${texts.length} texts`);
2459
2461
  }
2460
- logger.debug("Batch embeddings generated successfully", {
2461
- provider: this.providerName,
2462
- model: embeddingModelName,
2463
- count: embeddings.length,
2464
- embeddingDimension: embeddings[0]?.length,
2465
- });
2466
- return embeddings;
2467
2462
  }
2468
2463
  catch (error) {
2469
2464
  logger.error("Batch embedding generation failed", {
@@ -2473,6 +2468,26 @@ export class GoogleAIStudioProvider extends BaseProvider {
2473
2468
  });
2474
2469
  throw this.handleProviderError(error);
2475
2470
  }
2471
+ // Checked after the try on purpose: a thrown error is re-worded by
2472
+ // handleProviderError from keywords in its text, so an index such as 429
2473
+ // or 502 would turn this into a rate-limit or server error.
2474
+ const embeddings = rows.map((values, index) => {
2475
+ if (!values || values.length === 0) {
2476
+ logger.error("Batch embedding returned no values", {
2477
+ model: embeddingModelName,
2478
+ index,
2479
+ });
2480
+ throw new ProviderError(`Embedding for the text at index ${index} came back without values`, this.providerName);
2481
+ }
2482
+ return values;
2483
+ });
2484
+ logger.debug("Batch embeddings generated successfully", {
2485
+ provider: this.providerName,
2486
+ model: embeddingModelName,
2487
+ count: embeddings.length,
2488
+ embeddingDimension: embeddings[0]?.length,
2489
+ });
2490
+ return embeddings;
2476
2491
  }
2477
2492
  getApiKey() {
2478
2493
  const apiKey = this.credentials?.apiKey ||