@openeryc/pi-coding-agent 0.75.21 → 0.75.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/dist/modes/web/web-mode.d.ts.map +1 -1
- package/dist/modes/web/web-mode.js +73 -0
- package/dist/modes/web/web-mode.js.map +1 -1
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +20 -19
- package/dist/utils/version-check.js.map +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-mode.d.ts","sourceRoot":"","sources":["../../../src/modes/web/web-mode.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAiP/E,wBAAsB,UAAU,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgG5E","sourcesContent":["import { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { networkInterfaces } from \"node:os\";\nimport type { AgentSessionEvent } from \"../../core/agent-session.ts\";\nimport type { AgentSessionRuntime } from \"../../core/agent-session-runtime.ts\";\n\nconst PASSWORD = process.env.PI_WEB_PASSWORD;\n\nfunction checkAuth(req: IncomingMessage, res: ServerResponse): boolean {\n\tif (!PASSWORD) return true;\n\n\tconst auth = req.headers.authorization;\n\tif (auth) {\n\t\tconst [scheme, credentials] = auth.split(\" \");\n\t\tif (scheme === \"Basic\" && credentials) {\n\t\t\tconst decoded = Buffer.from(credentials, \"base64\").toString(\"utf-8\");\n\t\t\tconst [, password] = decoded.split(\":\");\n\t\t\tif (password === PASSWORD) return true;\n\t\t}\n\t}\n\n\tres.writeHead(401, {\n\t\t\"www-authenticate\": 'Basic realm=\"pi\", charset=\"UTF-8\"',\n\t\t\"content-type\": \"text/plain\",\n\t});\n\tres.end(\"Unauthorized\");\n\treturn false;\n}\n\nfunction getLocalIP(): string {\n\tconst interfaces = networkInterfaces();\n\tfor (const iface of Object.values(interfaces)) {\n\t\tif (!iface) continue;\n\t\tfor (const addr of iface) {\n\t\t\tif (addr.family === \"IPv4\" && !addr.internal) {\n\t\t\t\treturn addr.address;\n\t\t\t}\n\t\t}\n\t}\n\treturn \"127.0.0.1\";\n}\n\nfunction getPort(): number {\n\tconst env = process.env.PORT;\n\tif (env) {\n\t\tconst p = parseInt(env, 10);\n\t\tif (!Number.isNaN(p) && p > 0 && p < 65536) return p;\n\t}\n\treturn 0;\n}\n\nconst HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\n<title>pi</title>\n<style>\n*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\nhtml{font-size:14px;-webkit-text-size-adjust:100%}\nbody{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",system-ui,sans-serif;background:#0b1014;color:#c9d1d9;display:flex;flex-direction:column;height:100dvh;overflow:hidden}\n#header{display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background:#0d121a;border-bottom:1px solid #21262d;flex-shrink:0;gap:10px}\n#header-left{display:flex;align-items:center;gap:10px}\n#header h1{font-size:14px;font-weight:600;color:#e6edf3}\n#session-info{font-size:11px;color:#6e7681;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n#header-stats{display:flex;gap:12px;font-size:11px;color:#7d8590}\n#msgs{flex:1;overflow-y:auto;padding:16px 20px;scroll-behavior:smooth;display:flex;flex-direction:column;gap:14px}\n#msgs::-webkit-scrollbar{width:6px}\n#msgs::-webkit-scrollbar-track{background:transparent}\n#msgs::-webkit-scrollbar-thumb{background:#30363d;border-radius:3px}\n.msg-group{display:flex;flex-direction:column;gap:2px;animation:fadeIn .15s ease;max-width:86%}\n.msg-group.user{max-width:82%;align-self:flex-end}\n@keyframes fadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}\n.msg-label{font-size:10.5px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;padding:0 2px}\n.msg-group.user .msg-label{color:#7ec9ff;text-align:right}\n.msg-group.assistant .msg-label{color:#7d8590}\n.msg{border-radius:8px;font-size:13.5px;line-height:1.6;overflow-wrap:break-word;padding:10px 14px}\n.msg.user{background:#1b4a8b;color:#e6edf3;border-bottom-right-radius:2px}\n.msg.assistant{background:#1a1f2b;color:#c9d1d9;border-bottom-left-radius:2px;border:1px solid #21262d}\n.msg.system{background:transparent;color:#6e7681;font-size:12px;text-align:center;max-width:100%;padding:4px 0}\n.msg p{margin:0 0 6px}\n.msg p:last-child{margin-bottom:0}\n.msg strong{color:#f2a65a;font-weight:600}\n.msg em{color:#c9d1d9;font-style:italic}\n.msg code{font-family:\"JetBrains Mono\",\"Fira Code\",monospace;font-size:12px;background:#2d333b;color:#c9d1d9;padding:1px 5px;border-radius:3px}\n.msg pre{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:10px 12px;margin:8px 0;overflow-x:auto;font-size:12px;line-height:1.5}\n.msg pre code{background:transparent;padding:0;border-radius:0;font-size:inherit}\n.msg h1,.msg h2,.msg h3{margin:8px 0 4px;font-weight:600;color:#e6edf3}\n.msg h1{font-size:18px;border-bottom:1px solid #21262d;padding-bottom:4px}\n.msg h2{font-size:15px}\n.msg h3{font-size:13.5px;color:#c9d1d9}\n.msg ul,.msg ol{padding-left:20px;margin:4px 0 8px}\n.msg li{margin:2px 0}\n.msg hr{border:none;border-top:1px solid #21262d;margin:10px 0}\n.tool{border:1px solid #21262d;border-radius:8px;overflow:hidden;transition:border-color .2s}\n.tool:hover{border-color:#30363d}\n.tool-header{display:flex;align-items:center;justify-content:space-between;padding:7px 10px;background:#0d121a;cursor:pointer;user-select:none;gap:8px;transition:background .15s}\n.tool-header:hover{background:#161b22}\n.tool-icon{width:7px;height:7px;border-radius:50%;flex-shrink:0}\n.tool-icon.pending{background:#d29922;animation:pulse 1.5s infinite}\n.tool-icon.done{background:#3fb950}\n.tool-icon.error{background:#f85149}\n@keyframes pulse{0%,100%{opacity:.4}50%{opacity:1}}\n.tool-title{flex:1;font-size:12px;font-weight:500;color:#c9d1d9;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.tool-arrow{color:#6e7681;font-size:9px;transition:transform .2s}\n.tool.expanded .tool-arrow{transform:rotate(90deg)}\n.tool-body{font-family:\"JetBrains Mono\",\"Fira Code\",monospace;font-size:12px;line-height:1.55;padding:8px 10px;background:#0b1014;white-space:pre-wrap;word-break:break-all;overflow-x:auto;max-height:30vh;overflow-y:auto;display:none}\n.tool-body::-webkit-scrollbar{width:4px;height:4px}\n.tool-body::-webkit-scrollbar-thumb{background:#30363d;border-radius:2px}\n.tool.expanded .tool-body{display:block}\n.tool.mini .tool-body{max-height:12vh}\n.thinking{padding:5px 10px;border-left:2px solid #30363d;font-size:11.5px;color:#6e7681;font-style:italic}\n#input-area{background:#0d121a;border-top:1px solid #21262d;padding:12px 16px;flex-shrink:0}\n#input-area form{display:flex;gap:8px;max-width:900px;margin:0 auto}\n#prompt{flex:1;background:#161b22;border:1px solid #30363d;border-radius:8px;padding:9px 12px;color:#c9d1d9;font-size:13.5px;font-family:inherit;outline:none;transition:border-color .2s,box-shadow .2s}\n#prompt:focus{border-color:#4493f8;box-shadow:0 0 0 3px rgba(68,147,248,.15)}\n#prompt::placeholder{color:#484f58}\nbutton{background:#1f6feb;color:#fff;border:none;border-radius:8px;padding:9px 16px;font-size:13px;font-weight:500;cursor:pointer;transition:background .15s;flex-shrink:0}\nbutton:hover{background:#2b83ff}\nbutton:disabled{background:#21262d;color:#6e7681;cursor:not-allowed}\n.btn-mini{font-size:11px;padding:4px 10px;background:#21262d}\n.btn-mini:hover{background:#30363d}\n.spinner{display:none;width:16px;height:16px;border:2px solid #30363d;border-top-color:#4493f8;border-radius:50%;animation:spin .6s linear infinite}\n.spinner.active{display:inline-block}\n@keyframes spin{to{transform:rotate(360deg)}}\n@media(max-width:600px){\nhtml{font-size:13px}\n#msgs{padding:10px;gap:10px}\n.msg-group{max-width:94%}\n.msg-group.user{max-width:92%}\n#input-area{padding:8px 10px}\n#prompt{padding:7px 10px}\nbutton{padding:7px 12px}\n}\n</style>\n</head>\n<body>\n<div id=\"header\">\n<div id=\"header-left\"><h1>pi</h1><span id=\"session-info\"></span></div>\n<div id=\"header-stats\"><span id=\"stat-sessions\"></span><span id=\"stat-cost\"></span></div>\n</div>\n<div id=\"msgs\"></div>\n<div id=\"input-area\">\n<form id=\"f\" autocomplete=\"off\">\n<input id=\"prompt\" type=\"text\" placeholder=\"Message pi...\" autofocus autocomplete=\"off\">\n<button id=\"send\">Send</button>\n<span class=\"spinner\" id=\"spinner\"></span>\n</form>\n</div>\n<script>\nconst msgs=document.getElementById(\"msgs\"),f=document.getElementById(\"f\"),prompt=document.getElementById(\"prompt\"),\nsend=document.getElementById(\"send\"),spinner=document.getElementById(\"spinner\"),\nstatSessions=document.getElementById(\"stat-sessions\"),statCost=document.getElementById(\"stat-cost\"),\nsessionInfo=document.getElementById(\"session-info\");\nlet busy=false,toolEls={},curAssistant=null;\n\nfunction scrollDown(){msgs.scrollTop=msgs.scrollHeight}\nfunction esc(s){if(!s)return\"\";return s.replace(/&/g,\"&\").replace(/</g,\"<\").replace(/>/g,\">\")}\nfunction fmt(n){if(n<1e3)return n;if(n<1e4)return(n/1e3).toFixed(1)+\"k\";return Math.round(n/1e3)+\"k\"}\n\nfunction md(text){\nlet s=esc(text);\nconst blocks=[];\ns=s.replace(/\\`\\`\\`(\\\\w*)\\\\n([\\\\s\\\\S]*?)\\\\n\\`\\`\\`/g,(_,lang,code)=>{blocks.push('<pre><code>'+code+'</code></pre>');return'\\\\x00B'+(blocks.length-1)+'\\\\x00B'});\ns=s.replace(/\\`([^\\`]+)\\`/g,'<code>$1</code>');\ns=s.replace(/\\\\*\\\\*(.+?)\\\\*\\\\*/g,'<strong>$1</strong>');\ns=s.replace(/\\\\*(.+?)\\\\*/g,'<em>$1</em>');\ns=s.replace(/^### (.+$)/gm,'<h3>$1</h3>');\ns=s.replace(/^## (.+$)/gm,'<h2>$1</h2>');\ns=s.replace(/^# (.+$)/gm,'<h1>$1</h1>');\ns=s.replace(/^- (.+$)/gm,'<li>$1</li>');\ns=s.replace(/^> (.+$)/gm,'<blockquote>$1</blockquote>');\ns=s.replace(/((?:<li>.*<\\\\/li>\\\\n?)+)/g,'<ul>$1</ul>');\ns=s.replace(/^(---+|\\\\*\\\\*\\\\*+|___+)$/gm,'<hr>');\ns=s.replace(/\\\\x00B(\\\\d+)\\\\x00B/g,(_,i)=>blocks[parseInt(i)]);\ns=s.replace(/\\\\n\\\\n/g,'<br><br>');\ns=s.replace(/\\\\n/g,'<br>');\nreturn s\n}\n\nfunction addMsg(role,text){\nconst g=document.createElement(\"div\");g.className=\"msg-group \"+role;\nif(role===\"user\"){g.innerHTML='<div class=\"msg-label\">You</div><div class=\"msg user\">'+esc(text)+'</div>'}\nelse if(role===\"assistant\"){g.innerHTML='<div class=\"msg-label\">pi</div><div class=\"msg assistant\"></div>'}\nelse{g.innerHTML='<div class=\"msg system\">'+text+'</div>'}\nmsgs.appendChild(g);scrollDown();return g}\n\nfunction getAssistantDiv(){if(!curAssistant){curAssistant=addMsg(\"assistant\",\"\")}return curAssistant.querySelector(\".msg\")}\n\nfunction startTool(e){\nconst el=document.createElement(\"div\");el.className=\"tool\";el.id=\"t\"+e.toolCallId;\nconst title=esc(e.toolName)+(e.args?' '+esc(String(e.args.command||e.args.path||e.args.file_path||\"\").slice(0,80)):'');\nel.innerHTML='<div class=\"tool-header\"><span class=\"tool-icon pending\"></span><span class=\"tool-title\">'+title+'</span><span class=\"tool-arrow\">▶</span></div><div class=\"tool-body\"></div>';\nel.querySelector(\".tool-header\").onclick=()=>el.classList.toggle(\"expanded\");\nmsgs.appendChild(el);toolEls[e.toolCallId]=el;scrollDown()}\n\nfunction updateTool(id,result,isError){\nconst el=toolEls[id];if(!el)return;\nconst icon=el.querySelector(\".tool-icon\"),body=el.querySelector(\".tool-body\");\nif(isError){icon.className=\"tool-icon error\"}\nconst texts=result&&result.content?result.content.filter(c=>c&&c.type===\"text\").map(c=>c.text):[];\nif(texts.length){body.textContent=texts.join(\"\\\\n\");el.classList.add(\"expanded\",\"mini\")}}\n\nfunction finishTool(id,isError){\nconst el=toolEls[id];if(!el)return;\nconst icon=el.querySelector(\".tool-icon\");icon.className=\"tool-icon \"+(isError?\"error\":\"done\");\nconst body=el.querySelector(\".tool-body\");if(isError&&!body.textContent.trim())body.textContent=\"(no output)\"}\n\nfunction handle(d){\nswitch(d.type){\ncase\"agent_start\":curAssistant=null;break;\ncase\"text_delta\":{const ad=getAssistantDiv();ad.innerHTML+=md(d.delta);scrollDown();break}\ncase\"thinking_delta\":{let th=document.getElementById(\"think\"+d.contentIndex);if(!th){th=document.createElement(\"div\");th.className=\"thinking\";th.id=\"think\"+d.contentIndex;msgs.appendChild(th)}th.textContent+=d.delta;scrollDown();break}\ncase\"tool_execution_start\":startTool(d);break;\ncase\"tool_execution_update\":updateTool(d.toolCallId,d.result,false);break;\ncase\"tool_execution_end\":finishTool(d.toolCallId,d.isError);if(d.result)updateTool(d.toolCallId,d.result,d.isError);break;\ncase\"agent_end\":{let s=\"\";if(d.usage){s=\" · \"+fmt(d.usage.input)+\" in · \"+fmt(d.usage.output)+\" out\";if(d.usage.cost)s+=\" · $\"+d.usage.cost.total.toFixed(4)}addMsg(\"system\",\"Done\"+s);break}\ncase\"compaction\":addMsg(\"system\",\"Compacting...\");break;\ncase\"error\":addMsg(\"system\",\"Error: \"+esc(d.message));break;\ncase\"text_start\":case\"thinking_start\":case\"text_end\":case\"thinking_end\":case\"toolcall_start\":case\"toolcall_end\":case\"toolcall_delta\":break}}\n\nf.onsubmit=async e=>{e.preventDefault();if(busy)return;const text=prompt.value.trim();if(!text)return;prompt.value=\"\";addMsg(\"user\",text);busy=true;send.disabled=true;spinner.className=\"spinner active\";\ntry{const r=await fetch(\"/api/prompt\",{method:\"POST\",headers:{\"content-type\":\"application/json\"},body:JSON.stringify({text})});\nif(!r.ok){addMsg(\"system\",\"Error: \"+r.status);return}\nconst reader=r.body.getReader(),decoder=new TextDecoder();let buf=\"\";\nwhile(true){const{done,value}=await reader.read();if(done)break;buf+=decoder.decode(value,{stream:true});const lines=buf.split(\"\\\\n\");buf=lines.pop()||\"\";\nfor(const line of lines){if(!line.trim())continue;try{handle(JSON.parse(line))}catch{}}}}\ncatch(err){addMsg(\"system\",\"Error: \"+esc(err.message))}finally{busy=false;send.disabled=false;spinner.className=\"spinner\"}};\n\nfetch(\"/api/stats\").then(r=>r.json()).then(s=>{\nstatSessions.textContent=s.sessions+\" sessions\";\nstatCost.textContent=\"$\"+s.cost.toFixed(2);\n}).catch(()=>{});\n\nfetch(\"/api/session-info\").then(r=>r.json()).then(s=>{\nsessionInfo.textContent=s.id?(\"session: \"+s.id.slice(0,8)+(s.name?\" | \"+s.name:\"\")):\"\";\n}).catch(()=>{});\n</script>\n</body>\n</html>`;\n\nfunction sendEvent(res: ServerResponse, data: Record<string, unknown>): void {\n\tres.write(`${JSON.stringify(data)}\\n`);\n}\n\nexport async function runWebMode(runtime: AgentSessionRuntime): Promise<void> {\n\tconst session = runtime.session;\n\tconst port = getPort();\n\n\tconst server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n\t\tif (!checkAuth(req, res)) return;\n\n\t\tconst url = req.url ?? \"/\";\n\n\t\tif (req.method === \"POST\" && url === \"/api/prompt\") {\n\t\t\tconst chunks: Buffer[] = [];\n\t\t\treq.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n\t\t\tawait new Promise<void>((resolve) => req.on(\"end\", resolve));\n\t\t\tconst body = JSON.parse(Buffer.concat(chunks).toString());\n\t\t\tconst text = String(body.text ?? \"\").trim();\n\t\t\tif (!text) {\n\t\t\t\tres.writeHead(400);\n\t\t\t\tres.end(JSON.stringify({ error: \"missing text\" }));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tres.writeHead(200, { \"content-type\": \"text/plain; charset=utf-8\", \"cache-control\": \"no-cache\" });\n\n\t\t\tconst unsubscribe = session.subscribe((event: AgentSessionEvent) => {\n\t\t\t\tconst events = toSerializableEvents(event);\n\t\t\t\tfor (const evt of events) {\n\t\t\t\t\tsendEvent(res, evt);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\ttry {\n\t\t\t\tawait session.prompt(text);\n\t\t\t} catch (err) {\n\t\t\t\tsendEvent(res, { type: \"error\", message: String(err) });\n\t\t\t} finally {\n\t\t\t\tunsubscribe();\n\t\t\t\tres.end();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/stats\") {\n\t\t\tconst stats = await session.getUsageStats();\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({ sessions: stats.sessions, cost: stats.cost, input: stats.input, output: stats.output }),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/session-info\") {\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tid: session.sessionId,\n\t\t\t\t\tname: session.sessionManager.getSessionName(),\n\t\t\t\t\tfile: session.sessionFile,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tres.writeHead(200, { \"content-type\": \"text/html; charset=utf-8\" });\n\t\tres.end(HTML);\n\t});\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tserver.listen(port, \"0.0.0.0\", () => resolve());\n\t\tserver.on(\"error\", reject);\n\t});\n\n\tconst addr = server.address();\n\tif (!addr || typeof addr === \"string\") {\n\t\tconsole.error(\"Failed to start web server\");\n\t\treturn;\n\t}\n\n\tconst consolePort = `http://127.0.0.1:${addr.port}`;\n\tconst lan = getLocalIP();\n\tconsole.log(`\\n pi web UI: ${consolePort}`);\n\tif (lan !== \"127.0.0.1\") {\n\t\tconsole.log(` LAN: http://${lan}:${addr.port}`);\n\t}\n\tconsole.log(` session: ${session.sessionId.slice(0, 8)}`);\n\tconsole.log(\" (listening on all interfaces)\");\n\tif (PASSWORD) {\n\t\tconsole.log(` auth: Basic (user \"pi\", password from PI_WEB_PASSWORD)`);\n\t} else {\n\t\tconsole.log(\" auth: none (set PI_WEB_PASSWORD to enable)\");\n\t}\n\tconsole.log();\n\n\t// Keep process alive\n\tawait new Promise(() => {});\n\n\tserver.close();\n}\n\nfunction toSerializableEvents(event: AgentSessionEvent): Record<string, unknown>[] {\n\tswitch (event.type) {\n\t\tcase \"agent_start\":\n\t\t\treturn [{ type: \"agent_start\" }];\n\t\tcase \"agent_end\": {\n\t\t\tconst lastAssistant = event.messages.filter((m) => m.role === \"assistant\").at(-1);\n\t\t\tconst usage = lastAssistant && \"usage\" in lastAssistant ? lastAssistant.usage : undefined;\n\t\t\treturn [{ type: \"agent_end\", usage }];\n\t\t}\n\t\tcase \"message_update\": {\n\t\t\tconst ame: Record<string, unknown> = event.assistantMessageEvent;\n\t\t\tconst serialized = serializeAssistantMessageEvent(ame);\n\t\t\treturn serialized;\n\t\t}\n\t\tcase \"tool_execution_start\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_start\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\ttoolName: event.toolName,\n\t\t\t\t\targs: event.args,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"tool_execution_update\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_update\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\tresult: event.partialResult,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"tool_execution_end\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_end\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\tresult: event.result,\n\t\t\t\t\tisError: event.isError,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"compaction_start\":\n\t\tcase \"compaction_end\":\n\t\t\treturn [{ type: \"compaction\" }];\n\t\tdefault:\n\t\t\treturn [];\n\t}\n}\n\nfunction serializeAssistantMessageEvent(event: Record<string, unknown>): Record<string, unknown>[] {\n\tconst type = event.type as string;\n\tswitch (type) {\n\t\tcase \"start\":\n\t\t\treturn [{ type: \"agent_start\" }];\n\t\tcase \"text_start\":\n\t\t\treturn [{ type: \"text_start\", contentIndex: event.contentIndex }];\n\t\tcase \"text_delta\":\n\t\t\treturn [{ type: \"text_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"text_end\":\n\t\t\treturn [{ type: \"text_end\", content: event.content, contentIndex: event.contentIndex }];\n\t\tcase \"thinking_start\":\n\t\t\treturn [{ type: \"thinking_start\", contentIndex: event.contentIndex }];\n\t\tcase \"thinking_delta\":\n\t\t\treturn [{ type: \"thinking_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"thinking_end\":\n\t\t\treturn [{ type: \"thinking_end\", content: event.content, contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_start\":\n\t\t\treturn [{ type: \"toolcall_start\", contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_delta\":\n\t\t\treturn [{ type: \"toolcall_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_end\":\n\t\t\treturn [{ type: \"toolcall_end\", toolCall: event.toolCall, contentIndex: event.contentIndex }];\n\t\tcase \"done\":\n\t\t\treturn [{ type: \"done\", reason: event.reason }];\n\t\tcase \"error\":\n\t\t\treturn [{ type: \"error\", reason: event.reason }];\n\t\tdefault:\n\t\t\treturn [];\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"web-mode.d.ts","sourceRoot":"","sources":["../../../src/modes/web/web-mode.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAwP/E,wBAAsB,UAAU,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgJ5E","sourcesContent":["import { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { networkInterfaces } from \"node:os\";\nimport type { AgentSessionEvent } from \"../../core/agent-session.ts\";\nimport type { AgentSessionRuntime } from \"../../core/agent-session-runtime.ts\";\n\nconst PASSWORD = process.env.PI_WEB_PASSWORD;\n\nfunction checkAuth(req: IncomingMessage, res: ServerResponse): boolean {\n\tif (!PASSWORD) return true;\n\n\tconst auth = req.headers.authorization;\n\tif (auth) {\n\t\tconst [scheme, credentials] = auth.split(\" \");\n\t\tif (scheme === \"Basic\" && credentials) {\n\t\t\tconst decoded = Buffer.from(credentials, \"base64\").toString(\"utf-8\");\n\t\t\tconst [, password] = decoded.split(\":\");\n\t\t\tif (password === PASSWORD) return true;\n\t\t}\n\t}\n\n\tres.writeHead(401, {\n\t\t\"www-authenticate\": 'Basic realm=\"pi\", charset=\"UTF-8\"',\n\t\t\"content-type\": \"text/plain\",\n\t});\n\tres.end(\"Unauthorized\");\n\treturn false;\n}\n\nfunction getLocalIP(): string {\n\tconst interfaces = networkInterfaces();\n\tfor (const iface of Object.values(interfaces)) {\n\t\tif (!iface) continue;\n\t\tfor (const addr of iface) {\n\t\t\tif (addr.family === \"IPv4\" && !addr.internal) {\n\t\t\t\treturn addr.address;\n\t\t\t}\n\t\t}\n\t}\n\treturn \"127.0.0.1\";\n}\n\nfunction getPort(): number {\n\tconst env = process.env.PORT;\n\tif (env) {\n\t\tconst p = parseInt(env, 10);\n\t\tif (!Number.isNaN(p) && p > 0 && p < 65536) return p;\n\t}\n\treturn 0;\n}\n\nconst HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\n<title>pi</title>\n<style>\n*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\nhtml{font-size:14px;-webkit-text-size-adjust:100%}\nbody{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",system-ui,sans-serif;background:#0b1014;color:#c9d1d9;display:flex;flex-direction:column;height:100dvh;overflow:hidden}\n#header{display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background:#0d121a;border-bottom:1px solid #21262d;flex-shrink:0;gap:10px}\n#header-left{display:flex;align-items:center;gap:10px}\n#header h1{font-size:14px;font-weight:600;color:#e6edf3}\n#session-info{font-size:11px;color:#6e7681;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n#header-stats{display:flex;gap:12px;font-size:11px;color:#7d8590}\n#msgs{flex:1;overflow-y:auto;padding:16px 20px;scroll-behavior:smooth;display:flex;flex-direction:column;gap:14px}\n#msgs::-webkit-scrollbar{width:6px}\n#msgs::-webkit-scrollbar-track{background:transparent}\n#msgs::-webkit-scrollbar-thumb{background:#30363d;border-radius:3px}\n.msg-group{display:flex;flex-direction:column;gap:2px;animation:fadeIn .15s ease;max-width:86%}\n.msg-group.user{max-width:82%;align-self:flex-end}\n@keyframes fadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}\n.msg-label{font-size:10.5px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;padding:0 2px}\n.msg-group.user .msg-label{color:#7ec9ff;text-align:right}\n.msg-group.assistant .msg-label{color:#7d8590}\n.msg{border-radius:8px;font-size:13.5px;line-height:1.6;overflow-wrap:break-word;padding:10px 14px}\n.msg.user{background:#1b4a8b;color:#e6edf3;border-bottom-right-radius:2px}\n.msg.assistant{background:#1a1f2b;color:#c9d1d9;border-bottom-left-radius:2px;border:1px solid #21262d}\n.msg.system{background:transparent;color:#6e7681;font-size:12px;text-align:center;max-width:100%;padding:4px 0}\n.msg p{margin:0 0 6px}\n.msg p:last-child{margin-bottom:0}\n.msg strong{color:#f2a65a;font-weight:600}\n.msg em{color:#c9d1d9;font-style:italic}\n.msg code{font-family:\"JetBrains Mono\",\"Fira Code\",monospace;font-size:12px;background:#2d333b;color:#c9d1d9;padding:1px 5px;border-radius:3px}\n.msg pre{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:10px 12px;margin:8px 0;overflow-x:auto;font-size:12px;line-height:1.5}\n.msg pre code{background:transparent;padding:0;border-radius:0;font-size:inherit}\n.msg h1,.msg h2,.msg h3{margin:8px 0 4px;font-weight:600;color:#e6edf3}\n.msg h1{font-size:18px;border-bottom:1px solid #21262d;padding-bottom:4px}\n.msg h2{font-size:15px}\n.msg h3{font-size:13.5px;color:#c9d1d9}\n.msg ul,.msg ol{padding-left:20px;margin:4px 0 8px}\n.msg li{margin:2px 0}\n.msg hr{border:none;border-top:1px solid #21262d;margin:10px 0}\n.tool{border:1px solid #21262d;border-radius:8px;overflow:hidden;transition:border-color .2s}\n.tool:hover{border-color:#30363d}\n.tool-header{display:flex;align-items:center;justify-content:space-between;padding:7px 10px;background:#0d121a;cursor:pointer;user-select:none;gap:8px;transition:background .15s}\n.tool-header:hover{background:#161b22}\n.tool-icon{width:7px;height:7px;border-radius:50%;flex-shrink:0}\n.tool-icon.pending{background:#d29922;animation:pulse 1.5s infinite}\n.tool-icon.done{background:#3fb950}\n.tool-icon.error{background:#f85149}\n@keyframes pulse{0%,100%{opacity:.4}50%{opacity:1}}\n.tool-title{flex:1;font-size:12px;font-weight:500;color:#c9d1d9;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.tool-arrow{color:#6e7681;font-size:9px;transition:transform .2s}\n.tool.expanded .tool-arrow{transform:rotate(90deg)}\n.tool-body{font-family:\"JetBrains Mono\",\"Fira Code\",monospace;font-size:12px;line-height:1.55;padding:8px 10px;background:#0b1014;white-space:pre-wrap;word-break:break-all;overflow-x:auto;max-height:30vh;overflow-y:auto;display:none}\n.tool-body::-webkit-scrollbar{width:4px;height:4px}\n.tool-body::-webkit-scrollbar-thumb{background:#30363d;border-radius:2px}\n.tool.expanded .tool-body{display:block}\n.tool.mini .tool-body{max-height:12vh}\n.thinking{padding:5px 10px;border-left:2px solid #30363d;font-size:11.5px;color:#6e7681;font-style:italic}\n#input-area{background:#0d121a;border-top:1px solid #21262d;padding:12px 16px;flex-shrink:0}\n#input-area form{display:flex;gap:8px;max-width:900px;margin:0 auto}\n#prompt{flex:1;background:#161b22;border:1px solid #30363d;border-radius:8px;padding:9px 12px;color:#c9d1d9;font-size:13.5px;font-family:inherit;outline:none;transition:border-color .2s,box-shadow .2s}\n#prompt:focus{border-color:#4493f8;box-shadow:0 0 0 3px rgba(68,147,248,.15)}\n#prompt::placeholder{color:#484f58}\nbutton{background:#1f6feb;color:#fff;border:none;border-radius:8px;padding:9px 16px;font-size:13px;font-weight:500;cursor:pointer;transition:background .15s;flex-shrink:0}\nbutton:hover{background:#2b83ff}\nbutton:disabled{background:#21262d;color:#6e7681;cursor:not-allowed}\n.btn-mini{font-size:11px;padding:4px 10px;background:#21262d}\n.btn-mini:hover{background:#30363d}\n.spinner{display:none;width:16px;height:16px;border:2px solid #30363d;border-top-color:#4493f8;border-radius:50%;animation:spin .6s linear infinite}\n.spinner.active{display:inline-block}\n@keyframes spin{to{transform:rotate(360deg)}}\n@media(max-width:600px){\nhtml{font-size:13px}\n#msgs{padding:10px;gap:10px}\n.msg-group{max-width:94%}\n.msg-group.user{max-width:92%}\n#input-area{padding:8px 10px}\n#prompt{padding:7px 10px}\nbutton{padding:7px 12px}\n}\n</style>\n</head>\n<body>\n<div id=\"header\">\n<div id=\"header-left\"><h1>pi</h1><span id=\"session-info\"></span></div>\n<div id=\"header-stats\"><span id=\"stat-sessions\"></span><span id=\"stat-cost\"></span></div>\n</div>\n<div id=\"msgs\"></div>\n<div id=\"input-area\">\n<form id=\"f\" autocomplete=\"off\">\n<input id=\"prompt\" type=\"text\" placeholder=\"Message pi...\" autofocus autocomplete=\"off\">\n<button id=\"send\">Send</button>\n<span class=\"spinner\" id=\"spinner\"></span>\n</form>\n</div>\n<script>\nconst msgs=document.getElementById(\"msgs\"),f=document.getElementById(\"f\"),prompt=document.getElementById(\"prompt\"),\nsend=document.getElementById(\"send\"),spinner=document.getElementById(\"spinner\"),\nstatSessions=document.getElementById(\"stat-sessions\"),statCost=document.getElementById(\"stat-cost\"),\nsessionInfo=document.getElementById(\"session-info\");\nlet busy=false,toolEls={},curAssistant=null;\n\nfunction scrollDown(){msgs.scrollTop=msgs.scrollHeight}\nfunction esc(s){if(!s)return\"\";return s.replace(/&/g,\"&\").replace(/</g,\"<\").replace(/>/g,\">\")}\nfunction fmt(n){if(n<1e3)return n;if(n<1e4)return(n/1e3).toFixed(1)+\"k\";return Math.round(n/1e3)+\"k\"}\n\nfunction md(text){\nlet s=esc(text);\nconst blocks=[];\ns=s.replace(/\\`\\`\\`(\\\\w*)\\\\n([\\\\s\\\\S]*?)\\\\n\\`\\`\\`/g,(_,lang,code)=>{blocks.push('<pre><code>'+code+'</code></pre>');return'\\\\x00B'+(blocks.length-1)+'\\\\x00B'});\ns=s.replace(/\\`([^\\`]+)\\`/g,'<code>$1</code>');\ns=s.replace(/\\\\*\\\\*(.+?)\\\\*\\\\*/g,'<strong>$1</strong>');\ns=s.replace(/\\\\*(.+?)\\\\*/g,'<em>$1</em>');\ns=s.replace(/^### (.+$)/gm,'<h3>$1</h3>');\ns=s.replace(/^## (.+$)/gm,'<h2>$1</h2>');\ns=s.replace(/^# (.+$)/gm,'<h1>$1</h1>');\ns=s.replace(/^- (.+$)/gm,'<li>$1</li>');\ns=s.replace(/^> (.+$)/gm,'<blockquote>$1</blockquote>');\ns=s.replace(/((?:<li>.*<\\\\/li>\\\\n?)+)/g,'<ul>$1</ul>');\ns=s.replace(/^(---+|\\\\*\\\\*\\\\*+|___+)$/gm,'<hr>');\ns=s.replace(/\\\\x00B(\\\\d+)\\\\x00B/g,(_,i)=>blocks[parseInt(i)]);\ns=s.replace(/\\\\n\\\\n/g,'<br><br>');\ns=s.replace(/\\\\n/g,'<br>');\nreturn s\n}\n\nfunction addMsg(role,text){\nconst g=document.createElement(\"div\");g.className=\"msg-group \"+role;\nif(role===\"user\"){g.innerHTML='<div class=\"msg-label\">You</div><div class=\"msg user\">'+esc(text)+'</div>'}\nelse if(role===\"assistant\"){g.innerHTML='<div class=\"msg-label\">pi</div><div class=\"msg assistant\"></div>'}\nelse{g.innerHTML='<div class=\"msg system\">'+text+'</div>'}\nmsgs.appendChild(g);scrollDown();return g}\n\nfunction getAssistantDiv(){if(!curAssistant){curAssistant=addMsg(\"assistant\",\"\")}return curAssistant.querySelector(\".msg\")}\n\nfunction startTool(e){\nconst el=document.createElement(\"div\");el.className=\"tool\";el.id=\"t\"+e.toolCallId;\nconst title=esc(e.toolName)+(e.args?' '+esc(String(e.args.command||e.args.path||e.args.file_path||\"\").slice(0,80)):'');\nel.innerHTML='<div class=\"tool-header\"><span class=\"tool-icon pending\"></span><span class=\"tool-title\">'+title+'</span><span class=\"tool-arrow\">▶</span></div><div class=\"tool-body\"></div>';\nel.querySelector(\".tool-header\").onclick=()=>el.classList.toggle(\"expanded\");\nmsgs.appendChild(el);toolEls[e.toolCallId]=el;scrollDown()}\n\nfunction updateTool(id,result,isError){\nconst el=toolEls[id];if(!el)return;\nconst icon=el.querySelector(\".tool-icon\"),body=el.querySelector(\".tool-body\");\nif(isError){icon.className=\"tool-icon error\"}\nconst texts=result&&result.content?result.content.filter(c=>c&&c.type===\"text\").map(c=>c.text):[];\nif(texts.length){body.textContent=texts.join(\"\\\\n\");el.classList.add(\"expanded\",\"mini\")}}\n\nfunction finishTool(id,isError){\nconst el=toolEls[id];if(!el)return;\nconst icon=el.querySelector(\".tool-icon\");icon.className=\"tool-icon \"+(isError?\"error\":\"done\");\nconst body=el.querySelector(\".tool-body\");if(isError&&!body.textContent.trim())body.textContent=\"(no output)\"}\n\nfunction handle(d){\nswitch(d.type){\ncase\"agent_start\":curAssistant=null;break;\ncase\"text_delta\":{const ad=getAssistantDiv();ad.innerHTML+=md(d.delta);scrollDown();break}\ncase\"thinking_delta\":{let th=document.getElementById(\"think\"+d.contentIndex);if(!th){th=document.createElement(\"div\");th.className=\"thinking\";th.id=\"think\"+d.contentIndex;msgs.appendChild(th)}th.textContent+=d.delta;scrollDown();break}\ncase\"tool_execution_start\":startTool(d);break;\ncase\"tool_execution_update\":updateTool(d.toolCallId,d.result,false);break;\ncase\"tool_execution_end\":finishTool(d.toolCallId,d.isError);if(d.result)updateTool(d.toolCallId,d.result,d.isError);break;\ncase\"agent_end\":{let s=\"\";if(d.usage){s=\" · \"+fmt(d.usage.input)+\" in · \"+fmt(d.usage.output)+\" out\";if(d.usage.cost)s+=\" · $\"+d.usage.cost.total.toFixed(4)}addMsg(\"system\",\"Done\"+s);break}\ncase\"compaction\":addMsg(\"system\",\"Compacting...\");break;\ncase\"error\":addMsg(\"system\",\"Error: \"+esc(d.message));break;\ncase\"text_start\":case\"thinking_start\":case\"text_end\":case\"thinking_end\":case\"toolcall_start\":case\"toolcall_end\":case\"toolcall_delta\":break}}\n\nf.onsubmit=async e=>{e.preventDefault();if(busy)return;const text=prompt.value.trim();if(!text)return;prompt.value=\"\";addMsg(\"user\",text);busy=true;send.disabled=true;spinner.className=\"spinner active\";\ntry{const r=await fetch(\"/api/prompt\",{method:\"POST\",headers:{\"content-type\":\"application/json\"},body:JSON.stringify({text})});\nif(!r.ok){addMsg(\"system\",\"Error: \"+r.status);return}\nconst reader=r.body.getReader(),decoder=new TextDecoder();let buf=\"\";\nwhile(true){const{done,value}=await reader.read();if(done)break;buf+=decoder.decode(value,{stream:true});const lines=buf.split(\"\\\\n\");buf=lines.pop()||\"\";\nfor(const line of lines){if(!line.trim())continue;try{handle(JSON.parse(line))}catch{}}}}\ncatch(err){addMsg(\"system\",\"Error: \"+esc(err.message))}finally{busy=false;send.disabled=false;spinner.className=\"spinner\"}};\n\nfetch(\"/api/messages\").then(r=>r.json()).then(events=>{\nfor(const e of events){\nif(e.type===\"user\"){addMsg(\"user\",e.text)}\nelse{handle(e)}\n}\n}).catch(()=>{});\n\nfetch(\"/api/stats\").then(r=>r.json()).then(s=>{\nstatSessions.textContent=s.sessions+\" sessions\";\nstatCost.textContent=\"$\"+s.cost.toFixed(2);\n}).catch(()=>{});\n\nfetch(\"/api/session-info\").then(r=>r.json()).then(s=>{\nsessionInfo.textContent=s.id?(\"session: \"+s.id.slice(0,8)+(s.name?\" | \"+s.name:\"\")):\"\";\n}).catch(()=>{});\n</script>\n</body>\n</html>`;\n\nfunction sendEvent(res: ServerResponse, data: Record<string, unknown>): void {\n\tres.write(`${JSON.stringify(data)}\\n`);\n}\n\nexport async function runWebMode(runtime: AgentSessionRuntime): Promise<void> {\n\tconst session = runtime.session;\n\tconst port = getPort();\n\n\tconst server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n\t\tif (!checkAuth(req, res)) return;\n\n\t\tconst url = req.url ?? \"/\";\n\n\t\tif (req.method === \"POST\" && url === \"/api/prompt\") {\n\t\t\tconst chunks: Buffer[] = [];\n\t\t\treq.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n\t\t\tawait new Promise<void>((resolve) => req.on(\"end\", resolve));\n\t\t\tconst body = JSON.parse(Buffer.concat(chunks).toString());\n\t\t\tconst text = String(body.text ?? \"\").trim();\n\t\t\tif (!text) {\n\t\t\t\tres.writeHead(400);\n\t\t\t\tres.end(JSON.stringify({ error: \"missing text\" }));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tres.writeHead(200, { \"content-type\": \"text/plain; charset=utf-8\", \"cache-control\": \"no-cache\" });\n\n\t\t\tconst unsubscribe = session.subscribe((event: AgentSessionEvent) => {\n\t\t\t\tconst events = toSerializableEvents(event);\n\t\t\t\tfor (const evt of events) {\n\t\t\t\t\tsendEvent(res, evt);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\ttry {\n\t\t\t\tawait session.prompt(text);\n\t\t\t} catch (err) {\n\t\t\t\tsendEvent(res, { type: \"error\", message: String(err) });\n\t\t\t} finally {\n\t\t\t\tunsubscribe();\n\t\t\t\tres.end();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/stats\") {\n\t\t\tconst stats = await session.getUsageStats();\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({ sessions: stats.sessions, cost: stats.cost, input: stats.input, output: stats.output }),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/session-info\") {\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tid: session.sessionId,\n\t\t\t\t\tname: session.sessionManager.getSessionName(),\n\t\t\t\t\tfile: session.sessionFile,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/messages\") {\n\t\t\tconst entries = session.sessionManager.getEntries();\n\t\t\tconst events: Record<string, unknown>[] = [];\n\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.type !== \"message\") continue;\n\t\t\t\tconst msg = (entry as { message: { role: string; content: unknown; usage?: unknown } }).message;\n\t\t\t\tif (!msg) continue;\n\n\t\t\t\tif (msg.role === \"user\") {\n\t\t\t\t\tconst content = typeof msg.content === \"string\" ? msg.content : \"\";\n\t\t\t\t\tevents.push({ type: \"user\", text: content });\n\t\t\t\t} else if (msg.role === \"assistant\") {\n\t\t\t\t\tconst content = Array.isArray(msg.content) ? msg.content : [];\n\t\t\t\t\tfor (const block of content as Array<{ type: string; text?: string; toolCall?: unknown }>) {\n\t\t\t\t\t\tif (block.type === \"text\" && block.text) {\n\t\t\t\t\t\t\tevents.push({ type: \"text_delta\", delta: block.text });\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (block.type === \"toolCall\" && block.toolCall) {\n\t\t\t\t\t\t\tconst tc = block.toolCall as { toolCallId?: string; toolName?: string; input?: unknown };\n\t\t\t\t\t\t\tevents.push({\n\t\t\t\t\t\t\t\ttype: \"tool_execution_start\",\n\t\t\t\t\t\t\t\ttoolCallId: tc.toolCallId,\n\t\t\t\t\t\t\t\ttoolName: tc.toolName,\n\t\t\t\t\t\t\t\targs: tc.input,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (msg.role === \"toolResult\") {\n\t\t\t\t\tconst content = (msg as { toolCallId?: string; content?: Array<{ type: string; text?: string }> })\n\t\t\t\t\t\t.content;\n\t\t\t\t\tevents.push({\n\t\t\t\t\t\ttype: \"tool_execution_end\",\n\t\t\t\t\t\ttoolCallId: (msg as { toolCallId?: string }).toolCallId,\n\t\t\t\t\t\tresult: { content: content ?? [] },\n\t\t\t\t\t\tisError: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst usage = getSessionTokenCount(session);\n\t\t\tevents.push({ type: \"agent_end\", usage });\n\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(JSON.stringify(events));\n\t\t\treturn;\n\t\t}\n\n\t\tres.writeHead(200, { \"content-type\": \"text/html; charset=utf-8\" });\n\t\tres.end(HTML);\n\t});\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tserver.listen(port, \"0.0.0.0\", () => resolve());\n\t\tserver.on(\"error\", reject);\n\t});\n\n\tconst addr = server.address();\n\tif (!addr || typeof addr === \"string\") {\n\t\tconsole.error(\"Failed to start web server\");\n\t\treturn;\n\t}\n\n\tconst consolePort = `http://127.0.0.1:${addr.port}`;\n\tconst lan = getLocalIP();\n\tconsole.log(`\\n pi web UI: ${consolePort}`);\n\tif (lan !== \"127.0.0.1\") {\n\t\tconsole.log(` LAN: http://${lan}:${addr.port}`);\n\t}\n\tconsole.log(` session: ${session.sessionId.slice(0, 8)}`);\n\tconsole.log(\" (listening on all interfaces)\");\n\tif (PASSWORD) {\n\t\tconsole.log(` auth: Basic (user \"pi\", password from PI_WEB_PASSWORD)`);\n\t} else {\n\t\tconsole.log(\" auth: none (set PI_WEB_PASSWORD to enable)\");\n\t}\n\tconsole.log();\n\n\t// Keep process alive\n\tawait new Promise(() => {});\n\n\tserver.close();\n}\n\nfunction getSessionTokenCount(session: AgentSessionRuntime[\"session\"]): Record<string, unknown> | undefined {\n\tconst entries = session.sessionManager.getEntries();\n\tlet input = 0;\n\tlet output = 0;\n\tlet costTotal = 0;\n\tlet hasUsage = false;\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"message\") continue;\n\t\tconst msg = (\n\t\t\tentry as { message: { role: string; usage?: { input: number; output: number; cost: { total: number } } } }\n\t\t).message;\n\t\tif (msg?.role === \"assistant\" && msg.usage) {\n\t\t\tinput += msg.usage.input;\n\t\t\toutput += msg.usage.output;\n\t\t\tcostTotal += msg.usage.cost.total;\n\t\t\thasUsage = true;\n\t\t}\n\t}\n\treturn hasUsage ? { input, output, cost: { total: costTotal } } : undefined;\n}\n\nfunction toSerializableEvents(event: AgentSessionEvent): Record<string, unknown>[] {\n\tswitch (event.type) {\n\t\tcase \"agent_start\":\n\t\t\treturn [{ type: \"agent_start\" }];\n\t\tcase \"agent_end\": {\n\t\t\tconst lastAssistant = event.messages.filter((m) => m.role === \"assistant\").at(-1);\n\t\t\tconst usage = lastAssistant && \"usage\" in lastAssistant ? lastAssistant.usage : undefined;\n\t\t\treturn [{ type: \"agent_end\", usage }];\n\t\t}\n\t\tcase \"message_update\": {\n\t\t\tconst ame: Record<string, unknown> = event.assistantMessageEvent;\n\t\t\tconst serialized = serializeAssistantMessageEvent(ame);\n\t\t\treturn serialized;\n\t\t}\n\t\tcase \"tool_execution_start\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_start\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\ttoolName: event.toolName,\n\t\t\t\t\targs: event.args,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"tool_execution_update\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_update\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\tresult: event.partialResult,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"tool_execution_end\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_end\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\tresult: event.result,\n\t\t\t\t\tisError: event.isError,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"compaction_start\":\n\t\tcase \"compaction_end\":\n\t\t\treturn [{ type: \"compaction\" }];\n\t\tdefault:\n\t\t\treturn [];\n\t}\n}\n\nfunction serializeAssistantMessageEvent(event: Record<string, unknown>): Record<string, unknown>[] {\n\tconst type = event.type as string;\n\tswitch (type) {\n\t\tcase \"start\":\n\t\t\treturn [{ type: \"agent_start\" }];\n\t\tcase \"text_start\":\n\t\t\treturn [{ type: \"text_start\", contentIndex: event.contentIndex }];\n\t\tcase \"text_delta\":\n\t\t\treturn [{ type: \"text_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"text_end\":\n\t\t\treturn [{ type: \"text_end\", content: event.content, contentIndex: event.contentIndex }];\n\t\tcase \"thinking_start\":\n\t\t\treturn [{ type: \"thinking_start\", contentIndex: event.contentIndex }];\n\t\tcase \"thinking_delta\":\n\t\t\treturn [{ type: \"thinking_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"thinking_end\":\n\t\t\treturn [{ type: \"thinking_end\", content: event.content, contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_start\":\n\t\t\treturn [{ type: \"toolcall_start\", contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_delta\":\n\t\t\treturn [{ type: \"toolcall_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_end\":\n\t\t\treturn [{ type: \"toolcall_end\", toolCall: event.toolCall, contentIndex: event.contentIndex }];\n\t\tcase \"done\":\n\t\t\treturn [{ type: \"done\", reason: event.reason }];\n\t\tcase \"error\":\n\t\t\treturn [{ type: \"error\", reason: event.reason }];\n\t\tdefault:\n\t\t\treturn [];\n\t}\n}\n"]}
|
|
@@ -221,6 +221,13 @@ while(true){const{done,value}=await reader.read();if(done)break;buf+=decoder.dec
|
|
|
221
221
|
for(const line of lines){if(!line.trim())continue;try{handle(JSON.parse(line))}catch{}}}}
|
|
222
222
|
catch(err){addMsg("system","Error: "+esc(err.message))}finally{busy=false;send.disabled=false;spinner.className="spinner"}};
|
|
223
223
|
|
|
224
|
+
fetch("/api/messages").then(r=>r.json()).then(events=>{
|
|
225
|
+
for(const e of events){
|
|
226
|
+
if(e.type==="user"){addMsg("user",e.text)}
|
|
227
|
+
else{handle(e)}
|
|
228
|
+
}
|
|
229
|
+
}).catch(()=>{});
|
|
230
|
+
|
|
224
231
|
fetch("/api/stats").then(r=>r.json()).then(s=>{
|
|
225
232
|
statSessions.textContent=s.sessions+" sessions";
|
|
226
233
|
statCost.textContent="$"+s.cost.toFixed(2);
|
|
@@ -287,6 +294,53 @@ export async function runWebMode(runtime) {
|
|
|
287
294
|
}));
|
|
288
295
|
return;
|
|
289
296
|
}
|
|
297
|
+
if (req.method === "GET" && url === "/api/messages") {
|
|
298
|
+
const entries = session.sessionManager.getEntries();
|
|
299
|
+
const events = [];
|
|
300
|
+
for (const entry of entries) {
|
|
301
|
+
if (entry.type !== "message")
|
|
302
|
+
continue;
|
|
303
|
+
const msg = entry.message;
|
|
304
|
+
if (!msg)
|
|
305
|
+
continue;
|
|
306
|
+
if (msg.role === "user") {
|
|
307
|
+
const content = typeof msg.content === "string" ? msg.content : "";
|
|
308
|
+
events.push({ type: "user", text: content });
|
|
309
|
+
}
|
|
310
|
+
else if (msg.role === "assistant") {
|
|
311
|
+
const content = Array.isArray(msg.content) ? msg.content : [];
|
|
312
|
+
for (const block of content) {
|
|
313
|
+
if (block.type === "text" && block.text) {
|
|
314
|
+
events.push({ type: "text_delta", delta: block.text });
|
|
315
|
+
}
|
|
316
|
+
if (block.type === "toolCall" && block.toolCall) {
|
|
317
|
+
const tc = block.toolCall;
|
|
318
|
+
events.push({
|
|
319
|
+
type: "tool_execution_start",
|
|
320
|
+
toolCallId: tc.toolCallId,
|
|
321
|
+
toolName: tc.toolName,
|
|
322
|
+
args: tc.input,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
else if (msg.role === "toolResult") {
|
|
328
|
+
const content = msg
|
|
329
|
+
.content;
|
|
330
|
+
events.push({
|
|
331
|
+
type: "tool_execution_end",
|
|
332
|
+
toolCallId: msg.toolCallId,
|
|
333
|
+
result: { content: content ?? [] },
|
|
334
|
+
isError: false,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
const usage = getSessionTokenCount(session);
|
|
339
|
+
events.push({ type: "agent_end", usage });
|
|
340
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
341
|
+
res.end(JSON.stringify(events));
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
290
344
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
291
345
|
res.end(HTML);
|
|
292
346
|
});
|
|
@@ -318,6 +372,25 @@ export async function runWebMode(runtime) {
|
|
|
318
372
|
await new Promise(() => { });
|
|
319
373
|
server.close();
|
|
320
374
|
}
|
|
375
|
+
function getSessionTokenCount(session) {
|
|
376
|
+
const entries = session.sessionManager.getEntries();
|
|
377
|
+
let input = 0;
|
|
378
|
+
let output = 0;
|
|
379
|
+
let costTotal = 0;
|
|
380
|
+
let hasUsage = false;
|
|
381
|
+
for (const entry of entries) {
|
|
382
|
+
if (entry.type !== "message")
|
|
383
|
+
continue;
|
|
384
|
+
const msg = entry.message;
|
|
385
|
+
if (msg?.role === "assistant" && msg.usage) {
|
|
386
|
+
input += msg.usage.input;
|
|
387
|
+
output += msg.usage.output;
|
|
388
|
+
costTotal += msg.usage.cost.total;
|
|
389
|
+
hasUsage = true;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return hasUsage ? { input, output, cost: { total: costTotal } } : undefined;
|
|
393
|
+
}
|
|
321
394
|
function toSerializableEvents(event) {
|
|
322
395
|
switch (event.type) {
|
|
323
396
|
case "agent_start":
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-mode.js","sourceRoot":"","sources":["../../../src/modes/web/web-mode.ts"],"names":[],"mappings":"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","sourcesContent":["import { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { networkInterfaces } from \"node:os\";\nimport type { AgentSessionEvent } from \"../../core/agent-session.ts\";\nimport type { AgentSessionRuntime } from \"../../core/agent-session-runtime.ts\";\n\nconst PASSWORD = process.env.PI_WEB_PASSWORD;\n\nfunction checkAuth(req: IncomingMessage, res: ServerResponse): boolean {\n\tif (!PASSWORD) return true;\n\n\tconst auth = req.headers.authorization;\n\tif (auth) {\n\t\tconst [scheme, credentials] = auth.split(\" \");\n\t\tif (scheme === \"Basic\" && credentials) {\n\t\t\tconst decoded = Buffer.from(credentials, \"base64\").toString(\"utf-8\");\n\t\t\tconst [, password] = decoded.split(\":\");\n\t\t\tif (password === PASSWORD) return true;\n\t\t}\n\t}\n\n\tres.writeHead(401, {\n\t\t\"www-authenticate\": 'Basic realm=\"pi\", charset=\"UTF-8\"',\n\t\t\"content-type\": \"text/plain\",\n\t});\n\tres.end(\"Unauthorized\");\n\treturn false;\n}\n\nfunction getLocalIP(): string {\n\tconst interfaces = networkInterfaces();\n\tfor (const iface of Object.values(interfaces)) {\n\t\tif (!iface) continue;\n\t\tfor (const addr of iface) {\n\t\t\tif (addr.family === \"IPv4\" && !addr.internal) {\n\t\t\t\treturn addr.address;\n\t\t\t}\n\t\t}\n\t}\n\treturn \"127.0.0.1\";\n}\n\nfunction getPort(): number {\n\tconst env = process.env.PORT;\n\tif (env) {\n\t\tconst p = parseInt(env, 10);\n\t\tif (!Number.isNaN(p) && p > 0 && p < 65536) return p;\n\t}\n\treturn 0;\n}\n\nconst HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\n<title>pi</title>\n<style>\n*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\nhtml{font-size:14px;-webkit-text-size-adjust:100%}\nbody{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",system-ui,sans-serif;background:#0b1014;color:#c9d1d9;display:flex;flex-direction:column;height:100dvh;overflow:hidden}\n#header{display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background:#0d121a;border-bottom:1px solid #21262d;flex-shrink:0;gap:10px}\n#header-left{display:flex;align-items:center;gap:10px}\n#header h1{font-size:14px;font-weight:600;color:#e6edf3}\n#session-info{font-size:11px;color:#6e7681;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n#header-stats{display:flex;gap:12px;font-size:11px;color:#7d8590}\n#msgs{flex:1;overflow-y:auto;padding:16px 20px;scroll-behavior:smooth;display:flex;flex-direction:column;gap:14px}\n#msgs::-webkit-scrollbar{width:6px}\n#msgs::-webkit-scrollbar-track{background:transparent}\n#msgs::-webkit-scrollbar-thumb{background:#30363d;border-radius:3px}\n.msg-group{display:flex;flex-direction:column;gap:2px;animation:fadeIn .15s ease;max-width:86%}\n.msg-group.user{max-width:82%;align-self:flex-end}\n@keyframes fadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}\n.msg-label{font-size:10.5px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;padding:0 2px}\n.msg-group.user .msg-label{color:#7ec9ff;text-align:right}\n.msg-group.assistant .msg-label{color:#7d8590}\n.msg{border-radius:8px;font-size:13.5px;line-height:1.6;overflow-wrap:break-word;padding:10px 14px}\n.msg.user{background:#1b4a8b;color:#e6edf3;border-bottom-right-radius:2px}\n.msg.assistant{background:#1a1f2b;color:#c9d1d9;border-bottom-left-radius:2px;border:1px solid #21262d}\n.msg.system{background:transparent;color:#6e7681;font-size:12px;text-align:center;max-width:100%;padding:4px 0}\n.msg p{margin:0 0 6px}\n.msg p:last-child{margin-bottom:0}\n.msg strong{color:#f2a65a;font-weight:600}\n.msg em{color:#c9d1d9;font-style:italic}\n.msg code{font-family:\"JetBrains Mono\",\"Fira Code\",monospace;font-size:12px;background:#2d333b;color:#c9d1d9;padding:1px 5px;border-radius:3px}\n.msg pre{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:10px 12px;margin:8px 0;overflow-x:auto;font-size:12px;line-height:1.5}\n.msg pre code{background:transparent;padding:0;border-radius:0;font-size:inherit}\n.msg h1,.msg h2,.msg h3{margin:8px 0 4px;font-weight:600;color:#e6edf3}\n.msg h1{font-size:18px;border-bottom:1px solid #21262d;padding-bottom:4px}\n.msg h2{font-size:15px}\n.msg h3{font-size:13.5px;color:#c9d1d9}\n.msg ul,.msg ol{padding-left:20px;margin:4px 0 8px}\n.msg li{margin:2px 0}\n.msg hr{border:none;border-top:1px solid #21262d;margin:10px 0}\n.tool{border:1px solid #21262d;border-radius:8px;overflow:hidden;transition:border-color .2s}\n.tool:hover{border-color:#30363d}\n.tool-header{display:flex;align-items:center;justify-content:space-between;padding:7px 10px;background:#0d121a;cursor:pointer;user-select:none;gap:8px;transition:background .15s}\n.tool-header:hover{background:#161b22}\n.tool-icon{width:7px;height:7px;border-radius:50%;flex-shrink:0}\n.tool-icon.pending{background:#d29922;animation:pulse 1.5s infinite}\n.tool-icon.done{background:#3fb950}\n.tool-icon.error{background:#f85149}\n@keyframes pulse{0%,100%{opacity:.4}50%{opacity:1}}\n.tool-title{flex:1;font-size:12px;font-weight:500;color:#c9d1d9;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.tool-arrow{color:#6e7681;font-size:9px;transition:transform .2s}\n.tool.expanded .tool-arrow{transform:rotate(90deg)}\n.tool-body{font-family:\"JetBrains Mono\",\"Fira Code\",monospace;font-size:12px;line-height:1.55;padding:8px 10px;background:#0b1014;white-space:pre-wrap;word-break:break-all;overflow-x:auto;max-height:30vh;overflow-y:auto;display:none}\n.tool-body::-webkit-scrollbar{width:4px;height:4px}\n.tool-body::-webkit-scrollbar-thumb{background:#30363d;border-radius:2px}\n.tool.expanded .tool-body{display:block}\n.tool.mini .tool-body{max-height:12vh}\n.thinking{padding:5px 10px;border-left:2px solid #30363d;font-size:11.5px;color:#6e7681;font-style:italic}\n#input-area{background:#0d121a;border-top:1px solid #21262d;padding:12px 16px;flex-shrink:0}\n#input-area form{display:flex;gap:8px;max-width:900px;margin:0 auto}\n#prompt{flex:1;background:#161b22;border:1px solid #30363d;border-radius:8px;padding:9px 12px;color:#c9d1d9;font-size:13.5px;font-family:inherit;outline:none;transition:border-color .2s,box-shadow .2s}\n#prompt:focus{border-color:#4493f8;box-shadow:0 0 0 3px rgba(68,147,248,.15)}\n#prompt::placeholder{color:#484f58}\nbutton{background:#1f6feb;color:#fff;border:none;border-radius:8px;padding:9px 16px;font-size:13px;font-weight:500;cursor:pointer;transition:background .15s;flex-shrink:0}\nbutton:hover{background:#2b83ff}\nbutton:disabled{background:#21262d;color:#6e7681;cursor:not-allowed}\n.btn-mini{font-size:11px;padding:4px 10px;background:#21262d}\n.btn-mini:hover{background:#30363d}\n.spinner{display:none;width:16px;height:16px;border:2px solid #30363d;border-top-color:#4493f8;border-radius:50%;animation:spin .6s linear infinite}\n.spinner.active{display:inline-block}\n@keyframes spin{to{transform:rotate(360deg)}}\n@media(max-width:600px){\nhtml{font-size:13px}\n#msgs{padding:10px;gap:10px}\n.msg-group{max-width:94%}\n.msg-group.user{max-width:92%}\n#input-area{padding:8px 10px}\n#prompt{padding:7px 10px}\nbutton{padding:7px 12px}\n}\n</style>\n</head>\n<body>\n<div id=\"header\">\n<div id=\"header-left\"><h1>pi</h1><span id=\"session-info\"></span></div>\n<div id=\"header-stats\"><span id=\"stat-sessions\"></span><span id=\"stat-cost\"></span></div>\n</div>\n<div id=\"msgs\"></div>\n<div id=\"input-area\">\n<form id=\"f\" autocomplete=\"off\">\n<input id=\"prompt\" type=\"text\" placeholder=\"Message pi...\" autofocus autocomplete=\"off\">\n<button id=\"send\">Send</button>\n<span class=\"spinner\" id=\"spinner\"></span>\n</form>\n</div>\n<script>\nconst msgs=document.getElementById(\"msgs\"),f=document.getElementById(\"f\"),prompt=document.getElementById(\"prompt\"),\nsend=document.getElementById(\"send\"),spinner=document.getElementById(\"spinner\"),\nstatSessions=document.getElementById(\"stat-sessions\"),statCost=document.getElementById(\"stat-cost\"),\nsessionInfo=document.getElementById(\"session-info\");\nlet busy=false,toolEls={},curAssistant=null;\n\nfunction scrollDown(){msgs.scrollTop=msgs.scrollHeight}\nfunction esc(s){if(!s)return\"\";return s.replace(/&/g,\"&\").replace(/</g,\"<\").replace(/>/g,\">\")}\nfunction fmt(n){if(n<1e3)return n;if(n<1e4)return(n/1e3).toFixed(1)+\"k\";return Math.round(n/1e3)+\"k\"}\n\nfunction md(text){\nlet s=esc(text);\nconst blocks=[];\ns=s.replace(/\\`\\`\\`(\\\\w*)\\\\n([\\\\s\\\\S]*?)\\\\n\\`\\`\\`/g,(_,lang,code)=>{blocks.push('<pre><code>'+code+'</code></pre>');return'\\\\x00B'+(blocks.length-1)+'\\\\x00B'});\ns=s.replace(/\\`([^\\`]+)\\`/g,'<code>$1</code>');\ns=s.replace(/\\\\*\\\\*(.+?)\\\\*\\\\*/g,'<strong>$1</strong>');\ns=s.replace(/\\\\*(.+?)\\\\*/g,'<em>$1</em>');\ns=s.replace(/^### (.+$)/gm,'<h3>$1</h3>');\ns=s.replace(/^## (.+$)/gm,'<h2>$1</h2>');\ns=s.replace(/^# (.+$)/gm,'<h1>$1</h1>');\ns=s.replace(/^- (.+$)/gm,'<li>$1</li>');\ns=s.replace(/^> (.+$)/gm,'<blockquote>$1</blockquote>');\ns=s.replace(/((?:<li>.*<\\\\/li>\\\\n?)+)/g,'<ul>$1</ul>');\ns=s.replace(/^(---+|\\\\*\\\\*\\\\*+|___+)$/gm,'<hr>');\ns=s.replace(/\\\\x00B(\\\\d+)\\\\x00B/g,(_,i)=>blocks[parseInt(i)]);\ns=s.replace(/\\\\n\\\\n/g,'<br><br>');\ns=s.replace(/\\\\n/g,'<br>');\nreturn s\n}\n\nfunction addMsg(role,text){\nconst g=document.createElement(\"div\");g.className=\"msg-group \"+role;\nif(role===\"user\"){g.innerHTML='<div class=\"msg-label\">You</div><div class=\"msg user\">'+esc(text)+'</div>'}\nelse if(role===\"assistant\"){g.innerHTML='<div class=\"msg-label\">pi</div><div class=\"msg assistant\"></div>'}\nelse{g.innerHTML='<div class=\"msg system\">'+text+'</div>'}\nmsgs.appendChild(g);scrollDown();return g}\n\nfunction getAssistantDiv(){if(!curAssistant){curAssistant=addMsg(\"assistant\",\"\")}return curAssistant.querySelector(\".msg\")}\n\nfunction startTool(e){\nconst el=document.createElement(\"div\");el.className=\"tool\";el.id=\"t\"+e.toolCallId;\nconst title=esc(e.toolName)+(e.args?' '+esc(String(e.args.command||e.args.path||e.args.file_path||\"\").slice(0,80)):'');\nel.innerHTML='<div class=\"tool-header\"><span class=\"tool-icon pending\"></span><span class=\"tool-title\">'+title+'</span><span class=\"tool-arrow\">▶</span></div><div class=\"tool-body\"></div>';\nel.querySelector(\".tool-header\").onclick=()=>el.classList.toggle(\"expanded\");\nmsgs.appendChild(el);toolEls[e.toolCallId]=el;scrollDown()}\n\nfunction updateTool(id,result,isError){\nconst el=toolEls[id];if(!el)return;\nconst icon=el.querySelector(\".tool-icon\"),body=el.querySelector(\".tool-body\");\nif(isError){icon.className=\"tool-icon error\"}\nconst texts=result&&result.content?result.content.filter(c=>c&&c.type===\"text\").map(c=>c.text):[];\nif(texts.length){body.textContent=texts.join(\"\\\\n\");el.classList.add(\"expanded\",\"mini\")}}\n\nfunction finishTool(id,isError){\nconst el=toolEls[id];if(!el)return;\nconst icon=el.querySelector(\".tool-icon\");icon.className=\"tool-icon \"+(isError?\"error\":\"done\");\nconst body=el.querySelector(\".tool-body\");if(isError&&!body.textContent.trim())body.textContent=\"(no output)\"}\n\nfunction handle(d){\nswitch(d.type){\ncase\"agent_start\":curAssistant=null;break;\ncase\"text_delta\":{const ad=getAssistantDiv();ad.innerHTML+=md(d.delta);scrollDown();break}\ncase\"thinking_delta\":{let th=document.getElementById(\"think\"+d.contentIndex);if(!th){th=document.createElement(\"div\");th.className=\"thinking\";th.id=\"think\"+d.contentIndex;msgs.appendChild(th)}th.textContent+=d.delta;scrollDown();break}\ncase\"tool_execution_start\":startTool(d);break;\ncase\"tool_execution_update\":updateTool(d.toolCallId,d.result,false);break;\ncase\"tool_execution_end\":finishTool(d.toolCallId,d.isError);if(d.result)updateTool(d.toolCallId,d.result,d.isError);break;\ncase\"agent_end\":{let s=\"\";if(d.usage){s=\" · \"+fmt(d.usage.input)+\" in · \"+fmt(d.usage.output)+\" out\";if(d.usage.cost)s+=\" · $\"+d.usage.cost.total.toFixed(4)}addMsg(\"system\",\"Done\"+s);break}\ncase\"compaction\":addMsg(\"system\",\"Compacting...\");break;\ncase\"error\":addMsg(\"system\",\"Error: \"+esc(d.message));break;\ncase\"text_start\":case\"thinking_start\":case\"text_end\":case\"thinking_end\":case\"toolcall_start\":case\"toolcall_end\":case\"toolcall_delta\":break}}\n\nf.onsubmit=async e=>{e.preventDefault();if(busy)return;const text=prompt.value.trim();if(!text)return;prompt.value=\"\";addMsg(\"user\",text);busy=true;send.disabled=true;spinner.className=\"spinner active\";\ntry{const r=await fetch(\"/api/prompt\",{method:\"POST\",headers:{\"content-type\":\"application/json\"},body:JSON.stringify({text})});\nif(!r.ok){addMsg(\"system\",\"Error: \"+r.status);return}\nconst reader=r.body.getReader(),decoder=new TextDecoder();let buf=\"\";\nwhile(true){const{done,value}=await reader.read();if(done)break;buf+=decoder.decode(value,{stream:true});const lines=buf.split(\"\\\\n\");buf=lines.pop()||\"\";\nfor(const line of lines){if(!line.trim())continue;try{handle(JSON.parse(line))}catch{}}}}\ncatch(err){addMsg(\"system\",\"Error: \"+esc(err.message))}finally{busy=false;send.disabled=false;spinner.className=\"spinner\"}};\n\nfetch(\"/api/stats\").then(r=>r.json()).then(s=>{\nstatSessions.textContent=s.sessions+\" sessions\";\nstatCost.textContent=\"$\"+s.cost.toFixed(2);\n}).catch(()=>{});\n\nfetch(\"/api/session-info\").then(r=>r.json()).then(s=>{\nsessionInfo.textContent=s.id?(\"session: \"+s.id.slice(0,8)+(s.name?\" | \"+s.name:\"\")):\"\";\n}).catch(()=>{});\n</script>\n</body>\n</html>`;\n\nfunction sendEvent(res: ServerResponse, data: Record<string, unknown>): void {\n\tres.write(`${JSON.stringify(data)}\\n`);\n}\n\nexport async function runWebMode(runtime: AgentSessionRuntime): Promise<void> {\n\tconst session = runtime.session;\n\tconst port = getPort();\n\n\tconst server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n\t\tif (!checkAuth(req, res)) return;\n\n\t\tconst url = req.url ?? \"/\";\n\n\t\tif (req.method === \"POST\" && url === \"/api/prompt\") {\n\t\t\tconst chunks: Buffer[] = [];\n\t\t\treq.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n\t\t\tawait new Promise<void>((resolve) => req.on(\"end\", resolve));\n\t\t\tconst body = JSON.parse(Buffer.concat(chunks).toString());\n\t\t\tconst text = String(body.text ?? \"\").trim();\n\t\t\tif (!text) {\n\t\t\t\tres.writeHead(400);\n\t\t\t\tres.end(JSON.stringify({ error: \"missing text\" }));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tres.writeHead(200, { \"content-type\": \"text/plain; charset=utf-8\", \"cache-control\": \"no-cache\" });\n\n\t\t\tconst unsubscribe = session.subscribe((event: AgentSessionEvent) => {\n\t\t\t\tconst events = toSerializableEvents(event);\n\t\t\t\tfor (const evt of events) {\n\t\t\t\t\tsendEvent(res, evt);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\ttry {\n\t\t\t\tawait session.prompt(text);\n\t\t\t} catch (err) {\n\t\t\t\tsendEvent(res, { type: \"error\", message: String(err) });\n\t\t\t} finally {\n\t\t\t\tunsubscribe();\n\t\t\t\tres.end();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/stats\") {\n\t\t\tconst stats = await session.getUsageStats();\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({ sessions: stats.sessions, cost: stats.cost, input: stats.input, output: stats.output }),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/session-info\") {\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tid: session.sessionId,\n\t\t\t\t\tname: session.sessionManager.getSessionName(),\n\t\t\t\t\tfile: session.sessionFile,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tres.writeHead(200, { \"content-type\": \"text/html; charset=utf-8\" });\n\t\tres.end(HTML);\n\t});\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tserver.listen(port, \"0.0.0.0\", () => resolve());\n\t\tserver.on(\"error\", reject);\n\t});\n\n\tconst addr = server.address();\n\tif (!addr || typeof addr === \"string\") {\n\t\tconsole.error(\"Failed to start web server\");\n\t\treturn;\n\t}\n\n\tconst consolePort = `http://127.0.0.1:${addr.port}`;\n\tconst lan = getLocalIP();\n\tconsole.log(`\\n pi web UI: ${consolePort}`);\n\tif (lan !== \"127.0.0.1\") {\n\t\tconsole.log(` LAN: http://${lan}:${addr.port}`);\n\t}\n\tconsole.log(` session: ${session.sessionId.slice(0, 8)}`);\n\tconsole.log(\" (listening on all interfaces)\");\n\tif (PASSWORD) {\n\t\tconsole.log(` auth: Basic (user \"pi\", password from PI_WEB_PASSWORD)`);\n\t} else {\n\t\tconsole.log(\" auth: none (set PI_WEB_PASSWORD to enable)\");\n\t}\n\tconsole.log();\n\n\t// Keep process alive\n\tawait new Promise(() => {});\n\n\tserver.close();\n}\n\nfunction toSerializableEvents(event: AgentSessionEvent): Record<string, unknown>[] {\n\tswitch (event.type) {\n\t\tcase \"agent_start\":\n\t\t\treturn [{ type: \"agent_start\" }];\n\t\tcase \"agent_end\": {\n\t\t\tconst lastAssistant = event.messages.filter((m) => m.role === \"assistant\").at(-1);\n\t\t\tconst usage = lastAssistant && \"usage\" in lastAssistant ? lastAssistant.usage : undefined;\n\t\t\treturn [{ type: \"agent_end\", usage }];\n\t\t}\n\t\tcase \"message_update\": {\n\t\t\tconst ame: Record<string, unknown> = event.assistantMessageEvent;\n\t\t\tconst serialized = serializeAssistantMessageEvent(ame);\n\t\t\treturn serialized;\n\t\t}\n\t\tcase \"tool_execution_start\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_start\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\ttoolName: event.toolName,\n\t\t\t\t\targs: event.args,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"tool_execution_update\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_update\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\tresult: event.partialResult,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"tool_execution_end\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_end\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\tresult: event.result,\n\t\t\t\t\tisError: event.isError,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"compaction_start\":\n\t\tcase \"compaction_end\":\n\t\t\treturn [{ type: \"compaction\" }];\n\t\tdefault:\n\t\t\treturn [];\n\t}\n}\n\nfunction serializeAssistantMessageEvent(event: Record<string, unknown>): Record<string, unknown>[] {\n\tconst type = event.type as string;\n\tswitch (type) {\n\t\tcase \"start\":\n\t\t\treturn [{ type: \"agent_start\" }];\n\t\tcase \"text_start\":\n\t\t\treturn [{ type: \"text_start\", contentIndex: event.contentIndex }];\n\t\tcase \"text_delta\":\n\t\t\treturn [{ type: \"text_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"text_end\":\n\t\t\treturn [{ type: \"text_end\", content: event.content, contentIndex: event.contentIndex }];\n\t\tcase \"thinking_start\":\n\t\t\treturn [{ type: \"thinking_start\", contentIndex: event.contentIndex }];\n\t\tcase \"thinking_delta\":\n\t\t\treturn [{ type: \"thinking_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"thinking_end\":\n\t\t\treturn [{ type: \"thinking_end\", content: event.content, contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_start\":\n\t\t\treturn [{ type: \"toolcall_start\", contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_delta\":\n\t\t\treturn [{ type: \"toolcall_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_end\":\n\t\t\treturn [{ type: \"toolcall_end\", toolCall: event.toolCall, contentIndex: event.contentIndex }];\n\t\tcase \"done\":\n\t\t\treturn [{ type: \"done\", reason: event.reason }];\n\t\tcase \"error\":\n\t\t\treturn [{ type: \"error\", reason: event.reason }];\n\t\tdefault:\n\t\t\treturn [];\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"web-mode.js","sourceRoot":"","sources":["../../../src/modes/web/web-mode.ts"],"names":[],"mappings":"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","sourcesContent":["import { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { networkInterfaces } from \"node:os\";\nimport type { AgentSessionEvent } from \"../../core/agent-session.ts\";\nimport type { AgentSessionRuntime } from \"../../core/agent-session-runtime.ts\";\n\nconst PASSWORD = process.env.PI_WEB_PASSWORD;\n\nfunction checkAuth(req: IncomingMessage, res: ServerResponse): boolean {\n\tif (!PASSWORD) return true;\n\n\tconst auth = req.headers.authorization;\n\tif (auth) {\n\t\tconst [scheme, credentials] = auth.split(\" \");\n\t\tif (scheme === \"Basic\" && credentials) {\n\t\t\tconst decoded = Buffer.from(credentials, \"base64\").toString(\"utf-8\");\n\t\t\tconst [, password] = decoded.split(\":\");\n\t\t\tif (password === PASSWORD) return true;\n\t\t}\n\t}\n\n\tres.writeHead(401, {\n\t\t\"www-authenticate\": 'Basic realm=\"pi\", charset=\"UTF-8\"',\n\t\t\"content-type\": \"text/plain\",\n\t});\n\tres.end(\"Unauthorized\");\n\treturn false;\n}\n\nfunction getLocalIP(): string {\n\tconst interfaces = networkInterfaces();\n\tfor (const iface of Object.values(interfaces)) {\n\t\tif (!iface) continue;\n\t\tfor (const addr of iface) {\n\t\t\tif (addr.family === \"IPv4\" && !addr.internal) {\n\t\t\t\treturn addr.address;\n\t\t\t}\n\t\t}\n\t}\n\treturn \"127.0.0.1\";\n}\n\nfunction getPort(): number {\n\tconst env = process.env.PORT;\n\tif (env) {\n\t\tconst p = parseInt(env, 10);\n\t\tif (!Number.isNaN(p) && p > 0 && p < 65536) return p;\n\t}\n\treturn 0;\n}\n\nconst HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\n<title>pi</title>\n<style>\n*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\nhtml{font-size:14px;-webkit-text-size-adjust:100%}\nbody{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",system-ui,sans-serif;background:#0b1014;color:#c9d1d9;display:flex;flex-direction:column;height:100dvh;overflow:hidden}\n#header{display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background:#0d121a;border-bottom:1px solid #21262d;flex-shrink:0;gap:10px}\n#header-left{display:flex;align-items:center;gap:10px}\n#header h1{font-size:14px;font-weight:600;color:#e6edf3}\n#session-info{font-size:11px;color:#6e7681;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n#header-stats{display:flex;gap:12px;font-size:11px;color:#7d8590}\n#msgs{flex:1;overflow-y:auto;padding:16px 20px;scroll-behavior:smooth;display:flex;flex-direction:column;gap:14px}\n#msgs::-webkit-scrollbar{width:6px}\n#msgs::-webkit-scrollbar-track{background:transparent}\n#msgs::-webkit-scrollbar-thumb{background:#30363d;border-radius:3px}\n.msg-group{display:flex;flex-direction:column;gap:2px;animation:fadeIn .15s ease;max-width:86%}\n.msg-group.user{max-width:82%;align-self:flex-end}\n@keyframes fadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}\n.msg-label{font-size:10.5px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;padding:0 2px}\n.msg-group.user .msg-label{color:#7ec9ff;text-align:right}\n.msg-group.assistant .msg-label{color:#7d8590}\n.msg{border-radius:8px;font-size:13.5px;line-height:1.6;overflow-wrap:break-word;padding:10px 14px}\n.msg.user{background:#1b4a8b;color:#e6edf3;border-bottom-right-radius:2px}\n.msg.assistant{background:#1a1f2b;color:#c9d1d9;border-bottom-left-radius:2px;border:1px solid #21262d}\n.msg.system{background:transparent;color:#6e7681;font-size:12px;text-align:center;max-width:100%;padding:4px 0}\n.msg p{margin:0 0 6px}\n.msg p:last-child{margin-bottom:0}\n.msg strong{color:#f2a65a;font-weight:600}\n.msg em{color:#c9d1d9;font-style:italic}\n.msg code{font-family:\"JetBrains Mono\",\"Fira Code\",monospace;font-size:12px;background:#2d333b;color:#c9d1d9;padding:1px 5px;border-radius:3px}\n.msg pre{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:10px 12px;margin:8px 0;overflow-x:auto;font-size:12px;line-height:1.5}\n.msg pre code{background:transparent;padding:0;border-radius:0;font-size:inherit}\n.msg h1,.msg h2,.msg h3{margin:8px 0 4px;font-weight:600;color:#e6edf3}\n.msg h1{font-size:18px;border-bottom:1px solid #21262d;padding-bottom:4px}\n.msg h2{font-size:15px}\n.msg h3{font-size:13.5px;color:#c9d1d9}\n.msg ul,.msg ol{padding-left:20px;margin:4px 0 8px}\n.msg li{margin:2px 0}\n.msg hr{border:none;border-top:1px solid #21262d;margin:10px 0}\n.tool{border:1px solid #21262d;border-radius:8px;overflow:hidden;transition:border-color .2s}\n.tool:hover{border-color:#30363d}\n.tool-header{display:flex;align-items:center;justify-content:space-between;padding:7px 10px;background:#0d121a;cursor:pointer;user-select:none;gap:8px;transition:background .15s}\n.tool-header:hover{background:#161b22}\n.tool-icon{width:7px;height:7px;border-radius:50%;flex-shrink:0}\n.tool-icon.pending{background:#d29922;animation:pulse 1.5s infinite}\n.tool-icon.done{background:#3fb950}\n.tool-icon.error{background:#f85149}\n@keyframes pulse{0%,100%{opacity:.4}50%{opacity:1}}\n.tool-title{flex:1;font-size:12px;font-weight:500;color:#c9d1d9;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.tool-arrow{color:#6e7681;font-size:9px;transition:transform .2s}\n.tool.expanded .tool-arrow{transform:rotate(90deg)}\n.tool-body{font-family:\"JetBrains Mono\",\"Fira Code\",monospace;font-size:12px;line-height:1.55;padding:8px 10px;background:#0b1014;white-space:pre-wrap;word-break:break-all;overflow-x:auto;max-height:30vh;overflow-y:auto;display:none}\n.tool-body::-webkit-scrollbar{width:4px;height:4px}\n.tool-body::-webkit-scrollbar-thumb{background:#30363d;border-radius:2px}\n.tool.expanded .tool-body{display:block}\n.tool.mini .tool-body{max-height:12vh}\n.thinking{padding:5px 10px;border-left:2px solid #30363d;font-size:11.5px;color:#6e7681;font-style:italic}\n#input-area{background:#0d121a;border-top:1px solid #21262d;padding:12px 16px;flex-shrink:0}\n#input-area form{display:flex;gap:8px;max-width:900px;margin:0 auto}\n#prompt{flex:1;background:#161b22;border:1px solid #30363d;border-radius:8px;padding:9px 12px;color:#c9d1d9;font-size:13.5px;font-family:inherit;outline:none;transition:border-color .2s,box-shadow .2s}\n#prompt:focus{border-color:#4493f8;box-shadow:0 0 0 3px rgba(68,147,248,.15)}\n#prompt::placeholder{color:#484f58}\nbutton{background:#1f6feb;color:#fff;border:none;border-radius:8px;padding:9px 16px;font-size:13px;font-weight:500;cursor:pointer;transition:background .15s;flex-shrink:0}\nbutton:hover{background:#2b83ff}\nbutton:disabled{background:#21262d;color:#6e7681;cursor:not-allowed}\n.btn-mini{font-size:11px;padding:4px 10px;background:#21262d}\n.btn-mini:hover{background:#30363d}\n.spinner{display:none;width:16px;height:16px;border:2px solid #30363d;border-top-color:#4493f8;border-radius:50%;animation:spin .6s linear infinite}\n.spinner.active{display:inline-block}\n@keyframes spin{to{transform:rotate(360deg)}}\n@media(max-width:600px){\nhtml{font-size:13px}\n#msgs{padding:10px;gap:10px}\n.msg-group{max-width:94%}\n.msg-group.user{max-width:92%}\n#input-area{padding:8px 10px}\n#prompt{padding:7px 10px}\nbutton{padding:7px 12px}\n}\n</style>\n</head>\n<body>\n<div id=\"header\">\n<div id=\"header-left\"><h1>pi</h1><span id=\"session-info\"></span></div>\n<div id=\"header-stats\"><span id=\"stat-sessions\"></span><span id=\"stat-cost\"></span></div>\n</div>\n<div id=\"msgs\"></div>\n<div id=\"input-area\">\n<form id=\"f\" autocomplete=\"off\">\n<input id=\"prompt\" type=\"text\" placeholder=\"Message pi...\" autofocus autocomplete=\"off\">\n<button id=\"send\">Send</button>\n<span class=\"spinner\" id=\"spinner\"></span>\n</form>\n</div>\n<script>\nconst msgs=document.getElementById(\"msgs\"),f=document.getElementById(\"f\"),prompt=document.getElementById(\"prompt\"),\nsend=document.getElementById(\"send\"),spinner=document.getElementById(\"spinner\"),\nstatSessions=document.getElementById(\"stat-sessions\"),statCost=document.getElementById(\"stat-cost\"),\nsessionInfo=document.getElementById(\"session-info\");\nlet busy=false,toolEls={},curAssistant=null;\n\nfunction scrollDown(){msgs.scrollTop=msgs.scrollHeight}\nfunction esc(s){if(!s)return\"\";return s.replace(/&/g,\"&\").replace(/</g,\"<\").replace(/>/g,\">\")}\nfunction fmt(n){if(n<1e3)return n;if(n<1e4)return(n/1e3).toFixed(1)+\"k\";return Math.round(n/1e3)+\"k\"}\n\nfunction md(text){\nlet s=esc(text);\nconst blocks=[];\ns=s.replace(/\\`\\`\\`(\\\\w*)\\\\n([\\\\s\\\\S]*?)\\\\n\\`\\`\\`/g,(_,lang,code)=>{blocks.push('<pre><code>'+code+'</code></pre>');return'\\\\x00B'+(blocks.length-1)+'\\\\x00B'});\ns=s.replace(/\\`([^\\`]+)\\`/g,'<code>$1</code>');\ns=s.replace(/\\\\*\\\\*(.+?)\\\\*\\\\*/g,'<strong>$1</strong>');\ns=s.replace(/\\\\*(.+?)\\\\*/g,'<em>$1</em>');\ns=s.replace(/^### (.+$)/gm,'<h3>$1</h3>');\ns=s.replace(/^## (.+$)/gm,'<h2>$1</h2>');\ns=s.replace(/^# (.+$)/gm,'<h1>$1</h1>');\ns=s.replace(/^- (.+$)/gm,'<li>$1</li>');\ns=s.replace(/^> (.+$)/gm,'<blockquote>$1</blockquote>');\ns=s.replace(/((?:<li>.*<\\\\/li>\\\\n?)+)/g,'<ul>$1</ul>');\ns=s.replace(/^(---+|\\\\*\\\\*\\\\*+|___+)$/gm,'<hr>');\ns=s.replace(/\\\\x00B(\\\\d+)\\\\x00B/g,(_,i)=>blocks[parseInt(i)]);\ns=s.replace(/\\\\n\\\\n/g,'<br><br>');\ns=s.replace(/\\\\n/g,'<br>');\nreturn s\n}\n\nfunction addMsg(role,text){\nconst g=document.createElement(\"div\");g.className=\"msg-group \"+role;\nif(role===\"user\"){g.innerHTML='<div class=\"msg-label\">You</div><div class=\"msg user\">'+esc(text)+'</div>'}\nelse if(role===\"assistant\"){g.innerHTML='<div class=\"msg-label\">pi</div><div class=\"msg assistant\"></div>'}\nelse{g.innerHTML='<div class=\"msg system\">'+text+'</div>'}\nmsgs.appendChild(g);scrollDown();return g}\n\nfunction getAssistantDiv(){if(!curAssistant){curAssistant=addMsg(\"assistant\",\"\")}return curAssistant.querySelector(\".msg\")}\n\nfunction startTool(e){\nconst el=document.createElement(\"div\");el.className=\"tool\";el.id=\"t\"+e.toolCallId;\nconst title=esc(e.toolName)+(e.args?' '+esc(String(e.args.command||e.args.path||e.args.file_path||\"\").slice(0,80)):'');\nel.innerHTML='<div class=\"tool-header\"><span class=\"tool-icon pending\"></span><span class=\"tool-title\">'+title+'</span><span class=\"tool-arrow\">▶</span></div><div class=\"tool-body\"></div>';\nel.querySelector(\".tool-header\").onclick=()=>el.classList.toggle(\"expanded\");\nmsgs.appendChild(el);toolEls[e.toolCallId]=el;scrollDown()}\n\nfunction updateTool(id,result,isError){\nconst el=toolEls[id];if(!el)return;\nconst icon=el.querySelector(\".tool-icon\"),body=el.querySelector(\".tool-body\");\nif(isError){icon.className=\"tool-icon error\"}\nconst texts=result&&result.content?result.content.filter(c=>c&&c.type===\"text\").map(c=>c.text):[];\nif(texts.length){body.textContent=texts.join(\"\\\\n\");el.classList.add(\"expanded\",\"mini\")}}\n\nfunction finishTool(id,isError){\nconst el=toolEls[id];if(!el)return;\nconst icon=el.querySelector(\".tool-icon\");icon.className=\"tool-icon \"+(isError?\"error\":\"done\");\nconst body=el.querySelector(\".tool-body\");if(isError&&!body.textContent.trim())body.textContent=\"(no output)\"}\n\nfunction handle(d){\nswitch(d.type){\ncase\"agent_start\":curAssistant=null;break;\ncase\"text_delta\":{const ad=getAssistantDiv();ad.innerHTML+=md(d.delta);scrollDown();break}\ncase\"thinking_delta\":{let th=document.getElementById(\"think\"+d.contentIndex);if(!th){th=document.createElement(\"div\");th.className=\"thinking\";th.id=\"think\"+d.contentIndex;msgs.appendChild(th)}th.textContent+=d.delta;scrollDown();break}\ncase\"tool_execution_start\":startTool(d);break;\ncase\"tool_execution_update\":updateTool(d.toolCallId,d.result,false);break;\ncase\"tool_execution_end\":finishTool(d.toolCallId,d.isError);if(d.result)updateTool(d.toolCallId,d.result,d.isError);break;\ncase\"agent_end\":{let s=\"\";if(d.usage){s=\" · \"+fmt(d.usage.input)+\" in · \"+fmt(d.usage.output)+\" out\";if(d.usage.cost)s+=\" · $\"+d.usage.cost.total.toFixed(4)}addMsg(\"system\",\"Done\"+s);break}\ncase\"compaction\":addMsg(\"system\",\"Compacting...\");break;\ncase\"error\":addMsg(\"system\",\"Error: \"+esc(d.message));break;\ncase\"text_start\":case\"thinking_start\":case\"text_end\":case\"thinking_end\":case\"toolcall_start\":case\"toolcall_end\":case\"toolcall_delta\":break}}\n\nf.onsubmit=async e=>{e.preventDefault();if(busy)return;const text=prompt.value.trim();if(!text)return;prompt.value=\"\";addMsg(\"user\",text);busy=true;send.disabled=true;spinner.className=\"spinner active\";\ntry{const r=await fetch(\"/api/prompt\",{method:\"POST\",headers:{\"content-type\":\"application/json\"},body:JSON.stringify({text})});\nif(!r.ok){addMsg(\"system\",\"Error: \"+r.status);return}\nconst reader=r.body.getReader(),decoder=new TextDecoder();let buf=\"\";\nwhile(true){const{done,value}=await reader.read();if(done)break;buf+=decoder.decode(value,{stream:true});const lines=buf.split(\"\\\\n\");buf=lines.pop()||\"\";\nfor(const line of lines){if(!line.trim())continue;try{handle(JSON.parse(line))}catch{}}}}\ncatch(err){addMsg(\"system\",\"Error: \"+esc(err.message))}finally{busy=false;send.disabled=false;spinner.className=\"spinner\"}};\n\nfetch(\"/api/messages\").then(r=>r.json()).then(events=>{\nfor(const e of events){\nif(e.type===\"user\"){addMsg(\"user\",e.text)}\nelse{handle(e)}\n}\n}).catch(()=>{});\n\nfetch(\"/api/stats\").then(r=>r.json()).then(s=>{\nstatSessions.textContent=s.sessions+\" sessions\";\nstatCost.textContent=\"$\"+s.cost.toFixed(2);\n}).catch(()=>{});\n\nfetch(\"/api/session-info\").then(r=>r.json()).then(s=>{\nsessionInfo.textContent=s.id?(\"session: \"+s.id.slice(0,8)+(s.name?\" | \"+s.name:\"\")):\"\";\n}).catch(()=>{});\n</script>\n</body>\n</html>`;\n\nfunction sendEvent(res: ServerResponse, data: Record<string, unknown>): void {\n\tres.write(`${JSON.stringify(data)}\\n`);\n}\n\nexport async function runWebMode(runtime: AgentSessionRuntime): Promise<void> {\n\tconst session = runtime.session;\n\tconst port = getPort();\n\n\tconst server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n\t\tif (!checkAuth(req, res)) return;\n\n\t\tconst url = req.url ?? \"/\";\n\n\t\tif (req.method === \"POST\" && url === \"/api/prompt\") {\n\t\t\tconst chunks: Buffer[] = [];\n\t\t\treq.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n\t\t\tawait new Promise<void>((resolve) => req.on(\"end\", resolve));\n\t\t\tconst body = JSON.parse(Buffer.concat(chunks).toString());\n\t\t\tconst text = String(body.text ?? \"\").trim();\n\t\t\tif (!text) {\n\t\t\t\tres.writeHead(400);\n\t\t\t\tres.end(JSON.stringify({ error: \"missing text\" }));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tres.writeHead(200, { \"content-type\": \"text/plain; charset=utf-8\", \"cache-control\": \"no-cache\" });\n\n\t\t\tconst unsubscribe = session.subscribe((event: AgentSessionEvent) => {\n\t\t\t\tconst events = toSerializableEvents(event);\n\t\t\t\tfor (const evt of events) {\n\t\t\t\t\tsendEvent(res, evt);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\ttry {\n\t\t\t\tawait session.prompt(text);\n\t\t\t} catch (err) {\n\t\t\t\tsendEvent(res, { type: \"error\", message: String(err) });\n\t\t\t} finally {\n\t\t\t\tunsubscribe();\n\t\t\t\tres.end();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/stats\") {\n\t\t\tconst stats = await session.getUsageStats();\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({ sessions: stats.sessions, cost: stats.cost, input: stats.input, output: stats.output }),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/session-info\") {\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tid: session.sessionId,\n\t\t\t\t\tname: session.sessionManager.getSessionName(),\n\t\t\t\t\tfile: session.sessionFile,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/messages\") {\n\t\t\tconst entries = session.sessionManager.getEntries();\n\t\t\tconst events: Record<string, unknown>[] = [];\n\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.type !== \"message\") continue;\n\t\t\t\tconst msg = (entry as { message: { role: string; content: unknown; usage?: unknown } }).message;\n\t\t\t\tif (!msg) continue;\n\n\t\t\t\tif (msg.role === \"user\") {\n\t\t\t\t\tconst content = typeof msg.content === \"string\" ? msg.content : \"\";\n\t\t\t\t\tevents.push({ type: \"user\", text: content });\n\t\t\t\t} else if (msg.role === \"assistant\") {\n\t\t\t\t\tconst content = Array.isArray(msg.content) ? msg.content : [];\n\t\t\t\t\tfor (const block of content as Array<{ type: string; text?: string; toolCall?: unknown }>) {\n\t\t\t\t\t\tif (block.type === \"text\" && block.text) {\n\t\t\t\t\t\t\tevents.push({ type: \"text_delta\", delta: block.text });\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (block.type === \"toolCall\" && block.toolCall) {\n\t\t\t\t\t\t\tconst tc = block.toolCall as { toolCallId?: string; toolName?: string; input?: unknown };\n\t\t\t\t\t\t\tevents.push({\n\t\t\t\t\t\t\t\ttype: \"tool_execution_start\",\n\t\t\t\t\t\t\t\ttoolCallId: tc.toolCallId,\n\t\t\t\t\t\t\t\ttoolName: tc.toolName,\n\t\t\t\t\t\t\t\targs: tc.input,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (msg.role === \"toolResult\") {\n\t\t\t\t\tconst content = (msg as { toolCallId?: string; content?: Array<{ type: string; text?: string }> })\n\t\t\t\t\t\t.content;\n\t\t\t\t\tevents.push({\n\t\t\t\t\t\ttype: \"tool_execution_end\",\n\t\t\t\t\t\ttoolCallId: (msg as { toolCallId?: string }).toolCallId,\n\t\t\t\t\t\tresult: { content: content ?? [] },\n\t\t\t\t\t\tisError: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst usage = getSessionTokenCount(session);\n\t\t\tevents.push({ type: \"agent_end\", usage });\n\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(JSON.stringify(events));\n\t\t\treturn;\n\t\t}\n\n\t\tres.writeHead(200, { \"content-type\": \"text/html; charset=utf-8\" });\n\t\tres.end(HTML);\n\t});\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tserver.listen(port, \"0.0.0.0\", () => resolve());\n\t\tserver.on(\"error\", reject);\n\t});\n\n\tconst addr = server.address();\n\tif (!addr || typeof addr === \"string\") {\n\t\tconsole.error(\"Failed to start web server\");\n\t\treturn;\n\t}\n\n\tconst consolePort = `http://127.0.0.1:${addr.port}`;\n\tconst lan = getLocalIP();\n\tconsole.log(`\\n pi web UI: ${consolePort}`);\n\tif (lan !== \"127.0.0.1\") {\n\t\tconsole.log(` LAN: http://${lan}:${addr.port}`);\n\t}\n\tconsole.log(` session: ${session.sessionId.slice(0, 8)}`);\n\tconsole.log(\" (listening on all interfaces)\");\n\tif (PASSWORD) {\n\t\tconsole.log(` auth: Basic (user \"pi\", password from PI_WEB_PASSWORD)`);\n\t} else {\n\t\tconsole.log(\" auth: none (set PI_WEB_PASSWORD to enable)\");\n\t}\n\tconsole.log();\n\n\t// Keep process alive\n\tawait new Promise(() => {});\n\n\tserver.close();\n}\n\nfunction getSessionTokenCount(session: AgentSessionRuntime[\"session\"]): Record<string, unknown> | undefined {\n\tconst entries = session.sessionManager.getEntries();\n\tlet input = 0;\n\tlet output = 0;\n\tlet costTotal = 0;\n\tlet hasUsage = false;\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"message\") continue;\n\t\tconst msg = (\n\t\t\tentry as { message: { role: string; usage?: { input: number; output: number; cost: { total: number } } } }\n\t\t).message;\n\t\tif (msg?.role === \"assistant\" && msg.usage) {\n\t\t\tinput += msg.usage.input;\n\t\t\toutput += msg.usage.output;\n\t\t\tcostTotal += msg.usage.cost.total;\n\t\t\thasUsage = true;\n\t\t}\n\t}\n\treturn hasUsage ? { input, output, cost: { total: costTotal } } : undefined;\n}\n\nfunction toSerializableEvents(event: AgentSessionEvent): Record<string, unknown>[] {\n\tswitch (event.type) {\n\t\tcase \"agent_start\":\n\t\t\treturn [{ type: \"agent_start\" }];\n\t\tcase \"agent_end\": {\n\t\t\tconst lastAssistant = event.messages.filter((m) => m.role === \"assistant\").at(-1);\n\t\t\tconst usage = lastAssistant && \"usage\" in lastAssistant ? lastAssistant.usage : undefined;\n\t\t\treturn [{ type: \"agent_end\", usage }];\n\t\t}\n\t\tcase \"message_update\": {\n\t\t\tconst ame: Record<string, unknown> = event.assistantMessageEvent;\n\t\t\tconst serialized = serializeAssistantMessageEvent(ame);\n\t\t\treturn serialized;\n\t\t}\n\t\tcase \"tool_execution_start\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_start\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\ttoolName: event.toolName,\n\t\t\t\t\targs: event.args,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"tool_execution_update\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_update\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\tresult: event.partialResult,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"tool_execution_end\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_end\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\tresult: event.result,\n\t\t\t\t\tisError: event.isError,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"compaction_start\":\n\t\tcase \"compaction_end\":\n\t\t\treturn [{ type: \"compaction\" }];\n\t\tdefault:\n\t\t\treturn [];\n\t}\n}\n\nfunction serializeAssistantMessageEvent(event: Record<string, unknown>): Record<string, unknown>[] {\n\tconst type = event.type as string;\n\tswitch (type) {\n\t\tcase \"start\":\n\t\t\treturn [{ type: \"agent_start\" }];\n\t\tcase \"text_start\":\n\t\t\treturn [{ type: \"text_start\", contentIndex: event.contentIndex }];\n\t\tcase \"text_delta\":\n\t\t\treturn [{ type: \"text_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"text_end\":\n\t\t\treturn [{ type: \"text_end\", content: event.content, contentIndex: event.contentIndex }];\n\t\tcase \"thinking_start\":\n\t\t\treturn [{ type: \"thinking_start\", contentIndex: event.contentIndex }];\n\t\tcase \"thinking_delta\":\n\t\t\treturn [{ type: \"thinking_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"thinking_end\":\n\t\t\treturn [{ type: \"thinking_end\", content: event.content, contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_start\":\n\t\t\treturn [{ type: \"toolcall_start\", contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_delta\":\n\t\t\treturn [{ type: \"toolcall_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_end\":\n\t\t\treturn [{ type: \"toolcall_end\", toolCall: event.toolCall, contentIndex: event.contentIndex }];\n\t\tcase \"done\":\n\t\t\treturn [{ type: \"done\", reason: event.reason }];\n\t\tcase \"error\":\n\t\t\treturn [{ type: \"error\", reason: event.reason }];\n\t\tdefault:\n\t\t\treturn [];\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version-check.d.ts","sourceRoot":"","sources":["../../src/utils/version-check.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAsBD,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAcpG;AAED,wBAAgB,qBAAqB,CAAC,gBAAgB,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAM/F;AAED,wBAAsB,kBAAkB,CACvC,cAAc,EAAE,MAAM,EACtB,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"version-check.d.ts","sourceRoot":"","sources":["../../src/utils/version-check.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAsBD,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAcpG;AAED,wBAAgB,qBAAqB,CAAC,gBAAgB,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAM/F;AAED,wBAAsB,kBAAkB,CACvC,cAAc,EAAE,MAAM,EACtB,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAyBtC;AAED,wBAAsB,kBAAkB,CACvC,cAAc,EAAE,MAAM,EACtB,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAE7B;AAED,wBAAsB,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAUvG","sourcesContent":["import { PACKAGE_NAME } from \"../config.ts\";\nimport { getPiUserAgent } from \"./pi-user-agent.ts\";\n\nconst DEFAULT_VERSION_CHECK_TIMEOUT_MS = 10000;\n\nexport interface LatestPiRelease {\n\tversion: string;\n\tpackageName?: string;\n\tnote?: string;\n}\n\ninterface ParsedVersion {\n\tmajor: number;\n\tminor: number;\n\tpatch: number;\n\tprerelease?: string;\n}\n\nfunction parsePackageVersion(version: string): ParsedVersion | undefined {\n\tconst match = version.trim().match(/^v?(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?(?:\\+.*)?$/);\n\tif (!match) {\n\t\treturn undefined;\n\t}\n\treturn {\n\t\tmajor: Number.parseInt(match[1], 10),\n\t\tminor: Number.parseInt(match[2], 10),\n\t\tpatch: Number.parseInt(match[3], 10),\n\t\tprerelease: match[4],\n\t};\n}\n\nexport function comparePackageVersions(leftVersion: string, rightVersion: string): number | undefined {\n\tconst left = parsePackageVersion(leftVersion);\n\tconst right = parsePackageVersion(rightVersion);\n\tif (!left || !right) {\n\t\treturn undefined;\n\t}\n\n\tif (left.major !== right.major) return left.major - right.major;\n\tif (left.minor !== right.minor) return left.minor - right.minor;\n\tif (left.patch !== right.patch) return left.patch - right.patch;\n\tif (left.prerelease === right.prerelease) return 0;\n\tif (!left.prerelease) return 1;\n\tif (!right.prerelease) return -1;\n\treturn left.prerelease.localeCompare(right.prerelease);\n}\n\nexport function isNewerPackageVersion(candidateVersion: string, currentVersion: string): boolean {\n\tconst comparison = comparePackageVersions(candidateVersion, currentVersion);\n\tif (comparison !== undefined) {\n\t\treturn comparison > 0;\n\t}\n\treturn candidateVersion.trim() !== currentVersion.trim();\n}\n\nexport async function getLatestPiRelease(\n\tcurrentVersion: string,\n\toptions: { timeoutMs?: number } = {},\n): Promise<LatestPiRelease | undefined> {\n\tif (process.env.PI_SKIP_VERSION_CHECK || process.env.PI_OFFLINE) return undefined;\n\n\ttry {\n\t\tconst response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`, {\n\t\t\theaders: {\n\t\t\t\t\"User-Agent\": getPiUserAgent(currentVersion),\n\t\t\t\taccept: \"application/json\",\n\t\t\t},\n\t\t\tsignal: AbortSignal.timeout(options.timeoutMs ?? DEFAULT_VERSION_CHECK_TIMEOUT_MS),\n\t\t});\n\t\tif (!response.ok) return undefined;\n\n\t\tconst data = (await response.json()) as {\n\t\t\tversion?: unknown;\n\t\t};\n\t\tif (typeof data.version !== \"string\" || !data.version.trim()) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn {\n\t\t\tversion: data.version.trim(),\n\t\t};\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport async function getLatestPiVersion(\n\tcurrentVersion: string,\n\toptions: { timeoutMs?: number } = {},\n): Promise<string | undefined> {\n\treturn (await getLatestPiRelease(currentVersion, options))?.version;\n}\n\nexport async function checkForNewPiVersion(currentVersion: string): Promise<LatestPiRelease | undefined> {\n\ttry {\n\t\tconst latestRelease = await getLatestPiRelease(currentVersion);\n\t\tif (latestRelease && isNewerPackageVersion(latestRelease.version, currentVersion)) {\n\t\t\treturn latestRelease;\n\t\t}\n\t\treturn undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { PACKAGE_NAME } from "../config.js";
|
|
1
2
|
import { getPiUserAgent } from "./pi-user-agent.js";
|
|
2
|
-
const LATEST_VERSION_URL = "https://pi.dev/api/latest-version";
|
|
3
3
|
const DEFAULT_VERSION_CHECK_TIMEOUT_MS = 10000;
|
|
4
4
|
function parsePackageVersion(version) {
|
|
5
5
|
const match = version.trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+.*)?$/);
|
|
@@ -43,26 +43,27 @@ export function isNewerPackageVersion(candidateVersion, currentVersion) {
|
|
|
43
43
|
export async function getLatestPiRelease(currentVersion, options = {}) {
|
|
44
44
|
if (process.env.PI_SKIP_VERSION_CHECK || process.env.PI_OFFLINE)
|
|
45
45
|
return undefined;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
46
|
+
try {
|
|
47
|
+
const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`, {
|
|
48
|
+
headers: {
|
|
49
|
+
"User-Agent": getPiUserAgent(currentVersion),
|
|
50
|
+
accept: "application/json",
|
|
51
|
+
},
|
|
52
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? DEFAULT_VERSION_CHECK_TIMEOUT_MS),
|
|
53
|
+
});
|
|
54
|
+
if (!response.ok)
|
|
55
|
+
return undefined;
|
|
56
|
+
const data = (await response.json());
|
|
57
|
+
if (typeof data.version !== "string" || !data.version.trim()) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
version: data.version.trim(),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
57
65
|
return undefined;
|
|
58
66
|
}
|
|
59
|
-
const packageName = typeof data.packageName === "string" && data.packageName.trim() ? data.packageName.trim() : undefined;
|
|
60
|
-
const note = typeof data.note === "string" && data.note.trim() ? data.note.trim() : undefined;
|
|
61
|
-
return {
|
|
62
|
-
version: data.version.trim(),
|
|
63
|
-
packageName,
|
|
64
|
-
...(note ? { note } : {}),
|
|
65
|
-
};
|
|
66
67
|
}
|
|
67
68
|
export async function getLatestPiVersion(currentVersion, options = {}) {
|
|
68
69
|
return (await getLatestPiRelease(currentVersion, options))?.version;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version-check.js","sourceRoot":"","sources":["../../src/utils/version-check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"version-check.js","sourceRoot":"","sources":["../../src/utils/version-check.ts"],"names":[],"mappings":"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","sourcesContent":["import { PACKAGE_NAME } from \"../config.ts\";\nimport { getPiUserAgent } from \"./pi-user-agent.ts\";\n\nconst DEFAULT_VERSION_CHECK_TIMEOUT_MS = 10000;\n\nexport interface LatestPiRelease {\n\tversion: string;\n\tpackageName?: string;\n\tnote?: string;\n}\n\ninterface ParsedVersion {\n\tmajor: number;\n\tminor: number;\n\tpatch: number;\n\tprerelease?: string;\n}\n\nfunction parsePackageVersion(version: string): ParsedVersion | undefined {\n\tconst match = version.trim().match(/^v?(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?(?:\\+.*)?$/);\n\tif (!match) {\n\t\treturn undefined;\n\t}\n\treturn {\n\t\tmajor: Number.parseInt(match[1], 10),\n\t\tminor: Number.parseInt(match[2], 10),\n\t\tpatch: Number.parseInt(match[3], 10),\n\t\tprerelease: match[4],\n\t};\n}\n\nexport function comparePackageVersions(leftVersion: string, rightVersion: string): number | undefined {\n\tconst left = parsePackageVersion(leftVersion);\n\tconst right = parsePackageVersion(rightVersion);\n\tif (!left || !right) {\n\t\treturn undefined;\n\t}\n\n\tif (left.major !== right.major) return left.major - right.major;\n\tif (left.minor !== right.minor) return left.minor - right.minor;\n\tif (left.patch !== right.patch) return left.patch - right.patch;\n\tif (left.prerelease === right.prerelease) return 0;\n\tif (!left.prerelease) return 1;\n\tif (!right.prerelease) return -1;\n\treturn left.prerelease.localeCompare(right.prerelease);\n}\n\nexport function isNewerPackageVersion(candidateVersion: string, currentVersion: string): boolean {\n\tconst comparison = comparePackageVersions(candidateVersion, currentVersion);\n\tif (comparison !== undefined) {\n\t\treturn comparison > 0;\n\t}\n\treturn candidateVersion.trim() !== currentVersion.trim();\n}\n\nexport async function getLatestPiRelease(\n\tcurrentVersion: string,\n\toptions: { timeoutMs?: number } = {},\n): Promise<LatestPiRelease | undefined> {\n\tif (process.env.PI_SKIP_VERSION_CHECK || process.env.PI_OFFLINE) return undefined;\n\n\ttry {\n\t\tconst response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`, {\n\t\t\theaders: {\n\t\t\t\t\"User-Agent\": getPiUserAgent(currentVersion),\n\t\t\t\taccept: \"application/json\",\n\t\t\t},\n\t\t\tsignal: AbortSignal.timeout(options.timeoutMs ?? DEFAULT_VERSION_CHECK_TIMEOUT_MS),\n\t\t});\n\t\tif (!response.ok) return undefined;\n\n\t\tconst data = (await response.json()) as {\n\t\t\tversion?: unknown;\n\t\t};\n\t\tif (typeof data.version !== \"string\" || !data.version.trim()) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn {\n\t\t\tversion: data.version.trim(),\n\t\t};\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport async function getLatestPiVersion(\n\tcurrentVersion: string,\n\toptions: { timeoutMs?: number } = {},\n): Promise<string | undefined> {\n\treturn (await getLatestPiRelease(currentVersion, options))?.version;\n}\n\nexport async function checkForNewPiVersion(currentVersion: string): Promise<LatestPiRelease | undefined> {\n\ttry {\n\t\tconst latestRelease = await getLatestPiRelease(currentVersion);\n\t\tif (latestRelease && isNewerPackageVersion(latestRelease.version, currentVersion)) {\n\t\t\treturn latestRelease;\n\t\t}\n\t\treturn undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n"]}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extension-custom-provider",
|
|
3
|
-
"version": "0.75.
|
|
3
|
+
"version": "0.75.23",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pi-extension-custom-provider",
|
|
9
|
-
"version": "0.75.
|
|
9
|
+
"version": "0.75.23",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.52.0"
|
|
12
12
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extension-sandbox",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.23",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pi-extension-sandbox",
|
|
9
|
-
"version": "1.5.
|
|
9
|
+
"version": "1.5.23",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sandbox-runtime": "^0.0.26"
|
|
12
12
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extension-with-deps",
|
|
3
|
-
"version": "0.75.
|
|
3
|
+
"version": "0.75.23",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pi-extension-with-deps",
|
|
9
|
-
"version": "0.75.
|
|
9
|
+
"version": "0.75.23",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"ms": "^2.1.3"
|
|
12
12
|
},
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openeryc/pi-coding-agent",
|
|
3
|
-
"version": "0.75.
|
|
3
|
+
"version": "0.75.23",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@openeryc/pi-coding-agent",
|
|
9
|
-
"version": "0.75.
|
|
9
|
+
"version": "0.75.23",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@openeryc/pi-agent-core": "^0.75.
|
|
13
|
-
"@openeryc/pi-ai": "^0.75.
|
|
14
|
-
"@openeryc/pi-tui": "^0.75.
|
|
12
|
+
"@openeryc/pi-agent-core": "^0.75.23",
|
|
13
|
+
"@openeryc/pi-ai": "^0.75.23",
|
|
14
|
+
"@openeryc/pi-tui": "^0.75.23",
|
|
15
15
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
16
16
|
"chalk": "5.6.2",
|
|
17
17
|
"cross-spawn": "7.0.6",
|
|
@@ -699,11 +699,11 @@
|
|
|
699
699
|
]
|
|
700
700
|
},
|
|
701
701
|
"node_modules/@openeryc/pi-agent-core": {
|
|
702
|
-
"version": "0.75.
|
|
703
|
-
"resolved": "https://registry.npmjs.org/@openeryc/pi-agent-core/-/pi-agent-core-0.75.
|
|
702
|
+
"version": "0.75.23",
|
|
703
|
+
"resolved": "https://registry.npmjs.org/@openeryc/pi-agent-core/-/pi-agent-core-0.75.23.tgz",
|
|
704
704
|
"license": "MIT",
|
|
705
705
|
"dependencies": {
|
|
706
|
-
"@openeryc/pi-ai": "^0.75.
|
|
706
|
+
"@openeryc/pi-ai": "^0.75.23",
|
|
707
707
|
"ignore": "7.0.5",
|
|
708
708
|
"typebox": "1.1.38",
|
|
709
709
|
"yaml": "2.9.0"
|
|
@@ -713,8 +713,8 @@
|
|
|
713
713
|
}
|
|
714
714
|
},
|
|
715
715
|
"node_modules/@openeryc/pi-ai": {
|
|
716
|
-
"version": "0.75.
|
|
717
|
-
"resolved": "https://registry.npmjs.org/@openeryc/pi-ai/-/pi-ai-0.75.
|
|
716
|
+
"version": "0.75.23",
|
|
717
|
+
"resolved": "https://registry.npmjs.org/@openeryc/pi-ai/-/pi-ai-0.75.23.tgz",
|
|
718
718
|
"license": "MIT",
|
|
719
719
|
"dependencies": {
|
|
720
720
|
"@anthropic-ai/sdk": "0.91.1",
|
|
@@ -735,8 +735,8 @@
|
|
|
735
735
|
}
|
|
736
736
|
},
|
|
737
737
|
"node_modules/@openeryc/pi-tui": {
|
|
738
|
-
"version": "0.75.
|
|
739
|
-
"resolved": "https://registry.npmjs.org/@openeryc/pi-tui/-/pi-tui-0.75.
|
|
738
|
+
"version": "0.75.23",
|
|
739
|
+
"resolved": "https://registry.npmjs.org/@openeryc/pi-tui/-/pi-tui-0.75.23.tgz",
|
|
740
740
|
"license": "MIT",
|
|
741
741
|
"dependencies": {
|
|
742
742
|
"get-east-asian-width": "1.6.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openeryc/pi-coding-agent",
|
|
3
|
-
"version": "0.75.
|
|
3
|
+
"version": "0.75.23",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"prepublishOnly": "npm run clean && npm run build && npm run shrinkwrap"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@openeryc/pi-agent-core": "^0.75.
|
|
43
|
-
"@openeryc/pi-ai": "^0.75.
|
|
44
|
-
"@openeryc/pi-tui": "^0.75.
|
|
42
|
+
"@openeryc/pi-agent-core": "^0.75.23",
|
|
43
|
+
"@openeryc/pi-ai": "^0.75.23",
|
|
44
|
+
"@openeryc/pi-tui": "^0.75.23",
|
|
45
45
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
46
46
|
"chalk": "5.6.2",
|
|
47
47
|
"cross-spawn": "7.0.6",
|