@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.
Files changed (127) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/agent/agent.d.ts +104 -0
  3. package/dist/agent/agent.js +401 -0
  4. package/dist/agent/agentNetwork.d.ts +87 -0
  5. package/dist/agent/agentNetwork.js +458 -0
  6. package/dist/agent/communication/index.d.ts +9 -0
  7. package/dist/agent/communication/index.js +9 -0
  8. package/dist/agent/communication/message-bus.d.ts +113 -0
  9. package/dist/agent/communication/message-bus.js +406 -0
  10. package/dist/agent/coordination/coordinator.d.ts +83 -0
  11. package/dist/agent/coordination/coordinator.js +598 -0
  12. package/dist/agent/coordination/index.d.ts +10 -0
  13. package/dist/agent/coordination/index.js +10 -0
  14. package/dist/agent/coordination/task-distributor.d.ts +113 -0
  15. package/dist/agent/coordination/task-distributor.js +585 -0
  16. package/dist/agent/index.d.ts +38 -0
  17. package/dist/agent/index.js +44 -0
  18. package/dist/agent/orchestration/index.d.ts +10 -0
  19. package/dist/agent/orchestration/index.js +10 -0
  20. package/dist/agent/orchestration/orchestrator.d.ts +114 -0
  21. package/dist/agent/orchestration/orchestrator.js +476 -0
  22. package/dist/agent/orchestration/topology.d.ts +164 -0
  23. package/dist/agent/orchestration/topology.js +520 -0
  24. package/dist/agent/prompts/routingPrompts.d.ts +63 -0
  25. package/dist/agent/prompts/routingPrompts.js +201 -0
  26. package/dist/browser/neurolink.min.js +377 -358
  27. package/dist/cli/commands/agent.d.ts +37 -0
  28. package/dist/cli/commands/agent.js +718 -0
  29. package/dist/cli/factories/commandFactory.d.ts +8 -0
  30. package/dist/cli/factories/commandFactory.js +116 -0
  31. package/dist/cli/loop/optionsSchema.d.ts +1 -1
  32. package/dist/cli/parser.js +4 -0
  33. package/dist/index.d.ts +6 -0
  34. package/dist/index.js +19 -0
  35. package/dist/lib/agent/agent.d.ts +104 -0
  36. package/dist/lib/agent/agent.js +402 -0
  37. package/dist/lib/agent/agentNetwork.d.ts +87 -0
  38. package/dist/lib/agent/agentNetwork.js +459 -0
  39. package/dist/lib/agent/communication/index.d.ts +9 -0
  40. package/dist/lib/agent/communication/index.js +10 -0
  41. package/dist/lib/agent/communication/message-bus.d.ts +113 -0
  42. package/dist/lib/agent/communication/message-bus.js +407 -0
  43. package/dist/lib/agent/coordination/coordinator.d.ts +83 -0
  44. package/dist/lib/agent/coordination/coordinator.js +599 -0
  45. package/dist/lib/agent/coordination/index.d.ts +10 -0
  46. package/dist/lib/agent/coordination/index.js +11 -0
  47. package/dist/lib/agent/coordination/task-distributor.d.ts +113 -0
  48. package/dist/lib/agent/coordination/task-distributor.js +586 -0
  49. package/dist/lib/agent/index.d.ts +38 -0
  50. package/dist/lib/agent/index.js +45 -0
  51. package/dist/lib/agent/orchestration/index.d.ts +10 -0
  52. package/dist/lib/agent/orchestration/index.js +11 -0
  53. package/dist/lib/agent/orchestration/orchestrator.d.ts +114 -0
  54. package/dist/lib/agent/orchestration/orchestrator.js +477 -0
  55. package/dist/lib/agent/orchestration/topology.d.ts +164 -0
  56. package/dist/lib/agent/orchestration/topology.js +521 -0
  57. package/dist/lib/agent/prompts/routingPrompts.d.ts +63 -0
  58. package/dist/lib/agent/prompts/routingPrompts.js +202 -0
  59. package/dist/lib/index.d.ts +6 -0
  60. package/dist/lib/index.js +19 -0
  61. package/dist/lib/neurolink.d.ts +129 -0
  62. package/dist/lib/neurolink.js +276 -0
  63. package/dist/lib/processors/config/{fileTypes.js → fileExtensions.js} +1 -1
  64. package/dist/lib/processors/config/index.d.ts +2 -2
  65. package/dist/lib/processors/config/index.js +2 -2
  66. package/dist/lib/processors/config/{mimeTypes.js → mimeConstants.js} +1 -1
  67. package/dist/lib/processors/index.js +8 -0
  68. package/dist/lib/providers/googleAiStudio.js +28 -7
  69. package/dist/lib/providers/googleNativeGemini3.d.ts +18 -0
  70. package/dist/lib/providers/googleNativeGemini3.js +69 -3
  71. package/dist/lib/providers/googleVertex.js +33 -9
  72. package/dist/lib/types/agentNetwork.d.ts +1184 -0
  73. package/dist/lib/types/agentNetwork.js +8 -0
  74. package/dist/lib/types/cli.d.ts +66 -0
  75. package/dist/lib/types/common.d.ts +3 -0
  76. package/dist/lib/types/generate.d.ts +53 -0
  77. package/dist/lib/types/index.d.ts +2 -0
  78. package/dist/lib/types/index.js +3 -0
  79. package/dist/lib/types/ioProcessor.d.ts +119 -0
  80. package/dist/lib/types/ioProcessor.js +10 -0
  81. package/dist/lib/types/providers.d.ts +8 -0
  82. package/dist/lib/types/stream.d.ts +36 -0
  83. package/dist/lib/utils/piiDetector.d.ts +24 -0
  84. package/dist/lib/utils/piiDetector.js +221 -0
  85. package/dist/lib/utils/pricing.js +144 -26
  86. package/dist/lib/utils/responseValidator.d.ts +21 -0
  87. package/dist/lib/utils/responseValidator.js +354 -0
  88. package/dist/lib/utils/tokenUtils.d.ts +11 -0
  89. package/dist/lib/utils/tokenUtils.js +33 -2
  90. package/dist/lib/utils/tripwireEvaluator.d.ts +73 -0
  91. package/dist/lib/utils/tripwireEvaluator.js +285 -0
  92. package/dist/neurolink.d.ts +129 -0
  93. package/dist/neurolink.js +276 -0
  94. package/dist/processors/config/index.d.ts +2 -2
  95. package/dist/processors/config/index.js +2 -2
  96. package/dist/processors/index.js +8 -0
  97. package/dist/providers/googleAiStudio.js +28 -7
  98. package/dist/providers/googleNativeGemini3.d.ts +18 -0
  99. package/dist/providers/googleNativeGemini3.js +69 -3
  100. package/dist/providers/googleVertex.js +33 -9
  101. package/dist/types/agentNetwork.d.ts +1184 -0
  102. package/dist/types/agentNetwork.js +7 -0
  103. package/dist/types/cli.d.ts +66 -0
  104. package/dist/types/common.d.ts +3 -0
  105. package/dist/types/generate.d.ts +53 -0
  106. package/dist/types/index.d.ts +2 -0
  107. package/dist/types/index.js +3 -0
  108. package/dist/types/ioProcessor.d.ts +119 -0
  109. package/dist/types/ioProcessor.js +9 -0
  110. package/dist/types/providers.d.ts +8 -0
  111. package/dist/types/stream.d.ts +36 -0
  112. package/dist/utils/piiDetector.d.ts +24 -0
  113. package/dist/utils/piiDetector.js +220 -0
  114. package/dist/utils/pricing.js +144 -26
  115. package/dist/utils/responseValidator.d.ts +21 -0
  116. package/dist/utils/responseValidator.js +353 -0
  117. package/dist/utils/tokenUtils.d.ts +11 -0
  118. package/dist/utils/tokenUtils.js +33 -2
  119. package/dist/utils/tripwireEvaluator.d.ts +73 -0
  120. package/dist/utils/tripwireEvaluator.js +284 -0
  121. package/package.json +1 -1
  122. /package/dist/lib/processors/config/{fileTypes.d.ts → fileExtensions.d.ts} +0 -0
  123. /package/dist/lib/processors/config/{mimeTypes.d.ts → mimeConstants.d.ts} +0 -0
  124. /package/dist/processors/config/{fileTypes.d.ts → fileExtensions.d.ts} +0 -0
  125. /package/dist/processors/config/{fileTypes.js → fileExtensions.js} +0 -0
  126. /package/dist/processors/config/{mimeTypes.d.ts → mimeConstants.d.ts} +0 -0
  127. /package/dist/processors/config/{mimeTypes.js → mimeConstants.js} +0 -0
package/dist/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
@@ -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 "./mimeTypes.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 "./fileTypes.js";
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 "./mimeTypes.js";
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 "./fileTypes.js";
69
+ VIDEO_EXTENSIONS, VUE_EXTENSIONS, WORD_EXTENSIONS, XML_EXTENSIONS, YAML_EXTENSIONS, ZIG_EXTENSIONS, } from "./fileExtensions.js";
70
70
  // =============================================================================
71
71
  // LANGUAGE DETECTION
72
72
  // =============================================================================
@@ -122,3 +122,11 @@ 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.
@@ -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 Map();
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: totalInputTokens,
712
+ input: adjustedInputTokens,
706
713
  output: totalOutputTokens,
707
- total: totalInputTokens + totalOutputTokens,
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 Map();
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: totalInputTokens,
953
+ input: adjustedInputTokens,
936
954
  output: totalOutputTokens,
937
- total: totalInputTokens + totalOutputTokens,
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 Map();
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 { rawResponseParts, stepFunctionCalls, inputTokens, outputTokens };
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 { rawResponseParts, stepFunctionCalls, inputTokens, outputTokens };
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.