Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/young-papers-thank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"deepagents": minor
---

Add readRaw method to filesystem backend protocol
14 changes: 5 additions & 9 deletions examples/backends/store-backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,12 @@ export const agent = createDeepAgent({
async function main() {
const threadId = uuidv4();

const message = new HumanMessage(
"Research the latest trends in AI agents for 2025",
);
await agent.invoke(
{
messages: [
new HumanMessage("Research the latest trends in AI agents for 2025"),
],
},
{
recursionLimit: 50,
configurable: { thread_id: threadId },
},
{ messages: [message] },
{ recursionLimit: 50, configurable: { thread_id: threadId } },
);

const threadId2 = uuidv4();
Expand Down
1 change: 1 addition & 0 deletions examples/research/research-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ export const agent = createDeepAgent({
model: "claude-sonnet-4-20250514",
temperature: 0,
}),

tools: [internetSearch],
systemPrompt: researchInstructions,
subagents: [critiqueSubAgent, researchSubAgent],
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@
"@changesets/cli": "^2.29.7",
"@eslint/eslintrc": "^3.1.0",
"@eslint/js": "^9.19.0",
"@langchain/langgraph-checkpoint": "^0.0.13",
"@langchain/langgraph-checkpoint": "^1.0.0",
"@langchain/openai": "^1.0.0",
"@langchain/tavily": "^0.1.4",
"@langchain/tavily": "^1.0.0",
"@tsconfig/recommended": "^1.0.10",
"@types/micromatch": "^4.0.10",
"@types/node": "^22.13.5",
Expand Down
36 changes: 10 additions & 26 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions src/backends/composite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import type {
BackendProtocol,
EditResult,
FileData,
FileInfo,
GrepMatch,
WriteResult,
Expand Down Expand Up @@ -128,6 +129,17 @@ export class CompositeBackend implements BackendProtocol {
return await backend.read(strippedKey, offset, limit);
}

/**
* Read file content as raw FileData.
*
* @param filePath - Absolute file path
* @returns Raw file content as FileData
*/
async readRaw(filePath: string): Promise<FileData> {
const [backend, strippedKey] = this.getBackendAndKey(filePath);
return await backend.readRaw(strippedKey);
}

/**
* Structured search results or error string for invalid input.
*/
Expand Down
41 changes: 41 additions & 0 deletions src/backends/filesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import micromatch from "micromatch";
import type {
BackendProtocol,
EditResult,
FileData,
FileInfo,
GrepMatch,
WriteResult,
Expand Down Expand Up @@ -241,6 +242,46 @@ export class FilesystemBackend implements BackendProtocol {
}
}

/**
* Read file content as raw FileData.
*
* @param filePath - Absolute file path
* @returns Raw file content as FileData
*/
async readRaw(filePath: string): Promise<FileData> {
const resolvedPath = this.resolvePath(filePath);

let content: string;
let stat: fsSync.Stats;

if (SUPPORTS_NOFOLLOW) {
stat = await fs.stat(resolvedPath);
if (!stat.isFile()) throw new Error(`File '${filePath}' not found`);
const fd = await fs.open(
resolvedPath,
fsSync.constants.O_RDONLY | fsSync.constants.O_NOFOLLOW,
);
try {
content = await fd.readFile({ encoding: "utf-8" });
} finally {
await fd.close();
}
} else {
stat = await fs.lstat(resolvedPath);
if (stat.isSymbolicLink()) {
throw new Error(`Symlinks are not allowed: ${filePath}`);
}
if (!stat.isFile()) throw new Error(`File '${filePath}' not found`);
content = await fs.readFile(resolvedPath, "utf-8");
}

return {
content: content.split("\n"),
created_at: stat.ctime.toISOString(),
modified_at: stat.mtime.toISOString(),
};
}

/**
* Create a new file with content.
* Returns WriteResult. External storage sets filesUpdate=null.
Expand Down
8 changes: 8 additions & 0 deletions src/backends/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@ export interface BackendProtocol {
limit?: number,
): string | Promise<string>;

/**
* Read file content as raw FileData.
*
* @param filePath - Absolute file path
* @returns Raw file content as FileData
*/
readRaw(filePath: string): FileData | Promise<FileData>;

/**
* Structured search results or error string for invalid input.
*
Expand Down
14 changes: 14 additions & 0 deletions src/backends/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,20 @@ export class StateBackend implements BackendProtocol {
return formatReadResponse(fileData, offset, limit);
}

/**
* Read file content as raw FileData.
*
* @param filePath - Absolute file path
* @returns Raw file content as FileData
*/
readRaw(filePath: string): FileData {
const files = this.getFiles();
const fileData = files[filePath];

if (!fileData) throw new Error(`File '${filePath}' not found`);
return fileData;
}

/**
* Create a new file with content.
* Returns WriteResult with filesUpdate to update LangGraph state.
Expand Down
25 changes: 16 additions & 9 deletions src/backends/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,22 +238,29 @@ export class StoreBackend implements BackendProtocol {
offset: number = 0,
limit: number = 2000,
): Promise<string> {
const store = this.getStore();
const namespace = this.getNamespace();
const item = await store.get(namespace, filePath);

if (!item) {
return `Error: File '${filePath}' not found`;
}

try {
const fileData = this.convertStoreItemToFileData(item);
const fileData = await this.readRaw(filePath);
return formatReadResponse(fileData, offset, limit);
} catch (e: any) {
return `Error: ${e.message}`;
}
}

/**
* Read file content as raw FileData.
*
* @param filePath - Absolute file path
* @returns Raw file content as FileData
*/
async readRaw(filePath: string): Promise<FileData> {
const store = this.getStore();
const namespace = this.getNamespace();
const item = await store.get(namespace, filePath);

if (!item) throw new Error(`File '${filePath}' not found`);
return this.convertStoreItemToFileData(item);
}

/**
* Create a new file with content.
* Returns WriteResult. External storage sets filesUpdate=null.
Expand Down
Loading