@librechat/agents 3.3.3 → 3.3.5
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/agents/AgentContext.cjs +2 -1
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/constants.cjs +21 -0
- package/dist/cjs/common/constants.cjs.map +1 -1
- package/dist/cjs/events.cjs +10 -1
- package/dist/cjs/events.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +793 -111
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +3 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/hooks/HookRegistry.cjs +25 -0
- package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
- package/dist/cjs/hooks/index.cjs +12 -0
- package/dist/cjs/hooks/index.cjs.map +1 -1
- package/dist/cjs/hooks/types.cjs +1 -0
- package/dist/cjs/hooks/types.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +8 -0
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +79 -6
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +38 -13
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/google/utils/common.cjs +19 -7
- package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
- package/dist/cjs/llm/invoke.cjs +331 -5
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +41 -5
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/utils/index.cjs +25 -4
- package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs +4 -1
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/llm/preempt.cjs +132 -0
- package/dist/cjs/llm/preempt.cjs.map +1 -0
- package/dist/cjs/main.cjs +36 -5
- package/dist/cjs/messages/alternation.cjs +92 -0
- package/dist/cjs/messages/alternation.cjs.map +1 -0
- package/dist/cjs/messages/cache.cjs +54 -33
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/contextPruning.cjs +17 -43
- package/dist/cjs/messages/contextPruning.cjs.map +1 -1
- package/dist/cjs/messages/core.cjs +315 -23
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +290 -60
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/handoffCue.cjs +63 -0
- package/dist/cjs/messages/handoffCue.cjs.map +1 -0
- package/dist/cjs/messages/index.cjs +4 -1
- package/dist/cjs/messages/injected.cjs +60 -0
- package/dist/cjs/messages/injected.cjs.map +1 -0
- package/dist/cjs/messages/prune.cjs +503 -127
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/run.cjs +80 -7
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/handlers.cjs +18 -9
- package/dist/cjs/session/handlers.cjs.map +1 -1
- package/dist/cjs/stream.cjs +35 -10
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +28 -14
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +56 -63
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/search/keenable-scraper.cjs +90 -0
- package/dist/cjs/tools/search/keenable-scraper.cjs.map +1 -0
- package/dist/cjs/tools/search/tool.cjs +9 -1
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/toolOutputReferences.cjs +6 -0
- package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
- package/dist/cjs/utils/index.cjs +1 -1
- package/dist/cjs/utils/llm.cjs +1 -1
- package/dist/cjs/utils/llm.cjs.map +1 -1
- package/dist/cjs/utils/tokens.cjs +307 -20
- package/dist/cjs/utils/tokens.cjs.map +1 -1
- package/dist/cjs/utils/toolContent.cjs +1514 -0
- package/dist/cjs/utils/toolContent.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs +2 -1
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/constants.mjs +19 -1
- package/dist/esm/common/constants.mjs.map +1 -1
- package/dist/esm/events.mjs +10 -1
- package/dist/esm/events.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +798 -116
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +3 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/hooks/HookRegistry.mjs +25 -0
- package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
- package/dist/esm/hooks/index.mjs +12 -1
- package/dist/esm/hooks/index.mjs.map +1 -1
- package/dist/esm/hooks/types.mjs +1 -0
- package/dist/esm/hooks/types.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +9 -1
- package/dist/esm/langfuse.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +79 -6
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +38 -13
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/google/utils/common.mjs +19 -7
- package/dist/esm/llm/google/utils/common.mjs.map +1 -1
- package/dist/esm/llm/invoke.mjs +332 -8
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +41 -5
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openai/utils/index.mjs +25 -4
- package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs +4 -1
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/llm/preempt.mjs +131 -0
- package/dist/esm/llm/preempt.mjs.map +1 -0
- package/dist/esm/main.mjs +14 -11
- package/dist/esm/messages/alternation.mjs +91 -0
- package/dist/esm/messages/alternation.mjs.map +1 -0
- package/dist/esm/messages/cache.mjs +55 -34
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/contextPruning.mjs +17 -43
- package/dist/esm/messages/contextPruning.mjs.map +1 -1
- package/dist/esm/messages/core.mjs +305 -24
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +290 -61
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/handoffCue.mjs +61 -0
- package/dist/esm/messages/handoffCue.mjs.map +1 -0
- package/dist/esm/messages/index.mjs +4 -1
- package/dist/esm/messages/injected.mjs +60 -0
- package/dist/esm/messages/injected.mjs.map +1 -0
- package/dist/esm/messages/prune.mjs +500 -128
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/run.mjs +80 -7
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/handlers.mjs +19 -10
- package/dist/esm/session/handlers.mjs.map +1 -1
- package/dist/esm/stream.mjs +34 -11
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +28 -14
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +56 -63
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/search/keenable-scraper.mjs +88 -0
- package/dist/esm/tools/search/keenable-scraper.mjs.map +1 -0
- package/dist/esm/tools/search/tool.mjs +9 -1
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/toolOutputReferences.mjs +6 -0
- package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +1 -1
- package/dist/esm/utils/llm.mjs +1 -1
- package/dist/esm/utils/llm.mjs.map +1 -1
- package/dist/esm/utils/tokens.mjs +307 -21
- package/dist/esm/utils/tokens.mjs.map +1 -1
- package/dist/esm/utils/toolContent.mjs +1503 -0
- package/dist/esm/utils/toolContent.mjs.map +1 -0
- package/dist/types/common/constants.d.ts +18 -0
- package/dist/types/graphs/Graph.d.ts +139 -2
- package/dist/types/hooks/HookRegistry.d.ts +15 -0
- package/dist/types/hooks/index.d.ts +12 -1
- package/dist/types/hooks/types.d.ts +45 -6
- package/dist/types/index.d.ts +1 -1
- package/dist/types/llm/invoke.d.ts +34 -1
- package/dist/types/llm/openai/index.d.ts +4 -0
- package/dist/types/llm/openai/utils/index.d.ts +2 -0
- package/dist/types/llm/openrouter/index.d.ts +5 -0
- package/dist/types/llm/preempt.d.ts +40 -0
- package/dist/types/messages/alternation.d.ts +23 -0
- package/dist/types/messages/contextPruning.d.ts +2 -1
- package/dist/types/messages/core.d.ts +36 -0
- package/dist/types/messages/format.d.ts +6 -0
- package/dist/types/messages/handoffCue.d.ts +40 -0
- package/dist/types/messages/index.d.ts +3 -0
- package/dist/types/messages/injected.d.ts +3 -0
- package/dist/types/messages/prune.d.ts +16 -12
- package/dist/types/run.d.ts +7 -0
- package/dist/types/stream.d.ts +14 -0
- package/dist/types/tools/ToolNode.d.ts +0 -7
- package/dist/types/tools/search/keenable-scraper.d.ts +15 -0
- package/dist/types/tools/search/types.d.ts +31 -2
- package/dist/types/types/graph.d.ts +8 -1
- package/dist/types/types/run.d.ts +65 -0
- package/dist/types/types/stream.d.ts +1 -26
- package/dist/types/utils/tokens.d.ts +7 -0
- package/dist/types/utils/toolContent.d.ts +107 -0
- package/package.json +1 -1
- package/src/__tests__/stream.eagerEventExecution.test.ts +74 -0
- package/src/agents/AgentContext.ts +1 -0
- package/src/{splitStream.test.ts → aggregator.test.ts} +59 -666
- package/src/common/constants.ts +21 -0
- package/src/events.ts +15 -1
- package/src/graphs/Graph.ts +1261 -198
- package/src/graphs/MultiAgentGraph.ts +6 -1
- package/src/graphs/__tests__/Graph.contextOverflow.test.ts +1139 -4
- package/src/graphs/__tests__/Graph.preemptSignal.test.ts +126 -0
- package/src/hooks/HookRegistry.ts +40 -0
- package/src/hooks/__tests__/preemptBoundary.test.ts +152 -0
- package/src/hooks/index.ts +16 -2
- package/src/hooks/types.ts +47 -3
- package/src/index.ts +1 -1
- package/src/langfuse.ts +26 -1
- package/src/llm/anthropic/utils/message_inputs.ts +158 -12
- package/src/llm/anthropic/utils/tool-id-normalization.test.ts +109 -0
- package/src/llm/bedrock/utils/message_inputs.test.ts +131 -1
- package/src/llm/bedrock/utils/message_inputs.ts +100 -24
- package/src/llm/bedrock/utils/toolResultCachePoint.test.ts +15 -0
- package/src/llm/google/utils/common.test.ts +75 -9
- package/src/llm/google/utils/common.ts +34 -3
- package/src/llm/invoke.alternation.test.ts +87 -0
- package/src/llm/invoke.handoffCue.test.ts +112 -0
- package/src/llm/invoke.test.ts +488 -0
- package/src/llm/invoke.ts +611 -8
- package/src/llm/openai/index.ts +106 -6
- package/src/llm/openai/utils/index.ts +109 -53
- package/src/llm/openai/utils/messages.test.ts +330 -1
- package/src/llm/openrouter/index.ts +12 -2
- package/src/llm/openrouter/reasoning.test.ts +314 -0
- package/src/llm/preempt.test.ts +323 -0
- package/src/llm/preempt.ts +178 -0
- package/src/messages/__tests__/observationMasking.test.ts +93 -2
- package/src/messages/alternation.test.ts +212 -0
- package/src/messages/alternation.ts +112 -0
- package/src/messages/cache.tail.test.ts +193 -0
- package/src/messages/cache.test.ts +113 -0
- package/src/messages/cache.ts +92 -60
- package/src/messages/contextPruning.test.ts +184 -0
- package/src/messages/contextPruning.ts +49 -42
- package/src/messages/core.ts +653 -40
- package/src/messages/ensureThinkingBlock.test.ts +49 -1
- package/src/messages/foldToollessToolBlocks.test.ts +163 -5
- package/src/messages/format.ts +454 -91
- package/src/messages/formatAgentMessages.steer.test.ts +267 -0
- package/src/messages/formatAgentMessages.test.ts +604 -14
- package/src/messages/formatAgentMessages.tools.test.ts +103 -0
- package/src/messages/handoffCue.test.ts +96 -0
- package/src/messages/handoffCue.ts +78 -0
- package/src/messages/index.ts +3 -0
- package/src/messages/injected.test.ts +90 -0
- package/src/messages/injected.ts +74 -0
- package/src/messages/prune.ts +996 -183
- package/src/run.ts +91 -6
- package/src/scripts/preempt-probe.ts +330 -0
- package/src/scripts/preempt-scenarios.ts +388 -0
- package/src/session/handlers.ts +32 -12
- package/src/specs/handoffCue.test.ts +165 -0
- package/src/specs/langfuse-callbacks.test.ts +352 -2
- package/src/specs/preemptSeal.test.ts +309 -0
- package/src/specs/prune.test.ts +1083 -6
- package/src/specs/summarization-unit.test.ts +105 -0
- package/src/specs/tokens.test.ts +609 -32
- package/src/stream.dispatch.test.ts +63 -0
- package/src/stream.ts +59 -23
- package/src/summarization/__tests__/node.test.ts +77 -0
- package/src/summarization/node.ts +46 -18
- package/src/tools/ToolNode.ts +150 -99
- package/src/tools/__tests__/ToolNode.onResultCompletion.test.ts +148 -1
- package/src/tools/__tests__/ToolNode.outputReferences.test.ts +112 -3
- package/src/tools/__tests__/annotateMessagesForLLM.test.ts +32 -0
- package/src/tools/__tests__/directToolHooks.test.ts +255 -7
- package/src/tools/search/keenable-scraper.test.ts +153 -0
- package/src/tools/search/keenable-scraper.ts +137 -0
- package/src/tools/search/tool.ts +13 -2
- package/src/tools/search/types.ts +50 -3
- package/src/tools/toolOutputReferences.ts +6 -0
- package/src/types/graph.ts +12 -1
- package/src/types/run.ts +67 -0
- package/src/types/stream.ts +1 -42
- package/src/utils/llm.test.ts +18 -0
- package/src/utils/llm.ts +4 -1
- package/src/utils/tokens.ts +586 -31
- package/src/utils/toolContent.test.ts +1432 -0
- package/src/utils/toolContent.ts +2707 -0
- package/dist/cjs/splitStream.cjs +0 -151
- package/dist/cjs/splitStream.cjs.map +0 -1
- package/dist/esm/splitStream.mjs +0 -150
- package/dist/esm/splitStream.mjs.map +0 -1
- package/dist/types/mockStream.d.ts +0 -32
- package/dist/types/splitStream.d.ts +0 -37
- package/src/mockStream.ts +0 -99
- package/src/splitStream.ts +0 -234
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AIMessage, HumanMessage, ToolMessage } from '@langchain/core/messages';
|
|
2
2
|
import type { TPayload } from '@/types';
|
|
3
|
+
import { _convertMessagesToAnthropicPayload } from '@/llm/anthropic/utils/message_inputs';
|
|
3
4
|
import { ContentTypes, Constants, Providers } from '@/common';
|
|
4
5
|
import { formatAgentMessages } from './format';
|
|
5
6
|
|
|
@@ -324,3 +325,269 @@ describe('formatAgentMessages steer replay', () => {
|
|
|
324
325
|
]);
|
|
325
326
|
});
|
|
326
327
|
});
|
|
328
|
+
|
|
329
|
+
describe('formatAgentMessages trailing-steer anchor', () => {
|
|
330
|
+
const steerPart = (text: string): Record<string, unknown> =>
|
|
331
|
+
({
|
|
332
|
+
type: ContentTypes.STEER,
|
|
333
|
+
[ContentTypes.STEER]: text,
|
|
334
|
+
}) as unknown as Record<string, unknown>;
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* A steer that ends an assistant message leaves the replay on a
|
|
338
|
+
* HumanMessage. Followed by the next turn's user message, that reaches the
|
|
339
|
+
* provider as two adjacent user turns — a hard error on Bedrock and
|
|
340
|
+
* Mistral. Reachable today through abort, not only through preemption.
|
|
341
|
+
*/
|
|
342
|
+
it('anchors a trailing steer when another turn follows', () => {
|
|
343
|
+
const payload: TPayload = [
|
|
344
|
+
{ role: 'user', content: 'Original request' },
|
|
345
|
+
{
|
|
346
|
+
role: 'assistant',
|
|
347
|
+
content: [
|
|
348
|
+
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Working on it.' },
|
|
349
|
+
steerPart('Actually, stop and do the other thing.'),
|
|
350
|
+
],
|
|
351
|
+
},
|
|
352
|
+
{ role: 'user', content: 'Next turn' },
|
|
353
|
+
];
|
|
354
|
+
|
|
355
|
+
const { messages } = formatAgentMessages(payload);
|
|
356
|
+
|
|
357
|
+
const steerIndex = messages.findIndex(
|
|
358
|
+
(message) =>
|
|
359
|
+
message instanceof HumanMessage &&
|
|
360
|
+
message.additional_kwargs.source === 'steer'
|
|
361
|
+
);
|
|
362
|
+
expect(steerIndex).toBeGreaterThan(0);
|
|
363
|
+
|
|
364
|
+
const anchor = messages[steerIndex + 1];
|
|
365
|
+
expect(anchor).toBeInstanceOf(AIMessage);
|
|
366
|
+
expect(anchor.content).not.toBe('');
|
|
367
|
+
|
|
368
|
+
const following = messages[steerIndex + 2];
|
|
369
|
+
expect(following).toBeInstanceOf(HumanMessage);
|
|
370
|
+
expect(following.content).toEqual([{ type: 'text', text: 'Next turn' }]);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* `messagesStateReducer` treats a repeated id as replace-in-place, so an
|
|
375
|
+
* anchor stamped with the shared `sourceMessageId` would overwrite the very
|
|
376
|
+
* steer it exists to protect. It must reach the reducer unstamped.
|
|
377
|
+
*/
|
|
378
|
+
it('does not stamp the anchor with the shared source message id', () => {
|
|
379
|
+
const payload: TPayload = [
|
|
380
|
+
{ role: 'user', content: 'Original request' },
|
|
381
|
+
{
|
|
382
|
+
messageId: 'assistant_1',
|
|
383
|
+
role: 'assistant',
|
|
384
|
+
content: [
|
|
385
|
+
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Partial answer.' },
|
|
386
|
+
steerPart('Change of plan.'),
|
|
387
|
+
],
|
|
388
|
+
} as unknown as TPayload[number],
|
|
389
|
+
{ role: 'user', content: 'Next turn' },
|
|
390
|
+
];
|
|
391
|
+
|
|
392
|
+
const { messages } = formatAgentMessages(payload);
|
|
393
|
+
|
|
394
|
+
const steerIndex = messages.findIndex(
|
|
395
|
+
(message) => message.additional_kwargs.source === 'steer'
|
|
396
|
+
);
|
|
397
|
+
expect(steerIndex).toBeGreaterThan(0);
|
|
398
|
+
expect(messages[steerIndex].id).toBe('assistant_1');
|
|
399
|
+
expect(messages[steerIndex - 1].id).toBe('assistant_1');
|
|
400
|
+
|
|
401
|
+
const anchor = messages[steerIndex + 1];
|
|
402
|
+
expect(anchor).toBeInstanceOf(AIMessage);
|
|
403
|
+
expect(anchor.content).not.toBe('');
|
|
404
|
+
expect(anchor.id).not.toBe('assistant_1');
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* The anchor exists to keep the sequence provider-valid, so the assertion
|
|
409
|
+
* that matters is what a provider actually receives. An empty-string
|
|
410
|
+
* assistant turn with no tool calls survives the Anthropic converter
|
|
411
|
+
* verbatim — the empty-text repair covers only array content and tool-call
|
|
412
|
+
* turns — so an empty anchor would trade adjacent user turns for an
|
|
413
|
+
* empty-content 400.
|
|
414
|
+
*/
|
|
415
|
+
it('survives the Anthropic converter with non-empty assistant content', () => {
|
|
416
|
+
const payload: TPayload = [
|
|
417
|
+
{ role: 'user', content: 'Original request' },
|
|
418
|
+
{
|
|
419
|
+
role: 'assistant',
|
|
420
|
+
content: [
|
|
421
|
+
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Partial answer.' },
|
|
422
|
+
steerPart('Change of plan.'),
|
|
423
|
+
],
|
|
424
|
+
},
|
|
425
|
+
{ role: 'user', content: 'Next turn' },
|
|
426
|
+
];
|
|
427
|
+
|
|
428
|
+
const { messages } = formatAgentMessages(payload);
|
|
429
|
+
const { messages: anthropicMessages } =
|
|
430
|
+
_convertMessagesToAnthropicPayload(messages);
|
|
431
|
+
|
|
432
|
+
for (const message of anthropicMessages) {
|
|
433
|
+
if (message.role !== 'assistant') {
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
if (typeof message.content === 'string') {
|
|
437
|
+
expect(message.content.trim()).not.toBe('');
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
expect(message.content.length).toBeGreaterThan(0);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const roles = anthropicMessages.map((message) => message.role);
|
|
444
|
+
for (let i = 1; i < roles.length; i++) {
|
|
445
|
+
expect(roles[i] === 'user' && roles[i - 1] === 'user').toBe(false);
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* The old lookahead only proved a later ENTRY existed, not that it emitted.
|
|
451
|
+
* An entry with empty content is skipped silently, so a trailing steer
|
|
452
|
+
* followed only by such entries would leave the anchor as the final turn —
|
|
453
|
+
* an assistant prefill with no request after it.
|
|
454
|
+
*/
|
|
455
|
+
it('omits the anchor when no later payload entry emits a message', () => {
|
|
456
|
+
const payload: TPayload = [
|
|
457
|
+
{ role: 'user', content: 'Original request' },
|
|
458
|
+
{
|
|
459
|
+
role: 'assistant',
|
|
460
|
+
content: [
|
|
461
|
+
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Partial answer.' },
|
|
462
|
+
steerPart('Change of plan.'),
|
|
463
|
+
],
|
|
464
|
+
},
|
|
465
|
+
{ role: 'user', content: [] } as unknown as TPayload[number],
|
|
466
|
+
];
|
|
467
|
+
|
|
468
|
+
const { messages } = formatAgentMessages(payload);
|
|
469
|
+
const last = messages[messages.length - 1];
|
|
470
|
+
expect(last.additional_kwargs.source).toBe('steer');
|
|
471
|
+
expect(
|
|
472
|
+
messages.some(
|
|
473
|
+
(m) => m instanceof AIMessage && m.content === '_'
|
|
474
|
+
)
|
|
475
|
+
).toBe(false);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it('leaves the user turn last when the steer ends the payload', () => {
|
|
479
|
+
const payload: TPayload = [
|
|
480
|
+
{ role: 'user', content: 'Original request' },
|
|
481
|
+
{
|
|
482
|
+
role: 'assistant',
|
|
483
|
+
content: [
|
|
484
|
+
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Working on it.' },
|
|
485
|
+
steerPart('Actually, stop and do the other thing.'),
|
|
486
|
+
],
|
|
487
|
+
},
|
|
488
|
+
];
|
|
489
|
+
|
|
490
|
+
const { messages } = formatAgentMessages(payload);
|
|
491
|
+
|
|
492
|
+
const last = messages[messages.length - 1];
|
|
493
|
+
expect(last).toBeInstanceOf(HumanMessage);
|
|
494
|
+
expect(last.additional_kwargs.source).toBe('steer');
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it('does not anchor when assistant content follows the steer', () => {
|
|
498
|
+
const payload: TPayload = [
|
|
499
|
+
{ role: 'user', content: 'Original request' },
|
|
500
|
+
{
|
|
501
|
+
role: 'assistant',
|
|
502
|
+
content: [
|
|
503
|
+
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Working on it.' },
|
|
504
|
+
steerPart('Focus on item two.'),
|
|
505
|
+
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Focusing.' },
|
|
506
|
+
],
|
|
507
|
+
},
|
|
508
|
+
{ role: 'user', content: 'Next turn' },
|
|
509
|
+
];
|
|
510
|
+
|
|
511
|
+
const { messages } = formatAgentMessages(payload);
|
|
512
|
+
|
|
513
|
+
const emptyAssistants = messages.filter(
|
|
514
|
+
(message) => message instanceof AIMessage && message.content === ''
|
|
515
|
+
);
|
|
516
|
+
expect(emptyAssistants).toHaveLength(0);
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* A trailing steer followed by another assistant ENTRY needs no anchor —
|
|
521
|
+
* that entry's own assistant turn IS the separation. Emitting the
|
|
522
|
+
* placeholder anyway would put two assistant turns back to back, which
|
|
523
|
+
* strict-alternation providers can reject and nothing downstream merges
|
|
524
|
+
* (`coalesceAdjacentUserTurns` merges user turns only).
|
|
525
|
+
*/
|
|
526
|
+
it('suppresses the anchor when the next payload entry is an assistant turn', () => {
|
|
527
|
+
const payload: TPayload = [
|
|
528
|
+
{ role: 'user', content: 'Original request' },
|
|
529
|
+
{
|
|
530
|
+
role: 'assistant',
|
|
531
|
+
content: [
|
|
532
|
+
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Working on it.' },
|
|
533
|
+
steerPart('Actually, do the other thing.'),
|
|
534
|
+
],
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
role: 'assistant',
|
|
538
|
+
content: [
|
|
539
|
+
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Doing the other thing.' },
|
|
540
|
+
],
|
|
541
|
+
},
|
|
542
|
+
];
|
|
543
|
+
|
|
544
|
+
const { messages } = formatAgentMessages(payload);
|
|
545
|
+
|
|
546
|
+
expect(messages.map((message) => message.constructor.name)).toEqual([
|
|
547
|
+
'HumanMessage',
|
|
548
|
+
'AIMessage',
|
|
549
|
+
'HumanMessage',
|
|
550
|
+
'AIMessage',
|
|
551
|
+
]);
|
|
552
|
+
expect(messages[2].additional_kwargs.source).toBe('steer');
|
|
553
|
+
expect(messages[3].content).toEqual([
|
|
554
|
+
{ type: 'text', text: 'Doing the other thing.' },
|
|
555
|
+
]);
|
|
556
|
+
expect(
|
|
557
|
+
messages.some(
|
|
558
|
+
(message) => message instanceof AIMessage && message.content === '_'
|
|
559
|
+
)
|
|
560
|
+
).toBe(false);
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it('never emits two adjacent user turns around a trailing steer', () => {
|
|
564
|
+
const payload: TPayload = [
|
|
565
|
+
{ role: 'user', content: 'Original request' },
|
|
566
|
+
{
|
|
567
|
+
role: 'assistant',
|
|
568
|
+
content: [
|
|
569
|
+
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Partial answer.' },
|
|
570
|
+
steerPart('Change of plan.'),
|
|
571
|
+
],
|
|
572
|
+
},
|
|
573
|
+
{ role: 'user', content: 'Next turn' },
|
|
574
|
+
{
|
|
575
|
+
role: 'assistant',
|
|
576
|
+
content: [
|
|
577
|
+
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Second answer.' },
|
|
578
|
+
steerPart('Change again.'),
|
|
579
|
+
],
|
|
580
|
+
},
|
|
581
|
+
{ role: 'user', content: 'Third turn' },
|
|
582
|
+
];
|
|
583
|
+
|
|
584
|
+
const { messages } = formatAgentMessages(payload);
|
|
585
|
+
|
|
586
|
+
for (let i = 1; i < messages.length; i++) {
|
|
587
|
+
const adjacentHumans =
|
|
588
|
+
messages[i] instanceof HumanMessage &&
|
|
589
|
+
messages[i - 1] instanceof HumanMessage;
|
|
590
|
+
expect(adjacentHumans).toBe(false);
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
});
|