@librechat/agents 3.6.2 → 3.6.4
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 +35 -20
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hooks/HookRegistry.cjs +7 -1
- package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
- package/dist/cjs/hooks/index.cjs +1 -1
- package/dist/cjs/llm/openai/index.cjs +36 -0
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +4 -1
- package/dist/cjs/messages/core.cjs +3 -0
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/run.cjs +11 -5
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/tools/SubagentTool.cjs +8 -3
- package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +1 -1
- package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs +399 -0
- package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs.map +1 -0
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +168 -60
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/subagent/index.cjs +1 -0
- package/dist/esm/graphs/Graph.mjs +35 -20
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hooks/HookRegistry.mjs +7 -1
- package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
- package/dist/esm/hooks/index.mjs +1 -1
- package/dist/esm/llm/openai/index.mjs +37 -1
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/main.mjs +4 -3
- package/dist/esm/messages/core.mjs +3 -1
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/run.mjs +11 -5
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/tools/SubagentTool.mjs +8 -3
- package/dist/esm/tools/SubagentTool.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +1 -1
- package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs +399 -0
- package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs.map +1 -0
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +168 -60
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/subagent/index.mjs +1 -0
- package/dist/types/graphs/Graph.d.ts +6 -3
- package/dist/types/hooks/HookRegistry.d.ts +8 -0
- package/dist/types/messages/core.d.ts +2 -0
- package/dist/types/run.d.ts +1 -0
- package/dist/types/tools/SubagentTool.d.ts +3 -1
- package/dist/types/tools/subagent/InMemorySubagentTaskStore.d.ts +45 -0
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +26 -3
- package/dist/types/tools/subagent/index.d.ts +2 -0
- package/dist/types/types/graph.d.ts +11 -4
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/run.d.ts +6 -0
- package/dist/types/types/subagentTasks.d.ts +140 -0
- package/package.json +4 -4
- package/src/graphs/Graph.ts +84 -36
- package/src/hooks/HookRegistry.ts +18 -0
- package/src/llm/openai/index.ts +96 -0
- package/src/messages/core.ts +5 -0
- package/src/run.ts +20 -5
- package/src/tools/SubagentTool.ts +20 -2
- package/src/tools/subagent/InMemorySubagentTaskStore.ts +624 -0
- package/src/tools/subagent/SubagentExecutor.ts +341 -74
- package/src/tools/subagent/index.ts +2 -0
- package/src/types/graph.ts +11 -4
- package/src/types/index.ts +1 -0
- package/src/types/run.ts +6 -0
- package/src/types/subagentTasks.ts +129 -0
package/src/llm/openai/index.ts
CHANGED
|
@@ -43,6 +43,7 @@ import type { SeenScalarMetadata } from './streamMetadata';
|
|
|
43
43
|
import type { HeaderValue, HeadersLike } from './types';
|
|
44
44
|
import type { PromptCacheTtl } from '@/messages/cache';
|
|
45
45
|
import {
|
|
46
|
+
OPENAI_RESPONSES_ACTIVE_REASONING_ID_KEY,
|
|
46
47
|
OPENAI_RESPONSES_REPLAY_POSITIONS_KEY,
|
|
47
48
|
projectOpenAIResponsesToolMessageContent,
|
|
48
49
|
projectToolStreamContentForProvider,
|
|
@@ -606,6 +607,85 @@ function isResponsesReplayOutputItem(item: unknown): boolean {
|
|
|
606
607
|
);
|
|
607
608
|
}
|
|
608
609
|
|
|
610
|
+
type ResponsesReasoningSlot = {
|
|
611
|
+
encrypted_content?: string;
|
|
612
|
+
id?: string;
|
|
613
|
+
status?: string;
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
function getResponsesReasoningSlot(
|
|
617
|
+
reasoning: unknown
|
|
618
|
+
): ResponsesReasoningSlot | undefined {
|
|
619
|
+
return typeof reasoning === 'object' && reasoning != null
|
|
620
|
+
? (reasoning as ResponsesReasoningSlot)
|
|
621
|
+
: undefined;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function isSealedReasoningSlot(
|
|
625
|
+
slot: ResponsesReasoningSlot | undefined
|
|
626
|
+
): slot is ResponsesReasoningSlot & { encrypted_content: string } {
|
|
627
|
+
return (
|
|
628
|
+
typeof slot?.encrypted_content === 'string' &&
|
|
629
|
+
slot.encrypted_content.length > 0
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function resolveActiveReasoningItemId(
|
|
634
|
+
incoming: ResponsesReasoningSlot | undefined,
|
|
635
|
+
carried: unknown
|
|
636
|
+
): string | undefined {
|
|
637
|
+
if (typeof incoming?.id === 'string' && incoming.id.length > 0) {
|
|
638
|
+
return incoming.id;
|
|
639
|
+
}
|
|
640
|
+
return typeof carried === 'string' ? carried : undefined;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* A single `additional_kwargs.reasoning` slot has to stand in for a turn that
|
|
645
|
+
* can emit many reasoning items, and the chunk merge folds it field by field:
|
|
646
|
+
* `encrypted_content` and `status` are strings, so they concatenate, while
|
|
647
|
+
* `id` takes whichever item arrived last. An interrupted turn replays from
|
|
648
|
+
* that slot, handing the provider one item id welded to every item's
|
|
649
|
+
* ciphertext — rejected as "Encrypted content could not be decrypted or
|
|
650
|
+
* parsed". Keep the slot describing whichever item most recently sealed, so
|
|
651
|
+
* the id, its ciphertext, and its status always come from the same item.
|
|
652
|
+
*
|
|
653
|
+
* `activeItemId` tracks the item currently streaming, which is the one a
|
|
654
|
+
* terminal `encrypted_content` belongs to: the slot's own id has already been
|
|
655
|
+
* pinned back to the last sealed item by an earlier merge.
|
|
656
|
+
*/
|
|
657
|
+
function resealReasoningItemBoundary(
|
|
658
|
+
combined: AIMessageChunk,
|
|
659
|
+
accumulated: ResponsesReasoningSlot | undefined,
|
|
660
|
+
incoming: ResponsesReasoningSlot | undefined,
|
|
661
|
+
activeItemId: string | undefined
|
|
662
|
+
): void {
|
|
663
|
+
const merged = getResponsesReasoningSlot(
|
|
664
|
+
combined.additional_kwargs.reasoning
|
|
665
|
+
);
|
|
666
|
+
if (merged == null || incoming == null) {
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
if (isSealedReasoningSlot(incoming)) {
|
|
670
|
+
combined.additional_kwargs.reasoning = {
|
|
671
|
+
...merged,
|
|
672
|
+
id: activeItemId ?? merged.id,
|
|
673
|
+
encrypted_content: incoming.encrypted_content,
|
|
674
|
+
status: incoming.status,
|
|
675
|
+
};
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
if (!isSealedReasoningSlot(accumulated)) {
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
combined.additional_kwargs.reasoning = {
|
|
682
|
+
...merged,
|
|
683
|
+
id: accumulated.id,
|
|
684
|
+
encrypted_content: accumulated.encrypted_content,
|
|
685
|
+
status: accumulated.status,
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
|
|
609
689
|
/**
|
|
610
690
|
* LangChain's Responses converter places the authoritative terminal output in
|
|
611
691
|
* response_metadata.output. Its chunk merge has no way to delete provisional
|
|
@@ -619,10 +699,26 @@ class ResponsesReplayAIMessageChunk extends AIMessageChunk {
|
|
|
619
699
|
}
|
|
620
700
|
|
|
621
701
|
override concat(chunk: AIMessageChunk): this {
|
|
702
|
+
const accumulated = getResponsesReasoningSlot(
|
|
703
|
+
this.additional_kwargs.reasoning
|
|
704
|
+
);
|
|
705
|
+
const incoming = getResponsesReasoningSlot(
|
|
706
|
+
chunk.additional_kwargs.reasoning
|
|
707
|
+
);
|
|
708
|
+
const activeItemId = resolveActiveReasoningItemId(
|
|
709
|
+
incoming,
|
|
710
|
+
this.additional_kwargs[OPENAI_RESPONSES_ACTIVE_REASONING_ID_KEY]
|
|
711
|
+
);
|
|
622
712
|
const combined = super.concat(chunk);
|
|
713
|
+
resealReasoningItemBoundary(combined, accumulated, incoming, activeItemId);
|
|
714
|
+
if (activeItemId != null) {
|
|
715
|
+
combined.additional_kwargs[OPENAI_RESPONSES_ACTIVE_REASONING_ID_KEY] =
|
|
716
|
+
activeItemId;
|
|
717
|
+
}
|
|
623
718
|
if (!Array.isArray(chunk.response_metadata.output)) {
|
|
624
719
|
return combined;
|
|
625
720
|
}
|
|
721
|
+
delete combined.additional_kwargs[OPENAI_RESPONSES_ACTIVE_REASONING_ID_KEY];
|
|
626
722
|
delete combined.additional_kwargs[OPENAI_RESPONSES_REPLAY_POSITIONS_KEY];
|
|
627
723
|
const toolOutputs = combined.additional_kwargs.tool_outputs;
|
|
628
724
|
if (!Array.isArray(toolOutputs)) {
|
package/src/messages/core.ts
CHANGED
|
@@ -671,6 +671,10 @@ type ResponsesReplayProjection = 'fallback' | 'native';
|
|
|
671
671
|
export const OPENAI_RESPONSES_REPLAY_POSITIONS_KEY =
|
|
672
672
|
'__openai_responses_replay_positions__';
|
|
673
673
|
|
|
674
|
+
/** Reasoning item currently streaming, so a terminal ciphertext seals against its own id. */
|
|
675
|
+
export const OPENAI_RESPONSES_ACTIVE_REASONING_ID_KEY =
|
|
676
|
+
'__openai_responses_active_reasoning_id__';
|
|
677
|
+
|
|
674
678
|
export type ResponsesReplayPosition = {
|
|
675
679
|
contentIndex?: number;
|
|
676
680
|
itemId: string;
|
|
@@ -1813,6 +1817,7 @@ function projectPreemptedOpenAIResponsesMessage(
|
|
|
1813
1817
|
}
|
|
1814
1818
|
delete additionalKwargs.tool_outputs;
|
|
1815
1819
|
delete additionalKwargs[OPENAI_RESPONSES_REPLAY_POSITIONS_KEY];
|
|
1820
|
+
delete additionalKwargs[OPENAI_RESPONSES_ACTIVE_REASONING_ID_KEY];
|
|
1816
1821
|
delete additionalKwargs.__openai_function_call_ids__;
|
|
1817
1822
|
delete additionalKwargs.__openai_custom_tool_call_ids__;
|
|
1818
1823
|
|
package/src/run.ts
CHANGED
|
@@ -296,6 +296,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
296
296
|
private subagentUsageSink?: t.SubagentUsageSink;
|
|
297
297
|
private preemption?: t.StreamPreemption;
|
|
298
298
|
private streamLimits?: t.StreamLimits;
|
|
299
|
+
private subagentTasks?: t.SubagentTaskConfig;
|
|
299
300
|
private indexTokenCountMap?: Record<string, number>;
|
|
300
301
|
calibrationRatio: number = 1;
|
|
301
302
|
graphRunnable?: t.CompiledStateWorkflow;
|
|
@@ -363,6 +364,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
363
364
|
this.interruptingToolNames = config.interruptingToolNames;
|
|
364
365
|
this.toolExecution = config.toolExecution;
|
|
365
366
|
this.subagentUsageSink = config.subagentUsageSink;
|
|
367
|
+
this.subagentTasks = config.subagentTasks;
|
|
366
368
|
this.preemption = config.preemption;
|
|
367
369
|
this.streamLimits = config.streamLimits;
|
|
368
370
|
|
|
@@ -455,6 +457,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
455
457
|
indexTokenCountMap: this.indexTokenCountMap,
|
|
456
458
|
calibrationRatio: this.calibrationRatio,
|
|
457
459
|
subagentUsageSink: this.subagentUsageSink,
|
|
460
|
+
subagentTasks: this.subagentTasks,
|
|
458
461
|
preemption: this.preemption,
|
|
459
462
|
streamLimits: this.streamLimits,
|
|
460
463
|
},
|
|
@@ -493,6 +496,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
493
496
|
indexTokenCountMap: this.indexTokenCountMap,
|
|
494
497
|
calibrationRatio: this.calibrationRatio,
|
|
495
498
|
subagentUsageSink: this.subagentUsageSink,
|
|
499
|
+
subagentTasks: this.subagentTasks,
|
|
496
500
|
preemption: this.preemption,
|
|
497
501
|
streamLimits: this.streamLimits,
|
|
498
502
|
},
|
|
@@ -826,12 +830,23 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
826
830
|
* The producer stamped `completed_at` before dispatch. Carrying it
|
|
827
831
|
* through keeps the recorded duration the tool's, not the host
|
|
828
832
|
* handler's — this runs after an arbitrarily slow handler resolves.
|
|
833
|
+
*
|
|
834
|
+
* Isolated because this is a `finally`: a throw here would
|
|
835
|
+
* REPLACE an in-flight error from the handler above, so the
|
|
836
|
+
* host's original failure would be reported as whatever went
|
|
837
|
+
* wrong during closure instead. Closing is best-effort at this
|
|
838
|
+
* point either way — the end-of-run sweep still stamps any step
|
|
839
|
+
* this misses.
|
|
829
840
|
*/
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
841
|
+
try {
|
|
842
|
+
await this.Graph.recordStepCompletion(completion.stepId, {
|
|
843
|
+
toolCallId: completion.toolCallId,
|
|
844
|
+
metadata,
|
|
845
|
+
at: completion.completedAt,
|
|
846
|
+
});
|
|
847
|
+
} catch (_e) {
|
|
848
|
+
/** Never mask the handler's error with a closure failure */
|
|
849
|
+
}
|
|
835
850
|
}
|
|
836
851
|
}
|
|
837
852
|
}
|
|
@@ -27,6 +27,9 @@ const DESCRIPTION_PROP_DESCRIPTION =
|
|
|
27
27
|
const SUBAGENT_TYPE_PROP_DESCRIPTION =
|
|
28
28
|
'Which subagent type to delegate to. Must be one of the available types.';
|
|
29
29
|
|
|
30
|
+
const RUN_IN_BACKGROUND_PROP_DESCRIPTION =
|
|
31
|
+
'Set true to start the subagent as a detached process-local task and return a background_task_id immediately. Poll the host background-task tool to collect its result. The task can outlive this turn but does not survive a process restart.';
|
|
32
|
+
|
|
30
33
|
export const SubagentToolSchema = {
|
|
31
34
|
type: 'object',
|
|
32
35
|
properties: {
|
|
@@ -54,7 +57,10 @@ export const SubagentToolDefinition: LCTool = {
|
|
|
54
57
|
* Used by `Graph.createAgentNode()` when constructing the runtime tool instance.
|
|
55
58
|
* Extends `SubagentToolSchema` by populating `subagent_type.enum` dynamically.
|
|
56
59
|
*/
|
|
57
|
-
export function buildSubagentToolParams(
|
|
60
|
+
export function buildSubagentToolParams(
|
|
61
|
+
configs: SubagentConfig[],
|
|
62
|
+
options: { background?: boolean } = {}
|
|
63
|
+
): {
|
|
58
64
|
name: string;
|
|
59
65
|
schema: JsonSchemaType;
|
|
60
66
|
description: string;
|
|
@@ -79,10 +85,22 @@ export function buildSubagentToolParams(configs: SubagentConfig[]): {
|
|
|
79
85
|
enum: types,
|
|
80
86
|
description: `${SUBAGENT_TYPE_PROP_DESCRIPTION} Available: ${types.join(', ')}.`,
|
|
81
87
|
},
|
|
88
|
+
...(options.background === true
|
|
89
|
+
? {
|
|
90
|
+
run_in_background: {
|
|
91
|
+
type: 'boolean',
|
|
92
|
+
description: RUN_IN_BACKGROUND_PROP_DESCRIPTION,
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
: {}),
|
|
82
96
|
},
|
|
83
97
|
required: ['description', 'subagent_type'],
|
|
84
98
|
},
|
|
85
|
-
description: `${SubagentToolDescription}
|
|
99
|
+
description: `${SubagentToolDescription}${
|
|
100
|
+
options.background === true
|
|
101
|
+
? '\n\nBACKGROUND EXECUTION:\n- Set run_in_background to true when you do not need the result immediately. The call returns a background_task_id; use the host background-task tools to poll, steer, queue, interrupt, or cancel it.'
|
|
102
|
+
: ''
|
|
103
|
+
}\n\nAvailable types:\n${typeDescriptions}`,
|
|
86
104
|
};
|
|
87
105
|
}
|
|
88
106
|
|