@librechat/agents 3.7.6 → 3.7.8
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 +23 -3
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/constants.cjs +10 -0
- package/dist/cjs/common/constants.cjs.map +1 -1
- package/dist/cjs/eventActor/EventActorExecutor.cjs +305 -11
- package/dist/cjs/eventActor/EventActorExecutor.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +16 -5
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/langfuseTraceShaping.cjs +75 -0
- package/dist/cjs/langfuseTraceShaping.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/toolCache.cjs +1 -1
- package/dist/cjs/llm/fake.cjs +9 -5
- package/dist/cjs/llm/fake.cjs.map +1 -1
- package/dist/cjs/llm/invoke.cjs +1 -1
- package/dist/cjs/llm/openrouter/toolCache.cjs +1 -1
- package/dist/cjs/llm/prepareProviderRequest.cjs +2 -2
- package/dist/cjs/llm/providers.cjs +1 -1
- package/dist/cjs/llm/truncation.cjs +1 -0
- package/dist/cjs/main.cjs +3 -2
- package/dist/cjs/messages/format.cjs +298 -3
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/prune.cjs +1 -1
- package/dist/cjs/run.cjs +25 -15
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +1 -1
- package/dist/cjs/summarization/node.cjs +43 -9
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/summarization/semanticIndex.cjs +362 -0
- package/dist/cjs/summarization/semanticIndex.cjs.map +1 -0
- package/dist/cjs/tools/subagent/childGraphConfig.cjs +2 -1
- package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -1
- package/dist/cjs/utils/index.cjs +1 -1
- package/dist/esm/agents/AgentContext.mjs +23 -3
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/constants.mjs +10 -1
- package/dist/esm/common/constants.mjs.map +1 -1
- package/dist/esm/eventActor/EventActorExecutor.mjs +305 -11
- package/dist/esm/eventActor/EventActorExecutor.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +16 -5
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/langfuseTraceShaping.mjs +75 -0
- package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
- package/dist/esm/llm/bedrock/toolCache.mjs +1 -1
- package/dist/esm/llm/fake.mjs +9 -5
- package/dist/esm/llm/fake.mjs.map +1 -1
- package/dist/esm/llm/invoke.mjs +1 -1
- package/dist/esm/llm/openrouter/toolCache.mjs +1 -1
- package/dist/esm/llm/prepareProviderRequest.mjs +2 -2
- package/dist/esm/llm/providers.mjs +1 -1
- package/dist/esm/llm/truncation.mjs +1 -1
- package/dist/esm/main.mjs +4 -4
- package/dist/esm/messages/format.mjs +298 -3
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/prune.mjs +1 -1
- package/dist/esm/run.mjs +25 -15
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +1 -1
- package/dist/esm/summarization/node.mjs +43 -9
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/summarization/semanticIndex.mjs +360 -0
- package/dist/esm/summarization/semanticIndex.mjs.map +1 -0
- package/dist/esm/tools/subagent/childGraphConfig.mjs +2 -1
- package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +1 -1
- package/dist/types/agents/AgentContext.d.ts +27 -1
- package/dist/types/common/constants.d.ts +10 -0
- package/dist/types/eventActor/EventActorExecutor.d.ts +3 -1
- package/dist/types/eventActor/index.d.ts +1 -1
- package/dist/types/eventActor/types.d.ts +149 -5
- package/dist/types/graphs/Graph.d.ts +15 -0
- package/dist/types/llm/fake.d.ts +12 -2
- package/dist/types/messages/format.d.ts +10 -1
- package/dist/types/run.d.ts +17 -5
- package/dist/types/summarization/semanticIndex.d.ts +19 -0
- package/dist/types/types/graph.d.ts +9 -1
- package/dist/types/types/summarize.d.ts +38 -0
- package/package.json +2 -1
- package/src/agents/AgentContext.ts +61 -1
- package/src/common/constants.ts +11 -0
- package/src/eventActor/EventActorExecutor.ts +545 -16
- package/src/eventActor/index.ts +17 -0
- package/src/eventActor/types.ts +182 -3
- package/src/graphs/Graph.ts +41 -8
- package/src/langfuseTraceShaping.ts +94 -0
- package/src/llm/fake.ts +35 -4
- package/src/messages/format.ts +652 -4
- package/src/run.ts +36 -10
- package/src/summarization/node.ts +90 -33
- package/src/summarization/semanticIndex.ts +653 -0
- package/src/tools/subagent/childGraphConfig.ts +3 -0
- package/src/types/graph.ts +9 -0
- package/src/types/summarize.ts +50 -0
|
@@ -62,12 +62,129 @@ export type EventActorTerminalResult<TResult extends EventActorEvent> = {
|
|
|
62
62
|
status: 'completed_no_action';
|
|
63
63
|
result?: TResult;
|
|
64
64
|
};
|
|
65
|
+
/** JSON-safe interrupt descriptor retained with a suspended invocation fork. */
|
|
66
|
+
export interface EventActorInterrupt<TPayload extends EventActorEvent = EventActorEvent> {
|
|
67
|
+
id: string;
|
|
68
|
+
payload: TPayload;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Nonterminal adapter outcome. The checkpoint must already contain the pause;
|
|
72
|
+
* the SDK publishes its authority through `suspend` before exposing it.
|
|
73
|
+
*/
|
|
74
|
+
export interface EventActorAdapterSuspendedResult<TPayload extends EventActorEvent = EventActorEvent> {
|
|
75
|
+
status: 'suspended';
|
|
76
|
+
checkpoint: EventActorCheckpointFork;
|
|
77
|
+
interrupt: EventActorInterrupt<TPayload>;
|
|
78
|
+
}
|
|
79
|
+
export type EventActorAdapterInvocationResult<TResult extends EventActorEvent, TPayload extends EventActorEvent = EventActorEvent> = EventActorTerminalResult<TResult> | EventActorAdapterSuspendedResult<TPayload>;
|
|
80
|
+
/**
|
|
81
|
+
* Authenticated, versioned, JSON-safe evidence for one nonterminal invocation.
|
|
82
|
+
* Integrity does not make this evidence one-shot; the host's durable current
|
|
83
|
+
* suspension record is the replay and ownership fence. Hosts must capability-
|
|
84
|
+
* route each exact version during rolling deploys and drain or migrate current
|
|
85
|
+
* evidence before rotating away from its signing key.
|
|
86
|
+
*/
|
|
87
|
+
export interface EventActorSuspension<TPayload extends EventActorEvent = EventActorEvent> {
|
|
88
|
+
version: 1;
|
|
89
|
+
suspensionId: string;
|
|
90
|
+
attempt: number;
|
|
91
|
+
issuedAt: number;
|
|
92
|
+
expiresAt: number;
|
|
93
|
+
invocation: EventActorInvocationReference;
|
|
94
|
+
checkpoint: EventActorCheckpointFork;
|
|
95
|
+
interrupt: EventActorInterrupt<TPayload>;
|
|
96
|
+
suspensionDigest: string;
|
|
97
|
+
}
|
|
98
|
+
export interface EventActorSuspendedResult<TPayload extends EventActorEvent = EventActorEvent> {
|
|
99
|
+
status: 'suspended';
|
|
100
|
+
suspension: EventActorSuspension<TPayload>;
|
|
101
|
+
}
|
|
102
|
+
export interface EventActorSuspendRequest {
|
|
103
|
+
suspension: EventActorSuspension;
|
|
104
|
+
/** CAS predecessor required when a claimed resume pauses again. */
|
|
105
|
+
previous?: {
|
|
106
|
+
suspensionId: string;
|
|
107
|
+
attempt: number;
|
|
108
|
+
resumeAttemptId: string;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
export type EventActorSuspendResult = {
|
|
112
|
+
status: 'stored';
|
|
113
|
+
} | {
|
|
114
|
+
status: 'stale';
|
|
115
|
+
};
|
|
116
|
+
export interface EventActorAdapterResumeRequest {
|
|
117
|
+
suspension: EventActorSuspension;
|
|
118
|
+
resumeAttemptId: string;
|
|
119
|
+
value: EventActorEvent;
|
|
120
|
+
}
|
|
121
|
+
export type EventActorAdapterResumeResult<TResult extends EventActorEvent> = {
|
|
122
|
+
status: 'claimed';
|
|
123
|
+
result: EventActorAdapterInvocationResult<TResult>;
|
|
124
|
+
} | {
|
|
125
|
+
/** The host proved no action before returning this claimed failure. */
|
|
126
|
+
status: 'claimed_failed';
|
|
127
|
+
error: Error;
|
|
128
|
+
} | {
|
|
129
|
+
status: 'stale';
|
|
130
|
+
};
|
|
131
|
+
export interface EventActorResumeRequest {
|
|
132
|
+
suspension: EventActorSuspension;
|
|
133
|
+
resumeAttemptId: string;
|
|
134
|
+
value: EventActorEvent;
|
|
135
|
+
signal?: AbortSignal;
|
|
136
|
+
}
|
|
137
|
+
export interface EventActorCancelSuspensionRequest {
|
|
138
|
+
suspension: EventActorSuspension;
|
|
139
|
+
cancelAttemptId: string;
|
|
140
|
+
reason?: 'cancelled' | 'expired';
|
|
141
|
+
signal?: AbortSignal;
|
|
142
|
+
}
|
|
143
|
+
export interface EventActorAdapterCancelSuspensionRequest {
|
|
144
|
+
suspension: EventActorSuspension;
|
|
145
|
+
cancelAttemptId: string;
|
|
146
|
+
reason: 'cancelled' | 'expired';
|
|
147
|
+
}
|
|
148
|
+
export type EventActorCancelSuspensionResult = {
|
|
149
|
+
status: 'cancelled';
|
|
150
|
+
} | EventActorIndeterminateResult<EventActorEvent>;
|
|
151
|
+
export type EventActorAdapterCancelSuspensionResult = {
|
|
152
|
+
status: 'cancelled';
|
|
153
|
+
} | {
|
|
154
|
+
status: 'stale';
|
|
155
|
+
};
|
|
156
|
+
export interface EventActorSettleSuspensionRequest {
|
|
157
|
+
suspensionId: string;
|
|
158
|
+
attempt: number;
|
|
159
|
+
resumeAttemptId: string;
|
|
160
|
+
status: 'completed_no_action' | 'failed';
|
|
161
|
+
}
|
|
162
|
+
export type EventActorSettleSuspensionResult = {
|
|
163
|
+
status: 'settled';
|
|
164
|
+
} | {
|
|
165
|
+
status: 'stale';
|
|
166
|
+
};
|
|
65
167
|
export type EventActorAppliedResult<TResult extends EventActorEvent> = Extract<EventActorTerminalResult<TResult>, {
|
|
66
168
|
status: 'applied';
|
|
67
169
|
}> & {
|
|
68
|
-
/** Executor-issued
|
|
170
|
+
/** Executor-issued settlement for the invocation that produced this action. */
|
|
69
171
|
invocation: EventActorInvocationReference;
|
|
172
|
+
/** Authenticated cross-executor authority for a resumed terminal action. */
|
|
173
|
+
settlementAuthority?: EventActorSettlementAuthority;
|
|
70
174
|
};
|
|
175
|
+
/**
|
|
176
|
+
* Authenticated fence that binds a resumed terminal result to its claimed
|
|
177
|
+
* suspension. The host must consume it atomically with the actor-head CAS.
|
|
178
|
+
*/
|
|
179
|
+
export interface EventActorSettlementAuthority {
|
|
180
|
+
version: 1;
|
|
181
|
+
suspensionId: string;
|
|
182
|
+
attempt: number;
|
|
183
|
+
resumeAttemptId: string;
|
|
184
|
+
issuedAt: number;
|
|
185
|
+
expiresAt: number;
|
|
186
|
+
settlementDigest: string;
|
|
187
|
+
}
|
|
71
188
|
export interface EventActorIndeterminateResult<TResult extends EventActorEvent> {
|
|
72
189
|
/** Applied handling cannot be proven safe to retry; retain its fork. */
|
|
73
190
|
status: 'commit_indeterminate';
|
|
@@ -75,7 +192,7 @@ export interface EventActorIndeterminateResult<TResult extends EventActorEvent>
|
|
|
75
192
|
checkpoint: EventActorCheckpointFork;
|
|
76
193
|
error: Error;
|
|
77
194
|
}
|
|
78
|
-
export type EventActorInvocationResult<TResult extends EventActorEvent> = EventActorAppliedResult<TResult> | EventActorIndeterminateResult<TResult> | Extract<EventActorTerminalResult<TResult>, {
|
|
195
|
+
export type EventActorInvocationResult<TResult extends EventActorEvent> = EventActorAppliedResult<TResult> | EventActorIndeterminateResult<TResult> | EventActorSuspendedResult | Extract<EventActorTerminalResult<TResult>, {
|
|
79
196
|
status: 'completed_no_action';
|
|
80
197
|
}>;
|
|
81
198
|
export interface EventActorInvocationContext {
|
|
@@ -101,6 +218,8 @@ export interface EventActorCommitRequest<TResult extends EventActorEvent> {
|
|
|
101
218
|
expectedHead: EventActorHead;
|
|
102
219
|
checkpoint: EventActorCheckpointFork;
|
|
103
220
|
result: TResult;
|
|
221
|
+
/** Host must consume this suspension fence atomically with the head CAS. */
|
|
222
|
+
settlementAuthority?: EventActorSettlementAuthority;
|
|
104
223
|
retention: {
|
|
105
224
|
committedCheckpoints: 2;
|
|
106
225
|
dormantCheckpointTtlMs: number;
|
|
@@ -123,7 +242,11 @@ export interface EventActorDiscardRequest {
|
|
|
123
242
|
/**
|
|
124
243
|
* Host adapter for durable actor state and the concrete agent invocation.
|
|
125
244
|
* `commit` must compare both the expected generation and checkpoint identity
|
|
126
|
-
* atomically before advancing the logical actor head.
|
|
245
|
+
* atomically before advancing the logical actor head. When settlement authority
|
|
246
|
+
* is present, that same transaction must also verify and close the exact
|
|
247
|
+
* suspension/resume-attempt fence, including stale-head outcomes. Retrying an
|
|
248
|
+
* ambiguous acknowledgement must return the durable outcome rather than apply
|
|
249
|
+
* the same terminal transition again. The host mailbox
|
|
127
250
|
* deduplicates the logical `invocationId` before entering this seam, while each
|
|
128
251
|
* SDK execution attempt receives a distinct checkpoint namespace. Preparation
|
|
129
252
|
* methods own rollback until they return a ready invocation and must treat the
|
|
@@ -137,11 +260,28 @@ export interface EventActorDiscardRequest {
|
|
|
137
260
|
* fork: the SDK retains and surfaces it as `commit_conflict` for host
|
|
138
261
|
* reconciliation. `discard` must be idempotent for the same invocation because
|
|
139
262
|
* an ambiguous cleanup failure can be retried through the public lifecycle.
|
|
263
|
+
*
|
|
264
|
+
* Suspension-capable hosts implement all optional suspension methods. `suspend`
|
|
265
|
+
* publishes an initial suspension only while its logical invocation is current;
|
|
266
|
+
* with `previous`, it atomically replaces only the exact claimed predecessor.
|
|
267
|
+
* `resume` atomically claims the current suspension before applying its value to
|
|
268
|
+
* the declared interrupt and rejects duplicate or competing claims as `stale`.
|
|
269
|
+
* It must return `claimed_failed` only when it can prove no qualifying action;
|
|
270
|
+
* any failure after an action returns `applied`. `settleSuspension` atomically
|
|
271
|
+
* discards the claimed fork and closes its fence for definite no-action
|
|
272
|
+
* outcomes. `cancelSuspension`
|
|
273
|
+
* atomically claims, discards, and closes current state, including expired
|
|
274
|
+
* evidence; expiration alone never implies a safe action outcome.
|
|
140
275
|
*/
|
|
141
276
|
export interface EventActorHostAdapter<TEvent extends EventActorEvent, TResult extends EventActorEvent> {
|
|
142
277
|
prepare(request: EventActorAdapterPrepareRequest<TEvent>, context: EventActorPreparationContext): Promise<EventActorAdapterPreparation<TEvent>>;
|
|
143
278
|
coldContinue(request: EventActorAdapterPrepareRequest<TEvent>, head: EventActorHead, context: EventActorPreparationContext): Promise<EventActorInvocation<TEvent>>;
|
|
144
|
-
invoke(invocation: EventActorInvocation<TEvent>, context: EventActorInvocationContext): Promise<
|
|
279
|
+
invoke(invocation: EventActorInvocation<TEvent>, context: EventActorInvocationContext): Promise<EventActorAdapterInvocationResult<TResult>>;
|
|
280
|
+
suspend?(request: EventActorSuspendRequest): Promise<EventActorSuspendResult>;
|
|
281
|
+
resume?(request: EventActorAdapterResumeRequest, context: EventActorInvocationContext): Promise<EventActorAdapterResumeResult<TResult>>;
|
|
282
|
+
/** Atomically claims, discards, and closes the current suspension. */
|
|
283
|
+
cancelSuspension?(request: EventActorAdapterCancelSuspensionRequest, context: EventActorPreparationContext): Promise<EventActorAdapterCancelSuspensionResult>;
|
|
284
|
+
settleSuspension?(request: EventActorSettleSuspensionRequest): Promise<EventActorSettleSuspensionResult>;
|
|
145
285
|
commit(request: EventActorCommitRequest<TResult>): Promise<EventActorCommitResult>;
|
|
146
286
|
discard(request: EventActorDiscardRequest): Promise<void>;
|
|
147
287
|
}
|
|
@@ -165,7 +305,9 @@ export type EventActorExecutionResult<TResult extends EventActorEvent> = {
|
|
|
165
305
|
} | {
|
|
166
306
|
status: 'cancelled';
|
|
167
307
|
continuation: 'warm' | 'cold';
|
|
168
|
-
} | {
|
|
308
|
+
} | (EventActorSuspendedResult & {
|
|
309
|
+
continuation: 'warm' | 'cold';
|
|
310
|
+
}) | {
|
|
169
311
|
/** The action happened, but another head won the CAS. Reconcile; do not retry. */
|
|
170
312
|
status: 'commit_conflict';
|
|
171
313
|
result: TResult;
|
|
@@ -190,4 +332,6 @@ export interface EventActorExecutorOptions {
|
|
|
190
332
|
dormantCheckpointTtlMs?: number;
|
|
191
333
|
/** Stable private key of at least 32 bytes for cross-lifetime handoffs. */
|
|
192
334
|
preparationSigningKey?: string | Uint8Array;
|
|
335
|
+
/** Maximum UTF-8 byte size of canonical suspension evidence. */
|
|
336
|
+
maxSuspensionPayloadBytes?: number;
|
|
193
337
|
}
|
|
@@ -374,6 +374,21 @@ export declare class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode>
|
|
|
374
374
|
* hosts persist it as unfinished rather than complete.
|
|
375
375
|
*/
|
|
376
376
|
preemptIncomplete: boolean;
|
|
377
|
+
/**
|
|
378
|
+
* True when `routeMessage` sent a turn to `END` because the last AI
|
|
379
|
+
* message carries no tool call, AND the provider reports it stopped for
|
|
380
|
+
* hitting the output token ceiling (`getTruncationStopReason`). Plain-text
|
|
381
|
+
* and reasoning turns cut off this way carry no tool call for
|
|
382
|
+
* `assertNotTruncatedToolCall` to catch, so `toolsCondition` reads them as
|
|
383
|
+
* an ordinary finished turn otherwise — hosts read this flag to persist
|
|
384
|
+
* the turn as unfinished instead of a silently truncated "complete" answer.
|
|
385
|
+
*
|
|
386
|
+
* Deliberately separate from `preemptIncomplete`/`preemptHaltReason`: this
|
|
387
|
+
* has no interaction with the preempt/seal machinery (in particular the
|
|
388
|
+
* `preemptHaltReason` check at each model node's entry), so setting it
|
|
389
|
+
* cannot suppress an unrelated agent's turn in a multi-agent graph.
|
|
390
|
+
*/
|
|
391
|
+
outputTruncatedIncomplete: boolean;
|
|
377
392
|
/**
|
|
378
393
|
* `stopReason` from a `PreemptBoundary` hook that halted the turn.
|
|
379
394
|
*
|
package/dist/types/llm/fake.d.ts
CHANGED
|
@@ -11,15 +11,25 @@ export declare class FakeChatModel extends FakeListChatModel {
|
|
|
11
11
|
private splitStrategy;
|
|
12
12
|
private toolCalls;
|
|
13
13
|
private addedToolCalls;
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Attached to the last streamed chunk of every response, mirroring a real
|
|
16
|
+
* provider's terminal `finish_reason`/`stop_reason` on `generationInfo`.
|
|
17
|
+
* Lets truncation-path tests (`getTruncationStopReason`) drive a genuine
|
|
18
|
+
* `Run`/`StandardGraph` without a live provider. `undefined` (the
|
|
19
|
+
* default) reproduces the previous behavior exactly: no metadata on any
|
|
20
|
+
* chunk.
|
|
21
|
+
*/
|
|
22
|
+
private finalChunkGenerationInfo?;
|
|
23
|
+
constructor({ responses, sleep, emitCustomEvent, splitStrategy, toolCalls, finalChunkGenerationInfo, }: {
|
|
15
24
|
responses: string[];
|
|
16
25
|
sleep?: number;
|
|
17
26
|
emitCustomEvent?: boolean;
|
|
18
27
|
splitStrategy?: SplitStrategy;
|
|
19
28
|
toolCalls?: ToolCall[];
|
|
29
|
+
finalChunkGenerationInfo?: Record<string, unknown>;
|
|
20
30
|
});
|
|
21
31
|
private splitText;
|
|
22
|
-
_createResponseChunk(text: string, tool_call_chunks?: ToolCallChunk[]): ChatGenerationChunk;
|
|
32
|
+
_createResponseChunk(text: string, tool_call_chunks?: ToolCallChunk[], responseMetadata?: Record<string, unknown>): ChatGenerationChunk;
|
|
23
33
|
_streamResponseChunks(_messages: BaseMessage[], options: this['ParsedCallOptions'], runManager?: CallbackManagerForLLMRun): AsyncGenerator<ChatGenerationChunk>;
|
|
24
34
|
}
|
|
25
35
|
export declare function createFakeStreamingLLM({ responses, sleep, splitStrategy, toolCalls, }: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AIMessage, ToolMessage, BaseMessage, HumanMessage, SystemMessage } from '@langchain/core/messages';
|
|
2
2
|
import type { MessageContentImageUrl } from '@langchain/core/messages';
|
|
3
3
|
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
4
|
-
import type { MessageContentComplex, TPayload, ProviderName } from '@/types';
|
|
4
|
+
import type { MessageContentComplex, TPayload, ProviderName, CompactionSemanticIndex } from '@/types';
|
|
5
5
|
import { Providers } from '@/common';
|
|
6
6
|
interface MediaMessageParams {
|
|
7
7
|
message: {
|
|
@@ -105,6 +105,13 @@ interface FormatAgentMessagesOptions {
|
|
|
105
105
|
* historical `skill` tool_calls are not reconstructed into a HumanMessage,
|
|
106
106
|
* so the same SKILL.md body is not injected twice in one request. */
|
|
107
107
|
skipSkillBodyNames?: Set<string>;
|
|
108
|
+
/** Derive bounded compaction guidance during the formatter's existing
|
|
109
|
+
* persisted-content analysis. Tool intents are accepted only for names
|
|
110
|
+
* the host identifies as semantic-label fields; business `intent`
|
|
111
|
+
* parameters must remain ordinary tool input. */
|
|
112
|
+
compactionSemanticIndex?: {
|
|
113
|
+
intentToolNames?: ReadonlySet<string>;
|
|
114
|
+
};
|
|
108
115
|
}
|
|
109
116
|
/**
|
|
110
117
|
* Groups content parts by agent and formats them with agent labels
|
|
@@ -148,6 +155,8 @@ export declare const formatAgentMessages: (payload: TPayload, indexTokenCountMap
|
|
|
148
155
|
/** When a positional summary boundary sliced content from a message, the token
|
|
149
156
|
* count was proportionally reduced. Returned so the caller can log it. */
|
|
150
157
|
boundaryTokenAdjustment?: SummaryTokenAdjustment;
|
|
158
|
+
/** Bounded semantic guidance derived during persisted-content analysis. */
|
|
159
|
+
compactionSemanticIndex?: CompactionSemanticIndex;
|
|
151
160
|
};
|
|
152
161
|
/**
|
|
153
162
|
* Adds a value at key 0 for system messages and shifts all key indices by one in an indexTokenCountMap.
|
package/dist/types/run.d.ts
CHANGED
|
@@ -134,6 +134,15 @@ export declare class Run<_T extends t.BaseGraphState> {
|
|
|
134
134
|
*/
|
|
135
135
|
getPreemptStats(): t.PreemptStats;
|
|
136
136
|
getToolCount(): number;
|
|
137
|
+
/**
|
|
138
|
+
* True when the run's last turn ended at `END` because the provider hit
|
|
139
|
+
* its output token ceiling while producing plain text/reasoning — no tool
|
|
140
|
+
* call, so `assertNotTruncatedToolCall` never sees it and the graph reads
|
|
141
|
+
* the turn as an ordinary completion. Hosts check this alongside
|
|
142
|
+
* `getPreemptStats()` / `getHaltReason()` to decide whether to persist the
|
|
143
|
+
* response as unfinished instead of a silently truncated "complete" one.
|
|
144
|
+
*/
|
|
145
|
+
getOutputTruncated(): boolean;
|
|
137
146
|
/**
|
|
138
147
|
* Creates a custom event callback handler that intercepts custom events
|
|
139
148
|
* and processes them through our handler registry instead of EventStreamCallbackHandler
|
|
@@ -175,13 +184,16 @@ export declare class Run<_T extends t.BaseGraphState> {
|
|
|
175
184
|
*/
|
|
176
185
|
getInterrupt<TPayload = t.HumanInterruptPayload>(): t.RunInterruptResult<TPayload> | undefined;
|
|
177
186
|
/**
|
|
178
|
-
* Returns the
|
|
179
|
-
*
|
|
187
|
+
* Returns why the run ended without a natural completion, or `undefined`
|
|
188
|
+
* when it completed normally. Reasons include hook- and prompt-driven
|
|
189
|
+
* halts, `preempt_incomplete` when a cooperative seal ended the turn
|
|
190
|
+
* without continuation content, and `output_truncated` when the provider
|
|
191
|
+
* stopped a plain-text/reasoning response at its output-token ceiling.
|
|
180
192
|
*
|
|
181
193
|
* Hosts inspect this after `processStream` returns to distinguish a
|
|
182
|
-
* natural completion
|
|
183
|
-
*
|
|
184
|
-
*
|
|
194
|
+
* natural completion from a terminal partial response. Independent from
|
|
195
|
+
* `getInterrupt()` — a halted run has no interrupt; an interrupted run has
|
|
196
|
+
* no halt reason.
|
|
185
197
|
*/
|
|
186
198
|
getHaltReason(): string | undefined;
|
|
187
199
|
/**
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
2
|
+
import type { CompactionSemanticIndex } from '@/types';
|
|
3
|
+
export { COMPACTION_SEMANTIC_INDEX_LIMITS } from '@/common';
|
|
4
|
+
/** Records producer-side omissions without retaining the discarded entries. */
|
|
5
|
+
export declare function setCompactionSemanticIndexProvidedEntryCount(index: CompactionSemanticIndex, providedEntryCount: number): void;
|
|
6
|
+
export type RenderedCompactionSemanticIndex = {
|
|
7
|
+
appendix: string;
|
|
8
|
+
providedEntryCount: number;
|
|
9
|
+
entryCount: number;
|
|
10
|
+
charCount: number;
|
|
11
|
+
omittedEntryCount: number;
|
|
12
|
+
};
|
|
13
|
+
/** Captures caller-owned data before graph execution can cross an await. */
|
|
14
|
+
export declare function snapshotCompactionSemanticIndex(index: CompactionSemanticIndex | undefined): CompactionSemanticIndex | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Produces a deterministic, bounded compaction appendix. Invalid, stale,
|
|
17
|
+
* pending, redacted, conflicting, and out-of-range entries fail closed.
|
|
18
|
+
*/
|
|
19
|
+
export declare function renderCompactionSemanticIndex(index: CompactionSemanticIndex | undefined, messagesToRefine: BaseMessage[]): RenderedCompactionSemanticIndex;
|
|
@@ -4,7 +4,7 @@ import type { START, StateGraph, StateGraphArgs } from '@langchain/langgraph';
|
|
|
4
4
|
import type { RunnableConfig, Runnable } from '@langchain/core/runnables';
|
|
5
5
|
import type { ChatGenerationChunk } from '@langchain/core/outputs';
|
|
6
6
|
import type { GoogleAIToolType } from '@langchain/google-common';
|
|
7
|
-
import type { SummarizationNodeInput, SummarizeCompleteEvent, SummarizationConfig, SummarizeStartEvent, SummarizeDeltaEvent } from '@/types/summarize';
|
|
7
|
+
import type { SummarizationNodeInput, SummarizeCompleteEvent, CompactionSemanticIndex, SummarizationConfig, SummarizeStartEvent, SummarizeDeltaEvent } from '@/types/summarize';
|
|
8
8
|
import type { RunStep, RunStepDeltaEvent, RunStepResumeState, RunStepClosedEvent, MessageDeltaEvent, ReasoningDeltaEvent } from '@/types/stream';
|
|
9
9
|
import type { ToolMap, ToolSessionMap, ToolEndEvent, GenericTool, LCTool, ToolExecutionConfig, ToolExecuteBatchRequest } from '@/types/tools';
|
|
10
10
|
import type { TokenCounter, StreamLimits, StreamPreemption, TokenBudgetBreakdown } from '@/types/run';
|
|
@@ -693,6 +693,14 @@ interface AgentInputFields {
|
|
|
693
693
|
discoveredTools?: string[];
|
|
694
694
|
summarizationEnabled?: boolean;
|
|
695
695
|
summarizationConfig?: SummarizationConfig;
|
|
696
|
+
/**
|
|
697
|
+
* Optional host-supplied, user-visible guidance for compaction. The SDK
|
|
698
|
+
* validates, bounds, and scopes entries to the messages being compacted;
|
|
699
|
+
* raw conversation messages remain authoritative. Captured when the
|
|
700
|
+
* AgentContext is constructed; labels committed later in the same run are
|
|
701
|
+
* outside this construction-time interface.
|
|
702
|
+
*/
|
|
703
|
+
compactionSemanticIndex?: CompactionSemanticIndex;
|
|
696
704
|
/** Cross-run summary from a previous run, forwarded from formatAgentMessages.
|
|
697
705
|
* Injected into the dynamic system tail via AgentContext. */
|
|
698
706
|
initialSummary?: {
|
|
@@ -29,6 +29,39 @@ export type RetainRecentConfig = {
|
|
|
29
29
|
*/
|
|
30
30
|
tokens?: number;
|
|
31
31
|
};
|
|
32
|
+
export type CompactionSemanticIndexStatus = 'committed' | 'pending';
|
|
33
|
+
type CompactionSemanticIndexEntryBase = {
|
|
34
|
+
/** Persisted message that owns the indexed content. */
|
|
35
|
+
sourceMessageId: string;
|
|
36
|
+
/** Zero-based content-part index within the persisted source message. */
|
|
37
|
+
sourceContentIndex: number;
|
|
38
|
+
/** Monotonic host revision for this logical entry. */
|
|
39
|
+
revision: number;
|
|
40
|
+
/** Only committed entries may guide compaction. */
|
|
41
|
+
status: CompactionSemanticIndexStatus;
|
|
42
|
+
/** User-visible semantic guidance. Hidden reasoning must never be supplied. */
|
|
43
|
+
text: string;
|
|
44
|
+
/** Omits the entry entirely when host policy redacts its source. */
|
|
45
|
+
redacted?: boolean;
|
|
46
|
+
};
|
|
47
|
+
export type CompactionToolSemanticIndexEntry = CompactionSemanticIndexEntryBase & {
|
|
48
|
+
type: 'tool_intent' | 'tool_outcome';
|
|
49
|
+
toolCallId: string;
|
|
50
|
+
};
|
|
51
|
+
export type CompactionActivitySemanticIndexEntry = CompactionSemanticIndexEntryBase & {
|
|
52
|
+
type: 'activity_phase';
|
|
53
|
+
};
|
|
54
|
+
export type CompactionReasoningSemanticIndexEntry = CompactionSemanticIndexEntryBase & {
|
|
55
|
+
type: 'reasoning_label';
|
|
56
|
+
/** Stable identity shared by every user-visible label revision. */
|
|
57
|
+
reasoningStepId: string;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Source-addressed navigation hints for the compaction model. Entries remain
|
|
61
|
+
* advisory: raw messages are always sent and remain authoritative.
|
|
62
|
+
*/
|
|
63
|
+
export type CompactionSemanticIndexEntry = CompactionToolSemanticIndexEntry | CompactionActivitySemanticIndexEntry | CompactionReasoningSemanticIndexEntry;
|
|
64
|
+
export type CompactionSemanticIndex = readonly CompactionSemanticIndexEntry[];
|
|
32
65
|
export type SummarizationConfig = {
|
|
33
66
|
provider?: ProviderName;
|
|
34
67
|
model?: string;
|
|
@@ -86,6 +119,10 @@ export interface SummarizeStartEvent {
|
|
|
86
119
|
messagesToRefineCount: number;
|
|
87
120
|
/** Which summarization cycle this is (1-based, increments each time summarization fires) */
|
|
88
121
|
summaryVersion: number;
|
|
122
|
+
/** Committed, source-valid semantic hints included in the request. */
|
|
123
|
+
semanticIndexEntryCount?: number;
|
|
124
|
+
/** Serialized semantic-index characters included in the request. */
|
|
125
|
+
semanticIndexCharCount?: number;
|
|
89
126
|
}
|
|
90
127
|
export interface SummarizeDeltaEvent {
|
|
91
128
|
id: string;
|
|
@@ -99,3 +136,4 @@ export interface SummarizeCompleteEvent {
|
|
|
99
136
|
summary?: SummaryContentBlock;
|
|
100
137
|
error?: string;
|
|
101
138
|
}
|
|
139
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@librechat/agents",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.8",
|
|
4
4
|
"reova": {
|
|
5
5
|
"enabled": true,
|
|
6
6
|
"endpoint": "https://telemetry.reo.dev/data"
|
|
@@ -221,6 +221,7 @@
|
|
|
221
221
|
"bench:provider-derivation": "tsx ./src/scripts/bench-provider-derivation.ts",
|
|
222
222
|
"bench:provider-projection": "tsx ./src/scripts/bench-provider-request-projection.ts",
|
|
223
223
|
"bench:execution-world": "tsx ./src/scripts/bench-execution-world.ts",
|
|
224
|
+
"bench:compaction-semantic-index": "tsx ./src/scripts/bench-compaction-semantic-index.ts",
|
|
224
225
|
"probe:overflow": "tsx -r dotenv/config ./src/scripts/context-overflow-probe.ts",
|
|
225
226
|
"subagent": "tsx -r dotenv/config ./src/scripts/multi-agent-subagent.ts",
|
|
226
227
|
"subagent:events": "tsx -r dotenv/config ./src/scripts/subagent-event-driven-debug.ts",
|
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
Providers,
|
|
51
51
|
} from '@/common';
|
|
52
52
|
import { isTokenCounterCacheCompatible } from '@/llm/tokenCounterCacheCompatibility';
|
|
53
|
+
import { snapshotCompactionSemanticIndex } from '@/summarization/semanticIndex';
|
|
53
54
|
import { createExactTokenCountCache } from '@/llm/contextPressureMeter';
|
|
54
55
|
import { createSchemaOnlyTools } from '@/tools/schema';
|
|
55
56
|
import { apportionTokenCounts } from '@/utils/tokens';
|
|
@@ -74,6 +75,14 @@ type ProgrammaticToolInstructionTarget = {
|
|
|
74
75
|
executesDirectly: boolean;
|
|
75
76
|
};
|
|
76
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Consecutive summarization attempts that may return no usable summary before
|
|
80
|
+
* the run stops asking. Every such attempt spends a full model call over the
|
|
81
|
+
* whole history and leaves the message set exactly as it was, so the cap
|
|
82
|
+
* bounds spend as much as it bounds the compaction loop.
|
|
83
|
+
*/
|
|
84
|
+
const MAX_SUMMARIZATION_FAILURES = 3;
|
|
85
|
+
|
|
77
86
|
/**
|
|
78
87
|
* Encapsulates agent-specific state that can vary between agents in a multi-agent system
|
|
79
88
|
*/
|
|
@@ -108,6 +117,7 @@ export class AgentContext {
|
|
|
108
117
|
discoveredTools,
|
|
109
118
|
summarizationEnabled,
|
|
110
119
|
summarizationConfig,
|
|
120
|
+
compactionSemanticIndex,
|
|
111
121
|
initialSummary,
|
|
112
122
|
contextPruningConfig,
|
|
113
123
|
maxToolResultChars,
|
|
@@ -141,11 +151,18 @@ export class AgentContext {
|
|
|
141
151
|
discoveredTools,
|
|
142
152
|
summarizationEnabled,
|
|
143
153
|
summarizationConfig,
|
|
154
|
+
compactionSemanticIndex,
|
|
144
155
|
contextPruningConfig,
|
|
145
156
|
maxToolResultChars,
|
|
146
157
|
});
|
|
147
158
|
|
|
148
|
-
agentContext._sourceInputs =
|
|
159
|
+
agentContext._sourceInputs =
|
|
160
|
+
compactionSemanticIndex == null
|
|
161
|
+
? agentConfig
|
|
162
|
+
: {
|
|
163
|
+
...agentConfig,
|
|
164
|
+
compactionSemanticIndex: agentContext.compactionSemanticIndex,
|
|
165
|
+
};
|
|
149
166
|
agentContext.subagentConfigs = subagentConfigs;
|
|
150
167
|
agentContext.maxSubagentDepth = maxSubagentDepth;
|
|
151
168
|
/**
|
|
@@ -346,6 +363,8 @@ export class AgentContext {
|
|
|
346
363
|
summarizationEnabled?: boolean;
|
|
347
364
|
/** Summarization runtime settings used by graph pruning hooks */
|
|
348
365
|
summarizationConfig?: t.SummarizationConfig;
|
|
366
|
+
/** Host-supplied advisory guidance consumed only when compaction runs. */
|
|
367
|
+
compactionSemanticIndex?: t.CompactionSemanticIndex;
|
|
349
368
|
/** Current summary text produced by the summarize node, integrated into system message */
|
|
350
369
|
private summaryText?: string;
|
|
351
370
|
/** Token count of the current summary (tracked for token accounting) */
|
|
@@ -376,6 +395,13 @@ export class AgentContext {
|
|
|
376
395
|
* Summarization is allowed to fire again only when new messages appear.
|
|
377
396
|
*/
|
|
378
397
|
private _lastSummarizationMsgCount: number = 0;
|
|
398
|
+
/**
|
|
399
|
+
* Consecutive summarization attempts that produced no usable summary.
|
|
400
|
+
* An empty or failed summary leaves the message set exactly as it was, so
|
|
401
|
+
* the next prune cycle would ask again on identical state. Cleared by
|
|
402
|
+
* `setSummary` and by `reset()`.
|
|
403
|
+
*/
|
|
404
|
+
private _summarizationFailures: number = 0;
|
|
379
405
|
/**
|
|
380
406
|
* Forced compactions performed after a provider rejected a prompt as too
|
|
381
407
|
* large. Bounds the recovery loop so a model that keeps refusing cannot
|
|
@@ -430,6 +456,7 @@ export class AgentContext {
|
|
|
430
456
|
discoveredTools,
|
|
431
457
|
summarizationEnabled,
|
|
432
458
|
summarizationConfig,
|
|
459
|
+
compactionSemanticIndex,
|
|
433
460
|
contextPruningConfig,
|
|
434
461
|
maxToolResultChars,
|
|
435
462
|
}: {
|
|
@@ -456,6 +483,7 @@ export class AgentContext {
|
|
|
456
483
|
discoveredTools?: string[];
|
|
457
484
|
summarizationEnabled?: boolean;
|
|
458
485
|
summarizationConfig?: t.SummarizationConfig;
|
|
486
|
+
compactionSemanticIndex?: t.CompactionSemanticIndex;
|
|
459
487
|
contextPruningConfig?: t.ContextPruningConfig;
|
|
460
488
|
maxToolResultChars?: number;
|
|
461
489
|
}) {
|
|
@@ -495,6 +523,11 @@ export class AgentContext {
|
|
|
495
523
|
this.useLegacyContent = useLegacyContent ?? false;
|
|
496
524
|
this.summarizationEnabled = summarizationEnabled;
|
|
497
525
|
this.summarizationConfig = summarizationConfig;
|
|
526
|
+
if (compactionSemanticIndex != null) {
|
|
527
|
+
this.compactionSemanticIndex = snapshotCompactionSemanticIndex(
|
|
528
|
+
compactionSemanticIndex
|
|
529
|
+
);
|
|
530
|
+
}
|
|
498
531
|
this.contextPruningConfig = contextPruningConfig;
|
|
499
532
|
this.maxToolResultChars = maxToolResultChars;
|
|
500
533
|
|
|
@@ -1209,6 +1242,7 @@ export class AgentContext {
|
|
|
1209
1242
|
this.summaryTokenCount = this._durableSummaryTokenCount;
|
|
1210
1243
|
this.summaryPrecedesMessages = this.durableSummaryPrecedesMessages;
|
|
1211
1244
|
this._lastSummarizationMsgCount = 0;
|
|
1245
|
+
this._summarizationFailures = 0;
|
|
1212
1246
|
this.lastCallUsage = undefined;
|
|
1213
1247
|
this.totalTokensFresh = false;
|
|
1214
1248
|
this.restoreContextBudgetAfterOverflow();
|
|
@@ -1478,6 +1512,7 @@ export class AgentContext {
|
|
|
1478
1512
|
this._durableSummaryTokenCount = tokenCount;
|
|
1479
1513
|
this.durableSummaryPrecedesMessages = this.summaryPrecedesMessages;
|
|
1480
1514
|
this._summaryVersion += 1;
|
|
1515
|
+
this._summarizationFailures = 0;
|
|
1481
1516
|
this.systemRunnableStale = true;
|
|
1482
1517
|
this.pruneMessages = undefined;
|
|
1483
1518
|
}
|
|
@@ -1547,6 +1582,31 @@ export class AgentContext {
|
|
|
1547
1582
|
this._lastSummarizationMsgCount = msgCount;
|
|
1548
1583
|
}
|
|
1549
1584
|
|
|
1585
|
+
/**
|
|
1586
|
+
* Records a summarization attempt that produced no usable summary — an
|
|
1587
|
+
* empty model response, or a provider failure the run declined to paper
|
|
1588
|
+
* over with a metadata stub. Cleared by the next successful summary.
|
|
1589
|
+
*/
|
|
1590
|
+
recordSummarizationFailure(): void {
|
|
1591
|
+
this._summarizationFailures += 1;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
get summarizationFailures(): number {
|
|
1595
|
+
return this._summarizationFailures;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
/**
|
|
1599
|
+
* True once consecutive no-progress attempts reach {@link MAX_SUMMARIZATION_FAILURES}.
|
|
1600
|
+
* A summarizer that has returned nothing this many times in a row will keep
|
|
1601
|
+
* returning nothing: each empty result leaves the history unchanged, so the
|
|
1602
|
+
* next prune cycle re-triggers on the same state and the run burns its
|
|
1603
|
+
* recursion budget on empty summary steps. Summarization stays off for the
|
|
1604
|
+
* remainder of the run; `reset()` restores it for the next one.
|
|
1605
|
+
*/
|
|
1606
|
+
get summarizationExhausted(): boolean {
|
|
1607
|
+
return this._summarizationFailures >= MAX_SUMMARIZATION_FAILURES;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1550
1610
|
get overflowRecoveryAttempts(): number {
|
|
1551
1611
|
return this._overflowRecoveryAttempts;
|
|
1552
1612
|
}
|
package/src/common/constants.ts
CHANGED
|
@@ -42,3 +42,14 @@ export const ACTIVITY_LABEL_RUN_NAME = 'StepLabel';
|
|
|
42
42
|
export const REASONING_LABEL_RUN_NAME = 'ReasoningLabel';
|
|
43
43
|
export const ACTIVITY_PHASE_RUN_NAME = 'MultiStepLabel';
|
|
44
44
|
export const ACTIVITY_PHASE_LABEL_RUN_NAME = 'MultiStepLabelGeneration';
|
|
45
|
+
|
|
46
|
+
/** Shared admission and rendering bounds for compaction navigation hints. */
|
|
47
|
+
export const COMPACTION_SEMANTIC_INDEX_LIMITS = Object.freeze({
|
|
48
|
+
maxInputEntries: 256,
|
|
49
|
+
maxEntries: 64,
|
|
50
|
+
maxEntryChars: 512,
|
|
51
|
+
maxTotalChars: 4_096,
|
|
52
|
+
maxInputTextChars: 4_096,
|
|
53
|
+
maxIdentityChars: 512,
|
|
54
|
+
maxSourceContentIndex: 4_095,
|
|
55
|
+
} as const);
|