@librechat/agents 3.2.41 → 3.2.43

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.
Files changed (75) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +4 -3
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +5 -2
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +1 -1
  6. package/dist/cjs/llm/bedrock/index.cjs +9 -1
  7. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  8. package/dist/cjs/main.cjs +6 -0
  9. package/dist/cjs/messages/cache.cjs +43 -0
  10. package/dist/cjs/messages/cache.cjs.map +1 -1
  11. package/dist/cjs/session/JsonlSessionStore.cjs +1 -1
  12. package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs +2 -2
  13. package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs.map +1 -1
  14. package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs +118 -22
  15. package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs.map +1 -1
  16. package/dist/cjs/tools/local/CompileCheckTool.cjs +11 -3
  17. package/dist/cjs/tools/local/CompileCheckTool.cjs.map +1 -1
  18. package/dist/cjs/tools/local/FileCheckpointer.cjs +8 -3
  19. package/dist/cjs/tools/local/FileCheckpointer.cjs.map +1 -1
  20. package/dist/cjs/tools/local/LocalCodingTools.cjs +26 -7
  21. package/dist/cjs/tools/local/LocalCodingTools.cjs.map +1 -1
  22. package/dist/cjs/tools/local/LocalExecutionEngine.cjs +2 -2
  23. package/dist/cjs/tools/local/syntaxCheck.cjs +6 -2
  24. package/dist/cjs/tools/local/syntaxCheck.cjs.map +1 -1
  25. package/dist/cjs/tools/local/workspaceFS.cjs +20 -0
  26. package/dist/cjs/tools/local/workspaceFS.cjs.map +1 -1
  27. package/dist/esm/agents/AgentContext.mjs +5 -4
  28. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  29. package/dist/esm/graphs/Graph.mjs +6 -3
  30. package/dist/esm/graphs/Graph.mjs.map +1 -1
  31. package/dist/esm/hooks/createWorkspacePolicyHook.mjs +1 -1
  32. package/dist/esm/llm/bedrock/index.mjs +10 -2
  33. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  34. package/dist/esm/main.mjs +3 -3
  35. package/dist/esm/messages/cache.mjs +42 -1
  36. package/dist/esm/messages/cache.mjs.map +1 -1
  37. package/dist/esm/session/JsonlSessionStore.mjs +1 -1
  38. package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs +3 -3
  39. package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs.map +1 -1
  40. package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs +115 -23
  41. package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs.map +1 -1
  42. package/dist/esm/tools/local/CompileCheckTool.mjs +11 -3
  43. package/dist/esm/tools/local/CompileCheckTool.mjs.map +1 -1
  44. package/dist/esm/tools/local/FileCheckpointer.mjs +9 -4
  45. package/dist/esm/tools/local/FileCheckpointer.mjs.map +1 -1
  46. package/dist/esm/tools/local/LocalCodingTools.mjs +26 -7
  47. package/dist/esm/tools/local/LocalCodingTools.mjs.map +1 -1
  48. package/dist/esm/tools/local/LocalExecutionEngine.mjs +2 -2
  49. package/dist/esm/tools/local/syntaxCheck.mjs +6 -2
  50. package/dist/esm/tools/local/syntaxCheck.mjs.map +1 -1
  51. package/dist/esm/tools/local/workspaceFS.mjs +19 -1
  52. package/dist/esm/tools/local/workspaceFS.mjs.map +1 -1
  53. package/dist/types/agents/AgentContext.d.ts +3 -2
  54. package/dist/types/llm/bedrock/index.d.ts +7 -0
  55. package/dist/types/messages/cache.d.ts +27 -0
  56. package/dist/types/tools/cloudflare/CloudflareSandboxExecutionEngine.d.ts +53 -0
  57. package/dist/types/tools/local/workspaceFS.d.ts +13 -0
  58. package/package.json +1 -1
  59. package/src/agents/AgentContext.ts +10 -3
  60. package/src/graphs/Graph.ts +24 -6
  61. package/src/llm/bedrock/index.ts +19 -3
  62. package/src/llm/bedrock/llm.spec.ts +97 -0
  63. package/src/messages/cache.test.ts +63 -0
  64. package/src/messages/cache.ts +51 -0
  65. package/src/specs/vllm-reasoning-toolcalls.test.ts +340 -0
  66. package/src/tools/__tests__/CloudflareSandboxExecution.test.ts +323 -0
  67. package/src/tools/__tests__/LocalExecutionTools.test.ts +54 -0
  68. package/src/tools/cloudflare/CloudflareProgrammaticToolCalling.ts +7 -2
  69. package/src/tools/cloudflare/CloudflareSandboxExecutionEngine.ts +269 -37
  70. package/src/tools/local/CompileCheckTool.ts +19 -3
  71. package/src/tools/local/FileCheckpointer.ts +20 -4
  72. package/src/tools/local/LocalCodingTools.ts +61 -8
  73. package/src/tools/local/__tests__/FileCheckpointer.test.ts +42 -0
  74. package/src/tools/local/syntaxCheck.ts +14 -2
  75. package/src/tools/local/workspaceFS.ts +27 -0
