Skip to content

Commit f5af77e

Browse files
authored
Add file upload support (#221)
* Add file upload support * Fix base url when files added * Fix import
1 parent 8a1caef commit f5af77e

File tree

4 files changed

+87
-2
lines changed

4 files changed

+87
-2
lines changed

deno.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"@runt/pyodide-runtime-agent": "./packages/pyodide-runtime-agent/src/mod.ts",
2121
"@runt/python-runtime-agent": "./packages/python-runtime-agent/mod.ts",
2222
"@runt/tui": "./packages/tui/src/cli.tsx",
23-
"jsr:@runtimed/schema": "jsr:@runtimed/schema@0.1.12",
23+
"jsr:@runtimed/schema": "jsr:@runtimed/schema@0.3.0",
2424
"jsr:@std/cli": "jsr:@std/cli@^1.0.22",
2525
"jsr:@std/testing": "jsr:@std/testing@^1.0.15"
2626
},

packages/pyodide-runtime-agent/src/pyodide-agent.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,20 @@ export class PyodideRuntimeAgent extends RuntimeAgent {
374374
mountData,
375375
});
376376

377+
// Send uploaded files to worker
378+
const files = this.store.query(tables.files.select());
379+
// This is a bit of a hack because we don't have access to the artifact client in the worker.
380+
// We send the base URL to the worker for now.
381+
const artifactBaseUrl = this.config.artifactClient.getArtifactUrl("");
382+
383+
if (files.length > 0) {
384+
this.sendWorkerMessage("files", { files, artifactBaseUrl });
385+
}
386+
387+
this.onFilesUpload((files) => {
388+
this.sendWorkerMessage("files", { files, artifactBaseUrl });
389+
});
390+
377391
this.isInitialized = true;
378392
logger.info("Pyodide worker initialized successfully");
379393
} catch (error) {

packages/pyodide-runtime-agent/src/pyodide-worker.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
getEssentialPackages,
1515
isFirstRun,
1616
} from "./cache-utils.ts";
17+
import type { FileData } from "jsr:@runtimed/schema";
1718

1819
declare const self: DedicatedWorkerGlobalScope;
1920

@@ -112,6 +113,76 @@ await run_registered_tool("${data.toolName}", kwargs_string)
112113
break;
113114
}
114115

116+
case "files": {
117+
if (!pyodide) {
118+
throw new Error("Pyodide not initialized");
119+
}
120+
121+
try {
122+
// Cast the files to the expected type
123+
const files = data.files as readonly FileData[];
124+
// Cast the artifact base URL to the expected type
125+
const artifactBaseUrl = data.artifactBaseUrl as string;
126+
127+
if (!artifactBaseUrl) {
128+
throw new Error("Artifact base URL is required");
129+
}
130+
131+
const filesInDirectory = pyodide.FS.readdir("./");
132+
133+
// Write/delete the new files
134+
for (const file of files) {
135+
if (file.deletedAt) {
136+
if (!filesInDirectory.includes(file.fileName)) {
137+
continue;
138+
}
139+
console.log("worker deleting file", { file });
140+
// Just in case we couldn't match the file, we want to put a try/catch
141+
// to avoid the worker crashing
142+
try {
143+
pyodide.FS.unlink(`./${file.fileName}`);
144+
} catch (error) {
145+
// File doesn't exist, nothing to delete
146+
console.log("file does not exist, skipping deletion", {
147+
fileName: file.fileName,
148+
error,
149+
});
150+
}
151+
} else {
152+
console.log("worker fetching file", { file });
153+
console.log("artifactBaseUrl", artifactBaseUrl);
154+
const response = await fetch(
155+
artifactBaseUrl + file.artifactId,
156+
);
157+
158+
// Handle different file types based on mimeType
159+
if (file.mimeType && file.mimeType.startsWith("text/")) {
160+
// Text files
161+
const content = await response.text();
162+
pyodide.FS.writeFile(`./${file.fileName}`, content);
163+
} else {
164+
// Binary files (images, etc.)
165+
const arrayBuffer = await response.arrayBuffer();
166+
const uint8Array = new Uint8Array(arrayBuffer);
167+
pyodide.FS.writeFile(`./${file.fileName}`, uint8Array);
168+
}
169+
}
170+
}
171+
172+
console.log("worker wrote files and cleaned up old files", { data });
173+
174+
self.postMessage({ id, type: "response", data: { success: true } });
175+
} catch (error) {
176+
console.error("Error in files message handler", error);
177+
self.postMessage({
178+
id,
179+
type: "response",
180+
error: error instanceof Error ? error.message : String(error),
181+
});
182+
}
183+
break;
184+
}
185+
115186
case "shutdown": {
116187
await shutdownWorker();
117188
self.postMessage({ id, type: "response", data: { success: true } });

packages/schema/deno.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
},
2020
"imports": {
2121
"fractional-indexing": "npm:fractional-indexing@^3.2.0",
22-
"@runtimed/schema": "jsr:@runtimed/schema@0.1.12",
22+
"@runtimed/schema": "jsr:@runtimed/schema@0.3.0",
2323
"jsr:@std/testing": "jsr:@std/testing@^1.0.15"
2424
},
2525
"lint": {

0 commit comments

Comments
 (0)