Finalize HTTP experiment and publish browser and model trial results
Browse files- app/README.md +1 -1
- app/src/browser-http.mjs +1 -1
- app/tests/browser-http.test.mjs +2 -0
- app/tests/http-agent-live.mjs +7 -3
- assets/agent.worker-B7d5sboN.js +0 -0
- assets/index-A7Nq9zGD.js +0 -0
- index.html +1 -1
- scripts/stage.py +11 -2
- validation/deployment.json +20 -0
- runtime/ort-wasm-simd-threaded.asyncify-verified.wasm → validation/http-agent-fetch-count-write.png +2 -2
- validation/http-agent-live.json +0 -0
- validation/http-desktop.png +2 -2
- validation/http-mobile.png +2 -2
- validation/http-shell-live.json +31 -27
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 |
-
|
| 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 =>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 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-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
Git LFS Details
|
validation/http-mobile.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
validation/http-shell-live.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
| 1 |
{
|
| 2 |
-
"date": "2026-09-16T08:
|
| 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":
|
| 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":
|
| 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":
|
| 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":
|
| 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":
|
| 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":
|
| 54 |
"stdout": "{\"demo\":\"browser-http\",\"value\":144}\n",
|
| 55 |
"stderr": "",
|
| 56 |
"exitCode": 0,
|
| 57 |
"passed": true
|
| 58 |
},
|
| 59 |
{
|
| 60 |
-
"name": "
|
| 61 |
-
"command": "
|
| 62 |
-
"elapsedMs":
|
| 63 |
-
"stdout": "",
|
| 64 |
-
"stderr": "
|
| 65 |
-
"exitCode":
|
| 66 |
-
"passed":
|
| 67 |
},
|
| 68 |
{
|
| 69 |
"name": "http-404",
|
| 70 |
"command": "curl -fsS https://jsonplaceholder.typicode.com/does-not-exist",
|
| 71 |
-
"elapsedMs":
|
| 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":
|
| 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":
|
| 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":
|
| 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":
|
| 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":
|
| 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":
|
| 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
|
| 139 |
"method": "GET"
|
| 140 |
},
|
| 141 |
{
|
|
@@ -143,11 +143,11 @@
|
|
| 143 |
"method": "GET"
|
| 144 |
},
|
| 145 |
{
|
| 146 |
-
"url": "https://
|
| 147 |
"method": "GET"
|
| 148 |
},
|
| 149 |
{
|
| 150 |
-
"url": "https://
|
| 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
|
| 199 |
"method": "GET"
|
| 200 |
},
|
| 201 |
{
|
| 202 |
-
"url": "https://cdn-lfs-us-1.hf.co/repos/48/29/4829a3c0b18453b5b13362a29a513b6902552cc52273d2fb4092cb0203b93c28/592558545d0ef05a4d29fd27eeeb5b63468f1f378bb1c50359a60f8d2a88f668
|
| 203 |
"method": "GET"
|
| 204 |
}
|
| 205 |
],
|
|
@@ -235,5 +239,5 @@
|
|
| 235 |
"subgroups"
|
| 236 |
]
|
| 237 |
},
|
| 238 |
-
"passed":
|
| 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 |
}
|