Spaces:
Running
Running
Download app/src/agent.worker.mjs from deyunma/MiniCPM5-2B-WebGPU-Pi: direct link, hf CLI and curl.
- Browser
- Download file 6.68 kB
-
https://huggingface.co/spaces/deyunma/MiniCPM5-2B-WebGPU-Pi/resolve/main/app/src/agent.worker.mjs
- Command line
-
hf download hf://spaces/deyunma/MiniCPM5-2B-WebGPU-Pi/app/src/agent.worker.mjs
-
curl -L -o agent.worker.mjs https://huggingface.co/spaces/deyunma/MiniCPM5-2B-WebGPU-Pi/resolve/main/app/src/agent.worker.mjs
6.68 kB
| import { Agent } from '@earendil-works/pi-agent-core'; | |
| import { createInference, localModel } from './inference.mjs'; | |
| import { createWorkspace, initialFiles, legacyProductsCsv, legacyProductsJson, legacyCostProductsJson, legacySalesCsv, legacyWorkspaceReadme, legacyProfitReadme } from './workspace.mjs'; | |
| import { openStorage } from './storage.mjs'; | |
| import { isModelCached } from './download.mjs'; | |
| const emit = data => postMessage(data); | |
| const inference = createInference(emit); | |
| let workspace, agent, storage, busy = false, operation, turnCount = 0; | |
| const systemPrompt = `You are Pi, a coding assistant powered by MiniCPM5-2B, a language model from OpenBMB. | |
| The model, inference, tools, and workspace run entirely in the user's browser. Workspace files are stored in this browser, not on the user's operating-system filesystem. | |
| Use your tools to inspect and change actual files. Never claim a command ran or a file changed without a successful tool result. | |
| When the user explicitly asks you to use a tool, call that tool even if you already know the answer. | |
| Working directory: /workspace. Use read, write, edit, and bash. Keep tool calls and answers concise. | |
| The bash tool supports cat, ls, find, grep, sed, awk, jq, sort, wc, printf, pipes, redirects, and shell scripts. | |
| Use jq for JSON and awk for CSV and calculations; python, python3, node, npm, native binaries, and network access are unavailable. | |
| You can create and edit text files, including HTML, CSS, JavaScript, and shell scripts. HTML may reference external assets by URL; writing such a reference does not require network access from the shell. Do not claim an external asset was fetched or tested. | |
| Interpret short follow-ups using the conversation and inspect the relevant files when needed. | |
| Inspect files before editing them. After changing files, verify the result with a tool. Finish with a short factual answer.`; | |
| async function snapshot() { return { version: 1, demoVersion: 4, files: await workspace.snapshot(), entries: await workspace.serialize(), messages: agent.state.messages }; } | |
| async function persist() { | |
| const state = await snapshot(); | |
| try { await storage?.save(state); } catch (e) { emit({ type: 'persistence_error', error: 'Changes are in memory, but browser storage failed: ' + e.message }); } | |
| emit({ type: 'workspace', files: state.files }); | |
| return state; | |
| } | |
| const initialized = (async () => { | |
| let saved; | |
| try { storage = await openStorage(); saved = await storage.load(); } | |
| catch (e) { emit({ type: 'persistence_error', error: 'Workspace saving is unavailable: ' + e.message }); } | |
| workspace = createWorkspace(saved?.files, saved?.entries); | |
| await workspace.ready; | |
| const upgradeDemo = saved && (saved.demoVersion ?? 0) < 4; | |
| if (upgradeDemo) { | |
| // Move prices and quantities together only for a recognized untouched demo. | |
| // Never remove a price column if its companion file was edited or deleted. | |
| const paths = workspace.bash.fs.getAllPaths(); | |
| const csv = '/workspace/products.csv', json = '/workspace/products.json', sales = '/workspace/sales.csv'; | |
| const untouched = async (file, versions) => paths.includes(file) && (await workspace.bash.fs.lstat(file)).isFile && versions.includes(await workspace.read(file)); | |
| if (await untouched(sales, [legacySalesCsv])) { | |
| const oldJson = await untouched(json, [legacyProductsJson, legacyCostProductsJson]); | |
| const oldCsv = !paths.includes(json) && await untouched(csv, [legacyProductsCsv]); | |
| const originalDemo = (saved.demoVersion ?? 0) < 1 && !paths.includes(json) && !paths.includes(csv); | |
| if (oldJson || oldCsv || originalDemo) { | |
| await workspace.write(sales, initialFiles[sales]); | |
| await workspace.write(json, initialFiles[json]); | |
| if (oldCsv) await workspace.bash.fs.rm(csv); | |
| } | |
| } | |
| const readme = '/workspace/README.md'; | |
| if (await untouched(readme, [legacyWorkspaceReadme, legacyProfitReadme])) { | |
| await workspace.write(readme, initialFiles[readme]); | |
| } | |
| } | |
| agent = new Agent({ initialState: { model: localModel, thinkingLevel: 'medium', systemPrompt, tools: workspace.tools, messages: saved?.messages ?? [] }, | |
| streamFn: inference.streamFn, toolExecution: 'sequential', | |
| afterToolCall: ({ result, isError }) => ({ isError: isError || (result.details?.exitCode !== undefined && result.details.exitCode !== 0) }), | |
| shouldStopAfterTurn: ({ toolResults }) => { | |
| const stop = ++turnCount >= 12 && toolResults.length > 0; | |
| if (stop) emit({ type: 'notice', text: 'Paused after 12 agent turns. Send a follow-up to continue.' }); | |
| return stop; | |
| }, | |
| }); | |
| agent.subscribe(async event => { | |
| emit({ type: 'agent_event', event }); | |
| if (event.type === 'tool_execution_end' || event.type === 'agent_end') await persist(); | |
| }); | |
| const state = await snapshot(); | |
| if (upgradeDemo) { | |
| try { await storage?.save(state); } | |
| catch (e) { emit({ type: 'persistence_error', error: 'Changes are in memory, but browser storage failed: ' + e.message }); } | |
| } | |
| emit({ type: 'initialized', ...state }); | |
| })(); | |
| onmessage = async ({ data }) => { | |
| const { id, action, args = {} } = data; | |
| const reply = value => emit({ type: 'reply', id, value }); | |
| try { | |
| await initialized; | |
| if (action === 'stop') { operation?.abort(); agent.abort(); inference.stop(); return reply(true); } | |
| if (action === 'snapshot') return reply(await snapshot()); | |
| if (action === 'cache_status') return reply(await isModelCached()); | |
| if (busy) throw Error('Wait for the current operation or stop it first.'); | |
| busy = true; operation = new AbortController(); emit({ type: 'busy', action, cachedOnly: args.cachedOnly === true }); | |
| try { | |
| let value; | |
| if (action === 'load') { await inference.load(args, operation.signal); value = inference.device; } | |
| else if (action === 'prompt') { | |
| turnCount = 0; await agent.prompt(args.text); value = await snapshot(); | |
| } else if (action === 'shell') { | |
| value = await workspace.bash.exec(args.command, { signal: operation.signal }); await persist(); | |
| } else if (action === 'write') { await workspace.write(args.path, args.content); value = await persist(); } | |
| else if (action === 'new_chat') { agent.reset(); value = await persist(); } | |
| else if (action === 'read') value = await workspace.read(args.path); | |
| else throw Error('Unknown action: ' + action); | |
| reply(value); | |
| } finally { busy = false; operation = undefined; emit({ type: 'idle' }); } | |
| } catch (error) { emit({ type: 'reply', id, error: error?.name === 'AbortError' ? 'Stopped.' : String(error?.message ?? error), errorName: error?.name, errorCode: error?.code }); } | |
| }; | |