Download src/workspace.js from webml-community/MiniCPM5-2B-WebGPU-Pi: direct link, hf CLI and curl.
- Browser
- Download file 9.09 kB
-
https://huggingface.co/spaces/webml-community/MiniCPM5-2B-WebGPU-Pi/resolve/main/src/workspace.js
- Command line
-
hf download hf://spaces/webml-community/MiniCPM5-2B-WebGPU-Pi/src/workspace.js
-
curl -L -o workspace.js https://huggingface.co/spaces/webml-community/MiniCPM5-2B-WebGPU-Pi/resolve/main/src/workspace.js
9.09 kB
| import { Bash } from '../vendor/just-bash.js'; | |
| export const WORKSPACE_DB_NAME = 'minicpm5-gguf-workspace'; | |
| export const WORKSPACE_ROOT = '/workspace'; | |
| export const MAX_EDITOR_BYTES = 1024 * 1024; | |
| export const EXECUTION_LIMITS = Object.freeze({ | |
| maxExecutionTimeMs: 10_000, | |
| maxCommandCount: 3_000, | |
| maxFileSystemBytes: 16 * 1024 * 1024, | |
| maxOutputSize: 262_144, | |
| maxSourceBytes: 131_072, | |
| }); | |
| export const DEFAULT_FILES = Object.freeze({ | |
| '/workspace/README.md': '# Browser workspace\n\nYour files are saved in this browser.\n', | |
| '/workspace/sales.csv': 'product,units\nNotebook,12\nPencil,30\nFolder,8\n', | |
| '/workspace/products.json': JSON.stringify([ | |
| { product: 'Notebook', price: 5 }, | |
| { product: 'Pencil', price: 2 }, | |
| { product: 'Folder', price: 3 }, | |
| { product: 'Eraser', price: 1.5 }, | |
| { product: 'Stapler', price: 4.99 }, | |
| { product: 'Ruler', price: 2.5 }, | |
| ], null, 2) + '\n', | |
| }); | |
| const encoder = new TextEncoder(); | |
| const generatedRoots = ['/bin', '/usr/bin', '/dev', '/proc']; | |
| const isGenerated = path => generatedRoots.some(root => path === root || path.startsWith(root + '/')); | |
| export function truncateOutput(value, limit = 6_000) { | |
| return value.length > limit | |
| ? value.slice(0, limit) + '\n[Output truncated at 6,000 characters; use head, grep, or a narrower query.]' | |
| : value; | |
| } | |
| function result(content, details = {}) { | |
| return { content: truncateOutput(content), details }; | |
| } | |
| function checkAbort(signal) { | |
| if (signal?.aborted) throw signal.reason ?? new DOMException('The operation was aborted.', 'AbortError'); | |
| } | |
| function objectSchema(properties) { | |
| return { type: 'object', properties, required: Object.keys(properties), additionalProperties: false }; | |
| } | |
| function stringSchema(description) { | |
| return { type: 'string', ...(description ? { description } : {}) }; | |
| } | |
| export function bytesToBase64(bytes) { | |
| let binary = ''; | |
| for (let i = 0; i < bytes.length; i += 32_768) { | |
| binary += String.fromCharCode(...bytes.subarray(i, i + 32_768)); | |
| } | |
| return btoa(binary); | |
| } | |
| export function base64ToBytes(base64) { | |
| return Uint8Array.from(atob(base64), char => char.charCodeAt(0)); | |
| } | |
| /** A JavaScript-only shell: no host filesystem, network, Python, or JavaScript command. */ | |
| export function createWorkspace(files = DEFAULT_FILES, entries) { | |
| const initialFiles = entries | |
| ? Object.fromEntries(entries.filter(([, entry]) => entry.type === 'file').map(([path, entry]) => [ | |
| path, { mode: entry.mode, content: base64ToBytes(entry.base64) }, | |
| ])) | |
| : files; | |
| const bash = new Bash({ | |
| files: initialFiles, | |
| cwd: WORKSPACE_ROOT, | |
| executionLimitProfile: 'hardened', | |
| executionLimits: { ...EXECUTION_LIMITS }, | |
| }); | |
| const resolve = path => bash.fs.resolvePath(WORKSPACE_ROOT, path); | |
| const ready = (async () => { | |
| // File construction creates parents; restore their original modes afterward. | |
| for (const [path, entry] of entries ?? []) { | |
| if (entry.type === 'directory') { | |
| await bash.fs.mkdir(path, { recursive: true }); | |
| await bash.fs.chmod(path, entry.mode); | |
| } else if (entry.type === 'symlink') { | |
| const parent = path.slice(0, path.lastIndexOf('/')) || '/'; | |
| await bash.fs.mkdir(parent, { recursive: true }); | |
| await bash.fs.symlink(entry.target, path); | |
| } | |
| } | |
| })(); | |
| async function read(path, { signal } = {}) { | |
| await ready; | |
| checkAbort(signal); | |
| return bash.fs.readFile(resolve(path)); | |
| } | |
| async function write(path, content, { signal } = {}) { | |
| await ready; | |
| checkAbort(signal); | |
| if (typeof content !== 'string') throw new TypeError('File content must be a UTF-8 string.'); | |
| if (encoder.encode(content).length > MAX_EDITOR_BYTES) throw new Error('The editor accepts files up to 1 MiB.'); | |
| const absolutePath = resolve(path); | |
| await bash.fs.mkdir(absolutePath.slice(0, absolutePath.lastIndexOf('/')) || '/', { recursive: true }); | |
| checkAbort(signal); | |
| await bash.fs.writeFile(absolutePath, content); | |
| } | |
| const tools = [ | |
| { | |
| name: 'bash', | |
| description: 'Run a bash command in /workspace. Supports cat, ls, grep, sed, awk, jq, sort, pipes, redirects, and shell scripts. No network, Node.js, Python, npm, or native binaries. Files persist across commands; cd and variables reset each call.', | |
| parameters: objectSchema({ command: stringSchema('The shell command to execute.') }), | |
| async execute({ command }, { signal } = {}) { | |
| await ready; | |
| checkAbort(signal); | |
| const execution = await bash.exec(command, { signal }); | |
| checkAbort(signal); | |
| const content = `${execution.stdout}${execution.stderr ? '\nSTDERR:\n' + execution.stderr : ''}\nExit code: ${execution.exitCode}`; | |
| // The agent's repeated-failure guard relies on tools rejecting failed commands. | |
| if (execution.exitCode !== 0) { | |
| const error = new Error(truncateOutput(content)); | |
| error.exitCode = execution.exitCode; | |
| throw error; | |
| } | |
| return result(content, { exitCode: execution.exitCode }); | |
| }, | |
| }, | |
| { | |
| name: 'read', | |
| description: 'Read a UTF-8 text file. Use bash head/grep for large files.', | |
| parameters: objectSchema({ path: stringSchema() }), | |
| async execute({ path }, { signal } = {}) { | |
| await ready; | |
| checkAbort(signal); | |
| const absolutePath = await bash.fs.realpath(resolve(path)); | |
| return result(await read(absolutePath, { signal }), { path: absolutePath }); | |
| }, | |
| }, | |
| { | |
| name: 'write', | |
| description: 'Create or replace a UTF-8 text file. Parent directories are created.', | |
| parameters: objectSchema({ path: stringSchema(), content: stringSchema() }), | |
| async execute({ path, content }, context = {}) { | |
| await write(path, content, context); | |
| return result('Wrote ' + resolve(path)); | |
| }, | |
| }, | |
| { | |
| name: 'edit', | |
| description: 'Replace one exact occurrence of oldText with newText in a UTF-8 file. oldText must match exactly once.', | |
| parameters: objectSchema({ path: stringSchema(), oldText: stringSchema(), newText: stringSchema() }), | |
| async execute({ path, oldText, newText }, context = {}) { | |
| const content = await read(path, context); | |
| if (!oldText || content.split(oldText).length !== 2) { | |
| throw new Error('oldText must match exactly once. Read the file first.'); | |
| } | |
| await write(path, content.replace(oldText, () => newText), context); | |
| return result('Edited ' + resolve(path)); | |
| }, | |
| }, | |
| ]; | |
| async function snapshot() { | |
| await ready; | |
| const files = {}; | |
| for (const path of bash.fs.getAllPaths().sort()) { | |
| if (!isGenerated(path) && (await bash.fs.lstat(path)).isFile) files[path] = await bash.fs.readFile(path); | |
| } | |
| return files; | |
| } | |
| async function serialize() { | |
| await ready; | |
| const entries = []; | |
| for (const path of bash.fs.getAllPaths().sort()) { | |
| if (isGenerated(path)) continue; | |
| const stat = await bash.fs.lstat(path); | |
| if (stat.isSymbolicLink) { | |
| entries.push([path, { type: 'symlink', target: await bash.fs.readlink(path) }]); | |
| } else if (stat.isDirectory) { | |
| entries.push([path, { type: 'directory', mode: stat.mode }]); | |
| } else if (stat.isFile) { | |
| entries.push([path, { type: 'file', base64: bytesToBase64(await bash.fs.readFileBuffer(path)), mode: stat.mode }]); | |
| } | |
| } | |
| return entries; | |
| } | |
| return { bash, tools, ready, read, write, snapshot, serialize }; | |
| } | |
| /** Separate from legacy Pi storage because the new agent uses a different message schema. */ | |
| export async function createStore(indexedDB = globalThis.indexedDB) { | |
| if (!indexedDB) throw new Error('Browser storage is unavailable.'); | |
| const db = await new Promise((resolve, reject) => { | |
| const request = indexedDB.open(WORKSPACE_DB_NAME, 1); | |
| request.onupgradeneeded = () => request.result.createObjectStore('state'); | |
| request.onsuccess = () => resolve(request.result); | |
| request.onerror = () => reject(request.error); | |
| request.onblocked = () => reject(new Error('Close other tabs to enable browser storage.')); | |
| }); | |
| return { | |
| async load() { | |
| return new Promise((resolve, reject) => { | |
| const transaction = db.transaction('state'); | |
| const request = transaction.objectStore('state').get('current'); | |
| request.onsuccess = () => resolve(request.result); | |
| request.onerror = () => reject(request.error); | |
| transaction.onabort = () => reject(transaction.error ?? new Error('Loading browser storage was aborted.')); | |
| }); | |
| }, | |
| async save(state) { | |
| return new Promise((resolve, reject) => { | |
| const transaction = db.transaction('state', 'readwrite'); | |
| transaction.objectStore('state').put(state, 'current'); | |
| transaction.oncomplete = () => resolve(); | |
| transaction.onerror = () => reject(transaction.error); | |
| transaction.onabort = () => reject(transaction.error ?? new Error('Saving browser storage was aborted.')); | |
| }); | |
| }, | |
| close() { db.close(); }, | |
| }; | |
| } | |