@librechat/agents 3.7.1 → 3.7.3

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.
@@ -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/index.ts CHANGED
@@ -43,6 +43,9 @@ export * from './hooks';
43
43
  /* Programmatic sessions */
44
44
  export * from './session';
45
45
 
46
+ /* Event actors */
47
+ export * from './eventActor';
48
+
46
49
  /* HITL helpers */
47
50
  export * from './hitl';
48
51
 
@@ -40,6 +40,7 @@ config();
40
40
 
41
41
  const DEFAULT_MAX_ROUND_TRIPS = 20;
42
42
  const DEFAULT_RUN_TIMEOUT_MS = resolveCodeApiRunTimeoutMs();
43
+ const BASH_LAST_BACKGROUND_PID_GUARD = ': &\nwait "$!"';
43
44
 
44
45
  /** Bash reserved words that get `_tool` suffix when used as function names */
45
46
  const BASH_RESERVED = new Set([
@@ -171,6 +172,14 @@ export const BashProgrammaticToolCallingDefinition = {
171
172
  schema: BashProgrammaticToolCallingSchema,
172
173
  } as const;
173
174
 
175
+ function prepareBashProgrammaticCode(code: string): string {
176
+ /* The Code API's generated Bash wrapper reads `$!` after user code. A user
177
+ * `set -u` makes that expansion fail when no background process has run.
178
+ * Seed and reap a no-op job before user code so strict mode remains active
179
+ * for the payload while the wrapper can safely read its special parameter. */
180
+ return `${BASH_LAST_BACKGROUND_PID_GUARD}\n${code}`;
181
+ }
182
+
174
183
  function maybeParseJsonResultString(result: unknown): unknown {
175
184
  if (typeof result !== 'string') {
176
185
  return result;
@@ -320,6 +329,7 @@ export function createBashProgrammaticToolCallingTool(
320
329
  async (rawParams, config) => {
321
330
  const params = rawParams as ProgrammaticInvocationParams;
322
331
  const { code } = params;
332
+ const preparedCode = prepareBashProgrammaticCode(code);
323
333
  const timeout = clampCodeApiRunTimeoutMs(params.timeout, maxRunTimeoutMs);
324
334
 
325
335
  const toolCall = (config.toolCall ?? {}) as ToolCall &
@@ -417,7 +427,7 @@ export function createBashProgrammaticToolCallingTool(
417
427
  EXEC_ENDPOINT,
418
428
  {
419
429
  lang: 'bash',
420
- code,
430
+ code: preparedCode,
421
431
  tools: effectiveTools,
422
432
  session_id,
423
433
  timeout,