@juspay/neurolink 11.5.2 → 11.6.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.
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Bedrock's adapter onto the shared agentic loop engine.
3
+ *
4
+ * Bedrock had two hand-rolled turn loops — one for `Converse`, one for
5
+ * `ConverseStream` — that between them duplicated the same content-block
6
+ * accumulation three times. Everything here is the wire-format half of that:
7
+ * issuing the right command, folding a response into blocks, serializing tool
8
+ * results back into the conversation, and mapping the stop reason. The turn
9
+ * loop itself, the step cap, tool dispatch and usage accumulation belong to
10
+ * `runAgenticLoop`.
11
+ *
12
+ * One adapter covers both operations because the two differ only in how a
13
+ * step's content blocks arrive: `ConverseStream` delivers them as events that
14
+ * are pushed to the consumer as they land, `Converse` returns them whole.
15
+ * Block accumulation, tool-call extraction and the conversation shape are
16
+ * identical, and were identical in the hand-rolled loops too — which is why
17
+ * they drifted apart in the details.
18
+ */
19
+ import type { ConverseCommandInput, ConverseStreamCommandInput } from "@aws-sdk/client-bedrock-runtime";
20
+ import { type BedrockRuntimeClient } from "@aws-sdk/client-bedrock-runtime";
21
+ import type { AgenticLoopAdapter, BedrockContentBlock, BedrockMessage } from "../../types/index.js";
22
+ export declare function createBedrockLoopAdapter(config: {
23
+ client: BedrockRuntimeClient;
24
+ /** `ConverseStream` when true, `Converse` when false. */
25
+ streaming: boolean;
26
+ /** The region the client was constructed with, for profile resolution. */
27
+ region: string;
28
+ maxSteps: number;
29
+ /**
30
+ * Build the request for one step. Synchronous by contract, so anything
31
+ * async (tool declarations in particular) is resolved once before the turn
32
+ * starts rather than per step. Bedrock has no mid-turn tool discovery, so
33
+ * there is nothing that would need re-resolving.
34
+ */
35
+ buildCommandInput: (conversation: BedrockMessage[], step: number) => ConverseCommandInput & ConverseStreamCommandInput;
36
+ }): AgenticLoopAdapter<BedrockMessage[], BedrockContentBlock[]>;
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Bedrock's adapter onto the shared agentic loop engine.
3
+ *
4
+ * Bedrock had two hand-rolled turn loops — one for `Converse`, one for
5
+ * `ConverseStream` — that between them duplicated the same content-block
6
+ * accumulation three times. Everything here is the wire-format half of that:
7
+ * issuing the right command, folding a response into blocks, serializing tool
8
+ * results back into the conversation, and mapping the stop reason. The turn
9
+ * loop itself, the step cap, tool dispatch and usage accumulation belong to
10
+ * `runAgenticLoop`.
11
+ *
12
+ * One adapter covers both operations because the two differ only in how a
13
+ * step's content blocks arrive: `ConverseStream` delivers them as events that
14
+ * are pushed to the consumer as they land, `Converse` returns them whole.
15
+ * Block accumulation, tool-call extraction and the conversation shape are
16
+ * identical, and were identical in the hand-rolled loops too — which is why
17
+ * they drifted apart in the details.
18
+ */
19
+ import { ConverseCommand, ConverseStreamCommand, } from "@aws-sdk/client-bedrock-runtime";
20
+ import { withTimeout } from "../../utils/errorHandling.js";
21
+ import { withInferenceProfileFallback } from "./inferenceProfile.js";
22
+ const STEP_TIMEOUT_MS = 120_000;
23
+ function newToolUseId() {
24
+ return `tool_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
25
+ }
26
+ /**
27
+ * Finalize a block once its `contentBlockStop` arrives: parse whatever tool
28
+ * input accumulated, and attach the text a plain block collected.
29
+ *
30
+ * Attaching the text matters beyond this step. `buildToolResultMessages`
31
+ * replays these blocks back to the model as the assistant turn, so a text
32
+ * block left empty here silently drops the assistant's own words from the
33
+ * conversation on every subsequent step of the turn.
34
+ */
35
+ function finalizeBlock(block, text) {
36
+ if (!block) {
37
+ return;
38
+ }
39
+ if (block.toolUse && block._inputBuffer) {
40
+ try {
41
+ block.toolUse.input = JSON.parse(block._inputBuffer);
42
+ }
43
+ catch {
44
+ block.toolUse.input = {};
45
+ }
46
+ delete block._inputBuffer;
47
+ }
48
+ if (text && !block.toolUse) {
49
+ block.text = text;
50
+ }
51
+ }
52
+ function toolCallsFrom(blocks) {
53
+ return blocks
54
+ .filter((b) => b.toolUse)
55
+ .map((b) => ({
56
+ id: b.toolUse?.toolUseId ?? newToolUseId(),
57
+ name: b.toolUse?.name ?? "",
58
+ args: b.toolUse?.input ?? {},
59
+ }));
60
+ }
61
+ /** Fold a `ConverseStream` event sequence into finished content blocks. */
62
+ async function readStreamedStep(response, channel, signal) {
63
+ const blocks = [];
64
+ // Blocks are keyed by the index Bedrock stamps on every event rather than
65
+ // by arrival order, because a text block does not necessarily announce
66
+ // itself with `contentBlockStart` — the first thing seen for it can be a
67
+ // delta. Creating the block on whichever event arrives first is what keeps
68
+ // assistant text that precedes a tool call from being dropped: `raw` is
69
+ // replayed as the assistant turn on the next step, so a lost text block
70
+ // means the model stops seeing its own reasoning mid-turn.
71
+ const blocksByIndex = new Map();
72
+ const textByIndex = new Map();
73
+ const blockFor = (index) => {
74
+ let block = blocksByIndex.get(index);
75
+ if (!block) {
76
+ block = {};
77
+ blocksByIndex.set(index, block);
78
+ blocks.push(block);
79
+ }
80
+ return block;
81
+ };
82
+ let text = "";
83
+ let rawStopReason;
84
+ let inputTokens = 0;
85
+ let outputTokens = 0;
86
+ let cacheReadTokens = 0;
87
+ let cacheWriteTokens = 0;
88
+ if (response.stream) {
89
+ for await (const rawChunk of response.stream) {
90
+ if (signal.aborted) {
91
+ break;
92
+ }
93
+ const chunk = rawChunk;
94
+ if (chunk.contentBlockStart) {
95
+ blockFor(chunk.contentBlockStart.contentBlockIndex ?? 0);
96
+ }
97
+ if (chunk.contentBlockDelta?.delta?.text) {
98
+ const index = chunk.contentBlockDelta.contentBlockIndex ?? 0;
99
+ const delta = chunk.contentBlockDelta.delta.text;
100
+ blockFor(index);
101
+ text += delta;
102
+ textByIndex.set(index, (textByIndex.get(index) ?? "") + delta);
103
+ channel.push({ content: delta });
104
+ }
105
+ if (chunk.contentBlockStart?.start?.toolUse) {
106
+ const block = blockFor(chunk.contentBlockStart.contentBlockIndex ?? 0);
107
+ block.toolUse = {
108
+ name: chunk.contentBlockStart.start.toolUse.name ?? "",
109
+ input: {},
110
+ toolUseId: chunk.contentBlockStart.start.toolUse.toolUseId ?? newToolUseId(),
111
+ };
112
+ }
113
+ if (chunk.contentBlockDelta?.delta?.toolUse) {
114
+ const block = blockFor(chunk.contentBlockDelta.contentBlockIndex ?? 0);
115
+ block.toolUse ??= {
116
+ name: "",
117
+ input: {},
118
+ toolUseId: newToolUseId(),
119
+ };
120
+ const deltaInput = chunk.contentBlockDelta.delta.toolUse.input;
121
+ if (typeof deltaInput === "string") {
122
+ block._inputBuffer = (block._inputBuffer ?? "") + deltaInput;
123
+ }
124
+ else if (typeof deltaInput === "object" &&
125
+ deltaInput !== null &&
126
+ !Array.isArray(deltaInput)) {
127
+ block.toolUse.input = {
128
+ ...(block.toolUse.input ?? {}),
129
+ ...deltaInput,
130
+ };
131
+ }
132
+ }
133
+ if (chunk.contentBlockStop) {
134
+ const index = chunk.contentBlockStop.contentBlockIndex ?? 0;
135
+ finalizeBlock(blocksByIndex.get(index), textByIndex.get(index) ?? "");
136
+ }
137
+ if (chunk.messageStop) {
138
+ rawStopReason = chunk.messageStop.stopReason ?? "end_turn";
139
+ // Not a break: the metadata event carrying usage arrives after this.
140
+ continue;
141
+ }
142
+ if (chunk.metadata?.usage) {
143
+ inputTokens += chunk.metadata.usage.inputTokens ?? 0;
144
+ outputTokens += chunk.metadata.usage.outputTokens ?? 0;
145
+ // Converse follows the Anthropic additive convention — inputTokens is
146
+ // the uncached remainder, so cache reads/writes are counted apart.
147
+ cacheReadTokens += chunk.metadata.usage.cacheReadInputTokens ?? 0;
148
+ cacheWriteTokens += chunk.metadata.usage.cacheWriteInputTokens ?? 0;
149
+ break;
150
+ }
151
+ }
152
+ }
153
+ // Finalize anything the stream never closed. `contentBlockStop` is not
154
+ // guaranteed to arrive for every block: an abort breaks the loop, the
155
+ // metadata event breaks it, and a truncated stream simply ends. A block
156
+ // left unfinalized keeps `text` undefined and its tool input unparsed, and
157
+ // since `raw` is replayed as the assistant message — where an unrecognized
158
+ // block maps to `{ text: "" }`, which Bedrock rejects — that would fail the
159
+ // NEXT step of the turn rather than this one.
160
+ for (const [index, block] of blocksByIndex) {
161
+ finalizeBlock(block, textByIndex.get(index) ?? "");
162
+ }
163
+ // A block that opened and then received nothing carries no content at all,
164
+ // and `convertToAWSMessages` maps an unrecognized block to `{ text: "" }`,
165
+ // which Bedrock rejects. Drop them rather than replay them.
166
+ const usableBlocks = blocks.filter((block) => block.text || block.toolUse || block.image || block.document);
167
+ return {
168
+ text,
169
+ toolCalls: toolCallsFrom(usableBlocks),
170
+ usage: { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens },
171
+ rawStopReason,
172
+ raw: usableBlocks,
173
+ };
174
+ }
175
+ /** Fold a non-streaming `Converse` response into the same block shape. */
176
+ function readGeneratedStep(response) {
177
+ const blocks = [];
178
+ let text = "";
179
+ for (const item of response.output?.message?.content ?? []) {
180
+ const block = {};
181
+ if ("text" in item && item.text) {
182
+ block.text = item.text;
183
+ text += text ? ` ${item.text}` : item.text;
184
+ }
185
+ if ("toolUse" in item && item.toolUse) {
186
+ block.toolUse = {
187
+ toolUseId: item.toolUse.toolUseId ?? newToolUseId(),
188
+ name: item.toolUse.name ?? "",
189
+ input: item.toolUse.input ?? {},
190
+ };
191
+ }
192
+ blocks.push(block);
193
+ }
194
+ return {
195
+ text,
196
+ toolCalls: toolCallsFrom(blocks),
197
+ usage: {
198
+ inputTokens: response.usage?.inputTokens ?? 0,
199
+ outputTokens: response.usage?.outputTokens ?? 0,
200
+ cacheReadTokens: response.usage?.cacheReadInputTokens ?? 0,
201
+ cacheWriteTokens: response.usage?.cacheWriteInputTokens ?? 0,
202
+ },
203
+ rawStopReason: response.stopReason,
204
+ raw: blocks,
205
+ };
206
+ }
207
+ export function createBedrockLoopAdapter(config) {
208
+ return {
209
+ providerLabel: "bedrock",
210
+ maxSteps: config.maxSteps,
211
+ // No toolFailureBreaker: Bedrock has never had strike counting — a failing
212
+ // tool becomes one error tool-result with no cross-step memory. Preserved.
213
+ buildStepRequest(conversation, step) {
214
+ return { raw: config.buildCommandInput(conversation, step) };
215
+ },
216
+ async executeStep(request, channel, signal) {
217
+ const commandInput = request.raw;
218
+ // Both sends go through the inference-profile fallback: most current
219
+ // Bedrock models are not invocable by their bare id in a given region
220
+ // and need a geography- or global-prefixed profile id instead. This is
221
+ // where the provider's two Converse call sites now live.
222
+ if (!config.streaming) {
223
+ const response = await withInferenceProfileFallback(commandInput.modelId ?? "", config.region, (effectiveModelId) => withTimeout(config.client.send(new ConverseCommand({
224
+ ...commandInput,
225
+ modelId: effectiveModelId,
226
+ })), STEP_TIMEOUT_MS, new Error("Bedrock API call timed out")));
227
+ if (!response.output?.message) {
228
+ throw new Error("Invalid response structure from Bedrock API");
229
+ }
230
+ return readGeneratedStep(response);
231
+ }
232
+ const response = await withInferenceProfileFallback(commandInput.modelId ?? "", config.region, (effectiveModelId) => withTimeout(config.client.send(new ConverseStreamCommand({
233
+ ...commandInput,
234
+ modelId: effectiveModelId,
235
+ })), STEP_TIMEOUT_MS, new Error("Bedrock streaming API call timed out")));
236
+ return readStreamedStep(response, channel, signal);
237
+ },
238
+ buildToolResultMessages(conversation, stepResult, toolResults) {
239
+ const assistantMessage = {
240
+ role: "assistant",
241
+ content: stepResult.raw,
242
+ };
243
+ const toolResultMessage = {
244
+ role: "user",
245
+ content: toolResults.map((result) => ({
246
+ toolResult: {
247
+ toolUseId: result.id,
248
+ content: [
249
+ {
250
+ text: result.error
251
+ ? `Error executing tool ${result.name}: ${result.error}`
252
+ : String(typeof result.output === "string"
253
+ ? result.output
254
+ : JSON.stringify(result.output)),
255
+ },
256
+ ],
257
+ status: result.error ? "error" : "success",
258
+ },
259
+ })),
260
+ };
261
+ return [...conversation, assistantMessage, toolResultMessage];
262
+ },
263
+ mapFinishReason(rawStopReason, hadToolCallsAtCap) {
264
+ switch (rawStopReason) {
265
+ case "end_turn":
266
+ case "stop_sequence":
267
+ return "stop";
268
+ case "max_tokens":
269
+ return "length";
270
+ case "tool_use":
271
+ return "tool-calls";
272
+ default:
273
+ return hadToolCallsAtCap ? "tool-calls" : "stop";
274
+ }
275
+ },
276
+ };
277
+ }
278
+ //# sourceMappingURL=loopAdapter.js.map
@@ -833,6 +833,15 @@ export type BedrockContentBlock = {
833
833
  toolUse?: BedrockToolUse;
834
834
  toolResult?: BedrockToolResult;
835
835
  };
836
+ /**
837
+ * A Bedrock content block still being assembled from a ConverseStream event
838
+ * sequence. `_inputBuffer` holds the partial tool-call JSON that arrives
839
+ * across several `contentBlockDelta` events and is parsed away at
840
+ * `contentBlockStop`, so it never appears on a finished block.
841
+ */
842
+ export type BedrockPendingContentBlock = BedrockContentBlock & {
843
+ _inputBuffer?: string;
844
+ };
836
845
  /**
837
846
  * Bedrock message structure
838
847
  */
@@ -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