@@ -3,6 +3,7 @@ import { createTwoFilesPatch } from 'diff';
3
3
  import { tool } from '@langchain/core/tools';
4
4
  import type { DynamicStructuredTool } from '@langchain/core/tools';
5
5
  import type * as t from '@/types';
6
+ import { isWorkspaceClientTimeoutError } from './workspaceFS';
6
7
  import {
7
8
  createLocalBashProgrammaticToolCallingTool,
8
9
  createLocalProgrammaticToolCallingTool,
@@ -294,8 +295,14 @@ async function revertStrictWrite(
294
295
  } else {
295
296
  await fs.unlink(path);
296
297
  }
297
- } catch {
298
- /* best-effort: caller still sees the original syntax error */
298
+ } catch (error) {
299
+ // A timed-out revert leaves the rejected write on disk — the caller would
300
+ // otherwise claim "reverted to pre-write state" while the bad bytes remain.
301
+ // Surface it; for other failures stay best-effort (caller sees the original
302
+ // syntax error).
303
+ if (isWorkspaceClientTimeoutError(error)) {
304
+ throw error;
305
+ }
299
306
  }
300
307
  }
301
308
 
@@ -538,7 +545,12 @@ export function createLocalWriteFileTool(
538
545
  before = decoded.text;
539
546
  encoding = decoded;
540
547
  existed = true;
541
- } catch {
548
+ } catch (error) {
549
+ // A stalled-RPC timeout is NOT "file absent" — treating it as such would
550
+ // overwrite an existing file with fresh content. Surface it instead.
551
+ if (isWorkspaceClientTimeoutError(error)) {
552
+ throw error;
553
+ }
542
554
  existed = false;
543
555
  }
544
556
 
