@librechat/agents 3.1.70 → 3.1.71-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/graphs/Graph.cjs +45 -0
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/main.cjs +4 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/prune.cjs +9 -2
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/run.cjs +4 -0
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +43 -0
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +453 -45
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/toolOutputReferences.cjs +475 -0
- package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -0
- package/dist/cjs/utils/truncation.cjs +28 -0
- package/dist/cjs/utils/truncation.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +45 -0
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/main.mjs +2 -2
- package/dist/esm/messages/prune.mjs +9 -2
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/run.mjs +4 -0
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +42 -1
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +453 -45
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/toolOutputReferences.mjs +468 -0
- package/dist/esm/tools/toolOutputReferences.mjs.map +1 -0
- package/dist/esm/utils/truncation.mjs +27 -1
- package/dist/esm/utils/truncation.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +21 -0
- package/dist/types/run.d.ts +1 -0
- package/dist/types/tools/BashExecutor.d.ts +31 -0
- package/dist/types/tools/ToolNode.d.ts +86 -3
- package/dist/types/tools/toolOutputReferences.d.ts +205 -0
- package/dist/types/types/run.d.ts +9 -1
- package/dist/types/types/tools.d.ts +70 -0
- package/dist/types/utils/truncation.d.ts +21 -0
- package/package.json +1 -1
- package/src/graphs/Graph.ts +48 -0
- package/src/messages/prune.ts +9 -2
- package/src/run.ts +4 -0
- package/src/specs/prune.test.ts +413 -0
- package/src/tools/BashExecutor.ts +45 -0
- package/src/tools/ToolNode.ts +618 -55
- package/src/tools/__tests__/BashExecutor.test.ts +36 -0
- package/src/tools/__tests__/ToolNode.outputReferences.test.ts +1395 -0
- package/src/tools/__tests__/toolOutputReferences.test.ts +415 -0
- package/src/tools/toolOutputReferences.ts +590 -0
- package/src/types/run.ts +9 -1
- package/src/types/tools.ts +71 -0
- package/src/utils/__tests__/truncation.test.ts +66 -0
- package/src/utils/truncation.ts +30 -0
package/dist/types/run.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export declare class Run<_T extends t.BaseGraphState> {
|
|
|
10
10
|
private tokenCounter?;
|
|
11
11
|
private handlerRegistry?;
|
|
12
12
|
private hookRegistry?;
|
|
13
|
+
private toolOutputReferences?;
|
|
13
14
|
private indexTokenCountMap?;
|
|
14
15
|
calibrationRatio: number;
|
|
15
16
|
graphRunnable?: t.CompiledStateWorkflow;
|
|
@@ -19,7 +19,38 @@ export declare const BashExecutionToolSchema: {
|
|
|
19
19
|
readonly required: readonly ["command"];
|
|
20
20
|
};
|
|
21
21
|
export declare const BashExecutionToolDescription: string;
|
|
22
|
+
/**
|
|
23
|
+
* Supplemental prompt documenting the tool-output reference feature.
|
|
24
|
+
*
|
|
25
|
+
* Hosts should append this (separated by a blank line) to the base
|
|
26
|
+
* {@link BashExecutionToolDescription} only when
|
|
27
|
+
* `RunConfig.toolOutputReferences.enabled` is `true`. When the feature
|
|
28
|
+
* is disabled, including this text would tell the LLM to emit
|
|
29
|
+
* `{{tool0turn0}}` placeholders that pass through unsubstituted and
|
|
30
|
+
* leak into the shell.
|
|
31
|
+
*/
|
|
32
|
+
export declare const BashToolOutputReferencesGuide: string;
|
|
33
|
+
/**
|
|
34
|
+
* Composes the bash tool description, optionally appending the
|
|
35
|
+
* tool-output references guide. Hosts that enable
|
|
36
|
+
* `RunConfig.toolOutputReferences` should pass `enableToolOutputReferences: true`
|
|
37
|
+
* when registering the tool so the LLM learns the `{{…}}` syntax it
|
|
38
|
+
* will actually be able to use.
|
|
39
|
+
*/
|
|
40
|
+
export declare function buildBashExecutionToolDescription(options?: {
|
|
41
|
+
enableToolOutputReferences?: boolean;
|
|
42
|
+
}): string;
|
|
22
43
|
export declare const BashExecutionToolName = Constants.BASH_TOOL;
|
|
44
|
+
/**
|
|
45
|
+
* Default bash tool definition using the base description.
|
|
46
|
+
*
|
|
47
|
+
* When `RunConfig.toolOutputReferences.enabled` is `true`, build a
|
|
48
|
+
* reference-aware description with
|
|
49
|
+
* {@link buildBashExecutionToolDescription}
|
|
50
|
+
* (`{ enableToolOutputReferences: true }`) and construct a custom
|
|
51
|
+
* definition using it — using this constant as-is leaves the LLM
|
|
52
|
+
* unaware of the `{{tool<i>turn<n>}}` syntax.
|
|
53
|
+
*/
|
|
23
54
|
export declare const BashExecutionToolDefinition: {
|
|
24
55
|
readonly name: Constants.BASH_TOOL;
|
|
25
56
|
readonly description: string;
|
|
@@ -2,8 +2,25 @@ import { ToolCall } from '@langchain/core/messages/tool';
|
|
|
2
2
|
import { END, Command, MessagesAnnotation } from '@langchain/langgraph';
|
|
3
3
|
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
4
4
|
import type { BaseMessage } from '@langchain/core/messages';
|
|
5
|
+
import type { ResolvedArgsByCallId } from '@/tools/toolOutputReferences';
|
|
5
6
|
import type * as t from '@/types';
|
|
6
7
|
import { RunnableCallable } from '@/utils';
|
|
8
|
+
import { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
|
|
9
|
+
/**
|
|
10
|
+
* Per-call batch context for `runTool`. Bundles every optional
|
|
11
|
+
* batch-scoped value the method needs so the signature stays at
|
|
12
|
+
* three positional parameters even as new context fields are added.
|
|
13
|
+
*/
|
|
14
|
+
type RunToolBatchContext = {
|
|
15
|
+
/** Position of this call within the parent ToolNode batch. */
|
|
16
|
+
batchIndex?: number;
|
|
17
|
+
/** Batch turn shared across every call in the batch. */
|
|
18
|
+
turn?: number;
|
|
19
|
+
/** Registry partition scope (run id or anonymous batch id). */
|
|
20
|
+
batchScopeId?: string;
|
|
21
|
+
/** Batch-local sink for post-substitution args. */
|
|
22
|
+
resolvedArgsByCallId?: ResolvedArgsByCallId;
|
|
23
|
+
};
|
|
7
24
|
export declare class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
8
25
|
private toolMap;
|
|
9
26
|
private loadRuntimeTools?;
|
|
@@ -30,7 +47,36 @@ export declare class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
30
47
|
private maxToolResultChars;
|
|
31
48
|
/** Hook registry for PreToolUse/PostToolUse lifecycle hooks */
|
|
32
49
|
private hookRegistry?;
|
|
33
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Registry of tool outputs keyed by `tool<idx>turn<turn>`.
|
|
52
|
+
*
|
|
53
|
+
* Populated only when `toolOutputReferences.enabled` is true. The
|
|
54
|
+
* registry owns the run-scoped state (turn counter, last-seen runId,
|
|
55
|
+
* warn-once memo, stored outputs), so sharing a single instance
|
|
56
|
+
* across multiple ToolNodes in a run lets cross-agent `{{…}}`
|
|
57
|
+
* references resolve — which is why multi-agent graphs pass the
|
|
58
|
+
* *same* instance to every ToolNode they compile rather than each
|
|
59
|
+
* ToolNode building its own.
|
|
60
|
+
*/
|
|
61
|
+
private toolOutputRegistry?;
|
|
62
|
+
/**
|
|
63
|
+
* Monotonic counter used to mint a unique scope id for anonymous
|
|
64
|
+
* batches (ones invoked without a `run_id` in
|
|
65
|
+
* `config.configurable`). Each such batch gets its own registry
|
|
66
|
+
* partition so concurrent anonymous invocations can't delete each
|
|
67
|
+
* other's in-flight state.
|
|
68
|
+
*/
|
|
69
|
+
private anonBatchCounter;
|
|
70
|
+
constructor({ tools, toolMap, name, tags, errorHandler, toolCallStepIds, handleToolErrors, loadRuntimeTools, toolRegistry, sessions, eventDrivenMode, agentId, directToolNames, maxContextTokens, maxToolResultChars, hookRegistry, toolOutputReferences, toolOutputRegistry, }: t.ToolNodeConstructorParams);
|
|
71
|
+
/**
|
|
72
|
+
* Returns the run-scoped tool output registry, or `undefined` when
|
|
73
|
+
* the feature is disabled.
|
|
74
|
+
*
|
|
75
|
+
* @internal Exposed for test observation only. Host code should rely
|
|
76
|
+
* on `{{tool<i>turn<n>}}` substitution at tool-invocation time and
|
|
77
|
+
* not mutate the registry directly.
|
|
78
|
+
*/
|
|
79
|
+
_unsafeGetToolOutputRegistry(): ToolOutputReferenceRegistry | undefined;
|
|
34
80
|
/**
|
|
35
81
|
* Returns cached programmatic tools, computing once on first access.
|
|
36
82
|
* Single iteration builds both toolMap and toolDefs simultaneously.
|
|
@@ -42,9 +88,36 @@ export declare class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
42
88
|
*/
|
|
43
89
|
getToolUsageCounts(): ReadonlyMap<string, number>;
|
|
44
90
|
/**
|
|
45
|
-
* Runs a single tool call with error handling
|
|
91
|
+
* Runs a single tool call with error handling.
|
|
92
|
+
*
|
|
93
|
+
* `batchIndex` is the tool's position within the current ToolNode
|
|
94
|
+
* batch and, together with `this.currentTurn`, forms the key used to
|
|
95
|
+
* register the output for future `{{tool<idx>turn<turn>}}`
|
|
96
|
+
* substitutions. Omit when no registration should occur.
|
|
46
97
|
*/
|
|
47
|
-
protected runTool(call: ToolCall, config: RunnableConfig): Promise<BaseMessage | Command>;
|
|
98
|
+
protected runTool(call: ToolCall, config: RunnableConfig, batchContext?: RunToolBatchContext): Promise<BaseMessage | Command>;
|
|
99
|
+
/**
|
|
100
|
+
* Finalizes the LLM-visible content for a tool call and (when a
|
|
101
|
+
* `refKey` is provided) registers the full, raw output under that
|
|
102
|
+
* key.
|
|
103
|
+
*
|
|
104
|
+
* @param llmContent The content string the LLM will see. This is
|
|
105
|
+
* the already-truncated, post-hook view; the annotation is
|
|
106
|
+
* applied on top of it.
|
|
107
|
+
* @param registryContent The full, untruncated output to store in
|
|
108
|
+
* the registry so `{{tool<i>turn<n>}}` substitutions deliver the
|
|
109
|
+
* complete payload. Ignored when `refKey` is undefined.
|
|
110
|
+
* @param refKey Precomputed `tool<i>turn<n>` key, or undefined when
|
|
111
|
+
* the output is not to be registered (errors, disabled feature,
|
|
112
|
+
* unavailable batch/turn).
|
|
113
|
+
* @param unresolved Placeholder keys that did not resolve; appended
|
|
114
|
+
* as `[unresolved refs: …]` so the LLM can self-correct.
|
|
115
|
+
*
|
|
116
|
+
* `refKey` is passed in (rather than built from `this.currentTurn`)
|
|
117
|
+
* so parallel `invoke()` calls on the same ToolNode cannot race on
|
|
118
|
+
* the shared turn field.
|
|
119
|
+
*/
|
|
120
|
+
private applyOutputReference;
|
|
48
121
|
/**
|
|
49
122
|
* Builds code session context for injection into event-driven tool calls.
|
|
50
123
|
* Mirrors the session injection logic in runTool() for direct execution.
|
|
@@ -63,6 +136,10 @@ export declare class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
63
136
|
* By handling completions here in graph context (rather than in the
|
|
64
137
|
* stream consumer via ToolEndHandler), the race between the stream
|
|
65
138
|
* consumer and graph execution is eliminated.
|
|
139
|
+
*
|
|
140
|
+
* @param resolvedArgsByCallId Per-batch resolved-args sink populated
|
|
141
|
+
* by `runTool`. Threaded as a local map (instead of instance state)
|
|
142
|
+
* so concurrent batches cannot read each other's entries.
|
|
66
143
|
*/
|
|
67
144
|
private handleRunToolCompletions;
|
|
68
145
|
/**
|
|
@@ -93,6 +170,11 @@ export declare class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
93
170
|
* Injected messages are placed AFTER ToolMessages to respect provider
|
|
94
171
|
* message ordering (AIMessage tool_calls must be immediately followed
|
|
95
172
|
* by their ToolMessage results).
|
|
173
|
+
*
|
|
174
|
+
* `batchIndices` mirrors `toolCalls` and carries each call's position
|
|
175
|
+
* within the parent batch. `turn` is the per-`run()` batch index
|
|
176
|
+
* captured locally by the caller. Both are threaded so concurrent
|
|
177
|
+
* invocations cannot race on shared mutable state.
|
|
96
178
|
*/
|
|
97
179
|
private executeViaEvent;
|
|
98
180
|
protected run(input: any, config: RunnableConfig): Promise<T>;
|
|
@@ -100,3 +182,4 @@ export declare class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
100
182
|
private isMessagesState;
|
|
101
183
|
}
|
|
102
184
|
export declare function toolsCondition<T extends string>(state: BaseMessage[] | typeof MessagesAnnotation.State, toolNode: T, invokedToolIds?: Set<string>): T | typeof END;
|
|
185
|
+
export {};
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool output reference registry.
|
|
3
|
+
*
|
|
4
|
+
* When enabled via `RunConfig.toolOutputReferences.enabled`, ToolNode
|
|
5
|
+
* stores each successful tool output under a stable key
|
|
6
|
+
* (`tool<idx>turn<turn>`) where `idx` is the tool's position within a
|
|
7
|
+
* ToolNode batch and `turn` is the batch index within the run
|
|
8
|
+
* (incremented once per ToolNode invocation).
|
|
9
|
+
*
|
|
10
|
+
* Subsequent tool calls can pipe a previous output into their args by
|
|
11
|
+
* embedding `{{tool<idx>turn<turn>}}` inside any string argument;
|
|
12
|
+
* {@link ToolOutputReferenceRegistry.resolve} walks the args and
|
|
13
|
+
* substitutes the placeholders immediately before invocation.
|
|
14
|
+
*
|
|
15
|
+
* The registry stores the *raw, untruncated* tool output so a later
|
|
16
|
+
* `{{…}}` substitution pipes the full payload into the next tool —
|
|
17
|
+
* even when the LLM only saw a head+tail-truncated preview in
|
|
18
|
+
* `ToolMessage.content`. Outputs are stored without any annotation
|
|
19
|
+
* (the `_ref` key or the `[ref: ...]` prefix seen by the LLM is
|
|
20
|
+
* strictly a UX signal attached to `ToolMessage.content`). Keeping the
|
|
21
|
+
* registry pristine means downstream bash/jq piping receives the
|
|
22
|
+
* complete, verbatim output with no injected fields.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Non-global matcher for a single `{{tool<i>turn<n>}}` placeholder.
|
|
26
|
+
* Exported for consumers that want to detect references (e.g., syntax
|
|
27
|
+
* highlighting, docs). The stateful `g` variant lives inside the
|
|
28
|
+
* registry so nobody trips on `lastIndex`.
|
|
29
|
+
*/
|
|
30
|
+
export declare const TOOL_OUTPUT_REF_PATTERN: RegExp;
|
|
31
|
+
/** Object key used when a parsed-object output has `_ref` injected. */
|
|
32
|
+
export declare const TOOL_OUTPUT_REF_KEY = "_ref";
|
|
33
|
+
/**
|
|
34
|
+
* Object key used to carry unresolved reference warnings on a parsed-
|
|
35
|
+
* object output. Using a dedicated field instead of a trailing text
|
|
36
|
+
* line keeps the annotated `ToolMessage.content` parseable as JSON for
|
|
37
|
+
* downstream consumers that rely on the object shape.
|
|
38
|
+
*/
|
|
39
|
+
export declare const TOOL_OUTPUT_UNRESOLVED_KEY = "_unresolved_refs";
|
|
40
|
+
/** Single-line prefix prepended to non-object tool outputs so the LLM sees the reference key. */
|
|
41
|
+
export declare function buildReferencePrefix(key: string): string;
|
|
42
|
+
/** Stable registry key for a tool output. */
|
|
43
|
+
export declare function buildReferenceKey(toolIndex: number, turn: number): string;
|
|
44
|
+
export type ToolOutputReferenceRegistryOptions = {
|
|
45
|
+
/** Maximum characters stored per registered output. */
|
|
46
|
+
maxOutputSize?: number;
|
|
47
|
+
/** Maximum total characters retained across all registered outputs. */
|
|
48
|
+
maxTotalSize?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Upper bound on the number of concurrently-tracked runs. When
|
|
51
|
+
* exceeded, the oldest run bucket is evicted (FIFO). Defaults to 32.
|
|
52
|
+
*/
|
|
53
|
+
maxActiveRuns?: number;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Result of resolving placeholders in tool args.
|
|
57
|
+
*/
|
|
58
|
+
export type ResolveResult<T> = {
|
|
59
|
+
/** Arguments with placeholders replaced. Same shape as the input. */
|
|
60
|
+
resolved: T;
|
|
61
|
+
/** Reference keys that were referenced but had no stored value. */
|
|
62
|
+
unresolved: string[];
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Read-only view over a frozen registry snapshot. Returned by
|
|
66
|
+
* {@link ToolOutputReferenceRegistry.snapshot} for callers that need
|
|
67
|
+
* to resolve placeholders against the registry state at a specific
|
|
68
|
+
* point in time, ignoring any subsequent registrations.
|
|
69
|
+
*/
|
|
70
|
+
export interface ToolOutputResolveView {
|
|
71
|
+
resolve<T>(args: T): ResolveResult<T>;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Pre-resolved arg map keyed by `toolCallId`. Used by the mixed
|
|
75
|
+
* direct+event dispatch path to feed event calls' resolved args
|
|
76
|
+
* (captured pre-batch) into the dispatcher without re-resolving
|
|
77
|
+
* against the now-stale live registry.
|
|
78
|
+
*/
|
|
79
|
+
export type PreResolvedArgsMap = Map<string, {
|
|
80
|
+
resolved: Record<string, unknown>;
|
|
81
|
+
unresolved: string[];
|
|
82
|
+
}>;
|
|
83
|
+
/**
|
|
84
|
+
* Per-call sink for resolved args, keyed by `toolCallId`. Threaded
|
|
85
|
+
* as a per-batch local map so concurrent `ToolNode.run()` calls do
|
|
86
|
+
* not race on shared sink state.
|
|
87
|
+
*/
|
|
88
|
+
export type ResolvedArgsByCallId = Map<string, Record<string, unknown>>;
|
|
89
|
+
/**
|
|
90
|
+
* Ordered map of reference-key → stored output, partitioned by run so
|
|
91
|
+
* concurrent / interleaved runs sharing one registry cannot leak
|
|
92
|
+
* outputs between each other.
|
|
93
|
+
*
|
|
94
|
+
* Each public method takes a `runId` which selects the run's bucket.
|
|
95
|
+
* Hosts typically get one registry per run via `Graph`, in which
|
|
96
|
+
* case only a single bucket is ever populated; the partitioning
|
|
97
|
+
* exists so the registry also behaves correctly when a single
|
|
98
|
+
* instance is reused directly.
|
|
99
|
+
*/
|
|
100
|
+
export declare class ToolOutputReferenceRegistry {
|
|
101
|
+
private runStates;
|
|
102
|
+
private readonly maxOutputSize;
|
|
103
|
+
private readonly maxTotalSize;
|
|
104
|
+
private readonly maxActiveRuns;
|
|
105
|
+
/**
|
|
106
|
+
* Local stateful matcher used only by `replaceInString`. Kept
|
|
107
|
+
* off-module so callers of the exported `TOOL_OUTPUT_REF_PATTERN`
|
|
108
|
+
* never see a stale `lastIndex`.
|
|
109
|
+
*/
|
|
110
|
+
private static readonly PLACEHOLDER_MATCHER;
|
|
111
|
+
constructor(options?: ToolOutputReferenceRegistryOptions);
|
|
112
|
+
private keyFor;
|
|
113
|
+
private getOrCreate;
|
|
114
|
+
/** Registers (or replaces) the output stored under `key` for `runId`. */
|
|
115
|
+
set(runId: string | undefined, key: string, value: string): void;
|
|
116
|
+
/** Returns the stored value for `key` in `runId`'s bucket, or `undefined`. */
|
|
117
|
+
get(runId: string | undefined, key: string): string | undefined;
|
|
118
|
+
/** Total number of registered outputs across every run bucket. */
|
|
119
|
+
get size(): number;
|
|
120
|
+
/** Maximum characters retained per output (post-clip). */
|
|
121
|
+
get perOutputLimit(): number;
|
|
122
|
+
/** Maximum total characters retained *per run*. */
|
|
123
|
+
get totalLimit(): number;
|
|
124
|
+
/** Drops every run's state. */
|
|
125
|
+
clear(): void;
|
|
126
|
+
/**
|
|
127
|
+
* Explicitly release `runId`'s state. Safe to call when a run has
|
|
128
|
+
* finished. Hosts sharing one registry across runs should call this
|
|
129
|
+
* to reclaim memory deterministically; otherwise LRU eviction kicks
|
|
130
|
+
* in when `maxActiveRuns` runs accumulate.
|
|
131
|
+
*/
|
|
132
|
+
releaseRun(runId: string | undefined): void;
|
|
133
|
+
/**
|
|
134
|
+
* Claims the next batch turn synchronously from `runId`'s bucket.
|
|
135
|
+
*
|
|
136
|
+
* Must be called once at the start of each ToolNode batch before
|
|
137
|
+
* any `await`, so concurrent invocations within the same run see
|
|
138
|
+
* distinct turn values (reads are effectively atomic by JS's
|
|
139
|
+
* single-threaded execution of the sync prefix).
|
|
140
|
+
*
|
|
141
|
+
* If `runId` is missing the anonymous bucket is dropped and a
|
|
142
|
+
* fresh one created so each anonymous call behaves as its own run.
|
|
143
|
+
*/
|
|
144
|
+
nextTurn(runId: string | undefined): number;
|
|
145
|
+
/**
|
|
146
|
+
* Records that `toolName` has been warned about in `runId` (returns
|
|
147
|
+
* `true` on the first call per run, `false` after). Used by
|
|
148
|
+
* ToolNode to emit one log line per offending tool per run when a
|
|
149
|
+
* `ToolMessage.content` isn't a string.
|
|
150
|
+
*/
|
|
151
|
+
claimWarnOnce(runId: string | undefined, toolName: string): boolean;
|
|
152
|
+
/**
|
|
153
|
+
* Walks `args` and replaces every `{{tool<i>turn<n>}}` placeholder in
|
|
154
|
+
* string values with the stored output *from `runId`'s bucket*. Non-
|
|
155
|
+
* string values and object keys are left untouched. Unresolved
|
|
156
|
+
* references are left in-place and reported so the caller can
|
|
157
|
+
* surface them to the LLM. When no placeholder appears anywhere in
|
|
158
|
+
* the serialized args, the original input is returned without
|
|
159
|
+
* walking the tree.
|
|
160
|
+
*/
|
|
161
|
+
resolve<T>(runId: string | undefined, args: T): ResolveResult<T>;
|
|
162
|
+
/**
|
|
163
|
+
* Captures a frozen snapshot of `runId`'s current entries and
|
|
164
|
+
* returns a view that resolves placeholders against *only* that
|
|
165
|
+
* snapshot. The snapshot is decoupled from the live registry, so
|
|
166
|
+
* subsequent `set()` calls (for example, same-turn direct outputs
|
|
167
|
+
* registering while an event branch is still in flight) are
|
|
168
|
+
* invisible to the snapshot's `resolve`. Used by the mixed
|
|
169
|
+
* direct+event dispatch path to preserve same-turn isolation when
|
|
170
|
+
* a `PreToolUse` hook rewrites event args after directs have
|
|
171
|
+
* completed.
|
|
172
|
+
*/
|
|
173
|
+
snapshot(runId: string | undefined): ToolOutputResolveView;
|
|
174
|
+
private resolveAgainst;
|
|
175
|
+
private transform;
|
|
176
|
+
private replaceInString;
|
|
177
|
+
private evictWithinBucket;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Annotates `content` with a reference key and/or unresolved-ref
|
|
181
|
+
* warnings so the LLM sees both alongside the tool output.
|
|
182
|
+
*
|
|
183
|
+
* Behavior:
|
|
184
|
+
* - If `content` parses as a plain (non-array, non-null) JSON object
|
|
185
|
+
* and the object does not already have a conflicting `_ref` key,
|
|
186
|
+
* the reference key and (when present) `_unresolved_refs` array
|
|
187
|
+
* are injected as object fields, preserving JSON validity for
|
|
188
|
+
* downstream consumers that parse the output.
|
|
189
|
+
* - Otherwise (string output, JSON array/primitive, parse failure,
|
|
190
|
+
* or `_ref` collision), a `[ref: <key>]\n` prefix line is
|
|
191
|
+
* prepended and unresolved refs are appended as a trailing
|
|
192
|
+
* `[unresolved refs: …]` line.
|
|
193
|
+
*
|
|
194
|
+
* The annotated string is what the LLM sees as `ToolMessage.content`.
|
|
195
|
+
* The *original* (un-annotated) value is what gets stored in the
|
|
196
|
+
* registry, so downstream piping remains pristine.
|
|
197
|
+
*
|
|
198
|
+
* @param content Raw (post-truncation) tool output.
|
|
199
|
+
* @param key Reference key for this output, or undefined when
|
|
200
|
+
* there is nothing to register (errors etc.).
|
|
201
|
+
* @param unresolved Reference keys that failed to resolve during
|
|
202
|
+
* argument substitution. Surfaced so the LLM can
|
|
203
|
+
* self-correct its next tool call.
|
|
204
|
+
*/
|
|
205
|
+
export declare function annotateToolOutputWithReference(content: string, key: string | undefined, unresolved?: string[]): string;
|
|
@@ -7,7 +7,7 @@ import type * as s from '@/types/stream';
|
|
|
7
7
|
import type * as e from '@/common/enum';
|
|
8
8
|
import type * as g from '@/types/graph';
|
|
9
9
|
import type * as l from '@/types/llm';
|
|
10
|
-
import type { ToolSessionMap } from '@/types/tools';
|
|
10
|
+
import type { ToolSessionMap, ToolOutputReferencesConfig } from '@/types/tools';
|
|
11
11
|
import type { HookRegistry } from '@/hooks';
|
|
12
12
|
export type ZodObjectAny = z.ZodObject<any, any, any, any>;
|
|
13
13
|
export type BaseGraphConfig = {
|
|
@@ -134,6 +134,14 @@ export type RunConfig = {
|
|
|
134
134
|
* at run start, so ToolNode can inject session_id + files into tool calls.
|
|
135
135
|
*/
|
|
136
136
|
initialSessions?: ToolSessionMap;
|
|
137
|
+
/**
|
|
138
|
+
* Run-scoped tool output reference configuration. When `enabled` is
|
|
139
|
+
* `true`, tool outputs are registered under stable keys
|
|
140
|
+
* (`tool<idx>turn<turn>`) and subsequent tool calls can pipe previous
|
|
141
|
+
* outputs into their arguments via `{{tool<idx>turn<turn>}}`
|
|
142
|
+
* placeholders. Disabled by default so existing runs are unaffected.
|
|
143
|
+
*/
|
|
144
|
+
toolOutputReferences?: ToolOutputReferencesConfig;
|
|
137
145
|
};
|
|
138
146
|
export type ProvidedCallbacks = (BaseCallbackHandler | CallbackHandlerMethods)[] | undefined;
|
|
139
147
|
export type TokenCounter = (message: BaseMessage) => number;
|
|
@@ -2,6 +2,7 @@ import type { StructuredToolInterface } from '@langchain/core/tools';
|
|
|
2
2
|
import type { RunnableToolLike } from '@langchain/core/runnables';
|
|
3
3
|
import type { ToolCall } from '@langchain/core/messages/tool';
|
|
4
4
|
import type { HookRegistry } from '@/hooks';
|
|
5
|
+
import type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
|
|
5
6
|
import type { MessageContentComplex, ToolErrorData } from './stream';
|
|
6
7
|
/** Replacement type for `import type { ToolCall } from '@langchain/core/messages/tool'` in order to have stringified args typed */
|
|
7
8
|
export type CustomToolCall = {
|
|
@@ -52,6 +53,22 @@ export type ToolNodeOptions = {
|
|
|
52
53
|
* When provided, takes precedence over the value computed from maxContextTokens.
|
|
53
54
|
*/
|
|
54
55
|
maxToolResultChars?: number;
|
|
56
|
+
/**
|
|
57
|
+
* Run-scoped tool output reference configuration. When `enabled` is
|
|
58
|
+
* `true`, ToolNode registers successful outputs and substitutes
|
|
59
|
+
* `{{tool<idx>turn<turn>}}` placeholders found in string args.
|
|
60
|
+
*
|
|
61
|
+
* Ignored when `toolOutputRegistry` is also provided (host-supplied
|
|
62
|
+
* registry wins).
|
|
63
|
+
*/
|
|
64
|
+
toolOutputReferences?: ToolOutputReferencesConfig;
|
|
65
|
+
/**
|
|
66
|
+
* Pre-constructed registry instance shared across ToolNodes for the
|
|
67
|
+
* run. Graphs pass the same registry to every ToolNode they compile
|
|
68
|
+
* so cross-agent `{{tool<i>turn<n>}}` substitutions resolve. Takes
|
|
69
|
+
* precedence over `toolOutputReferences` when both are set.
|
|
70
|
+
*/
|
|
71
|
+
toolOutputRegistry?: ToolOutputReferenceRegistry;
|
|
55
72
|
};
|
|
56
73
|
export type ToolNodeConstructorParams = ToolRefs & ToolNodeOptions;
|
|
57
74
|
export type ToolEndEvent = {
|
|
@@ -199,6 +216,59 @@ export type ToolExecuteResult = {
|
|
|
199
216
|
};
|
|
200
217
|
/** Map of tool names to tool definitions */
|
|
201
218
|
export type LCToolRegistry = Map<string, LCTool>;
|
|
219
|
+
/**
|
|
220
|
+
* Run-scoped configuration for tool output references.
|
|
221
|
+
*
|
|
222
|
+
* When enabled, each successful tool result is registered under a stable
|
|
223
|
+
* key (`tool<idx>turn<turn>`). Later tool calls can pipe a previous
|
|
224
|
+
* output into their arguments by including the literal placeholder
|
|
225
|
+
* `{{tool<idx>turn<turn>}}` anywhere in a string argument; ToolNode
|
|
226
|
+
* substitutes it with the stored output immediately before invoking
|
|
227
|
+
* the tool.
|
|
228
|
+
*
|
|
229
|
+
* The registry stores the *raw, untruncated* tool output (subject to
|
|
230
|
+
* its own size caps) so a later substitution can pipe the full payload
|
|
231
|
+
* into the next tool even when the LLM only saw a head+tail-truncated
|
|
232
|
+
* preview in `ToolMessage.content`. Size limits are decoupled from the
|
|
233
|
+
* LLM-visible truncation budget and default to 5 MB total.
|
|
234
|
+
*
|
|
235
|
+
* Known limitations:
|
|
236
|
+
* - Tools that return a `ToolMessage` with array-type content
|
|
237
|
+
* (multi-part content blocks such as text + image) are not
|
|
238
|
+
* registered and cannot be cited via `{{tool<i>turn<n>}}`. A
|
|
239
|
+
* warning is logged so the missing reference is visible.
|
|
240
|
+
* - When a `PostToolUse` hook replaces `ToolMessage.content`, the
|
|
241
|
+
* *post-hook* content is what gets stored in the registry (and
|
|
242
|
+
* what the model sees), so `{{…}}` substitutions deliver the
|
|
243
|
+
* hooked output rather than the raw tool return. This matches the
|
|
244
|
+
* hook's "authoritative" role for output shaping.
|
|
245
|
+
*/
|
|
246
|
+
export type ToolOutputReferencesConfig = {
|
|
247
|
+
/** Enable the registry and placeholder substitution. Defaults to `false`. */
|
|
248
|
+
enabled?: boolean;
|
|
249
|
+
/**
|
|
250
|
+
* Maximum characters stored (and substituted) per registered output.
|
|
251
|
+
* Applied to the *raw* output before storage. Defaults to
|
|
252
|
+
* `HARD_MAX_TOOL_RESULT_CHARS` (~400 KB) — matching the
|
|
253
|
+
* LLM-visible tool-result truncation budget, which is also a safe
|
|
254
|
+
* payload size for shell `ARG_MAX` limits when a `{{…}}` expansion
|
|
255
|
+
* gets piped into a bash `command`. Hosts that want to preserve
|
|
256
|
+
* fuller fidelity (for example for non-bash API consumers) can
|
|
257
|
+
* raise this up to `maxTotalSize` (defaults to 5 MB) — be aware
|
|
258
|
+
* that large single-output substitutions may exceed shell
|
|
259
|
+
* argument-size limits on typical Linux/macOS.
|
|
260
|
+
*/
|
|
261
|
+
maxOutputSize?: number;
|
|
262
|
+
/**
|
|
263
|
+
* Hard cap on total characters retained across all registered outputs
|
|
264
|
+
* for the run. When exceeded, the oldest entries are evicted FIFO
|
|
265
|
+
* until the total fits. The effective per-output cap is
|
|
266
|
+
* `min(maxOutputSize, maxTotalSize)` so a single stored output can
|
|
267
|
+
* never exceed the aggregate bound. Defaults to
|
|
268
|
+
* `calculateMaxTotalToolOutputSize(maxOutputSize)` (5 MB).
|
|
269
|
+
*/
|
|
270
|
+
maxTotalSize?: number;
|
|
271
|
+
};
|
|
202
272
|
export type ProgrammaticCache = {
|
|
203
273
|
toolMap: ToolMap;
|
|
204
274
|
toolDefs: LCTool[];
|
|
@@ -10,6 +10,15 @@
|
|
|
10
10
|
* larger than this is almost certainly a bug (e.g., dumping a binary file).
|
|
11
11
|
*/
|
|
12
12
|
export declare const HARD_MAX_TOOL_RESULT_CHARS = 400000;
|
|
13
|
+
/**
|
|
14
|
+
* Absolute hard cap on the aggregate size (characters) of all registered
|
|
15
|
+
* tool outputs kept for `{{tool<i>turn<n>}}` substitution. Set at 5 MB
|
|
16
|
+
* because the registry stores *raw, untruncated* tool output — full
|
|
17
|
+
* fidelity for piping into downstream bash/jq — so the budget needs
|
|
18
|
+
* enough headroom to keep a handful of large responses without
|
|
19
|
+
* ballooning unbounded.
|
|
20
|
+
*/
|
|
21
|
+
export declare const HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE = 5000000;
|
|
13
22
|
/**
|
|
14
23
|
* Computes the dynamic max tool result size based on the model's context window.
|
|
15
24
|
* Uses 30% of the context window (in estimated characters, ~4 chars/token)
|
|
@@ -19,6 +28,18 @@ export declare const HARD_MAX_TOOL_RESULT_CHARS = 400000;
|
|
|
19
28
|
* @returns Maximum allowed characters for a single tool result.
|
|
20
29
|
*/
|
|
21
30
|
export declare function calculateMaxToolResultChars(contextWindowTokens?: number): number;
|
|
31
|
+
/**
|
|
32
|
+
* Computes the default aggregate size (characters) for the tool output
|
|
33
|
+
* reference registry based on the per-output budget. Mirrors
|
|
34
|
+
* `calculateMaxToolResultChars`'s shape: a multiple of the per-output
|
|
35
|
+
* cap, clamped to `HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE`.
|
|
36
|
+
*
|
|
37
|
+
* @param maxOutputSize - Per-output maximum characters (e.g., the
|
|
38
|
+
* ToolNode's `maxToolResultChars`). When omitted or non-positive,
|
|
39
|
+
* falls back to the absolute total cap.
|
|
40
|
+
* @returns Maximum total characters retained across the registry.
|
|
41
|
+
*/
|
|
42
|
+
export declare function calculateMaxTotalToolOutputSize(maxOutputSize?: number): number;
|
|
22
43
|
/**
|
|
23
44
|
* Truncates a tool-call input (the arguments/payload of a tool_use block)
|
|
24
45
|
* using head+tail strategy. Returns an object with `_truncated` (the
|
package/package.json
CHANGED
package/src/graphs/Graph.ts
CHANGED
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
import { SubagentExecutor, resolveSubagentConfigs } from '@/tools/subagent';
|
|
44
44
|
import { buildSubagentToolParams } from '@/tools/SubagentTool';
|
|
45
45
|
import { ToolNode as CustomToolNode, toolsCondition } from '@/tools/ToolNode';
|
|
46
|
+
import { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
|
|
46
47
|
import { safeDispatchCustomEvent, emitAgentLog } from '@/utils/events';
|
|
47
48
|
import { attemptInvoke, tryFallbackProviders } from '@/llm/invoke';
|
|
48
49
|
import { shouldTriggerSummarization } from '@/summarization';
|
|
@@ -128,6 +129,19 @@ export abstract class Graph<
|
|
|
128
129
|
invokedToolIds?: Set<string>;
|
|
129
130
|
handlerRegistry: HandlerRegistry | undefined;
|
|
130
131
|
hookRegistry: HookRegistry | undefined;
|
|
132
|
+
/**
|
|
133
|
+
* Run-scoped config for the tool output reference registry. Threaded
|
|
134
|
+
* from `RunConfig.toolOutputReferences` down into every ToolNode this
|
|
135
|
+
* graph compiles.
|
|
136
|
+
*/
|
|
137
|
+
toolOutputReferences: t.ToolOutputReferencesConfig | undefined;
|
|
138
|
+
/**
|
|
139
|
+
* Shared registry instance used by every ToolNode compiled from this
|
|
140
|
+
* graph. Lazily constructed on first access so multi-agent graphs
|
|
141
|
+
* produce one registry per run (not one per agent), letting cross-
|
|
142
|
+
* agent `{{tool<i>turn<n>}}` substitutions resolve.
|
|
143
|
+
*/
|
|
144
|
+
private _toolOutputRegistry?: ToolOutputReferenceRegistry;
|
|
131
145
|
/**
|
|
132
146
|
* Tool session contexts for automatic state persistence across tool invocations.
|
|
133
147
|
* Keyed by tool name (e.g., Constants.EXECUTE_CODE).
|
|
@@ -153,8 +167,40 @@ export abstract class Graph<
|
|
|
153
167
|
this.invokedToolIds = undefined;
|
|
154
168
|
this.handlerRegistry = undefined;
|
|
155
169
|
this.hookRegistry = undefined;
|
|
170
|
+
this.toolOutputReferences = undefined;
|
|
171
|
+
/**
|
|
172
|
+
* ToolNodes compiled from this graph captured the registry
|
|
173
|
+
* instance at construction time, so simply dropping the Graph's
|
|
174
|
+
* own reference would leave their captured reference — and every
|
|
175
|
+
* stored `tool<i>turn<n>` entry, plus up to `maxTotalSize` of raw
|
|
176
|
+
* output — alive across subsequent `processStream()` calls. Wipe
|
|
177
|
+
* the registry's contents first so subsequent runs start fresh.
|
|
178
|
+
*/
|
|
179
|
+
this._toolOutputRegistry?.clear();
|
|
180
|
+
this._toolOutputRegistry = undefined;
|
|
156
181
|
this.sessions.clear();
|
|
157
182
|
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Returns the shared `ToolOutputReferenceRegistry` for this run,
|
|
186
|
+
* constructing it on first access. Returns `undefined` when the
|
|
187
|
+
* feature is disabled. All ToolNodes compiled from this graph share
|
|
188
|
+
* this single instance so cross-agent `{{…}}` references resolve.
|
|
189
|
+
*/
|
|
190
|
+
protected getOrCreateToolOutputRegistry():
|
|
191
|
+
| ToolOutputReferenceRegistry
|
|
192
|
+
| undefined {
|
|
193
|
+
if (this.toolOutputReferences?.enabled !== true) {
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
if (this._toolOutputRegistry == null) {
|
|
197
|
+
this._toolOutputRegistry = new ToolOutputReferenceRegistry({
|
|
198
|
+
maxOutputSize: this.toolOutputReferences.maxOutputSize,
|
|
199
|
+
maxTotalSize: this.toolOutputReferences.maxTotalSize,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
return this._toolOutputRegistry;
|
|
203
|
+
}
|
|
158
204
|
}
|
|
159
205
|
|
|
160
206
|
export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
@@ -516,6 +562,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
516
562
|
directToolNames: directToolNames.size > 0 ? directToolNames : undefined,
|
|
517
563
|
maxContextTokens: agentContext?.maxContextTokens,
|
|
518
564
|
maxToolResultChars: agentContext?.maxToolResultChars,
|
|
565
|
+
toolOutputRegistry: this.getOrCreateToolOutputRegistry(),
|
|
519
566
|
errorHandler: (data, metadata) =>
|
|
520
567
|
StandardGraph.handleToolCallErrorStatic(this, data, metadata),
|
|
521
568
|
});
|
|
@@ -547,6 +594,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
547
594
|
sessions: this.sessions,
|
|
548
595
|
maxContextTokens: agentContext?.maxContextTokens,
|
|
549
596
|
maxToolResultChars: agentContext?.maxToolResultChars,
|
|
597
|
+
toolOutputRegistry: this.getOrCreateToolOutputRegistry(),
|
|
550
598
|
});
|
|
551
599
|
}
|
|
552
600
|
|
package/src/messages/prune.ts
CHANGED
|
@@ -683,10 +683,17 @@ export function getMessagesWithinTokenLimit({
|
|
|
683
683
|
) as ThinkingContentText | undefined;
|
|
684
684
|
thinkingStartIndex = thinkingBlock != null ? currentIndex : -1;
|
|
685
685
|
}
|
|
686
|
-
/**
|
|
686
|
+
/**
|
|
687
|
+
* Exited the trailing assistant/tool sequence without finding a
|
|
688
|
+
* thinking block. Anthropic does not require Claude to emit a
|
|
689
|
+
* thinking block before every tool call, so the absence of one is
|
|
690
|
+
* a valid sequence — clear thinkingEndIndex so the pruner does not
|
|
691
|
+
* treat it as malformed.
|
|
692
|
+
*/
|
|
687
693
|
if (
|
|
688
694
|
thinkingEndIndex > -1 &&
|
|
689
|
-
|
|
695
|
+
thinkingStartIndex < 0 &&
|
|
696
|
+
!thinkingBlock &&
|
|
690
697
|
messageType !== 'ai' &&
|
|
691
698
|
messageType !== 'tool'
|
|
692
699
|
) {
|