@juspay/neurolink 11.2.3 → 11.3.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 (87) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/browser/neurolink.min.js +396 -396
  3. package/dist/core/handlerRegistry.d.ts +29 -0
  4. package/dist/core/handlerRegistry.js +61 -0
  5. package/dist/core/loopEngine.d.ts +23 -0
  6. package/dist/core/loopEngine.js +245 -0
  7. package/dist/core/nativeToolFormat.d.ts +33 -0
  8. package/dist/core/nativeToolFormat.js +30 -0
  9. package/dist/core/streamChannel.d.ts +10 -0
  10. package/dist/core/streamChannel.js +76 -0
  11. package/dist/factories/providerRegistry.d.ts +4 -16
  12. package/dist/factories/providerRegistry.js +4 -56
  13. package/dist/lib/core/handlerRegistry.d.ts +29 -0
  14. package/dist/lib/core/handlerRegistry.js +62 -0
  15. package/dist/lib/core/loopEngine.d.ts +23 -0
  16. package/dist/lib/core/loopEngine.js +246 -0
  17. package/dist/lib/core/nativeToolFormat.d.ts +33 -0
  18. package/dist/lib/core/nativeToolFormat.js +31 -0
  19. package/dist/lib/core/streamChannel.d.ts +10 -0
  20. package/dist/lib/core/streamChannel.js +77 -0
  21. package/dist/lib/factories/providerRegistry.d.ts +4 -16
  22. package/dist/lib/factories/providerRegistry.js +4 -56
  23. package/dist/lib/providers/anthropic/cacheControl.d.ts +12 -0
  24. package/dist/lib/providers/anthropic/cacheControl.js +15 -0
  25. package/dist/lib/providers/anthropic/client.js +12 -45
  26. package/dist/lib/providers/googleAiStudio/client.js +7 -5
  27. package/dist/lib/providers/googleNativeGemini3/utils.d.ts +4 -11
  28. package/dist/lib/providers/googleNativeGemini3/utils.js +1 -75
  29. package/dist/lib/providers/googleVertex/client.js +28 -30
  30. package/dist/lib/providers/openaiChatCompletionsBase.js +10 -12
  31. package/dist/lib/providers/openaiChatCompletionsClient.d.ts +1 -5
  32. package/dist/lib/providers/openaiChatCompletionsClient.js +0 -26
  33. package/dist/lib/types/index.d.ts +3 -0
  34. package/dist/lib/types/index.js +3 -0
  35. package/dist/lib/types/loopEngine.d.ts +78 -0
  36. package/dist/lib/types/loopEngine.js +2 -0
  37. package/dist/lib/types/nativeTools.d.ts +16 -0
  38. package/dist/lib/types/nativeTools.js +2 -0
  39. package/dist/lib/types/openaiCompatible.d.ts +2 -2
  40. package/dist/lib/types/providers.d.ts +0 -13
  41. package/dist/lib/types/streaming.d.ts +15 -0
  42. package/dist/lib/types/streaming.js +2 -0
  43. package/dist/lib/utils/avatarProcessor.d.ts +5 -1
  44. package/dist/lib/utils/avatarProcessor.js +13 -18
  45. package/dist/lib/utils/musicProcessor.d.ts +5 -1
  46. package/dist/lib/utils/musicProcessor.js +13 -18
  47. package/dist/lib/utils/sttProcessor.d.ts +10 -2
  48. package/dist/lib/utils/sttProcessor.js +22 -18
  49. package/dist/lib/utils/ttsProcessor.d.ts +10 -2
  50. package/dist/lib/utils/ttsProcessor.js +22 -18
  51. package/dist/lib/utils/videoProcessor.d.ts +5 -1
  52. package/dist/lib/utils/videoProcessor.js +13 -18
  53. package/dist/lib/voice/RealtimeVoiceAPI.d.ts +1 -1
  54. package/dist/lib/voice/RealtimeVoiceAPI.js +17 -28
  55. package/dist/providers/anthropic/cacheControl.d.ts +12 -0
  56. package/dist/providers/anthropic/cacheControl.js +14 -0
  57. package/dist/providers/anthropic/client.js +12 -45
  58. package/dist/providers/googleAiStudio/client.js +7 -5
  59. package/dist/providers/googleNativeGemini3/utils.d.ts +4 -11
  60. package/dist/providers/googleNativeGemini3/utils.js +1 -75
  61. package/dist/providers/googleVertex/client.js +28 -30
  62. package/dist/providers/openaiChatCompletionsBase.js +10 -12
  63. package/dist/providers/openaiChatCompletionsClient.d.ts +1 -5
  64. package/dist/providers/openaiChatCompletionsClient.js +0 -26
  65. package/dist/types/index.d.ts +3 -0
  66. package/dist/types/index.js +3 -0
  67. package/dist/types/loopEngine.d.ts +78 -0
  68. package/dist/types/loopEngine.js +1 -0
  69. package/dist/types/nativeTools.d.ts +16 -0
  70. package/dist/types/nativeTools.js +1 -0
  71. package/dist/types/openaiCompatible.d.ts +2 -2
  72. package/dist/types/providers.d.ts +0 -13
  73. package/dist/types/streaming.d.ts +15 -0
  74. package/dist/types/streaming.js +1 -0
  75. package/dist/utils/avatarProcessor.d.ts +5 -1
  76. package/dist/utils/avatarProcessor.js +13 -18
  77. package/dist/utils/musicProcessor.d.ts +5 -1
  78. package/dist/utils/musicProcessor.js +13 -18
  79. package/dist/utils/sttProcessor.d.ts +10 -2
  80. package/dist/utils/sttProcessor.js +22 -18
  81. package/dist/utils/ttsProcessor.d.ts +10 -2
  82. package/dist/utils/ttsProcessor.js +22 -18
  83. package/dist/utils/videoProcessor.d.ts +5 -1
  84. package/dist/utils/videoProcessor.js +13 -18
  85. package/dist/voice/RealtimeVoiceAPI.d.ts +1 -1
  86. package/dist/voice/RealtimeVoiceAPI.js +17 -28
  87. package/package.json +9 -1
