@librechat/agents 3.4.4 → 3.4.6
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/common/enum.cjs +2 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +268 -14
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hitl/askUserQuestions.cjs +66 -0
- package/dist/cjs/hitl/askUserQuestions.cjs.map +1 -0
- package/dist/cjs/hitl/askUserQuestionsInterrupt.cjs +46 -0
- package/dist/cjs/hitl/askUserQuestionsInterrupt.cjs.map +1 -0
- package/dist/cjs/hitl/index.cjs +2 -0
- package/dist/cjs/langfuseTraceShaping.cjs +9 -6
- package/dist/cjs/langfuseTraceShaping.cjs.map +1 -1
- package/dist/cjs/main.cjs +6 -0
- package/dist/cjs/run.cjs +147 -14
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/AgentSession.cjs +93 -31
- package/dist/cjs/session/AgentSession.cjs.map +1 -1
- package/dist/cjs/session/handlers.cjs +10 -0
- package/dist/cjs/session/handlers.cjs.map +1 -1
- package/dist/cjs/stream.cjs +17 -3
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +1 -0
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +4 -2
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/handlers.cjs +1 -0
- package/dist/cjs/tools/handlers.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +28 -0
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/types/hitl.cjs +13 -0
- package/dist/cjs/types/hitl.cjs.map +1 -0
- package/dist/cjs/utils/handlers.cjs +3 -0
- package/dist/cjs/utils/handlers.cjs.map +1 -1
- package/dist/esm/common/enum.mjs +2 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +268 -14
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hitl/askUserQuestions.mjs +66 -0
- package/dist/esm/hitl/askUserQuestions.mjs.map +1 -0
- package/dist/esm/hitl/askUserQuestionsInterrupt.mjs +43 -0
- package/dist/esm/hitl/askUserQuestionsInterrupt.mjs.map +1 -0
- package/dist/esm/hitl/index.mjs +2 -0
- package/dist/esm/langfuseTraceShaping.mjs +9 -6
- package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
- package/dist/esm/main.mjs +3 -1
- package/dist/esm/run.mjs +147 -14
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/AgentSession.mjs +93 -31
- package/dist/esm/session/AgentSession.mjs.map +1 -1
- package/dist/esm/session/handlers.mjs +10 -0
- package/dist/esm/session/handlers.mjs.map +1 -1
- package/dist/esm/stream.mjs +17 -3
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +1 -0
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +4 -2
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/handlers.mjs +1 -0
- package/dist/esm/tools/handlers.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +28 -0
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/types/hitl.mjs +13 -0
- package/dist/esm/types/hitl.mjs.map +1 -0
- package/dist/esm/utils/handlers.mjs +3 -0
- package/dist/esm/utils/handlers.mjs.map +1 -1
- package/dist/types/common/enum.d.ts +2 -0
- package/dist/types/graphs/Graph.d.ts +87 -0
- package/dist/types/hitl/askUserQuestions.d.ts +24 -0
- package/dist/types/hitl/askUserQuestionsInterrupt.d.ts +11 -0
- package/dist/types/hitl/index.d.ts +2 -0
- package/dist/types/langfuseTraceShaping.d.ts +4 -3
- package/dist/types/run.d.ts +9 -1
- package/dist/types/session/types.d.ts +1 -1
- package/dist/types/summarization/node.d.ts +6 -0
- package/dist/types/types/graph.d.ts +3 -3
- package/dist/types/types/hitl.d.ts +31 -2
- package/dist/types/types/stream.d.ts +57 -0
- package/dist/types/utils/handlers.d.ts +1 -0
- package/package.json +2 -1
- package/src/aggregator.test.ts +127 -0
- package/src/common/enum.ts +2 -0
- package/src/graphs/Graph.ts +365 -7
- package/src/graphs/__tests__/Graph.closeRunStep.test.ts +385 -0
- package/src/hitl/askUserQuestions.ts +126 -0
- package/src/hitl/askUserQuestionsInterrupt.ts +115 -0
- package/src/hitl/index.ts +6 -0
- package/src/langfuseTraceShaping.ts +14 -7
- package/src/run.ts +219 -41
- package/src/session/AgentSession.ts +105 -13
- package/src/session/handlers.ts +14 -2
- package/src/session/types.ts +1 -0
- package/src/specs/activity-label-observability.live.test.ts +262 -0
- package/src/specs/activity-label-observability.test.ts +167 -0
- package/src/specs/ask-user-questions.live.test.ts +185 -0
- package/src/specs/ask-user-questions.test.ts +293 -0
- package/src/specs/langfuse-trace-shaping.test.ts +32 -0
- package/src/specs/run-step-timestamps.test.ts +414 -0
- package/src/stream.ts +29 -6
- package/src/summarization/node.ts +11 -0
- package/src/tools/ToolNode.ts +2 -0
- package/src/tools/__tests__/SubagentExecutor.test.ts +52 -10
- package/src/tools/__tests__/handlers.test.ts +2 -0
- package/src/tools/handlers.ts +1 -0
- package/src/tools/subagent/SubagentExecutor.ts +46 -4
- package/src/types/graph.ts +9 -6
- package/src/types/hitl.ts +36 -2
- package/src/types/stream.ts +65 -12
- package/src/utils/handlers.ts +13 -0
package/src/run.ts
CHANGED
|
@@ -49,6 +49,12 @@ import {
|
|
|
49
49
|
resolveLangfuseConfig,
|
|
50
50
|
resolveToolOutputTracingConfig,
|
|
51
51
|
} from '@/langfuseConfig';
|
|
52
|
+
import {
|
|
53
|
+
appendCallbacks,
|
|
54
|
+
filterCallbacks,
|
|
55
|
+
findCallback,
|
|
56
|
+
type CallbackEntry,
|
|
57
|
+
} from '@/utils/callbacks';
|
|
52
58
|
import {
|
|
53
59
|
resolveLangfuseRuntimeScope,
|
|
54
60
|
withLangfuseRuntimeScope,
|
|
@@ -59,12 +65,6 @@ import {
|
|
|
59
65
|
TitleMethod,
|
|
60
66
|
DEFAULT_RECURSION_LIMIT,
|
|
61
67
|
} from '@/common';
|
|
62
|
-
import {
|
|
63
|
-
appendCallbacks,
|
|
64
|
-
filterCallbacks,
|
|
65
|
-
findCallback,
|
|
66
|
-
type CallbackEntry,
|
|
67
|
-
} from '@/utils/callbacks';
|
|
68
68
|
import {
|
|
69
69
|
createCompletionTitleRunnable,
|
|
70
70
|
createTitleRunnable,
|
|
@@ -93,11 +93,15 @@ export const defaultOmitOptions = new Set([
|
|
|
93
93
|
'additionalModelRequestFields',
|
|
94
94
|
]);
|
|
95
95
|
|
|
96
|
+
const ACTIVITY_LABEL_TRACE_NAME = 'LibreChat Activity Label';
|
|
97
|
+
const ACTIVITY_PHASE_TRACE_NAME = 'LibreChat Activity Phase';
|
|
98
|
+
|
|
96
99
|
const CUSTOM_GRAPH_EVENTS = new Set<string>([
|
|
97
100
|
GraphEvents.ON_AGENT_UPDATE,
|
|
98
101
|
GraphEvents.ON_RUN_STEP,
|
|
99
102
|
GraphEvents.ON_RUN_STEP_DELTA,
|
|
100
103
|
GraphEvents.ON_RUN_STEP_COMPLETED,
|
|
104
|
+
GraphEvents.ON_RUN_STEP_CLOSED,
|
|
101
105
|
GraphEvents.ON_MESSAGE_DELTA,
|
|
102
106
|
GraphEvents.ON_REASONING_DELTA,
|
|
103
107
|
GraphEvents.ON_TOOL_EXECUTE,
|
|
@@ -113,6 +117,7 @@ const CUSTOM_GRAPH_EVENTS = new Set<string>([
|
|
|
113
117
|
const DIRECT_DISPATCHED_STEP_EVENTS = new Set<string>([
|
|
114
118
|
GraphEvents.ON_RUN_STEP,
|
|
115
119
|
GraphEvents.ON_RUN_STEP_DELTA,
|
|
120
|
+
GraphEvents.ON_RUN_STEP_CLOSED,
|
|
116
121
|
GraphEvents.ON_MESSAGE_DELTA,
|
|
117
122
|
GraphEvents.ON_REASONING_DELTA,
|
|
118
123
|
]);
|
|
@@ -125,6 +130,38 @@ function getStepScopedEventId(data: unknown): string | undefined {
|
|
|
125
130
|
return typeof candidate.id === 'string' ? candidate.id : undefined;
|
|
126
131
|
}
|
|
127
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Narrows an ON_RUN_STEP_COMPLETED payload (`{ result: ToolCompleteEvent }`)
|
|
135
|
+
* to the ids step closure needs. Returns undefined for malformed payloads and
|
|
136
|
+
* the resume race's empty step id.
|
|
137
|
+
*/
|
|
138
|
+
function getToolCompletion(
|
|
139
|
+
data: unknown
|
|
140
|
+
): { stepId: string; toolCallId?: string; completedAt?: number } | undefined {
|
|
141
|
+
if (data == null || typeof data !== 'object') {
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
const { result } = data as { result?: unknown };
|
|
145
|
+
if (result == null || typeof result !== 'object') {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
const candidate = result as {
|
|
149
|
+
id?: unknown;
|
|
150
|
+
tool_call?: { id?: unknown };
|
|
151
|
+
completed_at?: unknown;
|
|
152
|
+
};
|
|
153
|
+
if (typeof candidate.id !== 'string' || candidate.id === '') {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
const toolCallId = candidate.tool_call?.id;
|
|
157
|
+
const completedAt = candidate.completed_at;
|
|
158
|
+
return {
|
|
159
|
+
stepId: candidate.id,
|
|
160
|
+
...(typeof toolCallId === 'string' ? { toolCallId } : {}),
|
|
161
|
+
...(typeof completedAt === 'number' ? { completedAt } : {}),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
128
165
|
function isLangGraphResumeMapForInterrupt(
|
|
129
166
|
value: unknown,
|
|
130
167
|
interruptId: string
|
|
@@ -703,20 +740,50 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
703
740
|
return;
|
|
704
741
|
}
|
|
705
742
|
const handler = this.handlerRegistry?.getHandler(eventName);
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
743
|
+
/**
|
|
744
|
+
* Tool completions arriving over the custom-event channel are the only
|
|
745
|
+
* signal ToolNode (which holds no graph reference) emits — observe them
|
|
746
|
+
* here to drive step closure. Runs in `finally`, independent of handler
|
|
747
|
+
* registration, so an absent or throwing host handler cannot lose the
|
|
748
|
+
* close; duplicate callback echoes are absorbed by the terminal-status
|
|
749
|
+
* guard in `closeRunStep`.
|
|
750
|
+
*/
|
|
751
|
+
try {
|
|
752
|
+
if (handler && this.Graph) {
|
|
753
|
+
return await handler.handle(
|
|
754
|
+
eventName,
|
|
755
|
+
data as
|
|
756
|
+
| t.StreamEventData
|
|
757
|
+
| t.ModelEndData
|
|
758
|
+
| t.RunStep
|
|
759
|
+
| t.RunStepDeltaEvent
|
|
760
|
+
| t.RunStepClosedEvent
|
|
761
|
+
| t.MessageDeltaEvent
|
|
762
|
+
| t.ReasoningDeltaEvent
|
|
763
|
+
| { result: t.ToolEndEvent },
|
|
764
|
+
metadata,
|
|
765
|
+
this.Graph
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
} finally {
|
|
769
|
+
if (
|
|
770
|
+
eventName === GraphEvents.ON_RUN_STEP_COMPLETED &&
|
|
771
|
+
this.Graph != null
|
|
772
|
+
) {
|
|
773
|
+
const completion = getToolCompletion(data);
|
|
774
|
+
if (completion != null) {
|
|
775
|
+
/**
|
|
776
|
+
* The producer stamped `completed_at` before dispatch. Carrying it
|
|
777
|
+
* through keeps the recorded duration the tool's, not the host
|
|
778
|
+
* handler's — this runs after an arbitrarily slow handler resolves.
|
|
779
|
+
*/
|
|
780
|
+
await this.Graph.recordStepCompletion(completion.stepId, {
|
|
781
|
+
toolCallId: completion.toolCallId,
|
|
782
|
+
metadata,
|
|
783
|
+
at: completion.completedAt,
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
}
|
|
720
787
|
}
|
|
721
788
|
};
|
|
722
789
|
}
|
|
@@ -731,6 +798,26 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
731
798
|
);
|
|
732
799
|
}
|
|
733
800
|
|
|
801
|
+
/**
|
|
802
|
+
* Terminal status for steps still open at end-of-run: `cancelled` for
|
|
803
|
+
* intentional stops (caller abort, hook halt), `failed` for unexpected
|
|
804
|
+
* stream errors, `completed` for a natural finish. Reads `_haltedReason`
|
|
805
|
+
* behind a method boundary on purpose — it is assigned inside the
|
|
806
|
+
* `consumeStream` closure, which control-flow narrowing cannot see.
|
|
807
|
+
*/
|
|
808
|
+
private resolveSweepStatus(
|
|
809
|
+
streamThrew: boolean,
|
|
810
|
+
streamAborted: boolean
|
|
811
|
+
): Exclude<t.RunStepStatus, 'in_progress'> {
|
|
812
|
+
if (streamThrew) {
|
|
813
|
+
return streamAborted ? 'cancelled' : 'failed';
|
|
814
|
+
}
|
|
815
|
+
if (this._haltedReason != null) {
|
|
816
|
+
return 'cancelled';
|
|
817
|
+
}
|
|
818
|
+
return 'completed';
|
|
819
|
+
}
|
|
820
|
+
|
|
734
821
|
private getStreamLangfuseConfig(
|
|
735
822
|
graph: StandardGraph | MultiAgentGraph
|
|
736
823
|
): t.LangfuseConfig | undefined {
|
|
@@ -1027,6 +1114,14 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1027
1114
|
* preserving session hooks would leak them into the next run.
|
|
1028
1115
|
*/
|
|
1029
1116
|
let streamThrew = false;
|
|
1117
|
+
let streamAborted = false;
|
|
1118
|
+
/**
|
|
1119
|
+
* When the stream itself ended — captured before the post-stream work in
|
|
1120
|
+
* the `finally` (Stop/StopFailure hooks, Langfuse disposal, which can
|
|
1121
|
+
* force-flush) so a slow hook cannot inflate the terminal stamps that the
|
|
1122
|
+
* sweep writes onto steps that were still open.
|
|
1123
|
+
*/
|
|
1124
|
+
let terminalAt: number | undefined;
|
|
1030
1125
|
|
|
1031
1126
|
const consumeStream = async (): Promise<void> => {
|
|
1032
1127
|
/**
|
|
@@ -1095,11 +1190,28 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1095
1190
|
}
|
|
1096
1191
|
}
|
|
1097
1192
|
|
|
1193
|
+
/**
|
|
1194
|
+
* Stamped before the handler runs: the close below happens after an
|
|
1195
|
+
* arbitrarily slow host handler resolves, and the step's duration
|
|
1196
|
+
* should end when the model did, not when the host finished with it.
|
|
1197
|
+
*/
|
|
1198
|
+
const modelEndAt =
|
|
1199
|
+
eventName === GraphEvents.CHAT_MODEL_END ? Date.now() : undefined;
|
|
1098
1200
|
const handler = this.handlerRegistry?.getHandler(eventName);
|
|
1099
1201
|
if (handler) {
|
|
1100
1202
|
await handler.handle(eventName, data, metadata, this.Graph);
|
|
1101
1203
|
}
|
|
1102
1204
|
|
|
1205
|
+
/**
|
|
1206
|
+
* A finished model call ends its lane's open message step. Placed
|
|
1207
|
+
* here — not in `ModelEndHandler` — because hosts replace the
|
|
1208
|
+
* CHAT_MODEL_END handler with their own instance, which would
|
|
1209
|
+
* silently drop the close.
|
|
1210
|
+
*/
|
|
1211
|
+
if (eventName === GraphEvents.CHAT_MODEL_END && this.Graph != null) {
|
|
1212
|
+
await this.Graph.closeOpenMessageStep(metadata, modelEndAt);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1103
1215
|
/**
|
|
1104
1216
|
* Mid-flight halt: any hook (PreToolUse, PostToolUse,
|
|
1105
1217
|
* PostToolBatch, SubagentStart/Stop, PreCompact, PostCompact)
|
|
@@ -1128,6 +1240,8 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1128
1240
|
}
|
|
1129
1241
|
}
|
|
1130
1242
|
|
|
1243
|
+
terminalAt = Date.now();
|
|
1244
|
+
|
|
1131
1245
|
if (this._interrupt != null) {
|
|
1132
1246
|
await this.resolveInterruptResumeConfig(config);
|
|
1133
1247
|
}
|
|
@@ -1212,7 +1326,17 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1212
1326
|
)
|
|
1213
1327
|
);
|
|
1214
1328
|
} catch (err) {
|
|
1329
|
+
terminalAt = Date.now();
|
|
1215
1330
|
streamThrew = true;
|
|
1331
|
+
/**
|
|
1332
|
+
* Corroborate cancellation against an actually-aborted signal. A
|
|
1333
|
+
* provider SDK or host handler can reject with an `AbortError` while
|
|
1334
|
+
* nothing was cancelled — that is an unexpected failure (it also fires
|
|
1335
|
+
* `StopFailure`), and naming it `cancelled` would misreport abort
|
|
1336
|
+
* forensics.
|
|
1337
|
+
*/
|
|
1338
|
+
streamAborted =
|
|
1339
|
+
config.signal?.aborted === true || this.Graph.signal?.aborted === true;
|
|
1216
1340
|
if (this.hookRegistry?.hasHookFor('StopFailure', this.id) === true) {
|
|
1217
1341
|
const runMessages = this.Graph.getRunMessages() ?? [];
|
|
1218
1342
|
await executeHooks({
|
|
@@ -1260,6 +1384,29 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1260
1384
|
this.hookRegistry?.clearHaltSignal(this.id);
|
|
1261
1385
|
await disposeLangfuseHandler(langfuseHandler);
|
|
1262
1386
|
|
|
1387
|
+
/**
|
|
1388
|
+
* Terminal sweep: close every step that never reached a terminal
|
|
1389
|
+
* status — `completed` on a natural end, `cancelled` on caller abort
|
|
1390
|
+
* or hook halt, `failed` on an unexpected stream error. Skipped on a
|
|
1391
|
+
* HITL pause, where the open steps continue after `resume()`.
|
|
1392
|
+
*
|
|
1393
|
+
* Runs BEFORE the callback teardown below, so a caller observing
|
|
1394
|
+
* lifecycle events only through `RunnableConfig.callbacks` still
|
|
1395
|
+
* receives these closures rather than being left with unmatched
|
|
1396
|
+
* starts, and before `getContentParts()` so terminal stamps flow into
|
|
1397
|
+
* content and session serialization.
|
|
1398
|
+
*/
|
|
1399
|
+
if (!this.isAwaitingResume(streamThrew)) {
|
|
1400
|
+
try {
|
|
1401
|
+
await this.Graph.closeUnfinishedRunSteps(
|
|
1402
|
+
this.resolveSweepStatus(streamThrew, streamAborted),
|
|
1403
|
+
terminalAt
|
|
1404
|
+
);
|
|
1405
|
+
} catch {
|
|
1406
|
+
/* the sweep must never mask the stream outcome */
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1263
1410
|
/**
|
|
1264
1411
|
* Break the reference chain that keeps heavy data alive via
|
|
1265
1412
|
* LangGraph's internal `__pregel_scratchpad.currentTaskInput` →
|
|
@@ -1866,22 +2013,15 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1866
2013
|
? undefined
|
|
1867
2014
|
: (requestedContext ??
|
|
1868
2015
|
this.Graph.agentContexts.get(this.Graph.defaultAgentId));
|
|
1869
|
-
const traceMetadata = createLangfuseTraceMetadata({
|
|
1870
|
-
messageId: 'activity-label-' + this.id,
|
|
1871
|
-
agentName: labelContext?.name,
|
|
1872
|
-
});
|
|
1873
|
-
const labelRunName = getLangfuseTraceName(
|
|
1874
|
-
traceMetadata,
|
|
1875
|
-
'LibreChat Activity Label'
|
|
1876
|
-
);
|
|
1877
|
-
|
|
1878
2016
|
/** Shallow-cloned: activity labels run once per tool batch, and writing
|
|
1879
2017
|
* the Langfuse handler back onto a host-reused `chainOptions` would
|
|
1880
2018
|
* accumulate duplicate callbacks across batches. */
|
|
1881
2019
|
const labelChainOptions = {
|
|
1882
2020
|
...(chainOptions ?? {}),
|
|
1883
2021
|
} as Partial<RunnableConfig> & {
|
|
1884
|
-
configurable?: Record<string, unknown
|
|
2022
|
+
configurable?: Record<string, unknown> & {
|
|
2023
|
+
requestBody?: { parentMessageId?: unknown };
|
|
2024
|
+
};
|
|
1885
2025
|
};
|
|
1886
2026
|
const labelUserId =
|
|
1887
2027
|
typeof labelChainOptions.configurable?.user_id === 'string'
|
|
@@ -1891,6 +2031,42 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1891
2031
|
typeof labelChainOptions.configurable?.thread_id === 'string'
|
|
1892
2032
|
? (labelChainOptions.configurable.thread_id as string)
|
|
1893
2033
|
: undefined;
|
|
2034
|
+
const labelIndex = labelSeq - 1;
|
|
2035
|
+
const labelParentMessageId =
|
|
2036
|
+
labelChainOptions.configurable?.requestBody?.parentMessageId;
|
|
2037
|
+
/** An omitted `agentId` is attributable only when exactly one context
|
|
2038
|
+
* exists. Multi-agent callers remain unattributed instead of being
|
|
2039
|
+
* incorrectly assigned to the graph's default agent. */
|
|
2040
|
+
const labelAgentId =
|
|
2041
|
+
agentId ??
|
|
2042
|
+
(this.Graph?.agentContexts.size === 1
|
|
2043
|
+
? this.Graph.defaultAgentId
|
|
2044
|
+
: undefined);
|
|
2045
|
+
const labelAgentName =
|
|
2046
|
+
labelAgentId == null ? undefined : labelContext?.name;
|
|
2047
|
+
const labelMetadata: Record<string, unknown> = {
|
|
2048
|
+
sourceRunId: this.id,
|
|
2049
|
+
responseId: this.id,
|
|
2050
|
+
activityIndex: labelIndex,
|
|
2051
|
+
...(typeof labelParentMessageId === 'string'
|
|
2052
|
+
? { parentMessageId: labelParentMessageId }
|
|
2053
|
+
: {}),
|
|
2054
|
+
...(labelAgentId == null ? {} : { agentId: labelAgentId }),
|
|
2055
|
+
...(labelAgentName == null ? {} : { agentName: labelAgentName }),
|
|
2056
|
+
};
|
|
2057
|
+
const traceMetadata = {
|
|
2058
|
+
...createLangfuseTraceMetadata({
|
|
2059
|
+
messageId: 'activity-label-' + this.id,
|
|
2060
|
+
parentMessageId: labelParentMessageId,
|
|
2061
|
+
agentId: labelAgentId,
|
|
2062
|
+
agentName: labelAgentName,
|
|
2063
|
+
}),
|
|
2064
|
+
sourceRunId: this.id,
|
|
2065
|
+
responseId: this.id,
|
|
2066
|
+
activityIndex: String(labelIndex),
|
|
2067
|
+
};
|
|
2068
|
+
const labelRunName = labelChainOptions.runName ?? ACTIVITY_LABEL_TRACE_NAME;
|
|
2069
|
+
const labelTags = ['librechat', 'activity-label'];
|
|
1894
2070
|
const labelLangfuseConfig = resolveLangfuseConfig(
|
|
1895
2071
|
this.langfuse,
|
|
1896
2072
|
labelContext?.langfuse
|
|
@@ -1934,14 +2110,14 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1934
2110
|
userId: labelUserId,
|
|
1935
2111
|
sessionId: labelSessionId,
|
|
1936
2112
|
traceMetadata,
|
|
1937
|
-
tags:
|
|
2113
|
+
tags: labelTags,
|
|
1938
2114
|
traceIdSeed:
|
|
1939
2115
|
labelLangfuseConfig?.deterministicTraceId === true
|
|
1940
2116
|
? labelTraceSeed
|
|
1941
2117
|
: undefined,
|
|
1942
2118
|
runId: labelScopeRunId,
|
|
1943
2119
|
toolOutputTracing: labelRuntimeScope.toolOutputTracing,
|
|
1944
|
-
traceName:
|
|
2120
|
+
traceName: labelRunName,
|
|
1945
2121
|
});
|
|
1946
2122
|
}
|
|
1947
2123
|
if (labelLangfuseHandler != null) {
|
|
@@ -2027,7 +2203,12 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
2027
2203
|
const invokeConfig = Object.assign({}, labelChainOptions, {
|
|
2028
2204
|
run_id: labelRunId,
|
|
2029
2205
|
runId: labelRunId,
|
|
2030
|
-
runName:
|
|
2206
|
+
runName: labelRunName,
|
|
2207
|
+
tags: [...new Set([...(labelChainOptions.tags ?? []), ...labelTags])],
|
|
2208
|
+
metadata: {
|
|
2209
|
+
...(labelChainOptions.metadata ?? {}),
|
|
2210
|
+
...labelMetadata,
|
|
2211
|
+
},
|
|
2031
2212
|
}) as Partial<RunnableConfig>;
|
|
2032
2213
|
|
|
2033
2214
|
const invokeLabel = (
|
|
@@ -2038,9 +2219,9 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
2038
2219
|
langfuse: labelLangfuseConfig,
|
|
2039
2220
|
userId: labelUserId,
|
|
2040
2221
|
sessionId: labelSessionId,
|
|
2041
|
-
traceName:
|
|
2222
|
+
traceName: labelRunName,
|
|
2042
2223
|
traceMetadata,
|
|
2043
|
-
tags:
|
|
2224
|
+
tags: labelTags,
|
|
2044
2225
|
},
|
|
2045
2226
|
() =>
|
|
2046
2227
|
model.invoke(
|
|
@@ -2124,7 +2305,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
2124
2305
|
|
|
2125
2306
|
/**
|
|
2126
2307
|
* Generates one parent summary for two or more logical activities. The
|
|
2127
|
-
* summary model is traced as a dedicated activity-phase
|
|
2308
|
+
* summary model is traced as a dedicated activity-phase chain root in the
|
|
2128
2309
|
* conversation session, with the model callback recorded as its generation
|
|
2129
2310
|
* child. No session id means no phase trace, avoiding orphan observations.
|
|
2130
2311
|
*/
|
|
@@ -2289,11 +2470,8 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
2289
2470
|
: { contributingAgentIds: contributingAgentIds.join(',') }),
|
|
2290
2471
|
...(closingTextPhase == null ? {} : { closingTextPhase }),
|
|
2291
2472
|
};
|
|
2292
|
-
const
|
|
2293
|
-
|
|
2294
|
-
'LibreChat Activity Phase'
|
|
2295
|
-
);
|
|
2296
|
-
const phaseTraceName = phaseChainOptions.runName ?? phaseRunName;
|
|
2473
|
+
const phaseTraceName =
|
|
2474
|
+
phaseChainOptions.runName ?? ACTIVITY_PHASE_TRACE_NAME;
|
|
2297
2475
|
const phaseTags = [
|
|
2298
2476
|
'librechat',
|
|
2299
2477
|
'activity-phase',
|
|
@@ -616,13 +616,70 @@ function createManualCompactGraph(params: {
|
|
|
616
616
|
}): {
|
|
617
617
|
graph: Parameters<typeof createSummarizeNode>[0]['graph'];
|
|
618
618
|
completedSummary?: t.SummaryContentBlock;
|
|
619
|
+
closeOpenSteps: (
|
|
620
|
+
status: Exclude<t.RunStepStatus, 'in_progress'>
|
|
621
|
+
) => Promise<void>;
|
|
619
622
|
} {
|
|
620
623
|
const contentData: t.RunStep[] = [];
|
|
621
624
|
const contentIndexMap = new Map<string, number>();
|
|
625
|
+
/** Stamps the terminal state and publishes the single terminal event. */
|
|
626
|
+
const closeStep = async (
|
|
627
|
+
runStep: t.RunStep,
|
|
628
|
+
status: Exclude<t.RunStepStatus, 'in_progress'>,
|
|
629
|
+
at: number
|
|
630
|
+
): Promise<void> => {
|
|
631
|
+
if (runStep.status !== 'in_progress') {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
runStep.status = status;
|
|
635
|
+
if (status === 'completed') {
|
|
636
|
+
runStep.completed_at = at;
|
|
637
|
+
} else if (status === 'cancelled') {
|
|
638
|
+
runStep.cancelled_at = at;
|
|
639
|
+
} else {
|
|
640
|
+
runStep.failed_at = at;
|
|
641
|
+
}
|
|
642
|
+
const closedEvent: t.RunStepClosedEvent = {
|
|
643
|
+
id: runStep.id,
|
|
644
|
+
index: runStep.index,
|
|
645
|
+
type: runStep.type,
|
|
646
|
+
status,
|
|
647
|
+
closed_at: at,
|
|
648
|
+
};
|
|
649
|
+
if (runStep.created_at != null) {
|
|
650
|
+
closedEvent.created_at = runStep.created_at;
|
|
651
|
+
}
|
|
652
|
+
if (runStep.runId != null) {
|
|
653
|
+
closedEvent.runId = runStep.runId;
|
|
654
|
+
}
|
|
655
|
+
await params.customHandlers?.[GraphEvents.ON_RUN_STEP_CLOSED]?.handle(
|
|
656
|
+
GraphEvents.ON_RUN_STEP_CLOSED,
|
|
657
|
+
closedEvent
|
|
658
|
+
);
|
|
659
|
+
};
|
|
622
660
|
const result: {
|
|
623
661
|
graph: Parameters<typeof createSummarizeNode>[0]['graph'];
|
|
624
662
|
completedSummary?: t.SummaryContentBlock;
|
|
663
|
+
closeOpenSteps: (
|
|
664
|
+
status: Exclude<t.RunStepStatus, 'in_progress'>
|
|
665
|
+
) => Promise<void>;
|
|
625
666
|
} = {
|
|
667
|
+
/**
|
|
668
|
+
* Terminal sweep for compaction, which runs outside `Run.processStream`
|
|
669
|
+
* and so has no stream-level equivalent: a summarization model that
|
|
670
|
+
* rejects, aborts, or trips a stream limit after the step was published
|
|
671
|
+
* would otherwise strand observers with a permanently open step.
|
|
672
|
+
*/
|
|
673
|
+
closeOpenSteps: async (status): Promise<void> => {
|
|
674
|
+
const closedAt = Date.now();
|
|
675
|
+
for (const runStep of contentData) {
|
|
676
|
+
try {
|
|
677
|
+
await closeStep(runStep, status, closedAt);
|
|
678
|
+
} catch (_e) {
|
|
679
|
+
/** Delivery failure for one step must not halt the sweep */
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
},
|
|
626
683
|
graph: {
|
|
627
684
|
contentData,
|
|
628
685
|
contentIndexMap,
|
|
@@ -631,6 +688,8 @@ function createManualCompactGraph(params: {
|
|
|
631
688
|
hookRegistry: params.hooks,
|
|
632
689
|
streamLimits: resolveStreamLimits(params.streamLimits),
|
|
633
690
|
dispatchRunStep: async (runStep): Promise<void> => {
|
|
691
|
+
runStep.created_at ??= Date.now();
|
|
692
|
+
runStep.status ??= 'in_progress';
|
|
634
693
|
contentData.push(runStep);
|
|
635
694
|
contentIndexMap.set(runStep.id, runStep.index);
|
|
636
695
|
await params.customHandlers?.[GraphEvents.ON_RUN_STEP]?.handle(
|
|
@@ -638,12 +697,25 @@ function createManualCompactGraph(params: {
|
|
|
638
697
|
runStep
|
|
639
698
|
);
|
|
640
699
|
},
|
|
700
|
+
closeRunStep: async (stepId, status): Promise<void> => {
|
|
701
|
+
const stepIndex = contentIndexMap.get(stepId);
|
|
702
|
+
const runStep =
|
|
703
|
+
stepIndex === undefined ? undefined : contentData[stepIndex];
|
|
704
|
+
if (runStep == null) {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
await closeStep(runStep, status, Date.now());
|
|
708
|
+
},
|
|
641
709
|
dispatchRunStepCompleted: async (stepId, completed): Promise<void> => {
|
|
642
|
-
const
|
|
710
|
+
const completedAt = Date.now();
|
|
711
|
+
const stepIndex = contentIndexMap.get(stepId);
|
|
712
|
+
const runStep =
|
|
713
|
+
stepIndex === undefined ? undefined : contentData[stepIndex];
|
|
643
714
|
const resultWithStep = {
|
|
644
715
|
...completed,
|
|
645
716
|
id: stepId,
|
|
646
717
|
index: runStep?.index ?? 0,
|
|
718
|
+
completed_at: completedAt,
|
|
647
719
|
};
|
|
648
720
|
if (completed.type === 'summary') {
|
|
649
721
|
result.completedSummary = completed.summary;
|
|
@@ -653,6 +725,10 @@ function createManualCompactGraph(params: {
|
|
|
653
725
|
]?.handle(GraphEvents.ON_RUN_STEP_COMPLETED, {
|
|
654
726
|
result: resultWithStep,
|
|
655
727
|
} as unknown as Parameters<t.EventHandler['handle']>[1]);
|
|
728
|
+
if (runStep == null) {
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
await closeStep(runStep, 'completed', completedAt);
|
|
656
732
|
},
|
|
657
733
|
},
|
|
658
734
|
};
|
|
@@ -1302,19 +1378,35 @@ export class AgentSession {
|
|
|
1302
1378
|
graph.graph.contentData.length,
|
|
1303
1379
|
],
|
|
1304
1380
|
});
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1381
|
+
let summarizedState;
|
|
1382
|
+
try {
|
|
1383
|
+
summarizedState = await summarizeNode(
|
|
1384
|
+
{
|
|
1385
|
+
messages: sessionState.messages,
|
|
1386
|
+
summarizationRequest: {
|
|
1387
|
+
remainingContextTokens: agentContext.maxContextTokens ?? 0,
|
|
1388
|
+
agentId: agentContext.agentId,
|
|
1389
|
+
},
|
|
1311
1390
|
},
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
)
|
|
1391
|
+
{
|
|
1392
|
+
configurable: { thread_id: this.threadId },
|
|
1393
|
+
metadata: { run_id: compactRunId },
|
|
1394
|
+
}
|
|
1395
|
+
);
|
|
1396
|
+
} catch (error) {
|
|
1397
|
+
/**
|
|
1398
|
+
* `failed`, not `cancelled`: manual compaction is invoked without a
|
|
1399
|
+
* caller abort signal, so there is no cancellation source to
|
|
1400
|
+
* corroborate against. An error that merely carries the `AbortError`
|
|
1401
|
+
* name is an unexpected failure here, and it propagates to the caller
|
|
1402
|
+
* either way.
|
|
1403
|
+
*/
|
|
1404
|
+
await graph.closeOpenSteps('failed').catch(() => {
|
|
1405
|
+
/** the sweep must never mask the summarization failure */
|
|
1406
|
+
});
|
|
1407
|
+
throw error;
|
|
1408
|
+
}
|
|
1409
|
+
await graph.closeOpenSteps('completed');
|
|
1318
1410
|
const completedSummaryText = getSummaryText(graph.completedSummary);
|
|
1319
1411
|
const contextSummaryText = agentContext.getSummaryText();
|
|
1320
1412
|
let summaryText = completedSummaryText;
|
package/src/session/handlers.ts
CHANGED
|
@@ -5,13 +5,13 @@ import type {
|
|
|
5
5
|
AgentSessionUsage,
|
|
6
6
|
} from './types';
|
|
7
7
|
import type * as t from '@/types';
|
|
8
|
-
import { ModelEndHandler, ToolEndHandler } from '@/events';
|
|
9
|
-
import { toJsonValue } from './messageSerialization';
|
|
10
8
|
import {
|
|
11
9
|
createContentAggregator,
|
|
12
10
|
dispatchesChatModelStream,
|
|
13
11
|
SDK_STREAM_DISPATCH,
|
|
14
12
|
} from '@/stream';
|
|
13
|
+
import { ModelEndHandler, ToolEndHandler } from '@/events';
|
|
14
|
+
import { toJsonValue } from './messageSerialization';
|
|
15
15
|
import { createTimestamp } from './ids';
|
|
16
16
|
import { GraphEvents } from '@/common';
|
|
17
17
|
|
|
@@ -225,6 +225,18 @@ export function createRunHandlers(params: {
|
|
|
225
225
|
});
|
|
226
226
|
},
|
|
227
227
|
},
|
|
228
|
+
[GraphEvents.ON_RUN_STEP_CLOSED]: {
|
|
229
|
+
handle: async (event, data, metadata, graph): Promise<void> => {
|
|
230
|
+
emitEvent(createEvent('step.finished', data as t.RunStepClosedEvent));
|
|
231
|
+
await callUserHandler({
|
|
232
|
+
userHandlers: params.userHandlers,
|
|
233
|
+
event,
|
|
234
|
+
data,
|
|
235
|
+
metadata,
|
|
236
|
+
graph,
|
|
237
|
+
});
|
|
238
|
+
},
|
|
239
|
+
},
|
|
228
240
|
[GraphEvents.ON_MESSAGE_DELTA]: {
|
|
229
241
|
handle: async (event, data, metadata, graph): Promise<void> => {
|
|
230
242
|
const delta = data as t.MessageDeltaEvent;
|