@juspay/neurolink 9.42.1 → 9.44.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 (59) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/browser/neurolink.min.js +300 -300
  3. package/dist/cli/commands/mcp.js +15 -3
  4. package/dist/cli/commands/proxy.js +29 -6
  5. package/dist/core/baseProvider.js +12 -3
  6. package/dist/core/factory.js +4 -4
  7. package/dist/core/modules/ToolsManager.d.ts +1 -0
  8. package/dist/core/modules/ToolsManager.js +40 -42
  9. package/dist/core/toolEvents.d.ts +3 -0
  10. package/dist/core/toolEvents.js +7 -0
  11. package/dist/evaluation/scorers/scorerRegistry.js +3 -2
  12. package/dist/lib/core/baseProvider.js +12 -3
  13. package/dist/lib/core/factory.js +4 -4
  14. package/dist/lib/core/modules/ToolsManager.d.ts +1 -0
  15. package/dist/lib/core/modules/ToolsManager.js +40 -42
  16. package/dist/lib/core/toolEvents.d.ts +3 -0
  17. package/dist/lib/core/toolEvents.js +8 -0
  18. package/dist/lib/evaluation/scorers/scorerRegistry.js +3 -2
  19. package/dist/lib/neurolink.js +33 -19
  20. package/dist/lib/providers/googleNativeGemini3.d.ts +4 -0
  21. package/dist/lib/providers/googleNativeGemini3.js +39 -1
  22. package/dist/lib/providers/googleVertex.js +10 -2
  23. package/dist/lib/proxy/claudeFormat.js +2 -1
  24. package/dist/lib/proxy/proxyHealth.d.ts +17 -0
  25. package/dist/lib/proxy/proxyHealth.js +55 -0
  26. package/dist/lib/proxy/requestLogger.js +8 -3
  27. package/dist/lib/proxy/routingPolicy.d.ts +33 -0
  28. package/dist/lib/proxy/routingPolicy.js +255 -0
  29. package/dist/lib/proxy/snapshotPersistence.d.ts +2 -0
  30. package/dist/lib/proxy/snapshotPersistence.js +41 -0
  31. package/dist/lib/server/routes/claudeProxyRoutes.d.ts +1 -9
  32. package/dist/lib/server/routes/claudeProxyRoutes.js +304 -219
  33. package/dist/lib/tasks/store/redisTaskStore.js +34 -16
  34. package/dist/lib/types/cli.d.ts +4 -0
  35. package/dist/lib/types/proxyTypes.d.ts +87 -0
  36. package/dist/lib/types/tools.d.ts +18 -0
  37. package/dist/lib/utils/schemaConversion.d.ts +1 -0
  38. package/dist/lib/utils/schemaConversion.js +3 -0
  39. package/dist/neurolink.js +33 -19
  40. package/dist/providers/googleNativeGemini3.d.ts +4 -0
  41. package/dist/providers/googleNativeGemini3.js +39 -1
  42. package/dist/providers/googleVertex.js +10 -2
  43. package/dist/proxy/claudeFormat.js +2 -1
  44. package/dist/proxy/proxyHealth.d.ts +17 -0
  45. package/dist/proxy/proxyHealth.js +54 -0
  46. package/dist/proxy/requestLogger.js +8 -3
  47. package/dist/proxy/routingPolicy.d.ts +33 -0
  48. package/dist/proxy/routingPolicy.js +254 -0
  49. package/dist/proxy/snapshotPersistence.d.ts +2 -0
  50. package/dist/proxy/snapshotPersistence.js +40 -0
  51. package/dist/server/routes/claudeProxyRoutes.d.ts +1 -9
  52. package/dist/server/routes/claudeProxyRoutes.js +304 -219
  53. package/dist/tasks/store/redisTaskStore.js +34 -16
  54. package/dist/types/cli.d.ts +4 -0
  55. package/dist/types/proxyTypes.d.ts +87 -0
  56. package/dist/types/tools.d.ts +18 -0
  57. package/dist/utils/schemaConversion.d.ts +1 -0
  58. package/dist/utils/schemaConversion.js +3 -0
  59. package/package.json +1 -1