@@ -546,7 +558,21 @@ export function createLocalWriteFileTool(
546
558
  const finalText = encodeFile(input.content, encoding);
547
559
  await fs.writeFile(path, finalText, 'utf8');
548
560
 
549
- const syntax = await maybeRunSyntaxCheck(path, config);
561
+ let syntax;
562
+ try {
563
+ syntax = await maybeRunSyntaxCheck(path, config);
564
+ } catch (error) {
565
+ // A validation-read timeout must still honor strict mode's revert
566
+ // contract: restore the pre-write state before surfacing (best-effort —
567
+ // the revert may itself time out on the same stalled container).
568
+ if (
569
+ isWorkspaceClientTimeoutError(error) &&
570
+ config.postEditSyntaxCheck === 'strict'
571
+ ) {
572
+ await revertStrictWrite(fs, path, existed, before, encoding);
573
+ }
574
+ throw error;
575
+ }
550
576
 
551
577
  const diff = existed
552
578
  ? summariseDiff(path, before, input.content)
@@ -642,7 +668,20 @@ export function createLocalEditFileTool(
642
668
  const finalText = encodeFile(next, encoding);
643
669
  await fs.writeFile(path, finalText, 'utf8');
644
670
 
645
- const syntax = await maybeRunSyntaxCheck(path, config);
671
+ let syntax;
672
+ try {
673
+ syntax = await maybeRunSyntaxCheck(path, config);
674
+ } catch (error) {
675
+ // As in write_file: a validation-read timeout still triggers strict
676
+ // mode's revert (edit_file always operates on an existing file).
677
+ if (
678
+ isWorkspaceClientTimeoutError(error) &&
679
+ config.postEditSyntaxCheck === 'strict'
680
+ ) {
681
+ await revertStrictWrite(fs, path, true, original, encoding);
682
+ }
683
+ throw error;
684
+ }
646
685
 
647
686
  const diff = summariseDiff(path, original, next);
648
687
  const fuzzy = strategiesUsed.some((s) => s !== 'exact');
@@ -826,7 +865,12 @@ async function* walkFiles(
826
865
  let entries;
827
866
  try {
828
867
  entries = await fs.readdir(dir, { withFileTypes: true });
829
- } catch {
868
+ } catch (error) {
869
+ // A stalled-RPC timeout must surface, not be silently skipped as an
870
+ // unreadable directory — that would corrupt search results ("no matches").
871
+ if (isWorkspaceClientTimeoutError(error)) {
872
+ throw error;
873
+ }
830
874
  continue;
831
875
  }
832
876
  for (const entry of entries) {
@@ -1009,7 +1053,12 @@ async function fallbackGrep(
1009
1053
  let stat;
1010
1054
  try {
1011
1055
  stat = await fs.stat(file);
1012
- } catch {
1056
+ } catch (error) {
1057
+ // A stalled-RPC timeout must surface, not be skipped — skipping a file can
1058
+ // report "no matches" even when the (unreadable-due-to-stall) file matched.
1059
+ if (isWorkspaceClientTimeoutError(error)) {
1060
+ throw error;
1061
+ }
1013
1062
  continue;
1014
1063
  }
1015
1064
  if (stat.size > FALLBACK_GREP_MAX_FILE_BYTES) {
@@ -1021,7 +1070,11 @@ async function fallbackGrep(
1021
1070
  let content;
1022
1071
  try {
1023
1072
  content = await fs.readFile(file, 'utf8');
1024
- } catch {
1073
+ } catch (error) {
1074
+ // As above: a client-timeout means a stalled read, not an unreadable file.
1075
+ if (isWorkspaceClientTimeoutError(error)) {
1076
+ throw error;
1077
+ }
1025
1078
  continue;
1026
1079
  }
1027
1080
  if (content.includes('\0')) {
@@ -3,6 +3,10 @@ import { join } from 'path';
3
3
  import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from 'fs/promises';
4
4
  import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
5
5
  import { LocalFileCheckpointerImpl } from '../FileCheckpointer';
6
+ import {
7
+ WorkspaceClientTimeoutError,
8
+ type WorkspaceFS,
9
+ } from '../workspaceFS';
6
10
 
7
11
  /**
8
12
  * Pins the LocalFileCheckpointer's per-Run snapshot/rewind contract.
@@ -63,6 +67,44 @@ describe('LocalFileCheckpointerImpl', () => {
63
67
  await expect(stat(file)).rejects.toThrow();
64
68
  });
65
69
 
70
+ it('rethrows a client-timeout from stat instead of snapshotting as absent', async () => {
71
+ // A stalled stat() must NOT be recorded as "absent" — that would delete an
72
+ // existing file on rewind. The timeout has to surface.
73
+ const fs = {
74
+ stat: async () => {
75
+ throw new WorkspaceClientTimeoutError(
76
+ 'cloudflare sandbox listFiles exceeded 6000ms client-side timeout'
77
+ );
78
+ },
79
+ } as unknown as WorkspaceFS;
80
+ const cp = new LocalFileCheckpointerImpl(32 * 1024 * 1024, fs);
81
+
82
+ await expect(cp.captureBeforeWrite('/workspace/a.txt')).rejects.toThrow(
83
+ /client-side timeout/
84
+ );
85
+ // Nothing was snapshotted, so rewind is a no-op (no spurious deletion).
86
+ expect(await cp.rewind()).toBe(0);
87
+ });
88
+
89
+ it('rewind rethrows a client-timeout from a restore write instead of claiming success', async () => {
90
+ // A stalled restore write leaves the bad bytes on disk — rewind must surface
91
+ // the timeout, not silently count the path as restored.
92
+ const fs = {
93
+ stat: async () => ({ isFile: () => true, size: 5 }),
94
+ readFile: async () => Buffer.from('orig\n'),
95
+ mkdir: async () => undefined,
96
+ writeFile: async () => {
97
+ throw new WorkspaceClientTimeoutError(
98
+ 'cloudflare sandbox writeFile exceeded 6000ms client-side timeout'
99
+ );
100
+ },
101
+ } as unknown as WorkspaceFS;
102
+ const cp = new LocalFileCheckpointerImpl(32 * 1024 * 1024, fs);
103
+
104
+ await cp.captureBeforeWrite('/workspace/a.txt'); // snapshots 'present'
105
+ await expect(cp.rewind()).rejects.toThrow(/client-side timeout/);
106
+ });
107
+
66
108
  it('rewinds across multiple files in a single pass', async () => {
67
109
  const a = join(dir, 'multi-a.txt');
68
110
  const b = join(dir, 'multi-b.txt');
@@ -18,6 +18,7 @@
18
18
 
19
19
  import { extname } from 'path';
20
20
  import type * as t from '@/types';
21
+ import { isWorkspaceClientTimeoutError } from './workspaceFS';
21
22
  import {
22
23
  getSpawn,
23
24
  getWorkspaceFS,
@@ -168,7 +169,13 @@ const jsonCheck: SyntaxChecker = async (path, config) => {
168
169
  // file → catch returns undefined → ok: true) or read a different
169
170
  // file with the same absolute path. Codex P1 #24.
170
171
  const fs = getWorkspaceFS(config);
171
- const raw = await fs.readFile(path, 'utf8').catch(() => undefined);
172
+ const raw = await fs.readFile(path, 'utf8').catch((error) => {
173
+ // A stalled read must not pass the syntax gate as ok:true — surface it.
174
+ if (isWorkspaceClientTimeoutError(error)) {
175
+ throw error;
176
+ }
177
+ return undefined;
178
+ });
172
179
  if (raw == null) return { ok: true };
173
180
  try {
174
181
  JSON.parse(raw);
@@ -237,7 +244,12 @@ export async function runPostEditSyntaxCheck(
237
244
  };
238
245
  }
239
246
  return result;
240
- } catch {
247
+ } catch (error) {
248
+ // Don't swallow a stalled-RPC timeout as "no checker outcome" (which would
249
+ // let the write through without a real syntax gate) — surface it.
250
+ if (isWorkspaceClientTimeoutError(error)) {
251
+ throw error;
252
+ }
241
253
  return null;
242
254
  }
243
255
  }
@@ -39,6 +39,33 @@ export type ReaddirEntry = {
39
39
  isSymbolicLink(): boolean;
40
40
  };
41
41
 
42
+ /**
43
+ * Thrown when a {@link WorkspaceFS} operation is abandoned by a client-side
44
+ * backstop timeout (a stalled remote/sandbox RPC that never returned). This is
45
+ * DISTINCT from "file not found": callers that catch generic FS failures as
46
+ * absence — e.g. a pre-read before an overwrite, or a `stat` before snapshotting
47
+ * — MUST rethrow this so a stalled read isn't mistaken for a missing file (which
48
+ * would otherwise overwrite an existing file or snapshot it as absent).
49
+ */
50
+ export class WorkspaceClientTimeoutError extends Error {
51
+ readonly code = 'WORKSPACE_CLIENT_TIMEOUT';
52
+ constructor(message: string) {
53
+ super(message);
54
+ this.name = 'WorkspaceClientTimeoutError';
55
+ }
56
+ }
57
+
58
+ export function isWorkspaceClientTimeoutError(
59
+ error: unknown
60
+ ): error is WorkspaceClientTimeoutError {
61
+ return (
62
+ error instanceof WorkspaceClientTimeoutError ||
63
+ (typeof error === 'object' &&
64
+ error !== null &&
65
+ (error as { code?: unknown }).code === 'WORKSPACE_CLIENT_TIMEOUT')
66
+ );
67
+ }
68
+
42
69
  export interface WorkspaceFS {
43
70
  readFile(path: string, encoding: 'utf8'): Promise<string>;
44
71
  readFile(path: string): Promise<Buffer>;