@juspay/neurolink 12.47.14 → 12.48.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/auth/middleware/AuthMiddleware.d.ts +1 -1
- package/dist/browser/neurolink.min.js +578 -565
- package/dist/cli/factories/commandFactory.d.ts +7 -0
- package/dist/cli/factories/commandFactory.js +26 -0
- package/dist/cli/loop/optionsSchema.js +9 -0
- package/dist/context/budgetChecker.js +54 -2
- package/dist/context/contextDecision.js +2 -0
- package/dist/context/toolPairRepair.d.ts +12 -0
- package/dist/context/toolPairRepair.js +29 -3
- package/dist/core/baseProvider.d.ts +92 -3
- package/dist/core/baseProvider.js +254 -10
- package/dist/core/conversationMemoryManager.js +17 -2
- package/dist/core/loopEngine.js +1 -1
- package/dist/core/modules/MessageBuilder.js +11 -3
- package/dist/core/modules/TelemetryHandler.d.ts +2 -2
- package/dist/core/modules/ToolsManager.d.ts +35 -0
- package/dist/core/modules/ToolsManager.js +159 -55
- package/dist/core/nativeGenerateLoop.d.ts +58 -1
- package/dist/core/nativeGenerateLoop.js +277 -3
- package/dist/core/redisConversationMemoryManager.js +10 -3
- package/dist/core/resolveRequestKind.d.ts +24 -4
- package/dist/core/resolveRequestKind.js +31 -4
- package/dist/core/toolExecutionRecorder.d.ts +6 -0
- package/dist/core/toolExecutionRecorder.js +24 -2
- package/dist/core/toolRoutingDecision.js +1 -0
- package/dist/factories/mediaHandlerCatalog.js +5 -0
- package/dist/factories/providerDescriptors.js +15 -0
- package/dist/files/fileTools.d.ts +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +9 -2
- package/dist/memory/condensationPrompt.d.ts +8 -0
- package/dist/memory/condensationPrompt.js +29 -0
- package/dist/memory/hippocampusInitializer.d.ts +14 -2
- package/dist/memory/hippocampusInitializer.js +64 -3
- package/dist/neurolink.d.ts +252 -3
- package/dist/neurolink.js +898 -43
- package/dist/providers/amazonBedrock/client.js +6 -0
- package/dist/providers/amazonSagemaker.js +14 -1
- package/dist/providers/anthropic/client.js +148 -19
- package/dist/providers/anthropic/loopAdapter.js +2 -2
- package/dist/providers/googleAiStudio/client.js +2 -1
- package/dist/providers/openaiChatCompletionsBase.js +103 -23
- package/dist/providers/systemOneDecision.d.ts +6 -0
- package/dist/providers/systemOneDecision.js +14 -63
- package/dist/providers/typesafe.d.ts +6 -0
- package/dist/providers/typesafe.js +6 -0
- package/dist/rag/retrieval/searchDecision.js +1 -0
- package/dist/routing/classifierRouter.js +6 -5
- package/dist/routing/classifierStrategies.js +8 -1
- package/dist/types/classifierRouter.d.ts +16 -3
- package/dist/types/common.d.ts +2 -0
- package/dist/types/config.d.ts +9 -0
- package/dist/types/context.d.ts +8 -1
- package/dist/types/conversation.d.ts +87 -0
- package/dist/types/decision.d.ts +210 -1
- package/dist/types/decision.js +9 -1
- package/dist/types/dispatch.d.ts +11 -4
- package/dist/types/generate.d.ts +118 -54
- package/dist/types/loopEngine.d.ts +7 -2
- package/dist/types/memory.d.ts +177 -9
- package/dist/types/openaiCompatible.d.ts +11 -3
- package/dist/types/stream.d.ts +13 -16
- package/dist/types/stt.d.ts +58 -1
- package/dist/types/tools.d.ts +14 -0
- package/dist/types/tools.js +8 -0
- package/dist/types/tts.d.ts +108 -0
- package/dist/types/voice.d.ts +41 -4
- package/dist/utils/decisionAnswers.d.ts +31 -1
- package/dist/utils/decisionAnswers.js +114 -0
- package/dist/utils/decisionLimits.d.ts +39 -0
- package/dist/utils/decisionLimits.js +116 -0
- package/dist/utils/logger.d.ts +7 -0
- package/dist/utils/logger.js +9 -0
- package/dist/utils/messageBuilder.d.ts +45 -1
- package/dist/utils/messageBuilder.js +393 -16
- package/dist/utils/multimodalOptionsBuilder.d.ts +1 -0
- package/dist/utils/multimodalOptionsBuilder.js +1 -0
- package/dist/utils/speechText.d.ts +41 -0
- package/dist/utils/speechText.js +364 -0
- package/dist/utils/toolResultStatus.d.ts +26 -0
- package/dist/utils/toolResultStatus.js +73 -0
- package/dist/utils/ttsProcessor.d.ts +29 -0
- package/dist/utils/ttsProcessor.js +84 -2
- package/dist/voice/index.d.ts +1 -0
- package/dist/voice/index.js +3 -0
- package/dist/voice/providers/ElevenLabsSTT.d.ts +42 -0
- package/dist/voice/providers/ElevenLabsSTT.js +351 -0
- package/dist/voice/providers/ElevenLabsTTS.d.ts +81 -1
- package/dist/voice/providers/ElevenLabsTTS.js +415 -74
- package/docs-site/static/search-index.json +48 -33
- package/package.json +5 -2
- package/dist/utils/toolEndEmitter.d.ts +0 -25
- package/dist/utils/toolEndEmitter.js +0 -64
|
@@ -210,6 +210,10 @@ export class AmazonBedrockProvider extends BaseProvider {
|
|
|
210
210
|
});
|
|
211
211
|
// Cast options to StreamOptions for multimodal processing
|
|
212
212
|
const streamOptions = options;
|
|
213
|
+
// This provider bypasses BaseProvider's message builders, so the
|
|
214
|
+
// instance-level `conversationMemory.replayToolSteps` has to be folded
|
|
215
|
+
// in here or only the per-request value would ever apply.
|
|
216
|
+
this.applyToolReplayDefault(streamOptions);
|
|
213
217
|
const multimodalOptions = buildMultimodalOptions(streamOptions, this.providerName, this.modelName);
|
|
214
218
|
const multimodalMessages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
|
|
215
219
|
// Convert to Bedrock format
|
|
@@ -923,6 +927,8 @@ export class AmazonBedrockProvider extends BaseProvider {
|
|
|
923
927
|
hasPDFFiles: !!options.input?.pdfFiles?.length,
|
|
924
928
|
pdfFileCount: options.input?.pdfFiles?.length || 0,
|
|
925
929
|
});
|
|
930
|
+
// Same instance-default fold as the generate() path above.
|
|
931
|
+
this.applyToolReplayDefault(options);
|
|
926
932
|
const multimodalOptions = buildMultimodalOptions(options, this.providerName, this.modelName);
|
|
927
933
|
const multimodalMessages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
|
|
928
934
|
// Convert to Bedrock format
|
|
@@ -6,6 +6,7 @@ import { resolveToolExecutionRecords, toolCallsFromSummaries, } from "../core/to
|
|
|
6
6
|
import { transformToolExecutions } from "../utils/transformationUtils.js";
|
|
7
7
|
import { convertZodToJsonSchema } from "../utils/schemaConversion.js";
|
|
8
8
|
import { withProviderRetry } from "../utils/providerRetry.js";
|
|
9
|
+
import { resolveToolChoice } from "../utils/toolChoice.js";
|
|
9
10
|
import { DEFAULT_MAX_STEPS } from "../core/constants.js";
|
|
10
11
|
import { hasNativeDoGenerate, runNativeGenerateLoop, } from "../core/nativeGenerateLoop.js";
|
|
11
12
|
import { withSpan } from "../telemetry/withSpan.js";
|
|
@@ -233,6 +234,17 @@ export class AmazonSageMakerProvider extends BaseProvider {
|
|
|
233
234
|
...(responseFormat ? { responseFormat } : {}),
|
|
234
235
|
...(v3Tools.length > 0 ? { tools: v3Tools } : {}),
|
|
235
236
|
toolsRecord,
|
|
237
|
+
// Was never forwarded here, so a caller's toolChoice degraded to the
|
|
238
|
+
// endpoint's default on SageMaker alone; the language model already
|
|
239
|
+
// maps it onto `tool_choice`.
|
|
240
|
+
...(v3Tools.length > 0 && options.toolChoice
|
|
241
|
+
? { toolChoice: resolveToolChoice(options, toolsRecord, true) }
|
|
242
|
+
: {}),
|
|
243
|
+
...(options.toolChoiceSteps !== undefined
|
|
244
|
+
? { toolChoiceSteps: options.toolChoiceSteps }
|
|
245
|
+
: {}),
|
|
246
|
+
...(options.prepareStep ? { prepareStep: options.prepareStep } : {}),
|
|
247
|
+
modelId: this.modelName,
|
|
236
248
|
maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
|
|
237
249
|
...(options.maxTokens ? { maxOutputTokens: options.maxTokens } : {}),
|
|
238
250
|
...(options.temperature !== undefined
|
|
@@ -245,6 +257,7 @@ export class AmazonSageMakerProvider extends BaseProvider {
|
|
|
245
257
|
runStep: (call) => withProviderRetry(call, undefined, "sagemaker generate", undefined, options.abortSignal).catch((err) => {
|
|
246
258
|
throw this.handleProviderError(err);
|
|
247
259
|
}),
|
|
260
|
+
onRejectedToolCall: (name, error, id) => this.emitRejectedToolCall(name, error, id),
|
|
248
261
|
}, toolExecutionSummaries);
|
|
249
262
|
const enhanced = {
|
|
250
263
|
content: loop.text,
|
|
@@ -263,7 +276,7 @@ export class AmazonSageMakerProvider extends BaseProvider {
|
|
|
263
276
|
toolExecutions: resolveToolExecutionRecords(options, transformToolExecutions(toolExecutionSummaries)),
|
|
264
277
|
enhancedWithTools: loop.toolsUsed.length > 0,
|
|
265
278
|
};
|
|
266
|
-
return this.finalizeNativeGenerate(enhanced, options, startTime);
|
|
279
|
+
return this.finalizeNativeGenerate(enhanced, options, startTime, toolExecutionSummaries);
|
|
267
280
|
}
|
|
268
281
|
/**
|
|
269
282
|
* Streaming was previously an `executeStream` override that unconditionally
|
|
@@ -26,7 +26,7 @@ import { stringifyAnthropicToolOutput } from "./toolOutput.js";
|
|
|
26
26
|
import { createAnthropicLoopAdapter } from "./loopAdapter.js";
|
|
27
27
|
import { DEFAULT_BEFORE_STEP_TIMEOUT_MS } from "../../utils/parameterValidation.js";
|
|
28
28
|
import { runAgenticLoop } from "../../core/loopEngine.js";
|
|
29
|
-
import { hasNativeDoGenerate, runNativeGenerateLoop, } from "../../core/nativeGenerateLoop.js";
|
|
29
|
+
import { hasNativeDoGenerate, resolveStepToolChoice, runNativeGenerateLoop, toPrepareStepRecord, } from "../../core/nativeGenerateLoop.js";
|
|
30
30
|
import { withProviderRetry } from "../../utils/providerRetry.js";
|
|
31
31
|
import { resolveRequestKind } from "../../core/resolveRequestKind.js";
|
|
32
32
|
import { resolveToolExecutionRecords, toolCallsFromSummaries, } from "../../core/toolExecutionRecorder.js";
|
|
@@ -35,7 +35,6 @@ import { createAnthropicConfig, getProviderModel, validateApiKey, } from "../../
|
|
|
35
35
|
import { composeAbortSignals, composeAbortSignalsScoped, createTimeoutController, mergeAbortSignals, TimeoutError, } from "../../utils/timeout.js";
|
|
36
36
|
import { raceWithAbort } from "../../utils/async/index.js";
|
|
37
37
|
import { normalizeResolvedToolChoice, resolveToolChoice, } from "../../utils/toolChoice.js";
|
|
38
|
-
import { emitToolEndFromStepFinish } from "../../utils/toolEndEmitter.js";
|
|
39
38
|
import { NoOutputGeneratedError } from "../../utils/generationErrors.js";
|
|
40
39
|
import { buildNoOutputSentinel, stampNoOutputSpan, } from "../../utils/noOutputSentinel.js";
|
|
41
40
|
import { convertZodToJsonSchema } from "../../utils/schemaConversion.js";
|
|
@@ -1613,6 +1612,11 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1613
1612
|
...(hasTools && options.toolChoice
|
|
1614
1613
|
? { toolChoice: resolveToolChoice(options, toolsRecord, true) }
|
|
1615
1614
|
: {}),
|
|
1615
|
+
...(options.toolChoiceSteps !== undefined
|
|
1616
|
+
? { toolChoiceSteps: options.toolChoiceSteps }
|
|
1617
|
+
: {}),
|
|
1618
|
+
...(options.prepareStep ? { prepareStep: options.prepareStep } : {}),
|
|
1619
|
+
modelId,
|
|
1616
1620
|
...(responseFormat ? { responseFormat } : {}),
|
|
1617
1621
|
...(providerOptions ? { providerOptions } : {}),
|
|
1618
1622
|
maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
|
|
@@ -1627,6 +1631,7 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1627
1631
|
runStep: (call) => withProviderRetry(call, trace.getActiveSpan() ?? undefined, "anthropic generate", undefined, options.abortSignal).catch((err) => {
|
|
1628
1632
|
throw this.handleProviderError(err);
|
|
1629
1633
|
}),
|
|
1634
|
+
onRejectedToolCall: (name, error, id) => this.emitRejectedToolCall(name, error, id),
|
|
1630
1635
|
}, toolExecutionSummaries);
|
|
1631
1636
|
const enhanced = {
|
|
1632
1637
|
content: loop.text,
|
|
@@ -1660,7 +1665,7 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1660
1665
|
toolExecutions: resolveToolExecutionRecords(options, transformToolExecutions(toolExecutionSummaries)),
|
|
1661
1666
|
enhancedWithTools: loop.toolsUsed.length > 0,
|
|
1662
1667
|
};
|
|
1663
|
-
return this.finalizeNativeGenerate(enhanced, options, startTime);
|
|
1668
|
+
return this.finalizeNativeGenerate(enhanced, options, startTime, toolExecutionSummaries);
|
|
1664
1669
|
}
|
|
1665
1670
|
/**
|
|
1666
1671
|
* Fold a captured snapshot into the provider's usage bookkeeping and log it.
|
|
@@ -1748,6 +1753,11 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1748
1753
|
// options.tools is pre-merged by BaseProvider.stream() with base tools
|
|
1749
1754
|
// (MCP/built-in) + user-provided tools (RAG, etc.)
|
|
1750
1755
|
shouldUseTools = !options.disableTools && this.supportsTools();
|
|
1756
|
+
// Used as is: BaseProvider.stream() already event-wrapped, recorder-
|
|
1757
|
+
// wrapped and discovery-partitioned this record, and search_tools
|
|
1758
|
+
// hydrates new tools into THIS object mid-turn. A copy would lose the
|
|
1759
|
+
// null prototype, the non-enumerable resolver and every later
|
|
1760
|
+
// hydration, so a discovered tool the model then called was "not found".
|
|
1751
1761
|
toolsRecord = shouldUseTools
|
|
1752
1762
|
? options.tools || (await this.getAllTools())
|
|
1753
1763
|
: {};
|
|
@@ -1776,9 +1786,17 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1776
1786
|
throw this.handleProviderError(setupErr);
|
|
1777
1787
|
}
|
|
1778
1788
|
const modelId = this.modelName || getDefaultAnthropicModel();
|
|
1779
|
-
|
|
1780
|
-
|
|
1789
|
+
// The turn's choice in NeuroLink shape. Translated to the wire PER STEP
|
|
1790
|
+
// inside buildParams, because a forced choice applies only to the first
|
|
1791
|
+
// `toolChoiceSteps` steps — baked in once, it compelled a tool call on
|
|
1792
|
+
// every step and the turn ran until maxSteps.
|
|
1793
|
+
const turnToolChoice = shouldUseTools && anthropicTools && anthropicTools.length > 0
|
|
1794
|
+
? resolveToolChoice(options, toolsRecord, shouldUseTools)
|
|
1781
1795
|
: undefined;
|
|
1796
|
+
// Completed-step records handed to a caller's prepareStep hook. Filled by
|
|
1797
|
+
// the buildToolResultMessages wrapper below, which is the only per-step
|
|
1798
|
+
// hook that sees a step's calls and results together.
|
|
1799
|
+
const stepRecords = [];
|
|
1782
1800
|
// Extended thinking: enabled when the caller supplies an explicit token
|
|
1783
1801
|
// budget (mirrors the previous experimental_thinking gating). Thinking
|
|
1784
1802
|
// deltas stream out on the `reasoning` chunk channel.
|
|
@@ -1803,7 +1821,6 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1803
1821
|
},
|
|
1804
1822
|
});
|
|
1805
1823
|
const maxSteps = options.maxSteps || DEFAULT_MAX_STEPS;
|
|
1806
|
-
const emitter = this.neurolink?.getEventEmitter();
|
|
1807
1824
|
const channel = createStreamChannel();
|
|
1808
1825
|
const { push: pushChunk } = channel;
|
|
1809
1826
|
const { usagePromise, finishPromise, resolveUsage, resolveFinish } = createDeferredAnalytics();
|
|
@@ -1842,7 +1859,15 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1842
1859
|
});
|
|
1843
1860
|
let capturedProviderError;
|
|
1844
1861
|
const client = this.client;
|
|
1862
|
+
// Every attempted call, for the no-output sentinel below; the result
|
|
1863
|
+
// reports only calls that returned a value, the rule the generate loop
|
|
1864
|
+
// and Bedrock already follow.
|
|
1845
1865
|
const toolsUsed = [];
|
|
1866
|
+
const toolsSucceeded = [];
|
|
1867
|
+
// Every tool this turn ran, in the loop's own summary shape. Backs the
|
|
1868
|
+
// result's toolCalls / toolResults / toolExecutions getters, which read it
|
|
1869
|
+
// live so a consumer that drains the stream first sees the full turn.
|
|
1870
|
+
const streamToolSummaries = [];
|
|
1846
1871
|
const streamStartTime = Date.now();
|
|
1847
1872
|
// Mutable-reference contract from StreamResult.metadata: created before
|
|
1848
1873
|
// the loop and filled once the turn drains, because wrapper spreads
|
|
@@ -1965,7 +1990,27 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1965
1990
|
}
|
|
1966
1991
|
return { conversation: rebuilt };
|
|
1967
1992
|
};
|
|
1968
|
-
const buildParams = (conversation) => {
|
|
1993
|
+
const buildParams = async (conversation, step) => {
|
|
1994
|
+
// Consulted whenever there is a base choice OR a hook: a hook with
|
|
1995
|
+
// no base choice still gets to force a tool on a given step.
|
|
1996
|
+
// No declared tools → no tool_choice and no hook call; Anthropic
|
|
1997
|
+
// rejects a tool_choice on a request without tools.
|
|
1998
|
+
const anthropicToolChoice = (turnToolChoice !== undefined || options.prepareStep) &&
|
|
1999
|
+
anthropicTools &&
|
|
2000
|
+
anthropicTools.length > 0
|
|
2001
|
+
? // Per step, so a prepareStep-forced choice gets the same
|
|
2002
|
+
// Claude 5.5/5.1 relaxation as the turn's base choice.
|
|
2003
|
+
relaxForcedToolChoice(modelId, toolChoiceToAnthropic(await resolveStepToolChoice({
|
|
2004
|
+
base: turnToolChoice,
|
|
2005
|
+
step,
|
|
2006
|
+
toolChoiceSteps: options.toolChoiceSteps,
|
|
2007
|
+
prepareStep: options.prepareStep,
|
|
2008
|
+
steps: stepRecords,
|
|
2009
|
+
maxSteps,
|
|
2010
|
+
model: modelId,
|
|
2011
|
+
...(abortSignal ? { abortSignal } : {}),
|
|
2012
|
+
})))
|
|
2013
|
+
: undefined;
|
|
1969
2014
|
// Mid-turn discovery sync: search_tools (tools.discovery) hydrates
|
|
1970
2015
|
// new tools into toolsRecord between steps; Claude only calls tools
|
|
1971
2016
|
// declared in the request, so advertise them now.
|
|
@@ -2055,33 +2100,77 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
2055
2100
|
const adapter = {
|
|
2056
2101
|
...baseAdapter,
|
|
2057
2102
|
buildToolResultMessages: (conversation, stepResult, toolResults, engineStep) => {
|
|
2103
|
+
const settledAt = new Date();
|
|
2058
2104
|
for (const result of toolResults) {
|
|
2059
2105
|
toolsUsed.push(result.name);
|
|
2106
|
+
if (!result.error) {
|
|
2107
|
+
toolsSucceeded.push(result.name);
|
|
2108
|
+
}
|
|
2109
|
+
streamToolSummaries.push({
|
|
2110
|
+
toolCallId: result.id,
|
|
2111
|
+
toolName: result.name,
|
|
2112
|
+
input: result.args,
|
|
2113
|
+
...(result.error
|
|
2114
|
+
? { error: result.error }
|
|
2115
|
+
: { output: result.output }),
|
|
2116
|
+
// The engine reports a step's results together, after the
|
|
2117
|
+
// batch has settled, so per-tool timing is not available here;
|
|
2118
|
+
// `toolExecutions` prefers the execution recorder's own records,
|
|
2119
|
+
// which do carry it.
|
|
2120
|
+
startTime: settledAt,
|
|
2121
|
+
endTime: settledAt,
|
|
2122
|
+
stepIndex: engineStep,
|
|
2123
|
+
});
|
|
2060
2124
|
}
|
|
2125
|
+
const rawBlocks = stepResult.raw;
|
|
2126
|
+
stepRecords.push(toPrepareStepRecord({
|
|
2127
|
+
stepNumber: engineStep,
|
|
2128
|
+
content: rawBlocks.map((block) => block),
|
|
2129
|
+
text: stepResult.text,
|
|
2130
|
+
toolCalls: stepResult.toolCalls.map((call) => ({
|
|
2131
|
+
toolName: call.name,
|
|
2132
|
+
toolCallId: call.id,
|
|
2133
|
+
input: call.args,
|
|
2134
|
+
})),
|
|
2135
|
+
toolResults: toolResults.map((result) => ({
|
|
2136
|
+
toolName: result.name,
|
|
2137
|
+
toolCallId: result.id,
|
|
2138
|
+
output: result.error ? { error: result.error } : result.output,
|
|
2139
|
+
})),
|
|
2140
|
+
finishReason: "tool-calls",
|
|
2141
|
+
inputTokens: stepResult.usage.inputTokens,
|
|
2142
|
+
outputTokens: stepResult.usage.outputTokens,
|
|
2143
|
+
...(stepResult.usage.cacheReadTokens !== undefined ||
|
|
2144
|
+
stepResult.usage.cacheWriteTokens !== undefined
|
|
2145
|
+
? {
|
|
2146
|
+
cacheReadTokens: stepResult.usage.cacheReadTokens ?? 0,
|
|
2147
|
+
cacheWriteTokens: stepResult.usage.cacheWriteTokens ?? 0,
|
|
2148
|
+
}
|
|
2149
|
+
: {}),
|
|
2150
|
+
}));
|
|
2151
|
+
// `tool:start` / `tool:end` are emitted by the executor itself
|
|
2152
|
+
// (ToolsManager, external MCP, neurolink.executeTool), so nothing is
|
|
2153
|
+
// emitted here: a second `tool:end` per result — with a zeroed
|
|
2154
|
+
// responseTime — used to be, and doubled every consumer's count.
|
|
2061
2155
|
const toolCallsForStorage = toolResults.map((result) => ({
|
|
2062
|
-
type: "tool-call",
|
|
2063
2156
|
toolCallId: result.id,
|
|
2064
2157
|
toolName: result.name,
|
|
2065
2158
|
args: result.args,
|
|
2159
|
+
stepIndex: engineStep,
|
|
2066
2160
|
}));
|
|
2067
2161
|
const toolResultsForStorage = toolResults.map((result) => result.error
|
|
2068
2162
|
? {
|
|
2069
|
-
type: "tool-result",
|
|
2070
2163
|
toolCallId: result.id,
|
|
2071
2164
|
toolName: result.name,
|
|
2072
2165
|
error: result.error,
|
|
2166
|
+
stepIndex: engineStep,
|
|
2073
2167
|
}
|
|
2074
2168
|
: {
|
|
2075
|
-
type: "tool-result",
|
|
2076
2169
|
toolCallId: result.id,
|
|
2077
2170
|
toolName: result.name,
|
|
2078
|
-
|
|
2171
|
+
output: result.output,
|
|
2172
|
+
stepIndex: engineStep,
|
|
2079
2173
|
});
|
|
2080
|
-
emitToolEndFromStepFinish(emitter, toolResultsForStorage.map((tr) => ({
|
|
2081
|
-
toolName: tr.toolName,
|
|
2082
|
-
result: "result" in tr ? tr.result : undefined,
|
|
2083
|
-
error: "error" in tr ? tr.error : undefined,
|
|
2084
|
-
})));
|
|
2085
2174
|
this.handleToolExecutionStorage(toolCallsForStorage, toolResultsForStorage, options, new Date()).catch((storageErr) => {
|
|
2086
2175
|
logger.warn("[AnthropicProvider] Failed to store tool executions", {
|
|
2087
2176
|
provider: this.providerName,
|
|
@@ -2344,7 +2433,7 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
2344
2433
|
}
|
|
2345
2434
|
}
|
|
2346
2435
|
};
|
|
2347
|
-
|
|
2436
|
+
const result = {
|
|
2348
2437
|
stream: transformedStream(),
|
|
2349
2438
|
provider: this.providerName,
|
|
2350
2439
|
// Cell-3 identity fix: `this.modelName` is the raw, possibly-unset
|
|
@@ -2355,8 +2444,14 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
2355
2444
|
// build the request. Reporting the raw field previously let a
|
|
2356
2445
|
// default-model turn's StreamResult claim an undefined/empty model.
|
|
2357
2446
|
model: modelId,
|
|
2358
|
-
toolCalls
|
|
2359
|
-
|
|
2447
|
+
// `toolCalls` / `toolResults` / `toolExecutions` are defined as live
|
|
2448
|
+
// getters below, over the per-turn summaries.
|
|
2449
|
+
// The live array: pushed as steps settle, so a consumer reading after
|
|
2450
|
+
// the drain sees every tool that ran and returned a value (a thrown or
|
|
2451
|
+
// not-found call is not "used"). This was never returned before, so
|
|
2452
|
+
// `toolsUsed` came back empty for every tool on this path and
|
|
2453
|
+
// `enhancedWithTools` was always false.
|
|
2454
|
+
toolsUsed: toolsSucceeded,
|
|
2360
2455
|
metadata: turnMetadata,
|
|
2361
2456
|
// Wire the deferred usage/finish promises into the analytics collector
|
|
2362
2457
|
// (mirrors openaiChatCompletionsBase). Without this the loop computed a
|
|
@@ -2384,6 +2479,40 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
2384
2479
|
return analytics;
|
|
2385
2480
|
}),
|
|
2386
2481
|
};
|
|
2482
|
+
// Live getters, mirroring openaiChatCompletionsBase: the wrapper layers
|
|
2483
|
+
// (BaseProvider.stream, NeuroLink.stream) re-apply accessor descriptors
|
|
2484
|
+
// rather than spreading values, so these resolve when read, after the
|
|
2485
|
+
// background loop has run the tools — a plain value here would be the
|
|
2486
|
+
// empty snapshot taken before the first chunk.
|
|
2487
|
+
Object.defineProperty(result, "toolCalls", {
|
|
2488
|
+
enumerable: true,
|
|
2489
|
+
configurable: true,
|
|
2490
|
+
get: () => toolCallsFromSummaries(streamToolSummaries),
|
|
2491
|
+
});
|
|
2492
|
+
Object.defineProperty(result, "toolResults", {
|
|
2493
|
+
enumerable: true,
|
|
2494
|
+
configurable: true,
|
|
2495
|
+
get: () => streamToolSummaries.map((s) => ({
|
|
2496
|
+
toolName: s.toolName,
|
|
2497
|
+
id: s.toolCallId,
|
|
2498
|
+
status: s.error ? "failure" : "success",
|
|
2499
|
+
...(s.error ? { error: s.error } : {}),
|
|
2500
|
+
...(s.output !== undefined
|
|
2501
|
+
? { output: s.output }
|
|
2502
|
+
: {}),
|
|
2503
|
+
})),
|
|
2504
|
+
});
|
|
2505
|
+
Object.defineProperty(result, "toolExecutions", {
|
|
2506
|
+
enumerable: true,
|
|
2507
|
+
configurable: true,
|
|
2508
|
+
get: () => resolveToolExecutionRecords(options, transformToolExecutions(streamToolSummaries.map((s) => ({
|
|
2509
|
+
toolName: s.toolName,
|
|
2510
|
+
input: s.input,
|
|
2511
|
+
output: s.error ? { error: s.error } : s.output,
|
|
2512
|
+
duration: s.endTime.getTime() - s.startTime.getTime(),
|
|
2513
|
+
})))),
|
|
2514
|
+
});
|
|
2515
|
+
return result;
|
|
2387
2516
|
}
|
|
2388
2517
|
async isAvailable() {
|
|
2389
2518
|
try {
|
|
@@ -382,8 +382,8 @@ export function createAnthropicLoopAdapter(config) {
|
|
|
382
382
|
execute: async (args, opts) => execute(args, opts),
|
|
383
383
|
};
|
|
384
384
|
},
|
|
385
|
-
buildStepRequest(conversation, step) {
|
|
386
|
-
return { raw: config.buildParams(conversation, step) };
|
|
385
|
+
async buildStepRequest(conversation, step) {
|
|
386
|
+
return { raw: await config.buildParams(conversation, step) };
|
|
387
387
|
},
|
|
388
388
|
async executeStep(request, channel, signal) {
|
|
389
389
|
const params = request.raw;
|
|
@@ -8,6 +8,7 @@ import { ERROR_CODES, NeuroLinkError } from "../../utils/errorHandling.js";
|
|
|
8
8
|
import { logger } from "../../utils/logger.js";
|
|
9
9
|
import { drainDetachedPump } from "../../utils/drainDetachedPump.js";
|
|
10
10
|
import { createGeminiLoopAdapter } from "../../core/geminiLoopAdapter.js";
|
|
11
|
+
import { isDirectTTSRequest } from "../../core/resolveRequestKind.js";
|
|
11
12
|
import { runAgenticLoop } from "../../core/loopEngine.js";
|
|
12
13
|
import { DEFAULT_TOOL_MAX_RETRIES, resolveToolTimeoutMs, } from "../../core/constants.js";
|
|
13
14
|
import { isToolsSchemaExclusionInForce } from "../../core/modules/structuredOutputPolicy.js";
|
|
@@ -2043,7 +2044,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
2043
2044
|
// TTS direct-synthesis mode: synthesise the input text directly (no LLM
|
|
2044
2045
|
// call). BaseProvider.runGenerateInActiveContext does the same dispatch
|
|
2045
2046
|
// — replicated here because AI Studio's override bypasses that path.
|
|
2046
|
-
if (options.tts
|
|
2047
|
+
if (isDirectTTSRequest(options.tts)) {
|
|
2047
2048
|
logger.info("[GoogleAIStudio] Routing TTS direct-synthesis to handleDirectTTSSynthesis", { model: modelName });
|
|
2048
2049
|
return this.handleDirectTTSSynthesis(options, Date.now());
|
|
2049
2050
|
}
|
|
@@ -33,9 +33,8 @@ import { redactUrlCredentials } from "../utils/logSanitize.js";
|
|
|
33
33
|
import { NoOutputGeneratedError } from "../utils/generationErrors.js";
|
|
34
34
|
import { buildNoOutputSentinel, stampNoOutputSpan, } from "../utils/noOutputSentinel.js";
|
|
35
35
|
import { composeAbortSignalsScoped, createTimeoutController, mergeAbortSignals, TimeoutError, } from "../utils/timeout.js";
|
|
36
|
-
import { emitToolEndFromStepFinish } from "../utils/toolEndEmitter.js";
|
|
37
36
|
import { resolveRequestKind } from "../core/resolveRequestKind.js";
|
|
38
|
-
import { appendJsonSchemaInstruction, hasNativeDoGenerate, runNativeGenerateLoop, } from "../core/nativeGenerateLoop.js";
|
|
37
|
+
import { appendJsonSchemaInstruction, hasNativeDoGenerate, resolveStepToolChoice, runNativeGenerateLoop, toPrepareStepRecord, } from "../core/nativeGenerateLoop.js";
|
|
39
38
|
import { resolveToolExecutionRecords, toolCallsFromSummaries, } from "../core/toolExecutionRecorder.js";
|
|
40
39
|
import { convertZodToJsonSchema } from "../utils/schemaConversion.js";
|
|
41
40
|
import { coerceJsonToSchema, recoverScalarRoot, schemaAccepts, } from "../utils/json/coerce.js";
|
|
@@ -908,6 +907,11 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
908
907
|
}
|
|
909
908
|
const doGenerate = model.doGenerate.bind(model);
|
|
910
909
|
const shouldUseTools = !options.disableTools && this.supportsTools();
|
|
910
|
+
// `options.tools` arrives from BaseProvider's merge (getToolsForStream)
|
|
911
|
+
// already event-wrapped, recorder-wrapped and discovery-partitioned. It
|
|
912
|
+
// must be used AS IS: copying it would drop the discovery record's null
|
|
913
|
+
// prototype and non-enumerable resolver, and tools search_tools hydrates
|
|
914
|
+
// later land only in the original object.
|
|
911
915
|
const toolsRecord = shouldUseTools
|
|
912
916
|
? options.tools || (await this.getAllTools())
|
|
913
917
|
: {};
|
|
@@ -979,6 +983,11 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
979
983
|
...(withTools && hasTools && options.toolChoice
|
|
980
984
|
? { toolChoice: resolveToolChoice(options, toolsRecord, true) }
|
|
981
985
|
: {}),
|
|
986
|
+
...(options.toolChoiceSteps !== undefined
|
|
987
|
+
? { toolChoiceSteps: options.toolChoiceSteps }
|
|
988
|
+
: {}),
|
|
989
|
+
...(options.prepareStep ? { prepareStep: options.prepareStep } : {}),
|
|
990
|
+
modelId,
|
|
982
991
|
// The per-call `timeout` keeps its per-MODEL-CALL meaning once
|
|
983
992
|
// `turnTimeoutMs` owns the whole-turn deadline, and it reaches the
|
|
984
993
|
// model layer only through this channel. Without it each step fell
|
|
@@ -1008,6 +1017,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
1008
1017
|
runStep: (call) => withProviderRetry(call, trace.getActiveSpan() ?? undefined, `${this.providerName} generate`, undefined, options.abortSignal).catch((err) => {
|
|
1009
1018
|
throw this.handleProviderError(err);
|
|
1010
1019
|
}),
|
|
1020
|
+
onRejectedToolCall: (name, error, id) => this.emitRejectedToolCall(name, error, id),
|
|
1011
1021
|
}, toolExecutionSummaries);
|
|
1012
1022
|
};
|
|
1013
1023
|
// Structured output rides `response_format` first. When a vendor rejects
|
|
@@ -1138,7 +1148,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
1138
1148
|
toolExecutions: resolveToolExecutionRecords(options, transformToolExecutions(toolExecutionSummaries)),
|
|
1139
1149
|
enhancedWithTools: toolsUsed.length > 0,
|
|
1140
1150
|
};
|
|
1141
|
-
return this.finalizeNativeGenerate(enhanced, options, startTime);
|
|
1151
|
+
return this.finalizeNativeGenerate(enhanced, options, startTime, toolExecutionSummaries);
|
|
1142
1152
|
}
|
|
1143
1153
|
/**
|
|
1144
1154
|
* Recover structured output on a turn where tools forced `response_format`
|
|
@@ -1454,6 +1464,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
1454
1464
|
doGenerate,
|
|
1455
1465
|
conversation: conv,
|
|
1456
1466
|
toolsRecord: {},
|
|
1467
|
+
modelId,
|
|
1457
1468
|
...(format ? { responseFormat: format } : {}),
|
|
1458
1469
|
maxSteps: 1,
|
|
1459
1470
|
...(options.maxTokens
|
|
@@ -1575,7 +1586,10 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
1575
1586
|
let toolsRecord;
|
|
1576
1587
|
let wireNameMaps;
|
|
1577
1588
|
let openAITools;
|
|
1578
|
-
|
|
1589
|
+
// The turn's tool choice in NeuroLink shape; translated to the wire per
|
|
1590
|
+
// step (see stepToolChoice below) so a forced choice can lapse after
|
|
1591
|
+
// `toolChoiceSteps` instead of compelling a tool call on every step.
|
|
1592
|
+
let turnToolChoice;
|
|
1579
1593
|
// The prompt is kept in its pre-wire shape. Model middleware transforms
|
|
1580
1594
|
// `params.prompt`, and the conversion to the chat-completions wire format
|
|
1581
1595
|
// has to happen AFTER that or the transform would be discarded.
|
|
@@ -1583,6 +1597,8 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
1583
1597
|
try {
|
|
1584
1598
|
modelId = await this.resolveModelName();
|
|
1585
1599
|
const shouldUseTools = !options.disableTools && this.supportsTools();
|
|
1600
|
+
// Same identity rule as executeNativeGenerate: the merged record is
|
|
1601
|
+
// the discovery hot record and is never copied here.
|
|
1586
1602
|
toolsRecord = shouldUseTools
|
|
1587
1603
|
? options.tools || (await this.getAllTools())
|
|
1588
1604
|
: {};
|
|
@@ -1594,7 +1610,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
1594
1610
|
openAITools = shouldUseTools
|
|
1595
1611
|
? buildToolsForOpenAI(toolsRecord, wireNameMaps?.toWire)
|
|
1596
1612
|
: undefined;
|
|
1597
|
-
|
|
1613
|
+
turnToolChoice = resolveToolChoice(options, toolsRecord, shouldUseTools);
|
|
1598
1614
|
promptMessages = (await this.buildMessagesForStream(options));
|
|
1599
1615
|
}
|
|
1600
1616
|
catch (setupErr) {
|
|
@@ -1621,7 +1637,6 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
1621
1637
|
const url = this.getChatCompletionsURL(modelId);
|
|
1622
1638
|
const fetchImpl = createProxyFetch();
|
|
1623
1639
|
const maxSteps = options.maxSteps || DEFAULT_MAX_STEPS;
|
|
1624
|
-
const emitter = this.neurolink?.getEventEmitter();
|
|
1625
1640
|
const toolsUsed = [];
|
|
1626
1641
|
const toolExecutionSummaries = [];
|
|
1627
1642
|
const { usagePromise, finishPromise, resolveUsage, resolveFinish } = createDeferredAnalytics();
|
|
@@ -1715,10 +1730,22 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
1715
1730
|
options: sampled,
|
|
1716
1731
|
conversation,
|
|
1717
1732
|
openAITools,
|
|
1718
|
-
|
|
1733
|
+
// No declared tools → no tool_choice, and the hook is not consulted:
|
|
1734
|
+
// a forced choice on a tool-less request is a 400.
|
|
1735
|
+
stepToolChoice: async (step, steps) => openAITools && openAITools.length > 0
|
|
1736
|
+
? mapNeuroLinkToolChoice(await resolveStepToolChoice({
|
|
1737
|
+
base: turnToolChoice,
|
|
1738
|
+
step,
|
|
1739
|
+
toolChoiceSteps: options.toolChoiceSteps,
|
|
1740
|
+
prepareStep: options.prepareStep,
|
|
1741
|
+
steps,
|
|
1742
|
+
maxSteps,
|
|
1743
|
+
model: modelId,
|
|
1744
|
+
...(abortSignal ? { abortSignal } : {}),
|
|
1745
|
+
}), wireNameMaps?.toWire)
|
|
1746
|
+
: undefined,
|
|
1719
1747
|
toolsRecord,
|
|
1720
1748
|
toolNameFromWire: wireNameMaps?.fromWire,
|
|
1721
|
-
emitter,
|
|
1722
1749
|
toolsUsed,
|
|
1723
1750
|
toolExecutionSummaries,
|
|
1724
1751
|
pushChunk: channel.push,
|
|
@@ -2012,7 +2039,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
2012
2039
|
return result;
|
|
2013
2040
|
}
|
|
2014
2041
|
async runStreamLoop(args) {
|
|
2015
|
-
const { maxSteps, modelId, url, fetchImpl, abortSignal, options, conversation, openAITools,
|
|
2042
|
+
const { maxSteps, modelId, url, fetchImpl, abortSignal, options, conversation, openAITools, stepToolChoice, toolsRecord, toolNameFromWire, toolsUsed, toolExecutionSummaries, pushChunk, closeChannel, resolveUsage, resolveFinish, responseFormat, onModelObserved, } = args;
|
|
2016
2043
|
// Hoisted above the try so the catch can resolve the usage accumulated
|
|
2017
2044
|
// by steps that completed BEFORE the failure — those steps were billed.
|
|
2018
2045
|
let stepFinish = null;
|
|
@@ -2049,7 +2076,10 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
2049
2076
|
// two different payloads would be meaningless.
|
|
2050
2077
|
let lastObservedPromptTokens;
|
|
2051
2078
|
let lastSentEstimate;
|
|
2079
|
+
// Completed-step records handed to a caller's prepareStep hook.
|
|
2080
|
+
const stepRecords = [];
|
|
2052
2081
|
for (let step = 0; step < maxSteps; step++) {
|
|
2082
|
+
const openAIToolChoice = await stepToolChoice(step, stepRecords);
|
|
2053
2083
|
// Mid-turn discovery sync: search_tools (tools.discovery) hydrates
|
|
2054
2084
|
// new tools into toolsRecord between steps. Dispatch already re-reads
|
|
2055
2085
|
// the record — this block makes the request DECLARE them so the model
|
|
@@ -2137,11 +2167,54 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
2137
2167
|
conversation,
|
|
2138
2168
|
toolsRecord,
|
|
2139
2169
|
toolNameFromWire: effectiveToolNameFromWire,
|
|
2140
|
-
emitter,
|
|
2141
2170
|
toolsUsed,
|
|
2142
2171
|
toolExecutionSummaries,
|
|
2143
2172
|
options,
|
|
2173
|
+
stepIndex: step,
|
|
2144
2174
|
});
|
|
2175
|
+
const executed = toolExecutionSummaries.slice(-stepResult.toolCalls.size);
|
|
2176
|
+
stepRecords.push(toPrepareStepRecord({
|
|
2177
|
+
stepNumber: step,
|
|
2178
|
+
content: [
|
|
2179
|
+
...(stepResult.text
|
|
2180
|
+
? [{ type: "text", text: stepResult.text }]
|
|
2181
|
+
: []),
|
|
2182
|
+
...executed.map((s) => ({
|
|
2183
|
+
type: "tool-call",
|
|
2184
|
+
toolCallId: s.toolCallId,
|
|
2185
|
+
toolName: s.toolName,
|
|
2186
|
+
input: s.input,
|
|
2187
|
+
})),
|
|
2188
|
+
],
|
|
2189
|
+
text: stepResult.text,
|
|
2190
|
+
toolCalls: executed.map((s) => ({
|
|
2191
|
+
toolName: s.toolName,
|
|
2192
|
+
toolCallId: s.toolCallId,
|
|
2193
|
+
input: s.input,
|
|
2194
|
+
})),
|
|
2195
|
+
toolResults: executed.map((s) => ({
|
|
2196
|
+
toolName: s.toolName,
|
|
2197
|
+
toolCallId: s.toolCallId,
|
|
2198
|
+
output: s.output,
|
|
2199
|
+
})),
|
|
2200
|
+
finishReason: "tool-calls",
|
|
2201
|
+
// prompt_tokens already includes cached_tokens; subtract it so
|
|
2202
|
+
// this agrees with the generate loop's toDeferredUsage, which
|
|
2203
|
+
// reports the disjoint noCache figure for the same field —
|
|
2204
|
+
// otherwise a cached step reports cached tokens twice (once
|
|
2205
|
+
// here, once in cacheReadTokens) and a prepareStep hook that
|
|
2206
|
+
// budgets on steps[].usage sees different numbers from
|
|
2207
|
+
// stream() and generate() for the same step.
|
|
2208
|
+
inputTokens: Math.max(0, (stepResult.usage?.prompt_tokens ?? 0) -
|
|
2209
|
+
(stepResult.usage?.prompt_tokens_details?.cached_tokens ?? 0)),
|
|
2210
|
+
outputTokens: stepResult.usage?.completion_tokens ?? 0,
|
|
2211
|
+
...(stepResult.usage?.prompt_tokens_details?.cached_tokens !==
|
|
2212
|
+
undefined
|
|
2213
|
+
? {
|
|
2214
|
+
cacheReadTokens: Math.min(stepResult.usage.prompt_tokens_details.cached_tokens, stepResult.usage.prompt_tokens ?? 0),
|
|
2215
|
+
}
|
|
2216
|
+
: {}),
|
|
2217
|
+
}));
|
|
2145
2218
|
}
|
|
2146
2219
|
resolveUsage(toDeferredUsage());
|
|
2147
2220
|
resolveFinish(stepFinish ?? "stop");
|
|
@@ -2258,7 +2331,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
2258
2331
|
});
|
|
2259
2332
|
}
|
|
2260
2333
|
async executeToolBatch(args) {
|
|
2261
|
-
const { stepResult, conversation, toolsRecord, toolNameFromWire,
|
|
2334
|
+
const { stepResult, conversation, toolsRecord, toolNameFromWire, toolsUsed, toolExecutionSummaries, options, stepIndex, } = args;
|
|
2262
2335
|
const toolCallsForMessage = [];
|
|
2263
2336
|
for (const [, t] of stepResult.toolCalls) {
|
|
2264
2337
|
toolCallsForMessage.push({
|
|
@@ -2295,14 +2368,14 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
2295
2368
|
// loaded via search_tools — that's a real tool, not a hallucination.
|
|
2296
2369
|
const toolDef = toolsRecord[registryName] ??
|
|
2297
2370
|
resolveDeferredTool(toolsRecord, registryName);
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
input,
|
|
2302
|
-
});
|
|
2371
|
+
// No `tool:start` here either: registered and built-in tools emit their
|
|
2372
|
+
// own start/end pair from the executor that runs them, and this raw
|
|
2373
|
+
// emission made every one of them count twice.
|
|
2303
2374
|
if (!toolDef || typeof toolDef.execute !== "function") {
|
|
2304
2375
|
errorMsg = `Tool '${registryName}' is not registered.`;
|
|
2305
2376
|
output = { error: errorMsg };
|
|
2377
|
+
// Rejected before any execute ran, so no executor will report it.
|
|
2378
|
+
this.emitRejectedToolCall(registryName, errorMsg, t.id);
|
|
2306
2379
|
}
|
|
2307
2380
|
else {
|
|
2308
2381
|
try {
|
|
@@ -2323,6 +2396,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
2323
2396
|
...(errorMsg ? { error: errorMsg } : {}),
|
|
2324
2397
|
startTime: startedAt,
|
|
2325
2398
|
endTime: endedAt,
|
|
2399
|
+
stepIndex,
|
|
2326
2400
|
});
|
|
2327
2401
|
conversation.push({
|
|
2328
2402
|
role: "tool",
|
|
@@ -2331,21 +2405,27 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
2331
2405
|
});
|
|
2332
2406
|
}
|
|
2333
2407
|
const justExecuted = toolExecutionSummaries.slice(-stepResult.toolCalls.size);
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
...(s.error ? { error: s.error } : {}),
|
|
2338
|
-
})));
|
|
2408
|
+
// No `tool:end` here: the executor already emitted start/end with the
|
|
2409
|
+
// real duration, and a second emission per result doubled every
|
|
2410
|
+
// consumer's count.
|
|
2339
2411
|
try {
|
|
2412
|
+
// `args`, not `input`: both stores read `args`, so the previous
|
|
2413
|
+
// `input:` key persisted every tool call with empty arguments.
|
|
2340
2414
|
await this.handleToolExecutionStorage(justExecuted.map((s) => ({
|
|
2341
2415
|
toolCallId: s.toolCallId,
|
|
2342
2416
|
toolName: s.toolName,
|
|
2343
|
-
|
|
2344
|
-
|
|
2417
|
+
args: s.input !== null &&
|
|
2418
|
+
typeof s.input === "object" &&
|
|
2419
|
+
!Array.isArray(s.input)
|
|
2420
|
+
? s.input
|
|
2421
|
+
: {},
|
|
2422
|
+
...(s.stepIndex !== undefined ? { stepIndex: s.stepIndex } : {}),
|
|
2345
2423
|
})), justExecuted.map((s) => ({
|
|
2346
2424
|
toolCallId: s.toolCallId,
|
|
2347
2425
|
toolName: s.toolName,
|
|
2348
2426
|
output: s.output,
|
|
2427
|
+
...(s.error ? { error: s.error } : {}),
|
|
2428
|
+
...(s.stepIndex !== undefined ? { stepIndex: s.stepIndex } : {}),
|
|
2349
2429
|
})), options, new Date());
|
|
2350
2430
|
}
|
|
2351
2431
|
catch (err) {
|