Spaces:
Running on Zero
Running on Zero
GitHub Actions
feat: WebLLM browser agent with PeerJS mesh, HybridRAG, news signals, and easter-egg ticker
78cc96f | // src/agent/runtime.js | |
| // ReAct-style agent loop. Model-agnostic: works with any `llm.chat(...)` that | |
| // streams tokens via onToken and resolves to a string or { text }. | |
| import { DEFAULT_SYSTEM } from "./webllm-agent.js"; | |
| import { TOOL_HELP } from "./webllm-agent.js"; | |
| export function createAgentRuntime({ llm, tools, onLog, onToken, onState }) { | |
| let running = false; | |
| let aborter = null; | |
| async function runTool(name, args) { | |
| if (!tools[name]) throw new Error(`Unknown tool: ${name}`); | |
| return await tools[name](args || {}); | |
| } | |
| async function loop(userText, messages = [], maxIter = 8) { | |
| if (running) return; | |
| running = true; | |
| aborter = new AbortController(); | |
| onState?.({ running: true }); | |
| const chat = [ | |
| { role: "system", content: `${DEFAULT_SYSTEM}\n\nAvailable tools:\n${TOOL_HELP}` }, | |
| ...messages, | |
| { role: "user", content: userText }, | |
| ]; | |
| let last = ""; | |
| try { | |
| for (let iter = 0; iter < maxIter; iter++) { | |
| onLog?.(`iter ${iter + 1}/${maxIter}`); | |
| last = ""; | |
| const out = await llm.chat({ | |
| messages: chat, | |
| signal: aborter.signal, | |
| stream: true, | |
| temperature: 0.4, | |
| max_tokens: 900, | |
| onToken: (t) => { | |
| last += t; | |
| onToken?.(t); | |
| }, | |
| }); | |
| const text = typeof out === "string" ? out : (out?.text || last); | |
| const actionMatch = text.match(/action\s*:\s*(\{[\s\S]*?\})/i); | |
| if (!actionMatch) { | |
| onState?.({ running: false, final: text }); | |
| return text; | |
| } | |
| let action; | |
| try { | |
| action = JSON.parse(actionMatch[1]); | |
| } catch { | |
| chat.push({ role: "assistant", content: text }); | |
| chat.push({ role: "user", content: "Observation: action JSON parse error. Re-emit valid JSON." }); | |
| continue; | |
| } | |
| onLog?.(`tool: ${action.tool}(${JSON.stringify(stripTool(action))})`); | |
| let result; | |
| try { | |
| result = await runTool(action.tool, action); | |
| } catch (err) { | |
| result = `tool error: ${err?.message || err}`; | |
| } | |
| chat.push({ role: "assistant", content: text }); | |
| chat.push({ | |
| role: "user", | |
| content: `Observation from ${action.tool}: ${String(result).slice(0, 8000)}`, | |
| }); | |
| } | |
| onState?.({ running: false, final: last }); | |
| return last; | |
| } finally { | |
| running = false; | |
| aborter = null; | |
| onState?.({ running: false }); | |
| } | |
| } | |
| function stop() { | |
| if (aborter) aborter.abort(); | |
| running = false; | |
| onState?.({ running: false }); | |
| } | |
| return { loop, stop, isRunning: () => running }; | |
| } | |
| function stripTool(action) { | |
| const { tool, ...rest } = action; | |
| return rest; | |
| } | |