@juspay/neurolink 9.82.0 → 9.84.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 +18 -0
- package/dist/agent/agent.d.ts +104 -0
- package/dist/agent/agent.js +401 -0
- package/dist/agent/agentNetwork.d.ts +87 -0
- package/dist/agent/agentNetwork.js +458 -0
- package/dist/agent/communication/index.d.ts +9 -0
- package/dist/agent/communication/index.js +9 -0
- package/dist/agent/communication/message-bus.d.ts +113 -0
- package/dist/agent/communication/message-bus.js +406 -0
- package/dist/agent/coordination/coordinator.d.ts +83 -0
- package/dist/agent/coordination/coordinator.js +598 -0
- package/dist/agent/coordination/index.d.ts +10 -0
- package/dist/agent/coordination/index.js +10 -0
- package/dist/agent/coordination/task-distributor.d.ts +113 -0
- package/dist/agent/coordination/task-distributor.js +585 -0
- package/dist/agent/index.d.ts +38 -0
- package/dist/agent/index.js +44 -0
- package/dist/agent/orchestration/index.d.ts +10 -0
- package/dist/agent/orchestration/index.js +10 -0
- package/dist/agent/orchestration/orchestrator.d.ts +114 -0
- package/dist/agent/orchestration/orchestrator.js +476 -0
- package/dist/agent/orchestration/topology.d.ts +164 -0
- package/dist/agent/orchestration/topology.js +520 -0
- package/dist/agent/prompts/routingPrompts.d.ts +63 -0
- package/dist/agent/prompts/routingPrompts.js +201 -0
- package/dist/browser/neurolink.min.js +377 -358
- package/dist/cli/commands/agent.d.ts +37 -0
- package/dist/cli/commands/agent.js +718 -0
- package/dist/cli/factories/commandFactory.d.ts +8 -0
- package/dist/cli/factories/commandFactory.js +116 -0
- package/dist/cli/loop/optionsSchema.d.ts +1 -1
- package/dist/cli/parser.js +4 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +19 -0
- package/dist/lib/agent/agent.d.ts +104 -0
- package/dist/lib/agent/agent.js +402 -0
- package/dist/lib/agent/agentNetwork.d.ts +87 -0
- package/dist/lib/agent/agentNetwork.js +459 -0
- package/dist/lib/agent/communication/index.d.ts +9 -0
- package/dist/lib/agent/communication/index.js +10 -0
- package/dist/lib/agent/communication/message-bus.d.ts +113 -0
- package/dist/lib/agent/communication/message-bus.js +407 -0
- package/dist/lib/agent/coordination/coordinator.d.ts +83 -0
- package/dist/lib/agent/coordination/coordinator.js +599 -0
- package/dist/lib/agent/coordination/index.d.ts +10 -0
- package/dist/lib/agent/coordination/index.js +11 -0
- package/dist/lib/agent/coordination/task-distributor.d.ts +113 -0
- package/dist/lib/agent/coordination/task-distributor.js +586 -0
- package/dist/lib/agent/index.d.ts +38 -0
- package/dist/lib/agent/index.js +45 -0
- package/dist/lib/agent/orchestration/index.d.ts +10 -0
- package/dist/lib/agent/orchestration/index.js +11 -0
- package/dist/lib/agent/orchestration/orchestrator.d.ts +114 -0
- package/dist/lib/agent/orchestration/orchestrator.js +477 -0
- package/dist/lib/agent/orchestration/topology.d.ts +164 -0
- package/dist/lib/agent/orchestration/topology.js +521 -0
- package/dist/lib/agent/prompts/routingPrompts.d.ts +63 -0
- package/dist/lib/agent/prompts/routingPrompts.js +202 -0
- package/dist/lib/index.d.ts +6 -0
- package/dist/lib/index.js +19 -0
- package/dist/lib/neurolink.d.ts +129 -0
- package/dist/lib/neurolink.js +276 -0
- package/dist/lib/processors/config/{fileTypes.js → fileExtensions.js} +1 -1
- package/dist/lib/processors/config/index.d.ts +2 -2
- package/dist/lib/processors/config/index.js +2 -2
- package/dist/lib/processors/config/{mimeTypes.js → mimeConstants.js} +1 -1
- package/dist/lib/processors/index.js +8 -0
- package/dist/lib/providers/googleAiStudio.js +28 -7
- package/dist/lib/providers/googleNativeGemini3.d.ts +18 -0
- package/dist/lib/providers/googleNativeGemini3.js +69 -3
- package/dist/lib/providers/googleVertex.js +33 -9
- package/dist/lib/types/agentNetwork.d.ts +1184 -0
- package/dist/lib/types/agentNetwork.js +8 -0
- package/dist/lib/types/cli.d.ts +66 -0
- package/dist/lib/types/common.d.ts +3 -0
- package/dist/lib/types/generate.d.ts +53 -0
- package/dist/lib/types/index.d.ts +2 -0
- package/dist/lib/types/index.js +3 -0
- package/dist/lib/types/ioProcessor.d.ts +119 -0
- package/dist/lib/types/ioProcessor.js +10 -0
- package/dist/lib/types/providers.d.ts +8 -0
- package/dist/lib/types/stream.d.ts +36 -0
- package/dist/lib/utils/piiDetector.d.ts +24 -0
- package/dist/lib/utils/piiDetector.js +221 -0
- package/dist/lib/utils/pricing.js +144 -26
- package/dist/lib/utils/responseValidator.d.ts +21 -0
- package/dist/lib/utils/responseValidator.js +354 -0
- package/dist/lib/utils/tokenUtils.d.ts +11 -0
- package/dist/lib/utils/tokenUtils.js +33 -2
- package/dist/lib/utils/tripwireEvaluator.d.ts +73 -0
- package/dist/lib/utils/tripwireEvaluator.js +285 -0
- package/dist/neurolink.d.ts +129 -0
- package/dist/neurolink.js +276 -0
- package/dist/processors/config/index.d.ts +2 -2
- package/dist/processors/config/index.js +2 -2
- package/dist/processors/index.js +8 -0
- package/dist/providers/googleAiStudio.js +28 -7
- package/dist/providers/googleNativeGemini3.d.ts +18 -0
- package/dist/providers/googleNativeGemini3.js +69 -3
- package/dist/providers/googleVertex.js +33 -9
- package/dist/types/agentNetwork.d.ts +1184 -0
- package/dist/types/agentNetwork.js +7 -0
- package/dist/types/cli.d.ts +66 -0
- package/dist/types/common.d.ts +3 -0
- package/dist/types/generate.d.ts +53 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +3 -0
- package/dist/types/ioProcessor.d.ts +119 -0
- package/dist/types/ioProcessor.js +9 -0
- package/dist/types/providers.d.ts +8 -0
- package/dist/types/stream.d.ts +36 -0
- package/dist/utils/piiDetector.d.ts +24 -0
- package/dist/utils/piiDetector.js +220 -0
- package/dist/utils/pricing.js +144 -26
- package/dist/utils/responseValidator.d.ts +21 -0
- package/dist/utils/responseValidator.js +353 -0
- package/dist/utils/tokenUtils.d.ts +11 -0
- package/dist/utils/tokenUtils.js +33 -2
- package/dist/utils/tripwireEvaluator.d.ts +73 -0
- package/dist/utils/tripwireEvaluator.js +284 -0
- package/package.json +1 -1
- /package/dist/lib/processors/config/{fileTypes.d.ts → fileExtensions.d.ts} +0 -0
- /package/dist/lib/processors/config/{mimeTypes.d.ts → mimeConstants.d.ts} +0 -0
- /package/dist/processors/config/{fileTypes.d.ts → fileExtensions.d.ts} +0 -0
- /package/dist/processors/config/{fileTypes.js → fileExtensions.js} +0 -0
- /package/dist/processors/config/{mimeTypes.d.ts → mimeConstants.d.ts} +0 -0
- /package/dist/processors/config/{mimeTypes.js → mimeConstants.js} +0 -0
package/dist/lib/neurolink.js
CHANGED
|
@@ -90,6 +90,8 @@ import { runWorkflow } from "./workflow/core/workflowRunner.js";
|
|
|
90
90
|
import { ModelPool, classifyProviderError } from "./routing/index.js";
|
|
91
91
|
import { ClassifierRouter } from "./routing/classifierRouter.js";
|
|
92
92
|
import { looksLikeModelAccessDenied as sharedLooksLikeModelAccessDenied, isNonRetryableProviderError as sharedIsNonRetryableProviderError, } from "./utils/providerErrorClassification.js";
|
|
93
|
+
import { detectAndRedactPII } from "./utils/piiDetector.js";
|
|
94
|
+
import { validateResponse } from "./utils/responseValidator.js";
|
|
93
95
|
/**
|
|
94
96
|
* NL-002: Classify MCP error messages into categories for AI disambiguation.
|
|
95
97
|
* Returns a human-readable error category based on error message content.
|
|
@@ -3063,6 +3065,41 @@ Current user's request: ${currentInput}`;
|
|
|
3063
3065
|
if (!hasSttAudio && !isMediaModalityMode) {
|
|
3064
3066
|
this.assertInputText(options.input?.text, "Input text is required and must be a non-empty string");
|
|
3065
3067
|
}
|
|
3068
|
+
// Input validation (trimWhitespace, minLength, maxLength, requireContent).
|
|
3069
|
+
// Guard on a string input.text — release's media-only / STT flows may leave
|
|
3070
|
+
// it empty/undefined, and those never carry inputValidation anyway.
|
|
3071
|
+
if (options.inputValidation && typeof options.input.text === "string") {
|
|
3072
|
+
const iv = options.inputValidation;
|
|
3073
|
+
if (iv.trimWhitespace) {
|
|
3074
|
+
options.input.text = options.input.text.trim();
|
|
3075
|
+
}
|
|
3076
|
+
if (iv.requireContent && !options.input.text.trim()) {
|
|
3077
|
+
throw new Error("Input content is required but was empty or whitespace");
|
|
3078
|
+
}
|
|
3079
|
+
if (iv.minLength && options.input.text.length < iv.minLength) {
|
|
3080
|
+
throw new Error(`Input text is too short (${options.input.text.length} < ${iv.minLength})`);
|
|
3081
|
+
}
|
|
3082
|
+
if (iv.maxLength && options.input.text.length > iv.maxLength) {
|
|
3083
|
+
throw new Error(`Input text is too long (${options.input.text.length} > ${iv.maxLength})`);
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
// PII detection and redaction
|
|
3087
|
+
if (options.piiDetection?.enabled &&
|
|
3088
|
+
typeof options.input.text === "string") {
|
|
3089
|
+
const piiResult = await detectAndRedactPII(options.input.text, {
|
|
3090
|
+
enabled: true,
|
|
3091
|
+
action: options.piiDetection.action ?? "warn",
|
|
3092
|
+
detectTypes: options.piiDetection.detectTypes,
|
|
3093
|
+
customPatterns: options.piiDetection.customPatterns,
|
|
3094
|
+
allowList: options.piiDetection.allowList,
|
|
3095
|
+
redactionText: options.piiDetection.redactionText,
|
|
3096
|
+
});
|
|
3097
|
+
if (piiResult.action === "abort") {
|
|
3098
|
+
throw new Error(piiResult.feedback ?? "Request blocked: PII detected in input");
|
|
3099
|
+
}
|
|
3100
|
+
// Replace input text with redacted version
|
|
3101
|
+
options.input.text = piiResult.text;
|
|
3102
|
+
}
|
|
3066
3103
|
this.enforceSessionBudget(options.maxBudgetUsd);
|
|
3067
3104
|
this.applyGenerateLifecycleMiddleware(options);
|
|
3068
3105
|
await this.applyAuthenticatedRequestContext(options);
|
|
@@ -3702,6 +3739,29 @@ Current user's request: ${currentInput}`;
|
|
|
3702
3739
|
reasoningTokens: textResult.reasoningTokens,
|
|
3703
3740
|
...(textResult.retries && { retries: textResult.retries }),
|
|
3704
3741
|
};
|
|
3742
|
+
// Response validation (if configured)
|
|
3743
|
+
if (options.responseValidation) {
|
|
3744
|
+
const validationResult = validateResponse(generateResult.content ?? "", {
|
|
3745
|
+
minLength: options.responseValidation.minLength,
|
|
3746
|
+
maxLength: options.responseValidation.maxLength,
|
|
3747
|
+
requiredPhrases: options.responseValidation.requiredPhrases,
|
|
3748
|
+
forbiddenPhrases: options.responseValidation.forbiddenPhrases,
|
|
3749
|
+
jsonSchema: options.responseValidation.jsonSchema,
|
|
3750
|
+
customValidator: options.responseValidation.customValidator,
|
|
3751
|
+
truncationAction: options.responseValidation.truncationAction,
|
|
3752
|
+
truncationSuffix: options.responseValidation.truncationSuffix,
|
|
3753
|
+
retryOnFailure: options.responseValidation.retryOnFailure,
|
|
3754
|
+
maxRetries: options.responseValidation.maxRetries,
|
|
3755
|
+
});
|
|
3756
|
+
if (validationResult.action === "abort") {
|
|
3757
|
+
throw new Error(validationResult.feedback ??
|
|
3758
|
+
`Response validation failed: ${validationResult.issues.map((i) => i.message).join("; ")}`);
|
|
3759
|
+
}
|
|
3760
|
+
// Apply validated/truncated text
|
|
3761
|
+
if (validationResult.text !== generateResult.content) {
|
|
3762
|
+
generateResult.content = validationResult.text;
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3705
3765
|
if (generateResult.analytics?.cost && generateResult.analytics.cost > 0) {
|
|
3706
3766
|
this._sessionCostUsd += generateResult.analytics.cost;
|
|
3707
3767
|
}
|
|
@@ -6452,6 +6512,37 @@ Current user's request: ${currentInput}`;
|
|
|
6452
6512
|
}
|
|
6453
6513
|
async validateStreamRequestOptions(options, startTime) {
|
|
6454
6514
|
await this.validateStreamInput(options);
|
|
6515
|
+
// Input validation for stream
|
|
6516
|
+
if (options.inputValidation && options.input?.text) {
|
|
6517
|
+
const iv = options.inputValidation;
|
|
6518
|
+
if (iv.trimWhitespace) {
|
|
6519
|
+
options.input.text = options.input.text.trim();
|
|
6520
|
+
}
|
|
6521
|
+
if (iv.requireContent && !options.input.text.trim()) {
|
|
6522
|
+
throw new Error("Input content is required but was empty or whitespace");
|
|
6523
|
+
}
|
|
6524
|
+
if (iv.minLength && options.input.text.length < iv.minLength) {
|
|
6525
|
+
throw new Error(`Input text is too short (${options.input.text.length} < ${iv.minLength})`);
|
|
6526
|
+
}
|
|
6527
|
+
if (iv.maxLength && options.input.text.length > iv.maxLength) {
|
|
6528
|
+
throw new Error(`Input text is too long (${options.input.text.length} > ${iv.maxLength})`);
|
|
6529
|
+
}
|
|
6530
|
+
}
|
|
6531
|
+
// PII detection and redaction for stream
|
|
6532
|
+
if (options.piiDetection?.enabled && options.input?.text) {
|
|
6533
|
+
const piiResult = await detectAndRedactPII(options.input.text, {
|
|
6534
|
+
enabled: true,
|
|
6535
|
+
action: options.piiDetection.action ?? "warn",
|
|
6536
|
+
detectTypes: options.piiDetection.detectTypes,
|
|
6537
|
+
customPatterns: options.piiDetection.customPatterns,
|
|
6538
|
+
allowList: options.piiDetection.allowList,
|
|
6539
|
+
redactionText: options.piiDetection.redactionText,
|
|
6540
|
+
});
|
|
6541
|
+
if (piiResult.action === "abort") {
|
|
6542
|
+
throw new Error(piiResult.feedback ?? "Request blocked: PII detected in input");
|
|
6543
|
+
}
|
|
6544
|
+
options.input.text = piiResult.text;
|
|
6545
|
+
}
|
|
6455
6546
|
this.enforceSessionBudget(options.maxBudgetUsd);
|
|
6456
6547
|
await this.applyAuthenticatedRequestContext(options);
|
|
6457
6548
|
this.emitStreamStartEvents(options, startTime);
|
|
@@ -11216,6 +11307,191 @@ Current user's request: ${currentInput}`;
|
|
|
11216
11307
|
const { getPreset } = await withTimeout(import("./evaluation/pipeline/index.js"), 10000, ErrorFactory.evaluationTimeout("evaluation module load", 10000));
|
|
11217
11308
|
return getPreset(presetName);
|
|
11218
11309
|
}
|
|
11310
|
+
// ============================================================================
|
|
11311
|
+
// MULTI-AGENT ORCHESTRATION METHODS
|
|
11312
|
+
// ============================================================================
|
|
11313
|
+
/**
|
|
11314
|
+
* Create an Agent instance for multi-agent orchestration.
|
|
11315
|
+
*
|
|
11316
|
+
* Agents are specialized AI entities with defined instructions, tools, and behavior.
|
|
11317
|
+
* They can be composed into networks for complex task orchestration.
|
|
11318
|
+
*
|
|
11319
|
+
* @param definition - Agent definition specifying behavior and capabilities
|
|
11320
|
+
* @returns A new Agent instance
|
|
11321
|
+
*
|
|
11322
|
+
* @example
|
|
11323
|
+
* ```typescript
|
|
11324
|
+
* const researcher = neurolink.createAgent({
|
|
11325
|
+
* id: 'researcher',
|
|
11326
|
+
* name: 'Research Agent',
|
|
11327
|
+
* description: 'Searches and analyzes information from various sources',
|
|
11328
|
+
* instructions: 'You are a research assistant. Search thoroughly and cite sources.',
|
|
11329
|
+
* tools: ['websearchGrounding', 'readFile'],
|
|
11330
|
+
* model: 'gpt-4o'
|
|
11331
|
+
* });
|
|
11332
|
+
*
|
|
11333
|
+
* const result = await researcher.execute('Find recent AI breakthroughs');
|
|
11334
|
+
* ```
|
|
11335
|
+
*
|
|
11336
|
+
* @see {@link AgentDefinition} for definition options
|
|
11337
|
+
* @see {@link Agent} for agent methods
|
|
11338
|
+
* @since 8.38.0
|
|
11339
|
+
*/
|
|
11340
|
+
async createAgent(definition) {
|
|
11341
|
+
const { Agent } = await import("./agent/agent.js");
|
|
11342
|
+
logger.debug("[NeuroLink] Creating agent", {
|
|
11343
|
+
id: definition.id,
|
|
11344
|
+
name: definition.name,
|
|
11345
|
+
tools: definition.tools?.length || 0,
|
|
11346
|
+
});
|
|
11347
|
+
return new Agent(definition, this);
|
|
11348
|
+
}
|
|
11349
|
+
/**
|
|
11350
|
+
* Create an AgentNetwork for multi-agent orchestration.
|
|
11351
|
+
*
|
|
11352
|
+
* Networks coordinate multiple agents, workflows, and tools with intelligent
|
|
11353
|
+
* LLM-powered routing. The router agent analyzes tasks and delegates to
|
|
11354
|
+
* the most appropriate primitive.
|
|
11355
|
+
*
|
|
11356
|
+
* @param config - Network configuration with agents, workflows, and routing settings
|
|
11357
|
+
* @returns A new AgentNetwork instance
|
|
11358
|
+
*
|
|
11359
|
+
* @example
|
|
11360
|
+
* ```typescript
|
|
11361
|
+
* const network = neurolink.createNetwork({
|
|
11362
|
+
* name: 'Content Team',
|
|
11363
|
+
* description: 'Collaborative content creation pipeline',
|
|
11364
|
+
* agents: [
|
|
11365
|
+
* {
|
|
11366
|
+
* id: 'researcher',
|
|
11367
|
+
* name: 'Researcher',
|
|
11368
|
+
* description: 'Finds and verifies information',
|
|
11369
|
+
* instructions: 'Research topics thoroughly...',
|
|
11370
|
+
* },
|
|
11371
|
+
* {
|
|
11372
|
+
* id: 'writer',
|
|
11373
|
+
* name: 'Writer',
|
|
11374
|
+
* description: 'Creates engaging content',
|
|
11375
|
+
* instructions: 'Write clear, engaging content...',
|
|
11376
|
+
* },
|
|
11377
|
+
* {
|
|
11378
|
+
* id: 'editor',
|
|
11379
|
+
* name: 'Editor',
|
|
11380
|
+
* description: 'Reviews and improves content',
|
|
11381
|
+
* instructions: 'Review for clarity and accuracy...',
|
|
11382
|
+
* }
|
|
11383
|
+
* ],
|
|
11384
|
+
* router: {
|
|
11385
|
+
* model: 'gpt-4o',
|
|
11386
|
+
* confidenceThreshold: 0.7
|
|
11387
|
+
* }
|
|
11388
|
+
* });
|
|
11389
|
+
*
|
|
11390
|
+
* const result = await network.execute({
|
|
11391
|
+
* message: 'Write an article about quantum computing'
|
|
11392
|
+
* });
|
|
11393
|
+
* ```
|
|
11394
|
+
*
|
|
11395
|
+
* @see {@link AgentNetworkConfig} for configuration options
|
|
11396
|
+
* @see {@link AgentNetwork} for network methods
|
|
11397
|
+
* @since 8.38.0
|
|
11398
|
+
*/
|
|
11399
|
+
async createNetwork(config) {
|
|
11400
|
+
const { AgentNetwork } = await import("./agent/agentNetwork.js");
|
|
11401
|
+
logger.debug("[NeuroLink] Creating agent network", {
|
|
11402
|
+
name: config.name,
|
|
11403
|
+
agentCount: config.agents.length,
|
|
11404
|
+
workflowCount: config.workflows?.length || 0,
|
|
11405
|
+
toolCount: config.tools?.length || 0,
|
|
11406
|
+
});
|
|
11407
|
+
return new AgentNetwork(config, this);
|
|
11408
|
+
}
|
|
11409
|
+
/**
|
|
11410
|
+
* Execute an agent network with the given input.
|
|
11411
|
+
*
|
|
11412
|
+
* @param network - The agent network to execute
|
|
11413
|
+
* @param input - Execution input (message and context)
|
|
11414
|
+
* @param options - Optional execution options
|
|
11415
|
+
* @returns Network execution result with content, trace, and usage
|
|
11416
|
+
*
|
|
11417
|
+
* @see {@link NetworkExecutionInput} for input options
|
|
11418
|
+
* @see {@link NetworkExecutionResult} for result structure
|
|
11419
|
+
* @since 8.38.0
|
|
11420
|
+
*/
|
|
11421
|
+
async executeNetwork(network, input, options) {
|
|
11422
|
+
logger.debug("[NeuroLink] Executing agent network", {
|
|
11423
|
+
networkId: network.id,
|
|
11424
|
+
networkName: network.name,
|
|
11425
|
+
hasContext: !!input.context,
|
|
11426
|
+
});
|
|
11427
|
+
return network.execute(input, options);
|
|
11428
|
+
}
|
|
11429
|
+
/**
|
|
11430
|
+
* Stream agent network execution with real-time events.
|
|
11431
|
+
*
|
|
11432
|
+
* @param network - The agent network to stream
|
|
11433
|
+
* @param input - Execution input (message and context)
|
|
11434
|
+
* @param options - Optional execution options
|
|
11435
|
+
* @returns Async iterable of network stream chunks
|
|
11436
|
+
*
|
|
11437
|
+
* @see {@link NetworkStreamChunk} for chunk types
|
|
11438
|
+
* @since 8.38.0
|
|
11439
|
+
*/
|
|
11440
|
+
async *streamNetwork(network, input, options) {
|
|
11441
|
+
logger.debug("[NeuroLink] Streaming agent network", {
|
|
11442
|
+
networkId: network.id,
|
|
11443
|
+
networkName: network.name,
|
|
11444
|
+
hasContext: !!input.context,
|
|
11445
|
+
});
|
|
11446
|
+
yield* network.stream(input, options);
|
|
11447
|
+
}
|
|
11448
|
+
// ============================================================================
|
|
11449
|
+
// ADVANCED ORCHESTRATION METHODS
|
|
11450
|
+
// ============================================================================
|
|
11451
|
+
/**
|
|
11452
|
+
* Create a NetworkOrchestrator for managing multiple agent networks.
|
|
11453
|
+
*
|
|
11454
|
+
* @param config - Orchestrator configuration options
|
|
11455
|
+
* @returns A new NetworkOrchestrator instance
|
|
11456
|
+
* @since 8.38.0
|
|
11457
|
+
*/
|
|
11458
|
+
async createOrchestrator(config) {
|
|
11459
|
+
const { NetworkOrchestrator } = await import("./agent/orchestration/index.js");
|
|
11460
|
+
logger.debug("[NeuroLink] Creating network orchestrator", {
|
|
11461
|
+
maxConcurrentExecutions: config?.maxConcurrentExecutions,
|
|
11462
|
+
defaultMode: config?.defaultMode,
|
|
11463
|
+
});
|
|
11464
|
+
return new NetworkOrchestrator(this, config);
|
|
11465
|
+
}
|
|
11466
|
+
/**
|
|
11467
|
+
* Create an AgentCoordinator for managing agent coordination strategies.
|
|
11468
|
+
*
|
|
11469
|
+
* @param config - Coordinator configuration options
|
|
11470
|
+
* @returns A new AgentCoordinator instance
|
|
11471
|
+
* @since 8.38.0
|
|
11472
|
+
*/
|
|
11473
|
+
async createCoordinator(config) {
|
|
11474
|
+
const { AgentCoordinator } = await import("./agent/coordination/index.js");
|
|
11475
|
+
logger.debug("[NeuroLink] Creating agent coordinator", {
|
|
11476
|
+
strategy: config?.strategy,
|
|
11477
|
+
maxConcurrency: config?.maxConcurrency,
|
|
11478
|
+
});
|
|
11479
|
+
return new AgentCoordinator(config);
|
|
11480
|
+
}
|
|
11481
|
+
/**
|
|
11482
|
+
* Create a MessageBus for inter-agent communication.
|
|
11483
|
+
*
|
|
11484
|
+
* @param config - Message bus configuration options
|
|
11485
|
+
* @returns A new MessageBus instance
|
|
11486
|
+
* @since 8.38.0
|
|
11487
|
+
*/
|
|
11488
|
+
async createMessageBus(config) {
|
|
11489
|
+
const { MessageBus } = await import("./agent/communication/index.js");
|
|
11490
|
+
logger.debug("[NeuroLink] Creating message bus", {
|
|
11491
|
+
maxHistorySize: config?.maxHistorySize,
|
|
11492
|
+
});
|
|
11493
|
+
return new MessageBus(config);
|
|
11494
|
+
}
|
|
11219
11495
|
/**
|
|
11220
11496
|
* Dispose of all resources and cleanup connections
|
|
11221
11497
|
* Call this method when done using the NeuroLink instance to prevent resource leaks
|
|
@@ -521,4 +521,4 @@ export const FILE_EXTENSIONS = {
|
|
|
521
521
|
// =============================================================================
|
|
522
522
|
// TYPE EXPORTS
|
|
523
523
|
// =============================================================================
|
|
524
|
-
//# sourceMappingURL=
|
|
524
|
+
//# sourceMappingURL=fileExtensions.js.map
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* const maxSize = SIZE_LIMITS.IMAGE_MAX_MB; // 10
|
|
27
27
|
* ```
|
|
28
28
|
*/
|
|
29
|
-
export { ARCHIVE_MIME_TYPES, AUDIO_MIME_TYPES, DATA_MIME_TYPES, DOCUMENT_MIME_TYPES, EXTENSION_MIME_MAP, getMimeTypeForExtension, IMAGE_MIME_TYPES, MIME_TYPES, SOURCE_CODE_MIME_TYPES, TEXT_MIME_TYPES, VIDEO_MIME_TYPES, } from "./
|
|
30
|
-
export { ADA_EXTENSIONS, AI_VISION_EXTENSIONS, ARCHIVE_EXTENSIONS, ASSEMBLY_EXTENSIONS, AUDIO_EXTENSIONS, C_EXTENSIONS, CLOJURE_EXTENSIONS, COBOL_EXTENSIONS, CONFIG_EXTENSIONS, CPP_EXTENSIONS, CRYSTAL_EXTENSIONS, CSHARP_EXTENSIONS, CSS_EXTENSIONS, CSV_EXTENSIONS, D_EXTENSIONS, DART_EXTENSIONS, DATA_EXTENSIONS, DATABASE_EXTENSIONS, DESIGN_EXTENSIONS, DOCKERFILE_EXTENSIONS, DOCUMENT_EXTENSIONS, EJS_EXTENSIONS, ELIXIR_EXTENSIONS, ERLANG_EXTENSIONS, EXCEL_EXTENSIONS, EXECUTABLE_EXTENSIONS, FILE_EXTENSIONS, FORTRAN_EXTENSIONS, FSHARP_EXTENSIONS, GO_EXTENSIONS, GROOVY_EXTENSIONS, HANDLEBARS_EXTENSIONS, HASKELL_EXTENSIONS, HTML_EXTENSIONS, IMAGE_EXTENSIONS, JAVA_EXTENSIONS, JAVASCRIPT_EXTENSIONS, JSON_EXTENSIONS, JULIA_EXTENSIONS, KOTLIN_EXTENSIONS, LESS_EXTENSIONS, LISP_EXTENSIONS, LUA_EXTENSIONS, MAKEFILE_EXTENSIONS, MARKDOWN_EXTENSIONS, NIM_EXTENSIONS, OBJECTIVE_C_EXTENSIONS, OCAML_EXTENSIONS, OPENDOCUMENT_EXTENSIONS, PASCAL_EXTENSIONS, PDF_EXTENSIONS, PERL_EXTENSIONS, PHP_EXTENSIONS, POWERPOINT_EXTENSIONS, POWERSHELL_EXTENSIONS, PUG_EXTENSIONS, PYTHON_EXTENSIONS, R_EXTENSIONS, RTF_EXTENSIONS, RUBY_EXTENSIONS, RUST_EXTENSIONS, SCALA_EXTENSIONS, SCHEME_EXTENSIONS, SCSS_EXTENSIONS, SHELL_EXTENSIONS, SOURCE_CODE_EXTENSIONS, SQL_EXTENSIONS, STYLUS_EXTENSIONS, SVELTE_EXTENSIONS, SWIFT_EXTENSIONS, TEXT_EXTENSIONS, TYPESCRIPT_EXTENSIONS, V_EXTENSIONS, VIDEO_EXTENSIONS, VUE_EXTENSIONS, WORD_EXTENSIONS, XML_EXTENSIONS, YAML_EXTENSIONS, ZIG_EXTENSIONS, } from "./
|
|
29
|
+
export { ARCHIVE_MIME_TYPES, AUDIO_MIME_TYPES, DATA_MIME_TYPES, DOCUMENT_MIME_TYPES, EXTENSION_MIME_MAP, getMimeTypeForExtension, IMAGE_MIME_TYPES, MIME_TYPES, SOURCE_CODE_MIME_TYPES, TEXT_MIME_TYPES, VIDEO_MIME_TYPES, } from "./mimeConstants.js";
|
|
30
|
+
export { ADA_EXTENSIONS, AI_VISION_EXTENSIONS, ARCHIVE_EXTENSIONS, ASSEMBLY_EXTENSIONS, AUDIO_EXTENSIONS, C_EXTENSIONS, CLOJURE_EXTENSIONS, COBOL_EXTENSIONS, CONFIG_EXTENSIONS, CPP_EXTENSIONS, CRYSTAL_EXTENSIONS, CSHARP_EXTENSIONS, CSS_EXTENSIONS, CSV_EXTENSIONS, D_EXTENSIONS, DART_EXTENSIONS, DATA_EXTENSIONS, DATABASE_EXTENSIONS, DESIGN_EXTENSIONS, DOCKERFILE_EXTENSIONS, DOCUMENT_EXTENSIONS, EJS_EXTENSIONS, ELIXIR_EXTENSIONS, ERLANG_EXTENSIONS, EXCEL_EXTENSIONS, EXECUTABLE_EXTENSIONS, FILE_EXTENSIONS, FORTRAN_EXTENSIONS, FSHARP_EXTENSIONS, GO_EXTENSIONS, GROOVY_EXTENSIONS, HANDLEBARS_EXTENSIONS, HASKELL_EXTENSIONS, HTML_EXTENSIONS, IMAGE_EXTENSIONS, JAVA_EXTENSIONS, JAVASCRIPT_EXTENSIONS, JSON_EXTENSIONS, JULIA_EXTENSIONS, KOTLIN_EXTENSIONS, LESS_EXTENSIONS, LISP_EXTENSIONS, LUA_EXTENSIONS, MAKEFILE_EXTENSIONS, MARKDOWN_EXTENSIONS, NIM_EXTENSIONS, OBJECTIVE_C_EXTENSIONS, OCAML_EXTENSIONS, OPENDOCUMENT_EXTENSIONS, PASCAL_EXTENSIONS, PDF_EXTENSIONS, PERL_EXTENSIONS, PHP_EXTENSIONS, POWERPOINT_EXTENSIONS, POWERSHELL_EXTENSIONS, PUG_EXTENSIONS, PYTHON_EXTENSIONS, R_EXTENSIONS, RTF_EXTENSIONS, RUBY_EXTENSIONS, RUST_EXTENSIONS, SCALA_EXTENSIONS, SCHEME_EXTENSIONS, SCSS_EXTENSIONS, SHELL_EXTENSIONS, SOURCE_CODE_EXTENSIONS, SQL_EXTENSIONS, STYLUS_EXTENSIONS, SVELTE_EXTENSIONS, SWIFT_EXTENSIONS, TEXT_EXTENSIONS, TYPESCRIPT_EXTENSIONS, V_EXTENSIONS, VIDEO_EXTENSIONS, VUE_EXTENSIONS, WORD_EXTENSIONS, XML_EXTENSIONS, YAML_EXTENSIONS, ZIG_EXTENSIONS, } from "./fileExtensions.js";
|
|
31
31
|
export { detectLanguageFromFilename, EXACT_FILENAME_MAP, getLanguageIdentifier, getSupportedExtensions, getSupportedFilenames, isSourceCodeFile, LANGUAGE_MAP, } from "./languageMap.js";
|
|
32
32
|
export { ARCHIVE_LIMITS, bytesToMB, formatBytes, getSizeLimitForType, isWithinSizeLimit, mbToBytes, PROCESSING_LIMITS, SIZE_LIMITS, SIZE_LIMITS_BYTES, SIZE_LIMITS_MB, validateFileSize, YAML_LIMITS, } from "./sizeLimits.js";
|
|
@@ -36,7 +36,7 @@ EXTENSION_MIME_MAP, getMimeTypeForExtension,
|
|
|
36
36
|
IMAGE_MIME_TYPES,
|
|
37
37
|
// Types
|
|
38
38
|
// Combined MIME types
|
|
39
|
-
MIME_TYPES, SOURCE_CODE_MIME_TYPES, TEXT_MIME_TYPES, VIDEO_MIME_TYPES, } from "./
|
|
39
|
+
MIME_TYPES, SOURCE_CODE_MIME_TYPES, TEXT_MIME_TYPES, VIDEO_MIME_TYPES, } from "./mimeConstants.js";
|
|
40
40
|
// =============================================================================
|
|
41
41
|
// FILE EXTENSIONS
|
|
42
42
|
// =============================================================================
|
|
@@ -66,7 +66,7 @@ JSON_EXTENSIONS, JULIA_EXTENSIONS, KOTLIN_EXTENSIONS, LESS_EXTENSIONS, LISP_EXTE
|
|
|
66
66
|
// Text extensions
|
|
67
67
|
TEXT_EXTENSIONS, TYPESCRIPT_EXTENSIONS, V_EXTENSIONS,
|
|
68
68
|
// Multimedia extensions
|
|
69
|
-
VIDEO_EXTENSIONS, VUE_EXTENSIONS, WORD_EXTENSIONS, XML_EXTENSIONS, YAML_EXTENSIONS, ZIG_EXTENSIONS, } from "./
|
|
69
|
+
VIDEO_EXTENSIONS, VUE_EXTENSIONS, WORD_EXTENSIONS, XML_EXTENSIONS, YAML_EXTENSIONS, ZIG_EXTENSIONS, } from "./fileExtensions.js";
|
|
70
70
|
// =============================================================================
|
|
71
71
|
// LANGUAGE DETECTION
|
|
72
72
|
// =============================================================================
|
|
@@ -364,4 +364,4 @@ export function getMimeTypeForExtension(ext) {
|
|
|
364
364
|
// =============================================================================
|
|
365
365
|
// TYPE EXPORTS
|
|
366
366
|
// =============================================================================
|
|
367
|
-
//# sourceMappingURL=
|
|
367
|
+
//# sourceMappingURL=mimeConstants.js.map
|
|
@@ -122,4 +122,12 @@ export { getProcessorForFile, getSupportedFileTypes, isFileTypeSupported, proces
|
|
|
122
122
|
// CLI HELPERS
|
|
123
123
|
// =============================================================================
|
|
124
124
|
export { detectMimeType, fileExists, getCliUsage, getFileExtension, getSupportedFileTypes as getCliSupportedFileTypes, listSupportedFileTypes, loadFileFromPath, processFileFromPath, } from "./cli/index.js";
|
|
125
|
+
// =============================================================================
|
|
126
|
+
// STANDALONE SAFETY UTILITIES (extracted from the former I/O processor system)
|
|
127
|
+
// =============================================================================
|
|
128
|
+
// PII detection, response validation, and tripwire evaluation are now
|
|
129
|
+
// standalone utilities in src/lib/utils/ and wired directly into
|
|
130
|
+
// generate() and stream() via native options (piiDetection, responseValidation,
|
|
131
|
+
// inputValidation). See src/lib/utils/piiDetector.ts, responseValidator.ts,
|
|
132
|
+
// and tripwireEvaluator.ts.
|
|
125
133
|
//# sourceMappingURL=index.js.map
|
|
@@ -10,7 +10,7 @@ import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../
|
|
|
10
10
|
import { withTimeout } from "../utils/async/index.js";
|
|
11
11
|
import { estimateTokens } from "../utils/tokenEstimation.js";
|
|
12
12
|
import { transformToolExecutions } from "../utils/transformationUtils.js";
|
|
13
|
-
import { buildGeminiResponseSchema, buildNativeConfig, buildNativeToolDeclarations, collectStreamChunks, collectStreamChunksIncremental, computeMaxSteps, createTextChannel, buildUserPartsWithMultimodal, executeNativeToolCalls, extractTextFromParts, extractThoughtSignature, handleMaxStepsTermination, prependConversationMessages, pushModelResponseToHistory, } from "./googleNativeGemini3.js";
|
|
13
|
+
import { buildGeminiResponseSchema, buildNativeConfig, buildNativeToolDeclarations, collectStreamChunks, collectStreamChunksIncremental, computeMaxSteps, createTextChannel, buildUserPartsWithMultimodal, executeNativeToolCalls, extractTextFromParts, extractThoughtSignature, handleMaxStepsTermination, prependConversationMessages, pushModelResponseToHistory, DedupExecuteMap, } from "./googleNativeGemini3.js";
|
|
14
14
|
import { createProxyFetch } from "../proxy/proxyFetch.js";
|
|
15
15
|
// Google AI Live API types now imported from ../types/providerSpecific.js
|
|
16
16
|
// Import proper types for multimodal message handling
|
|
@@ -521,7 +521,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
521
521
|
});
|
|
522
522
|
// Convert tools
|
|
523
523
|
let toolsConfig;
|
|
524
|
-
let executeMap = new
|
|
524
|
+
let executeMap = new DedupExecuteMap();
|
|
525
525
|
let originalNameMap = new Map();
|
|
526
526
|
if (options.tools &&
|
|
527
527
|
Object.keys(options.tools).length > 0 &&
|
|
@@ -584,6 +584,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
584
584
|
let lastStepText = "";
|
|
585
585
|
let totalInputTokens = 0;
|
|
586
586
|
let totalOutputTokens = 0;
|
|
587
|
+
let totalCacheReadTokens = 0;
|
|
587
588
|
let step = 0;
|
|
588
589
|
let completedWithFinalAnswer = false;
|
|
589
590
|
const failedTools = new Map();
|
|
@@ -614,6 +615,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
614
615
|
const chunkResult = await collectStreamChunksIncremental(rawStream, channel);
|
|
615
616
|
totalInputTokens += chunkResult.inputTokens;
|
|
616
617
|
totalOutputTokens += chunkResult.outputTokens;
|
|
618
|
+
totalCacheReadTokens += chunkResult.cacheReadTokens ?? 0;
|
|
617
619
|
const stepText = extractTextFromParts(chunkResult.rawResponseParts);
|
|
618
620
|
// If no function calls, this was the final step — channel
|
|
619
621
|
// already received all text parts incrementally.
|
|
@@ -698,13 +700,23 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
698
700
|
span.setAttribute(ATTR.GEN_AI_INPUT_TOKENS, totalInputTokens);
|
|
699
701
|
span.setAttribute(ATTR.GEN_AI_OUTPUT_TOKENS, totalOutputTokens);
|
|
700
702
|
span.setAttribute(ATTR.GEN_AI_FINISH_REASON, hitStepLimitWithoutFinalAnswer ? "max_steps" : "stop");
|
|
703
|
+
// Gemini promptTokenCount is OVERLAPPING: it already includes
|
|
704
|
+
// cachedContentTokenCount. Subtract once here so calculateCost
|
|
705
|
+
// bills the cached portion at the cheaper cacheRead rate without
|
|
706
|
+
// double-counting. Total billable tokens are conserved.
|
|
707
|
+
const adjustedInputTokens = Math.max(0, totalInputTokens - totalCacheReadTokens);
|
|
701
708
|
analyticsResolve({
|
|
702
709
|
provider: this.providerName,
|
|
703
710
|
model: modelName,
|
|
704
711
|
tokenUsage: {
|
|
705
|
-
input:
|
|
712
|
+
input: adjustedInputTokens,
|
|
706
713
|
output: totalOutputTokens,
|
|
707
|
-
total:
|
|
714
|
+
total: adjustedInputTokens +
|
|
715
|
+
totalCacheReadTokens +
|
|
716
|
+
totalOutputTokens,
|
|
717
|
+
...(totalCacheReadTokens > 0
|
|
718
|
+
? { cacheReadTokens: totalCacheReadTokens }
|
|
719
|
+
: {}),
|
|
708
720
|
},
|
|
709
721
|
requestDuration: responseTime,
|
|
710
722
|
timestamp: new Date().toISOString(),
|
|
@@ -796,7 +808,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
796
808
|
});
|
|
797
809
|
// Convert tools (a0269210: trust options.tools — already merged + filtered upstream)
|
|
798
810
|
let toolsConfig;
|
|
799
|
-
let executeMap = new
|
|
811
|
+
let executeMap = new DedupExecuteMap();
|
|
800
812
|
let originalNameMap = new Map();
|
|
801
813
|
const shouldUseTools = !options.disableTools;
|
|
802
814
|
if (shouldUseTools) {
|
|
@@ -831,6 +843,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
831
843
|
let lastStepText = "";
|
|
832
844
|
let totalInputTokens = 0;
|
|
833
845
|
let totalOutputTokens = 0;
|
|
846
|
+
let totalCacheReadTokens = 0;
|
|
834
847
|
const allToolCalls = [];
|
|
835
848
|
const toolExecutions = [];
|
|
836
849
|
let step = 0;
|
|
@@ -856,6 +869,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
856
869
|
const chunkResult = await collectStreamChunks(stream);
|
|
857
870
|
totalInputTokens += chunkResult.inputTokens;
|
|
858
871
|
totalOutputTokens += chunkResult.outputTokens;
|
|
872
|
+
totalCacheReadTokens += chunkResult.cacheReadTokens ?? 0;
|
|
859
873
|
const stepText = extractTextFromParts(chunkResult.rawResponseParts);
|
|
860
874
|
// If no function calls, we're done
|
|
861
875
|
if (chunkResult.stepFunctionCalls.length === 0) {
|
|
@@ -927,14 +941,21 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
927
941
|
// analytics / evaluation / tracing stay attached. The native AI
|
|
928
942
|
// Studio generate path bypasses BaseProvider.generate(), so
|
|
929
943
|
// skipping enhanceResult would silently drop those features.
|
|
944
|
+
// Gemini promptTokenCount is OVERLAPPING (already includes
|
|
945
|
+
// cachedContentTokenCount). Subtract once so the cached portion is
|
|
946
|
+
// billed at the cheaper cacheRead rate without double-counting.
|
|
947
|
+
const adjustedInputTokens = Math.max(0, totalInputTokens - totalCacheReadTokens);
|
|
930
948
|
const baseResult = {
|
|
931
949
|
content: finalText,
|
|
932
950
|
provider: this.providerName,
|
|
933
951
|
model: modelName,
|
|
934
952
|
usage: {
|
|
935
|
-
input:
|
|
953
|
+
input: adjustedInputTokens,
|
|
936
954
|
output: totalOutputTokens,
|
|
937
|
-
total:
|
|
955
|
+
total: adjustedInputTokens + totalCacheReadTokens + totalOutputTokens,
|
|
956
|
+
...(totalCacheReadTokens > 0
|
|
957
|
+
? { cacheReadTokens: totalCacheReadTokens }
|
|
958
|
+
: {}),
|
|
938
959
|
},
|
|
939
960
|
responseTime,
|
|
940
961
|
toolsUsed: allToolCalls.map((tc) => tc.toolName),
|
|
@@ -10,6 +10,24 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import type { GenerateStopReason, ThinkingConfig, ChatMessage, CollectedChunkResult, MinimalChatMessage, NativeFunctionCall, NativeFunctionResponse, NativeToolDeclarationsResult, NativeToolsConfig, TextChannel, VertexNativePart, GeminiMultimodalInput } from "../types/index.js";
|
|
12
12
|
import type { Tool } from "../types/index.js";
|
|
13
|
+
/**
|
|
14
|
+
* A per-turn tool execute map that deduplicates identical tool calls.
|
|
15
|
+
*
|
|
16
|
+
* Gemini occasionally re-emits a tool call with identical arguments across
|
|
17
|
+
* agentic steps even though the prior result is already in the conversation
|
|
18
|
+
* history (BZ-3327). Re-executing produces duplicate side effects and
|
|
19
|
+
* duplicate reports for the user, plus wasted tokens. This map caches the
|
|
20
|
+
* result of each {tool name + args} executed within the turn and returns it
|
|
21
|
+
* for any identical re-request instead of running the tool again.
|
|
22
|
+
*
|
|
23
|
+
* Providers build a fresh executeMap per request, so the cache scope is
|
|
24
|
+
* exactly one turn. Only `.get()` is overridden (the sole access path used by
|
|
25
|
+
* the native agentic loops), so iteration still yields the raw executors.
|
|
26
|
+
*/
|
|
27
|
+
export declare class DedupExecuteMap extends Map<string, Tool["execute"]> {
|
|
28
|
+
private readonly resultCache;
|
|
29
|
+
get(name: string): Tool["execute"] | undefined;
|
|
30
|
+
}
|
|
13
31
|
export declare function sanitizeForGoogleFunctionName(name: string): string;
|
|
14
32
|
/**
|
|
15
33
|
* Resolve a sanitized Gemini tool name to one that is both unique within
|
|
@@ -17,6 +17,52 @@ import { convertZodToJsonSchema, ensureNestedSchemaTypes, inlineJsonSchema, isZo
|
|
|
17
17
|
import { createNativeThinkingConfig } from "../utils/thinkingConfig.js";
|
|
18
18
|
import { jsonSchema as aiJsonSchema, tool as createAISDKTool, } from "../utils/tool.js";
|
|
19
19
|
// ── Functions ──
|
|
20
|
+
/** Stable, key-order-independent serialization of tool args for the dedup key. */
|
|
21
|
+
function stableStringifyForDedup(value) {
|
|
22
|
+
return JSON.stringify(value, (_key, val) => val && typeof val === "object" && !Array.isArray(val)
|
|
23
|
+
? Object.keys(val)
|
|
24
|
+
.sort()
|
|
25
|
+
.reduce((acc, key) => {
|
|
26
|
+
acc[key] = val[key];
|
|
27
|
+
return acc;
|
|
28
|
+
}, {})
|
|
29
|
+
: val);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A per-turn tool execute map that deduplicates identical tool calls.
|
|
33
|
+
*
|
|
34
|
+
* Gemini occasionally re-emits a tool call with identical arguments across
|
|
35
|
+
* agentic steps even though the prior result is already in the conversation
|
|
36
|
+
* history (BZ-3327). Re-executing produces duplicate side effects and
|
|
37
|
+
* duplicate reports for the user, plus wasted tokens. This map caches the
|
|
38
|
+
* result of each {tool name + args} executed within the turn and returns it
|
|
39
|
+
* for any identical re-request instead of running the tool again.
|
|
40
|
+
*
|
|
41
|
+
* Providers build a fresh executeMap per request, so the cache scope is
|
|
42
|
+
* exactly one turn. Only `.get()` is overridden (the sole access path used by
|
|
43
|
+
* the native agentic loops), so iteration still yields the raw executors.
|
|
44
|
+
*/
|
|
45
|
+
export class DedupExecuteMap extends Map {
|
|
46
|
+
resultCache = new Map();
|
|
47
|
+
get(name) {
|
|
48
|
+
const execute = super.get(name);
|
|
49
|
+
if (!execute) {
|
|
50
|
+
return execute;
|
|
51
|
+
}
|
|
52
|
+
const resultCache = this.resultCache;
|
|
53
|
+
const wrapped = async (args, options) => {
|
|
54
|
+
const key = `${name}::${stableStringifyForDedup(args)}`;
|
|
55
|
+
if (resultCache.has(key)) {
|
|
56
|
+
logger.warn(`[DedupExecuteMap] Tool "${name}" re-requested with identical arguments in the same turn — reusing the previous result instead of re-executing.`);
|
|
57
|
+
return resultCache.get(key);
|
|
58
|
+
}
|
|
59
|
+
const result = await execute(args, options);
|
|
60
|
+
resultCache.set(key, result);
|
|
61
|
+
return result;
|
|
62
|
+
};
|
|
63
|
+
return wrapped;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
20
66
|
/**
|
|
21
67
|
* Google's `function_declarations[].name` validator regex.
|
|
22
68
|
*
|
|
@@ -323,7 +369,7 @@ export function normalizeToolsForJsonSchemaProvider(tools) {
|
|
|
323
369
|
*/
|
|
324
370
|
export function buildNativeToolDeclarations(tools) {
|
|
325
371
|
const functionDeclarations = [];
|
|
326
|
-
const executeMap = new
|
|
372
|
+
const executeMap = new DedupExecuteMap();
|
|
327
373
|
const skippedTools = [];
|
|
328
374
|
const renamedTools = [];
|
|
329
375
|
// Disambiguate distinct originals that collapse onto the same sanitized
|
|
@@ -514,6 +560,7 @@ export async function collectStreamChunks(stream) {
|
|
|
514
560
|
const stepFunctionCalls = [];
|
|
515
561
|
let inputTokens = 0;
|
|
516
562
|
let outputTokens = 0;
|
|
563
|
+
let cacheReadTokens = 0;
|
|
517
564
|
for await (const chunk of stream) {
|
|
518
565
|
// Extract raw parts from candidates FIRST
|
|
519
566
|
// This avoids using chunk.text which triggers SDK warning when
|
|
@@ -533,9 +580,18 @@ export async function collectStreamChunks(stream) {
|
|
|
533
580
|
if (usage) {
|
|
534
581
|
inputTokens = Math.max(inputTokens, usage.promptTokenCount || 0);
|
|
535
582
|
outputTokens = Math.max(outputTokens, usage.candidatesTokenCount || 0);
|
|
583
|
+
// cachedContentTokenCount is OVERLAPPING (a subset already inside
|
|
584
|
+
// promptTokenCount). Surface it so the call site subtracts once.
|
|
585
|
+
cacheReadTokens = Math.max(cacheReadTokens, usage.cachedContentTokenCount || 0);
|
|
536
586
|
}
|
|
537
587
|
}
|
|
538
|
-
return {
|
|
588
|
+
return {
|
|
589
|
+
rawResponseParts,
|
|
590
|
+
stepFunctionCalls,
|
|
591
|
+
inputTokens,
|
|
592
|
+
outputTokens,
|
|
593
|
+
cacheReadTokens,
|
|
594
|
+
};
|
|
539
595
|
}
|
|
540
596
|
/**
|
|
541
597
|
* Create a push-based text channel that bridges a background producer
|
|
@@ -627,6 +683,7 @@ export async function collectStreamChunksIncremental(stream, channel) {
|
|
|
627
683
|
const stepFunctionCalls = [];
|
|
628
684
|
let inputTokens = 0;
|
|
629
685
|
let outputTokens = 0;
|
|
686
|
+
let cacheReadTokens = 0;
|
|
630
687
|
for await (const chunk of stream) {
|
|
631
688
|
const chunkRecord = chunk;
|
|
632
689
|
const candidates = chunkRecord.candidates;
|
|
@@ -648,9 +705,18 @@ export async function collectStreamChunksIncremental(stream, channel) {
|
|
|
648
705
|
if (usage) {
|
|
649
706
|
inputTokens = Math.max(inputTokens, usage.promptTokenCount || 0);
|
|
650
707
|
outputTokens = Math.max(outputTokens, usage.candidatesTokenCount || 0);
|
|
708
|
+
// cachedContentTokenCount is OVERLAPPING (a subset already inside
|
|
709
|
+
// promptTokenCount). Surface it so the call site subtracts once.
|
|
710
|
+
cacheReadTokens = Math.max(cacheReadTokens, usage.cachedContentTokenCount || 0);
|
|
651
711
|
}
|
|
652
712
|
}
|
|
653
|
-
return {
|
|
713
|
+
return {
|
|
714
|
+
rawResponseParts,
|
|
715
|
+
stepFunctionCalls,
|
|
716
|
+
inputTokens,
|
|
717
|
+
outputTokens,
|
|
718
|
+
cacheReadTokens,
|
|
719
|
+
};
|
|
654
720
|
}
|
|
655
721
|
/**
|
|
656
722
|
* Extract the thoughtSignature token from raw response parts.
|