Mike0021 commited on
Commit
f104170
·
verified ·
1 Parent(s): 39371ea

Document verified curl support and add live browser HTTP tests

Browse files
README.md CHANGED
@@ -23,7 +23,7 @@ tags:
23
 
24
  An independent duplicate of [victor/MiniCPM5-2B-WebGPU-Pi](https://huggingface.co/spaces/victor/MiniCPM5-2B-WebGPU-Pi) at revision `959e272adeb7931e32d3eb2177dc1d5e96c3e967`. The original Space is unchanged.
25
 
26
- MiniCPM5-2B inference and just-bash run in a browser worker. This copy adds real `curl`/`wget` requests through browser `fetch`, with no backend or proxy. The first model download is about 1.84 GB, verified and cached locally. WebGPU with shader-f16 is required for inference; the Shell can run HTTP commands without loading the model.
27
 
28
  Try in Shell:
29
 
@@ -38,6 +38,6 @@ Allowed HTTPS hosts: `api.github.com`, `raw.githubusercontent.com`, `huggingface
38
 
39
  Browser CORS applies. Redirects require the final URL directly: browsers hide redirect destinations, so this copy reports an error instead of silently returning empty output or following unchecked hosts. Cookies are omitted. Explicit request URLs, headers, and bodies are sent to the destination; inference and workspace storage remain local. Node.js, Python, npm, and native programs remain unavailable.
40
 
41
- Source is under `app/`. Rebuild with `cd app && npm ci && npm test && npm run build`, then `python3 scripts/stage.py`. The HTTP implementation is in `app/src/browser-http.mjs`; it uses just-bash's existing curl/wget commands and custom transport hook. Tests and results are in `validation/`.
42
 
43
  The small 4-bit model can make mistakes. Shell transport checks and actual-model trials are reported separately; they do not establish general agent reliability or physical-phone performance.
 
23
 
24
  An independent duplicate of [victor/MiniCPM5-2B-WebGPU-Pi](https://huggingface.co/spaces/victor/MiniCPM5-2B-WebGPU-Pi) at revision `959e272adeb7931e32d3eb2177dc1d5e96c3e967`. The original Space is unchanged.
25
 
26
+ MiniCPM5-2B inference and just-bash run in a browser worker. This copy adds real `curl` requests through browser `fetch`, with no backend or proxy. The first model download is about 1.84 GB, verified and cached locally. WebGPU with shader-f16 is required for inference; the Shell can run HTTP commands without loading the model.
27
 
28
  Try in Shell:
29
 
 
38
 
39
  Browser CORS applies. Redirects require the final URL directly: browsers hide redirect destinations, so this copy reports an error instead of silently returning empty output or following unchecked hosts. Cookies are omitted. Explicit request URLs, headers, and bodies are sent to the destination; inference and workspace storage remain local. Node.js, Python, npm, and native programs remain unavailable.
40
 
41
+ Source is under `app/`. Rebuild with `cd app && npm ci && npm test && npm run build`, then `python3 scripts/stage.py`. The HTTP implementation is in `app/src/browser-http.mjs`; it uses just-bash's existing curl commands and custom transport hook. Tests and results are in `validation/`.
42
 
43
  The small 4-bit model can make mistakes. Shell transport checks and actual-model trials are reported separately; they do not establish general agent reliability or physical-phone performance.
app/README.md CHANGED
@@ -52,7 +52,7 @@ Message padding follows Pi 0.85.1 defaults: one column of output padding, one in
52
 
53
  The full Node CLI, host filesystem, OAuth providers, extensions, package installation, and RPC server are not included. xterm.js is the terminal emulator; it does not add Node.js or a native shell. Model inference and tool execution remain in the browser worker. Terminal control sequences from model/file output are stripped before rendering, and Markdown images do not trigger downloads.
54
 
55
- just-bash is a simulated shell, not a Linux VM. Shell scripts, pipes, redirects, `awk`, `jq`, `sed`, `grep`, `find`, and other supported text commands execute in JavaScript. Node.js, Python, npm, native executables, and SQLite are unavailable. This HTTP experiment enables curl/wget for selected HTTPS APIs through a browser fetch adapter; see public/http-help.html for hosts, examples, and limits. The installed browser bundle also excludes working gzip/zlib commands. Shell cwd and variables reset between commands; filesystem changes persist. Commands have a 10-second deadline, 3,000-command cap, 16 MiB filesystem budget, and 256 KiB output budget. Tool output passed to the model is capped at 6,000 characters.
56
 
57
  Local model and filesystem tool calls can run with the browser network disabled after loading. HTTP commands require a connection and CORS permission from the destination. Reopening the app itself still requires its static HTML/JS/WASM assets; there is no offline service worker. Browser storage belongs to this site's origin and may be evicted by the browser. Export important work.
58
 
 
52
 
53
  The full Node CLI, host filesystem, OAuth providers, extensions, package installation, and RPC server are not included. xterm.js is the terminal emulator; it does not add Node.js or a native shell. Model inference and tool execution remain in the browser worker. Terminal control sequences from model/file output are stripped before rendering, and Markdown images do not trigger downloads.
54
 
55
+ just-bash is a simulated shell, not a Linux VM. Shell scripts, pipes, redirects, `awk`, `jq`, `sed`, `grep`, `find`, and other supported text commands execute in JavaScript. Node.js, Python, npm, native executables, and SQLite are unavailable. This HTTP experiment enables curl for selected HTTPS APIs through a browser fetch adapter; see public/http-help.html for hosts, examples, and limits. The installed browser bundle also excludes working gzip/zlib commands. Shell cwd and variables reset between commands; filesystem changes persist. Commands have a 10-second deadline, 3,000-command cap, 16 MiB filesystem budget, and 256 KiB output budget. Tool output passed to the model is capped at 6,000 characters.
56
 
57
  Local model and filesystem tool calls can run with the browser network disabled after loading. HTTP commands require a connection and CORS permission from the destination. Reopening the app itself still requires its static HTML/JS/WASM assets; there is no offline service worker. Browser storage belongs to this site's origin and may be evicted by the browser. Export important work.
58
 
app/index.html CHANGED
@@ -65,7 +65,7 @@
65
  </form>
66
  <div id="shell-hint"><span>Enter to run · Shift+Enter for newline</span><span>↑↓ history</span></div>
67
  </details>
68
- <p class="workspace-note">HTTP experiment: curl / wget can reach selected APIs. Browser CORS applies. <a href="./http-help.html" target="_blank" rel="noopener">Hosts &amp; examples ↗</a></p>
69
  </aside>
70
  </main>
71
  <dialog id="model-dialog" aria-labelledby="model-title" aria-describedby="model-description">
 
65
  </form>
66
  <div id="shell-hint"><span>Enter to run · Shift+Enter for newline</span><span>↑↓ history</span></div>
67
  </details>
68
+ <p class="workspace-note">HTTP experiment: curl can reach selected APIs. Browser CORS applies. <a href="./http-help.html" target="_blank" rel="noopener">Hosts &amp; examples ↗</a></p>
69
  </aside>
70
  </main>
71
  <dialog id="model-dialog" aria-labelledby="model-title" aria-describedby="model-description">
app/public/http-help.html CHANGED
@@ -3,7 +3,7 @@
3
  <title>Browser HTTP experiment</title>
4
  <style>html{color-scheme:dark;background:#18181e;color:#e4e4eb;font:17px/1.6 system-ui}body{max-width:760px;margin:40px auto;padding:0 20px}a{color:#a4e2d4}pre{background:#262630;padding:16px;overflow:auto;border-radius:12px}code{font-size:14px}</style>
5
  <h1>HTTP from the browser shell</h1>
6
- <p>This experimental copy enables real <code>curl</code> and <code>wget</code> requests. The model and shell run on your device. Requests go directly from your browser to the destination; there is no proxy. Request URLs, headers, and bodies are sent to that service.</p>
7
  <p>Allowed HTTPS hosts: <code>api.github.com</code>, <code>raw.githubusercontent.com</code>, <code>huggingface.co</code>, <code>jsonplaceholder.typicode.com</code>, and <code>httpbin.org</code>. GET, HEAD, and POST are enabled. Cookies are omitted. Requests have an 8-second deadline and a 1 MiB response limit.</p>
8
  <h2>Try in Shell</h2>
9
  <pre>curl -fsS https://jsonplaceholder.typicode.com/todos/1 -o todo.json
 
3
  <title>Browser HTTP experiment</title>
4
  <style>html{color-scheme:dark;background:#18181e;color:#e4e4eb;font:17px/1.6 system-ui}body{max-width:760px;margin:40px auto;padding:0 20px}a{color:#a4e2d4}pre{background:#262630;padding:16px;overflow:auto;border-radius:12px}code{font-size:14px}</style>
5
  <h1>HTTP from the browser shell</h1>
6
+ <p>This experimental copy enables real <code>curl</code> requests. The model and shell run on your device. Requests go directly from your browser to the destination; there is no proxy. Request URLs, headers, and bodies are sent to that service.</p>
7
  <p>Allowed HTTPS hosts: <code>api.github.com</code>, <code>raw.githubusercontent.com</code>, <code>huggingface.co</code>, <code>jsonplaceholder.typicode.com</code>, and <code>httpbin.org</code>. GET, HEAD, and POST are enabled. Cookies are omitted. Requests have an 8-second deadline and a 1 MiB response limit.</p>
8
  <h2>Try in Shell</h2>
9
  <pre>curl -fsS https://jsonplaceholder.typicode.com/todos/1 -o todo.json
app/src/browser-http.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // just-bash's curl/wget use this browser transport. There is no proxy/server.
2
  export const HTTP_ORIGINS = Object.freeze([
3
  'https://api.github.com',
4
  'https://raw.githubusercontent.com',
 
1
+ // just-bash's curl use this browser transport. There is no proxy/server.
2
  export const HTTP_ORIGINS = Object.freeze([
3
  'https://api.github.com',
4
  'https://raw.githubusercontent.com',
app/src/workspace.mjs CHANGED
@@ -52,7 +52,7 @@ export function createWorkspace(files = initialFiles, entries) {
52
  await bash.fs.writeFile(resolved, content);
53
  }
54
  const tools = [{
55
- name: 'bash', label: 'Bash', description: `Run a bash command in /workspace. Supports cat, ls, grep, sed, awk, jq, sort, pipes, redirects, shell scripts, and curl/wget. HTTP GET/HEAD/POST is enabled for ${HTTP_ORIGINS.map(x => new URL(x).host).join(', ')}. Use curl -fsS URL; use set -o pipefail before piping HTTP output to jq. Browser CORS applies; redirects require the final URL. HTTP limits: 8 seconds, 1 MiB. No Node.js, Python, npm, or native binaries. Files persist across commands; cd and variables reset each call.`,
56
  parameters: Type.Object({ command: Type.String({ description: 'The shell command to execute.' }) }),
57
  execute: async (_id, { command }, signal) => {
58
  const r = await bash.exec(command, { signal });
 
52
  await bash.fs.writeFile(resolved, content);
53
  }
54
  const tools = [{
55
+ name: 'bash', label: 'Bash', description: `Run a bash command in /workspace. Supports cat, ls, grep, sed, awk, jq, sort, pipes, redirects, shell scripts, and curl. HTTP GET/HEAD/POST is enabled for ${HTTP_ORIGINS.map(x => new URL(x).host).join(', ')}. Use curl -fsS URL; use set -o pipefail before piping HTTP output to jq. Browser CORS applies; redirects require the final URL. HTTP limits: 8 seconds, 1 MiB. No Node.js, Python, npm, or native binaries. Files persist across commands; cd and variables reset each call.`,
56
  parameters: Type.Object({ command: Type.String({ description: 'The shell command to execute.' }) }),
57
  execute: async (_id, { command }, signal) => {
58
  const r = await bash.exec(command, { signal });
app/tests/http-agent-live.mjs ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { chromium } from 'playwright';
2
+ import fs from 'node:fs/promises';
3
+
4
+ const root = new URL('../..', import.meta.url).pathname;
5
+ const url = process.argv[2] ?? 'https://mike0021-minicpm5-2b-webgpu-pi-http.static.hf.space/';
6
+ const result = { date: new Date().toISOString(), url, cases: [], errors: [], requests: [] };
7
+ const context = await chromium.launchPersistentContext(root + '/validation/http-browser-profile', {
8
+ executablePath:'/usr/bin/chromium', headless:true, viewport:{width:1440,height:1000},
9
+ args:['--enable-unsafe-webgpu','--use-angle=vulkan','--enable-features=Vulkan','--disable-vulkan-surface'],
10
+ });
11
+ context.setDefaultTimeout(300_000);
12
+ const page = await context.newPage();
13
+ page.on('pageerror', e => result.errors.push(e.message));
14
+ context.on('request', r => result.requests.push({url:r.url(),method:r.method()}));
15
+ const rpc = (action,args) => page.evaluate(({action,args})=>window.browserPi.rpc(action,args),{action,args});
16
+ const save = () => fs.writeFile(root+'/validation/http-agent-live.json',JSON.stringify(result,null,2)+'\n');
17
+ const tasks = [
18
+ { name:'fetch-and-answer', prompt:'Fetch https://jsonplaceholder.typicode.com/todos/1 and tell me its title and whether it is completed.', check: c => c.httpToolSuccess && /delectus aut autem/.test(c.final) && /false|not complet|incomplete/i.test(c.final) },
19
+ { name:'fetch-count-write', prompt:'Download https://jsonplaceholder.typicode.com/users into users.json. Count the users and save a report containing that count to users-report.txt. Verify the report.', check: c => c.httpToolSuccess && JSON.parse(c.state.files['/workspace/users.json']).length === 10 && /\b10\b/.test(c.state.files['/workspace/users-report.txt']??'') && c.toolResults.some(t => !t.isError && /10/.test(JSON.stringify(t.content)) && t.toolName === 'read') },
20
+ { name:'github-api', prompt:'Use the GitHub API to tell me the full repository name and license of huggingface/transformers.js.', check: c => c.httpToolSuccess && /huggingface\/transformers\.js/.test(c.final) && /Apache[- ]2\.0/i.test(c.final) },
21
+ { name:'post-json', prompt:'POST the JSON {"demo":"browser-http","value":144} to https://httpbin.org/post. Tell me the JSON it echoes back.', check: c => c.httpToolSuccess && /browser-http/.test(c.final) && /144/.test(c.final) && c.requests.some(r=>r.method==='POST'&&r.url==='https://httpbin.org/post') },
22
+ { name:'blocked-host', prompt:'Fetch https://example.com/ and tell me its page title.', check: c => /not (enabled|allowed|permitted)|not.*allowed.*host|restrict|allowlist|allow.list|cannot.*(access|fetch)|can.t.*(access|fetch)/i.test(c.final) && !/Example Domain/.test(c.final) && !c.requests.some(r=>new URL(r.url).hostname==='example.com') },
23
+ { name:'cors-error', prompt:'Fetch https://huggingface.co/ and tell me its page title.', check: c => /CORS|browser.*block|block.*browser|request failed|unable.*fetch|cannot.*fetch|can.t.*fetch/i.test(c.final) && c.toolResults.some(t => t.isError && /CORS/.test(JSON.stringify(t.content))) },
24
+ { name:'fetch-and-answer-repeat', prompt:'Fetch https://jsonplaceholder.typicode.com/todos/1 and tell me its title and whether it is completed.', check: c => c.httpToolSuccess && /delectus aut autem/.test(c.final) && /false|not complet|incomplete/i.test(c.final) },
25
+ ];
26
+ let monitor;
27
+ try {
28
+ await page.goto(url); await page.waitForFunction(()=>window.browserPi?.terminal); await page.evaluate(()=>window.browserPi.ready);
29
+ result.wasCached=await rpc('cache_status'); console.log('MODEL_CACHE',result.wasCached);
30
+ const start=Date.now();
31
+ monitor=setInterval(async()=>{try{console.log('LOAD_PROGRESS',Math.round((Date.now()-start)/1000),await page.locator('#load-status').textContent(),await page.locator('#load-detail').textContent());}catch{}},20000);
32
+ await page.evaluate(()=>window.browserPi.load()); clearInterval(monitor);
33
+ result.modelLoadMs=Date.now()-start; result.device=await page.evaluate(()=>window.browserPi.diagnostics.device);
34
+ console.log('MODEL_LOADED',result.modelLoadMs,JSON.stringify(result.device)); await save();
35
+ for (const task of tasks) {
36
+ await page.locator('#new-chat').click(); await page.waitForFunction(()=>!window.browserPi.busy);
37
+ const start=Date.now(), requestStart=result.requests.length;
38
+ console.log('START',task.name);
39
+ let count=0;
40
+ monitor=setInterval(async()=>{try{const state=await rpc('snapshot');const tools=state.messages.filter(m=>m.role==='toolResult');console.log('PROGRESS',task.name,Math.round((Date.now()-start)/1000),'tools',tools.length);for(const t of tools.slice(count))console.log('TOOL',t.toolName,t.isError,JSON.stringify(t.content).slice(0,400));count=tools.length;}catch{}},15000);
41
+ const pending=page.evaluate(text=>window.browserPi.prompt(text),task.prompt);
42
+ let timer;
43
+ try { await Promise.race([pending,new Promise((_,reject)=>{timer=setTimeout(()=>reject(Error('Trial exceeded 180 seconds')),180000);})]); }
44
+ catch(e){ await rpc('stop'); await pending.catch(()=>{}); task.failure=e.message; }
45
+ finally{clearTimeout(timer);clearInterval(monitor);}
46
+ const state=await rpc('snapshot');
47
+ const entry={name:task.name,prompt:task.prompt,elapsedMs:Date.now()-start,state,requests:result.requests.slice(requestStart),failure:task.failure};
48
+ entry.final=state.messages.filter(m=>m.role==='assistant').at(-1)?.content.filter(c=>c.type==='text').map(c=>c.text).join('\n')??'';
49
+ entry.toolResults=state.messages.filter(m=>m.role==='toolResult');
50
+ entry.calls=state.messages.filter(m=>m.role==='assistant').flatMap(m=>m.content.filter(c=>c.type==='toolCall'));
51
+ entry.httpToolSuccess=entry.toolResults.some(t=>t.toolName==='bash'&&!t.isError&&entry.calls.some(c=>c.id===t.toolCallId&&/\bcurl\b|\bwget\b/.test(c.arguments?.command??'')));
52
+ try{entry.passed=!entry.failure&&task.check(entry);}catch(e){entry.passed=false;entry.checkError=e.message;}
53
+ result.cases.push(entry);await save();
54
+ console.log('RESULT',JSON.stringify({name:entry.name,elapsedMs:entry.elapsedMs,passed:entry.passed,final:entry.final,calls:entry.calls}));
55
+ await page.screenshot({path:root+'/validation/http-agent-'+task.name+'.png',fullPage:true});
56
+ }
57
+ result.passed=result.cases.every(c=>c.passed)&&!result.errors.length;
58
+ result.diagnostics=await page.evaluate(()=>window.browserPi.diagnostics);
59
+ console.log('SUMMARY',JSON.stringify({passed:result.passed,cases:result.cases.map(c=>({name:c.name,passed:c.passed,seconds:c.elapsedMs/1000}))}));
60
+ }catch(e){result.failure=e.stack;console.log('FAIL',e.stack);process.exitCode=1;}
61
+ finally{clearInterval(monitor);await save();await context.close();}
app/tests/http-live.mjs ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { chromium } from 'playwright';
2
+ import fs from 'node:fs/promises';
3
+ import assert from 'node:assert/strict';
4
+
5
+ const root = new URL('../..', import.meta.url).pathname;
6
+ const url = process.argv[2] ?? 'https://mike0021-minicpm5-2b-webgpu-pi-http.static.hf.space/';
7
+ const result = { date: new Date().toISOString(), url, cases: [], errors: [], requests: [] };
8
+ const context = await chromium.launchPersistentContext(root + '/validation/http-browser-profile', {
9
+ executablePath: '/usr/bin/chromium', headless: true, viewport: { width: 1440, height: 1000 },
10
+ args: ['--enable-unsafe-webgpu', '--use-angle=vulkan', '--enable-features=Vulkan', '--disable-vulkan-surface'],
11
+ });
12
+ context.setDefaultTimeout(30_000);
13
+ const page = await context.newPage();
14
+ page.on('pageerror', e => result.errors.push(e.message));
15
+ context.on('request', r => { if (!r.url().startsWith(new URL(url).origin)) result.requests.push({ url: r.url(), method: r.method() }); });
16
+ const rpc = (action, args) => page.evaluate(({ action, args }) => window.browserPi.rpc(action, args), { action, args });
17
+ const save = () => fs.writeFile(root + '/validation/http-shell-live.json', JSON.stringify(result, null, 2) + '\n');
18
+ const cases = [
19
+ ['json-get-save', 'curl -fsS https://jsonplaceholder.typicode.com/todos/1 -o todo.json && jq -r .title todo.json', r => r.exitCode === 0 && r.stdout.includes('delectus aut autem')],
20
+ ['github-pipeline', "set -o pipefail; curl -fsS https://api.github.com/repos/huggingface/transformers.js | jq -r '.full_name, .license.spdx_id'", r => r.exitCode === 0 && r.stdout.includes('huggingface/transformers.js')],
21
+ ['hf-metadata', 'set -o pipefail; curl -fsS https://huggingface.co/api/models/openbmb/MiniCPM5-2B | jq -r .id', r => r.exitCode === 0 && /openbmb\/MiniCPM5-2B/i.test(r.stdout)],
22
+ ['raw-github', 'curl -fsS https://raw.githubusercontent.com/huggingface/transformers.js/main/package.json -o package.json && jq -r .name package.json', r => r.exitCode === 0 && r.stdout.includes('@huggingface/transformers')],
23
+ ['head', 'curl -I https://jsonplaceholder.typicode.com/todos/1', r => r.exitCode === 0 && r.stdout.includes('200')],
24
+ ['post-json', `set -o pipefail; curl -fsS https://httpbin.org/post -H 'Content-Type: application/json' -d '{"demo":"browser-http","value":144}' | jq -c .json`, r => r.exitCode === 0 && r.stdout.includes('browser-http') && r.stdout.includes('144')],
25
+ ['request-headers', "set -o pipefail; curl -fsS https://httpbin.org/headers -H 'X-Demo: browser-http' | jq -r '.headers[\"X-Demo\"]'", r => r.exitCode === 0 && r.stdout.trim() === 'browser-http'],
26
+ ['http-404', 'curl -fsS https://jsonplaceholder.typicode.com/does-not-exist', r => r.exitCode !== 0 && /404/.test(r.stderr)],
27
+ ['blocked-host', 'curl -fsS https://example.com/', r => r.exitCode !== 0 && /not enabled/.test(r.stderr)],
28
+ ['cors-failure', 'curl -fsS https://huggingface.co/', r => r.exitCode !== 0 && /CORS/.test(r.stderr)],
29
+ ['redirect-failure', 'curl -fsSL https://httpbin.org/redirect-to?url=https%3A%2F%2Fhttpbin.org%2Fget', r => r.exitCode !== 0 && /redirect/i.test(r.stderr)],
30
+ ['timeout', 'curl -fsS --max-time 1 https://httpbin.org/delay/5', r => r.exitCode !== 0 && /timed out/i.test(r.stderr)],
31
+ ['failed-pipeline', 'set -o pipefail; curl -fsS https://example.com/ | jq .', r => r.exitCode !== 0],
32
+ ];
33
+ try {
34
+ await page.goto(url); await page.waitForFunction(() => window.browserPi?.terminal); await page.evaluate(() => window.browserPi.ready);
35
+ assert.match(await page.locator('.workspace-note').textContent(), /HTTP experiment/);
36
+ for (const [name, command, check] of cases) {
37
+ const start = Date.now(); const response = await rpc('shell', { command });
38
+ const entry = { name, command, elapsedMs: Date.now() - start, stdout: response.stdout, stderr: response.stderr, exitCode: response.exitCode, passed: check(response) };
39
+ result.cases.push(entry); console.log(JSON.stringify(entry)); await save();
40
+ }
41
+ const start = Date.now(); const pending = rpc('shell', { command: 'curl -fsS https://httpbin.org/delay/7' });
42
+ await page.waitForFunction(() => window.browserPi.busy); await page.waitForTimeout(200); await rpc('stop');
43
+ let cancelled; try { cancelled = await pending; } catch(e) { cancelled = { stderr: e.message, exitCode: 1 }; }
44
+ result.cases.push({ name: 'cancel', elapsedMs: Date.now() - start, stdout: cancelled.stdout, stderr: cancelled.stderr, exitCode: cancelled.exitCode, passed: cancelled.exitCode !== 0 && Date.now() - start < 2500 });
45
+ await page.locator('#command').fill('curl -fsS https://jsonplaceholder.typicode.com/todos/1 -o todo.json && jq . todo.json');
46
+ await page.locator('#shell-run').click();
47
+ await page.waitForFunction(() => !window.browserPi.busy && document.querySelector('.shell-entry:last-child .shell-status.success'));
48
+ await page.screenshot({ path: root + '/validation/http-desktop.png', fullPage: true });
49
+ await page.reload(); await page.waitForFunction(() => window.browserPi); await page.evaluate(() => window.browserPi.ready);
50
+ const saved = await rpc('read', { path: '/workspace/todo.json' });
51
+ result.cases.push({ name: 'file-survives-reload', passed: JSON.parse(saved).title === 'delectus aut autem' });
52
+ await page.setViewportSize({ width: 390, height: 844 });
53
+ await page.screenshot({ path: root + '/validation/http-mobile.png', fullPage: true });
54
+ result.mobileOverflow = await page.evaluate(() => document.documentElement.scrollWidth > innerWidth);
55
+ result.device = await page.evaluate(async () => { const a = await navigator.gpu?.requestAdapter({powerPreference:'high-performance'}); return a ? { vendor:a.info.vendor, architecture:a.info.architecture, features:[...a.features] } : null; });
56
+ result.passed = result.cases.every(c => c.passed) && !result.errors.length && !result.mobileOverflow;
57
+ console.log('SUMMARY', JSON.stringify({passed:result.passed, cases:result.cases.length, device:result.device, errors:result.errors}));
58
+ } catch (e) { result.failure = e.stack; console.log('FAIL',e.stack); process.exitCode=1; }
59
+ finally { await save(); await context.close(); }
assets/agent.worker-euGpP_Md.js ADDED
The diff for this file is too large to render. See raw diff
 