@@ -2246,6 +2246,11 @@ ${tools.length > 0 ? tools.map((t) => `- **${t}**: TODO: Add description`).join(
2246
2246
  }
2247
2247
  const serverId = argv.server;
2248
2248
  const foundTool = this.findToolForAnnotation(servers, toolName, serverId);
2249
+ if (foundTool === "ambiguous") {
2250
+ logger.error(chalk.red(`Tool '${toolName}' exists on multiple servers. Use --server <id> to specify which server to annotate.`));
2251
+ logger.always(chalk.yellow("Use 'neurolink mcp annotate --list' to see available tools and their server IDs."));
2252
+ process.exit(1);
2253
+ }
2249
2254
  if (!foundTool) {
2250
2255
  logger.error(chalk.red(`Tool '${toolName}' not found.${serverId ? ` Server: ${serverId}` : ""}`));
2251
2256
  logger.always(chalk.yellow("Use 'neurolink mcp annotate --list' to see available tools."));
@@ -2335,22 +2340,29 @@ ${tools.length > 0 ? tools.map((t) => `- **${t}**: TODO: Add description`).join(
2335
2340
  return allTools;
2336
2341
  }
2337
2342
  static findToolForAnnotation(servers, toolName, serverId) {
2343
+ const matches = [];
2338
2344
  for (const server of servers) {
2339
2345
  if (serverId && server.id !== serverId) {
2340
2346
  continue;
2341
2347
  }
2342
2348
  for (const tool of server.tools || []) {
2343
2349
  if (tool.name === toolName) {
2344
- return {
2350
+ matches.push({
2345
2351
  name: tool.name,
2346
2352
  description: tool.description,
2347
2353
  serverId: server.id,
2348
2354
  serverName: server.name,
2349
- };
2355
+ });
2350
2356
  }
2351
2357
  }
2352
2358
  }
2353
- return null;
2359
+ if (matches.length === 0) {
2360
+ return null;
2361
+ }
2362
+ if (matches.length > 1) {
2363
+ return "ambiguous";
2364
+ }
2365
+ return matches[0] ?? null;
2354
2366
  }
2355
2367
  static buildAnnotationsFromArgs(argv, foundTool) {
2356
2368
  let annotations = {};
@@ -14,6 +14,7 @@ import { homedir } from "node:os";
14
14
  import { join, resolve } from "node:path";
15
15
  import chalk from "chalk";
16
16
  import ora from "ora";
17
+ import { buildProxyHealthResponse, createProxyReadinessState, markProxyReady, waitForProxyReadiness, } from "../../lib/proxy/proxyHealth.js";
17
18
  import { logger } from "../../lib/utils/logger.js";
18
19
  import { formatUptime, isProcessRunning, StateFileManager, } from "../utils/serverUtils.js";
19
20
  import { loadProxyEnvFile, resolveProxyEnvFile, } from "../../lib/proxy/proxyEnv.js";
@@ -390,6 +391,7 @@ async function createProxyStartApp(params) {
390
391
  const { createClaudeProxyRoutes } = await import("../../lib/server/routes/claudeProxyRoutes.js");
391
392
  const { Hono } = await import("hono");
392
393
  const app = new Hono();
394
+ const readiness = createProxyReadinessState();
393
395
  app.onError((err, c) => {
394
396
  const errMsg = err instanceof Error ? err.message : String(err);
395
397
  logger.always(`[proxy] unhandled error: ${errMsg}`);
@@ -520,23 +522,31 @@ async function createProxyStartApp(params) {
520
522
  return c.json(result ?? {});
521
523
  });
522
524
  }
523
- app.get("/health", (c) => c.json({
524
- status: "ok",
525
+ app.get("/health", (c) => c.json(buildProxyHealthResponse(readiness, {
525
526
  strategy: params.strategy,
526
- uptime: process.uptime(),
527
+ passthrough: params.passthrough,
527
528
  version: PROXY_VERSION,
528
- }));
529
+ })));
529
530
  app.get("/status", async (c) => {
530
531
  const { getStats } = await import("../../lib/proxy/usageStats.js");
531
532
  const stats = getStats();
533
+ const health = buildProxyHealthResponse(readiness, {
534
+ strategy: params.strategy,
535
+ passthrough: params.passthrough,
536
+ version: PROXY_VERSION,
537
+ });
532
538
  return c.json({
533
539
  status: "running",
540
+ ready: health.ready,
541
+ acceptingConnections: health.acceptingConnections,
542
+ readyAt: health.readyAt,
534
543
  pid: process.pid,
535
544
  port: params.port,
536
545
  host: params.host,
537
546
  strategy: params.strategy,
538
547
  uptime: process.uptime(),
539
548
  version: PROXY_VERSION,
549
+ health,
540
550
  stats: {
541
551
  totalAttempts: stats.totalAttempts,
542
552
  totalRequests: stats.totalRequests,
@@ -562,7 +572,7 @@ async function createProxyStartApp(params) {
562
572
  : null,
563
573
  });
564
574
  });
565
- return { app };
575
+ return { app, readiness };
566
576
  }
567
577
  async function initializeProxyOpenTelemetry() {
568
578
  try {
@@ -724,6 +734,12 @@ async function startProxyRuntime(params) {
724
734
  hostname: params.host,
725
735
  });
726
736
  const guardPid = spawnFailOpenGuard(params.host, params.port, process.pid);
737
+ const readinessHost = params.host === "0.0.0.0" ? "127.0.0.1" : params.host;
738
+ await waitForProxyReadiness({
739
+ host: readinessHost,
740
+ port: params.port,
741
+ });
742
+ markProxyReady(params.readiness);
727
743
  const fallbackChain = params.proxyConfig?.routing?.fallbackChain?.map((entry) => ({
728
744
  provider: entry.provider,
729
745
  model: entry.model,
@@ -734,6 +750,12 @@ async function startProxyRuntime(params) {
734
750
  host: params.host,
735
751
  strategy: params.strategy,
736
752
  startTime: new Date().toISOString(),
753
+ ready: true,
754
+ readyAt: params.readiness.readyAtMs
755
+ ? new Date(params.readiness.readyAtMs).toISOString()
756
+ : undefined,
757
+ healthPath: "/health",
758
+ statusPath: "/status",
737
759
  envFile: params.loadedEnvFile,
738
760
  fallbackChain,
739
761
  guardPid,
@@ -785,7 +807,7 @@ async function startProxyCommandHandler(argv) {
785
807
  }
786
808
  const port = argv.port ?? 55669;
787
809
  const host = argv.host ?? "127.0.0.1";
788
- const { app } = await createProxyStartApp({
810
+ const { app, readiness } = await createProxyStartApp({
789
811
  neurolink,
790
812
  modelRouter,
791
813
  strategy,
@@ -802,6 +824,7 @@ async function startProxyCommandHandler(argv) {
802
824
  argv,
803
825
  spinner,
804
826
  app,
827
+ readiness,
805
828
  host,
806
829
  port,
807
830
  strategy,
@@ -131,9 +131,15 @@ export class BaseProvider {
131
131
  }
132
132
  }
133
133
  // CRITICAL: Image generation models don't support real streaming
134
- // Force fake streaming for image models to ensure image output is yielded
134
+ // Force fake streaming for image models to ensure image output is yielded.
135
+ // Skip this path when the caller explicitly requests non-image output (e.g.
136
+ // JSON analysis) so dual-mode models like gemini-3.1-flash-image-preview
137
+ // can still perform text/structured generation.
135
138
  const isImageModel = IMAGE_GENERATION_MODELS.some((m) => this.modelName.includes(m));
136
- if (isImageModel) {
139
+ const requestsNonImageOutput = options.output?.format === "json" ||
140
+ options.output?.format === "structured" ||
141
+ options.output?.format === "text";
142
+ if (isImageModel && !requestsNonImageOutput) {
137
143
  logger.info(`Image model detected, forcing fake streaming`, {
138
144
  provider: this.providerName,
139
145
  model: this.modelName,
@@ -537,7 +543,10 @@ export class BaseProvider {
537
543
  return await this.handleVideoGeneration(options, startTime);
538
544
  }
539
545
  const isImageModel = IMAGE_GENERATION_MODELS.some((m) => this.modelName.includes(m));
540
- if (isImageModel) {
546
+ const requestsNonImageOutput = options.output?.format === "json" ||
547
+ options.output?.format === "structured" ||
548
+ options.output?.format === "text";
549
+ if (isImageModel && !requestsNonImageOutput) {
541
550
  logger.info(`Image generation model detected, routing to executeImageGeneration`, {
542
551
  provider: this.providerName,
543
552
  model: this.modelName,
@@ -7,7 +7,7 @@ import { ProviderRegistry } from "../factories/providerRegistry.js";
7
7
  import { getBestProvider } from "../utils/providerUtils.js";
8
8
  import { logger } from "../utils/logger.js";
9
9
  import { dynamicModelProvider } from "./dynamicModels.js";
10
- import { withTimeout } from "../utils/errorHandling.js";
10
+ import { withTimeout, ErrorFactory } from "../utils/errorHandling.js";
11
11
  const componentIdentifier = "aiProviderFactory";
12
12
  const factoryTracer = tracers.factory;
13
13
  /**
@@ -34,7 +34,7 @@ export class AIProviderFactory {
34
34
  const functionTag = "AIProviderFactory.initializeDynamicProviderWithTimeout";
35
35
  const INIT_TIMEOUT = 10000; // 10 seconds total timeout for initialization
36
36
  try {
37
- await withTimeout(dynamicModelProvider.initialize(), INIT_TIMEOUT, new Error("Dynamic provider initialization timeout"));
37
+ await withTimeout(dynamicModelProvider.initialize(), INIT_TIMEOUT, ErrorFactory.toolTimeout("dynamic-provider-init", INIT_TIMEOUT));
38
38
  logger.debug(`[${functionTag}] Dynamic model provider initialized successfully`);
39
39
  }
40
40
  catch (error) {
@@ -168,7 +168,7 @@ export class AIProviderFactory {
168
168
  }
169
169
  }
170
170
  static async createResolvedProvider(providerName, resolvedModelName, sdk, region, functionTag) {
171
- await withTimeout(ProviderRegistry.registerAllProviders(), 30_000, new Error("Provider registration timed out"));
171
+ await withTimeout(ProviderRegistry.registerAllProviders(), 30_000, ErrorFactory.toolTimeout("provider-registration", 30_000));
172
172
  const normalizedName = this.normalizeProviderName(providerName);
173
173
  const finalModelName = resolvedModelName === "default" || resolvedModelName === null
174
174
  ? undefined
@@ -179,7 +179,7 @@ export class AIProviderFactory {
179
179
  resolvedModelName: resolvedModelName || "not resolved",
180
180
  finalModelName: finalModelName || "using provider default",
181
181
  });
182
- const provider = await withTimeout(ProviderFactory.createProvider(normalizedName, finalModelName, sdk, region), 30_000, new Error(`Provider creation timed out for ${normalizedName}`));
182
+ const provider = await withTimeout(ProviderFactory.createProvider(normalizedName, finalModelName, sdk, region), 30_000, ErrorFactory.toolTimeout(`provider-creation:${normalizedName}`, 30_000));
183
183
  return { normalizedName, finalModelName, provider };
184
184
  }
185
185
  /**
@@ -35,6 +35,7 @@ export declare class ToolsManager {
35
35
  * Set session context for MCP tools
36
36
  */
37
37
  setSessionContext(sessionId?: string, userId?: string): void;
38
+ private emitToolEvent;
38
39
  /**
39
40
  * Set up tool executor for a provider to enable actual tool execution
40
41
  * @param sdk - The NeuroLinkSDK instance for tool execution
@@ -16,6 +16,7 @@
16
16
  */
17
17
  import { tool as createAISDKTool, jsonSchema } from "ai";
18
18
  import { z } from "zod";
19
+ import { createToolEventPayload } from "../toolEvents.js";
19
20
  import { tracers, ATTR, withSpan } from "../../telemetry/index.js";
20
21
  import { SpanStatusCode } from "@opentelemetry/api";
21
22
  import { logger } from "../../utils/logger.js";
@@ -50,6 +51,13 @@ export class ToolsManager {
50
51
  this.sessionId = sessionId;
51
52
  this.userId = userId;
52
53
  }
54
+ emitToolEvent(eventName, toolName, payload) {
55
+ if (this.neurolink?.getEventEmitter) {
56
+ this.neurolink
57
+ .getEventEmitter()
58
+ .emit(eventName, createToolEventPayload(toolName, payload));
59
+ }
60
+ }
53
61
  /**
54
62
  * Set up tool executor for a provider to enable actual tool execution
55
63
  * @param sdk - The NeuroLinkSDK instance for tool execution
@@ -175,27 +183,24 @@ export class ToolsManager {
175
183
  tools[toolName] = {
176
184
  ...directTool,
177
185
  execute: async (params) => {
178
- // 🔧 EMIT TOOL START EVENT - Bedrock-compatible format
179
- if (this.neurolink?.getEventEmitter) {
180
- const emitter = this.neurolink.getEventEmitter();
181
- emitter.emit("tool:start", { tool: toolName, input: params });
182
- }
186
+ const startTime = Date.now();
187
+ this.emitToolEvent("tool:start", toolName, { input: params });
183
188
  try {
184
189
  const result = await originalExecute(params);
185
- // 🔧 EMIT TOOL END EVENT - Bedrock-compatible format
186
- if (this.neurolink?.getEventEmitter) {
187
- const emitter = this.neurolink.getEventEmitter();
188
- emitter.emit("tool:end", { tool: toolName, result });
189
- }
190
+ this.emitToolEvent("tool:end", toolName, {
191
+ result,
192
+ success: true,
193
+ responseTime: Date.now() - startTime,
194
+ });
190
195
  return result;
191
196
  }
192
197
  catch (error) {
193
- // 🔧 EMIT TOOL END EVENT FOR ERROR - Bedrock-compatible format
194
- if (this.neurolink?.getEventEmitter) {
195
- const emitter = this.neurolink.getEventEmitter();
196
- const errorMsg = error instanceof Error ? error.message : String(error);
197
- emitter.emit("tool:end", { tool: toolName, error: errorMsg });
198
- }
198
+ const errorMsg = error instanceof Error ? error.message : String(error);
199
+ this.emitToolEvent("tool:end", toolName, {
200
+ error: errorMsg,
201
+ success: false,
202
+ responseTime: Date.now() - startTime,
203
+ });
199
204
  throw error;
200
205
  }
201
206
  },
@@ -443,48 +448,41 @@ export class ToolsManager {
443
448
  description: tool.description || `External MCP tool ${tool.name}`,
444
449
  inputSchema: finalSchema, // AI SDK v6 uses inputSchema (not parameters)
445
450
  execute: async (params) => {
446
- // Emit tool start event
447
- if (this.neurolink?.getEventEmitter) {
448
- const emitter = this.neurolink.getEventEmitter();
449
- emitter.emit("tool:start", { tool: tool.name, input: params });
450
- }
451
+ const startTime = Date.now();
452
+ this.emitToolEvent("tool:start", tool.name, { input: params });
451
453
  // Execute via NeuroLink's direct tool execution
452
454
  if (this.neurolink &&
453
455
  typeof this.neurolink.executeExternalMCPTool === "function") {
454
456
  try {
455
457
  const result = await this.neurolink.executeExternalMCPTool(tool.serverId || "unknown", tool.name, params);
456
- // Emit tool end event (success)
457
- if (this.neurolink?.getEventEmitter) {
458
- const emitter = this.neurolink.getEventEmitter();
459
- emitter.emit("tool:end", { tool: tool.name, result });
460
- }
458
+ this.emitToolEvent("tool:end", tool.name, {
459
+ result,
460
+ success: true,
461
+ responseTime: Date.now() - startTime,
462
+ });
461
463
  return result;
462
464
  }
463
465
  catch (mcpError) {
464
- // Emit tool end event (error)
465
- if (this.neurolink?.getEventEmitter) {
466
- const emitter = this.neurolink.getEventEmitter();
467
- const errorMsg = mcpError instanceof Error
468
- ? mcpError.message
469
- : String(mcpError);
470
- emitter.emit("tool:end", { tool: tool.name, error: errorMsg });
471
- }
466
+ const errorMsg = mcpError instanceof Error ? mcpError.message : String(mcpError);
467
+ this.emitToolEvent("tool:end", tool.name, {
468
+ error: errorMsg,
469
+ success: false,
470
+ responseTime: Date.now() - startTime,
471
+ });
472
472
  logger.error(`External MCP tool failed: ${tool.name}`, {
473
473
  serverId: tool.serverId,
474
- error: mcpError instanceof Error
475
- ? mcpError.message
476
- : String(mcpError),
474
+ error: errorMsg,
477
475
  });
478
476
  throw mcpError;
479
477
  }
480
478
  }
481
479
  else {
482
480
  const error = `Cannot execute external MCP tool: NeuroLink executeExternalMCPTool not available`;
483
- // Emit tool end event (error - no executor)
484
- if (this.neurolink?.getEventEmitter) {
485
- const emitter = this.neurolink.getEventEmitter();
486
- emitter.emit("tool:end", { tool: tool.name, error });
487
- }
481
+ this.emitToolEvent("tool:end", tool.name, {
482
+ error,
483
+ success: false,
484
+ responseTime: Date.now() - startTime,
485
+ });
488
486
  logger.error(error);
489
487
  throw new Error(error);
490
488
  }
@@ -0,0 +1,3 @@
1
+ import type { ToolEventPayload } from "../types/index.js";
2
+ export type { ToolEventPayload };
3
+ export declare function createToolEventPayload(toolName: string, payload?: Omit<ToolEventPayload, "tool" | "toolName">): ToolEventPayload;
@@ -0,0 +1,7 @@
1
+ export function createToolEventPayload(toolName, payload = {}) {
2
+ return {
3
+ ...payload,
4
+ tool: toolName,
5
+ toolName,
6
+ };
7
+ }
@@ -412,8 +412,9 @@ export class ScorerRegistry {
412
412
  ScorerRegistry.initialized = true;
413
413
  logger.debug(`Registered ${ScorerRegistry.scorers.size} built-in scorers (including aliases)`);
414
414
  }
415
- finally {
416
- // Keep initPromise for future callers to await
415
+ catch (err) {
416
+ ScorerRegistry.initPromise = null; // allow retry on next call
417
+ throw err;
417
418
  }
418
419
  })();
419
420
  return ScorerRegistry.initPromise;
@@ -131,9 +131,15 @@ export class BaseProvider {
131
131
  }
132
132
  }
133
133
  // CRITICAL: Image generation models don't support real streaming
134
- // Force fake streaming for image models to ensure image output is yielded
134
+ // Force fake streaming for image models to ensure image output is yielded.
135
+ // Skip this path when the caller explicitly requests non-image output (e.g.
136
+ // JSON analysis) so dual-mode models like gemini-3.1-flash-image-preview
137
+ // can still perform text/structured generation.
135
138
  const isImageModel = IMAGE_GENERATION_MODELS.some((m) => this.modelName.includes(m));
136
- if (isImageModel) {
139
+ const requestsNonImageOutput = options.output?.format === "json" ||
140
+ options.output?.format === "structured" ||
141
+ options.output?.format === "text";
142
+ if (isImageModel && !requestsNonImageOutput) {
137
143
  logger.info(`Image model detected, forcing fake streaming`, {
138
144
  provider: this.providerName,
139
145
  model: this.modelName,
@@ -537,7 +543,10 @@ export class BaseProvider {
537
543
  return await this.handleVideoGeneration(options, startTime);
538
544
  }
539
545
  const isImageModel = IMAGE_GENERATION_MODELS.some((m) => this.modelName.includes(m));
540
- if (isImageModel) {
546
+ const requestsNonImageOutput = options.output?.format === "json" ||
547
+ options.output?.format === "structured" ||
548
+ options.output?.format === "text";
549
+ if (isImageModel && !requestsNonImageOutput) {
541
550
  logger.info(`Image generation model detected, routing to executeImageGeneration`, {
542
551
  provider: this.providerName,
543
552
  model: this.modelName,
@@ -7,7 +7,7 @@ import { ProviderRegistry } from "../factories/providerRegistry.js";
7
7
  import { getBestProvider } from "../utils/providerUtils.js";
8
8
  import { logger } from "../utils/logger.js";
9
9
  import { dynamicModelProvider } from "./dynamicModels.js";
10
- import { withTimeout } from "../utils/errorHandling.js";
10
+ import { withTimeout, ErrorFactory } from "../utils/errorHandling.js";
11
11
  import { AIProviderName } from "../constants/enums.js";
12
12
  const componentIdentifier = "aiProviderFactory";
13
13
  const factoryTracer = tracers.factory;
@@ -35,7 +35,7 @@ export class AIProviderFactory {
35
35
  const functionTag = "AIProviderFactory.initializeDynamicProviderWithTimeout";
36
36
  const INIT_TIMEOUT = 10000; // 10 seconds total timeout for initialization
37
37
  try {
38
- await withTimeout(dynamicModelProvider.initialize(), INIT_TIMEOUT, new Error("Dynamic provider initialization timeout"));
38
+ await withTimeout(dynamicModelProvider.initialize(), INIT_TIMEOUT, ErrorFactory.toolTimeout("dynamic-provider-init", INIT_TIMEOUT));
39
39
  logger.debug(`[${functionTag}] Dynamic model provider initialized successfully`);
40
40
  }
41
41
  catch (error) {
@@ -169,7 +169,7 @@ export class AIProviderFactory {
169
169
  }
170
170
  }
171
171
  static async createResolvedProvider(providerName, resolvedModelName, sdk, region, functionTag) {
172
- await withTimeout(ProviderRegistry.registerAllProviders(), 30_000, new Error("Provider registration timed out"));
172
+ await withTimeout(ProviderRegistry.registerAllProviders(), 30_000, ErrorFactory.toolTimeout("provider-registration", 30_000));
173
173
  const normalizedName = this.normalizeProviderName(providerName);
174
174
  const finalModelName = resolvedModelName === "default" || resolvedModelName === null
175
175
  ? undefined
@@ -180,7 +180,7 @@ export class AIProviderFactory {
180
180
  resolvedModelName: resolvedModelName || "not resolved",
181
181
  finalModelName: finalModelName || "using provider default",
182
182
  });
183
- const provider = await withTimeout(ProviderFactory.createProvider(normalizedName, finalModelName, sdk, region), 30_000, new Error(`Provider creation timed out for ${normalizedName}`));
183
+ const provider = await withTimeout(ProviderFactory.createProvider(normalizedName, finalModelName, sdk, region), 30_000, ErrorFactory.toolTimeout(`provider-creation:${normalizedName}`, 30_000));
184
184
  return { normalizedName, finalModelName, provider };
185
185
  }
186
186
  /**
@@ -35,6 +35,7 @@ export declare class ToolsManager {
35
35
  * Set session context for MCP tools
36
36
  */
37
37
  setSessionContext(sessionId?: string, userId?: string): void;
38
+ private emitToolEvent;
38
39
  /**
39
40
  * Set up tool executor for a provider to enable actual tool execution
40
41
  * @param sdk - The NeuroLinkSDK instance for tool execution
@@ -16,6 +16,7 @@
16
16
  */
17
17
  import { tool as createAISDKTool, jsonSchema } from "ai";
18
18
  import { z } from "zod";
19
+ import { createToolEventPayload } from "../toolEvents.js";
19
20
  import { tracers, ATTR, withSpan } from "../../telemetry/index.js";
20
21
  import { SpanStatusCode } from "@opentelemetry/api";
21
22
  import { logger } from "../../utils/logger.js";
@@ -50,6 +51,13 @@ export class ToolsManager {
50
51
  this.sessionId = sessionId;
51
52
  this.userId = userId;
52
53
  }
54
+ emitToolEvent(eventName, toolName, payload) {
55
+ if (this.neurolink?.getEventEmitter) {
56
+ this.neurolink
57
+ .getEventEmitter()
58
+ .emit(eventName, createToolEventPayload(toolName, payload));
59
+ }
60
+ }
53
61
  /**
54
62
  * Set up tool executor for a provider to enable actual tool execution
55
63
  * @param sdk - The NeuroLinkSDK instance for tool execution
@@ -175,27 +183,24 @@ export class ToolsManager {
175
183
  tools[toolName] = {
176
184
  ...directTool,
177
185
  execute: async (params) => {
178
- // 🔧 EMIT TOOL START EVENT - Bedrock-compatible format
179
- if (this.neurolink?.getEventEmitter) {
180
- const emitter = this.neurolink.getEventEmitter();
181
- emitter.emit("tool:start", { tool: toolName, input: params });
182
- }
186
+ const startTime = Date.now();
187
+ this.emitToolEvent("tool:start", toolName, { input: params });
183
188
  try {
184
189
  const result = await originalExecute(params);
185
- // 🔧 EMIT TOOL END EVENT - Bedrock-compatible format
186
- if (this.neurolink?.getEventEmitter) {
187
- const emitter = this.neurolink.getEventEmitter();
188
- emitter.emit("tool:end", { tool: toolName, result });
189
- }
190
+ this.emitToolEvent("tool:end", toolName, {
191
+ result,
192
+ success: true,
193
+ responseTime: Date.now() - startTime,
194
+ });
190
195
  return result;
191
196
  }
192
197
  catch (error) {
193
- // 🔧 EMIT TOOL END EVENT FOR ERROR - Bedrock-compatible format
194
- if (this.neurolink?.getEventEmitter) {
195
- const emitter = this.neurolink.getEventEmitter();
196
- const errorMsg = error instanceof Error ? error.message : String(error);
197
- emitter.emit("tool:end", { tool: toolName, error: errorMsg });
198
- }
198
+ const errorMsg = error instanceof Error ? error.message : String(error);
199
+ this.emitToolEvent("tool:end", toolName, {
200
+ error: errorMsg,
201
+ success: false,
202
+ responseTime: Date.now() - startTime,
203
+ });
199
204
  throw error;
200
205
  }
201
206
  },
@@ -443,48 +448,41 @@ export class ToolsManager {
443
448
  description: tool.description || `External MCP tool ${tool.name}`,
444
449
  inputSchema: finalSchema, // AI SDK v6 uses inputSchema (not parameters)
445
450
  execute: async (params) => {
446
- // Emit tool start event
447
- if (this.neurolink?.getEventEmitter) {
448
- const emitter = this.neurolink.getEventEmitter();
449
- emitter.emit("tool:start", { tool: tool.name, input: params });
450
- }
451
+ const startTime = Date.now();
452
+ this.emitToolEvent("tool:start", tool.name, { input: params });
451
453
  // Execute via NeuroLink's direct tool execution
452
454
  if (this.neurolink &&
453
455
  typeof this.neurolink.executeExternalMCPTool === "function") {
454
456
  try {
455
457
  const result = await this.neurolink.executeExternalMCPTool(tool.serverId || "unknown", tool.name, params);
456
- // Emit tool end event (success)
457
- if (this.neurolink?.getEventEmitter) {
458
- const emitter = this.neurolink.getEventEmitter();
459
- emitter.emit("tool:end", { tool: tool.name, result });
460
- }
458
+ this.emitToolEvent("tool:end", tool.name, {
459
+ result,
460
+ success: true,
461
+ responseTime: Date.now() - startTime,
462
+ });
461
463
  return result;
462
464
  }
463
465
  catch (mcpError) {
464
- // Emit tool end event (error)
465
- if (this.neurolink?.getEventEmitter) {
466
- const emitter = this.neurolink.getEventEmitter();
467
- const errorMsg = mcpError instanceof Error
468
- ? mcpError.message
469
- : String(mcpError);
470
- emitter.emit("tool:end", { tool: tool.name, error: errorMsg });
471
- }
466
+ const errorMsg = mcpError instanceof Error ? mcpError.message : String(mcpError);
467
+ this.emitToolEvent("tool:end", tool.name, {
468
+ error: errorMsg,
469
+ success: false,
470
+ responseTime: Date.now() - startTime,
471
+ });
472
472
  logger.error(`External MCP tool failed: ${tool.name}`, {
473
473
  serverId: tool.serverId,
474
- error: mcpError instanceof Error
475
- ? mcpError.message
476
- : String(mcpError),
474
+ error: errorMsg,
477
475
  });
478
476
  throw mcpError;
479
477
  }
480
478
  }
481
479
  else {
482
480
  const error = `Cannot execute external MCP tool: NeuroLink executeExternalMCPTool not available`;
483
- // Emit tool end event (error - no executor)
484
- if (this.neurolink?.getEventEmitter) {
485
- const emitter = this.neurolink.getEventEmitter();
486
- emitter.emit("tool:end", { tool: tool.name, error });
487
- }
481
+ this.emitToolEvent("tool:end", tool.name, {
482
+ error,
483
+ success: false,
484
+ responseTime: Date.now() - startTime,
485
+ });
488
486
  logger.error(error);
489
487
  throw new Error(error);
490
488
  }
@@ -0,0 +1,3 @@
1
+ import type { ToolEventPayload } from "../types/index.js";
2
+ export type { ToolEventPayload };
3
+ export declare function createToolEventPayload(toolName: string, payload?: Omit<ToolEventPayload, "tool" | "toolName">): ToolEventPayload;
@@ -0,0 +1,8 @@
1
+ export function createToolEventPayload(toolName, payload = {}) {
2
+ return {
3
+ ...payload,
4
+ tool: toolName,
5
+ toolName,
6
+ };
7
+ }
8
+ //# sourceMappingURL=toolEvents.js.map
@@ -412,8 +412,9 @@ export class ScorerRegistry {
412
412
  ScorerRegistry.initialized = true;
413
413
  logger.debug(`Registered ${ScorerRegistry.scorers.size} built-in scorers (including aliases)`);
414
414
  }
415
- finally {
416
- // Keep initPromise for future callers to await
415
+ catch (err) {
416
+ ScorerRegistry.initPromise = null; // allow retry on next call
417
+ throw err;
417
418
  }
418
419
  })();
419
420
  return ScorerRegistry.initPromise;