Mike0021 commited on
Commit
36a016c
·
verified ·
1 Parent(s): bca7a63

Finalize HTTP experiment and publish browser and model trial results

Browse files
app/README.md CHANGED
@@ -118,7 +118,7 @@ Results and screenshots are in the repository's `validation/agent-browser*` file
118
 
119
  Upload **the contents of `app/dist`** to an HTTPS static host. It contains the app and local ONNX Runtime assets; weights are fetched from the pinned public Hugging Face repository and cached in OPFS. No build or inference server is needed after deployment.
120
 
121
- The public deployment is [townbox/MiniCPM5-2B-WebGPU-Pi](https://huggingface.co/spaces/townbox/MiniCPM5-2B-WebGPU-Pi). From the repository root, `python scripts/prepare_space.py` stages the built app, source, licenses, and selected validation files in `.space-agent/` for upload.
122
 
123
  Browsers that deny storage in third-party embeds get an **Open workspace in a new tab** link. The top-level Space app can then use its own storage without changing browser privacy settings. Embedded-page tests restore normal Chromium storage partitioning because Playwright 1.58 disables it by default; the restricted-storage path is tested separately.
124
 
 
118
 
119
  Upload **the contents of `app/dist`** to an HTTPS static host. It contains the app and local ONNX Runtime assets; weights are fetched from the pinned public Hugging Face repository and cached in OPFS. No build or inference server is needed after deployment.
120
 
121
+ This experiment is deployed at [Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP](https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP). From this checkout, `python3 scripts/stage.py` stages the build, source, licenses, and HTTP verification results in `.space-http/`.
122
 
123
  Browsers that deny storage in third-party embeds get an **Open workspace in a new tab** link. The top-level Space app can then use its own storage without changing browser privacy settings. Embedded-page tests restore normal Chromium storage partitioning because Playwright 1.58 disables it by default; the restricted-storage path is tested separately.
124
 
app/src/browser-http.mjs CHANGED
@@ -38,7 +38,7 @@ export function createBrowserFetch({ transport = globalThis.fetch, origins = HTT
38
  throw Error('HTTP redirect cannot be followed safely in this browser shell. Use the final HTTPS URL directly (including exact capitalization).');
39
  }
40
  if (response.status === 0 || response.type === 'opaque') throw Error('HTTP response is unreadable. The destination must allow browser access with CORS.');
41
- if (Number(response.headers.get('content-length')) > maxBytes) {
42
  await response.body?.cancel();
43
  throw Error(`HTTP response exceeds the ${maxBytes}-byte limit.`);
44
  }
 
38
  throw Error('HTTP redirect cannot be followed safely in this browser shell. Use the final HTTPS URL directly (including exact capitalization).');
39
  }
40
  if (response.status === 0 || response.type === 'opaque') throw Error('HTTP response is unreadable. The destination must allow browser access with CORS.');
41
+ if (method !== 'HEAD' && Number(response.headers.get('content-length')) > maxBytes) {
42
  await response.body?.cancel();
43
  throw Error(`HTTP response exceeds the ${maxBytes}-byte limit.`);
44
  }
app/tests/browser-http.test.mjs CHANGED
@@ -44,6 +44,8 @@ test('Response cap works for content-length and streaming bodies', async () => {
44
  await assert.rejects(fetch(endpoint), /10-byte limit/); assert(cancelled);
45
  const known = createBrowserFetch({ maxBytes: 10, transport: async () => new Response('abc', { headers: { 'Content-Length': '11' } }) });
46
  await assert.rejects(known(endpoint), /10-byte limit/);
 
 
47
  });
48
  test('Timeout and explicit cancellation propagate and abort transport', async () => {
49
  const transport = (_url, { signal }) => new Promise((_, reject) => {
 
44
  await assert.rejects(fetch(endpoint), /10-byte limit/); assert(cancelled);
45
  const known = createBrowserFetch({ maxBytes: 10, transport: async () => new Response('abc', { headers: { 'Content-Length': '11' } }) });
46
  await assert.rejects(known(endpoint), /10-byte limit/);
47
+ const head = createBrowserFetch({ maxBytes: 10, transport: async () => new Response(null, { headers: { 'Content-Length': '1000000' } }) });
48
+ assert.equal((await head(endpoint, { method: 'HEAD' })).body.length, 0);
49
  });
50
  test('Timeout and explicit cancellation propagate and abort transport', async () => {
51
  const transport = (_url, { signal }) => new Promise((_, reject) => {
app/tests/http-agent-live.mjs CHANGED
@@ -11,16 +11,20 @@ const context = await chromium.launchPersistentContext(root + '/validation/http-
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;
 
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 => {
15
+ const address = new URL(r.url());
16
+ if (/hf\.co$/.test(address.hostname) || address.hostname.includes('xethub')) address.search = '';
17
+ result.requests.push({url:address.href,method:r.method()});
18
+ });
19
  const rpc = (action,args) => page.evaluate(({action,args})=>window.browserPi.rpc(action,args),{action,args});
20
  const save = () => fs.writeFile(root+'/validation/http-agent-live.json',JSON.stringify(result,null,2)+'\n');
21
  const tasks = [
22
  { 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) },
23
  { 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') },
24
  { 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) },
25
+ { 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.filter(r=>r.method==='POST'&&r.url==='https://httpbin.org/post').length === 1 },
26
  { 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') },
27
+ { 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))) && !/bot detection|blocking automated/i.test(c.final) && c.requests.filter(r=>r.url==='https://huggingface.co/').length <= 2 },
28
  { 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) },
29
  ];
30
  let monitor;
assets/agent.worker-B7d5sboN.js ADDED
The diff for this file is too large to render. See raw diff
 
assets/index-A7Nq9zGD.js ADDED
The diff for this file is too large to render. See raw diff
 
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-wZi62oe2.js"></script>
25
  <link rel="stylesheet" crossorigin href="./assets/index-CLisF1Ml.css">
26
  </head>
27
  <body>
 
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-A7Nq9zGD.js"></script>
25
  <link rel="stylesheet" crossorigin href="./assets/index-CLisF1Ml.css">
26
  </head>
27
  <body>
scripts/stage.py CHANGED
@@ -1,5 +1,5 @@
1
  from pathlib import Path
2
- import shutil, hashlib, json
3
  root = Path(__file__).resolve().parent.parent
4
  out = root / '.space-http'
5
  if out.exists(): shutil.rmtree(out)
@@ -11,7 +11,16 @@ shutil.copytree(root / 'app', out / 'app', ignore=shutil.ignore_patterns('node_m
11
  shutil.copytree(root / 'scripts', out / 'scripts')
12
  (out / 'validation').mkdir()
13
  for path in (root / 'validation').glob('*'):
14
- if path.is_file() and path.suffix in ['.json', '.md', '.png']: shutil.copyfile(path, out / 'validation' / path.name)
 
 
 
 
 
 
 
 
 
15
  files = {str(p.relative_to(out)): hashlib.sha256(p.read_bytes()).hexdigest() for p in sorted(out.rglob('*')) if p.is_file()}
16
  (out / 'deployment-manifest.json').write_text(json.dumps({'source': 'victor/MiniCPM5-2B-WebGPU-Pi', 'sourceRevision': '959e272adeb7931e32d3eb2177dc1d5e96c3e967', 'files': files}, indent=2)+'\n')
17
  print(f'Staged {len(files)+1} files for Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP')
 
1
  from pathlib import Path
2
+ import shutil, hashlib, json, re
3
  root = Path(__file__).resolve().parent.parent
4
  out = root / '.space-http'
5
  if out.exists(): shutil.rmtree(out)
 
11
  shutil.copytree(root / 'scripts', out / 'scripts')
12
  (out / 'validation').mkdir()
13
  for path in (root / 'validation').glob('*'):
14
+ # Keep temporary failures and screenshots containing HTTP echo metadata local.
15
+ if path.is_file() and path.name in ['source.json', 'deployment.json', 'http-shell-live.json', 'http-agent-live.json', 'http-embedded.json', 'REPORT.md', 'http-desktop.png', 'http-mobile.png', 'http-agent-fetch-count-write.png']:
16
+ destination = out / 'validation' / path.name
17
+ if path.suffix == '.json':
18
+ data = path.read_text()
19
+ data = re.sub(r'(?<![\d.])(?:\d{1,3}\.){3}\d{1,3}(?![\d.])', '<redacted IP>', data)
20
+ # Signed download credentials are transient and not needed for verification.
21
+ data = re.sub(r'(https://[^"\s]*?(?:hf\.co|xethub[^/]*)/[^"\s?]*)\?[^"\s]*', r'\1', data)
22
+ destination.write_text(data)
23
+ else: shutil.copyfile(path, destination)
24
  files = {str(p.relative_to(out)): hashlib.sha256(p.read_bytes()).hexdigest() for p in sorted(out.rglob('*')) if p.is_file()}
25
  (out / 'deployment-manifest.json').write_text(json.dumps({'source': 'victor/MiniCPM5-2B-WebGPU-Pi', 'sourceRevision': '959e272adeb7931e32d3eb2177dc1d5e96c3e967', 'files': files}, indent=2)+'\n')
26
  print(f'Staged {len(files)+1} files for Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP')
validation/deployment.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "sourceRevision": "959e272adeb7931e32d3eb2177dc1d5e96c3e967",
3
+ "sourceUnchanged": true,
4
+ "duplicateRevision": "f104170c6d090d802c727aaad8dfa811f06d03ed",
5
+ "runtime": {
6
+ "stage": "RUNNING",
7
+ "hardware": {
8
+ "current": null,
9
+ "requested": null
10
+ },
11
+ "replicas": {
12
+ "requested": 1,
13
+ "current": 1
14
+ }
15
+ },
16
+ "host": "https://mike0021-minicpm5-2b-webgpu-pi-http.static.hf.space",
17
+ "assets/agent.worker-euGpP_Md.js": true,
18
+ "assets/index-wZi62oe2.js": true,
19
+ "http-help.html": false
20
+ }
runtime/ort-wasm-simd-threaded.asyncify-verified.wasm → validation/http-agent-fetch-count-write.png RENAMED
File without changes
validation/http-agent-live.json ADDED
The diff for this file is too large to render. See raw diff
 
validation/http-desktop.png CHANGED

Git LFS Details

  • SHA256: ef603eaaa1aa9d55aeb9ae62c399325bfbf610be01d131ae1d61313bbd844479
  • Pointer size: 130 Bytes
  • Size of remote file: 77.5 kB

Git LFS Details

  • SHA256: 56d33ecd20d44677a644efcf384cfbf479c3d742d571b2d594576579e14eb707
  • Pointer size: 130 Bytes
  • Size of remote file: 77.2 kB
validation/http-mobile.png CHANGED

Git LFS Details

  • SHA256: a2e16cbb3015bfb220da278104d909563b8779f1a9699fee649e55c32c8c2a99
  • Pointer size: 130 Bytes
  • Size of remote file: 63.8 kB

Git LFS Details

  • SHA256: 4442082671196175e794f517061cd9f37dd6977f1424a2b58ea2c90516050626
  • Pointer size: 130 Bytes
  • Size of remote file: 63.6 kB
validation/http-shell-live.json CHANGED
@@ -1,11 +1,11 @@
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,
@@ -14,7 +14,7 @@
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,
@@ -23,7 +23,7 @@
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,
@@ -32,7 +32,7 @@
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,
@@ -41,7 +41,7 @@
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,
@@ -50,25 +50,25 @@
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,
@@ -77,7 +77,7 @@
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,
@@ -86,7 +86,7 @@
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,
@@ -95,7 +95,7 @@
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,
@@ -104,7 +104,7 @@
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,
@@ -113,7 +113,7 @@
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,
@@ -121,7 +121,7 @@
121
  },
122
  {
123
  "name": "cancel",
124
- "elapsedMs": 228,
125
  "stdout": "",
126
  "stderr": "bash: execution aborted\n",
127
  "exitCode": 124,
@@ -135,7 +135,7 @@
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
  {
@@ -143,11 +143,11 @@
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
  {
@@ -166,6 +166,10 @@
166
  "url": "https://httpbin.org/post",
167
  "method": "POST"
168
  },
 
 
 
 
169
  {
170
  "url": "https://jsonplaceholder.typicode.com/does-not-exist",
171
  "method": "GET"
@@ -195,11 +199,11 @@
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
  ],
@@ -235,5 +239,5 @@
235
  "subgroups"
236
  ]
237
  },
238
- "passed": false
239
  }
 
1
  {
2
+ "date": "2026-09-16T08:34:50.371Z",
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": 80,
9
  "stdout": "delectus aut autem\n",
10
  "stderr": "",
11
  "exitCode": 0,
 
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": 267,
18
  "stdout": "huggingface/transformers.js\nApache-2.0\n",
19
  "stderr": "",
20
  "exitCode": 0,
 
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": 139,
27
  "stdout": "openbmb/MiniCPM5-2B\n",
28
  "stderr": "",
29
  "exitCode": 0,
 
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": 14,
36
  "stdout": "@huggingface/transformers-monorepo\n",
37
  "stderr": "",
38
  "exitCode": 0,
 
41
  {
42
  "name": "head",
43
  "command": "curl -I https://jsonplaceholder.typicode.com/todos/1",
44
+ "elapsedMs": 25,
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,
 
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": 369,
54
  "stdout": "{\"demo\":\"browser-http\",\"value\":144}\n",
55
  "stderr": "",
56
  "exitCode": 0,
57
  "passed": true
58
  },
59
  {
60
+ "name": "request-headers",
61
+ "command": "set -o pipefail; curl -fsS https://httpbin.org/headers -H 'X-Demo: browser-http' | jq -r '.headers[\"X-Demo\"]'",
62
+ "elapsedMs": 197,
63
+ "stdout": "browser-http\n",
64
+ "stderr": "",
65
+ "exitCode": 0,
66
+ "passed": true
67
  },
68
  {
69
  "name": "http-404",
70
  "command": "curl -fsS https://jsonplaceholder.typicode.com/does-not-exist",
71
+ "elapsedMs": 24,
72
  "stdout": "",
73
  "stderr": "curl: (22) The requested URL returned error: 404\n",
74
  "exitCode": 22,
 
77
  {
78
  "name": "blocked-host",
79
  "command": "curl -fsS https://example.com/",
80
+ "elapsedMs": 8,
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,
 
86
  {
87
  "name": "cors-failure",
88
  "command": "curl -fsS https://huggingface.co/",
89
+ "elapsedMs": 107,
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,
 
95
  {
96
  "name": "redirect-failure",
97
  "command": "curl -fsSL https://httpbin.org/redirect-to?url=https%3A%2F%2Fhttpbin.org%2Fget",
98
+ "elapsedMs": 191,
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,
 
104
  {
105
  "name": "timeout",
106
  "command": "curl -fsS --max-time 1 https://httpbin.org/delay/5",
107
+ "elapsedMs": 1012,
108
  "stdout": "",
109
  "stderr": "curl: (1) HTTP request timed out after 1000 ms.\n",
110
  "exitCode": 1,
 
113
  {
114
  "name": "failed-pipeline",
115
  "command": "set -o pipefail; curl -fsS https://example.com/ | jq .",
116
+ "elapsedMs": 9,
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,
 
121
  },
122
  {
123
  "name": "cancel",
124
+ "elapsedMs": 221,
125
  "stdout": "",
126
  "stderr": "bash: execution aborted\n",
127
  "exitCode": 124,
 
135
  "errors": [],
136
  "requests": [
137
  {
138
+ "url": "https://cdn-lfs-us-1.hf.co/repos/48/29/4829a3c0b18453b5b13362a29a513b6902552cc52273d2fb4092cb0203b93c28/a766512083268f7d7bb31f37171d6cd05a5af81e38c2612ac41cbb473a2c5f6c",
139
  "method": "GET"
140
  },
141
  {
 
143
  "method": "GET"
144
  },
145
  {
146
+ "url": "https://api.github.com/repos/huggingface/transformers.js",
147
  "method": "GET"
148
  },
149
  {
150
+ "url": "https://cdn-lfs-us-1.hf.co/repos/48/29/4829a3c0b18453b5b13362a29a513b6902552cc52273d2fb4092cb0203b93c28/592558545d0ef05a4d29fd27eeeb5b63468f1f378bb1c50359a60f8d2a88f668",
151
  "method": "GET"
152
  },
153
  {
 
166
  "url": "https://httpbin.org/post",
167
  "method": "POST"
168
  },
169
+ {
170
+ "url": "https://httpbin.org/headers",
171
+ "method": "GET"
172
+ },
173
  {
174
  "url": "https://jsonplaceholder.typicode.com/does-not-exist",
175
  "method": "GET"
 
199
  "method": "GET"
200
  },
201
  {
202
+ "url": "https://cdn-lfs-us-1.hf.co/repos/48/29/4829a3c0b18453b5b13362a29a513b6902552cc52273d2fb4092cb0203b93c28/a766512083268f7d7bb31f37171d6cd05a5af81e38c2612ac41cbb473a2c5f6c",
203
  "method": "GET"
204
  },
205
  {
206
+ "url": "https://cdn-lfs-us-1.hf.co/repos/48/29/4829a3c0b18453b5b13362a29a513b6902552cc52273d2fb4092cb0203b93c28/592558545d0ef05a4d29fd27eeeb5b63468f1f378bb1c50359a60f8d2a88f668",
207
  "method": "GET"
208
  }
209
  ],
 
239
  "subgroups"
240
  ]
241
  },
242
+ "passed": true
243
  }