assets/index-wZi62oe2.js ADDED
The diff for this file is too large to render. See raw diff
 
http-help.html CHANGED
@@ -3,7 +3,7 @@
3
  <title>Browser HTTP experiment</title>
4
  <style>html{color-scheme:dark;background:#18181e;color:#e4e4eb;font:17px/1.6 system-ui}body{max-width:760px;margin:40px auto;padding:0 20px}a{color:#a4e2d4}pre{background:#262630;padding:16px;overflow:auto;border-radius:12px}code{font-size:14px}</style>
5
  <h1>HTTP from the browser shell</h1>
6
- <p>This experimental copy enables real <code>curl</code> and <code>wget</code> requests. The model and shell run on your device. Requests go directly from your browser to the destination; there is no proxy. Request URLs, headers, and bodies are sent to that service.</p>
7
  <p>Allowed HTTPS hosts: <code>api.github.com</code>, <code>raw.githubusercontent.com</code>, <code>huggingface.co</code>, <code>jsonplaceholder.typicode.com</code>, and <code>httpbin.org</code>. GET, HEAD, and POST are enabled. Cookies are omitted. Requests have an 8-second deadline and a 1 MiB response limit.</p>
8
  <h2>Try in Shell</h2>
9
  <pre>curl -fsS https://jsonplaceholder.typicode.com/todos/1 -o todo.json
 
3
  <title>Browser HTTP experiment</title>
4
  <style>html{color-scheme:dark;background:#18181e;color:#e4e4eb;font:17px/1.6 system-ui}body{max-width:760px;margin:40px auto;padding:0 20px}a{color:#a4e2d4}pre{background:#262630;padding:16px;overflow:auto;border-radius:12px}code{font-size:14px}</style>
5
  <h1>HTTP from the browser shell</h1>
6
+ <p>This experimental copy enables real <code>curl</code> requests. The model and shell run on your device. Requests go directly from your browser to the destination; there is no proxy. Request URLs, headers, and bodies are sent to that service.</p>
7
  <p>Allowed HTTPS hosts: <code>api.github.com</code>, <code>raw.githubusercontent.com</code>, <code>huggingface.co</code>, <code>jsonplaceholder.typicode.com</code>, and <code>httpbin.org</code>. GET, HEAD, and POST are enabled. Cookies are omitted. Requests have an 8-second deadline and a 1 MiB response limit.</p>
8
  <h2>Try in Shell</h2>
9
  <pre>curl -fsS https://jsonplaceholder.typicode.com/todos/1 -o todo.json
index.html CHANGED
@@ -21,7 +21,7 @@
21
  <meta name="twitter:image" content="https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/resolve/main/social-thumbnail-v3.png">
22
  <meta name="twitter:image:alt" content="A coding agent running entirely in your browser, above the Pi terminal and its pixel cat mascot.">
23
  <title>Pi · Coding agent in your browser</title>
24
- <script type="module" crossorigin src="./assets/index-D2Hvfy3l.js"></script>
25
  <link rel="stylesheet" crossorigin href="./assets/index-CLisF1Ml.css">
26
  </head>
27
  <body>
@@ -67,7 +67,7 @@
67
  </form>
68
  <div id="shell-hint"><span>Enter to run · Shift+Enter for newline</span><span>↑↓ history</span></div>
69
  </details>
70
- <p class="workspace-note">HTTP experiment: curl / wget can reach selected APIs. Browser CORS applies. <a href="./http-help.html" target="_blank" rel="noopener">Hosts &amp; examples ↗</a></p>
71
  </aside>
72
  </main>
73
  <dialog id="model-dialog" aria-labelledby="model-title" aria-describedby="model-description">
 
21
  <meta name="twitter:image" content="https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/resolve/main/social-thumbnail-v3.png">
22
  <meta name="twitter:image:alt" content="A coding agent running entirely in your browser, above the Pi terminal and its pixel cat mascot.">
23
  <title>Pi · Coding agent in your browser</title>
24
+ <script type="module" crossorigin src="./assets/index-wZi62oe2.js"></script>
25
  <link rel="stylesheet" crossorigin href="./assets/index-CLisF1Ml.css">
26
  </head>
27
  <body>
 
67
  </form>
68
  <div id="shell-hint"><span>Enter to run · Shift+Enter for newline</span><span>↑↓ history</span></div>
69
  </details>
70
+ <p class="workspace-note">HTTP experiment: curl can reach selected APIs. Browser CORS applies. <a href="./http-help.html" target="_blank" rel="noopener">Hosts &amp; examples ↗</a></p>
71
  </aside>
72
  </main>
73
  <dialog id="model-dialog" aria-labelledby="model-title" aria-describedby="model-description">
validation/http-desktop.png ADDED

Git LFS Details

  • SHA256: ef603eaaa1aa9d55aeb9ae62c399325bfbf610be01d131ae1d61313bbd844479
  • Pointer size: 130 Bytes
  • Size of remote file: 77.5 kB
validation/http-mobile.png ADDED

Git LFS Details

  • SHA256: a2e16cbb3015bfb220da278104d909563b8779f1a9699fee649e55c32c8c2a99
  • Pointer size: 130 Bytes
  • Size of remote file: 63.8 kB
validation/http-shell-live-initial.json ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "date": "2026-09-16T08:32:15.891Z",
3
+ "url": "https://mike0021-minicpm5-2b-webgpu-pi-http.static.hf.space/",
4
+ "cases": [
5
+ {
6
+ "name": "json-get-save",
7
+ "command": "curl -fsS https://jsonplaceholder.typicode.com/todos/1 -o todo.json && jq -r .title todo.json",
8
+ "elapsedMs": 332,
9
+ "stdout": "delectus aut autem\n",
10
+ "stderr": "",
11
+ "exitCode": 0,
12
+ "passed": true
13
+ },
14
+ {
15
+ "name": "github-pipeline",
16
+ "command": "set -o pipefail; curl -fsS https://api.github.com/repos/huggingface/transformers.js | jq -r '.full_name, .license.spdx_id'",
17
+ "elapsedMs": 304,
18
+ "stdout": "huggingface/transformers.js\nApache-2.0\n",
19
+ "stderr": "",
20
+ "exitCode": 0,
21
+ "passed": true
22
+ },
23
+ {
24
+ "name": "hf-metadata",
25
+ "command": "set -o pipefail; curl -fsS https://huggingface.co/api/models/openbmb/MiniCPM5-2B | jq -r .id",
26
+ "elapsedMs": 146,
27
+ "stdout": "openbmb/MiniCPM5-2B\n",
28
+ "stderr": "",
29
+ "exitCode": 0,
30
+ "passed": true
31
+ },
32
+ {
33
+ "name": "raw-github",
34
+ "command": "curl -fsS https://raw.githubusercontent.com/huggingface/transformers.js/main/package.json -o package.json && jq -r .name package.json",
35
+ "elapsedMs": 256,
36
+ "stdout": "@huggingface/transformers-monorepo\n",
37
+ "stderr": "",
38
+ "exitCode": 0,
39
+ "passed": true
40
+ },
41
+ {
42
+ "name": "head",
43
+ "command": "curl -I https://jsonplaceholder.typicode.com/todos/1",
44
+ "elapsedMs": 23,
45
+ "stdout": "HTTP/1.1 200 \r\ncache-control: max-age=43200\r\ncontent-type: application/json; charset=utf-8\r\nexpires: -1\r\npragma: no-cache\r\n",
46
+ "stderr": "",
47
+ "exitCode": 0,
48
+ "passed": true
49
+ },
50
+ {
51
+ "name": "post-json",
52
+ "command": "set -o pipefail; curl -fsS https://httpbin.org/post -H 'Content-Type: application/json' -d '{\"demo\":\"browser-http\",\"value\":144}' | jq -c .json",
53
+ "elapsedMs": 620,
54
+ "stdout": "{\"demo\":\"browser-http\",\"value\":144}\n",
55
+ "stderr": "",
56
+ "exitCode": 0,
57
+ "passed": true
58
+ },
59
+ {
60
+ "name": "wget",
61
+ "command": "wget -q -O downloaded.json https://jsonplaceholder.typicode.com/todos/2 && jq -r .id downloaded.json",
62
+ "elapsedMs": 6,
63
+ "stdout": "",
64
+ "stderr": "bash: wget: command not found\n",
65
+ "exitCode": 127,
66
+ "passed": false
67
+ },
68
+ {
69
+ "name": "http-404",
70
+ "command": "curl -fsS https://jsonplaceholder.typicode.com/does-not-exist",
71
+ "elapsedMs": 289,
72
+ "stdout": "",
73
+ "stderr": "curl: (22) The requested URL returned error: 404\n",
74
+ "exitCode": 22,
75
+ "passed": true
76
+ },
77
+ {
78
+ "name": "blocked-host",
79
+ "command": "curl -fsS https://example.com/",
80
+ "elapsedMs": 9,
81
+ "stdout": "",
82
+ "stderr": "curl: (1) HTTP destination is not enabled in this experiment. Allowed hosts: api.github.com, raw.githubusercontent.com, huggingface.co, jsonplaceholder.typicode.com, httpbin.org.\n",
83
+ "exitCode": 1,
84
+ "passed": true
85
+ },
86
+ {
87
+ "name": "cors-failure",
88
+ "command": "curl -fsS https://huggingface.co/",
89
+ "elapsedMs": 20,
90
+ "stdout": "",
91
+ "stderr": "curl: (1) HTTP request failed: the server may block browser requests (CORS), or the connection failed. No proxy is used.\n",
92
+ "exitCode": 1,
93
+ "passed": true
94
+ },
95
+ {
96
+ "name": "redirect-failure",
97
+ "command": "curl -fsSL https://httpbin.org/redirect-to?url=https%3A%2F%2Fhttpbin.org%2Fget",
98
+ "elapsedMs": 98,
99
+ "stdout": "",
100
+ "stderr": "curl: (1) HTTP redirect cannot be followed safely in this browser shell. Use the final HTTPS URL directly (including exact capitalization).\n",
101
+ "exitCode": 1,
102
+ "passed": true
103
+ },
104
+ {
105
+ "name": "timeout",
106
+ "command": "curl -fsS --max-time 1 https://httpbin.org/delay/5",
107
+ "elapsedMs": 1013,
108
+ "stdout": "",
109
+ "stderr": "curl: (1) HTTP request timed out after 1000 ms.\n",
110
+ "exitCode": 1,
111
+ "passed": true
112
+ },
113
+ {
114
+ "name": "failed-pipeline",
115
+ "command": "set -o pipefail; curl -fsS https://example.com/ | jq .",
116
+ "elapsedMs": 7,
117
+ "stdout": "",
118
+ "stderr": "curl: (1) HTTP destination is not enabled in this experiment. Allowed hosts: api.github.com, raw.githubusercontent.com, huggingface.co, jsonplaceholder.typicode.com, httpbin.org.\n",
119
+ "exitCode": 1,
120
+ "passed": true
121
+ },
122
+ {
123
+ "name": "cancel",
124
+ "elapsedMs": 228,
125
+ "stdout": "",
126
+ "stderr": "bash: execution aborted\n",
127
+ "exitCode": 124,
128
+ "passed": true
129
+ },
130
+ {
131
+ "name": "file-survives-reload",
132
+ "passed": true
133
+ }
134
+ ],
135
+ "errors": [],
136
+ "requests": [
137
+ {
138
+ "url": "https://cdn-lfs-us-1.hf.co/repos/48/29/4829a3c0b18453b5b13362a29a513b6902552cc52273d2fb4092cb0203b93c28/a766512083268f7d7bb31f37171d6cd05a5af81e38c2612ac41cbb473a2c5f6c?response-content-disposition=inline%3B+filename*%3DUTF-8%27%27modal-cat-v1.png%3B+filename%3D%22modal-cat-v1.png%22%3B&response-content-type=image%2Fpng&Expires=1789551136&Policy=eyJTdGF0ZW1lbnQiOlt7IkNvbmRpdGlvbiI6eyJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTc4OTU1MTEzNn19LCJSZXNvdXJjZSI6Imh0dHBzOi8vY2RuLWxmcy11cy0xLmhmLmNvL3JlcG9zLzQ4LzI5LzQ4MjlhM2MwYjE4NDUzYjViMTMzNjJhMjlhNTEzYjY5MDI1NTJjYzUyMjczZDJmYjQwOTJjYjAyMDNiOTNjMjgvYTc2NjUxMjA4MzI2OGY3ZDdiYjMxZjM3MTcxZDZjZDA1YTVhZjgxZTM4YzI2MTJhYzQxY2JiNDczYTJjNWY2Yz9yZXNwb25zZS1jb250ZW50LWRpc3Bvc2l0aW9uPSomcmVzcG9uc2UtY29udGVudC10eXBlPSoifV19&Signature=zvLU2rLtliCwfPfBwZpRPOvXLhlbi696TFONTVXatc3dEBN6j9QaxyN4NA6dM4WLhS0vw7ojBS4uqnPC1h6%7EFl26zTns0SOmBe4CGrMYAKZzykTJopfMiOjVD95q0i0FIyd33YoYW6V8cydbRusCD66UChrRSO1wNxghVXdSmc%7EjpnvC3IGypRUDWZRx4gAbSVlUUSi8KCqPfJpLXirxg-ymyaXqM-f7Ank14VpWs1cBsK1n2GMUjAkku-Rua%7EeJIVzbwDSr-Pp3E166FzSRrcXEKrWtMQkVIGmKwS3Y5ZXEHjpOZYBTofeF5DcpY4bp4s9EVYBwC9PZvZ5m4U6lAQ__&Key-Pair-Id=K1HG1QXK3AL3BR",
139
+ "method": "GET"
140
+ },
141
+ {
142
+ "url": "https://jsonplaceholder.typicode.com/todos/1",
143
+ "method": "GET"
144
+ },
145
+ {
146
+ "url": "https://cdn-lfs-us-1.hf.co/repos/48/29/4829a3c0b18453b5b13362a29a513b6902552cc52273d2fb4092cb0203b93c28/592558545d0ef05a4d29fd27eeeb5b63468f1f378bb1c50359a60f8d2a88f668?response-content-disposition=inline%3B+filename*%3DUTF-8%27%27empty-cat-v1.png%3B+filename%3D%22empty-cat-v1.png%22%3B&response-content-type=image%2Fpng&Expires=1789551138&Policy=eyJTdGF0ZW1lbnQiOlt7IkNvbmRpdGlvbiI6eyJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTc4OTU1MTEzOH19LCJSZXNvdXJjZSI6Imh0dHBzOi8vY2RuLWxmcy11cy0xLmhmLmNvL3JlcG9zLzQ4LzI5LzQ4MjlhM2MwYjE4NDUzYjViMTMzNjJhMjlhNTEzYjY5MDI1NTJjYzUyMjczZDJmYjQwOTJjYjAyMDNiOTNjMjgvNTkyNTU4NTQ1ZDBlZjA1YTRkMjlmZDI3ZWVlYjViNjM0NjhmMWYzNzhiYjFjNTAzNTlhNjBmOGQyYTg4ZjY2OD9yZXNwb25zZS1jb250ZW50LWRpc3Bvc2l0aW9uPSomcmVzcG9uc2UtY29udGVudC10eXBlPSoifV19&Signature=Dyg2qtKjNHulfVg2ldV0AkgfVJyw7XoODSeRdZpmVNBvB71h6G3t5eZO3qs0GQTtiR3dLumQMkTSKCt9palZFJ5co8HF3%7EUZ0aSZmdpC33QZnj8xfx8sMX-WUwKF4VP3HX5FNJL28QQWEsr0u1R36LQQwha06rTrP88Y4XjWtDmhNZWEcn-kNdlCU9LIkDDi6KqalEbVlLJUzP48cNKTjmGMeJsBSt4asREfoa4b56noz2qRl0mXGZnq6W9fERUHBho-RvfTiRatd2s1D0D63QSFgabbEU%7Ek7BYiZfj8HMz8W%7EGqVJ7KRBBfcXwV%7EZ0lEscq-XeJ650vi8ChPnynvw__&Key-Pair-Id=K1HG1QXK3AL3BR",
147
+ "method": "GET"
148
+ },
149
+ {
150
+ "url": "https://api.github.com/repos/huggingface/transformers.js",
151
+ "method": "GET"
152
+ },
153
+ {
154
+ "url": "https://huggingface.co/api/models/openbmb/MiniCPM5-2B",
155
+ "method": "GET"
156
+ },
157
+ {
158
+ "url": "https://raw.githubusercontent.com/huggingface/transformers.js/main/package.json",
159
+ "method": "GET"
160
+ },
161
+ {
162
+ "url": "https://jsonplaceholder.typicode.com/todos/1",
163
+ "method": "HEAD"
164
+ },
165
+ {
166
+ "url": "https://httpbin.org/post",
167
+ "method": "POST"
168
+ },
169
+ {
170
+ "url": "https://jsonplaceholder.typicode.com/does-not-exist",
171
+ "method": "GET"
172
+ },
173
+ {
174
+ "url": "https://huggingface.co/",
175
+ "method": "GET"
176
+ },
177
+ {
178
+ "url": "https://httpbin.org/redirect-to?url=https%3A%2F%2Fhttpbin.org%2Fget",
179
+ "method": "GET"
180
+ },
181
+ {
182
+ "url": "https://httpbin.org/get",
183
+ "method": "GET"
184
+ },
185
+ {
186
+ "url": "https://httpbin.org/delay/5",
187
+ "method": "GET"
188
+ },
189
+ {
190
+ "url": "https://httpbin.org/delay/7",
191
+ "method": "GET"
192
+ },
193
+ {
194
+ "url": "https://jsonplaceholder.typicode.com/todos/1",
195
+ "method": "GET"
196
+ },
197
+ {
198
+ "url": "https://cdn-lfs-us-1.hf.co/repos/48/29/4829a3c0b18453b5b13362a29a513b6902552cc52273d2fb4092cb0203b93c28/a766512083268f7d7bb31f37171d6cd05a5af81e38c2612ac41cbb473a2c5f6c?response-content-disposition=inline%3B+filename*%3DUTF-8%27%27modal-cat-v1.png%3B+filename%3D%22modal-cat-v1.png%22%3B&response-content-type=image%2Fpng&Expires=1789551141&Policy=eyJTdGF0ZW1lbnQiOlt7IkNvbmRpdGlvbiI6eyJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTc4OTU1MTE0MX19LCJSZXNvdXJjZSI6Imh0dHBzOi8vY2RuLWxmcy11cy0xLmhmLmNvL3JlcG9zLzQ4LzI5LzQ4MjlhM2MwYjE4NDUzYjViMTMzNjJhMjlhNTEzYjY5MDI1NTJjYzUyMjczZDJmYjQwOTJjYjAyMDNiOTNjMjgvYTc2NjUxMjA4MzI2OGY3ZDdiYjMxZjM3MTcxZDZjZDA1YTVhZjgxZTM4YzI2MTJhYzQxY2JiNDczYTJjNWY2Yz9yZXNwb25zZS1jb250ZW50LWRpc3Bvc2l0aW9uPSomcmVzcG9uc2UtY29udGVudC10eXBlPSoifV19&Signature=hlz8Jg3q0-D5HXj7-rLPSH5stU8POxEw2WAksgMbEqDnRZlcfc5r86HMB2Of6Ac6IeFNM3ux1SN1GNDgmrd0DZY2hmfB8XlEu2wlId7Bxq3t7vN0RnPPrPlcXKLkV4ObLW95kt0asoq9AwDWlNfoTQ-s9q21aJQEsit4EzQxodq3aEWrMYQlL-rHDKC-aQ9eT7CRd2blA9qSEqiToQGuUnk4c--Ln72Hl3-WWCIzErBta6Zxefr2yI83xPzMuZJm85Mo6fW802-JQW3YKWW3SCNTANi9XrFUBsVVhXGnIxiAmgbxBl6mCwFiJTC9-hrXThdrks5EjkaOZhHgWoGWZQ__&Key-Pair-Id=K1HG1QXK3AL3BR",
199
+ "method": "GET"
200
+ },
201
+ {
202
+ "url": "https://cdn-lfs-us-1.hf.co/repos/48/29/4829a3c0b18453b5b13362a29a513b6902552cc52273d2fb4092cb0203b93c28/592558545d0ef05a4d29fd27eeeb5b63468f1f378bb1c50359a60f8d2a88f668?response-content-disposition=inline%3B+filename*%3DUTF-8%27%27empty-cat-v1.png%3B+filename%3D%22empty-cat-v1.png%22%3B&response-content-type=image%2Fpng&Expires=1789551142&Policy=eyJTdGF0ZW1lbnQiOlt7IkNvbmRpdGlvbiI6eyJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTc4OTU1MTE0Mn19LCJSZXNvdXJjZSI6Imh0dHBzOi8vY2RuLWxmcy11cy0xLmhmLmNvL3JlcG9zLzQ4LzI5LzQ4MjlhM2MwYjE4NDUzYjViMTMzNjJhMjlhNTEzYjY5MDI1NTJjYzUyMjczZDJmYjQwOTJjYjAyMDNiOTNjMjgvNTkyNTU4NTQ1ZDBlZjA1YTRkMjlmZDI3ZWVlYjViNjM0NjhmMWYzNzhiYjFjNTAzNTlhNjBmOGQyYTg4ZjY2OD9yZXNwb25zZS1jb250ZW50LWRpc3Bvc2l0aW9uPSomcmVzcG9uc2UtY29udGVudC10eXBlPSoifV19&Signature=P-AW35Km78478HKxD1xCRfdIfORz1cXnanLSAsU7%7EZaEAyfDCTooeUEeqAfBw-3qwzs1sSetBFgaz8NcjgVL7W-1I23KeYqVqBlfB1fBfzfc08qlUUmmHGDdWg2rsI0Uc2REszQcnIiAqOAMTg894MkIGSzebQIGj3kI9DyZXfKeEL0j8pD%7E2LjDq0VHRxi4FJGIX4QCxKAZMKB6xUkXWSU7s%7E984HiM%7EKWFdDUpconQVpfMJ34PKzy7BhjOcMBPpGNsnsR69V4lVJVAgcQtqXW%7E4V9vx4GzAtvLvZ30eA55TWW7KE0KbUcETnI2p5e4xDB1pPaHH6KMR%7Elr6Bs82A__&Key-Pair-Id=K1HG1QXK3AL3BR",
203
+ "method": "GET"
204
+ }
205
+ ],
206
+ "mobileOverflow": false,
207
+ "device": {
208
+ "vendor": "amd",
209
+ "architecture": "rdna-3",
210
+ "features": [
211
+ "depth32float-stencil8",
212
+ "chromium-experimental-subgroup-matrix",
213
+ "rg11b10ufloat-renderable",
214
+ "texture-formats-tier1",
215
+ "bgra8unorm-storage",
216
+ "chromium-experimental-multi-draw-indirect",
217
+ "texture-compression-bc",
218
+ "dual-source-blending",
219
+ "chromium-experimental-sampling-resource-table",
220
+ "core-features-and-limits",
221
+ "chromium-experimental-timestamp-query-inside-passes",
222
+ "float32-filterable",
223
+ "indirect-first-instance",
224
+ "float32-blendable",
225
+ "subgroup-size-control",
226
+ "texture-compression-unaligned",
227
+ "depth-clip-control",
228
+ "texture-compression-bc-sliced-3d",
229
+ "clip-distances",
230
+ "shader-f16",
231
+ "timestamp-query",
232
+ "texture-formats-tier2",
233
+ "primitive-index",
234
+ "texture-component-swizzle",
235
+ "subgroups"
236
+ ]
237
+ },
238
+ "passed": false
239
+ }
validation/http-shell-live.json ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "date": "2026-09-16T08:32:15.891Z",
3
+ "url": "https://mike0021-minicpm5-2b-webgpu-pi-http.static.hf.space/",
4
+ "cases": [
5
+ {
6
+ "name": "json-get-save",
7
+ "command": "curl -fsS https://jsonplaceholder.typicode.com/todos/1 -o todo.json && jq -r .title todo.json",
8
+ "elapsedMs": 332,
9
+ "stdout": "delectus aut autem\n",
10
+ "stderr": "",
11
+ "exitCode": 0,
12
+ "passed": true
13
+ },
14
+ {
15
+ "name": "github-pipeline",
16
+ "command": "set -o pipefail; curl -fsS https://api.github.com/repos/huggingface/transformers.js | jq -r '.full_name, .license.spdx_id'",
17
+ "elapsedMs": 304,
18
+ "stdout": "huggingface/transformers.js\nApache-2.0\n",
19
+ "stderr": "",
20
+ "exitCode": 0,
21
+ "passed": true
22
+ },
23
+ {
24
+ "name": "hf-metadata",
25
+ "command": "set -o pipefail; curl -fsS https://huggingface.co/api/models/openbmb/MiniCPM5-2B | jq -r .id",
26
+ "elapsedMs": 146,
27
+ "stdout": "openbmb/MiniCPM5-2B\n",
28
+ "stderr": "",
29
+ "exitCode": 0,
30
+ "passed": true
31
+ },
32
+ {
33
+ "name": "raw-github",
34
+ "command": "curl -fsS https://raw.githubusercontent.com/huggingface/transformers.js/main/package.json -o package.json && jq -r .name package.json",
35
+ "elapsedMs": 256,
36
+ "stdout": "@huggingface/transformers-monorepo\n",
37
+ "stderr": "",
38
+ "exitCode": 0,
39
+ "passed": true
40
+ },
41
+ {
42
+ "name": "head",
43
+ "command": "curl -I https://jsonplaceholder.typicode.com/todos/1",
44
+ "elapsedMs": 23,
45
+ "stdout": "HTTP/1.1 200 \r\ncache-control: max-age=43200\r\ncontent-type: application/json; charset=utf-8\r\nexpires: -1\r\npragma: no-cache\r\n",
46
+ "stderr": "",
47
+ "exitCode": 0,
48
+ "passed": true
49
+ },
50
+ {
51
+ "name": "post-json",
52
+ "command": "set -o pipefail; curl -fsS https://httpbin.org/post -H 'Content-Type: application/json' -d '{\"demo\":\"browser-http\",\"value\":144}' | jq -c .json",
53
+ "elapsedMs": 620,
54
+ "stdout": "{\"demo\":\"browser-http\",\"value\":144}\n",
55
+ "stderr": "",
56
+ "exitCode": 0,
57
+ "passed": true
58
+ },
59
+ {
60
+ "name": "wget",
61
+ "command": "wget -q -O downloaded.json https://jsonplaceholder.typicode.com/todos/2 && jq -r .id downloaded.json",
62
+ "elapsedMs": 6,
63
+ "stdout": "",
64
+ "stderr": "bash: wget: command not found\n",
65
+ "exitCode": 127,
66
+ "passed": false
67
+ },
68
+ {
69
+ "name": "http-404",
70
+ "command": "curl -fsS https://jsonplaceholder.typicode.com/does-not-exist",
71
+ "elapsedMs": 289,
72
+ "stdout": "",
73
+ "stderr": "curl: (22) The requested URL returned error: 404\n",
74
+ "exitCode": 22,
75
+ "passed": true
76
+ },
77
+ {
78
+ "name": "blocked-host",
79
+ "command": "curl -fsS https://example.com/",
80
+ "elapsedMs": 9,
81
+ "stdout": "",
82
+ "stderr": "curl: (1) HTTP destination is not enabled in this experiment. Allowed hosts: api.github.com, raw.githubusercontent.com, huggingface.co, jsonplaceholder.typicode.com, httpbin.org.\n",
83
+ "exitCode": 1,
84
+ "passed": true
85
+ },
86
+ {
87
+ "name": "cors-failure",
88
+ "command": "curl -fsS https://huggingface.co/",
89
+ "elapsedMs": 20,
90
+ "stdout": "",
91
+ "stderr": "curl: (1) HTTP request failed: the server may block browser requests (CORS), or the connection failed. No proxy is used.\n",
92
+ "exitCode": 1,
93
+ "passed": true
94
+ },
95
+ {
96
+ "name": "redirect-failure",
97
+ "command": "curl -fsSL https://httpbin.org/redirect-to?url=https%3A%2F%2Fhttpbin.org%2Fget",
98
+ "elapsedMs": 98,
99
+ "stdout": "",
100
+ "stderr": "curl: (1) HTTP redirect cannot be followed safely in this browser shell. Use the final HTTPS URL directly (including exact capitalization).\n",
101
+ "exitCode": 1,
102
+ "passed": true
103
+ },
104
+ {
105
+ "name": "timeout",
106
+ "command": "curl -fsS --max-time 1 https://httpbin.org/delay/5",
107
+ "elapsedMs": 1013,
108
+ "stdout": "",
109
+ "stderr": "curl: (1) HTTP request timed out after 1000 ms.\n",
110
+ "exitCode": 1,
111
+ "passed": true
112
+ },
113
+ {
114
+ "name": "failed-pipeline",
115
+ "command": "set -o pipefail; curl -fsS https://example.com/ | jq .",
116
+ "elapsedMs": 7,
117
+ "stdout": "",
118
+ "stderr": "curl: (1) HTTP destination is not enabled in this experiment. Allowed hosts: api.github.com, raw.githubusercontent.com, huggingface.co, jsonplaceholder.typicode.com, httpbin.org.\n",
119
+ "exitCode": 1,
120
+ "passed": true
121
+ },
122
+ {
123
+ "name": "cancel",
124
+ "elapsedMs": 228,
125
+ "stdout": "",
126
+ "stderr": "bash: execution aborted\n",
127
+ "exitCode": 124,
128
+ "passed": true
129
+ },
130
+ {
131
+ "name": "file-survives-reload",
132
+ "passed": true
133
+ }
134
+ ],
135
+ "errors": [],
136
+ "requests": [
137
+ {
138
+ "url": "https://cdn-lfs-us-1.hf.co/repos/48/29/4829a3c0b18453b5b13362a29a513b6902552cc52273d2fb4092cb0203b93c28/a766512083268f7d7bb31f37171d6cd05a5af81e38c2612ac41cbb473a2c5f6c?response-content-disposition=inline%3B+filename*%3DUTF-8%27%27modal-cat-v1.png%3B+filename%3D%22modal-cat-v1.png%22%3B&response-content-type=image%2Fpng&Expires=1789551136&Policy=eyJTdGF0ZW1lbnQiOlt7IkNvbmRpdGlvbiI6eyJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTc4OTU1MTEzNn19LCJSZXNvdXJjZSI6Imh0dHBzOi8vY2RuLWxmcy11cy0xLmhmLmNvL3JlcG9zLzQ4LzI5LzQ4MjlhM2MwYjE4NDUzYjViMTMzNjJhMjlhNTEzYjY5MDI1NTJjYzUyMjczZDJmYjQwOTJjYjAyMDNiOTNjMjgvYTc2NjUxMjA4MzI2OGY3ZDdiYjMxZjM3MTcxZDZjZDA1YTVhZjgxZTM4YzI2MTJhYzQxY2JiNDczYTJjNWY2Yz9yZXNwb25zZS1jb250ZW50LWRpc3Bvc2l0aW9uPSomcmVzcG9uc2UtY29udGVudC10eXBlPSoifV19&Signature=zvLU2rLtliCwfPfBwZpRPOvXLhlbi696TFONTVXatc3dEBN6j9QaxyN4NA6dM4WLhS0vw7ojBS4uqnPC1h6%7EFl26zTns0SOmBe4CGrMYAKZzykTJopfMiOjVD95q0i0FIyd33YoYW6V8cydbRusCD66UChrRSO1wNxghVXdSmc%7EjpnvC3IGypRUDWZRx4gAbSVlUUSi8KCqPfJpLXirxg-ymyaXqM-f7Ank14VpWs1cBsK1n2GMUjAkku-Rua%7EeJIVzbwDSr-Pp3E166FzSRrcXEKrWtMQkVIGmKwS3Y5ZXEHjpOZYBTofeF5DcpY4bp4s9EVYBwC9PZvZ5m4U6lAQ__&Key-Pair-Id=K1HG1QXK3AL3BR",
139
+ "method": "GET"
140
+ },
141
+ {
142
+ "url": "https://jsonplaceholder.typicode.com/todos/1",
143
+ "method": "GET"
144
+ },
145
+ {
146
+ "url": "https://cdn-lfs-us-1.hf.co/repos/48/29/4829a3c0b18453b5b13362a29a513b6902552cc52273d2fb4092cb0203b93c28/592558545d0ef05a4d29fd27eeeb5b63468f1f378bb1c50359a60f8d2a88f668?response-content-disposition=inline%3B+filename*%3DUTF-8%27%27empty-cat-v1.png%3B+filename%3D%22empty-cat-v1.png%22%3B&response-content-type=image%2Fpng&Expires=1789551138&Policy=eyJTdGF0ZW1lbnQiOlt7IkNvbmRpdGlvbiI6eyJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTc4OTU1MTEzOH19LCJSZXNvdXJjZSI6Imh0dHBzOi8vY2RuLWxmcy11cy0xLmhmLmNvL3JlcG9zLzQ4LzI5LzQ4MjlhM2MwYjE4NDUzYjViMTMzNjJhMjlhNTEzYjY5MDI1NTJjYzUyMjczZDJmYjQwOTJjYjAyMDNiOTNjMjgvNTkyNTU4NTQ1ZDBlZjA1YTRkMjlmZDI3ZWVlYjViNjM0NjhmMWYzNzhiYjFjNTAzNTlhNjBmOGQyYTg4ZjY2OD9yZXNwb25zZS1jb250ZW50LWRpc3Bvc2l0aW9uPSomcmVzcG9uc2UtY29udGVudC10eXBlPSoifV19&Signature=Dyg2qtKjNHulfVg2ldV0AkgfVJyw7XoODSeRdZpmVNBvB71h6G3t5eZO3qs0GQTtiR3dLumQMkTSKCt9palZFJ5co8HF3%7EUZ0aSZmdpC33QZnj8xfx8sMX-WUwKF4VP3HX5FNJL28QQWEsr0u1R36LQQwha06rTrP88Y4XjWtDmhNZWEcn-kNdlCU9LIkDDi6KqalEbVlLJUzP48cNKTjmGMeJsBSt4asREfoa4b56noz2qRl0mXGZnq6W9fERUHBho-RvfTiRatd2s1D0D63QSFgabbEU%7Ek7BYiZfj8HMz8W%7EGqVJ7KRBBfcXwV%7EZ0lEscq-XeJ650vi8ChPnynvw__&Key-Pair-Id=K1HG1QXK3AL3BR",
147
+ "method": "GET"
148
+ },
149
+ {
150
+ "url": "https://api.github.com/repos/huggingface/transformers.js",
151
+ "method": "GET"
152
+ },
153
+ {
154
+ "url": "https://huggingface.co/api/models/openbmb/MiniCPM5-2B",
155
+ "method": "GET"
156
+ },
157
+ {
158
+ "url": "https://raw.githubusercontent.com/huggingface/transformers.js/main/package.json",
159
+ "method": "GET"
160
+ },
161
+ {
162
+ "url": "https://jsonplaceholder.typicode.com/todos/1",
163
+ "method": "HEAD"
164
+ },
165
+ {
166
+ "url": "https://httpbin.org/post",
167
+ "method": "POST"
168
+ },
169
+ {
170
+ "url": "https://jsonplaceholder.typicode.com/does-not-exist",
171
+ "method": "GET"
172
+ },
173
+ {
174
+ "url": "https://huggingface.co/",
175
+ "method": "GET"
176
+ },
177
+ {
178
+ "url": "https://httpbin.org/redirect-to?url=https%3A%2F%2Fhttpbin.org%2Fget",
179
+ "method": "GET"
180
+ },
181
+ {
182
+ "url": "https://httpbin.org/get",
183
+ "method": "GET"
184
+ },
185
+ {
186
+ "url": "https://httpbin.org/delay/5",
187
+ "method": "GET"
188
+ },
189
+ {
190
+ "url": "https://httpbin.org/delay/7",
191
+ "method": "GET"
192
+ },
193
+ {
194
+ "url": "https://jsonplaceholder.typicode.com/todos/1",
195
+ "method": "GET"
196
+ },
197
+ {
198
+ "url": "https://cdn-lfs-us-1.hf.co/repos/48/29/4829a3c0b18453b5b13362a29a513b6902552cc52273d2fb4092cb0203b93c28/a766512083268f7d7bb31f37171d6cd05a5af81e38c2612ac41cbb473a2c5f6c?response-content-disposition=inline%3B+filename*%3DUTF-8%27%27modal-cat-v1.png%3B+filename%3D%22modal-cat-v1.png%22%3B&response-content-type=image%2Fpng&Expires=1789551141&Policy=eyJTdGF0ZW1lbnQiOlt7IkNvbmRpdGlvbiI6eyJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTc4OTU1MTE0MX19LCJSZXNvdXJjZSI6Imh0dHBzOi8vY2RuLWxmcy11cy0xLmhmLmNvL3JlcG9zLzQ4LzI5LzQ4MjlhM2MwYjE4NDUzYjViMTMzNjJhMjlhNTEzYjY5MDI1NTJjYzUyMjczZDJmYjQwOTJjYjAyMDNiOTNjMjgvYTc2NjUxMjA4MzI2OGY3ZDdiYjMxZjM3MTcxZDZjZDA1YTVhZjgxZTM4YzI2MTJhYzQxY2JiNDczYTJjNWY2Yz9yZXNwb25zZS1jb250ZW50LWRpc3Bvc2l0aW9uPSomcmVzcG9uc2UtY29udGVudC10eXBlPSoifV19&Signature=hlz8Jg3q0-D5HXj7-rLPSH5stU8POxEw2WAksgMbEqDnRZlcfc5r86HMB2Of6Ac6IeFNM3ux1SN1GNDgmrd0DZY2hmfB8XlEu2wlId7Bxq3t7vN0RnPPrPlcXKLkV4ObLW95kt0asoq9AwDWlNfoTQ-s9q21aJQEsit4EzQxodq3aEWrMYQlL-rHDKC-aQ9eT7CRd2blA9qSEqiToQGuUnk4c--Ln72Hl3-WWCIzErBta6Zxefr2yI83xPzMuZJm85Mo6fW802-JQW3YKWW3SCNTANi9XrFUBsVVhXGnIxiAmgbxBl6mCwFiJTC9-hrXThdrks5EjkaOZhHgWoGWZQ__&Key-Pair-Id=K1HG1QXK3AL3BR",
199
+ "method": "GET"
200
+ },
201
+ {
202
+ "url": "https://cdn-lfs-us-1.hf.co/repos/48/29/4829a3c0b18453b5b13362a29a513b6902552cc52273d2fb4092cb0203b93c28/592558545d0ef05a4d29fd27eeeb5b63468f1f378bb1c50359a60f8d2a88f668?response-content-disposition=inline%3B+filename*%3DUTF-8%27%27empty-cat-v1.png%3B+filename%3D%22empty-cat-v1.png%22%3B&response-content-type=image%2Fpng&Expires=1789551142&Policy=eyJTdGF0ZW1lbnQiOlt7IkNvbmRpdGlvbiI6eyJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTc4OTU1MTE0Mn19LCJSZXNvdXJjZSI6Imh0dHBzOi8vY2RuLWxmcy11cy0xLmhmLmNvL3JlcG9zLzQ4LzI5LzQ4MjlhM2MwYjE4NDUzYjViMTMzNjJhMjlhNTEzYjY5MDI1NTJjYzUyMjczZDJmYjQwOTJjYjAyMDNiOTNjMjgvNTkyNTU4NTQ1ZDBlZjA1YTRkMjlmZDI3ZWVlYjViNjM0NjhmMWYzNzhiYjFjNTAzNTlhNjBmOGQyYTg4ZjY2OD9yZXNwb25zZS1jb250ZW50LWRpc3Bvc2l0aW9uPSomcmVzcG9uc2UtY29udGVudC10eXBlPSoifV19&Signature=P-AW35Km78478HKxD1xCRfdIfORz1cXnanLSAsU7%7EZaEAyfDCTooeUEeqAfBw-3qwzs1sSetBFgaz8NcjgVL7W-1I23KeYqVqBlfB1fBfzfc08qlUUmmHGDdWg2rsI0Uc2REszQcnIiAqOAMTg894MkIGSzebQIGj3kI9DyZXfKeEL0j8pD%7E2LjDq0VHRxi4FJGIX4QCxKAZMKB6xUkXWSU7s%7E984HiM%7EKWFdDUpconQVpfMJ34PKzy7BhjOcMBPpGNsnsR69V4lVJVAgcQtqXW%7E4V9vx4GzAtvLvZ30eA55TWW7KE0KbUcETnI2p5e4xDB1pPaHH6KMR%7Elr6Bs82A__&Key-Pair-Id=K1HG1QXK3AL3BR",
203
+ "method": "GET"
204
+ }
205
+ ],
206
+ "mobileOverflow": false,
207
+ "device": {
208
+ "vendor": "amd",
209
+ "architecture": "rdna-3",
210
+ "features": [
211
+ "depth32float-stencil8",
212
+ "chromium-experimental-subgroup-matrix",
213
+ "rg11b10ufloat-renderable",
214
+ "texture-formats-tier1",
215
+ "bgra8unorm-storage",
216
+ "chromium-experimental-multi-draw-indirect",
217
+ "texture-compression-bc",
218
+ "dual-source-blending",
219
+ "chromium-experimental-sampling-resource-table",
220
+ "core-features-and-limits",
221
+ "chromium-experimental-timestamp-query-inside-passes",
222
+ "float32-filterable",
223
+ "indirect-first-instance",
224
+ "float32-blendable",
225
+ "subgroup-size-control",
226
+ "texture-compression-unaligned",
227
+ "depth-clip-control",
228
+ "texture-compression-bc-sliced-3d",
229
+ "clip-distances",
230
+ "shader-f16",
231
+ "timestamp-query",
232
+ "texture-formats-tier2",
233
+ "primitive-index",
234
+ "texture-component-swizzle",
235
+ "subgroups"
236
+ ]
237
+ },
238
+ "passed": false
239
+ }