@librechat/agents 3.7.5 → 3.7.7

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.
@@ -5,7 +5,15 @@ export {
5
5
  export type {
6
6
  EventActorAdapterPrepareRequest,
7
7
  EventActorAdapterPreparation,
8
+ EventActorAdapterInvocationResult,
9
+ EventActorAdapterResumeRequest,
10
+ EventActorAdapterResumeResult,
11
+ EventActorAdapterSuspendedResult,
8
12
  EventActorAppliedResult,
13
+ EventActorAdapterCancelSuspensionRequest,
14
+ EventActorAdapterCancelSuspensionResult,
15
+ EventActorCancelSuspensionRequest,
16
+ EventActorCancelSuspensionResult,
9
17
  EventActorCheckpointFork,
10
18
  EventActorCheckpointReference,
11
19
  EventActorCommitRequest,
@@ -21,12 +29,21 @@ export type {
21
29
  EventActorInvocation,
22
30
  EventActorInvocationContext,
23
31
  EventActorIndeterminateResult,
32
+ EventActorInterrupt,
24
33
  EventActorInvocationReference,
25
34
  EventActorInvocationResult,
26
35
  EventActorPreparation,
27
36
  EventActorPreparationContext,
28
37
  EventActorPreparedInvocation,
29
38
  EventActorPrepareRequest,
39
+ EventActorResumeRequest,
40
+ EventActorSettleSuspensionRequest,
41
+ EventActorSettleSuspensionResult,
42
+ EventActorSettlementAuthority,
30
43
  EventActorSettlementResult,
44
+ EventActorSuspendRequest,
45
+ EventActorSuspendResult,
46
+ EventActorSuspendedResult,
47
+ EventActorSuspension,
31
48
  EventActorTerminalResult,
32
49
  } from './types';
@@ -74,14 +74,156 @@ export type EventActorTerminalResult<TResult extends EventActorEvent> =
74
74
  }
75
75
  | { status: 'completed_no_action'; result?: TResult };
76
76
 
77
+ /** JSON-safe interrupt descriptor retained with a suspended invocation fork. */
78
+ export interface EventActorInterrupt<
79
+ TPayload extends EventActorEvent = EventActorEvent,
80
+ > {
81
+ id: string;
82
+ payload: TPayload;
83
+ }
84
+
85
+ /**
86
+ * Nonterminal adapter outcome. The checkpoint must already contain the pause;
87
+ * the SDK publishes its authority through `suspend` before exposing it.
88
+ */
89
+ export interface EventActorAdapterSuspendedResult<
90
+ TPayload extends EventActorEvent = EventActorEvent,
91
+ > {
92
+ status: 'suspended';
93
+ checkpoint: EventActorCheckpointFork;
94
+ interrupt: EventActorInterrupt<TPayload>;
95
+ }
96
+
97
+ export type EventActorAdapterInvocationResult<
98
+ TResult extends EventActorEvent,
99
+ TPayload extends EventActorEvent = EventActorEvent,
100
+ > =
101
+ | EventActorTerminalResult<TResult>
102
+ | EventActorAdapterSuspendedResult<TPayload>;
103
+
104
+ /**
105
+ * Authenticated, versioned, JSON-safe evidence for one nonterminal invocation.
106
+ * Integrity does not make this evidence one-shot; the host's durable current
107
+ * suspension record is the replay and ownership fence. Hosts must capability-
108
+ * route each exact version during rolling deploys and drain or migrate current
109
+ * evidence before rotating away from its signing key.
110
+ */
111
+ export interface EventActorSuspension<
112
+ TPayload extends EventActorEvent = EventActorEvent,
113
+ > {
114
+ version: 1;
115
+ suspensionId: string;
116
+ attempt: number;
117
+ issuedAt: number;
118
+ expiresAt: number;
119
+ invocation: EventActorInvocationReference;
120
+ checkpoint: EventActorCheckpointFork;
121
+ interrupt: EventActorInterrupt<TPayload>;
122
+ suspensionDigest: string;
123
+ }
124
+
125
+ export interface EventActorSuspendedResult<
126
+ TPayload extends EventActorEvent = EventActorEvent,
127
+ > {
128
+ status: 'suspended';
129
+ suspension: EventActorSuspension<TPayload>;
130
+ }
131
+
132
+ export interface EventActorSuspendRequest {
133
+ suspension: EventActorSuspension;
134
+ /** CAS predecessor required when a claimed resume pauses again. */
135
+ previous?: {
136
+ suspensionId: string;
137
+ attempt: number;
138
+ resumeAttemptId: string;
139
+ };
140
+ }
141
+
142
+ export type EventActorSuspendResult =
143
+ | { status: 'stored' }
144
+ | { status: 'stale' };
145
+
146
+ export interface EventActorAdapterResumeRequest {
147
+ suspension: EventActorSuspension;
148
+ resumeAttemptId: string;
149
+ value: EventActorEvent;
150
+ }
151
+
152
+ export type EventActorAdapterResumeResult<TResult extends EventActorEvent> =
153
+ | {
154
+ status: 'claimed';
155
+ result: EventActorAdapterInvocationResult<TResult>;
156
+ }
157
+ | {
158
+ /** The host proved no action before returning this claimed failure. */
159
+ status: 'claimed_failed';
160
+ error: Error;
161
+ }
162
+ | { status: 'stale' };
163
+
164
+ export interface EventActorResumeRequest {
165
+ suspension: EventActorSuspension;
166
+ resumeAttemptId: string;
167
+ value: EventActorEvent;
168
+ signal?: AbortSignal;
169
+ }
170
+
171
+ export interface EventActorCancelSuspensionRequest {
172
+ suspension: EventActorSuspension;
173
+ cancelAttemptId: string;
174
+ reason?: 'cancelled' | 'expired';
175
+ signal?: AbortSignal;
176
+ }
177
+
178
+ export interface EventActorAdapterCancelSuspensionRequest {
179
+ suspension: EventActorSuspension;
180
+ cancelAttemptId: string;
181
+ reason: 'cancelled' | 'expired';
182
+ }
183
+
184
+ export type EventActorCancelSuspensionResult =
185
+ | { status: 'cancelled' }
186
+ | EventActorIndeterminateResult<EventActorEvent>;
187
+
188
+ export type EventActorAdapterCancelSuspensionResult =
189
+ | { status: 'cancelled' }
190
+ | { status: 'stale' };
191
+
192
+ export interface EventActorSettleSuspensionRequest {
193
+ suspensionId: string;
194
+ attempt: number;
195
+ resumeAttemptId: string;
196
+ status: 'completed_no_action' | 'failed';
197
+ }
198
+
199
+ export type EventActorSettleSuspensionResult =
200
+ | { status: 'settled' }
201
+ | { status: 'stale' };
202
+
77
203
  export type EventActorAppliedResult<TResult extends EventActorEvent> = Extract<
78
204
  EventActorTerminalResult<TResult>,
79
205
  { status: 'applied' }
80
206
  > & {
81
- /** Executor-issued one-shot settlement for the invocation that produced this action. */
207
+ /** Executor-issued settlement for the invocation that produced this action. */
82
208
  invocation: EventActorInvocationReference;
209
+ /** Authenticated cross-executor authority for a resumed terminal action. */
210
+ settlementAuthority?: EventActorSettlementAuthority;
83
211
  };
84
212
 
213
+ /**
214
+ * Authenticated fence that binds a resumed terminal result to its claimed
215
+ * suspension. The host must consume it atomically with the actor-head CAS.
216
+ */
217
+ export interface EventActorSettlementAuthority {
218
+ version: 1;
219
+ suspensionId: string;
220
+ attempt: number;
221
+ resumeAttemptId: string;
222
+ issuedAt: number;
223
+ expiresAt: number;
224
+ settlementDigest: string;
225
+ }
226
+
85
227
  export interface EventActorIndeterminateResult<
86
228
  TResult extends EventActorEvent,
87
229
  > {
@@ -95,6 +237,7 @@ export interface EventActorIndeterminateResult<
95
237
  export type EventActorInvocationResult<TResult extends EventActorEvent> =
96
238
  | EventActorAppliedResult<TResult>
97
239
  | EventActorIndeterminateResult<TResult>
240
+ | EventActorSuspendedResult
98
241
  | Extract<
99
242
  EventActorTerminalResult<TResult>,
100
243
  { status: 'completed_no_action' }
@@ -128,6 +271,8 @@ export interface EventActorCommitRequest<TResult extends EventActorEvent> {
128
271
  expectedHead: EventActorHead;
129
272
  checkpoint: EventActorCheckpointFork;
130
273
  result: TResult;
274
+ /** Host must consume this suspension fence atomically with the head CAS. */
275
+ settlementAuthority?: EventActorSettlementAuthority;
131
276
  retention: {
132
277
  committedCheckpoints: 2;
133
278
  dormantCheckpointTtlMs: number;
@@ -156,7 +301,11 @@ export interface EventActorDiscardRequest {
156
301
  /**
157
302
  * Host adapter for durable actor state and the concrete agent invocation.
158
303
  * `commit` must compare both the expected generation and checkpoint identity
159
- * atomically before advancing the logical actor head. The host mailbox
304
+ * atomically before advancing the logical actor head. When settlement authority
305
+ * is present, that same transaction must also verify and close the exact
306
+ * suspension/resume-attempt fence, including stale-head outcomes. Retrying an
307
+ * ambiguous acknowledgement must return the durable outcome rather than apply
308
+ * the same terminal transition again. The host mailbox
160
309
  * deduplicates the logical `invocationId` before entering this seam, while each
161
310
  * SDK execution attempt receives a distinct checkpoint namespace. Preparation
162
311
  * methods own rollback until they return a ready invocation and must treat the
@@ -170,6 +319,18 @@ export interface EventActorDiscardRequest {
170
319
  * fork: the SDK retains and surfaces it as `commit_conflict` for host
171
320
  * reconciliation. `discard` must be idempotent for the same invocation because
172
321
  * an ambiguous cleanup failure can be retried through the public lifecycle.
322
+ *
323
+ * Suspension-capable hosts implement all optional suspension methods. `suspend`
324
+ * publishes an initial suspension only while its logical invocation is current;
325
+ * with `previous`, it atomically replaces only the exact claimed predecessor.
326
+ * `resume` atomically claims the current suspension before applying its value to
327
+ * the declared interrupt and rejects duplicate or competing claims as `stale`.
328
+ * It must return `claimed_failed` only when it can prove no qualifying action;
329
+ * any failure after an action returns `applied`. `settleSuspension` atomically
330
+ * discards the claimed fork and closes its fence for definite no-action
331
+ * outcomes. `cancelSuspension`
332
+ * atomically claims, discards, and closes current state, including expired
333
+ * evidence; expiration alone never implies a safe action outcome.
173
334
  */
174
335
  export interface EventActorHostAdapter<
175
336
  TEvent extends EventActorEvent,
@@ -187,7 +348,20 @@ export interface EventActorHostAdapter<
187
348
  invoke(
188
349
  invocation: EventActorInvocation<TEvent>,
189
350
  context: EventActorInvocationContext
190
- ): Promise<EventActorTerminalResult<TResult>>;
351
+ ): Promise<EventActorAdapterInvocationResult<TResult>>;
352
+ suspend?(request: EventActorSuspendRequest): Promise<EventActorSuspendResult>;
353
+ resume?(
354
+ request: EventActorAdapterResumeRequest,
355
+ context: EventActorInvocationContext
356
+ ): Promise<EventActorAdapterResumeResult<TResult>>;
357
+ /** Atomically claims, discards, and closes the current suspension. */
358
+ cancelSuspension?(
359
+ request: EventActorAdapterCancelSuspensionRequest,
360
+ context: EventActorPreparationContext
361
+ ): Promise<EventActorAdapterCancelSuspensionResult>;
362
+ settleSuspension?(
363
+ request: EventActorSettleSuspensionRequest
364
+ ): Promise<EventActorSettleSuspensionResult>;
191
365
  commit(
192
366
  request: EventActorCommitRequest<TResult>
193
367
  ): Promise<EventActorCommitResult>;
@@ -219,6 +393,9 @@ export type EventActorExecutionResult<TResult extends EventActorEvent> =
219
393
  status: 'cancelled';
220
394
  continuation: 'warm' | 'cold';
221
395
  }
396
+ | (EventActorSuspendedResult & {
397
+ continuation: 'warm' | 'cold';
398
+ })
222
399
  | {
223
400
  /** The action happened, but another head won the CAS. Reconcile; do not retry. */
224
401
  status: 'commit_conflict';
@@ -247,4 +424,6 @@ export interface EventActorExecutorOptions {
247
424
  dormantCheckpointTtlMs?: number;
248
425
  /** Stable private key of at least 32 bytes for cross-lifetime handoffs. */
249
426
  preparationSigningKey?: string | Uint8Array;
427
+ /** Maximum UTF-8 byte size of canonical suspension evidence. */
428
+ maxSuspensionPayloadBytes?: number;
250
429
  }
@@ -16,6 +16,23 @@ const KEENABLE_DATE_RANGES: Record<DATE_RANGE, string> = {
16
16
  [DATE_RANGE.PAST_YEAR]: '1y',
17
17
  };
18
18
 
19
+ /** Characters of page text kept per result. Keenable returns the whole page on
20
+ * every search result, an order of magnitude more than the other providers here
21
+ * return, which would crowd out the reranker and the LLM output budget. */
22
+ const SNIPPET_MAX_LENGTH = 500;
23
+
24
+ /** Keenable sends both `description` and `snippet` on every result: `snippet`
25
+ * carries the page text and `description` is the page's meta description, which
26
+ * is empty for most pages. Either field can arrive as an empty string, so the
27
+ * pick is on emptiness rather than nullishness: a `??` chain would keep an empty
28
+ * `snippet` over a `description` that does carry text. */
29
+ function toSnippet(result: t.KeenableSearchResult): string {
30
+ const snippet = (result.snippet ?? '').replace(/\s+/g, ' ').trim();
31
+ const description = (result.description ?? '').replace(/\s+/g, ' ').trim();
32
+ const text = snippet !== '' ? snippet : description;
33
+ return text.slice(0, SNIPPET_MAX_LENGTH);
34
+ }
35
+
19
36
  export const createKeenableAPI = (
20
37
  apiKey?: string,
21
38
  apiUrl?: string,
@@ -84,7 +101,7 @@ export const createKeenableAPI = (
84
101
  const organic: t.OrganicResult[] = rawResults.map((result) => ({
85
102
  title: result.title ?? '',
86
103
  link: result.url ?? '',
87
- snippet: result.description ?? result.snippet ?? '',
104
+ snippet: toSnippet(result),
88
105
  date: result.published_at,
89
106
  }));
90
107