@librechat/agents 3.1.88 → 3.1.90
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 +25 -1
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hooks/executeHooks.cjs +14 -7
- package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/index.cjs +8 -2
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +34 -0
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/main.cjs +9 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/stream.cjs +115 -8
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +10 -9
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +12 -8
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +35 -11
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/CodeSessionFileSummary.cjs +63 -0
- package/dist/cjs/tools/CodeSessionFileSummary.cjs.map +1 -0
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +16 -12
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +32 -12
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +319 -29
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/toolOutputReferences.cjs +8 -0
- package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
- package/dist/cjs/utils/events.cjs +3 -1
- package/dist/cjs/utils/events.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +25 -1
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hooks/executeHooks.mjs +14 -7
- package/dist/esm/hooks/executeHooks.mjs.map +1 -1
- package/dist/esm/llm/anthropic/index.mjs +9 -3
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +33 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/main.mjs +2 -1
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/stream.mjs +115 -8
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +11 -10
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +13 -9
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +29 -12
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/CodeSessionFileSummary.mjs +60 -0
- package/dist/esm/tools/CodeSessionFileSummary.mjs.map +1 -0
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +17 -13
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +32 -12
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +320 -31
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/toolOutputReferences.mjs +8 -1
- package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
- package/dist/esm/utils/events.mjs +3 -1
- package/dist/esm/utils/events.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +8 -0
- package/dist/types/llm/anthropic/index.d.ts +3 -1
- package/dist/types/llm/anthropic/utils/message_inputs.d.ts +4 -0
- package/dist/types/tools/BashExecutor.d.ts +3 -3
- package/dist/types/tools/CodeExecutor.d.ts +10 -3
- package/dist/types/tools/CodeSessionFileSummary.d.ts +3 -0
- package/dist/types/tools/ProgrammaticToolCalling.d.ts +4 -4
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +8 -5
- package/dist/types/types/tools.d.ts +11 -3
- package/dist/types/utils/events.d.ts +1 -1
- package/package.json +1 -1
- package/src/__tests__/stream.eagerEventExecution.test.ts +1073 -221
- package/src/graphs/Graph.ts +27 -5
- package/src/hooks/__tests__/executeHooks.test.ts +38 -0
- package/src/hooks/executeHooks.ts +27 -7
- package/src/llm/anthropic/index.ts +27 -3
- package/src/llm/anthropic/llm.spec.ts +60 -1
- package/src/llm/anthropic/utils/message_inputs.ts +46 -0
- package/src/specs/subagent.test.ts +87 -1
- package/src/stream.ts +163 -12
- package/src/tools/BashExecutor.ts +21 -10
- package/src/tools/BashProgrammaticToolCalling.ts +21 -9
- package/src/tools/CodeExecutor.ts +55 -12
- package/src/tools/CodeSessionFileSummary.ts +80 -0
- package/src/tools/ProgrammaticToolCalling.ts +25 -12
- package/src/tools/ToolNode.ts +142 -116
- package/src/tools/__tests__/BashExecutor.test.ts +9 -0
- package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +43 -0
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +100 -16
- package/src/tools/__tests__/SubagentExecutor.test.ts +540 -6
- package/src/tools/__tests__/ToolNode.eagerEventExecution.test.ts +278 -14
- package/src/tools/__tests__/ToolNode.outputReferences.test.ts +52 -0
- package/src/tools/__tests__/subagentHooks.test.ts +237 -0
- package/src/tools/subagent/SubagentExecutor.ts +514 -36
- package/src/types/tools.ts +11 -3
- package/src/utils/events.ts +4 -2
package/src/graphs/Graph.ts
CHANGED
|
@@ -145,6 +145,14 @@ export abstract class Graph<
|
|
|
145
145
|
/** Set of invoked tool call IDs from non-message run steps completed mid-run, if any */
|
|
146
146
|
invokedToolIds?: Set<string>;
|
|
147
147
|
handlerRegistry: HandlerRegistry | undefined;
|
|
148
|
+
/**
|
|
149
|
+
* True when event-driven tool execution can be routed through callbacks even
|
|
150
|
+
* though this graph intentionally does not own the full handler registry.
|
|
151
|
+
* Self-spawned subagent graphs use this shape: their callback forwarder sends
|
|
152
|
+
* `ON_TOOL_EXECUTE` to the parent's handler, while child run-step events stay
|
|
153
|
+
* wrapped as `ON_SUBAGENT_UPDATE` instead of leaking as parent events.
|
|
154
|
+
*/
|
|
155
|
+
eventToolExecutionAvailable: boolean = false;
|
|
148
156
|
hookRegistry: HookRegistry | undefined;
|
|
149
157
|
/**
|
|
150
158
|
* Run-scoped HITL configuration. When `humanInTheLoop?.enabled` is
|
|
@@ -167,10 +175,8 @@ export abstract class Graph<
|
|
|
167
175
|
eagerEventToolExecution: t.EagerEventToolExecutionConfig | undefined;
|
|
168
176
|
eagerEventToolExecutions: Map<string, t.EagerEventToolExecution> = new Map();
|
|
169
177
|
eagerEventToolUsageCount: Map<string, number> = new Map();
|
|
170
|
-
private eagerEventToolUsageCountsByAgentId: Map<
|
|
171
|
-
|
|
172
|
-
Map<string, number>
|
|
173
|
-
> = new Map();
|
|
178
|
+
private eagerEventToolUsageCountsByAgentId: Map<string, Map<string, number>> =
|
|
179
|
+
new Map();
|
|
174
180
|
eagerEventToolCallChunks: Map<string, t.EagerEventToolCallChunkState> =
|
|
175
181
|
new Map();
|
|
176
182
|
/**
|
|
@@ -1554,7 +1560,23 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1554
1560
|
parentAgentId: agentContext.agentId,
|
|
1555
1561
|
tokenCounter: agentContext.tokenCounter,
|
|
1556
1562
|
maxDepth: effectiveSubagentDepth,
|
|
1557
|
-
createChildGraph: (input): StandardGraph =>
|
|
1563
|
+
createChildGraph: (input): StandardGraph => {
|
|
1564
|
+
const childGraph = new StandardGraph(input);
|
|
1565
|
+
childGraph.hookRegistry = this.hookRegistry;
|
|
1566
|
+
/**
|
|
1567
|
+
* Do not propagate `humanInTheLoop` into the child graph yet:
|
|
1568
|
+
* nested subagent interrupts need a stable child checkpoint and
|
|
1569
|
+
* resume bridge. Child hooks still fire; `ask` decisions fail
|
|
1570
|
+
* closed inside the subagent until that flow is implemented.
|
|
1571
|
+
*/
|
|
1572
|
+
childGraph.toolOutputReferences = this.toolOutputReferences;
|
|
1573
|
+
childGraph.eagerEventToolExecution = this.eagerEventToolExecution;
|
|
1574
|
+
childGraph.toolExecution = this.toolExecution;
|
|
1575
|
+
childGraph.eventToolExecutionAvailable =
|
|
1576
|
+
this.handlerRegistry?.getHandler(GraphEvents.ON_TOOL_EXECUTE) !=
|
|
1577
|
+
null;
|
|
1578
|
+
return childGraph;
|
|
1579
|
+
},
|
|
1558
1580
|
});
|
|
1559
1581
|
|
|
1560
1582
|
const subagentTool = tool(async (rawInput, config) => {
|
|
@@ -109,6 +109,44 @@ describe('executeHooks', () => {
|
|
|
109
109
|
consoleWarnSpy.mockRestore();
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
+
describe('abort listener management', () => {
|
|
113
|
+
it('uses one abort listener for many hooks on one matcher', async () => {
|
|
114
|
+
const registry = new HookRegistry();
|
|
115
|
+
const listenerCounts = new Map<AbortSignal, number>();
|
|
116
|
+
let maxAbortListeners = 0;
|
|
117
|
+
const addEventListenerSpy = jest
|
|
118
|
+
.spyOn(AbortSignal.prototype, 'addEventListener')
|
|
119
|
+
.mockImplementation(function (
|
|
120
|
+
this: AbortSignal,
|
|
121
|
+
type: string,
|
|
122
|
+
_listener: EventListenerOrEventListenerObject | null
|
|
123
|
+
): void {
|
|
124
|
+
if (type !== 'abort') {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const count = (listenerCounts.get(this) ?? 0) + 1;
|
|
128
|
+
listenerCounts.set(this, count);
|
|
129
|
+
maxAbortListeners = Math.max(maxAbortListeners, count);
|
|
130
|
+
});
|
|
131
|
+
const hooks = Array.from({ length: 12 }, () =>
|
|
132
|
+
runStartHook(async (): Promise<RunStartHookOutput> => ({}))
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
registry.register('RunStart', { hooks });
|
|
137
|
+
|
|
138
|
+
await executeHooks({
|
|
139
|
+
registry,
|
|
140
|
+
input: runStartInput(),
|
|
141
|
+
timeoutMs: 1000,
|
|
142
|
+
});
|
|
143
|
+
expect(maxAbortListeners).toBe(1);
|
|
144
|
+
} finally {
|
|
145
|
+
addEventListenerSpy.mockRestore();
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
112
150
|
describe('empty matcher set', () => {
|
|
113
151
|
it('returns an empty aggregated result when no matchers are registered', async () => {
|
|
114
152
|
const registry = new HookRegistry();
|
|
@@ -46,6 +46,11 @@ interface HookOutcome {
|
|
|
46
46
|
timedOut: boolean;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
interface AbortRace {
|
|
50
|
+
promise: Promise<never>;
|
|
51
|
+
cleanup: () => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
49
54
|
function freshResult(): AggregatedHookResult {
|
|
50
55
|
return {
|
|
51
56
|
additionalContexts: [],
|
|
@@ -110,10 +115,10 @@ async function runHook(
|
|
|
110
115
|
hook: WideCallback,
|
|
111
116
|
input: HookInput,
|
|
112
117
|
signal: AbortSignal,
|
|
118
|
+
abortPromise: Promise<never>,
|
|
113
119
|
matcher: WideMatcher
|
|
114
120
|
): Promise<HookOutcome> {
|
|
115
121
|
const hookPromise = Promise.resolve().then(() => hook(input, signal));
|
|
116
|
-
const { promise: abortPromise, cleanup } = makeAbortPromise(signal);
|
|
117
122
|
try {
|
|
118
123
|
const output = await Promise.race([hookPromise, abortPromise]);
|
|
119
124
|
return { matcher, output, error: null, timedOut: false };
|
|
@@ -124,8 +129,22 @@ async function runHook(
|
|
|
124
129
|
error: describeError(err),
|
|
125
130
|
timedOut: isTimeout(err),
|
|
126
131
|
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function runMatcherHooks(
|
|
136
|
+
matcher: WideMatcher,
|
|
137
|
+
input: HookInput,
|
|
138
|
+
signal: AbortSignal
|
|
139
|
+
): Promise<HookOutcome[]> {
|
|
140
|
+
const abortRace: AbortRace = makeAbortPromise(signal);
|
|
141
|
+
const tasks = matcher.hooks.map((hook) =>
|
|
142
|
+
runHook(hook, input, signal, abortRace.promise, matcher)
|
|
143
|
+
);
|
|
144
|
+
try {
|
|
145
|
+
return await Promise.all(tasks);
|
|
127
146
|
} finally {
|
|
128
|
-
cleanup();
|
|
147
|
+
abortRace.cleanup();
|
|
129
148
|
}
|
|
130
149
|
}
|
|
131
150
|
|
|
@@ -373,7 +392,7 @@ export async function executeHooks(
|
|
|
373
392
|
}
|
|
374
393
|
|
|
375
394
|
// --- SYNC CRITICAL SECTION: once-matcher removal must complete before any await ---
|
|
376
|
-
const tasks: Promise<HookOutcome>[] = [];
|
|
395
|
+
const tasks: Promise<HookOutcome[]>[] = [];
|
|
377
396
|
for (const matcher of matchers) {
|
|
378
397
|
if (!matchesQuery(matcher.pattern, matchQuery)) {
|
|
379
398
|
continue;
|
|
@@ -381,18 +400,19 @@ export async function executeHooks(
|
|
|
381
400
|
if (matcher.once === true) {
|
|
382
401
|
registry.removeMatcher(event, matcher, sessionId);
|
|
383
402
|
}
|
|
403
|
+
if (matcher.hooks.length === 0) {
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
384
406
|
const perHookTimeout = matcher.timeout ?? timeoutMs;
|
|
385
407
|
const matcherSignal = combineSignals(signal, perHookTimeout);
|
|
386
|
-
|
|
387
|
-
tasks.push(runHook(hook, input, matcherSignal, matcher));
|
|
388
|
-
}
|
|
408
|
+
tasks.push(runMatcherHooks(matcher, input, matcherSignal));
|
|
389
409
|
}
|
|
390
410
|
// --- END SYNC CRITICAL SECTION ---
|
|
391
411
|
if (tasks.length === 0) {
|
|
392
412
|
return freshResult();
|
|
393
413
|
}
|
|
394
414
|
|
|
395
|
-
const outcomes = await Promise.all(tasks);
|
|
415
|
+
const outcomes = (await Promise.all(tasks)).flat();
|
|
396
416
|
reportErrors(outcomes, event, logger);
|
|
397
417
|
const aggregated = fold(outcomes);
|
|
398
418
|
/**
|
|
@@ -17,9 +17,13 @@ import type {
|
|
|
17
17
|
ChatAnthropicToolType,
|
|
18
18
|
AnthropicMCPServerURLDefinition,
|
|
19
19
|
AnthropicContextManagementConfigParam,
|
|
20
|
+
AnthropicRequestOptions,
|
|
20
21
|
} from '@/llm/anthropic/types';
|
|
21
22
|
import { _makeMessageChunkFromAnthropicEvent } from './utils/message_outputs';
|
|
22
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
_convertMessagesToAnthropicPayload,
|
|
25
|
+
stripUnsupportedAssistantPrefill,
|
|
26
|
+
} from './utils/message_inputs';
|
|
23
27
|
import { handleToolChoice } from './utils/tools';
|
|
24
28
|
|
|
25
29
|
const DEFAULT_STREAM_DELAY = 25;
|
|
@@ -591,6 +595,26 @@ export class CustomAnthropic extends ChatAnthropicMessages {
|
|
|
591
595
|
});
|
|
592
596
|
}
|
|
593
597
|
|
|
598
|
+
protected override async createStreamWithRetry(
|
|
599
|
+
request: AnthropicStreamingMessageCreateParams,
|
|
600
|
+
options?: AnthropicRequestOptions
|
|
601
|
+
): ReturnType<ChatAnthropicMessages['createStreamWithRetry']> {
|
|
602
|
+
return super.createStreamWithRetry(
|
|
603
|
+
stripUnsupportedAssistantPrefill(request),
|
|
604
|
+
options
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
protected override async completionWithRetry(
|
|
609
|
+
request: AnthropicMessageCreateParams,
|
|
610
|
+
options: AnthropicRequestOptions
|
|
611
|
+
): ReturnType<ChatAnthropicMessages['completionWithRetry']> {
|
|
612
|
+
return super.completionWithRetry(
|
|
613
|
+
stripUnsupportedAssistantPrefill(request),
|
|
614
|
+
options
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
|
|
594
618
|
async *_streamResponseChunks(
|
|
595
619
|
messages: BaseMessage[],
|
|
596
620
|
options: this['ParsedCallOptions'],
|
|
@@ -599,11 +623,11 @@ export class CustomAnthropic extends ChatAnthropicMessages {
|
|
|
599
623
|
this.resetTokenEvents();
|
|
600
624
|
const params = this.invocationParams(options);
|
|
601
625
|
const formattedMessages = _convertMessagesToAnthropicPayload(messages);
|
|
602
|
-
const payload = {
|
|
626
|
+
const payload = stripUnsupportedAssistantPrefill({
|
|
603
627
|
...params,
|
|
604
628
|
...formattedMessages,
|
|
605
629
|
stream: true,
|
|
606
|
-
} as const;
|
|
630
|
+
} as const);
|
|
607
631
|
const coerceContentToString =
|
|
608
632
|
!_toolsInParams(payload) &&
|
|
609
633
|
!_documentsInParams(payload) &&
|
|
@@ -64,7 +64,11 @@ import type {
|
|
|
64
64
|
ToolEndEvent,
|
|
65
65
|
TPayload,
|
|
66
66
|
} from '@/types';
|
|
67
|
-
import {
|
|
67
|
+
import {
|
|
68
|
+
_convertMessagesToAnthropicPayload,
|
|
69
|
+
modelDisallowsAssistantPrefill,
|
|
70
|
+
stripUnsupportedAssistantPrefill,
|
|
71
|
+
} from './utils/message_inputs';
|
|
68
72
|
import {
|
|
69
73
|
_makeMessageChunkFromAnthropicEvent,
|
|
70
74
|
getAnthropicUsageMetadata,
|
|
@@ -2637,6 +2641,61 @@ describe('Anthropic Reasoning with contentBlocks', () => {
|
|
|
2637
2641
|
});
|
|
2638
2642
|
});
|
|
2639
2643
|
|
|
2644
|
+
describe('Claude assistant prefill compatibility', () => {
|
|
2645
|
+
test.each([
|
|
2646
|
+
'claude-sonnet-4-6',
|
|
2647
|
+
'claude-sonnet-4-6@20260217',
|
|
2648
|
+
'claude-opus-4-7',
|
|
2649
|
+
'claude-opus-4-10',
|
|
2650
|
+
'global.anthropic.claude-opus-4-6-v1:0',
|
|
2651
|
+
'anthropic/claude-sonnet-4.6',
|
|
2652
|
+
'anthropic/claude-sonnet-4.12',
|
|
2653
|
+
])('detects %s as not supporting assistant prefill', (model) => {
|
|
2654
|
+
expect(modelDisallowsAssistantPrefill(model)).toBe(true);
|
|
2655
|
+
});
|
|
2656
|
+
|
|
2657
|
+
test.each([
|
|
2658
|
+
'claude-sonnet-4-5-20250929',
|
|
2659
|
+
'claude-opus-4-20250514',
|
|
2660
|
+
'anthropic.claude-opus-4-20250514-v1:0',
|
|
2661
|
+
'gpt-5.4',
|
|
2662
|
+
])('leaves %s prefill support unchanged', (model) => {
|
|
2663
|
+
expect(modelDisallowsAssistantPrefill(model)).toBe(false);
|
|
2664
|
+
});
|
|
2665
|
+
|
|
2666
|
+
test('strips trailing assistant messages for Claude 4.6+ requests', () => {
|
|
2667
|
+
const request = {
|
|
2668
|
+
model: 'claude-opus-4-6',
|
|
2669
|
+
max_tokens: 100,
|
|
2670
|
+
messages: [
|
|
2671
|
+
{ role: 'user' as const, content: 'What changed?' },
|
|
2672
|
+
{ role: 'assistant' as const, content: 'Draft prefill' },
|
|
2673
|
+
{ role: 'assistant' as const, content: 'Another prefill' },
|
|
2674
|
+
],
|
|
2675
|
+
};
|
|
2676
|
+
|
|
2677
|
+
const sanitized = stripUnsupportedAssistantPrefill(request);
|
|
2678
|
+
|
|
2679
|
+
expect(sanitized).not.toBe(request);
|
|
2680
|
+
expect(sanitized.messages).toEqual([
|
|
2681
|
+
{ role: 'user', content: 'What changed?' },
|
|
2682
|
+
]);
|
|
2683
|
+
});
|
|
2684
|
+
|
|
2685
|
+
test('does not strip assistant messages for older Claude models', () => {
|
|
2686
|
+
const request = {
|
|
2687
|
+
model: 'claude-sonnet-4-5-20250929',
|
|
2688
|
+
max_tokens: 100,
|
|
2689
|
+
messages: [
|
|
2690
|
+
{ role: 'user' as const, content: 'Write JSON only.' },
|
|
2691
|
+
{ role: 'assistant' as const, content: '{' },
|
|
2692
|
+
],
|
|
2693
|
+
};
|
|
2694
|
+
|
|
2695
|
+
expect(stripUnsupportedAssistantPrefill(request)).toBe(request);
|
|
2696
|
+
});
|
|
2697
|
+
});
|
|
2698
|
+
|
|
2640
2699
|
const opus46Model = 'claude-opus-4-6';
|
|
2641
2700
|
|
|
2642
2701
|
describe('Opus 4.6', () => {
|
|
@@ -49,6 +49,10 @@ type GoogleFunctionCallBlock = MessageContentComplex & {
|
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
const ANTHROPIC_EMPTY_TEXT_PLACEHOLDER = '_';
|
|
52
|
+
const CLAUDE_4_RELEASE_DATE_MODEL_PATTERN =
|
|
53
|
+
/claude-(?:opus|sonnet|haiku)-4-\d{8}(?:[-.@]|$)/i;
|
|
54
|
+
const CLAUDE_4_MINOR_MODEL_PATTERN =
|
|
55
|
+
/claude-(?:opus|sonnet|haiku)-4[-.](\d+)(?:[-.@]|$)/i;
|
|
52
56
|
|
|
53
57
|
function _formatImage(imageUrl: string) {
|
|
54
58
|
const parsed = parseBase64DataUrl({ dataUrl: imageUrl });
|
|
@@ -796,6 +800,48 @@ export function _convertMessagesToAnthropicPayload(
|
|
|
796
800
|
} as AnthropicMessageCreateParams;
|
|
797
801
|
}
|
|
798
802
|
|
|
803
|
+
export function modelDisallowsAssistantPrefill(model?: string): boolean {
|
|
804
|
+
const modelId = model ?? '';
|
|
805
|
+
if (CLAUDE_4_RELEASE_DATE_MODEL_PATTERN.test(modelId)) {
|
|
806
|
+
return false;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
const match = CLAUDE_4_MINOR_MODEL_PATTERN.exec(modelId);
|
|
810
|
+
if (!match) {
|
|
811
|
+
return false;
|
|
812
|
+
}
|
|
813
|
+
return Number(match[1]) >= 6;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
export function stripUnsupportedAssistantPrefill<
|
|
817
|
+
T extends Pick<AnthropicMessageCreateParams, 'messages'> & { model?: string },
|
|
818
|
+
>(request: T): T {
|
|
819
|
+
if (!modelDisallowsAssistantPrefill(request.model)) {
|
|
820
|
+
return request;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const messages = request.messages;
|
|
824
|
+
if (
|
|
825
|
+
messages.length <= 1 ||
|
|
826
|
+
messages[messages.length - 1]?.role !== 'assistant'
|
|
827
|
+
) {
|
|
828
|
+
return request;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const nextMessages = [...messages];
|
|
832
|
+
while (
|
|
833
|
+
nextMessages.length > 1 &&
|
|
834
|
+
nextMessages[nextMessages.length - 1]?.role === 'assistant'
|
|
835
|
+
) {
|
|
836
|
+
nextMessages.pop();
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
return {
|
|
840
|
+
...request,
|
|
841
|
+
messages: nextMessages,
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
|
|
799
845
|
function mergeMessages(messages: AnthropicMessageCreateParams['messages']) {
|
|
800
846
|
if (messages.length <= 1) {
|
|
801
847
|
return messages;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HumanMessage } from '@langchain/core/messages';
|
|
1
|
+
import { AIMessage, HumanMessage } from '@langchain/core/messages';
|
|
2
2
|
import { FakeListChatModel } from '@langchain/core/utils/testing';
|
|
3
3
|
import type { ToolCall } from '@langchain/core/messages/tool';
|
|
4
4
|
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
@@ -220,6 +220,92 @@ describe('Subagent Integration', () => {
|
|
|
220
220
|
expect(subagentTool).toBeDefined();
|
|
221
221
|
});
|
|
222
222
|
|
|
223
|
+
it('inherits eager event-tool settings into self-spawn child graphs', async () => {
|
|
224
|
+
const originalCreateWorkflow = StandardGraph.prototype.createWorkflow;
|
|
225
|
+
const observedChildGraphs: Array<{
|
|
226
|
+
eagerEventToolExecution: StandardGraph['eagerEventToolExecution'];
|
|
227
|
+
toolOutputReferences: StandardGraph['toolOutputReferences'];
|
|
228
|
+
eventToolExecutionAvailable: boolean;
|
|
229
|
+
}> = [];
|
|
230
|
+
const createWorkflowSpy = jest
|
|
231
|
+
.spyOn(StandardGraph.prototype, 'createWorkflow')
|
|
232
|
+
.mockImplementation(function (this: StandardGraph) {
|
|
233
|
+
if (this.runId?.includes('_sub_') === true) {
|
|
234
|
+
observedChildGraphs.push({
|
|
235
|
+
eagerEventToolExecution: this.eagerEventToolExecution,
|
|
236
|
+
toolOutputReferences: this.toolOutputReferences,
|
|
237
|
+
eventToolExecutionAvailable: this.eventToolExecutionAvailable,
|
|
238
|
+
});
|
|
239
|
+
return {
|
|
240
|
+
invoke: jest.fn(async () => ({
|
|
241
|
+
messages: [new AIMessage('child done')],
|
|
242
|
+
})),
|
|
243
|
+
} as unknown as ReturnType<StandardGraph['createWorkflow']>;
|
|
244
|
+
}
|
|
245
|
+
return originalCreateWorkflow.call(this);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const agentWithSelfSpawn: t.AgentInputs = {
|
|
249
|
+
agentId: 'self-parent',
|
|
250
|
+
provider: Providers.OPENAI,
|
|
251
|
+
clientOptions: { modelName: 'gpt-4o-mini', apiKey: 'test-key' },
|
|
252
|
+
instructions: 'Agent with self-spawn for context isolation.',
|
|
253
|
+
maxContextTokens: 8000,
|
|
254
|
+
toolDefinitions: [{ name: 'mcp_lookup' }],
|
|
255
|
+
subagentConfigs: [
|
|
256
|
+
{
|
|
257
|
+
type: 'isolated',
|
|
258
|
+
name: 'Isolated Worker',
|
|
259
|
+
description: 'Runs a task with isolated context',
|
|
260
|
+
self: true,
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const run = await Run.create<t.IState>({
|
|
266
|
+
runId: `self-spawn-eager-${Date.now()}`,
|
|
267
|
+
graphConfig: {
|
|
268
|
+
type: 'standard',
|
|
269
|
+
agents: [agentWithSelfSpawn],
|
|
270
|
+
},
|
|
271
|
+
customHandlers: {
|
|
272
|
+
[GraphEvents.ON_TOOL_EXECUTE]: {
|
|
273
|
+
handle: async () => undefined,
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
eagerEventToolExecution: { enabled: true },
|
|
277
|
+
toolOutputReferences: { enabled: true },
|
|
278
|
+
returnContent: true,
|
|
279
|
+
skipCleanup: true,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const context = (run.Graph as StandardGraph).agentContexts.get(
|
|
283
|
+
'self-parent'
|
|
284
|
+
);
|
|
285
|
+
const subagentTool = (context?.graphTools as t.GenericTool[]).find(
|
|
286
|
+
(tool) => 'name' in tool && tool.name === Constants.SUBAGENT
|
|
287
|
+
);
|
|
288
|
+
expect(subagentTool).toBeDefined();
|
|
289
|
+
|
|
290
|
+
await subagentTool!.invoke(
|
|
291
|
+
{
|
|
292
|
+
description: 'Use your MCP tool.',
|
|
293
|
+
subagent_type: 'isolated',
|
|
294
|
+
},
|
|
295
|
+
callerConfig
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
expect(observedChildGraphs).toEqual([
|
|
299
|
+
{
|
|
300
|
+
eagerEventToolExecution: { enabled: true },
|
|
301
|
+
toolOutputReferences: { enabled: true },
|
|
302
|
+
eventToolExecutionAvailable: true,
|
|
303
|
+
},
|
|
304
|
+
]);
|
|
305
|
+
|
|
306
|
+
createWorkflowSpy.mockRestore();
|
|
307
|
+
});
|
|
308
|
+
|
|
223
309
|
it('should not create subagent tool when maxSubagentDepth is 0', async () => {
|
|
224
310
|
const agentWithZeroDepth: t.AgentInputs = {
|
|
225
311
|
...createParentAgent(),
|