Skip to content

Commit 493c5d5

Browse files
feat: batch writes (#175)
1 parent 04381a0 commit 493c5d5

File tree

3 files changed

+105
-0
lines changed

3 files changed

+105
-0
lines changed

package-lock.json

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

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
"chalk": "^5.4.1",
109109
"cli-table3": "^0.6.3",
110110
"date-fns": "^4.1.0",
111+
"fflate": "^0.8.2",
111112
"isbinaryfile": "^5.0.4",
112113
"ora": "^8.2.0",
113114
"path": "^0.12.7",

src/SandboxClient/filesystem.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { type IAgentClient } from "../AgentClient/agent-client-interface";
33
import { Disposable } from "../utils/disposable";
44
import { Emitter, type Event } from "../utils/event";
55
import { Tracer, SpanStatusCode } from "@opentelemetry/api";
6+
import { zip } from "fflate";
67

78
export type FSStatResult = {
89
type: "file" | "directory";
@@ -39,6 +40,11 @@ export type Watcher = {
3940
onEvent: Event<WatchEvent>;
4041
};
4142

43+
export type BatchWriteFile = {
44+
path: string;
45+
content: string | Uint8Array;
46+
};
47+
4248
export class FileSystem {
4349
private disposable = new Disposable();
4450
private tracer?: Tracer;
@@ -137,6 +143,97 @@ export class FileSystem {
137143
);
138144
}
139145

146+
/**
147+
* Batch write multiple files by zipping them, uploading the zip, and extracting it on the sandbox.
148+
* This is more efficient than writing many files individually.
149+
* Files will be created/overwritten as needed.
150+
*/
151+
async batchWrite(files: BatchWriteFile[]): Promise<void> {
152+
return this.withSpan(
153+
"fs.batchWrite",
154+
{
155+
"fs.fileCount": files.length,
156+
},
157+
async () => {
158+
if (files.length === 0) {
159+
return;
160+
}
161+
162+
// Create a zip file containing all the files
163+
const zipData: Record<string, Uint8Array> = {};
164+
165+
for (const file of files) {
166+
const content =
167+
typeof file.content === "string"
168+
? new TextEncoder().encode(file.content)
169+
: file.content;
170+
zipData[file.path] = content;
171+
}
172+
173+
// Create the zip using fflate
174+
const zipBytes = await new Promise<Uint8Array>((resolve, reject) => {
175+
zip(zipData, (err, data) => {
176+
if (err) reject(err);
177+
else resolve(data);
178+
});
179+
});
180+
181+
// Write the zip file to a temporary location
182+
const tempZipPath = `/tmp/batch_write_${Date.now()}.zip`;
183+
await this.writeFile(tempZipPath, zipBytes);
184+
185+
try {
186+
// Extract the zip file using unzip command
187+
const result = await this.agentClient.shells.create(
188+
this.agentClient.workspacePath,
189+
{ cols: 128, rows: 24 },
190+
`cd ${this.agentClient.workspacePath} && unzip -o ${tempZipPath}`,
191+
"COMMAND",
192+
true
193+
);
194+
195+
if (result.status === "ERROR" || result.status === "KILLED") {
196+
throw new Error(
197+
`Failed to extract batch files: ${
198+
result.buffer?.join("\n") || "Unknown error"
199+
}`
200+
);
201+
}
202+
203+
// Wait for the command to complete if it's still running
204+
if (result.status === "RUNNING") {
205+
// Wait for shell exit event
206+
await new Promise<void>((resolve, reject) => {
207+
const disposable = this.agentClient.shells.onShellExited(
208+
({ shellId, exitCode }) => {
209+
if (shellId === result.shellId) {
210+
disposable.dispose();
211+
if (exitCode === 0) {
212+
resolve();
213+
} else {
214+
reject(
215+
new Error(
216+
`Unzip command failed with exit code ${exitCode}`
217+
)
218+
);
219+
}
220+
}
221+
}
222+
);
223+
});
224+
}
225+
} finally {
226+
// Always clean up the temporary zip file, regardless of success or failure
227+
try {
228+
await this.remove(tempZipPath);
229+
} catch {
230+
// Ignore cleanup errors - file might already be deleted or not exist
231+
}
232+
}
233+
}
234+
);
235+
}
236+
140237
/**
141238
* Create a directory.
142239
*/

0 commit comments

Comments
 (0)