@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.
- package/CHANGELOG.md +6 -0
- package/dist/browser/neurolink.min.js +396 -396
- package/dist/core/handlerRegistry.d.ts +29 -0
- package/dist/core/handlerRegistry.js +61 -0
- package/dist/core/loopEngine.d.ts +23 -0
- package/dist/core/loopEngine.js +245 -0
- package/dist/core/nativeToolFormat.d.ts +33 -0
- package/dist/core/nativeToolFormat.js +30 -0
- package/dist/core/streamChannel.d.ts +10 -0
- package/dist/core/streamChannel.js +76 -0
- package/dist/factories/providerRegistry.d.ts +4 -16
- package/dist/factories/providerRegistry.js +4 -56
- package/dist/lib/core/handlerRegistry.d.ts +29 -0
- package/dist/lib/core/handlerRegistry.js +62 -0
- package/dist/lib/core/loopEngine.d.ts +23 -0
- package/dist/lib/core/loopEngine.js +246 -0
- package/dist/lib/core/nativeToolFormat.d.ts +33 -0
- package/dist/lib/core/nativeToolFormat.js +31 -0
- package/dist/lib/core/streamChannel.d.ts +10 -0
- package/dist/lib/core/streamChannel.js +77 -0
- package/dist/lib/factories/providerRegistry.d.ts +4 -16
- package/dist/lib/factories/providerRegistry.js +4 -56
- package/dist/lib/providers/anthropic/cacheControl.d.ts +12 -0
- package/dist/lib/providers/anthropic/cacheControl.js +15 -0
- package/dist/lib/providers/anthropic/client.js +12 -45
- package/dist/lib/providers/googleAiStudio/client.js +7 -5
- package/dist/lib/providers/googleNativeGemini3/utils.d.ts +4 -11
- package/dist/lib/providers/googleNativeGemini3/utils.js +1 -75
- package/dist/lib/providers/googleVertex/client.js +28 -30
- package/dist/lib/providers/openaiChatCompletionsBase.js +10 -12
- package/dist/lib/providers/openaiChatCompletionsClient.d.ts +1 -5
- package/dist/lib/providers/openaiChatCompletionsClient.js +0 -26
- package/dist/lib/types/index.d.ts +3 -0
- package/dist/lib/types/index.js +3 -0
- package/dist/lib/types/loopEngine.d.ts +78 -0
- package/dist/lib/types/loopEngine.js +2 -0
- package/dist/lib/types/nativeTools.d.ts +16 -0
- package/dist/lib/types/nativeTools.js +2 -0
- package/dist/lib/types/openaiCompatible.d.ts +2 -2
- package/dist/lib/types/providers.d.ts +0 -13
- package/dist/lib/types/streaming.d.ts +15 -0
- package/dist/lib/types/streaming.js +2 -0
- package/dist/lib/utils/avatarProcessor.d.ts +5 -1
- package/dist/lib/utils/avatarProcessor.js +13 -18
- package/dist/lib/utils/musicProcessor.d.ts +5 -1
- package/dist/lib/utils/musicProcessor.js +13 -18
- package/dist/lib/utils/sttProcessor.d.ts +10 -2
- package/dist/lib/utils/sttProcessor.js +22 -18
- package/dist/lib/utils/ttsProcessor.d.ts +10 -2
- package/dist/lib/utils/ttsProcessor.js +22 -18
- package/dist/lib/utils/videoProcessor.d.ts +5 -1
- package/dist/lib/utils/videoProcessor.js +13 -18
- package/dist/lib/voice/RealtimeVoiceAPI.d.ts +1 -1
- package/dist/lib/voice/RealtimeVoiceAPI.js +17 -28
- package/dist/providers/anthropic/cacheControl.d.ts +12 -0
- package/dist/providers/anthropic/cacheControl.js +14 -0
- package/dist/providers/anthropic/client.js +12 -45
- package/dist/providers/googleAiStudio/client.js +7 -5
- package/dist/providers/googleNativeGemini3/utils.d.ts +4 -11
- package/dist/providers/googleNativeGemini3/utils.js +1 -75
- package/dist/providers/googleVertex/client.js +28 -30
- package/dist/providers/openaiChatCompletionsBase.js +10 -12
- package/dist/providers/openaiChatCompletionsClient.d.ts +1 -5
- package/dist/providers/openaiChatCompletionsClient.js +0 -26
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.js +3 -0
- package/dist/types/loopEngine.d.ts +78 -0
- package/dist/types/loopEngine.js +1 -0
- package/dist/types/nativeTools.d.ts +16 -0
- package/dist/types/nativeTools.js +1 -0
- package/dist/types/openaiCompatible.d.ts +2 -2
- package/dist/types/providers.d.ts +0 -13
- package/dist/types/streaming.d.ts +15 -0
- package/dist/types/streaming.js +1 -0
- package/dist/utils/avatarProcessor.d.ts +5 -1
- package/dist/utils/avatarProcessor.js +13 -18
- package/dist/utils/musicProcessor.d.ts +5 -1
- package/dist/utils/musicProcessor.js +13 -18
- package/dist/utils/sttProcessor.d.ts +10 -2
- package/dist/utils/sttProcessor.js +22 -18
- package/dist/utils/ttsProcessor.d.ts +10 -2
- package/dist/utils/ttsProcessor.js +22 -18
- package/dist/utils/videoProcessor.d.ts +5 -1
- package/dist/utils/videoProcessor.js +13 -18
- package/dist/voice/RealtimeVoiceAPI.d.ts +1 -1
- package/dist/voice/RealtimeVoiceAPI.js +17 -28
- package/package.json +9 -1
|
@@ -10,6 +10,7 @@ import { logger } from "../utils/logger.js";
|
|
|
10
10
|
import { RealtimeError } from "./errors.js";
|
|
11
11
|
import { DEFAULT_REALTIME_CONFIG, REALTIME_ERROR_CODES, } from "../types/index.js";
|
|
12
12
|
import { ErrorCategory, ErrorSeverity } from "../constants/enums.js";
|
|
13
|
+
import { HandlerRegistry } from "../core/handlerRegistry.js";
|
|
13
14
|
/**
|
|
14
15
|
* Realtime Processor class for orchestrating realtime voice operations
|
|
15
16
|
*
|
|
@@ -39,7 +40,7 @@ export class RealtimeProcessor {
|
|
|
39
40
|
/**
|
|
40
41
|
* Handler registry mapping provider names to Realtime handlers
|
|
41
42
|
*/
|
|
42
|
-
static
|
|
43
|
+
static registry = new HandlerRegistry("RealtimeProcessor");
|
|
43
44
|
/**
|
|
44
45
|
* Active sessions by provider
|
|
45
46
|
*/
|
|
@@ -51,17 +52,10 @@ export class RealtimeProcessor {
|
|
|
51
52
|
* @param handler - Realtime handler implementation
|
|
52
53
|
*/
|
|
53
54
|
static registerHandler(providerName, handler) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
throw new Error("Handler is required");
|
|
59
|
-
}
|
|
60
|
-
const normalizedName = providerName.toLowerCase();
|
|
61
|
-
if (this.handlers.has(normalizedName)) {
|
|
62
|
-
logger.warn(`[RealtimeProcessor] Overwriting existing handler for provider: ${normalizedName}`);
|
|
63
|
-
}
|
|
64
|
-
this.handlers.set(normalizedName, handler);
|
|
55
|
+
const normalizedName = providerName
|
|
56
|
+
? providerName.toLowerCase()
|
|
57
|
+
: providerName;
|
|
58
|
+
this.registry.register(providerName, handler);
|
|
65
59
|
logger.debug(`[RealtimeProcessor] Registered Realtime handler for provider: ${normalizedName}`);
|
|
66
60
|
}
|
|
67
61
|
/**
|
|
@@ -71,24 +65,19 @@ export class RealtimeProcessor {
|
|
|
71
65
|
* already-registered primary handler when backfilling its aliases.
|
|
72
66
|
*/
|
|
73
67
|
static getHandler(providerName) {
|
|
74
|
-
|
|
75
|
-
return this.handlers.get(normalizedName);
|
|
68
|
+
return this.registry.get(providerName);
|
|
76
69
|
}
|
|
77
70
|
/**
|
|
78
71
|
* Check if a provider is supported
|
|
79
72
|
*/
|
|
80
73
|
static supports(providerName) {
|
|
81
|
-
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
const normalizedName = providerName.toLowerCase();
|
|
85
|
-
return this.handlers.has(normalizedName);
|
|
74
|
+
return this.registry.supports(providerName);
|
|
86
75
|
}
|
|
87
76
|
/**
|
|
88
77
|
* Get list of all registered providers
|
|
89
78
|
*/
|
|
90
79
|
static getProviders() {
|
|
91
|
-
return
|
|
80
|
+
return this.registry.list();
|
|
92
81
|
}
|
|
93
82
|
/**
|
|
94
83
|
* Connect to a realtime session
|
|
@@ -101,7 +90,7 @@ export class RealtimeProcessor {
|
|
|
101
90
|
static async connect(provider, config, handlers) {
|
|
102
91
|
const handler = this.getHandler(provider);
|
|
103
92
|
if (!handler) {
|
|
104
|
-
throw RealtimeError.providerNotSupported(provider,
|
|
93
|
+
throw RealtimeError.providerNotSupported(provider, this.registry.list());
|
|
105
94
|
}
|
|
106
95
|
if (!handler.isConfigured()) {
|
|
107
96
|
throw RealtimeError.providerNotConfigured(provider);
|
|
@@ -145,7 +134,7 @@ export class RealtimeProcessor {
|
|
|
145
134
|
static async disconnect(provider) {
|
|
146
135
|
const handler = this.getHandler(provider);
|
|
147
136
|
if (!handler) {
|
|
148
|
-
throw RealtimeError.providerNotSupported(provider,
|
|
137
|
+
throw RealtimeError.providerNotSupported(provider, this.registry.list());
|
|
149
138
|
}
|
|
150
139
|
if (!handler.isConnected()) {
|
|
151
140
|
logger.warn(`[RealtimeProcessor] No active session for provider: ${provider}`);
|
|
@@ -174,7 +163,7 @@ export class RealtimeProcessor {
|
|
|
174
163
|
static async sendAudio(provider, audio) {
|
|
175
164
|
const handler = this.getHandler(provider);
|
|
176
165
|
if (!handler) {
|
|
177
|
-
throw RealtimeError.providerNotSupported(provider,
|
|
166
|
+
throw RealtimeError.providerNotSupported(provider, this.registry.list());
|
|
178
167
|
}
|
|
179
168
|
if (!handler.isConnected()) {
|
|
180
169
|
throw RealtimeError.sessionNotActive(provider);
|
|
@@ -199,7 +188,7 @@ export class RealtimeProcessor {
|
|
|
199
188
|
static async sendText(provider, text) {
|
|
200
189
|
const handler = this.getHandler(provider);
|
|
201
190
|
if (!handler) {
|
|
202
|
-
throw RealtimeError.providerNotSupported(provider,
|
|
191
|
+
throw RealtimeError.providerNotSupported(provider, this.registry.list());
|
|
203
192
|
}
|
|
204
193
|
if (!handler.isConnected()) {
|
|
205
194
|
throw RealtimeError.sessionNotActive(provider);
|
|
@@ -243,7 +232,7 @@ export class RealtimeProcessor {
|
|
|
243
232
|
static async triggerResponse(provider) {
|
|
244
233
|
const handler = this.getHandler(provider);
|
|
245
234
|
if (!handler) {
|
|
246
|
-
throw RealtimeError.providerNotSupported(provider,
|
|
235
|
+
throw RealtimeError.providerNotSupported(provider, this.registry.list());
|
|
247
236
|
}
|
|
248
237
|
if (!handler.isConnected()) {
|
|
249
238
|
throw RealtimeError.sessionNotActive(provider);
|
|
@@ -276,7 +265,7 @@ export class RealtimeProcessor {
|
|
|
276
265
|
static async cancelResponse(provider) {
|
|
277
266
|
const handler = this.getHandler(provider);
|
|
278
267
|
if (!handler) {
|
|
279
|
-
throw RealtimeError.providerNotSupported(provider,
|
|
268
|
+
throw RealtimeError.providerNotSupported(provider, this.registry.list());
|
|
280
269
|
}
|
|
281
270
|
if (!handler.isConnected()) {
|
|
282
271
|
return; // Nothing to cancel
|
|
@@ -335,15 +324,15 @@ export class RealtimeProcessor {
|
|
|
335
324
|
static clearHandlers() {
|
|
336
325
|
// Disconnect all active sessions
|
|
337
326
|
for (const [provider] of this.sessions) {
|
|
338
|
-
const handler = this.
|
|
327
|
+
const handler = this.registry.get(provider);
|
|
339
328
|
if (handler?.isConnected()) {
|
|
340
329
|
handler.disconnect().catch(() => {
|
|
341
330
|
// Ignore errors during cleanup
|
|
342
331
|
});
|
|
343
332
|
}
|
|
344
333
|
}
|
|
345
|
-
this.handlers.clear();
|
|
346
334
|
this.sessions.clear();
|
|
335
|
+
this.registry.clear();
|
|
347
336
|
logger.debug("[RealtimeProcessor] Cleared all handlers and sessions");
|
|
348
337
|
}
|
|
349
338
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type Anthropic from "@anthropic-ai/sdk";
|
|
2
|
+
/**
|
|
3
|
+
* Read an Anthropic cache breakpoint from a message/part/tool carrier.
|
|
4
|
+
* MessageBuilder marks system messages (and GenerationHandler marks the last
|
|
5
|
+
* tool definition) with `providerOptions.anthropic.cacheControl` — the
|
|
6
|
+
* AI-SDK-era prompt-caching contract this native path must keep honoring.
|
|
7
|
+
*
|
|
8
|
+
* Extracted from anthropic/client.ts so `src/lib/core/nativeToolFormat.ts`
|
|
9
|
+
* can share it without importing the provider client (which would create a
|
|
10
|
+
* circular import: client.ts -> core/nativeToolFormat.ts -> client.ts).
|
|
11
|
+
*/
|
|
12
|
+
export declare const cacheControlOf: (carrier: unknown) => Anthropic.Messages.CacheControlEphemeral | undefined;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read an Anthropic cache breakpoint from a message/part/tool carrier.
|
|
3
|
+
* MessageBuilder marks system messages (and GenerationHandler marks the last
|
|
4
|
+
* tool definition) with `providerOptions.anthropic.cacheControl` — the
|
|
5
|
+
* AI-SDK-era prompt-caching contract this native path must keep honoring.
|
|
6
|
+
*
|
|
7
|
+
* Extracted from anthropic/client.ts so `src/lib/core/nativeToolFormat.ts`
|
|
8
|
+
* can share it without importing the provider client (which would create a
|
|
9
|
+
* circular import: client.ts -> core/nativeToolFormat.ts -> client.ts).
|
|
10
|
+
*/
|
|
11
|
+
export const cacheControlOf = (carrier) => {
|
|
12
|
+
const cc = carrier?.providerOptions?.anthropic?.cacheControl;
|
|
13
|
+
return cc?.type === "ephemeral" ? { type: "ephemeral" } : undefined;
|
|
14
|
+
};
|
|
@@ -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 {
|
|
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
|
-
?
|
|
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
|
|
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(...(
|
|
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
|
-
|
|
1906
|
+
channel.close();
|
|
1936
1907
|
});
|
|
1937
1908
|
loopPromise.catch(() => {
|
|
1938
1909
|
// Swallowed by design: the generator below surfaces loop errors after
|
|
1939
|
-
// draining the
|
|
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
|
|
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,
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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,
|
|
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:
|
|
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,
|
|
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
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
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
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
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 =
|
|
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(
|
|
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"
|