@juspay/neurolink 13.5.1 → 13.6.1
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 +2 -2
- package/dist/browser/neurolink.min.js +421 -421
- package/dist/core/baseProvider.d.ts +3 -1
- package/dist/core/baseProvider.js +28 -8
- package/dist/core/modules/ToolsManager.js +10 -3
- package/dist/core/nativeGenerateLoop.d.ts +2 -0
- package/dist/core/nativeGenerateLoop.js +35 -8
- package/dist/neurolink.js +2 -1
- package/dist/providers/openaiChatCompletionsBase.js +25 -14
- package/dist/tools/toolDiscovery.js +2 -1
- package/dist/tools/toolPolicy.js +22 -2
- package/dist/types/config.d.ts +31 -0
- package/dist/types/generate.d.ts +9 -1
- package/dist/types/toolResolution.d.ts +5 -1
- package/dist/utils/toolExecutionId.d.ts +7 -0
- package/dist/utils/toolExecutionId.js +9 -0
- package/package.json +1 -1
|
@@ -152,7 +152,9 @@ export declare abstract class BaseProvider implements AIProvider {
|
|
|
152
152
|
private sortToolRecord;
|
|
153
153
|
/**
|
|
154
154
|
* On-demand discovery (`tools.discovery: true`): defer external MCP tool
|
|
155
|
-
* schemas
|
|
155
|
+
* schemas — or, with `tools.discoveryScope: "all"`, every non-built-in
|
|
156
|
+
* tool — behind one `search_tools` meta-tool, except names matching
|
|
157
|
+
* `tools.discoveryAlwaysVisible`. Built-in tools, per-call
|
|
156
158
|
* tools, explicitly whitelisted tools, and session-pinned (previously
|
|
157
159
|
* discovered) tools always stay hot. No-op when discovery is off — with a
|
|
158
160
|
* one-time WARN when the catalog is large enough that selection accuracy
|
|
@@ -1246,7 +1246,9 @@ export class BaseProvider {
|
|
|
1246
1246
|
}
|
|
1247
1247
|
/**
|
|
1248
1248
|
* On-demand discovery (`tools.discovery: true`): defer external MCP tool
|
|
1249
|
-
* schemas
|
|
1249
|
+
* schemas — or, with `tools.discoveryScope: "all"`, every non-built-in
|
|
1250
|
+
* tool — behind one `search_tools` meta-tool, except names matching
|
|
1251
|
+
* `tools.discoveryAlwaysVisible`. Built-in tools, per-call
|
|
1250
1252
|
* tools, explicitly whitelisted tools, and session-pinned (previously
|
|
1251
1253
|
* discovered) tools always stay hot. No-op when discovery is off — with a
|
|
1252
1254
|
* one-time WARN when the catalog is large enough that selection accuracy
|
|
@@ -1273,8 +1275,13 @@ export class BaseProvider {
|
|
|
1273
1275
|
}
|
|
1274
1276
|
return tools;
|
|
1275
1277
|
}
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
+
// "external" defers only attached MCP servers' tools; "all" defers every
|
|
1279
|
+
// tool in the record except the built-ins.
|
|
1280
|
+
const builtinNames = new Set(Object.keys(this.directTools ?? {}));
|
|
1281
|
+
const candidateNames = policy.discoveryScope === "all"
|
|
1282
|
+
? Object.keys(tools).filter((name) => !builtinNames.has(name))
|
|
1283
|
+
: (this.neurolink?.getExternalMCPTools() ?? []).map((t) => t.name);
|
|
1284
|
+
if (candidateNames.length === 0) {
|
|
1278
1285
|
return tools;
|
|
1279
1286
|
}
|
|
1280
1287
|
// Session pinning requires a caller-provided sessionId. Without one there
|
|
@@ -1305,12 +1312,15 @@ export class BaseProvider {
|
|
|
1305
1312
|
explicitPatterns.push(toolChoice.toolName);
|
|
1306
1313
|
}
|
|
1307
1314
|
const explicitMatcher = explicitPatterns.length > 0 ? toolNameMatcher(explicitPatterns) : null;
|
|
1308
|
-
const
|
|
1309
|
-
|
|
1310
|
-
|
|
1315
|
+
const alwaysVisiblePatterns = policy.discoveryAlwaysVisible ?? [];
|
|
1316
|
+
const alwaysVisibleMatcher = alwaysVisiblePatterns.length > 0
|
|
1317
|
+
? toolNameMatcher(alwaysVisiblePatterns)
|
|
1318
|
+
: null;
|
|
1319
|
+
const deferrableNames = candidateNames.filter((name) => name in tools &&
|
|
1311
1320
|
!perCallNames.has(name) &&
|
|
1312
|
-
!(explicitMatcher ? explicitMatcher(name) : false)
|
|
1313
|
-
|
|
1321
|
+
!(explicitMatcher ? explicitMatcher(name) : false) &&
|
|
1322
|
+
!(alwaysVisibleMatcher ? alwaysVisibleMatcher(name) : false));
|
|
1323
|
+
const partitioned = partitionToolsForDiscovery(tools, {
|
|
1314
1324
|
deferrableNames,
|
|
1315
1325
|
pinnedNames,
|
|
1316
1326
|
onHydrate: (names) => {
|
|
@@ -1319,6 +1329,16 @@ export class BaseProvider {
|
|
|
1319
1329
|
}
|
|
1320
1330
|
},
|
|
1321
1331
|
});
|
|
1332
|
+
// The meta-tool is built after the event wrappers ran, so without this
|
|
1333
|
+
// a search_tools call never reaches tool:start / tool:end listeners.
|
|
1334
|
+
const metaTool = partitioned["search_tools"];
|
|
1335
|
+
if (isDiscoveryMetaTool(metaTool)) {
|
|
1336
|
+
partitioned["search_tools"] =
|
|
1337
|
+
this.toolsManager.wrapExternalToolsWithEvents({
|
|
1338
|
+
search_tools: metaTool,
|
|
1339
|
+
})["search_tools"];
|
|
1340
|
+
}
|
|
1341
|
+
return partitioned;
|
|
1322
1342
|
}
|
|
1323
1343
|
static warnedLargeCatalog = false;
|
|
1324
1344
|
warnLargeCatalogOnce(toolCount) {
|
|
@@ -6,6 +6,7 @@ import { logger } from "../../utils/logger.js";
|
|
|
6
6
|
import { TOOL_EVENTS_WRAPPED } from "../../types/index.js";
|
|
7
7
|
import { getKeyCount } from "../../utils/transformationUtils.js";
|
|
8
8
|
import { extractIsErrorText } from "../../utils/toolResultStatus.js";
|
|
9
|
+
import { createToolExecutionId } from "../../utils/toolExecutionId.js";
|
|
9
10
|
import { convertJsonSchemaToZod } from "../../utils/schemaConversion.js";
|
|
10
11
|
import { generateToolOutputPreview, DEFAULT_MAX_PREVIEW_BYTES, RETRIEVE_CONTEXT_TOOL_NAME, } from "../../context/toolOutputLimits.js";
|
|
11
12
|
/**
|
|
@@ -512,6 +513,12 @@ export class ToolsManager {
|
|
|
512
513
|
const idField = typeof toolCallId === "string" && toolCallId.length > 0
|
|
513
514
|
? { toolCallId }
|
|
514
515
|
: {};
|
|
516
|
+
// Same per-invocation id executeTool gives registered tools, so a
|
|
517
|
+
// listener can pair this start with its end.
|
|
518
|
+
const eventIds = {
|
|
519
|
+
...idField,
|
|
520
|
+
executionId: createToolExecutionId(toolName),
|
|
521
|
+
};
|
|
515
522
|
return withSpan({
|
|
516
523
|
name: toolSpanName(toolName),
|
|
517
524
|
tracer: tracers.mcp,
|
|
@@ -521,7 +528,7 @@ export class ToolsManager {
|
|
|
521
528
|
const startTime = Date.now();
|
|
522
529
|
this.emitToolEvent("tool:start", toolName, {
|
|
523
530
|
input: params,
|
|
524
|
-
...
|
|
531
|
+
...eventIds,
|
|
525
532
|
});
|
|
526
533
|
let result;
|
|
527
534
|
try {
|
|
@@ -533,7 +540,7 @@ export class ToolsManager {
|
|
|
533
540
|
error: errorMsg,
|
|
534
541
|
success: false,
|
|
535
542
|
responseTime: Date.now() - startTime,
|
|
536
|
-
...
|
|
543
|
+
...eventIds,
|
|
537
544
|
});
|
|
538
545
|
throw error;
|
|
539
546
|
}
|
|
@@ -549,7 +556,7 @@ export class ToolsManager {
|
|
|
549
556
|
success: isErrorText === undefined,
|
|
550
557
|
...(isErrorText !== undefined ? { error: isErrorText } : {}),
|
|
551
558
|
responseTime: Date.now() - startTime,
|
|
552
|
-
...
|
|
559
|
+
...eventIds,
|
|
553
560
|
});
|
|
554
561
|
return result;
|
|
555
562
|
});
|
|
@@ -84,6 +84,8 @@ export declare const toPrepareStepRecord: (step: {
|
|
|
84
84
|
export declare const hasNativeDoGenerate: (value: unknown) => value is {
|
|
85
85
|
doGenerate: (options: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
86
86
|
};
|
|
87
|
+
/** A tool's inputSchema with its lazy (function) form, if any, resolved. */
|
|
88
|
+
export declare const resolveToolInputSchema: (schema: Tool["inputSchema"]) => Exclude<Tool["inputSchema"], (...args: never[]) => unknown>;
|
|
87
89
|
/**
|
|
88
90
|
* Spell a JSON Schema into the conversation's system turn.
|
|
89
91
|
*
|
|
@@ -17,6 +17,7 @@ import { observeGeneration } from "../telemetry/observation.js";
|
|
|
17
17
|
import { StepSpans, withGeneration } from "../telemetry/withSpan.js";
|
|
18
18
|
import { logger } from "../utils/logger.js";
|
|
19
19
|
import { guardToolExecutor } from "./toolExecutionGuards.js";
|
|
20
|
+
import { resolveLiveTool } from "../tools/toolDiscovery.js";
|
|
20
21
|
const DEFAULT_TOOL_CHOICE_STEPS = 1;
|
|
21
22
|
/**
|
|
22
23
|
* A choice that compels a tool call: `"required"` or a named tool, in either
|
|
@@ -74,9 +75,9 @@ const warnUndeclaredForcedTool = (toolName, step, source) => {
|
|
|
74
75
|
};
|
|
75
76
|
/**
|
|
76
77
|
* The names this request actually carries, in the record shape
|
|
77
|
-
* `declaredTools` takes.
|
|
78
|
-
*
|
|
79
|
-
*
|
|
78
|
+
* `declaredTools` takes. `args.toolsRecord` can hold more than is declared
|
|
79
|
+
* (discovery hydrates into it), so only the sent declarations answer "is it
|
|
80
|
+
* offered".
|
|
80
81
|
* Null prototype: a tool may be named `__proto__` or `constructor`.
|
|
81
82
|
*/
|
|
82
83
|
const declaredToolRecord = (tools) => {
|
|
@@ -336,6 +337,8 @@ const parseToolInput = (raw) => {
|
|
|
336
337
|
return { input: {}, error: "arguments were not valid JSON" };
|
|
337
338
|
}
|
|
338
339
|
};
|
|
340
|
+
/** A tool's inputSchema with its lazy (function) form, if any, resolved. */
|
|
341
|
+
export const resolveToolInputSchema = (schema) => typeof schema === "function" ? resolveToolInputSchema(schema()) : schema;
|
|
339
342
|
/**
|
|
340
343
|
* Validate parsed input against the tool's own schema when it exposes one.
|
|
341
344
|
*
|
|
@@ -346,8 +349,13 @@ const parseToolInput = (raw) => {
|
|
|
346
349
|
* validator this loop has no business carrying.
|
|
347
350
|
*/
|
|
348
351
|
const validateToolInput = (tool, input) => {
|
|
349
|
-
const schema = tool?.inputSchema
|
|
350
|
-
|
|
352
|
+
const schema = tool?.inputSchema
|
|
353
|
+
? resolveToolInputSchema(tool.inputSchema)
|
|
354
|
+
: undefined;
|
|
355
|
+
if (!schema ||
|
|
356
|
+
typeof schema !== "object" ||
|
|
357
|
+
!("safeParse" in schema) ||
|
|
358
|
+
typeof schema.safeParse !== "function") {
|
|
351
359
|
return undefined;
|
|
352
360
|
}
|
|
353
361
|
const result = schema.safeParse(input);
|
|
@@ -478,11 +486,28 @@ async function runGenerateSteps(args, toolExecutionSummaries, stepSpans) {
|
|
|
478
486
|
// answer is that result — not a thrown turn.
|
|
479
487
|
let preReask;
|
|
480
488
|
const hasTools = Boolean(args.tools && args.tools.length > 0);
|
|
489
|
+
// The declarations actually sent. Starts as the caller's list and grows when
|
|
490
|
+
// a tool joins toolsRecord mid-turn (discovery loads tools into it).
|
|
491
|
+
const liveTools = args.tools ? [...args.tools] : undefined;
|
|
492
|
+
const declaredNames = declaredToolRecord(args.tools);
|
|
481
493
|
// Completed-step records for `prepareStep`; one per step that returned.
|
|
482
494
|
const stepRecords = [];
|
|
483
495
|
for (let step = 0; step < args.maxSteps; step++) {
|
|
484
496
|
steps = step + 1;
|
|
485
497
|
stepSpans.begin(steps);
|
|
498
|
+
if (liveTools && args.declareTool) {
|
|
499
|
+
const loaded = [];
|
|
500
|
+
for (const name of Object.keys(args.toolsRecord)) {
|
|
501
|
+
if (!declaredNames[name]) {
|
|
502
|
+
liveTools.push(args.declareTool(name, args.toolsRecord[name]));
|
|
503
|
+
declaredNames[name] = true;
|
|
504
|
+
loaded.push(name);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (loaded.length > 0) {
|
|
508
|
+
logger.debug(`[nativeGenerateLoop] ${loaded.length} tool(s) hydrated mid-turn via discovery: ${loaded.join(", ")}`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
486
511
|
// The forced-choice window and the caller's hook are resolved BEFORE the
|
|
487
512
|
// re-ask override below: the re-ask has to win, since its whole point is
|
|
488
513
|
// a request with tools disabled.
|
|
@@ -498,7 +523,7 @@ async function runGenerateSteps(args, toolExecutionSummaries, stepSpans) {
|
|
|
498
523
|
steps: stepRecords,
|
|
499
524
|
maxSteps: args.maxSteps,
|
|
500
525
|
model: args.modelId,
|
|
501
|
-
declaredTools: declaredToolRecord(
|
|
526
|
+
declaredTools: declaredToolRecord(liveTools),
|
|
502
527
|
...(args.abortSignal ? { abortSignal: args.abortSignal } : {}),
|
|
503
528
|
})
|
|
504
529
|
: undefined;
|
|
@@ -519,7 +544,7 @@ async function runGenerateSteps(args, toolExecutionSummaries, stepSpans) {
|
|
|
519
544
|
}
|
|
520
545
|
const runThisStep = () => generateObservedStep(args, {
|
|
521
546
|
prompt: args.conversation,
|
|
522
|
-
...(
|
|
547
|
+
...(liveTools && liveTools.length > 0 ? { tools: liveTools } : {}),
|
|
523
548
|
// The v3 call option is an OBJECT — `{ type: "none" }`. Passing the
|
|
524
549
|
// bare string "none" type-checks against `unknown` and is then
|
|
525
550
|
// dropped by every converter that switches on `choice.type`, so the
|
|
@@ -688,7 +713,9 @@ async function runGenerateSteps(args, toolExecutionSummaries, stepSpans) {
|
|
|
688
713
|
const startTime = new Date();
|
|
689
714
|
const parsed = parseToolInput(call.input);
|
|
690
715
|
const input = parsed.input;
|
|
691
|
-
|
|
716
|
+
// resolveLiveTool also loads a deferred tool the model called by its
|
|
717
|
+
// catalog name without searching first.
|
|
718
|
+
const tool = resolveLiveTool(args.toolsRecord, name);
|
|
692
719
|
let output;
|
|
693
720
|
let failure;
|
|
694
721
|
const rejection = parsed.error ??
|
package/dist/neurolink.js
CHANGED
|
@@ -76,6 +76,7 @@ import { SpanSerializer } from "./observability/utils/spanSerializer.js";
|
|
|
76
76
|
import { endSpanWhenDropped, withSpan } from "./telemetry/withSpan.js";
|
|
77
77
|
import { getActiveTraceContext } from "./telemetry/traceContext.js";
|
|
78
78
|
import { calculateCost } from "./utils/pricing.js";
|
|
79
|
+
import { createToolExecutionId } from "./utils/toolExecutionId.js";
|
|
79
80
|
import { acquireTelemetry, ensureTelemetry, flushOpenTelemetry, getLangfuseHealthStatus, isOpenTelemetryInitialized, releaseTelemetry, setLangfuseContext, traceMetadataFromContext, } from "./services/server/ai/observability/instrumentation.js";
|
|
80
81
|
import { clearChecklistState, createChecklistTools, getChecklistState, resolveChecklistSessionId, } from "./agent/taskChecklist.js";
|
|
81
82
|
import { cancelDelegates as cancelBackgroundDelegates, collectDelegates as collectBackgroundDelegates, configureDelegation, createDelegationTools, spawnDelegate as spawnBackgroundDelegate, } from "./agent/backgroundDelegation.js";
|
|
@@ -10599,7 +10600,7 @@ Current user's request: ${currentInput}`;
|
|
|
10599
10600
|
* @returns executionId for tracking this specific execution
|
|
10600
10601
|
*/
|
|
10601
10602
|
emitToolStart(toolName, input, startTime = Date.now()) {
|
|
10602
|
-
const executionId =
|
|
10603
|
+
const executionId = createToolExecutionId(toolName, startTime);
|
|
10603
10604
|
// Create execution context for tracking
|
|
10604
10605
|
const context = {
|
|
10605
10606
|
executionId,
|
|
@@ -36,7 +36,7 @@ import { NoOutputGeneratedError } from "../utils/generationErrors.js";
|
|
|
36
36
|
import { buildNoOutputSentinel, stampNoOutputSpan, } from "../utils/noOutputSentinel.js";
|
|
37
37
|
import { composeAbortSignalsScoped, createTimeoutController, mergeAbortSignals, TimeoutError, } from "../utils/timeout.js";
|
|
38
38
|
import { resolveRequestKind } from "../core/resolveRequestKind.js";
|
|
39
|
-
import { appendJsonSchemaInstruction, hasNativeDoGenerate, resolveStepToolChoice, runNativeGenerateLoop, toPrepareStepRecord, } from "../core/nativeGenerateLoop.js";
|
|
39
|
+
import { appendJsonSchemaInstruction, hasNativeDoGenerate, resolveToolInputSchema, resolveStepToolChoice, runNativeGenerateLoop, toPrepareStepRecord, } from "../core/nativeGenerateLoop.js";
|
|
40
40
|
import { resolveToolExecutionRecords, toolCallsFromSummaries, } from "../core/toolExecutionRecorder.js";
|
|
41
41
|
import { convertZodToJsonSchema } from "../utils/schemaConversion.js";
|
|
42
42
|
import { coerceJsonToSchema, recoverScalarRoot, schemaAccepts, } from "../utils/json/coerce.js";
|
|
@@ -925,18 +925,19 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
925
925
|
? options.tools || (await this.getAllTools())
|
|
926
926
|
: {};
|
|
927
927
|
// v3 tool shape — the same one doGenerate already converts internally.
|
|
928
|
+
// Also handed to the loop, which declares tools discovery loads mid-turn.
|
|
929
|
+
const declareTool = (name, tool) => {
|
|
930
|
+
return {
|
|
931
|
+
type: "function",
|
|
932
|
+
name,
|
|
933
|
+
description: tool.description ?? "",
|
|
934
|
+
inputSchema: tool.inputSchema
|
|
935
|
+
? convertZodToJsonSchema(resolveToolInputSchema(tool.inputSchema))
|
|
936
|
+
: { type: "object", properties: {} },
|
|
937
|
+
};
|
|
938
|
+
};
|
|
928
939
|
const v3Tools = shouldUseTools
|
|
929
|
-
? Object.entries(toolsRecord).map(([name, t]) =>
|
|
930
|
-
const tool = t;
|
|
931
|
-
return {
|
|
932
|
-
type: "function",
|
|
933
|
-
name,
|
|
934
|
-
description: tool.description ?? "",
|
|
935
|
-
inputSchema: (tool.inputSchema
|
|
936
|
-
? convertZodToJsonSchema(tool.inputSchema)
|
|
937
|
-
: { type: "object", properties: {} }),
|
|
938
|
-
};
|
|
939
|
-
})
|
|
940
|
+
? Object.entries(toolsRecord).map(([name, t]) => declareTool(name, t))
|
|
940
941
|
: undefined;
|
|
941
942
|
const hasTools = !!v3Tools && v3Tools.length > 0;
|
|
942
943
|
// Structured output rides response_format, which is what Output.object did
|
|
@@ -964,6 +965,14 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
964
965
|
// model has already given. Dropping them is what makes `response_format`
|
|
965
966
|
// legal again on a vendor that refuses the two together.
|
|
966
967
|
withTools = true) => {
|
|
968
|
+
// What this pass sends: the initial declarations plus any the loop adds
|
|
969
|
+
// for tools discovery loads mid-turn, so the guard counts them too.
|
|
970
|
+
const passTools = withTools && v3Tools ? [...v3Tools] : undefined;
|
|
971
|
+
const declarePassTool = (name, tool) => {
|
|
972
|
+
const declaration = declareTool(name, tool);
|
|
973
|
+
passTools?.push(declaration);
|
|
974
|
+
return declaration;
|
|
975
|
+
};
|
|
967
976
|
return runNativeGenerateLoop({
|
|
968
977
|
doGenerate,
|
|
969
978
|
provider: this.providerName,
|
|
@@ -983,12 +992,14 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
983
992
|
// the estimate past the reclaim threshold and drop older tool
|
|
984
993
|
// exchanges out of a reformat request that would have fit.
|
|
985
994
|
getFixedOverheadTokens: () => estimateTokens(serializeForEstimate({
|
|
986
|
-
...(
|
|
995
|
+
...(passTools ? { tools: passTools } : {}),
|
|
987
996
|
responseFormat: format,
|
|
988
997
|
}), this.providerName),
|
|
989
998
|
}),
|
|
990
999
|
conversation: conv,
|
|
991
|
-
...(
|
|
1000
|
+
...(passTools
|
|
1001
|
+
? { tools: [...passTools], declareTool: declarePassTool }
|
|
1002
|
+
: {}),
|
|
992
1003
|
toolsRecord,
|
|
993
1004
|
...(withTools && hasTools && options.toolChoice
|
|
994
1005
|
? { toolChoice: resolveToolChoice(options, toolsRecord, true) }
|
|
@@ -244,7 +244,8 @@ export function resolveLiveTool(record, name) {
|
|
|
244
244
|
function buildSearchTool(entries, allTools, hotRecord, onHydrate) {
|
|
245
245
|
const catalog = renderCatalog(entries);
|
|
246
246
|
const searchTool = createAISDKTool({
|
|
247
|
-
description: `Search and load additional tools on demand. ${entries.length} more tools are available but not yet loaded
|
|
247
|
+
description: `Search and load additional tools on demand. ${entries.length} more tools are available but not yet loaded. ` +
|
|
248
|
+
`Before telling the user you cannot do something, check this catalog: if a listed tool fits, call it directly by its exact name, or call search_tools with a short description of the task.\n` +
|
|
248
249
|
`${catalog}\n` +
|
|
249
250
|
`Call this with a task description or an exact tool name; matching tools are loaded and become directly callable.`,
|
|
250
251
|
inputSchema: z.object({
|
package/dist/tools/toolPolicy.js
CHANGED
|
@@ -186,8 +186,28 @@ export function resolveToolPolicy(input) {
|
|
|
186
186
|
}
|
|
187
187
|
// --- discovery -----------------------------------------------------------
|
|
188
188
|
const discovery = instanceConfig?.discovery === true;
|
|
189
|
+
let discoveryScope = "external";
|
|
190
|
+
let discoveryAlwaysVisible = [];
|
|
189
191
|
if (discovery) {
|
|
190
192
|
sources.push("tools.discovery");
|
|
191
|
-
|
|
192
|
-
|
|
193
|
+
const configScope = instanceConfig?.discoveryScope;
|
|
194
|
+
if (configScope === "all") {
|
|
195
|
+
discoveryScope = "all";
|
|
196
|
+
}
|
|
197
|
+
else if (configScope !== undefined && configScope !== "external") {
|
|
198
|
+
warnOnce("shape:tools.discoveryScope", `[ToolPolicy] Ignoring unknown tools.discoveryScope; using "external"`, { received: configScope });
|
|
199
|
+
}
|
|
200
|
+
discoveryAlwaysVisible =
|
|
201
|
+
normalizeConfigList(instanceConfig?.discoveryAlwaysVisible, "tools.discoveryAlwaysVisible", false) ?? [];
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
enabled,
|
|
205
|
+
include,
|
|
206
|
+
includeBound,
|
|
207
|
+
exclude,
|
|
208
|
+
discovery,
|
|
209
|
+
discoveryScope,
|
|
210
|
+
discoveryAlwaysVisible,
|
|
211
|
+
sources,
|
|
212
|
+
};
|
|
193
213
|
}
|
package/dist/types/config.d.ts
CHANGED
|
@@ -443,9 +443,40 @@ export type ToolConfig = {
|
|
|
443
443
|
* full. Note: the instance-level `include` list deliberately does NOT
|
|
444
444
|
* force tools hot — it scopes the catalog, and discovery defers within
|
|
445
445
|
* that scope (a scoped-but-large catalog is exactly where deferral pays).
|
|
446
|
+
*
|
|
447
|
+
* What the model and your listeners see once discovery is on: the
|
|
448
|
+
* `search_tools` description ends with an instruction to check the catalog
|
|
449
|
+
* before saying a task cannot be done; each `search_tools` call emits
|
|
450
|
+
* `tool:start` and `tool:end` (paired by `executionId`) and is traced and
|
|
451
|
+
* observed like any other tool call; and the events of every wrapped
|
|
452
|
+
* external tool carry an `executionId`.
|
|
446
453
|
* Default: false.
|
|
447
454
|
*/
|
|
448
455
|
discovery?: boolean;
|
|
456
|
+
/**
|
|
457
|
+
* Which tools `discovery` may defer.
|
|
458
|
+
* - `"external"`: tools of servers attached with `addExternalMCPServer`.
|
|
459
|
+
* - `"all"`: every tool in the request's record except the built-in tools,
|
|
460
|
+
* so tools registered with `registerTool`/`registerTools` and in-process
|
|
461
|
+
* MCP tools are deferred as well. That includes the tools NeuroLink
|
|
462
|
+
* registers for its own features through `registerTool()`, such as
|
|
463
|
+
* `retrieve_context` (memory retrieval), the skills tools, and the tools
|
|
464
|
+
* of `registerTaskTools`, `registerDelegationTools`,
|
|
465
|
+
* `registerBackgroundCommandTools` and `registerGitTools`. Name them in
|
|
466
|
+
* `discoveryAlwaysVisible` to keep them in full.
|
|
467
|
+
* Per-call tools, per-call whitelists, forced `toolChoice` tools and
|
|
468
|
+
* already-discovered tools stay hot in both scopes. Ignored unless
|
|
469
|
+
* `discovery` is true.
|
|
470
|
+
* Default: "external".
|
|
471
|
+
*/
|
|
472
|
+
discoveryScope?: "external" | "all";
|
|
473
|
+
/**
|
|
474
|
+
* Tool-name patterns (exact names or `*` globs) that `discovery` never
|
|
475
|
+
* defers, in either scope. E.g. `["utility-server_*"]` keeps every tool
|
|
476
|
+
* whose name starts with `utility-server_` in full on every request.
|
|
477
|
+
* Ignored unless `discovery` is true.
|
|
478
|
+
*/
|
|
479
|
+
discoveryAlwaysVisible?: string[];
|
|
449
480
|
/** Whether built-in tools should be disabled (equivalent to excluding all direct tools) */
|
|
450
481
|
disableBuiltinTools?: boolean;
|
|
451
482
|
/** Whether custom tools are allowed */
|
package/dist/types/generate.d.ts
CHANGED
|
@@ -1839,7 +1839,15 @@ export type NativeGenerateLoopArgs = {
|
|
|
1839
1839
|
/** Tool declarations in the v3 shape doGenerate already knows how to convert. */
|
|
1840
1840
|
tools?: Array<Record<string, unknown>>;
|
|
1841
1841
|
/** Registered tools, used to execute a call the model asks for. */
|
|
1842
|
-
toolsRecord: Record<string,
|
|
1842
|
+
toolsRecord: Record<string, Tool>;
|
|
1843
|
+
/**
|
|
1844
|
+
* Builds the v3 declaration for one tool. When set, every step declares any
|
|
1845
|
+
* tool that joined `toolsRecord` mid-turn (discovery's search_tools loads
|
|
1846
|
+
* tools into it), so the model can call what it just loaded. Only the
|
|
1847
|
+
* OpenAI-compatible `generate()` path supplies it; the other providers that
|
|
1848
|
+
* run this loop do not declare a tool `search_tools` loaded mid-turn.
|
|
1849
|
+
*/
|
|
1850
|
+
declareTool?: (name: string, tool: Tool) => Record<string, unknown>;
|
|
1843
1851
|
/** The turn's resolved tool choice, applied per step via `resolveStepToolChoice`. */
|
|
1844
1852
|
toolChoice?: unknown;
|
|
1845
1853
|
/** Leading steps a forced `toolChoice` stays in force (default 1). */
|
|
@@ -35,8 +35,12 @@ export type ResolvedToolPolicy = {
|
|
|
35
35
|
includeBound?: string[];
|
|
36
36
|
/** Denylist of tool-name patterns (exact names or `*` globs), applied after include. */
|
|
37
37
|
exclude: string[];
|
|
38
|
-
/** Defer
|
|
38
|
+
/** Defer tool schemas behind the search_tools meta-tool. */
|
|
39
39
|
discovery: boolean;
|
|
40
|
+
/** Which tools discovery may defer. Absent means "external". */
|
|
41
|
+
discoveryScope?: "external" | "all";
|
|
42
|
+
/** Tool-name patterns (exact names or `*` globs) discovery never defers. Absent means none. */
|
|
43
|
+
discoveryAlwaysVisible?: string[];
|
|
40
44
|
/** Which option/config sources contributed to this policy (telemetry/debugging). */
|
|
41
45
|
sources: string[];
|
|
42
46
|
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Builds the id that pairs one tool call's `tool:start` and `tool:end` events.
|
|
3
|
+
* @param toolName {string} - Name of the tool being run.
|
|
4
|
+
* @param startTime {number} - When the call started, in epoch milliseconds.
|
|
5
|
+
* @returns {string} `<toolName>-<startTime>-<nine random base-36 characters>`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createToolExecutionId(toolName: string, startTime?: number): string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Builds the id that pairs one tool call's `tool:start` and `tool:end` events.
|
|
3
|
+
* @param toolName {string} - Name of the tool being run.
|
|
4
|
+
* @param startTime {number} - When the call started, in epoch milliseconds.
|
|
5
|
+
* @returns {string} `<toolName>-<startTime>-<nine random base-36 characters>`.
|
|
6
|
+
*/
|
|
7
|
+
export function createToolExecutionId(toolName, startTime = Date.now()) {
|
|
8
|
+
return `${toolName}-${startTime}-${Math.random().toString(36).slice(2, 11)}`;
|
|
9
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.6.1",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002/call) for routing, tool selection and context budgeting. MCP-native (4 transports), voice TTS/STT/realtime, RAG, agents, memory, compaction, 9 observability exporters. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · TypeSafe Jev and more.",
|
|
6
6
|
"author": {
|