@librechat/agents 3.7.0 → 3.7.2
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 +11 -2
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/eventActor/EventActorExecutor.cjs +797 -0
- package/dist/cjs/eventActor/EventActorExecutor.cjs.map +1 -0
- package/dist/cjs/eventActor/index.cjs +1 -0
- package/dist/cjs/graphs/Graph.cjs +7 -2
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/llm/invoke.cjs +56 -9
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/main.cjs +15 -1
- package/dist/cjs/messages/content.cjs +8 -5
- package/dist/cjs/messages/content.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +15 -5
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/index.cjs +2 -1
- package/dist/cjs/messages/projectionInvariant.cjs +74 -0
- package/dist/cjs/messages/projectionInvariant.cjs.map +1 -0
- package/dist/cjs/messages/provenance.cjs +30 -8
- package/dist/cjs/messages/provenance.cjs.map +1 -1
- package/dist/cjs/messages/recency.cjs +202 -7
- package/dist/cjs/messages/recency.cjs.map +1 -1
- package/dist/cjs/messages/toolResultTypes.cjs +1 -0
- package/dist/cjs/session/AgentSession.cjs +4 -23
- package/dist/cjs/session/AgentSession.cjs.map +1 -1
- package/dist/cjs/session/deriveMessages.cjs +25 -0
- package/dist/cjs/session/deriveMessages.cjs.map +1 -0
- package/dist/cjs/session/index.cjs +1 -0
- package/dist/cjs/summarization/node.cjs +13 -6
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs +65 -8
- package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +11 -2
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/eventActor/EventActorExecutor.mjs +796 -0
- package/dist/esm/eventActor/EventActorExecutor.mjs.map +1 -0
- package/dist/esm/eventActor/index.mjs +2 -0
- package/dist/esm/graphs/Graph.mjs +8 -3
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/llm/invoke.mjs +56 -9
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/main.mjs +8 -4
- package/dist/esm/messages/content.mjs +8 -6
- package/dist/esm/messages/content.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +16 -6
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/index.mjs +2 -1
- package/dist/esm/messages/projectionInvariant.mjs +72 -0
- package/dist/esm/messages/projectionInvariant.mjs.map +1 -0
- package/dist/esm/messages/provenance.mjs +30 -9
- package/dist/esm/messages/provenance.mjs.map +1 -1
- package/dist/esm/messages/recency.mjs +201 -8
- package/dist/esm/messages/recency.mjs.map +1 -1
- package/dist/esm/messages/toolResultTypes.mjs +1 -1
- package/dist/esm/session/AgentSession.mjs +4 -23
- package/dist/esm/session/AgentSession.mjs.map +1 -1
- package/dist/esm/session/deriveMessages.mjs +25 -0
- package/dist/esm/session/deriveMessages.mjs.map +1 -0
- package/dist/esm/session/index.mjs +1 -0
- package/dist/esm/summarization/node.mjs +14 -7
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs +65 -8
- package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +6 -1
- package/dist/types/eventActor/EventActorExecutor.d.ts +18 -0
- package/dist/types/eventActor/index.d.ts +2 -0
- package/dist/types/eventActor/types.d.ts +193 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/messages/content.d.ts +4 -1
- package/dist/types/messages/format.d.ts +6 -0
- package/dist/types/messages/index.d.ts +1 -0
- package/dist/types/messages/projectionInvariant.d.ts +25 -0
- package/dist/types/messages/provenance.d.ts +10 -0
- package/dist/types/messages/recency.d.ts +30 -18
- package/dist/types/session/deriveMessages.d.ts +11 -0
- package/dist/types/session/index.d.ts +2 -0
- package/dist/types/tools/subagent/InMemorySubagentTaskStore.d.ts +13 -1
- package/dist/types/types/graph.d.ts +1 -1
- package/dist/types/types/subagentTasks.d.ts +22 -0
- package/dist/types/types/summarize.d.ts +15 -13
- package/package.json +68 -77
- package/src/agents/AgentContext.ts +19 -5
- package/src/eventActor/EventActorExecutor.ts +1499 -0
- package/src/eventActor/index.ts +32 -0
- package/src/eventActor/types.ts +250 -0
- package/src/graphs/Graph.ts +9 -1
- package/src/index.ts +3 -0
- package/src/llm/invoke.ts +102 -23
- package/src/messages/content.ts +20 -10
- package/src/messages/format.ts +29 -5
- package/src/messages/index.ts +1 -0
- package/src/messages/projectionInvariant.ts +134 -0
- package/src/messages/provenance.ts +60 -18
- package/src/messages/recency.ts +429 -27
- package/src/session/AgentSession.ts +4 -30
- package/src/session/deriveMessages.ts +37 -0
- package/src/session/index.ts +2 -0
- package/src/summarization/node.ts +37 -15
- package/src/tools/subagent/InMemorySubagentTaskStore.ts +139 -6
- package/src/types/graph.ts +1 -0
- package/src/types/subagentTasks.ts +28 -0
- package/src/types/summarize.ts +15 -13
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export {
|
|
2
|
+
EventActorExecutor,
|
|
3
|
+
createEventActorExecutor,
|
|
4
|
+
} from './EventActorExecutor';
|
|
5
|
+
export type {
|
|
6
|
+
EventActorAdapterPrepareRequest,
|
|
7
|
+
EventActorAdapterPreparation,
|
|
8
|
+
EventActorAppliedResult,
|
|
9
|
+
EventActorCheckpointFork,
|
|
10
|
+
EventActorCheckpointReference,
|
|
11
|
+
EventActorCommitRequest,
|
|
12
|
+
EventActorCommitResult,
|
|
13
|
+
EventActorDiscardReason,
|
|
14
|
+
EventActorDiscardRequest,
|
|
15
|
+
EventActorEvent,
|
|
16
|
+
EventActorExecutionRequest,
|
|
17
|
+
EventActorExecutionResult,
|
|
18
|
+
EventActorExecutorOptions,
|
|
19
|
+
EventActorHead,
|
|
20
|
+
EventActorHostAdapter,
|
|
21
|
+
EventActorInvocation,
|
|
22
|
+
EventActorInvocationContext,
|
|
23
|
+
EventActorIndeterminateResult,
|
|
24
|
+
EventActorInvocationReference,
|
|
25
|
+
EventActorInvocationResult,
|
|
26
|
+
EventActorPreparation,
|
|
27
|
+
EventActorPreparationContext,
|
|
28
|
+
EventActorPreparedInvocation,
|
|
29
|
+
EventActorPrepareRequest,
|
|
30
|
+
EventActorSettlementResult,
|
|
31
|
+
EventActorTerminalResult,
|
|
32
|
+
} from './types';
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
2
|
+
|
|
3
|
+
/** Durable event payload accepted by the actor lifecycle. */
|
|
4
|
+
export type EventActorEvent =
|
|
5
|
+
| null
|
|
6
|
+
| boolean
|
|
7
|
+
| number
|
|
8
|
+
| string
|
|
9
|
+
| readonly EventActorEvent[]
|
|
10
|
+
| { readonly [key: string]: EventActorEvent };
|
|
11
|
+
|
|
12
|
+
/** Stable reference to one persisted LangGraph checkpoint. */
|
|
13
|
+
export interface EventActorCheckpointReference {
|
|
14
|
+
threadId: string;
|
|
15
|
+
checkpointId?: string;
|
|
16
|
+
checkpointNs: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Committed logical head read before an event invocation is prepared. */
|
|
20
|
+
export interface EventActorHead {
|
|
21
|
+
actorThreadId: string;
|
|
22
|
+
generation: number;
|
|
23
|
+
checkpoint?: EventActorCheckpointReference;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Invocation-owned checkpoint fork that cannot become authoritative in place. */
|
|
27
|
+
export interface EventActorCheckpointFork
|
|
28
|
+
extends EventActorCheckpointReference {
|
|
29
|
+
invocationId: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface EventActorInvocationReference {
|
|
33
|
+
actorThreadId: string;
|
|
34
|
+
invocationId: string;
|
|
35
|
+
depth: number;
|
|
36
|
+
continuation: 'warm' | 'cold';
|
|
37
|
+
base: EventActorHead;
|
|
38
|
+
fork: EventActorCheckpointFork;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface EventActorInvocation<TEvent extends EventActorEvent>
|
|
42
|
+
extends EventActorInvocationReference {
|
|
43
|
+
event: TEvent;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface EventActorPreparedInvocation<TEvent extends EventActorEvent>
|
|
47
|
+
extends EventActorInvocation<TEvent> {
|
|
48
|
+
/**
|
|
49
|
+
* Executor-authenticated, time-bounded binding over the complete prepared
|
|
50
|
+
* invocation. Its wire representation is opaque to callers.
|
|
51
|
+
*/
|
|
52
|
+
preparationDigest: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type EventActorAdapterPreparation<TEvent extends EventActorEvent> =
|
|
56
|
+
| { status: 'ready'; invocation: EventActorInvocation<TEvent> }
|
|
57
|
+
| { status: 'checkpoint_unavailable'; head: EventActorHead };
|
|
58
|
+
|
|
59
|
+
export type EventActorPreparation<TEvent extends EventActorEvent> =
|
|
60
|
+
| { status: 'ready'; invocation: EventActorPreparedInvocation<TEvent> }
|
|
61
|
+
| {
|
|
62
|
+
status: 'checkpoint_unavailable';
|
|
63
|
+
request: EventActorPrepareRequest<TEvent>;
|
|
64
|
+
head: EventActorHead;
|
|
65
|
+
/** Executor-authenticated binding over this exact request/head pair. */
|
|
66
|
+
preparationDigest: string;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export type EventActorTerminalResult<TResult extends EventActorEvent> =
|
|
70
|
+
| {
|
|
71
|
+
status: 'applied';
|
|
72
|
+
result: TResult;
|
|
73
|
+
checkpoint: EventActorCheckpointFork;
|
|
74
|
+
}
|
|
75
|
+
| { status: 'completed_no_action'; result?: TResult };
|
|
76
|
+
|
|
77
|
+
export type EventActorAppliedResult<TResult extends EventActorEvent> = Extract<
|
|
78
|
+
EventActorTerminalResult<TResult>,
|
|
79
|
+
{ status: 'applied' }
|
|
80
|
+
> & {
|
|
81
|
+
/** Executor-issued one-shot settlement for the invocation that produced this action. */
|
|
82
|
+
invocation: EventActorInvocationReference;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export interface EventActorIndeterminateResult<
|
|
86
|
+
TResult extends EventActorEvent,
|
|
87
|
+
> {
|
|
88
|
+
/** Applied handling cannot be proven safe to retry; retain its fork. */
|
|
89
|
+
status: 'commit_indeterminate';
|
|
90
|
+
result?: TResult;
|
|
91
|
+
checkpoint: EventActorCheckpointFork;
|
|
92
|
+
error: Error;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type EventActorInvocationResult<TResult extends EventActorEvent> =
|
|
96
|
+
| EventActorAppliedResult<TResult>
|
|
97
|
+
| EventActorIndeterminateResult<TResult>
|
|
98
|
+
| Extract<
|
|
99
|
+
EventActorTerminalResult<TResult>,
|
|
100
|
+
{ status: 'completed_no_action' }
|
|
101
|
+
>;
|
|
102
|
+
|
|
103
|
+
export interface EventActorInvocationContext {
|
|
104
|
+
signal: AbortSignal;
|
|
105
|
+
config: RunnableConfig;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface EventActorPreparationContext {
|
|
109
|
+
/** Explicit task-owned cancellation; parent-run ambient signals are excluded. */
|
|
110
|
+
signal: AbortSignal;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface EventActorPrepareRequest<TEvent extends EventActorEvent> {
|
|
114
|
+
actorThreadId: string;
|
|
115
|
+
invocationId: string;
|
|
116
|
+
depth: number;
|
|
117
|
+
event: TEvent;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface EventActorAdapterPrepareRequest<TEvent extends EventActorEvent>
|
|
121
|
+
extends EventActorPrepareRequest<TEvent> {
|
|
122
|
+
/** Unique execution-attempt namespace; invocationId remains the logical idempotency key. */
|
|
123
|
+
checkpointNs: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface EventActorCommitRequest<TResult extends EventActorEvent> {
|
|
127
|
+
invocation: EventActorInvocationReference;
|
|
128
|
+
expectedHead: EventActorHead;
|
|
129
|
+
checkpoint: EventActorCheckpointFork;
|
|
130
|
+
result: TResult;
|
|
131
|
+
retention: {
|
|
132
|
+
committedCheckpoints: 2;
|
|
133
|
+
dormantCheckpointTtlMs: number;
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export type EventActorCommitResult =
|
|
138
|
+
| { status: 'committed'; head: EventActorHead }
|
|
139
|
+
| { status: 'stale'; head?: EventActorHead };
|
|
140
|
+
|
|
141
|
+
/** Public settlement outcome after an action has already been applied. */
|
|
142
|
+
export type EventActorSettlementResult<TResult extends EventActorEvent> =
|
|
143
|
+
| EventActorCommitResult
|
|
144
|
+
| EventActorIndeterminateResult<TResult>;
|
|
145
|
+
|
|
146
|
+
export type EventActorDiscardReason =
|
|
147
|
+
| 'cancelled'
|
|
148
|
+
| 'completed_no_action'
|
|
149
|
+
| 'failed';
|
|
150
|
+
|
|
151
|
+
export interface EventActorDiscardRequest {
|
|
152
|
+
invocation: EventActorInvocationReference;
|
|
153
|
+
reason: EventActorDiscardReason;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Host adapter for durable actor state and the concrete agent invocation.
|
|
158
|
+
* `commit` must compare both the expected generation and checkpoint identity
|
|
159
|
+
* atomically before advancing the logical actor head. The host mailbox
|
|
160
|
+
* deduplicates the logical `invocationId` before entering this seam, while each
|
|
161
|
+
* SDK execution attempt receives a distinct checkpoint namespace. Preparation
|
|
162
|
+
* methods own rollback until they return a ready invocation and must treat the
|
|
163
|
+
* request event as immutable. On cancellation they roll back and reject with
|
|
164
|
+
* `context.signal.reason`; cleanup failures reject with their own error so they
|
|
165
|
+
* remain observable. `invoke` returns only after its provider, stream, timer,
|
|
166
|
+
* and executor resources have been released. Once qualifying action evidence
|
|
167
|
+
* exists, `invoke` must return `applied` even if a later abort or provider
|
|
168
|
+
* failure occurs; a thrown error is therefore a definite no-action failure
|
|
169
|
+
* whose fork is safe to discard. `commit` must not reclaim an applied stale
|
|
170
|
+
* fork: the SDK retains and surfaces it as `commit_conflict` for host
|
|
171
|
+
* reconciliation. `discard` must be idempotent for the same invocation because
|
|
172
|
+
* an ambiguous cleanup failure can be retried through the public lifecycle.
|
|
173
|
+
*/
|
|
174
|
+
export interface EventActorHostAdapter<
|
|
175
|
+
TEvent extends EventActorEvent,
|
|
176
|
+
TResult extends EventActorEvent,
|
|
177
|
+
> {
|
|
178
|
+
prepare(
|
|
179
|
+
request: EventActorAdapterPrepareRequest<TEvent>,
|
|
180
|
+
context: EventActorPreparationContext
|
|
181
|
+
): Promise<EventActorAdapterPreparation<TEvent>>;
|
|
182
|
+
coldContinue(
|
|
183
|
+
request: EventActorAdapterPrepareRequest<TEvent>,
|
|
184
|
+
head: EventActorHead,
|
|
185
|
+
context: EventActorPreparationContext
|
|
186
|
+
): Promise<EventActorInvocation<TEvent>>;
|
|
187
|
+
invoke(
|
|
188
|
+
invocation: EventActorInvocation<TEvent>,
|
|
189
|
+
context: EventActorInvocationContext
|
|
190
|
+
): Promise<EventActorTerminalResult<TResult>>;
|
|
191
|
+
commit(
|
|
192
|
+
request: EventActorCommitRequest<TResult>
|
|
193
|
+
): Promise<EventActorCommitResult>;
|
|
194
|
+
discard(request: EventActorDiscardRequest): Promise<void>;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export interface EventActorExecutionRequest<TEvent extends EventActorEvent> {
|
|
198
|
+
actorThreadId: string;
|
|
199
|
+
invocationId: string;
|
|
200
|
+
event: TEvent;
|
|
201
|
+
depth?: number;
|
|
202
|
+
/** Explicit task-owned signal. Ambient parent-run signals are ignored. */
|
|
203
|
+
signal?: AbortSignal;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export type EventActorExecutionResult<TResult extends EventActorEvent> =
|
|
207
|
+
| {
|
|
208
|
+
status: 'applied';
|
|
209
|
+
result: TResult;
|
|
210
|
+
head: EventActorHead;
|
|
211
|
+
continuation: 'warm' | 'cold';
|
|
212
|
+
}
|
|
213
|
+
| {
|
|
214
|
+
status: 'completed_no_action';
|
|
215
|
+
result?: TResult;
|
|
216
|
+
continuation: 'warm' | 'cold';
|
|
217
|
+
}
|
|
218
|
+
| {
|
|
219
|
+
status: 'cancelled';
|
|
220
|
+
continuation: 'warm' | 'cold';
|
|
221
|
+
}
|
|
222
|
+
| {
|
|
223
|
+
/** The action happened, but another head won the CAS. Reconcile; do not retry. */
|
|
224
|
+
status: 'commit_conflict';
|
|
225
|
+
result: TResult;
|
|
226
|
+
checkpoint: EventActorCheckpointFork;
|
|
227
|
+
head?: EventActorHead;
|
|
228
|
+
continuation: 'warm' | 'cold';
|
|
229
|
+
}
|
|
230
|
+
| {
|
|
231
|
+
/** Applied handling cannot be proven safe to retry; retain its fork. */
|
|
232
|
+
status: 'commit_indeterminate';
|
|
233
|
+
result?: TResult;
|
|
234
|
+
checkpoint: EventActorCheckpointFork;
|
|
235
|
+
error: Error;
|
|
236
|
+
continuation: 'warm' | 'cold';
|
|
237
|
+
}
|
|
238
|
+
| {
|
|
239
|
+
status: 'failed';
|
|
240
|
+
error: Error;
|
|
241
|
+
continuation: 'warm' | 'cold';
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export interface EventActorExecutorOptions {
|
|
245
|
+
maxDepth?: number;
|
|
246
|
+
/** Also bounds signed preparation authority and local terminal fences. */
|
|
247
|
+
dormantCheckpointTtlMs?: number;
|
|
248
|
+
/** Stable private key of at least 32 bytes for cross-lifetime handoffs. */
|
|
249
|
+
preparationSigningKey?: string | Uint8Array;
|
|
250
|
+
}
|
package/src/graphs/Graph.ts
CHANGED
|
@@ -68,6 +68,7 @@ import {
|
|
|
68
68
|
makeIsDeferred,
|
|
69
69
|
partitionAndMarkAnthropicToolCache,
|
|
70
70
|
DEFAULT_RETAIN_RECENT_TURNS,
|
|
71
|
+
resolveIntraTurnRetainTokens,
|
|
71
72
|
splitAtRecencyBoundary,
|
|
72
73
|
convertInjectedMessages,
|
|
73
74
|
coalesceAdjacentUserTurns,
|
|
@@ -3936,6 +3937,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3936
3937
|
*/
|
|
3937
3938
|
const estimatedPromptTokens = getEstimatedPromptTokens(contextUsage);
|
|
3938
3939
|
|
|
3940
|
+
const recencyTokenCounter =
|
|
3941
|
+
agentContext.contextPressureTokenCounts?.count ??
|
|
3942
|
+
agentContext.tokenCounter;
|
|
3939
3943
|
const canSummarizeOverflow =
|
|
3940
3944
|
agentContext.summarizationEnabled === true &&
|
|
3941
3945
|
splitAtRecencyBoundary(messages, {
|
|
@@ -3943,7 +3947,11 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3943
3947
|
agentContext.summarizationConfig?.retainRecent?.turns ??
|
|
3944
3948
|
DEFAULT_RETAIN_RECENT_TURNS,
|
|
3945
3949
|
tokens: agentContext.summarizationConfig?.retainRecent?.tokens,
|
|
3946
|
-
tokenCounter:
|
|
3950
|
+
tokenCounter: recencyTokenCounter,
|
|
3951
|
+
intraTurnTokens: resolveIntraTurnRetainTokens({
|
|
3952
|
+
tokens: agentContext.summarizationConfig?.retainRecent?.tokens,
|
|
3953
|
+
maxContextTokens: agentContext.maxContextTokens,
|
|
3954
|
+
}),
|
|
3947
3955
|
}).head.length > 0;
|
|
3948
3956
|
|
|
3949
3957
|
const getLocalProviderOverflowMeasurement = (
|
package/src/index.ts
CHANGED
package/src/llm/invoke.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { concat } from '@langchain/core/utils/stream';
|
|
2
2
|
import { AIMessageChunk } from '@langchain/core/messages';
|
|
3
|
+
import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
|
|
3
4
|
import { getCallbackManagerForConfig } from '@langchain/core/runnables';
|
|
4
5
|
import {
|
|
5
6
|
CallbackManager,
|
|
@@ -40,7 +41,12 @@ import { assertNotTruncatedToolCall } from '@/llm/truncation';
|
|
|
40
41
|
import { safeDispatchCustomEvent } from '@/utils/events';
|
|
41
42
|
import { getContextOverflowInfo } from '@/utils/errors';
|
|
42
43
|
import { appendCallbacks } from '@/utils/callbacks';
|
|
43
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
inspectProviderMessageProjection,
|
|
46
|
+
ProviderMessageProjectionInvariantError,
|
|
47
|
+
resolveProviderMessageProjectionInvariantMode,
|
|
48
|
+
modifyDeltaProperties,
|
|
49
|
+
} from '@/messages';
|
|
44
50
|
import { canSealPreempt } from '@/llm/preempt';
|
|
45
51
|
import { initializeModel } from '@/llm/init';
|
|
46
52
|
|
|
@@ -106,6 +112,79 @@ export type OnChunk = (
|
|
|
106
112
|
/** Unique per-model-attempt sequence; see the stamp in `attemptInvoke`. */
|
|
107
113
|
let streamLimitAttemptSeq = 0;
|
|
108
114
|
|
|
115
|
+
function createModelStartHandler({
|
|
116
|
+
config,
|
|
117
|
+
mode,
|
|
118
|
+
provider,
|
|
119
|
+
captureRunId,
|
|
120
|
+
}: {
|
|
121
|
+
config: RunnableConfig;
|
|
122
|
+
mode: ReturnType<typeof resolveProviderMessageProjectionInvariantMode>;
|
|
123
|
+
provider: t.ProviderName;
|
|
124
|
+
captureRunId?: (runId: string) => void;
|
|
125
|
+
}): BaseCallbackHandler {
|
|
126
|
+
let inspected = false;
|
|
127
|
+
const handler = BaseCallbackHandler.fromMethods({
|
|
128
|
+
handleChatModelStart: async (
|
|
129
|
+
_llm: Serialized,
|
|
130
|
+
messageBatches: BaseMessage[][],
|
|
131
|
+
runId: string
|
|
132
|
+
): Promise<void> => {
|
|
133
|
+
captureRunId?.(runId);
|
|
134
|
+
if (mode === 'off' || inspected) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
inspected = true;
|
|
138
|
+
const report = inspectProviderMessageProjection(messageBatches[0] ?? []);
|
|
139
|
+
if (report.valid) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (mode === 'assert') {
|
|
143
|
+
throw new ProviderMessageProjectionInvariantError(report);
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
const callbackManager = await getCallbackManagerForConfig(config);
|
|
147
|
+
await callbackManager?.handleCustomEvent?.(
|
|
148
|
+
GraphEvents.ON_AGENT_LOG,
|
|
149
|
+
{
|
|
150
|
+
level: 'warn',
|
|
151
|
+
scope: 'projection',
|
|
152
|
+
message: 'Provider message projection has provenance gaps',
|
|
153
|
+
data: { provider, report },
|
|
154
|
+
runId,
|
|
155
|
+
} satisfies t.AgentLogEvent,
|
|
156
|
+
runId
|
|
157
|
+
);
|
|
158
|
+
} catch {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
handler.name = 'provider-message-projection-invariant';
|
|
164
|
+
handler.raiseError = mode === 'assert';
|
|
165
|
+
handler.awaitHandlers = true;
|
|
166
|
+
return handler;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function withModelStartHandler({
|
|
170
|
+
config,
|
|
171
|
+
mode,
|
|
172
|
+
provider,
|
|
173
|
+
captureRunId,
|
|
174
|
+
}: {
|
|
175
|
+
config: RunnableConfig;
|
|
176
|
+
mode: ReturnType<typeof resolveProviderMessageProjectionInvariantMode>;
|
|
177
|
+
provider: t.ProviderName;
|
|
178
|
+
captureRunId?: (runId: string) => void;
|
|
179
|
+
}): RunnableConfig {
|
|
180
|
+
return {
|
|
181
|
+
...config,
|
|
182
|
+
callbacks: appendCallbacks(config.callbacks, [
|
|
183
|
+
createModelStartHandler({ config, mode, provider, captureRunId }),
|
|
184
|
+
]),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
109
188
|
function getManualToolStreamNormalizationProvider(
|
|
110
189
|
provider: t.ProviderName
|
|
111
190
|
): t.ProviderName {
|
|
@@ -632,6 +711,24 @@ async function attemptInvokeBody(
|
|
|
632
711
|
config: RunnableConfig
|
|
633
712
|
): Promise<Partial<t.BaseGraphState>> {
|
|
634
713
|
const { model, messages: messagesForProvider, provider } = request;
|
|
714
|
+
const projectionInvariantMode =
|
|
715
|
+
resolveProviderMessageProjectionInvariantMode();
|
|
716
|
+
let sealedRunId: string | undefined;
|
|
717
|
+
let invocationConfig = config;
|
|
718
|
+
const captureModelRunId =
|
|
719
|
+
model.stream != null && context?.preemption != null;
|
|
720
|
+
if (projectionInvariantMode !== 'off' || captureModelRunId) {
|
|
721
|
+
invocationConfig = withModelStartHandler({
|
|
722
|
+
config,
|
|
723
|
+
mode: projectionInvariantMode,
|
|
724
|
+
provider,
|
|
725
|
+
captureRunId: captureModelRunId
|
|
726
|
+
? (runId: string): void => {
|
|
727
|
+
sealedRunId ??= runId;
|
|
728
|
+
}
|
|
729
|
+
: undefined,
|
|
730
|
+
});
|
|
731
|
+
}
|
|
635
732
|
|
|
636
733
|
/**
|
|
637
734
|
* Stamp the provider that is ACTUALLY serving this invocation onto the
|
|
@@ -647,28 +744,10 @@ async function attemptInvokeBody(
|
|
|
647
744
|
* Observed, not dictated. `handleChatModelStart` fires with the chat
|
|
648
745
|
* model's real run id before the first chunk, which is the only way to
|
|
649
746
|
* name the run a seal has to close — pinning `config.runId` does not
|
|
650
|
-
* survive the bound runnable.
|
|
651
|
-
*
|
|
747
|
+
* survive the bound runnable. The same handler owns the opt-in projection
|
|
748
|
+
* invariant so enabled diagnostics do not stack a second model callback.
|
|
652
749
|
*/
|
|
653
|
-
|
|
654
|
-
const streamConfig =
|
|
655
|
-
context?.preemption == null
|
|
656
|
-
? config
|
|
657
|
-
: {
|
|
658
|
-
...config,
|
|
659
|
-
callbacks: appendCallbacks(config.callbacks, [
|
|
660
|
-
{
|
|
661
|
-
handleChatModelStart: (
|
|
662
|
-
_llm: Serialized,
|
|
663
|
-
_messages: BaseMessage[][],
|
|
664
|
-
runId: string
|
|
665
|
-
): void => {
|
|
666
|
-
sealedRunId ??= runId;
|
|
667
|
-
},
|
|
668
|
-
},
|
|
669
|
-
]),
|
|
670
|
-
};
|
|
671
|
-
const stream = await model.stream(messagesForProvider, streamConfig);
|
|
750
|
+
const stream = await model.stream(messagesForProvider, invocationConfig);
|
|
672
751
|
let finalChunk: AIMessageChunk | undefined;
|
|
673
752
|
let preempted = false;
|
|
674
753
|
const registeredStreamHandler =
|
|
@@ -855,7 +934,7 @@ async function attemptInvokeBody(
|
|
|
855
934
|
return { messages: [finalChunk as AIMessageChunk] };
|
|
856
935
|
}
|
|
857
936
|
|
|
858
|
-
const finalMessage = await model.invoke(messagesForProvider,
|
|
937
|
+
const finalMessage = await model.invoke(messagesForProvider, invocationConfig);
|
|
859
938
|
if ((finalMessage.tool_calls?.length ?? 0) > 0) {
|
|
860
939
|
finalMessage.tool_calls = finalMessage.tool_calls?.filter(
|
|
861
940
|
(tool_call: ToolCall) => !!tool_call.name
|
package/src/messages/content.ts
CHANGED
|
@@ -22,6 +22,20 @@ export const isLegacyConvertible = (message: BaseMessage): boolean => {
|
|
|
22
22
|
return message.content.every((block) => block.type === ContentTypes.TEXT);
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
/** Joins the text of {@link isLegacyConvertible} content blocks into the exact
|
|
26
|
+
* string {@link formatContentStrings} has always produced for them. */
|
|
27
|
+
export const flattenLegacyContent = (
|
|
28
|
+
blocks: MessageContentComplex[]
|
|
29
|
+
): string => {
|
|
30
|
+
const content = blocks.reduce((acc, curr) => {
|
|
31
|
+
if (curr.type === ContentTypes.TEXT) {
|
|
32
|
+
return `${acc}${curr[ContentTypes.TEXT] ?? ''}\n`;
|
|
33
|
+
}
|
|
34
|
+
return acc;
|
|
35
|
+
}, '');
|
|
36
|
+
return content.trim();
|
|
37
|
+
};
|
|
38
|
+
|
|
25
39
|
/**
|
|
26
40
|
* Formats an array of messages for LangChain, making sure all content fields are strings
|
|
27
41
|
* @param {Array<HumanMessage | AIMessage | SystemMessage | ToolMessage>} payload - The array of messages to format.
|
|
@@ -39,16 +53,12 @@ export const formatContentStrings = (
|
|
|
39
53
|
continue;
|
|
40
54
|
}
|
|
41
55
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return acc;
|
|
49
|
-
}, '');
|
|
50
|
-
|
|
51
|
-
result.push(cloneMessage(message, content.trim()));
|
|
56
|
+
result.push(
|
|
57
|
+
cloneMessage(
|
|
58
|
+
message,
|
|
59
|
+
flattenLegacyContent(message.content as MessageContentComplex[])
|
|
60
|
+
)
|
|
61
|
+
);
|
|
52
62
|
}
|
|
53
63
|
|
|
54
64
|
return result;
|
package/src/messages/format.ts
CHANGED
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
hasBijectiveProviderContentPartMapping,
|
|
47
47
|
inspectProviderMessageProvenance,
|
|
48
48
|
inspectProviderSourceMessageIds,
|
|
49
|
+
setFreshProviderMessageProvenance,
|
|
49
50
|
setInvalidProviderMessageProvenance,
|
|
50
51
|
setProviderMessageProvenance,
|
|
51
52
|
} from './provenance';
|
|
@@ -57,6 +58,7 @@ import {
|
|
|
57
58
|
} from '@/utils/toolContent';
|
|
58
59
|
import { normalizeAnthropicToolCallId } from '@/llm/anthropic/utils/message_inputs';
|
|
59
60
|
import { toLangChainContent, toLangChainMessageFields } from './langchain';
|
|
61
|
+
import { flattenLegacyContent, isLegacyConvertible } from './content';
|
|
60
62
|
import { HARD_MAX_TOOL_RESULT_CHARS } from '@/utils/truncation';
|
|
61
63
|
import { Providers, ContentTypes, Constants } from '@/common';
|
|
62
64
|
import { emitAgentLog } from '@/utils/events';
|
|
@@ -378,6 +380,12 @@ type SourceContentPartIndices = number | readonly number[];
|
|
|
378
380
|
|
|
379
381
|
interface FormatAgentMessagesOptions {
|
|
380
382
|
provider?: ProviderName;
|
|
383
|
+
/** Emit flattenable text content as the joined string the legacy-content
|
|
384
|
+
* projection would produce, so the per-request `formatContentStrings` pass
|
|
385
|
+
* finds nothing to convert and every history message keeps its identity —
|
|
386
|
+
* which is what lets exact-count reuse skip re-tokenizing it. Set this if
|
|
387
|
+
* and only if the run's provider uses legacy string content. */
|
|
388
|
+
legacyContent?: boolean;
|
|
381
389
|
/** Reconstruct hidden `reasoning_content` from `THINK` parts onto prior
|
|
382
390
|
* tool-call messages. Explicit opt-in for OpenAI-compatible endpoints that
|
|
383
391
|
* replay reasoning across turns; defaults to on for DeepSeek thinking-mode. */
|
|
@@ -1380,7 +1388,7 @@ function stampSourceMessageIdentity(
|
|
|
1380
1388
|
},
|
|
1381
1389
|
];
|
|
1382
1390
|
}
|
|
1383
|
-
|
|
1391
|
+
setFreshProviderMessageProvenance(message, partsToStamp);
|
|
1384
1392
|
if (sourceMessageId == null || derivedIndex !== 0) {
|
|
1385
1393
|
return;
|
|
1386
1394
|
}
|
|
@@ -1996,6 +2004,22 @@ export const formatAgentMessages = (
|
|
|
1996
2004
|
* assistant turn. Held rather than emitted so an entry that produces
|
|
1997
2005
|
* nothing cannot leave the anchor stranded as the final turn.
|
|
1998
2006
|
*/
|
|
2007
|
+
const legacyContentEnabled = options?.legacyContent === true;
|
|
2008
|
+
/** Emission choke point: every formatted message enters the result here, so
|
|
2009
|
+
* the legacy flatten happens once per message with no closing rescan. The
|
|
2010
|
+
* summary boundary slices payload entries before formatting, and nothing
|
|
2011
|
+
* mutates an emitted message's content afterwards, so flattening at
|
|
2012
|
+
* emission and flattening at return are equivalent. */
|
|
2013
|
+
const emitFormattedMessage = (message: (typeof messages)[number]): void => {
|
|
2014
|
+
if (legacyContentEnabled && isLegacyConvertible(message)) {
|
|
2015
|
+
const flattened = flattenLegacyContent(
|
|
2016
|
+
message.content as MessageContentComplex[]
|
|
2017
|
+
);
|
|
2018
|
+
message.content = flattened;
|
|
2019
|
+
message.lc_kwargs.content = flattened;
|
|
2020
|
+
}
|
|
2021
|
+
messages.push(message);
|
|
2022
|
+
};
|
|
1999
2023
|
let pendingSteerAnchor = false;
|
|
2000
2024
|
/**
|
|
2001
2025
|
* Emits the deferred anchor ahead of `next` — the message about to be
|
|
@@ -2018,7 +2042,7 @@ export const formatAgentMessages = (
|
|
|
2018
2042
|
'assistant'
|
|
2019
2043
|
);
|
|
2020
2044
|
stampSourceMessageIdentity(anchor, undefined, 0, 'synthetic');
|
|
2021
|
-
|
|
2045
|
+
emitFormattedMessage(anchor);
|
|
2022
2046
|
};
|
|
2023
2047
|
// If indexTokenCountMap is provided, create a new map to track the updated indices
|
|
2024
2048
|
const updatedIndexTokenCountMap: Record<number, number> = {};
|
|
@@ -2098,7 +2122,7 @@ export const formatAgentMessages = (
|
|
|
2098
2122
|
provenanceParts
|
|
2099
2123
|
);
|
|
2100
2124
|
flushSteerAnchor(formattedMessage);
|
|
2101
|
-
|
|
2125
|
+
emitFormattedMessage(formattedMessage);
|
|
2102
2126
|
|
|
2103
2127
|
// Update the index mapping for this message
|
|
2104
2128
|
indexMapping[i] = [messages.length - 1];
|
|
@@ -2359,7 +2383,7 @@ export const formatAgentMessages = (
|
|
|
2359
2383
|
flushSteerAnchor(formattedMessages[0]);
|
|
2360
2384
|
}
|
|
2361
2385
|
for (const formattedMessage of formattedMessages) {
|
|
2362
|
-
|
|
2386
|
+
emitFormattedMessage(formattedMessage);
|
|
2363
2387
|
}
|
|
2364
2388
|
if (endsWithSteerMessage(formattedMessages)) {
|
|
2365
2389
|
pendingSteerAnchor = true;
|
|
@@ -2390,7 +2414,7 @@ export const formatAgentMessages = (
|
|
|
2390
2414
|
'user'
|
|
2391
2415
|
);
|
|
2392
2416
|
stampSourceMessageIdentity(skillMessage, undefined, 0, 'synthetic');
|
|
2393
|
-
|
|
2417
|
+
emitFormattedMessage(skillMessage);
|
|
2394
2418
|
}
|
|
2395
2419
|
}
|
|
2396
2420
|
}
|
package/src/messages/index.ts
CHANGED