@@ -33,8 +33,11 @@ import { resolveClaudeMaxTokens } from "../../utils/tokenLimits.js";
33
33
  import { withProviderRetry } from "../../utils/providerRetry.js";
34
34
  import { toAnthropicImageBlock, fileToAnthropicBlock, } from "../anthropicImageBlocks.js";
35
35
  import { resolveSamplingParams } from "../../models/modelRegistry.js";
36
- import { createChunkQueue, createDeferredAnalytics, stringifyToolInput, } from "../openaiChatCompletionsClient.js";
36
+ import { createDeferredAnalytics, stringifyToolInput, } from "../openaiChatCompletionsClient.js";
37
+ import { createStreamChannel } from "../../core/streamChannel.js";
38
+ import { toNativeToolDeclarations } from "../../core/nativeToolFormat.js";
37
39
  import { ANTHROPIC_BETA_HEADERS } from "./constants.js";
40
+ import { cacheControlOf } from "./cacheControl.js";
38
41
  import { appendFinalResultInstruction, appendFinalResultTool, FINAL_RESULT_TOOL_NAME, stringifyFinalResultInput, } from "./structuredOutput.js";
39
42
  // AnthropicProviderConfig is imported from types/providers.ts
40
43
  // Re-export for backward compatibility
@@ -180,16 +183,6 @@ const detectAuthMethod = (oauthToken) => {
180
183
  // ───────────────────────────────────────────────────────────────────────────
181
184
  // Native Messages-API conversion helpers (NeuroLink/V3 shapes → Anthropic)
182
185
  // ───────────────────────────────────────────────────────────────────────────
183
- /**
184
- * Read an Anthropic cache breakpoint from a message/part/tool carrier.
185
- * MessageBuilder marks system messages (and GenerationHandler marks the last
186
- * tool definition) with `providerOptions.anthropic.cacheControl` — the
187
- * AI-SDK-era prompt-caching contract this native path must keep honoring.
188
- */
189
- const cacheControlOf = (carrier) => {
190
- const cc = carrier?.providerOptions?.anthropic?.cacheControl;
191
- return cc?.type === "ephemeral" ? { type: "ephemeral" } : undefined;
192
- };
193
186
  /** Serialize a tool-result `output` into text for a tool_result block. */
194
187
  const stringifyAnthropicToolOutput = (output) => {
195
188
  if (output === null || output === undefined) {
@@ -400,29 +393,6 @@ const messagesToAnthropic = (msgs) => {
400
393
  messages,
401
394
  };
402
395
  };
403
- /** Convert a NeuroLink tool record into Anthropic tool definitions. */
404
- const toolsToAnthropic = (tools) => {
405
- const entries = Object.entries(tools);
406
- if (entries.length === 0) {
407
- return undefined;
408
- }
409
- return entries.map(([name, tool]) => {
410
- const t = tool;
411
- const rawSchema = t.inputSchema ?? t.parameters;
412
- const input_schema = (rawSchema
413
- ? convertZodToJsonSchema(rawSchema)
414
- : { type: "object", properties: {} });
415
- // GenerationHandler marks the last tool definition with a cache
416
- // breakpoint when prompt caching is active — keep honoring it.
417
- const cc = cacheControlOf(tool);
418
- return {
419
- name,
420
- ...(t.description ? { description: t.description } : {}),
421
- input_schema,
422
- ...(cc ? { cache_control: cc } : {}),
423
- };
424
- });
425
- };
426
396
  /** Map a NeuroLink tool choice onto Anthropic's tool_choice shape. */
427
397
  const toolChoiceToAnthropic = (choice) => {
428
398
  if (!choice || choice === "auto") {
@@ -1423,7 +1393,7 @@ export class AnthropicProvider extends BaseProvider {
1423
1393
  ? options.tools || (await this.getAllTools())
1424
1394
  : {};
1425
1395
  anthropicTools = shouldUseTools
1426
- ? toolsToAnthropic(toolsRecord)
1396
+ ? toNativeToolDeclarations(toolsRecord, "input_schema")
1427
1397
  : undefined;
1428
1398
  // Build message array from options with multimodal support, then
1429
1399
  // convert to the Anthropic Messages payload (system + content blocks).
@@ -1471,7 +1441,8 @@ export class AnthropicProvider extends BaseProvider {
1471
1441
  });
1472
1442
  const maxSteps = options.maxSteps || DEFAULT_MAX_STEPS;
1473
1443
  const emitter = this.neurolink?.getEventEmitter();
1474
- const { pushChunk, nextChunk } = createChunkQueue();
1444
+ const channel = createStreamChannel();
1445
+ const { push: pushChunk } = channel;
1475
1446
  const { usagePromise, finishPromise, resolveUsage, resolveFinish } = createDeferredAnalytics();
1476
1447
  usagePromise
1477
1448
  .then((usage) => {
@@ -1566,7 +1537,7 @@ export class AnthropicProvider extends BaseProvider {
1566
1537
  const declared = new Set(anthropicTools.map((t) => t.name));
1567
1538
  const hydrated = Object.fromEntries(Object.entries(toolsRecord).filter(([name]) => !declared.has(name)));
1568
1539
  if (Object.keys(hydrated).length > 0) {
1569
- anthropicTools.push(...(toolsToAnthropic(hydrated) ?? []));
1540
+ anthropicTools.push(...(toNativeToolDeclarations(hydrated, "input_schema") ?? []));
1570
1541
  logger.info(`[Anthropic] ${Object.keys(hydrated).length} tool(s) hydrated mid-turn via discovery: ${Object.keys(hydrated).join(", ")}`);
1571
1542
  }
1572
1543
  }
@@ -1932,22 +1903,18 @@ export class AnthropicProvider extends BaseProvider {
1932
1903
  }
1933
1904
  }
1934
1905
  timeoutController?.cleanup();
1935
- pushChunk({ done: true });
1906
+ channel.close();
1936
1907
  });
1937
1908
  loopPromise.catch(() => {
1938
1909
  // Swallowed by design: the generator below surfaces loop errors after
1939
- // draining the queue; this guard only prevents an unhandled-rejection
1910
+ // draining the channel; this guard only prevents an unhandled-rejection
1940
1911
  // crash when the consumer abandons the stream early.
1941
1912
  });
1942
1913
  const providerName = this.providerName;
1943
1914
  const transformedStream = async function* () {
1944
1915
  let contentYielded = 0;
1945
1916
  try {
1946
- for (;;) {
1947
- const chunk = await nextChunk();
1948
- if ("done" in chunk) {
1949
- break;
1950
- }
1917
+ for await (const chunk of channel.iterable) {
1951
1918
  if ("content" in chunk &&
1952
1919
  typeof chunk.content === "string" &&
1953
1920
  chunk.content.length > 0) {
@@ -1955,7 +1922,7 @@ export class AnthropicProvider extends BaseProvider {
1955
1922
  }
1956
1923
  yield chunk;
1957
1924
  }
1958
- // Surface any error the loop threw after draining the queue.
1925
+ // Surface any error the loop threw after draining the channel.
1959
1926
  await loopPromise;
1960
1927
  // No-output path: stream completed normally but yielded zero text.
1961
1928
  if (contentYielded === 0 && toolsUsed.length === 0) {
@@ -14,7 +14,9 @@ import { withTimeout } from "../../utils/async/index.js";
14
14
  import { estimateTokens } from "../../utils/tokenEstimation.js";
15
15
  import { transformToolExecutions } from "../../utils/transformationUtils.js";
16
16
  import { resolveToolExecutionRecords } from "../../core/toolExecutionRecorder.js";
17
- import { buildGeminiResponseSchema, buildNativeConfig, buildNativeToolDeclarations, collectStreamChunks, collectStreamChunksIncremental, computeMaxSteps, createContextGuard, createTextChannel, buildUserPartsWithMultimodal, executeNativeToolCalls, extractTextFromParts, extractThoughtSignature, handleMaxStepsTermination, prependConversationMessages, pushModelResponseToHistory, refreshNativeToolDeclarations, DedupExecuteMap, } from "../googleNativeGemini3/index.js";
17
+ import { buildGeminiResponseSchema, buildNativeConfig, collectStreamChunks, collectStreamChunksIncremental, computeMaxSteps, createContextGuard, buildUserPartsWithMultimodal, executeNativeToolCalls, extractTextFromParts, extractThoughtSignature, handleMaxStepsTermination, prependConversationMessages, pushModelResponseToHistory, refreshNativeToolDeclarations, DedupExecuteMap, } from "../googleNativeGemini3/index.js";
18
+ import { createStreamChannel } from "../../core/streamChannel.js";
19
+ import { toNativeToolDeclarations } from "../../core/nativeToolFormat.js";
18
20
  import { createProxyFetch } from "../../proxy/proxyFetch.js";
19
21
  // Google AI Live API types now imported from ../types/providerSpecific.js
20
22
  // Import proper types for multimodal message handling
@@ -653,7 +655,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
653
655
  if (options.tools &&
654
656
  Object.keys(options.tools).length > 0 &&
655
657
  !options.disableTools) {
656
- const result = buildNativeToolDeclarations(options.tools);
658
+ const result = toNativeToolDeclarations(options.tools, "functionDeclarations");
657
659
  declarationsResult = result;
658
660
  toolsConfig = result.toolsConfig;
659
661
  executeMap = result.executeMap;
@@ -686,7 +688,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
686
688
  const composedSignal = composeAbortSignals(options.abortSignal, timeoutController?.controller.signal);
687
689
  // Create a push-based text channel so the caller receives tokens as
688
690
  // they arrive from the network rather than after full buffering.
689
- const channel = createTextChannel();
691
+ const channel = createStreamChannel();
690
692
  // Shared mutable state updated by the background agentic loop.
691
693
  const allToolCalls = [];
692
694
  // Mirror the Vertex Gemini stream path: track tool executions so
@@ -859,7 +861,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
859
861
  const fallback = handleMaxStepsTermination("[GoogleAIStudio]", step, maxSteps, "", // finalText is empty — model didn't stop on its own
860
862
  lastStepText);
861
863
  if (fallback) {
862
- channel.push(fallback);
864
+ channel.push({ content: fallback });
863
865
  }
864
866
  }
865
867
  const responseTime = Date.now() - startTime;
@@ -1002,7 +1004,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
1002
1004
  if (shouldUseTools && !exclusionInForce) {
1003
1005
  const tools = options.tools || {};
1004
1006
  if (Object.keys(tools).length > 0) {
1005
- const result = buildNativeToolDeclarations(tools);
1007
+ const result = toNativeToolDeclarations(tools, "functionDeclarations");
1006
1008
  declarationsResult = result;
1007
1009
  toolsConfig = result.toolsConfig;
1008
1010
  executeMap = result.executeMap;
@@ -8,7 +8,7 @@
8
8
  * This module extracts the functions that are duplicated between the two
9
9
  * providers so they can share a single implementation.
10
10
  */
11
- import type { GenerateStopReason, ThinkingConfig, ChatMessage, CollectedChunkResult, MinimalChatMessage, NativeFunctionCall, NativeFunctionResponse, NativeToolDeclarationsResult, NativeToolsConfig, TextChannel, VertexNativePart, GeminiMultimodalInput, MultimodalAudioEntry } from "../../types/index.js";
11
+ import type { GenerateStopReason, ThinkingConfig, ChatMessage, CollectedChunkResult, MinimalChatMessage, NativeFunctionCall, NativeFunctionResponse, NativeToolDeclarationsResult, NativeToolsConfig, StreamChannel, VertexNativePart, GeminiMultimodalInput, MultimodalAudioEntry } from "../../types/index.js";
12
12
  import type { Tool } from "../../types/index.js";
13
13
  /**
14
14
  * A per-turn tool execute map that deduplicates identical tool calls.
@@ -166,15 +166,6 @@ export declare function collectStreamChunks(stream: AsyncIterable<{
166
166
  functionCalls?: NativeFunctionCall[];
167
167
  [key: string]: unknown;
168
168
  }>): Promise<CollectedChunkResult>;
169
- /**
170
- * Create a push-based text channel that bridges a background producer
171
- * (the agentic tool-calling loop) with an async-iterable consumer.
172
- *
173
- * This enables truly incremental streaming: text parts are yielded to the
174
- * caller as they arrive from the network, rather than being buffered until
175
- * the model finishes generating.
176
- */
177
- export declare function createTextChannel(): TextChannel;
178
169
  /**
179
170
  * Iterate a single stream step incrementally, pushing text parts to `channel`
180
171
  * as they arrive from the network while simultaneously accumulating the full
@@ -189,7 +180,9 @@ export declare function createTextChannel(): TextChannel;
189
180
  export declare function collectStreamChunksIncremental(stream: AsyncIterable<{
190
181
  functionCalls?: NativeFunctionCall[];
191
182
  [key: string]: unknown;
192
- }>, channel: TextChannel): Promise<CollectedChunkResult>;
183
+ }>, channel: StreamChannel<{
184
+ content: string;
185
+ }>): Promise<CollectedChunkResult>;
193
186
  /**
194
187
  * Extract the thoughtSignature token from raw response parts.
195
188
  * Returns the last thoughtSignature found (each step may produce one).
@@ -639,80 +639,6 @@ export async function collectStreamChunks(stream) {
639
639
  reasoningTokens,
640
640
  };
641
641
  }
642
- /**
643
- * Create a push-based text channel that bridges a background producer
644
- * (the agentic tool-calling loop) with an async-iterable consumer.
645
- *
646
- * This enables truly incremental streaming: text parts are yielded to the
647
- * caller as they arrive from the network, rather than being buffered until
648
- * the model finishes generating.
649
- */
650
- export function createTextChannel() {
651
- const queue = [];
652
- let done = false;
653
- let fatalError = undefined;
654
- // Resolve the current "wait for data" promise when new data arrives
655
- let notify = null;
656
- function wake() {
657
- if (notify) {
658
- const fn = notify;
659
- notify = null;
660
- fn();
661
- }
662
- }
663
- function push(text) {
664
- if (done) {
665
- return;
666
- }
667
- queue.push({ content: text });
668
- wake();
669
- }
670
- function close() {
671
- done = true;
672
- wake();
673
- }
674
- function error(err) {
675
- done = true;
676
- fatalError = err;
677
- wake();
678
- }
679
- let readIndex = 0;
680
- async function* iterable() {
681
- try {
682
- while (true) {
683
- if (readIndex < queue.length) {
684
- yield queue[readIndex++];
685
- // Periodically compact consumed chunks to avoid unbounded retention
686
- if (readIndex > 1024 && readIndex * 2 >= queue.length) {
687
- queue.splice(0, readIndex);
688
- readIndex = 0;
689
- }
690
- }
691
- else if (done) {
692
- if (fatalError !== undefined) {
693
- throw fatalError instanceof Error
694
- ? fatalError
695
- : new Error(String(fatalError));
696
- }
697
- return;
698
- }
699
- else {
700
- // Wait until the producer pushes data or signals completion
701
- await new Promise((resolve) => {
702
- notify = resolve;
703
- });
704
- }
705
- }
706
- }
707
- finally {
708
- // Consumer stopped reading (e.g. disconnect/cancel): stop buffering.
709
- done = true;
710
- queue.length = 0;
711
- notify?.();
712
- }
713
- }
714
- return { push, close, error, iterable: iterable() };
715
- }
716
642
  /**
717
643
  * Iterate a single stream step incrementally, pushing text parts to `channel`
718
644
  * as they arrive from the network while simultaneously accumulating the full
@@ -741,7 +667,7 @@ export async function collectStreamChunksIncremental(stream, channel) {
741
667
  rawResponseParts.push(part);
742
668
  // Forward text parts to the consumer immediately
743
669
  if (typeof part.text === "string" && part.text.length > 0) {
744
- channel.push(part.text);
670
+ channel.push({ content: part.text });
745
671
  }
746
672
  }
747
673
  }
@@ -29,7 +29,9 @@ import { convertZodToJsonSchema, inlineJsonSchema, ensureNestedSchemaTypes, } fr
29
29
  import { createNativeThinkingConfig } from "../../utils/thinkingConfig.js";
30
30
  import { TimeoutError, raceWithAbort, withTimeout, } from "../../utils/async/index.js";
31
31
  import { parseTimeout } from "../../utils/timeout.js";
32
- import { appendStepText, buildAbortedTurnMessage, buildContextCapMessage, buildToolLoopCapMessage, buildTurnStalledMessage, buildTurnTimeoutMessage, buildWrapupNudgeText, createContextGuard, createTextChannel, createTurnClock, extractThoughtSignature, isAbortError, mapGeminiFinishReason, prependConversationMessages, resolveTurnStopReason, DedupExecuteMap, } from "../googleNativeGemini3/index.js";
32
+ import { appendStepText, buildAbortedTurnMessage, buildContextCapMessage, buildToolLoopCapMessage, buildTurnStalledMessage, buildTurnTimeoutMessage, buildWrapupNudgeText, createContextGuard, createTurnClock, extractThoughtSignature, isAbortError, mapGeminiFinishReason, prependConversationMessages, resolveTurnStopReason, DedupExecuteMap, } from "../googleNativeGemini3/index.js";
33
+ import { createStreamChannel } from "../../core/streamChannel.js";
34
+ import { toNativeToolDeclarations } from "../../core/nativeToolFormat.js";
33
35
  import { getAvailableInputTokens, getContextWindowSize, } from "../../constants/contextWindows.js";
34
36
  import { resolveLiveTool } from "../../tools/toolDiscovery.js";
35
37
  import { ATTR, LANGFUSE_ATTR, spanJsonAttribute, tracers, withClientSpan, withClientStreamSpan, withSpan, } from "../../telemetry/index.js";
@@ -1145,17 +1147,14 @@ export class GoogleVertexProvider extends BaseProvider {
1145
1147
  if (options.tools &&
1146
1148
  Object.keys(options.tools).length > 0 &&
1147
1149
  !options.disableTools) {
1148
- const functionDeclarations = [];
1149
- for (const [name, tool] of Object.entries(options.tools)) {
1150
- functionDeclarations.push(this.buildGeminiFunctionDeclaration(name, tool));
1151
- if (tool.execute) {
1152
- executeMap.set(name, tool.execute);
1153
- }
1150
+ const declared = toNativeToolDeclarations(options.tools, "functionDeclarations");
1151
+ tools = declared.toolsConfig;
1152
+ for (const [name, execute] of declared.executeMap) {
1153
+ executeMap.set(name, execute);
1154
1154
  }
1155
- tools = [{ functionDeclarations }];
1156
1155
  logger.debug("[GoogleVertex] Converted tools for native SDK", {
1157
- toolCount: functionDeclarations.length,
1158
- toolNames: functionDeclarations.map((t) => t.name),
1156
+ toolCount: declared.toolsConfig[0].functionDeclarations.length,
1157
+ toolNames: declared.toolsConfig[0].functionDeclarations.map((t) => t.name),
1159
1158
  });
1160
1159
  }
1161
1160
  // Check if we need to use the final_result tool pattern for structured output with tools
@@ -2154,17 +2153,14 @@ export class GoogleVertexProvider extends BaseProvider {
2154
2153
  let tools;
2155
2154
  const executeMap = new DedupExecuteMap();
2156
2155
  if (Object.keys(combinedTools).length > 0) {
2157
- const functionDeclarations = [];
2158
- for (const [name, tool] of Object.entries(combinedTools)) {
2159
- functionDeclarations.push(this.buildGeminiFunctionDeclaration(name, tool));
2160
- if (tool.execute) {
2161
- executeMap.set(name, tool.execute);
2162
- }
2156
+ const declared = toNativeToolDeclarations(combinedTools, "functionDeclarations");
2157
+ tools = declared.toolsConfig;
2158
+ for (const [name, execute] of declared.executeMap) {
2159
+ executeMap.set(name, execute);
2163
2160
  }
2164
- tools = [{ functionDeclarations }];
2165
2161
  logger.debug("[GoogleVertex] Converted tools for native SDK generate", {
2166
- toolCount: functionDeclarations.length,
2167
- toolNames: functionDeclarations.map((t) => t.name),
2162
+ toolCount: declared.toolsConfig[0].functionDeclarations.length,
2163
+ toolNames: declared.toolsConfig[0].functionDeclarations.map((t) => t.name),
2168
2164
  });
2169
2165
  }
2170
2166
  // Check if we need to use the final_result tool pattern for structured output with tools
@@ -3374,7 +3370,7 @@ export class GoogleVertexProvider extends BaseProvider {
3374
3370
  : maxSteps;
3375
3371
  const allToolCalls = [];
3376
3372
  const toolExecutions = [];
3377
- const channel = createTextChannel();
3373
+ const channel = createStreamChannel();
3378
3374
  // Mutable holders the StreamResult references. Background loop updates
3379
3375
  // these as state progresses; consumer reads them after iterating the
3380
3376
  // stream to completion (channel.close() is called AFTER mutations).
@@ -3593,7 +3589,7 @@ export class GoogleVertexProvider extends BaseProvider {
3593
3589
  firstDeltaSeen = true;
3594
3590
  generationSpan.setAttribute(LANGFUSE_ATTR.OBSERVATION_COMPLETION_START_TIME, new Date().toISOString());
3595
3591
  }
3596
- channel.push(delta);
3592
+ channel.push({ content: delta });
3597
3593
  liveTextPushedLength += delta.length;
3598
3594
  }
3599
3595
  });
@@ -3694,7 +3690,7 @@ export class GoogleVertexProvider extends BaseProvider {
3694
3690
  const finalResultCall = toolUseBlocks.find((block) => block.name === "final_result");
3695
3691
  if (finalResultCall) {
3696
3692
  structuredOutputRef.value = finalResultCall.input;
3697
- channel.push(JSON.stringify(finalResultCall.input));
3693
+ channel.push({ content: JSON.stringify(finalResultCall.input) });
3698
3694
  modelFinished = true;
3699
3695
  logger.debug("[GoogleVertex] Extracted structured output from final_result tool (stream)", { keys: Object.keys(finalResultCall.input) });
3700
3696
  break;
@@ -4052,7 +4048,7 @@ export class GoogleVertexProvider extends BaseProvider {
4052
4048
  maxSteps,
4053
4049
  toolCallCount: externalToolCallCount,
4054
4050
  });
4055
- channel.push(exitMessage);
4051
+ channel.push({ content: exitMessage });
4056
4052
  aggregatedTurnText = exitMessage;
4057
4053
  }
4058
4054
  }
@@ -4117,7 +4113,9 @@ export class GoogleVertexProvider extends BaseProvider {
4117
4113
  const forcedFinalResult = response.content.find((block) => block.type === "tool_use" && block.name === "final_result");
4118
4114
  if (forcedFinalResult) {
4119
4115
  structuredOutputRef.value = forcedFinalResult.input;
4120
- channel.push(JSON.stringify(forcedFinalResult.input));
4116
+ channel.push({
4117
+ content: JSON.stringify(forcedFinalResult.input),
4118
+ });
4121
4119
  synthesizedFinalAnswer = true;
4122
4120
  logger.debug("[GoogleVertex] Forced finalization returned structured output (stream)", { keys: Object.keys(forcedFinalResult.input) });
4123
4121
  }
@@ -4125,7 +4123,7 @@ export class GoogleVertexProvider extends BaseProvider {
4125
4123
  const capMessage = hitContextLimit
4126
4124
  ? buildContextCapMessage(externalToolCallCount)
4127
4125
  : buildToolLoopCapMessage(maxSteps, externalToolCallCount);
4128
- channel.push(capMessage);
4126
+ channel.push({ content: capMessage });
4129
4127
  aggregatedTurnText += capMessage;
4130
4128
  }
4131
4129
  }
@@ -4151,7 +4149,7 @@ export class GoogleVertexProvider extends BaseProvider {
4151
4149
  maxSteps,
4152
4150
  toolCallCount: externalToolCallCount,
4153
4151
  });
4154
- channel.push(exitMessage);
4152
+ channel.push({ content: exitMessage });
4155
4153
  aggregatedTurnText += exitMessage;
4156
4154
  }
4157
4155
  }
@@ -4222,14 +4220,14 @@ export class GoogleVertexProvider extends BaseProvider {
4222
4220
  .join("");
4223
4221
  if (backstopText) {
4224
4222
  synthesizedFinalAnswer = true;
4225
- channel.push(backstopText);
4223
+ channel.push({ content: backstopText });
4226
4224
  aggregatedTurnText = backstopText;
4227
4225
  }
4228
4226
  else {
4229
4227
  const capMessage = hitContextLimit
4230
4228
  ? buildContextCapMessage(externalToolCallCount)
4231
4229
  : buildToolLoopCapMessage(maxSteps, externalToolCallCount);
4232
- channel.push(capMessage);
4230
+ channel.push({ content: capMessage });
4233
4231
  aggregatedTurnText = capMessage;
4234
4232
  }
4235
4233
  }
@@ -4255,7 +4253,7 @@ export class GoogleVertexProvider extends BaseProvider {
4255
4253
  maxSteps,
4256
4254
  toolCallCount: externalToolCallCount,
4257
4255
  });
4258
- channel.push(exitMessage);
4256
+ channel.push({ content: exitMessage });
4259
4257
  aggregatedTurnText = exitMessage;
4260
4258
  }
4261
4259
  }
@@ -4281,7 +4279,7 @@ export class GoogleVertexProvider extends BaseProvider {
4281
4279
  maxSteps,
4282
4280
  toolCallCount: externalToolCallTotal,
4283
4281
  });
4284
- channel.push(exitMessage);
4282
+ channel.push({ content: exitMessage });
4285
4283
  aggregatedTurnText = exitMessage;
4286
4284
  }
4287
4285
  // Honest finish reason (same mapping as the generate twin): "length"
@@ -36,7 +36,8 @@ import { resolveToolChoice } from "../utils/toolChoice.js";
36
36
  import { transformToolExecutions } from "../utils/transformationUtils.js";
37
37
  import { withProviderRetry } from "../utils/providerRetry.js";
38
38
  import { resolveDeferredTool } from "../tools/toolDiscovery.js";
39
- import { buildAPIError, buildBody, buildToolsForOpenAI, buildWireToolNameMaps, createChunkQueue, createDeferredAnalytics, ensureJsonWordInBody, estimateWireTokens, mapNeuroLinkToolChoice, mergeUsage, messageBuilderToOpenAI, parseSSEStream, stringifyToolOutput, stripTrailingSlash, v3ResponseFormatToOpenAI, v3ToolChoiceToOpenAI, v3ToolsToOpenAI, } from "./openaiChatCompletionsClient.js";
39
+ import { buildAPIError, buildBody, buildToolsForOpenAI, buildWireToolNameMaps, createDeferredAnalytics, ensureJsonWordInBody, estimateWireTokens, mapNeuroLinkToolChoice, mergeUsage, messageBuilderToOpenAI, parseSSEStream, stringifyToolOutput, stripTrailingSlash, v3ResponseFormatToOpenAI, v3ToolChoiceToOpenAI, v3ToolsToOpenAI, } from "./openaiChatCompletionsClient.js";
40
+ import { createStreamChannel } from "../core/streamChannel.js";
40
41
  /**
41
42
  * Safety margin (tokens) when fitting `max_tokens` to a runtime-discovered
42
43
  * context window: the char-based input estimate and the backend's own prompt
@@ -647,7 +648,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
647
648
  const toolsUsed = [];
648
649
  const toolExecutionSummaries = [];
649
650
  const { usagePromise, finishPromise, resolveUsage, resolveFinish } = createDeferredAnalytics();
650
- const { pushChunk, nextChunk } = createChunkQueue();
651
+ const channel = createStreamChannel();
651
652
  // Per-provider lifecycle hook (e.g. OTel span wrap for LiteLLM).
652
653
  const lifecycle = this.onStreamStart(modelId);
653
654
  const loopPromise = this.runStreamLoop({
@@ -665,7 +666,8 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
665
666
  emitter,
666
667
  toolsUsed,
667
668
  toolExecutionSummaries,
668
- pushChunk,
669
+ pushChunk: channel.push,
670
+ closeChannel: channel.close,
669
671
  resolveUsage,
670
672
  resolveFinish,
671
673
  });
@@ -695,11 +697,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
695
697
  const transformedStream = async function* () {
696
698
  let contentYielded = 0;
697
699
  try {
698
- for (;;) {
699
- const chunk = await nextChunk();
700
- if ("done" in chunk) {
701
- break;
702
- }
700
+ for await (const chunk of channel.iterable) {
703
701
  if ("content" in chunk &&
704
702
  typeof chunk.content === "string" &&
705
703
  chunk.content.length > 0) {
@@ -707,7 +705,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
707
705
  }
708
706
  yield chunk;
709
707
  }
710
- // Surface any error that the loop threw after we drained the queue.
708
+ // Surface any error that the loop threw after we drained the channel.
711
709
  await loopPromise;
712
710
  // No-output path: stream completed normally but yielded zero text.
713
711
  // Build an enriched sentinel + stamp the active OTel span so
@@ -782,7 +780,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
782
780
  return result;
783
781
  }
784
782
  async runStreamLoop(args) {
785
- const { maxSteps, modelId, url, fetchImpl, abortSignal, options, conversation, openAITools, openAIToolChoice, toolsRecord, toolNameFromWire, emitter, toolsUsed, toolExecutionSummaries, pushChunk, resolveUsage, resolveFinish, } = args;
783
+ const { maxSteps, modelId, url, fetchImpl, abortSignal, options, conversation, openAITools, openAIToolChoice, toolsRecord, toolNameFromWire, emitter, toolsUsed, toolExecutionSummaries, pushChunk, closeChannel, resolveUsage, resolveFinish, } = args;
786
784
  // Hoisted above the try so the catch can resolve the usage accumulated
787
785
  // by steps that completed BEFORE the failure — those steps were billed.
788
786
  let stepFinish = null;
@@ -905,7 +903,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
905
903
  }
906
904
  resolveUsage(toDeferredUsage());
907
905
  resolveFinish(stepFinish ?? "stop");
908
- pushChunk({ done: true });
906
+ closeChannel();
909
907
  return {
910
908
  finishReason: stepFinish ?? "stop",
911
909
  usage: stepUsage,
@@ -919,7 +917,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
919
917
  // instead of zeroing the whole turn.
920
918
  resolveUsage(toDeferredUsage());
921
919
  resolveFinish("error");
922
- pushChunk({ done: true });
920
+ closeChannel();
923
921
  throw err;
924
922
  }
925
923
  }
@@ -13,7 +13,7 @@
13
13
  * Nothing here imports from "ai" or "@ai-sdk/*". The whole point of this
14
14
  * module is to be the native replacement for the AI SDK's OpenAI wrapper.
15
15
  */
16
- import type { OpenAICompatBuildBodyArgs, OpenAICompatChatMessage, OpenAICompatChatRequest, OpenAICompatChatTool, OpenAICompatMessage, OpenAICompatMessageContent, OpenAICompatResponseFormat, OpenAICompatSSEResult, OpenAICompatStreamChunk, OpenAICompatToolChoiceWire, OpenAICompatUsage, OpenAICompatV3CallToolChoice, OpenAICompatV3CallTools, DeferredUsage, Tool } from "../types/index.js";
16
+ import type { OpenAICompatBuildBodyArgs, OpenAICompatChatMessage, OpenAICompatChatRequest, OpenAICompatChatTool, OpenAICompatMessage, OpenAICompatMessageContent, OpenAICompatResponseFormat, OpenAICompatSSEResult, OpenAICompatToolChoiceWire, OpenAICompatUsage, OpenAICompatV3CallToolChoice, OpenAICompatV3CallTools, DeferredUsage, Tool } from "../types/index.js";
17
17
  export declare const stripTrailingSlash: (s: string) => string;
18
18
  /**
19
19
  * Build a bijective original ↔ wire tool-name map. Returns undefined when
@@ -67,8 +67,4 @@ export declare const createDeferredAnalytics: () => {
67
67
  resolveUsage: (u: DeferredUsage) => void;
68
68
  resolveFinish: (reason: string) => void;
69
69
  };
70
- export declare const createChunkQueue: () => {
71
- pushChunk: (c: OpenAICompatStreamChunk) => void;
72
- nextChunk: () => Promise<OpenAICompatStreamChunk>;
73
- };
74
70
  export declare const mergeUsage: (a: OpenAICompatUsage | undefined, b: OpenAICompatUsage | undefined) => OpenAICompatUsage | undefined;
@@ -649,32 +649,6 @@ export const createDeferredAnalytics = () => {
649
649
  });
650
650
  return { usagePromise, finishPromise, resolveUsage, resolveFinish };
651
651
  };
652
- // Single-producer / single-consumer chunk queue. The streaming loop pushes
653
- // `{content}` deltas as they arrive from SSE and a final `{done:true}` when
654
- // it finishes; the consumer's AsyncIterable pulls from `nextChunk()`.
655
- export const createChunkQueue = () => {
656
- const chunkQueue = [];
657
- let pendingResolve;
658
- const pushChunk = (c) => {
659
- if (pendingResolve) {
660
- const r = pendingResolve;
661
- pendingResolve = undefined;
662
- r(c);
663
- }
664
- else {
665
- chunkQueue.push(c);
666
- }
667
- };
668
- const nextChunk = () => new Promise((resolve) => {
669
- if (chunkQueue.length > 0) {
670
- resolve(chunkQueue.shift());
671
- }
672
- else {
673
- pendingResolve = resolve;
674
- }
675
- });
676
- return { pushChunk, nextChunk };
677
- };
678
652
  export const mergeUsage = (a, b) => {
679
653
  if (!a) {
680
654
  return b;
@@ -32,12 +32,14 @@ export * from "./hitl.js";
32
32
  export * from "./isolatedAgent.js";
33
33
  export * from "./knowledge.js";
34
34
  export * from "./livekit.js";
35
+ export * from "./loopEngine.js";
35
36
  export * from "./mcp.js";
36
37
  export * from "./mcpOutput.js";
37
38
  export * from "./memory.js";
38
39
  export * from "./middleware.js";
39
40
  export * from "./model.js";
40
41
  export * from "./multimodal.js";
42
+ export * from "./nativeTools.js";
41
43
  export * from "./observability.js";
42
44
  export * from "./openaiCompatible.js";
43
45
  export * from "./ppt.js";
@@ -51,6 +53,7 @@ export * from "./server.js";
51
53
  export * from "./service.js";
52
54
  export * from "./skills.js";
53
55
  export * from "./stream.js";
56
+ export * from "./streaming.js";
54
57
  export * from "./subscription.js";
55
58
  export * from "./task.js";
56
59
  export * from "./taskClassification.js";
@@ -33,12 +33,14 @@ export * from "./hitl.js";
33
33
  export * from "./isolatedAgent.js";
34
34
  export * from "./knowledge.js";
35
35
  export * from "./livekit.js";
36
+ export * from "./loopEngine.js";
36
37
  export * from "./mcp.js";
37
38
  export * from "./mcpOutput.js";
38
39
  export * from "./memory.js";
39
40
  export * from "./middleware.js";
40
41
  export * from "./model.js";
41
42
  export * from "./multimodal.js";
43
+ export * from "./nativeTools.js";
42
44
  export * from "./observability.js";
43
45
  export * from "./openaiCompatible.js";
44
46
  export * from "./ppt.js";
@@ -52,6 +54,7 @@ export * from "./server.js";
52
54
  export * from "./service.js";
53
55
  export * from "./skills.js";
54
56
  export * from "./stream.js";
57
+ export * from "./streaming.js";
55
58
  export * from "./subscription.js";
56
59
  export * from "./task.js";
57
60
  export * from "./taskClassification.js";