@juspay/neurolink 9.42.1 → 9.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/browser/neurolink.min.js +300 -300
- package/dist/cli/commands/mcp.js +15 -3
- package/dist/cli/commands/proxy.js +29 -6
- package/dist/core/baseProvider.js +12 -3
- package/dist/core/factory.js +4 -4
- package/dist/core/modules/ToolsManager.d.ts +1 -0
- package/dist/core/modules/ToolsManager.js +40 -42
- package/dist/core/toolEvents.d.ts +3 -0
- package/dist/core/toolEvents.js +7 -0
- package/dist/evaluation/scorers/scorerRegistry.js +3 -2
- package/dist/lib/core/baseProvider.js +12 -3
- package/dist/lib/core/factory.js +4 -4
- package/dist/lib/core/modules/ToolsManager.d.ts +1 -0
- package/dist/lib/core/modules/ToolsManager.js +40 -42
- package/dist/lib/core/toolEvents.d.ts +3 -0
- package/dist/lib/core/toolEvents.js +8 -0
- package/dist/lib/evaluation/scorers/scorerRegistry.js +3 -2
- package/dist/lib/neurolink.js +33 -19
- package/dist/lib/providers/googleNativeGemini3.d.ts +4 -0
- package/dist/lib/providers/googleNativeGemini3.js +39 -1
- package/dist/lib/providers/googleVertex.js +10 -2
- package/dist/lib/proxy/claudeFormat.js +2 -1
- package/dist/lib/proxy/proxyHealth.d.ts +17 -0
- package/dist/lib/proxy/proxyHealth.js +55 -0
- package/dist/lib/proxy/requestLogger.js +8 -3
- package/dist/lib/proxy/routingPolicy.d.ts +33 -0
- package/dist/lib/proxy/routingPolicy.js +255 -0
- package/dist/lib/proxy/snapshotPersistence.d.ts +2 -0
- package/dist/lib/proxy/snapshotPersistence.js +41 -0
- package/dist/lib/server/routes/claudeProxyRoutes.d.ts +1 -9
- package/dist/lib/server/routes/claudeProxyRoutes.js +304 -219
- package/dist/lib/tasks/store/redisTaskStore.js +34 -16
- package/dist/lib/types/cli.d.ts +4 -0
- package/dist/lib/types/proxyTypes.d.ts +87 -0
- package/dist/lib/types/tools.d.ts +18 -0
- package/dist/lib/utils/schemaConversion.d.ts +1 -0
- package/dist/lib/utils/schemaConversion.js +3 -0
- package/dist/neurolink.js +33 -19
- package/dist/providers/googleNativeGemini3.d.ts +4 -0
- package/dist/providers/googleNativeGemini3.js +39 -1
- package/dist/providers/googleVertex.js +10 -2
- package/dist/proxy/claudeFormat.js +2 -1
- package/dist/proxy/proxyHealth.d.ts +17 -0
- package/dist/proxy/proxyHealth.js +54 -0
- package/dist/proxy/requestLogger.js +8 -3
- package/dist/proxy/routingPolicy.d.ts +33 -0
- package/dist/proxy/routingPolicy.js +254 -0
- package/dist/proxy/snapshotPersistence.d.ts +2 -0
- package/dist/proxy/snapshotPersistence.js +40 -0
- package/dist/server/routes/claudeProxyRoutes.d.ts +1 -9
- package/dist/server/routes/claudeProxyRoutes.js +304 -219
- package/dist/tasks/store/redisTaskStore.js +34 -16
- package/dist/types/cli.d.ts +4 -0
- package/dist/types/proxyTypes.d.ts +87 -0
- package/dist/types/tools.d.ts +18 -0
- package/dist/utils/schemaConversion.d.ts +1 -0
- package/dist/utils/schemaConversion.js +3 -0
- package/package.json +1 -1
package/dist/cli/commands/mcp.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
package/dist/core/factory.js
CHANGED
|
@@ -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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
179
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
447
|
-
|
|
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
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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:
|
|
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
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
}
|
|
@@ -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
|
-
|
|
416
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
package/dist/lib/core/factory.js
CHANGED
|
@@ -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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
179
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
447
|
-
|
|
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
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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:
|
|
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
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
}
|
|
@@ -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
|
-
|
|
416
|
-
|
|
415
|
+
catch (err) {
|
|
416
|
+
ScorerRegistry.initPromise = null; // allow retry on next call
|
|
417
|
+
throw err;
|
|
417
418
|
}
|
|
418
419
|
})();
|
|
419
420
|
return ScorerRegistry.initPromise;
|