@juspay/neurolink 9.80.4 → 9.81.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 +12 -0
- package/dist/browser/neurolink.min.js +351 -351
- package/dist/cli/loop/optionsSchema.js +16 -0
- package/dist/core/analytics.js +22 -0
- package/dist/core/constants.d.ts +13 -0
- package/dist/core/constants.js +13 -0
- package/dist/lib/core/analytics.js +22 -0
- package/dist/lib/core/constants.d.ts +13 -0
- package/dist/lib/core/constants.js +13 -0
- package/dist/lib/neurolink.js +30 -3
- package/dist/lib/providers/googleNativeGemini3.d.ts +92 -4
- package/dist/lib/providers/googleNativeGemini3.js +186 -4
- package/dist/lib/providers/googleVertex.d.ts +15 -0
- package/dist/lib/providers/googleVertex.js +603 -120
- package/dist/lib/types/analytics.d.ts +10 -0
- package/dist/lib/types/generate.d.ts +75 -0
- package/dist/lib/types/stream.d.ts +21 -1
- package/dist/neurolink.js +30 -3
- package/dist/providers/googleNativeGemini3.d.ts +92 -4
- package/dist/providers/googleNativeGemini3.js +186 -4
- package/dist/providers/googleVertex.d.ts +15 -0
- package/dist/providers/googleVertex.js +603 -120
- package/dist/types/analytics.d.ts +10 -0
- package/dist/types/generate.d.ts +75 -0
- package/dist/types/stream.d.ts +21 -1
- package/package.json +1 -1
|
@@ -49,6 +49,22 @@ export const textGenerationOptionsSchema = {
|
|
|
49
49
|
type: "number",
|
|
50
50
|
description: "Timeout for the generation request in milliseconds.",
|
|
51
51
|
},
|
|
52
|
+
turnTimeoutMs: {
|
|
53
|
+
type: "number",
|
|
54
|
+
description: "Wall-clock cap for the whole agentic turn in milliseconds (all steps + tool executions).",
|
|
55
|
+
},
|
|
56
|
+
stallTimeoutMs: {
|
|
57
|
+
type: "number",
|
|
58
|
+
description: "Maximum time with no progress (no chunk, no tool activity) before the turn ends as stalled, in milliseconds.",
|
|
59
|
+
},
|
|
60
|
+
wrapupTimeLeadMs: {
|
|
61
|
+
type: "number",
|
|
62
|
+
description: "Remaining-turn-time threshold that triggers a wrap-up nudge to the model, in milliseconds.",
|
|
63
|
+
},
|
|
64
|
+
toolTimeoutMs: {
|
|
65
|
+
type: "number",
|
|
66
|
+
description: "Per-tool-execution timeout in milliseconds (default 300000). A timed-out tool fails that step; the turn continues.",
|
|
67
|
+
},
|
|
52
68
|
disableTools: {
|
|
53
69
|
type: "boolean",
|
|
54
70
|
description: "Disable all tool usage for the AI.",
|
package/dist/core/analytics.js
CHANGED
|
@@ -18,6 +18,15 @@ export function createAnalytics(provider, model, result, responseTime, context)
|
|
|
18
18
|
const tokens = extractTokenUsage(result);
|
|
19
19
|
// Estimate cost based on provider and tokens
|
|
20
20
|
const cost = estimateCost(provider, model, tokens);
|
|
21
|
+
// Turn-lifecycle telemetry from native agentic loops (Vertex
|
|
22
|
+
// Gemini/Claude): stopReason / rawFinishReason / stepsUsed ride the
|
|
23
|
+
// provider result; toolCallCount and elapsedMs derive from it.
|
|
24
|
+
const turnResult = result;
|
|
25
|
+
const toolCallCount = Array.isArray(turnResult.toolExecutions)
|
|
26
|
+
? turnResult.toolExecutions.length
|
|
27
|
+
: Array.isArray(turnResult.toolsUsed)
|
|
28
|
+
? turnResult.toolsUsed.length
|
|
29
|
+
: undefined;
|
|
21
30
|
const analytics = {
|
|
22
31
|
provider,
|
|
23
32
|
model,
|
|
@@ -26,6 +35,19 @@ export function createAnalytics(provider, model, result, responseTime, context)
|
|
|
26
35
|
requestDuration: responseTime,
|
|
27
36
|
context: context,
|
|
28
37
|
timestamp: new Date().toISOString(),
|
|
38
|
+
...(typeof turnResult.stepsUsed === "number" && {
|
|
39
|
+
stepsUsed: turnResult.stepsUsed,
|
|
40
|
+
}),
|
|
41
|
+
...(toolCallCount !== undefined && { toolCallCount }),
|
|
42
|
+
...(typeof turnResult.stopReason === "string" && {
|
|
43
|
+
stopReason: turnResult.stopReason,
|
|
44
|
+
}),
|
|
45
|
+
...(typeof turnResult.responseTime === "number" && {
|
|
46
|
+
elapsedMs: turnResult.responseTime,
|
|
47
|
+
}),
|
|
48
|
+
...(typeof turnResult.rawFinishReason === "string" && {
|
|
49
|
+
rawFinishReason: turnResult.rawFinishReason,
|
|
50
|
+
}),
|
|
29
51
|
};
|
|
30
52
|
logger.debug(`[${functionTag}] Analytics created`, {
|
|
31
53
|
provider,
|
package/dist/core/constants.d.ts
CHANGED
|
@@ -23,6 +23,19 @@ export declare const DEFAULT_MAX_STEPS = 200;
|
|
|
23
23
|
export declare const DEFAULT_TOOL_MAX_RETRIES = 2;
|
|
24
24
|
/** Defensive wall-clock ceiling for a native Gemini-3 agentic turn (generate + stream). */
|
|
25
25
|
export declare const DEFAULT_GEMINI_STREAM_TIMEOUT_MS = 300000;
|
|
26
|
+
/**
|
|
27
|
+
* Default per-tool-execution timeout for native agentic loops. A tool that
|
|
28
|
+
* exceeds it fails with an error tool_result and costs one step — the turn
|
|
29
|
+
* continues instead of hanging on a wedged tool. Override per call with
|
|
30
|
+
* `toolTimeoutMs`.
|
|
31
|
+
*/
|
|
32
|
+
export declare const DEFAULT_TOOL_EXECUTION_TIMEOUT_MS = 300000;
|
|
33
|
+
/**
|
|
34
|
+
* Default wrap-up lead applied when `turnTimeoutMs` is set but
|
|
35
|
+
* `wrapupTimeLeadMs` is not: with less than this much turn time remaining,
|
|
36
|
+
* a wrap-up nudge rides the next tool-result turn.
|
|
37
|
+
*/
|
|
38
|
+
export declare const DEFAULT_WRAPUP_TIME_LEAD_MS = 120000;
|
|
26
39
|
export declare const TOOL_STORAGE_TIMEOUT_MS = 5000;
|
|
27
40
|
export declare const STEP_LIMITS: {
|
|
28
41
|
min: number;
|
package/dist/core/constants.js
CHANGED
|
@@ -109,6 +109,19 @@ export const DEFAULT_MAX_STEPS = 200;
|
|
|
109
109
|
export const DEFAULT_TOOL_MAX_RETRIES = 2; // Maximum retries per tool before permanently failing
|
|
110
110
|
/** Defensive wall-clock ceiling for a native Gemini-3 agentic turn (generate + stream). */
|
|
111
111
|
export const DEFAULT_GEMINI_STREAM_TIMEOUT_MS = 300_000;
|
|
112
|
+
/**
|
|
113
|
+
* Default per-tool-execution timeout for native agentic loops. A tool that
|
|
114
|
+
* exceeds it fails with an error tool_result and costs one step — the turn
|
|
115
|
+
* continues instead of hanging on a wedged tool. Override per call with
|
|
116
|
+
* `toolTimeoutMs`.
|
|
117
|
+
*/
|
|
118
|
+
export const DEFAULT_TOOL_EXECUTION_TIMEOUT_MS = 300_000;
|
|
119
|
+
/**
|
|
120
|
+
* Default wrap-up lead applied when `turnTimeoutMs` is set but
|
|
121
|
+
* `wrapupTimeLeadMs` is not: with less than this much turn time remaining,
|
|
122
|
+
* a wrap-up nudge rides the next tool-result turn.
|
|
123
|
+
*/
|
|
124
|
+
export const DEFAULT_WRAPUP_TIME_LEAD_MS = 120_000;
|
|
112
125
|
// Fire-and-forget tool storage writes (Redis). 5s is generous for a single
|
|
113
126
|
// Redis write; if breached, the .catch logs a warning.
|
|
114
127
|
export const TOOL_STORAGE_TIMEOUT_MS = 5000;
|
|
@@ -18,6 +18,15 @@ export function createAnalytics(provider, model, result, responseTime, context)
|
|
|
18
18
|
const tokens = extractTokenUsage(result);
|
|
19
19
|
// Estimate cost based on provider and tokens
|
|
20
20
|
const cost = estimateCost(provider, model, tokens);
|
|
21
|
+
// Turn-lifecycle telemetry from native agentic loops (Vertex
|
|
22
|
+
// Gemini/Claude): stopReason / rawFinishReason / stepsUsed ride the
|
|
23
|
+
// provider result; toolCallCount and elapsedMs derive from it.
|
|
24
|
+
const turnResult = result;
|
|
25
|
+
const toolCallCount = Array.isArray(turnResult.toolExecutions)
|
|
26
|
+
? turnResult.toolExecutions.length
|
|
27
|
+
: Array.isArray(turnResult.toolsUsed)
|
|
28
|
+
? turnResult.toolsUsed.length
|
|
29
|
+
: undefined;
|
|
21
30
|
const analytics = {
|
|
22
31
|
provider,
|
|
23
32
|
model,
|
|
@@ -26,6 +35,19 @@ export function createAnalytics(provider, model, result, responseTime, context)
|
|
|
26
35
|
requestDuration: responseTime,
|
|
27
36
|
context: context,
|
|
28
37
|
timestamp: new Date().toISOString(),
|
|
38
|
+
...(typeof turnResult.stepsUsed === "number" && {
|
|
39
|
+
stepsUsed: turnResult.stepsUsed,
|
|
40
|
+
}),
|
|
41
|
+
...(toolCallCount !== undefined && { toolCallCount }),
|
|
42
|
+
...(typeof turnResult.stopReason === "string" && {
|
|
43
|
+
stopReason: turnResult.stopReason,
|
|
44
|
+
}),
|
|
45
|
+
...(typeof turnResult.responseTime === "number" && {
|
|
46
|
+
elapsedMs: turnResult.responseTime,
|
|
47
|
+
}),
|
|
48
|
+
...(typeof turnResult.rawFinishReason === "string" && {
|
|
49
|
+
rawFinishReason: turnResult.rawFinishReason,
|
|
50
|
+
}),
|
|
29
51
|
};
|
|
30
52
|
logger.debug(`[${functionTag}] Analytics created`, {
|
|
31
53
|
provider,
|
|
@@ -23,6 +23,19 @@ export declare const DEFAULT_MAX_STEPS = 200;
|
|
|
23
23
|
export declare const DEFAULT_TOOL_MAX_RETRIES = 2;
|
|
24
24
|
/** Defensive wall-clock ceiling for a native Gemini-3 agentic turn (generate + stream). */
|
|
25
25
|
export declare const DEFAULT_GEMINI_STREAM_TIMEOUT_MS = 300000;
|
|
26
|
+
/**
|
|
27
|
+
* Default per-tool-execution timeout for native agentic loops. A tool that
|
|
28
|
+
* exceeds it fails with an error tool_result and costs one step — the turn
|
|
29
|
+
* continues instead of hanging on a wedged tool. Override per call with
|
|
30
|
+
* `toolTimeoutMs`.
|
|
31
|
+
*/
|
|
32
|
+
export declare const DEFAULT_TOOL_EXECUTION_TIMEOUT_MS = 300000;
|
|
33
|
+
/**
|
|
34
|
+
* Default wrap-up lead applied when `turnTimeoutMs` is set but
|
|
35
|
+
* `wrapupTimeLeadMs` is not: with less than this much turn time remaining,
|
|
36
|
+
* a wrap-up nudge rides the next tool-result turn.
|
|
37
|
+
*/
|
|
38
|
+
export declare const DEFAULT_WRAPUP_TIME_LEAD_MS = 120000;
|
|
26
39
|
export declare const TOOL_STORAGE_TIMEOUT_MS = 5000;
|
|
27
40
|
export declare const STEP_LIMITS: {
|
|
28
41
|
min: number;
|
|
@@ -109,6 +109,19 @@ export const DEFAULT_MAX_STEPS = 200;
|
|
|
109
109
|
export const DEFAULT_TOOL_MAX_RETRIES = 2; // Maximum retries per tool before permanently failing
|
|
110
110
|
/** Defensive wall-clock ceiling for a native Gemini-3 agentic turn (generate + stream). */
|
|
111
111
|
export const DEFAULT_GEMINI_STREAM_TIMEOUT_MS = 300_000;
|
|
112
|
+
/**
|
|
113
|
+
* Default per-tool-execution timeout for native agentic loops. A tool that
|
|
114
|
+
* exceeds it fails with an error tool_result and costs one step — the turn
|
|
115
|
+
* continues instead of hanging on a wedged tool. Override per call with
|
|
116
|
+
* `toolTimeoutMs`.
|
|
117
|
+
*/
|
|
118
|
+
export const DEFAULT_TOOL_EXECUTION_TIMEOUT_MS = 300_000;
|
|
119
|
+
/**
|
|
120
|
+
* Default wrap-up lead applied when `turnTimeoutMs` is set but
|
|
121
|
+
* `wrapupTimeLeadMs` is not: with less than this much turn time remaining,
|
|
122
|
+
* a wrap-up nudge rides the next tool-result turn.
|
|
123
|
+
*/
|
|
124
|
+
export const DEFAULT_WRAPUP_TIME_LEAD_MS = 120_000;
|
|
112
125
|
// Fire-and-forget tool storage writes (Redis). 5s is generous for a single
|
|
113
126
|
// Redis write; if breached, the .catch logs a warning.
|
|
114
127
|
export const TOOL_STORAGE_TIMEOUT_MS = 5000;
|
package/dist/lib/neurolink.js
CHANGED
|
@@ -3513,6 +3513,10 @@ Current user's request: ${currentInput}`;
|
|
|
3513
3513
|
stt: options.stt,
|
|
3514
3514
|
fileRegistry: this.fileRegistry,
|
|
3515
3515
|
timeout: options.timeout,
|
|
3516
|
+
turnTimeoutMs: options.turnTimeoutMs,
|
|
3517
|
+
stallTimeoutMs: options.stallTimeoutMs,
|
|
3518
|
+
wrapupTimeLeadMs: options.wrapupTimeLeadMs,
|
|
3519
|
+
toolTimeoutMs: options.toolTimeoutMs,
|
|
3516
3520
|
abortSignal: options.abortSignal,
|
|
3517
3521
|
skipToolPromptInjection: options.skipToolPromptInjection,
|
|
3518
3522
|
middleware: options.middleware,
|
|
@@ -3644,6 +3648,12 @@ Current user's request: ${currentInput}`;
|
|
|
3644
3648
|
content: textResult.content,
|
|
3645
3649
|
structuredData: textResult.structuredData,
|
|
3646
3650
|
finishReason: textResult.finishReason,
|
|
3651
|
+
// Turn-exit discriminator + raw provider stop reason + step count from
|
|
3652
|
+
// native agentic loops — this DTO is an explicit field list, so new
|
|
3653
|
+
// provider-result fields MUST be copied here or they silently vanish.
|
|
3654
|
+
stopReason: textResult.stopReason,
|
|
3655
|
+
rawFinishReason: textResult.rawFinishReason,
|
|
3656
|
+
stepsUsed: textResult.stepsUsed,
|
|
3647
3657
|
jsonRepaired: textResult.jsonRepaired,
|
|
3648
3658
|
jsonTruncated: textResult.jsonTruncated,
|
|
3649
3659
|
provider: textResult.provider,
|
|
@@ -5063,6 +5073,9 @@ Current user's request: ${currentInput}`;
|
|
|
5063
5073
|
usage: result.usage,
|
|
5064
5074
|
responseTime,
|
|
5065
5075
|
finishReason: result.finishReason,
|
|
5076
|
+
stopReason: result.stopReason,
|
|
5077
|
+
rawFinishReason: result.rawFinishReason,
|
|
5078
|
+
stepsUsed: result.stepsUsed,
|
|
5066
5079
|
toolsUsed: result.toolsUsed || [],
|
|
5067
5080
|
toolExecutions: transformedToolExecutions,
|
|
5068
5081
|
enhancedWithTools: Boolean(hasToolExecutions),
|
|
@@ -5198,6 +5211,9 @@ Current user's request: ${currentInput}`;
|
|
|
5198
5211
|
usage: poolResult.usage,
|
|
5199
5212
|
responseTime,
|
|
5200
5213
|
finishReason: poolResult.finishReason,
|
|
5214
|
+
stopReason: poolResult.stopReason,
|
|
5215
|
+
rawFinishReason: poolResult.rawFinishReason,
|
|
5216
|
+
stepsUsed: poolResult.stepsUsed,
|
|
5201
5217
|
toolsUsed: poolResult.toolsUsed || [],
|
|
5202
5218
|
toolExecutions: poolResult.toolExecutions?.map((te) => {
|
|
5203
5219
|
const t = te;
|
|
@@ -5474,6 +5490,9 @@ Current user's request: ${currentInput}`;
|
|
|
5474
5490
|
usage: result.usage,
|
|
5475
5491
|
responseTime,
|
|
5476
5492
|
finishReason: result.finishReason,
|
|
5493
|
+
stopReason: result.stopReason,
|
|
5494
|
+
rawFinishReason: result.rawFinishReason,
|
|
5495
|
+
stepsUsed: result.stepsUsed,
|
|
5477
5496
|
toolsUsed: result.toolsUsed || [],
|
|
5478
5497
|
// Map toolExecutions from EnhancedGenerateResult shape ({name,input,output})
|
|
5479
5498
|
// to TextGenerationResult shape ({toolName,executionTime,success}).
|
|
@@ -6471,7 +6490,7 @@ Current user's request: ${currentInput}`;
|
|
|
6471
6490
|
factoryResult,
|
|
6472
6491
|
sessionId: enhancedOptions.context?.sessionId,
|
|
6473
6492
|
});
|
|
6474
|
-
const { stream: mcpStream, provider: providerName, usage: streamUsage, model: streamModel, finishReason: streamFinishReason, toolCalls: streamToolCalls, toolResults: streamToolResults, analytics: streamAnalytics, } = await this.createMCPStream(enhancedOptions);
|
|
6493
|
+
const { stream: mcpStream, provider: providerName, usage: streamUsage, model: streamModel, finishReason: streamFinishReason, toolCalls: streamToolCalls, toolResults: streamToolResults, analytics: streamAnalytics, metadata: providerStreamMetadata, } = await this.createMCPStream(enhancedOptions);
|
|
6475
6494
|
const streamState = {
|
|
6476
6495
|
finishReason: streamFinishReason ?? "stop",
|
|
6477
6496
|
toolCalls: streamToolCalls,
|
|
@@ -6818,6 +6837,7 @@ Current user's request: ${currentInput}`;
|
|
|
6818
6837
|
guardrailsBlocked: metadata.guardrailsBlocked,
|
|
6819
6838
|
error: metadata.error,
|
|
6820
6839
|
events: eventSequence,
|
|
6840
|
+
providerMetadata: providerStreamMetadata,
|
|
6821
6841
|
});
|
|
6822
6842
|
}
|
|
6823
6843
|
catch (error) {
|
|
@@ -7612,6 +7632,7 @@ Current user's request: ${currentInput}`;
|
|
|
7612
7632
|
toolCalls: poolStreamResult.toolCalls ?? [],
|
|
7613
7633
|
toolResults: poolStreamResult.toolResults ?? [],
|
|
7614
7634
|
analytics: poolStreamResult.analytics,
|
|
7635
|
+
metadata: poolStreamResult.metadata,
|
|
7615
7636
|
};
|
|
7616
7637
|
}
|
|
7617
7638
|
catch (poolStreamError) {
|
|
@@ -7665,6 +7686,12 @@ Current user's request: ${currentInput}`;
|
|
|
7665
7686
|
toolCalls: streamResult.toolCalls ?? [],
|
|
7666
7687
|
toolResults: streamResult.toolResults ?? [],
|
|
7667
7688
|
analytics: streamResult.analytics,
|
|
7689
|
+
// Pass the provider's metadata object THROUGH BY REFERENCE: native
|
|
7690
|
+
// background-loop streams (Vertex Gemini/Claude) resolve
|
|
7691
|
+
// finishReason/stopReason/rawFinishReason/stepsUsed onto it only when
|
|
7692
|
+
// the loop finishes — snapshotting fields here would freeze them as
|
|
7693
|
+
// undefined before the stream is drained.
|
|
7694
|
+
metadata: streamResult.metadata,
|
|
7668
7695
|
};
|
|
7669
7696
|
}
|
|
7670
7697
|
/**
|
|
@@ -7707,14 +7734,14 @@ Current user's request: ${currentInput}`;
|
|
|
7707
7734
|
analytics: streamResult.analytics,
|
|
7708
7735
|
evaluation: streamResult.evaluation,
|
|
7709
7736
|
events: config.events && config.events.length > 0 ? config.events : undefined,
|
|
7710
|
-
metadata: {
|
|
7737
|
+
metadata: Object.assign(config.providerMetadata ?? {}, {
|
|
7711
7738
|
streamId: config.streamId,
|
|
7712
7739
|
startTime: config.startTime,
|
|
7713
7740
|
responseTime: config.responseTime,
|
|
7714
7741
|
fallback: config.fallback || false,
|
|
7715
7742
|
guardrailsBlocked: config.guardrailsBlocked,
|
|
7716
7743
|
error: config.error,
|
|
7717
|
-
},
|
|
7744
|
+
}),
|
|
7718
7745
|
};
|
|
7719
7746
|
}
|
|
7720
7747
|
/**
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* This module extracts the functions that are duplicated between the two
|
|
9
9
|
* providers so they can share a single implementation.
|
|
10
10
|
*/
|
|
11
|
-
import type { ThinkingConfig, ChatMessage, CollectedChunkResult, MinimalChatMessage, NativeFunctionCall, NativeFunctionResponse, NativeToolDeclarationsResult, NativeToolsConfig, TextChannel, VertexNativePart, GeminiMultimodalInput } from "../types/index.js";
|
|
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
13
|
export declare function sanitizeForGoogleFunctionName(name: string): string;
|
|
14
14
|
/**
|
|
@@ -109,8 +109,14 @@ export declare function computeMaxSteps(rawMaxSteps?: number): number;
|
|
|
109
109
|
* Returns a plain string (not a `{ unified, raw }` object): the Vertex result
|
|
110
110
|
* builders and the consuming layer (neurolink.ts `finishReason || "unknown"`
|
|
111
111
|
* and `finishReason === "length"`) compare against plain strings.
|
|
112
|
+
*
|
|
113
|
+
* MALFORMED_FUNCTION_CALL / UNEXPECTED_TOOL_CALL map to "error", NOT
|
|
114
|
+
* "tool-calls": they are provider/model failures, while "tool-calls" is the
|
|
115
|
+
* exclusive contract for "step budget exhausted while the model still wanted
|
|
116
|
+
* tools" — consumers (e.g. curator's step-cap intercept) branch on it and
|
|
117
|
+
* were rendering fake step-limit messages for 2-4-step malformed-call turns.
|
|
112
118
|
*/
|
|
113
|
-
export declare function mapGeminiFinishReason(raw: string | null | undefined): "stop" | "length" | "tool-calls" | "content-filter";
|
|
119
|
+
export declare function mapGeminiFinishReason(raw: string | null | undefined): "stop" | "length" | "tool-calls" | "content-filter" | "error";
|
|
114
120
|
/**
|
|
115
121
|
* Append a step's text to a running cross-step accumulator, ignoring empty
|
|
116
122
|
* steps and inserting a single newline between non-empty contributions.
|
|
@@ -215,10 +221,92 @@ export declare function handleMaxStepsTermination(logLabel: string, step: number
|
|
|
215
221
|
export declare function isAbortError(error: unknown): boolean;
|
|
216
222
|
/**
|
|
217
223
|
* Build a graceful, user-facing message for when a single agentic turn hits
|
|
218
|
-
* the step cap
|
|
219
|
-
*
|
|
224
|
+
* the step cap without producing a final answer. Replaces the legacy
|
|
225
|
+
* bracketed "Tool execution limit reached" placeholder.
|
|
226
|
+
*
|
|
227
|
+
* Emit this ONLY when `step >= maxSteps` genuinely terminated the loop —
|
|
228
|
+
* aborted/timed-out/stalled turns must use {@link buildAbortedTurnMessage},
|
|
229
|
+
* {@link buildTurnTimeoutMessage}, or {@link buildTurnStalledMessage}, never
|
|
230
|
+
* this text (a killed 23-step turn once claimed "reached the 200-step limit").
|
|
220
231
|
*/
|
|
221
232
|
export declare function buildToolLoopCapMessage(maxSteps: number, toolCallCount: number): string;
|
|
233
|
+
/**
|
|
234
|
+
* Honest message for a turn ended by the `turnTimeoutMs` wall-clock deadline
|
|
235
|
+
* (stopReason "time-limit"). Sibling of {@link buildToolLoopCapMessage} —
|
|
236
|
+
* time exits must never claim a step limit was reached.
|
|
237
|
+
*/
|
|
238
|
+
export declare function buildTurnTimeoutMessage(elapsedMs: number, toolCallCount: number): string;
|
|
239
|
+
/**
|
|
240
|
+
* Honest message for a turn ended by the `stallTimeoutMs` no-progress
|
|
241
|
+
* watchdog (stopReason "stalled") — typically a wedged tool or a hung
|
|
242
|
+
* model call.
|
|
243
|
+
*/
|
|
244
|
+
export declare function buildTurnStalledMessage(stallTimeoutMs: number, toolCallCount: number): string;
|
|
245
|
+
/**
|
|
246
|
+
* Honest message for a caller-aborted turn (admin kill, coding-task
|
|
247
|
+
* short-circuit — stopReason "aborted").
|
|
248
|
+
*/
|
|
249
|
+
export declare function buildAbortedTurnMessage(toolCallCount: number): string;
|
|
250
|
+
/**
|
|
251
|
+
* Wrap-up nudge injected when the remaining turn time drops inside the
|
|
252
|
+
* `wrapupTimeLeadMs` window — the time-budget twin of the soft step-budget
|
|
253
|
+
* nudge. Rides as a trailing text block on the tool-result user turn.
|
|
254
|
+
*/
|
|
255
|
+
export declare function buildWrapupNudgeText(useFinalResultTool: boolean): string;
|
|
256
|
+
/**
|
|
257
|
+
* Resolve the turn's `stopReason` discriminator from the loop's exit
|
|
258
|
+
* bookkeeping. Precedence: time conditions beat the generic abort flag
|
|
259
|
+
* (the deadline/stall watchdogs abort through the same internal controller),
|
|
260
|
+
* abort beats step-cap, and a provider-error finishReason (e.g. persistent
|
|
261
|
+
* MALFORMED_FUNCTION_CALL) beats "completed".
|
|
262
|
+
*/
|
|
263
|
+
export declare function resolveTurnStopReason(params: {
|
|
264
|
+
timedOut: boolean;
|
|
265
|
+
stalled: boolean;
|
|
266
|
+
wasAborted: boolean;
|
|
267
|
+
/** Step budget ran out AND no clean/forced answer was produced. */
|
|
268
|
+
cappedWithoutAnswer: boolean;
|
|
269
|
+
/** The turn's resolved unified finishReason. */
|
|
270
|
+
finishReason?: string;
|
|
271
|
+
}): GenerateStopReason;
|
|
272
|
+
/**
|
|
273
|
+
* Wall-clock + progress watchdogs for a native agentic turn.
|
|
274
|
+
*
|
|
275
|
+
* - Deadline: `turnTimeoutMs` (caller knob) or `defaultTurnTimeoutMs` (the
|
|
276
|
+
* loop's pre-existing defensive bound) arms a whole-turn timer.
|
|
277
|
+
* - Stall: when `stallTimeoutMs` is set, a low-frequency interval checks the
|
|
278
|
+
* time since the last recorded progress (chunk received, tool started or
|
|
279
|
+
* finished, step started).
|
|
280
|
+
*
|
|
281
|
+
* Both fire `onDeadline(kind)` exactly once each and latch a flag the loop's
|
|
282
|
+
* terminal handling reads to pick the honest exit message and `stopReason`.
|
|
283
|
+
* Timers are unref'd so they never hold the process open; call `dispose()`
|
|
284
|
+
* in the loop's finally.
|
|
285
|
+
*/
|
|
286
|
+
export declare function createTurnClock(params: {
|
|
287
|
+
/** Explicit whole-turn deadline (ms); wins over defaultTurnTimeoutMs. */
|
|
288
|
+
turnTimeoutMs?: number;
|
|
289
|
+
/** Loop-specific defensive default when turnTimeoutMs is unset (undefined = no deadline). */
|
|
290
|
+
defaultTurnTimeoutMs?: number;
|
|
291
|
+
/** No-progress watchdog (ms); undefined = disabled. */
|
|
292
|
+
stallTimeoutMs?: number;
|
|
293
|
+
/** Wrap-up lead (ms); only honored when turnTimeoutMs is explicitly set. */
|
|
294
|
+
wrapupTimeLeadMs?: number;
|
|
295
|
+
onDeadline: (kind: "timeout" | "stall") => void;
|
|
296
|
+
}): {
|
|
297
|
+
readonly timedOut: boolean;
|
|
298
|
+
readonly stalled: boolean;
|
|
299
|
+
/** True when the turn ended on a time condition (deadline or stall). */
|
|
300
|
+
readonly expired: boolean;
|
|
301
|
+
/** The effective whole-turn deadline in ms (explicit or defensive default). */
|
|
302
|
+
readonly turnTimeoutMs: number | undefined;
|
|
303
|
+
elapsedMs(): number;
|
|
304
|
+
/** Record progress: chunk received, tool started/finished, step started. */
|
|
305
|
+
noteProgress(): void;
|
|
306
|
+
/** True when an explicit deadline exists and remaining time is inside the wrap-up lead. */
|
|
307
|
+
shouldNudgeWrapup(): boolean;
|
|
308
|
+
dispose(): void;
|
|
309
|
+
};
|
|
222
310
|
/**
|
|
223
311
|
* Push model response parts to conversation history, preserving thoughtSignature
|
|
224
312
|
* for Gemini 3 multi-turn tool calling.
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { randomUUID } from "node:crypto";
|
|
12
12
|
import { existsSync, readFileSync } from "node:fs";
|
|
13
13
|
import { extname } from "node:path";
|
|
14
|
-
import { DEFAULT_MAX_STEPS, DEFAULT_TOOL_MAX_RETRIES, } from "../core/constants.js";
|
|
14
|
+
import { DEFAULT_MAX_STEPS, DEFAULT_TOOL_MAX_RETRIES, DEFAULT_WRAPUP_TIME_LEAD_MS, } from "../core/constants.js";
|
|
15
15
|
import { logger } from "../utils/logger.js";
|
|
16
16
|
import { convertZodToJsonSchema, ensureNestedSchemaTypes, inlineJsonSchema, isZodSchema, normalizeJsonSchemaObject, } from "../utils/schemaConversion.js";
|
|
17
17
|
import { createNativeThinkingConfig } from "../utils/thinkingConfig.js";
|
|
@@ -463,6 +463,12 @@ export function computeMaxSteps(rawMaxSteps) {
|
|
|
463
463
|
* Returns a plain string (not a `{ unified, raw }` object): the Vertex result
|
|
464
464
|
* builders and the consuming layer (neurolink.ts `finishReason || "unknown"`
|
|
465
465
|
* and `finishReason === "length"`) compare against plain strings.
|
|
466
|
+
*
|
|
467
|
+
* MALFORMED_FUNCTION_CALL / UNEXPECTED_TOOL_CALL map to "error", NOT
|
|
468
|
+
* "tool-calls": they are provider/model failures, while "tool-calls" is the
|
|
469
|
+
* exclusive contract for "step budget exhausted while the model still wanted
|
|
470
|
+
* tools" — consumers (e.g. curator's step-cap intercept) branch on it and
|
|
471
|
+
* were rendering fake step-limit messages for 2-4-step malformed-call turns.
|
|
466
472
|
*/
|
|
467
473
|
export function mapGeminiFinishReason(raw) {
|
|
468
474
|
switch (raw) {
|
|
@@ -470,7 +476,7 @@ export function mapGeminiFinishReason(raw) {
|
|
|
470
476
|
return "length";
|
|
471
477
|
case "MALFORMED_FUNCTION_CALL":
|
|
472
478
|
case "UNEXPECTED_TOOL_CALL":
|
|
473
|
-
return "
|
|
479
|
+
return "error";
|
|
474
480
|
case "SAFETY":
|
|
475
481
|
case "RECITATION":
|
|
476
482
|
case "BLOCKLIST":
|
|
@@ -832,8 +838,13 @@ export function isAbortError(error) {
|
|
|
832
838
|
}
|
|
833
839
|
/**
|
|
834
840
|
* Build a graceful, user-facing message for when a single agentic turn hits
|
|
835
|
-
* the step cap
|
|
836
|
-
*
|
|
841
|
+
* the step cap without producing a final answer. Replaces the legacy
|
|
842
|
+
* bracketed "Tool execution limit reached" placeholder.
|
|
843
|
+
*
|
|
844
|
+
* Emit this ONLY when `step >= maxSteps` genuinely terminated the loop —
|
|
845
|
+
* aborted/timed-out/stalled turns must use {@link buildAbortedTurnMessage},
|
|
846
|
+
* {@link buildTurnTimeoutMessage}, or {@link buildTurnStalledMessage}, never
|
|
847
|
+
* this text (a killed 23-step turn once claimed "reached the 200-step limit").
|
|
837
848
|
*/
|
|
838
849
|
export function buildToolLoopCapMessage(maxSteps, toolCallCount) {
|
|
839
850
|
const calls = toolCallCount > 0
|
|
@@ -842,6 +853,177 @@ export function buildToolLoopCapMessage(maxSteps, toolCallCount) {
|
|
|
842
853
|
return (`${calls}reached the ${maxSteps}-step limit for a single turn before I could finish. ` +
|
|
843
854
|
`Please narrow the request or break it into smaller asks and I'll continue.`);
|
|
844
855
|
}
|
|
856
|
+
/** Format an elapsed duration as "Xm Ys" (or "Ys" under a minute). */
|
|
857
|
+
function formatElapsed(elapsedMs) {
|
|
858
|
+
const totalSeconds = Math.max(0, Math.round(elapsedMs / 1000));
|
|
859
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
860
|
+
const seconds = totalSeconds % 60;
|
|
861
|
+
return minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Honest message for a turn ended by the `turnTimeoutMs` wall-clock deadline
|
|
865
|
+
* (stopReason "time-limit"). Sibling of {@link buildToolLoopCapMessage} —
|
|
866
|
+
* time exits must never claim a step limit was reached.
|
|
867
|
+
*/
|
|
868
|
+
export function buildTurnTimeoutMessage(elapsedMs, toolCallCount) {
|
|
869
|
+
const calls = toolCallCount > 0
|
|
870
|
+
? ` I completed ${toolCallCount} tool call${toolCallCount === 1 ? "" : "s"} before stopping;`
|
|
871
|
+
: "";
|
|
872
|
+
return (`I had to stop after ${formatElapsed(elapsedMs)} — this turn hit its ` +
|
|
873
|
+
`processing time limit.${calls} ask me to continue and I'll pick up from there.`);
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Honest message for a turn ended by the `stallTimeoutMs` no-progress
|
|
877
|
+
* watchdog (stopReason "stalled") — typically a wedged tool or a hung
|
|
878
|
+
* model call.
|
|
879
|
+
*/
|
|
880
|
+
export function buildTurnStalledMessage(stallTimeoutMs, toolCallCount) {
|
|
881
|
+
const calls = toolCallCount > 0
|
|
882
|
+
? ` I completed ${toolCallCount} tool call${toolCallCount === 1 ? "" : "s"} before stopping;`
|
|
883
|
+
: "";
|
|
884
|
+
return (`I had to stop because this turn made no progress for ` +
|
|
885
|
+
`${formatElapsed(stallTimeoutMs)} — a tool or model call appears to be stuck.` +
|
|
886
|
+
`${calls} ask me to continue and I'll pick up from there.`);
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Honest message for a caller-aborted turn (admin kill, coding-task
|
|
890
|
+
* short-circuit — stopReason "aborted").
|
|
891
|
+
*/
|
|
892
|
+
export function buildAbortedTurnMessage(toolCallCount) {
|
|
893
|
+
const calls = toolCallCount > 0
|
|
894
|
+
? ` I completed ${toolCallCount} tool call${toolCallCount === 1 ? "" : "s"} before stopping.`
|
|
895
|
+
: "";
|
|
896
|
+
return `This turn was stopped before I could finish.${calls}`;
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Wrap-up nudge injected when the remaining turn time drops inside the
|
|
900
|
+
* `wrapupTimeLeadMs` window — the time-budget twin of the soft step-budget
|
|
901
|
+
* nudge. Rides as a trailing text block on the tool-result user turn.
|
|
902
|
+
*/
|
|
903
|
+
export function buildWrapupNudgeText(useFinalResultTool) {
|
|
904
|
+
return ("NOTE: processing time for this turn is nearly up. Consolidate what you have and " +
|
|
905
|
+
(useFinalResultTool
|
|
906
|
+
? "call final_result with your best answer now."
|
|
907
|
+
: "provide your final answer now."));
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Resolve the turn's `stopReason` discriminator from the loop's exit
|
|
911
|
+
* bookkeeping. Precedence: time conditions beat the generic abort flag
|
|
912
|
+
* (the deadline/stall watchdogs abort through the same internal controller),
|
|
913
|
+
* abort beats step-cap, and a provider-error finishReason (e.g. persistent
|
|
914
|
+
* MALFORMED_FUNCTION_CALL) beats "completed".
|
|
915
|
+
*/
|
|
916
|
+
export function resolveTurnStopReason(params) {
|
|
917
|
+
if (params.timedOut) {
|
|
918
|
+
return "time-limit";
|
|
919
|
+
}
|
|
920
|
+
if (params.stalled) {
|
|
921
|
+
return "stalled";
|
|
922
|
+
}
|
|
923
|
+
if (params.wasAborted) {
|
|
924
|
+
return "aborted";
|
|
925
|
+
}
|
|
926
|
+
if (params.cappedWithoutAnswer) {
|
|
927
|
+
return "step-cap";
|
|
928
|
+
}
|
|
929
|
+
if (params.finishReason === "error") {
|
|
930
|
+
return "provider-error";
|
|
931
|
+
}
|
|
932
|
+
return "completed";
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* Wall-clock + progress watchdogs for a native agentic turn.
|
|
936
|
+
*
|
|
937
|
+
* - Deadline: `turnTimeoutMs` (caller knob) or `defaultTurnTimeoutMs` (the
|
|
938
|
+
* loop's pre-existing defensive bound) arms a whole-turn timer.
|
|
939
|
+
* - Stall: when `stallTimeoutMs` is set, a low-frequency interval checks the
|
|
940
|
+
* time since the last recorded progress (chunk received, tool started or
|
|
941
|
+
* finished, step started).
|
|
942
|
+
*
|
|
943
|
+
* Both fire `onDeadline(kind)` exactly once each and latch a flag the loop's
|
|
944
|
+
* terminal handling reads to pick the honest exit message and `stopReason`.
|
|
945
|
+
* Timers are unref'd so they never hold the process open; call `dispose()`
|
|
946
|
+
* in the loop's finally.
|
|
947
|
+
*/
|
|
948
|
+
export function createTurnClock(params) {
|
|
949
|
+
const startedAt = Date.now();
|
|
950
|
+
const isValidMs = (value) => value !== undefined && Number.isFinite(value) && value > 0;
|
|
951
|
+
const effectiveTurnTimeoutMs = isValidMs(params.turnTimeoutMs)
|
|
952
|
+
? params.turnTimeoutMs
|
|
953
|
+
: isValidMs(params.defaultTurnTimeoutMs)
|
|
954
|
+
? params.defaultTurnTimeoutMs
|
|
955
|
+
: undefined;
|
|
956
|
+
// Nudging against the loop's defensive default would inject prompt text
|
|
957
|
+
// into turns whose caller never opted into a time budget — only nudge
|
|
958
|
+
// against an explicit turnTimeoutMs.
|
|
959
|
+
const wrapupLeadMs = isValidMs(params.turnTimeoutMs)
|
|
960
|
+
? (params.wrapupTimeLeadMs ?? DEFAULT_WRAPUP_TIME_LEAD_MS)
|
|
961
|
+
: undefined;
|
|
962
|
+
let timedOut = false;
|
|
963
|
+
let stalled = false;
|
|
964
|
+
let lastProgressAt = startedAt;
|
|
965
|
+
let deadlineTimer;
|
|
966
|
+
let stallTimer;
|
|
967
|
+
if (effectiveTurnTimeoutMs !== undefined) {
|
|
968
|
+
deadlineTimer = setTimeout(() => {
|
|
969
|
+
timedOut = true;
|
|
970
|
+
params.onDeadline("timeout");
|
|
971
|
+
}, effectiveTurnTimeoutMs);
|
|
972
|
+
deadlineTimer.unref?.();
|
|
973
|
+
}
|
|
974
|
+
if (isValidMs(params.stallTimeoutMs)) {
|
|
975
|
+
const stallTimeoutMs = params.stallTimeoutMs;
|
|
976
|
+
const checkEveryMs = Math.min(Math.max(1000, Math.floor(stallTimeoutMs / 4)), 15_000);
|
|
977
|
+
stallTimer = setInterval(() => {
|
|
978
|
+
if (!stalled &&
|
|
979
|
+
!timedOut &&
|
|
980
|
+
Date.now() - lastProgressAt >= stallTimeoutMs) {
|
|
981
|
+
stalled = true;
|
|
982
|
+
params.onDeadline("stall");
|
|
983
|
+
}
|
|
984
|
+
}, checkEveryMs);
|
|
985
|
+
stallTimer.unref?.();
|
|
986
|
+
}
|
|
987
|
+
return {
|
|
988
|
+
get timedOut() {
|
|
989
|
+
return timedOut;
|
|
990
|
+
},
|
|
991
|
+
get stalled() {
|
|
992
|
+
return stalled;
|
|
993
|
+
},
|
|
994
|
+
/** True when the turn ended on a time condition (deadline or stall). */
|
|
995
|
+
get expired() {
|
|
996
|
+
return timedOut || stalled;
|
|
997
|
+
},
|
|
998
|
+
/** The effective whole-turn deadline in ms (explicit or defensive default). */
|
|
999
|
+
get turnTimeoutMs() {
|
|
1000
|
+
return effectiveTurnTimeoutMs;
|
|
1001
|
+
},
|
|
1002
|
+
elapsedMs() {
|
|
1003
|
+
return Date.now() - startedAt;
|
|
1004
|
+
},
|
|
1005
|
+
/** Record progress: chunk received, tool started/finished, step started. */
|
|
1006
|
+
noteProgress() {
|
|
1007
|
+
lastProgressAt = Date.now();
|
|
1008
|
+
},
|
|
1009
|
+
/** True when an explicit deadline exists and remaining time is inside the wrap-up lead. */
|
|
1010
|
+
shouldNudgeWrapup() {
|
|
1011
|
+
if (effectiveTurnTimeoutMs === undefined || wrapupLeadMs === undefined) {
|
|
1012
|
+
return false;
|
|
1013
|
+
}
|
|
1014
|
+
const remaining = effectiveTurnTimeoutMs - (Date.now() - startedAt);
|
|
1015
|
+
return remaining > 0 && remaining <= wrapupLeadMs;
|
|
1016
|
+
},
|
|
1017
|
+
dispose() {
|
|
1018
|
+
if (deadlineTimer) {
|
|
1019
|
+
clearTimeout(deadlineTimer);
|
|
1020
|
+
}
|
|
1021
|
+
if (stallTimer) {
|
|
1022
|
+
clearInterval(stallTimer);
|
|
1023
|
+
}
|
|
1024
|
+
},
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
845
1027
|
/**
|
|
846
1028
|
* Push model response parts to conversation history, preserving thoughtSignature
|
|
847
1029
|
* for Gemini 3 multi-turn tool calling.
|
|
@@ -256,6 +256,21 @@ export declare class GoogleVertexProvider extends BaseProvider {
|
|
|
256
256
|
* exporters. Mirrors the Bedrock + Ollama pattern.
|
|
257
257
|
*/
|
|
258
258
|
private emitGenerationEnd;
|
|
259
|
+
/**
|
|
260
|
+
* Emit a `turn:lifecycle` event on the SDK emitter (alongside
|
|
261
|
+
* tool:start/tool:end) so loop conditions that previously only reached
|
|
262
|
+
* process logs — step-cap, time-limit, stall, abort, tool timeouts,
|
|
263
|
+
* malformed-call retries — are observable by consumers' own pipelines.
|
|
264
|
+
*/
|
|
265
|
+
private emitTurnEvent;
|
|
266
|
+
/**
|
|
267
|
+
* Pick the honest terminal message for a native-loop exit by its ACTUAL
|
|
268
|
+
* cause. The step-cap text is the fallback for genuine budget exhaustion
|
|
269
|
+
* only — time/stall/abort exits must never claim a step limit was reached
|
|
270
|
+
* (the 2026-07-03 "reached the 200-step limit" incident was a wall-clock
|
|
271
|
+
* abort wearing the cap message).
|
|
272
|
+
*/
|
|
273
|
+
private buildLoopExitMessage;
|
|
259
274
|
protected formatProviderError(error: unknown): Error;
|
|
260
275
|
/**
|
|
261
276
|
* Memory-safe cache management for model configurations
|