@librechat/agents 3.3.5 → 3.3.7
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/hooks/createWorkspacePolicyHook.cjs +1 -1
- package/dist/cjs/llm/openai/index.cjs +51 -6
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +16 -0
- package/dist/cjs/session/JsonlSessionStore.cjs +1 -1
- package/dist/cjs/stream.cjs +6 -1
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +3 -1
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +2 -0
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +19 -16
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +2 -0
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ReadFile.cjs +8 -4
- package/dist/cjs/tools/ReadFile.cjs.map +1 -1
- package/dist/cjs/tools/SkillTool.cjs +2 -0
- package/dist/cjs/tools/SkillTool.cjs.map +1 -1
- package/dist/cjs/tools/SubagentTool.cjs +3 -0
- package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +88 -27
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/ToolSearch.cjs +5 -1
- package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
- package/dist/cjs/tools/intentArg.cjs +256 -0
- package/dist/cjs/tools/intentArg.cjs.map +1 -0
- package/dist/cjs/tools/local/CompileCheckTool.cjs +3 -2
- package/dist/cjs/tools/local/CompileCheckTool.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalCodingTools.cjs +13 -12
- package/dist/cjs/tools/local/LocalCodingTools.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalExecutionEngine.cjs +2 -2
- package/dist/cjs/tools/ptcTimeout.cjs.map +1 -1
- package/dist/cjs/tools/search/schema.cjs +2 -0
- package/dist/cjs/tools/search/schema.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +31 -1
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +1 -0
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/toolOutputReferences.cjs +41 -7
- package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
- package/dist/esm/hooks/createWorkspacePolicyHook.mjs +1 -1
- package/dist/esm/llm/openai/index.mjs +51 -6
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/main.mjs +4 -3
- package/dist/esm/session/JsonlSessionStore.mjs +1 -1
- package/dist/esm/stream.mjs +6 -1
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +3 -1
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +2 -0
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +19 -16
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +2 -0
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ReadFile.mjs +8 -4
- package/dist/esm/tools/ReadFile.mjs.map +1 -1
- package/dist/esm/tools/SkillTool.mjs +2 -0
- package/dist/esm/tools/SkillTool.mjs.map +1 -1
- package/dist/esm/tools/SubagentTool.mjs +3 -0
- package/dist/esm/tools/SubagentTool.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +88 -28
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/ToolSearch.mjs +5 -1
- package/dist/esm/tools/ToolSearch.mjs.map +1 -1
- package/dist/esm/tools/intentArg.mjs +244 -0
- package/dist/esm/tools/intentArg.mjs.map +1 -0
- package/dist/esm/tools/local/CompileCheckTool.mjs +3 -2
- package/dist/esm/tools/local/CompileCheckTool.mjs.map +1 -1
- package/dist/esm/tools/local/LocalCodingTools.mjs +13 -12
- package/dist/esm/tools/local/LocalCodingTools.mjs.map +1 -1
- package/dist/esm/tools/local/LocalExecutionEngine.mjs +2 -2
- package/dist/esm/tools/ptcTimeout.mjs.map +1 -1
- package/dist/esm/tools/search/schema.mjs +2 -0
- package/dist/esm/tools/search/schema.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +31 -2
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +1 -0
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/toolOutputReferences.mjs +41 -7
- package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/llm/openai/index.d.ts +0 -11
- package/dist/types/tools/BashExecutor.d.ts +18 -0
- package/dist/types/tools/CodeExecutor.d.ts +18 -0
- package/dist/types/tools/ReadFile.d.ts +18 -0
- package/dist/types/tools/SkillTool.d.ts +18 -0
- package/dist/types/tools/SubagentTool.d.ts +9 -0
- package/dist/types/tools/ToolNode.d.ts +37 -5
- package/dist/types/tools/ToolSearch.d.ts +18 -0
- package/dist/types/tools/intentArg.d.ts +181 -0
- package/dist/types/tools/ptcTimeout.d.ts +2 -0
- package/dist/types/tools/search/schema.d.ts +18 -0
- package/dist/types/tools/search/tool.d.ts +18 -0
- package/dist/types/tools/toolOutputReferences.d.ts +12 -2
- package/dist/types/types/stream.d.ts +18 -0
- package/dist/types/types/tools.d.ts +27 -2
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/llm/openai/index.ts +74 -10
- package/src/llm/openai/llm.spec.ts +105 -0
- package/src/stream.ts +12 -1
- package/src/tools/BashExecutor.ts +6 -0
- package/src/tools/BashProgrammaticToolCalling.ts +2 -0
- package/src/tools/CodeExecutor.ts +6 -0
- package/src/tools/ProgrammaticToolCalling.ts +2 -0
- package/src/tools/ReadFile.ts +2 -0
- package/src/tools/SkillTool.ts +2 -0
- package/src/tools/SubagentTool.ts +3 -0
- package/src/tools/ToolNode.ts +190 -43
- package/src/tools/ToolSearch.ts +3 -0
- package/src/tools/__tests__/ToolNode.onResultCompletion.test.ts +174 -0
- package/src/tools/__tests__/intentArg.test.ts +357 -0
- package/src/tools/__tests__/intentCoverage.test.ts +139 -0
- package/src/tools/__tests__/toolOutputReferences.test.ts +83 -0
- package/src/tools/intentArg.ts +357 -0
- package/src/tools/local/CompileCheckTool.ts +4 -3
- package/src/tools/local/LocalCodingTools.ts +14 -13
- package/src/tools/ptcTimeout.ts +2 -0
- package/src/tools/search/outcome.test.ts +90 -0
- package/src/tools/search/schema.ts +3 -0
- package/src/tools/search/tool.ts +47 -1
- package/src/tools/subagent/SubagentExecutor.ts +2 -1
- package/src/tools/toolOutputReferences.ts +77 -9
- package/src/types/stream.ts +18 -0
- package/src/types/tools.ts +24 -2
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
normalizeCodeApiRequestError,
|
|
17
17
|
resolveCodeApiAuthHeaders,
|
|
18
18
|
} from './CodeExecutor';
|
|
19
|
+
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
19
20
|
import { Constants } from '@/common';
|
|
20
21
|
|
|
21
22
|
config();
|
|
@@ -26,6 +27,7 @@ const EXEC_ENDPOINT = `${baseEndpoint}/exec`;
|
|
|
26
27
|
export const BashExecutionToolSchema = {
|
|
27
28
|
type: 'object',
|
|
28
29
|
properties: {
|
|
30
|
+
intent: { ...INTENT_PROPERTY },
|
|
29
31
|
command: {
|
|
30
32
|
type: 'string',
|
|
31
33
|
description: `The bash command or script to execute.
|
|
@@ -187,16 +189,20 @@ function createBashExecutionTool(
|
|
|
187
189
|
/* Drop any model-supplied `runtime_session_hint` from the raw args: the
|
|
188
190
|
* hint must only come from ToolNode's injected `_runtime_session_hint`
|
|
189
191
|
* (below), never from the tool call itself. */
|
|
192
|
+
/* `intent` is a UI display label — never part of the wire body. */
|
|
190
193
|
const {
|
|
191
194
|
command,
|
|
195
|
+
intent: _ignoredIntent,
|
|
192
196
|
runtime_session_hint: _ignoredModelHint,
|
|
193
197
|
...rest
|
|
194
198
|
} = rawInput as {
|
|
195
199
|
command: string;
|
|
200
|
+
intent?: unknown;
|
|
196
201
|
runtime_session_hint?: unknown;
|
|
197
202
|
args?: string[];
|
|
198
203
|
};
|
|
199
204
|
void _ignoredModelHint;
|
|
205
|
+
void _ignoredIntent;
|
|
200
206
|
const { session_id, _injected_files, _runtime_session_hint } =
|
|
201
207
|
(config.toolCall ?? {}) as {
|
|
202
208
|
session_id?: string;
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
executeTools,
|
|
22
22
|
formatCompletedResponse,
|
|
23
23
|
} from './ProgrammaticToolCalling';
|
|
24
|
+
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
24
25
|
import { Constants } from '@/common';
|
|
25
26
|
|
|
26
27
|
config();
|
|
@@ -115,6 +116,7 @@ export function createBashProgrammaticToolCallingSchema(
|
|
|
115
116
|
return {
|
|
116
117
|
type: 'object',
|
|
117
118
|
properties: {
|
|
119
|
+
intent: { ...INTENT_PROPERTY },
|
|
118
120
|
code: {
|
|
119
121
|
type: 'string',
|
|
120
122
|
minLength: 1,
|
|
@@ -5,6 +5,7 @@ import { getEnvironmentVariable } from '@langchain/core/utils/env';
|
|
|
5
5
|
import { tool, DynamicStructuredTool } from '@langchain/core/tools';
|
|
6
6
|
import type * as t from '@/types';
|
|
7
7
|
import { appendCodeSessionFileSummary } from '@/tools/CodeSessionFileSummary';
|
|
8
|
+
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
8
9
|
import { EnvVar, Constants } from '@/common';
|
|
9
10
|
|
|
10
11
|
export {
|
|
@@ -75,6 +76,7 @@ const SUPPORTED_LANGUAGES = [
|
|
|
75
76
|
export const CodeExecutionToolSchema = {
|
|
76
77
|
type: 'object',
|
|
77
78
|
properties: {
|
|
79
|
+
intent: { ...INTENT_PROPERTY },
|
|
78
80
|
lang: {
|
|
79
81
|
type: 'string',
|
|
80
82
|
enum: SUPPORTED_LANGUAGES,
|
|
@@ -346,18 +348,22 @@ function createCodeExecutionTool(
|
|
|
346
348
|
* injected `_runtime_session_hint` (below). Spreading `...rest` into
|
|
347
349
|
* postData would otherwise let a tool call opt itself into / pick a
|
|
348
350
|
* stateful runtime even when statefulSessions is off. */
|
|
351
|
+
/* `intent` is a UI display label — never part of the wire body. */
|
|
349
352
|
const {
|
|
350
353
|
lang,
|
|
351
354
|
code,
|
|
355
|
+
intent: _ignoredIntent,
|
|
352
356
|
runtime_session_hint: _ignoredModelHint,
|
|
353
357
|
...rest
|
|
354
358
|
} = rawInput as {
|
|
355
359
|
lang: SupportedLanguage;
|
|
356
360
|
code: string;
|
|
361
|
+
intent?: unknown;
|
|
357
362
|
runtime_session_hint?: unknown;
|
|
358
363
|
args?: string[];
|
|
359
364
|
};
|
|
360
365
|
void _ignoredModelHint;
|
|
366
|
+
void _ignoredIntent;
|
|
361
367
|
/**
|
|
362
368
|
* Extract session context from config.toolCall (injected by ToolNode).
|
|
363
369
|
* - session_id: associates with the previous run.
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
createCodeApiRunTimeoutSchema,
|
|
25
25
|
resolveCodeApiRunTimeoutMs,
|
|
26
26
|
} from './ptcTimeout';
|
|
27
|
+
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
27
28
|
import { Constants } from '@/common';
|
|
28
29
|
|
|
29
30
|
config();
|
|
@@ -89,6 +90,7 @@ export function createProgrammaticToolCallingSchema(
|
|
|
89
90
|
return {
|
|
90
91
|
type: 'object',
|
|
91
92
|
properties: {
|
|
93
|
+
intent: { ...INTENT_PROPERTY },
|
|
92
94
|
code: {
|
|
93
95
|
type: 'string',
|
|
94
96
|
minLength: 1,
|
package/src/tools/ReadFile.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/tools/ReadFile.ts
|
|
2
|
+
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
2
3
|
import { Constants } from '@/common';
|
|
3
4
|
|
|
4
5
|
export const ReadFileToolName = Constants.READ_FILE;
|
|
@@ -22,6 +23,7 @@ CONSTRAINTS:
|
|
|
22
23
|
export const ReadFileToolSchema = {
|
|
23
24
|
type: 'object',
|
|
24
25
|
properties: {
|
|
26
|
+
intent: { ...INTENT_PROPERTY },
|
|
25
27
|
path: {
|
|
26
28
|
type: 'string',
|
|
27
29
|
description:
|
package/src/tools/SkillTool.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/tools/SkillTool.ts
|
|
2
|
+
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
2
3
|
import { Constants } from '@/common';
|
|
3
4
|
|
|
4
5
|
export const SkillToolName = Constants.SKILL_TOOL;
|
|
@@ -26,6 +27,7 @@ CONSTRAINTS:
|
|
|
26
27
|
export const SkillToolSchema = {
|
|
27
28
|
type: 'object',
|
|
28
29
|
properties: {
|
|
30
|
+
intent: { ...INTENT_PROPERTY },
|
|
29
31
|
skillName: {
|
|
30
32
|
type: 'string',
|
|
31
33
|
description:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { JsonSchemaType, LCTool } from '@/types/tools';
|
|
2
2
|
import type { SubagentConfig } from '@/types';
|
|
3
|
+
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
3
4
|
import { Constants } from '@/common';
|
|
4
5
|
|
|
5
6
|
export const SubagentToolName = Constants.SUBAGENT;
|
|
@@ -29,6 +30,7 @@ const SUBAGENT_TYPE_PROP_DESCRIPTION =
|
|
|
29
30
|
export const SubagentToolSchema = {
|
|
30
31
|
type: 'object',
|
|
31
32
|
properties: {
|
|
33
|
+
intent: { ...INTENT_PROPERTY },
|
|
32
34
|
description: {
|
|
33
35
|
type: 'string',
|
|
34
36
|
description: DESCRIPTION_PROP_DESCRIPTION,
|
|
@@ -67,6 +69,7 @@ export function buildSubagentToolParams(configs: SubagentConfig[]): {
|
|
|
67
69
|
schema: {
|
|
68
70
|
type: 'object',
|
|
69
71
|
properties: {
|
|
72
|
+
intent: { ...INTENT_PROPERTY },
|
|
70
73
|
description: {
|
|
71
74
|
type: 'string',
|
|
72
75
|
description: DESCRIPTION_PROP_DESCRIPTION,
|
package/src/tools/ToolNode.ts
CHANGED
|
@@ -31,6 +31,7 @@ import type {
|
|
|
31
31
|
PreResolvedArgsMap,
|
|
32
32
|
ResolvedArgsByCallId,
|
|
33
33
|
ResolveResult,
|
|
34
|
+
ResolveOptions,
|
|
34
35
|
} from '@/tools/toolOutputReferences';
|
|
35
36
|
import type {
|
|
36
37
|
HookRegistry,
|
|
@@ -47,6 +48,13 @@ import {
|
|
|
47
48
|
serializeStructuredValueBounded,
|
|
48
49
|
serializeToolContentBounded,
|
|
49
50
|
} from '@/utils/toolContent';
|
|
51
|
+
import {
|
|
52
|
+
INTENT_ARG,
|
|
53
|
+
readOutcomeFields,
|
|
54
|
+
resolveToolOutcome,
|
|
55
|
+
isIntentLabelProperty,
|
|
56
|
+
outcomeFieldsFromResult,
|
|
57
|
+
} from '@/tools/intentArg';
|
|
50
58
|
import {
|
|
51
59
|
buildToolExecutionRequestPlan,
|
|
52
60
|
resolveRuntimeSessionHint,
|
|
@@ -126,8 +134,37 @@ type RunToolBatchContext<T = unknown> = {
|
|
|
126
134
|
* which relies on `node:async_hooks` and is browser-incompatible).
|
|
127
135
|
*/
|
|
128
136
|
runInput?: T;
|
|
137
|
+
/** Batch-local error-completion ownership (see {@link ToolErrorOwnership}). */
|
|
138
|
+
errorOwnership?: ToolErrorOwnership;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Batch-local record of who owns each failed call's completion event.
|
|
143
|
+
*
|
|
144
|
+
* Kept per invocation rather than on the instance: tool-call ids are
|
|
145
|
+
* provider-scoped (and synthetic ids can repeat), so concurrent `run()`s on
|
|
146
|
+
* one ToolNode would otherwise share and cross-consume these markers — one
|
|
147
|
+
* invocation's thrown-error marker suppressing another's only completion,
|
|
148
|
+
* or leaving a stale marker behind when an interrupt aborts a batch before
|
|
149
|
+
* the output loop reads it.
|
|
150
|
+
*
|
|
151
|
+
* - `handlerOwned`: the errorHandler ran and dispatched (or threw — a throw
|
|
152
|
+
* is not proof it didn't dispatch). The output loop must skip these.
|
|
153
|
+
* - `undispatched`: the handler explicitly reported it could NOT dispatch,
|
|
154
|
+
* so the output loop owns the completion instead.
|
|
155
|
+
*
|
|
156
|
+
* A call in NEITHER set returned an error `ToolMessage` without ever
|
|
157
|
+
* entering the catch path, so the output loop owns it too.
|
|
158
|
+
*/
|
|
159
|
+
export type ToolErrorOwnership = {
|
|
160
|
+
handlerOwned: Set<string>;
|
|
161
|
+
undispatched: Set<string>;
|
|
129
162
|
};
|
|
130
163
|
|
|
164
|
+
export function createToolErrorOwnership(): ToolErrorOwnership {
|
|
165
|
+
return { handlerOwned: new Set(), undispatched: new Set() };
|
|
166
|
+
}
|
|
167
|
+
|
|
131
168
|
type BoundedToolOutput = {
|
|
132
169
|
content: string;
|
|
133
170
|
registryContent: string;
|
|
@@ -499,12 +536,13 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
499
536
|
toolCallStepIds?: Map<string, string>;
|
|
500
537
|
errorHandler?: t.ToolNodeConstructorParams['errorHandler'];
|
|
501
538
|
/**
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
*
|
|
505
|
-
*
|
|
539
|
+
* Fallback error-completion ownership for calls that reach `runTool`
|
|
540
|
+
* outside a batch context (direct `runTool` use in tests / embedders).
|
|
541
|
+
* Batch-scoped ownership is threaded via `RunToolBatchContext` instead —
|
|
542
|
+
* see {@link ToolErrorOwnership} for why per-invocation scoping matters.
|
|
506
543
|
*/
|
|
507
|
-
private
|
|
544
|
+
private looseErrorOwnership: ToolErrorOwnership =
|
|
545
|
+
createToolErrorOwnership();
|
|
508
546
|
private toolUsageCount: Map<string, number>;
|
|
509
547
|
/** Maps toolCallId → turn captured in runTool, used by handleRunToolCompletions */
|
|
510
548
|
private toolCallTurns: Map<string, number> = new Map();
|
|
@@ -952,14 +990,17 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
952
990
|
args: T
|
|
953
991
|
) => ResolveResult<T>;
|
|
954
992
|
let resolveFn: ResolveFn | undefined;
|
|
993
|
+
const resolveOptions = {
|
|
994
|
+
substituteIntentKey: this.toolDeclaresBusinessIntent(call.name),
|
|
995
|
+
};
|
|
955
996
|
if (preBatchSnapshot != null) {
|
|
956
997
|
resolveFn = <T>(_runId: string | undefined, args: T): ResolveResult<T> =>
|
|
957
|
-
preBatchSnapshot.resolve(args);
|
|
998
|
+
preBatchSnapshot.resolve(args, resolveOptions);
|
|
958
999
|
} else if (registry != null) {
|
|
959
1000
|
resolveFn = <T>(
|
|
960
1001
|
runIdArg: string | undefined,
|
|
961
1002
|
args: T
|
|
962
|
-
): ResolveResult<T> => registry.resolve(runIdArg, args);
|
|
1003
|
+
): ResolveResult<T> => registry.resolve(runIdArg, args, resolveOptions);
|
|
963
1004
|
}
|
|
964
1005
|
/**
|
|
965
1006
|
* Precompute the reference key once per call — captured locally
|
|
@@ -1266,15 +1307,21 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1266
1307
|
},
|
|
1267
1308
|
config.metadata
|
|
1268
1309
|
);
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1310
|
+
const ownership =
|
|
1311
|
+
batchContext.errorOwnership ?? this.looseErrorOwnership;
|
|
1312
|
+
if (call.id != null && call.id !== '') {
|
|
1313
|
+
if (dispatched === false) {
|
|
1314
|
+
/**
|
|
1315
|
+
* The handler could not dispatch the error completion (typically
|
|
1316
|
+
* a resume pass, where a fast-failing tool errors before the
|
|
1317
|
+
* step replay registers its run step). Remember the call so the
|
|
1318
|
+
* output loop dispatches the completion itself instead of
|
|
1319
|
+
* assuming the handler covered it.
|
|
1320
|
+
*/
|
|
1321
|
+
ownership.undispatched.add(call.id);
|
|
1322
|
+
} else {
|
|
1323
|
+
ownership.handlerOwned.add(call.id);
|
|
1324
|
+
}
|
|
1278
1325
|
}
|
|
1279
1326
|
} catch (handlerError) {
|
|
1280
1327
|
// A THROWN handler is not proof the completion wasn't dispatched: the
|
|
@@ -1284,6 +1331,11 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1284
1331
|
// fallback loop re-emit a duplicate completion — only an explicit
|
|
1285
1332
|
// `false` return (handled above) means "nothing dispatched"; a throw
|
|
1286
1333
|
// is just logged.
|
|
1334
|
+
if (call.id != null && call.id !== '') {
|
|
1335
|
+
(
|
|
1336
|
+
batchContext.errorOwnership ?? this.looseErrorOwnership
|
|
1337
|
+
).handlerOwned.add(call.id);
|
|
1338
|
+
}
|
|
1287
1339
|
// eslint-disable-next-line no-console
|
|
1288
1340
|
console.error('Error in errorHandler:', {
|
|
1289
1341
|
toolName: call.name,
|
|
@@ -1448,13 +1500,20 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1448
1500
|
// (later-awaited) `runTool` will actually run with — both are
|
|
1449
1501
|
// anchored to the pre-batch registry state.
|
|
1450
1502
|
let resolvedArgs = call.args as Record<string, unknown>;
|
|
1503
|
+
const hookResolveOptions = {
|
|
1504
|
+
substituteIntentKey: this.toolDeclaresBusinessIntent(call.name),
|
|
1505
|
+
};
|
|
1451
1506
|
if (batchContext.preBatchSnapshot != null) {
|
|
1452
|
-
const { resolved } = batchContext.preBatchSnapshot.resolve(
|
|
1507
|
+
const { resolved } = batchContext.preBatchSnapshot.resolve(
|
|
1508
|
+
call.args,
|
|
1509
|
+
hookResolveOptions
|
|
1510
|
+
);
|
|
1453
1511
|
resolvedArgs = resolved as Record<string, unknown>;
|
|
1454
1512
|
} else if (this.toolOutputRegistry != null) {
|
|
1455
1513
|
const { resolved } = this.toolOutputRegistry.resolve(
|
|
1456
1514
|
registryRunId,
|
|
1457
|
-
call.args
|
|
1515
|
+
call.args,
|
|
1516
|
+
hookResolveOptions
|
|
1458
1517
|
);
|
|
1459
1518
|
resolvedArgs = resolved as Record<string, unknown>;
|
|
1460
1519
|
}
|
|
@@ -1655,6 +1714,27 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1655
1714
|
}
|
|
1656
1715
|
}
|
|
1657
1716
|
|
|
1717
|
+
/**
|
|
1718
|
+
* A hook (`PreToolUse.updatedInput`) or HITL `edit` decision rewrote the
|
|
1719
|
+
* args: expose the EFFECTIVE args to downstream completion handling
|
|
1720
|
+
* (`handleRunToolCompletions` reads this sink), so the emitted
|
|
1721
|
+
* `tool_call.args` — and any intent/outcome label resolved from them —
|
|
1722
|
+
* reflect what the tool actually ran with. `runTool`'s own placeholder
|
|
1723
|
+
* substitution may overwrite this entry with the post-substitution args,
|
|
1724
|
+
* which is strictly more accurate.
|
|
1725
|
+
*/
|
|
1726
|
+
if (
|
|
1727
|
+
effectiveCall !== call &&
|
|
1728
|
+
batchContext.resolvedArgsByCallId != null &&
|
|
1729
|
+
call.id != null &&
|
|
1730
|
+
call.id !== ''
|
|
1731
|
+
) {
|
|
1732
|
+
batchContext.resolvedArgsByCallId.set(
|
|
1733
|
+
call.id,
|
|
1734
|
+
effectiveCall.args as Record<string, unknown>
|
|
1735
|
+
);
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1658
1738
|
const output = await this.runTool(effectiveCall, config, {
|
|
1659
1739
|
...batchContext,
|
|
1660
1740
|
usageCount,
|
|
@@ -2008,8 +2088,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2008
2088
|
calls: ToolCall[],
|
|
2009
2089
|
outputs: (BaseMessage | Command)[],
|
|
2010
2090
|
config: RunnableConfig,
|
|
2011
|
-
resolvedArgsByCallId?: ResolvedArgsByCallId
|
|
2091
|
+
resolvedArgsByCallId?: ResolvedArgsByCallId,
|
|
2092
|
+
errorOwnership?: ToolErrorOwnership
|
|
2012
2093
|
): Promise<void> {
|
|
2094
|
+
const ownership = errorOwnership ?? this.looseErrorOwnership;
|
|
2013
2095
|
for (let i = 0; i < calls.length; i++) {
|
|
2014
2096
|
const call = calls[i];
|
|
2015
2097
|
const output = outputs[i];
|
|
@@ -2022,23 +2104,24 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2022
2104
|
const toolMessage = output as ToolMessage;
|
|
2023
2105
|
const toolCallId = call.id ?? '';
|
|
2024
2106
|
|
|
2025
|
-
// Skip error ToolMessages when errorHandler
|
|
2026
|
-
//
|
|
2027
|
-
//
|
|
2028
|
-
//
|
|
2029
|
-
//
|
|
2030
|
-
//
|
|
2107
|
+
// Skip error ToolMessages only when the errorHandler OWNS the completion —
|
|
2108
|
+
// it ran and dispatched (or threw) via handleToolCallErrorStatic, so
|
|
2109
|
+
// dispatching again here would double-dispatch. Two cases fall through:
|
|
2110
|
+
// - the handler reported it could NOT dispatch (no run step registered yet
|
|
2111
|
+
// at error time, e.g. a fast-failing tool on a resume pass); by now the
|
|
2112
|
+
// step replay has usually registered the id, so this loop's dispatch is
|
|
2113
|
+
// the only terminal event the client's tool-call part will ever get.
|
|
2114
|
+
// - the tool RETURNED an error ToolMessage rather than throwing, so the
|
|
2115
|
+
// catch path (and the handler with it) never ran at all.
|
|
2116
|
+
// Markers are CONSUMED: leaving one set would let a later re-entry (same
|
|
2117
|
+
// ToolNode re-executing the batch) take the wrong branch, and the sets
|
|
2118
|
+
// would grow unbounded across a long-lived graph's failing calls.
|
|
2031
2119
|
if (toolMessage.status === 'error' && this.errorHandler != null) {
|
|
2032
|
-
if (
|
|
2033
|
-
|
|
2034
|
-
// Leaving it set would let a later re-entry (same ToolNode instance
|
|
2035
|
-
// re-executing the batch, where the handler CAN dispatch) fall
|
|
2036
|
-
// through here too and double-dispatch — and the set would grow
|
|
2037
|
-
// unbounded across a long-lived graph's fast-failing calls.
|
|
2038
|
-
this.undispatchedToolErrors.delete(toolCallId);
|
|
2039
|
-
} else {
|
|
2120
|
+
if (ownership.handlerOwned.has(toolCallId)) {
|
|
2121
|
+
ownership.handlerOwned.delete(toolCallId);
|
|
2040
2122
|
continue;
|
|
2041
2123
|
}
|
|
2124
|
+
ownership.undispatched.delete(toolCallId);
|
|
2042
2125
|
}
|
|
2043
2126
|
|
|
2044
2127
|
if (this.sessions && this.participatesInCodeSession(call.name)) {
|
|
@@ -2079,6 +2162,13 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2079
2162
|
* the tool actually received rather than leaking the template.
|
|
2080
2163
|
*/
|
|
2081
2164
|
const effectiveArgs = resolvedArgsByCallId?.get(toolCallId) ?? call.args;
|
|
2165
|
+
/** Authored outcomes apply to failed calls too (“Search failed for…”);
|
|
2166
|
+
* without one, an error call simply stays unlabeled. */
|
|
2167
|
+
const outcome = resolveToolOutcome(
|
|
2168
|
+
effectiveArgs,
|
|
2169
|
+
readOutcomeFields(toolMessage.artifact),
|
|
2170
|
+
{ isError: toolMessage.status === 'error' }
|
|
2171
|
+
);
|
|
2082
2172
|
const tool_call: t.ProcessedToolCall = {
|
|
2083
2173
|
args: serializeToolContentBounded(
|
|
2084
2174
|
(effectiveArgs as unknown) ?? {},
|
|
@@ -2088,6 +2178,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2088
2178
|
id: toolCallId,
|
|
2089
2179
|
output: contentString,
|
|
2090
2180
|
progress: 1,
|
|
2181
|
+
...(outcome != null && { outcome }),
|
|
2091
2182
|
};
|
|
2092
2183
|
|
|
2093
2184
|
await safeDispatchCustomEvent(
|
|
@@ -2165,7 +2256,8 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2165
2256
|
} else if (registry != null) {
|
|
2166
2257
|
const { resolved, unresolved } = registry.resolve(
|
|
2167
2258
|
registryRunId,
|
|
2168
|
-
originalArgs
|
|
2259
|
+
originalArgs,
|
|
2260
|
+
{ substituteIntentKey: this.toolDeclaresBusinessIntent(call.name) }
|
|
2169
2261
|
);
|
|
2170
2262
|
resolvedArgs = resolved as Record<string, unknown>;
|
|
2171
2263
|
if (unresolved.length > 0 && call.id != null) {
|
|
@@ -2386,9 +2478,14 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2386
2478
|
): void => {
|
|
2387
2479
|
if (registry != null) {
|
|
2388
2480
|
const view: ToolOutputResolveView = preBatchSnapshot ?? {
|
|
2389
|
-
resolve: <T>(args: T) =>
|
|
2481
|
+
resolve: <T>(args: T, options?: ResolveOptions) =>
|
|
2482
|
+
registry.resolve(registryRunId, args, options),
|
|
2390
2483
|
};
|
|
2391
|
-
const { resolved, unresolved } = view.resolve(nextArgs
|
|
2484
|
+
const { resolved, unresolved } = view.resolve(nextArgs, {
|
|
2485
|
+
substituteIntentKey: this.toolDeclaresBusinessIntent(
|
|
2486
|
+
entry.call.name
|
|
2487
|
+
),
|
|
2488
|
+
});
|
|
2392
2489
|
entry.args = resolved as Record<string, unknown>;
|
|
2393
2490
|
if (entry.call.id != null) {
|
|
2394
2491
|
if (unresolved.length > 0) {
|
|
@@ -3120,7 +3217,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3120
3217
|
request?.args ?? {},
|
|
3121
3218
|
contentString,
|
|
3122
3219
|
config,
|
|
3123
|
-
request?.turn
|
|
3220
|
+
request?.turn,
|
|
3221
|
+
resolveToolOutcome(request?.args, outcomeFieldsFromResult(result), {
|
|
3222
|
+
isError: result.status === 'error',
|
|
3223
|
+
})
|
|
3124
3224
|
);
|
|
3125
3225
|
}
|
|
3126
3226
|
|
|
@@ -3355,13 +3455,43 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3355
3455
|
}
|
|
3356
3456
|
}
|
|
3357
3457
|
|
|
3458
|
+
/**
|
|
3459
|
+
* Whether `name`'s own schema declares a BUSINESS parameter called
|
|
3460
|
+
* `intent` (declared, and not the injected label contract — see
|
|
3461
|
+
* `isIntentLabelProperty`). Such a parameter must keep participating in
|
|
3462
|
+
* `{{tool…}}` placeholder substitution; only the display label is exempt.
|
|
3463
|
+
*/
|
|
3464
|
+
private toolDeclaresBusinessIntent(name: string): boolean {
|
|
3465
|
+
const instance = this.toolMap.get(name) as
|
|
3466
|
+
| {
|
|
3467
|
+
schema?: {
|
|
3468
|
+
shape?: Record<string, unknown>;
|
|
3469
|
+
properties?: Record<string, unknown>;
|
|
3470
|
+
};
|
|
3471
|
+
}
|
|
3472
|
+
| undefined;
|
|
3473
|
+
const instanceProp =
|
|
3474
|
+
instance?.schema?.properties?.[INTENT_ARG] ??
|
|
3475
|
+
instance?.schema?.shape?.[INTENT_ARG];
|
|
3476
|
+
if (instanceProp != null) {
|
|
3477
|
+
return !isIntentLabelProperty(instanceProp);
|
|
3478
|
+
}
|
|
3479
|
+
const defProp =
|
|
3480
|
+
this.toolRegistry?.get(name)?.parameters?.properties?.[INTENT_ARG];
|
|
3481
|
+
if (defProp != null) {
|
|
3482
|
+
return !isIntentLabelProperty(defProp);
|
|
3483
|
+
}
|
|
3484
|
+
return false;
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3358
3487
|
private async dispatchStepCompleted(
|
|
3359
3488
|
toolCallId: string,
|
|
3360
3489
|
toolName: string,
|
|
3361
3490
|
args: Record<string, unknown>,
|
|
3362
3491
|
output: string,
|
|
3363
3492
|
config: RunnableConfig,
|
|
3364
|
-
turn?: number
|
|
3493
|
+
turn?: number,
|
|
3494
|
+
outcome?: string
|
|
3365
3495
|
): Promise<boolean> {
|
|
3366
3496
|
const stepId = this.toolCallStepIds?.get(toolCallId) ?? '';
|
|
3367
3497
|
if (!stepId) {
|
|
@@ -3386,6 +3516,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3386
3516
|
id: toolCallId,
|
|
3387
3517
|
output,
|
|
3388
3518
|
progress: 1,
|
|
3519
|
+
...(outcome != null && { outcome }),
|
|
3389
3520
|
} as t.ProcessedToolCall,
|
|
3390
3521
|
},
|
|
3391
3522
|
},
|
|
@@ -3422,7 +3553,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3422
3553
|
request.args,
|
|
3423
3554
|
output,
|
|
3424
3555
|
config,
|
|
3425
|
-
request.turn
|
|
3556
|
+
request.turn,
|
|
3557
|
+
resolveToolOutcome(request.args, outcomeFieldsFromResult(result), {
|
|
3558
|
+
isError: result.status === 'error',
|
|
3559
|
+
})
|
|
3426
3560
|
);
|
|
3427
3561
|
}
|
|
3428
3562
|
|
|
@@ -3550,6 +3684,8 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3550
3684
|
* ToolNode cannot read or wipe each other's entries.
|
|
3551
3685
|
*/
|
|
3552
3686
|
const resolvedArgsByCallId = new Map<string, Record<string, unknown>>();
|
|
3687
|
+
/** Per-invocation error-completion ownership — see `ToolErrorOwnership`. */
|
|
3688
|
+
const errorOwnership = createToolErrorOwnership();
|
|
3553
3689
|
/**
|
|
3554
3690
|
* Claim this batch's turn synchronously from the registry (or
|
|
3555
3691
|
* fall back to 0 when the feature is disabled). The registry is
|
|
@@ -3595,6 +3731,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3595
3731
|
turn,
|
|
3596
3732
|
batchScopeId,
|
|
3597
3733
|
resolvedArgsByCallId,
|
|
3734
|
+
errorOwnership,
|
|
3598
3735
|
additionalContextsSink: directAdditionalContexts,
|
|
3599
3736
|
runInput: sendState as T,
|
|
3600
3737
|
}
|
|
@@ -3618,7 +3755,8 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3618
3755
|
// HumanMessage isn't a tool result.
|
|
3619
3756
|
[sendOutput],
|
|
3620
3757
|
config,
|
|
3621
|
-
resolvedArgsByCallId
|
|
3758
|
+
resolvedArgsByCallId,
|
|
3759
|
+
errorOwnership
|
|
3622
3760
|
);
|
|
3623
3761
|
} else {
|
|
3624
3762
|
let messages: BaseMessage[];
|
|
@@ -3743,7 +3881,12 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3743
3881
|
for (const entry of eventEntries) {
|
|
3744
3882
|
if (entry.call.id != null) {
|
|
3745
3883
|
const { resolved, unresolved } = preBatchSnapshot.resolve(
|
|
3746
|
-
entry.call.args as Record<string, unknown
|
|
3884
|
+
entry.call.args as Record<string, unknown>,
|
|
3885
|
+
{
|
|
3886
|
+
substituteIntentKey: this.toolDeclaresBusinessIntent(
|
|
3887
|
+
entry.call.name
|
|
3888
|
+
),
|
|
3889
|
+
}
|
|
3747
3890
|
);
|
|
3748
3891
|
preResolvedEventArgs.set(entry.call.id, {
|
|
3749
3892
|
resolved: resolved as Record<string, unknown>,
|
|
@@ -3768,6 +3911,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3768
3911
|
turn,
|
|
3769
3912
|
batchScopeId,
|
|
3770
3913
|
resolvedArgsByCallId,
|
|
3914
|
+
errorOwnership,
|
|
3771
3915
|
preBatchSnapshot,
|
|
3772
3916
|
additionalContextsSink: directAdditionalContexts,
|
|
3773
3917
|
runInput: input as T,
|
|
@@ -3780,7 +3924,8 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3780
3924
|
directCalls,
|
|
3781
3925
|
directOutputs,
|
|
3782
3926
|
config,
|
|
3783
|
-
resolvedArgsByCallId
|
|
3927
|
+
resolvedArgsByCallId,
|
|
3928
|
+
errorOwnership
|
|
3784
3929
|
);
|
|
3785
3930
|
}
|
|
3786
3931
|
|
|
@@ -3833,6 +3978,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3833
3978
|
turn,
|
|
3834
3979
|
batchScopeId,
|
|
3835
3980
|
resolvedArgsByCallId,
|
|
3981
|
+
errorOwnership,
|
|
3836
3982
|
preBatchSnapshot,
|
|
3837
3983
|
additionalContextsSink: directAdditionalContexts,
|
|
3838
3984
|
runInput: input as T,
|
|
@@ -3842,7 +3988,8 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3842
3988
|
filteredCalls,
|
|
3843
3989
|
toolOutputs,
|
|
3844
3990
|
config,
|
|
3845
|
-
resolvedArgsByCallId
|
|
3991
|
+
resolvedArgsByCallId,
|
|
3992
|
+
errorOwnership
|
|
3846
3993
|
);
|
|
3847
3994
|
// Append accumulated additionalContexts as a single
|
|
3848
3995
|
// HumanMessage so the next model turn sees them. Codex P2 #39.
|
package/src/tools/ToolSearch.ts
CHANGED
|
@@ -24,6 +24,7 @@ import fetch, { RequestInit } from 'node-fetch';
|
|
|
24
24
|
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
25
25
|
import { tool, DynamicStructuredTool } from '@langchain/core/tools';
|
|
26
26
|
import type * as t from '@/types';
|
|
27
|
+
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
27
28
|
import { getCodeBaseURL } from './CodeExecutor';
|
|
28
29
|
import { Constants } from '@/common';
|
|
29
30
|
|
|
@@ -51,6 +52,7 @@ const MCP_SERVER_DESCRIPTION =
|
|
|
51
52
|
export const ToolSearchToolSchema = {
|
|
52
53
|
type: 'object',
|
|
53
54
|
properties: {
|
|
55
|
+
intent: { ...INTENT_PROPERTY },
|
|
54
56
|
query: {
|
|
55
57
|
type: 'string',
|
|
56
58
|
maxLength: MAX_PATTERN_LENGTH,
|
|
@@ -117,6 +119,7 @@ function createToolSearchSchema(mode: t.ToolSearchMode): ToolSearchSchema {
|
|
|
117
119
|
return {
|
|
118
120
|
type: 'object',
|
|
119
121
|
properties: {
|
|
122
|
+
intent: { ...INTENT_PROPERTY },
|
|
120
123
|
query: {
|
|
121
124
|
type: 'string',
|
|
122
125
|
maxLength: MAX_PATTERN_LENGTH,
|