@juspay/neurolink 11.6.0 → 11.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.
@@ -60,6 +60,8 @@ export function runAgenticLoop(adapter, initialConversation, options) {
60
60
  let conversation = initialConversation;
61
61
  let usage = { inputTokens: 0, outputTokens: 0 };
62
62
  let finalText = "";
63
+ // The most recent step's text, kept only for the step-cap case below.
64
+ let lastStepText = "";
63
65
  let rawStopReason;
64
66
  const allToolCalls = [];
65
67
  const allToolExecutions = [];
@@ -113,6 +115,7 @@ export function runAgenticLoop(adapter, initialConversation, options) {
113
115
  }
114
116
  usage = sumUsage(usage, stepResult.usage);
115
117
  rawStopReason = stepResult.rawStopReason;
118
+ lastStepText = stepResult.text || lastStepText;
116
119
  if (adapter.isMalformedStep?.(stepResult) &&
117
120
  !malformedRetryUsed &&
118
121
  !internalAbort.signal.aborted) {
@@ -153,7 +156,16 @@ export function runAgenticLoop(adapter, initialConversation, options) {
153
156
  });
154
157
  continue;
155
158
  }
156
- const tool = options.tools?.[call.name];
159
+ // Second lookup path for adapters that discover tools mid-turn.
160
+ // The miss is defined as "nothing executable under this name"
161
+ // rather than "no key under this name", because the guard directly
162
+ // below already treats a present-but-unexecutable entry as absent —
163
+ // a deferred-catalog placeholder is exactly that shape, and it is
164
+ // precisely what hydration exists to resolve.
165
+ const declaredTool = options.tools?.[call.name];
166
+ const tool = declaredTool?.execute
167
+ ? declaredTool
168
+ : (adapter.resolveToolOnMiss?.(call.name) ?? declaredTool);
157
169
  if (!tool?.execute) {
158
170
  const output = breaker
159
171
  ? {
@@ -211,7 +223,14 @@ export function runAgenticLoop(adapter, initialConversation, options) {
211
223
  }
212
224
  const finishReason = adapter.mapFinishReason(rawStopReason, hadToolCallsAtCap);
213
225
  return {
214
- text: finalText,
226
+ // `finalText` is only set by a step that asked for no tools, so a turn
227
+ // that runs out of steps mid-tool-call would otherwise return "" and
228
+ // throw away everything the model actually said. An empty result also
229
+ // reads as a failed generation to callers that retry on empty content,
230
+ // turning one capped turn into several. Fall back to the last step's
231
+ // text in that case only — when the loop ended normally, an empty
232
+ // final step genuinely means the model said nothing.
233
+ text: finalText || (hadToolCallsAtCap ? lastStepText : ""),
215
234
  toolCalls: allToolCalls,
216
235
  toolExecutions: allToolExecutions,
217
236
  usage,
@@ -60,6 +60,8 @@ export function runAgenticLoop(adapter, initialConversation, options) {
60
60
  let conversation = initialConversation;
61
61
  let usage = { inputTokens: 0, outputTokens: 0 };
62
62
  let finalText = "";
63
+ // The most recent step's text, kept only for the step-cap case below.
64
+ let lastStepText = "";
63
65
  let rawStopReason;
64
66
  const allToolCalls = [];
65
67
  const allToolExecutions = [];
@@ -113,6 +115,7 @@ export function runAgenticLoop(adapter, initialConversation, options) {
113
115
  }
114
116
  usage = sumUsage(usage, stepResult.usage);
115
117
  rawStopReason = stepResult.rawStopReason;
118
+ lastStepText = stepResult.text || lastStepText;
116
119
  if (adapter.isMalformedStep?.(stepResult) &&
117
120
  !malformedRetryUsed &&
118
121
  !internalAbort.signal.aborted) {
@@ -153,7 +156,16 @@ export function runAgenticLoop(adapter, initialConversation, options) {
153
156
  });
154
157
  continue;
155
158
  }
156
- const tool = options.tools?.[call.name];
159
+ // Second lookup path for adapters that discover tools mid-turn.
160
+ // The miss is defined as "nothing executable under this name"
161
+ // rather than "no key under this name", because the guard directly
162
+ // below already treats a present-but-unexecutable entry as absent —
163
+ // a deferred-catalog placeholder is exactly that shape, and it is
164
+ // precisely what hydration exists to resolve.
165
+ const declaredTool = options.tools?.[call.name];
166
+ const tool = declaredTool?.execute
167
+ ? declaredTool
168
+ : (adapter.resolveToolOnMiss?.(call.name) ?? declaredTool);
157
169
  if (!tool?.execute) {
158
170
  const output = breaker
159
171
  ? {
@@ -211,7 +223,14 @@ export function runAgenticLoop(adapter, initialConversation, options) {
211
223
  }
212
224
  const finishReason = adapter.mapFinishReason(rawStopReason, hadToolCallsAtCap);
213
225
  return {
214
- text: finalText,
226
+ // `finalText` is only set by a step that asked for no tools, so a turn
227
+ // that runs out of steps mid-tool-call would otherwise return "" and
228
+ // throw away everything the model actually said. An empty result also
229
+ // reads as a failed generation to callers that retry on empty content,
230
+ // turning one capped turn into several. Fall back to the last step's
231
+ // text in that case only — when the loop ended normally, an empty
232
+ // final step genuinely means the model said nothing.
233
+ text: finalText || (hadToolCallsAtCap ? lastStepText : ""),
215
234
  toolCalls: allToolCalls,
216
235
  toolExecutions: allToolExecutions,
217
236
  usage,
@@ -38,21 +38,37 @@ export declare class AmazonBedrockProvider extends BaseProvider {
38
38
  protected getDefaultEmbeddingModel(): string;
39
39
  generate(optionsOrPrompt: TextGenerationOptions | string): Promise<EnhancedGenerateResult | null>;
40
40
  private conversationLoop;
41
- private callBedrock;
42
- private handleBedrockResponse;
43
41
  private convertToAWSMessages;
42
+ /**
43
+ * `tools` is passed in rather than re-resolved from `getAllTools()`. That
44
+ * call returns only the provider's own registry, so resolving here meant a
45
+ * tool the caller passed to generate/stream could never execute — the
46
+ * streaming path advertised it to the model and then failed every call to
47
+ * it with "Tool not found", and the generate path never advertised it at
48
+ * all. The turn's full merged tool set is resolved once by the caller and
49
+ * handed down.
50
+ */
44
51
  private executeSingleTool;
52
+ /**
53
+ * Resolve the turn's tools once: whatever the caller passed, else the
54
+ * provider's own registry. `BaseProvider.stream()` has already merged base
55
+ * tools into `options.tools` by the time it reaches the streaming path;
56
+ * the generate path has no such pre-merge, so it falls back here.
57
+ */
58
+ private resolveTurnTools;
59
+ /**
60
+ * Present the resolved tools in the shape `runAgenticLoop` dispatches
61
+ * through. Execution still goes through `executeSingleTool`, so the tool
62
+ * span, the parameter defaults and the ToolResult unwrapping are unchanged
63
+ * — only which tools are reachable changes.
64
+ */
65
+ private toEngineTools;
45
66
  private convertAISDKToolsToToolDefinitions;
46
67
  private formatToolsForBedrock;
47
68
  private convertToBedrockMessages;
48
69
  getBedrockClient(): BedrockRuntimeClient;
49
70
  protected executeStream(options: StreamOptions): Promise<StreamResult>;
50
71
  private streamingConversationLoop;
51
- private convertToAsyncIterable;
52
- private prepareStreamCommand;
53
- private processStreamResponse;
54
- private handleStreamStopReason;
55
- private executeStreamTools;
56
72
  /**
57
73
  * Health check for Amazon Bedrock service
58
74
  * Uses ListFoundationModels API to validate connectivity and permissions