Skip to content

Commit e8c7ef0

Browse files
authored
Merge pull request #66 from langchain-ai/dqbd/read-raw-filesystem-backend
feat: add readRaw method to filesystem backend protocol
2 parents dc1fa13 + 73445c2 commit e8c7ef0

File tree

12 files changed

+381
-89
lines changed

12 files changed

+381
-89
lines changed

.changeset/young-papers-thank.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"deepagents": minor
3+
---
4+
5+
Add readRaw method to filesystem backend protocol

examples/backends/store-backend.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,12 @@ export const agent = createDeepAgent({
6565
async function main() {
6666
const threadId = uuidv4();
6767

68+
const message = new HumanMessage(
69+
"Research the latest trends in AI agents for 2025",
70+
);
6871
await agent.invoke(
69-
{
70-
messages: [
71-
new HumanMessage("Research the latest trends in AI agents for 2025"),
72-
],
73-
},
74-
{
75-
recursionLimit: 50,
76-
configurable: { thread_id: threadId },
77-
},
72+
{ messages: [message] },
73+
{ recursionLimit: 50, configurable: { thread_id: threadId } },
7874
);
7975

8076
const threadId2 = uuidv4();

examples/research/research-agent.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ export const agent = createDeepAgent({
202202
model: "claude-sonnet-4-20250514",
203203
temperature: 0,
204204
}),
205+
205206
tools: [internetSearch],
206207
systemPrompt: researchInstructions,
207208
subagents: [critiqueSubAgent, researchSubAgent],

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@
5252
"@changesets/cli": "^2.29.7",
5353
"@eslint/eslintrc": "^3.1.0",
5454
"@eslint/js": "^9.19.0",
55-
"@langchain/langgraph-checkpoint": "^0.0.13",
55+
"@langchain/langgraph-checkpoint": "^1.0.0",
5656
"@langchain/openai": "^1.0.0",
57-
"@langchain/tavily": "^0.1.4",
57+
"@langchain/tavily": "^1.0.0",
5858
"@tsconfig/recommended": "^1.0.10",
5959
"@types/micromatch": "^4.0.10",
6060
"@types/node": "^22.13.5",

pnpm-lock.yaml

Lines changed: 10 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/backends/composite.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import type {
66
BackendProtocol,
77
EditResult,
8+
FileData,
89
FileInfo,
910
GrepMatch,
1011
WriteResult,
@@ -128,6 +129,17 @@ export class CompositeBackend implements BackendProtocol {
128129
return await backend.read(strippedKey, offset, limit);
129130
}
130131

132+
/**
133+
* Read file content as raw FileData.
134+
*
135+
* @param filePath - Absolute file path
136+
* @returns Raw file content as FileData
137+
*/
138+
async readRaw(filePath: string): Promise<FileData> {
139+
const [backend, strippedKey] = this.getBackendAndKey(filePath);
140+
return await backend.readRaw(strippedKey);
141+
}
142+
131143
/**
132144
* Structured search results or error string for invalid input.
133145
*/

src/backends/filesystem.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import micromatch from "micromatch";
1717
import type {
1818
BackendProtocol,
1919
EditResult,
20+
FileData,
2021
FileInfo,
2122
GrepMatch,
2223
WriteResult,
@@ -241,6 +242,46 @@ export class FilesystemBackend implements BackendProtocol {
241242
}
242243
}
243244

245+
/**
246+
* Read file content as raw FileData.
247+
*
248+
* @param filePath - Absolute file path
249+
* @returns Raw file content as FileData
250+
*/
251+
async readRaw(filePath: string): Promise<FileData> {
252+
const resolvedPath = this.resolvePath(filePath);
253+
254+
let content: string;
255+
let stat: fsSync.Stats;
256+
257+
if (SUPPORTS_NOFOLLOW) {
258+
stat = await fs.stat(resolvedPath);
259+
if (!stat.isFile()) throw new Error(`File '${filePath}' not found`);
260+
const fd = await fs.open(
261+
resolvedPath,
262+
fsSync.constants.O_RDONLY | fsSync.constants.O_NOFOLLOW,
263+
);
264+
try {
265+
content = await fd.readFile({ encoding: "utf-8" });
266+
} finally {
267+
await fd.close();
268+
}
269+
} else {
270+
stat = await fs.lstat(resolvedPath);
271+
if (stat.isSymbolicLink()) {
272+
throw new Error(`Symlinks are not allowed: ${filePath}`);
273+
}
274+
if (!stat.isFile()) throw new Error(`File '${filePath}' not found`);
275+
content = await fs.readFile(resolvedPath, "utf-8");
276+
}
277+
278+
return {
279+
content: content.split("\n"),
280+
created_at: stat.ctime.toISOString(),
281+
modified_at: stat.mtime.toISOString(),
282+
};
283+
}
284+
244285
/**
245286
* Create a new file with content.
246287
* Returns WriteResult. External storage sets filesUpdate=null.

src/backends/protocol.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,14 @@ export interface BackendProtocol {
128128
limit?: number,
129129
): string | Promise<string>;
130130

131+
/**
132+
* Read file content as raw FileData.
133+
*
134+
* @param filePath - Absolute file path
135+
* @returns Raw file content as FileData
136+
*/
137+
readRaw(filePath: string): FileData | Promise<FileData>;
138+
131139
/**
132140
* Structured search results or error string for invalid input.
133141
*

src/backends/state.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,20 @@ export class StateBackend implements BackendProtocol {
124124
return formatReadResponse(fileData, offset, limit);
125125
}
126126

127+
/**
128+
* Read file content as raw FileData.
129+
*
130+
* @param filePath - Absolute file path
131+
* @returns Raw file content as FileData
132+
*/
133+
readRaw(filePath: string): FileData {
134+
const files = this.getFiles();
135+
const fileData = files[filePath];
136+
137+
if (!fileData) throw new Error(`File '${filePath}' not found`);
138+
return fileData;
139+
}
140+
127141
/**
128142
* Create a new file with content.
129143
* Returns WriteResult with filesUpdate to update LangGraph state.

src/backends/store.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -238,22 +238,29 @@ export class StoreBackend implements BackendProtocol {
238238
offset: number = 0,
239239
limit: number = 2000,
240240
): Promise<string> {
241-
const store = this.getStore();
242-
const namespace = this.getNamespace();
243-
const item = await store.get(namespace, filePath);
244-
245-
if (!item) {
246-
return `Error: File '${filePath}' not found`;
247-
}
248-
249241
try {
250-
const fileData = this.convertStoreItemToFileData(item);
242+
const fileData = await this.readRaw(filePath);
251243
return formatReadResponse(fileData, offset, limit);
252244
} catch (e: any) {
253245
return `Error: ${e.message}`;
254246
}
255247
}
256248

249+
/**
250+
* Read file content as raw FileData.
251+
*
252+
* @param filePath - Absolute file path
253+
* @returns Raw file content as FileData
254+
*/
255+
async readRaw(filePath: string): Promise<FileData> {
256+
const store = this.getStore();
257+
const namespace = this.getNamespace();
258+
const item = await store.get(namespace, filePath);
259+
260+
if (!item) throw new Error(`File '${filePath}' not found`);
261+
return this.convertStoreItemToFileData(item);
262+
}
263+
257264
/**
258265
* Create a new file with content.
259266
* Returns WriteResult. External storage sets filesUpdate=null.

0 commit comments

Comments
 (0)